diff --git a/esbuild.config.mjs b/esbuild.config.mjs index 4697ae9f..bb77bbf5 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -25,7 +25,7 @@ const mockedPlugins = [replace({ // update usage of moment from obsidian to the node implementation of moment we have 'import {moment} from \'obsidian\';': 'import moment from \'moment\';', // remove the use of obsidian in the options to allow for docs.js to run - 'import {Setting} from \'obsidian\';': '', + 'import {App, Setting, ToggleComponent} from \'obsidian\';': '', // remove the use of obsidian in settings helper to allow for docs.js to run 'import {App, MarkdownRenderer} from \'obsidian\';': '', // remove the use of obsidian in the auto-correct files picker to allow for docs.js to run @@ -38,6 +38,8 @@ const mockedPlugins = [replace({ 'import {App, TFile} from \'obsidian\';': '', // remove the use of obsidian in parse results modal to allow for docs.js to run 'import {Modal, App} from \'obsidian\';': 'class Modal {}', + // remove the use of app from a couple of settings for docs.js to run + 'import {App} from \'obsidian\';': '', }, delimiters: ['', ''], })]; diff --git a/src/lang/locale/en.ts b/src/lang/locale/en.ts index 2d2fd113..75b9ecd2 100644 --- a/src/lang/locale/en.ts +++ b/src/lang/locale/en.ts @@ -111,6 +111,12 @@ export default { 'copy-aria-label': 'Copy', + 'disabled-other-rule-notice': 'If you enable {NAME_1}, it will disable {NAME_2}. Would you like to proceed?', + 'disabled-conflicting-rule-notice': '{NAME_1}, conflicts with {NAME_2}, so it has been turned off. You can switch which setting is off in the settings tab.', + + // confirm-rule-disable-modal.ts + 'ok': 'Ok', + // parse-results-modal.ts 'parse-results-heading-text': 'Custom Parse Values', 'file-parse-description-text': 'The following is the list of custom replacements found in {FILE}.', diff --git a/src/main.ts b/src/main.ts index 78877fe5..0e5fe9da 100644 --- a/src/main.ts +++ b/src/main.ts @@ -129,69 +129,13 @@ export default class LinterPlugin extends Plugin { setLogLevel(this.settings.logLevel); await this.setOrUpdateMomentInstance(); - let updateMade = false; - if (!this.settings.settingsConvertedToConfigKeyValues) { - updateMade = await this.moveConfigValuesToKeyBasedFormat(); - } - - if ('lintOnFileContentChangeDelay' in this.settings) { - this.settings.ruleConfigs['yaml-timestamp']['update-on-file-contents-updated'] = this.settings['lintOnFileContentChangeDelay']; - - delete this.settings['lintOnFileContentChangeDelay']; - updateMade = true; - } - - // make sure to load the defaults of any missing rules to make sure they do not cause issues on the settings page - for (const rule of rules) { - if (!this.settings.ruleConfigs[rule.alias]) { - this.settings.ruleConfigs[rule.alias] = rule.getDefaultOptions(); - } - - // remove this after a reasonable amount of time - if (rule.alias == 'space-between-chinese-japanese-or-korean-and-english-or-numbers') { - const defaults = rule.getDefaultOptions(); - if (!('english-symbols-punctuation-before' in this.settings.ruleConfigs[rule.alias])) { - this.settings.ruleConfigs[rule.alias]['english-symbols-punctuation-before'] = defaults['english-symbols-punctuation-before']; - updateMade = true; - } - - if (!('english-symbols-punctuation-after' in this.settings.ruleConfigs[rule.alias])) { - this.settings.ruleConfigs[rule.alias]['english-symbols-punctuation-after'] = defaults['english-symbols-punctuation-after']; - updateMade = true; - } - } else if (rule.alias == 'yaml-timestamp') { - const defaults = rule.getDefaultOptions(); - if ('force-retention-of-create-value' in this.settings.ruleConfigs[rule.alias]) { - if (!('date-created-source-of-truth' in this.settings.ruleConfigs[rule.alias])) { - if (this.settings.ruleConfigs[rule.alias]['force-retention-of-create-value']) { - this.settings.ruleConfigs[rule.alias]['date-created-source-of-truth'] = 'frontmatter'; - } else { - this.settings.ruleConfigs[rule.alias]['date-created-source-of-truth'] = defaults['date-created-source-of-truth']; - } - } - - delete this.settings.ruleConfigs[rule.alias]['force-retention-of-create-value']; - updateMade = true; - } - - if (!('date-modified-source-of-truth' in this.settings.ruleConfigs[rule.alias])) { - this.settings.ruleConfigs[rule.alias]['date-modified-source-of-truth'] = defaults['date-modified-source-of-truth']; - updateMade = true; - } - } - } - this.updatePasteOverrideStatus(); this.updateHasCustomCommandStatus(); - - if (updateMade) { - await this.saveSettings(); - } } async saveSettings() { if (!this.hasLoadedMisspellingFiles) { - await this.loadAutoCorrectFiles(); + await this.loadAutoCorrectFiles(false); } await this.saveData(this.settings); @@ -328,7 +272,8 @@ export default class LinterPlugin extends Plugin { this.eventRefs.push(eventRef); this.app.workspace.onLayoutReady(async () => { - await this.loadAutoCorrectFiles(); + await this.makeSureSettingsFilledInAndCleanupSettings(); + await this.loadAutoCorrectFiles(true); }); // Source for save setting @@ -375,13 +320,17 @@ export default class LinterPlugin extends Plugin { } } - async loadAutoCorrectFiles() { + async loadAutoCorrectFiles(isOnload: boolean) { const customAutoCorrectSettings = this.settings.ruleConfigs['auto-correct-common-misspellings']; if (!customAutoCorrectSettings || !customAutoCorrectSettings.enabled) { return; } await downloadMisspellings(this, async (message: string) => { + if (isOnload) { + message = 'Obsidian Linter:\n' + message; + } + new Notice(message); this.settings.ruleConfigs['auto-correct-common-misspellings'].enabled = false; @@ -619,6 +568,109 @@ export default class LinterPlugin extends Plugin { moment.locale(oldLocale); } + private async makeSureSettingsFilledInAndCleanupSettings() { + let updateMade = false; + + // migrate settings over to the new format if they are using the now deprecated format that uses + // actual settings names for the key in the json + if (!this.settings.settingsConvertedToConfigKeyValues) { + updateMade = await this.moveConfigValuesToKeyBasedFormat(); + } + + // move a recently moved setting to its new location + if ('lintOnFileContentChangeDelay' in this.settings) { + this.settings.ruleConfigs['yaml-timestamp']['update-on-file-contents-updated'] = this.settings['lintOnFileContentChangeDelay']; + + delete this.settings['lintOnFileContentChangeDelay']; + updateMade = true; + } + + // check for and fix invalid settings + let noticeText = 'Obsidian Linter:'; + let conflictingRulePresent = false; + if (this.settings.ruleConfigs['header-increment'] && this.settings.ruleConfigs['header-increment'].enabled && this.settings.ruleConfigs['header-increment']['start-at-h2'] && + this.settings.ruleConfigs['file-name-heading'] && this.settings.ruleConfigs['file-name-heading'].enabled + ) { + this.settings.ruleConfigs['header-increment']['start-at-h2'] = false; + updateMade = true; + conflictingRulePresent = true; + + noticeText += '\n' + getTextInLanguage('disabled-conflicting-rule-notice').replace('{NAME_1}', getTextInLanguage('rules.header-increment.start-at-h2.name')).replace('{NAME_2}', getTextInLanguage('rules.file-name-heading.name')); + } + + if (this.settings.ruleConfigs['paragraph-blank-lines'] && this.settings.ruleConfigs['paragraph-blank-lines'].enabled && + this.settings.ruleConfigs['two-spaces-between-lines-with-content'] && this.settings.ruleConfigs['two-spaces-between-lines-with-content'].enabled + ) { + this.settings.ruleConfigs['paragraph-blank-lines'].enabled = false; + updateMade = true; + + if (conflictingRulePresent) { + noticeText += '\n'; + } + conflictingRulePresent = true; + + noticeText += '\n' + getTextInLanguage('disabled-conflicting-rule-notice').replace('{NAME_1}', getTextInLanguage('rules.paragraph-blank-lines.name')).replace('{NAME_2}', getTextInLanguage('rules.two-spaces-between-lines-with-content.name')); + } + + if (conflictingRulePresent) { + new Notice(noticeText, userClickTimeout); + } + + // make sure to load the defaults of any missing rules to make sure they do not cause issues on the settings page + for (const rule of rules) { + const ruleDefaults = rule.getDefaultOptions(); + if (!this.settings.ruleConfigs[rule.alias]) { + this.settings.ruleConfigs[rule.alias] = ruleDefaults; + updateMade = true; + continue; + } + + // remove this after a reasonable amount of time + if (rule.alias == 'space-between-chinese-japanese-or-korean-and-english-or-numbers') { + if (!('english-symbols-punctuation-before' in this.settings.ruleConfigs[rule.alias])) { + this.settings.ruleConfigs[rule.alias]['english-symbols-punctuation-before'] = ruleDefaults['english-symbols-punctuation-before']; + updateMade = true; + } + + if (!('english-symbols-punctuation-after' in this.settings.ruleConfigs[rule.alias])) { + this.settings.ruleConfigs[rule.alias]['english-symbols-punctuation-after'] = ruleDefaults['english-symbols-punctuation-after']; + updateMade = true; + } + } else if (rule.alias == 'yaml-timestamp') { + const defaults = rule.getDefaultOptions(); + if ('force-retention-of-create-value' in this.settings.ruleConfigs[rule.alias]) { + if (!('date-created-source-of-truth' in this.settings.ruleConfigs[rule.alias])) { + if (this.settings.ruleConfigs[rule.alias]['force-retention-of-create-value']) { + this.settings.ruleConfigs[rule.alias]['date-created-source-of-truth'] = 'frontmatter'; + } else { + this.settings.ruleConfigs[rule.alias]['date-created-source-of-truth'] = defaults['date-created-source-of-truth']; + } + } + + delete this.settings.ruleConfigs[rule.alias]['force-retention-of-create-value']; + updateMade = true; + } + + if (!('date-modified-source-of-truth' in this.settings.ruleConfigs[rule.alias])) { + this.settings.ruleConfigs[rule.alias]['date-modified-source-of-truth'] = defaults['date-modified-source-of-truth']; + updateMade = true; + } + } + + // make sure new/empty settings on a rule that exists get filled in with their default value as well + for (const key of Object.keys(ruleDefaults)) { + if (!Object.hasOwn(this.settings.ruleConfigs[rule.alias], key)) { + this.settings.ruleConfigs[rule.alias][key] = ruleDefaults[key]; + updateMade = true; + } + } + } + + if (updateMade) { + await this.saveSettings(); + } + } + private createDebouncedFileUpdate(): Debouncer<[TFile, Editor], Promise> { let delay = 5000; switch (this.settings.ruleConfigs['yaml-timestamp']['update-on-file-contents-updated'] ?? AfterFileChangeLintTimes.Never) { diff --git a/src/option.ts b/src/option.ts index b02e018b..2d49c0d3 100644 --- a/src/option.ts +++ b/src/option.ts @@ -1,4 +1,4 @@ -import {Setting} from 'obsidian'; +import {App, Setting, ToggleComponent} from 'obsidian'; import {getTextInLanguage, LanguageStringKey} from './lang/helpers'; import LinterPlugin from './main'; import {hideEl, unhideEl, setElContent} from './ui/helpers'; @@ -63,21 +63,24 @@ export abstract class Option { export class BooleanOption extends Option { public defaultValue: boolean; + private toggleComponent: ToggleComponent; - constructor(configKey: string, nameKey: LanguageStringKey, descriptionKey: LanguageStringKey, defaultValue: any, ruleAlias?: string | null, private onChange?: (value: boolean) => void) { + constructor(configKey: string, nameKey: LanguageStringKey, descriptionKey: LanguageStringKey, defaultValue: any, ruleAlias?: string | null, private onChange?: (value: boolean, app: App) => void) { super(configKey, nameKey, descriptionKey, defaultValue, ruleAlias); } public display(containerEl: HTMLElement, settings: LinterSettings, plugin: LinterPlugin): void { this.setting = new Setting(containerEl) .addToggle((toggle) => { + this.toggleComponent = toggle; + toggle.setValue(settings.ruleConfigs[this.ruleAlias][this.configKey]); toggle.onChange((value) => { this.setOption(value, settings); plugin.settings = settings; if (this.onChange) { - this.onChange(value); + this.onChange(value, plugin.app); } void plugin.saveSettings(); @@ -86,6 +89,14 @@ export class BooleanOption extends Option { this.parseNameAndDescriptionAndRemoveSettingBorder(); } + + getValue(): boolean { + return this.toggleComponent.getValue(); + } + + setValue(value: boolean) { + this.toggleComponent.setValue(value); + } } export class TextOption extends Option { diff --git a/src/rules.ts b/src/rules.ts index 87c70dd9..754d89fc 100644 --- a/src/rules.ts +++ b/src/rules.ts @@ -11,6 +11,7 @@ import {LinterError} from './linter-error'; import {getTextInLanguage, LanguageStringKey} from './lang/helpers'; import {ignoreListOfTypes, IgnoreType} from './utils/ignore-types'; import {LinterSettings} from './settings-data'; +import {App} from 'obsidian'; export type Options = { [optionName: string]: any}; @@ -41,6 +42,7 @@ export class Rule { * @param {Array