diff --git a/CHANGELOG.md b/CHANGELOG.md index 9cc54107..5043e458 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ - [#441](https://github.com/estruyf/vscode-front-matter/issues/441): Show input descriptions for snippet and data forms - [#442](https://github.com/estruyf/vscode-front-matter/issues/442): Hide sidebar on data view when data file is selecte + show dropdown of data files +- [#788](https://github.com/estruyf/vscode-front-matter/issues/788): Show a warning on setting update when it exists in an extended configuration - [#798](https://github.com/estruyf/vscode-front-matter/issues/798): Changed dialog to slide-over for the snippet forms - [#799](https://github.com/estruyf/vscode-front-matter/issues/799): Added `frontMatter.logging` setting to define the logging output. Options are `info`, `warn`, `error`, and `verbose`. Default is `info`. - [#800](https://github.com/estruyf/vscode-front-matter/issues/800): Add colors for the Front Matter CMS output diff --git a/src/commands/Folders.ts b/src/commands/Folders.ts index 9b0cc27c..89df4710 100644 --- a/src/commands/Folders.ts +++ b/src/commands/Folders.ts @@ -467,9 +467,8 @@ export class Folders { path: Folders.relWsFolder(folder, wsFolder) }; - if (detail['$schema'] || detail.extended) { - return null; - } + delete detail['$schema']; + delete detail.extended; if (detail.locale && detail.locale === detail.defaultLocale) { // Check if the folder was on the original list @@ -490,7 +489,7 @@ export class Folders { }) .filter((folder) => folder !== null); - await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true); + await Settings.safeUpdate(SETTING_CONTENT_PAGE_FOLDERS, folderDetails, true); // Reinitialize the folder listeners PagesListener.startWatchers(); diff --git a/src/commands/Project.ts b/src/commands/Project.ts index e4047e21..d275b4d0 100644 --- a/src/commands/Project.ts +++ b/src/commands/Project.ts @@ -82,7 +82,7 @@ categories: [] await Settings.createTeamSettings(); // Add the default content type - await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true); + await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, [DEFAULT_CONTENT_TYPE], true); if (sampleTemplate !== undefined) { await Project.createSampleTemplate(); diff --git a/src/helpers/ContentType.ts b/src/helpers/ContentType.ts index ec49b945..a9fec798 100644 --- a/src/helpers/ContentType.ts +++ b/src/helpers/ContentType.ts @@ -303,7 +303,7 @@ export class ContentType { contentTypes.push(newContentType); } - await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true); + await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true); const configPath = await Settings.projectConfigPath(); const notificationAction = await Notifications.info( @@ -350,7 +350,7 @@ export class ContentType { const index = contentTypes.findIndex((ct) => ct.name === contentType.name); contentTypes[index].fields = updatedFields; - await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true); + await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true); const configPath = await Settings.projectConfigPath(); const notificationAction = await Notifications.info( @@ -804,7 +804,7 @@ export class ContentType { fields: blockFields }; fieldGroups.push(newFieldGroup); - await Settings.update(SETTING_TAXONOMY_FIELD_GROUPS, fieldGroups, true); + await Settings.safeUpdate(SETTING_TAXONOMY_FIELD_GROUPS, fieldGroups, true); fields.push({ title: field, diff --git a/src/helpers/Extension.ts b/src/helpers/Extension.ts index a6e22a31..b265114d 100644 --- a/src/helpers/Extension.ts +++ b/src/helpers/Extension.ts @@ -236,7 +236,7 @@ export class Extension { } ); - await Settings.update( + await Settings.safeUpdate( SETTING_TEMPLATES_ENABLED, answer?.toLocaleLowerCase() === l10n.t(LocalizationKey.commonYes), true @@ -266,7 +266,7 @@ export class Extension { return f; }); - await Settings.update(SETTING_CONTENT_PAGE_FOLDERS, folders, true); + await Settings.safeUpdate(SETTING_CONTENT_PAGE_FOLDERS, folders, true); } // The tags and categories settings need to be moved to the database diff --git a/src/helpers/FrameworkDetector.ts b/src/helpers/FrameworkDetector.ts index dd44466e..51972743 100644 --- a/src/helpers/FrameworkDetector.ts +++ b/src/helpers/FrameworkDetector.ts @@ -244,7 +244,7 @@ export class FrameworkDetector { }); } - await Settings.update(SETTING_CONTENT_STATIC_FOLDER, assetFoler, true); + await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, assetFoler, true); } catch (e) { Logger.error( `Something failed while processing your Hexo configuration. ${(e as Error).message}` diff --git a/src/helpers/Logger.ts b/src/helpers/Logger.ts index 0345395e..9cfeac8a 100644 --- a/src/helpers/Logger.ts +++ b/src/helpers/Logger.ts @@ -4,7 +4,7 @@ import { format } from 'date-fns'; import { COMMAND_NAME, SETTING_LOGGING } from '../constants'; import { Settings } from '.'; -export type LoggerLocation = 'VSCODE' | 'DASHBOARD' | 'PANEL'; +export type LoggerLocation = 'VSCODE' | 'DASHBOARD' | 'PANEL' | 'SETTING'; export class Logger { private static instance: Logger; diff --git a/src/helpers/Notifications.ts b/src/helpers/Notifications.ts index fe6d1b7c..d699de99 100644 --- a/src/helpers/Notifications.ts +++ b/src/helpers/Notifications.ts @@ -43,6 +43,30 @@ export class Notifications { return Promise.resolve(undefined); } + /** + * Show a warning notification to the user with a link to the output channel + * @param message + * @param items + * @returns + */ + public static warningWithOutput(message: string, ...items: any): Thenable { + Logger.info(`${EXTENSION_NAME}: ${message}`, 'VSCODE', 'WARNING'); + + if (this.shouldShow('WARNING')) { + return window.showWarningMessage( + `${EXTENSION_NAME}: ${message} ${l10n.t( + LocalizationKey.notificationsOutputChannelDescription, + `[${l10n.t(LocalizationKey.notificationsOutputChannelLink)}](command:${ + COMMAND_NAME.showOutputChannel + })` + )}`, + ...items + ); + } + + return Promise.resolve(undefined); + } + /** * Show an error notification to the user * @param message diff --git a/src/helpers/SettingsHelper.ts b/src/helpers/SettingsHelper.ts index 11093072..8415a01e 100644 --- a/src/helpers/SettingsHelper.ts +++ b/src/helpers/SettingsHelper.ts @@ -77,6 +77,8 @@ export class Settings { private static readConfigPromise: Promise | undefined = undefined; private static project: Project | undefined = undefined; private static configDebouncer = debounceCallback(); + private static hasExtendedConfig: boolean = false; + private static extendedConfig: { extended: any[]; splitted: any[]; dynamic: boolean } = {} as any; public static async registerCommands() { const ext = Extension.getInstance(); @@ -374,12 +376,54 @@ export class Settings { return setting; } + /** + * Safely updates a setting value. + * + * @param name - The name of the setting. + * @param value - The new value for the setting. + * @param updateGlobal - Indicates whether to update the global setting or not. Default is `false`. + * @returns A promise that resolves when the setting is updated. + */ + public static async safeUpdate( + name: string, + value: T, + updateGlobal: boolean = false + ): Promise { + if (Settings.hasExtendedConfig) { + const configKey = `${CONFIG_KEY}.${name}`; + + if ( + Settings.extendedConfig.extended.includes(configKey) || + Settings.extendedConfig.splitted.includes(configKey) || + Settings.extendedConfig.dynamic + ) { + Notifications.warningWithOutput( + `Cannot update setting "${configKey}" because you've extended or split the Front Matter CMS configuration. Please manually add your changes. Check the output for the setting update.` + ); + + Logger.info(`Updating setting: ${configKey}`, 'SETTING'); + Logger.info( + `New value: +${JSON.stringify(value, null, 2)}`, + 'SETTING' + ); + return; + } + } + + await Settings.update(name, value, updateGlobal); + } + /** * String update config setting * @param name * @param value */ - public static async update(name: string, value: T, updateGlobal: boolean = false) { + public static async update( + name: string, + value: T, + updateGlobal: boolean = false + ): Promise { const fmConfig = await Settings.projectConfigPath(); if (updateGlobal) { @@ -423,7 +467,7 @@ export class Settings { const workspaceSettingValue = Settings.hasWorkspaceSettings(name); if (workspaceSettingValue) { - await Settings.update(name, undefined); + await Settings.safeUpdate(name, undefined); } // Make sure to reload the whole config + all the data files @@ -515,7 +559,7 @@ export class Settings { customTaxonomies[taxIdx].options.push(option); customTaxonomies[taxIdx].options = [...new Set(customTaxonomies[taxIdx].options)]; customTaxonomies[taxIdx].options = customTaxonomies[taxIdx].options.sort().filter((o) => !!o); - await Settings.update(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true); + await Settings.safeUpdate(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true); } /** @@ -532,7 +576,7 @@ export class Settings { customTaxonomies[taxIdx].options = options; } - await Settings.update(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true); + await Settings.safeUpdate(SETTING_TAXONOMY_CUSTOM, customTaxonomies, true); } /** @@ -548,8 +592,8 @@ export class Settings { const setting = Settings.config.inspect(settingName); if (setting && typeof setting.workspaceValue !== 'undefined') { - await Settings.update(settingName, setting.workspaceValue, true); - await Settings.update(settingName, undefined); + await Settings.safeUpdate(settingName, setting.workspaceValue, true); + await Settings.safeUpdate(settingName, undefined); } } } @@ -652,6 +696,13 @@ export class Settings { */ private static async readConfig() { try { + Settings.hasExtendedConfig = false; + Settings.extendedConfig = { + extended: [], + splitted: [], + dynamic: false + }; + const fmConfig = await Settings.projectConfigPath(); if (fmConfig && (await existsAsync(fmConfig))) { const localConfig = await readFileAsync(fmConfig, 'utf8'); @@ -671,6 +722,8 @@ export class Settings { ); if (configFiles.length === 0) { Logger.info(`No ".frontmatter/config" config files found.`); + } else { + Settings.hasExtendedConfig = true; } // Sort the files by fsPath @@ -707,6 +760,8 @@ export class Settings { ); if (dynamicConfig) { + Settings.hasExtendedConfig = true; + Settings.extendedConfig.dynamic = true; Settings.globalConfig = dynamicConfig; Logger.info(`Dynamic config file loaded`); } @@ -738,11 +793,17 @@ export class Settings { return; } + Settings.hasExtendedConfig = true; const originalConfig = Object.assign({}, Settings.globalConfig); const extendsConfig: string[] = Settings.globalConfig[extendsConfigName]; for (const externalConfig of extendsConfig) { if (externalConfig.endsWith(`.json`)) { const config = await Settings.getExternalConfig(externalConfig); + + // Store the config keys + const keys = Object.keys(config); + Settings.extendedConfig.extended.push(...keys); + await Settings.extendConfig(config, originalConfig); } } @@ -777,6 +838,7 @@ export class Settings { Settings.globalConfig = {}; } + Settings.extendedConfig.splitted.push(`${CONFIG_KEY}.${relSettingName}`); Settings.updateGlobalConfigSetting(relSettingName, configJson, configFilePath, filePath); } catch (e) { Logger.error(`Error reading config file: ${configFile.fsPath}`); diff --git a/src/listeners/dashboard/SettingsListener.ts b/src/listeners/dashboard/SettingsListener.ts index bdb42738..659ddb5c 100644 --- a/src/listeners/dashboard/SettingsListener.ts +++ b/src/listeners/dashboard/SettingsListener.ts @@ -101,7 +101,7 @@ export class SettingsListener extends BaseListener { if (typeof setting.name !== 'undefined' && typeof setting.value !== 'undefined') { const value = Settings.get(setting.name); if (value !== setting.value) { - await Settings.update(setting.name, setting.value, true); + await Settings.safeUpdate(setting.name, setting.value, true); } } } @@ -190,7 +190,7 @@ export class SettingsListener extends BaseListener { */ private static async update(data: { name: string; value: any; global?: boolean }) { if (data.name) { - await Settings.update(data.name, data.value, data.global); + await Settings.safeUpdate(data.name, data.value, data.global); this.getSettings(true); } } @@ -213,7 +213,7 @@ export class SettingsListener extends BaseListener { * @param frameworkId */ public static async setFramework(frameworkId: string | null) { - await Settings.update(SETTING_FRAMEWORK_ID, frameworkId, true); + await Settings.safeUpdate(SETTING_FRAMEWORK_ID, frameworkId, true); if (frameworkId) { const allFrameworks = FrameworkDetector.getAll(); @@ -222,16 +222,16 @@ export class SettingsListener extends BaseListener { ); if (framework) { if (framework.static && typeof framework.static === 'string') { - await Settings.update(SETTING_CONTENT_STATIC_FOLDER, framework.static, true); + await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, framework.static, true); } if (framework.server) { - await Settings.update(SETTING_PREVIEW_HOST, framework.server, true); + await Settings.safeUpdate(SETTING_PREVIEW_HOST, framework.server, true); } await FrameworkDetector.checkDefaultSettings(framework); } else { - await Settings.update(SETTING_CONTENT_STATIC_FOLDER, '', true); + await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, '', true); } } @@ -239,7 +239,7 @@ export class SettingsListener extends BaseListener { } public static async addAssetsFolder(assetFolder: string | StaticFolder) { - await Settings.update(SETTING_CONTENT_STATIC_FOLDER, assetFolder, true); + await Settings.safeUpdate(SETTING_CONTENT_STATIC_FOLDER, assetFolder, true); SettingsListener.getSettings(true); } diff --git a/src/listeners/dashboard/SnippetListener.ts b/src/listeners/dashboard/SnippetListener.ts index b29d3b24..49904567 100644 --- a/src/listeners/dashboard/SnippetListener.ts +++ b/src/listeners/dashboard/SnippetListener.ts @@ -71,7 +71,7 @@ export class SnippetListener extends BaseListener { snippets[title] = snippetContent; - await Settings.update(SETTING_CONTENT_SNIPPETS, snippets, true); + await Settings.safeUpdate(SETTING_CONTENT_SNIPPETS, snippets, true); SettingsListener.getSettings(true); } @@ -93,7 +93,7 @@ export class SnippetListener extends BaseListener { return acc; }, {} as Snippets); - await Settings.update(SETTING_CONTENT_SNIPPETS, snippetsToStore, true); + await Settings.safeUpdate(SETTING_CONTENT_SNIPPETS, snippetsToStore, true); SettingsListener.getSettings(true); } diff --git a/src/listeners/dashboard/SsgListener.ts b/src/listeners/dashboard/SsgListener.ts index c82292cf..c7199df8 100644 --- a/src/listeners/dashboard/SsgListener.ts +++ b/src/listeners/dashboard/SsgListener.ts @@ -97,7 +97,7 @@ export class SsgListener extends BaseListener { } contentTypes.push(contentType); - await Settings.update(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true); + await Settings.safeUpdate(SETTING_TAXONOMY_CONTENT_TYPES, contentTypes, true); SsgListener.sendRequest(command as any, requestId, {}); SettingsListener.getSettings(true); diff --git a/src/listeners/panel/SettingsListener.ts b/src/listeners/panel/SettingsListener.ts index ff3f0dea..3ddc9a46 100644 --- a/src/listeners/panel/SettingsListener.ts +++ b/src/listeners/panel/SettingsListener.ts @@ -75,7 +75,7 @@ export class SettingsListener extends BaseListener { * @param value */ private static async updateSetting(setting: string, value: any) { - await Settings.update(setting, value); + await Settings.safeUpdate(setting, value); this.getSettings(); } diff --git a/src/models/ContentFolder.ts b/src/models/ContentFolder.ts index 66bb39d9..8fb37b13 100644 --- a/src/models/ContentFolder.ts +++ b/src/models/ContentFolder.ts @@ -10,8 +10,8 @@ export interface ContentFolder { filePrefix?: string; contentTypes?: string[]; originalPath?: string; - $schema?: string; - extended?: boolean; + $schema?: string; // Extended config + extended?: boolean; // Extended config locale?: string; localeTitle?: string;