From 46872f81ac3150a02ecdb4d65f47771e30b565e3 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 27 Sep 2022 09:00:14 +0200 Subject: [PATCH 01/49] Include CMS in the display name --- package.json | 2 +- scripts/beta-release.js | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/package.json b/package.json index fce189f3..4573c085 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "vscode-front-matter-beta", - "displayName": "Front Matter", + "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, Hexo, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", "version": "8.1.1", diff --git a/scripts/beta-release.js b/scripts/beta-release.js index 262738f8..782ee92c 100644 --- a/scripts/beta-release.js +++ b/scripts/beta-release.js @@ -8,7 +8,7 @@ const version = packageJson.version.split('.'); packageJson.version = `${version[0]}.${version[1]}.${process.argv[process.argv.length-1].substr(0, 7)}`; packageJson.preview = true; packageJson.name = "vscode-front-matter-beta"; -packageJson.displayName = `${packageJson.displayName} BETA`; +packageJson.displayName = `${packageJson.displayName} (BETA)`; packageJson.description = `BETA Version of Front Matter. ${packageJson.description}`; packageJson.icon = "assets/frontmatter-beta.png"; packageJson.homepage = "https://beta.frontmatter.codes"; From cb2194bc48e613e9c6e181ceb0e126288a61f84b Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 27 Sep 2022 12:04:50 +0200 Subject: [PATCH 02/49] 8.2.0 --- package-lock.json | 4 ++-- package.json | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/package-lock.json b/package-lock.json index 8271f84a..b0f494ba 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "vscode-front-matter-beta", - "version": "8.1.1", + "version": "8.2.0", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "vscode-front-matter-beta", - "version": "8.1.1", + "version": "8.2.0", "license": "MIT", "dependencies": { "node-fetch": "^2.6.7" diff --git a/package.json b/package.json index 4573c085..9d694875 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, Hexo, NextJs, Gatsby, and many more...", "icon": "assets/frontmatter-teal-128x128.png", - "version": "8.1.1", + "version": "8.2.0", "preview": false, "publisher": "eliostruyf", "galleryBanner": { From 2b8f08c03ce0d7cf56bd6c8a851150f3ff0a0c7a Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 27 Sep 2022 13:16:44 +0200 Subject: [PATCH 03/49] #406 - Single data entries --- CHANGELOG.md | 12 +++ package.json | 10 ++ .../components/DataView/DataView.tsx | 96 +++++++++++-------- src/helpers/DashboardSettings.ts | 3 +- src/listeners/dashboard/DataListener.ts | 2 +- src/models/DataFile.ts | 1 + src/models/DataFolder.ts | 1 + 7 files changed, 84 insertions(+), 41 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 59604a7b..4cd34d34 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,17 @@ # Change Log +## [8.2.0] - 2022-xx-xx + +### ✨ New features + +### 🎨 Enhancements + +- [#406](https://github.com/estruyf/vscode-front-matter/issues/406): Added support for single data entries in the data dashboard + +### ⚡️ Optimizations + +### 🐞 Fixes + ## [8.1.1] - 2022-09-23 ### 🐞 Fixes diff --git a/package.json b/package.json index 9d694875..70f986d1 100644 --- a/package.json +++ b/package.json @@ -550,6 +550,11 @@ "type": "string", "default": "content", "description": "If you are using data types, you can specify your type ID." + }, + "singleEntry": { + "type": "boolean", + "description": "If you want to use a single entry for your data file.", + "default": false } }, "additionalProperties": false, @@ -601,6 +606,11 @@ "type": "string", "default": "content", "description": "If you are using data types, you can specify your type ID." + }, + "singleEntry": { + "type": "boolean", + "description": "If you want to use a single entry for your data files in the folder.", + "default": false } }, "additionalProperties": false, diff --git a/src/dashboardWebView/components/DataView/DataView.tsx b/src/dashboardWebView/components/DataView/DataView.tsx index 0f4d9a41..8f3939df 100644 --- a/src/dashboardWebView/components/DataView/DataView.tsx +++ b/src/dashboardWebView/components/DataView/DataView.tsx @@ -3,7 +3,7 @@ import { Header } from '../Header'; import { useRecoilValue } from 'recoil'; import { SettingsSelector } from '../../state'; import { DataForm } from './DataForm'; -import { useCallback, useEffect, useState } from 'react'; +import { useCallback, useEffect, useMemo, useState } from 'react'; import { DataFile } from '../../../models/DataFile'; import { Messenger } from '@estruyf/vscode/dist/client'; import { DashboardMessage } from '../../DashboardMessage'; @@ -27,7 +27,7 @@ export interface IDataViewProps {} export const DataView: React.FunctionComponent = (props: React.PropsWithChildren) => { const [ selectedData, setSelectedData ] = useState(null); const [ selectedIndex, setSelectedIndex ] = useState(null); - const [ dataEntries, setDataEntries ] = useState(null); + const [ dataEntries, setDataEntries ] = useState(null); const settings = useRecoilValue(SettingsSelector); const setSchema = (dataFile: DataFile) => { @@ -57,6 +57,12 @@ export const DataView: React.FunctionComponent = (props: React.P const onSubmit = useCallback((data: any) => { + if (selectedData?.singleEntry) { + // Needs to add a single entry + updateData(data); + return; + } + const dataClone: any[] = Object.assign([], dataEntries); if (selectedIndex !== null && selectedIndex !== undefined) { dataClone[selectedIndex] = data; @@ -102,6 +108,14 @@ export const DataView: React.FunctionComponent = (props: React.P }); }, [selectedData]); + const dataEntry = useMemo(() => { + if (selectedData?.singleEntry) { + return dataEntries || {}; + } + + return (dataEntries && selectedIndex !== null && selectedIndex !== undefined) ? dataEntries[selectedIndex] : null; + }, [selectedData, , dataEntries, selectedIndex]); + useEffect(() => { Messenger.listen(messageListener); @@ -171,49 +185,53 @@ export const DataView: React.FunctionComponent = (props: React.P { selectedData ? ( <> -
-

Your {selectedData?.title?.toLowerCase() || ""} data items

+ { + !selectedData.singleEntry && ( +
+

Your {selectedData?.title?.toLowerCase() || ""} data items

-
- { - (dataEntries && dataEntries.length > 0) ? ( - <> - - { - (dataEntries || []).map((dataEntry, idx) => ( - setSelectedIndex(index)} - onDeleteItem={deleteItem} - /> - )) - } - - - - ) : ( -
-

No {selectedData.title.toLowerCase()} data entries found

-
- ) - } -
-
-
+
+ { + (dataEntries && dataEntries.length > 0) ? ( + <> + + { + (dataEntries as any[] || []).map((dataEntry, idx) => ( + setSelectedIndex(index)} + onDeleteItem={deleteItem} + /> + )) + } + + + + ) : ( +
+

No {selectedData.title.toLowerCase()} data entries found

+
+ ) + } +
+
+ ) + } +

Create or modify your {selectedData.title.toLowerCase()} data

