From 16c84ea89850e3000fd6bb46c42e48c26346602d Mon Sep 17 00:00:00 2001 From: kotaindah55 Date: Tue, 4 Mar 2025 17:03:18 +0200 Subject: [PATCH] refactor: reconfigure some types for tokens settings --- src/types/index.ts | 237 +++++++++++++++++++++++++--- src/utils/configureDelimLookup.ts | 45 +++--- src/utils/decToHex.ts | 3 + src/utils/deepCopy.ts | 3 + src/utils/getTheme.ts | 5 + src/utils/index.ts | 9 +- src/utils/isInlineFormat.ts | 6 + src/utils/moveElement.ts | 11 ++ src/utils/reconfigureDelimLookup.ts | 14 ++ src/utils/trimTag.ts | 5 + 10 files changed, 293 insertions(+), 45 deletions(-) create mode 100644 src/utils/decToHex.ts create mode 100644 src/utils/deepCopy.ts create mode 100644 src/utils/getTheme.ts create mode 100644 src/utils/isInlineFormat.ts create mode 100644 src/utils/moveElement.ts create mode 100644 src/utils/reconfigureDelimLookup.ts create mode 100644 src/utils/trimTag.ts diff --git a/src/types/index.ts b/src/types/index.ts index 07c3a2e..5307b50 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -1,40 +1,56 @@ import type { SyntaxNode, Tree } from "@lezer/common"; -import type { Format, SettingOpt1, TokenRole, TokenStatus } from "src/enums"; -import { Text } from "@codemirror/state" +import type { Format, MarkdownViewMode, TokenLevel, Delimiter, TokenStatus, Field, DisplayBehaviour } from "src/enums"; +import { Line, RangeSet, RangeValue, Text, Range } from "@codemirror/state" +import { Decoration, DecorationSet } from "@codemirror/view"; +import { ColorComponent, DropdownComponent, ExtraButtonComponent, IconName, SliderComponent, TextAreaComponent, TextComponent, ToggleComponent } from "obsidian"; +import ExtendedMarkdownSyntax from "main"; /** Token interface */ export type Token = { /** Format type */ type: Format, // 3-bit + /** Either block or inline */ + level: TokenLevel, // 1 bit /** Token status */ status: TokenStatus, // 2-bit /** Start offset */ from: number, // 16-bit /** End offset */ to: number, // 16-bit - /** Token role */ - role: TokenRole, // 3-bit + /** Length of its opening delimiter. */ + openLen: number, + /** Length of its closing delimiter if any, otherwise it should be zero. */ + closeLen: number, + /** Length of its tag if any, otherwise it should be zero. */ + tagLen: number, + /** Whether the range of tag is covered by the range of content or not. */ + tagAsContent: boolean; + /** The tag was considered as invalid when it does not satisfy its rule. */ + validTag: boolean; /** - * Pointer to a specific token, which is an index refering - * to that pointed token in its token group. If the token was - * inside a token sequence, it will refer to its opening delimiter. - * Otherwise, it will point to itself. + * Closed by blank line instead of closing delimiter. If `closeLen` is 0, the + * token's type isn't space-restricted format, and `closedByBlankLine` is `false`, + * then indicates that the token definitely was located in the end of the document + * (`Token.to == Doc.length`). */ - pointer: number, // 16-bit - size: number // 8-bit + closedByBlankLine: boolean; } -/** Accumulation of `ChangeSet` generated by the `composeChange` function */ +/** Accumulation of `ChangeSet` generated by the `composeChange` function. */ export type ChangedRange = { /** * Start offset of a changed range, measured from - * the text both before and after the change + * the text both before and after the change. */ from: number, - /** End offset of a changed range, measured from the doc before the change */ + /** End offset of a changed range, measured from the doc before the change. */ initTo: number, - /** End offset of a changed range, measured from the doc after the change */ + /** End offset of a changed range, measured from the doc after the change. */ changedTo: number; + /** + * Length difference. Can be negative value, indicating that replaced text + * is longer than the substitute. + */ length: number; } @@ -50,8 +66,13 @@ export type DelimSpec = { * act as minimum length. */ exactLen: boolean, - /** Should be `TokenRole.OPEN` or `TokenRole.CLOSE` */ - role: TokenRole + /** Should be `Delimiter.OPEN` or `Delimiter.CLOSE` */ + role: Delimiter, + /** + * When true, any space after the opening delimiter or before the closing + * one doesn't make the delimiter invalid. Default is false. + */ + allowSpaceOnDelim?: boolean } /** @@ -66,28 +87,192 @@ export type StateConfig = { maxLine?: number } +/** + * Region defined here as a `PlainRange` collection arranged in an array. + * Each range inside should be sorted in order according to its `from`. + */ +export type Region = PlainRange[]; + export type NodeSpec = { node: SyntaxNode, type: string } -export type AlignFormat = Extract; +export type InlineFormat = Extract; -export type MainFormat = Extract; +export type BlockFormat = Exclude; + +export type NonBuiltinInlineFormat = Exclude; export type TokenGroup = Token[]; -export type MainFormat2 = Exclude; +export type ColorConfig = { + tag: string, + name: string, + color: string, + showInMenu: boolean +}; export type PluginSettings = { - insertion: SettingOpt1; - spoiler: SettingOpt1; - superscript: SettingOpt1; - subscript: SettingOpt1; - customHighlight: SettingOpt1; - customAlign: SettingOpt1; + insertion: MarkdownViewMode; + spoiler: MarkdownViewMode; + superscript: MarkdownViewMode; + subscript: MarkdownViewMode; + customHighlight: MarkdownViewMode; + customSpan: MarkdownViewMode; + fencedDiv: MarkdownViewMode; editorEscape: boolean; colorButton: boolean; + decoratePDF: boolean; + colorConfigs: ColorConfig[]; + lightModeHlOpacity: number; + darkModeHlOpacity: number; + hlTagDisplayBehaviour: DisplayBehaviour; + spanTagDisplayBehaviour: DisplayBehaviour; + showHlTagInPreviewMode: boolean; + showSpanTagInPreviewMode: boolean; + alwaysShowFencedDivTag: MarkdownViewMode; } -export type CharPos = { from: number, to: number }; \ No newline at end of file +export type PlainRange = { from: number, to: number }; + +export type InlineFormatRule = { + char: string, + length: number, + exactLen: boolean, + allowSpace: boolean, + mustBeClosed: boolean, + class: string, + getEl: (cls?: string) => Element, + builtin: boolean +} + +export type BlockFormatRule = { + char: string, + length: number, + exactLen: boolean, + class: string, +} + +export type FormatRule = InlineFormatRule & BlockFormatRule; + +export interface TokenDecoration extends Decoration { + spec: { + class: string, + token: Token + }; +} + +export type TokenDecorationSet = RangeSet; + +export type IndexCache = { number: number } + +export type DecoSetCollection = Record<"inlineSet" | "blockSet" | "omittedSet" | "colorBtnSet" | "revealedSpoilerSet", DecorationSet>; + +export type IterLineSpec = { + doc: Text, + fromLn: number, + toLn?: number, + callback: (line: Line, doc: Text) => boolean | void +} + +export type IterTokenGroupSpec = { + tokens: TokenGroup, + ranges: PlainRange[] | readonly PlainRange[], + indexCache: IndexCache + callback: (token: Token) => unknown, +} + +export type RangeSetUpdate = { + add?: readonly Range[]; + sort?: boolean; + filter?: (from: number, to: number, value: T) => boolean; + filterFrom?: number; + filterTo?: number; +} + +export type OptionType = number; + +export type Options = T extends OptionType ? Record : never; + +export type FieldComponentRecord = { + [Field.TOGGLE]: ToggleComponent, + [Field.MULTI_TOGGLE]: ExtraButtonComponent, + [Field.DROPDOWN]: DropdownComponent, + [Field.COLOR]: ColorComponent, + [Field.TEXT]: TextComponent, + [Field.TEXT_AREA]: TextAreaComponent, + [Field.SLIDER]: SliderComponent +}; + +export type FieldValueRecord = { + [Field.TOGGLE]: boolean, + [Field.MULTI_TOGGLE]: OptionType, + [Field.DROPDOWN]: OptionType, + [Field.COLOR]: string, + [Field.TEXT]: string, + [Field.TEXT_AREA]: string, + [Field.SLIDER]: number +}; + +export type FieldSpecRecord = { + [Field.TOGGLE]: null, + [Field.MULTI_TOGGLE]: { options: Options }, + [Field.DROPDOWN]: { options: Options }, + [Field.COLOR]: null, + [Field.TEXT]: { placeholder?: string }, + [Field.TEXT_AREA]: { placeholder?: string, resizeable?: boolean } + [Field.SLIDER]: { min: number, max: number, step: number } +}; + +export type FieldValue = FieldValueRecord[TField]; + +export type FieldComponent = FieldComponentRecord[TField]; + +export type FieldSpec = FieldSpecRecord[TField]; + +export type AbstractFieldDesc = { + type: TField, + record: TRecord extends Record ? TRecord : never, + key: TKey extends keyof TRecord ? TRecord[TKey] extends infer TValue ? TValue extends FieldValue ? TKey : never : never : never, + callback?: (component: FieldComponent, plugin: ExtendedMarkdownSyntax) => unknown, + spec: FieldSpec, + update?: { + stylesheet?: boolean, + internal?: boolean + } +}; + +export type ToggleFieldDesc = AbstractFieldDesc; +export type MultiToggleFieldDesc = AbstractFieldDesc; +export type DropdownFieldDesc = AbstractFieldDesc; +export type ColorFieldDesc = AbstractFieldDesc; +export type TextFieldDesc = AbstractFieldDesc; +export type TextAreaFieldDesc = AbstractFieldDesc; +export type SliderFieldDesc = AbstractFieldDesc; + +export type FieldGroup = ( + ToggleFieldDesc | + MultiToggleFieldDesc | + DropdownFieldDesc | + ColorFieldDesc | + TextFieldDesc | + TextAreaFieldDesc | + SliderFieldDesc +)[]; + +export type SettingItem = { + name: string, + desc?: string, + fields?: FieldGroup, + preservedForColorSettings?: boolean +}; + +export type SettingGroup = { + heading?: string, + desc?: string, + collapsible?: boolean, + items: SettingItem[] +}; + +export type SettingRoot = SettingGroup[]; \ No newline at end of file diff --git a/src/utils/configureDelimLookup.ts b/src/utils/configureDelimLookup.ts index 0c0ef7f..062fc14 100644 --- a/src/utils/configureDelimLookup.ts +++ b/src/utils/configureDelimLookup.ts @@ -1,34 +1,43 @@ -import { Format, SettingOpt1 } from "src/enums"; -import { FormatRules } from "src/shared-configs"; +import { Format, MarkdownViewMode } from "src/enums"; +import { InlineRules } from "src/shared-configs"; import { PluginSettings } from "src/types"; import { EditorDelimLookup } from "src/editor-mode/parser/configs"; import { PreviewDelimLookup } from "src/preview-mode/configs"; export function configureDelimLookup(settings: PluginSettings) { // editor - if (settings.insertion & SettingOpt1.EDITOR_MODE) { - EditorDelimLookup[FormatRules[Format.INSERTION].char] = Format.INSERTION; + if (settings.insertion & MarkdownViewMode.EDITOR_MODE) { + EditorDelimLookup[InlineRules[Format.INSERTION].char] = Format.INSERTION; } - if (settings.spoiler & SettingOpt1.EDITOR_MODE) { - EditorDelimLookup[FormatRules[Format.SPOILER].char] = Format.SPOILER; + if (settings.spoiler & MarkdownViewMode.EDITOR_MODE) { + EditorDelimLookup[InlineRules[Format.SPOILER].char] = Format.SPOILER; } - if (settings.superscript & SettingOpt1.EDITOR_MODE) { - EditorDelimLookup[FormatRules[Format.SUPERSCRIPT].char] = Format.SUPERSCRIPT; + if (settings.superscript & MarkdownViewMode.EDITOR_MODE) { + EditorDelimLookup[InlineRules[Format.SUPERSCRIPT].char] = Format.SUPERSCRIPT; } - if (settings.subscript & SettingOpt1.EDITOR_MODE) { - EditorDelimLookup[FormatRules[Format.SUBSCRIPT].char] = Format.SUBSCRIPT; + if (settings.subscript & MarkdownViewMode.EDITOR_MODE) { + EditorDelimLookup[InlineRules[Format.SUBSCRIPT].char] = Format.SUBSCRIPT; + } + if (settings.customHighlight & MarkdownViewMode.EDITOR_MODE) { + EditorDelimLookup[InlineRules[Format.HIGHLIGHT].char] = Format.HIGHLIGHT; + } + if (settings.customSpan & MarkdownViewMode.EDITOR_MODE) { + EditorDelimLookup[InlineRules[Format.CUSTOM_SPAN].char] = Format.CUSTOM_SPAN; } // preview - if (settings.insertion & SettingOpt1.PREVIEW_MODE) { - PreviewDelimLookup[FormatRules[Format.INSERTION].char] = Format.INSERTION; + if (settings.insertion & MarkdownViewMode.PREVIEW_MODE) { + PreviewDelimLookup[InlineRules[Format.INSERTION].char] = Format.INSERTION; } - if (settings.spoiler & SettingOpt1.PREVIEW_MODE) { - PreviewDelimLookup[FormatRules[Format.SPOILER].char] = Format.SPOILER; + if (settings.spoiler & MarkdownViewMode.PREVIEW_MODE) { + PreviewDelimLookup[InlineRules[Format.SPOILER].char] = Format.SPOILER; } - if (settings.superscript & SettingOpt1.PREVIEW_MODE) { - PreviewDelimLookup[FormatRules[Format.SUPERSCRIPT].char] = Format.SUPERSCRIPT; + if (settings.superscript & MarkdownViewMode.PREVIEW_MODE) { + PreviewDelimLookup[InlineRules[Format.SUPERSCRIPT].char] = Format.SUPERSCRIPT; } - if (settings.subscript & SettingOpt1.PREVIEW_MODE) { - PreviewDelimLookup[FormatRules[Format.SUBSCRIPT].char] = Format.SUBSCRIPT; + if (settings.subscript & MarkdownViewMode.PREVIEW_MODE) { + PreviewDelimLookup[InlineRules[Format.SUBSCRIPT].char] = Format.SUBSCRIPT; + } + if (settings.customSpan & MarkdownViewMode.PREVIEW_MODE) { + PreviewDelimLookup[InlineRules[Format.CUSTOM_SPAN].char] = Format.CUSTOM_SPAN; } } \ No newline at end of file diff --git a/src/utils/decToHex.ts b/src/utils/decToHex.ts new file mode 100644 index 0000000..e102c04 --- /dev/null +++ b/src/utils/decToHex.ts @@ -0,0 +1,3 @@ +export function decToHex(value: number, minDigit = 2) { + return value.toString(16).padStart(minDigit, "0"); +} \ No newline at end of file diff --git a/src/utils/deepCopy.ts b/src/utils/deepCopy.ts new file mode 100644 index 0000000..a59cdc1 --- /dev/null +++ b/src/utils/deepCopy.ts @@ -0,0 +1,3 @@ +export function deepCopy(obj: T) { + return JSON.parse(JSON.stringify(obj)) as T; +} \ No newline at end of file diff --git a/src/utils/getTheme.ts b/src/utils/getTheme.ts new file mode 100644 index 0000000..98eafcb --- /dev/null +++ b/src/utils/getTheme.ts @@ -0,0 +1,5 @@ +export function getTheme() { + if (document.body.hasClass("theme-dark")) { return "dark" } + if (document.body.hasClass("theme-light")) { return "light" } + return null; +} \ No newline at end of file diff --git a/src/utils/index.ts b/src/utils/index.ts index 133c9b1..4910074 100644 --- a/src/utils/index.ts +++ b/src/utils/index.ts @@ -1 +1,8 @@ -export * from "./configureDelimLookup"; \ No newline at end of file +export * from "./configureDelimLookup"; +export * from "./isInlineFormat"; +export * from "./trimTag"; +export * from "./reconfigureDelimLookup"; +export * from "./deepCopy"; +export * from "./moveElement"; +export * from "./getTheme"; +export * from "./decToHex"; \ No newline at end of file diff --git a/src/utils/isInlineFormat.ts b/src/utils/isInlineFormat.ts new file mode 100644 index 0000000..e3a01e7 --- /dev/null +++ b/src/utils/isInlineFormat.ts @@ -0,0 +1,6 @@ +import { Format } from "src/enums"; +import { InlineFormat } from "src/types"; + +export function isInlineFormat(type: Format): type is InlineFormat { + return type >= Format.INSERTION && type <= Format.CUSTOM_SPAN +} \ No newline at end of file diff --git a/src/utils/moveElement.ts b/src/utils/moveElement.ts new file mode 100644 index 0000000..393f67c --- /dev/null +++ b/src/utils/moveElement.ts @@ -0,0 +1,11 @@ +export function moveElement(el: Element, direction: 1 | -1) { + let parentEl = el.parentElement; + if (!parentEl) { return false } + if (direction > 0) { + let nextEl = el.nextElementSibling; + parentEl.insertAfter(el, nextEl); + } else if (direction < 0) { + let prevEl = el.previousElementSibling; + parentEl.insertBefore(el, prevEl); + } +} \ No newline at end of file diff --git a/src/utils/reconfigureDelimLookup.ts b/src/utils/reconfigureDelimLookup.ts new file mode 100644 index 0000000..9367b19 --- /dev/null +++ b/src/utils/reconfigureDelimLookup.ts @@ -0,0 +1,14 @@ +import { EditorDelimLookup } from "src/editor-mode/parser/configs"; +import { PreviewDelimLookup } from "src/preview-mode/configs"; +import { configureDelimLookup } from "src/utils"; +import { PluginSettings } from "src/types"; + +export function reconfigureDelimLookup(settings: PluginSettings) { + for (let key in EditorDelimLookup) { + delete EditorDelimLookup[key]; + } + for (let key in PreviewDelimLookup) { + delete PreviewDelimLookup[key]; + } + configureDelimLookup(settings); +} \ No newline at end of file diff --git a/src/utils/trimTag.ts b/src/utils/trimTag.ts new file mode 100644 index 0000000..0325262 --- /dev/null +++ b/src/utils/trimTag.ts @@ -0,0 +1,5 @@ +export function trimTag(tagStr: string) { + return tagStr + .trim() + .replaceAll(/\s{2,}/g, " "); +} \ No newline at end of file