import type * as Presets from "@jsamr/counter-style/presets"; type CounterStyleType = Exclude< keyof typeof Presets, "circle" | "disc" | "square" >; export type OrderedCounterStyleType = | CounterStyleType | "customIdent" | "string"; export interface OrderedDecoratorOptions { styleType?: OrderedCounterStyleType; delimiter?: string; trailingDelimiter?: boolean; customTrailingDelimiter?: string; leadingDelimiter?: boolean; customLeadingDelimiter?: string; customIdents?: string[]; specifiedString?: string; ignoreTopLevel?: number; allowZeroLevel?: boolean; maxRecLevel?: number; } export interface IndependentDecoratorSettings { styleType: OrderedCounterStyleType; delimiter: string; trailingDelimiter: boolean; customTrailingDelimiter: string; leadingDelimiter: boolean; customLeadingDelimiter: string; customIdents: string; specifiedString: string; } export interface IndependentDecoratorOptions { maxRecLevel?: number; allowZeroLevel?: boolean; ignoreTopLevel?: number; orderedRecLevel?: number; h1?: Partial; h2?: Partial; h3?: Partial; h4?: Partial; h5?: Partial; h6?: Partial; } interface IndependentSettings { orderedRecLevel: number; h1: IndependentDecoratorSettings; h2: IndependentDecoratorSettings; h3: IndependentDecoratorSettings; h4: IndependentDecoratorSettings; h5: IndependentDecoratorSettings; h6: IndependentDecoratorSettings; } export interface HeadingDecoratorSettings { enabledInEachNote?: boolean; decoratorMode?: DecoratorMode; opacity: OpacityOptions; position: PostionOptions; maxRecLevel?: number; orderedAllowZeroLevel?: boolean; orderedBasedOnExisting?: boolean; orderedAlwaysIgnore?: boolean; orderedIgnoreMaximum?: number; orderedIgnoreSingle: boolean; orderedStyleType: OrderedCounterStyleType; orderedDelimiter: string; orderedTrailingDelimiter: boolean; orderedCustomTrailingDelimiter?: string; orderedLeadingDelimiter?: boolean; orderedCustomLeadingDelimiter?: string; orderedCustomIdents: string; orderedSpecifiedString: string; independentSettings?: IndependentSettings; unorderedLevelHeadings: string; } export type HeadingPluginSettings = { metadataKeyword: string; folderBlacklist: string[]; fileRegexBlacklist: string[]; enabledInReading: boolean; enabledReadingSettings: boolean; readingRenderPolicy: RenderPolicy; enabledInPreview: boolean; enabledPreviewSettings: boolean; enabledInSource: boolean; enabledSourceSettings: boolean; sourceHideNumberSigns: boolean; enabledInOutline: boolean; enabledOutlineSettings: boolean; enabledInQuietOutline: boolean; enabledQuietOutlineSettings: boolean; enabledInFileExplorer: boolean; enabledFileExplorerSettings: boolean; } & Record; export type HeadingPluginData = Omit< HeadingPluginSettings, | "commonSettings" | "metadataKeyword" | "folderBlacklist" | "fileRegexBlacklist" | "enabledInReading" | "enabledReadingSettings" | "readingRenderPolicy" | "readingSettings" | "enabledPreviewSettings" | "enabledSourceSettings" | "enabledInOutline" | "outlineSettings" | "enabledInQuietOutline" | "enabledOutlineSettings" | "quietOutlineSettings" | "enabledQuietOutlineSettings" | "enabledInFileExplorer" | "enabledFileExplorerSettings" | "fileExplorerSettings" >; export const className = { heading: "custom-heading-decorator", reading: "reading-custom-heading-decorator", preview: "preview-custom-heading-decorator", source: "source-custom-heading-decorator", outline: "outline-custom-heading-decorator", outlineContainer: "outline-custom-heading-container", quietOutline: "quiet-outline-custom-heading-decorator", quietOutlineContainer: "quiet-outline-custom-heading-container", fileExplorer: "file-explorer-custom-heading-decorator", fileExplorerContainer: "file-explorer-custom-heading-container", before: "before-heading-decorator", beforeInside: "before-inside-heading-decorator", after: "after-heading-decorator", afterInside: "after-inside-heading-decorator", hideSourceNumberSigns: "hide-source-number-signs", settingContainer: "heading-decorator-setting-container", settingItem: "heading-decorator-setting-item", }; export const headingsSelector = ".el-h1 h1, .el-h2 h2, .el-h3 h3, .el-h4 h4, .el-h5 h5, .el-h6 h6"; export const defaultHeadingTuple: HeadingTuple = [ "H1", "H2", "H3", "H4", "H5", "H6", ]; const defaultCustomidents = "Ⓐ Ⓑ Ⓒ Ⓓ Ⓔ Ⓕ Ⓖ Ⓗ Ⓘ Ⓙ Ⓚ Ⓛ Ⓜ Ⓝ Ⓞ Ⓟ Ⓠ Ⓡ Ⓢ Ⓣ Ⓤ Ⓥ Ⓦ Ⓧ Ⓨ Ⓩ"; function defaultIndependentDecoratorSettings(): IndependentDecoratorSettings { return { styleType: "decimal", delimiter: ".", trailingDelimiter: false, customTrailingDelimiter: "", leadingDelimiter: false, customLeadingDelimiter: "", customIdents: defaultCustomidents, specifiedString: "#", }; } /** * Defaults independent settings * * @returns independent settings */ export function defaultIndependentSettings(): IndependentSettings { return { orderedRecLevel: 6, h1: defaultIndependentDecoratorSettings(), h2: defaultIndependentDecoratorSettings(), h3: defaultIndependentDecoratorSettings(), h4: defaultIndependentDecoratorSettings(), h5: defaultIndependentDecoratorSettings(), h6: defaultIndependentDecoratorSettings(), }; } /** * Default settings for heading decorator. * * @returns default settings for heading decorator. */ export function defaultHeadingDecoratorSettings(): HeadingDecoratorSettings { return { enabledInEachNote: true, opacity: 20, position: "before", maxRecLevel: 6, decoratorMode: "orderd", orderedDelimiter: ".", orderedTrailingDelimiter: false, orderedCustomTrailingDelimiter: "", orderedLeadingDelimiter: false, orderedCustomLeadingDelimiter: "", orderedStyleType: "decimal", orderedCustomIdents: defaultCustomidents, orderedSpecifiedString: "#", orderedAllowZeroLevel: false, orderedBasedOnExisting: false, orderedAlwaysIgnore: false, orderedIgnoreSingle: false, orderedIgnoreMaximum: 6, independentSettings: defaultIndependentSettings(), unorderedLevelHeadings: defaultHeadingTuple.join(" "), }; } /** * Default plugin settings. * * @returns default plugin settings. */ export function defaultSettings(): HeadingPluginSettings { return { metadataKeyword: "heading", fileRegexBlacklist: [], folderBlacklist: [], enabledInReading: true, enabledReadingSettings: false, readingSettings: defaultHeadingDecoratorSettings(), readingRenderPolicy: "partial", enabledInPreview: true, enabledPreviewSettings: false, previewSettings: defaultHeadingDecoratorSettings(), enabledInSource: false, enabledSourceSettings: false, sourceSettings: defaultHeadingDecoratorSettings(), sourceHideNumberSigns: false, enabledInOutline: false, enabledOutlineSettings: false, outlineSettings: defaultHeadingDecoratorSettings(), enabledInQuietOutline: false, enabledQuietOutlineSettings: false, quietOutlineSettings: defaultHeadingDecoratorSettings(), enabledInFileExplorer: false, enabledFileExplorerSettings: false, fileExplorerSettings: defaultHeadingDecoratorSettings(), commonSettings: defaultHeadingDecoratorSettings(), }; } /** * Get unordered level headings from settings. * * @param value - The value to split and filter. * @returns An array of strings representing the unordered level headings. */ export function getUnorderedLevelHeadings(value: string): HeadingTuple { const arr = value.split(/\s+/g).filter((v) => v); if (arr.length > 6) { return arr.slice(0, 6) as HeadingTuple; } return [...defaultHeadingTuple]; } /** * Get ordered custom idents from settings. * * @param value - The value to split and filter. * @returns An array of strings representing the ordered custom idents. */ export function getOrderedCustomIdents(value: string) { return value.split(/\s+/g).filter((v) => v); } /** * Get the class name for a given position. * * @param position The position to get the class name for. * @param ignoreInsideFlag Whether to ignore the inside flag. * @returns The class name. */ export function getPositionClassName( position: PostionOptions, ignoreInsideFlag?: boolean ): string { switch (position) { case "before": return className.before; case "after": return className.after; case "before-inside": return ignoreInsideFlag ? className.before : className.beforeInside; case "after-inside": return ignoreInsideFlag ? className.after : className.afterInside; default: return ""; } } /** * Diff level between two numbers. * * @param current current level. * @param last last level. * @returns boolean. if current level is less than or equal to last level, return true. otherwise, return false. */ export function diffLevel(current: number, last: number): boolean { const diff = current - last; if (diff > 0) { return false; } return true; } /** * Compare Markdown text. * * @param source The source heading text. * @param outline The outline heading text. * @returns boolean. if left Markdown text is equal to right Markdown text, return true. otherwise, return false. */ export function compareMarkdownText(source: string, outline: string): boolean { if (source === outline) { return true; } else { source = source.replaceAll(/\[(.+)\]\(.*\)/g, "$1"); if (source === outline) { return true; } source = source.replaceAll(/\[\[.+\|(.+)\]\]/g, "$1"); if (source === outline) { return true; } source = source.replaceAll(/\[\[(.+)#(.+)\]\]/g, "$1 > $2"); if (source === outline) { return true; } source = source.replaceAll(/\[\[#?(.+)\]\]/g, "$1"); if (source === outline) { return true; } if ( source.replaceAll(/[-+>`=_~*\\\s]/g, "") === outline.replaceAll(/[-+>`=_~*\\\s]/g, "") ) { return true; } } return false; } /** * Get boolean value from unknown. * * @param value unknown value. * @returns boolean or null. if value is boolean, return value. otherwise, return null or convert to boolean based on value. if value is not convertible to boolean, return null. */ export function getBoolean(value: unknown) { if (value == null) { return null; } if (typeof value === "boolean") { return value; } else { const trueSet = new Set(["true", "yes", "on", "1", 1]); if (trueSet.has(value)) { return true; } const falseSet = new Set(["false", "no", "off", "0", 0]); if (falseSet.has(value)) { return false; } } return null; } /** * Check if css string contains enable/disable heading classes. * * @param cssString CSS string. * @param mode Mode to check for. * @returns CSSEnabledStatus object. */ export function checkEnabledCSS( cssString: string, mode: HeadingMetadataSettingsType ) { const status: CSSEnabledStatus = { mode: null, all: null, }; const cssClasses = cssString.split(" ").map((c) => c.trim()); for (const cssClass of cssClasses) { switch (cssClass) { case `enable-${mode}-heading`: status.mode = true; break; case `disable-${mode}-heading`: status.mode = false; break; case "enable-heading": status.all = true; break; case "disable-heading": status.all = false; break; } } return status; } /** * Converts a string to a regex object. * @param value The string to convert to a regex. * @returns A regex object if the string is a valid regex, otherwise null. */ export function stringToRegex(value: string) { const str = value.trim(); if (!str) { return null; } try { const re = new RegExp( str.substring(1, str.lastIndexOf("/")), str.substring(str.lastIndexOf("/") + 1) ); return re; } catch { return null; } } /** * Find the first non-hash character index in a string. If the string starts with a hash, it will find the first non-hash character after the hash. Otherwise, it will find the first non-space character. * * @param text The string to search. * @returns The index of the first non-hash character. If no such character exists, returns 0. */ export function findFirstCharacterIndex(text: string): number { if (text.trim().startsWith("#")) { for (let index = 0; index < text.length; index++) { if (text[index] !== "#" && text[index].trim()) { return index; } } } else { for (let index = 0; index < text.length; index++) { if (text[index].trim()) { return index; } } } return 0; }