Merge pull request #224 from estruyf/dev

This commit is contained in:
Elio Struyf
2022-01-10 13:01:03 +01:00
committed by GitHub
12 changed files with 157 additions and 50 deletions
+11
View File
@@ -1,5 +1,16 @@
# Change Log
## [5.10.0] - 2022-01-10
### 🎨 Enhancements
- [#218](https://github.com/estruyf/vscode-front-matter/issues/218): Add support for creating `mdx` files from templates and content types. This introduced a new setting: `frontMatter.content.defaultFileType`.
- [#220](https://github.com/estruyf/vscode-front-matter/issues/220): Add support DateTime updates in `mdx` files when the `mdx extension` is not installed.
### 🐞 Fixes
- [#221](https://github.com/estruyf/vscode-front-matter/issues/221): Automatic DateTime switch from on text change to on save to prevent multiple updates.
## [5.9.0] - 2022-01-01 - 🎇🎆
### 🎨 Enhancements
+2 -2
View File
@@ -1,12 +1,12 @@
{
"name": "vscode-front-matter-beta",
"version": "5.9.0",
"version": "5.10.0",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "vscode-front-matter-beta",
"version": "5.9.0",
"version": "5.10.0",
"license": "MIT",
"devDependencies": {
"@bendera/vscode-webview-elements": "0.6.2",
+20 -1
View File
@@ -3,7 +3,7 @@
"displayName": "Front Matter",
"description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...",
"icon": "assets/frontmatter-teal-128x128.png",
"version": "5.9.0",
"version": "5.10.0",
"preview": false,
"publisher": "eliostruyf",
"galleryBanner": {
@@ -97,6 +97,16 @@
"markdownDescription": "Specify if you want to automatically update the modified date of your article/page. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.autoupdatedate)",
"scope": "Content"
},
"frontMatter.content.defaultFileType": {
"type": "string",
"default": "md",
"enum": [
"md",
"mdx"
],
"markdownDescription": "Specify the default file type for the content to create. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.content.defaultfiletype)",
"scope": "Content"
},
"frontMatter.content.defaultSorting": {
"type": "string",
"default": "",
@@ -397,6 +407,15 @@
"type": "string",
"description": "Define the type of field"
},
"fileType": {
"type": "string",
"default": "",
"enum": [
"md",
"mdx"
],
"description": "Specifies the type of content you want to create."
},
"fields": {
"type": "array",
"description": "Define the fields of the content type",
+40 -27
View File
@@ -15,9 +15,6 @@ import { parseWinPath } from '../helpers/parseWinPath';
export class Article {
private static prevContent = "";
/**
* Insert taxonomy
*
@@ -119,7 +116,37 @@ export class Article {
return;
}
const article = ArticleHelper.getFrontMatter(editor);
const updatedArticle = this.setLastModifiedDateInner(editor.document);
if (typeof updatedArticle === "undefined") {
return;
}
ArticleHelper.update(
editor,
updatedArticle as matter.GrayMatterFile<string>
);
}
public static async setLastModifiedDateOnSave(
document: vscode.TextDocument
): Promise<vscode.TextEdit[]> {
const updatedArticle = this.setLastModifiedDateInner(document);
if (typeof updatedArticle === "undefined") {
return [];
}
const update = ArticleHelper.generateUpdate(document, updatedArticle);
return [update];
}
private static setLastModifiedDateInner(
document: vscode.TextDocument
): matter.GrayMatterFile<string> | undefined {
const article = ArticleHelper.getFrontMatterFromDocument(document);
if (!article) {
return;
}
@@ -128,8 +155,7 @@ export class Article {
const dateField = Settings.get(SETTING_MODIFIED_FIELD) as string || DefaultFields.LastModified;
try {
cloneArticle.data[dateField] = Article.formatDate(new Date());
ArticleHelper.update(editor, cloneArticle);
return cloneArticle;
} catch (e: any) {
Notifications.error(`Something failed while parsing the date format. Check your "${CONFIG_KEY}${SETTING_DATE_FORMAT}" setting.`);
}
@@ -238,30 +264,17 @@ export class Article {
/**
* Article auto updater
* @param fileChanges
* @param event
*/
public static async autoUpdate(fileChanges: vscode.TextDocumentChangeEvent) {
const txtChanges = fileChanges.contentChanges.map(c => c.text);
const editor = vscode.window.activeTextEditor;
public static async autoUpdate(event: vscode.TextDocumentWillSaveEvent) {
const document = event.document;
if (document && ArticleHelper.isMarkdownFile(document)) {
const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE);
if (txtChanges.length > 0 && editor && ArticleHelper.isMarkdownFile()) {
const autoUpdate = Settings.get(SETTING_AUTO_UPDATE_DATE);
if (autoUpdate) {
const article = ArticleHelper.getFrontMatter(editor);
if (!article) {
return;
}
if (article.content === Article.prevContent) {
return;
}
Article.prevContent = article.content;
Article.setLastModifiedDate();
if (autoUpdate) {
event.waitUntil(Article.setLastModifiedDateOnSave(document));
}
}
}
}
/**
+3 -1
View File
@@ -5,6 +5,7 @@ import { Notifications } from "../helpers/Notifications";
import { Template } from "./Template";
import { Folders } from "./Folders";
import { Settings } from "../helpers";
import { SETTINGS_CONTENT_DEFAULT_FILETYPE } from "../constants";
export class Project {
@@ -27,6 +28,7 @@ categories: []
public static async init(sampleTemplate: boolean = true) {
try {
Settings.createTeamSettings();
const fileType = Settings.get<string>(SETTINGS_CONTENT_DEFAULT_FILETYPE);
const folder = Template.getSettings();
const templatePath = Project.templatePath();
@@ -35,7 +37,7 @@ categories: []
return;
}
const article = Uri.file(join(templatePath.fsPath, "article.md"));
const article = Uri.file(join(templatePath.fsPath, `article.${fileType}`));
if (!fs.existsSync(templatePath.fsPath)) {
await workspace.fs.createDirectory(templatePath);
+6 -3
View File
@@ -2,7 +2,7 @@ import { Questions } from './../helpers/Questions';
import * as vscode from 'vscode';
import * as path from 'path';
import * as fs from 'fs';
import { SETTING_TEMPLATES_FOLDER, SETTING_TEMPLATES_PREFIX } from '../constants';
import { SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, SETTING_TEMPLATES_PREFIX } from '../constants';
import { ArticleHelper, Settings } from '../helpers';
import { Article } from '.';
import { Notifications } from '../helpers/Notifications';
@@ -12,6 +12,7 @@ import { Folders } from './Folders';
import { ContentType } from '../helpers/ContentType';
import { ContentType as IContentType } from '../models';
import { PagesListener } from '../listeners';
import { extname } from 'path';
export class Template {
@@ -50,6 +51,7 @@ export class Template {
public static async generate() {
const folder = Template.getSettings();
const editor = vscode.window.activeTextEditor;
const fileType = Settings.get<string>(SETTINGS_CONTENT_DEFAULT_FILETYPE);
if (folder && editor && ArticleHelper.isMarkdownFile()) {
const article = ArticleHelper.getFrontMatter(editor);
@@ -83,7 +85,7 @@ export class Template {
if (templatePath) {
let fileContents = ArticleHelper.stringifyFrontMatter(keepContents === "no" ? "" : clonedArticle.content, clonedArticle.data);
const templateFile = path.join(templatePath.fsPath, `${titleValue}.md`);
const templateFile = path.join(templatePath.fsPath, `${titleValue}.${fileType}`);
fs.writeFileSync(templateFile, fileContents, { encoding: "utf-8" });
Notifications.info(`Template created and is now available in your ${folder} folder.`);
@@ -140,7 +142,8 @@ export class Template {
contentType = contentTypes?.find(t => t.name === templateData.data.type);
}
let newFilePath: string | undefined = ArticleHelper.createContent(contentType, folderPath, titleValue);
const fileExtension = extname(template.fsPath).replace(".", "");
let newFilePath: string | undefined = ArticleHelper.createContent(contentType, folderPath, titleValue, fileExtension);
if (!newFilePath) {
return;
}
+2
View File
@@ -50,6 +50,8 @@ export const SETTINGS_CONTENT_WYSIWYG = "content.wysiwyg";
export const SETTINGS_CONTENT_SORTING_DEFAULT = "content.defaultSorting";
export const SETTINGS_MEDIA_SORTING_DEFAULT = "content.defaultSorting";
export const SETTINGS_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType";
export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart";
export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet";
+3 -4
View File
@@ -177,8 +177,7 @@ export async function activate(context: vscode.ExtensionContext) {
triggerShowDraftStatus();
// Listener for file edit changes
editDebounce = debounceCallback();
subscriptions.push(vscode.workspace.onDidChangeTextDocument(triggerFileChange));
subscriptions.push(vscode.workspace.onWillSaveTextDocument(handleAutoDateUpdate));
// Listener for file saves
subscriptions.push(vscode.workspace.onDidSaveTextDocument((doc: vscode.TextDocument) => {
@@ -231,8 +230,8 @@ export async function activate(context: vscode.ExtensionContext) {
export function deactivate() {}
const triggerFileChange = (e: vscode.TextDocumentChangeEvent) => {
editDebounce(() => Article.autoUpdate(e), 1000);
const handleAutoDateUpdate = (e: vscode.TextDocumentWillSaveEvent) => {
Article.autoUpdate(e);
};
const triggerShowDraftStatus = () => {
+62 -11
View File
@@ -3,7 +3,7 @@ import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/
import * as vscode from 'vscode';
import * as matter from "gray-matter";
import * as fs from "fs";
import { DefaultFields, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants';
import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants';
import { DumpOptions } from 'js-yaml';
import { TomlEngine, getFmLanguage, getFormatOpts } from './TomlEngine';
import { Extension, Settings } from '.';
@@ -27,8 +27,17 @@ export class ArticleHelper {
* @param editor
*/
public static getFrontMatter(editor: vscode.TextEditor) {
const fileContents = editor.document.getText();
return ArticleHelper.parseFile(fileContents, editor.document.fileName);
return ArticleHelper.getFrontMatterFromDocument(editor.document);
}
/**
* Get the contents of the specified document
*
* @param document The document to parse.
*/
public static getFrontMatterFromDocument(document: vscode.TextDocument) {
const fileContents = document.getText();
return ArticleHelper.parseFile(fileContents, document.fileName);
}
/**
@@ -47,11 +56,37 @@ export class ArticleHelper {
* @param article
*/
public static async update(editor: vscode.TextEditor, article: matter.GrayMatterFile<string>) {
const update = this.generateUpdate(editor.document, article);
await editor.edit(builder => builder.replace(update.range, update.newText));
}
/**
* Generate the update to be applied to the article.
* @param article
*/
public static generateUpdate(document: vscode.TextDocument, article: matter.GrayMatterFile<string>): vscode.TextEdit {
const nrOfLines = document.lineCount as number;
const range = new vscode.Range(new vscode.Position(0, 0), new vscode.Position(nrOfLines, 0));
const removeQuotes = Settings.get(SETTING_REMOVE_QUOTES) as string[];
const commaSeparated = Settings.get<string[]>(SETTING_COMMA_SEPARATED_FIELDS);
// Check if there is a line ending
const lines = article.content.split("\n");
const lastLine = lines.pop();
const endsWithNewLine = lastLine !== undefined && lastLine.trim() === "";
let newMarkdown = this.stringifyFrontMatter(article.content, Object.assign({}, article.data));
// Logic to not include a new line at the end of the file
if (!endsWithNewLine) {
const lines = newMarkdown.split("\n");
const lastLine = lines.pop();
if (lastLine !== undefined && lastLine?.trim() === "") {
newMarkdown = lines.join("\n");
}
}
// Check for field where quotes need to be removed
if (removeQuotes && removeQuotes.length) {
for (const toRemove of removeQuotes) {
@@ -68,8 +103,7 @@ export class ArticleHelper {
}
}
const nrOfLines = editor.document.lineCount as number;
await editor.edit(builder => builder.replace(new vscode.Range(new vscode.Position(0, 0), new vscode.Position(nrOfLines, 0)), newMarkdown));
return vscode.TextEdit.replace(range, newMarkdown);
}
/**
@@ -109,9 +143,25 @@ export class ArticleHelper {
/**
* Checks if the current file is a markdown file
*/
public static isMarkdownFile() {
const editor = vscode.window.activeTextEditor;
return (editor && editor.document && (editor.document.languageId.toLowerCase() === "markdown" || editor.document.languageId.toLowerCase() === "mdx"));
public static isMarkdownFile(document: vscode.TextDocument | undefined | null = null) {
const supportedLanguages = ["markdown", "mdx"];
const supportedFileExtensions = [".md", ".mdx"];
const languageId = document?.languageId?.toLowerCase();
const isSupportedLanguage = languageId && supportedLanguages.includes(languageId);
document ??= vscode.window.activeTextEditor?.document;
/**
* It's possible that the file is a file type we support but the user hasn't installed
* language support for. In that case, we'll manually check the extension as a proxy
* for whether or not we support the file.
*/
if (!isSupportedLanguage) {
const fileName = document?.fileName?.toLowerCase();
return fileName && supportedFileExtensions.findIndex(fileExtension => fileName.endsWith(fileExtension)) > -1;
}
return isSupportedLanguage;
}
/**
@@ -188,8 +238,9 @@ export class ArticleHelper {
* @param titleValue
* @returns The new file path
*/
public static createContent(contentType: ContentType | undefined, folderPath: string, titleValue: string): string | undefined {
public static createContent(contentType: ContentType | undefined, folderPath: string, titleValue: string, fileExtension?: string): string | undefined {
const prefix = Settings.get<string>(SETTING_TEMPLATES_PREFIX);
const fileType = Settings.get<string>(SETTINGS_CONTENT_DEFAULT_FILETYPE);
// Name of the file or folder to create
const sanitizedName = ArticleHelper.sanitize(titleValue);
@@ -203,10 +254,10 @@ export class ArticleHelper {
return;
} else {
mkdirSync(newFolder);
newFilePath = join(newFolder, `index.md`);
newFilePath = join(newFolder, `index.${fileExtension || contentType.fileType || fileType}`);
}
} else {
let newFileName = `${sanitizedName}.md`;
let newFileName = `${sanitizedName}.${fileExtension || contentType?.fileType || fileType}`;
if (prefix && typeof prefix === "string") {
newFileName = `${format(new Date(), DateHelper.formatUpdate(prefix) as string)}-${newFileName}`;
+6
View File
@@ -91,6 +91,12 @@ export class ContentType {
return Settings.get<IContentType[]>(SETTING_TAXONOMY_CONTENT_TYPES);
}
/**
* Create a new file with the specified content type
* @param contentType
* @param folderPath
* @returns
*/
private static async create(contentType: IContentType, folderPath: string) {
const titleValue = await Questions.ContentTitle();
+1 -1
View File
@@ -38,7 +38,7 @@ export class MediaHelpers {
if (stateValue !== HOME_PAGE_NAVIGATION_ID) {
// Support for page bundles
if (viewData?.data?.filePath && viewData?.data?.filePath.endsWith('index.md')) {
if (viewData?.data?.filePath && (viewData?.data?.filePath.endsWith('index.md') || viewData?.data?.filePath.endsWith('index.mdx'))) {
const folderPath = parse(viewData.data.filePath).dir;
selectedFolder = folderPath;
} else if (stateValue && existsSync(stateValue)) {
+1
View File
@@ -26,6 +26,7 @@ export interface ContentType {
name: string;
fields: Field[];
fileType?: "md" | "mdx";
previewPath?: string | null;
pageBundle?: boolean;
}