{ selectedData ? ( setSelectedIndex(null)} /> ) : ( diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index 58357923..c40cc197 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -113,7 +113,8 @@ export class DashboardSettings { fileType: dataFile.fsPath.endsWith('.json') ? 'json' : 'yaml', labelField: folder.labelField, schema: folder.schema, - type: folder.type + type: folder.type, + singleEntry: typeof folder.singleEntry === 'boolean' ? folder.singleEntry : false, } as DataFile) } } diff --git a/src/listeners/dashboard/DataListener.ts b/src/listeners/dashboard/DataListener.ts index 5155d55b..10530503 100644 --- a/src/listeners/dashboard/DataListener.ts +++ b/src/listeners/dashboard/DataListener.ts @@ -36,7 +36,7 @@ export class DataListener extends BaseListener { * @param msgData */ private static processDataUpdate(msgData: any) { - const { file, fileType, entries } = msgData as { file: string, fileType: string, entries: any[] }; + const { file, fileType, entries } = msgData as { file: string, fileType: string, entries: unknown | unknown[] }; const absPath = Folders.getAbsFilePath(file); if (!existsSync(absPath)) { diff --git a/src/models/DataFile.ts b/src/models/DataFile.ts index 48a375f6..ef449f72 100644 --- a/src/models/DataFile.ts +++ b/src/models/DataFile.ts @@ -6,4 +6,5 @@ export interface DataFile { labelField: string; schema?: any; type?: string; + singleEntry?: boolean; } \ No newline at end of file diff --git a/src/models/DataFolder.ts b/src/models/DataFolder.ts index 20337ed2..3f48a5c3 100644 --- a/src/models/DataFolder.ts +++ b/src/models/DataFolder.ts @@ -6,4 +6,5 @@ export interface DataFolder { labelField: string; schema?: any; type?: string; + singleEntry?: boolean; } \ No newline at end of file From 27887bedef05bb145980e5b6ea73fe6b2d5ed4e0 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 29 Sep 2022 20:36:02 +0200 Subject: [PATCH 04/49] #412 - splitting configuration files --- CHANGELOG.md | 2 + src/commands/Dashboard.ts | 2 +- src/commands/Diagnostics.ts | 7 ++ src/explorerView/ExplorerView.ts | 2 +- src/extension.ts | 4 +- src/helpers/SettingsHelper.ts | 111 ++++++++++++++++++++++++++----- 6 files changed, 107 insertions(+), 21 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 4cd34d34..c94a2eea 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -4,6 +4,8 @@ ### ✨ New features +- [#412](https://github.com/estruyf/vscode-front-matter/issues/412): Allow `frontmatter.json` to be split in multiple files + ### 🎨 Enhancements - [#406](https://github.com/estruyf/vscode-front-matter/issues/406): Added support for single data entries in the data dashboard diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index e0d888b9..b99c44ee 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -130,7 +130,7 @@ export class Dashboard { await commands.executeCommand('setContext', CONTEXT.isDashboardOpen, false); }); - SettingsHelper.onConfigChange((global?: any) => { + SettingsHelper.onConfigChange(() => { SettingsListener.getSettings(); }); diff --git a/src/commands/Diagnostics.ts b/src/commands/Diagnostics.ts index 23974cfb..fc24be1d 100644 --- a/src/commands/Diagnostics.ts +++ b/src/commands/Diagnostics.ts @@ -3,6 +3,7 @@ import { ViewColumn, workspace } from "vscode"; import ContentProvider from "../providers/ContentProvider"; import { join } from "path"; import { ContentFolder } from "../models"; +import { Settings } from "../helpers/SettingsHelper"; export class Diagnostics { @@ -38,6 +39,12 @@ ${all} # Folders to search files ${folderData.join("\n")} + +# Complete frontmatter.json config + +\`\`\`json +${JSON.stringify(Settings.globalConfig, null, 2)} +\`\`\` `; ContentProvider.show(logging, `${projectName} diagnostics`, "markdown", ViewColumn.One); diff --git a/src/explorerView/ExplorerView.ts b/src/explorerView/ExplorerView.ts index a61bd54a..58966fdb 100644 --- a/src/explorerView/ExplorerView.ts +++ b/src/explorerView/ExplorerView.ts @@ -100,7 +100,7 @@ export class ExplorerView implements WebviewViewProvider, Disposable { } }, this); - Settings.onConfigChange((global?: any) => { + Settings.onConfigChange(() => { SettingsListener.getSettings(); }); } diff --git a/src/extension.ts b/src/extension.ts index aa5317a3..c463e958 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -41,7 +41,7 @@ export async function activate(context: vscode.ExtensionContext) { return undefined; } - SettingsHelper.init(); + await SettingsHelper.init(); extension.migrateSettings(); SettingsHelper.checkToPromote(); @@ -201,7 +201,7 @@ export async function activate(context: vscode.ExtensionContext) { }); // Things to do when configuration changes - SettingsHelper.onConfigChange((global?: any) => { + SettingsHelper.onConfigChange(() => { Preview.init(); GitListener.init(); diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 9f711b66..5fbac08e 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -4,10 +4,10 @@ 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 } 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 } from '../constants'; import { Folders } from '../commands/Folders'; -import { join, basename } from 'path'; -import { existsSync, readFileSync, watch, writeFileSync } from 'fs'; +import { join, basename, dirname, parse } from 'path'; +import { existsSync, readFileSync, writeFileSync } from 'fs'; import { Extension } from './Extension'; import { debounceCallback } from './DebounceCallback'; import { Logger } from './Logger'; @@ -15,14 +15,16 @@ import * as jsoncParser from 'jsonc-parser'; export class Settings { public static globalFile = "frontmatter.json"; + public static globalConfigFolder = ".frontmatter/config"; + public static globalConfig: any; private static config: vscode.WorkspaceConfiguration; - private static globalConfig: any; private static isInitialized: boolean = false; private static listeners: any[] = []; private static fileCreationWatcher: vscode.FileSystemWatcher | undefined; + private static readConfigPromise: Promise | undefined = undefined; - public static init() { - Settings.readConfig(); + public static async init() { + await Settings.readConfig(); Settings.listeners = []; @@ -34,8 +36,7 @@ export class Settings { Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY); - Settings.onConfigChange((global?: any) => { - Settings.readConfig(); + Settings.onConfigChange(async () => { Settings.config = vscode.workspace.getConfiguration(CONFIG_KEY); }); } @@ -97,19 +98,26 @@ export class Settings { if (Settings.checkProjectConfig(filename)) { Logger.info(`Config change detected - ${projectConfig} saved`); - const file = await workspace.openTextDocument(e.uri); - if (file) { - const fileContents = file.getText(); - const json = jsoncParser.parse(fileContents); - configDebouncer(() => callback(json), 200); - // callback(json) + Logger.info(`Reloading config...`); + if (Settings.readConfigPromise === undefined) { + Settings.readConfigPromise = Settings.readConfig(); } + await Settings.readConfigPromise; + + Logger.info(`Reloaded config...`); + configDebouncer(() => callback(), 200); } }); - workspace.onDidDeleteFiles((e) => { + workspace.onDidDeleteFiles(async (e) => { const needCallback = e?.files.find(f => Settings.checkProjectConfig(f.fsPath)); if (needCallback) { + Logger.info(`Reloading config...`); + if (Settings.readConfigPromise === undefined) { + Settings.readConfigPromise = Settings.readConfig(); + } + await Settings.readConfigPromise; + callback(); } }); @@ -391,7 +399,11 @@ export class Settings { */ private static checkProjectConfig(filePath: string) { const fmConfig = Settings.projectConfigPath; - if (fmConfig && existsSync(fmConfig)) { + filePath = parseWinPath(filePath); + + if (filePath.includes(Settings.globalConfigFolder)) { + return true; + } else if (fmConfig && existsSync(fmConfig)) { return filePath && basename(filePath).toLowerCase() === Settings.globalFile.toLowerCase() && fmConfig.toLowerCase() === filePath.toLowerCase(); @@ -403,7 +415,7 @@ export class Settings { /** * Read the global config file */ - private static readConfig() { + private static async readConfig() { try { const fmConfig = Settings.projectConfigPath; if (fmConfig && existsSync(fmConfig)) { @@ -413,11 +425,76 @@ export class Settings { } else { Settings.globalConfig = undefined; } + + // Read the files from the config folder + let configFiles = await workspace.findFiles(`**/${Settings.globalConfigFolder}/**`); + if (configFiles.length === 0) { + Logger.info(`No ".frontmatter/config" config files found.`); + } + + // 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); + } } catch (e) { Settings.globalConfig = undefined; Notifications.error(`Error reading "frontmatter.json" config file. Check [output window](command:${COMMAND_NAME.showOutputChannel}) for more details.`); Logger.error((e as Error).message); } + + Settings.readConfigPromise = undefined; + } + + /** + * Process the config file + * @param configFile + * @returns + */ + private static async processConfigFile(configFile: Uri) { + try { + const config = await workspace.fs.readFile(configFile); + const configJson = jsoncParser.parse(config.toString()); + + const filePath = parseWinPath(configFile.fsPath); + const configFilePath = filePath.split(Settings.globalConfigFolder).pop(); + if (!configFilePath) { + return; + } + Logger.info(`Processing "${configFilePath}" config file.`); + + // Get the path without the filename + const configFolder = parseWinPath(dirname(configFilePath)); + let relSettingName = configFolder.split('/').join('.'); + if (relSettingName.startsWith('.')) { + relSettingName = relSettingName.substring(1); + } + + const settingName = `frontMatter${relSettingName.startsWith('.') ? '' : '.'}${relSettingName}`; + + if (!Settings.globalConfig) { + Settings.globalConfig = {}; + } + + // Array settings + if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES || + relSettingName === SETTING_CONTENT_PAGE_FOLDERS || + relSettingName === SETTING_CONTENT_PLACEHOLDERS) { + const crntValue = Settings.globalConfig[settingName] || []; + Settings.globalConfig[settingName] = [...crntValue, configJson]; + } + // Object settings + else if (relSettingName === SETTING_CONTENT_SNIPPETS) { + // Filename is the key + const fileName = parse(configFilePath).name; + const crntValue = Settings.globalConfig[settingName] || {}; + Settings.globalConfig[settingName] = { ...crntValue, ...{ [fileName]: configJson } }; + } + } catch (e) { + Logger.error(`Error reading config file: ${configFile.fsPath}`); + Logger.error((e as Error).message); + } } /** From 07d67bf881e6ec9002dc5448cff8ca0c76621acc Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 30 Sep 2022 09:54:55 +0200 Subject: [PATCH 05/49] #412 - allow config folders to use lowercase --- src/helpers/SettingsHelper.ts | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 5fbac08e..55c4a4f8 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -478,14 +478,15 @@ export class Settings { } // Array settings - if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES || - relSettingName === SETTING_CONTENT_PAGE_FOLDERS || - relSettingName === SETTING_CONTENT_PLACEHOLDERS) { + relSettingName = relSettingName.toLowerCase(); + if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES.toLowerCase() || + relSettingName === SETTING_CONTENT_PAGE_FOLDERS.toLowerCase() || + relSettingName === SETTING_CONTENT_PLACEHOLDERS.toLowerCase()) { const crntValue = Settings.globalConfig[settingName] || []; Settings.globalConfig[settingName] = [...crntValue, configJson]; } // Object settings - else if (relSettingName === SETTING_CONTENT_SNIPPETS) { + else if (relSettingName === SETTING_CONTENT_SNIPPETS.toLowerCase()) { // Filename is the key const fileName = parse(configFilePath).name; const crntValue = Settings.globalConfig[settingName] || {}; From 13a71cfd82c3b54be21561cdfb22a81921baba01 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 30 Sep 2022 10:44:14 +0200 Subject: [PATCH 06/49] #412 - Update setting casing --- src/helpers/SettingsHelper.ts | 20 ++++++++++++++------ 1 file changed, 14 insertions(+), 6 deletions(-) diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 55c4a4f8..659fd45c 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -471,8 +471,6 @@ export class Settings { relSettingName = relSettingName.substring(1); } - const settingName = `frontMatter${relSettingName.startsWith('.') ? '' : '.'}${relSettingName}`; - if (!Settings.globalConfig) { Settings.globalConfig = {}; } @@ -482,15 +480,25 @@ export class Settings { if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES.toLowerCase() || relSettingName === SETTING_CONTENT_PAGE_FOLDERS.toLowerCase() || relSettingName === SETTING_CONTENT_PLACEHOLDERS.toLowerCase()) { - const crntValue = Settings.globalConfig[settingName] || []; - Settings.globalConfig[settingName] = [...crntValue, configJson]; + + let settingNameValue = "" + if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES.toLowerCase()) { + settingNameValue = SETTING_TAXONOMY_CONTENT_TYPES; + } else if (relSettingName === SETTING_CONTENT_PAGE_FOLDERS.toLowerCase()) { + settingNameValue = SETTING_CONTENT_PAGE_FOLDERS; + } else if (relSettingName === SETTING_CONTENT_PLACEHOLDERS.toLowerCase()) { + settingNameValue = SETTING_CONTENT_PLACEHOLDERS; + } + + const crntValue = Settings.globalConfig[`${CONFIG_KEY}.${settingNameValue}`] || []; + Settings.globalConfig[`${CONFIG_KEY}.${settingNameValue}`] = [...crntValue, configJson]; } // Object settings else if (relSettingName === SETTING_CONTENT_SNIPPETS.toLowerCase()) { // Filename is the key const fileName = parse(configFilePath).name; - const crntValue = Settings.globalConfig[settingName] || {}; - Settings.globalConfig[settingName] = { ...crntValue, ...{ [fileName]: configJson } }; + const crntValue = Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CONTENT_SNIPPETS}`] || {}; + Settings.globalConfig[`${CONFIG_KEY}.${SETTING_CONTENT_SNIPPETS}`] = { ...crntValue, ...{ [fileName]: configJson } }; } } catch (e) { Logger.error(`Error reading config file: ${configFile.fsPath}`); From 5254f2b7f963b0a1e50d2e8e71a35d0a2014ef8e Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 30 Sep 2022 14:44:07 +0200 Subject: [PATCH 07/49] #412 Support added for data files and custom scripts --- src/helpers/SettingsHelper.ts | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 659fd45c..e3eafc3c 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 } 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 } from '../constants'; import { Folders } from '../commands/Folders'; import { join, basename, dirname, parse } from 'path'; import { existsSync, readFileSync, writeFileSync } from 'fs'; @@ -427,7 +427,7 @@ export class Settings { } // Read the files from the config folder - let configFiles = await workspace.findFiles(`**/${Settings.globalConfigFolder}/**`); + let configFiles = await workspace.findFiles(`**/${Settings.globalConfigFolder}/**/*.json`); if (configFiles.length === 0) { Logger.info(`No ".frontmatter/config" config files found.`); } @@ -470,17 +470,22 @@ export class Settings { if (relSettingName.startsWith('.')) { relSettingName = relSettingName.substring(1); } + relSettingName = relSettingName.toLowerCase(); if (!Settings.globalConfig) { Settings.globalConfig = {}; } // Array settings - relSettingName = relSettingName.toLowerCase(); if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES.toLowerCase() || relSettingName === SETTING_CONTENT_PAGE_FOLDERS.toLowerCase() || - relSettingName === SETTING_CONTENT_PLACEHOLDERS.toLowerCase()) { + relSettingName === SETTING_CONTENT_PLACEHOLDERS.toLowerCase() || + relSettingName === SETTING_CUSTOM_SCRIPTS.toLowerCase() || + relSettingName === SETTING_DATA_FILES.toLowerCase() || + relSettingName === SETTING_DATA_FOLDERS.toLowerCase() || + relSettingName === SETTING_DATA_TYPES.toLowerCase()) { + // Get the correct setting name let settingNameValue = "" if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES.toLowerCase()) { settingNameValue = SETTING_TAXONOMY_CONTENT_TYPES; @@ -488,6 +493,14 @@ export class Settings { settingNameValue = SETTING_CONTENT_PAGE_FOLDERS; } else if (relSettingName === SETTING_CONTENT_PLACEHOLDERS.toLowerCase()) { settingNameValue = SETTING_CONTENT_PLACEHOLDERS; + } else if (relSettingName === SETTING_CUSTOM_SCRIPTS.toLowerCase()) { + settingNameValue = SETTING_CUSTOM_SCRIPTS; + } else if (relSettingName === SETTING_DATA_FILES.toLowerCase()) { + settingNameValue = SETTING_DATA_FILES; + } else if (relSettingName === SETTING_DATA_FOLDERS.toLowerCase()) { + settingNameValue = SETTING_DATA_FOLDERS; + } else if (relSettingName === SETTING_DATA_TYPES.toLowerCase()) { + settingNameValue = SETTING_DATA_TYPES; } const crntValue = Settings.globalConfig[`${CONFIG_KEY}.${settingNameValue}`] || []; From a8d2c428bcd33897b1a5d7ce8945ecbb97ff6fed Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 1 Oct 2022 10:36:29 +0200 Subject: [PATCH 08/49] #428 - Image inserting UX enhancement --- CHANGELOG.md | 1 + .../components/Media/Item.tsx | 42 +++++++++++++++++-- 2 files changed, 39 insertions(+), 4 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index c94a2eea..bd0190d9 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,6 +9,7 @@ ### 🎨 Enhancements - [#406](https://github.com/estruyf/vscode-front-matter/issues/406): Added support for single data entries in the data dashboard +- [#428](https://github.com/estruyf/vscode-front-matter/issues/428): Improved UX for inserting images to your content ### ⚡️ Optimizations diff --git a/src/dashboardWebView/components/Media/Item.tsx b/src/dashboardWebView/components/Media/Item.tsx index 46e7bc38..5c1f2845 100644 --- a/src/dashboardWebView/components/Media/Item.tsx +++ b/src/dashboardWebView/components/Media/Item.tsx @@ -41,6 +41,10 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi const selectedFolder = useRecoilValue(SelectedMediaFolderSelector); const viewData = useRecoilValue(ViewDataSelector); + const hasViewData = useMemo(() => { + return viewData?.data?.filePath !== undefined; + }, [viewData]); + const [referenceElement, setReferenceElement] = useState(null); const [popperElement, setPopperElement] = useState(null); const { styles, attributes, forceUpdate } = usePopper(referenceElement, popperElement, { @@ -57,6 +61,10 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi return keys.filter(key => (settings.snippets || {})[key].isMediaSnippet).map(key => ({ title: key, ...(settings.snippets || {})[key]})); }, [settings]); + const showMediaSnippet = useMemo(() => { + return viewData?.data?.position && mediaSnippets.length > 0; + }, [viewData, mediaSnippets]); + const getFolder = () => { if (settings?.wsFolder && media.fsPath) { let relPath = media.fsPath.split(settings.wsFolder).pop(); @@ -350,15 +358,15 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi }, [media.fsPath]); useEffect(() => { - if (!viewData?.data?.filePath) { + if (!hasViewData) { clearFormData(); } - }, [viewData]); + }, [viewData, hasViewData]); return ( <>
  • - +
  • + { + (viewData?.data?.position && mediaSnippets.length > 0) && ( +
    + +
    + ) + } +
    + ) + }
    @@ -443,7 +477,7 @@ export const Item: React.FunctionComponent = ({media}: React.PropsWi viewData?.data?.filePath ? ( <> Insert image markdown
    } + title={
    Insert image
    } onClick={insertToArticle} /> { From 5fbb05f083e269a965bb97165fff8441884cd3a5 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sat, 1 Oct 2022 20:30:18 +0200 Subject: [PATCH 09/49] #431 - Performance improvements for first load --- e2e/src/command.test.ts | 2 - src/commands/Dashboard.ts | 2 +- src/commands/Folders.ts | 2 +- src/commands/Project.ts | 2 +- src/extension.ts | 7 +- src/helpers/DashboardSettings.ts | 17 +- src/listeners/dashboard/PagesListener.ts | 190 ++--------------- src/listeners/dashboard/SettingsListener.ts | 8 +- src/listeners/dashboard/SnippetListener.ts | 4 +- src/services/PagesParser.ts | 219 ++++++++++++++++++++ 10 files changed, 263 insertions(+), 190 deletions(-) create mode 100644 src/services/PagesParser.ts diff --git a/e2e/src/command.test.ts b/e2e/src/command.test.ts index 482c37b2..103afe18 100644 --- a/e2e/src/command.test.ts +++ b/e2e/src/command.test.ts @@ -68,11 +68,9 @@ describe("Initialization testing", function() { async function notificationExists(workbench: Workbench, text: string): Promise { const notifications = await (await (new StatusBar()).openNotificationsCenter()).getNotifications(NotificationType.Info); - console.log(`Notifications:`, notifications.length); for (const notification of notifications) { const message = await notification.getMessage(); - console.log(message) if (message.indexOf(text) >= 0) { return notification; } diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index b99c44ee..d34e682f 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -131,7 +131,7 @@ export class Dashboard { }); SettingsHelper.onConfigChange(() => { - SettingsListener.getSettings(); + SettingsListener.getSettings(true); }); Dashboard.webview.webview.onDidReceiveMessage(async (msg) => { diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index c12977fd..ea39ae10 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -137,7 +137,7 @@ export class Folders { Telemetry.send(TelemetryEvent.registerFolder); - SettingsListener.getSettings(); + SettingsListener.getSettings(true); } } diff --git a/src/commands/Project.ts b/src/commands/Project.ts index 8e708f4c..d7cb3488 100644 --- a/src/commands/Project.ts +++ b/src/commands/Project.ts @@ -55,7 +55,7 @@ categories: [] SettingsListener.setFramework(framework.name); } - SettingsListener.getSettings(); + SettingsListener.getSettings(true); } catch (err: any) { Logger.error(`Project::init: ${err?.message || err}`); Notifications.error(`Sorry, something went wrong - ${err?.message || err}`); diff --git a/src/extension.ts b/src/extension.ts index c463e958..f949b35c 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -15,7 +15,7 @@ import { TagType } from './panelWebView/TagType'; import { ExplorerView } from './explorerView/ExplorerView'; import { Extension } from './helpers/Extension'; import { DashboardData } from './models/DashboardData'; -import { debounceCallback, Logger, Settings as SettingsHelper } from './helpers'; +import { DashboardSettings, debounceCallback, Logger, Settings as SettingsHelper } from './helpers'; import { Content } from './commands/Content'; import ContentProvider from './providers/ContentProvider'; import { Wysiwyg } from './commands/Wysiwyg'; @@ -25,6 +25,7 @@ import { Backers } from './commands/Backers'; import { DataListener, SettingsListener } from './listeners/panel'; import { NavigationType } from './dashboardWebView/models'; import { ModeSwitch } from './services/ModeSwitch'; +import { PagesParser } from './services/PagesParser'; let frontMatterStatusBar: vscode.StatusBarItem; let statusDebouncer: { (fnc: any, time: number): void; }; @@ -266,6 +267,10 @@ export async function activate(context: vscode.ExtensionContext) { // Git GitListener.init(); + // Once everything is registered, the page parsing can start in the background + DashboardSettings.get(); + PagesParser.start(); + // Subscribe all commands subscriptions.push( insertTags, diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index c40cc197..cd254173 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -16,15 +16,24 @@ import { parseWinPath } from './parseWinPath'; export class DashboardSettings { + private static cachedSettings: ISettings | undefined = undefined; - public static async get() { + public static async get(clear: boolean = false) { + if (!this.cachedSettings || clear) { + this.cachedSettings = await this.getSettings(); + } + + return this.cachedSettings; + } + + public static async getSettings() { const ext = Extension.getInstance(); const wsFolder = Folders.getWorkspaceFolder(); const isInitialized = Project.isInitialized(); const gitActions = Settings.get(SETTING_GIT_ENABLED); const pagination = Settings.get(SETTING_DASHBOARD_CONTENT_PAGINATION) - return { + const settings = { git: { isGitRepo: gitActions ? await GitListener.isGitRepository() : false, actions: gitActions || false @@ -71,7 +80,9 @@ export class DashboardSettings { dataTypes: Settings.get(SETTING_DATA_TYPES), snippets: Settings.get(SETTING_CONTENT_SNIPPETS), isBacker: await ext.getState(CONTEXT.backer, 'global') - } as ISettings + } as ISettings; + + return settings; } /** diff --git a/src/listeners/dashboard/PagesListener.ts b/src/listeners/dashboard/PagesListener.ts index af42d614..a7b92fe5 100644 --- a/src/listeners/dashboard/PagesListener.ts +++ b/src/listeners/dashboard/PagesListener.ts @@ -1,21 +1,17 @@ -import { DEFAULT_CONTENT_TYPE_NAME } from './../../constants/ContentType'; -import { isValidFile } from '../../helpers/isValidFile'; -import { existsSync, unlinkSync } from "fs"; -import { basename, dirname, join } from "path"; +import { unlinkSync } from "fs"; +import { basename } from "path"; import { commands, FileSystemWatcher, RelativePattern, TextDocument, Uri, workspace } from "vscode"; import { Dashboard } from "../../commands/Dashboard"; import { Folders } from "../../commands/Folders"; -import { COMMAND_NAME, DefaultFields, ExtensionState, SETTING_SEO_DESCRIPTION_FIELD } from "../../constants"; +import { COMMAND_NAME, ExtensionState } from "../../constants"; import { DashboardCommand } from "../../dashboardWebView/DashboardCommand"; import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; import { Page } from "../../dashboardWebView/models"; -import { ArticleHelper, Extension, Logger, Settings } from "../../helpers"; -import { ContentType } from "../../helpers/ContentType"; -import { DateHelper } from "../../helpers/DateHelper"; -import { Notifications } from "../../helpers/Notifications"; +import { ArticleHelper, Extension, Logger } from "../../helpers"; import { BaseListener } from "./BaseListener"; import { DataListener } from '../panel'; import Fuse from 'fuse.js'; +import { PagesParser } from '../../services/PagesParser'; export class PagesListener extends BaseListener { @@ -132,7 +128,7 @@ export class PagesListener extends BaseListener { if (pageIdx !== -1) { const stats = await workspace.fs.stat(file); const crntPage = this.lastPages[pageIdx]; - const updatedPage = this.processPageContent(file.fsPath, stats.mtime, basename(file.fsPath), crntPage.fmFolder); + const updatedPage = PagesParser.processPageContent(file.fsPath, stats.mtime, basename(file.fsPath), crntPage.fmFolder); if (updatedPage) { this.lastPages[pageIdx] = updatedPage; this.sendPageData(this.lastPages); @@ -156,43 +152,19 @@ export class PagesListener extends BaseListener { if (cachedPages) { this.sendPageData(cachedPages); } + } else { + PagesParser.reset(); } - // Update the dashboard with the fresh data - const folderInfo = await Folders.getInfo(); - const pages: Page[] = []; + PagesParser.getPages(async (pages: Page[]) => { + this.lastPages = pages; + this.sendPageData(pages); - if (folderInfo) { - for (const folder of folderInfo) { - for (const file of folder.lastModified) { - if (isValidFile(file.fileName)) { - try { - const page = this.processPageContent(file.filePath, file.mtime, file.fileName, folder.title); + this.sendMsg(DashboardCommand.searchReady, true); - if (page && !pages.find(p => p.fmFilePath === page.fmFilePath)) { - pages.push(page); - } - - } catch (error: any) { - if ((error as Error)?.message.toLowerCase() === "webview is disposed") { - continue; - } - - Logger.error(`PagesListener::getPagesData: ${file.filePath} - ${error.message}`); - Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`); - } - } - } - } - } - - this.lastPages = pages; - this.sendPageData(pages); - - this.sendMsg(DashboardCommand.searchReady, true); - - await ext.setState(ExtensionState.Dashboard.Pages.Cache, pages, "workspace"); - await this.createSearchIndex(pages); + await ext.setState(ExtensionState.Dashboard.Pages.Cache, pages, "workspace"); + await this.createSearchIndex(pages); + }); } /** @@ -245,136 +217,4 @@ export class PagesListener extends BaseListener { public static refresh() { this.getPagesData(true); } - - /** - * Process the page content - * @param filePath - * @param fileMtime - * @param fileName - * @param folderTitle - * @returns - */ - private static processPageContent(filePath: string, fileMtime: number, fileName: string, folderTitle: string): Page | undefined { - const article = ArticleHelper.getFrontMatterByPath(filePath); - - if (article?.data.title) { - const wsFolder = Folders.getWorkspaceFolder(); - const descriptionField = Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description; - - const dateField = ArticleHelper.getPublishDateField(article) || DefaultFields.PublishingDate; - const dateFieldValue = article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField]) : undefined; - - const modifiedField = ArticleHelper.getModifiedDateField(article) || null; - const modifiedFieldValue = modifiedField && article?.data[modifiedField] ? DateHelper.tryParse(article?.data[modifiedField])?.getTime() : undefined; - - const staticFolder = Folders.getStaticFolderRelativePath(); - - const page: Page = { - ...article.data, - // FrontMatter properties - fmFolder: folderTitle, - fmFilePath: filePath, - fmFileName: fileName, - fmDraft: ContentType.getDraftStatus(article?.data), - fmModified: modifiedFieldValue ? modifiedFieldValue : fileMtime, - fmPublished: dateFieldValue ? dateFieldValue.getTime() : null, - fmYear: dateFieldValue ? dateFieldValue.getFullYear() : null, - fmPreviewImage: "", - fmTags: [], - fmCategories: [], - fmContentType: DEFAULT_CONTENT_TYPE_NAME, - fmBody: article?.content || "", - // Make sure these are always set - title: article?.data.title, - slug: article?.data.slug, - date: article?.data[dateField] || "", - draft: article?.data.draft, - description: article?.data[descriptionField] || "", - }; - - const contentType = ArticleHelper.getContentType(article.data); - if (contentType) { - page.fmContentType = contentType.name; - } - - let previewFieldParents = ContentType.findPreviewField(contentType.fields); - if (previewFieldParents.length === 0) { - const previewField = contentType.fields.find(field => field.type === "image" && field.name === "preview"); - if (previewField) { - previewFieldParents = ["preview"]; - } - } - - let tagParents = ContentType.findFieldByType(contentType.fields, "tags"); - const tagsValue = ContentType.getFieldValue(article.data, tagParents.length !== 0 ? tagParents : ["tags"]); - page.fmTags = typeof tagsValue === "string" ? tagsValue.split(",") : tagsValue; - - let categoryParents = ContentType.findFieldByType(contentType.fields, "categories"); - const categoriesValue = ContentType.getFieldValue(article.data, categoryParents.length !== 0 ? categoryParents : ["categories"]); - page.fmCategories = typeof categoriesValue === "string" ? categoriesValue.split(",") : categoriesValue; - - // Check if parent fields were retrieved, if not there was no image present - if (previewFieldParents.length > 0) { - let fieldValue = null; - let crntPageData = article?.data; - - for (let i = 0; i < previewFieldParents.length; i++) { - const previewField = previewFieldParents[i]; - - if (i === previewFieldParents.length - 1) { - fieldValue = crntPageData[previewField]; - } else { - if (!crntPageData[previewField]) { - continue; - } - - crntPageData = crntPageData[previewField]; - - // Check for preview image in block data - if (crntPageData instanceof Array && crntPageData.length > 0) { - // Get the first field block that contains the next field data - const fieldData = crntPageData.find(item => item[previewFieldParents[i + 1]]); - if (fieldData) { - crntPageData = fieldData; - } else { - continue; - } - } - } - } - - if (fieldValue && wsFolder) { - if (fieldValue && Array.isArray(fieldValue)) { - if (fieldValue.length > 0) { - fieldValue = fieldValue[0]; - } else { - fieldValue = undefined; - } - } - - // Revalidate as the array could have been empty - if (fieldValue) { - const staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue); - const contentFolderPath = join(dirname(filePath), fieldValue); - - let previewUri = null; - if (existsSync(staticPath)) { - previewUri = Uri.file(staticPath); - } else if (existsSync(contentFolderPath)) { - previewUri = Uri.file(contentFolderPath); - } - - if (previewUri) { - const preview = Dashboard.getWebview()?.asWebviewUri(previewUri); - page["fmPreviewImage"] = preview?.toString() || ""; - } - } - } - } - - return page; - } - - return; - } } \ No newline at end of file diff --git a/src/listeners/dashboard/SettingsListener.ts b/src/listeners/dashboard/SettingsListener.ts index 728c6356..e9c7ee9b 100644 --- a/src/listeners/dashboard/SettingsListener.ts +++ b/src/listeners/dashboard/SettingsListener.ts @@ -42,15 +42,15 @@ export class SettingsListener extends BaseListener { private static async update(data: { name: string, value: any }) { if (data.name) { await Settings.update(data.name, data.value); - this.getSettings(); + this.getSettings(true); } } /** * Retrieve the settings for the dashboard */ - public static async getSettings() { - const settings = await DashboardSettings.get(); + public static async getSettings(clear: boolean = false) { + const settings = await DashboardSettings.get(clear); this.sendMsg(DashboardCommand.settings, settings); } @@ -74,7 +74,7 @@ export class SettingsListener extends BaseListener { } } - SettingsListener.getSettings(); + SettingsListener.getSettings(true); } private static addFolder(folder: string) { diff --git a/src/listeners/dashboard/SnippetListener.ts b/src/listeners/dashboard/SnippetListener.ts index d1887c3b..f472f4cc 100644 --- a/src/listeners/dashboard/SnippetListener.ts +++ b/src/listeners/dashboard/SnippetListener.ts @@ -57,7 +57,7 @@ export class SnippetListener extends BaseListener { snippets[title] = snippetContent; await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true); - SettingsListener.getSettings(); + SettingsListener.getSettings(true); } private static async updateSnippet(data: any) { @@ -69,7 +69,7 @@ export class SnippetListener extends BaseListener { } await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true); - SettingsListener.getSettings(); + SettingsListener.getSettings(true); } private static async insertSnippet(data: any) { diff --git a/src/services/PagesParser.ts b/src/services/PagesParser.ts new file mode 100644 index 00000000..1bf31b8b --- /dev/null +++ b/src/services/PagesParser.ts @@ -0,0 +1,219 @@ +import { parseWinPath } from './../helpers/parseWinPath'; +import { existsSync } from "fs"; +import { dirname, join } from "path"; +import { StatusBarAlignment, Uri, window } from "vscode"; +import { Dashboard } from "../commands/Dashboard"; +import { Folders } from "../commands/Folders"; +import { DefaultFields, DEFAULT_CONTENT_TYPE_NAME, SETTING_SEO_DESCRIPTION_FIELD } from "../constants"; +import { Page } from "../dashboardWebView/models"; +import { ArticleHelper, ContentType, DateHelper, isValidFile, Logger, Notifications, Settings } from "../helpers"; + + +export class PagesParser { + public static allPages: Page[] = []; + private static parser: Promise | undefined; + private static initialized: boolean = false; + + public static start() { + if (!this.parser) { + this.parser = this.parsePages(); + } + } + + public static getPages(cb: (pages: Page[]) => void) { + if (this.parser) { + this.parser.then(() => cb(PagesParser.allPages)); + } else if (!PagesParser.initialized) { + this.parser = this.parsePages(); + this.parser.then(() => cb(PagesParser.allPages)); + } else if (PagesParser.allPages === undefined || PagesParser.allPages.length === 0) { + this.parser = this.parsePages(); + this.parser.then(() => cb(PagesParser.allPages)); + } else { + cb(PagesParser.allPages); + } + } + + public static async reset() { + this.parser = undefined; + PagesParser.allPages = []; + } + + public static async parsePages() { + // Update the dashboard with the fresh data + const folderInfo = await Folders.getInfo(); + const pages: Page[] = []; + const statusBar = window.createStatusBarItem(StatusBarAlignment.Left); + + if (folderInfo) { + statusBar.text = '$(sync~spin) Processing pages...'; + statusBar.show(); + + for (const folder of folderInfo) { + for (const file of folder.lastModified) { + if (isValidFile(file.fileName)) { + try { + const page = this.processPageContent(file.filePath, file.mtime, file.fileName, folder.title); + + if (page && !pages.find(p => p.fmFilePath === page.fmFilePath)) { + pages.push(page); + } + + } catch (error: any) { + if ((error as Error)?.message.toLowerCase() === "webview is disposed") { + continue; + } + + Logger.error(`PagesParser::parsePages: ${file.filePath} - ${error.message}`); + Notifications.error(`File error: ${file.filePath} - ${error?.message || error}`); + } + } + } + } + } + + this.parser = undefined; + this.initialized = true; + PagesParser.allPages = [...pages]; + statusBar.hide(); + } + + /** + * Process the page content + * @param filePath + * @param fileMtime + * @param fileName + * @param folderTitle + * @returns + */ + public static processPageContent(filePath: string, fileMtime: number, fileName: string, folderTitle: string): Page | undefined { + const article = ArticleHelper.getFrontMatterByPath(filePath); + + if (article?.data.title) { + const wsFolder = Folders.getWorkspaceFolder(); + const descriptionField = Settings.get(SETTING_SEO_DESCRIPTION_FIELD) as string || DefaultFields.Description; + + const dateField = ArticleHelper.getPublishDateField(article) || DefaultFields.PublishingDate; + const dateFieldValue = article?.data[dateField] ? DateHelper.tryParse(article?.data[dateField]) : undefined; + + const modifiedField = ArticleHelper.getModifiedDateField(article) || null; + const modifiedFieldValue = modifiedField && article?.data[modifiedField] ? DateHelper.tryParse(article?.data[modifiedField])?.getTime() : undefined; + + const staticFolder = Folders.getStaticFolderRelativePath(); + + const page: Page = { + ...article.data, + // FrontMatter properties + fmFolder: folderTitle, + fmFilePath: filePath, + fmFileName: fileName, + fmDraft: ContentType.getDraftStatus(article?.data), + fmModified: modifiedFieldValue ? modifiedFieldValue : fileMtime, + fmPublished: dateFieldValue ? dateFieldValue.getTime() : null, + fmYear: dateFieldValue ? dateFieldValue.getFullYear() : null, + fmPreviewImage: "", + fmTags: [], + fmCategories: [], + fmContentType: DEFAULT_CONTENT_TYPE_NAME, + fmBody: article?.content || "", + // Make sure these are always set + title: article?.data.title, + slug: article?.data.slug, + date: article?.data[dateField] || "", + draft: article?.data.draft, + description: article?.data[descriptionField] || "", + }; + + const contentType = ArticleHelper.getContentType(article.data); + if (contentType) { + page.fmContentType = contentType.name; + } + + let previewFieldParents = ContentType.findPreviewField(contentType.fields); + if (previewFieldParents.length === 0) { + const previewField = contentType.fields.find(field => field.type === "image" && field.name === "preview"); + if (previewField) { + previewFieldParents = ["preview"]; + } + } + + let tagParents = ContentType.findFieldByType(contentType.fields, "tags"); + const tagsValue = ContentType.getFieldValue(article.data, tagParents.length !== 0 ? tagParents : ["tags"]); + page.fmTags = typeof tagsValue === "string" ? tagsValue.split(",") : tagsValue; + + let categoryParents = ContentType.findFieldByType(contentType.fields, "categories"); + const categoriesValue = ContentType.getFieldValue(article.data, categoryParents.length !== 0 ? categoryParents : ["categories"]); + page.fmCategories = typeof categoriesValue === "string" ? categoriesValue.split(",") : categoriesValue; + + // Check if parent fields were retrieved, if not there was no image present + if (previewFieldParents.length > 0) { + let fieldValue = null; + let crntPageData = article?.data; + + for (let i = 0; i < previewFieldParents.length; i++) { + const previewField = previewFieldParents[i]; + + if (i === previewFieldParents.length - 1) { + fieldValue = crntPageData[previewField]; + } else { + if (!crntPageData[previewField]) { + continue; + } + + crntPageData = crntPageData[previewField]; + + // Check for preview image in block data + if (crntPageData instanceof Array && crntPageData.length > 0) { + // Get the first field block that contains the next field data + const fieldData = crntPageData.find(item => item[previewFieldParents[i + 1]]); + if (fieldData) { + crntPageData = fieldData; + } else { + continue; + } + } + } + } + + if (fieldValue && wsFolder) { + if (fieldValue && Array.isArray(fieldValue)) { + if (fieldValue.length > 0) { + fieldValue = fieldValue[0]; + } else { + fieldValue = undefined; + } + } + + // Revalidate as the array could have been empty + if (fieldValue) { + const staticPath = join(wsFolder.fsPath, staticFolder || "", fieldValue); + const contentFolderPath = join(dirname(filePath), fieldValue); + + let previewUri = null; + if (existsSync(staticPath)) { + previewUri = Uri.file(staticPath); + } else if (existsSync(contentFolderPath)) { + previewUri = Uri.file(contentFolderPath); + } + + if (previewUri) { + const previewPath = Dashboard.getWebview()?.asWebviewUri(previewUri); + let preview = previewPath?.toString(); + + if (!preview) { + const fileUrl = parseWinPath(previewUri.fsPath); + preview = `https://file%2B.vscode-resource.vscode-cdn.net/${fileUrl.startsWith(`/`) ? fileUrl.substr(1) : fileUrl}`; + } + + page["fmPreviewImage"] = preview?.toString() || ""; + } + } + } + } + + return page; + } + + return; + } +} \ No newline at end of file From 726a26850d0f5912032d381dbef3196720bda14a Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sun, 2 Oct 2022 14:23:51 +0200 Subject: [PATCH 10/49] #431 - Cache changes + Tab navigation --- src/dashboardWebView/hooks/usePages.tsx | 63 +++++++++++++++++------- src/dashboardWebView/models/Page.ts | 6 ++- src/listeners/dashboard/PagesListener.ts | 3 +- src/services/PagesParser.ts | 50 +++++++++++++++++-- 4 files changed, 95 insertions(+), 27 deletions(-) diff --git a/src/dashboardWebView/hooks/usePages.tsx b/src/dashboardWebView/hooks/usePages.tsx index f54a685d..d290aa5a 100644 --- a/src/dashboardWebView/hooks/usePages.tsx +++ b/src/dashboardWebView/hooks/usePages.tsx @@ -13,6 +13,7 @@ import { parseWinPath } from '../../helpers/parseWinPath'; export default function usePages(pages: Page[]) { const [ pageItems, setPageItems ] = useState([]); + const [ sortedPages, setSortedPages ] = useState([]); const [ sorting, setSorting ] = useRecoilState(SortingAtom); const [ tabInfo , setTabInfo ] = useRecoilState(TabInfoAtom); const settings = useRecoilValue(SettingsSelector); @@ -22,8 +23,10 @@ export default function usePages(pages: Page[]) { const tag = useRecoilValue(TagSelector); const category = useRecoilValue(CategorySelector); - const processPages = useCallback((searchedPages: Page[]) => { - const draftField = settings?.draftField; + /** + * Process all the pages by applying the sorting, filtering and searching. + */ + const processPages = useCallback((searchedPages: Page[], fullProcess: boolean = true) => { const framework = settings?.crntFramework; // Filter the pages @@ -93,40 +96,52 @@ export default function usePages(pages: Page[]) { pagesSorted = pagesSorted.filter(page => page.fmCategories && page.fmCategories.includes(category)); } + setSortedPages(pagesSorted); + }, [ settings, tab, folder, search, tag, category, sorting, tabInfo ]); + + + /** + * Process the pages when the tab changes + */ + const processByTab = useCallback((pages: Page[]) => { + const draftField = settings?.draftField; + + let crntPages: Page[] = Object.assign([], pages); + // Process the tab data const draftTypes = Object.assign({}, tabInfo); - draftTypes[Tab.All] = pagesSorted.length; + draftTypes[Tab.All] = crntPages.length; // Filter by draft status if (draftField && draftField.type === 'choice') { const draftChoices = settings?.draftField?.choices; for (const choice of (draftChoices || [])) { if (choice) { - draftTypes[choice] = pagesSorted.filter(page => page.fmDraft === choice).length; + draftTypes[choice] = crntPages.filter(page => page.fmDraft === choice).length; } } if (tab !== Tab.All) { - pagesSorted = pagesSorted.filter(page => page.fmDraft === tab); + crntPages = crntPages.filter(page => page.fmDraft === tab); } else { - pagesSorted = pagesSorted; + crntPages = crntPages; } } else { // Draft field is a boolean field const draftFieldName = draftField?.name || "draft"; - const drafts = pagesSorted.filter(page => page[draftFieldName] == true || page[draftFieldName] === "true"); - const published = pagesSorted.filter(page => page[draftFieldName] == false || page[draftFieldName] === "false" || typeof page[draftFieldName] === "undefined"); + const drafts = crntPages.filter(page => page[draftFieldName] == true || page[draftFieldName] === "true"); + const published = crntPages.filter(page => page[draftFieldName] == false || page[draftFieldName] === "false" || typeof page[draftFieldName] === "undefined"); draftTypes[Tab.Draft] = draftField?.invert ? published.length : drafts.length; draftTypes[Tab.Published] = draftField?.invert ? drafts.length : published.length; if (tab === Tab.Published) { - pagesSorted = draftField?.invert ? drafts : published; + crntPages = draftField?.invert ? drafts : published; } else if (tab === Tab.Draft) { - pagesSorted = draftField?.invert ? published : drafts; + crntPages = draftField?.invert ? published : drafts; } else { - pagesSorted = pagesSorted; + crntPages = crntPages; } } @@ -134,10 +149,14 @@ export default function usePages(pages: Page[]) { setTabInfo(draftTypes); // Set the pages - setPageItems(pagesSorted); - }, [ settings, tab, folder, search, tag, category, sorting, tabInfo ]); - + setPageItems(crntPages); + }, [ tab, tabInfo, settings ]); + + /** + * Search listener for filtered pages + * @param message + */ const searchListener = (message: MessageEvent>) => { switch (message.data.command) { case DashboardMessage.searchPages: @@ -146,6 +165,7 @@ export default function usePages(pages: Page[]) { } }; + useEffect(() => { let usedSorting = sorting; @@ -160,15 +180,20 @@ export default function usePages(pages: Page[]) { // Check if search needs to be performed let searchedPages = pages; if (search) { - // const fuse = new Fuse(pages, fuseOptions); - // const results = fuse.search(search); - // searchedPages = results.map(page => page.item); - Messenger.send(DashboardMessage.searchPages, { query: search }); } else { processPages(searchedPages); } - }, [ settings?.draftField, pages, sorting, search, tab, tag, category, folder ]); + }, [ settings?.draftField, pages, sorting, search, tag, category, folder ]); + + + useEffect(() => { + console.log("useEffect: tab", tab, sortedPages.length); + if (sortedPages.length > 0) { + processByTab(sortedPages); + } + }, [sortedPages, tab]) + useEffect(() => { Messenger.listen(searchListener); diff --git a/src/dashboardWebView/models/Page.ts b/src/dashboardWebView/models/Page.ts index 3d4dc1d7..ce690799 100644 --- a/src/dashboardWebView/models/Page.ts +++ b/src/dashboardWebView/models/Page.ts @@ -1,6 +1,10 @@ -import { Uri } from "vscode"; export interface Page { + // Properties for caching + fmCachePath: string; + fmCacheModifiedTime: number; + + // Front matter fields fmFolder: string; fmFilePath: string; fmFileName: string; diff --git a/src/listeners/dashboard/PagesListener.ts b/src/listeners/dashboard/PagesListener.ts index a7b92fe5..37531d6b 100644 --- a/src/listeners/dashboard/PagesListener.ts +++ b/src/listeners/dashboard/PagesListener.ts @@ -161,8 +161,7 @@ export class PagesListener extends BaseListener { this.sendPageData(pages); this.sendMsg(DashboardCommand.searchReady, true); - - await ext.setState(ExtensionState.Dashboard.Pages.Cache, pages, "workspace"); + await this.createSearchIndex(pages); }); } diff --git a/src/services/PagesParser.ts b/src/services/PagesParser.ts index 1bf31b8b..4a7bb6c6 100644 --- a/src/services/PagesParser.ts +++ b/src/services/PagesParser.ts @@ -4,22 +4,30 @@ import { dirname, join } from "path"; import { StatusBarAlignment, Uri, window } from "vscode"; import { Dashboard } from "../commands/Dashboard"; import { Folders } from "../commands/Folders"; -import { DefaultFields, DEFAULT_CONTENT_TYPE_NAME, SETTING_SEO_DESCRIPTION_FIELD } from "../constants"; +import { DefaultFields, DEFAULT_CONTENT_TYPE_NAME, ExtensionState, SETTING_SEO_DESCRIPTION_FIELD } from "../constants"; import { Page } from "../dashboardWebView/models"; -import { ArticleHelper, ContentType, DateHelper, isValidFile, Logger, Notifications, Settings } from "../helpers"; +import { ArticleHelper, ContentType, DateHelper, Extension, isValidFile, Logger, Notifications, Settings } from "../helpers"; export class PagesParser { public static allPages: Page[] = []; + public static cachedPages: Page[] | undefined = undefined; private static parser: Promise | undefined; private static initialized: boolean = false; + /** + * Start the page parser + */ public static start() { if (!this.parser) { this.parser = this.parsePages(); } } + /** + * Retrieve the pages + * @param cb + */ public static getPages(cb: (pages: Page[]) => void) { if (this.parser) { this.parser.then(() => cb(PagesParser.allPages)); @@ -34,12 +42,20 @@ export class PagesParser { } } + /** + * Reset the cache + */ public static async reset() { this.parser = undefined; PagesParser.allPages = []; } + /** + * Parse all pages in the workspace + */ public static async parsePages() { + const ext = Extension.getInstance(); + // Update the dashboard with the fresh data const folderInfo = await Folders.getInfo(); const pages: Page[] = []; @@ -53,12 +69,15 @@ export class PagesParser { for (const file of folder.lastModified) { if (isValidFile(file.fileName)) { try { - const page = this.processPageContent(file.filePath, file.mtime, file.fileName, folder.title); + let page = await PagesParser.getCachedPage(file.filePath, file.mtime); - if (page && !pages.find(p => p.fmFilePath === page.fmFilePath)) { + if (!page) { + page = this.processPageContent(file.filePath, file.mtime, file.fileName, folder.title); + } + + if (page && !pages.find(p => p.fmFilePath === page?.fmFilePath)) { pages.push(page); } - } catch (error: any) { if ((error as Error)?.message.toLowerCase() === "webview is disposed") { continue; @@ -72,12 +91,30 @@ export class PagesParser { } } + await ext.setState(ExtensionState.Dashboard.Pages.Cache, pages, "workspace"); + PagesParser.cachedPages = undefined; + this.parser = undefined; this.initialized = true; PagesParser.allPages = [...pages]; statusBar.hide(); } + /** + * Find the page in the cached data + * @param filePath + * @param modifiedTime + * @returns + */ + public static async getCachedPage(filePath: string, modifiedTime: number): Promise { + if (!PagesParser.cachedPages) { + const ext = Extension.getInstance(); + PagesParser.cachedPages = await ext.getState(ExtensionState.Dashboard.Pages.Cache, "workspace") || []; + } + + return PagesParser.cachedPages.find(p => p.fmCachePath === parseWinPath(filePath) && p.fmCacheModifiedTime === modifiedTime); + } + /** * Process the page content * @param filePath @@ -103,6 +140,9 @@ export class PagesParser { const page: Page = { ...article.data, + // Cache properties + fmCachePath: parseWinPath(filePath), + fmCacheModifiedTime: fileMtime, // FrontMatter properties fmFolder: folderTitle, fmFilePath: filePath, From 0c6ae47a7b6ceef9940c42d134160da18fa850d4 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sun, 2 Oct 2022 14:25:11 +0200 Subject: [PATCH 11/49] #434 - Webview errors are logged in the extension output --- CHANGELOG.md | 1 + src/commands/Dashboard.ts | 3 +- src/dashboardWebView/DashboardMessage.ts | 1 + src/dashboardWebView/components/App.tsx | 44 ++++++++++++------- .../components/ErrorView/index.tsx | 14 ++++++ src/listeners/dashboard/LogListener.ts | 21 +++++++++ src/listeners/dashboard/index.ts | 1 + 7 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 src/dashboardWebView/components/ErrorView/index.tsx create mode 100644 src/listeners/dashboard/LogListener.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bd0190d9..ba79bc65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#406](https://github.com/estruyf/vscode-front-matter/issues/406): Added support for single data entries in the data dashboard - [#428](https://github.com/estruyf/vscode-front-matter/issues/428): Improved UX for inserting images to your content +- [#434](https://github.com/estruyf/vscode-front-matter/issues/434): Webview errors are logged in the extension output ### ⚡️ Optimizations diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index d34e682f..2bcc13e4 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -7,7 +7,7 @@ import { Extension } from '../helpers/Extension'; import { WebviewHelper } from '@estruyf/vscode'; import { DashboardData } from '../models/DashboardData'; import { MediaLibrary } from '../helpers/MediaLibrary'; -import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener, SnippetListener, TaxonomyListener } from '../listeners/dashboard'; +import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener, SnippetListener, TaxonomyListener, LogListener } from '../listeners/dashboard'; import { MediaListener as PanelMediaListener } from '../listeners/panel' import { GitListener, ModeListener } from '../listeners/general'; @@ -148,6 +148,7 @@ export class Dashboard { ModeListener.process(msg); GitListener.process(msg); TaxonomyListener.process(msg); + LogListener.process(msg); }); } diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index a261c287..a09ae77d 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -57,4 +57,5 @@ export enum DashboardMessage { setState = 'setState', runCustomScript = 'runCustomScript', sendTelemetry = 'sendTelemetry', + logError = 'logError', } \ No newline at end of file diff --git a/src/dashboardWebView/components/App.tsx b/src/dashboardWebView/components/App.tsx index 36085faf..9c56f034 100644 --- a/src/dashboardWebView/components/App.tsx +++ b/src/dashboardWebView/components/App.tsx @@ -16,6 +16,9 @@ import { Route, Routes, useNavigate } from 'react-router-dom'; import { routePaths } from '..'; import { useEffect, useMemo } from 'react'; import { UnknownView } from './UnknownView'; +import { ErrorBoundary } from '@sentry/react'; +import { ErrorView } from './ErrorView'; +import { DashboardMessage } from '../DashboardMessage'; export interface IAppProps { showWelcome: boolean; @@ -68,23 +71,32 @@ export const App: React.FunctionComponent = ({showWelcome}: React.Pro } return ( -
    - - } /> - } /> - } /> - } /> - - { - allowDataView && } /> - } + )} + onError={(error: Error, componentStack: string, eventId: string) => { + Messenger.send(DashboardMessage.logError, `Event ID: ${eventId} +Message: ${error.message} - { - allowTaxonomyView && } /> - } +Stack: ${componentStack}`); + }}> +
    + + } /> + } /> + } /> + } /> + + { + allowDataView && } /> + } - } /> - -
    + { + allowTaxonomyView && } /> + } + + } /> +
    +
    + ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/ErrorView/index.tsx b/src/dashboardWebView/components/ErrorView/index.tsx new file mode 100644 index 00000000..17efb741 --- /dev/null +++ b/src/dashboardWebView/components/ErrorView/index.tsx @@ -0,0 +1,14 @@ +import { ExclamationIcon } from '@heroicons/react/solid'; +import * as React from 'react'; + +export interface IErrorViewProps {} + +export const ErrorView: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( +
    + +

    Sorry, something went wrong.

    +

    Please close the dashboard and try again.

    +
    + ); +}; \ No newline at end of file diff --git a/src/listeners/dashboard/LogListener.ts b/src/listeners/dashboard/LogListener.ts new file mode 100644 index 00000000..2c43f12c --- /dev/null +++ b/src/listeners/dashboard/LogListener.ts @@ -0,0 +1,21 @@ +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; +import { Logger } from "../../helpers"; +import { BaseListener } from "./BaseListener"; + + +export class LogListener extends BaseListener { + + /** + * Process the messages for the dashboard views + * @param msg + */ + public static process(msg: { command: DashboardMessage, data: any }) { + super.process(msg); + + switch(msg.command) { + case DashboardMessage.logError: + Logger.error(msg.data); + break; + } + } +} \ No newline at end of file diff --git a/src/listeners/dashboard/index.ts b/src/listeners/dashboard/index.ts index a26e99d8..8ede40ed 100644 --- a/src/listeners/dashboard/index.ts +++ b/src/listeners/dashboard/index.ts @@ -8,3 +8,4 @@ export * from './SettingsListener'; export * from './SnippetListener'; export * from './TelemetryListener'; export * from './TaxonomyListener'; +export * from './LogListener'; From 9f3cfd9d3a0700b1bd658edce8c36efab84cd526 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sun, 2 Oct 2022 14:25:11 +0200 Subject: [PATCH 12/49] #434 - Webview errors are logged in the extension output --- CHANGELOG.md | 1 + src/commands/Dashboard.ts | 3 +- src/dashboardWebView/DashboardMessage.ts | 1 + src/dashboardWebView/components/App.tsx | 44 ++++++++++++------- .../components/ErrorView/index.tsx | 14 ++++++ src/listeners/dashboard/LogListener.ts | 21 +++++++++ src/listeners/dashboard/index.ts | 1 + 7 files changed, 68 insertions(+), 17 deletions(-) create mode 100644 src/dashboardWebView/components/ErrorView/index.tsx create mode 100644 src/listeners/dashboard/LogListener.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index bd0190d9..ba79bc65 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#406](https://github.com/estruyf/vscode-front-matter/issues/406): Added support for single data entries in the data dashboard - [#428](https://github.com/estruyf/vscode-front-matter/issues/428): Improved UX for inserting images to your content +- [#434](https://github.com/estruyf/vscode-front-matter/issues/434): Webview errors are logged in the extension output ### ⚡️ Optimizations diff --git a/src/commands/Dashboard.ts b/src/commands/Dashboard.ts index b99c44ee..82758a17 100644 --- a/src/commands/Dashboard.ts +++ b/src/commands/Dashboard.ts @@ -7,7 +7,7 @@ import { Extension } from '../helpers/Extension'; import { WebviewHelper } from '@estruyf/vscode'; import { DashboardData } from '../models/DashboardData'; import { MediaLibrary } from '../helpers/MediaLibrary'; -import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener, SnippetListener, TaxonomyListener } from '../listeners/dashboard'; +import { DashboardListener, MediaListener, SettingsListener, TelemetryListener, DataListener, PagesListener, ExtensionListener, SnippetListener, TaxonomyListener, LogListener } from '../listeners/dashboard'; import { MediaListener as PanelMediaListener } from '../listeners/panel' import { GitListener, ModeListener } from '../listeners/general'; @@ -148,6 +148,7 @@ export class Dashboard { ModeListener.process(msg); GitListener.process(msg); TaxonomyListener.process(msg); + LogListener.process(msg); }); } diff --git a/src/dashboardWebView/DashboardMessage.ts b/src/dashboardWebView/DashboardMessage.ts index a261c287..a09ae77d 100644 --- a/src/dashboardWebView/DashboardMessage.ts +++ b/src/dashboardWebView/DashboardMessage.ts @@ -57,4 +57,5 @@ export enum DashboardMessage { setState = 'setState', runCustomScript = 'runCustomScript', sendTelemetry = 'sendTelemetry', + logError = 'logError', } \ No newline at end of file diff --git a/src/dashboardWebView/components/App.tsx b/src/dashboardWebView/components/App.tsx index 36085faf..9c56f034 100644 --- a/src/dashboardWebView/components/App.tsx +++ b/src/dashboardWebView/components/App.tsx @@ -16,6 +16,9 @@ import { Route, Routes, useNavigate } from 'react-router-dom'; import { routePaths } from '..'; import { useEffect, useMemo } from 'react'; import { UnknownView } from './UnknownView'; +import { ErrorBoundary } from '@sentry/react'; +import { ErrorView } from './ErrorView'; +import { DashboardMessage } from '../DashboardMessage'; export interface IAppProps { showWelcome: boolean; @@ -68,23 +71,32 @@ export const App: React.FunctionComponent = ({showWelcome}: React.Pro } return ( -
    - - } /> - } /> - } /> - } /> - - { - allowDataView && } /> - } + )} + onError={(error: Error, componentStack: string, eventId: string) => { + Messenger.send(DashboardMessage.logError, `Event ID: ${eventId} +Message: ${error.message} - { - allowTaxonomyView && } /> - } +Stack: ${componentStack}`); + }}> +
    + + } /> + } /> + } /> + } /> + + { + allowDataView && } /> + } - } /> - -
    + { + allowTaxonomyView && } /> + } + + } /> +
    +
    + ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/ErrorView/index.tsx b/src/dashboardWebView/components/ErrorView/index.tsx new file mode 100644 index 00000000..17efb741 --- /dev/null +++ b/src/dashboardWebView/components/ErrorView/index.tsx @@ -0,0 +1,14 @@ +import { ExclamationIcon } from '@heroicons/react/solid'; +import * as React from 'react'; + +export interface IErrorViewProps {} + +export const ErrorView: React.FunctionComponent = (props: React.PropsWithChildren) => { + return ( +
    + +

    Sorry, something went wrong.

    +

    Please close the dashboard and try again.

    +
    + ); +}; \ No newline at end of file diff --git a/src/listeners/dashboard/LogListener.ts b/src/listeners/dashboard/LogListener.ts new file mode 100644 index 00000000..2c43f12c --- /dev/null +++ b/src/listeners/dashboard/LogListener.ts @@ -0,0 +1,21 @@ +import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; +import { Logger } from "../../helpers"; +import { BaseListener } from "./BaseListener"; + + +export class LogListener extends BaseListener { + + /** + * Process the messages for the dashboard views + * @param msg + */ + public static process(msg: { command: DashboardMessage, data: any }) { + super.process(msg); + + switch(msg.command) { + case DashboardMessage.logError: + Logger.error(msg.data); + break; + } + } +} \ No newline at end of file diff --git a/src/listeners/dashboard/index.ts b/src/listeners/dashboard/index.ts index a26e99d8..8ede40ed 100644 --- a/src/listeners/dashboard/index.ts +++ b/src/listeners/dashboard/index.ts @@ -8,3 +8,4 @@ export * from './SettingsListener'; export * from './SnippetListener'; export * from './TelemetryListener'; export * from './TaxonomyListener'; +export * from './LogListener'; From 5c9d7eda1763fa11e9ed9e812fd6e1cd0ee7d8a1 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Sun, 2 Oct 2022 20:01:59 +0200 Subject: [PATCH 13/49] #433 - fix title and description rendering if not string --- CHANGELOG.md | 2 ++ .../components/Contents/Item.tsx | 28 +++++++++++++++---- 2 files changed, 24 insertions(+), 6 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index ba79bc65..2951ed9c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -16,6 +16,8 @@ ### 🐞 Fixes +- [#433](https://github.com/estruyf/vscode-front-matter/issues/433): Fix issue with rendering an incorrect title value on the content dashboard + ## [8.1.1] - 2022-09-23 ### 🐞 Fixes diff --git a/src/dashboardWebView/components/Contents/Item.tsx b/src/dashboardWebView/components/Contents/Item.tsx index bfd4becc..3f140774 100644 --- a/src/dashboardWebView/components/Contents/Item.tsx +++ b/src/dashboardWebView/components/Contents/Item.tsx @@ -19,6 +19,22 @@ export const Item: React.FunctionComponent = ({ fmFilePath, date, ti const view = useRecoilValue(ViewSelector); const settings = useRecoilValue(SettingsSelector); const draftField = useMemo(() => settings?.draftField, [settings]); + + const escapedTitle = useMemo(() => { + if (title && typeof title !== 'string') { + return ''; + } + + return title; + }, [title]); + + const escapedDescription = useMemo(() => { + if (description && typeof description !== 'string') { + return ''; + } + + return description; + }, [description]); const openFile = () => { Messenger.send(DashboardMessage.openFile, fmFilePath); @@ -57,7 +73,7 @@ export const Item: React.FunctionComponent = ({ fmFilePath, date, ti + - + { tags && tags.length > 0 && ( @@ -110,13 +126,13 @@ export const Item: React.FunctionComponent = ({ fmFilePath, date, ti
    Date: Mon, 3 Oct 2022 13:22:40 +0200 Subject: [PATCH 14/49] Clear cache command --- package.json | 5 ++++ src/commands/Cache.ts | 24 +++++++++++++++++++ src/commands/index.ts | 10 ++++++++ src/constants/Extension.ts | 3 +++ .../components/Contents/Item.tsx | 4 ++-- src/extension.ts | 4 ++++ src/services/PagesParser.ts | 14 +++++++++-- 7 files changed, 60 insertions(+), 4 deletions(-) create mode 100644 src/commands/Cache.ts diff --git a/package.json b/package.json index 70f986d1..d67f9d0d 100644 --- a/package.json +++ b/package.json @@ -1742,6 +1742,11 @@ "command": "frontMatter.git.sync", "title": "Sync", "category": "Front Matter" + }, + { + "command": "frontMatter.cache.clear", + "title": "Clear cache", + "category": "Front Matter" } ], "menus": { diff --git a/src/commands/Cache.ts b/src/commands/Cache.ts new file mode 100644 index 00000000..5175f5b8 --- /dev/null +++ b/src/commands/Cache.ts @@ -0,0 +1,24 @@ +import { commands } from "vscode"; +import { COMMAND_NAME, ExtensionState } from "../constants"; +import { Extension, Notifications } from "../helpers"; + +export class Cache { + + public static async registerCommands() { + const ext = Extension.getInstance(); + const subscriptions = ext.subscriptions; + + subscriptions.push( + commands.registerCommand(COMMAND_NAME.clearCache, Cache.clear) + ); + } + + 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"); + + Notifications.info("Cache cleared"); + } +} \ No newline at end of file diff --git a/src/commands/index.ts b/src/commands/index.ts index d5aa6bdc..7c391ede 100644 --- a/src/commands/index.ts +++ b/src/commands/index.ts @@ -1,3 +1,13 @@ export * from './Article'; +export * from './Backers'; +export * from './Cache'; +export * from './Content'; +export * from './Dashboard'; +export * from './Diagnostics'; +export * from './Folders'; +export * from './Preview'; +export * from './Project'; export * from './Settings'; export * from './StatusListener'; +export * from './Template'; +export * from './Wysiwyg'; diff --git a/src/constants/Extension.ts b/src/constants/Extension.ts index 6e0eb252..4dc68df7 100644 --- a/src/constants/Extension.ts +++ b/src/constants/Extension.ts @@ -72,4 +72,7 @@ export const COMMAND_NAME = { // Config reloadConfig: getCommandName("config.reload"), + + // Cache + clearCache: getCommandName("cache.clear"), }; \ No newline at end of file diff --git a/src/dashboardWebView/components/Contents/Item.tsx b/src/dashboardWebView/components/Contents/Item.tsx index 3f140774..2b7f3d4a 100644 --- a/src/dashboardWebView/components/Contents/Item.tsx +++ b/src/dashboardWebView/components/Contents/Item.tsx @@ -95,9 +95,9 @@ export const Item: React.FunctionComponent = ({ fmFilePath, date, ti onOpen={openFile} />
    - + - + { tags && tags.length > 0 && ( diff --git a/src/extension.ts b/src/extension.ts index f949b35c..40ec3644 100644 --- a/src/extension.ts +++ b/src/extension.ts @@ -8,6 +8,7 @@ import { Folders } from './commands/Folders'; import { Preview } from './commands/Preview'; import { Project } from './commands/Project'; import { Template } from './commands/Template'; +import { Cache } from './commands/Cache'; import { COMMAND_NAME, TelemetryEvent } from './constants'; import { TaxonomyType } from './models'; import { MarkdownFoldingProvider } from './providers/MarkdownFoldingProvider'; @@ -271,6 +272,9 @@ export async function activate(context: vscode.ExtensionContext) { DashboardSettings.get(); PagesParser.start(); + // Cache commands + Cache.registerCommands(); + // Subscribe all commands subscriptions.push( insertTags, diff --git a/src/services/PagesParser.ts b/src/services/PagesParser.ts index 4a7bb6c6..7c50b787 100644 --- a/src/services/PagesParser.ts +++ b/src/services/PagesParser.ts @@ -138,6 +138,16 @@ export class PagesParser { const staticFolder = Folders.getStaticFolderRelativePath(); + let escapedTitle = article?.data.title; + if (escapedTitle && typeof escapedTitle !== "string") { + escapedTitle = ""; + } + + let escapedDescription = article?.data[descriptionField] || ""; + if (escapedDescription && typeof escapedDescription !== "string") { + escapedDescription = ""; + } + const page: Page = { ...article.data, // Cache properties @@ -157,11 +167,11 @@ export class PagesParser { fmContentType: DEFAULT_CONTENT_TYPE_NAME, fmBody: article?.content || "", // Make sure these are always set - title: article?.data.title, + title: escapedTitle, slug: article?.data.slug, date: article?.data[dateField] || "", draft: article?.data.draft, - description: article?.data[descriptionField] || "", + description: escapedDescription, }; const contentType = ArticleHelper.getContentType(article.data); From 5a565f1154e33244d1d2dcf3248d305118b407b9 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 3 Oct 2022 14:07:14 +0200 Subject: [PATCH 15/49] #412 - Override duplicates in config split --- .../components/Header/Header.tsx | 2 +- src/helpers/SettingsHelper.ts | 68 +++++++++++++------ 2 files changed, 48 insertions(+), 22 deletions(-) diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index 5be485bb..05437604 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -180,7 +180,7 @@ export const Header: React.FunctionComponent = ({header, totalPage
    { - (settings?.dashboardState.contents.pagination) && (totalPages || 0) > PAGE_LIMIT && (!grouping || grouping === GroupOption.none) && ( + (settings?.dashboardState?.contents?.pagination) && (totalPages || 0) > PAGE_LIMIT && (!grouping || grouping === GroupOption.none) && (
    diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index e3eafc3c..b566345c 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -3,7 +3,7 @@ import { Telemetry } from './Telemetry'; import { Notifications } from './Notifications'; import { commands, Uri, workspace, window } from 'vscode'; import * as vscode from 'vscode'; -import { ContentType, CustomTaxonomy, TaxonomyType } from '../models'; +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 { Folders } from '../commands/Folders'; import { join, basename, dirname, parse } from 'path'; @@ -477,35 +477,41 @@ export class Settings { } // Array settings - if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES.toLowerCase() || - relSettingName === SETTING_CONTENT_PAGE_FOLDERS.toLowerCase() || - relSettingName === SETTING_CONTENT_PLACEHOLDERS.toLowerCase() || - relSettingName === SETTING_CUSTOM_SCRIPTS.toLowerCase() || - relSettingName === SETTING_DATA_FILES.toLowerCase() || - relSettingName === SETTING_DATA_FOLDERS.toLowerCase() || - relSettingName === SETTING_DATA_TYPES.toLowerCase()) { + if (relSettingName === SETTING_CUSTOM_SCRIPTS.toLowerCase()) { // Get the correct setting name let settingNameValue = "" - if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES.toLowerCase()) { - settingNameValue = SETTING_TAXONOMY_CONTENT_TYPES; - } else if (relSettingName === SETTING_CONTENT_PAGE_FOLDERS.toLowerCase()) { - settingNameValue = SETTING_CONTENT_PAGE_FOLDERS; - } else if (relSettingName === SETTING_CONTENT_PLACEHOLDERS.toLowerCase()) { - settingNameValue = SETTING_CONTENT_PLACEHOLDERS; - } else if (relSettingName === SETTING_CUSTOM_SCRIPTS.toLowerCase()) { + if (relSettingName === SETTING_CUSTOM_SCRIPTS.toLowerCase()) { settingNameValue = SETTING_CUSTOM_SCRIPTS; - } else if (relSettingName === SETTING_DATA_FILES.toLowerCase()) { - settingNameValue = SETTING_DATA_FILES; - } else if (relSettingName === SETTING_DATA_FOLDERS.toLowerCase()) { - settingNameValue = SETTING_DATA_FOLDERS; - } else if (relSettingName === SETTING_DATA_TYPES.toLowerCase()) { - settingNameValue = SETTING_DATA_TYPES; } const crntValue = Settings.globalConfig[`${CONFIG_KEY}.${settingNameValue}`] || []; Settings.globalConfig[`${CONFIG_KEY}.${settingNameValue}`] = [...crntValue, configJson]; } + // Content types + else if (relSettingName === SETTING_TAXONOMY_CONTENT_TYPES.toLowerCase()) { + Settings.updateGlobalConfigArraySetting(SETTING_TAXONOMY_CONTENT_TYPES, "name", configJson); + } + // Data files + else if (relSettingName === SETTING_DATA_FILES.toLowerCase()) { + Settings.updateGlobalConfigArraySetting(SETTING_DATA_FILES, "id", configJson); + } + // Data folders + else if (relSettingName === SETTING_DATA_FOLDERS.toLowerCase()) { + Settings.updateGlobalConfigArraySetting(SETTING_DATA_FOLDERS, "id", configJson); + } + // Data types + else if (relSettingName === SETTING_DATA_TYPES.toLowerCase()) { + Settings.updateGlobalConfigArraySetting(SETTING_DATA_TYPES, "id", configJson); + } + // Page folders + else if (relSettingName === SETTING_CONTENT_PAGE_FOLDERS.toLowerCase()) { + Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_PAGE_FOLDERS, "path", configJson); + } + // Placeholders + else if (relSettingName === SETTING_CONTENT_PLACEHOLDERS.toLowerCase()) { + Settings.updateGlobalConfigArraySetting(SETTING_CONTENT_PLACEHOLDERS, "id", configJson); + } // Object settings else if (relSettingName === SETTING_CONTENT_SNIPPETS.toLowerCase()) { // Filename is the key @@ -519,6 +525,26 @@ export class Settings { } } + /** + * Update an array setting in the global config + * @param settingName + * @param fieldName + * @param configJson + */ + 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[itemIdx] = configJson; + } else { + crntValue.push(configJson); + } + + Settings.globalConfig[`${CONFIG_KEY}.${settingName}`] = [...crntValue]; + } + /** * Create a file creation watcher */ From 45eb542619fcdeec1f6017ac278d6823634a7078 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Mon, 3 Oct 2022 21:31:23 +0200 Subject: [PATCH 16/49] #431 - Allow pagination page nr --- package.json | 4 +- .../components/Contents/Overview.tsx | 9 +-- .../components/Header/Header.tsx | 13 ++-- .../components/Header/Pagination.tsx | 36 +++++------ .../components/Header/PaginationStatus.tsx | 29 +++++---- src/dashboardWebView/hooks/useMedia.tsx | 13 ++-- src/dashboardWebView/hooks/usePages.tsx | 1 - src/dashboardWebView/hooks/usePagination.tsx | 62 +++++++++++++++++++ src/dashboardWebView/models/Settings.ts | 2 +- src/helpers/DashboardSettings.ts | 2 +- 10 files changed, 120 insertions(+), 51 deletions(-) create mode 100644 src/dashboardWebView/hooks/usePagination.tsx diff --git a/package.json b/package.json index d67f9d0d..c32a2db2 100644 --- a/package.json +++ b/package.json @@ -447,9 +447,9 @@ "scope": "Custom scripts" }, "frontMatter.dashboard.content.pagination": { - "type": "boolean", + "type": ["boolean", "number"], "default": true, - "markdownDescription": "Specify if you want to enable/disable pagination for your content. [Check in the docs](https://frontmatter.codes/docs/settings#frontmatter.dashboard.content.pagination)", + "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" }, "frontMatter.dashboard.content.cardTags": { diff --git a/src/dashboardWebView/components/Contents/Overview.tsx b/src/dashboardWebView/components/Contents/Overview.tsx index b83f1c7b..3cd2a760 100644 --- a/src/dashboardWebView/components/Contents/Overview.tsx +++ b/src/dashboardWebView/components/Contents/Overview.tsx @@ -9,9 +9,9 @@ import { GroupOption } from '../../constants/GroupOption'; import { Page } from '../../models/Page'; import { Settings } from '../../models/Settings'; import { GroupingSelector, PageAtom } from '../../state'; -import { PAGE_LIMIT } from '../Header/Pagination'; import { Item } from './Item'; import { List } from './List'; +import usePagination from '../../hooks/usePagination'; export interface IOverviewProps { pages: Page[]; @@ -21,14 +21,15 @@ export interface IOverviewProps { export const Overview: React.FunctionComponent = ({pages, settings}: React.PropsWithChildren) => { const grouping = useRecoilValue(GroupingSelector); const page = useRecoilValue(PageAtom); + const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination); const pagedPages = useMemo(() => { - if (settings?.dashboardState.contents.pagination) { - return pages.slice(page * PAGE_LIMIT, ((page + 1) * PAGE_LIMIT)); + if (pageSetNr) { + return pages.slice(page * pageSetNr, ((page + 1) * pageSetNr)); } return pages; - }, [pages, page, settings]); + }, [pages, page, pageSetNr]); const groupName = useCallback((groupId, groupedPages) => { if (grouping === GroupOption.Draft) { diff --git a/src/dashboardWebView/components/Header/Header.tsx b/src/dashboardWebView/components/Header/Header.tsx index 5be485bb..230ddeeb 100644 --- a/src/dashboardWebView/components/Header/Header.tsx +++ b/src/dashboardWebView/components/Header/Header.tsx @@ -23,8 +23,10 @@ import { useLocation, useNavigate } from 'react-router-dom'; import { routePaths } from '../..'; import { useEffect, useMemo } from 'react'; import { SyncButton } from './SyncButton'; -import { PAGE_LIMIT, Pagination } from './Pagination'; +import { Pagination } from './Pagination'; import { GroupOption } from '../../constants/GroupOption'; +import usePagination from '../../hooks/usePagination'; +import { PaginationStatus } from './PaginationStatus'; export interface IHeaderProps { header?: React.ReactNode; @@ -37,13 +39,14 @@ export interface IHeaderProps { folders?: string[]; } -export const Header: React.FunctionComponent = ({header, totalPages, folders, settings }: React.PropsWithChildren) => { +export const Header: React.FunctionComponent = ({header, totalPages, settings }: React.PropsWithChildren) => { const [ crntTag, setCrntTag ] = useRecoilState(TagAtom); const [ crntCategory, setCrntCategory ] = useRecoilState(CategoryAtom); const grouping = useRecoilValue(GroupingSelector); const resetSorting = useResetRecoilState(SortingAtom); const location = useLocation(); const navigate = useNavigate(); + const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination); const createContent = () => { Messenger.send(DashboardMessage.createContent); @@ -180,8 +183,10 @@ export const Header: React.FunctionComponent = ({header, totalPage
    { - (settings?.dashboardState.contents.pagination) && (totalPages || 0) > PAGE_LIMIT && (!grouping || grouping === GroupOption.none) && ( -
    + (pageSetNr > 0) && (totalPages || 0) > pageSetNr && (!grouping || grouping === GroupOption.none) && ( +
    + +
    ) diff --git a/src/dashboardWebView/components/Header/Pagination.tsx b/src/dashboardWebView/components/Header/Pagination.tsx index 3b52baf3..967046a8 100644 --- a/src/dashboardWebView/components/Header/Pagination.tsx +++ b/src/dashboardWebView/components/Header/Pagination.tsx @@ -1,43 +1,37 @@ import * as React from 'react'; -import { useEffect, useMemo } from 'react'; -import { useLocation } from 'react-router-dom'; +import { useCallback, useEffect, useMemo } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; -import { routePaths } from '../..'; -import { MediaTotalSelector, PageAtom } from '../../state'; +import usePagination from '../../hooks/usePagination'; +import { MediaTotalSelector, PageAtom, SettingsAtom } from '../../state'; import { PaginationButton } from './PaginationButton'; export interface IPaginationProps { totalPages?: number; } -export const PAGE_LIMIT = 16; - export const Pagination: React.FunctionComponent = ({ totalPages }: React.PropsWithChildren) => { const [ page, setPage ] = useRecoilState(PageAtom); const totalMedia = useRecoilValue(MediaTotalSelector); - const location = useLocation(); + const settings = useRecoilValue(SettingsAtom); + const { pageSetNr, totalPagesNr } = usePagination(settings?.dashboardState.contents.pagination, totalPages, totalMedia); - const totalItems: number = useMemo(() => { - if (location.pathname === routePaths.contents) { - return Math.ceil((totalPages || 0) / PAGE_LIMIT) - 1 - } else { - return Math.ceil(totalMedia / PAGE_LIMIT) - 1; - } - }, [location.pathname, totalPages, totalMedia]); - - const getButtons = (): number[] => { + const getButtons = useCallback((): number[] => { const maxButtons = 5; const buttons: number[] = []; const start = page - maxButtons; const end = page + maxButtons; for (let i = start; i <= end; i++) { - if (i >= 0 && i <= totalItems) { + if (i >= 0 && i <= totalPagesNr) { buttons.push(i); } } return buttons; - }; + }, [page, totalPagesNr]); + + useEffect(() => { + setPage(0); + }, [pageSetNr]); useEffect(() => { setPage(0); @@ -77,13 +71,13 @@ export const Pagination: React.FunctionComponent = ({ totalPag = totalItems} + disabled={page >= totalPagesNr} onClick={() => setPage(page + 1)} /> = totalItems} - onClick={() => setPage(totalItems)} /> + disabled={page >= totalPagesNr} + onClick={() => setPage(totalPagesNr)} />
    ); }; \ No newline at end of file diff --git a/src/dashboardWebView/components/Header/PaginationStatus.tsx b/src/dashboardWebView/components/Header/PaginationStatus.tsx index b145e18e..ce3caea6 100644 --- a/src/dashboardWebView/components/Header/PaginationStatus.tsx +++ b/src/dashboardWebView/components/Header/PaginationStatus.tsx @@ -1,27 +1,32 @@ import * as React from 'react'; +import { useMemo } from 'react'; import { useRecoilValue } from 'recoil'; -import { MediaTotalSelector, PageAtom } from '../../state'; -import { PAGE_LIMIT } from './Pagination'; +import usePagination from '../../hooks/usePagination'; +import { MediaTotalSelector, PageAtom, SettingsAtom } from '../../state'; -export interface IPaginationStatusProps {} +export interface IPaginationStatusProps { + totalPages?: number; +} -export const PaginationStatus: React.FunctionComponent = (props: React.PropsWithChildren) => { +export const PaginationStatus: React.FunctionComponent = ({ totalPages }: React.PropsWithChildren) => { const totalMedia = useRecoilValue(MediaTotalSelector); const page = useRecoilValue(PageAtom); + const settings = useRecoilValue(SettingsAtom); + const { pageSetNr, totalItems } = usePagination(settings?.dashboardState.contents.pagination, totalPages || 0, totalMedia); - const getTotalPage = () => { - const mediaItems = ((page + 1) * PAGE_LIMIT); - if (totalMedia < mediaItems) { - return totalMedia; + const totelItemsOnPage = useMemo(() => { + const items = ((page + 1) * pageSetNr); + if (totalItems < items) { + return totalItems; } - return mediaItems; - }; + return totalItems; + }, [page, totalMedia, pageSetNr]); return (

    - Showing {(page * PAGE_LIMIT) + 1} to {getTotalPage()} of{' '} - {totalMedia} results + Showing {(page * pageSetNr) + 1} to {totelItemsOnPage} of{' '} + {totalItems} results

    ); diff --git a/src/dashboardWebView/hooks/useMedia.tsx b/src/dashboardWebView/hooks/useMedia.tsx index 95fcd21c..44c4ddeb 100644 --- a/src/dashboardWebView/hooks/useMedia.tsx +++ b/src/dashboardWebView/hooks/useMedia.tsx @@ -4,9 +4,9 @@ import { useState, useEffect, useCallback } from 'react'; import { useRecoilState, useRecoilValue } from 'recoil'; import { MediaInfo, MediaPaths } from '../../models'; import { DashboardCommand } from '../DashboardCommand'; -import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, PageAtom, SearchAtom, SelectedMediaFolderAtom } from '../state'; +import { LoadingAtom, MediaFoldersAtom, MediaTotalAtom, PageAtom, SearchAtom, SelectedMediaFolderAtom, SettingsAtom } from '../state'; import Fuse from 'fuse.js'; -import { PAGE_LIMIT } from '../components/Header/Pagination'; +import usePagination from './usePagination'; const fuseOptions: Fuse.IFuseOptions = { keys: [ @@ -28,10 +28,12 @@ export default function useMedia() { const [ , setFolders ] = useRecoilState(MediaFoldersAtom); const [ , setLoading ] = useRecoilState(LoadingAtom); const search = useRecoilValue(SearchAtom); + const settings = useRecoilValue(SettingsAtom); + const { pageSetNr } = usePagination(settings?.dashboardState.contents.pagination); const getMedia = useCallback(() => { - return searchedMedia.slice(page * PAGE_LIMIT, ((page + 1) * PAGE_LIMIT)); - }, [searchedMedia, page]); + return searchedMedia.slice(page * pageSetNr, ((page + 1) * pageSetNr)); + }, [searchedMedia, page, pageSetNr]); const messageListener = (message: MessageEvent>) => { if (message.data.command === DashboardCommand.media) { @@ -57,8 +59,9 @@ export default function useMedia() { return; } + setTotal(media.length); setSearchedMedia(media); - }, [search]); + }, [search, media]); useEffect(() => { Messenger.listen(messageListener); diff --git a/src/dashboardWebView/hooks/usePages.tsx b/src/dashboardWebView/hooks/usePages.tsx index d290aa5a..f36c496c 100644 --- a/src/dashboardWebView/hooks/usePages.tsx +++ b/src/dashboardWebView/hooks/usePages.tsx @@ -188,7 +188,6 @@ export default function usePages(pages: Page[]) { useEffect(() => { - console.log("useEffect: tab", tab, sortedPages.length); if (sortedPages.length > 0) { processByTab(sortedPages); } diff --git a/src/dashboardWebView/hooks/usePagination.tsx b/src/dashboardWebView/hooks/usePagination.tsx new file mode 100644 index 00000000..4cf5a5b1 --- /dev/null +++ b/src/dashboardWebView/hooks/usePagination.tsx @@ -0,0 +1,62 @@ +import { useMemo } from 'react'; +import { useLocation } from 'react-router-dom'; +import { routePaths } from '..'; + +export const PAGE_LIMIT = 16; + +export default function usePagination(value: number | boolean | null | undefined, totalPages?: number, totalMedia?: number) { + const location = useLocation(); + + const pagination = useMemo(() => { + if (location.pathname === routePaths.contents) { + if (typeof value === 'number') { + const pageNr = value > 0 ? value : 0; + if (pageNr > 52) { + return 52; + } + return pageNr; + } else if (typeof value === 'boolean') { + return value ? PAGE_LIMIT : 0; + } + } + + return PAGE_LIMIT; + }, [value, location.pathname]); + + + const totalPagesNr: number = useMemo(() => { + if (location.pathname === routePaths.contents) { + if (totalPages) { + return Math.ceil((totalPages || 0) / pagination) - 1 + } + } else { + if (totalMedia) { + return Math.ceil(totalMedia / pagination) - 1; + } + } + return 0; + }, [location.pathname, totalPages, totalMedia, pagination]); + + /** + * The total items (pages or media) + */ + const totalItems: number = useMemo(() => { + if (location.pathname === routePaths.contents) { + if (totalPages) { + return totalPages; + } + } else { + if (totalMedia) { + return totalMedia; + } + } + return 0; + }, [location.pathname, totalPages, totalMedia, pagination]); + + + return { + pageSetNr: pagination, + totalPagesNr, + totalItems + }; +} \ No newline at end of file diff --git a/src/dashboardWebView/models/Settings.ts b/src/dashboardWebView/models/Settings.ts index ee1ab44e..a29cc4d2 100644 --- a/src/dashboardWebView/models/Settings.ts +++ b/src/dashboardWebView/models/Settings.ts @@ -44,7 +44,7 @@ export interface ContentsViewState { defaultSorting: string | null | undefined; tags: string | null | undefined; templatesEnabled: boolean | null | undefined; - pagination: boolean | null | undefined; + pagination: boolean | number | null | undefined; } export interface MediaViewState extends ContentsViewState { diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index cd254173..6119a1fb 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -31,7 +31,7 @@ export class DashboardSettings { const wsFolder = Folders.getWorkspaceFolder(); const isInitialized = Project.isInitialized(); const gitActions = Settings.get(SETTING_GIT_ENABLED); - const pagination = Settings.get(SETTING_DASHBOARD_CONTENT_PAGINATION) + const pagination = Settings.get(SETTING_DASHBOARD_CONTENT_PAGINATION) const settings = { git: { From 888e5c5229a063776f49f4bfa5bcde2ca3cd859b Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 4 Oct 2022 13:47:41 +0200 Subject: [PATCH 17/49] Get webview URI for Windows --- src/services/PagesParser.ts | 30 ++++++++++++++++++++++++------ 1 file changed, 24 insertions(+), 6 deletions(-) diff --git a/src/services/PagesParser.ts b/src/services/PagesParser.ts index 7c50b787..24bb3b8a 100644 --- a/src/services/PagesParser.ts +++ b/src/services/PagesParser.ts @@ -247,15 +247,13 @@ export class PagesParser { } if (previewUri) { - const previewPath = Dashboard.getWebview()?.asWebviewUri(previewUri); - let preview = previewPath?.toString(); + let previewPath = Dashboard.getWebview()?.asWebviewUri(previewUri); - if (!preview) { - const fileUrl = parseWinPath(previewUri.fsPath); - preview = `https://file%2B.vscode-resource.vscode-cdn.net/${fileUrl.startsWith(`/`) ? fileUrl.substr(1) : fileUrl}`; + if (!previewPath) { + previewPath = PagesParser.getWebviewUri(previewUri); } - page["fmPreviewImage"] = preview?.toString() || ""; + page["fmPreviewImage"] = previewPath?.toString() || ""; } } } @@ -266,4 +264,24 @@ export class PagesParser { return; } + + /** + * Get the webview URI + * @param resource + * @returns + */ + private static getWebviewUri(resource: Uri) { + // Logic from: https://github.com/microsoft/vscode/blob/main/src/vs/workbench/common/webview.ts + const webviewResourceBaseHost = 'vscode-cdn.net'; + const webviewRootResourceAuthority = `vscode-resource.${webviewResourceBaseHost}`; + + const authority = `${resource.scheme}+${encodeURI(resource.authority)}.${webviewRootResourceAuthority}`; + return Uri.from({ + scheme: "https", + authority, + path: resource.path, + query: resource.query, + fragment: resource.fragment + }); + } } \ No newline at end of file From f89d4fce3f691a69e489549c0345f86acf0c06aa Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Tue, 4 Oct 2022 17:05:04 +0200 Subject: [PATCH 18/49] #431 - Update changelog --- CHANGELOG.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2951ed9c..e32ae3c3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ ### ⚡️ Optimizations +- [#431](https://github.com/estruyf/vscode-front-matter/issues/431): Performance improvements for the content dashboard + ### 🐞 Fixes - [#433](https://github.com/estruyf/vscode-front-matter/issues/433): Fix issue with rendering an incorrect title value on the content dashboard From 4e850e5cb904322bd2cb59b84b6a824fb1c196b2 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 6 Oct 2022 14:23:49 +0200 Subject: [PATCH 19/49] Fix field error message color --- CHANGELOG.md | 1 + src/panelWebView/styles.css | 1 + 2 files changed, 2 insertions(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e32ae3c3..60d4e3af 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ ### 🐞 Fixes +- Fix field error message color - [#433](https://github.com/estruyf/vscode-front-matter/issues/433): Fix issue with rendering an incorrect title value on the content dashboard ## [8.1.1] - 2022-09-23 diff --git a/src/panelWebView/styles.css b/src/panelWebView/styles.css index 70703a7d..efdfd560 100644 --- a/src/panelWebView/styles.css +++ b/src/panelWebView/styles.css @@ -348,6 +348,7 @@ } .metadata_field__required__message { + color: var(--vscode-inputValidation-errorBorder); padding-top: .5rem; font-size: .9rem; margin-left: .5rem; From fad5ad7243ce6a394292de1ba261b5ecd82b5792 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 6 Oct 2022 21:36:51 +0200 Subject: [PATCH 20/49] Moving away from fs sync methods --- src/commands/Folders.ts | 9 +++++---- src/commands/Project.ts | 13 +++++++------ src/commands/Template.ts | 14 +++++++------- src/helpers/ArticleHelper.ts | 13 +++++++------ src/helpers/ContentType.ts | 9 +++++---- src/helpers/CustomScript.ts | 6 +++--- src/helpers/DashboardSettings.ts | 2 +- src/helpers/DataFileHelper.ts | 9 +++++---- src/helpers/Extension.ts | 2 +- src/helpers/FrameworkDetector.ts | 17 +++++++++-------- src/helpers/MediaHelpers.ts | 17 +++++++++-------- src/helpers/SettingsHelper.ts | 17 +++++++++-------- src/helpers/TaxonomyHelper.ts | 10 +++++----- src/listeners/dashboard/DataListener.ts | 14 ++++++++------ src/listeners/dashboard/PagesListener.ts | 6 +++--- src/listeners/dashboard/SettingsListener.ts | 4 ++-- src/services/PagesParser.ts | 6 +++--- src/utils/copyFileAsync.ts | 4 ++++ src/utils/existsAsync.ts | 6 ++++++ src/utils/index.ts | 7 +++++++ src/utils/mkdirAsync.ts | 4 ++++ src/utils/readFileAsync.ts | 4 ++++ src/utils/readdirAsync.ts | 4 ++++ src/utils/unlinkAsync.ts | 4 ++++ src/utils/writeFileAsync.ts | 4 ++++ 25 files changed, 126 insertions(+), 79 deletions(-) create mode 100644 src/utils/copyFileAsync.ts create mode 100644 src/utils/existsAsync.ts create mode 100644 src/utils/index.ts create mode 100644 src/utils/mkdirAsync.ts create mode 100644 src/utils/readFileAsync.ts create mode 100644 src/utils/readdirAsync.ts create mode 100644 src/utils/unlinkAsync.ts create mode 100644 src/utils/writeFileAsync.ts diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index ea39ae10..69ff8ef8 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -7,7 +7,7 @@ import uniqBy = require("lodash.uniqby"); import { Template } from "./Template"; import { Notifications } from "../helpers/Notifications"; import { Logger, Settings } from "../helpers"; -import { existsSync, mkdirSync } from 'fs'; +import { existsSync } from 'fs'; import { format } from 'date-fns'; import { Dashboard } from './Dashboard'; import { parseWinPath } from '../helpers/parseWinPath'; @@ -16,6 +16,7 @@ import { MediaListener, PagesListener, SettingsListener } from '../listeners/das import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes'; import { Telemetry } from '../helpers/Telemetry'; import { glob } from 'glob'; +import { mkdirAsync } from '../utils/mkdirAsync'; export const WORKSPACE_PLACEHOLDER = `[[workspace]]`; @@ -63,7 +64,7 @@ export class Folders { parentFolders.push(folder); if (!existsSync(folderPath)) { - mkdirSync(folderPath); + await mkdirAsync(folderPath); } } @@ -210,9 +211,9 @@ export class Folders { if (!projectFolder) { window.showWorkspaceFolderPick({ placeHolder: `Please select the main workspace folder for Front Matter to use.` - }).then(selectedFolder => { + }).then(async (selectedFolder) => { if (selectedFolder) { - Settings.createGlobalFile(selectedFolder.uri); + await Settings.createGlobalFile(selectedFolder.uri); // Full reload to make sure the whole extension is reloaded correctly commands.executeCommand(`workbench.action.reloadWindow`); } diff --git a/src/commands/Project.ts b/src/commands/Project.ts index d7cb3488..c9daa83b 100644 --- a/src/commands/Project.ts +++ b/src/commands/Project.ts @@ -2,13 +2,14 @@ import { DEFAULT_CONTENT_TYPE } from './../constants/ContentType'; import { Telemetry } from './../helpers/Telemetry'; import { workspace, Uri } from "vscode"; import { join } from "path"; -import * as fs from "fs"; import { Notifications } from "../helpers/Notifications"; import { Template } from "./Template"; import { Folders } from "./Folders"; import { FrameworkDetector, Logger, Settings } from "../helpers"; import { SETTING_CONTENT_DEFAULT_FILETYPE, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants"; import { SettingsListener } from '../listeners/dashboard'; +import { writeFileAsync } from '../utils'; +import { existsSync } from 'fs'; export class Project { @@ -34,7 +35,7 @@ categories: [] */ public static async init(sampleTemplate?: boolean) { try { - Settings.createTeamSettings(); + await Settings.createTeamSettings(); // Add the default content type Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true); @@ -49,10 +50,10 @@ categories: [] // Check if you can find the framework const wsFolder = Folders.getWorkspaceFolder(); - const framework = FrameworkDetector.get(wsFolder?.fsPath || ""); + const framework = await FrameworkDetector.get(wsFolder?.fsPath || ""); if (framework) { - SettingsListener.setFramework(framework.name); + await SettingsListener.setFramework(framework.name); } SettingsListener.getSettings(true); @@ -79,12 +80,12 @@ categories: [] const article = Uri.file(join(templatePath.fsPath, `article.${fileType}`)); - if (!fs.existsSync(templatePath.fsPath)) { + if (!existsSync(templatePath.fsPath)) { await workspace.fs.createDirectory(templatePath); } if (sampleTemplate) { - fs.writeFileSync(article.fsPath, Project.content, { encoding: "utf-8" }); + await writeFileAsync(article.fsPath, Project.content, { encoding: "utf-8" }); Notifications.info("Sample template created."); } } diff --git a/src/commands/Template.ts b/src/commands/Template.ts index 03b6119c..6201c2c3 100644 --- a/src/commands/Template.ts +++ b/src/commands/Template.ts @@ -1,7 +1,6 @@ import { Questions } from './../helpers/Questions'; import * as vscode from 'vscode'; import * as path from 'path'; -import * as fs from 'fs'; import { SETTING_CONTENT_DEFAULT_FILETYPE, SETTING_TEMPLATES_FOLDER, TelemetryEvent } from '../constants'; import { ArticleHelper, Settings } from '../helpers'; import { Article } from '.'; @@ -12,6 +11,7 @@ import { ContentType as IContentType } from '../models'; import { PagesListener } from '../listeners/dashboard'; import { extname } from 'path'; import { Telemetry } from '../helpers/Telemetry'; +import { writeFileAsync, copyFileAsync } from '../utils'; export class Template { @@ -60,7 +60,7 @@ export class Template { let fileContents = ArticleHelper.stringifyFrontMatter(keepContents === "no" ? "" : clonedArticle.content, clonedArticle.data); const templateFile = path.join(templatePath.fsPath, `${titleValue}.${fileType}`); - fs.writeFileSync(templateFile, fileContents, { encoding: "utf-8" }); + await writeFileAsync(templateFile, fileContents, { encoding: "utf-8" }); Notifications.info(`Template created and is now available in your ${folder} folder.`); } @@ -120,23 +120,23 @@ export class Template { return; } - const templateData = ArticleHelper.getFrontMatterByPath(template.fsPath); + const templateData = await ArticleHelper.getFrontMatterByPath(template.fsPath); let contentType: IContentType | undefined; if (templateData && templateData.data && templateData.data.type) { contentType = contentTypes?.find(t => t.name === templateData.data.type); } const fileExtension = extname(template.fsPath).replace(".", ""); - let newFilePath: string | undefined = ArticleHelper.createContent(contentType, folderPath, titleValue, fileExtension); + let newFilePath: string | undefined = await ArticleHelper.createContent(contentType, folderPath, titleValue, fileExtension); if (!newFilePath) { return; } // Start the new file creation - fs.copyFileSync(template.fsPath, newFilePath); + await copyFileAsync(template.fsPath, newFilePath); // Update the properties inside the template - let frontMatter = ArticleHelper.getFrontMatterByPath(newFilePath); + let frontMatter = await ArticleHelper.getFrontMatterByPath(newFilePath); if (!frontMatter) { Notifications.warning(`Something failed when retrieving the newly created file.`); return; @@ -147,7 +147,7 @@ export class Template { frontMatter = Article.updateDate(frontMatter); - fs.writeFileSync(newFilePath, ArticleHelper.stringifyFrontMatter(frontMatter.content, frontMatter.data), { encoding: "utf8" }); + await writeFileAsync(newFilePath, ArticleHelper.stringifyFrontMatter(frontMatter.content, frontMatter.data), { encoding: "utf8" }); await vscode.commands.executeCommand('vscode.open', vscode.Uri.file(newFilePath)); } diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index 2fa1ba5c..670ed422 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -4,7 +4,6 @@ import { Uri, workspace } from 'vscode'; 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, SETTING_CONTENT_DEFAULT_FILETYPE, SETTING_CONTENT_PLACEHOLDERS, SETTING_CONTENT_SUPPORTED_FILETYPES, SETTING_FILE_PRESERVE_CASING, SETTING_COMMA_SEPARATED_FIELDS, SETTING_DATE_FIELD, SETTING_DATE_FORMAT, SETTING_INDENT_ARRAY, SETTING_REMOVE_QUOTES, SETTING_SITE_BASEURL, SETTING_TAXONOMY_CONTENT_TYPES, SETTING_TEMPLATES_PREFIX, SETTING_MODIFIED_FIELD, DefaultFieldValues } from '../constants'; import { DumpOptions } from 'js-yaml'; import { FrontMatterParser, ParsedFrontMatter } from '../parsers'; @@ -15,7 +14,7 @@ import { Article } from '../commands'; import { join } from 'path'; import { EditorHelper } from '@estruyf/vscode'; import sanitize from '../helpers/Sanitize'; -import { existsSync, mkdirSync } from 'fs'; +import { existsSync } from 'fs'; import { ContentType } from '../models'; import { DateHelper } from './DateHelper'; import { DiagnosticSeverity, Position, window, Range } from 'vscode'; @@ -26,6 +25,8 @@ import { Content } from 'mdast'; import { processKnownPlaceholders } from './PlaceholderHelper'; import { CustomScript } from './CustomScript'; import { Folders } from '../commands/Folders'; +import { readFileAsync } from '../utils'; +import { mkdirAsync } from '../utils/mkdirAsync'; export class ArticleHelper { private static notifiedFiles: string[] = []; @@ -70,8 +71,8 @@ export class ArticleHelper { * Retrieve the file's front matter by its path * @param filePath */ - public static getFrontMatterByPath(filePath: string) { - const file = fs.readFileSync(filePath, { encoding: "utf-8" }); + public static async getFrontMatterByPath(filePath: string) { + const file = await readFileAsync(filePath, { encoding: "utf-8" }); return ArticleHelper.parseFile(file, filePath); } @@ -328,7 +329,7 @@ export class ArticleHelper { * @param titleValue * @returns The new file path */ - public static createContent(contentType: ContentType | undefined, folderPath: string, titleValue: string, fileExtension?: string): string | undefined { + public static async createContent(contentType: ContentType | undefined, folderPath: string, titleValue: string, fileExtension?: string): Promise { FrontMatterParser.currentContent = null; const prefix = Settings.get(SETTING_TEMPLATES_PREFIX); @@ -345,7 +346,7 @@ export class ArticleHelper { Notifications.error(`A page bundle with the name ${sanitizedName} already exists in ${folderPath}`); return; } else { - mkdirSync(newFolder); + await mkdirAsync(newFolder); newFilePath = join(newFolder, `index.${fileExtension || contentType.fileType || fileType}`); } } else { diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 7b64a2d9..6cc7be6f 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -6,13 +6,14 @@ import { ContentType as IContentType, DraftField, Field, FieldGroup, FieldType, import { Uri, commands, window, ProgressLocation, workspace } from 'vscode'; import { Folders } from "../commands/Folders"; import { Questions } from "./Questions"; -import { existsSync, writeFileSync } from "fs"; +import { existsSync } from "fs"; import { Notifications } from "./Notifications"; import { DEFAULT_CONTENT_TYPE_NAME } from "../constants/ContentType"; import { Telemetry } from './Telemetry'; import { processKnownPlaceholders } from './PlaceholderHelper'; import { basename } from 'path'; import { ParsedFrontMatter } from '../parsers'; +import { writeFileAsync } from '../utils'; export class ContentType { @@ -558,10 +559,10 @@ export class ContentType { let templateData: ParsedFrontMatter | null = null; if (templatePath) { templatePath = Folders.getAbsFilePath(templatePath); - templateData = ArticleHelper.getFrontMatterByPath(templatePath); + templateData = await ArticleHelper.getFrontMatterByPath(templatePath); } - let newFilePath: string | undefined = ArticleHelper.createContent(contentType, folderPath, titleValue); + let newFilePath: string | undefined = await ArticleHelper.createContent(contentType, folderPath, titleValue); if (!newFilePath) { return; } @@ -586,7 +587,7 @@ export class ContentType { const content = ArticleHelper.stringifyFrontMatter(templateData?.content || ``, data); - writeFileSync(newFilePath, content, { encoding: "utf8" }); + await writeFileAsync(newFilePath, content, { encoding: "utf8" }); // Check if the content type has a post script to execute if (contentType.postScript) { diff --git a/src/helpers/CustomScript.ts b/src/helpers/CustomScript.ts index 5f2db7db..7789b8cb 100644 --- a/src/helpers/CustomScript.ts +++ b/src/helpers/CustomScript.ts @@ -77,7 +77,7 @@ export class CustomScript { articlePath = editor.document.uri.fsPath; article = ArticleHelper.getFrontMatter(editor); } else { - article = ArticleHelper.getFrontMatterByPath(path); + article = await ArticleHelper.getFrontMatterByPath(path); } if (articlePath && article) { @@ -119,7 +119,7 @@ export class CustomScript { if (folder.lastModified.length > 0) { for await (const file of folder.lastModified) { try { - const article = ArticleHelper.getFrontMatterByPath(file.filePath); + const article = await ArticleHelper.getFrontMatterByPath(file.filePath); if (article) { const crntOutput = await CustomScript.runScript(wsPath, article, file.filePath, script); if (crntOutput) { @@ -220,7 +220,7 @@ export class CustomScript { articlePath = editor.document.uri.fsPath; article = ArticleHelper.getFrontMatter(editor); } else { - article = ArticleHelper.getFrontMatterByPath(articlePath); + article = await ArticleHelper.getFrontMatterByPath(articlePath); } if (article && article.data) { diff --git a/src/helpers/DashboardSettings.ts b/src/helpers/DashboardSettings.ts index 6119a1fb..b31c6286 100644 --- a/src/helpers/DashboardSettings.ts +++ b/src/helpers/DashboardSettings.ts @@ -53,7 +53,7 @@ export class DashboardSettings { customSorting: Settings.get(SETTING_CONTENT_SORTING), contentFolders: Folders.get(), crntFramework: Settings.get(SETTING_FRAMEWORK_ID), - framework: (!isInitialized && wsFolder) ? FrameworkDetector.get(wsFolder.fsPath) : null, + framework: (!isInitialized && wsFolder) ? await FrameworkDetector.get(wsFolder.fsPath) : null, scripts: (Settings.get(SETTING_CUSTOM_SCRIPTS) || []), date: { format: Settings.get(SETTING_DATE_FORMAT) || "" diff --git a/src/helpers/DataFileHelper.ts b/src/helpers/DataFileHelper.ts index e7c59d12..25f1547c 100644 --- a/src/helpers/DataFileHelper.ts +++ b/src/helpers/DataFileHelper.ts @@ -1,4 +1,4 @@ -import { existsSync, readFileSync } from "fs"; +import { existsSync } from "fs"; import { Folders } from "../commands/Folders"; import { DataFile } from "../models"; import * as yaml from 'js-yaml'; @@ -7,6 +7,7 @@ import { Notifications } from "./Notifications"; import { commands } from "vscode"; import { COMMAND_NAME, SETTING_DATA_FILES } from "../constants"; import { Settings } from "./SettingsHelper"; +import { readFileAsync } from "../utils"; export class DataFileHelper { @@ -16,10 +17,10 @@ export class DataFileHelper { * @param filePath * @returns */ - public static get(filePath: string) { + public static async get(filePath: string) { const absPath = Folders.getAbsFilePath(filePath); if (existsSync(absPath)) { - return readFileSync(absPath, 'utf8'); + return await readFileAsync(absPath, 'utf8'); } return null; @@ -53,7 +54,7 @@ export class DataFileHelper { public static async process(data: DataFile) { try { const { file, fileType } = data; - const dataFile = DataFileHelper.get(file); + const dataFile = await DataFileHelper.get(file); if (fileType === "yaml") { return yaml.safeLoad(dataFile || ""); diff --git a/src/helpers/Extension.ts b/src/helpers/Extension.ts index 4ab882ec..6a01ad69 100644 --- a/src/helpers/Extension.ts +++ b/src/helpers/Extension.ts @@ -154,7 +154,7 @@ export class Extension { // Create team settings if (Settings.hasSettings()) { - Settings.createTeamSettings(); + await Settings.createTeamSettings(); } const hideDateDeprecation = await Extension.getInstance().getState(ExtensionState.Updates.v7_0_0.dateFields, "workspace"); diff --git a/src/helpers/FrameworkDetector.ts b/src/helpers/FrameworkDetector.ts index d1607e0c..5c1c0b97 100644 --- a/src/helpers/FrameworkDetector.ts +++ b/src/helpers/FrameworkDetector.ts @@ -1,5 +1,5 @@ import * as jsoncParser from 'jsonc-parser'; -import { existsSync, readFileSync } from "fs"; +import { existsSync } from "fs"; import jsyaml = require("js-yaml"); import { join, resolve } from "path"; import { commands, Uri } from "vscode"; @@ -8,6 +8,7 @@ import { COMMAND_NAME } from "../constants"; import { FrameworkDetectors } from "../constants/FrameworkDetectors"; import { Framework } from "../models"; import { Logger } from "./Logger"; +import { readFileAsync } from '../utils'; export class FrameworkDetector { @@ -19,7 +20,7 @@ export class FrameworkDetector { return FrameworkDetectors.map((detector: any) => detector.framework); } - private static check(folder: string) { + private static async check(folder: string) { let dependencies = null; let devDependencies = null; let gemContent = null; @@ -28,7 +29,7 @@ export class FrameworkDetector { try { const pkgFile = join(folder, 'package.json'); if (existsSync(pkgFile)) { - let packageJson: any = readFileSync(pkgFile, "utf8"); + let packageJson: any = await readFileAsync(pkgFile, "utf8"); if (packageJson) { packageJson = typeof packageJson === "string" ? jsoncParser.parse(packageJson) : packageJson; @@ -44,7 +45,7 @@ export class FrameworkDetector { try { const gemFile = join(folder, 'Gemfile'); if (existsSync(gemFile)) { - gemContent = readFileSync(gemFile, "utf8"); + gemContent = await readFileAsync(gemFile, "utf8"); } } catch (e) { // do nothing @@ -81,21 +82,21 @@ export class FrameworkDetector { return undefined; } - public static checkDefaultSettings(framework: Framework) { + public static async checkDefaultSettings(framework: Framework) { if (framework.name.toLowerCase() === "jekyll") { - FrameworkDetector.jekyll(); + await FrameworkDetector.jekyll(); } } - private static jekyll() { + private static async jekyll() { try { const wsFolder = Folders.getWorkspaceFolder(); const jekyllConfig = join(wsFolder?.fsPath || "", '_config.yml'); let collectionDir = ""; if (existsSync(jekyllConfig)) { - const content = readFileSync(jekyllConfig, "utf8"); + const content = await readFileAsync(jekyllConfig, "utf8"); // Convert YAML to JSON const config = jsyaml.safeLoad(content); diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index d6295df5..6573913e 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -4,15 +4,16 @@ import { Folders } from "../commands/Folders"; import { DEFAULT_CONTENT_TYPE, ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTING_MEDIA_SUPPORTED_MIMETYPES } from "../constants"; import { SortingOption } from "../dashboardWebView/models"; import { MediaInfo, MediaPaths, SortOrder, SortType } from "../models"; -import { basename, extname, join, parse, dirname, relative } from "path"; -import { existsSync, readdirSync, statSync, unlinkSync, writeFileSync } from "fs"; -import { commands, Uri, workspace, window, Position } from "vscode"; +import { basename, join, parse, dirname, relative } from "path"; +import { existsSync, statSync } from "fs"; +import { Uri, workspace, window, Position } from "vscode"; import imageSize from "image-size"; import { EditorHelper } from "@estruyf/vscode"; import { SortOption } from "../dashboardWebView/constants/SortOption"; import { DataListener, MediaListener } from "../listeners/panel"; import { ArticleHelper } from "./ArticleHelper"; import { lookup } from "mime-types"; +import { readdirAsync, unlinkAsync, writeFileAsync } from "../utils"; export class MediaHelpers { @@ -151,14 +152,14 @@ export class MediaHelpers { if (selectedFolder) { if (existsSync(selectedFolder)) { - allFolders = readdirSync(selectedFolder, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(selectedFolder, dir.name))); + allFolders = (await readdirAsync(selectedFolder, { withFileTypes: true })).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(selectedFolder, dir.name))); } } else { if (pageBundleContentTypes.length > 0) { for (const contentFolder of contentFolders) { const contentPath = contentFolder.path; if (contentPath && existsSync(contentPath)) { - const subFolders = readdirSync(contentPath, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(contentPath, dir.name))); + const subFolders = (await readdirAsync(contentPath, { withFileTypes: true })).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(contentPath, dir.name))); allContentFolders = [...allContentFolders, ...subFolders]; } } @@ -166,7 +167,7 @@ export class MediaHelpers { const staticPath = join(parseWinPath(wsFolder?.fsPath || ""), staticFolder || ""); if (staticPath && existsSync(staticPath)) { - allFolders = readdirSync(staticPath, { withFileTypes: true }).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(staticPath, dir.name))); + allFolders = (await readdirAsync(staticPath, { withFileTypes: true })).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(staticPath, dir.name))); } } @@ -231,7 +232,7 @@ export class MediaHelpers { const imgData = decodeBase64(contents); if (imgData) { - writeFileSync(staticPath, imgData.data); + await writeFileAsync(staticPath, imgData.data); Notifications.info(`File ${fileName} uploaded to: ${folder}`); return true; @@ -255,7 +256,7 @@ export class MediaHelpers { } try { - unlinkSync(file); + await unlinkAsync(file); MediaHelpers.media = []; return true; diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index e3eafc3c..a98314d6 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -7,11 +7,12 @@ 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 } from '../constants'; import { Folders } from '../commands/Folders'; import { join, basename, dirname, parse } from 'path'; -import { existsSync, readFileSync, writeFileSync } from 'fs'; +import { existsSync } from 'fs'; import { Extension } from './Extension'; import { debounceCallback } from './DebounceCallback'; import { Logger } from './Logger'; import * as jsoncParser from 'jsonc-parser'; +import { readFileAsync, writeFileAsync } from '../utils'; export class Settings { public static globalFile = "frontmatter.json"; @@ -182,10 +183,10 @@ export class Settings { if (updateGlobal) { if (fmConfig && existsSync(fmConfig)) { - const localConfig = readFileSync(fmConfig, 'utf8'); + const localConfig = await readFileAsync(fmConfig, 'utf8'); Settings.globalConfig = jsoncParser.parse(localConfig); Settings.globalConfig[`${CONFIG_KEY}.${name}`] = value; - writeFileSync(fmConfig, JSON.stringify(Settings.globalConfig, null, 2), 'utf8'); + await writeFileAsync(fmConfig, JSON.stringify(Settings.globalConfig, null, 2), 'utf8'); const workspaceSettingValue = Settings.hasWorkspaceSettings(name); if (workspaceSettingValue) { @@ -215,16 +216,16 @@ export class Settings { /** * Create team settings */ - public static createTeamSettings() { + public static async createTeamSettings() { const wsFolder = Folders.getWorkspaceFolder(); - this.createGlobalFile(wsFolder); + await this.createGlobalFile(wsFolder); } /** * Create the frontmatter.json file * @param wsFolder */ - public static createGlobalFile(wsFolder: Uri | undefined | null) { + public static async createGlobalFile(wsFolder: Uri | undefined | null) { const initialConfig = { "$schema": `https://${Extension.getInstance().isBetaVersion() ? `beta.` : ``}frontmatter.codes/frontmatter.schema.json` }; @@ -232,7 +233,7 @@ export class Settings { if (wsFolder) { const configPath = join(wsFolder.fsPath, Settings.globalFile); if (!existsSync(configPath)) { - writeFileSync(configPath, JSON.stringify(initialConfig, null, 2), 'utf8'); + await writeFileAsync(configPath, JSON.stringify(initialConfig, null, 2), 'utf8'); } } } @@ -419,7 +420,7 @@ export class Settings { try { const fmConfig = Settings.projectConfigPath; if (fmConfig && existsSync(fmConfig)) { - const localConfig = readFileSync(fmConfig, 'utf8'); + const localConfig = await readFileAsync(fmConfig, 'utf8'); Settings.globalConfig = jsoncParser.parse(localConfig); commands.executeCommand('setContext', CONTEXT.isEnabled, true); } else { diff --git a/src/helpers/TaxonomyHelper.ts b/src/helpers/TaxonomyHelper.ts index 1327f321..1ffeac24 100644 --- a/src/helpers/TaxonomyHelper.ts +++ b/src/helpers/TaxonomyHelper.ts @@ -4,13 +4,13 @@ import { CustomTaxonomy, TaxonomyType, ContentType as IContentType } from "../mo import { FilesHelper } from "./FilesHelper"; import { ProgressLocation, window } from "vscode"; import { parseWinPath } from "./parseWinPath"; -import { readFileSync, writeFileSync } from "fs"; import { FrontMatterParser } from "../parsers"; import { DumpOptions } from "js-yaml"; import { Settings } from "./SettingsHelper"; import { Notifications } from "./Notifications"; import { ArticleHelper } from './ArticleHelper'; import { ContentType } from './ContentType'; +import { readFileAsync, writeFileAsync } from '../utils'; export class TaxonomyHelper { @@ -186,7 +186,7 @@ export class TaxonomyHelper { for (const file of allFiles) { progress.report({ increment: (++i/progressNr) }); - const mdFile = readFileSync(parseWinPath(file.fsPath), { encoding: "utf8" }); + const mdFile = await readFileAsync(parseWinPath(file.fsPath), { encoding: "utf8" }); if (mdFile) { try { @@ -217,7 +217,7 @@ export class TaxonomyHelper { const spaces = window.activeTextEditor?.options?.tabSize; // Update the file - writeFileSync(parseWinPath(file.fsPath), FrontMatterParser.toFile(article.content, article.data, mdFile, { + await writeFileAsync(parseWinPath(file.fsPath), FrontMatterParser.toFile(article.content, article.data, mdFile, { indent: spaces || 2 } as DumpOptions as any), { encoding: "utf8" }); } @@ -291,7 +291,7 @@ export class TaxonomyHelper { for (const file of allFiles) { progress.report({ increment: (++i/progressNr) }); - const mdFile = readFileSync(parseWinPath(file.fsPath), { encoding: "utf8" }); + const mdFile = await readFileAsync(parseWinPath(file.fsPath), { encoding: "utf8" }); if (mdFile) { try { @@ -324,7 +324,7 @@ export class TaxonomyHelper { const spaces = window.activeTextEditor?.options?.tabSize; // Update the file - writeFileSync(parseWinPath(file.fsPath), FrontMatterParser.toFile(article.content, article.data, mdFile, { + await writeFileAsync(parseWinPath(file.fsPath), FrontMatterParser.toFile(article.content, article.data, mdFile, { indent: spaces || 2 } as DumpOptions as any), { encoding: "utf8" }); } diff --git a/src/listeners/dashboard/DataListener.ts b/src/listeners/dashboard/DataListener.ts index 10530503..54395e76 100644 --- a/src/listeners/dashboard/DataListener.ts +++ b/src/listeners/dashboard/DataListener.ts @@ -4,10 +4,12 @@ import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; import { BaseListener } from "./BaseListener"; import { DashboardCommand } from '../../dashboardWebView/DashboardCommand'; import { Folders } from '../../commands/Folders'; -import { existsSync, writeFileSync, mkdirSync, readFileSync } from 'fs'; +import { existsSync } from 'fs'; import { dirname } from 'path'; import * as yaml from 'js-yaml'; import { DataFileHelper } from '../../helpers'; +import { readFileAsync, writeFileAsync } from '../../utils'; +import { mkdirAsync } from '../../utils/mkdirAsync'; export class DataListener extends BaseListener { @@ -35,28 +37,28 @@ export class DataListener extends BaseListener { * Process the data update * @param msgData */ - private static processDataUpdate(msgData: any) { + private static async processDataUpdate(msgData: any) { const { file, fileType, entries } = msgData as { file: string, fileType: string, entries: unknown | unknown[] }; const absPath = Folders.getAbsFilePath(file); if (!existsSync(absPath)) { const dirPath = dirname(absPath); if (!existsSync(dirPath)) { - mkdirSync(dirPath, { recursive: true }); + await mkdirAsync(dirPath, { recursive: true }); } } - const fileContent = readFileSync(absPath, 'utf8'); + const fileContent = await readFileAsync(absPath, 'utf8'); // check if file content ends with newline const newFileContent = fileContent.endsWith('\n'); const insertFinalNewLine = newFileContent || workspace.getConfiguration().get('files.insertFinalNewline'); if (fileType === 'yaml') { const yamlData = yaml.safeDump(entries); - writeFileSync(absPath, insertFinalNewLine ? `${yamlData}\n` : yamlData, 'utf8'); + await writeFileAsync(absPath, insertFinalNewLine ? `${yamlData}\n` : yamlData, 'utf8'); } else { const jsonData = JSON.stringify(entries, null, 2); - writeFileSync(absPath, insertFinalNewLine ? `${jsonData}\n` : jsonData, 'utf8'); + await writeFileAsync(absPath, insertFinalNewLine ? `${jsonData}\n` : jsonData, 'utf8'); } this.processDataFile(msgData); diff --git a/src/listeners/dashboard/PagesListener.ts b/src/listeners/dashboard/PagesListener.ts index 37531d6b..2d167318 100644 --- a/src/listeners/dashboard/PagesListener.ts +++ b/src/listeners/dashboard/PagesListener.ts @@ -1,4 +1,3 @@ -import { unlinkSync } from "fs"; import { basename } from "path"; import { commands, FileSystemWatcher, RelativePattern, TextDocument, Uri, workspace } from "vscode"; import { Dashboard } from "../../commands/Dashboard"; @@ -12,6 +11,7 @@ import { BaseListener } from "./BaseListener"; import { DataListener } from '../panel'; import Fuse from 'fuse.js'; import { PagesParser } from '../../services/PagesParser'; +import { unlinkAsync } from "../../utils"; export class PagesListener extends BaseListener { @@ -106,7 +106,7 @@ export class PagesListener extends BaseListener { Logger.info(`Deleting file: ${path}`) - unlinkSync(path); + await unlinkAsync(path); this.lastPages = this.lastPages.filter(p => p.fmFilePath !== path); this.sendPageData(this.lastPages); @@ -128,7 +128,7 @@ export class PagesListener extends BaseListener { if (pageIdx !== -1) { const stats = await workspace.fs.stat(file); const crntPage = this.lastPages[pageIdx]; - const updatedPage = PagesParser.processPageContent(file.fsPath, stats.mtime, basename(file.fsPath), crntPage.fmFolder); + const updatedPage = await PagesParser.processPageContent(file.fsPath, stats.mtime, basename(file.fsPath), crntPage.fmFolder); if (updatedPage) { this.lastPages[pageIdx] = updatedPage; this.sendPageData(this.lastPages); diff --git a/src/listeners/dashboard/SettingsListener.ts b/src/listeners/dashboard/SettingsListener.ts index e9c7ee9b..ffce8394 100644 --- a/src/listeners/dashboard/SettingsListener.ts +++ b/src/listeners/dashboard/SettingsListener.ts @@ -59,7 +59,7 @@ export class SettingsListener extends BaseListener { * Set the current site-generator or framework + related settings * @param frameworkId */ - public static setFramework(frameworkId: string | null) { + public static async setFramework(frameworkId: string | null) { Settings.update(SETTING_FRAMEWORK_ID, frameworkId, true); if (frameworkId) { @@ -68,7 +68,7 @@ export class SettingsListener extends BaseListener { if (framework) { Settings.update(SETTING_CONTENT_STATIC_FOLDER, framework.static, true); - FrameworkDetector.checkDefaultSettings(framework); + await FrameworkDetector.checkDefaultSettings(framework); } else { Settings.update(SETTING_CONTENT_STATIC_FOLDER, "", true); } diff --git a/src/services/PagesParser.ts b/src/services/PagesParser.ts index 24bb3b8a..965ceef3 100644 --- a/src/services/PagesParser.ts +++ b/src/services/PagesParser.ts @@ -72,7 +72,7 @@ export class PagesParser { let page = await PagesParser.getCachedPage(file.filePath, file.mtime); if (!page) { - page = this.processPageContent(file.filePath, file.mtime, file.fileName, folder.title); + page = await this.processPageContent(file.filePath, file.mtime, file.fileName, folder.title); } if (page && !pages.find(p => p.fmFilePath === page?.fmFilePath)) { @@ -123,8 +123,8 @@ export class PagesParser { * @param folderTitle * @returns */ - public static processPageContent(filePath: string, fileMtime: number, fileName: string, folderTitle: string): Page | undefined { - const article = ArticleHelper.getFrontMatterByPath(filePath); + public static async processPageContent(filePath: string, fileMtime: number, fileName: string, folderTitle: string): Promise { + const article = await ArticleHelper.getFrontMatterByPath(filePath); if (article?.data.title) { const wsFolder = Folders.getWorkspaceFolder(); diff --git a/src/utils/copyFileAsync.ts b/src/utils/copyFileAsync.ts new file mode 100644 index 00000000..e5d1924e --- /dev/null +++ b/src/utils/copyFileAsync.ts @@ -0,0 +1,4 @@ +import { promisify } from "util"; +import { copyFile as copyFileCb } from "fs"; + +export const copyFileAsync = promisify(copyFileCb); \ No newline at end of file diff --git a/src/utils/existsAsync.ts b/src/utils/existsAsync.ts new file mode 100644 index 00000000..c4f793d8 --- /dev/null +++ b/src/utils/existsAsync.ts @@ -0,0 +1,6 @@ +import { stat } from "fs"; +import { promisify } from "util"; + +export const existsAsync = async (path: string) => { + return promisify(stat)(path).then(() => true).catch(() => false); +}; \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts new file mode 100644 index 00000000..cc092021 --- /dev/null +++ b/src/utils/index.ts @@ -0,0 +1,7 @@ +export * from './copyFileAsync'; +export * from './existsAsync'; +export * from './mkdirAsync'; +export * from './readFileAsync'; +export * from './readdirAsync'; +export * from './unlinkAsync'; +export * from './writeFileAsync'; diff --git a/src/utils/mkdirAsync.ts b/src/utils/mkdirAsync.ts new file mode 100644 index 00000000..6c52bbcf --- /dev/null +++ b/src/utils/mkdirAsync.ts @@ -0,0 +1,4 @@ +import { promisify } from "util"; +import { mkdir as mkdirCb } from "fs"; + +export const mkdirAsync = promisify(mkdirCb); \ No newline at end of file diff --git a/src/utils/readFileAsync.ts b/src/utils/readFileAsync.ts new file mode 100644 index 00000000..3f4ba8cb --- /dev/null +++ b/src/utils/readFileAsync.ts @@ -0,0 +1,4 @@ +import { promisify } from "util"; +import { readFile as readFileCb } from "fs"; + +export const readFileAsync = promisify(readFileCb); \ No newline at end of file diff --git a/src/utils/readdirAsync.ts b/src/utils/readdirAsync.ts new file mode 100644 index 00000000..82fd807c --- /dev/null +++ b/src/utils/readdirAsync.ts @@ -0,0 +1,4 @@ +import { promisify } from "util"; +import { readdir as readdirCb } from "fs"; + +export const readdirAsync = promisify(readdirCb); \ No newline at end of file diff --git a/src/utils/unlinkAsync.ts b/src/utils/unlinkAsync.ts new file mode 100644 index 00000000..46d8d917 --- /dev/null +++ b/src/utils/unlinkAsync.ts @@ -0,0 +1,4 @@ +import { promisify } from "util"; +import { unlink as unlinkCb } from "fs"; + +export const unlinkAsync = promisify(unlinkCb); \ No newline at end of file diff --git a/src/utils/writeFileAsync.ts b/src/utils/writeFileAsync.ts new file mode 100644 index 00000000..6250fdd3 --- /dev/null +++ b/src/utils/writeFileAsync.ts @@ -0,0 +1,4 @@ +import { promisify } from "util"; +import { writeFile as writeFileCb } from "fs"; + +export const writeFileAsync = promisify(writeFileCb); \ No newline at end of file From a072957793cbb6679176c03e2af417804f6ad09d Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Thu, 6 Oct 2022 21:49:53 +0200 Subject: [PATCH 21/49] Updated exists to async --- src/commands/Folders.ts | 3 ++- src/commands/Project.ts | 5 ++--- src/helpers/ArticleHelper.ts | 7 +++---- src/helpers/ContentType.ts | 11 +++++------ src/helpers/CustomScript.ts | 6 +++--- src/helpers/DataFileHelper.ts | 5 ++--- src/helpers/FrameworkDetector.ts | 15 +++++++-------- src/helpers/MediaHelpers.ts | 20 ++++++++++---------- src/helpers/MediaLibrary.ts | 8 ++++---- src/helpers/SettingsHelper.ts | 8 ++++---- src/listeners/dashboard/DataListener.ts | 7 +++---- src/listeners/dashboard/MediaListener.ts | 4 ++-- src/services/PagesParser.ts | 6 +++--- src/utils/index.ts | 1 + src/utils/renameAsync.ts | 4 ++++ 15 files changed, 55 insertions(+), 55 deletions(-) create mode 100644 src/utils/renameAsync.ts diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 69ff8ef8..74238d0a 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -17,6 +17,7 @@ import { DEFAULT_FILE_TYPES } from '../constants/DefaultFileTypes'; import { Telemetry } from '../helpers/Telemetry'; import { glob } from 'glob'; import { mkdirAsync } from '../utils/mkdirAsync'; +import { existsAsync } from '../utils'; export const WORKSPACE_PLACEHOLDER = `[[workspace]]`; @@ -63,7 +64,7 @@ export class Folders { parentFolders.push(folder); - if (!existsSync(folderPath)) { + if (!(await existsAsync(folderPath))) { await mkdirAsync(folderPath); } } diff --git a/src/commands/Project.ts b/src/commands/Project.ts index c9daa83b..a6303a8d 100644 --- a/src/commands/Project.ts +++ b/src/commands/Project.ts @@ -8,8 +8,7 @@ import { Folders } from "./Folders"; import { FrameworkDetector, Logger, Settings } from "../helpers"; import { SETTING_CONTENT_DEFAULT_FILETYPE, SETTING_TAXONOMY_CONTENT_TYPES, TelemetryEvent } from "../constants"; import { SettingsListener } from '../listeners/dashboard'; -import { writeFileAsync } from '../utils'; -import { existsSync } from 'fs'; +import { existsAsync, writeFileAsync } from '../utils'; export class Project { @@ -80,7 +79,7 @@ categories: [] const article = Uri.file(join(templatePath.fsPath, `article.${fileType}`)); - if (!existsSync(templatePath.fsPath)) { + if (!(await existsAsync(templatePath.fsPath))) { await workspace.fs.createDirectory(templatePath); } diff --git a/src/helpers/ArticleHelper.ts b/src/helpers/ArticleHelper.ts index 670ed422..ed051543 100644 --- a/src/helpers/ArticleHelper.ts +++ b/src/helpers/ArticleHelper.ts @@ -14,7 +14,6 @@ import { Article } from '../commands'; import { join } from 'path'; import { EditorHelper } from '@estruyf/vscode'; import sanitize from '../helpers/Sanitize'; -import { existsSync } from 'fs'; import { ContentType } from '../models'; import { DateHelper } from './DateHelper'; import { DiagnosticSeverity, Position, window, Range } from 'vscode'; @@ -25,7 +24,7 @@ import { Content } from 'mdast'; import { processKnownPlaceholders } from './PlaceholderHelper'; import { CustomScript } from './CustomScript'; import { Folders } from '../commands/Folders'; -import { readFileAsync } from '../utils'; +import { existsAsync, readFileAsync } from '../utils'; import { mkdirAsync } from '../utils/mkdirAsync'; export class ArticleHelper { @@ -342,7 +341,7 @@ export class ArticleHelper { // Create a folder with the `index.md` file if (contentType?.pageBundle) { const newFolder = join(folderPath, sanitizedName); - if (existsSync(newFolder)) { + if (await existsAsync(newFolder)) { Notifications.error(`A page bundle with the name ${sanitizedName} already exists in ${folderPath}`); return; } else { @@ -358,7 +357,7 @@ export class ArticleHelper { newFilePath = join(folderPath, newFileName); - if (existsSync(newFilePath)) { + if (await existsAsync(newFilePath)) { Notifications.warning(`Content with the title already exists. Please specify a new title.`); return; } diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index 6cc7be6f..5308a92c 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -6,14 +6,13 @@ import { ContentType as IContentType, DraftField, Field, FieldGroup, FieldType, import { Uri, commands, window, ProgressLocation, workspace } from 'vscode'; import { Folders } from "../commands/Folders"; import { Questions } from "./Questions"; -import { existsSync } from "fs"; import { Notifications } from "./Notifications"; import { DEFAULT_CONTENT_TYPE_NAME } from "../constants/ContentType"; import { Telemetry } from './Telemetry'; import { processKnownPlaceholders } from './PlaceholderHelper'; import { basename } from 'path'; import { ParsedFrontMatter } from '../parsers'; -import { writeFileAsync } from '../utils'; +import { existsAsync, writeFileAsync } from '../utils'; export class ContentType { @@ -198,9 +197,9 @@ export class ContentType { Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true); const configPath = Settings.projectConfigPath; - const notificationAction = await Notifications.info(`Content type ${contentTypeName} has been ${overrideBool ? `updated` : `generated`}.`, configPath && existsSync(configPath) ? `Open settings` : undefined); + const notificationAction = await Notifications.info(`Content type ${contentTypeName} has been ${overrideBool ? `updated` : `generated`}.`, configPath && await existsAsync(configPath) ? `Open settings` : undefined); - if (notificationAction === "Open settings" && configPath && existsSync(configPath)) { + if (notificationAction === "Open settings" && configPath && await existsAsync(configPath)) { commands.executeCommand('vscode.open', Uri.file(configPath)); } } @@ -232,9 +231,9 @@ export class ContentType { Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true); const configPath = Settings.projectConfigPath; - const notificationAction = await Notifications.info(`Content type ${contentType.name} has been updated.`, configPath && existsSync(configPath) ? `Open settings` : undefined); + const notificationAction = await Notifications.info(`Content type ${contentType.name} has been updated.`, configPath && await existsAsync(configPath) ? `Open settings` : undefined); - if (notificationAction === "Open settings" && configPath && existsSync(configPath)) { + if (notificationAction === "Open settings" && configPath && await existsAsync(configPath)) { commands.executeCommand('vscode.open', Uri.file(configPath)); } } diff --git a/src/helpers/CustomScript.ts b/src/helpers/CustomScript.ts index 7789b8cb..95525db5 100644 --- a/src/helpers/CustomScript.ts +++ b/src/helpers/CustomScript.ts @@ -14,7 +14,7 @@ import { DashboardCommand } from '../dashboardWebView/DashboardCommand'; import { ParsedFrontMatter } from '../parsers'; import { TelemetryEvent } from '../constants/TelemetryEvent'; import { SETTING_CUSTOM_SCRIPTS } from '../constants'; -import { existsSync } from 'fs'; +import { existsAsync } from '../utils'; export class CustomScript { @@ -264,7 +264,7 @@ export class CustomScript { * @returns */ public static async executeScript(script: ICustomScript, wsPath: string, args: string): Promise { - return new Promise((resolve, reject) => { + return new Promise(async (resolve, reject) => { // Check the command to use let command = script.nodeBin || "node"; @@ -274,7 +274,7 @@ export class CustomScript { const scriptPath = join(wsPath, script.script); - if (!existsSync(scriptPath)) { + if (!await existsAsync(scriptPath)) { reject(new Error(`Script not found: ${scriptPath}`)); return; } diff --git a/src/helpers/DataFileHelper.ts b/src/helpers/DataFileHelper.ts index 25f1547c..aa04dc7b 100644 --- a/src/helpers/DataFileHelper.ts +++ b/src/helpers/DataFileHelper.ts @@ -1,4 +1,3 @@ -import { existsSync } from "fs"; import { Folders } from "../commands/Folders"; import { DataFile } from "../models"; import * as yaml from 'js-yaml'; @@ -7,7 +6,7 @@ import { Notifications } from "./Notifications"; import { commands } from "vscode"; import { COMMAND_NAME, SETTING_DATA_FILES } from "../constants"; import { Settings } from "./SettingsHelper"; -import { readFileAsync } from "../utils"; +import { existsAsync, readFileAsync } from "../utils"; export class DataFileHelper { @@ -19,7 +18,7 @@ export class DataFileHelper { */ public static async get(filePath: string) { const absPath = Folders.getAbsFilePath(filePath); - if (existsSync(absPath)) { + if (await existsAsync(absPath)) { return await readFileAsync(absPath, 'utf8'); } diff --git a/src/helpers/FrameworkDetector.ts b/src/helpers/FrameworkDetector.ts index 5c1c0b97..1c5a54e5 100644 --- a/src/helpers/FrameworkDetector.ts +++ b/src/helpers/FrameworkDetector.ts @@ -1,5 +1,4 @@ import * as jsoncParser from 'jsonc-parser'; -import { existsSync } from "fs"; import jsyaml = require("js-yaml"); import { join, resolve } from "path"; import { commands, Uri } from "vscode"; @@ -8,7 +7,7 @@ import { COMMAND_NAME } from "../constants"; import { FrameworkDetectors } from "../constants/FrameworkDetectors"; import { Framework } from "../models"; import { Logger } from "./Logger"; -import { readFileAsync } from '../utils'; +import { existsAsync, readFileAsync } from '../utils'; export class FrameworkDetector { @@ -28,7 +27,7 @@ export class FrameworkDetector { // Try fetching the package JSON file try { const pkgFile = join(folder, 'package.json'); - if (existsSync(pkgFile)) { + if (await existsAsync(pkgFile)) { let packageJson: any = await readFileAsync(pkgFile, "utf8"); if (packageJson) { packageJson = typeof packageJson === "string" ? jsoncParser.parse(packageJson) : packageJson; @@ -44,7 +43,7 @@ export class FrameworkDetector { // Try fetching the Gemfile try { const gemFile = join(folder, 'Gemfile'); - if (existsSync(gemFile)) { + if (await existsAsync(gemFile)) { gemContent = await readFileAsync(gemFile, "utf8"); } } catch (e) { @@ -71,7 +70,7 @@ export class FrameworkDetector { // Verify by files for (const filename of detector.requiredFiles ?? []) { - const fileExists = existsSync(resolve(folder, filename)); + const fileExists = await existsAsync(resolve(folder, filename)); if (fileExists) { return detector.framework; } @@ -95,7 +94,7 @@ export class FrameworkDetector { const jekyllConfig = join(wsFolder?.fsPath || "", '_config.yml'); let collectionDir = ""; - if (existsSync(jekyllConfig)) { + if (await existsAsync(jekyllConfig)) { const content = await readFileAsync(jekyllConfig, "utf8"); // Convert YAML to JSON const config = jsyaml.safeLoad(content); @@ -108,7 +107,7 @@ export class FrameworkDetector { const draftsPath = join(wsFolder?.fsPath || "", collectionDir, "_drafts"); const postsPath = join(wsFolder?.fsPath || "", collectionDir, "_posts"); - if (existsSync(draftsPath)) { + if (await existsAsync(draftsPath)) { const folderUri = Uri.file(draftsPath); commands.executeCommand(COMMAND_NAME.registerFolder, { title: "drafts", @@ -116,7 +115,7 @@ export class FrameworkDetector { }); } - if (existsSync(postsPath)) { + if (await existsAsync(postsPath)) { const folderUri = Uri.file(postsPath); commands.executeCommand(COMMAND_NAME.registerFolder, { title: "posts", diff --git a/src/helpers/MediaHelpers.ts b/src/helpers/MediaHelpers.ts index 6573913e..68d77bc6 100644 --- a/src/helpers/MediaHelpers.ts +++ b/src/helpers/MediaHelpers.ts @@ -5,7 +5,7 @@ import { DEFAULT_CONTENT_TYPE, ExtensionState, HOME_PAGE_NAVIGATION_ID, SETTING_ import { SortingOption } from "../dashboardWebView/models"; import { MediaInfo, MediaPaths, SortOrder, SortType } from "../models"; import { basename, join, parse, dirname, relative } from "path"; -import { existsSync, statSync } from "fs"; +import { statSync } from "fs"; import { Uri, workspace, window, Position } from "vscode"; import imageSize from "image-size"; import { EditorHelper } from "@estruyf/vscode"; @@ -13,7 +13,7 @@ import { SortOption } from "../dashboardWebView/constants/SortOption"; import { DataListener, MediaListener } from "../listeners/panel"; import { ArticleHelper } from "./ArticleHelper"; import { lookup } from "mime-types"; -import { readdirAsync, unlinkAsync, writeFileAsync } from "../utils"; +import { existsAsync, readdirAsync, unlinkAsync, writeFileAsync } from "../utils"; export class MediaHelpers { @@ -49,7 +49,7 @@ export class MediaHelpers { if (viewData?.data?.filePath && (viewData?.data?.filePath.endsWith('index.md') || viewData?.data?.filePath.endsWith('index.mdx'))) { const folderPath = parse(viewData.data.filePath).dir; selectedFolder = folderPath; - } else if (stateValue && existsSync(stateValue)) { + } else if (stateValue && await existsAsync(stateValue)) { selectedFolder = stateValue; } } @@ -151,14 +151,14 @@ export class MediaHelpers { let allFolders: string[] = []; if (selectedFolder) { - if (existsSync(selectedFolder)) { + if (await existsAsync(selectedFolder)) { allFolders = (await readdirAsync(selectedFolder, { withFileTypes: true })).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(selectedFolder, dir.name))); } } else { if (pageBundleContentTypes.length > 0) { for (const contentFolder of contentFolders) { const contentPath = contentFolder.path; - if (contentPath && existsSync(contentPath)) { + if (contentPath && await existsAsync(contentPath)) { const subFolders = (await readdirAsync(contentPath, { withFileTypes: true })).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(contentPath, dir.name))); allContentFolders = [...allContentFolders, ...subFolders]; } @@ -166,7 +166,7 @@ export class MediaHelpers { } const staticPath = join(parseWinPath(wsFolder?.fsPath || ""), staticFolder || ""); - if (staticPath && existsSync(staticPath)) { + if (staticPath && await existsAsync(staticPath)) { allFolders = (await readdirAsync(staticPath, { withFileTypes: true })).filter(dir => dir.isDirectory()).map(dir => parseWinPath(join(staticPath, dir.name))); } } @@ -219,11 +219,11 @@ export class MediaHelpers { absFolderPath = folder; } - if (!existsSync(absFolderPath)) { + if (!(await existsAsync(absFolderPath))) { absFolderPath = join(wsPath, folder || ""); } - if (!existsSync(absFolderPath)) { + if (!(await existsAsync(absFolderPath))) { Notifications.error(`We couldn't find your selected folder.`); return; } @@ -351,14 +351,14 @@ export class MediaHelpers { * Update the metadata of a media file * @param data */ - public static updateMetadata(data: any) { + public static async updateMetadata(data: any) { const { file, filename, page, folder, ...metadata }: { file:string; filename:string; page: number; folder: string | null; metadata: any; } = data; const mediaLib = MediaLibrary.getInstance(); mediaLib.set(file, metadata); // Check if filename needs to be updated - mediaLib.updateFilename(file, filename); + await mediaLib.updateFilename(file, filename); } /** diff --git a/src/helpers/MediaLibrary.ts b/src/helpers/MediaLibrary.ts index 430966df..5edc0ff4 100644 --- a/src/helpers/MediaLibrary.ts +++ b/src/helpers/MediaLibrary.ts @@ -3,10 +3,10 @@ import { workspace } from 'vscode'; import { JsonDB } from 'node-json-db/dist/JsonDB'; import { basename, dirname, join, parse } from 'path'; import { Folders, WORKSPACE_PLACEHOLDER } from '../commands/Folders'; -import { existsSync, renameSync } from 'fs'; import { Notifications } from './Notifications'; import { parseWinPath } from './parseWinPath'; import { LocalStore } from '../constants'; +import { existsAsync, renameAsync } from '../utils'; interface MediaRecord { description: string; @@ -75,7 +75,7 @@ export class MediaLibrary { } } - public updateFilename(filePath: string, filename: string) { + public async updateFilename(filePath: string, filename: string) { const name = basename(filePath); if (name !== filename && filename) { @@ -84,10 +84,10 @@ export class MediaLibrary { const newFileInfo = parse(filename); const newPath = join(dirname(filePath), `${newFileInfo.name}${oldFileInfo.ext}`); - if (existsSync(newPath)) { + if (await existsAsync(newPath)) { Notifications.warning(`The name "${filename}" already exists at the file location.`); } else { - renameSync(filePath, newPath); + await renameAsync(filePath, newPath); this.rename(filePath, newPath); MediaHelpers.resetMedia(); } diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index a98314d6..5190a825 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -12,7 +12,7 @@ import { Extension } from './Extension'; import { debounceCallback } from './DebounceCallback'; import { Logger } from './Logger'; import * as jsoncParser from 'jsonc-parser'; -import { readFileAsync, writeFileAsync } from '../utils'; +import { existsAsync, readFileAsync, writeFileAsync } from '../utils'; export class Settings { public static globalFile = "frontmatter.json"; @@ -182,7 +182,7 @@ export class Settings { const fmConfig = Settings.projectConfigPath; if (updateGlobal) { - if (fmConfig && existsSync(fmConfig)) { + if (fmConfig && await existsAsync(fmConfig)) { const localConfig = await readFileAsync(fmConfig, 'utf8'); Settings.globalConfig = jsoncParser.parse(localConfig); Settings.globalConfig[`${CONFIG_KEY}.${name}`] = value; @@ -232,7 +232,7 @@ export class Settings { if (wsFolder) { const configPath = join(wsFolder.fsPath, Settings.globalFile); - if (!existsSync(configPath)) { + if (!(await existsAsync(configPath))) { await writeFileAsync(configPath, JSON.stringify(initialConfig, null, 2), 'utf8'); } } @@ -419,7 +419,7 @@ export class Settings { private static async readConfig() { try { const fmConfig = Settings.projectConfigPath; - if (fmConfig && existsSync(fmConfig)) { + if (fmConfig && await existsAsync(fmConfig)) { const localConfig = await readFileAsync(fmConfig, 'utf8'); Settings.globalConfig = jsoncParser.parse(localConfig); commands.executeCommand('setContext', CONTEXT.isEnabled, true); diff --git a/src/listeners/dashboard/DataListener.ts b/src/listeners/dashboard/DataListener.ts index 54395e76..fa9e3e32 100644 --- a/src/listeners/dashboard/DataListener.ts +++ b/src/listeners/dashboard/DataListener.ts @@ -4,11 +4,10 @@ import { DashboardMessage } from "../../dashboardWebView/DashboardMessage"; import { BaseListener } from "./BaseListener"; import { DashboardCommand } from '../../dashboardWebView/DashboardCommand'; import { Folders } from '../../commands/Folders'; -import { existsSync } from 'fs'; import { dirname } from 'path'; import * as yaml from 'js-yaml'; import { DataFileHelper } from '../../helpers'; -import { readFileAsync, writeFileAsync } from '../../utils'; +import { existsAsync, readFileAsync, writeFileAsync } from '../../utils'; import { mkdirAsync } from '../../utils/mkdirAsync'; @@ -41,9 +40,9 @@ export class DataListener extends BaseListener { const { file, fileType, entries } = msgData as { file: string, fileType: string, entries: unknown | unknown[] }; const absPath = Folders.getAbsFilePath(file); - if (!existsSync(absPath)) { + if (!await existsAsync(absPath)) { const dirPath = dirname(absPath); - if (!existsSync(dirPath)) { + if (!await existsAsync(dirPath)) { await mkdirAsync(dirPath, { recursive: true }); } } diff --git a/src/listeners/dashboard/MediaListener.ts b/src/listeners/dashboard/MediaListener.ts index cd940b7c..6be6ac55 100644 --- a/src/listeners/dashboard/MediaListener.ts +++ b/src/listeners/dashboard/MediaListener.ts @@ -113,11 +113,11 @@ export class MediaListener extends BaseListener { * Update media metadata * @param data */ - private static update(data: any) { + private static async update(data: any) { try { const { page, folder } = data; - MediaHelpers.updateMetadata(data); + await MediaHelpers.updateMetadata(data); this.sendMediaFiles(page || 0, folder || ""); } catch {} diff --git a/src/services/PagesParser.ts b/src/services/PagesParser.ts index 965ceef3..df4e9dfa 100644 --- a/src/services/PagesParser.ts +++ b/src/services/PagesParser.ts @@ -1,5 +1,4 @@ import { parseWinPath } from './../helpers/parseWinPath'; -import { existsSync } from "fs"; import { dirname, join } from "path"; import { StatusBarAlignment, Uri, window } from "vscode"; import { Dashboard } from "../commands/Dashboard"; @@ -7,6 +6,7 @@ import { Folders } from "../commands/Folders"; import { DefaultFields, DEFAULT_CONTENT_TYPE_NAME, ExtensionState, SETTING_SEO_DESCRIPTION_FIELD } from "../constants"; import { Page } from "../dashboardWebView/models"; import { ArticleHelper, ContentType, DateHelper, Extension, isValidFile, Logger, Notifications, Settings } from "../helpers"; +import { existsAsync } from '../utils'; export class PagesParser { @@ -240,9 +240,9 @@ export class PagesParser { const contentFolderPath = join(dirname(filePath), fieldValue); let previewUri = null; - if (existsSync(staticPath)) { + if (await existsAsync(staticPath)) { previewUri = Uri.file(staticPath); - } else if (existsSync(contentFolderPath)) { + } else if (await existsAsync(contentFolderPath)) { previewUri = Uri.file(contentFolderPath); } diff --git a/src/utils/index.ts b/src/utils/index.ts index cc092021..c23da193 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -3,5 +3,6 @@ export * from './existsAsync'; export * from './mkdirAsync'; export * from './readFileAsync'; export * from './readdirAsync'; +export * from './renameAsync'; export * from './unlinkAsync'; export * from './writeFileAsync'; diff --git a/src/utils/renameAsync.ts b/src/utils/renameAsync.ts new file mode 100644 index 00000000..ed28c3a1 --- /dev/null +++ b/src/utils/renameAsync.ts @@ -0,0 +1,4 @@ +import { promisify } from "util"; +import { rename as renameCb } from "fs"; + +export const renameAsync = promisify(renameCb); \ No newline at end of file From 8a8db67e82346ebf92dd6615b11c3faee5a58d3e Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 7 Oct 2022 13:32:42 +0200 Subject: [PATCH 22/49] #427 - Add hexo as SSG option --- src/constants/FrameworkDetectors.ts | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/src/constants/FrameworkDetectors.ts b/src/constants/FrameworkDetectors.ts index 76e9f488..7f11381d 100644 --- a/src/constants/FrameworkDetectors.ts +++ b/src/constants/FrameworkDetectors.ts @@ -85,5 +85,17 @@ export const FrameworkDetectors = [{ commands: { start: "npx @11ty/eleventy --serve" } + }, + { + framework: { + name: "hexo", + dist: "public", + build: "npx hexo-cli generate" + }, + requiredFiles: ["_config.js"], + requiredDependencies: ["hexo"], + commands: { + start: "npx hexo-cli server" + } } ]; \ No newline at end of file From b9a0c656d324492f2f2a709e6a67e579a3b00cd5 Mon Sep 17 00:00:00 2001 From: Elio Struyf Date: Fri, 7 Oct 2022 13:32:49 +0200 Subject: [PATCH 23/49] async updates for settings --- src/commands/Project.ts | 2 +- src/dashboardWebView/components/Steps/StepsToGetStarted.tsx | 6 +++--- src/helpers/ContentType.ts | 4 ++-- src/helpers/Extension.ts | 2 +- src/helpers/SettingsHelper.ts | 4 +++- src/listeners/dashboard/SettingsListener.ts | 6 +++--- 6 files changed, 13 insertions(+), 11 deletions(-) diff --git a/src/commands/Project.ts b/src/commands/Project.ts index a6303a8d..748bd0bf 100644 --- a/src/commands/Project.ts +++ b/src/commands/Project.ts @@ -37,7 +37,7 @@ categories: [] await Settings.createTeamSettings(); // Add the default content type - Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true); + await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true); if (sampleTemplate !== undefined) { await Project.createSampleTemplate(); diff --git a/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx b/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx index a3c48e6c..bb5f67c1 100644 --- a/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx +++ b/src/dashboardWebView/components/Steps/StepsToGetStarted.tsx @@ -163,10 +163,10 @@ export const StepsToGetStarted: React.FunctionComponent ]; React.useEffect(() => { - if (settings.crntFramework) { - setFramework(settings.crntFramework); + if (settings.crntFramework || settings.framework?.name) { + setFramework(settings.crntFramework || settings.framework?.name || null); } - }, [settings.crntFramework]); + }, [settings.crntFramework, settings.framework]); return (