diff --git a/src/commands/Article.ts b/src/commands/Article.ts index 3fe20014..ac92a1f8 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -13,6 +13,7 @@ import { DateHelper } from '../helpers/DateHelper'; import { parseWinPath } from '../helpers/parseWinPath'; import { Telemetry } from '../helpers/Telemetry'; import { ParsedFrontMatter } from '../parsers'; +import { MediaListener } from '../listeners/panel'; export class Article { @@ -337,7 +338,7 @@ export class Article { } as DashboardData); // Let the editor panel know you are selecting an image - ExplorerView.getInstance().getMediaSelection(); + MediaListener.getMediaSelection(); } /** diff --git a/src/commands/Backers.ts b/src/commands/Backers.ts index 215a03eb..55ea0168 100644 --- a/src/commands/Backers.ts +++ b/src/commands/Backers.ts @@ -5,6 +5,7 @@ import { Credentials } from "../services/Credentials"; import fetch from "node-fetch"; import { ExplorerView } from '../explorerView/ExplorerView'; import { Dashboard } from './Dashboard'; +import { SettingsListener } from '../listeners/panel'; export class Backers { private static creds: Credentials | null = null; @@ -60,7 +61,7 @@ export class Backers { if (!prevData) { const explorerView = ExplorerView.getInstance(); if (explorerView.visible) { - explorerView.getSettings(); + SettingsListener.getSettings(); } if (Dashboard.isOpen) { diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index ac2f8a5b..a06c1b8d 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -1,5 +1,3 @@ -import { PagesListener } from './../listeners/PagesListener'; -import { ExtensionListener } from './../listeners/ExtensionListener'; import { SETTINGS_DASHBOARD_OPENONSTART, CONTEXT } from '../constants'; import { join } from "path"; import { commands, Uri, ViewColumn, Webview, WebviewPanel, window } from "vscode"; @@ -10,7 +8,8 @@ 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 } from '../listeners'; +import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener } from '../listeners/dashboard'; +import { MediaListener as PanelMediaListener } from '../listeners/panel' export class Dashboard { private static webview: WebviewPanel | null = null; @@ -115,8 +114,7 @@ export class Dashboard { Dashboard.webview.onDidChangeViewState(async () => { if (!this.webview?.visible) { Dashboard._viewData = undefined; - const panel = ExplorerView.getInstance(extensionUri); - panel.getMediaSelection(); + PanelMediaListener.getMediaSelection(); Dashboard.postWebviewMessage({ command: DashboardCommand.viewData, data: null }); } @@ -127,8 +125,7 @@ export class Dashboard { Dashboard.webview.onDidDispose(async () => { Dashboard.isDisposed = true; Dashboard._viewData = undefined; - const panel = ExplorerView.getInstance(extensionUri); - panel.getMediaSelection(); + PanelMediaListener.getMediaSelection(); await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, false); }); diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 47546262..c1efe2cb 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -12,7 +12,7 @@ import { format } from 'date-fns'; import { Dashboard } from './Dashboard'; import { parseWinPath } from '../helpers/parseWinPath'; import { MediaHelpers } from '../helpers/MediaHelpers'; -import { MediaListener, PagesListener } from '../listeners'; +import { MediaListener, PagesListener } from '../listeners/dashboard'; import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes'; import { Telemetry } from '../helpers/Telemetry'; diff --git a/src/commands/StatusListener.ts b/src/commands/StatusListener.ts index e77b783e..7d1283df 100644 --- a/src/commands/StatusListener.ts +++ b/src/commands/StatusListener.ts @@ -4,6 +4,7 @@ import { ArticleHelper, SeoHelper, Settings } from '../helpers'; import { ExplorerView } from '../explorerView/ExplorerView'; import { DefaultFields } from '../constants'; import { ContentType } from '../helpers/ContentType'; +import { DataListener } from '../listeners/panel'; export class StatusListener { @@ -58,7 +59,7 @@ export class StatusListener { const panel = ExplorerView.getInstance(); if (panel && panel.visible) { - panel.pushMetadata(article!.data); + DataListener.pushMetadata(article!.data); } return; @@ -68,7 +69,7 @@ export class StatusListener { } else { const panel = ExplorerView.getInstance(); if (panel && panel.visible) { - panel.pushMetadata(null); + DataListener.pushMetadata(null); } } diff --git a/src/commands/Template.ts b/src/commands/Template.ts index 1e38c4f0..65d3b7f6 100644 --- a/src/commands/Template.ts +++ b/src/commands/Template.ts @@ -11,7 +11,7 @@ import { Project } from './Project'; import { Folders } from './Folders'; import { ContentType } from '../helpers/ContentType'; import { ContentType as IContentType } from '../models'; -import { PagesListener } from '../listeners'; +import { PagesListener } from '../listeners/dashboard'; import { extname } from 'path'; import { Telemetry } from '../helpers/Telemetry'; diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index 1384bc99..7d5a97b3 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -1,32 +1,12 @@ -import { DashboardData } from '../models/DashboardData'; -import { Template } from '../commands/Template'; -import { DefaultFields, SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, SETTING_AUTO_UPDATE_DATE, SETTING_CUSTOM_SCRIPTS, SETTING_SEO_CONTENT_MIN_LENGTH, SETTING_SEO_DESCRIPTION_FIELD, SETTING_SLUG_UPDATE_FILE_NAME, SETTING_PREVIEW_HOST, SETTING_DATE_FORMAT, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_PANEL_FREEFORM, SETTING_SEO_DESCRIPTION_LENGTH, SETTING_SEO_TITLE_LENGTH, SETTING_SLUG_PREFIX, SETTING_SLUG_SUFFIX, SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS, SETTINGS_CONTENT_DRAFT_FIELD, SETTING_SEO_SLUG_LENGTH, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CUSTOM, CONTEXT, SETTINGS_FRAMEWORK_ID, SETTINGS_FRAMEWORK_START, TelemetryEvent, SETTINGS_DATA_TYPES } from '../constants'; -import * as os from 'os'; -import { PanelSettings, CustomScript as ICustomScript } from '../models/PanelSettings'; -import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window, workspace, commands, env as vscodeEnv, ThemeIcon } from "vscode"; -import { ArticleHelper, Settings } from "../helpers"; +import { ArticleListener, ExtensionListener, MediaListener, ScriptListener, TaxonomyListener, DataListener, SettingsListener } from './../listeners/panel'; +import { TelemetryEvent } from '../constants'; +import { CancellationToken, Disposable, Uri, Webview, WebviewView, WebviewViewProvider, WebviewViewResolveContext, window } from "vscode"; +import { Logger, Settings } from "../helpers"; import { Command } from "../panelWebView/Command"; -import { CommandToCode } from '../panelWebView/CommandToCode'; -import { Article } from '../commands'; import { TagType } from '../panelWebView/TagType'; -import { CustomTaxonomyData, DraftField, Field, ScriptType, TaxonomyType } from '../models'; -import { exec } from 'child_process'; -import { fromMarkdown } from 'mdast-util-from-markdown'; -import { Content } from 'mdast'; -import { COMMAND_NAME, EXTENSION_BETA_ID, EXTENSION_ID } from '../constants/Extension'; -import { Folders } from '../commands/Folders'; -import { Preview } from '../commands/Preview'; -import { openFileInEditor } from '../helpers/openFileInEditor'; import { WebviewHelper } from '@estruyf/vscode'; import { Extension } from '../helpers/Extension'; -import { Dashboard } from '../commands/Dashboard'; -import { ImageHelper } from '../helpers/ImageHelper'; -import { CustomScript } from '../helpers/CustomScript'; -import { Link, Parent } from 'mdast-util-from-markdown/lib'; import { Telemetry } from '../helpers/Telemetry'; -import { DataType } from '../models/DataType'; - -const FILE_LIMIT = 10; export class ExplorerView implements WebviewViewProvider, Disposable { public static readonly viewType = "frontMatter.explorer"; @@ -65,6 +45,10 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } } + public getWebview() { + return this.panel?.webview; + } + /** * Default resolve webview panel * @param webviewView @@ -87,122 +71,15 @@ export class ExplorerView implements WebviewViewProvider, Disposable { ); webviewView.webview.onDidReceiveMessage(async (msg) => { - switch(msg.command) { - case CommandToCode.getData: - this.getSettings(); - this.getFoldersAndFiles(); - this.getFileData(); - break; - case CommandToCode.updateSlug: - Article.generateSlug(); - break; - case CommandToCode.updateLastMod: - Article.setLastModifiedDate(); - break; - case CommandToCode.publish: - Article.toggleDraft(); - break; - case CommandToCode.updateTags: - this.updateTags(TagType.tags, msg.data?.values || [], msg.data?.parents || []); - break; - case CommandToCode.updateCategories: - this.updateTags(TagType.categories, msg.data?.values || [], msg.data?.parents || []); - break; - case CommandToCode.updateKeywords: - this.updateTags(TagType.keywords, msg.data?.values || [], msg.data?.parents || []); - break; - case CommandToCode.updateCustomTaxonomy: - this.updateCustomTaxonomy(msg.data); - break; - case CommandToCode.addTagToSettings: - this.addTags(TagType.tags, msg.data); - break; - case CommandToCode.addCategoryToSettings: - this.addTags(TagType.categories, msg.data); - break; - case CommandToCode.addToCustomTaxonomy: - this.addCustomTaxonomy(msg.data); - break; - case CommandToCode.openSettings: - const isBeta = Extension.getInstance().isBetaVersion(); - commands.executeCommand('workbench.action.openSettings', `@ext:${isBeta ? EXTENSION_BETA_ID : EXTENSION_ID}`); - break; - case CommandToCode.openFile: - if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") { - commands.executeCommand('remote-wsl.revealInExplorer'); - } else { - commands.executeCommand('revealFileInOS'); - } - break; - case CommandToCode.runCustomScript: - this.runCustomScript(msg); - break; - case CommandToCode.openProject: - const wsFolder = Folders.getWorkspaceFolder(); - if (wsFolder) { - const wsPath = wsFolder.fsPath; - if (os.type() === "Darwin") { - exec(`open ${wsPath}`); - } else if (os.type() === "Windows_NT") { - exec(`explorer ${wsPath}`); - } else if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") { - exec('explorer.exe `wslpath -w "$PWD"`'); - } else { - exec(`xdg-open ${wsPath}`); - } - } - break; - case CommandToCode.initProject: - await commands.executeCommand(COMMAND_NAME.init); - this.getSettings(); - break; - case CommandToCode.createContent: - await commands.executeCommand(COMMAND_NAME.createContent); - break; - case CommandToCode.createTemplate: - await commands.executeCommand(COMMAND_NAME.createTemplate); - break; - case CommandToCode.updateModifiedUpdating: - this.updateSetting(SETTING_AUTO_UPDATE_DATE, msg.data || false); - break; - case CommandToCode.toggleWritingSettings: - this.toggleWritingSettings(); - break; - case CommandToCode.updateFmHighlight: - this.updateSetting(SETTINGS_CONTENT_FRONTMATTER_HIGHLIGHT, (msg.data !== null && msg.data !== undefined) ? msg.data : false); - break; - case CommandToCode.toggleCenterMode: - await commands.executeCommand(`workbench.action.toggleCenteredLayout`); - break; - case CommandToCode.openPreview: - await commands.executeCommand(COMMAND_NAME.preview); - break; - case CommandToCode.openDashboard: - await commands.executeCommand(COMMAND_NAME.dashboard); - break; - case CommandToCode.updatePreviewUrl: - this.updateSetting(SETTING_PREVIEW_HOST, msg.data || ""); - break; - case CommandToCode.openInEditor: - openFileInEditor(msg.data); - break; - case CommandToCode.updateMetadata: - this.updateMetadata(msg.data); - break; - case CommandToCode.selectImage: - await commands.executeCommand(COMMAND_NAME.dashboard, { - type: "media", - data: msg.data - } as DashboardData); - this.getMediaSelection(); - break; - case CommandToCode.frameworkCommand: - this.openTerminalWithCommand(msg.data.command); - break; - case CommandToCode.updateStartCommand: - await this.updateSetting(SETTINGS_FRAMEWORK_START, msg.data || ""); - break; - } + Logger.info(`Receiving message from webview to panel: ${msg.command}`); + + ArticleListener.process(msg); + DataListener.process(msg); + ExtensionListener.process(msg); + MediaListener.process(msg); + ScriptListener.process(msg); + SettingsListener.process(msg); + TaxonomyListener.process(msg); }); webviewView.onDidChangeVisibility(() => { @@ -213,67 +90,24 @@ export class ExplorerView implements WebviewViewProvider, Disposable { }); window.onDidChangeActiveTextEditor(() => { - this.postWebviewMessage({ command: Command.loading, data: true }); + this.sendMessage({ command: Command.loading, data: true }); + if (this.visible) { - this.getFileData(); + DataListener.getFileData(); } }, this); Settings.onConfigChange((global?: any) => { - this.getSettings(); + SettingsListener.getSettings(); }); } /** - * Triggers a metadata change in the panel - * @param metadata + * Post data to the panel + * @param msg */ - public pushMetadata(metadata: any) { - const wsFolder = Folders.getWorkspaceFolder(); - const filePath = window.activeTextEditor?.document.uri.fsPath; - const commaSeparated = Settings.get(SETTING_COMMA_SEPARATED_FIELDS); - const contentTypes = Settings.get(SETTING_TAXONOMY_CONTENT_TYPES); - - const articleDetails = this.getArticleDetails(); - - if (articleDetails) { - metadata.articleDetails = articleDetails; - } - - let updatedMetadata = Object.assign({}, metadata); - if (commaSeparated) { - for (const key of commaSeparated) { - if (updatedMetadata[key] && typeof updatedMetadata[key] === "string") { - updatedMetadata[key] = updatedMetadata[key].split(",").map((s: string) => s.trim()); - } - } - } - - const keys = Object.keys(updatedMetadata); - if (keys.length > 0) { - updatedMetadata.filePath = filePath; - } - - if (keys.length > 0 && contentTypes && wsFolder) { - // Get the current content type - const contentType = ArticleHelper.getContentType(updatedMetadata); - if (contentType) { - this.processImageFields(updatedMetadata, contentType.fields) - } - } - - // Check slug - if (!updatedMetadata[DefaultFields.Slug]) { - const slug = Article.getSlug(); - - if (slug) { - updatedMetadata[DefaultFields.Slug] = slug; - } - } - - this.postWebviewMessage({ command: Command.metadata, data: { - ...updatedMetadata - }}); + public sendMessage(msg: { command: Command, data?: any }) { + this.panel?.webview?.postMessage(msg); } /** @@ -282,9 +116,9 @@ export class ExplorerView implements WebviewViewProvider, Disposable { */ public triggerInputFocus(tagType: TagType) { if (tagType === TagType.tags) { - this.postWebviewMessage({ command: Command.focusOnTags }); + this.sendMessage({ command: Command.focusOnTags }); } else { - this.postWebviewMessage({ command: Command.focusOnCategories }); + this.sendMessage({ command: Command.focusOnCategories }); } } @@ -292,396 +126,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { * Trigger all sections to close */ public collapseAll() { - this.postWebviewMessage({ command: Command.closeSections }); - } - - /** - * Update the metadata of the article - */ - public async updateMetadata({field, parents, value }: { field: string, value: any, parents?: string[], fieldData?: { multiple: boolean, value: string[] } }) { - if (!field) { - return; - } - - const editor = window.activeTextEditor; - if (!editor) { - return; - } - - const article = ArticleHelper.getFrontMatter(editor); - if (!article) { - return; - } - - const contentType = ArticleHelper.getContentType(article.data); - const dateFields = contentType.fields.filter((field) => field.type === "datetime"); - const imageFields = contentType.fields.filter((field) => field.type === "image" && field.multiple); - - // Support multi-level fields - let parentObj = article.data; - for (const parent of parents || []) { - parentObj = parentObj[parent]; - } - - for (const dateField of dateFields) { - if ((field === dateField.name) && value) { - parentObj[field] = Article.formatDate(new Date(value)); - } else if (!imageFields.find(f => f.name === field)) { - // Only override the field data if it is not an multiselect image field - parentObj[field] = value; - } - } - - for (const imageField of imageFields) { - if (field === imageField.name) { - // If value is an array, it means it comes from the explorer view itself (deletion) - if (Array.isArray(value)) { - parentObj[field] = value || []; - } else { // Otherwise it is coming from the media dashboard (addition) - let fieldValue = parentObj[field]; - if (fieldValue && !Array.isArray(fieldValue)) { - fieldValue = [fieldValue]; - } - const crntData = Object.assign([], fieldValue); - const allRelPaths = [...(crntData || []), value]; - parentObj[field] = [...new Set(allRelPaths)].filter(f => f); - } - } - } - - ArticleHelper.update(editor, article); - this.pushMetadata(article.data); - } - - /** - * Open a terminal and run the passed command - * @param command - */ - private openTerminalWithCommand(command: string) { - if (command) { - let terminal = window.activeTerminal; - - if (!terminal || (terminal && terminal.state.isInteractedWith === true)) { - terminal = window.createTerminal({ - name: `Starting local server`, - iconPath: new ThemeIcon('server-environment'), - message: `Starting local server`, - }); - } - - if (terminal) { - terminal.sendText(command); - terminal.show(false); - } - } - } - - /** - * Run a custom script - * @param msg - */ - private runCustomScript(msg: { command: string, data: any}) { - const scripts: ICustomScript[] | undefined = Settings.get(SETTING_CUSTOM_SCRIPTS); - - if (msg?.data?.title && msg?.data?.script && scripts) { - const customScript = scripts.find((s: ICustomScript) => s.title === msg.data.title); - if (customScript?.script && customScript?.title) { - CustomScript.run(customScript); - } - } - } - - /** - * Return the media selection - */ - public async getMediaSelection() { - this.postWebviewMessage({ - command: Command.mediaSelectionData, - data: Dashboard.viewData - }); - } - - /** - * Retrieve the extension settings - */ - public async getSettings() { - this.postWebviewMessage({ - command: Command.settings, - data: { - seo: { - title: Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1, - slug: Settings.get(SETTING_SEO_SLUG_LENGTH) as number || -1, - description: Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1, - content: Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1, - descriptionField: Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description - }, - slug: { - prefix: Settings.get(SETTING_SLUG_PREFIX) || "", - suffix: Settings.get(SETTING_SLUG_SUFFIX) || "", - updateFileName: !!Settings.get(SETTING_SLUG_UPDATE_FILE_NAME), - }, - date: { - format: Settings.get(SETTING_DATE_FORMAT) - }, - tags: Settings.get(SETTING_TAXONOMY_TAGS, true) || [], - categories: Settings.get(SETTING_TAXONOMY_CATEGORIES, true) || [], - customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [], - freeform: Settings.get(SETTING_PANEL_FREEFORM), - scripts: (Settings.get(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type === ScriptType.Content || !s.type), - isInitialized: await Template.isInitialized(), - modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false, - writingSettingsEnabled: this.isWritingSettingsEnabled() || false, - fmHighlighting: Settings.get(SETTINGS_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), - isBacker: await Extension.getInstance().getState(CONTEXT.backer, 'global'), - framework: Settings.get(SETTINGS_FRAMEWORK_ID), - commands: { - start: Settings.get(SETTINGS_FRAMEWORK_START) - }, - dataTypes: Settings.get(SETTINGS_DATA_TYPES), - } as PanelSettings - }); - } - - /** - * Retrieve the information about the registered folders and its files - */ - public async getFoldersAndFiles() { - this.postWebviewMessage({ - command: Command.folderInfo, - data: await Folders.getInfo(FILE_LIMIT) || null - }); - } - - /** - * Process the image fields in the content type - * @param updatedMetadata - * @param fields - * @param parents - */ - private processImageFields(updatedMetadata: any, fields: Field[], parents: string[] = []) { - const imageFields = fields.filter((field) => field.type === "image"); - - // Support multi-level fields - let parentObj = updatedMetadata; - for (const parent of parents || []) { - parentObj = parentObj[parent]; - } - - // Process image fields - if (parentObj) { - for (const field of imageFields) { - if (parentObj[field.name]) { - const imageData = ImageHelper.allRelToAbs(field, parentObj[field.name]) - - if (imageData) { - if (field.multiple && imageData instanceof Array) { - const preview = imageData.map(preview => preview && preview.absPath ? ({ - ...preview, - webviewUrl: this.panel?.webview.asWebviewUri(preview.absPath).toString() - }) : null); - - parentObj[field.name] = preview || []; - } else if (!field.multiple && !Array.isArray(imageData) && imageData.absPath) { - const preview = this.panel?.webview.asWebviewUri(imageData.absPath); - parentObj[field.name] = { - ...imageData, - webviewUrl: preview ? preview.toString() : null - }; - } - } else { - parentObj[field.name] = field.multiple ? [] : ""; - } - } - } - - // Check if there are sub-fields to process - const subFields = fields.filter((field) => field.type === "fields"); - if (subFields?.length > 0) { - for (const field of subFields) { - this.processImageFields(updatedMetadata, field.fields || [], [...parents, field.name]); - } - } - } - } - - /** - * Retrieve the file its front matter - */ - private getFileData() { - const editor = window.activeTextEditor; - if (!editor) { - return ""; - } - - const article = ArticleHelper.getFrontMatter(editor); - if (article?.data) { - this.pushMetadata(article!.data); - } - } - - /** - * Update the tags in the current document - * @param tagType - * @param values - */ - private updateTags(tagType: TagType, values: string[], parents: string[]) { - const editor = window.activeTextEditor; - if (!editor) { - return ""; - } - - const article = ArticleHelper.getFrontMatter(editor); - if (article && article.data) { - - // Support multi-level fields - let parentObj = article.data; - for (const parent of parents || []) { - parentObj = parentObj[parent]; - } - - parentObj[tagType.toLowerCase()] = values || []; - ArticleHelper.update(editor, article); - this.pushMetadata(article!.data); - } - } - - /** - * Update the tags in the current document - * @param data - */ - private updateCustomTaxonomy(data: CustomTaxonomyData) { - if (!data?.id || !data?.name) { - return; - } - - const editor = window.activeTextEditor; - if (!editor) { - return ""; - } - - const article = ArticleHelper.getFrontMatter(editor); - if (article && article.data) { - - // Support multi-level fields - let parentObj = article.data; - for (const parent of data.parents || []) { - parentObj = parentObj[parent]; - } - - parentObj[data.name] = data.options || []; - ArticleHelper.update(editor, article); - this.pushMetadata(article!.data); - } - } - - /** - * Add tag to the settings - * @param data - */ - private async addCustomTaxonomy(data: CustomTaxonomyData) { - if (!data?.id || !data?.option) { - return; - } - - await Settings.updateCustomTaxonomy(data.id, data.option); - } - - /** - * Add tag to the settings - * @param tagType - * @param value - */ - private async addTags(tagType: TagType, value: string) { - if (value) { - let options = tagType === TagType.tags ? Settings.get(SETTING_TAXONOMY_TAGS, true) : Settings.get(SETTING_TAXONOMY_CATEGORIES, true); - - if (!options) { - options = []; - } - - options.push(value); - const taxType = tagType === TagType.tags ? TaxonomyType.Tag : TaxonomyType.Category; - await Settings.updateTaxonomy(taxType, options); - } - } - - /** - * Get article details - */ - private getArticleDetails() { - const baseUrl = Settings.get(SETTING_SITE_BASEURL); - const editor = window.activeTextEditor; - if (!editor) { - return null; - } - - if (!ArticleHelper.isMarkdownFile()) { - return null; - } - - const article = ArticleHelper.getFrontMatter(editor); - - if (article && article.content) { - let content = article.content; - content = content.replace(/({{(.*?)}})/g, ''); // remove hugo shortcodes - - const mdTree = fromMarkdown(content); - const elms: Parent[] | Link[] = this.getAllElms(mdTree); - - const headings = elms.filter(node => node.type === 'heading'); - const paragraphs = elms.filter(node => node.type === 'paragraph').length; - const images = elms.filter(node => node.type === 'image').length; - const links: string[] = elms.filter(node => node.type === 'link').map(node => (node as Link).url); - - const internalLinks = links.filter(link => !link.startsWith('http') || (baseUrl && link.toLowerCase().includes((baseUrl || "").toLowerCase()))).length; - let externalLinks = links.filter(link => link.startsWith('http')); - if (baseUrl) { - externalLinks = externalLinks.filter(link => !link.toLowerCase().includes(baseUrl.toLowerCase())); - } - - const headers = []; - for (const header of headings) { - const text = header?.children?.filter((node: any) => node.type === 'text').map((node: any) => node.value).join(" "); - if (text) { - headers.push(text); - } - } - - const wordCount = this.wordCount(0, mdTree); - - return { - headings: headings.length, - headingsText: headers, - paragraphs, - images, - internalLinks, - externalLinks: externalLinks.length, - wordCount, - content: article.content - }; - } - - return null; - } - - private getAllElms(node: Content | any, allElms?: any[]): any[] { - if (!allElms) { - allElms = []; - } - - if (node.children?.length > 0) { - for (const child of node.children) { - allElms.push(Object.assign({}, child)); - this.getAllElms(child, allElms); - } - } - - return allElms; + this.sendMessage({ command: Command.closeSections }); } private counts(acc: any, node: any) { @@ -695,69 +140,6 @@ export class ExplorerView implements WebviewViewProvider, Disposable { ); } - /** - * Get the word count for the current document - */ - private wordCount(count: number, node: Content | any) { - if (node.type === "text") { - return count + node.value.split(" ").length; - } else { - return (node.children || []).reduce((childCount: number, childNode: any) => this.wordCount(childCount, childNode), count); - } - } - - /** - * Toggle the writing settings - */ - private async toggleWritingSettings() { - const config = workspace.getConfiguration("", { languageId: "markdown" }); - const enabled = this.isWritingSettingsEnabled(); - - await config.update("editor.fontSize", enabled ? undefined : 14, false, true); - await config.update("editor.lineHeight", enabled ? undefined : 26, false, true); - await config.update("editor.wordWrap", enabled ? undefined : "wordWrapColumn", false, true); - await config.update("editor.wordWrapColumn", enabled ? undefined : 64, false, true); - await config.update("editor.lineNumbers", enabled ? undefined : "off", false, true); - await config.update("editor.quickSuggestions", enabled ? undefined : false, false, true); - await config.update("editor.minimap.enabled", enabled ? undefined : false, false, true); - - this.getSettings(); - } - - /** - * Check if the writing settings are enabled - */ - private isWritingSettingsEnabled() { - const config = workspace.getConfiguration("", { languageId: "markdown" }); - - const fontSize = config.get("editor.fontSize"); - const lineHeight = config.get("editor.lineHeight"); - const wordWrap = config.get("editor.wordWrap"); - const wordWrapColumn = config.get("editor.wordWrapColumn"); - const lineNumbers = config.get("editor.lineNumbers"); - const quickSuggestions = config.get("editor.quickSuggestions"); - - return fontSize && lineHeight && wordWrap && wordWrapColumn && lineNumbers && quickSuggestions !== undefined; - } - - /** - * Updates a setting and refreshes the retrieved settings - * @param setting - * @param value - */ - private async updateSetting(setting: string, value: any) { - await Settings.update(setting, value); - this.getSettings(); - } - - /** - * Post data to the panel - * @param msg - */ - private postWebviewMessage(msg: { command: Command, data?: any }) { - this.panel?.webview?.postMessage(msg); - } - /** * Retrieve the webview HTML contents * @param webView diff --git a/src/extension.ts b/src/extension.ts index 0381b7af..50b26c42 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -19,8 +19,9 @@ import { Content } from './commands/Content'; import ContentProvider from './providers/ContentProvider'; import { Wysiwyg } from './commands/Wysiwyg'; import { Diagnostics } from './commands/Diagnostics'; -import { PagesListener } from './listeners'; +import { PagesListener } from './listeners/dashboard'; import { Backers } from './commands/Backers'; +import { DataListener, SettingsListener } from './listeners/panel'; let frontMatterStatusBar: vscode.StatusBarItem; let statusDebouncer: { (fnc: any, time: number): void; }; @@ -171,9 +172,8 @@ export async function activate(context: vscode.ExtensionContext) { Template.init(); Preview.init(); - const exView = ExplorerView.getInstance(); - exView.getSettings(); - exView.getFoldersAndFiles(); + SettingsListener.getSettings(); + DataListener.getFoldersAndFiles(); MarkdownFoldingProvider.triggerHighlighting(); }); @@ -197,7 +197,7 @@ export async function activate(context: vscode.ExtensionContext) { subscriptions.push(vscode.workspace.onDidSaveTextDocument((doc: vscode.TextDocument) => { if (doc.languageId === 'markdown') { // Optimize the list of recently changed files - ExplorerView.getInstance().getFoldersAndFiles(); + DataListener.getFoldersAndFiles(); } })); diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index bb84db7b..e950659f 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, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX } from '../constants'; +import { DefaultFields, SETTINGS_CONTENT_DEFAULT_FILETYPE, SETTINGS_CONTENT_PLACEHOLDERS, SETTINGS_CONTENT_SUPPORTED_FILETYPES, 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 '.'; @@ -17,6 +17,9 @@ import { ContentType } from '../models'; import { DateHelper } from './DateHelper'; import { DiagnosticSeverity, Position, window, Range } from 'vscode'; import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes'; +import { fromMarkdown } from 'mdast-util-from-markdown'; +import { Link, Parent } from 'mdast-util-from-markdown/lib'; +import { Content } from 'mdast'; export class ArticleHelper { private static notifiedFiles: string[] = []; @@ -347,6 +350,98 @@ export class ArticleHelper { return value; } + /** + * Get the details of the current article + * @returns + */ + public static getDetails() { + const baseUrl = Settings.get(SETTING_SITE_BASEURL); + const editor = window.activeTextEditor; + if (!editor) { + return null; + } + + if (!ArticleHelper.isMarkdownFile()) { + return null; + } + + const article = ArticleHelper.getFrontMatter(editor); + + if (article && article.content) { + let content = article.content; + content = content.replace(/({{(.*?)}})/g, ''); // remove hugo shortcodes + + const mdTree = fromMarkdown(content); + const elms: Parent[] | Link[] = this.getAllElms(mdTree); + + const headings = elms.filter(node => node.type === 'heading'); + const paragraphs = elms.filter(node => node.type === 'paragraph').length; + const images = elms.filter(node => node.type === 'image').length; + const links: string[] = elms.filter(node => node.type === 'link').map(node => (node as Link).url); + + const internalLinks = links.filter(link => !link.startsWith('http') || (baseUrl && link.toLowerCase().includes((baseUrl || "").toLowerCase()))).length; + let externalLinks = links.filter(link => link.startsWith('http')); + if (baseUrl) { + externalLinks = externalLinks.filter(link => !link.toLowerCase().includes(baseUrl.toLowerCase())); + } + + const headers = []; + for (const header of headings) { + const text = header?.children?.filter((node: any) => node.type === 'text').map((node: any) => node.value).join(" "); + if (text) { + headers.push(text); + } + } + + const wordCount = this.wordCount(0, mdTree); + + return { + headings: headings.length, + headingsText: headers, + paragraphs, + images, + internalLinks, + externalLinks: externalLinks.length, + wordCount, + content: article.content + }; + } + + return null; + } + + /** + * Retrieve all the elements from the markdown content + * @param node + * @param allElms + * @returns + */ + private static getAllElms(node: Content | any, allElms?: any[]): any[] { + if (!allElms) { + allElms = []; + } + + if (node.children?.length > 0) { + for (const child of node.children) { + allElms.push(Object.assign({}, child)); + this.getAllElms(child, allElms); + } + } + + return allElms; + } + + /** + * Get the word count for the current document + */ + private static wordCount(count: number, node: Content | any) { + if (node.type === "text") { + return count + node.value.split(" ").length; + } else { + return (node.children || []).reduce((childCount: number, childNode: any) => this.wordCount(childCount, childNode), count); + } + } + /** * Parse a markdown file and its front matter * @param fileContents diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 6fbfd6ee..7ef56c7c 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -1,4 +1,4 @@ -import { PagesListener } from './../listeners/PagesListener'; +import { PagesListener } from './../listeners/dashboard'; import { ArticleHelper, Settings } from "."; import { SETTINGS_CONTENT_DRAFT_FIELD, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants"; import { ContentType as IContentType, DraftField, Field } from '../models'; diff --git a/src/helpers/ImageHelper.ts b/src/helpers/ImageHelper.ts index 3bb402ac..adebc19f 100644 --- a/src/helpers/ImageHelper.ts +++ b/src/helpers/ImageHelper.ts @@ -1,3 +1,4 @@ +import { ExplorerView } from './../explorerView/ExplorerView'; import { Uri, window } from 'vscode'; import { dirname, join } from "path"; import { Field } from '../models'; @@ -78,4 +79,57 @@ export class ImageHelper { } return relPath; } + + /** + * Process the image fields in the content type + * @param updatedMetadata + * @param fields + * @param parents + */ + public static processImageFields(updatedMetadata: any, fields: Field[], parents: string[] = []) { + const imageFields = fields.filter((field) => field.type === "image"); + const panel = ExplorerView.getInstance(); + + // Support multi-level fields + let parentObj = updatedMetadata; + for (const parent of parents || []) { + parentObj = parentObj[parent]; + } + + // Process image fields + if (parentObj) { + for (const field of imageFields) { + if (parentObj[field.name]) { + const imageData = ImageHelper.allRelToAbs(field, parentObj[field.name]) + + if (imageData) { + if (field.multiple && imageData instanceof Array) { + const preview = imageData.map(preview => preview && preview.absPath ? ({ + ...preview, + webviewUrl: panel.getWebview()?.asWebviewUri(preview.absPath).toString() + }) : null); + + parentObj[field.name] = preview || []; + } else if (!field.multiple && !Array.isArray(imageData) && imageData.absPath) { + const preview = panel.getWebview()?.asWebviewUri(imageData.absPath); + parentObj[field.name] = { + ...imageData, + webviewUrl: preview ? preview.toString() : null + }; + } + } else { + parentObj[field.name] = field.multiple ? [] : ""; + } + } + } + + // Check if there are sub-fields to process + const subFields = fields.filter((field) => field.type === "fields"); + if (subFields?.length > 0) { + for (const field of subFields) { + this.processImageFields(updatedMetadata, field.fields || [], [...parents, field.name]); + } + } + } + } } \ No newline at end of file diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index 16c7eb69..13f2800c 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -11,6 +11,7 @@ import imageSize from "image-size"; import { EditorHelper } from "@estruyf/vscode"; import { ExplorerView } from "../explorerView/ExplorerView"; import { SortOption } from "../dashboardWebView/constants/SortOption"; +import { DataListener, MediaListener } from "../listeners/panel"; export class MediaHelpers { @@ -301,10 +302,10 @@ export class MediaHelpers { } }); } - panel.getMediaSelection(); + MediaListener.getMediaSelection(); } else { - panel.getMediaSelection(); - panel.updateMetadata({field: data.fieldName, value: data.image, parents: data.parents }); + MediaListener.getMediaSelection(); + DataListener.updateMetadata({field: data.fieldName, value: data.image, parents: data.parents }); } } } diff --git a/src/helpers/PanelSettings.ts b/src/helpers/PanelSettings.ts new file mode 100644 index 00000000..f5a3c3b4 --- /dev/null +++ b/src/helpers/PanelSettings.ts @@ -0,0 +1,66 @@ +import { workspace } from "vscode" +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_TAGS } from "../constants" +import { CustomScript, DataType, DraftField, PanelSettings as IPanelSettings, ScriptType } from "../models" + +export class PanelSettings { + + public static async get(): Promise { + return { + seo: { + title: Settings.get(SETTING_SEO_TITLE_LENGTH) as number || -1, + slug: Settings.get(SETTING_SEO_SLUG_LENGTH) as number || -1, + description: Settings.get(SETTING_SEO_DESCRIPTION_LENGTH) as number || -1, + content: Settings.get(SETTING_SEO_CONTENT_MIN_LENGTH) as number || -1, + descriptionField: Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description + }, + slug: { + prefix: Settings.get(SETTING_SLUG_PREFIX) || "", + suffix: Settings.get(SETTING_SLUG_SUFFIX) || "", + updateFileName: !!Settings.get(SETTING_SLUG_UPDATE_FILE_NAME), + }, + date: { + format: Settings.get(SETTING_DATE_FORMAT) || "" + }, + tags: Settings.get(SETTING_TAXONOMY_TAGS, true) || [], + categories: Settings.get(SETTING_TAXONOMY_CATEGORIES, true) || [], + customTaxonomy: Settings.get(SETTING_TAXONOMY_CUSTOM, true) || [], + freeform: Settings.get(SETTING_PANEL_FREEFORM), + scripts: (Settings.get(SETTING_CUSTOM_SCRIPTS) || []).filter(s => s.type === ScriptType.Content || !s.type), + isInitialized: await Template.isInitialized(), + modifiedDateUpdate: Settings.get(SETTING_AUTO_UPDATE_DATE) || false, + writingSettingsEnabled: this.isWritingSettingsEnabled() || false, + fmHighlighting: Settings.get(SETTINGS_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), + isBacker: await Extension.getInstance().getState(CONTEXT.backer, 'global'), + framework: Settings.get(SETTINGS_FRAMEWORK_ID), + commands: { + start: Settings.get(SETTINGS_FRAMEWORK_START) + }, + dataTypes: Settings.get(SETTINGS_DATA_TYPES), + } + } + + /** + * Check if the writing settings are enabled + */ + public static isWritingSettingsEnabled(): boolean { + const config = workspace.getConfiguration("", { languageId: "markdown" }); + + const fontSize = config.get("editor.fontSize"); + const lineHeight = config.get("editor.lineHeight"); + const wordWrap = config.get("editor.wordWrap"); + const wordWrapColumn = config.get("editor.wordWrapColumn"); + const lineNumbers = config.get("editor.lineNumbers"); + const quickSuggestions = config.get("editor.quickSuggestions"); + + return( fontSize && lineHeight && wordWrap && wordWrapColumn && lineNumbers && quickSuggestions !== undefined) as boolean; + } +} \ No newline at end of file diff --git a/src/listeners/BaseListener.ts b/src/listeners/dashboard/BaseListener.ts similarity index 61% rename from src/listeners/BaseListener.ts rename to src/listeners/dashboard/BaseListener.ts index 006885ea..bcdab00f 100644 --- a/src/listeners/BaseListener.ts +++ b/src/listeners/dashboard/BaseListener.ts @@ -1,7 +1,7 @@ -import { Dashboard } from "../commands/Dashboard"; -import { DashboardCommand } from "../dashboardWebView/DashboardCommand"; -import { DashboardMessage } from "../dashboardWebView/DashboardMessage"; -import { Logger } from "../helpers/Logger"; +import { Dashboard } from "../../commands/Dashboard"; +import { DashboardCommand } from "../../dashboardWebView/DashboardCommand"; +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; +import { Logger } from "../../helpers/Logger"; export abstract class BaseListener { diff --git a/src/listeners/DashboardListener.ts b/src/listeners/dashboard/DashboardListener.ts similarity index 70% rename from src/listeners/DashboardListener.ts rename to src/listeners/dashboard/DashboardListener.ts index c737fe42..8c10aad2 100644 --- a/src/listeners/DashboardListener.ts +++ b/src/listeners/dashboard/DashboardListener.ts @@ -1,8 +1,8 @@ -import { Dashboard } from "../commands/Dashboard"; -import { ExtensionState } from "../constants"; -import { DashboardCommand } from "../dashboardWebView/DashboardCommand"; -import { DashboardMessage } from "../dashboardWebView/DashboardMessage"; -import { Extension } from "../helpers"; +import { Dashboard } from "../../commands/Dashboard"; +import { ExtensionState } from "../../constants"; +import { DashboardCommand } from "../../dashboardWebView/DashboardCommand"; +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; +import { Extension } from "../../helpers"; import { BaseListener } from "./BaseListener"; diff --git a/src/listeners/DataListener.ts b/src/listeners/dashboard/DataListener.ts similarity index 89% rename from src/listeners/DataListener.ts rename to src/listeners/dashboard/DataListener.ts index 7cc42c91..27e6c382 100644 --- a/src/listeners/DataListener.ts +++ b/src/listeners/dashboard/DataListener.ts @@ -1,12 +1,12 @@ -import { DataFile } from './../models/DataFile'; -import { DashboardMessage } from "../dashboardWebView/DashboardMessage"; +import { DataFile } from './../../models/DataFile'; +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; import { BaseListener } from "./BaseListener"; -import { DashboardCommand } from '../dashboardWebView/DashboardCommand'; -import { Folders } from '../commands/Folders'; +import { DashboardCommand } from '../../dashboardWebView/DashboardCommand'; +import { Folders } from '../../commands/Folders'; import { existsSync, readFileSync, writeFileSync, mkdirSync } from 'fs'; import { dirname } from 'path'; import * as yaml from 'js-yaml'; -import { Logger, Notifications } from '../helpers'; +import { Logger, Notifications } from '../../helpers'; import { commands } from 'vscode'; diff --git a/src/listeners/ExtensionListener.ts b/src/listeners/dashboard/ExtensionListener.ts similarity index 80% rename from src/listeners/ExtensionListener.ts rename to src/listeners/dashboard/ExtensionListener.ts index 7132c85c..4c9ec7e5 100644 --- a/src/listeners/ExtensionListener.ts +++ b/src/listeners/dashboard/ExtensionListener.ts @@ -1,9 +1,9 @@ import { commands, env } from "vscode"; import { SettingsListener } from "."; -import { COMMAND_NAME } from "../constants"; -import { DashboardMessage } from "../dashboardWebView/DashboardMessage"; -import { CustomScript, Extension } from "../helpers"; -import { openFileInEditor } from "../helpers/openFileInEditor"; +import { COMMAND_NAME } from "../../constants"; +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; +import { CustomScript, Extension } from "../../helpers"; +import { openFileInEditor } from "../../helpers/openFileInEditor"; import { BaseListener } from "./BaseListener"; diff --git a/src/listeners/MediaListener.ts b/src/listeners/dashboard/MediaListener.ts similarity index 89% rename from src/listeners/MediaListener.ts rename to src/listeners/dashboard/MediaListener.ts index 42874088..21ad392c 100644 --- a/src/listeners/MediaListener.ts +++ b/src/listeners/dashboard/MediaListener.ts @@ -1,11 +1,11 @@ -import { Telemetry } from './../helpers/Telemetry'; -import { MediaHelpers } from './../helpers/MediaHelpers'; -import { DashboardMessage } from "../dashboardWebView/DashboardMessage"; +import { Telemetry } from '../../helpers/Telemetry'; +import { MediaHelpers } from '../../helpers/MediaHelpers'; +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; import { BaseListener } from "./BaseListener"; -import { DashboardCommand } from '../dashboardWebView/DashboardCommand'; -import { SortingOption } from '../dashboardWebView/models'; +import { DashboardCommand } from '../../dashboardWebView/DashboardCommand'; +import { SortingOption } from '../../dashboardWebView/models'; import { commands, env, Uri } from 'vscode'; -import { COMMAND_NAME, TelemetryEvent } from '../constants'; +import { COMMAND_NAME, TelemetryEvent } from '../../constants'; import * as os from 'os'; diff --git a/src/listeners/PagesListener.ts b/src/listeners/dashboard/PagesListener.ts similarity index 91% rename from src/listeners/PagesListener.ts rename to src/listeners/dashboard/PagesListener.ts index f77ddc85..7487a345 100644 --- a/src/listeners/PagesListener.ts +++ b/src/listeners/dashboard/PagesListener.ts @@ -1,17 +1,17 @@ -import { isValidFile } from './../helpers/isValidFile'; +import { isValidFile } from '../../helpers/isValidFile'; import { existsSync } from "fs"; 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 { DashboardCommand } from "../dashboardWebView/DashboardCommand"; -import { DashboardMessage } from "../dashboardWebView/DashboardMessage"; -import { Page } from "../dashboardWebView/models"; -import { ArticleHelper, Logger, Settings } from "../helpers"; -import { ContentType } from "../helpers/ContentType"; -import { DateHelper } from "../helpers/DateHelper"; -import { Notifications } from "../helpers/Notifications"; +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 { DashboardCommand } from "../../dashboardWebView/DashboardCommand"; +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; +import { Page } from "../../dashboardWebView/models"; +import { ArticleHelper, Logger, Settings } from "../../helpers"; +import { ContentType } from "../../helpers/ContentType"; +import { DateHelper } from "../../helpers/DateHelper"; +import { Notifications } from "../../helpers/Notifications"; import { BaseListener } from "./BaseListener"; diff --git a/src/listeners/SettingsListener.ts b/src/listeners/dashboard/SettingsListener.ts similarity index 82% rename from src/listeners/SettingsListener.ts rename to src/listeners/dashboard/SettingsListener.ts index 09ef84d0..18be3859 100644 --- a/src/listeners/SettingsListener.ts +++ b/src/listeners/dashboard/SettingsListener.ts @@ -1,9 +1,9 @@ -import { SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_FRAMEWORK_ID } from "../constants"; -import { DashboardCommand } from "../dashboardWebView/DashboardCommand"; -import { DashboardMessage } from "../dashboardWebView/DashboardMessage"; -import { DashboardSettings, Settings } from "../helpers"; -import { FrameworkDetector } from "../helpers/FrameworkDetector"; -import { Framework } from "../models"; +import { SETTINGS_CONTENT_STATIC_FOLDER, SETTINGS_FRAMEWORK_ID } from "../../constants"; +import { DashboardCommand } from "../../dashboardWebView/DashboardCommand"; +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; +import { DashboardSettings, Settings } from "../../helpers"; +import { FrameworkDetector } from "../../helpers/FrameworkDetector"; +import { Framework } from "../../models"; import { BaseListener } from "./BaseListener"; diff --git a/src/listeners/TelemetryListener.ts b/src/listeners/dashboard/TelemetryListener.ts similarity index 77% rename from src/listeners/TelemetryListener.ts rename to src/listeners/dashboard/TelemetryListener.ts index c603d16a..b482c2cd 100644 --- a/src/listeners/TelemetryListener.ts +++ b/src/listeners/dashboard/TelemetryListener.ts @@ -1,5 +1,5 @@ -import { DashboardMessage } from "../dashboardWebView/DashboardMessage"; -import { Telemetry } from "../helpers/Telemetry"; +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; +import { Telemetry } from "../../helpers/Telemetry"; import { BaseListener } from "./BaseListener"; diff --git a/src/listeners/index.ts b/src/listeners/dashboard/index.ts similarity index 88% rename from src/listeners/index.ts rename to src/listeners/dashboard/index.ts index 6a61ba18..ab3dbdfa 100644 --- a/src/listeners/index.ts +++ b/src/listeners/dashboard/index.ts @@ -1,4 +1,3 @@ -export * from './BaseListener'; export * from './DashboardListener'; export * from './DataListener'; export * from './ExtensionListener'; diff --git a/src/listeners/panel/ArticleListener.ts b/src/listeners/panel/ArticleListener.ts new file mode 100644 index 00000000..b602732f --- /dev/null +++ b/src/listeners/panel/ArticleListener.ts @@ -0,0 +1,27 @@ +import { Article } from "../../commands"; +import { CommandToCode } from "../../panelWebView/CommandToCode"; +import { BaseListener } from "./BaseListener"; + + +export class ArticleListener extends BaseListener { + + /** + * Process the messages for the dashboard views + * @param msg + */ + public static process(msg: { command: any, data: any }) { + super.process(msg); + + switch(msg.command) { + case CommandToCode.updateSlug: + Article.generateSlug(); + break; + case CommandToCode.updateLastMod: + Article.setLastModifiedDate(); + break; + case CommandToCode.publish: + Article.toggleDraft(); + break; + } + } +} \ No newline at end of file diff --git a/src/listeners/panel/BaseListener.ts b/src/listeners/panel/BaseListener.ts new file mode 100644 index 00000000..0a0e87d2 --- /dev/null +++ b/src/listeners/panel/BaseListener.ts @@ -0,0 +1,28 @@ +import { Extension } from './../../helpers/Extension'; +import { ExplorerView } from './../../explorerView/ExplorerView'; +import { Logger } from "../../helpers"; +import { CommandToCode } from "../../panelWebView/CommandToCode"; +import { Command } from '../../panelWebView/Command'; + + +export abstract class BaseListener { + + public static process(msg: { command: CommandToCode, data: any }) {} + + /** + * Send a message to the webview + * @param command + * @param data + */ + public static sendMsg(command: Command, data: any) { + Logger.info(`Sending message to panel: ${command}`); + + const extPath = Extension.getInstance().extensionPath; + const panel = ExplorerView.getInstance(extPath); + + panel.sendMessage({ + command, + data + }); + } +} \ No newline at end of file diff --git a/src/listeners/panel/DataListener.ts b/src/listeners/panel/DataListener.ts new file mode 100644 index 00000000..7b2c64c7 --- /dev/null +++ b/src/listeners/panel/DataListener.ts @@ -0,0 +1,196 @@ +import { ImageHelper } from './../../helpers/ImageHelper'; +import { Folders } from "../../commands/Folders"; +import { Command } from "../../panelWebView/Command"; +import { CommandToCode } from "../../panelWebView/CommandToCode"; +import { BaseListener } from "./BaseListener"; +import { commands, ThemeIcon, window } from 'vscode'; +import { ArticleHelper, Settings } from "../../helpers"; +import { COMMAND_NAME, DefaultFields, SETTING_COMMA_SEPARATED_FIELDS, SETTING_TAXONOMY_CONTENT_TYPES } from "../../constants"; +import { Article } from '../../commands'; + +const FILE_LIMIT = 10; + +export class DataListener extends BaseListener { + + /** + * Process the messages for the dashboard views + * @param msg + */ + public static process(msg: { command: CommandToCode, data: any }) { + super.process(msg); + + switch(msg.command) { + case CommandToCode.getData: + this.getFoldersAndFiles(); + this.getFileData(); + break; + case CommandToCode.createContent: + commands.executeCommand(COMMAND_NAME.createContent); + break; + case CommandToCode.createTemplate: + commands.executeCommand(COMMAND_NAME.createTemplate); + break; + case CommandToCode.updateMetadata: + this.updateMetadata(msg.data); + break; + case CommandToCode.frameworkCommand: + this.openTerminalWithCommand(msg.data.command); + break; + } + } + + /** + * Retrieve the information about the registered folders and its files + */ + public static async getFoldersAndFiles() { + const folders = await Folders.getInfo(FILE_LIMIT) || null; + + this.sendMsg(Command.folderInfo, folders); + } + + /** + * Triggers a metadata change in the panel + * @param metadata + */ + public static pushMetadata(metadata: any) { + const wsFolder = Folders.getWorkspaceFolder(); + const filePath = window.activeTextEditor?.document.uri.fsPath; + const commaSeparated = Settings.get(SETTING_COMMA_SEPARATED_FIELDS); + const contentTypes = Settings.get(SETTING_TAXONOMY_CONTENT_TYPES); + + const articleDetails = ArticleHelper.getDetails(); + + if (articleDetails) { + metadata.articleDetails = articleDetails; + } + + let updatedMetadata = Object.assign({}, metadata); + if (commaSeparated) { + for (const key of commaSeparated) { + if (updatedMetadata[key] && typeof updatedMetadata[key] === "string") { + updatedMetadata[key] = updatedMetadata[key].split(",").map((s: string) => s.trim()); + } + } + } + + const keys = Object.keys(updatedMetadata); + if (keys.length > 0) { + updatedMetadata.filePath = filePath; + } + + if (keys.length > 0 && contentTypes && wsFolder) { + // Get the current content type + const contentType = ArticleHelper.getContentType(updatedMetadata); + if (contentType) { + ImageHelper.processImageFields(updatedMetadata, contentType.fields) + } + } + + // Check slug + if (!updatedMetadata[DefaultFields.Slug]) { + const slug = Article.getSlug(); + + if (slug) { + updatedMetadata[DefaultFields.Slug] = slug; + } + } + + this.sendMsg(Command.metadata, updatedMetadata); + } + + /** + * Update the metadata of the article + */ + public static async updateMetadata({field, parents, value }: { field: string, value: any, parents?: string[], fieldData?: { multiple: boolean, value: string[] } }) { + if (!field) { + return; + } + + const editor = window.activeTextEditor; + if (!editor) { + return; + } + + const article = ArticleHelper.getFrontMatter(editor); + if (!article) { + return; + } + + const contentType = ArticleHelper.getContentType(article.data); + const dateFields = contentType.fields.filter((field) => field.type === "datetime"); + const imageFields = contentType.fields.filter((field) => field.type === "image" && field.multiple); + + // Support multi-level fields + let parentObj = article.data; + for (const parent of parents || []) { + parentObj = parentObj[parent]; + } + + for (const dateField of dateFields) { + if ((field === dateField.name) && value) { + parentObj[field] = Article.formatDate(new Date(value)); + } else if (!imageFields.find(f => f.name === field)) { + // Only override the field data if it is not an multiselect image field + parentObj[field] = value; + } + } + + for (const imageField of imageFields) { + if (field === imageField.name) { + // If value is an array, it means it comes from the explorer view itself (deletion) + if (Array.isArray(value)) { + parentObj[field] = value || []; + } else { // Otherwise it is coming from the media dashboard (addition) + let fieldValue = parentObj[field]; + if (fieldValue && !Array.isArray(fieldValue)) { + fieldValue = [fieldValue]; + } + const crntData = Object.assign([], fieldValue); + const allRelPaths = [...(crntData || []), value]; + parentObj[field] = [...new Set(allRelPaths)].filter(f => f); + } + } + } + + ArticleHelper.update(editor, article); + this.pushMetadata(article.data); + } + + /** + * Retrieve the file its front matter + */ + public static async getFileData() { + const editor = window.activeTextEditor; + if (!editor) { + return ""; + } + + const article = ArticleHelper.getFrontMatter(editor); + if (article?.data) { + this.pushMetadata(article!.data); + } + } + + /** + * Open a terminal and run the passed command + * @param command + */ + private static openTerminalWithCommand(command: string) { + if (command) { + let terminal = window.activeTerminal; + + if (!terminal || (terminal && terminal.state.isInteractedWith === true)) { + terminal = window.createTerminal({ + name: `Starting local server`, + iconPath: new ThemeIcon('server-environment'), + message: `Starting local server`, + }); + } + + if (terminal) { + terminal.sendText(command); + terminal.show(false); + } + } + } +} \ No newline at end of file diff --git a/src/listeners/panel/ExtensionListener.ts b/src/listeners/panel/ExtensionListener.ts new file mode 100644 index 00000000..5061744c --- /dev/null +++ b/src/listeners/panel/ExtensionListener.ts @@ -0,0 +1,83 @@ +import { CommandToCode } from "../../panelWebView/CommandToCode"; +import { BaseListener } from "./BaseListener"; +import { commands, env as vscodeEnv } from 'vscode'; +import * as os from 'os'; +import { exec } from 'child_process'; +import { Folders } from "../../commands/Folders"; +import { COMMAND_NAME } from "../../constants"; +import { SettingsListener } from "."; +import { openFileInEditor } from "../../helpers"; + + +export class ExtensionListener extends BaseListener { + + /** + * Process the messages for the dashboard views + * @param msg + */ + public static process(msg: { command: any, data: any }) { + super.process(msg); + + switch(msg.command) { + case CommandToCode.openFile: + this.openFile(); + break; + case CommandToCode.openProject: + this.openFolder(); + break; + case CommandToCode.openInEditor: + openFileInEditor(msg.data); + break; + case CommandToCode.initProject: + this.initialize(); + break; + case CommandToCode.toggleCenterMode: + commands.executeCommand(`workbench.action.toggleCenteredLayout`); + break; + case CommandToCode.openPreview: + commands.executeCommand(COMMAND_NAME.preview); + break; + case CommandToCode.openDashboard: + commands.executeCommand(COMMAND_NAME.dashboard); + break; + } + } + + /** + * Initialize project + */ + private static async initialize() { + await commands.executeCommand(COMMAND_NAME.init); + SettingsListener.getSettings(); + } + + /** + * Open the file in your explorer + */ + private static openFile() { + if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") { + commands.executeCommand('remote-wsl.revealInExplorer'); + } else { + commands.executeCommand('revealFileInOS'); + } + } + + /** + * Opens the project folder + */ + private static openFolder() { + const wsFolder = Folders.getWorkspaceFolder(); + if (wsFolder) { + const wsPath = wsFolder.fsPath; + if (os.type() === "Darwin") { + exec(`open ${wsPath}`); + } else if (os.type() === "Windows_NT") { + exec(`explorer ${wsPath}`); + } else if (os.type() === "Linux" && vscodeEnv.remoteName?.toLowerCase() === "wsl") { + exec('explorer.exe `wslpath -w "$PWD"`'); + } else { + exec(`xdg-open ${wsPath}`); + } + } + } +} \ No newline at end of file diff --git a/src/listeners/panel/MediaListener.ts b/src/listeners/panel/MediaListener.ts new file mode 100644 index 00000000..bf87c224 --- /dev/null +++ b/src/listeners/panel/MediaListener.ts @@ -0,0 +1,43 @@ +import { commands } from "vscode"; +import { Dashboard } from "../../commands/Dashboard"; +import { COMMAND_NAME } from "../../constants"; +import { DashboardData } from "../../models"; +import { Command } from "../../panelWebView/Command"; +import { CommandToCode } from "../../panelWebView/CommandToCode"; +import { BaseListener } from "./BaseListener"; + + +export class MediaListener extends BaseListener { + + /** + * Process the messages for the dashboard views + * @param msg + */ + public static process(msg: { command: any, data: any }) { + super.process(msg); + + switch(msg.command) { + case CommandToCode.selectImage: + this.selectMedia(msg); + break; + } + } + + /** + * Select a media file + */ + private static async selectMedia(msg: { data: any }) { + await commands.executeCommand(COMMAND_NAME.dashboard, { + type: "media", + data: msg.data + } as DashboardData); + this.getMediaSelection(); + } + + /** + * Return the media selection + */ + public static async getMediaSelection() { + this.sendMsg(Command.mediaSelectionData, Dashboard.viewData); + } +} \ No newline at end of file diff --git a/src/listeners/panel/ScriptListener.ts b/src/listeners/panel/ScriptListener.ts new file mode 100644 index 00000000..c630819e --- /dev/null +++ b/src/listeners/panel/ScriptListener.ts @@ -0,0 +1,38 @@ +import { SETTING_CUSTOM_SCRIPTS } from "../../constants"; +import { CustomScript, Settings } from "../../helpers"; +import { CustomScript as ICustomScript } from "../../models"; +import { CommandToCode } from "../../panelWebView/CommandToCode"; +import { BaseListener } from "./BaseListener"; + + +export class ScriptListener extends BaseListener { + + /** + * Process the messages for the dashboard views + * @param msg + */ + public static process(msg: { command: any, data: any }) { + super.process(msg); + + switch(msg.command) { + case CommandToCode.runCustomScript: + this.runCustomScript(msg); + break; + } + } + + /** + * Run a custom script + * @param msg + */ + private static runCustomScript(msg: { command: string, data: any}) { + const scripts: ICustomScript[] | undefined = Settings.get(SETTING_CUSTOM_SCRIPTS); + + if (msg?.data?.title && msg?.data?.script && scripts) { + const customScript = scripts.find((s: ICustomScript) => s.title === msg.data.title); + if (customScript?.script && customScript?.title) { + CustomScript.run(customScript); + } + } + } +} \ No newline at end of file diff --git a/src/listeners/panel/SettingsListener.ts b/src/listeners/panel/SettingsListener.ts new file mode 100644 index 00000000..9bb9f414 --- /dev/null +++ b/src/listeners/panel/SettingsListener.ts @@ -0,0 +1,87 @@ +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, Settings } from "../../helpers"; +import { PanelSettings } from "../../helpers/PanelSettings"; +import { Command } from "../../panelWebView/Command"; +import { CommandToCode } from "../../panelWebView/CommandToCode"; +import { BaseListener } from "./BaseListener"; + + +export class SettingsListener extends BaseListener { + + /** + * Process the messages for the dashboard views + * @param msg + */ + public static process(msg: { command: CommandToCode, data: any }) { + super.process(msg); + + switch(msg.command) { + case CommandToCode.getData: + this.getSettings(); + break; + case CommandToCode.openSettings: + this.openVSCodeSettings(); + break; + case CommandToCode.toggleWritingSettings: + this.toggleWritingSettings(); + break; + case CommandToCode.updateModifiedUpdating: + 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); + break; + case CommandToCode.updatePreviewUrl: + this.updateSetting(SETTING_PREVIEW_HOST, msg.data || ""); + break; + case CommandToCode.updateStartCommand: + this.updateSetting(SETTINGS_FRAMEWORK_START, msg.data || ""); + break; + } + } + + /** + * Retrieve the extension settings required to render the panel + */ + public static async getSettings() { + const panelSettings = await PanelSettings.get(); + this.sendMsg(Command.settings, panelSettings); + } + + /** + * Open the settings view of VS Code + */ + public static openVSCodeSettings() { + const isBeta = Extension.getInstance().isBetaVersion(); + commands.executeCommand('workbench.action.openSettings', `@ext:${isBeta ? EXTENSION_BETA_ID : EXTENSION_ID}`); + } + + /** + * Updates a setting and refreshes the retrieved settings + * @param setting + * @param value + */ + private static async updateSetting(setting: string, value: any) { + await Settings.update(setting, value); + this.getSettings(); + } + + /** + * Toggle the writing settings + */ + private static async toggleWritingSettings() { + const config = workspace.getConfiguration("", { languageId: "markdown" }); + const enabled = PanelSettings.isWritingSettingsEnabled(); + + await config.update("editor.fontSize", enabled ? undefined : 14, false, true); + await config.update("editor.lineHeight", enabled ? undefined : 26, false, true); + await config.update("editor.wordWrap", enabled ? undefined : "wordWrapColumn", false, true); + await config.update("editor.wordWrapColumn", enabled ? undefined : 64, false, true); + await config.update("editor.lineNumbers", enabled ? undefined : "off", false, true); + await config.update("editor.quickSuggestions", enabled ? undefined : false, false, true); + await config.update("editor.minimap.enabled", enabled ? undefined : false, false, true); + + this.getSettings(); + } +} \ No newline at end of file diff --git a/src/listeners/panel/TaxonomyListener.ts b/src/listeners/panel/TaxonomyListener.ts new file mode 100644 index 00000000..6b1fa5de --- /dev/null +++ b/src/listeners/panel/TaxonomyListener.ts @@ -0,0 +1,130 @@ +import { CommandToCode } from "../../panelWebView/CommandToCode"; +import { TagType } from "../../panelWebView/TagType"; +import { BaseListener } from "./BaseListener"; +import { window } from "vscode"; +import { ArticleHelper, Settings } from "../../helpers"; +import { CustomTaxonomyData, TaxonomyType } from "../../models"; +import { DataListener } from "."; +import { SETTING_TAXONOMY_CATEGORIES, SETTING_TAXONOMY_TAGS } from "../../constants"; + + +export class TaxonomyListener extends BaseListener { + + /** + * Process the messages for the dashboard views + * @param msg + */ + public static process(msg: { command: any, data: any }) { + super.process(msg); + + switch(msg.command) { + case CommandToCode.updateTags: + this.updateTags(TagType.tags, msg.data?.values || [], msg.data?.parents || []); + break; + case CommandToCode.updateCategories: + this.updateTags(TagType.categories, msg.data?.values || [], msg.data?.parents || []); + break; + case CommandToCode.updateKeywords: + this.updateTags(TagType.keywords, msg.data?.values || [], msg.data?.parents || []); + break; + case CommandToCode.updateCustomTaxonomy: + this.updateCustomTaxonomy(msg.data); + break; + case CommandToCode.addTagToSettings: + this.addTags(TagType.tags, msg.data); + break; + case CommandToCode.addCategoryToSettings: + this.addTags(TagType.categories, msg.data); + break; + case CommandToCode.addToCustomTaxonomy: + this.addCustomTaxonomy(msg.data); + break; + } + } + + /** + * Update the tags in the current document + * @param tagType + * @param values + */ + private static updateTags(tagType: TagType, values: string[], parents: string[]) { + const editor = window.activeTextEditor; + if (!editor) { + return ""; + } + + const article = ArticleHelper.getFrontMatter(editor); + if (article && article.data) { + + // Support multi-level fields + let parentObj = article.data; + for (const parent of parents || []) { + parentObj = parentObj[parent]; + } + + parentObj[tagType.toLowerCase()] = values || []; + ArticleHelper.update(editor, article); + DataListener.pushMetadata(article!.data); + } + } + + /** + * Update the tags in the current document + * @param data + */ + private static updateCustomTaxonomy(data: CustomTaxonomyData) { + if (!data?.id || !data?.name) { + return; + } + + const editor = window.activeTextEditor; + if (!editor) { + return ""; + } + + const article = ArticleHelper.getFrontMatter(editor); + if (article && article.data) { + + // Support multi-level fields + let parentObj = article.data; + for (const parent of data.parents || []) { + parentObj = parentObj[parent]; + } + + parentObj[data.name] = data.options || []; + ArticleHelper.update(editor, article); + DataListener.pushMetadata(article!.data); + } + } + + /** + * Add tag to the settings + * @param data + */ + private static async addCustomTaxonomy(data: CustomTaxonomyData) { + if (!data?.id || !data?.option) { + return; + } + + await Settings.updateCustomTaxonomy(data.id, data.option); + } + + /** + * Add tag to the settings + * @param tagType + * @param value + */ + private static async addTags(tagType: TagType, value: string) { + if (value) { + let options = tagType === TagType.tags ? Settings.get(SETTING_TAXONOMY_TAGS, true) : Settings.get(SETTING_TAXONOMY_CATEGORIES, true); + + if (!options) { + options = []; + } + + options.push(value); + const taxType = tagType === TagType.tags ? TaxonomyType.Tag : TaxonomyType.Category; + await Settings.updateTaxonomy(taxType, options); + } + } +} \ No newline at end of file diff --git a/src/listeners/panel/index.ts b/src/listeners/panel/index.ts new file mode 100644 index 00000000..d7c44fcc --- /dev/null +++ b/src/listeners/panel/index.ts @@ -0,0 +1,8 @@ +export * from './ArticleListener'; +export * from './BaseListener'; +export * from './DataListener'; +export * from './ExtensionListener'; +export * from './MediaListener'; +export * from './ScriptListener'; +export * from './SettingsListener'; +export * from './TaxonomyListener'; diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index b405a240..3da6353c 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -11,20 +11,21 @@ export interface PanelSettings { date: DateInfo; categories: string[]; customTaxonomy: CustomTaxonomy[]; - freeform: boolean; + freeform: boolean | undefined; scripts: CustomScript[]; isInitialized: boolean; modifiedDateUpdate: boolean; writingSettingsEnabled: boolean; - fmHighlighting: boolean; + fmHighlighting: boolean | undefined; preview: PreviewSettings; contentTypes: ContentType[]; dashboardViewData: DashboardData | undefined; - draftField: DraftField; + draftField: DraftField | undefined; isBacker: boolean | undefined; framework: string | undefined; commands: FrameworkCommands; dataTypes: DataType[] | undefined; + commaSeparatedFields: string[]; } export interface FrameworkCommands { @@ -70,8 +71,9 @@ export interface SEO { } export interface Slug { - prefix: number; - suffix: number; + prefix: number | string; + suffix: number | string; + updateFileName?: boolean; } export interface FolderInfo { diff --git a/src/models/index.ts b/src/models/index.ts index 6d34d5a8..d10b96b9 100644 --- a/src/models/index.ts +++ b/src/models/index.ts @@ -2,6 +2,9 @@ export * from './Choice'; export * from './ContentFolder'; export * from './CustomTaxonomyData'; export * from './DashboardData'; +export * from './DataFile'; +export * from './DataFolder'; +export * from './DataType'; export * from './DraftField'; export * from './Framework'; export * from './MediaPaths'; diff --git a/src/panelWebView/components/Metadata.tsx b/src/panelWebView/components/Metadata.tsx index c57bae5c..fa5a6de1 100644 --- a/src/panelWebView/components/Metadata.tsx +++ b/src/panelWebView/components/Metadata.tsx @@ -161,7 +161,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, icon={} crntSelected={parent[field.name] as string[] || []} options={settings?.tags || []} - freeform={settings.freeform} + freeform={settings.freeform || false} focussed={focusElm === TagType.tags} unsetFocus={unsetFocus} parents={parentFields} /> @@ -179,7 +179,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, icon={} crntSelected={selectedValues as string[] || []} options={taxonomyData?.options || []} - freeform={settings.freeform} + freeform={settings.freeform || false} focussed={focusElm === TagType.custom} unsetFocus={unsetFocus} fieldName={field.name} @@ -196,7 +196,7 @@ const Metadata: React.FunctionComponent = ({settings, metadata, icon={} crntSelected={parent.categories as string[] || []} options={settings.categories} - freeform={settings.freeform} + freeform={settings.freeform || false} focussed={focusElm === TagType.categories} unsetFocus={unsetFocus} parents={parentFields} /> @@ -210,8 +210,8 @@ const Metadata: React.FunctionComponent = ({settings, metadata, sendUpdate(field.name, value, parentFields)} />