diff --git a/CHANGELOG.md b/CHANGELOG.md index b7a38790..797f7533 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,20 @@ # Change Log +## [5.7.0] - 2021-12-07 - [Release Notes](https://beta.frontmatter.codes/updates/v5.7.0) + +### 🎨 Enhancements + +- [#188](https://github.com/estruyf/vscode-front-matter/issues/188): Support for `.markdown` files added to the dashboard +- [#190](https://github.com/estruyf/vscode-front-matter/issues/190): Diagnostic output for the extension +- [#194](https://github.com/estruyf/vscode-front-matter/issues/194): WYSIWYG controls added for markdown files + configuration to enable/disable the functionality + +### 🐞 Fixes + +- [#191](https://github.com/estruyf/vscode-front-matter/issues/191): Fix beta settings page +- [#200](https://github.com/estruyf/vscode-front-matter/issues/200): Fix last modified date sorting for media files +- [#201](https://github.com/estruyf/vscode-front-matter/issues/201): Fix overflow issue with the media filename +- [#202](https://github.com/estruyf/vscode-front-matter/issues/202): Fix checkbox label color for light themes + ## [5.6.0] - 2021-11-23 ### 🎨 Enhancements diff --git a/README.beta.md b/README.beta.md index 96c7932e..e5649abd 100644 --- a/README.beta.md +++ b/README.beta.md @@ -111,7 +111,10 @@ If you have the courage to test out the beta features, we made available a beta

- + + + + diff --git a/README.md b/README.md index 0bbbf7d7..28be793d 100644 --- a/README.md +++ b/README.md @@ -109,7 +109,10 @@ If you have the courage to test out the beta features, we made available a beta

