diff --git a/common/data.ts b/common/data.ts index a62b48c..9c197f4 100644 --- a/common/data.ts +++ b/common/data.ts @@ -40,7 +40,7 @@ export type DecoratorOptions = | OrderedDecoratorOptions | UnorderedDecoratorOptions; -interface HeadingDecoratorSettings { +export interface HeadingDecoratorSettings { enabledInEachNote?: boolean; ordered: boolean; @@ -106,6 +106,8 @@ export const outlineHeadingDecoratorClassName = "outline-custom-heading-decorator"; 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 beforeDecoratorClassName = "before-heading-decorator"; diff --git a/common/dom.ts b/common/dom.ts index 86b81d9..2bfab65 100644 --- a/common/dom.ts +++ b/common/dom.ts @@ -7,7 +7,6 @@ import { afterDecoratorClassName, afterInsideDecoratorClassName, outlineHeadingDecoratorClassName, - quietOutlineHeadingDecoratorClassName, fileExplorerHeadingDecoratorClassName, compareMarkdownText, } from "./data"; @@ -198,82 +197,6 @@ export function cancelOutlineDecorator(element: HTMLElement): void { } } -/** - * Query the heading level of an HTML element that is part of the quiet outline. - * - * @param element - * @returns The heading level or `-1` if not found. - */ -export function queryHeadingLevelByQuietOutlineElement( - element: HTMLElement -): number { - const classList = element.classList; - if (classList.contains("level-1")) { - return 1; - } else if (classList.contains("level-2")) { - return 2; - } else if (classList.contains("level-3")) { - return 3; - } else if (classList.contains("level-4")) { - return 4; - } else if (classList.contains("level-5")) { - return 5; - } else if (classList.contains("level-6")) { - return 6; - } - - return -1; -} - -/** - * Decorate an HTML element that is part of the quiet outline with a heading decorator. - * - * @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 decorateQuietOutlineElement( - element: HTMLElement, - content: string, - opacity: OpacityOptions, - position: PostionOptions -): void { - if (content) { - const nodeContent = element.querySelector( - ".n-tree-node-content" - ); - if (nodeContent) { - nodeContent.dataset.headingDecorator = content; - nodeContent.dataset.decoratorOpacity = `${opacity}%`; - nodeContent.classList.add( - quietOutlineHeadingDecoratorClassName, - getPositionClassName(position, true) - ); - } - } -} - -/** - * Cancel the decorator from an HTML element that is part of the quiet outline. - * - * @param element The HTML element to cancel the decorator. - */ -export function cancelQuietOutlineDecorator(element: HTMLElement): void { - const nodeContent = element.querySelector( - ".n-tree-node-content" - ); - if (nodeContent) { - delete nodeContent.dataset.headingDecorator; - delete nodeContent.dataset.decoratorOpacity; - nodeContent.classList.remove( - quietOutlineHeadingDecoratorClassName, - beforeDecoratorClassName, - afterDecoratorClassName - ); - } -} - /** * Get the level of a file heading item based on its margin-left value. * diff --git a/components/quiet-outline.ts b/components/quiet-outline.ts new file mode 100644 index 0000000..5946568 --- /dev/null +++ b/components/quiet-outline.ts @@ -0,0 +1,173 @@ +import type { HeadingDecoratorSettings } from "../common/data"; +import { + quietOutlineHeadingDecoratorClassName, + quietOutlineContainerClassName, + beforeDecoratorClassName, + afterDecoratorClassName, + getOrderedCustomIdents, + getUnorderedLevelHeadings, +} from "../common/data"; +import { Querier, Counter } from "../common/counter"; + +export function quietOutlineHandler( + settings: HeadingDecoratorSettings, + container: HTMLElement, + headingELements: NodeListOf +): void { + const { + opacity, + position, + ordered, + orderedDelimiter, + orderedTrailingDelimiter, + orderedStyleType, + orderedSpecifiedString, + orderedCustomIdents, + orderedIgnoreSingle, + orderedIgnoreMaximum = 6, + orderedBasedOnExisting, + orderedAllowZeroLevel, + unorderedLevelHeadings, + } = settings; + + container.classList.add(quietOutlineContainerClassName); + + let ignoreTopLevel = 0; + if (ordered && (orderedIgnoreSingle || orderedBasedOnExisting)) { + const queier = new Querier(orderedAllowZeroLevel); + for (const eleIndex in headingELements) { + const level = queryHeadingLevelByQuietOutlineElement( + headingELements[eleIndex] + ); + queier.handler(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), + }); + + headingELements.forEach((headingEle) => { + const level = queryHeadingLevelByQuietOutlineElement(headingEle); + const decoratorContent = counter.decorator(level); + decorateQuietOutlineElement( + headingEle, + decoratorContent, + opacity, + position + ); + }); +} + +/** + * Cancel the decoration for all headings in a container. + * + * @param container The container element that contains the headings. + */ +export function cancelQuietOutlineDecoration(container: HTMLElement): void { + if (container.classList.contains(quietOutlineContainerClassName)) { + container.classList.remove(quietOutlineContainerClassName); + + const headingElements = + container.querySelectorAll(".n-tree-node"); + + headingElements.forEach((ele) => { + cancelQuietOutlineDecorator(ele); + }); + } +} + +/** + * Query the heading level of an HTML element that is part of the quiet outline. + * + * @param element + * @returns The heading level or `-1` if not found. + */ +function queryHeadingLevelByQuietOutlineElement(element: HTMLElement): number { + const classList = element.classList; + if (classList.contains("level-1")) { + return 1; + } else if (classList.contains("level-2")) { + return 2; + } else if (classList.contains("level-3")) { + return 3; + } else if (classList.contains("level-4")) { + return 4; + } else if (classList.contains("level-5")) { + return 5; + } else if (classList.contains("level-6")) { + return 6; + } + + return -1; +} + +/** + * Decorate an HTML element that is part of the quiet outline with a heading decorator. + * + * @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 decorateQuietOutlineElement( + element: HTMLElement, + content: string, + opacity: OpacityOptions, + position: PostionOptions +): void { + if (content) { + const nodeContent = element.querySelector( + ".n-tree-node-content" + ); + if (nodeContent) { + nodeContent.dataset.headingDecorator = content; + nodeContent.dataset.decoratorOpacity = `${opacity}%`; + + //? Remove potential residual class names + nodeContent.classList.remove( + position === "after" + ? beforeDecoratorClassName + : afterDecoratorClassName + ); + nodeContent.classList.add( + quietOutlineHeadingDecoratorClassName, + position === "after" + ? afterDecoratorClassName + : beforeDecoratorClassName + ); + } + } +} + +/** + * Cancel the decorator from an HTML element that is part of the quiet outline. + * + * @param element The HTML element to cancel the decorator. + */ +function cancelQuietOutlineDecorator(element: HTMLElement): void { + const nodeContent = element.querySelector( + ".n-tree-node-content" + ); + if (nodeContent) { + delete nodeContent.dataset.headingDecorator; + delete nodeContent.dataset.decoratorOpacity; + nodeContent.classList.remove( + quietOutlineHeadingDecoratorClassName, + beforeDecoratorClassName, + afterDecoratorClassName + ); + } +} diff --git a/main.ts b/main.ts index e156acf..c0d9459 100644 --- a/main.ts +++ b/main.ts @@ -41,9 +41,6 @@ import { getTreeItemText, getFileHeadingItemLevel, compareHeadingText, - queryHeadingLevelByQuietOutlineElement, - decorateQuietOutlineElement, - cancelQuietOutlineDecorator, } from "./common/dom"; import { HeadingViewPlugin, @@ -53,6 +50,10 @@ import { } from "./components/view"; import { ViewChildComponent } from "./components/child"; import { FolderSuggest } from "./components/suggest"; +import { + quietOutlineHandler, + cancelQuietOutlineDecoration, +} from "./components/quiet-outline"; interface ObsidianEditor extends Editor { cm: EditorView; @@ -611,6 +612,12 @@ export default class HeadingPlugin extends Plugin { view, viewContent, () => { + const containerElement = + viewContent.querySelector(".n-tree"); + if (!containerElement) { + return; + } + const headingELements = viewContent.querySelectorAll(".n-tree-node"); if (headingELements.length === 0) { @@ -633,82 +640,38 @@ export default class HeadingPlugin extends Plugin { frontmatter ); - const { - enabledInEachNote, - opacity, - position, - ordered, - orderedDelimiter, - orderedTrailingDelimiter, - orderedStyleType, - orderedSpecifiedString, - orderedCustomIdents, - orderedIgnoreSingle, - orderedIgnoreMaximum = 6, - orderedBasedOnExisting, - orderedAllowZeroLevel, - unorderedLevelHeadings, - } = this.settings.quietOutlineSettings; + const { enabledInEachNote } = this.settings.quietOutlineSettings; + let enabled = true; if (metadataEnabled == null) { if (enabledInEachNote != undefined && !enabledInEachNote) { - return; + enabled = false; } if (this.getEnabledFromBlacklist(file.path)) { - return; + enabled = false; } } else if (!metadataEnabled) { + enabled = false; + } + + if (!enabled) { + cancelQuietOutlineDecoration(containerElement); return; } - let ignoreTopLevel = 0; - if (ordered && (orderedIgnoreSingle || orderedBasedOnExisting)) { - const queier = new Querier(orderedAllowZeroLevel); - for (const eleIndex in headingELements) { - const level = queryHeadingLevelByQuietOutlineElement( - headingELements[eleIndex] - ); - queier.handler(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), - }); - - headingELements.forEach((headingEle) => { - const level = queryHeadingLevelByQuietOutlineElement(headingEle); - const decoratorContent = counter.decorator(level); - decorateQuietOutlineElement( - headingEle, - decoratorContent, - opacity, - position - ); - }); + quietOutlineHandler( + this.settings.quietOutlineSettings, + containerElement, + headingELements + ); }, () => { - const headingElements = - viewContent.querySelectorAll(".n-tree-node"); - headingElements.forEach((ele) => { - cancelQuietOutlineDecorator(ele); - }); + const containerElement = + viewContent.querySelector(".n-tree"); + if (containerElement) { + cancelQuietOutlineDecoration(containerElement); + } } );