From 55729c1e13ee548dc58d6c89d40bf2b091694252 Mon Sep 17 00:00:00 2001 From: dragonish Date: Thu, 29 May 2025 00:00:00 +0800 Subject: [PATCH] enh(plugin): make the decorator settings changes on the third-party plugin take effect immediately When changing the relevant decorator settings, re-render the decorator on the third-party plugin. --- common/data.ts | 3 + common/dom.ts | 151 ------------- components/child.ts | 14 +- components/file-explorer.ts | 187 ++++++++++++++++ components/outline.ts | 221 +++++++++++++++++++ components/plugin.ts | 423 +++++++++++++++++------------------- components/setting-tab.ts | 12 +- 7 files changed, 617 insertions(+), 394 deletions(-) create mode 100644 components/file-explorer.ts create mode 100644 components/outline.ts diff --git a/common/data.ts b/common/data.ts index 0b6d23e..db3db31 100644 --- a/common/data.ts +++ b/common/data.ts @@ -104,12 +104,15 @@ export const sourceHeadingDecoratorClassName = "source-custom-heading-decorator"; export const outlineHeadingDecoratorClassName = "outline-custom-heading-decorator"; +export const outlineContainerClassName = "outline-custom-heading-container"; export const quietOutlineHeadingDecoratorClassName = "quiet-outline-custom-heading-decorator"; export const quietOutlineContainerClassName = "quiet-outline-custom-heading-container"; export const fileExplorerHeadingDecoratorClassName = "file-explorer-custom-heading-decorator"; +export const fileExplorerContainerClassName = + "file-explorer-custom-heading-container"; export const beforeDecoratorClassName = "before-heading-decorator"; export const beforeInsideDecoratorClassName = "before-inside-heading-decorator"; export const afterDecoratorClassName = "after-heading-decorator"; diff --git a/common/dom.ts b/common/dom.ts index 2bfab65..f27d675 100644 --- a/common/dom.ts +++ b/common/dom.ts @@ -1,4 +1,3 @@ -import { htmlToMarkdown } from "obsidian"; import { headingDecoratorClassName, readingHeadingDecoratorClassName, @@ -6,9 +5,6 @@ import { beforeInsideDecoratorClassName, afterDecoratorClassName, afterInsideDecoratorClassName, - outlineHeadingDecoratorClassName, - fileExplorerHeadingDecoratorClassName, - compareMarkdownText, } from "./data"; /** @@ -118,150 +114,3 @@ export function decorateHTMLElement( } } } - -/** - * Get the tree item level of a given element. - * - * @param element The element to get the tree item level for. - * @returns The tree item level. - */ -export function getTreeItemLevel(element: Element): number { - let level = 0; - let current = element.closest(".tree-item"); - while (current) { - level++; - current = current.parentElement?.closest(".tree-item") || null; - } - return level; -} - -/** - * Get the text of a given tree item. - * - * @param element The HTML element to get the text for. - * @returns The text of the tree item. - */ -export function getTreeItemText(element: HTMLElement): string { - const inner = element.querySelector( - ".tree-item-self .tree-item-inner" - ); - return inner ? inner.innerText : ""; -} - -/** - * Decorate an outline HTML element with a given content, opacity and position. - * - * @param element The HTML element to decorate. - * @param content The content to decorate with. - * @param opacity The opacity of the decorator. - * @param position The position of the decorator. - */ -export function decorateOutlineElement( - element: HTMLElement, - content: string, - opacity: OpacityOptions, - position: PostionOptions -): void { - if (content) { - const inner = element.querySelector( - ".tree-item-self .tree-item-inner" - ); - if (inner) { - inner.dataset.headingDecorator = content; - inner.dataset.decoratorOpacity = `${opacity}%`; - inner.classList.add( - outlineHeadingDecoratorClassName, - getPositionClassName(position, true) - ); - } - } -} - -/** - * Cancel an outline decorator from an HTML element. - * - * @param element The HTML element to cancel the decorator. - */ -export function cancelOutlineDecorator(element: HTMLElement): void { - const inner = element.querySelector( - ".tree-item-self .tree-item-inner" - ); - if (inner) { - delete inner.dataset.headingDecorator; - delete inner.dataset.decoratorOpacity; - inner.classList.remove( - outlineHeadingDecoratorClassName, - beforeDecoratorClassName, - afterDecoratorClassName - ); - } -} - -/** - * Get the level of a file heading item based on its margin-left value. - * - * @param element The file heading item element. - * @param marginMultiplier The multiplier used to calculate the level. - * @returns The level of the file heading item. - */ -export function getFileHeadingItemLevel( - element: HTMLElement, - marginMultiplier: number -): number { - const marginLeft = element.style.marginLeft; - if (marginLeft) { - const value = parseInt(marginLeft.replace("px", "")); - return Math.floor(value / marginMultiplier) + 1; - } - return 0; -} - -/** - * Decorate a file heading element with a custom content and style. - * - * @param element The file heading element to decorate. - * @param content The content to decorate with. - * @param opacity The opacity of the decorator. - * @param position The position of the decorator. - */ -export function decorateFileHeadingElement( - element: HTMLElement, - content: string, - opacity: OpacityOptions, - position: PostionOptions -): void { - if (content) { - element.dataset.headingDecorator = content; - element.dataset.decoratorOpacity = `${opacity}%`; - element.classList.add( - fileExplorerHeadingDecoratorClassName, - getPositionClassName(position, true) - ); - } -} - -/** - * Cancel a file heading decorator from an element. - * - * @param element The file heading element to cancel decorator. - */ -export function cancelFileHeadingDecorator(element: HTMLElement): void { - delete element.dataset.headingDecorator; - delete element.dataset.decoratorOpacity; - element.classList.remove( - fileExplorerHeadingDecoratorClassName, - beforeDecoratorClassName, - afterDecoratorClassName - ); -} - -/** - * Compare heading text. - * - * @param source The source heading text. - * @param outline The outline heading text. - * @returns true if the two headings are equal, false otherwise. - */ -export function compareHeadingText(source: string, outline: string): boolean { - return compareMarkdownText(htmlToMarkdown(source), htmlToMarkdown(outline)); -} diff --git a/components/child.ts b/components/child.ts index 4b60a7b..28e615f 100644 --- a/components/child.ts +++ b/components/child.ts @@ -40,15 +40,19 @@ export class ViewChildComponent extends Component { } } + render(): void { + this.observer?.disconnect(); + this.decorationCallback(); + if (this.containerEl) { + this.observer?.observe(this.containerEl, this.config); + } + } + onload(): void { this.decorationCallback(); this.observer = new MutationObserver(() => { - this.observer?.disconnect(); - this.decorationCallback(); - if (this.containerEl) { - this.observer?.observe(this.containerEl, this.config); - } + this.render(); }); if (this.containerEl) { diff --git a/components/file-explorer.ts b/components/file-explorer.ts new file mode 100644 index 0000000..ae62137 --- /dev/null +++ b/components/file-explorer.ts @@ -0,0 +1,187 @@ +import { HeadingCache } from "obsidian"; +import type { HeadingDecoratorSettings } from "../common/data"; +import { + fileExplorerHeadingDecoratorClassName, + fileExplorerContainerClassName, + beforeDecoratorClassName, + afterDecoratorClassName, + getOrderedCustomIdents, + getUnorderedLevelHeadings, +} from "../common/data"; +import { Querier, Counter } from "../common/counter"; + +/** + * File Explorer Heading Decorator. + * + * @param settings The heading decorator settings. + * @param container The container element. + * @param headingElements The heading elements. + * @param cacheHeadings The cache headings. + */ +export function fileExplorerHandler( + settings: HeadingDecoratorSettings, + container: HTMLElement, + headingElements: NodeListOf, + cacheHeadings: HeadingCache[] +): void { + const { + opacity, + position, + ordered, + orderedDelimiter, + orderedTrailingDelimiter, + orderedStyleType, + orderedSpecifiedString, + orderedCustomIdents, + orderedIgnoreSingle, + orderedIgnoreMaximum = 6, + orderedBasedOnExisting, + orderedAllowZeroLevel, + unorderedLevelHeadings, + } = settings; + + container.classList.add(fileExplorerContainerClassName); + + let ignoreTopLevel = 0; + if (ordered && (orderedIgnoreSingle || orderedBasedOnExisting)) { + const queier = new Querier(orderedAllowZeroLevel); + for (const cacheHeading of cacheHeadings) { + queier.handler(cacheHeading.level); + ignoreTopLevel = queier.query(orderedIgnoreSingle, orderedIgnoreMaximum); + if (ignoreTopLevel === 0) { + break; + } + } + } + + const counter = new Counter({ + ordered, + delimiter: orderedDelimiter, + trailingDelimiter: orderedTrailingDelimiter, + styleType: orderedStyleType, + customIdents: getOrderedCustomIdents(orderedCustomIdents), + specifiedString: orderedSpecifiedString, + ignoreTopLevel, + allowZeroLevel: orderedAllowZeroLevel, + levelHeadings: getUnorderedLevelHeadings(unorderedLevelHeadings), + }); + + const marginMultiplier = + parseInt( + getComputedStyle(document.body).getPropertyValue( + "--clickable-heading-margin-multiplier" + ) + ) || 10; + + for ( + let i = 0, j = 0; + i < headingElements.length && j < cacheHeadings.length; + i++, j++ + ) { + const readLevel = getFileHeadingItemLevel( + headingElements[i], + marginMultiplier + ); + const readText = headingElements[i].innerText; + let cacheLevel = cacheHeadings[j].level; + + while ( + j < cacheHeadings.length - 1 && + (cacheLevel !== readLevel || cacheHeadings[j].heading !== readText) + ) { + counter.handler(cacheLevel); + j++; + cacheLevel = cacheHeadings[j].level; + } + + const decoratorContent = counter.decorator(cacheLevel); + decorateFileHeadingElement( + headingElements[i], + decoratorContent, + opacity, + position + ); + } +} + +/** + * Cancel the decoration of file headings in a container. + * + * @param container The container element containing the file headings. + */ +export function cancelFileExplorerDecoration(container: HTMLElement): void { + if (container.classList.contains(fileExplorerContainerClassName)) { + container.classList.remove(fileExplorerContainerClassName); + + const headingElements = container.querySelectorAll( + ".file-heading-container .clickable-heading" + ); + + headingElements.forEach((ele) => { + cancelFileHeadingDecorator(ele); + }); + } +} + +/** + * Decorate a file heading element with a custom content and style. + * + * @param element The file heading element to decorate. + * @param content The content to decorate with. + * @param opacity The opacity of the decorator. + * @param position The position of the decorator. + */ +function decorateFileHeadingElement( + element: HTMLElement, + content: string, + opacity: OpacityOptions, + position: PostionOptions +): void { + if (content) { + element.dataset.headingDecorator = content; + element.dataset.decoratorOpacity = `${opacity}%`; + + //? Remove potential residual class names + element.classList.remove( + position === "after" ? beforeDecoratorClassName : afterDecoratorClassName + ); + element.classList.add( + fileExplorerHeadingDecoratorClassName, + position === "after" ? afterDecoratorClassName : beforeDecoratorClassName + ); + } +} + +/** + * Cancel a file heading decorator from an element. + * + * @param element The file heading element to cancel decorator. + */ +function cancelFileHeadingDecorator(element: HTMLElement): void { + delete element.dataset.headingDecorator; + delete element.dataset.decoratorOpacity; + element.classList.remove( + fileExplorerHeadingDecoratorClassName, + beforeDecoratorClassName, + afterDecoratorClassName + ); +} + +/** + * Get the level of a file heading item based on its margin-left value. + * + * @param element The file heading item element. + * @param marginMultiplier The multiplier used to calculate the level. + * @returns The level of the file heading item. + */ +function getFileHeadingItemLevel( + element: HTMLElement, + marginMultiplier: number +): number { + const marginLeft = element.style.marginLeft; + if (marginLeft) { + const value = parseInt(marginLeft.replace("px", "")); + return Math.floor(value / marginMultiplier) + 1; + } + return 0; +} diff --git a/components/outline.ts b/components/outline.ts new file mode 100644 index 0000000..3a195d6 --- /dev/null +++ b/components/outline.ts @@ -0,0 +1,221 @@ +import { HeadingCache, htmlToMarkdown } from "obsidian"; +import type { HeadingDecoratorSettings } from "../common/data"; +import { + outlineHeadingDecoratorClassName, + outlineContainerClassName, + beforeDecoratorClassName, + afterDecoratorClassName, + getOrderedCustomIdents, + getUnorderedLevelHeadings, + diffLevel, + compareMarkdownText, +} from "../common/data"; +import { Querier, Counter } from "../common/counter"; + +/** + * Handle the outline rendering based on the given settings and heading elements. + * + * @param settings The heading decorator settings. + * @param container The container element to render the outline. + * @param headingElements The heading elements to render the outline. + * @param cacheHeadings The cache headings to render the outline. + */ +export function outlineHandler( + settings: HeadingDecoratorSettings, + container: HTMLElement, + headingElements: NodeListOf, + cacheHeadings: HeadingCache[] +): void { + const { + opacity, + position, + ordered, + orderedDelimiter, + orderedTrailingDelimiter, + orderedStyleType, + orderedSpecifiedString, + orderedCustomIdents, + orderedIgnoreSingle, + orderedIgnoreMaximum = 6, + orderedBasedOnExisting, + orderedAllowZeroLevel, + unorderedLevelHeadings, + } = settings; + + container.classList.add(outlineContainerClassName); + + let ignoreTopLevel = 0; + if (ordered && (orderedIgnoreSingle || orderedBasedOnExisting)) { + const queier = new Querier(orderedAllowZeroLevel); + for (const cacheHeading of cacheHeadings) { + queier.handler(cacheHeading.level); + ignoreTopLevel = queier.query(orderedIgnoreSingle, orderedIgnoreMaximum); + if (ignoreTopLevel === 0) { + break; + } + } + } + + const counter = new Counter({ + ordered, + delimiter: orderedDelimiter, + trailingDelimiter: orderedTrailingDelimiter, + styleType: orderedStyleType, + customIdents: getOrderedCustomIdents(orderedCustomIdents), + specifiedString: orderedSpecifiedString, + ignoreTopLevel, + allowZeroLevel: orderedAllowZeroLevel, + levelHeadings: getUnorderedLevelHeadings(unorderedLevelHeadings), + }); + + let lastCacheLevel = 0; + let lastReadLevel = 0; + for ( + let i = 0, j = 0; + i < headingElements.length && j < cacheHeadings.length; + i++, j++ + ) { + const readLevel = getTreeItemLevel(headingElements[i]); + const readText = getTreeItemText(headingElements[i]); + let cacheLevel = cacheHeadings[j].level; + if (i > 0) { + const diff = diffLevel(readLevel, lastReadLevel); + while ( + j < cacheHeadings.length - 1 && + (diffLevel(cacheLevel, lastCacheLevel) !== diff || + !compareHeadingText(cacheHeadings[j].heading, readText)) + ) { + counter.handler(cacheLevel); + j++; + cacheLevel = cacheHeadings[j].level; + } + } + + const decoratorContent = counter.decorator(cacheLevel); + decorateOutlineElement( + headingElements[i], + decoratorContent, + opacity, + position + ); + + lastCacheLevel = cacheLevel; + lastReadLevel = readLevel; + } +} + +/** + * Cancel the outline decoration for a given container. + * + * @param container The container element that holds the outline elements. + */ +export function cancelOutlineDecoration(container: HTMLElement): void { + if (container.classList.contains(outlineContainerClassName)) { + container.classList.remove(outlineContainerClassName); + + const headingElements = + container.querySelectorAll(".tree-item"); + + headingElements.forEach((ele) => { + cancelOutlineDecorator(ele); + }); + } +} + +/** + * Decorate an outline HTML element with a given content, opacity and position. + * + * @param element The HTML element to decorate. + * @param content The content to decorate with. + * @param opacity The opacity of the decorator. + * @param position The position of the decorator. + */ +function decorateOutlineElement( + element: HTMLElement, + content: string, + opacity: OpacityOptions, + position: PostionOptions +): void { + if (content) { + const inner = element.querySelector( + ".tree-item-self .tree-item-inner" + ); + if (inner) { + inner.dataset.headingDecorator = content; + inner.dataset.decoratorOpacity = `${opacity}%`; + + //? Remove potential residual class names + inner.classList.remove( + position === "after" + ? beforeDecoratorClassName + : afterDecoratorClassName + ); + inner.classList.add( + outlineHeadingDecoratorClassName, + position === "after" + ? afterDecoratorClassName + : beforeDecoratorClassName + ); + } + } +} + +/** + * Cancel an outline decorator from an HTML element. + * + * @param element The HTML element to cancel the decorator. + */ +function cancelOutlineDecorator(element: HTMLElement): void { + const inner = element.querySelector( + ".tree-item-self .tree-item-inner" + ); + if (inner) { + delete inner.dataset.headingDecorator; + delete inner.dataset.decoratorOpacity; + inner.classList.remove( + outlineHeadingDecoratorClassName, + beforeDecoratorClassName, + afterDecoratorClassName + ); + } +} + +/** + * Get the tree item level of a given element. + * + * @param element The element to get the tree item level for. + * @returns The tree item level. + */ +function getTreeItemLevel(element: Element): number { + let level = 0; + let current = element.closest(".tree-item"); + while (current) { + level++; + current = current.parentElement?.closest(".tree-item") || null; + } + return level; +} + +/** + * Get the text of a given tree item. + * + * @param element The HTML element to get the text for. + * @returns The text of the tree item. + */ +function getTreeItemText(element: HTMLElement): string { + const inner = element.querySelector( + ".tree-item-self .tree-item-inner" + ); + return inner ? inner.innerText : ""; +} + +/** + * Compare heading text. + * + * @param source The source heading text. + * @param outline The outline heading text. + * @returns true if the two headings are equal, false otherwise. + */ +function compareHeadingText(source: string, outline: string): boolean { + return compareMarkdownText(htmlToMarkdown(source), htmlToMarkdown(outline)); +} diff --git a/components/plugin.ts b/components/plugin.ts index 60c8cfe..56e163f 100644 --- a/components/plugin.ts +++ b/components/plugin.ts @@ -13,7 +13,6 @@ import { headingsSelector, getUnorderedLevelHeadings, getOrderedCustomIdents, - diffLevel, getBoolean, checkEnabledCSS, stringToRegex, @@ -21,18 +20,7 @@ import { } from "../common/data"; import { Counter, Querier } from "../common/counter"; import { Heading } from "../common/heading"; -import { - decorateHTMLElement, - decorateOutlineElement, - cancelOutlineDecorator, - decorateFileHeadingElement, - cancelFileHeadingDecorator, - queryHeadingLevelByElement, - getTreeItemLevel, - getTreeItemText, - getFileHeadingItemLevel, - compareHeadingText, -} from "../common/dom"; +import { decorateHTMLElement, queryHeadingLevelByElement } from "../common/dom"; import { HeadingEditorViewPlugin, headingDecorationsField, @@ -40,10 +28,15 @@ import { updateEditorMode, } from "./editor"; import { ViewChildComponent } from "./child"; +import { outlineHandler, cancelOutlineDecoration } from "./outline"; import { quietOutlineHandler, cancelQuietOutlineDecoration, } from "./quiet-outline"; +import { + fileExplorerHandler, + cancelFileExplorerDecoration, +} from "./file-explorer"; import { HeadingSettingTab } from "./setting-tab"; interface ObsidianEditor extends Editor { @@ -54,15 +47,18 @@ interface ObsidianWorkspaceLeaf extends WorkspaceLeaf { id: string; } -interface SettingsChangeState { - outline?: boolean; - quietOutline?: boolean; - fileExplorer?: boolean; +type OnChangeCallback = (path: string, newValue: unknown) => void; + +interface RevocableProxy { + proxy: HeadingPluginSettings; + revoke: () => void; } export class HeadingPlugin extends Plugin { settings: HeadingPluginSettings; + private revokes: (() => void)[] = []; + private outlineIdSet: Set = new Set(); private outlineComponents: ViewChildComponent[] = []; @@ -72,6 +68,94 @@ export class HeadingPlugin extends Plugin { private fileExplorerIdSet: Set = new Set(); private fileExplorerComponents: ViewChildComponent[] = []; + private debouncedRerenderPreviewMarkdown = debounce( + this.rerenderPreviewMarkdown.bind(this), + 1000, + true + ); + + private debouncedRerenderOutlineDecorator = debounce( + this.rerenderOutlineDecorator.bind(this), + 1000, + true + ); + + private debouncedRerenderQuietOutlineDecorator = debounce( + this.rerenderQuietOutlineDecorator.bind(this), + 1000, + true + ); + + private debouncedRerenderFileExplorerDecorator = debounce( + this.rerenderFileExplorerDecorator.bind(this), + 1000, + true + ); + + private createDeepRevocableProxy( + obj: T, + onChange: OnChangeCallback, + revokes: (() => void)[], + cache = new WeakMap(), + path = "" + ): RevocableProxy { + if (typeof obj !== "object" || obj === null) { + return { proxy: obj, revoke: () => {} }; + } + + // If it has already been proxy, return the cached proxy directly. + if (cache.has(obj)) { + return cache.get(obj)!; + } + + const revocable = Proxy.revocable(obj, { + get: (target, prop, receiver) => { + const value = Reflect.get(target, prop, receiver); + if (typeof value === "object" && value !== null) { + const newPath = path ? `${path}.${String(prop)}` : String(prop); + return this.createDeepRevocableProxy( + value as unknown as HeadingPluginSettings, + onChange, + revokes, + cache, + newPath + ).proxy; + } + return value; + }, + set: (target, prop, value, receiver) => { + const oldValue = Reflect.get(target, prop, receiver); + const result = Reflect.set(target, prop, value, receiver); + if (oldValue !== value) { + const changedPath = path ? `${path}.${String(prop)}` : String(prop); + onChange(changedPath, value); + } + return result; + }, + }); + revokes.push(revocable.revoke); + cache.set(obj, revocable); + return revocable; + } + + private async loadSettings() { + const rawSettings = Object.assign( + defalutSettings(), + await this.loadData() + ); + + this.revokes.forEach((revoke) => revoke()); + this.revokes = []; + + const { proxy } = this.createDeepRevocableProxy( + rawSettings, + this.settingsChanged.bind(this), + this.revokes + ); + + this.settings = proxy; + } + async onload() { await this.loadSettings(); @@ -294,42 +378,55 @@ export class HeadingPlugin extends Plugin { this.unloadOutlineComponents(); this.unloadQuietOutlineComponents(); this.unloadFileExplorerComponents(); + + this.revokes.forEach((revoke) => revoke()); + this.revokes = []; } - async loadSettings() { - this.settings = Object.assign({}, defalutSettings(), await this.loadData()); - } - - async saveSettings(state?: SettingsChangeState) { + async saveSettings() { await this.saveData(this.settings); + } - if (state) { - if (state.outline != undefined) { - if (state.outline) { - this.loadOutlineComponents(); - } else { - this.unloadOutlineComponents(); - } + private settingsChanged(path: string, value: unknown) { + if (path === "enabledInOutline") { + if (value) { + this.loadOutlineComponents(); + } else { + this.unloadOutlineComponents(); } - - if (state.quietOutline != undefined) { - if (state.quietOutline) { - this.loadQuietOutlineComponents(); - } else { - this.unloadQuietOutlineComponents(); - } + } else if (path === "enabledInQuietOutline") { + if (value) { + this.loadQuietOutlineComponents(); + } else { + this.unloadQuietOutlineComponents(); } - - if (state.fileExplorer != undefined) { - if (state.fileExplorer) { - this.loadFileExplorerComponents(); - } else { - this.unloadFileExplorerComponents(); - } + } else if (path === "enabledInFileExplorer") { + if (value) { + this.loadFileExplorerComponents(); + } else { + this.unloadFileExplorerComponents(); } + } else if (path.startsWith("outlineSettings")) { + this.debouncedRerenderOutlineDecorator(); + } else if (path.startsWith("quietOutlineSettings")) { + this.debouncedRerenderQuietOutlineDecorator(); + } else if (path.startsWith("fileExplorerSettings")) { + this.debouncedRerenderFileExplorerDecorator(); + } else if ( + path === "enabledInReading" || + path.startsWith("readingSettings") + ) { + this.debouncedRerenderPreviewMarkdown(); + } else if ( + path === "metadataKeyword" || + path.startsWith("fileRegexBlacklist") || + path.startsWith("folderBlacklist") + ) { + this.debouncedRerenderPreviewMarkdown(); + this.debouncedRerenderOutlineDecorator(); + this.debouncedRerenderQuietOutlineDecorator(); + this.debouncedRerenderFileExplorerDecorator(); } - - this.debouncedSaveSettings(); } private loadOutlineComponents() { @@ -452,103 +549,36 @@ export class HeadingPlugin extends Plugin { frontmatter ); - const { - enabledInEachNote, - opacity, - position, - ordered, - orderedDelimiter, - orderedTrailingDelimiter, - orderedStyleType, - orderedSpecifiedString, - orderedCustomIdents, - orderedIgnoreSingle, - orderedIgnoreMaximum = 6, - orderedBasedOnExisting, - orderedAllowZeroLevel, - unorderedLevelHeadings, - } = this.settings.outlineSettings; + const { enabledInEachNote } = this.settings.outlineSettings; + let enabled = true; if (metadataEnabled == null) { if (enabledInEachNote != undefined && !enabledInEachNote) { - return; + enabled = false; } if (this.getEnabledFromBlacklist(state.file)) { - return; + enabled = false; } } else if (!metadataEnabled) { - return; + enabled = false; } - let ignoreTopLevel = 0; - if (ordered && (orderedIgnoreSingle || orderedBasedOnExisting)) { - const queier = new Querier(orderedAllowZeroLevel); - for (const cacheHeading of cacheHeadings) { - queier.handler(cacheHeading.level); - ignoreTopLevel = queier.query( - orderedIgnoreSingle, - orderedIgnoreMaximum - ); - if (ignoreTopLevel === 0) { - break; - } - } - } - - const counter = new Counter({ - ordered, - delimiter: orderedDelimiter, - trailingDelimiter: orderedTrailingDelimiter, - styleType: orderedStyleType, - customIdents: getOrderedCustomIdents(orderedCustomIdents), - specifiedString: orderedSpecifiedString, - ignoreTopLevel, - allowZeroLevel: orderedAllowZeroLevel, - levelHeadings: getUnorderedLevelHeadings(unorderedLevelHeadings), - }); - - let lastCacheLevel = 0; - let lastReadLevel = 0; - for ( - let i = 0, j = 0; - i < headingElements.length && j < cacheHeadings.length; - i++, j++ - ) { - const readLevel = getTreeItemLevel(headingElements[i]); - const readText = getTreeItemText(headingElements[i]); - let cacheLevel = cacheHeadings[j].level; - if (i > 0) { - const diff = diffLevel(readLevel, lastReadLevel); - while ( - j < cacheHeadings.length - 1 && - (diffLevel(cacheLevel, lastCacheLevel) !== diff || - !compareHeadingText(cacheHeadings[j].heading, readText)) - ) { - counter.handler(cacheLevel); - j++; - cacheLevel = cacheHeadings[j].level; - } - } - - const decoratorContent = counter.decorator(cacheLevel); - decorateOutlineElement( - headingElements[i], - decoratorContent, - opacity, - position + if (enabled) { + outlineHandler( + this.settings.outlineSettings, + viewContent, + headingElements, + cacheHeadings ); - - lastCacheLevel = cacheLevel; - lastReadLevel = readLevel; + } else { + cancelOutlineDecoration(viewContent); } }, () => { - const headingElements = - viewContent.querySelectorAll(".tree-item"); - headingElements.forEach((ele) => { - cancelOutlineDecorator(ele); - }); + if (viewContent) { + cancelOutlineDecoration(viewContent); + } } ); @@ -628,16 +658,15 @@ export class HeadingPlugin extends Plugin { enabled = false; } - if (!enabled) { + if (enabled) { + quietOutlineHandler( + this.settings.quietOutlineSettings, + containerElement, + headingELements + ); + } else { cancelQuietOutlineDecoration(containerElement); - return; } - - quietOutlineHandler( - this.settings.quietOutlineSettings, - containerElement, - headingELements - ); }, () => { const containerElement = @@ -718,108 +747,38 @@ export class HeadingPlugin extends Plugin { frontmatter ); - const { - enabledInEachNote, - opacity, - position, - ordered, - orderedDelimiter, - orderedTrailingDelimiter, - orderedStyleType, - orderedSpecifiedString, - orderedCustomIdents, - orderedIgnoreSingle, - orderedIgnoreMaximum = 6, - orderedBasedOnExisting, - orderedAllowZeroLevel, - unorderedLevelHeadings, - } = this.settings.fileExplorerSettings; + const { enabledInEachNote } = this.settings.fileExplorerSettings; + let enabled = true; if (metadataEnabled == null) { if (enabledInEachNote != undefined && !enabledInEachNote) { - return; + enabled = false; } if (this.getEnabledFromBlacklist(filePath)) { - return; + enabled = false; } } else if (!metadataEnabled) { - return; + enabled = false; } - let ignoreTopLevel = 0; - if (ordered && (orderedIgnoreSingle || orderedBasedOnExisting)) { - const queier = new Querier(orderedAllowZeroLevel); - for (const cacheHeading of cacheHeadings) { - queier.handler(cacheHeading.level); - ignoreTopLevel = queier.query( - orderedIgnoreSingle, - orderedIgnoreMaximum - ); - if (ignoreTopLevel === 0) { - break; - } - } - } - - const counter = new Counter({ - ordered, - delimiter: orderedDelimiter, - trailingDelimiter: orderedTrailingDelimiter, - styleType: orderedStyleType, - customIdents: getOrderedCustomIdents(orderedCustomIdents), - specifiedString: orderedSpecifiedString, - ignoreTopLevel, - allowZeroLevel: orderedAllowZeroLevel, - levelHeadings: getUnorderedLevelHeadings(unorderedLevelHeadings), - }); - - const marginMultiplier = - parseInt( - getComputedStyle(document.body).getPropertyValue( - "--clickable-heading-margin-multiplier" - ) - ) || 10; - - for ( - let i = 0, j = 0; - i < headingElements.length && j < cacheHeadings.length; - i++, j++ - ) { - const readLevel = getFileHeadingItemLevel( - headingElements[i], - marginMultiplier - ); - const readText = headingElements[i].innerText; - let cacheLevel = cacheHeadings[j].level; - - while ( - j < cacheHeadings.length - 1 && - (cacheLevel !== readLevel || - cacheHeadings[j].heading !== readText) - ) { - counter.handler(cacheLevel); - j++; - cacheLevel = cacheHeadings[j].level; - } - - const decoratorContent = counter.decorator(cacheLevel); - decorateFileHeadingElement( - headingElements[i], - decoratorContent, - opacity, - position + if (enabled) { + fileExplorerHandler( + this.settings.fileExplorerSettings, + navFile, + headingElements, + cacheHeadings ); + } else { + cancelFileExplorerDecoration(navFile); } }); }, () => { - const headingElements = - navFilesContainer.querySelectorAll( - ".nav-file-title .file-heading-container .clickable-heading" - ); - headingElements.forEach((ele) => { - cancelFileHeadingDecorator(ele); + const containerElements = + navFilesContainer.querySelectorAll(".nav-file-title"); + containerElements.forEach((ele) => { + cancelFileExplorerDecoration(ele); }); } ); @@ -834,12 +793,6 @@ export class HeadingPlugin extends Plugin { }); } - private debouncedSaveSettings = debounce( - this.rerenderPreviewMarkdown.bind(this), - 1000, - true - ); - /** * Rerender Preview Markdown. * @param file @@ -853,6 +806,18 @@ export class HeadingPlugin extends Plugin { } } + private rerenderOutlineDecorator() { + this.outlineComponents.forEach((vc) => vc.render()); + } + + private rerenderQuietOutlineDecorator() { + this.quietOutlineComponents.forEach((vc) => vc.render()); + } + + private rerenderFileExplorerDecorator() { + this.fileExplorerComponents.forEach((vc) => vc.render()); + } + async getPluginData(): Promise { const { metadataKeyword, diff --git a/components/setting-tab.ts b/components/setting-tab.ts index ce6fdbb..33f7209 100644 --- a/components/setting-tab.ts +++ b/components/setting-tab.ts @@ -121,9 +121,7 @@ export class HeadingSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.enabledInOutline) .onChange(async (value) => { this.plugin.settings.enabledInOutline = value; - await this.plugin.saveSettings({ - outline: this.plugin.settings.enabledInOutline, - }); + await this.plugin.saveSettings(); }) ); @@ -145,9 +143,7 @@ export class HeadingSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.enabledInQuietOutline) .onChange(async (value) => { this.plugin.settings.enabledInQuietOutline = value; - await this.plugin.saveSettings({ - quietOutline: this.plugin.settings.enabledInQuietOutline, - }); + await this.plugin.saveSettings(); }) ); @@ -182,9 +178,7 @@ export class HeadingSettingTab extends PluginSettingTab { .setValue(this.plugin.settings.enabledInFileExplorer) .onChange(async (value) => { this.plugin.settings.enabledInFileExplorer = value; - await this.plugin.saveSettings({ - fileExplorer: this.plugin.settings.enabledInFileExplorer, - }); + await this.plugin.saveSettings(); }) );