- + + + + diff --git a/assets/icons/blockquote-dark.svg b/assets/icons/blockquote-dark.svg new file mode 100644 index 00000000..a9cebe94 --- /dev/null +++ b/assets/icons/blockquote-dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/blockquote-light.svg b/assets/icons/blockquote-light.svg new file mode 100644 index 00000000..eb10a05b --- /dev/null +++ b/assets/icons/blockquote-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/bold-dark.svg b/assets/icons/bold-dark.svg new file mode 100644 index 00000000..6ba1d39f --- /dev/null +++ b/assets/icons/bold-dark.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/assets/icons/bold-light.svg b/assets/icons/bold-light.svg new file mode 100644 index 00000000..9c887220 --- /dev/null +++ b/assets/icons/bold-light.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/assets/icons/code-dark.svg b/assets/icons/code-dark.svg new file mode 100644 index 00000000..a99e9aeb --- /dev/null +++ b/assets/icons/code-dark.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assets/icons/code-light.svg b/assets/icons/code-light.svg new file mode 100644 index 00000000..1c0e436e --- /dev/null +++ b/assets/icons/code-light.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assets/icons/codeblock-dark.svg b/assets/icons/codeblock-dark.svg new file mode 100644 index 00000000..8145c45f --- /dev/null +++ b/assets/icons/codeblock-dark.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/icons/codeblock-light.svg b/assets/icons/codeblock-light.svg new file mode 100644 index 00000000..29714eb5 --- /dev/null +++ b/assets/icons/codeblock-light.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/icons/heading-dark.svg b/assets/icons/heading-dark.svg new file mode 100644 index 00000000..12e37896 --- /dev/null +++ b/assets/icons/heading-dark.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/heading-light.svg b/assets/icons/heading-light.svg new file mode 100644 index 00000000..39010990 --- /dev/null +++ b/assets/icons/heading-light.svg @@ -0,0 +1,10 @@ + + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/italic-dark.svg b/assets/icons/italic-dark.svg new file mode 100644 index 00000000..ac651c36 --- /dev/null +++ b/assets/icons/italic-dark.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assets/icons/italic-light.svg b/assets/icons/italic-light.svg new file mode 100644 index 00000000..427ab51d --- /dev/null +++ b/assets/icons/italic-light.svg @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/assets/icons/options-dark.svg b/assets/icons/options-dark.svg new file mode 100644 index 00000000..0c0f9b9b --- /dev/null +++ b/assets/icons/options-dark.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/icons/options-light.svg b/assets/icons/options-light.svg new file mode 100644 index 00000000..b5c10374 --- /dev/null +++ b/assets/icons/options-light.svg @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/assets/icons/ordered-list-dark.svg b/assets/icons/ordered-list-dark.svg new file mode 100644 index 00000000..1d351b8e --- /dev/null +++ b/assets/icons/ordered-list-dark.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/assets/icons/ordered-list-light.svg b/assets/icons/ordered-list-light.svg new file mode 100644 index 00000000..b238e15b --- /dev/null +++ b/assets/icons/ordered-list-light.svg @@ -0,0 +1,8 @@ + + + + + + + + \ No newline at end of file diff --git a/assets/icons/strikethrough-dark.svg b/assets/icons/strikethrough-dark.svg new file mode 100644 index 00000000..a8a8a038 --- /dev/null +++ b/assets/icons/strikethrough-dark.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/assets/icons/strikethrough-light.svg b/assets/icons/strikethrough-light.svg new file mode 100644 index 00000000..bd67607c --- /dev/null +++ b/assets/icons/strikethrough-light.svg @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/assets/icons/unordered-list-dark.svg b/assets/icons/unordered-list-dark.svg new file mode 100644 index 00000000..9a66e6c9 --- /dev/null +++ b/assets/icons/unordered-list-dark.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/assets/icons/unordered-list-light.svg b/assets/icons/unordered-list-light.svg new file mode 100644 index 00000000..29de1d80 --- /dev/null +++ b/assets/icons/unordered-list-light.svg @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index 0037f791..0c71e3db 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,6 +1,6 @@ { "name": "vscode-front-matter-beta", - "version": "5.6.0", + "version": "5.7.0", "lockfileVersion": 1, "requires": true, "dependencies": { diff --git a/package.json b/package.json index 8e3831e4..faba76cf 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Front Matter", "description": "An essential Visual Studio Code extension when you want to manage the markdown pages of your static site like: Hugo, Jekyll, Hexo, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", - "version": "5.6.0", + "version": "5.7.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { @@ -240,6 +240,12 @@ }, "scope": "Content" }, + "frontMatter.content.wysiwyg": { + "type": "boolean", + "default": true, + "markdownDescription": "Specifies if you want to enable/disable the What You See, Is What You Get (WYSIWYG) markdown controls. [Check in the docs](https://frontmatter.codes/docs/settings#frontMatter.content.wysiwyg)", + "scope": "Content" + }, "frontMatter.custom.scripts": { "type": "array", "default": [], @@ -819,15 +825,170 @@ "command": "frontMatter.setLastModifiedDate", "title": "Set lastmod date", "category": "Front matter" + }, + { + "command": "frontMatter.markup.bold", + "title": "Bold", + "category": "Front matter", + "icon": { + "light": "assets/icons/bold-light.svg", + "dark": "assets/icons/bold-dark.svg" + } + }, + { + "command": "frontMatter.markup.italic", + "title": "Italic", + "category": "Front matter", + "icon": { + "light": "assets/icons/italic-light.svg", + "dark": "assets/icons/italic-dark.svg" + } + }, + { + "command": "frontMatter.markup.strikethrough", + "title": "Strikethrough", + "category": "Front matter", + "icon": { + "light": "assets/icons/strikethrough-light.svg", + "dark": "assets/icons/strikethrough-dark.svg" + } + }, + { + "command": "frontMatter.markup.code", + "title": "Code", + "category": "Front matter", + "icon": { + "light": "assets/icons/code-light.svg", + "dark": "assets/icons/code-dark.svg" + } + }, + { + "command": "frontMatter.markup.codeblock", + "title": "Codeblock", + "category": "Front matter", + "icon": { + "light": "assets/icons/codeblock-light.svg", + "dark": "assets/icons/codeblock-dark.svg" + } + }, + { + "command": "frontMatter.markup.blockquote", + "title": "Codeblock", + "category": "Front matter", + "icon": { + "light": "assets/icons/blockquote-light.svg", + "dark": "assets/icons/blockquote-dark.svg" + } + }, + { + "command": "frontMatter.markup.heading", + "title": "Heading", + "category": "Front matter", + "icon": { + "light": "assets/icons/heading-light.svg", + "dark": "assets/icons/heading-dark.svg" + } + }, + { + "command": "frontMatter.markup.unorderedlist", + "title": "Unordered list", + "category": "Front matter", + "icon": { + "light": "assets/icons/unordered-list-light.svg", + "dark": "assets/icons/unordered-list-dark.svg" + } + }, + { + "command": "frontMatter.markup.orderedlist", + "title": "Ordered list", + "category": "Front matter", + "icon": { + "light": "assets/icons/ordered-list-light.svg", + "dark": "assets/icons/ordered-list-dark.svg" + } + }, + { + "command": "frontMatter.markup.tasklist", + "title": "Task list", + "category": "Front matter" + }, + { + "command": "frontMatter.markup.options", + "title": "Other markup options", + "category": "Front matter", + "icon": { + "light": "assets/icons/options-light.svg", + "dark": "assets/icons/options-dark.svg" + } + }, + { + "command": "frontMatter.diagnostics", + "title": "Diagnostic logging", + "category": "Front matter" } ], "menus": { "editor/title": [ + { + "command": "frontMatter.markup.heading", + "group": "navigation@-132", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, + { + "command": "frontMatter.markup.bold", + "group": "navigation@-131", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, + { + "command": "frontMatter.markup.italic", + "group": "navigation@-130", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, + { + "command": "frontMatter.markup.strikethrough", + "group": "navigation@-129", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, + { + "command": "frontMatter.markup.blockquote", + "group": "navigation@-128", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, { "command": "frontMatter.insertImage", - "group": "navigation@-99", + "group": "navigation@-127", "when": "resourceLangId == markdown" }, + { + "command": "frontMatter.markup.options", + "group": "navigation@-126", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, + { + "command": "frontMatter.markup.orderedlist", + "group": "1_markup@1", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, + { + "command": "frontMatter.markup.unorderedlist", + "group": "1_markup@2", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, + { + "command": "frontMatter.markup.tasklist", + "group": "1_markup@3", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, + { + "command": "frontMatter.markup.code", + "group": "1_markup@4", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, + { + "command": "frontMatter.markup.codeblock", + "group": "1_markup@5", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, { "command": "frontMatter.dashboard", "group": "navigation@-98", @@ -888,6 +1049,50 @@ { "command": "frontMatter.dashboard.close", "when": "false" + }, + { + "command": "frontMatter.markup.bold", + "when": "false" + }, + { + "command": "frontMatter.markup.italic", + "when": "false" + }, + { + "command": "frontMatter.markup.strikethrough", + "when": "false" + }, + { + "command": "frontMatter.markup.code", + "when": "false" + }, + { + "command": "frontMatter.markup.codeblock", + "when": "false" + }, + { + "command": "frontMatter.markup.blockquote", + "when": "false" + }, + { + "command": "frontMatter.markup.heading", + "when": "false" + }, + { + "command": "frontMatter.markup.unorderedlist", + "when": "false" + }, + { + "command": "frontMatter.markup.orderedlist", + "when": "false" + }, + { + "command": "frontMatter.markup.tasklist", + "when": "false" + }, + { + "command": "frontMatter.markup.options", + "when": "false" } ], "view/title": [ @@ -968,4 +1173,4 @@ "webpack-cli": "3.3.12" }, "dependencies": {} -} \ No newline at end of file +} diff --git a/src/commands/Article.ts b/src/commands/Article.ts index b169b87d..86ce503c 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -203,7 +203,7 @@ export class Article { const file = parseWinPath(editor.document.fileName); - if (!file.endsWith(`.md`) && !file.endsWith(`.mdx`)) { + if (!file.endsWith(`.md`) && !file.endsWith(`.markdown`) && !file.endsWith(`.mdx`)) { return; } diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index a765d3d7..ffeacf11 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -434,7 +434,7 @@ export class Dashboard { if (crntSort?.type === SortType.string) { allMedia = allMedia.sort(Sorting.alphabetically("fsPath")); } else if (crntSort?.type === SortType.date) { - allMedia = allMedia.sort(Sorting.date("mtime")); + allMedia = allMedia.sort(Sorting.dateWithFallback("mtime", "fsPath")); } else { allMedia = allMedia.sort(Sorting.alphabetically("fsPath")); } @@ -548,7 +548,7 @@ export class Dashboard { if (folderInfo) { for (const folder of folderInfo) { for (const file of folder.lastModified) { - if (file.fileName.endsWith(`.md`) || file.fileName.endsWith(`.mdx`)) { + if (file.fileName.endsWith(`.md`) || file.fileName.endsWith(`.markdown`) || file.fileName.endsWith(`.mdx`)) { try { const article = ArticleHelper.getFrontMatterByPath(file.filePath); diff --git a/src/commands/Diagnostics.ts b/src/commands/Diagnostics.ts new file mode 100644 index 00000000..69093109 --- /dev/null +++ b/src/commands/Diagnostics.ts @@ -0,0 +1,63 @@ +import { Folders } from "./Folders"; +import { ViewColumn, workspace } from "vscode"; +import ContentProvider from "../providers/ContentProvider"; +import { join } from "path"; +import { ContentFolder } from "../models"; + + +export class Diagnostics { + + public static async show() { + const folders = Folders.get(); + const projectName = Folders.getProjectFolderName(); + const wsFolder = Folders.getWorkspaceFolder(); + + const folderData = []; + for (const folder of folders) { + folderData.push(await Diagnostics.processFolder(folder, projectName)); + } + + const all = await Diagnostics.allProjectFiles(); + + const logging = `# Project name + +${projectName} + +# Folders + +${folders.map(f => `- ${f.title}: "${f.path}"`).join("\n")} + +# Workspace folder + +${wsFolder ? wsFolder.fsPath : "No workspace folder"} + +# Total files + +${all} + +# Folders to search files + +${folderData.join("\n")} + `; + + ContentProvider.show(logging, `${projectName} diagnostics`, "markdown", ViewColumn.One); + } + + private static async allProjectFiles() { + const allFiles = await workspace.findFiles(`**/*.*`); + return `Total files found: ${allFiles.length}`; + } + + private static async processFolder(folder: ContentFolder, projectName: string) { + let projectStart = folder.path.split(projectName).pop(); + projectStart = projectStart || ""; + projectStart = projectStart?.replace(/\\/g, '/'); + projectStart = projectStart?.startsWith('/') ? projectStart.substr(1) : projectStart; + + const mdFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md')); + const mdxFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx')); + const markdownFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown')); + + return `- Project start length: ${projectStart.length} | Search in: "${join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.*')}" | mdFiles: ${mdFiles.length} | mdxFiles: ${mdxFiles.length} | markdownFiles: ${markdownFiles.length}`; + } +} \ No newline at end of file diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 19653ab2..1b0c45ce 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -6,7 +6,7 @@ import { ContentFolder, FileInfo, FolderInfo } from "../models"; import uniqBy = require("lodash.uniqby"); import { Template } from "./Template"; import { Notifications } from "../helpers/Notifications"; -import { FilesHelper, Settings } from "../helpers"; +import { Settings } from "../helpers"; import { existsSync, mkdirSync } from 'fs'; import { format } from 'date-fns'; import { Dashboard } from './Dashboard'; @@ -207,12 +207,14 @@ export class Folders { try { const projectName = Folders.getProjectFolderName(); let projectStart = folder.path.split(projectName).pop(); + if (projectStart) { projectStart = projectStart.replace(/\\/g, '/'); projectStart = projectStart.startsWith('/') ? projectStart.substr(1) : projectStart; const mdFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.md')); + const markdownFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.markdown')); const mdxFiles = await workspace.findFiles(join(projectStart, folder.excludeSubdir ? '/' : '**/', '*.mdx')); - let files = [...mdFiles, ...mdxFiles]; + let files = [...mdFiles, ...markdownFiles, ...mdxFiles]; if (files) { let fileStats: FileInfo[] = []; diff --git a/src/commands/Wysiwyg.ts b/src/commands/Wysiwyg.ts new file mode 100644 index 00000000..66367c37 --- /dev/null +++ b/src/commands/Wysiwyg.ts @@ -0,0 +1,230 @@ +import { commands, window, Selection, QuickPickItem } from "vscode"; +import { COMMAND_NAME, CONTEXT, SETTINGS_CONTENT_WYSIWYG } from "../constants"; +import { Settings } from "../helpers"; + +enum MarkupType { + bold = 1, + italic, + strikethrough, + code, + codeblock, + blockquote, + heading, + unorderedList, + orderedList, + taskList +} + +export class Wysiwyg { + + /** + * Registers the markup commands for the WYSIWYG controls + * @param subscriptions + * @returns + */ + public static async registerCommands(subscriptions: any) { + + const wysiwygEnabled = Settings.get(SETTINGS_CONTENT_WYSIWYG); + + if (!wysiwygEnabled) { + return; + } + + await commands.executeCommand('setContext', CONTEXT.wysiwyg, true); + + // Surrounding markup + subscriptions.push(commands.registerCommand(COMMAND_NAME.bold, () => this.addMarkup(MarkupType.bold))); + subscriptions.push(commands.registerCommand(COMMAND_NAME.italic, () => this.addMarkup(MarkupType.italic))); + subscriptions.push(commands.registerCommand(COMMAND_NAME.strikethrough, () => this.addMarkup(MarkupType.strikethrough))); + subscriptions.push(commands.registerCommand(COMMAND_NAME.code, () => this.addMarkup(MarkupType.code))); + subscriptions.push(commands.registerCommand(COMMAND_NAME.codeblock, () => this.addMarkup(MarkupType.codeblock))); + + // Prefix markup + subscriptions.push(commands.registerCommand(COMMAND_NAME.heading, () => this.addMarkup(MarkupType.heading))); + subscriptions.push(commands.registerCommand(COMMAND_NAME.blockquote, () => this.addMarkup(MarkupType.blockquote))); + subscriptions.push(commands.registerCommand(COMMAND_NAME.unorderedlist, () => this.addMarkup(MarkupType.unorderedList))); + subscriptions.push(commands.registerCommand(COMMAND_NAME.orderedlist, () => this.addMarkup(MarkupType.orderedList))); + subscriptions.push(commands.registerCommand(COMMAND_NAME.taskList, () => this.addMarkup(MarkupType.taskList))); + + // Options + subscriptions.push(commands.registerCommand(COMMAND_NAME.options, async () => { + const qpItems: QuickPickItem[] = [ + { label: "$(list-unordered) Unordered list", detail: "Add an unordered list", alwaysShow: true, }, + { label: "$(list-ordered) Ordered list", detail: "Add an ordered list", alwaysShow: true }, + { label: "$(tasklist) Task list", detail: "Add a task list", alwaysShow: true }, + { label: "$(code) Code", detail: "Add inline code snippet", alwaysShow: true }, + { label: "$(symbol-namespace) Code block", detail: "Add a code block", alwaysShow: true }, + ] + + const option = await window.showQuickPick([ ...qpItems ], { + placeHolder: "Which type of markup would you like to insert?", + canPickMany: false, + ignoreFocusOut: false, + }); + + if (option) { + if (option.label === qpItems[0].label) { + await this.addMarkup(MarkupType.unorderedList); + } else if (option.label === qpItems[1].label) { + await this.addMarkup(MarkupType.orderedList); + } else if (option.label === qpItems[2].label) { + await this.addMarkup(MarkupType.taskList); + } else if (option.label === qpItems[3].label) { + await this.addMarkup(MarkupType.code); + } else if (option.label === qpItems[4].label) { + await this.addMarkup(MarkupType.codeblock); + } + } + })); + } + + /** + * Add the markup to the content + * @param type + * @returns + */ + private static async addMarkup(type: MarkupType) { + const editor = window.activeTextEditor; + if (!editor) { + return; + } + + const selection = editor.selection; + const hasTextSelection = !selection.isEmpty; + + const markers = this.getMarkers(type); + if (!markers) { + return; + } + + const crntSelection = selection.active; + + if (hasTextSelection) { + // Replace the selection and surround with the markup + const selectionText = editor.document.getText(selection); + const txt = await this.insertText(markers, type, selectionText); + + editor.edit(builder => { + builder.replace(selection, txt); + }); + } else { + const txt = await this.insertText(markers, type); + + // Insert the markers where cursor is located. + const markerLength = this.isMarkupWrapping(type) ? txt.length + 1 : markers.length; + let newPosition = crntSelection.with(crntSelection.line, crntSelection.character + markerLength); + + await editor.edit(builder => { + builder.insert(newPosition, txt); + }); + + if (type === MarkupType.codeblock) { + newPosition = crntSelection.with(crntSelection.line + 1, 0); + } + + editor.selection = new Selection(newPosition, newPosition); + } + } + + /** + * Check if the text will be wrapped + * @param type + * @returns + */ + private static isMarkupWrapping(type: MarkupType) { + return ( + type === MarkupType.blockquote || + type === MarkupType.heading || + type === MarkupType.unorderedList || + type === MarkupType.orderedList || + type === MarkupType.taskList + ); + } + + /** + * Insert text at the current cursor position + */ + private static async insertText(marker: string | undefined, type: MarkupType, text: string | null = null) { + const crntText = text || this.lineBreak(type); + + if (this.isMarkupWrapping(type)) { + if (type === MarkupType.heading) { + const headingLvl = await window.showQuickPick([ + "Heading 1", + "Heading 2", + "Heading 3", + "Heading 4", + "Heading 5", + "Heading 6" + ], { + canPickMany: false, + placeHolder: "Which heading level do you want to insert?", + ignoreFocusOut: false + }); + + if (headingLvl) { + const headingNr = parseInt(headingLvl.replace("Heading ", "")); + return `${Array(headingNr + 1).join(marker)} ${crntText}`; + } + } + + if (type === MarkupType.unorderedList || type === MarkupType.taskList) { + const lines = crntText.split("\n").map(line => `${marker} ${line}`); + return lines.join("\n"); + } + + if (type === MarkupType.orderedList) { + const lines = crntText.split("\n").map((line, idx) => `${idx+1}. ${line}`); + return lines.join("\n"); + } + + return `${marker} ${crntText}`; + } else { + return `${marker}${crntText}${marker}`; + } + } + + /** + * Check if linebreak needs to be added + * @param type + * @returns + */ + private static lineBreak(type: MarkupType) { + if (type === MarkupType.codeblock) { + return `\n\n`; + } + return ""; + } + + /** + * Retrieve the type of markers + * @param type + * @returns + */ + private static getMarkers(type: MarkupType) { + switch(type) { + case MarkupType.bold: + return `**`; + case MarkupType.italic: + return `*`; + case MarkupType.strikethrough: + return `~~`; + case MarkupType.code: + return "`"; + case MarkupType.codeblock: + return "```"; + case MarkupType.blockquote: + return ">"; + case MarkupType.heading: + return "#"; + case MarkupType.unorderedList: + return "-"; + case MarkupType.orderedList: + return "1."; + case MarkupType.taskList: + return "- [ ]"; + default: + return; + } + } +} \ No newline at end of file diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index 49ce954d..001311fa 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -32,4 +32,18 @@ export const COMMAND_NAME = { promote: getCommandName("promoteSettings"), insertImage: getCommandName("insertImage"), createFolder: getCommandName("createFolder"), + diagnostics: getCommandName("diagnostics"), + + // WYSIWYG + bold: getCommandName("markup.bold"), + italic: getCommandName("markup.italic"), + strikethrough: getCommandName("markup.strikethrough"), + code: getCommandName("markup.code"), + codeblock: getCommandName("markup.codeblock"), + heading: getCommandName("markup.heading"), + blockquote: getCommandName("markup.blockquote"), + unorderedlist: getCommandName("markup.unorderedlist"), + orderedlist: getCommandName("markup.orderedlist"), + taskList: getCommandName("markup.tasklist"), + options: getCommandName("markup.options"), }; \ No newline at end of file diff --git a/src/constants/context.ts b/src/constants/context.ts index a8754720..376640ad 100644 --- a/src/constants/context.ts +++ b/src/constants/context.ts @@ -4,4 +4,5 @@ export const CONTEXT = { canOpenDashboard: "frontMatterCanOpenDashboard", isEnabled: "frontMatter:enabled", isDashboardOpen: "frontMatter:dashboard:open", + wysiwyg: "frontMatter:markdown:wysiwyg", }; \ No newline at end of file diff --git a/src/constants/settings.ts b/src/constants/settings.ts index 06eb3bb0..528f4d11 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -43,6 +43,7 @@ export const SETTINGS_CONTENT_STATIC_FOLDER = "content.publicFolder"; export const SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight"; export const SETTINGS_CONTENT_DRAFT_FIELD = "content.draftField"; export const SETTINGS_CONTENT_SORTING = "content.sorting"; +export const SETTINGS_CONTENT_WYSIWYG = "content.wysiwyg"; export const SETTINGS_CONTENT_SORTING_DEFAULT = "content.defaultSorting"; export const SETTINGS_MEDIA_SORTING_DEFAULT = "content.defaultSorting"; diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index 8a30926b..e091ba47 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -15,7 +15,7 @@ import { MenuItem, MenuItems } from '../Menu'; import { Alert } from '../Modals/Alert'; import { Metadata } from '../Modals/Metadata'; import { MenuButton } from './MenuButton' - + export interface IItemProps { media: MediaInfo; } @@ -252,7 +252,7 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi -

+

{basename(parseWinPath(media.fsPath) || "")}

{ diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index 8035eecd..4699da1c 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -13,7 +13,7 @@ import { CustomTaxonomyData, DraftField, ScriptType, TaxonomyType } from '../mod import { exec } from 'child_process'; import { fromMarkdown } from 'mdast-util-from-markdown'; import { Content } from 'mdast'; -import { COMMAND_NAME } from '../constants/Extension'; +import { COMMAND_NAME, EXTENSION_BETA_ID, EXTENSION_ID } from '../constants/Extension'; import { Folders } from '../commands/Folders'; import { Preview } from '../commands/Preview'; import { openFileInEditor } from '../helpers/openFileInEditor'; @@ -122,7 +122,8 @@ export class ExplorerView implements WebviewViewProvider, Disposable { this.addCustomTaxonomy(msg.data); break; case CommandToCode.openSettings: - commands.executeCommand('workbench.action.openSettings', '@ext:eliostruyf.vscode-front-matter'); + const isBeta = Extension.getInstance().isBetaVersion(); + commands.executeCommand('workbench.action.openSettings', `@ext:${isBeta ? EXTENSION_BETA_ID : EXTENSION_ID}`); break; case CommandToCode.openFile: if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") { diff --git a/src/extension.ts b/src/extension.ts index 3995918a..ac0b662d 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -16,6 +16,8 @@ import { DashboardData } from './models/DashboardData'; import { Settings as SettingsHelper } from './helpers'; import { Content } from './commands/Content'; import ContentProvider from './providers/ContentProvider'; +import { Wysiwyg } from './commands/Wysiwyg'; +import { Diagnostics } from './commands/Diagnostics'; let frontMatterStatusBar: vscode.StatusBarItem; let statusDebouncer: { (fnc: any, time: number): void; }; @@ -56,7 +58,7 @@ export async function activate(context: vscode.ExtensionContext) { // Register the explorer view const explorerSidebar = ExplorerView.getInstance(extensionUri); - let explorerView = vscode.window.registerWebviewViewProvider(ExplorerView.viewType, explorerSidebar, { + const explorerView = vscode.window.registerWebviewViewProvider(ExplorerView.viewType, explorerSidebar, { webviewOptions: { retainContextWhenHidden: true } @@ -65,35 +67,35 @@ export async function activate(context: vscode.ExtensionContext) { // Folding the front matter of markdown files vscode.languages.registerFoldingRangeProvider(mdSelector, new MarkdownFoldingProvider()); - let insertTags = vscode.commands.registerCommand(COMMAND_NAME.insertTags, async () => { + const insertTags = vscode.commands.registerCommand(COMMAND_NAME.insertTags, async () => { await vscode.commands.executeCommand('workbench.view.extension.frontmatter-explorer'); await vscode.commands.executeCommand('workbench.action.focusSideBar'); explorerSidebar.triggerInputFocus(TagType.tags); }); - let insertCategories = vscode.commands.registerCommand(COMMAND_NAME.insertCategories, async () => { + const insertCategories = vscode.commands.registerCommand(COMMAND_NAME.insertCategories, async () => { await vscode.commands.executeCommand('workbench.view.extension.frontmatter-explorer'); await vscode.commands.executeCommand('workbench.action.focusSideBar'); explorerSidebar.triggerInputFocus(TagType.categories); }); - let createTag = vscode.commands.registerCommand(COMMAND_NAME.createTag, () => { + const createTag = vscode.commands.registerCommand(COMMAND_NAME.createTag, () => { Settings.create(TaxonomyType.Tag); }); - let createCategory = vscode.commands.registerCommand(COMMAND_NAME.createCategory, () => { + const createCategory = vscode.commands.registerCommand(COMMAND_NAME.createCategory, () => { Settings.create(TaxonomyType.Category); }); - let exportTaxonomy = vscode.commands.registerCommand(COMMAND_NAME.exportTaxonomy, Settings.export); + const exportTaxonomy = vscode.commands.registerCommand(COMMAND_NAME.exportTaxonomy, Settings.export); - let remap = vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap); + const remap = vscode.commands.registerCommand(COMMAND_NAME.remap, Settings.remap); - let setLastModifiedDate = vscode.commands.registerCommand(COMMAND_NAME.setLastModifiedDate, Article.setLastModifiedDate); + const setLastModifiedDate = vscode.commands.registerCommand(COMMAND_NAME.setLastModifiedDate, Article.setLastModifiedDate); - let generateSlug = vscode.commands.registerCommand(COMMAND_NAME.generateSlug, Article.generateSlug); + const generateSlug = vscode.commands.registerCommand(COMMAND_NAME.generateSlug, Article.generateSlug); - let createFromTemplate = vscode.commands.registerCommand(COMMAND_NAME.createFromTemplate, (folder: vscode.Uri) => { + const createFromTemplate = vscode.commands.registerCommand(COMMAND_NAME.createFromTemplate, (folder: vscode.Uri) => { const folderPath = Folders.getFolderPath(folder); if (folderPath) { Template.create(folderPath); @@ -183,6 +185,12 @@ export async function activate(context: vscode.ExtensionContext) { // Create the editor experience for bulk scripts subscriptions.push(vscode.workspace.registerTextDocumentContentProvider(ContentProvider.scheme, new ContentProvider())); + // What you see, is what you get + Wysiwyg.registerCommands(subscriptions); + + // Diagnostics + subscriptions.push(vscode.commands.registerCommand(COMMAND_NAME.diagnostics, Diagnostics.show)); + // Subscribe all commands subscriptions.push( insertTags, diff --git a/src/helpers/Sorting.ts b/src/helpers/Sorting.ts index 20055e6a..fdd6de87 100644 --- a/src/helpers/Sorting.ts +++ b/src/helpers/Sorting.ts @@ -34,6 +34,31 @@ export class Sorting { }; }; + /** + * Sort by date with a fallback + * @param property + * @returns + */ + public static dateWithFallback = (property: string, fallback: string) => { + return (a: any, b: any) => { + const dateA = DateHelper.tryParse(a[property]); + const dateB = DateHelper.tryParse(b[property]); + + // Sort by date + var dCount = (dateA || new Date(0)).getTime() - (dateB || new Date(0)).getTime(); + if(dCount) return dCount; + + // If there is a tie, sort by fallback property + if (a[fallback] < b[fallback]) { + return -1; + } + if (a[fallback] > b[fallback]) { + return 1; + } + return 0; + }; + }; + /** * Sort by number * @param property diff --git a/src/panelWebView/components/FileItem.tsx b/src/panelWebView/components/FileItem.tsx index 1d2db400..e4c84613 100644 --- a/src/panelWebView/components/FileItem.tsx +++ b/src/panelWebView/components/FileItem.tsx @@ -19,7 +19,7 @@ const FileItem: React.FunctionComponent = ({ name, path }: React
  • { - (name.endsWith('.md') || name.endsWith('.mdx')) ? ( + (name.endsWith('.md') || name.endsWith('.markdown') || name.endsWith('.mdx')) ? ( ) : ( diff --git a/src/panelWebView/index.tsx b/src/panelWebView/index.tsx index eb3242d5..39d87c3f 100644 --- a/src/panelWebView/index.tsx +++ b/src/panelWebView/index.tsx @@ -14,8 +14,9 @@ import '@bendera/vscode-webview-elements/dist/vscode-table-row'; import '@bendera/vscode-webview-elements/dist/vscode-table-cell'; import '@bendera/vscode-webview-elements/dist/vscode-collapsible'; import '@bendera/vscode-webview-elements/dist/vscode-label'; +import '@bendera/vscode-webview-elements/dist/vscode-checkbox'; -import '@vscode/webview-ui-toolkit/dist/esm/checkbox'; +// import '@vscode/webview-ui-toolkit/dist/esm/checkbox'; const elm = document.querySelector("#app"); const version = elm?.getAttribute("data-version"); diff --git a/src/providers/ContentProvider.ts b/src/providers/ContentProvider.ts index 755e5406..302fb51d 100644 --- a/src/providers/ContentProvider.ts +++ b/src/providers/ContentProvider.ts @@ -9,14 +9,14 @@ export default class ContentProvider implements TextDocumentContentProvider { return uri.query; } - public static async show(data: string, title: string, outputType?: string) { + public static async show(data: string, title: string, outputType?: string, column: ViewColumn = ViewColumn.Beside) { const apiData = JSON.stringify(data, null, 2); const uri = Uri.parse(`${ContentProvider.scheme}:${title} output`); const doc = await workspace.openTextDocument(uri); - await window.showTextDocument(doc, { preview: true, viewColumn: ViewColumn.Beside, preserveFocus: true }); + await window.showTextDocument(doc, { preview: true, viewColumn: column, preserveFocus: true }); const workEdits = new WorkspaceEdit(); workEdits.replace(doc.uri, new Range(new Position(0, 0), new Position(doc.lineCount, 0)), data);