From 576ee9ca9dd1caa7f56131f815dcedaafd432df5 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 2 Mar 2022 08:40:30 +0100 Subject: [PATCH 01/10] First steps to create the snippet dashboard --- src/commands/Article.ts | 4 +- src/commands/Dashboard.ts | 4 +- src/commands/Folders.ts | 10 ++--- src/commands/Project.ts | 4 +- src/commands/Template.ts | 4 +- src/commands/Wysiwyg.ts | 4 +- src/constants/settings.ts | 41 ++++++++++--------- .../components/Contents/Contents.tsx | 13 +++--- src/dashboardWebView/components/Dashboard.tsx | 9 ++++ .../components/Header/Tabs.tsx | 9 +++- .../components/Layout/PageLayout.tsx | 26 ++++++++++++ .../components/Media/Media.tsx | 11 ++--- .../components/SnippetsView/Snippets.tsx | 18 ++++++++ src/dashboardWebView/components/Startup.tsx | 4 +- src/dashboardWebView/models/NavigationType.ts | 1 + src/dashboardWebView/models/Settings.ts | 1 + src/helpers/ArticleHelper.ts | 10 ++--- src/helpers/ContentType.ts | 4 +- src/helpers/DashboardSettings.ts | 25 +++++------ src/helpers/Extension.ts | 6 +-- src/helpers/ImageHelper.ts | 6 +-- src/helpers/MediaHelpers.ts | 6 +-- src/helpers/PanelSettings.ts | 12 +++--- src/helpers/isValidFile.ts | 4 +- src/listeners/dashboard/PagesListener.ts | 4 +- src/listeners/dashboard/SettingsListener.ts | 8 ++-- src/listeners/panel/SettingsListener.ts | 6 +-- src/providers/MarkdownFoldingProvider.ts | 6 +-- 28 files changed, 159 insertions(+), 101 deletions(-) create mode 100644 src/dashboardWebView/components/Layout/PageLayout.tsx create mode 100644 src/dashboardWebView/components/SnippetsView/Snippets.tsx diff --git a/src/commands/Article.ts b/src/commands/Article.ts index c737a136..cc55675e 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -1,5 +1,5 @@ import { isValidFile } from './../helpers/isValidFile'; -import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX, CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTINGS_CONTENT_PLACEHOLDERS, TelemetryEvent } from './../constants'; +import { SETTING_AUTO_UPDATE_DATE, SETTING_MODIFIED_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TEMPLATES_PREFIX, CONFIG_KEY, SETTING_DATE_FORMAT, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_CONTENT_PLACEHOLDERS, TelemetryEvent } from './../constants'; import * as vscode from 'vscode'; import { Field, TaxonomyType } from "../models"; import { format } from "date-fns"; @@ -200,7 +200,7 @@ export class Article { } // Update the fields containing a custom placeholder that depends on slug - const placeholders = Settings.get<{id: string, value: string}[]>(SETTINGS_CONTENT_PLACEHOLDERS); + const placeholders = Settings.get<{id: string, value: string}[]>(SETTING_CONTENT_PLACEHOLDERS); const customPlaceholders = placeholders?.filter(p => p.value.includes("{{slug}}")); for (const customPlaceholder of (customPlaceholders || [])) { const customPlaceholderFields = contentType.fields.filter(f => f.default === `{{${customPlaceholder.id}}}`); diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index a06c1b8d..36e4ae9c 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -1,4 +1,4 @@ -import { SETTINGS_DASHBOARD_OPENONSTART, CONTEXT } from '../constants'; +import { SETTING_DASHBOARD_OPENONSTART, CONTEXT } from '../constants'; import { join } from "path"; import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from "vscode"; import { Logger, Settings as SettingsHelper } from '../helpers'; @@ -24,7 +24,7 @@ export class Dashboard { * Init the dashboard */ public static async init() { - const openOnStartup = SettingsHelper.get(SETTINGS_DASHBOARD_OPENONSTART); + const openOnStartup = SettingsHelper.get(SETTING_DASHBOARD_OPENONSTART); if (openOnStartup) { Dashboard.open(); } diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index ad664ce2..7650be95 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -1,5 +1,5 @@ import { Questions } from './../helpers/Questions'; -import { SETTINGS_CONTENT_PAGE_FOLDERS, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_CONTENT_SUPPORTED_FILETYPES, TelemetryEvent } from './../constants'; +import { SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_STATIC_FOLDER, SETTING_CONTENT_SUPPORTED_FILETYPES, TelemetryEvent } from './../constants'; import { commands, Uri, workspace, window } from "vscode"; import { basename, join } from "path"; import { ContentFolder, FileInfo, FolderInfo } from "../models"; @@ -26,7 +26,7 @@ export class Folders { */ public static async addMediaFolder(data?: {selectedFolder?: string}) { let wsFolder = Folders.getWorkspaceFolder(); - const staticFolder = Settings.get(SETTINGS_CONTENT_STATIC_FOLDER); + const staticFolder = Settings.get(SETTING_CONTENT_STATIC_FOLDER); let startPath = ""; @@ -210,7 +210,7 @@ export class Folders { * Get the registered folders information */ public static async getInfo(limit?: number): Promise { - const supportedFiles = Settings.get(SETTINGS_CONTENT_SUPPORTED_FILETYPES); + const supportedFiles = Settings.get(SETTING_CONTENT_SUPPORTED_FILETYPES); const folders = Folders.get(); if (folders && folders.length > 0) { let folderInfo: FolderInfo[] = []; @@ -281,7 +281,7 @@ export class Folders { */ public static get(): ContentFolder[] { const wsFolder = Folders.getWorkspaceFolder(); - const folders: ContentFolder[] = Settings.get(SETTINGS_CONTENT_PAGE_FOLDERS) as ContentFolder[]; + const folders: ContentFolder[] = Settings.get(SETTING_CONTENT_PAGE_FOLDERS) as ContentFolder[]; return folders.map(folder => ({ ...folder, @@ -301,7 +301,7 @@ export class Folders { path: Folders.relWsFolder(folder, wsFolder) })); - await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, folderDetails, true); + await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true); // Reinitialize the folder listeners PagesListener.startWatchers(); diff --git a/src/commands/Project.ts b/src/commands/Project.ts index b6dddbd2..5595c207 100644 --- a/src/commands/Project.ts +++ b/src/commands/Project.ts @@ -6,7 +6,7 @@ import { Notifications } from "../helpers/Notifications"; import { Template } from "./Template"; import { Folders } from "./Folders"; import { Settings } from "../helpers"; -import { SETTINGS_CONTENT_DEFAULT_FILETYPE, TelemetryEvent } from "../constants"; +import { SETTING_CONTENT_DEFAULT_FILETYPE, TelemetryEvent } from "../constants"; export class Project { @@ -29,7 +29,7 @@ categories: [] public static async init(sampleTemplate: boolean = true) { try { Settings.createTeamSettings(); - const fileType = Settings.get(SETTINGS_CONTENT_DEFAULT_FILETYPE); + const fileType = Settings.get(SETTING_CONTENT_DEFAULT_FILETYPE); const folder = Template.getSettings(); const templatePath = Project.templatePath(); diff --git a/src/commands/Template.ts b/src/commands/Template.ts index 65d3b7f6..fa5659b0 100644 --- a/src/commands/Template.ts +++ b/src/commands/Template.ts @@ -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 { SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, TelemetryEvent } from '../constants'; +import { SETTING_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, TelemetryEvent } from '../constants'; import { ArticleHelper, Settings } from '../helpers'; import { Article } from '.'; import { Notifications } from '../helpers/Notifications'; @@ -52,7 +52,7 @@ export class Template { public static async generate() { const folder = Template.getSettings(); const editor = vscode.window.activeTextEditor; - const fileType = Settings.get(SETTINGS_CONTENT_DEFAULT_FILETYPE); + const fileType = Settings.get(SETTING_CONTENT_DEFAULT_FILETYPE); if (folder && editor && ArticleHelper.isMarkdownFile()) { const article = ArticleHelper.getFrontMatter(editor); diff --git a/src/commands/Wysiwyg.ts b/src/commands/Wysiwyg.ts index 66367c37..70201ce0 100644 --- a/src/commands/Wysiwyg.ts +++ b/src/commands/Wysiwyg.ts @@ -1,5 +1,5 @@ import { commands, window, Selection, QuickPickItem } from "vscode"; -import { COMMAND_NAME, CONTEXT, SETTINGS_CONTENT_WYSIWYG } from "../constants"; +import { COMMAND_NAME, CONTEXT, SETTING_CONTENT_WYSIWYG } from "../constants"; import { Settings } from "../helpers"; enum MarkupType { @@ -24,7 +24,7 @@ export class Wysiwyg { */ public static async registerCommands(subscriptions: any) { - const wysiwygEnabled = Settings.get(SETTINGS_CONTENT_WYSIWYG); + const wysiwygEnabled = Settings.get(SETTING_CONTENT_WYSIWYG); if (!wysiwygEnabled) { return; diff --git a/src/constants/settings.ts b/src/constants/settings.ts index d6664e6f..36d358b6 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -43,35 +43,36 @@ export const SETTING_PREVIEW_PATHNAME = "preview.pathName"; export const SETTING_CUSTOM_SCRIPTS = "custom.scripts"; export const SETTING_AUTO_UPDATE_DATE = "content.autoUpdateDate"; -export const SETTINGS_CONTENT_PAGE_FOLDERS = "content.pageFolders"; -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_PLACEHOLDERS = "content.placeholders"; +export const SETTING_CONTENT_PAGE_FOLDERS = "content.pageFolders"; +export const SETTING_CONTENT_STATIC_FOLDER = "content.publicFolder"; +export const SETTING_CONTENT_FRONTMATTER_HIGHLIGHT = "content.fmHighlight"; +export const SETTING_CONTENT_DRAFT_FIELD = "content.draftField"; +export const SETTING_CONTENT_SORTING = "content.sorting"; +export const SETTING_CONTENT_WYSIWYG = "content.wysiwyg"; +export const SETTING_CONTENT_PLACEHOLDERS = "content.placeholders"; +export const SETTING_CONTENT_SNIPPETS = "content.snippets"; -export const SETTINGS_CONTENT_SORTING_DEFAULT = "content.defaultSorting"; -export const SETTINGS_MEDIA_SORTING_DEFAULT = "content.defaultSorting"; +export const SETTING_CONTENT_SORTING_DEFAULT = "content.defaultSorting"; +export const SETTING_MEDIA_SORTING_DEFAULT = "content.defaultSorting"; -export const SETTINGS_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType"; -export const SETTINGS_CONTENT_SUPPORTED_FILETYPES = "content.supportedFileTypes"; +export const SETTING_CONTENT_DEFAULT_FILETYPE = "content.defaultFileType"; +export const SETTING_CONTENT_SUPPORTED_FILETYPES = "content.supportedFileTypes"; -export const SETTINGS_DASHBOARD_OPENONSTART = "dashboard.openOnStart"; -export const SETTINGS_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet"; +export const SETTING_DASHBOARD_OPENONSTART = "dashboard.openOnStart"; +export const SETTING_DASHBOARD_MEDIA_SNIPPET = "dashboard.mediaSnippet"; -export const SETTINGS_DATA_FILES = "data.files"; -export const SETTINGS_DATA_FOLDERS = "data.folders"; -export const SETTINGS_DATA_TYPES = "data.types"; +export const SETTING_DATA_FILES = "data.files"; +export const SETTING_DATA_FOLDERS = "data.folders"; +export const SETTING_DATA_TYPES = "data.types"; -export const SETTINGS_FILE_PRESERVE_CASING = "file.preserveCasing"; +export const SETTING_FILE_PRESERVE_CASING = "file.preserveCasing"; -export const SETTINGS_FRAMEWORK_ID = "framework.id"; -export const SETTINGS_FRAMEWORK_START = "framework.startCommand"; +export const SETTING_FRAMEWORK_ID = "framework.id"; +export const SETTING_FRAMEWORK_START = "framework.startCommand"; export const SETTING_SITE_BASEURL = "site.baseURL"; /** * @deprecated */ -export const SETTINGS_CONTENT_FOLDERS = "content.folders"; +export const SETTING_CONTENT_FOLDERS = "content.folders"; diff --git a/src/dashboardWebView/components/Contents/Contents.tsx b/src/dashboardWebView/components/Contents/Contents.tsx index 25c2718e..9982119f 100644 --- a/src/dashboardWebView/components/Contents/Contents.tsx +++ b/src/dashboardWebView/components/Contents/Contents.tsx @@ -2,7 +2,6 @@ import * as React from 'react'; import { useRecoilValue } from 'recoil'; import { Page } from '../../models'; import { SettingsSelector } from '../../state'; -import { Header } from '../Header'; import { Overview } from './Overview'; import { Spinner } from '../Spinner'; import { SponsorMsg } from '../SponsorMsg'; @@ -11,6 +10,7 @@ import { useEffect } from 'react'; import { Messenger } from '@estruyf/vscode/dist/client'; import { DashboardMessage } from '../../DashboardMessage'; import { TelemetryEvent } from '../../../constants'; +import { PageLayout } from '../Layout/PageLayout'; export interface IContentsProps { pages: Page[]; @@ -30,17 +30,14 @@ export const Contents: React.FunctionComponent = ({pages, loadin }, []); return ( -
-
- +
{ loading ? : }
-
+ ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/Dashboard.tsx b/src/dashboardWebView/components/Dashboard.tsx index 5cfbd97c..d999a298 100644 --- a/src/dashboardWebView/components/Dashboard.tsx +++ b/src/dashboardWebView/components/Dashboard.tsx @@ -9,6 +9,7 @@ import { Contents } from './Contents/Contents'; import { Media } from './Media/Media'; import { NavigationType } from '../models'; import { DataView } from './DataView'; +import { Snippets } from './SnippetsView/Snippets'; export interface IDashboardProps { showWelcome: boolean; @@ -31,6 +32,14 @@ export const Dashboard: React.FunctionComponent = ({showWelcome return ; } + if (view === NavigationType.Snippets) { + return ( +
+ +
+ ); + } + if (view === NavigationType.Media) { return (
diff --git a/src/dashboardWebView/components/Header/Tabs.tsx b/src/dashboardWebView/components/Header/Tabs.tsx index 35e664f4..6d15115b 100644 --- a/src/dashboardWebView/components/Header/Tabs.tsx +++ b/src/dashboardWebView/components/Header/Tabs.tsx @@ -1,4 +1,4 @@ -import { DatabaseIcon, PhotographIcon } from '@heroicons/react/outline'; +import { CodeIcon, DatabaseIcon, PhotographIcon } from '@heroicons/react/outline'; import * as React from 'react'; import { useRecoilValue } from 'recoil'; import { MarkdownIcon } from '../../../panelWebView/components/Icons/MarkdownIcon'; @@ -29,6 +29,13 @@ export const Tabs: React.FunctionComponent = ({ onNavigate }: React. Media +
  • + + Snippets + +
  • { (settings?.dataFiles && settings.dataFiles.length > 0) && (
  • diff --git a/src/dashboardWebView/components/Layout/PageLayout.tsx b/src/dashboardWebView/components/Layout/PageLayout.tsx new file mode 100644 index 00000000..c2083d48 --- /dev/null +++ b/src/dashboardWebView/components/Layout/PageLayout.tsx @@ -0,0 +1,26 @@ +import * as React from 'react'; +import { useRecoilValue } from 'recoil'; +import { SettingsSelector } from '../../state'; +import { Header } from '../Header'; + +export interface IPageLayoutProps { + folders?: string[] | undefined + totalPages?: number | undefined +} + +export const PageLayout: React.FunctionComponent = ({ folders, totalPages, children }: React.PropsWithChildren) => { + const settings = useRecoilValue(SettingsSelector); + + return ( +
    +
    + +
    + { children } +
    +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Media/Media.tsx b/src/dashboardWebView/components/Media/Media.tsx index 7f33f42e..a2ac3964 100644 --- a/src/dashboardWebView/components/Media/Media.tsx +++ b/src/dashboardWebView/components/Media/Media.tsx @@ -3,7 +3,6 @@ import {UploadIcon} from '@heroicons/react/outline'; import * as React from 'react'; import { useRecoilValue } from 'recoil'; import { LoadingAtom, MediaFoldersAtom, SelectedMediaFolderAtom, SettingsSelector, ViewDataSelector } from '../../state'; -import { Header } from '../Header'; import { Spinner } from '../Spinner'; import { SponsorMsg } from '../SponsorMsg'; import { Item } from './Item'; @@ -16,6 +15,7 @@ import { FrontMatterIcon } from '../../../panelWebView/components/Icons/FrontMat import { FolderItem } from './FolderItem'; import useMedia from '../../hooks/useMedia'; import { TelemetryEvent } from '../../../constants'; +import { PageLayout } from '../Layout/PageLayout'; export interface IMediaProps {} @@ -56,11 +56,8 @@ export const Media: React.FunctionComponent = (props: React.PropsWi }); return ( -
    -
    - -
    - + +
    { viewData?.data?.filePath && (
    @@ -123,6 +120,6 @@ export const Media: React.FunctionComponent = (props: React.PropsWi -
    + ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/SnippetsView/Snippets.tsx b/src/dashboardWebView/components/SnippetsView/Snippets.tsx new file mode 100644 index 00000000..5c1e43f2 --- /dev/null +++ b/src/dashboardWebView/components/SnippetsView/Snippets.tsx @@ -0,0 +1,18 @@ +import * as React from 'react'; +import { useRecoilValue } from 'recoil'; +import { SettingsSelector } from '../../state'; +import { PageLayout } from '../Layout/PageLayout'; + +export interface ISnippetsProps {} + +export const Snippets: React.FunctionComponent = (props: React.PropsWithChildren) => { + const settings = useRecoilValue(SettingsSelector); + + return ( + + { + JSON.stringify(settings?.snippets, null, 2) + } + + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/Startup.tsx b/src/dashboardWebView/components/Startup.tsx index 07156d94..8cb95fbe 100644 --- a/src/dashboardWebView/components/Startup.tsx +++ b/src/dashboardWebView/components/Startup.tsx @@ -1,5 +1,5 @@ import * as React from 'react'; -import { SETTINGS_DASHBOARD_OPENONSTART } from '../../constants'; +import { SETTING_DASHBOARD_OPENONSTART } from '../../constants'; import { Messenger } from '@estruyf/vscode/dist/client'; import { DashboardMessage } from '../DashboardMessage'; import { Settings } from '../models/Settings'; @@ -13,7 +13,7 @@ export const Startup: React.FunctionComponent = ({settings}: Reac const onChange = (e: React.ChangeEvent) => { setIsChecked(e.target.checked); - Messenger.send(DashboardMessage.updateSetting, { name: SETTINGS_DASHBOARD_OPENONSTART, value: e.target.checked }); + Messenger.send(DashboardMessage.updateSetting, { name: SETTING_DASHBOARD_OPENONSTART, value: e.target.checked }); }; React.useEffect(() => { diff --git a/src/dashboardWebView/models/NavigationType.ts b/src/dashboardWebView/models/NavigationType.ts index eba82bf9..2fd1da24 100644 --- a/src/dashboardWebView/models/NavigationType.ts +++ b/src/dashboardWebView/models/NavigationType.ts @@ -2,4 +2,5 @@ export enum NavigationType { Contents = "contents", Media = "media", Data = "data", + Snippets = "snippets", } \ No newline at end of file diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index 0dca39c7..e91aaf0f 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -29,6 +29,7 @@ export interface Settings { dataFiles: DataFile[] | undefined; dataTypes: DataType[] | undefined; isBacker: boolean | undefined; + snippets: any | undefined; } export interface DashboardState { diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index 71ab9e66..5f9ca6e0 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -2,7 +2,7 @@ import { MarkdownFoldingProvider } from './../providers/MarkdownFoldingProvider' import { DEFAULT_CONTENT_TYPE, DEFAULT_CONTENT_TYPE_NAME } from './../constants/ContentType'; import * as vscode from 'vscode'; import * as fs from "fs"; -import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTINGS_CONTENT_SUPPORTED_FILETYPES, SETTINGS_FILE_PRESERVE_CASING, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants'; +import { DefaultFields, SETTING_CONTENT_DEFAULT_FILETYPE, SETTING_CONTENT_PLACEHOLDERS, SETTING_CONTENT_SUPPORTED_FILETYPES, SETTING_FILE_PRESERVE_CASING, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants'; import { DumpOptions } from 'js-yaml'; import { FrontMatterParser, ParsedFrontMatter } from '../parsers'; import { Extension, Logger, Settings, SlugHelper } from '.'; @@ -143,7 +143,7 @@ export class ArticleHelper { */ public static isMarkdownFile(document: vscode.TextDocument | undefined | null = null) { const supportedLanguages = ["markdown", "mdx"]; - const fileTypes = Settings.get(SETTINGS_CONTENT_SUPPORTED_FILETYPES); + const fileTypes = Settings.get(SETTING_CONTENT_SUPPORTED_FILETYPES); const supportedFileExtensions = fileTypes ? fileTypes.map(f => f.startsWith(`.`) ? f : `.${f}`) : DEFAULT_FILE_TYPES; const languageId = document?.languageId?.toLowerCase(); const isSupportedLanguage = languageId && supportedLanguages.includes(languageId); @@ -227,7 +227,7 @@ export class ArticleHelper { * @returns */ public static sanitize(value: string): string { - const preserveCasing = Settings.get(SETTINGS_FILE_PRESERVE_CASING) as boolean; + const preserveCasing = Settings.get(SETTING_FILE_PRESERVE_CASING) as boolean; return sanitize((preserveCasing ? value : value.toLowerCase()).replace(/ /g, "-")); } @@ -240,7 +240,7 @@ export class ArticleHelper { */ public static createContent(contentType: ContentType | undefined, folderPath: string, titleValue: string, fileExtension?: string): string | undefined { const prefix = Settings.get(SETTING_TEMPLATES_PREFIX); - const fileType = Settings.get(SETTINGS_CONTENT_DEFAULT_FILETYPE); + const fileType = Settings.get(SETTING_CONTENT_DEFAULT_FILETYPE); // Name of the file or folder to create const sanitizedName = ArticleHelper.sanitize(titleValue); @@ -336,7 +336,7 @@ export class ArticleHelper { */ public static processCustomPlaceholders(value: string, title: string) { if (value && typeof value === "string") { - const placeholders = Settings.get<{id: string, value: string}[]>(SETTINGS_CONTENT_PLACEHOLDERS); + const placeholders = Settings.get<{id: string, value: string}[]>(SETTING_CONTENT_PLACEHOLDERS); if (placeholders && placeholders.length > 0) { for (const placeholder of placeholders) { if (value.includes(`{{${placeholder.id}}}`)) { diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 89693f95..875ebd23 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -1,6 +1,6 @@ import { PagesListener } from './../listeners/dashboard'; import { ArticleHelper, Settings } from "."; -import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants"; +import { SETTING_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants"; import { ContentType as IContentType, DraftField, Field } from '../models'; import { Uri, commands } from 'vscode'; import { Folders } from "../commands/Folders"; @@ -18,7 +18,7 @@ export class ContentType { * @returns */ public static getDraftField() { - const draftField = Settings.get(SETTINGS_CONTENT_DRAFT_FIELD); + const draftField = Settings.get(SETTING_CONTENT_DRAFT_FIELD); if (draftField) { return draftField; } diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index de5faaaa..af8005ff 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -2,7 +2,7 @@ import { basename, join } from "path"; import { workspace } from "vscode"; import { Folders } from "../commands/Folders"; import { Template } from "../commands/Template"; -import { CONTEXT, ExtensionState, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_SORTING, SETTINGS_CONTENT_SORTING_DEFAULT, SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_DASHBOARD_MEDIA_SNIPPET, SETTINGS_DASHBOARD_OPENONSTART, SETTINGS_DATA_FILES, SETTINGS_DATA_FOLDERS, SETTINGS_DATA_TYPES, SETTINGS_FRAMEWORK_ID, SETTINGS_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES } from "../constants"; +import { CONTEXT, ExtensionState, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SORTING, SETTING_CONTENT_SORTING_DEFAULT, SETTING_CONTENT_STATIC_FOLDER, SETTING_DASHBOARD_MEDIA_SNIPPET, SETTING_DASHBOARD_OPENONSTART, SETTING_DATA_FILES, SETTING_DATA_FOLDERS, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_MEDIA_SORTING_DEFAULT, SETTING_CUSTOM_SCRIPTS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_SNIPPETS } from "../constants"; import { DashboardViewType, SortingOption, Settings as ISettings } from "../dashboardWebView/models"; import { CustomScript, DraftField, ScriptType, SortingSetting, TaxonomyType } from "../models"; import { DataFile } from "../models/DataFile"; @@ -23,35 +23,36 @@ export class DashboardSettings { return { beta: ext.isBetaVersion(), wsFolder: wsFolder ? wsFolder.fsPath : '', - staticFolder: Settings.get(SETTINGS_CONTENT_STATIC_FOLDER), + staticFolder: Settings.get(SETTING_CONTENT_STATIC_FOLDER), folders: Folders.get(), initialized: isInitialized, tags: Settings.getTaxonomy(TaxonomyType.Tag), categories: Settings.getTaxonomy(TaxonomyType.Category), - openOnStart: Settings.get(SETTINGS_DASHBOARD_OPENONSTART), + openOnStart: Settings.get(SETTING_DASHBOARD_OPENONSTART), versionInfo: ext.getVersion(), pageViewType: await ext.getState(ExtensionState.PagesView, "workspace"), - mediaSnippet: Settings.get(SETTINGS_DASHBOARD_MEDIA_SNIPPET) || [], + mediaSnippet: Settings.get(SETTING_DASHBOARD_MEDIA_SNIPPET) || [], contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [], - draftField: Settings.get(SETTINGS_CONTENT_DRAFT_FIELD), - customSorting: Settings.get(SETTINGS_CONTENT_SORTING), + draftField: Settings.get(SETTING_CONTENT_DRAFT_FIELD), + customSorting: Settings.get(SETTING_CONTENT_SORTING), contentFolders: Folders.get(), - crntFramework: Settings.get(SETTINGS_FRAMEWORK_ID), + crntFramework: Settings.get(SETTING_FRAMEWORK_ID), framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null, scripts: (Settings.get(SETTING_CUSTOM_SCRIPTS) || []), dashboardState: { contents: { sorting: await ext.getState(ExtensionState.Dashboard.Contents.Sorting, "workspace"), - defaultSorting: Settings.get(SETTINGS_CONTENT_SORTING_DEFAULT) + defaultSorting: Settings.get(SETTING_CONTENT_SORTING_DEFAULT) }, media: { sorting: await ext.getState(ExtensionState.Dashboard.Media.Sorting, "workspace"), - defaultSorting: Settings.get(SETTINGS_MEDIA_SORTING_DEFAULT), + defaultSorting: Settings.get(SETTING_MEDIA_SORTING_DEFAULT), selectedFolder: await ext.getState(ExtensionState.SelectedFolder, "workspace") } }, dataFiles: await this.getDataFiles(), - dataTypes: Settings.get(SETTINGS_DATA_TYPES), + dataTypes: Settings.get(SETTING_DATA_TYPES), + snippets: Settings.get(SETTING_CONTENT_SNIPPETS), isBacker: await ext.getState(CONTEXT.backer, 'global') } as ISettings } @@ -62,8 +63,8 @@ export class DashboardSettings { */ private static async getDataFiles(): Promise { const wsPath = Folders.getWorkspaceFolder()?.fsPath; - const files = Settings.get(SETTINGS_DATA_FILES); - const folders = Settings.get(SETTINGS_DATA_FOLDERS); + const files = Settings.get(SETTING_DATA_FILES); + const folders = Settings.get(SETTING_DATA_FOLDERS); let clonedFiles = Object.assign([], files); if (folders) { diff --git a/src/helpers/Extension.ts b/src/helpers/Extension.ts index eba367b0..dd5f2698 100644 --- a/src/helpers/Extension.ts +++ b/src/helpers/Extension.ts @@ -2,7 +2,7 @@ import { existsSync, renameSync } from "fs"; import { basename, join } from "path"; import { extensions, Uri, ExtensionContext, window, workspace, commands, ExtensionMode, DiagnosticCollection, languages } from "vscode"; import { Folders, WORKSPACE_PLACEHOLDER } from "../commands/Folders"; -import { EXTENSION_NAME, GITHUB_LINK, SETTINGS_CONTENT_FOLDERS, SETTINGS_CONTENT_PAGE_FOLDERS, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, DEFAULT_CONTENT_TYPE_NAME, EXTENSION_BETA_ID, EXTENSION_ID, ExtensionState, DefaultFields, LocalStore, SETTING_TEMPLATES_FOLDER } from "../constants"; +import { EXTENSION_NAME, GITHUB_LINK, SETTING_CONTENT_FOLDERS, SETTING_CONTENT_PAGE_FOLDERS, SETTING_DATE_FIELD, SETTING_MODIFIED_FIELD, SETTING_SEO_DESCRIPTION_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, DEFAULT_CONTENT_TYPE_NAME, EXTENSION_BETA_ID, EXTENSION_ID, ExtensionState, DefaultFields, LocalStore, SETTING_TEMPLATES_FOLDER } from "../constants"; import { ContentType } from "../models"; import { Notifications } from "./Notifications"; import { parseWinPath } from "./parseWinPath"; @@ -137,7 +137,7 @@ export class Extension { // Migration to version 3.1.0 if (major < 3 || (major === 3 && minor < 1)) { - const folders = Settings.get(SETTINGS_CONTENT_FOLDERS); + const folders = Settings.get(SETTING_CONTENT_FOLDERS); if (folders && folders.length > 0) { const workspace = Folders.getWorkspaceFolder(); const projectFolder = basename(workspace?.fsPath || ""); @@ -147,7 +147,7 @@ export class Extension { path: `${WORKSPACE_PLACEHOLDER}${folder.fsPath.split(projectFolder).slice(1).join('')}`.split('\\').join('/') })); - await Settings.update(SETTINGS_CONTENT_PAGE_FOLDERS, paths); + await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, paths); } } diff --git a/src/helpers/ImageHelper.ts b/src/helpers/ImageHelper.ts index d5567e51..73c8be0a 100644 --- a/src/helpers/ImageHelper.ts +++ b/src/helpers/ImageHelper.ts @@ -5,7 +5,7 @@ import { Field } from '../models'; import { existsSync } from 'fs'; import { Folders } from '../commands/Folders'; import { Settings } from './SettingsHelper'; -import { SETTINGS_CONTENT_STATIC_FOLDER } from '../constants'; +import { SETTING_CONTENT_STATIC_FOLDER } from '../constants'; import { parseWinPath } from './parseWinPath'; export class ImageHelper { @@ -51,7 +51,7 @@ export class ImageHelper { */ public static relToAbs(filePath: string, value: string) { const wsFolder = Folders.getWorkspaceFolder(); - const staticFolder = Settings.get(SETTINGS_CONTENT_STATIC_FOLDER); + const staticFolder = Settings.get(SETTING_CONTENT_STATIC_FOLDER); const staticPath = join(parseWinPath(wsFolder?.fsPath || ""), staticFolder || "", value); const contentFolderPath = filePath ? join(dirname(filePath), value) : null; @@ -73,7 +73,7 @@ export class ImageHelper { */ public static absToRel(imgValue: string) { const wsFolder = Folders.getWorkspaceFolder(); - const staticFolder = Settings.get(SETTINGS_CONTENT_STATIC_FOLDER); + const staticFolder = Settings.get(SETTING_CONTENT_STATIC_FOLDER); let relPath = imgValue || ""; if (imgValue) { diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index 3383977b..bd1bfede 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -1,7 +1,7 @@ import { decodeBase64Image, Extension, MediaLibrary, Notifications, parseWinPath, Settings, Sorting } from "."; import { Dashboard } from "../commands/Dashboard"; import { Folders } from "../commands/Folders"; -import { ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTINGS_CONTENT_STATIC_FOLDER } from "../constants"; +import { ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTING_CONTENT_STATIC_FOLDER } from "../constants"; import { SortingOption } from "../dashboardWebView/models"; import { MediaInfo, MediaPaths, SortOrder, SortType } from "../models"; import { basename, extname, join, parse, dirname } from "path"; @@ -26,7 +26,7 @@ export class MediaHelpers { */ public static async getMedia(page: number = 0, requestedFolder: string = '', sort: SortingOption | null = null) { const wsFolder = Folders.getWorkspaceFolder(); - const staticFolder = Settings.get(SETTINGS_CONTENT_STATIC_FOLDER); + const staticFolder = Settings.get(SETTING_CONTENT_STATIC_FOLDER); const contentFolders = Folders.get(); const viewData = Dashboard.viewData; let selectedFolder = requestedFolder; @@ -199,7 +199,7 @@ export class MediaHelpers { public static async saveFile({fileName, contents, folder}: { fileName: string; contents: string; folder: string | null }) { if (fileName && contents) { const wsFolder = Folders.getWorkspaceFolder(); - const staticFolder = Settings.get(SETTINGS_CONTENT_STATIC_FOLDER); + const staticFolder = Settings.get(SETTING_CONTENT_STATIC_FOLDER); const wsPath = wsFolder ? wsFolder.fsPath : ""; let absFolderPath = join(wsPath, staticFolder || ""); diff --git a/src/helpers/PanelSettings.ts b/src/helpers/PanelSettings.ts index 3d860625..6664baf6 100644 --- a/src/helpers/PanelSettings.ts +++ b/src/helpers/PanelSettings.ts @@ -3,7 +3,7 @@ import { Extension, Settings } from "." import { Dashboard } from "../commands/Dashboard" import { Preview } from "../commands/Preview" import { Template } from "../commands/Template" -import { CONTEXT, DefaultFields, SETTINGS_CONTENT_DRAFT_FIELD, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTINGS_DATA_TYPES, SETTINGS_FRAMEWORK_ID, SETTINGS_FRAMEWORK_START, SETTING_AUTO_UPDATE_DATE, SETTING_COMMA_SEPARATED_FIELDS, SETTING_CUSTOM_SCRIPTS, SETTING_DATE_FORMAT, SETTING_PANEL_FREEFORM, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_SLUG_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TAXONOMY_FIELD_GROUPS, SETTING_TAXONOMY_TAGS } from "../constants" +import { CONTEXT, DefaultFields, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_DATA_TYPES, SETTING_FRAMEWORK_ID, SETTING_FRAMEWORK_START, SETTING_AUTO_UPDATE_DATE, SETTING_COMMA_SEPARATED_FIELDS, SETTING_CUSTOM_SCRIPTS, SETTING_DATE_FORMAT, SETTING_PANEL_FREEFORM, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_SLUG_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TAXONOMY_CUSTOM, SETTING_TAXONOMY_FIELD_GROUPS, SETTING_TAXONOMY_TAGS } from "../constants" import { CustomScript, DataType, DraftField, FieldGroup, PanelSettings as IPanelSettings, ScriptType } from "../models" export class PanelSettings { @@ -33,18 +33,18 @@ export class PanelSettings { isInitialized: await Template.isInitialized(), modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false, writingSettingsEnabled: this.isWritingSettingsEnabled() || false, - fmHighlighting: Settings.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT), + fmHighlighting: Settings.get(SETTING_CONTENT_FRONTMATTER_HIGHLIGHT), preview: Preview.getSettings(), commaSeparatedFields: Settings.get(SETTING_COMMA_SEPARATED_FIELDS) || [], contentTypes: Settings.get(SETTING_TAXONOMY_CONTENT_TYPES) || [], dashboardViewData: Dashboard.viewData, - draftField: Settings.get(SETTINGS_CONTENT_DRAFT_FIELD), + draftField: Settings.get(SETTING_CONTENT_DRAFT_FIELD), isBacker: await Extension.getInstance().getState(CONTEXT.backer, 'global'), - framework: Settings.get(SETTINGS_FRAMEWORK_ID), + framework: Settings.get(SETTING_FRAMEWORK_ID), commands: { - start: Settings.get(SETTINGS_FRAMEWORK_START) + start: Settings.get(SETTING_FRAMEWORK_START) }, - dataTypes: Settings.get(SETTINGS_DATA_TYPES), + dataTypes: Settings.get(SETTING_DATA_TYPES), fieldGroups: Settings.get(SETTING_TAXONOMY_FIELD_GROUPS), } } diff --git a/src/helpers/isValidFile.ts b/src/helpers/isValidFile.ts index 80ebdf4c..a7ccf599 100644 --- a/src/helpers/isValidFile.ts +++ b/src/helpers/isValidFile.ts @@ -1,11 +1,11 @@ import { DEFAULT_FILE_TYPES } from './../constants/DefaultFileTypes'; import { Settings } from "."; -import { SETTINGS_CONTENT_SUPPORTED_FILETYPES } from "../constants"; +import { SETTING_CONTENT_SUPPORTED_FILETYPES } from "../constants"; import { extname } from 'path'; export const isValidFile = (fileName: string) => { - let supportedFiles = Settings.get(SETTINGS_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES; + let supportedFiles = Settings.get(SETTING_CONTENT_SUPPORTED_FILETYPES) || DEFAULT_FILE_TYPES; supportedFiles = supportedFiles.map(f => f.startsWith(`.`) ? f : `.${f}`); // Get the extension of the file path diff --git a/src/listeners/dashboard/PagesListener.ts b/src/listeners/dashboard/PagesListener.ts index eeea4fe8..3aef855d 100644 --- a/src/listeners/dashboard/PagesListener.ts +++ b/src/listeners/dashboard/PagesListener.ts @@ -4,7 +4,7 @@ import { basename, dirname, join } from "path"; import { commands, FileSystemWatcher, RelativePattern, Uri, workspace } from "vscode"; import { Dashboard } from "../../commands/Dashboard"; import { Folders } from "../../commands/Folders"; -import { COMMAND_NAME, DefaultFields, SETTINGS_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD } from "../../constants"; +import { COMMAND_NAME, DefaultFields, SETTING_CONTENT_STATIC_FOLDER, SETTING_DATE_FIELD, SETTING_SEO_DESCRIPTION_FIELD } from "../../constants"; import { DashboardCommand } from "../../dashboardWebView/DashboardCommand"; import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; import { Page } from "../../dashboardWebView/models"; @@ -140,7 +140,7 @@ export class PagesListener extends BaseListener { const wsFolder = Folders.getWorkspaceFolder(); const descriptionField = Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description; const dateField = Settings.get(SETTING_DATE_FIELD) as string || DefaultFields.PublishingDate; - const staticFolder = Settings.get(SETTINGS_CONTENT_STATIC_FOLDER); + const staticFolder = Settings.get(SETTING_CONTENT_STATIC_FOLDER); const page: Page = { ...article.data, diff --git a/src/listeners/dashboard/SettingsListener.ts b/src/listeners/dashboard/SettingsListener.ts index 18be3859..806e4585 100644 --- a/src/listeners/dashboard/SettingsListener.ts +++ b/src/listeners/dashboard/SettingsListener.ts @@ -1,4 +1,4 @@ -import { SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_FRAMEWORK_ID } from "../../constants"; +import { SETTING_CONTENT_STATIC_FOLDER, SETTING_FRAMEWORK_ID } from "../../constants"; import { DashboardCommand } from "../../dashboardWebView/DashboardCommand"; import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; import { DashboardSettings, Settings } from "../../helpers"; @@ -54,15 +54,15 @@ export class SettingsListener extends BaseListener { * @param frameworkId */ private static setFramework(frameworkId: string | null) { - Settings.update(SETTINGS_FRAMEWORK_ID, frameworkId, true); + Settings.update(SETTING_FRAMEWORK_ID, frameworkId, true); if (frameworkId) { const allFrameworks = FrameworkDetector.getAll(); const framework = allFrameworks.find((f: Framework) => f.name === frameworkId); if (framework) { - Settings.update(SETTINGS_CONTENT_STATIC_FOLDER, framework.static, true); + Settings.update(SETTING_CONTENT_STATIC_FOLDER, framework.static, true); } else { - Settings.update(SETTINGS_CONTENT_STATIC_FOLDER, "", true); + Settings.update(SETTING_CONTENT_STATIC_FOLDER, "", true); } } } diff --git a/src/listeners/panel/SettingsListener.ts b/src/listeners/panel/SettingsListener.ts index 9bb9f414..416f0043 100644 --- a/src/listeners/panel/SettingsListener.ts +++ b/src/listeners/panel/SettingsListener.ts @@ -1,5 +1,5 @@ import { commands, workspace } from "vscode"; -import { EXTENSION_BETA_ID, EXTENSION_ID, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTINGS_FRAMEWORK_START, SETTING_AUTO_UPDATE_DATE, SETTING_PREVIEW_HOST } from "../../constants"; +import { EXTENSION_BETA_ID, EXTENSION_ID, SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_FRAMEWORK_START, SETTING_AUTO_UPDATE_DATE, SETTING_PREVIEW_HOST } from "../../constants"; import { Extension, Settings } from "../../helpers"; import { PanelSettings } from "../../helpers/PanelSettings"; import { Command } from "../../panelWebView/Command"; @@ -30,13 +30,13 @@ export class SettingsListener extends BaseListener { this.updateSetting(SETTING_AUTO_UPDATE_DATE, msg.data || false); break; case CommandToCode.updateFmHighlight: - this.updateSetting(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, (msg.data !== null && msg.data !== undefined) ? msg.data : false); + this.updateSetting(SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, (msg.data !== null && msg.data !== undefined) ? msg.data : false); break; case CommandToCode.updatePreviewUrl: this.updateSetting(SETTING_PREVIEW_HOST, msg.data || ""); break; case CommandToCode.updateStartCommand: - this.updateSetting(SETTINGS_FRAMEWORK_START, msg.data || ""); + this.updateSetting(SETTING_FRAMEWORK_START, msg.data || ""); break; } } diff --git a/src/providers/MarkdownFoldingProvider.ts b/src/providers/MarkdownFoldingProvider.ts index fbf4235a..35a33054 100644 --- a/src/providers/MarkdownFoldingProvider.ts +++ b/src/providers/MarkdownFoldingProvider.ts @@ -1,7 +1,7 @@ import { ArticleHelper } from '../helpers'; import { languages, TextEditorDecorationType } from 'vscode'; import { CancellationToken, FoldingContext, FoldingRange, FoldingRangeKind, FoldingRangeProvider, Range, TextDocument, window, Position } from 'vscode'; -import { SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTINGS_CONTENT_SUPPORTED_FILETYPES, SETTING_FRONTMATTER_TYPE } from '../constants'; +import { SETTING_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_CONTENT_SUPPORTED_FILETYPES, SETTING_FRONTMATTER_TYPE } from '../constants'; import { Settings } from '../helpers'; import { FrontMatterDecorationProvider } from './FrontMatterDecorationProvider'; @@ -12,7 +12,7 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider { private static decType: TextEditorDecorationType | null = null; public static register() { - const supportedFiles = Settings.get(SETTINGS_CONTENT_SUPPORTED_FILETYPES); + const supportedFiles = Settings.get(SETTING_CONTENT_SUPPORTED_FILETYPES); languages.registerFoldingRangeProvider({ language: 'markdown', scheme: 'file' }, new MarkdownFoldingProvider()); @@ -41,7 +41,7 @@ export class MarkdownFoldingProvider implements FoldingRangeProvider { const isSupported = ArticleHelper.isMarkdownFile(activeDoc); if (isSupported) { - const fmHighlight = Settings.get(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT); + const fmHighlight = Settings.get(SETTING_CONTENT_FRONTMATTER_HIGHLIGHT); const range = this.getFrontMatterRange(); From 55053acd38c1fd15b9f4c42cabd48a16124de4ac Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 2 Mar 2022 13:27:16 +0100 Subject: [PATCH 02/10] Remove unnecessary activation events --- package.json | 5 ----- 1 file changed, 5 deletions(-) diff --git a/package.json b/package.json index 66d151bc..db0eb1aa 100644 --- a/package.json +++ b/package.json @@ -47,11 +47,6 @@ "activationEvents": [ "workspaceContains:**/.frontmatter", "workspaceContains:**/frontmatter.json", - "onCommand:frontMatter.init", - "onCommand:frontMatter.dashboard", - "onCommand:frontMatter.dashboard.data", - "onCommand:frontMatter.dashboard.media", - "onCommand:workbench.view.extension.frontmatter-explorer", "onView:frontMatter.explorer", "onStartupFinished" ], From 00e590bc6733dd899467e0d849f67e063050c10d Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 2 Mar 2022 16:03:07 +0100 Subject: [PATCH 03/10] Snippet parser implementation --- src/constants/charCode.ts | 439 +++++++ .../components/Media/Media.tsx | 4 +- .../components/SnippetsView/Item.tsx | 60 + .../components/SnippetsView/Snippets.tsx | 33 +- src/helpers/SnippetParser.ts | 1083 +++++++++++++++++ 5 files changed, 1615 insertions(+), 4 deletions(-) create mode 100644 src/constants/charCode.ts create mode 100644 src/dashboardWebView/components/SnippetsView/Item.tsx create mode 100644 src/helpers/SnippetParser.ts diff --git a/src/constants/charCode.ts b/src/constants/charCode.ts new file mode 100644 index 00000000..771b2c03 --- /dev/null +++ b/src/constants/charCode.ts @@ -0,0 +1,439 @@ +/** + * An inlined enum containing useful character codes (to be used with String.charCodeAt). + * Please leave the const keyword such that it gets inlined when compiled to JavaScript! + * + * SOURCE: https://github.com/microsoft/vscode/blob/32b031eeefc4fd27a21659d35070967bfe965bcc/src/vs/base/common/charCode.ts + */ + export const enum CharCode { + Null = 0, + /** + * The `\b` character. + */ + Backspace = 8, + /** + * The `\t` character. + */ + Tab = 9, + /** + * The `\n` character. + */ + LineFeed = 10, + /** + * The `\r` character. + */ + CarriageReturn = 13, + Space = 32, + /** + * The `!` character. + */ + ExclamationMark = 33, + /** + * The `"` character. + */ + DoubleQuote = 34, + /** + * The `#` character. + */ + Hash = 35, + /** + * The `$` character. + */ + DollarSign = 36, + /** + * The `%` character. + */ + PercentSign = 37, + /** + * The `&` character. + */ + Ampersand = 38, + /** + * The `'` character. + */ + SingleQuote = 39, + /** + * The `(` character. + */ + OpenParen = 40, + /** + * The `)` character. + */ + CloseParen = 41, + /** + * The `*` character. + */ + Asterisk = 42, + /** + * The `+` character. + */ + Plus = 43, + /** + * The `,` character. + */ + Comma = 44, + /** + * The `-` character. + */ + Dash = 45, + /** + * The `.` character. + */ + Period = 46, + /** + * The `/` character. + */ + Slash = 47, + + Digit0 = 48, + Digit1 = 49, + Digit2 = 50, + Digit3 = 51, + Digit4 = 52, + Digit5 = 53, + Digit6 = 54, + Digit7 = 55, + Digit8 = 56, + Digit9 = 57, + + /** + * The `:` character. + */ + Colon = 58, + /** + * The `;` character. + */ + Semicolon = 59, + /** + * The `<` character. + */ + LessThan = 60, + /** + * The `=` character. + */ + Equals = 61, + /** + * The `>` character. + */ + GreaterThan = 62, + /** + * The `?` character. + */ + QuestionMark = 63, + /** + * The `@` character. + */ + AtSign = 64, + + A = 65, + B = 66, + C = 67, + D = 68, + E = 69, + F = 70, + G = 71, + H = 72, + I = 73, + J = 74, + K = 75, + L = 76, + M = 77, + N = 78, + O = 79, + P = 80, + Q = 81, + R = 82, + S = 83, + T = 84, + U = 85, + V = 86, + W = 87, + X = 88, + Y = 89, + Z = 90, + + /** + * The `[` character. + */ + OpenSquareBracket = 91, + /** + * The `\` character. + */ + Backslash = 92, + /** + * The `]` character. + */ + CloseSquareBracket = 93, + /** + * The `^` character. + */ + Caret = 94, + /** + * The `_` character. + */ + Underline = 95, + /** + * The ``(`)`` character. + */ + BackTick = 96, + + a = 97, + b = 98, + c = 99, + d = 100, + e = 101, + f = 102, + g = 103, + h = 104, + i = 105, + j = 106, + k = 107, + l = 108, + m = 109, + n = 110, + o = 111, + p = 112, + q = 113, + r = 114, + s = 115, + t = 116, + u = 117, + v = 118, + w = 119, + x = 120, + y = 121, + z = 122, + + /** + * The `{` character. + */ + OpenCurlyBrace = 123, + /** + * The `|` character. + */ + Pipe = 124, + /** + * The `}` character. + */ + CloseCurlyBrace = 125, + /** + * The `~` character. + */ + Tilde = 126, + + U_Combining_Grave_Accent = 0x0300, // U+0300 Combining Grave Accent + U_Combining_Acute_Accent = 0x0301, // U+0301 Combining Acute Accent + U_Combining_Circumflex_Accent = 0x0302, // U+0302 Combining Circumflex Accent + U_Combining_Tilde = 0x0303, // U+0303 Combining Tilde + U_Combining_Macron = 0x0304, // U+0304 Combining Macron + U_Combining_Overline = 0x0305, // U+0305 Combining Overline + U_Combining_Breve = 0x0306, // U+0306 Combining Breve + U_Combining_Dot_Above = 0x0307, // U+0307 Combining Dot Above + U_Combining_Diaeresis = 0x0308, // U+0308 Combining Diaeresis + U_Combining_Hook_Above = 0x0309, // U+0309 Combining Hook Above + U_Combining_Ring_Above = 0x030A, // U+030A Combining Ring Above + U_Combining_Double_Acute_Accent = 0x030B, // U+030B Combining Double Acute Accent + U_Combining_Caron = 0x030C, // U+030C Combining Caron + U_Combining_Vertical_Line_Above = 0x030D, // U+030D Combining Vertical Line Above + U_Combining_Double_Vertical_Line_Above = 0x030E, // U+030E Combining Double Vertical Line Above + U_Combining_Double_Grave_Accent = 0x030F, // U+030F Combining Double Grave Accent + U_Combining_Candrabindu = 0x0310, // U+0310 Combining Candrabindu + U_Combining_Inverted_Breve = 0x0311, // U+0311 Combining Inverted Breve + U_Combining_Turned_Comma_Above = 0x0312, // U+0312 Combining Turned Comma Above + U_Combining_Comma_Above = 0x0313, // U+0313 Combining Comma Above + U_Combining_Reversed_Comma_Above = 0x0314, // U+0314 Combining Reversed Comma Above + U_Combining_Comma_Above_Right = 0x0315, // U+0315 Combining Comma Above Right + U_Combining_Grave_Accent_Below = 0x0316, // U+0316 Combining Grave Accent Below + U_Combining_Acute_Accent_Below = 0x0317, // U+0317 Combining Acute Accent Below + U_Combining_Left_Tack_Below = 0x0318, // U+0318 Combining Left Tack Below + U_Combining_Right_Tack_Below = 0x0319, // U+0319 Combining Right Tack Below + U_Combining_Left_Angle_Above = 0x031A, // U+031A Combining Left Angle Above + U_Combining_Horn = 0x031B, // U+031B Combining Horn + U_Combining_Left_Half_Ring_Below = 0x031C, // U+031C Combining Left Half Ring Below + U_Combining_Up_Tack_Below = 0x031D, // U+031D Combining Up Tack Below + U_Combining_Down_Tack_Below = 0x031E, // U+031E Combining Down Tack Below + U_Combining_Plus_Sign_Below = 0x031F, // U+031F Combining Plus Sign Below + U_Combining_Minus_Sign_Below = 0x0320, // U+0320 Combining Minus Sign Below + U_Combining_Palatalized_Hook_Below = 0x0321, // U+0321 Combining Palatalized Hook Below + U_Combining_Retroflex_Hook_Below = 0x0322, // U+0322 Combining Retroflex Hook Below + U_Combining_Dot_Below = 0x0323, // U+0323 Combining Dot Below + U_Combining_Diaeresis_Below = 0x0324, // U+0324 Combining Diaeresis Below + U_Combining_Ring_Below = 0x0325, // U+0325 Combining Ring Below + U_Combining_Comma_Below = 0x0326, // U+0326 Combining Comma Below + U_Combining_Cedilla = 0x0327, // U+0327 Combining Cedilla + U_Combining_Ogonek = 0x0328, // U+0328 Combining Ogonek + U_Combining_Vertical_Line_Below = 0x0329, // U+0329 Combining Vertical Line Below + U_Combining_Bridge_Below = 0x032A, // U+032A Combining Bridge Below + U_Combining_Inverted_Double_Arch_Below = 0x032B, // U+032B Combining Inverted Double Arch Below + U_Combining_Caron_Below = 0x032C, // U+032C Combining Caron Below + U_Combining_Circumflex_Accent_Below = 0x032D, // U+032D Combining Circumflex Accent Below + U_Combining_Breve_Below = 0x032E, // U+032E Combining Breve Below + U_Combining_Inverted_Breve_Below = 0x032F, // U+032F Combining Inverted Breve Below + U_Combining_Tilde_Below = 0x0330, // U+0330 Combining Tilde Below + U_Combining_Macron_Below = 0x0331, // U+0331 Combining Macron Below + U_Combining_Low_Line = 0x0332, // U+0332 Combining Low Line + U_Combining_Double_Low_Line = 0x0333, // U+0333 Combining Double Low Line + U_Combining_Tilde_Overlay = 0x0334, // U+0334 Combining Tilde Overlay + U_Combining_Short_Stroke_Overlay = 0x0335, // U+0335 Combining Short Stroke Overlay + U_Combining_Long_Stroke_Overlay = 0x0336, // U+0336 Combining Long Stroke Overlay + U_Combining_Short_Solidus_Overlay = 0x0337, // U+0337 Combining Short Solidus Overlay + U_Combining_Long_Solidus_Overlay = 0x0338, // U+0338 Combining Long Solidus Overlay + U_Combining_Right_Half_Ring_Below = 0x0339, // U+0339 Combining Right Half Ring Below + U_Combining_Inverted_Bridge_Below = 0x033A, // U+033A Combining Inverted Bridge Below + U_Combining_Square_Below = 0x033B, // U+033B Combining Square Below + U_Combining_Seagull_Below = 0x033C, // U+033C Combining Seagull Below + U_Combining_X_Above = 0x033D, // U+033D Combining X Above + U_Combining_Vertical_Tilde = 0x033E, // U+033E Combining Vertical Tilde + U_Combining_Double_Overline = 0x033F, // U+033F Combining Double Overline + U_Combining_Grave_Tone_Mark = 0x0340, // U+0340 Combining Grave Tone Mark + U_Combining_Acute_Tone_Mark = 0x0341, // U+0341 Combining Acute Tone Mark + U_Combining_Greek_Perispomeni = 0x0342, // U+0342 Combining Greek Perispomeni + U_Combining_Greek_Koronis = 0x0343, // U+0343 Combining Greek Koronis + U_Combining_Greek_Dialytika_Tonos = 0x0344, // U+0344 Combining Greek Dialytika Tonos + U_Combining_Greek_Ypogegrammeni = 0x0345, // U+0345 Combining Greek Ypogegrammeni + U_Combining_Bridge_Above = 0x0346, // U+0346 Combining Bridge Above + U_Combining_Equals_Sign_Below = 0x0347, // U+0347 Combining Equals Sign Below + U_Combining_Double_Vertical_Line_Below = 0x0348, // U+0348 Combining Double Vertical Line Below + U_Combining_Left_Angle_Below = 0x0349, // U+0349 Combining Left Angle Below + U_Combining_Not_Tilde_Above = 0x034A, // U+034A Combining Not Tilde Above + U_Combining_Homothetic_Above = 0x034B, // U+034B Combining Homothetic Above + U_Combining_Almost_Equal_To_Above = 0x034C, // U+034C Combining Almost Equal To Above + U_Combining_Left_Right_Arrow_Below = 0x034D, // U+034D Combining Left Right Arrow Below + U_Combining_Upwards_Arrow_Below = 0x034E, // U+034E Combining Upwards Arrow Below + U_Combining_Grapheme_Joiner = 0x034F, // U+034F Combining Grapheme Joiner + U_Combining_Right_Arrowhead_Above = 0x0350, // U+0350 Combining Right Arrowhead Above + U_Combining_Left_Half_Ring_Above = 0x0351, // U+0351 Combining Left Half Ring Above + U_Combining_Fermata = 0x0352, // U+0352 Combining Fermata + U_Combining_X_Below = 0x0353, // U+0353 Combining X Below + U_Combining_Left_Arrowhead_Below = 0x0354, // U+0354 Combining Left Arrowhead Below + U_Combining_Right_Arrowhead_Below = 0x0355, // U+0355 Combining Right Arrowhead Below + U_Combining_Right_Arrowhead_And_Up_Arrowhead_Below = 0x0356, // U+0356 Combining Right Arrowhead And Up Arrowhead Below + U_Combining_Right_Half_Ring_Above = 0x0357, // U+0357 Combining Right Half Ring Above + U_Combining_Dot_Above_Right = 0x0358, // U+0358 Combining Dot Above Right + U_Combining_Asterisk_Below = 0x0359, // U+0359 Combining Asterisk Below + U_Combining_Double_Ring_Below = 0x035A, // U+035A Combining Double Ring Below + U_Combining_Zigzag_Above = 0x035B, // U+035B Combining Zigzag Above + U_Combining_Double_Breve_Below = 0x035C, // U+035C Combining Double Breve Below + U_Combining_Double_Breve = 0x035D, // U+035D Combining Double Breve + U_Combining_Double_Macron = 0x035E, // U+035E Combining Double Macron + U_Combining_Double_Macron_Below = 0x035F, // U+035F Combining Double Macron Below + U_Combining_Double_Tilde = 0x0360, // U+0360 Combining Double Tilde + U_Combining_Double_Inverted_Breve = 0x0361, // U+0361 Combining Double Inverted Breve + U_Combining_Double_Rightwards_Arrow_Below = 0x0362, // U+0362 Combining Double Rightwards Arrow Below + U_Combining_Latin_Small_Letter_A = 0x0363, // U+0363 Combining Latin Small Letter A + U_Combining_Latin_Small_Letter_E = 0x0364, // U+0364 Combining Latin Small Letter E + U_Combining_Latin_Small_Letter_I = 0x0365, // U+0365 Combining Latin Small Letter I + U_Combining_Latin_Small_Letter_O = 0x0366, // U+0366 Combining Latin Small Letter O + U_Combining_Latin_Small_Letter_U = 0x0367, // U+0367 Combining Latin Small Letter U + U_Combining_Latin_Small_Letter_C = 0x0368, // U+0368 Combining Latin Small Letter C + U_Combining_Latin_Small_Letter_D = 0x0369, // U+0369 Combining Latin Small Letter D + U_Combining_Latin_Small_Letter_H = 0x036A, // U+036A Combining Latin Small Letter H + U_Combining_Latin_Small_Letter_M = 0x036B, // U+036B Combining Latin Small Letter M + U_Combining_Latin_Small_Letter_R = 0x036C, // U+036C Combining Latin Small Letter R + U_Combining_Latin_Small_Letter_T = 0x036D, // U+036D Combining Latin Small Letter T + U_Combining_Latin_Small_Letter_V = 0x036E, // U+036E Combining Latin Small Letter V + U_Combining_Latin_Small_Letter_X = 0x036F, // U+036F Combining Latin Small Letter X + + /** + * Unicode Character 'LINE SEPARATOR' (U+2028) + * http://www.fileformat.info/info/unicode/char/2028/index.htm + */ + LINE_SEPARATOR = 0x2028, + /** + * Unicode Character 'PARAGRAPH SEPARATOR' (U+2029) + * http://www.fileformat.info/info/unicode/char/2029/index.htm + */ + PARAGRAPH_SEPARATOR = 0x2029, + /** + * Unicode Character 'NEXT LINE' (U+0085) + * http://www.fileformat.info/info/unicode/char/0085/index.htm + */ + NEXT_LINE = 0x0085, + + // http://www.fileformat.info/info/unicode/category/Sk/list.htm + U_CIRCUMFLEX = 0x005E, // U+005E CIRCUMFLEX + U_GRAVE_ACCENT = 0x0060, // U+0060 GRAVE ACCENT + U_DIAERESIS = 0x00A8, // U+00A8 DIAERESIS + U_MACRON = 0x00AF, // U+00AF MACRON + U_ACUTE_ACCENT = 0x00B4, // U+00B4 ACUTE ACCENT + U_CEDILLA = 0x00B8, // U+00B8 CEDILLA + U_MODIFIER_LETTER_LEFT_ARROWHEAD = 0x02C2, // U+02C2 MODIFIER LETTER LEFT ARROWHEAD + U_MODIFIER_LETTER_RIGHT_ARROWHEAD = 0x02C3, // U+02C3 MODIFIER LETTER RIGHT ARROWHEAD + U_MODIFIER_LETTER_UP_ARROWHEAD = 0x02C4, // U+02C4 MODIFIER LETTER UP ARROWHEAD + U_MODIFIER_LETTER_DOWN_ARROWHEAD = 0x02C5, // U+02C5 MODIFIER LETTER DOWN ARROWHEAD + U_MODIFIER_LETTER_CENTRED_RIGHT_HALF_RING = 0x02D2, // U+02D2 MODIFIER LETTER CENTRED RIGHT HALF RING + U_MODIFIER_LETTER_CENTRED_LEFT_HALF_RING = 0x02D3, // U+02D3 MODIFIER LETTER CENTRED LEFT HALF RING + U_MODIFIER_LETTER_UP_TACK = 0x02D4, // U+02D4 MODIFIER LETTER UP TACK + U_MODIFIER_LETTER_DOWN_TACK = 0x02D5, // U+02D5 MODIFIER LETTER DOWN TACK + U_MODIFIER_LETTER_PLUS_SIGN = 0x02D6, // U+02D6 MODIFIER LETTER PLUS SIGN + U_MODIFIER_LETTER_MINUS_SIGN = 0x02D7, // U+02D7 MODIFIER LETTER MINUS SIGN + U_BREVE = 0x02D8, // U+02D8 BREVE + U_DOT_ABOVE = 0x02D9, // U+02D9 DOT ABOVE + U_RING_ABOVE = 0x02DA, // U+02DA RING ABOVE + U_OGONEK = 0x02DB, // U+02DB OGONEK + U_SMALL_TILDE = 0x02DC, // U+02DC SMALL TILDE + U_DOUBLE_ACUTE_ACCENT = 0x02DD, // U+02DD DOUBLE ACUTE ACCENT + U_MODIFIER_LETTER_RHOTIC_HOOK = 0x02DE, // U+02DE MODIFIER LETTER RHOTIC HOOK + U_MODIFIER_LETTER_CROSS_ACCENT = 0x02DF, // U+02DF MODIFIER LETTER CROSS ACCENT + U_MODIFIER_LETTER_EXTRA_HIGH_TONE_BAR = 0x02E5, // U+02E5 MODIFIER LETTER EXTRA-HIGH TONE BAR + U_MODIFIER_LETTER_HIGH_TONE_BAR = 0x02E6, // U+02E6 MODIFIER LETTER HIGH TONE BAR + U_MODIFIER_LETTER_MID_TONE_BAR = 0x02E7, // U+02E7 MODIFIER LETTER MID TONE BAR + U_MODIFIER_LETTER_LOW_TONE_BAR = 0x02E8, // U+02E8 MODIFIER LETTER LOW TONE BAR + U_MODIFIER_LETTER_EXTRA_LOW_TONE_BAR = 0x02E9, // U+02E9 MODIFIER LETTER EXTRA-LOW TONE BAR + U_MODIFIER_LETTER_YIN_DEPARTING_TONE_MARK = 0x02EA, // U+02EA MODIFIER LETTER YIN DEPARTING TONE MARK + U_MODIFIER_LETTER_YANG_DEPARTING_TONE_MARK = 0x02EB, // U+02EB MODIFIER LETTER YANG DEPARTING TONE MARK + U_MODIFIER_LETTER_UNASPIRATED = 0x02ED, // U+02ED MODIFIER LETTER UNASPIRATED + U_MODIFIER_LETTER_LOW_DOWN_ARROWHEAD = 0x02EF, // U+02EF MODIFIER LETTER LOW DOWN ARROWHEAD + U_MODIFIER_LETTER_LOW_UP_ARROWHEAD = 0x02F0, // U+02F0 MODIFIER LETTER LOW UP ARROWHEAD + U_MODIFIER_LETTER_LOW_LEFT_ARROWHEAD = 0x02F1, // U+02F1 MODIFIER LETTER LOW LEFT ARROWHEAD + U_MODIFIER_LETTER_LOW_RIGHT_ARROWHEAD = 0x02F2, // U+02F2 MODIFIER LETTER LOW RIGHT ARROWHEAD + U_MODIFIER_LETTER_LOW_RING = 0x02F3, // U+02F3 MODIFIER LETTER LOW RING + U_MODIFIER_LETTER_MIDDLE_GRAVE_ACCENT = 0x02F4, // U+02F4 MODIFIER LETTER MIDDLE GRAVE ACCENT + U_MODIFIER_LETTER_MIDDLE_DOUBLE_GRAVE_ACCENT = 0x02F5, // U+02F5 MODIFIER LETTER MIDDLE DOUBLE GRAVE ACCENT + U_MODIFIER_LETTER_MIDDLE_DOUBLE_ACUTE_ACCENT = 0x02F6, // U+02F6 MODIFIER LETTER MIDDLE DOUBLE ACUTE ACCENT + U_MODIFIER_LETTER_LOW_TILDE = 0x02F7, // U+02F7 MODIFIER LETTER LOW TILDE + U_MODIFIER_LETTER_RAISED_COLON = 0x02F8, // U+02F8 MODIFIER LETTER RAISED COLON + U_MODIFIER_LETTER_BEGIN_HIGH_TONE = 0x02F9, // U+02F9 MODIFIER LETTER BEGIN HIGH TONE + U_MODIFIER_LETTER_END_HIGH_TONE = 0x02FA, // U+02FA MODIFIER LETTER END HIGH TONE + U_MODIFIER_LETTER_BEGIN_LOW_TONE = 0x02FB, // U+02FB MODIFIER LETTER BEGIN LOW TONE + U_MODIFIER_LETTER_END_LOW_TONE = 0x02FC, // U+02FC MODIFIER LETTER END LOW TONE + U_MODIFIER_LETTER_SHELF = 0x02FD, // U+02FD MODIFIER LETTER SHELF + U_MODIFIER_LETTER_OPEN_SHELF = 0x02FE, // U+02FE MODIFIER LETTER OPEN SHELF + U_MODIFIER_LETTER_LOW_LEFT_ARROW = 0x02FF, // U+02FF MODIFIER LETTER LOW LEFT ARROW + U_GREEK_LOWER_NUMERAL_SIGN = 0x0375, // U+0375 GREEK LOWER NUMERAL SIGN + U_GREEK_TONOS = 0x0384, // U+0384 GREEK TONOS + U_GREEK_DIALYTIKA_TONOS = 0x0385, // U+0385 GREEK DIALYTIKA TONOS + U_GREEK_KORONIS = 0x1FBD, // U+1FBD GREEK KORONIS + U_GREEK_PSILI = 0x1FBF, // U+1FBF GREEK PSILI + U_GREEK_PERISPOMENI = 0x1FC0, // U+1FC0 GREEK PERISPOMENI + U_GREEK_DIALYTIKA_AND_PERISPOMENI = 0x1FC1, // U+1FC1 GREEK DIALYTIKA AND PERISPOMENI + U_GREEK_PSILI_AND_VARIA = 0x1FCD, // U+1FCD GREEK PSILI AND VARIA + U_GREEK_PSILI_AND_OXIA = 0x1FCE, // U+1FCE GREEK PSILI AND OXIA + U_GREEK_PSILI_AND_PERISPOMENI = 0x1FCF, // U+1FCF GREEK PSILI AND PERISPOMENI + U_GREEK_DASIA_AND_VARIA = 0x1FDD, // U+1FDD GREEK DASIA AND VARIA + U_GREEK_DASIA_AND_OXIA = 0x1FDE, // U+1FDE GREEK DASIA AND OXIA + U_GREEK_DASIA_AND_PERISPOMENI = 0x1FDF, // U+1FDF GREEK DASIA AND PERISPOMENI + U_GREEK_DIALYTIKA_AND_VARIA = 0x1FED, // U+1FED GREEK DIALYTIKA AND VARIA + U_GREEK_DIALYTIKA_AND_OXIA = 0x1FEE, // U+1FEE GREEK DIALYTIKA AND OXIA + U_GREEK_VARIA = 0x1FEF, // U+1FEF GREEK VARIA + U_GREEK_OXIA = 0x1FFD, // U+1FFD GREEK OXIA + U_GREEK_DASIA = 0x1FFE, // U+1FFE GREEK DASIA + + U_IDEOGRAPHIC_FULL_STOP = 0x3002, // U+3002 IDEOGRAPHIC FULL STOP + U_LEFT_CORNER_BRACKET = 0x300C, // U+300C LEFT CORNER BRACKET + U_RIGHT_CORNER_BRACKET = 0x300D, // U+300D RIGHT CORNER BRACKET + U_LEFT_BLACK_LENTICULAR_BRACKET = 0x3010, // U+3010 LEFT BLACK LENTICULAR BRACKET + U_RIGHT_BLACK_LENTICULAR_BRACKET = 0x3011, // U+3011 RIGHT BLACK LENTICULAR BRACKET + + + U_OVERLINE = 0x203E, // Unicode Character 'OVERLINE' + + /** + * UTF-8 BOM + * Unicode Character 'ZERO WIDTH NO-BREAK SPACE' (U+FEFF) + * http://www.fileformat.info/info/unicode/char/feff/index.htm + */ + UTF8_BOM = 65279, + + U_FULLWIDTH_SEMICOLON = 0xFF1B, // U+FF1B FULLWIDTH SEMICOLON + U_FULLWIDTH_COMMA = 0xFF0C, // U+FF0C FULLWIDTH COMMA +} \ No newline at end of file diff --git a/src/dashboardWebView/components/Media/Media.tsx b/src/dashboardWebView/components/Media/Media.tsx index a2ac3964..d00769cc 100644 --- a/src/dashboardWebView/components/Media/Media.tsx +++ b/src/dashboardWebView/components/Media/Media.tsx @@ -61,8 +61,8 @@ export const Media: React.FunctionComponent = (props: React.PropsWi { viewData?.data?.filePath && (
    -

    Select the image you want to use for your article.

    -

    You can also drag and drop images from your desktop and select that once uploaded.

    +

    Select the media file to add to your content.

    +

    You can also drag and drop images from your desktop and select them once uploaded.

    ) } diff --git a/src/dashboardWebView/components/SnippetsView/Item.tsx b/src/dashboardWebView/components/SnippetsView/Item.tsx new file mode 100644 index 00000000..3d1fbf59 --- /dev/null +++ b/src/dashboardWebView/components/SnippetsView/Item.tsx @@ -0,0 +1,60 @@ +import * as React from 'react'; +import { useEffect } from 'react'; +import { useRecoilValue } from 'recoil'; +import { Choice, Scanner, SnippetParser, TokenType, Variable, VariableResolver } from '../../../helpers/SnippetParser'; +import { ViewDataSelector } from '../../state'; + +export interface IItemProps { + snippet: any; +} + +export const Item: React.FunctionComponent = ({ snippet }: React.PropsWithChildren) => { + const viewData = useRecoilValue(ViewDataSelector); + + // Todo: On add, show dialog to insert the placeholders and content + + useEffect(() => { + const snippetParser = new SnippetParser(); + const body = snippet.body.join(`\n`); + + const parsed = snippetParser.parse(body); + const placeholders = parsed.placeholderInfo.all; + + for (const placeholder of placeholders) { + const tmString = placeholder.toTextmateString(); + + for (const child of placeholder.children as any[]) { + if (child instanceof Choice) { + console.log(child.options) + } else { + console.log(tmString, child.value); + } + } + } + + const resolver: VariableResolver = { + resolve: (variable: Variable): string | undefined => { + console.log(`variable`, variable); + return undefined; + } + }; + + parsed.resolveVariables(resolver); + }, []); + + return ( +
  • +
    {snippet.title}
    +

    {snippet.description}

    +
    + { + viewData?.data?.filePath ? ( +
    Add
    + ) : ( +
    Edit
    + ) + } +
    +
  • + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/SnippetsView/Snippets.tsx b/src/dashboardWebView/components/SnippetsView/Snippets.tsx index 5c1e43f2..1534a7c1 100644 --- a/src/dashboardWebView/components/SnippetsView/Snippets.tsx +++ b/src/dashboardWebView/components/SnippetsView/Snippets.tsx @@ -1,17 +1,46 @@ +import { CodeIcon } from '@heroicons/react/outline'; import * as React from 'react'; import { useRecoilValue } from 'recoil'; -import { SettingsSelector } from '../../state'; +import { AddIcon } from '../../../panelWebView/components/Icons/AddIcon'; +import { SettingsSelector, ViewDataSelector } from '../../state'; import { PageLayout } from '../Layout/PageLayout'; +import { Item } from './Item'; export interface ISnippetsProps {} export const Snippets: React.FunctionComponent = (props: React.PropsWithChildren) => { const settings = useRecoilValue(SettingsSelector); + const viewData = useRecoilValue(ViewDataSelector); + + const snippets = settings?.snippets || []; return ( { - JSON.stringify(settings?.snippets, null, 2) + viewData?.data?.filePath && ( +
    +

    Select the snippet to add to your content.

    +
    + ) + } + + { + snippets && snippets.length > 0 ? ( +
      + { + snippets.map((snippet: any, index: number) => ( + + )) + } +
    + ) : ( +
    +
    + +

    No snippets found

    +
    +
    + ) }
    ); diff --git a/src/helpers/SnippetParser.ts b/src/helpers/SnippetParser.ts new file mode 100644 index 00000000..81700d58 --- /dev/null +++ b/src/helpers/SnippetParser.ts @@ -0,0 +1,1083 @@ +/*--------------------------------------------------------------------------------------------- + * Copyright (c) Microsoft Corporation. All rights reserved. + * Licensed under the MIT License. See License.txt in the project root for license information. + *--------------------------------------------------------------------------------------------*/ + +// SOURCE: https://github.com/microsoft/vscode/tree/main/src/vs/editor/contrib/snippet/browser + +import { CharCode } from '../constants/charCode'; + +export const enum TokenType { + Dollar, + Colon, + Comma, + CurlyOpen, + CurlyClose, + Backslash, + Forwardslash, + Pipe, + Int, + VariableName, + Format, + Plus, + Dash, + QuestionMark, + EOF +} + +export interface Token { + type: TokenType; + pos: number; + len: number; +} + + +export class Scanner { + + private static _table: { [ch: number]: TokenType } = { + [CharCode.DollarSign]: TokenType.Dollar, + [CharCode.Colon]: TokenType.Colon, + [CharCode.Comma]: TokenType.Comma, + [CharCode.OpenCurlyBrace]: TokenType.CurlyOpen, + [CharCode.CloseCurlyBrace]: TokenType.CurlyClose, + [CharCode.Backslash]: TokenType.Backslash, + [CharCode.Slash]: TokenType.Forwardslash, + [CharCode.Pipe]: TokenType.Pipe, + [CharCode.Plus]: TokenType.Plus, + [CharCode.Dash]: TokenType.Dash, + [CharCode.QuestionMark]: TokenType.QuestionMark, + }; + + static isDigitCharacter(ch: number): boolean { + return ch >= CharCode.Digit0 && ch <= CharCode.Digit9; + } + + static isVariableCharacter(ch: number): boolean { + return ch === CharCode.Underline + || (ch >= CharCode.a && ch <= CharCode.z) + || (ch >= CharCode.A && ch <= CharCode.Z); + } + + value: string = ''; + pos: number = 0; + + text(value: string) { + this.value = value; + this.pos = 0; + } + + tokenText(token: Token): string { + return this.value.substr(token.pos, token.len); + } + + next(): Token { + + if (this.pos >= this.value.length) { + return { type: TokenType.EOF, pos: this.pos, len: 0 }; + } + + let pos = this.pos; + let len = 0; + let ch = this.value.charCodeAt(pos); + let type: TokenType; + + // static types + type = Scanner._table[ch]; + if (typeof type === 'number') { + this.pos += 1; + return { type, pos, len: 1 }; + } + + // number + if (Scanner.isDigitCharacter(ch)) { + type = TokenType.Int; + do { + len += 1; + ch = this.value.charCodeAt(pos + len); + } while (Scanner.isDigitCharacter(ch)); + + this.pos += len; + return { type, pos, len }; + } + + // variable name + if (Scanner.isVariableCharacter(ch)) { + type = TokenType.VariableName; + do { + ch = this.value.charCodeAt(pos + (++len)); + } while (Scanner.isVariableCharacter(ch) || Scanner.isDigitCharacter(ch)); + + this.pos += len; + return { type, pos, len }; + } + + + // format + type = TokenType.Format; + do { + len += 1; + ch = this.value.charCodeAt(pos + len); + } while ( + !isNaN(ch) + && typeof Scanner._table[ch] === 'undefined' // not static token + && !Scanner.isDigitCharacter(ch) // not number + && !Scanner.isVariableCharacter(ch) // not variable + ); + + this.pos += len; + return { type, pos, len }; + } +} + +export abstract class Marker { + + readonly _markerBrand: any; + + public parent!: Marker; + protected _children: Marker[] = []; + + appendChild(child: Marker): this { + if (child instanceof Text && this._children[this._children.length - 1] instanceof Text) { + // this and previous child are text -> merge them + (this._children[this._children.length - 1]).value += child.value; + } else { + // normal adoption of child + child.parent = this; + this._children.push(child); + } + return this; + } + + replace(child: Marker, others: Marker[]): void { + const { parent } = child; + const idx = parent.children.indexOf(child); + const newChildren = parent.children.slice(0); + newChildren.splice(idx, 1, ...others); + parent._children = newChildren; + + (function _fixParent(children: Marker[], parent: Marker) { + for (const child of children) { + child.parent = parent; + _fixParent(child.children, child); + } + })(others, parent); + } + + get children(): Marker[] { + return this._children; + } + + get snippet(): TextmateSnippet | undefined { + let candidate: Marker = this; + while (true) { + if (!candidate) { + return undefined; + } + if (candidate instanceof TextmateSnippet) { + return candidate; + } + candidate = candidate.parent; + } + } + + toString(): string { + return this.children.reduce((prev, cur) => prev + cur.toString(), ''); + } + + abstract toTextmateString(): string; + + len(): number { + return 0; + } + + abstract clone(): Marker; +} + +export class Text extends Marker { + + static escape(value: string): string { + return value.replace(/\$|}|\\/g, '\\$&'); + } + + constructor(public value: string) { + super(); + } + override toString() { + return this.value; + } + toTextmateString(): string { + return Text.escape(this.value); + } + override len(): number { + return this.value.length; + } + clone(): Text { + return new Text(this.value); + } +} + +export abstract class TransformableMarker extends Marker { + public transform?: Transform; +} + +export class Placeholder extends TransformableMarker { + static compareByIndex(a: Placeholder, b: Placeholder): number { + if (a.index === b.index) { + return 0; + } else if (a.isFinalTabstop) { + return 1; + } else if (b.isFinalTabstop) { + return -1; + } else if (a.index < b.index) { + return -1; + } else if (a.index > b.index) { + return 1; + } else { + return 0; + } + } + + constructor(public index: number) { + super(); + } + + get isFinalTabstop() { + return this.index === 0; + } + + get choice(): Choice | undefined { + return this._children.length === 1 && this._children[0] instanceof Choice + ? this._children[0] as Choice + : undefined; + } + + toTextmateString(): string { + let transformString = ''; + if (this.transform) { + transformString = this.transform.toTextmateString(); + } + if (this.children.length === 0 && !this.transform) { + return `\$${this.index}`; + } else if (this.children.length === 0) { + return `\${${this.index}${transformString}}`; + } else if (this.choice) { + return `\${${this.index}|${this.choice.toTextmateString()}|${transformString}}`; + } else { + return `\${${this.index}:${this.children.map(child => child.toTextmateString()).join('')}${transformString}}`; + } + } + + clone(): Placeholder { + let ret = new Placeholder(this.index); + if (this.transform) { + ret.transform = this.transform.clone(); + } + ret._children = this.children.map(child => child.clone()); + return ret; + } +} + +export class Choice extends Marker { + + readonly options: Text[] = []; + + override appendChild(marker: Marker): this { + if (marker instanceof Text) { + marker.parent = this; + this.options.push(marker); + } + return this; + } + + override toString() { + return this.options[0].value; + } + + toTextmateString(): string { + return this.options + .map(option => option.value.replace(/\||,/g, '\\$&')) + .join(','); + } + + override len(): number { + return this.options[0].len(); + } + + clone(): Choice { + let ret = new Choice(); + this.options.forEach(ret.appendChild, ret); + return ret; + } +} + +export class Transform extends Marker { + + regexp: RegExp = new RegExp(''); + + resolve(value: string): string { + const _this = this; + let didMatch = false; + let ret = value.replace(this.regexp, function () { + didMatch = true; + return _this._replace(Array.prototype.slice.call(arguments, 0, -2)); + }); + // when the regex didn't match and when the transform has + // else branches, then run those + if (!didMatch && this._children.some(child => child instanceof FormatString && Boolean(child.elseValue))) { + ret = this._replace([]); + } + return ret; + } + + private _replace(groups: string[]): string { + let ret = ''; + for (const marker of this._children) { + if (marker instanceof FormatString) { + let value = groups[marker.index] || ''; + value = marker.resolve(value); + ret += value; + } else { + ret += marker.toString(); + } + } + return ret; + } + + override toString(): string { + return ''; + } + + toTextmateString(): string { + return `/${this.regexp.source}/${this.children.map(c => c.toTextmateString())}/${(this.regexp.ignoreCase ? 'i' : '') + (this.regexp.global ? 'g' : '')}`; + } + + clone(): Transform { + let ret = new Transform(); + ret.regexp = new RegExp(this.regexp.source, '' + (this.regexp.ignoreCase ? 'i' : '') + (this.regexp.global ? 'g' : '')); + ret._children = this.children.map(child => child.clone()); + return ret; + } + +} + +export class FormatString extends Marker { + + constructor( + readonly index: number, + readonly shorthandName?: string, + readonly ifValue?: string, + readonly elseValue?: string, + ) { + super(); + } + + resolve(value?: string): string { + if (this.shorthandName === 'upcase') { + return !value ? '' : value.toLocaleUpperCase(); + } else if (this.shorthandName === 'downcase') { + return !value ? '' : value.toLocaleLowerCase(); + } else if (this.shorthandName === 'capitalize') { + return !value ? '' : (value[0].toLocaleUpperCase() + value.substr(1)); + } else if (this.shorthandName === 'pascalcase') { + return !value ? '' : this._toPascalCase(value); + } else if (this.shorthandName === 'camelcase') { + return !value ? '' : this._toCamelCase(value); + } else if (Boolean(value) && typeof this.ifValue === 'string') { + return this.ifValue; + } else if (!Boolean(value) && typeof this.elseValue === 'string') { + return this.elseValue; + } else { + return value || ''; + } + } + + private _toPascalCase(value: string): string { + const match = value.match(/[a-z0-9]+/gi); + if (!match) { + return value; + } + return match.map(word => { + return word.charAt(0).toUpperCase() + + word.substr(1).toLowerCase(); + }) + .join(''); + } + + private _toCamelCase(value: string): string { + const match = value.match(/[a-z0-9]+/gi); + if (!match) { + return value; + } + return match.map((word, index) => { + if (index === 0) { + return word.toLowerCase(); + } else { + return word.charAt(0).toUpperCase() + + word.substr(1).toLowerCase(); + } + }) + .join(''); + } + + toTextmateString(): string { + let value = '${'; + value += this.index; + if (this.shorthandName) { + value += `:/${this.shorthandName}`; + + } else if (this.ifValue && this.elseValue) { + value += `:?${this.ifValue}:${this.elseValue}`; + } else if (this.ifValue) { + value += `:+${this.ifValue}`; + } else if (this.elseValue) { + value += `:-${this.elseValue}`; + } + value += '}'; + return value; + } + + clone(): FormatString { + let ret = new FormatString(this.index, this.shorthandName, this.ifValue, this.elseValue); + return ret; + } +} + +export class Variable extends TransformableMarker { + + constructor(public name: string) { + super(); + } + + resolve(resolver: VariableResolver): boolean { + let value = resolver.resolve(this); + if (this.transform) { + value = this.transform.resolve(value || ''); + } + if (value !== undefined) { + this._children = [new Text(value)]; + return true; + } + return false; + } + + toTextmateString(): string { + let transformString = ''; + if (this.transform) { + transformString = this.transform.toTextmateString(); + } + if (this.children.length === 0) { + return `\${${this.name}${transformString}}`; + } else { + return `\${${this.name}:${this.children.map(child => child.toTextmateString()).join('')}${transformString}}`; + } + } + + clone(): Variable { + const ret = new Variable(this.name); + if (this.transform) { + ret.transform = this.transform.clone(); + } + ret._children = this.children.map(child => child.clone()); + return ret; + } +} + +export interface VariableResolver { + resolve(variable: Variable): string | undefined; +} + +function walk(marker: Marker[], visitor: (marker: Marker) => boolean): void { + const stack = [...marker]; + while (stack.length > 0) { + const marker = stack.shift()!; + const recurse = visitor(marker); + if (!recurse) { + break; + } + stack.unshift(...marker.children); + } +} + +export class TextmateSnippet extends Marker { + + private _placeholders?: { all: Placeholder[]; last?: Placeholder }; + + get placeholderInfo() { + if (!this._placeholders) { + // fill in placeholders + let all: Placeholder[] = []; + let last: Placeholder | undefined; + this.walk(function (candidate) { + if (candidate instanceof Placeholder) { + all.push(candidate); + last = !last || last.index < candidate.index ? candidate : last; + } + return true; + }); + this._placeholders = { all, last }; + } + return this._placeholders; + } + + get placeholders(): Placeholder[] { + const { all } = this.placeholderInfo; + return all; + } + + offset(marker: Marker): number { + let pos = 0; + let found = false; + this.walk(candidate => { + if (candidate === marker) { + found = true; + return false; + } + pos += candidate.len(); + return true; + }); + + if (!found) { + return -1; + } + return pos; + } + + fullLen(marker: Marker): number { + let ret = 0; + walk([marker], marker => { + ret += marker.len(); + return true; + }); + return ret; + } + + enclosingPlaceholders(placeholder: Placeholder): Placeholder[] { + let ret: Placeholder[] = []; + let { parent } = placeholder; + while (parent) { + if (parent instanceof Placeholder) { + ret.push(parent); + } + parent = parent.parent; + } + return ret; + } + + resolveVariables(resolver: VariableResolver): this { + this.walk(candidate => { + if (candidate instanceof Variable) { + if (candidate.resolve(resolver)) { + this._placeholders = undefined; + } + } + return true; + }); + return this; + } + + override appendChild(child: Marker) { + this._placeholders = undefined; + return super.appendChild(child); + } + + override replace(child: Marker, others: Marker[]): void { + this._placeholders = undefined; + return super.replace(child, others); + } + + toTextmateString(): string { + return this.children.reduce((prev, cur) => prev + cur.toTextmateString(), ''); + } + + clone(): TextmateSnippet { + let ret = new TextmateSnippet(); + this._children = this.children.map(child => child.clone()); + return ret; + } + + walk(visitor: (marker: Marker) => boolean): void { + walk(this.children, visitor); + } +} + +export class SnippetParser { + + static escape(value: string): string { + return value.replace(/\$|}|\\/g, '\\$&'); + } + + static guessNeedsClipboard(template: string): boolean { + return /\${?CLIPBOARD/.test(template); + } + + private _scanner: Scanner = new Scanner(); + private _token: Token = { type: TokenType.EOF, pos: 0, len: 0 }; + + text(value: string): string { + return this.parse(value).toString(); + } + + parse(value: string, insertFinalTabstop?: boolean, enforceFinalTabstop?: boolean): TextmateSnippet { + + this._scanner.text(value); + this._token = this._scanner.next(); + + const snippet = new TextmateSnippet(); + while (this._parse(snippet)) { + // nothing + } + + // fill in values for placeholders. the first placeholder of an index + // that has a value defines the value for all placeholders with that index + const placeholderDefaultValues = new Map(); + const incompletePlaceholders: Placeholder[] = []; + let placeholderCount = 0; + snippet.walk(marker => { + if (marker instanceof Placeholder) { + placeholderCount += 1; + if (marker.isFinalTabstop) { + placeholderDefaultValues.set(0, undefined); + } else if (!placeholderDefaultValues.has(marker.index) && marker.children.length > 0) { + placeholderDefaultValues.set(marker.index, marker.children); + } else { + incompletePlaceholders.push(marker); + } + } + return true; + }); + for (const placeholder of incompletePlaceholders) { + const defaultValues = placeholderDefaultValues.get(placeholder.index); + if (defaultValues) { + const clone = new Placeholder(placeholder.index); + clone.transform = placeholder.transform; + for (const child of defaultValues) { + clone.appendChild(child.clone()); + } + snippet.replace(placeholder, [clone]); + } + } + + if (!enforceFinalTabstop) { + enforceFinalTabstop = placeholderCount > 0 && insertFinalTabstop; + } + + if (!placeholderDefaultValues.has(0) && enforceFinalTabstop) { + // the snippet uses placeholders but has no + // final tabstop defined -> insert at the end + snippet.appendChild(new Placeholder(0)); + } + + return snippet; + } + + private _accept(type?: TokenType): boolean; + private _accept(type: TokenType | undefined, value: true): string; + private _accept(type: TokenType, value?: boolean): boolean | string { + if (type === undefined || this._token.type === type) { + let ret = !value ? true : this._scanner.tokenText(this._token); + this._token = this._scanner.next(); + return ret; + } + return false; + } + + private _backTo(token: Token): false { + this._scanner.pos = token.pos + token.len; + this._token = token; + return false; + } + + private _until(type: TokenType): false | string { + const start = this._token; + while (this._token.type !== type) { + if (this._token.type === TokenType.EOF) { + return false; + } else if (this._token.type === TokenType.Backslash) { + const nextToken = this._scanner.next(); + if (nextToken.type !== TokenType.Dollar + && nextToken.type !== TokenType.CurlyClose + && nextToken.type !== TokenType.Backslash) { + return false; + } + } + this._token = this._scanner.next(); + } + const value = this._scanner.value.substring(start.pos, this._token.pos).replace(/\\(\$|}|\\)/g, '$1'); + this._token = this._scanner.next(); + return value; + } + + private _parse(marker: Marker): boolean { + return this._parseEscaped(marker) + || this._parseTabstopOrVariableName(marker) + || this._parseComplexPlaceholder(marker) + || this._parseComplexVariable(marker) + || this._parseAnything(marker); + } + + // \$, \\, \} -> just text + private _parseEscaped(marker: Marker): boolean { + let value: string; + if (value = this._accept(TokenType.Backslash, true)) { + // saw a backslash, append escaped token or that backslash + value = this._accept(TokenType.Dollar, true) + || this._accept(TokenType.CurlyClose, true) + || this._accept(TokenType.Backslash, true) + || value; + + marker.appendChild(new Text(value)); + return true; + } + return false; + } + + // $foo -> variable, $1 -> tabstop + private _parseTabstopOrVariableName(parent: Marker): boolean { + let value: string; + const token = this._token; + const match = this._accept(TokenType.Dollar) + && (value = this._accept(TokenType.VariableName, true) || this._accept(TokenType.Int, true)); + + if (!match) { + return this._backTo(token); + } + + parent.appendChild(/^\d+$/.test(value!) + ? new Placeholder(Number(value!)) + : new Variable(value!) + ); + return true; + } + + // ${1:}, ${1} -> placeholder + private _parseComplexPlaceholder(parent: Marker): boolean { + let index: string; + const token = this._token; + const match = this._accept(TokenType.Dollar) + && this._accept(TokenType.CurlyOpen) + && (index = this._accept(TokenType.Int, true)); + + if (!match) { + return this._backTo(token); + } + + const placeholder = new Placeholder(Number(index!)); + + if (this._accept(TokenType.Colon)) { + // ${1:} + while (true) { + + // ...} -> done + if (this._accept(TokenType.CurlyClose)) { + parent.appendChild(placeholder); + return true; + } + + if (this._parse(placeholder)) { + continue; + } + + // fallback + parent.appendChild(new Text('${' + index! + ':')); + placeholder.children.forEach(parent.appendChild, parent); + return true; + } + } else if (placeholder.index > 0 && this._accept(TokenType.Pipe)) { + // ${1|one,two,three|} + const choice = new Choice(); + + while (true) { + if (this._parseChoiceElement(choice)) { + + if (this._accept(TokenType.Comma)) { + // opt, -> more + continue; + } + + if (this._accept(TokenType.Pipe)) { + placeholder.appendChild(choice); + if (this._accept(TokenType.CurlyClose)) { + // ..|} -> done + parent.appendChild(placeholder); + return true; + } + } + } + + this._backTo(token); + return false; + } + + } else if (this._accept(TokenType.Forwardslash)) { + // ${1///} + if (this._parseTransform(placeholder)) { + parent.appendChild(placeholder); + return true; + } + + this._backTo(token); + return false; + + } else if (this._accept(TokenType.CurlyClose)) { + // ${1} + parent.appendChild(placeholder); + return true; + + } else { + // ${1 <- missing curly or colon + return this._backTo(token); + } + } + + private _parseChoiceElement(parent: Choice): boolean { + const token = this._token; + const values: string[] = []; + + while (true) { + if (this._token.type === TokenType.Comma || this._token.type === TokenType.Pipe) { + break; + } + let value: string; + if (value = this._accept(TokenType.Backslash, true)) { + // \, \|, or \\ + value = this._accept(TokenType.Comma, true) + || this._accept(TokenType.Pipe, true) + || this._accept(TokenType.Backslash, true) + || value; + } else { + value = this._accept(undefined, true); + } + if (!value) { + // EOF + this._backTo(token); + return false; + } + values.push(value); + } + + if (values.length === 0) { + this._backTo(token); + return false; + } + + parent.appendChild(new Text(values.join(''))); + return true; + } + + // ${foo:}, ${foo} -> variable + private _parseComplexVariable(parent: Marker): boolean { + let name: string; + const token = this._token; + const match = this._accept(TokenType.Dollar) + && this._accept(TokenType.CurlyOpen) + && (name = this._accept(TokenType.VariableName, true)); + + if (!match) { + return this._backTo(token); + } + + const variable = new Variable(name!); + + if (this._accept(TokenType.Colon)) { + // ${foo:} + while (true) { + + // ...} -> done + if (this._accept(TokenType.CurlyClose)) { + parent.appendChild(variable); + return true; + } + + if (this._parse(variable)) { + continue; + } + + // fallback + parent.appendChild(new Text('${' + name! + ':')); + variable.children.forEach(parent.appendChild, parent); + return true; + } + + } else if (this._accept(TokenType.Forwardslash)) { + // ${foo///} + if (this._parseTransform(variable)) { + parent.appendChild(variable); + return true; + } + + this._backTo(token); + return false; + + } else if (this._accept(TokenType.CurlyClose)) { + // ${foo} + parent.appendChild(variable); + return true; + + } else { + // ${foo <- missing curly or colon + return this._backTo(token); + } + } + + private _parseTransform(parent: TransformableMarker): boolean { + // ...//} + + let transform = new Transform(); + let regexValue = ''; + let regexOptions = ''; + + // (1) /regex + while (true) { + if (this._accept(TokenType.Forwardslash)) { + break; + } + + let escaped: string; + if (escaped = this._accept(TokenType.Backslash, true)) { + escaped = this._accept(TokenType.Forwardslash, true) || escaped; + regexValue += escaped; + continue; + } + + if (this._token.type !== TokenType.EOF) { + regexValue += this._accept(undefined, true); + continue; + } + return false; + } + + // (2) /format + while (true) { + if (this._accept(TokenType.Forwardslash)) { + break; + } + + let escaped: string; + if (escaped = this._accept(TokenType.Backslash, true)) { + escaped = this._accept(TokenType.Backslash, true) || this._accept(TokenType.Forwardslash, true) || escaped; + transform.appendChild(new Text(escaped)); + continue; + } + + if (this._parseFormatString(transform) || this._parseAnything(transform)) { + continue; + } + return false; + } + + // (3) /option + while (true) { + if (this._accept(TokenType.CurlyClose)) { + break; + } + if (this._token.type !== TokenType.EOF) { + regexOptions += this._accept(undefined, true); + continue; + } + return false; + } + + try { + transform.regexp = new RegExp(regexValue, regexOptions); + } catch (e) { + // invalid regexp + return false; + } + + parent.transform = transform; + return true; + } + + private _parseFormatString(parent: Transform): boolean { + + const token = this._token; + if (!this._accept(TokenType.Dollar)) { + return false; + } + + let complex = false; + if (this._accept(TokenType.CurlyOpen)) { + complex = true; + } + + let index = this._accept(TokenType.Int, true); + + if (!index) { + this._backTo(token); + return false; + + } else if (!complex) { + // $1 + parent.appendChild(new FormatString(Number(index))); + return true; + + } else if (this._accept(TokenType.CurlyClose)) { + // ${1} + parent.appendChild(new FormatString(Number(index))); + return true; + + } else if (!this._accept(TokenType.Colon)) { + this._backTo(token); + return false; + } + + if (this._accept(TokenType.Forwardslash)) { + // ${1:/upcase} + let shorthand = this._accept(TokenType.VariableName, true); + if (!shorthand || !this._accept(TokenType.CurlyClose)) { + this._backTo(token); + return false; + } else { + parent.appendChild(new FormatString(Number(index), shorthand)); + return true; + } + + } else if (this._accept(TokenType.Plus)) { + // ${1:+} + let ifValue = this._until(TokenType.CurlyClose); + if (ifValue) { + parent.appendChild(new FormatString(Number(index), undefined, ifValue, undefined)); + return true; + } + + } else if (this._accept(TokenType.Dash)) { + // ${2:-} + let elseValue = this._until(TokenType.CurlyClose); + if (elseValue) { + parent.appendChild(new FormatString(Number(index), undefined, undefined, elseValue)); + return true; + } + + } else if (this._accept(TokenType.QuestionMark)) { + // ${2:?:} + let ifValue = this._until(TokenType.Colon); + if (ifValue) { + let elseValue = this._until(TokenType.CurlyClose); + if (elseValue) { + parent.appendChild(new FormatString(Number(index), undefined, ifValue, elseValue)); + return true; + } + } + + } else { + // ${1:} + let elseValue = this._until(TokenType.CurlyClose); + if (elseValue) { + parent.appendChild(new FormatString(Number(index), undefined, undefined, elseValue)); + return true; + } + } + + this._backTo(token); + return false; + } + + private _parseAnything(marker: Marker): boolean { + if (this._token.type !== TokenType.EOF) { + marker.appendChild(new Text(this._scanner.tokenText(this._token))); + this._accept(undefined); + return true; + } + return false; + } +} \ No newline at end of file From 48ac869e400d419913056e102739486363fe5924 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 2 Mar 2022 17:03:29 +0100 Subject: [PATCH 04/10] Insert snippet into the content --- src/commands/Dashboard.ts | 3 +- src/dashboardWebView/DashboardMessage.ts | 1 + .../components/SnippetsView/Item.tsx | 11 ++++- src/listeners/dashboard/SnippetListener.ts | 45 +++++++++++++++++++ src/listeners/dashboard/index.ts | 2 + 5 files changed, 60 insertions(+), 2 deletions(-) create mode 100644 src/listeners/dashboard/SnippetListener.ts diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index 36e4ae9c..66cad2f1 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -8,7 +8,7 @@ import { WebviewHelper } from '@estruyf/vscode'; import { DashboardData } from '../models/DashboardData'; import { ExplorerView } from '../explorerView/ExplorerView'; import { MediaLibrary } from '../helpers/MediaLibrary'; -import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener } from '../listeners/dashboard'; +import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener, SnippetListener } from '../listeners/dashboard'; import { MediaListener as PanelMediaListener } from '../listeners/panel' export class Dashboard { @@ -143,6 +143,7 @@ export class Dashboard { SettingsListener.process(msg); DataListener.process(msg); TelemetryListener.process(msg); + SnippetListener.process(msg); }); } diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index c64974a0..ad5ccb61 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -25,4 +25,5 @@ export enum DashboardMessage { getDataEntries = 'getDataEntries', putDataEntries = 'putDataEntries', sendTelemetry = 'sendTelemetry', + insertSnippet = 'insertSnippet', } \ No newline at end of file diff --git a/src/dashboardWebView/components/SnippetsView/Item.tsx b/src/dashboardWebView/components/SnippetsView/Item.tsx index 3d1fbf59..5ae1906e 100644 --- a/src/dashboardWebView/components/SnippetsView/Item.tsx +++ b/src/dashboardWebView/components/SnippetsView/Item.tsx @@ -1,7 +1,9 @@ +import { Messenger } from '@estruyf/vscode/dist/client'; import * as React from 'react'; import { useEffect } from 'react'; import { useRecoilValue } from 'recoil'; import { Choice, Scanner, SnippetParser, TokenType, Variable, VariableResolver } from '../../../helpers/SnippetParser'; +import { DashboardMessage } from '../../DashboardMessage'; import { ViewDataSelector } from '../../state'; export interface IItemProps { @@ -13,6 +15,13 @@ export const Item: React.FunctionComponent = ({ snippet }: React.Pro // Todo: On add, show dialog to insert the placeholders and content + const insertToArticle = () => { + Messenger.send(DashboardMessage.insertSnippet, { + file: viewData?.data?.filePath, + snippet: snippet.body.join(`\n`) + }); + }; + useEffect(() => { const snippetParser = new SnippetParser(); const body = snippet.body.join(`\n`); @@ -49,7 +58,7 @@ export const Item: React.FunctionComponent = ({ snippet }: React.Pro
    { viewData?.data?.filePath ? ( -
    Add
    + ) : (
    Edit
    ) diff --git a/src/listeners/dashboard/SnippetListener.ts b/src/listeners/dashboard/SnippetListener.ts new file mode 100644 index 00000000..16e7ad69 --- /dev/null +++ b/src/listeners/dashboard/SnippetListener.ts @@ -0,0 +1,45 @@ +import { EditorHelper } from "@estruyf/vscode"; +import { Position, window } from "vscode"; +import { Dashboard } from "../../commands/Dashboard"; +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; +import { BaseListener } from "./BaseListener"; + + +export class SnippetListener extends BaseListener { + + public static process(msg: { command: DashboardMessage, data: any }) { + super.process(msg); + + switch(msg.command) { + case DashboardMessage.insertSnippet: + this.insertSnippet(msg.data); + break; + } + } + + private static async insertSnippet(data: any) { + const { file, snippet } = data; + + if (!file || !snippet) { + return; + } + + await EditorHelper.showFile(data.file); + Dashboard.resetViewData(); + + const editor = window.activeTextEditor; + const position = editor?.selection?.active; + if (!position) { + return; + } + + const selection = editor?.selection; + await editor?.edit(builder => { + if (selection !== undefined) { + builder.replace(selection, snippet); + } else { + builder.insert(position, snippet); + } + }); + } +} \ No newline at end of file diff --git a/src/listeners/dashboard/index.ts b/src/listeners/dashboard/index.ts index ab3dbdfa..dcef562a 100644 --- a/src/listeners/dashboard/index.ts +++ b/src/listeners/dashboard/index.ts @@ -1,7 +1,9 @@ +export * from './BaseListener'; export * from './DashboardListener'; export * from './DataListener'; export * from './ExtensionListener'; export * from './MediaListener'; export * from './PagesListener'; export * from './SettingsListener'; +export * from './SnippetListener'; export * from './TelemetryListener'; From a6bdfc34210f377599d6fef3b14f47fa6a6b5567 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 2 Mar 2022 22:00:45 +0100 Subject: [PATCH 05/10] Snippet dialog --- .../Modals/{Metadata.tsx => FormDialog.tsx} | 4 +- .../components/SnippetsView/Item.tsx | 99 ++++++++++--------- .../components/SnippetsView/SnippetForm.tsx | 72 ++++++++++++++ .../components/SnippetsView/Snippets.tsx | 14 ++- src/helpers/SnippetParser.ts | 58 ++++++----- 5 files changed, 172 insertions(+), 75 deletions(-) rename src/dashboardWebView/components/Modals/{Metadata.tsx => FormDialog.tsx} (93%) create mode 100644 src/dashboardWebView/components/SnippetsView/SnippetForm.tsx diff --git a/src/dashboardWebView/components/Modals/Metadata.tsx b/src/dashboardWebView/components/Modals/FormDialog.tsx similarity index 93% rename from src/dashboardWebView/components/Modals/Metadata.tsx rename to src/dashboardWebView/components/Modals/FormDialog.tsx index df9d5109..729e05a0 100644 --- a/src/dashboardWebView/components/Modals/Metadata.tsx +++ b/src/dashboardWebView/components/Modals/FormDialog.tsx @@ -2,7 +2,7 @@ import { Dialog, Transition } from '@headlessui/react'; import * as React from 'react'; import { Fragment, useRef } from 'react'; -export interface IMetadataProps { +export interface IFormDialogProps { title: string; description: string; okBtnText: string; @@ -13,7 +13,7 @@ export interface IMetadataProps { trigger: () => void; } -export const Metadata: React.FunctionComponent = ({title, description, cancelBtnText, okBtnText, dismiss, isSaveDisabled, trigger, children}: React.PropsWithChildren) => { +export const FormDialog: React.FunctionComponent = ({title, description, cancelBtnText, okBtnText, dismiss, isSaveDisabled, trigger, children}: React.PropsWithChildren) => { const cancelButtonRef = useRef(null); diff --git a/src/dashboardWebView/components/SnippetsView/Item.tsx b/src/dashboardWebView/components/SnippetsView/Item.tsx index 5ae1906e..bc84673a 100644 --- a/src/dashboardWebView/components/SnippetsView/Item.tsx +++ b/src/dashboardWebView/components/SnippetsView/Item.tsx @@ -1,17 +1,22 @@ import { Messenger } from '@estruyf/vscode/dist/client'; +import { DotsHorizontalIcon, PlusIcon } from '@heroicons/react/outline'; import * as React from 'react'; -import { useEffect } from 'react'; +import { useEffect, useState } from 'react'; import { useRecoilValue } from 'recoil'; import { Choice, Scanner, SnippetParser, TokenType, Variable, VariableResolver } from '../../../helpers/SnippetParser'; import { DashboardMessage } from '../../DashboardMessage'; import { ViewDataSelector } from '../../state'; +import { FormDialog } from '../Modals/FormDialog'; +import { SnippetForm } from './SnippetForm'; export interface IItemProps { + title: string; snippet: any; } -export const Item: React.FunctionComponent = ({ snippet }: React.PropsWithChildren) => { +export const Item: React.FunctionComponent = ({ title, snippet }: React.PropsWithChildren) => { const viewData = useRecoilValue(ViewDataSelector); + const [ showInsertDialog, setShowInsertDialog ] = useState(false); // Todo: On add, show dialog to insert the placeholders and content @@ -21,49 +26,55 @@ export const Item: React.FunctionComponent = ({ snippet }: React.Pro snippet: snippet.body.join(`\n`) }); }; - - useEffect(() => { - const snippetParser = new SnippetParser(); - const body = snippet.body.join(`\n`); - - const parsed = snippetParser.parse(body); - const placeholders = parsed.placeholderInfo.all; - - for (const placeholder of placeholders) { - const tmString = placeholder.toTextmateString(); - - for (const child of placeholder.children as any[]) { - if (child instanceof Choice) { - console.log(child.options) - } else { - console.log(tmString, child.value); - } - } - } - - const resolver: VariableResolver = { - resolve: (variable: Variable): string | undefined => { - console.log(`variable`, variable); - return undefined; - } - }; - - parsed.resolveVariables(resolver); - }, []); return ( -
  • -
    {snippet.title}
    -

    {snippet.description}

    -
    - { - viewData?.data?.filePath ? ( - - ) : ( -
    Edit
    - ) - } -
    -
  • + <> +
  • +
    {title}
    + +
    +
    +
    + +
    + +
    + { + viewData?.data?.filePath ? ( + <> + + + ) : ( +
    Edit
    + ) + } +
    +
    +
    + +

    {snippet.description}

    +
  • + + { + showInsertDialog && ( + setShowInsertDialog(false)} + okBtnText='Insert' + cancelBtnText='Cancel'> + + + + + ) + } + ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx b/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx new file mode 100644 index 00000000..d57b76d9 --- /dev/null +++ b/src/dashboardWebView/components/SnippetsView/SnippetForm.tsx @@ -0,0 +1,72 @@ +import * as React from 'react'; +import { useEffect, useState } from 'react'; +import { Choice, SnippetParser, Variable, VariableResolver } from '../../../helpers/SnippetParser'; + +export interface ISnippetFormProps { + snippet: any; +} + +export const SnippetForm: React.FunctionComponent = ({ snippet }: React.PropsWithChildren) => { + const [ fields, setFields ] = useState([]); + + useEffect(() => { + const snippetParser = new SnippetParser(); + const body = snippet.body.join(`\n`); + + const parsed = snippetParser.parse(body); + const placeholders = parsed.placeholderInfo.all; + + const allFields: any[] = []; + + for (const placeholder of placeholders) { + const tmString = placeholder.toTextmateString(); + console.log(`tmString`, placeholder); + + if (placeholder.children.length === 0) { + allFields.push({ + type: 'text', + name: placeholder.index, + value: '', + tmString + }); + } else { + for (const child of placeholder.children as any[]) { + if (child instanceof Choice) { + allFields.push({ + type: 'select', + name: placeholder.index, + value: (child as any).value, + options: child.options, + tmString + }); + } else { + allFields.push({ + type: 'text', + name: placeholder.index, + value: (child as any).value, + tmString + }); + } + } + } + } + + setFields(allFields); + }, []); + + return ( +
    +
    {snippet.body.join(`\n`)}
    + +
      + { + fields.map((field: any, index: number) => ( +
    • +

      {field.name} - {field.value} - {(field.options || []).join(',')}

      +
    • + )) + } +
    +
    + ); +}; \ No newline at end of file diff --git a/src/dashboardWebView/components/SnippetsView/Snippets.tsx b/src/dashboardWebView/components/SnippetsView/Snippets.tsx index 1534a7c1..ea579897 100644 --- a/src/dashboardWebView/components/SnippetsView/Snippets.tsx +++ b/src/dashboardWebView/components/SnippetsView/Snippets.tsx @@ -1,7 +1,7 @@ import { CodeIcon } from '@heroicons/react/outline'; import * as React from 'react'; +import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; -import { AddIcon } from '../../../panelWebView/components/Icons/AddIcon'; import { SettingsSelector, ViewDataSelector } from '../../state'; import { PageLayout } from '../Layout/PageLayout'; import { Item } from './Item'; @@ -12,7 +12,8 @@ export const Snippets: React.FunctionComponent = (props: React.P const settings = useRecoilValue(SettingsSelector); const viewData = useRecoilValue(ViewDataSelector); - const snippets = settings?.snippets || []; + const snippetKeys = useMemo(() => Object.keys(settings?.snippets) || [], [settings?.snippets]); + const snippets = settings?.snippets || {}; return ( @@ -25,11 +26,14 @@ export const Snippets: React.FunctionComponent = (props: React.P } { - snippets && snippets.length > 0 ? ( + snippetKeys && snippetKeys.length > 0 ? (
      { - snippets.map((snippet: any, index: number) => ( - + snippetKeys.map((snippetKey: any, index: number) => ( + )) }
    diff --git a/src/helpers/SnippetParser.ts b/src/helpers/SnippetParser.ts index 81700d58..cf278388 100644 --- a/src/helpers/SnippetParser.ts +++ b/src/helpers/SnippetParser.ts @@ -221,23 +221,8 @@ export abstract class TransformableMarker extends Marker { } export class Placeholder extends TransformableMarker { - static compareByIndex(a: Placeholder, b: Placeholder): number { - if (a.index === b.index) { - return 0; - } else if (a.isFinalTabstop) { - return 1; - } else if (b.isFinalTabstop) { - return -1; - } else if (a.index < b.index) { - return -1; - } else if (a.index > b.index) { - return 1; - } else { - return 0; - } - } - constructor(public index: number) { + constructor(public index: number | string) { super(); } @@ -629,7 +614,7 @@ export class SnippetParser { // fill in values for placeholders. the first placeholder of an index // that has a value defines the value for all placeholders with that index - const placeholderDefaultValues = new Map(); + const placeholderDefaultValues = new Map(); const incompletePlaceholders: Placeholder[] = []; let placeholderCount = 0; snippet.walk(marker => { @@ -876,7 +861,7 @@ export class SnippetParser { return this._backTo(token); } - const variable = new Variable(name!); + const placeholder = new Placeholder(String(name!)); if (this._accept(TokenType.Colon)) { // ${foo:} @@ -884,24 +869,49 @@ export class SnippetParser { // ...} -> done if (this._accept(TokenType.CurlyClose)) { - parent.appendChild(variable); + parent.appendChild(placeholder); return true; } - if (this._parse(variable)) { + if (this._parse(placeholder)) { continue; } // fallback parent.appendChild(new Text('${' + name! + ':')); - variable.children.forEach(parent.appendChild, parent); + placeholder.children.forEach(parent.appendChild, parent); return true; } + } else if (this._accept(TokenType.Pipe)) { + const choice = new Choice(); + + while (true) { + if (this._parseChoiceElement(choice)) { + + if (this._accept(TokenType.Comma)) { + // opt, -> more + continue; + } + + if (this._accept(TokenType.Pipe)) { + placeholder.appendChild(choice); + if (this._accept(TokenType.CurlyClose)) { + // ..|} -> done + parent.appendChild(placeholder); + return true; + } + } + } + + this._backTo(token); + return false; + } + } else if (this._accept(TokenType.Forwardslash)) { // ${foo///} - if (this._parseTransform(variable)) { - parent.appendChild(variable); + if (this._parseTransform(placeholder)) { + parent.appendChild(placeholder); return true; } @@ -910,7 +920,7 @@ export class SnippetParser { } else if (this._accept(TokenType.CurlyClose)) { // ${foo} - parent.appendChild(variable); + parent.appendChild(placeholder); return true; } else { From 0e42e1ea00dcd895aed5334e0d7e58481707c9ff Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 3 Mar 2022 16:26:36 +0100 Subject: [PATCH 06/10] Code snippets changes: add, update, delete, and more --- CHANGELOG.md | 7 +- assets/empty.svg | 2 + assets/icons/scissors-dark.svg | 3 + assets/icons/scissors-light.svg | 3 + assets/walkthrough/documentation.md | 3 + assets/walkthrough/get-started.md | 11 ++ assets/walkthrough/support-the-project.md | 8 ++ package.json | 77 ++++++++-- src/commands/Article.ts | 24 ++++ src/commands/Project.ts | 3 + src/commands/Template.ts | 4 + src/commands/Wysiwyg.ts | 3 + src/constants/Extension.ts | 6 +- src/constants/TelemetryEvent.ts | 1 + src/constants/context.ts | 1 + src/dashboardWebView/DashboardMessage.ts | 2 + .../components/Contents/Item.tsx | 2 +- .../components/Header/Header.tsx | 7 +- .../components/Header/Tabs.tsx | 4 +- .../components/Layout/PageLayout.tsx | 4 +- .../components/Media/Item.tsx | 2 +- .../components/Modals/FormDialog.tsx | 7 +- .../components/SnippetsView/Item.tsx | 136 +++++++++++++++--- .../components/SnippetsView/NewForm.tsx | 108 ++++++++++++++ .../components/SnippetsView/SnippetForm.tsx | 122 +++++++++++++--- .../components/SnippetsView/Snippets.tsx | 76 +++++++++- src/dashboardWebView/hooks/useMessages.tsx | 2 + src/extension.ts | 15 +- src/helpers/SnippetParser.ts | 4 +- src/listeners/dashboard/SnippetListener.ts | 41 ++++++ src/models/DashboardData.ts | 4 +- 31 files changed, 630 insertions(+), 62 deletions(-) create mode 100644 assets/empty.svg create mode 100644 assets/icons/scissors-dark.svg create mode 100644 assets/icons/scissors-light.svg create mode 100644 assets/walkthrough/documentation.md create mode 100644 assets/walkthrough/get-started.md create mode 100644 assets/walkthrough/support-the-project.md create mode 100644 src/dashboardWebView/components/SnippetsView/NewForm.tsx diff --git a/CHANGELOG.md b/CHANGELOG.md index 9bb4f8d3..31340439 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,12 +1,17 @@ # Change Log -## [6.2.0] - 2022-03-xx +## [7.0.0] - 2022-03-xx + +### ✨ Features + +- [#175](https://github.com/estruyf/vscode-front-matter/issues/175): New snippet support + dashboard ### 🎨 Enhancements - Light color theme enhancements to media cards - Light color theme enhancements to folder cards - [#272](https://github.com/estruyf/vscode-front-matter/issues/272): New slide over panel for showing details of media files +- [#276](https://github.com/estruyf/vscode-front-matter/issues/276): Add a Front Matter walkthrough for VS Code ## [6.1.0] - 2022-02-28 - [Release notes](https://beta.frontmatter.codes/updates/v6.1.0) diff --git a/assets/empty.svg b/assets/empty.svg new file mode 100644 index 00000000..8872d161 --- /dev/null +++ b/assets/empty.svg @@ -0,0 +1,2 @@ + + \ No newline at end of file diff --git a/assets/icons/scissors-dark.svg b/assets/icons/scissors-dark.svg new file mode 100644 index 00000000..514110d3 --- /dev/null +++ b/assets/icons/scissors-dark.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/icons/scissors-light.svg b/assets/icons/scissors-light.svg new file mode 100644 index 00000000..780b674e --- /dev/null +++ b/assets/icons/scissors-light.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/assets/walkthrough/documentation.md b/assets/walkthrough/documentation.md new file mode 100644 index 00000000..d5751e00 --- /dev/null +++ b/assets/walkthrough/documentation.md @@ -0,0 +1,3 @@ +## Documentation + +Our documentation can be found at: [https://frontmatter.codes/docs](https://frontmatter.codes/docs) \ No newline at end of file diff --git a/assets/walkthrough/get-started.md b/assets/walkthrough/get-started.md new file mode 100644 index 00000000..9cfcfb31 --- /dev/null +++ b/assets/walkthrough/get-started.md @@ -0,0 +1,11 @@ +## Getting started + +Thanks for installing Front Matter! + +To get started, open our dashboard which will guide you through the initialization process of your project. + +When you haven't initialized your project yet, you will see the Front Matter's welcome screen on which you will have to perform the following steps: + +- Project initialization +- Content folders registration +- Framework initialization diff --git a/assets/walkthrough/support-the-project.md b/assets/walkthrough/support-the-project.md new file mode 100644 index 00000000..5b9362cd --- /dev/null +++ b/assets/walkthrough/support-the-project.md @@ -0,0 +1,8 @@ +## Support the project + +Front Matter is an open source project and we are always looking for new contributors, supporters, and partners. If you are interested in backing the project, please consider supporting it by donating. You can donate at via the following links: + +- [GitHub Sponsors](https://github.com/sponsors/estruyf) +- [Open Collective](https://opencollective.com/frontmatter) + +> Each sponsor/backer will be mentioned on the [Front Matter](https://frontmatter.codes) website and on the [GitHub repository](https://github.com/estruyf/vscode-front-matter). \ No newline at end of file diff --git a/package.json b/package.json index db0eb1aa..c36b4970 100644 --- a/package.json +++ b/package.json @@ -1089,7 +1089,7 @@ }, { "command": "frontMatter.markup.blockquote", - "title": "Codeblock", + "title": "Blockquote", "category": "Front matter", "icon": { "light": "assets/icons/blockquote-light.svg", @@ -1180,6 +1180,15 @@ "light": "/assets/icons/media-light.svg" } }, + { + "command": "frontMatter.insertSnippet", + "title": "Insert snippet into your content", + "category": "Front matter", + "icon": { + "dark": "/assets/icons/scissors-dark.svg", + "light": "/assets/icons/scissors-light.svg" + } + }, { "command": "frontMatter.insertTags", "title": "Insert tags", @@ -1212,6 +1221,15 @@ "light": "/assets/icons/frontmatter-small-light.svg" } }, + { + "command": "frontMatter.dashboard.snippets", + "title": "Open snippets dashboard", + "category": "Front matter", + "icon": { + "dark": "/assets/icons/frontmatter-small-dark.svg", + "light": "/assets/icons/frontmatter-small-light.svg" + } + }, { "command": "frontMatter.dashboard.media", "title": "Open media dashboard", @@ -1287,32 +1305,32 @@ "editor/title": [ { "command": "frontMatter.markup.heading", - "group": "navigation@-132", + "group": "navigation@-133", "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" }, { "command": "frontMatter.markup.bold", - "group": "navigation@-131", + "group": "navigation@-132", "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" }, { "command": "frontMatter.markup.italic", - "group": "navigation@-130", + "group": "navigation@-131", "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" }, { "command": "frontMatter.markup.strikethrough", - "group": "navigation@-129", + "group": "navigation@-130", "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" }, { - "command": "frontMatter.markup.blockquote", - "group": "navigation@-128", - "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + "command": "frontMatter.insertSnippet", + "group": "navigation@-129", + "when": "resourceLangId == markdown" }, { "command": "frontMatter.insertImage", - "group": "navigation@-127", + "group": "navigation@-128", "when": "resourceLangId == markdown" }, { @@ -1345,6 +1363,11 @@ "group": "1_markup@5", "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" }, + { + "command": "frontMatter.markup.blockquote", + "group": "1_markup@6", + "when": "resourceLangId == markdown && frontMatter:markdown:wysiwyg" + }, { "command": "frontMatter.dashboard", "group": "navigation@-98", @@ -1466,6 +1489,42 @@ "text.html.markdown" ] } + ], + "walkthroughs": [ + { + "id": "frontmatter.welcome", + "title": "Get started with Front Matter", + "description": "Discover the features of Front Matter and learn how to use the CMS for your SSG or static site.", + "steps": [ + { + "id": "frontmatter.welcome.init", + "title": "Get started", + "description": "Initial steps to get started.\n[Open dashboard](command:frontMatter.dashboard)", + "media": { + "markdown": "assets/walkthrough/get-started.md" + }, + "completionEvents": ["onContext:frontMatterInitialized"] + }, + { + "id": "frontmatter.welcome.documentation", + "title": "Documentation", + "description": "Check out the documentation for Front Matter.\n[View our documentation](https://frontmatter.codes/docs)", + "media": { + "markdown": "assets/walkthrough/documentation.md" + }, + "completionEvents": ["onLink:https://frontmatter.codes/docs"] + }, + { + "id": "frontmatter.welcome.supporter", + "title": "Support the project", + "description": "Become a supporter.\n[Support the project](https://github.com/sponsors/estruyf)", + "media": { + "markdown": "assets/walkthrough/support-the-project.md" + }, + "completionEvents": ["onLink:https://github.com/sponsors/estruyf"] + } + ] + } ] }, "scripts": { diff --git a/src/commands/Article.ts b/src/commands/Article.ts index cc55675e..a7afe679 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -13,6 +13,7 @@ import { parseWinPath } from '../helpers/parseWinPath'; import { Telemetry } from '../helpers/Telemetry'; import { ParsedFrontMatter } from '../parsers'; import { MediaListener } from '../listeners/panel'; +import { NavigationType } from '../dashboardWebView/models'; export class Article { @@ -340,6 +341,29 @@ export class Article { MediaListener.getMediaSelection(); } + /** + * Insert a snippet into the article + */ + public static async insertSnippet() { + let editor = vscode.window.activeTextEditor; + if (!editor) { + return; + } + + const position = editor.selection.active; + const selectionText = editor.document.getText(editor.selection); + + await vscode.commands.executeCommand(COMMAND_NAME.dashboard, { + type: NavigationType.Snippets, + data: { + filePath: editor.document.uri.fsPath, + fieldName: basename(editor.document.uri.fsPath), + position, + selection: selectionText + } + } as DashboardData); + } + /** * Get the current article */ diff --git a/src/commands/Project.ts b/src/commands/Project.ts index 5595c207..0fbc2fb2 100644 --- a/src/commands/Project.ts +++ b/src/commands/Project.ts @@ -7,6 +7,7 @@ import { Template } from "./Template"; import { Folders } from "./Folders"; import { Settings } from "../helpers"; import { SETTING_CONTENT_DEFAULT_FILETYPE, TelemetryEvent } from "../constants"; +import { SettingsListener } from '../listeners/dashboard'; export class Project { @@ -50,6 +51,8 @@ categories: [] } Telemetry.send(TelemetryEvent.initialization) + + SettingsListener.getSettings(); } catch (err: any) { Notifications.error(`Sorry, something went wrong - ${err?.message || err}`); } diff --git a/src/commands/Template.ts b/src/commands/Template.ts index fa5659b0..d164ed0f 100644 --- a/src/commands/Template.ts +++ b/src/commands/Template.ts @@ -23,6 +23,10 @@ export class Template { public static async init() { const isInitialized = await Template.isInitialized(); await vscode.commands.executeCommand('setContext', CONTEXT.canInit, !isInitialized); + + if (isInitialized) { + await vscode.commands.executeCommand('setContext', CONTEXT.initialized, true); + } } /** diff --git a/src/commands/Wysiwyg.ts b/src/commands/Wysiwyg.ts index 70201ce0..657335d2 100644 --- a/src/commands/Wysiwyg.ts +++ b/src/commands/Wysiwyg.ts @@ -54,6 +54,7 @@ export class Wysiwyg { { 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 }, + { label: "$(quote) Blockquote", detail: "Add a blockquote", alwaysShow: true }, ] const option = await window.showQuickPick([ ...qpItems ], { @@ -73,6 +74,8 @@ export class Wysiwyg { await this.addMarkup(MarkupType.code); } else if (option.label === qpItems[4].label) { await this.addMarkup(MarkupType.codeblock); + } else if (option.label === qpItems[5].label) { + await this.addMarkup(MarkupType.blockquote); } } })); diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index 561a0541..e0446325 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -29,13 +29,17 @@ export const COMMAND_NAME = { preview: getCommandName("preview"), dashboard: getCommandName("dashboard"), dashboardMedia: getCommandName("dashboard.media"), + dashboardSnippets: getCommandName("dashboard.snippets"), dashboardData: getCommandName("dashboard.data"), dashboardClose: getCommandName("dashboard.close"), promote: getCommandName("promoteSettings"), - insertImage: getCommandName("insertImage"), createFolder: getCommandName("createFolder"), diagnostics: getCommandName("diagnostics"), + // Insert dashboards + insertImage: getCommandName("insertImage"), + insertSnippet: getCommandName("insertSnippet"), + // WYSIWYG bold: getCommandName("markup.bold"), italic: getCommandName("markup.italic"), diff --git a/src/constants/TelemetryEvent.ts b/src/constants/TelemetryEvent.ts index dfca5182..56a0aae5 100644 --- a/src/constants/TelemetryEvent.ts +++ b/src/constants/TelemetryEvent.ts @@ -4,6 +4,7 @@ export const TelemetryEvent = { openContentDashboard: 'openContentDashboard', openMediaDashboard: 'openMediaDashboard', openDataDashboard: 'openDataDashboard', + openSnippetsDashboard: 'openSnippetsDashboard', closeDashboard: 'closeDashboard', generateSlug: 'generateSlug', createContentFromTemplate: 'createContentFromTemplate', diff --git a/src/constants/context.ts b/src/constants/context.ts index 8f029de2..6f5b7689 100644 --- a/src/constants/context.ts +++ b/src/constants/context.ts @@ -1,5 +1,6 @@ export const CONTEXT = { canInit: "frontMatterCanInit", + initialized: "frontMatterInitialized", canOpenPreview: "frontMatterCanOpenPreview", canOpenDashboard: "frontMatterCanOpenDashboard", isEnabled: "frontMatter:enabled", diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index ad5ccb61..305bbda4 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -26,4 +26,6 @@ export enum DashboardMessage { putDataEntries = 'putDataEntries', sendTelemetry = 'sendTelemetry', insertSnippet = 'insertSnippet', + addSnippet = 'addSnippet', + updateSnippet = 'updateSnippet', } \ No newline at end of file diff --git a/src/dashboardWebView/components/Contents/Item.tsx b/src/dashboardWebView/components/Contents/Item.tsx index 29d62e18..05b8b6d4 100644 --- a/src/dashboardWebView/components/Contents/Item.tsx +++ b/src/dashboardWebView/components/Contents/Item.tsx @@ -23,7 +23,7 @@ export const Item: React.FunctionComponent = ({ fmFilePath, date, ti if (view === DashboardViewType.Grid) { return (
  • -
  • { diff --git a/src/dashboardWebView/components/Layout/PageLayout.tsx b/src/dashboardWebView/components/Layout/PageLayout.tsx index c2083d48..634bf70e 100644 --- a/src/dashboardWebView/components/Layout/PageLayout.tsx +++ b/src/dashboardWebView/components/Layout/PageLayout.tsx @@ -4,16 +4,18 @@ import { SettingsSelector } from '../../state'; import { Header } from '../Header'; export interface IPageLayoutProps { + header?: React.ReactNode; folders?: string[] | undefined totalPages?: number | undefined } -export const PageLayout: React.FunctionComponent = ({ folders, totalPages, children }: React.PropsWithChildren) => { +export const PageLayout: React.FunctionComponent = ({ header, folders, totalPages, children }: React.PropsWithChildren) => { const settings = useRecoilValue(SettingsSelector); return (
    diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index 4d46bc75..ba79099c 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -205,7 +205,7 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi return ( <> -
  • +
  • - ) : ( -
    Edit
    ) } + + + +
  • -

    {snippet.description}

    +

    {snippet.description}

  • { @@ -70,11 +135,48 @@ export const Item: React.FunctionComponent = ({ title, snippet }: Re cancelBtnText='Cancel'> + ref={formRef} + snippet={snippet} + selection={viewData?.data?.selection} /> ) } + + { + showEditDialog && ( + + + setSnippetTitle(value)} + onDescriptionUpdate={(value: string) => setSnippetDescription(value)} + onBodyUpdate={(value: string) => setSnippetOriginalBody(value)} /> + + + ) + } + + { + showAlert && ( + setShowAlert(false)} + trigger={onDelete} /> + ) + } ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/SnippetsView/NewForm.tsx b/src/dashboardWebView/components/SnippetsView/NewForm.tsx new file mode 100644 index 00000000..fc319203 --- /dev/null +++ b/src/dashboardWebView/components/SnippetsView/NewForm.tsx @@ -0,0 +1,108 @@ +import { ChevronDownIcon, ChevronUpIcon } from '@heroicons/react/outline'; +import * as React from 'react'; + +export interface INewFormProps { + title: string; + description: string; + body: string; + + onTitleUpdate: (value: string) => void; + onDescriptionUpdate: (value: string) => void; + onBodyUpdate: (value: string) => void; +} + +export const NewForm: React.FunctionComponent = ({ title, description, body, onTitleUpdate, onDescriptionUpdate, onBodyUpdate }: React.PropsWithChildren) => { + const [ showDetails, setShowDetails ] = React.useState(false); + + return ( +
    +
    + +
    + onTitleUpdate(e.currentTarget.value)} + /> +
    +
    + +
    + +
    + onDescriptionUpdate(e.currentTarget.value)} + /> +
    +
    + +
    + +
    +