From c75ab2a07be6388f8211e5f76b64635ffaaf6023 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 12 Dec 2022 09:02:14 +0100 Subject: [PATCH 01/20] Update sponsor --- README.md | 18 +++++++++++++----- 1 file changed, 13 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 08ce0bf5..a8554ad2 100644 --- a/README.md +++ b/README.md @@ -163,21 +163,29 @@ You can open showcase issues for the following things:

- + Front Matter contributors

## 🖤 Backers & Sponsors 👇 🤘

- + Front Matter sponsors +

+ +
+ +

+ + Powered by Vercel +


- - + + Supported by the BEJS Community

@@ -190,6 +198,6 @@ You can open showcase issues for the following things:

- + Front Matter visitors

\ No newline at end of file From 75b01cde7b29f2416917bff9c263f66a6506398c Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 13 Dec 2022 12:00:01 +0100 Subject: [PATCH 02/20] #484 - Script support for different environments --- CHANGELOG.md | 12 ++++++++++++ package.json | 20 ++++++++++++++++++++ src/helpers/CustomScript.ts | 28 ++++++++++++++++++++++++---- src/models/PanelSettings.ts | 9 +++++++++ 4 files changed, 65 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 482c901d..ca9f34e2 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## [8.3.0] - 2022-xx-xx + +### ✨ New features + +### 🎨 Enhancements + +- [#484](https://github.com/estruyf/vscode-front-matter/issues/484): Support for overriding scripts per environment type + +### ⚡️ Optimizations + +### 🐞 Fixes + ## [8.2.0] - 2022-12-08 - [Release notes](https://beta.frontmatter.codes/updates/v8.2.0) ### ✨ New features diff --git a/package.json b/package.json index 45ea8eb7..361508bd 100644 --- a/package.json +++ b/package.json @@ -451,6 +451,26 @@ "type": "boolean", "description": "Hide the action from the UI", "default": false + }, + "environments": { + "type": "array", + "items": { + "type": "object", + "properties": { + "type": { + "type": "string", + "enum": ["macos", "linux", "windows"], + "description": "The environment type for which the script needs to be used" + }, + "script": { + "type": "string", + "description": "Path to the script to execute" + }, + "command": { + "$ref": "#scriptCommand" + } + } + } } }, "additionalProperties": false, diff --git a/src/helpers/CustomScript.ts b/src/helpers/CustomScript.ts index e80c893c..e14a1d24 100644 --- a/src/helpers/CustomScript.ts +++ b/src/helpers/CustomScript.ts @@ -1,5 +1,5 @@ import { Settings } from './SettingsHelper'; -import { CommandType } from './../models/PanelSettings'; +import { CommandType, EnvironmentType } from './../models/PanelSettings'; import { CustomScript as ICustomScript, ScriptType } from '../models/PanelSettings'; import { window, env as vscodeEnv, ProgressLocation } from 'vscode'; import { ArticleHelper, Logger, Telemetry } from '.'; @@ -186,7 +186,8 @@ export class CustomScript { try { let articleData = ""; if (os.type() === "Windows_NT") { - articleData = `"${JSON.stringify(article?.data).replace(/"/g, `""`)}"`; + const jsonData = JSON.stringify(article?.data) + articleData = `'${jsonData.replace(/"/g, `\"`)}'`; } else { articleData = JSON.stringify(article?.data).replace(/'/g, "%27"); articleData = `'${articleData}'`; @@ -269,14 +270,33 @@ export class CustomScript { */ public static async executeScript(script: ICustomScript, wsPath: string, args: string): Promise { return new Promise(async (resolve, reject) => { - + const osType = os.type(); + // Check the command to use let command = script.nodeBin || "node"; if (script.command && script.command !== CommandType.Node) { command = script.command; } - const scriptPath = join(wsPath, script.script); + let scriptPath = join(wsPath, script.script); + + // Check if there is an environments overwrite required + if (script.environments) { + let crntType: EnvironmentType | null = null; + if (osType === "Windows_NT") { + crntType = "windows" + } else if (osType === "Darwin") { + crntType = "macos" + } else { + crntType = "linux" + } + + const environment = script.environments.find(e => e.type === crntType); + if (environment && environment.script && environment.command) { + command = environment.command; + scriptPath = join(wsPath, environment.script); + } + } if (!await existsAsync(scriptPath)) { reject(new Error(`Script not found: ${scriptPath}`)); diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index b438fcff..647e919d 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -148,6 +148,15 @@ export interface CustomScript { type?: ScriptType; command?: CommandType | string; hidden?: boolean; + environments?: EnvironmentScript[]; +} + +export type EnvironmentType = "windows" | "macos" | "linux"; + +export interface EnvironmentScript { + type: EnvironmentType; + script: string; + command: CommandType | string; } export interface PreviewSettings { From 8b3889f997de5032426a396a7faaaa27b4babc0c Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 14 Dec 2022 09:00:53 +0100 Subject: [PATCH 03/20] #484 - validate if command is available --- src/helpers/CustomScript.ts | 33 ++++++++++++++++++++++++++++++--- 1 file changed, 30 insertions(+), 3 deletions(-) diff --git a/src/helpers/CustomScript.ts b/src/helpers/CustomScript.ts index e14a1d24..5aaecc19 100644 --- a/src/helpers/CustomScript.ts +++ b/src/helpers/CustomScript.ts @@ -4,7 +4,7 @@ import { CustomScript as ICustomScript, ScriptType } from '../models/PanelSettin import { window, env as vscodeEnv, ProgressLocation } from 'vscode'; import { ArticleHelper, Logger, Telemetry } from '.'; import { Folders } from '../commands/Folders'; -import { exec } from 'child_process'; +import { exec, execSync } from 'child_process'; import * as os from 'os'; import { join } from 'path'; import { Notifications } from './Notifications'; @@ -293,8 +293,10 @@ export class CustomScript { const environment = script.environments.find(e => e.type === crntType); if (environment && environment.script && environment.command) { - command = environment.command; - scriptPath = join(wsPath, environment.script); + if (await CustomScript.validateCommand(environment.command)) { + command = environment.command; + scriptPath = join(wsPath, environment.script); + } } } @@ -321,4 +323,29 @@ export class CustomScript { }); }); } + + /** + * Validate if the command is exists + * @param command + * @returns + */ + private static async validateCommand(command: string) { + try { + return new Promise((resolve, reject) => { + exec(command, (error, stdout) => { + console.log(error, stdout); + + if (error) { + resolve(false); + return; + } + + resolve(true); + }); + }); + } catch (e) { + Logger.error(`Invalid command: ${command}`); + return false; + } + } } \ No newline at end of file From 85c4a869e37c74c9339202e8e182349eb8d95094 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 14 Dec 2022 09:40:32 +0100 Subject: [PATCH 04/20] #484 - Workspace path update --- src/helpers/CustomScript.ts | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/src/helpers/CustomScript.ts b/src/helpers/CustomScript.ts index 5aaecc19..f3ad84e3 100644 --- a/src/helpers/CustomScript.ts +++ b/src/helpers/CustomScript.ts @@ -3,7 +3,7 @@ import { CommandType, EnvironmentType } from './../models/PanelSettings'; import { CustomScript as ICustomScript, ScriptType } from '../models/PanelSettings'; import { window, env as vscodeEnv, ProgressLocation } from 'vscode'; import { ArticleHelper, Logger, Telemetry } from '.'; -import { Folders } from '../commands/Folders'; +import { Folders, WORKSPACE_PLACEHOLDER } from '../commands/Folders'; import { exec, execSync } from 'child_process'; import * as os from 'os'; import { join } from 'path'; @@ -279,7 +279,10 @@ export class CustomScript { } let scriptPath = join(wsPath, script.script); - + if (script.script.includes(WORKSPACE_PLACEHOLDER)) { + scriptPath = Folders.getAbsFilePath(script.script); + } + // Check if there is an environments overwrite required if (script.environments) { let crntType: EnvironmentType | null = null; @@ -296,6 +299,9 @@ export class CustomScript { if (await CustomScript.validateCommand(environment.command)) { command = environment.command; scriptPath = join(wsPath, environment.script); + if (environment.script.includes(WORKSPACE_PLACEHOLDER)) { + scriptPath = Folders.getAbsFilePath(environment.script); + } } } } @@ -331,18 +337,9 @@ export class CustomScript { */ private static async validateCommand(command: string) { try { - return new Promise((resolve, reject) => { - exec(command, (error, stdout) => { - console.log(error, stdout); + execSync(command); - if (error) { - resolve(false); - return; - } - - resolve(true); - }); - }); + return true; } catch (e) { Logger.error(`Invalid command: ${command}`); return false; From fc1d750c5e421588bf4f5a444257474f9c35f8d8 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 14 Dec 2022 09:45:17 +0100 Subject: [PATCH 05/20] #470 - Fix initialize project dashboard description --- CHANGELOG.md | 2 ++ src/dashboardWebView/components/Steps/StepsToGetStarted.tsx | 2 +- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ca9f34e2..8fd5a246 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -12,6 +12,8 @@ ### 🐞 Fixes +- [#470](https://github.com/estruyf/vscode-front-matter/issues/470): Fix `initialize project` dashboard description + ## [8.2.0] - 2022-12-08 - [Release notes](https://beta.frontmatter.codes/updates/v8.2.0) ### ✨ New features diff --git a/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx b/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx index bb5f67c1..4f2ce1e1 100644 --- a/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx +++ b/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx @@ -66,7 +66,7 @@ export const StepsToGetStarted: React.FunctionComponent { id: `welcome-init`, name: 'Initialize project', - description: <>Initialize the project with a template folder and sample markdown file. The template folder can be used to define your own templates. Start by clicking on this action., + description: <>Initialize the project will create the required files and folders for using the Front Matter CMS. Start by clicking on this action., status: settings.initialized ? Status.Completed : Status.NotStarted, onClick: settings.initialized ? undefined : () => { Messenger.send(DashboardMessage.initializeProject); } }, From 12559bae4a69b54a86d7fc203236a7a650456711 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 14 Dec 2022 10:17:47 +0100 Subject: [PATCH 06/20] #482 - Default content type description update --- CHANGELOG.md | 1 + src/helpers/ContentType.ts | 2 +- 2 files changed, 2 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8fd5a246..d0bc354d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -13,6 +13,7 @@ ### 🐞 Fixes - [#470](https://github.com/estruyf/vscode-front-matter/issues/470): Fix `initialize project` dashboard description +- [#482](https://github.com/estruyf/vscode-front-matter/issues/482): Update the description when you want to overwrite the default content type description ## [8.2.0] - 2022-12-08 - [Release notes](https://beta.frontmatter.codes/updates/v8.2.0) diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 351d8493..85153a81 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -121,7 +121,7 @@ export class ContentType { const override = await window.showQuickPick(["Yes", "No"], { title: "Override default content type", - placeHolder: "Do you want to override the default content type?", + placeHolder: "Do you want to overwrite the default content type configuration with the fields used in the current field?", ignoreFocusOut: true }); const overrideBool = override === "Yes"; From 1764965aa7ecf28028e209b65367a65b641acffa Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Wed, 14 Dec 2022 10:18:22 +0100 Subject: [PATCH 07/20] 8.3.0 --- package-lock.json | 4 ++-- package.json | 18 ++++++++++++++---- 2 files changed, 16 insertions(+), 6 deletions(-) diff --git a/package-lock.json b/package-lock.json index b0f494ba..b24e2dbe 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-front-matter-beta", - "version": "8.2.0", + "version": "8.3.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-front-matter-beta", - "version": "8.2.0", + "version": "8.3.0", "license": "MIT", "dependencies": { "node-fetch": "^2.6.7" diff --git a/package.json b/package.json index 361508bd..5accf506 100644 --- a/package.json +++ b/package.json @@ -3,7 +3,7 @@ "displayName": "Front Matter CMS", "description": "Front Matter is a CMS that runs within Visual Studio Code. It gives you the power and control of a full-blown CMS while also providing you the flexibility and speed of the static site generator of your choice like: Hugo, Jekyll, Docusaurus, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", - "version": "8.2.0", + "version": "8.3.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { @@ -208,7 +208,10 @@ "description": "Defines a custom preview path for the folder." }, "filePrefix": { - "type": [ "null", "string" ], + "type": [ + "null", + "string" + ], "description": "Defines a prefix for the file name." }, "contentTypes": { @@ -459,7 +462,11 @@ "properties": { "type": { "type": "string", - "enum": ["macos", "linux", "windows"], + "enum": [ + "macos", + "linux", + "windows" + ], "description": "The environment type for which the script needs to be used" }, "script": { @@ -482,7 +489,10 @@ "scope": "Custom scripts" }, "frontMatter.dashboard.content.pagination": { - "type": ["boolean", "number"], + "type": [ + "boolean", + "number" + ], "default": true, "markdownDescription": "Specify if you want to enable/disable pagination for your content. You can define your page number up to 52. Default items per page is `16`. Disabling the pagination can be done by setting it to `false`. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.content.pagination)", "scope": "Dashboard" From cc2c6dc217b2f370c5eeb50cdfdb7fb7ebd994e7 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 22 Dec 2022 18:03:04 +0100 Subject: [PATCH 08/20] Allow external configurations - start --- package.json | 8 ++ scripts/settings-export.js | 43 ++++++++++ src/constants/settings.ts | 2 + src/helpers/SettingsHelper.ts | 148 +++++++++++++++++++++++++--------- 4 files changed, 165 insertions(+), 36 deletions(-) create mode 100644 scripts/settings-export.js diff --git a/package.json b/package.json index 5accf506..4015b7f5 100644 --- a/package.json +++ b/package.json @@ -79,6 +79,14 @@ "configuration": { "title": "Front Matter: use frontmatter.json for shared team settings", "properties": { + "frontMatter.extends": { + "type": "array", + "markdownDescription": "Specify the list of paths/URLs to extend the Front Matter CMS config. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.extends)", + "default": [], + "items": { + "type": "string" + } + }, "frontMatter.content.autoUpdateDate": { "type": "boolean", "default": false, diff --git a/scripts/settings-export.js b/scripts/settings-export.js new file mode 100644 index 00000000..0dea78ef --- /dev/null +++ b/scripts/settings-export.js @@ -0,0 +1,43 @@ + + +const packageJson = require('../package.json'); + +for (const key of Object.keys(packageJson.contributes.configuration.properties)) { + const type = packageJson.contributes.configuration.properties[key].type; + + if (type.includes('object') || type.includes('array')) { + console.log(`${key} - ${packageJson.contributes.configuration.properties[key].type}`); + } +} + +// TO IGNORE +// frontMatter.extends - array +// frontMatter.dashboard.mediaSnippet - array + +// TO PROCESS AS A WHOLE OBJECT +// frontMatter.content.draftField - object +// frontMatter.content.supportedFileTypes - array +// frontMatter.global.notifications - array +// frontMatter.global.disabledNotificaitons - array +// frontMatter.media.supportedMimeTypes - array +// frontMatter.taxonomy.commaSeparatedFields - array + +// MERGE ARRAYS +// frontMatter.taxonomy.categories - array +// frontMatter.taxonomy.tags - array +// frontMatter.taxonomy.noPropertyValueQuotes - array + +// PROCESS ITEM BY ITEM +// frontMatter.content.pageFolders - array - path +// frontMatter.content.placeholders - array - id +// frontMatter.content.sorting - array - id +// frontMatter.custom.scripts - array - id +// frontMatter.data.files - array - id +// frontMatter.data.folders - array - id +// frontMatter.data.types - array - id +// frontMatter.global.modes - array - id +// frontMatter.taxonomy.fieldGroups - array - id +// frontMatter.taxonomy.customTaxonomy - array - id +// frontMatter.taxonomy.contentTypes - array,null - name + +// frontMatter.content.snippets - object \ No newline at end of file diff --git a/src/constants/settings.ts b/src/constants/settings.ts index ca046281..740aac2b 100644 --- a/src/constants/settings.ts +++ b/src/constants/settings.ts @@ -2,6 +2,8 @@ export const EXTENSION_NAME = "Front Matter"; export const CONFIG_KEY = "frontMatter"; +export const SETTING_EXTENDS = "extends"; + export const SETTING_GLOBAL_NOTIFICATIONS = "global.notifications"; export const SETTING_GLOBAL_NOTIFICATIONS_DISABLED = "global.disabledNotifications"; export const SETTING_GLOBAL_MODES = "global.modes"; diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 04e69e03..5b26d163 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -3,8 +3,8 @@ import { Telemetry } from './Telemetry'; import { Notifications } from './Notifications'; import { commands, Uri, workspace, window } from 'vscode'; import * as vscode from 'vscode'; -import { ContentFolder, ContentType, CustomPlaceholder, CustomTaxonomy, DataFile, DataFolder, DataType, TaxonomyType } from '../models'; -import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY, CONTEXT, ExtensionState, SETTING_TAXONOMY_CUSTOM, TelemetryEvent, COMMAND_NAME, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_SNIPPETS, SETTING_CONTENT_PLACEHOLDERS, SETTING_CUSTOM_SCRIPTS, SETTING_DATA_FILES, SETTING_DATA_TYPES, SETTING_DATA_FOLDERS } from '../constants'; +import { ContentType, CustomTaxonomy, TaxonomyType } from '../models'; +import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY, CONTEXT, ExtensionState, SETTING_TAXONOMY_CUSTOM, TelemetryEvent, COMMAND_NAME, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_SNIPPETS, SETTING_CONTENT_PLACEHOLDERS, SETTING_CUSTOM_SCRIPTS, SETTING_DATA_FILES, SETTING_DATA_TYPES, SETTING_DATA_FOLDERS, SETTING_EXTENDS } from '../constants'; import { Folders } from '../commands/Folders'; import { join, basename, dirname, parse } from 'path'; import { existsSync } from 'fs'; @@ -13,6 +13,7 @@ import { debounceCallback } from './DebounceCallback'; import { Logger } from './Logger'; import * as jsoncParser from 'jsonc-parser'; import { existsAsync, readFileAsync, writeFileAsync } from '../utils'; +import fetch from 'node-fetch'; export class Settings { public static globalFile = "frontmatter.json"; @@ -432,6 +433,9 @@ export class Settings { Settings.globalConfig = undefined; } + // Check if the config got external configs + await Settings.processExternalConfig(); + // Read the files from the config folder let configFiles = await workspace.findFiles(`**/${Settings.globalConfigFolder}/**/*.json`); if (configFiles.length === 0) { @@ -440,7 +444,6 @@ export class Settings { // Sort the files by fsPath configFiles = configFiles.sort((a, b) => a.fsPath.localeCompare(b.fsPath)); - for await (const configFile of configFiles) { await Settings.processConfigFile(configFile); } @@ -453,6 +456,70 @@ export class Settings { Settings.readConfigPromise = undefined; } + /** + * Process the external configs + */ + private static async processExternalConfig() { + const extendsConfigName = `${CONFIG_KEY}.${SETTING_EXTENDS}`; + if (!Settings.globalConfig || !Settings.globalConfig[extendsConfigName]) { + return; + } + + const originalConfig = Object.assign({}, Settings.globalConfig); + const extendsConfig: string[] = Settings.globalConfig[extendsConfigName]; + for (const externalConfig of extendsConfig) { + if (externalConfig.endsWith(`.json`)) { + let config: any = undefined; + + if (externalConfig.startsWith('https://')) { + try { + const response = await fetch(externalConfig); + if (response.ok) { + config = await response.json(); + } + } catch (e) { + Logger.error(`Error fetching external config "${externalConfig}".`); + } + } else { + const configPath = join(Folders.getWorkspaceFolder()?.fsPath || '', externalConfig); + if (await existsAsync(configPath)) { + const configTxt = await readFileAsync(configPath, 'utf8'); + config = jsoncParser.parse(configTxt); + } else { + Logger.error(`External config "${externalConfig}" not found.`); + } + } + + // Check if the config contains data and loop through it + if (config) { + // We need to loop through the config to make sure the objects and arrays are merged + for (const key in config) { + if (config.hasOwnProperty(key)) { + const value = config[key]; + + if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + if (typeof originalConfig[key] === 'undefined') { + Settings.globalConfig[key] = value; + } + } else if (typeof value === 'object' && value !== null) { + // Check if array + if (Array.isArray(value)) { + for (const item of value) { + Settings.updateGlobalConfigSetting(key.replace(`${CONFIG_KEY}.`, ''), item); + } + } else { + for (const itemKey in value) { + // Process the object key/item + } + } + } + } + } + } + } + } + } + /** * Process the config file * @param configFile @@ -482,45 +549,54 @@ export class Settings { Settings.globalConfig = {}; } - // Array settings - if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CUSTOM_SCRIPTS)) { - const crntValue = Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CUSTOM_SCRIPTS}`] || []; - Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CUSTOM_SCRIPTS}`] = [...crntValue, configJson]; - } - // Content types - else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_TAXONOMY_CONTENT_TYPES)) { - Settings.updateGlobalConfigArraySetting(SETTING_TAXONOMY_CONTENT_TYPES, "name", configJson); - } - // Data files - else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_DATA_FILES)) { - Settings.updateGlobalConfigArraySetting(SETTING_DATA_FILES, "id", configJson); - } - // Data folders - else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_DATA_FOLDERS)) { - Settings.updateGlobalConfigArraySetting(SETTING_DATA_FOLDERS, "id", configJson); - } - // Data types - else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_DATA_TYPES)) { - Settings.updateGlobalConfigArraySetting(SETTING_DATA_TYPES, "id", configJson); - } - // Page folders - else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_PAGE_FOLDERS)) { - Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_PAGE_FOLDERS, "path", configJson); - } - // Placeholders - else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_PLACEHOLDERS)) { - Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_PLACEHOLDERS, "id", configJson); - } - // Object settings - else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_SNIPPETS)) { - Settings.updateGlobalConfigObjectByNameSetting(SETTING_CONTENT_SNIPPETS, configFilePath, configJson, filePath); - } + Settings.updateGlobalConfigSetting(relSettingName, configJson, configFilePath, filePath); } catch (e) { Logger.error(`Error reading config file: ${configFile.fsPath}`); Logger.error((e as Error).message); } } + /** + * Update the global config array/object settings + * @param relSettingName + * @param configJson + */ + private static updateGlobalConfigSetting(relSettingName: string, configJson: any, configFilePath?: string, filePath?: string): void { + // Array settings + if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CUSTOM_SCRIPTS)) { + const crntValue = Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CUSTOM_SCRIPTS}`] || []; + Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CUSTOM_SCRIPTS}`] = [...crntValue, configJson]; + } + // Content types + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_TAXONOMY_CONTENT_TYPES)) { + Settings.updateGlobalConfigArraySetting(SETTING_TAXONOMY_CONTENT_TYPES, "name", configJson); + } + // Data files + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_DATA_FILES)) { + Settings.updateGlobalConfigArraySetting(SETTING_DATA_FILES, "id", configJson); + } + // Data folders + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_DATA_FOLDERS)) { + Settings.updateGlobalConfigArraySetting(SETTING_DATA_FOLDERS, "id", configJson); + } + // Data types + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_DATA_TYPES)) { + Settings.updateGlobalConfigArraySetting(SETTING_DATA_TYPES, "id", configJson); + } + // Page folders + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_PAGE_FOLDERS)) { + Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_PAGE_FOLDERS, "path", configJson); + } + // Placeholders + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_PLACEHOLDERS)) { + Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_PLACEHOLDERS, "id", configJson); + } + // Snippets + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_SNIPPETS) && configFilePath && filePath) { + Settings.updateGlobalConfigObjectByNameSetting(SETTING_CONTENT_SNIPPETS, configFilePath, configJson, filePath); + } + } + /** * Check if the setting name is equal or starts with the reference setting name * @param value From 86a8ef68b63e9671da1e31b03733aa22e9d2ef9d Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 22 Dec 2022 21:04:24 +0100 Subject: [PATCH 09/20] Settings override --- scripts/settings-export.js | 12 +++---- src/helpers/SettingsHelper.ts | 68 +++++++++++++++++++++++++++++------ 2 files changed, 63 insertions(+), 17 deletions(-) diff --git a/scripts/settings-export.js b/scripts/settings-export.js index 0dea78ef..047b8f06 100644 --- a/scripts/settings-export.js +++ b/scripts/settings-export.js @@ -1,5 +1,3 @@ - - const packageJson = require('../package.json'); for (const key of Object.keys(packageJson.contributes.configuration.properties)) { @@ -28,16 +26,18 @@ for (const key of Object.keys(packageJson.contributes.configuration.properties)) // frontMatter.taxonomy.noPropertyValueQuotes - array // PROCESS ITEM BY ITEM -// frontMatter.content.pageFolders - array - path -// frontMatter.content.placeholders - array - id -// frontMatter.content.sorting - array - id // frontMatter.custom.scripts - array - id +// frontMatter.taxonomy.contentTypes - array,null - name // frontMatter.data.files - array - id // frontMatter.data.folders - array - id // frontMatter.data.types - array - id +// frontMatter.content.pageFolders - array - path +// frontMatter.content.placeholders - array - id +// frontMatter.content.sorting - array - id // frontMatter.global.modes - array - id // frontMatter.taxonomy.fieldGroups - array - id // frontMatter.taxonomy.customTaxonomy - array - id -// frontMatter.taxonomy.contentTypes - array,null - name + + // frontMatter.content.snippets - object \ No newline at end of file diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 5b26d163..4506879f 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -4,7 +4,7 @@ import { Notifications } from './Notifications'; import { commands, Uri, workspace, window } from 'vscode'; import * as vscode from 'vscode'; import { ContentType, CustomTaxonomy, TaxonomyType } from '../models'; -import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY, CONTEXT, ExtensionState, SETTING_TAXONOMY_CUSTOM, TelemetryEvent, COMMAND_NAME, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_SNIPPETS, SETTING_CONTENT_PLACEHOLDERS, SETTING_CUSTOM_SCRIPTS, SETTING_DATA_FILES, SETTING_DATA_TYPES, SETTING_DATA_FOLDERS, SETTING_EXTENDS } from '../constants'; +import { SETTING_TAXONOMY_TAGS, SETTING_TAXONOMY_CATEGORIES, CONFIG_KEY, CONTEXT, ExtensionState, SETTING_TAXONOMY_CUSTOM, TelemetryEvent, COMMAND_NAME, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_CONTENT_PAGE_FOLDERS, SETTING_CONTENT_SNIPPETS, SETTING_CONTENT_PLACEHOLDERS, SETTING_CUSTOM_SCRIPTS, SETTING_DATA_FILES, SETTING_DATA_TYPES, SETTING_DATA_FOLDERS, SETTING_EXTENDS, SETTING_CONTENT_SORTING, SETTING_GLOBAL_MODES, SETTING_TAXONOMY_FIELD_GROUPS, SETTING_CONTENT_DRAFT_FIELD, SETTING_CONTENT_SUPPORTED_FILETYPES, SETTING_GLOBAL_NOTIFICATIONS, SETTING_GLOBAL_NOTIFICATIONS_DISABLED, SETTING_MEDIA_SUPPORTED_MIMETYPES, SETTING_COMMA_SEPARATED_FIELDS, SETTING_REMOVE_QUOTES } from '../constants'; import { Folders } from '../commands/Folders'; import { join, basename, dirname, parse } from 'path'; import { existsSync } from 'fs'; @@ -496,20 +496,50 @@ export class Settings { for (const key in config) { if (config.hasOwnProperty(key)) { const value = config[key]; + const settingName = key.replace(`${CONFIG_KEY}.`, ''); - if (typeof value === 'string' || typeof value === 'number' || typeof value === 'boolean') { + if (typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean') { if (typeof originalConfig[key] === 'undefined') { Settings.globalConfig[key] = value; } - } else if (typeof value === 'object' && value !== null) { + } + // Objects and arrays to override + else if (settingName === SETTING_CONTENT_DRAFT_FIELD || + settingName === SETTING_CONTENT_SUPPORTED_FILETYPES || + settingName === SETTING_GLOBAL_NOTIFICATIONS || + settingName === SETTING_GLOBAL_NOTIFICATIONS_DISABLED || + settingName === SETTING_MEDIA_SUPPORTED_MIMETYPES || + settingName === SETTING_COMMA_SEPARATED_FIELDS) { + if (typeof originalConfig[key] === 'undefined') { + Settings.globalConfig[key] = value; + } + } + else if (typeof value === 'object' && value !== null) { // Check if array if (Array.isArray(value)) { - for (const item of value) { - Settings.updateGlobalConfigSetting(key.replace(`${CONFIG_KEY}.`, ''), item); + if (settingName === SETTING_TAXONOMY_CATEGORIES || + settingName === SETTING_TAXONOMY_TAGS || + settingName === SETTING_REMOVE_QUOTES) { + // Merge the arrays + Settings.globalConfig[key] = [...(Settings.globalConfig[key] || []), ...(originalConfig[key] || []), ...value]; + // Filter out the doubles + Settings.globalConfig[key] = Settings.globalConfig[key].filter((item: any, index: number) => { + return Settings.globalConfig[key].indexOf(item) === index; + }, Settings.globalConfig[key]); + } else { + for (const item of value) { + Settings.updateGlobalConfigSetting(settingName, item); + } } - } else { + } else if (settingName === SETTING_CONTENT_SNIPPETS) { for (const itemKey in value) { - // Process the object key/item + const crntValue = Settings.globalConfig[key] || {}; + + if (!crntValue[itemKey]) { + Settings.globalConfig[key] = { ...crntValue, ...{ [itemKey]: value[itemKey] } }; + } } } } @@ -562,10 +592,11 @@ export class Settings { * @param configJson */ private static updateGlobalConfigSetting(relSettingName: string, configJson: any, configFilePath?: string, filePath?: string): void { - // Array settings + // Custom scripts if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CUSTOM_SCRIPTS)) { - const crntValue = Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CUSTOM_SCRIPTS}`] || []; - Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CUSTOM_SCRIPTS}`] = [...crntValue, configJson]; + // const crntValue = Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CUSTOM_SCRIPTS}`] || []; + // Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CUSTOM_SCRIPTS}`] = [...crntValue, configJson]; + Settings.updateGlobalConfigArraySetting(SETTING_CUSTOM_SCRIPTS, "id", configJson); } // Content types else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_TAXONOMY_CONTENT_TYPES)) { @@ -591,6 +622,22 @@ export class Settings { else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_PLACEHOLDERS)) { Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_PLACEHOLDERS, "id", configJson); } + // Sorting + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_SORTING)) { + Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_SORTING, "id", configJson); + } + // Modes + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_GLOBAL_MODES)) { + Settings.updateGlobalConfigArraySetting(SETTING_GLOBAL_MODES, "id", configJson); + } + // Field groups + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_TAXONOMY_FIELD_GROUPS)) { + Settings.updateGlobalConfigArraySetting(SETTING_TAXONOMY_FIELD_GROUPS, "id", configJson); + } + // Custom taxonomy + else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_TAXONOMY_CUSTOM)) { + Settings.updateGlobalConfigArraySetting(SETTING_TAXONOMY_CUSTOM, "id", configJson); + } // Snippets else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CONTENT_SNIPPETS) && configFilePath && filePath) { Settings.updateGlobalConfigObjectByNameSetting(SETTING_CONTENT_SNIPPETS, configFilePath, configJson, filePath); @@ -619,7 +666,6 @@ export class Settings { private static updateGlobalConfigArraySetting(settingName: string, fieldName: string, configJson: any): void { const crntValue: T[] = Settings.globalConfig[`${CONFIG_KEY}.${settingName}`] || []; - // Check if folder is already added const itemIdx = crntValue.findIndex((item: any) => item[fieldName] === configJson[fieldName]); if (itemIdx === -1) { crntValue.push(configJson); From cfa805add99c43f7eb4643102b053794936a9f13 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 23 Dec 2022 10:26:20 +0100 Subject: [PATCH 10/20] #407 - support for external config files --- src/commands/Cache.ts | 11 ++ src/constants/ExtensionState.ts | 4 + src/helpers/SettingsHelper.ts | 210 +++++++++++++++++++------------- src/utils/fetchWithTimeout.ts | 13 ++ src/utils/index.ts | 1 + 5 files changed, 157 insertions(+), 82 deletions(-) create mode 100644 src/utils/fetchWithTimeout.ts diff --git a/src/commands/Cache.ts b/src/commands/Cache.ts index 5175f5b8..67b6b387 100644 --- a/src/commands/Cache.ts +++ b/src/commands/Cache.ts @@ -13,11 +13,22 @@ export class Cache { ); } + public static async get(key: string, type: "workspace" | "global"): Promise { + const ext = Extension.getInstance(); + const cache = await ext.getState(key, type); + return cache || undefined; + } + + public static async set(key: string, data: any, type: "workspace" | "global") { + await Extension.getInstance().setState(key, data, "workspace"); + } + private static async clear() { const ext = Extension.getInstance(); await ext.setState(ExtensionState.Dashboard.Pages.Cache, undefined, "workspace"); await ext.setState(ExtensionState.Dashboard.Pages.Index, undefined, "workspace"); + await ext.setState(ExtensionState.Settings.Extends, undefined, "workspace"); Notifications.info("Cache cleared"); } diff --git a/src/constants/ExtensionState.ts b/src/constants/ExtensionState.ts index 76aad254..c72eac71 100644 --- a/src/constants/ExtensionState.ts +++ b/src/constants/ExtensionState.ts @@ -19,6 +19,10 @@ export const ExtensionState = { } }, + Settings: { + Extends: `frontMatter:Settings:Extends`, + }, + Updates: { v7_0_0: { dateFields: `frontMatter:Updates:v7.0.0:dateFields` diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 4506879f..610991f6 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -12,8 +12,8 @@ import { Extension } from './Extension'; import { debounceCallback } from './DebounceCallback'; import { Logger } from './Logger'; import * as jsoncParser from 'jsonc-parser'; -import { existsAsync, readFileAsync, writeFileAsync } from '../utils'; -import fetch from 'node-fetch'; +import { existsAsync, fetchWithTimeout, readFileAsync, writeFileAsync } from '../utils'; +import { Cache } from '../commands'; export class Settings { public static globalFile = "frontmatter.json"; @@ -469,83 +469,8 @@ export class Settings { const extendsConfig: string[] = Settings.globalConfig[extendsConfigName]; for (const externalConfig of extendsConfig) { if (externalConfig.endsWith(`.json`)) { - let config: any = undefined; - - if (externalConfig.startsWith('https://')) { - try { - const response = await fetch(externalConfig); - if (response.ok) { - config = await response.json(); - } - } catch (e) { - Logger.error(`Error fetching external config "${externalConfig}".`); - } - } else { - const configPath = join(Folders.getWorkspaceFolder()?.fsPath || '', externalConfig); - if (await existsAsync(configPath)) { - const configTxt = await readFileAsync(configPath, 'utf8'); - config = jsoncParser.parse(configTxt); - } else { - Logger.error(`External config "${externalConfig}" not found.`); - } - } - - // Check if the config contains data and loop through it - if (config) { - // We need to loop through the config to make sure the objects and arrays are merged - for (const key in config) { - if (config.hasOwnProperty(key)) { - const value = config[key]; - const settingName = key.replace(`${CONFIG_KEY}.`, ''); - - if (typeof value === 'string' || - typeof value === 'number' || - typeof value === 'boolean') { - if (typeof originalConfig[key] === 'undefined') { - Settings.globalConfig[key] = value; - } - } - // Objects and arrays to override - else if (settingName === SETTING_CONTENT_DRAFT_FIELD || - settingName === SETTING_CONTENT_SUPPORTED_FILETYPES || - settingName === SETTING_GLOBAL_NOTIFICATIONS || - settingName === SETTING_GLOBAL_NOTIFICATIONS_DISABLED || - settingName === SETTING_MEDIA_SUPPORTED_MIMETYPES || - settingName === SETTING_COMMA_SEPARATED_FIELDS) { - if (typeof originalConfig[key] === 'undefined') { - Settings.globalConfig[key] = value; - } - } - else if (typeof value === 'object' && value !== null) { - // Check if array - if (Array.isArray(value)) { - if (settingName === SETTING_TAXONOMY_CATEGORIES || - settingName === SETTING_TAXONOMY_TAGS || - settingName === SETTING_REMOVE_QUOTES) { - // Merge the arrays - Settings.globalConfig[key] = [...(Settings.globalConfig[key] || []), ...(originalConfig[key] || []), ...value]; - // Filter out the doubles - Settings.globalConfig[key] = Settings.globalConfig[key].filter((item: any, index: number) => { - return Settings.globalConfig[key].indexOf(item) === index; - }, Settings.globalConfig[key]); - } else { - for (const item of value) { - Settings.updateGlobalConfigSetting(settingName, item); - } - } - } else if (settingName === SETTING_CONTENT_SNIPPETS) { - for (const itemKey in value) { - const crntValue = Settings.globalConfig[key] || {}; - - if (!crntValue[itemKey]) { - Settings.globalConfig[key] = { ...crntValue, ...{ [itemKey]: value[itemKey] } }; - } - } - } - } - } - } - } + const config = await Settings.getExternalConfig(externalConfig); + await Settings.extendConfig(config, originalConfig); } } } @@ -586,6 +511,72 @@ export class Settings { } } + /** + * Extend the config with external config data + * @param config + * @param originalConfig The original config data is used to make sure we don't override settings coming from the fontmatter.json file. + * @returns + */ + private static async extendConfig(config: any, originalConfig: any) { + if (!config) { + return; + } + + // We need to loop through the config to make sure the objects and arrays are merged + for (const key in config) { + if (config.hasOwnProperty(key)) { + const value = config[key]; + const settingName = key.replace(`${CONFIG_KEY}.`, ''); + + if (typeof value === 'string' || + typeof value === 'number' || + typeof value === 'boolean') { + if (typeof originalConfig[key] === 'undefined') { + Settings.globalConfig[key] = value; + } + } + // Objects and arrays to override + else if (settingName === SETTING_CONTENT_DRAFT_FIELD || + settingName === SETTING_CONTENT_SUPPORTED_FILETYPES || + settingName === SETTING_GLOBAL_NOTIFICATIONS || + settingName === SETTING_GLOBAL_NOTIFICATIONS_DISABLED || + settingName === SETTING_MEDIA_SUPPORTED_MIMETYPES || + settingName === SETTING_COMMA_SEPARATED_FIELDS) { + if (typeof originalConfig[key] === 'undefined') { + Settings.globalConfig[key] = value; + } + } + else if (typeof value === 'object' && value !== null) { + // Check if array + if (Array.isArray(value)) { + if (settingName === SETTING_TAXONOMY_CATEGORIES || + settingName === SETTING_TAXONOMY_TAGS || + settingName === SETTING_REMOVE_QUOTES) { + // Merge the arrays + Settings.globalConfig[key] = [...(Settings.globalConfig[key] || []), ...(originalConfig[key] || []), ...value]; + // Filter out the doubles + Settings.globalConfig[key] = Settings.globalConfig[key].filter((item: any, index: number) => { + return Settings.globalConfig[key].indexOf(item) === index; + }, Settings.globalConfig[key]); + } else { + for (const item of value) { + Settings.updateGlobalConfigSetting(settingName, item); + } + } + } else if (settingName === SETTING_CONTENT_SNIPPETS) { + for (const itemKey in value) { + const crntValue = Settings.globalConfig[key] || {}; + + if (!crntValue[itemKey]) { + Settings.globalConfig[key] = { ...crntValue, ...{ [itemKey]: value[itemKey] } }; + } + } + } + } + } + } + } + /** * Update the global config array/object settings * @param relSettingName @@ -596,7 +587,7 @@ export class Settings { if (Settings.isEqualOrStartsWith(relSettingName, SETTING_CUSTOM_SCRIPTS)) { // const crntValue = Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CUSTOM_SCRIPTS}`] || []; // Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CUSTOM_SCRIPTS}`] = [...crntValue, configJson]; - Settings.updateGlobalConfigArraySetting(SETTING_CUSTOM_SCRIPTS, "id", configJson); + Settings.updateGlobalConfigArraySetting(SETTING_CUSTOM_SCRIPTS, "id", configJson, "script"); } // Content types else if (Settings.isEqualOrStartsWith(relSettingName, SETTING_TAXONOMY_CONTENT_TYPES)) { @@ -663,10 +654,18 @@ export class Settings { * @param fieldName * @param configJson */ - private static updateGlobalConfigArraySetting(settingName: string, fieldName: string, configJson: any): void { + private static updateGlobalConfigArraySetting(settingName: string, fieldName: string, configJson: any, fallbackFieldName?: string): void { const crntValue: T[] = Settings.globalConfig[`${CONFIG_KEY}.${settingName}`] || []; - const itemIdx = crntValue.findIndex((item: any) => item[fieldName] === configJson[fieldName]); + const itemIdx = crntValue.findIndex((item: any) => { + if (typeof item[fieldName] !== "undefined") { + return item[fieldName] === configJson[fieldName]; + } else if (fallbackFieldName && typeof item[fallbackFieldName] !== "undefined") { + return item[fallbackFieldName] === configJson[fallbackFieldName]; + } else { + return false; + } + }); if (itemIdx === -1) { crntValue.push(configJson); } @@ -728,4 +727,51 @@ export class Settings { l(); }); } + + /** + * Retrieve the external configuration + * @param configPath + * @returns + */ + private static async getExternalConfig(configPath: string): Promise { + let config: any = undefined; + + if (configPath.startsWith('https://')) { + try { + let cachedResponse = await Cache.get<{[config: string]: { expires: number, data: any }}>(ExtensionState.Settings.Extends, "workspace"); + + if (cachedResponse && cachedResponse[configPath] && cachedResponse[configPath].expires > new Date().getTime()) { + config = cachedResponse[configPath].data; + } else { + const response = await fetchWithTimeout(configPath, { method: 'GET' }); + if (response.ok) { + config = await response.json(); + + if (!cachedResponse) { + cachedResponse = {}; + } + + cachedResponse[configPath] = { + expires: (new Date(new Date().getTime() + (1000 * 60 * 10))).getTime(), + data: config + }; + + await Cache.set(ExtensionState.Settings.Extends, cachedResponse, "workspace"); + } + } + } catch (e) { + Logger.error(`Error fetching external config "${configPath}".`); + } + } else { + const absConfigPath = join(Folders.getWorkspaceFolder()?.fsPath || '', configPath); + if (await existsAsync(absConfigPath)) { + const configTxt = await readFileAsync(absConfigPath, 'utf8'); + config = jsoncParser.parse(configTxt); + } else { + Logger.error(`External config "${configPath}" not found.`); + } + } + + return config; + } } \ No newline at end of file diff --git a/src/utils/fetchWithTimeout.ts b/src/utils/fetchWithTimeout.ts new file mode 100644 index 00000000..dbcc8da0 --- /dev/null +++ b/src/utils/fetchWithTimeout.ts @@ -0,0 +1,13 @@ +import fetch from 'node-fetch'; + +export const fetchWithTimeout = async (url: string, options: any, timeout = 5000) => { + try { + const controller = new AbortController(); + const id = setTimeout(() => controller.abort(), timeout); + const response = await fetch(url, { ...options, signal: controller.signal }); + clearTimeout(id); + return response; + } catch (error) { + throw new Error(`Request timed out: ${url}`); + } +} \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 9b5f7e53..09cf93fe 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1,5 +1,6 @@ export * from './copyFileAsync'; export * from './existsAsync'; +export * from './fetchWithTimeout'; export * from './fieldWhenClause'; export * from './mkdirAsync'; export * from './readFileAsync'; From 0c5224b5f9a80381aaeb4b299549a9acf7818e34 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 23 Dec 2022 10:27:39 +0100 Subject: [PATCH 11/20] Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index d0bc354d..92e0f944 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### ✨ New features +- [#407](https://github.com/estruyf/vscode-front-matter/issues/407): External config support + ### 🎨 Enhancements - [#484](https://github.com/estruyf/vscode-front-matter/issues/484): Support for overriding scripts per environment type From d42561fbf5d54879fbbca22590cb09dca9a80e27 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 23 Dec 2022 12:02:49 +0100 Subject: [PATCH 12/20] #469 - Fix for using the root folder as content folder --- CHANGELOG.md | 1 + src/commands/Folders.ts | 9 +++++++-- 2 files changed, 8 insertions(+), 2 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 92e0f944..3f1880af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,7 @@ ### 🐞 Fixes +- [#469](https://github.com/estruyf/vscode-front-matter/issues/469): Fix for using the root folder as content folder - [#470](https://github.com/estruyf/vscode-front-matter/issues/470): Fix `initialize project` dashboard description - [#482](https://github.com/estruyf/vscode-front-matter/issues/482): Update the description when you want to overwrite the default content type description diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index ed280a5f..7ea268d2 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -253,14 +253,19 @@ export class Folders { try { let projectStart = parseWinPath(folder.path).replace(wsFolder, ""); - if (projectStart) { + if (typeof projectStart === 'string') { projectStart = projectStart.replace(/\\/g, '/'); projectStart = projectStart.startsWith('/') ? projectStart.substring(1) : projectStart; let files: Uri[] = []; for (const fileType of (supportedFiles || DEFAULT_FILE_TYPES)) { - const filePath = join(projectStart, folder.excludeSubdir ? '/' : '**', `*${fileType.startsWith('.') ? '' : '.'}${fileType}`); + let filePath = join(projectStart, folder.excludeSubdir ? '/' : '**', `*${fileType.startsWith('.') ? '' : '.'}${fileType}`); + + if (projectStart === '' && folder.excludeSubdir) { + filePath = `*${fileType.startsWith('.') ? '' : '.'}${fileType}`; + } + const foundFiles = await workspace.findFiles(filePath, '**/node_modules/**'); files = [...files, ...foundFiles]; } From 663d346e2e2a48b8ba0e922bc8fe3651093c5d72 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 23 Dec 2022 12:07:26 +0100 Subject: [PATCH 13/20] Temporarily remove badges --- README.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.md b/README.md index a8554ad2..8150225e 100644 --- a/README.md +++ b/README.md @@ -7,14 +7,6 @@

