From 138c7841c99c8080491af8469dda79d650dfc826 Mon Sep 17 00:00:00 2001 From: dragonish Date: Fri, 9 Jan 2026 19:27:54 +0800 Subject: [PATCH] feat: add a new splice decorator mode --- common/counter.ts | 85 ++++++++ common/data.ts | 71 +++++++ components/editor.ts | 19 ++ components/file-explorer.ts | 19 ++ components/outline.ts | 19 ++ components/quiet-outline.ts | 19 ++ components/reading.ts | 19 ++ components/setting-tab.ts | 223 +++++++++++++++++++- locales/en.json | 3 +- locales/zh-TW.json | 3 +- locales/zh.json | 3 +- test/common/counter.spec.ts | 398 +++++++++++++++++++++++++++++++++++- types.d.ts | 2 +- 13 files changed, 876 insertions(+), 7 deletions(-) diff --git a/common/counter.ts b/common/counter.ts index a082f33..6dd6bd3 100644 --- a/common/counter.ts +++ b/common/counter.ts @@ -2,6 +2,8 @@ import type { OrderedDecoratorOptions, IndependentDecoratorOptions, IndependentDecoratorSettings, + SpliceDecoratorOptions, + SpliceDecoratorSettings, } from "./data"; import { getOrderedCustomIdents } from "./data"; import * as presets from "@jsamr/counter-style/presets"; @@ -222,6 +224,89 @@ export class IndependentCounter extends Querier implements Counter { } } +export class SpliceCounter extends Querier implements Counter { + constructor(private decoratorOptions: SpliceDecoratorOptions) { + super(decoratorOptions?.allowZeroLevel, decoratorOptions?.maxRecLevel); + } + + decorator(level: number): string { + let result = ""; + if (level < 1 || level > this.maxRecLevel) { + return result; + } + const { + delimiter = ".", + trailingDelimiter = false, + customTrailingDelimiter = "", + leadingDelimiter = false, + customLeadingDelimiter = "", + h1 = {}, + h2 = {}, + h3 = {}, + h4 = {}, + h5 = {}, + h6 = {}, + ignoreTopLevel = 0, + } = this.decoratorOptions; + const levels = this.handler(level).slice(ignoreTopLevel); + + const results = levels.map((l, i) => { + let r = ""; + let used: Partial; + switch (i + ignoreTopLevel + 1) { + case 6: + used = h6; + break; + case 5: + used = h5; + break; + case 4: + used = h4; + break; + case 3: + used = h3; + break; + case 2: + used = h2; + break; + case 1: + default: + used = h1; + break; + } + + const { + styleType = "decimal", + customIdents = "", + specifiedString = "#", + } = used; + + switch (styleType) { + case "customIdent": + r = l > customIdents.length ? l.toString() : customIdents[l - 1]; + break; + case "string": + r = specifiedString.trim() || "#"; + break; + default: + r = presets[styleType].renderCounter(l); + break; + } + return r; + }); + + result = results.join(delimiter); + if (result.length > 0) { + result = + (leadingDelimiter ? customLeadingDelimiter || delimiter : "") + + result + + (trailingDelimiter ? customTrailingDelimiter || delimiter : ""); + } + + return result; + } +} + export class UnorderedCounter implements Counter { constructor(private levelHeadings: HeadingTuple, private maxRecLevel = 6) {} diff --git a/common/data.ts b/common/data.ts index bbb5147..dd0c54b 100644 --- a/common/data.ts +++ b/common/data.ts @@ -34,6 +34,12 @@ export interface IndependentDecoratorSettings { specifiedString: string; } +export interface SpliceDecoratorSettings { + styleType: OrderedCounterStyleType; + customIdents: string; + specifiedString: string; +} + export interface IndependentDecoratorOptions { maxRecLevel?: number; allowZeroLevel?: boolean; @@ -59,6 +65,40 @@ export interface IndependentSettings { h6: IndependentDecoratorSettings; } +export interface SpliceDecoratorOptions { + maxRecLevel?: number; + allowZeroLevel?: boolean; + ignoreTopLevel?: number; + + delimiter?: string; + trailingDelimiter?: boolean; + customTrailingDelimiter?: string; + leadingDelimiter?: boolean; + customLeadingDelimiter?: string; + + h1?: Partial; + h2?: Partial; + h3?: Partial; + h4?: Partial; + h5?: Partial; + h6?: Partial; +} + +export interface SpliceSettings { + delimiter: string; + trailingDelimiter: boolean; + customTrailingDelimiter: string; + leadingDelimiter: boolean; + customLeadingDelimiter: string; + + h1: SpliceDecoratorSettings; + h2: SpliceDecoratorSettings; + h3: SpliceDecoratorSettings; + h4: SpliceDecoratorSettings; + h5: SpliceDecoratorSettings; + h6: SpliceDecoratorSettings; +} + export interface HeadingDecoratorSettings { enabledInEachNote?: boolean; @@ -83,6 +123,7 @@ export interface HeadingDecoratorSettings { orderedSpecifiedString: string; independentSettings?: IndependentSettings; + spliceSettings?: SpliceSettings; unorderedLevelHeadings: string; } @@ -176,6 +217,14 @@ function defaultIndependentDecoratorSettings(): IndependentDecoratorSettings { }; } +function defaultSpliceDecoratorSettings(): SpliceDecoratorSettings { + return { + styleType: "decimal", + customIdents: defaultCustomidents, + specifiedString: "#", + }; +} + /** * Defaults independent settings * @@ -193,6 +242,27 @@ export function defaultIndependentSettings(): IndependentSettings { }; } +/** + * Defaults splice settings + * + * @returns splice settings + */ +export function defaultSpliceSettings(): SpliceSettings { + return { + delimiter: ".", + trailingDelimiter: false, + customTrailingDelimiter: "", + leadingDelimiter: false, + customLeadingDelimiter: "", + h1: defaultSpliceDecoratorSettings(), + h2: defaultSpliceDecoratorSettings(), + h3: defaultSpliceDecoratorSettings(), + h4: defaultSpliceDecoratorSettings(), + h5: defaultSpliceDecoratorSettings(), + h6: defaultSpliceDecoratorSettings(), + }; +} + /** * Default settings for heading decorator. * @@ -219,6 +289,7 @@ export function defaultHeadingDecoratorSettings(): HeadingDecoratorSettings { orderedIgnoreSingle: false, orderedIgnoreMaximum: 6, independentSettings: defaultIndependentSettings(), + spliceSettings: defaultSpliceSettings(), unorderedLevelHeadings: defaultHeadingTuple.join(" "), }; } diff --git a/components/editor.ts b/components/editor.ts index 2f35035..5acc63d 100644 --- a/components/editor.ts +++ b/components/editor.ts @@ -20,6 +20,7 @@ import { UnorderedCounter, OrderedCounter, IndependentCounter, + SpliceCounter, } from "../common/counter"; import { Heading } from "../common/heading"; @@ -122,6 +123,7 @@ export class HeadingEditorViewPlugin implements PluginValue { orderedAllowZeroLevel, unorderedLevelHeadings, independentSettings, + spliceSettings, } = isLivePreviwMode ? pluginData.previewSettings : pluginData.sourceSettings; @@ -175,6 +177,23 @@ export class HeadingEditorViewPlugin implements PluginValue { h5: independentSettings?.h5, h6: independentSettings?.h6, }); + } else if (decoratorMode === "splice") { + counter = new SpliceCounter({ + maxRecLevel, + ignoreTopLevel, + allowZeroLevel: orderedAllowZeroLevel, + delimiter: spliceSettings?.delimiter, + trailingDelimiter: spliceSettings?.trailingDelimiter, + customTrailingDelimiter: spliceSettings?.customTrailingDelimiter, + leadingDelimiter: spliceSettings?.leadingDelimiter, + customLeadingDelimiter: spliceSettings?.customLeadingDelimiter, + h1: spliceSettings?.h1, + h2: spliceSettings?.h2, + h3: spliceSettings?.h3, + h4: spliceSettings?.h4, + h5: spliceSettings?.h5, + h6: spliceSettings?.h6, + }); } else { counter = new OrderedCounter({ maxRecLevel, diff --git a/components/file-explorer.ts b/components/file-explorer.ts index 50c26fe..3e190c8 100644 --- a/components/file-explorer.ts +++ b/components/file-explorer.ts @@ -10,6 +10,7 @@ import { UnorderedCounter, OrderedCounter, IndependentCounter, + SpliceCounter, } from "../common/counter"; /** @@ -46,6 +47,7 @@ export function fileExplorerHandler( orderedAllowZeroLevel, unorderedLevelHeadings, independentSettings, + spliceSettings, } = settings; container.classList.add(className.fileExplorerContainer); @@ -87,6 +89,23 @@ export function fileExplorerHandler( h5: independentSettings?.h5, h6: independentSettings?.h6, }); + } else if (decoratorMode === "splice") { + counter = new SpliceCounter({ + maxRecLevel, + ignoreTopLevel, + allowZeroLevel: orderedAllowZeroLevel, + delimiter: spliceSettings?.delimiter, + trailingDelimiter: spliceSettings?.trailingDelimiter, + customTrailingDelimiter: spliceSettings?.customTrailingDelimiter, + leadingDelimiter: spliceSettings?.leadingDelimiter, + customLeadingDelimiter: spliceSettings?.customLeadingDelimiter, + h1: spliceSettings?.h1, + h2: spliceSettings?.h2, + h3: spliceSettings?.h3, + h4: spliceSettings?.h4, + h5: spliceSettings?.h5, + h6: spliceSettings?.h6, + }); } else { counter = new OrderedCounter({ maxRecLevel, diff --git a/components/outline.ts b/components/outline.ts index 20d2c83..ffff8b7 100644 --- a/components/outline.ts +++ b/components/outline.ts @@ -12,6 +12,7 @@ import { UnorderedCounter, OrderedCounter, IndependentCounter, + SpliceCounter, } from "../common/counter"; /** @@ -48,6 +49,7 @@ export function outlineHandler( orderedAllowZeroLevel, unorderedLevelHeadings, independentSettings, + spliceSettings, } = settings; container.classList.add(className.outlineContainer); @@ -89,6 +91,23 @@ export function outlineHandler( h5: independentSettings?.h5, h6: independentSettings?.h6, }); + } else if (decoratorMode === "splice") { + counter = new SpliceCounter({ + maxRecLevel, + ignoreTopLevel, + allowZeroLevel: orderedAllowZeroLevel, + delimiter: spliceSettings?.delimiter, + trailingDelimiter: spliceSettings?.trailingDelimiter, + customTrailingDelimiter: spliceSettings?.customTrailingDelimiter, + leadingDelimiter: spliceSettings?.leadingDelimiter, + customLeadingDelimiter: spliceSettings?.customLeadingDelimiter, + h1: spliceSettings?.h1, + h2: spliceSettings?.h2, + h3: spliceSettings?.h3, + h4: spliceSettings?.h4, + h5: spliceSettings?.h5, + h6: spliceSettings?.h6, + }); } else { counter = new OrderedCounter({ maxRecLevel, diff --git a/components/quiet-outline.ts b/components/quiet-outline.ts index 2599779..fc9b90a 100644 --- a/components/quiet-outline.ts +++ b/components/quiet-outline.ts @@ -9,6 +9,7 @@ import { UnorderedCounter, OrderedCounter, IndependentCounter, + SpliceCounter, } from "../common/counter"; /** @@ -43,6 +44,7 @@ export function quietOutlineHandler( orderedAllowZeroLevel, unorderedLevelHeadings, independentSettings, + spliceSettings, } = settings; container.classList.add(className.quietOutlineContainer); @@ -87,6 +89,23 @@ export function quietOutlineHandler( h5: independentSettings?.h5, h6: independentSettings?.h6, }); + } else if (decoratorMode === "splice") { + counter = new SpliceCounter({ + maxRecLevel, + ignoreTopLevel, + allowZeroLevel: orderedAllowZeroLevel, + delimiter: spliceSettings?.delimiter, + trailingDelimiter: spliceSettings?.trailingDelimiter, + customTrailingDelimiter: spliceSettings?.customTrailingDelimiter, + leadingDelimiter: spliceSettings?.leadingDelimiter, + customLeadingDelimiter: spliceSettings?.customLeadingDelimiter, + h1: spliceSettings?.h1, + h2: spliceSettings?.h2, + h3: spliceSettings?.h3, + h4: spliceSettings?.h4, + h5: spliceSettings?.h5, + h6: spliceSettings?.h6, + }); } else { counter = new OrderedCounter({ maxRecLevel, diff --git a/components/reading.ts b/components/reading.ts index ff481c3..0a14e53 100644 --- a/components/reading.ts +++ b/components/reading.ts @@ -12,6 +12,7 @@ import { UnorderedCounter, OrderedCounter, IndependentCounter, + SpliceCounter, } from "../common/counter"; /** @@ -47,6 +48,7 @@ export function readingOrderedHandler( orderedBasedOnExisting, orderedAllowZeroLevel, independentSettings, + spliceSettings, } = settings; let ignoreTopLevel = 0; @@ -91,6 +93,23 @@ export function readingOrderedHandler( h5: independentSettings?.h5, h6: independentSettings?.h6, }); + } else if (decoratorMode === "splice") { + counter = new SpliceCounter({ + maxRecLevel, + ignoreTopLevel, + allowZeroLevel: orderedAllowZeroLevel, + delimiter: spliceSettings?.delimiter, + trailingDelimiter: spliceSettings?.trailingDelimiter, + customTrailingDelimiter: spliceSettings?.customTrailingDelimiter, + leadingDelimiter: spliceSettings?.leadingDelimiter, + customLeadingDelimiter: spliceSettings?.customLeadingDelimiter, + h1: spliceSettings?.h1, + h2: spliceSettings?.h2, + h3: spliceSettings?.h3, + h4: spliceSettings?.h4, + h5: spliceSettings?.h5, + h6: spliceSettings?.h6, + }); } else { counter = new OrderedCounter({ maxRecLevel, diff --git a/components/setting-tab.ts b/components/setting-tab.ts index 26ae449..82697ee 100644 --- a/components/setting-tab.ts +++ b/components/setting-tab.ts @@ -4,9 +4,15 @@ import type { OrderedCounterStyleType, HeadingDecoratorSettings, IndependentDecoratorSettings, + SpliceDecoratorSettings, IndependentSettings, + SpliceSettings, +} from "../common/data"; +import { + className, + defaultIndependentSettings, + defaultSpliceSettings, } from "../common/data"; -import { className, defaultIndependentSettings } from "../common/data"; import { getStyleTypeOptions } from "../common/options"; import { FolderSuggest } from "./suggest"; @@ -435,7 +441,7 @@ export class HeadingSettingTab extends PluginSettingTab { } private isDecoratorModeValue(value: string): value is DecoratorMode { - return ["orderd", "independent", "unordered"].includes(value); + return ["orderd", "independent", "splice", "unordered"].includes(value); } private isPositionValue(value: string): value is PostionOptions { @@ -513,6 +519,7 @@ export class HeadingSettingTab extends PluginSettingTab { const options: Record = { orderd: i18n.t("setting.ordered"), independent: i18n.t("setting.independent"), + splice: i18n.t("setting.splice"), unordered: i18n.t("setting.unordered"), }; dropdown @@ -530,18 +537,28 @@ export class HeadingSettingTab extends PluginSettingTab { orderedContainerEl.show(); logicContainerEl.show(); independentContainerEl.hide(); + spliceContainerEl.hide(); unorderedContainerEl.hide(); break; case "independent": independentContainerEl.show(); logicContainerEl.show(); orderedContainerEl.hide(); + spliceContainerEl.hide(); + unorderedContainerEl.hide(); + break; + case "splice": + spliceContainerEl.show(); + logicContainerEl.show(); + orderedContainerEl.hide(); + independentContainerEl.hide(); unorderedContainerEl.hide(); break; case "unordered": unorderedContainerEl.show(); orderedContainerEl.hide(); independentContainerEl.hide(); + spliceContainerEl.hide(); logicContainerEl.hide(); break; } @@ -638,6 +655,18 @@ export class HeadingSettingTab extends PluginSettingTab { independentContainerEl.hide(); } + const spliceContainerEl = containerEl.createDiv(className.settingContainer); + if (!settings[settingsType].spliceSettings) { + settings[settingsType].spliceSettings = defaultSpliceSettings(); + } + this.spliceSettings( + spliceContainerEl, + settings[settingsType].spliceSettings + ); + if (settings[settingsType].decoratorMode !== "splice") { + spliceContainerEl.hide(); + } + const unorderedContainerEl = containerEl.createDiv( className.settingContainer ); @@ -966,6 +995,121 @@ export class HeadingSettingTab extends PluginSettingTab { ); } + private spliceSettings(containerEl: HTMLElement, settings: SpliceSettings) { + const { + plugin: { i18n }, + } = this; + + new Setting(containerEl).setName(i18n.t("setting.splice")).setHeading(); + + //* delimiter + new Setting(containerEl) + .setName(i18n.t("setting.orderedDelimiter")) + .setDesc(i18n.t("setting.orderedDelimiterDesc")) + .addText((text) => + text.setValue(settings.delimiter).onChange((value) => { + settings.delimiter = value; + this.plugin.saveSettings(); + }) + ); + + //* trailingDelimiter + new Setting(containerEl) + .setName(i18n.t("setting.orderedTrailingDelimiter")) + .setDesc(i18n.t("setting.orderedTrailingDelimiterDesc")) + .addToggle((toggle) => + toggle.setValue(settings.trailingDelimiter).onChange((value) => { + settings.trailingDelimiter = value; + value + ? customTrailingDelimiterContainerEl.show() + : customTrailingDelimiterContainerEl.hide(); + this.plugin.saveSettings(); + }) + ); + + const customTrailingDelimiterContainerEl = containerEl.createDiv( + className.settingItem + ); + + //* customTrailingDelimiter + new Setting(customTrailingDelimiterContainerEl) + .setName(i18n.t("setting.orderedCustomTrailingDelimiter")) + .setDesc(i18n.t("setting.orderedCustomTrailingDelimiterDesc")) + .addText((text) => { + text + .setValue(settings.customTrailingDelimiter || "") + .onChange((value) => { + settings.customTrailingDelimiter = value; + this.plugin.saveSettings(); + }); + }); + + if (!settings.trailingDelimiter) { + customTrailingDelimiterContainerEl.hide(); + } + + //* leadingDelimiter + new Setting(containerEl) + .setName(i18n.t("setting.orderedLeadingDelimiter")) + .setDesc(i18n.t("setting.orderedLeadingDelimiterDesc")) + .addToggle((toggle) => + toggle + .setValue(settings.leadingDelimiter || false) + .onChange((value) => { + settings.leadingDelimiter = value; + value + ? customLeadingDelimiterContainerEl.show() + : customLeadingDelimiterContainerEl.hide(); + this.plugin.saveSettings(); + }) + ); + + const customLeadingDelimiterContainerEl = containerEl.createDiv( + className.settingItem + ); + + //* customLeadingDelimiter + new Setting(customLeadingDelimiterContainerEl) + .setName(i18n.t("setting.orderedCustomLeadingDelimiter")) + .setDesc(i18n.t("setting.orderedCustomLeadingDelimiterDesc")) + .addText((text) => { + text + .setValue(settings.customLeadingDelimiter || "") + .onChange((value) => { + settings.customLeadingDelimiter = value; + this.plugin.saveSettings(); + }); + }); + + if (!settings.leadingDelimiter) { + customLeadingDelimiterContainerEl.hide(); + } + + //* h1 + new Setting(containerEl).setName(i18n.t("setting.h1")).setHeading(); + this.spliceDecoratorSettings(containerEl, settings.h1); + + //* h2 + new Setting(containerEl).setName(i18n.t("setting.h2")).setHeading(); + this.spliceDecoratorSettings(containerEl, settings.h2); + + //* h3 + new Setting(containerEl).setName(i18n.t("setting.h3")).setHeading(); + this.spliceDecoratorSettings(containerEl, settings.h3); + + //* h4 + new Setting(containerEl).setName(i18n.t("setting.h4")).setHeading(); + this.spliceDecoratorSettings(containerEl, settings.h4); + + //* h5 + new Setting(containerEl).setName(i18n.t("setting.h5")).setHeading(); + this.spliceDecoratorSettings(containerEl, settings.h5); + + //* h6 + new Setting(containerEl).setName(i18n.t("setting.h6")).setHeading(); + this.spliceDecoratorSettings(containerEl, settings.h6); + } + private manageFolderBlacklist(scrollToTop = false) { const { containerEl, @@ -1254,4 +1398,79 @@ export class HeadingSettingTab extends PluginSettingTab { specifiedStringContainerEl.hide(); } } + + private spliceDecoratorSettings( + containerEl: HTMLElement, + settings: SpliceDecoratorSettings + ) { + const { + plugin: { i18n }, + } = this; + + //* styleType + new Setting(containerEl) + .setName(i18n.t("setting.orderedStyleType")) + .setDesc(i18n.t("setting.orderedStyleTypeDesc")) + .addDropdown((dropdown) => + dropdown + .addOptions(this.styleTypeOptions) + .setValue(settings.styleType) + .onChange((value: OrderedCounterStyleType) => { + settings.styleType = value; + switch (value) { + case "customIdent": + customIdentsContainerEl.show(); + specifiedStringContainerEl.hide(); + break; + case "string": + specifiedStringContainerEl.show(); + customIdentsContainerEl.hide(); + break; + default: + customIdentsContainerEl.hide(); + specifiedStringContainerEl.hide(); + break; + } + this.plugin.saveSettings(); + }) + ); + + const customIdentsContainerEl = containerEl.createDiv( + className.settingItem + ); + + //* customIdents + new Setting(customIdentsContainerEl) + .setName(i18n.t("setting.orderedCustomIdents")) + .setDesc(i18n.t("setting.orderedCustomIdentsDesc")) + .addText((text) => + text.setValue(settings.customIdents).onChange((value) => { + settings.customIdents = value; + this.plugin.saveSettings(); + }) + ); + + if (settings.styleType !== "customIdent") { + customIdentsContainerEl.hide(); + } + + const specifiedStringContainerEl = containerEl.createDiv( + className.settingItem + ); + + //* specifiedString + new Setting(specifiedStringContainerEl) + .setName(i18n.t("setting.orderedSpecifiedString")) + .setDesc(i18n.t("setting.orderedSpecifiedStringDesc")) + .addText((text) => + text.setValue(settings.specifiedString).onChange((value) => { + settings.specifiedString = value; + this.plugin.saveSettings(); + }) + ); + + if (settings.styleType !== "string") { + specifiedStringContainerEl.hide(); + } + } } diff --git a/locales/en.json b/locales/en.json index 3acfaf3..fe0e7ac 100644 --- a/locales/en.json +++ b/locales/en.json @@ -160,6 +160,7 @@ "h3": "H3", "h4": "H4", "h5": "H5", - "h6": "H6" + "h6": "H6", + "splice": "Splice" } } diff --git a/locales/zh-TW.json b/locales/zh-TW.json index c37acc8..bd99a21 100644 --- a/locales/zh-TW.json +++ b/locales/zh-TW.json @@ -160,6 +160,7 @@ "h3": "H3 級別", "h4": "H4 級別", "h5": "H5 級別", - "h6": "H6 級別" + "h6": "H6 級別", + "splice": "分層拼接" } } diff --git a/locales/zh.json b/locales/zh.json index ee3b06e..8884618 100644 --- a/locales/zh.json +++ b/locales/zh.json @@ -160,6 +160,7 @@ "h3": "H3 级别", "h4": "H4 级别", "h5": "H5 级别", - "h6": "H6 级别" + "h6": "H6 级别", + "splice": "分层拼接" } } diff --git a/test/common/counter.spec.ts b/test/common/counter.spec.ts index 90c409a..0a39c1a 100644 --- a/test/common/counter.spec.ts +++ b/test/common/counter.spec.ts @@ -1,11 +1,15 @@ import "mocha"; import { expect } from "chai"; -import type { IndependentDecoratorSettings } from "../../common/data"; +import type { + IndependentDecoratorSettings, + SpliceDecoratorSettings, +} from "../../common/data"; import { Querier, UnorderedCounter, OrderedCounter, IndependentCounter, + SpliceCounter, } from "../../common/counter"; describe("common/counter", function () { @@ -1446,4 +1450,396 @@ describe("common/counter", function () { expect(counter3.decorator(6)).to.equal("2.1.1.1."); }); }); + + describe("SpliceCounter", function () { + const h1: Partial = { + styleType: "upperAlpha", + }; + const h2: Partial = { + styleType: "lowerAlpha", + }; + const h3: Partial = { + styleType: "upperRoman", + }; + const h4: Partial = { + styleType: "lowerRoman", + }; + const h5: Partial = { + styleType: "decimal", + }; + const h6: Partial = { + styleType: "decimalLeadingZero", + }; + + it("SpliceCounter.decorator", function () { + const counter1 = new SpliceCounter({}); + expect(counter1.decorator(-1)).to.be.empty; + expect(counter1.decorator(0)).to.be.empty; + expect(counter1.decorator(1)).to.equal("1"); + expect(counter1.decorator(2)).to.equal("1.1"); + expect(counter1.decorator(2)).to.equal("1.2"); + expect(counter1.decorator(2)).to.equal("1.3"); + expect(counter1.decorator(2)).to.equal("1.4"); + expect(counter1.decorator(2)).to.equal("1.5"); + expect(counter1.decorator(2)).to.equal("1.6"); + expect(counter1.decorator(2)).to.equal("1.7"); + expect(counter1.decorator(2)).to.equal("1.8"); + expect(counter1.decorator(2)).to.equal("1.9"); + expect(counter1.decorator(2)).to.equal("1.10"); + expect(counter1.decorator(2)).to.equal("1.11"); + expect(counter1.decorator(1)).to.equal("2"); + expect(counter1.decorator(7)).to.be.empty; + + const counter2 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + }); + expect(counter2.decorator(-1)).to.be.empty; + expect(counter2.decorator(0)).to.be.empty; + expect(counter2.decorator(1)).to.equal("A"); + expect(counter2.decorator(2)).to.equal("A.a"); + expect(counter2.decorator(2)).to.equal("A.b"); + expect(counter2.decorator(2)).to.equal("A.c"); + expect(counter2.decorator(2)).to.equal("A.d"); + expect(counter2.decorator(2)).to.equal("A.e"); + expect(counter2.decorator(3)).to.equal("A.e.I"); + expect(counter2.decorator(4)).to.equal("A.e.I.i"); + expect(counter2.decorator(5)).to.equal("A.e.I.i.1"); + expect(counter2.decorator(6)).to.equal("A.e.I.i.1.01"); + expect(counter2.decorator(1)).to.equal("B"); + expect(counter2.decorator(2)).to.equal("B.a"); + expect(counter2.decorator(3)).to.equal("B.a.I"); + expect(counter2.decorator(2)).to.equal("B.b"); + }); + + it("SpliceCounter.decorator with custom delimiter", function () { + const counter = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + delimiter: ",", + }); + + expect(counter.decorator(-1)).to.be.empty; + expect(counter.decorator(0)).to.be.empty; + expect(counter.decorator(1)).to.equal("A"); + expect(counter.decorator(2)).to.equal("A,a"); + expect(counter.decorator(2)).to.equal("A,b"); + expect(counter.decorator(2)).to.equal("A,c"); + expect(counter.decorator(2)).to.equal("A,d"); + expect(counter.decorator(2)).to.equal("A,e"); + expect(counter.decorator(3)).to.equal("A,e,I"); + expect(counter.decorator(4)).to.equal("A,e,I,i"); + expect(counter.decorator(5)).to.equal("A,e,I,i,1"); + expect(counter.decorator(6)).to.equal("A,e,I,i,1,01"); + expect(counter.decorator(1)).to.equal("B"); + expect(counter.decorator(2)).to.equal("B,a"); + expect(counter.decorator(3)).to.equal("B,a,I"); + expect(counter.decorator(2)).to.equal("B,b"); + }); + + it("SpliceCounter.decorator with trailing delimiter", function () { + const counter1 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + delimiter: ".", + trailingDelimiter: true, + }); + expect(counter1.decorator(-1)).to.be.empty; + expect(counter1.decorator(0)).to.be.empty; + expect(counter1.decorator(1)).to.equal("A."); + expect(counter1.decorator(2)).to.equal("A.a."); + expect(counter1.decorator(2)).to.equal("A.b."); + expect(counter1.decorator(2)).to.equal("A.c."); + expect(counter1.decorator(2)).to.equal("A.d."); + expect(counter1.decorator(2)).to.equal("A.e."); + expect(counter1.decorator(3)).to.equal("A.e.I."); + expect(counter1.decorator(4)).to.equal("A.e.I.i."); + expect(counter1.decorator(5)).to.equal("A.e.I.i.1."); + expect(counter1.decorator(6)).to.equal("A.e.I.i.1.01."); + expect(counter1.decorator(1)).to.equal("B."); + expect(counter1.decorator(2)).to.equal("B.a."); + expect(counter1.decorator(3)).to.equal("B.a.I."); + expect(counter1.decorator(2)).to.equal("B.b."); + + const counter2 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + delimiter: ".", + trailingDelimiter: true, + customTrailingDelimiter: ",", + }); + expect(counter2.decorator(-1)).to.be.empty; + expect(counter2.decorator(0)).to.be.empty; + expect(counter2.decorator(1)).to.equal("A,"); + expect(counter2.decorator(2)).to.equal("A.a,"); + expect(counter2.decorator(2)).to.equal("A.b,"); + expect(counter2.decorator(2)).to.equal("A.c,"); + expect(counter2.decorator(2)).to.equal("A.d,"); + expect(counter2.decorator(2)).to.equal("A.e,"); + expect(counter2.decorator(3)).to.equal("A.e.I,"); + expect(counter2.decorator(4)).to.equal("A.e.I.i,"); + expect(counter2.decorator(5)).to.equal("A.e.I.i.1,"); + expect(counter2.decorator(6)).to.equal("A.e.I.i.1.01,"); + expect(counter2.decorator(1)).to.equal("B,"); + expect(counter2.decorator(2)).to.equal("B.a,"); + expect(counter2.decorator(3)).to.equal("B.a.I,"); + expect(counter2.decorator(2)).to.equal("B.b,"); + }); + + it("SpliceCounter.decorator with leading delimiter", function () { + const counter1 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + delimiter: ".", + leadingDelimiter: true, + }); + expect(counter1.decorator(-1)).to.be.empty; + expect(counter1.decorator(0)).to.be.empty; + expect(counter1.decorator(1)).to.equal(".A"); + expect(counter1.decorator(2)).to.equal(".A.a"); + expect(counter1.decorator(2)).to.equal(".A.b"); + expect(counter1.decorator(2)).to.equal(".A.c"); + expect(counter1.decorator(2)).to.equal(".A.d"); + expect(counter1.decorator(2)).to.equal(".A.e"); + expect(counter1.decorator(3)).to.equal(".A.e.I"); + expect(counter1.decorator(4)).to.equal(".A.e.I.i"); + expect(counter1.decorator(5)).to.equal(".A.e.I.i.1"); + expect(counter1.decorator(6)).to.equal(".A.e.I.i.1.01"); + expect(counter1.decorator(1)).to.equal(".B"); + expect(counter1.decorator(2)).to.equal(".B.a"); + expect(counter1.decorator(3)).to.equal(".B.a.I"); + expect(counter1.decorator(2)).to.equal(".B.b"); + + const counter2 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + delimiter: ".", + leadingDelimiter: true, + customLeadingDelimiter: "> ", + }); + expect(counter2.decorator(-1)).to.be.empty; + expect(counter2.decorator(0)).to.be.empty; + expect(counter2.decorator(1)).to.equal("> A"); + expect(counter2.decorator(2)).to.equal("> A.a"); + expect(counter2.decorator(2)).to.equal("> A.b"); + expect(counter2.decorator(2)).to.equal("> A.c"); + expect(counter2.decorator(2)).to.equal("> A.d"); + expect(counter2.decorator(2)).to.equal("> A.e"); + expect(counter2.decorator(3)).to.equal("> A.e.I"); + expect(counter2.decorator(4)).to.equal("> A.e.I.i"); + expect(counter2.decorator(5)).to.equal("> A.e.I.i.1"); + expect(counter2.decorator(6)).to.equal("> A.e.I.i.1.01"); + expect(counter2.decorator(1)).to.equal("> B"); + expect(counter2.decorator(2)).to.equal("> B.a"); + expect(counter2.decorator(3)).to.equal("> B.a.I"); + expect(counter2.decorator(2)).to.equal("> B.b"); + }); + + it("SpliceCounter.decorator with custom leading delimiter and trailing delimiter", function () { + const counter1 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + delimiter: ".", + trailingDelimiter: true, + leadingDelimiter: true, + }); + expect(counter1.decorator(-1)).to.be.empty; + expect(counter1.decorator(0)).to.be.empty; + expect(counter1.decorator(1)).to.equal(".A."); + expect(counter1.decorator(2)).to.equal(".A.a."); + expect(counter1.decorator(2)).to.equal(".A.b."); + expect(counter1.decorator(2)).to.equal(".A.c."); + expect(counter1.decorator(2)).to.equal(".A.d."); + expect(counter1.decorator(2)).to.equal(".A.e."); + expect(counter1.decorator(3)).to.equal(".A.e.I."); + expect(counter1.decorator(4)).to.equal(".A.e.I.i."); + expect(counter1.decorator(5)).to.equal(".A.e.I.i.1."); + expect(counter1.decorator(6)).to.equal(".A.e.I.i.1.01."); + expect(counter1.decorator(1)).to.equal(".B."); + expect(counter1.decorator(2)).to.equal(".B.a."); + expect(counter1.decorator(3)).to.equal(".B.a.I."); + expect(counter1.decorator(2)).to.equal(".B.b."); + + const counter2 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + delimiter: ".", + trailingDelimiter: true, + customTrailingDelimiter: ")", + leadingDelimiter: true, + customLeadingDelimiter: "(", + }); + expect(counter2.decorator(-1)).to.be.empty; + expect(counter2.decorator(0)).to.be.empty; + expect(counter2.decorator(1)).to.equal("(A)"); + expect(counter2.decorator(2)).to.equal("(A.a)"); + expect(counter2.decorator(2)).to.equal("(A.b)"); + expect(counter2.decorator(2)).to.equal("(A.c)"); + expect(counter2.decorator(2)).to.equal("(A.d)"); + expect(counter2.decorator(2)).to.equal("(A.e)"); + expect(counter2.decorator(3)).to.equal("(A.e.I)"); + expect(counter2.decorator(4)).to.equal("(A.e.I.i)"); + expect(counter2.decorator(5)).to.equal("(A.e.I.i.1)"); + expect(counter2.decorator(6)).to.equal("(A.e.I.i.1.01)"); + expect(counter2.decorator(1)).to.equal("(B)"); + expect(counter2.decorator(2)).to.equal("(B.a)"); + expect(counter2.decorator(3)).to.equal("(B.a.I)"); + expect(counter2.decorator(2)).to.equal("(B.b)"); + }); + + it("SpliceCounter.decorator with ignoreTopLevel", function () { + const counter1 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + ignoreTopLevel: 1, + }); + + expect(counter1.decorator(1)).to.be.empty; + expect(counter1.decorator(2)).to.equal("a"); + expect(counter1.decorator(3)).to.equal("a.I"); + expect(counter1.decorator(4)).to.equal("a.I.i"); + expect(counter1.decorator(5)).to.equal("a.I.i.1"); + expect(counter1.decorator(6)).to.equal("a.I.i.1.01"); + expect(counter1.decorator(7)).to.be.empty; + expect(counter1.decorator(2)).to.equal("b"); + expect(counter1.decorator(3)).to.equal("b.I"); + expect(counter1.decorator(4)).to.equal("b.I.i"); + expect(counter1.decorator(5)).to.equal("b.I.i.1"); + expect(counter1.decorator(6)).to.equal("b.I.i.1.01"); + expect(counter1.decorator(7)).to.be.empty; + expect(counter1.decorator(2)).to.equal("c"); + expect(counter1.decorator(6)).to.equal("c.I.i.1.01"); + + const counter2 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + ignoreTopLevel: 2, + }); + + expect(counter2.decorator(1)).to.be.empty; + expect(counter2.decorator(2)).to.be.empty; + expect(counter2.decorator(3)).to.equal("I"); + expect(counter2.decorator(4)).to.equal("I.i"); + expect(counter2.decorator(5)).to.equal("I.i.1"); + expect(counter2.decorator(6)).to.equal("I.i.1.01"); + expect(counter2.decorator(7)).to.be.empty; + expect(counter2.decorator(3)).to.equal("II"); + expect(counter2.decorator(6)).to.equal("II.i.1.01"); + }); + + it("SpliceCounter.decorator with allowZeroLevel", function () { + const counter = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + allowZeroLevel: true, + }); + + expect(counter.decorator(3)).to.equal("0.0.I"); + expect(counter.decorator(4)).to.equal("0.0.I.i"); + expect(counter.decorator(5)).to.equal("0.0.I.i.1"); + expect(counter.decorator(6)).to.equal("0.0.I.i.1.01"); + + expect(counter.decorator(3)).to.equal("0.0.II"); + expect(counter.decorator(4)).to.equal("0.0.II.i"); + expect(counter.decorator(5)).to.equal("0.0.II.i.1"); + expect(counter.decorator(6)).to.equal("0.0.II.i.1.01"); + + expect(counter.decorator(2)).to.equal("0.a"); + expect(counter.decorator(5)).to.equal("0.a.0.0.1"); + expect(counter.decorator(6)).to.equal("0.a.0.0.1.01"); + }); + + it("SpliceCounter.decorator with maxRecLevel", function () { + const counter1 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + maxRecLevel: 4, + }); + + expect(counter1.decorator(1)).to.equal("A"); + expect(counter1.decorator(2)).to.equal("A.a"); + expect(counter1.decorator(3)).to.equal("A.a.I"); + expect(counter1.decorator(4)).to.equal("A.a.I.i"); + expect(counter1.decorator(5)).to.be.empty; + expect(counter1.decorator(6)).to.be.empty; + expect(counter1.decorator(7)).to.be.empty; + expect(counter1.decorator(2)).to.equal("A.b"); + expect(counter1.decorator(3)).to.equal("A.b.I"); + expect(counter1.decorator(4)).to.equal("A.b.I.i"); + expect(counter1.decorator(5)).to.be.empty; + expect(counter1.decorator(6)).to.be.empty; + expect(counter1.decorator(2)).to.equal("A.c"); + expect(counter1.decorator(6)).to.be.empty; + + const counter2 = new SpliceCounter({ + h1, + h2, + h3, + h4, + h5, + h6, + maxRecLevel: 4, + ignoreTopLevel: 2, + }); + + expect(counter2.decorator(1)).to.be.empty; + expect(counter2.decorator(2)).to.be.empty; + expect(counter2.decorator(3)).to.equal("I"); + expect(counter2.decorator(4)).to.equal("I.i"); + expect(counter2.decorator(5)).to.be.empty; + expect(counter2.decorator(6)).to.be.empty; + expect(counter2.decorator(7)).to.be.empty; + expect(counter2.decorator(3)).to.equal("II"); + expect(counter2.decorator(4)).to.equal("II.i"); + expect(counter2.decorator(6)).to.be.empty; + }); + }); }); diff --git a/types.d.ts b/types.d.ts index 2b057ca..ec08f7a 100644 --- a/types.d.ts +++ b/types.d.ts @@ -10,7 +10,7 @@ type _TupleOf = R["length"] extends N type LevelTuple = TupleOf; type HeadingTuple = TupleOf; -type DecoratorMode = "orderd" | "independent" | "unordered"; +type DecoratorMode = "orderd" | "independent" | "splice" | "unordered"; type OpacityOptions = 10 | 20 | 30 | 40 | 50 | 60 | 70 | 80 | 90 | 100; type PostionOptions = "before" | "before-inside" | "after" | "after-inside"; type RenderPolicy = "partial" | "full";