Front Matter a CMS running straight in Visual Studio Code

- - Visual Studio Marketplace - - - Number of installs - - Ratings - Sponsor the project From 50f2e7ea726f7ac9b809cb15a4afb8c506bca006 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 23 Dec 2022 12:12:27 +0100 Subject: [PATCH 14/20] Update readme --- README.beta.md | 8 -------- 1 file changed, 8 deletions(-) diff --git a/README.beta.md b/README.beta.md index 42b5c509..9afcfc97 100644 --- a/README.beta.md +++ b/README.beta.md @@ -9,14 +9,6 @@

This is the BETA version of Front Matter. If you were looking for the main version, check it out at frontmatter.codes

- - Visual Studio Marketplace - - - Number of installs - - Ratings - Sponsor the project From 70316c4c2845838065734f3f95e36c78440dd8a6 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 23 Dec 2022 15:00:49 +0100 Subject: [PATCH 15/20] #474 - Allow to define the file prefix on content types --- CHANGELOG.md | 1 + package.json | 7 ++++++ src/commands/Article.ts | 12 ++++------- src/helpers/ArticleHelper.ts | 41 ++++++++++++++++++++++++++++-------- src/models/PanelSettings.ts | 1 + 5 files changed, 45 insertions(+), 17 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3f1880af..5d6e42d5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,7 @@ ### 🎨 Enhancements +- [#474](https://github.com/estruyf/vscode-front-matter/issues/474): Allow to define the file prefix on content types - [#484](https://github.com/estruyf/vscode-front-matter/issues/484): Support for overriding scripts per environment type ### ⚡️ Optimizations diff --git a/package.json b/package.json index 4015b7f5..107164ed 100644 --- a/package.json +++ b/package.json @@ -1292,6 +1292,13 @@ "type": "string", "default": "", "description": "An optional post script that can be used after new content creation." + }, + "filePrefix": { + "type": [ + "null", + "string" + ], + "description": "Defines a prefix for the file name." } }, "additionalProperties": false, diff --git a/src/commands/Article.ts b/src/commands/Article.ts index a16624f0..48dd675c 100644 --- a/src/commands/Article.ts +++ b/src/commands/Article.ts @@ -198,7 +198,6 @@ export class Article { Telemetry.send(TelemetryEvent.generateSlug); const updateFileName = Settings.get(SETTING_SLUG_UPDATE_FILE_NAME) as string; - let filePrefix = Settings.get(SETTING_TEMPLATES_PREFIX); const editor = vscode.window.activeTextEditor; if (!editor) { @@ -210,13 +209,10 @@ export class Article { return; } - // Retrieve the file prefix from the folder - const filePrefixOnFolder = Folders.getFilePrefixBeFilePath(editor.document.uri.fsPath); - if (typeof filePrefixOnFolder !== "undefined") { - filePrefix = filePrefixOnFolder; - } - + let filePrefix = Settings.get(SETTING_TEMPLATES_PREFIX); const contentType = ArticleHelper.getContentType(article.data); + filePrefix = ArticleHelper.getFilePrefix(editor.document.uri.fsPath, contentType); + const titleField = "title"; const articleTitle: string = article.data[titleField]; const slugInfo = Article.generateSlug(articleTitle); @@ -259,7 +255,7 @@ export class Article { let newFileName = `${slugName}${ext}`; if (filePrefix && typeof filePrefix === "string") { - newFileName = `${format(new Date(), DateHelper.formatUpdate(filePrefix) as string)}-${newFileName}`; + newFileName = `${filePrefix}-${newFileName}`; } const newPath = editor.document.uri.fsPath.replace(fileName, newFileName); diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index 258b285b..260142b0 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -331,17 +331,10 @@ export class ArticleHelper { public static async createContent(contentType: ContentType | undefined, folderPath: string, titleValue: string, fileExtension?: string): Promise { FrontMatterParser.currentContent = null; - let prefix = Settings.get(SETTING_TEMPLATES_PREFIX); const fileType = Settings.get(SETTING_CONTENT_DEFAULT_FILETYPE); - const filePrefixOnFolder = Folders.getFilePrefixByFolderPath(folderPath); - if (typeof filePrefixOnFolder !== "undefined") { - prefix = filePrefixOnFolder; - } - - if (prefix && typeof prefix === "string") { - prefix = `${format(new Date(), DateHelper.formatUpdate(prefix) as string)}`; - } + let prefix = Settings.get(SETTING_TEMPLATES_PREFIX); + prefix = ArticleHelper.getFilePrefix(folderPath, contentType); // Name of the file or folder to create let sanitizedName = ArticleHelper.sanitize(titleValue); @@ -379,6 +372,36 @@ export class ArticleHelper { return newFilePath; } + /** + * Retrieve the file prefix + * @param filePath + * @param contentType + * @returns + */ + public static getFilePrefix(filePath?: string, contentType?: ContentType): string | undefined { + let prefix = undefined; + + // Retrieve the file prefix from the folder + if (filePath) { + const filePrefixOnFolder = Folders.getFilePrefixByFolderPath(filePath); + if (typeof filePrefixOnFolder !== "undefined") { + prefix = filePrefixOnFolder; + } + } + + // Retrieve the file prefix from the content type + if (contentType && typeof contentType.filePrefix !== "undefined") { + prefix = contentType.filePrefix; + } + + // Process the prefix date formatting + if (prefix && typeof prefix === "string") { + prefix = `${format(new Date(), DateHelper.formatUpdate(prefix) as string)}`; + } + + return prefix; + } + /** * Update placeholder values in the front matter content * @param data diff --git a/src/models/PanelSettings.ts b/src/models/PanelSettings.ts index 647e919d..ebd13131 100644 --- a/src/models/PanelSettings.ts +++ b/src/models/PanelSettings.ts @@ -49,6 +49,7 @@ export interface ContentType { pageBundle?: boolean; template?: string; postScript?: string; + filePrefix?: string; } export type FieldType = "string" | "number" | "datetime" | "boolean" | "image" | "choice" | "tags" | "categories" | "draft" | "taxonomy" | "fields" | "json" | "block" | "file" | "dataFile" | "list" | "slug" | "divider" | "heading"; From ddef00726ba2281b199e712886c9bd3adabb7b22 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 24 Dec 2022 09:42:40 +0100 Subject: [PATCH 16/20] update badges --- README.beta.md | 8 ++++++++ README.md | 8 ++++++++ 2 files changed, 16 insertions(+) diff --git a/README.beta.md b/README.beta.md index 9afcfc97..578eca12 100644 --- a/README.beta.md +++ b/README.beta.md @@ -9,6 +9,14 @@

This is the BETA version of Front Matter. If you were looking for the main version, check it out at frontmatter.codes

+ + Visual Studio Marketplace + + + Number of installs + + Ratings + Sponsor the project diff --git a/README.md b/README.md index 8150225e..b02a9877 100644 --- a/README.md +++ b/README.md @@ -7,6 +7,14 @@

Front Matter a CMS running straight in Visual Studio Code

+ + Visual Studio Marketplace + + + Number of installs + + Ratings + Sponsor the project From 8a099de8595845fdc78a5ba5951d4e2416583121 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 24 Dec 2022 09:47:13 +0100 Subject: [PATCH 17/20] Updated vsce dependency --- .github/workflows/release-beta.yml | 2 +- .github/workflows/release.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release-beta.yml b/.github/workflows/release-beta.yml index 8214674f..12a88a6f 100644 --- a/.github/workflows/release-beta.yml +++ b/.github/workflows/release-beta.yml @@ -24,7 +24,7 @@ jobs: run: node scripts/beta-release.js $GITHUB_RUN_ID - name: Publish - run: npx vsce publish -p ${{ secrets.VSCE_PAT }} --baseImagesUrl https://raw.githubusercontent.com/estruyf/vscode-front-matter/dev + run: npx @vscode/vsce publish -p ${{ secrets.VSCE_PAT }} --baseImagesUrl https://raw.githubusercontent.com/estruyf/vscode-front-matter/dev - name: Publish to open-vsx.org run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }} diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 10f6d950..ec3ae27f 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,7 +24,7 @@ jobs: run: node scripts/main-release.js - name: Publish - run: npx vsce publish -p ${{ secrets.VSCE_PAT }} + run: npx @vscode/vsce publish -p ${{ secrets.VSCE_PAT }} - name: Publish to open-vsx.org run: npx ovsx publish -p ${{ secrets.OPEN_VSX_PAT }} \ No newline at end of file From e19b4d7d6cd12d8571ce5380d928fb5131d2986b Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 24 Jan 2023 10:44:16 +0100 Subject: [PATCH 18/20] #493 - fix issue with custom placeholders --- CHANGELOG.md | 1 + src/helpers/ArticleHelper.ts | 4 +++- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 5d6e42d5..efc761b3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ - [#469](https://github.com/estruyf/vscode-front-matter/issues/469): Fix for using the root folder as content folder - [#470](https://github.com/estruyf/vscode-front-matter/issues/470): Fix `initialize project` dashboard description - [#482](https://github.com/estruyf/vscode-front-matter/issues/482): Update the description when you want to overwrite the default content type description +- [#493](https://github.com/estruyf/vscode-front-matter/issues/493): Fix an issue where a custom placeholder value is replaced by an `array` instead of a `string` ## [8.2.0] - 2022-12-08 - [Release notes](https://beta.frontmatter.codes/updates/v8.2.0) diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index 260142b0..64c16559 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -460,7 +460,9 @@ export class ArticleHelper { // Do nothing } } else { - output = output.split("\n"); + if (output.includes("\n")) { + output = output.split("\n"); + } } placeHolderValue = output; From 5c622556051a4c078c4801aabb65903146360109 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 30 Jan 2023 10:11:06 +0100 Subject: [PATCH 19/20] #494 - Support added for remote images as previews --- CHANGELOG.md | 1 + src/commands/Dashboard.ts | 2 +- src/explorerView/ExplorerView.ts | 2 +- src/helpers/ImageHelper.ts | 8 ++--- src/services/PagesParser.ts | 51 ++++++++++++++++++-------------- 5 files changed, 35 insertions(+), 29 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index efc761b3..8859ffaf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#474](https://github.com/estruyf/vscode-front-matter/issues/474): Allow to define the file prefix on content types - [#484](https://github.com/estruyf/vscode-front-matter/issues/484): Support for overriding scripts per environment type +- [#494](https://github.com/estruyf/vscode-front-matter/issues/494): Support for external image URLs in previews ### ⚡️ Optimizations diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index 2bcc13e4..e130d6e8 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -199,7 +199,7 @@ export class Dashboard { const csp = [ `default-src 'none';`, - `img-src ${`vscode-file://vscode-app`} ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline'`, + `img-src ${`vscode-file://vscode-app`} ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline' https://*`, `script-src ${isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`} 'unsafe-eval'`, `style-src ${webView.cspSource} 'self' 'unsafe-inline'`, `font-src ${webView.cspSource}`, diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index 58966fdb..115330ee 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -173,7 +173,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { const csp = [ `default-src 'none';`, - `img-src ${`vscode-file://vscode-app`} ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline'`, + `img-src ${`vscode-file://vscode-app`} ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline' https://*`, `script-src 'unsafe-eval' ${isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`}`, `style-src ${webView.cspSource} 'self' 'unsafe-inline'`, `font-src ${webView.cspSource}`, diff --git a/src/helpers/ImageHelper.ts b/src/helpers/ImageHelper.ts index 78a8925f..a6c962c6 100644 --- a/src/helpers/ImageHelper.ts +++ b/src/helpers/ImageHelper.ts @@ -27,14 +27,14 @@ export class ImageHelper { if (Array.isArray(value)) { previewUri = value.map(v => ({ original: v, - absPath: ImageHelper.relToAbs(filePath, v) + absPath: v.startsWith("http") ? v : ImageHelper.relToAbs(filePath, v) })); } } else { if (typeof value === "string") { return { original: value, - absPath: ImageHelper.relToAbs(filePath, value) + absPath: value.startsWith("http") ? value : ImageHelper.relToAbs(filePath, value) }; } } @@ -122,12 +122,12 @@ export class ImageHelper { if (field.multiple && imageData instanceof Array) { const preview = imageData.map(preview => preview && preview.absPath ? ({ ...preview, - webviewUrl: panel.getWebview()?.asWebviewUri(preview.absPath).toString() + webviewUrl: typeof preview.absPath === "string" ? preview.absPath : 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); + const preview = typeof imageData.absPath === "string" ? imageData.absPath : panel.getWebview()?.asWebviewUri(imageData.absPath); parentObj[field.name] = { ...imageData, webviewUrl: preview ? preview.toString() : null diff --git a/src/services/PagesParser.ts b/src/services/PagesParser.ts index 0120e4b1..b7091173 100644 --- a/src/services/PagesParser.ts +++ b/src/services/PagesParser.ts @@ -238,31 +238,36 @@ export class PagesParser { // Revalidate as the array could have been empty if (fieldValue) { - let staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue); + // Check if the value already starts with https - if that is the case, it is an external image + if (fieldValue.startsWith("http")) { + page.fmPreviewImage = fieldValue; + } else { + let staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue); - if (staticFolder === STATIC_FOLDER_PLACEHOLDER.hexo.placeholder) { - const crntFilePath = parseWinPath(filePath) - const pathWithoutExtension = crntFilePath.replace(extname(crntFilePath), ''); - staticPath = join(pathWithoutExtension, fieldValue); - } - - const contentFolderPath = join(dirname(filePath), fieldValue); - - let previewUri = null; - if (await existsAsync(staticPath)) { - previewUri = Uri.file(staticPath); - } else if (await existsAsync(contentFolderPath)) { - previewUri = Uri.file(contentFolderPath); - } - - if (previewUri) { - let previewPath = Dashboard.getWebview()?.asWebviewUri(previewUri); - - if (!previewPath) { - previewPath = PagesParser.getWebviewUri(previewUri); + if (staticFolder === STATIC_FOLDER_PLACEHOLDER.hexo.placeholder) { + const crntFilePath = parseWinPath(filePath) + const pathWithoutExtension = crntFilePath.replace(extname(crntFilePath), ''); + staticPath = join(pathWithoutExtension, fieldValue); + } + + const contentFolderPath = join(dirname(filePath), fieldValue); + + let previewUri = null; + if (await existsAsync(staticPath)) { + previewUri = Uri.file(staticPath); + } else if (await existsAsync(contentFolderPath)) { + previewUri = Uri.file(contentFolderPath); + } + + if (previewUri) { + let previewPath = Dashboard.getWebview()?.asWebviewUri(previewUri); + + if (!previewPath) { + previewPath = PagesParser.getWebviewUri(previewUri); + } + + page["fmPreviewImage"] = previewPath?.toString() || ""; } - - page["fmPreviewImage"] = previewPath?.toString() || ""; } } } From bf4b66564cfd8e5194be5274b767eec0b521bc98 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 30 Jan 2023 10:19:05 +0100 Subject: [PATCH 20/20] #497 - Support for movie media added --- CHANGELOG.md | 1 + src/commands/Dashboard.ts | 1 + src/dashboardWebView/components/Media/Item.tsx | 6 +++++- 3 files changed, 7 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 8859ffaf..4aa454c4 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -11,6 +11,7 @@ - [#474](https://github.com/estruyf/vscode-front-matter/issues/474): Allow to define the file prefix on content types - [#484](https://github.com/estruyf/vscode-front-matter/issues/484): Support for overriding scripts per environment type - [#494](https://github.com/estruyf/vscode-front-matter/issues/494): Support for external image URLs in previews +- [#497](https://github.com/estruyf/vscode-front-matter/issues/497): Support for movie media previews in the content dashboard ### ⚡️ Optimizations diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index e130d6e8..9894c9cd 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -200,6 +200,7 @@ export class Dashboard { const csp = [ `default-src 'none';`, `img-src ${`vscode-file://vscode-app`} ${webView.cspSource} https://api.visitorbadge.io 'self' 'unsafe-inline' https://*`, + `media-src ${`vscode-file://vscode-app`} ${webView.cspSource} 'self' 'unsafe-inline' https://*`, `script-src ${isProd ? `'nonce-${nonce}'` : `http://${localServerUrl} http://0.0.0.0:${localPort}`} 'unsafe-eval'`, `style-src ${webView.cspSource} 'self' 'unsafe-inline'`, `font-src ${webView.cspSource}`, diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index 6435b82b..3ade9e88 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -321,10 +321,14 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi }, [media, isImageFile, isVideoFile, isAudioFile]); const renderMedia = useMemo(() => { - if (isVideoFile || isAudioFile) { + if (isAudioFile) { return null; } + if (isVideoFile) { + return