refactor: reconfigure some types for tokens settings

This commit is contained in:
kotaindah55 2025-03-04 17:03:18 +02:00
parent 784532b1d2
commit 16c84ea898
10 changed files with 293 additions and 45 deletions

View file

@ -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<Format, Format.ALIGN_LEFT | Format.ALIGN_RIGHT | Format.ALIGN_CENTER | Format.ALIGN_JUSTIFY>;
export type InlineFormat = Extract<Format, Format.INSERTION | Format.SPOILER | Format.SUPERSCRIPT | Format.SUBSCRIPT | Format.HIGHLIGHT | Format.CUSTOM_SPAN>;
export type MainFormat = Extract<Format, Format.INSERTION | Format.SPOILER | Format.SUPERSCRIPT | Format.SUBSCRIPT | Format.HIGHLIGHT>;
export type BlockFormat = Exclude<Format, InlineFormat>;
export type NonBuiltinInlineFormat = Exclude<InlineFormat, Format.HIGHLIGHT>;
export type TokenGroup = Token[];
export type MainFormat2 = Exclude<MainFormat, Format.HIGHLIGHT>;
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 };
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<TokenDecoration>;
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<T extends RangeValue> = {
add?: readonly Range<T>[];
sort?: boolean;
filter?: (from: number, to: number, value: T) => boolean;
filterFrom?: number;
filterTo?: number;
}
export type OptionType = number;
export type Options<T, U = string> = T extends OptionType ? Record<OptionType, U> : 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<TValue> = {
[Field.TOGGLE]: null,
[Field.MULTI_TOGGLE]: { options: Options<TValue, { icon: IconName, tooltip?: string }> },
[Field.DROPDOWN]: { options: Options<TValue> },
[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<TField extends Field> = FieldValueRecord[TField];
export type FieldComponent<TField extends Field> = FieldComponentRecord[TField];
export type FieldSpec<TField extends Field, TValue> = FieldSpecRecord<TValue>[TField];
export type AbstractFieldDesc<TField extends Field, TRecord, TKey extends keyof TRecord> = {
type: TField,
record: TRecord extends Record<string, unknown> ? TRecord : never,
key: TKey extends keyof TRecord ? TRecord[TKey] extends infer TValue ? TValue extends FieldValue<TField> ? TKey : never : never : never,
callback?: (component: FieldComponent<TField>, plugin: ExtendedMarkdownSyntax) => unknown,
spec: FieldSpec<TField, TRecord[TKey]>,
update?: {
stylesheet?: boolean,
internal?: boolean
}
};
export type ToggleFieldDesc<TRecord> = AbstractFieldDesc<Field.TOGGLE, TRecord, keyof TRecord>;
export type MultiToggleFieldDesc<TRecord> = AbstractFieldDesc<Field.MULTI_TOGGLE, TRecord, keyof TRecord>;
export type DropdownFieldDesc<TRecord> = AbstractFieldDesc<Field.DROPDOWN, TRecord, keyof TRecord>;
export type ColorFieldDesc<TRecord> = AbstractFieldDesc<Field.COLOR, TRecord, keyof TRecord>;
export type TextFieldDesc<TRecord> = AbstractFieldDesc<Field.TEXT, TRecord, keyof TRecord>;
export type TextAreaFieldDesc<TRecord> = AbstractFieldDesc<Field.TEXT_AREA, TRecord, keyof TRecord>;
export type SliderFieldDesc<TRecord> = AbstractFieldDesc<Field.SLIDER, TRecord, keyof TRecord>;
export type FieldGroup<TRecord> = (
ToggleFieldDesc<TRecord> |
MultiToggleFieldDesc<TRecord> |
DropdownFieldDesc<TRecord> |
ColorFieldDesc<TRecord> |
TextFieldDesc<TRecord> |
TextAreaFieldDesc<TRecord> |
SliderFieldDesc<TRecord>
)[];
export type SettingItem<TRecord> = {
name: string,
desc?: string,
fields?: FieldGroup<TRecord>,
preservedForColorSettings?: boolean
};
export type SettingGroup<TRecord> = {
heading?: string,
desc?: string,
collapsible?: boolean,
items: SettingItem<TRecord>[]
};
export type SettingRoot<TRecord> = SettingGroup<TRecord>[];

View file

@ -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;
}
}

3
src/utils/decToHex.ts Normal file
View file

@ -0,0 +1,3 @@
export function decToHex(value: number, minDigit = 2) {
return value.toString(16).padStart(minDigit, "0");
}

3
src/utils/deepCopy.ts Normal file
View file

@ -0,0 +1,3 @@
export function deepCopy<T extends object>(obj: T) {
return JSON.parse(JSON.stringify(obj)) as T;
}

5
src/utils/getTheme.ts Normal file
View file

@ -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;
}

View file

@ -1 +1,8 @@
export * from "./configureDelimLookup";
export * from "./configureDelimLookup";
export * from "./isInlineFormat";
export * from "./trimTag";
export * from "./reconfigureDelimLookup";
export * from "./deepCopy";
export * from "./moveElement";
export * from "./getTheme";
export * from "./decToHex";

View file

@ -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
}

11
src/utils/moveElement.ts Normal file
View file

@ -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);
}
}

View file

@ -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);
}

5
src/utils/trimTag.ts Normal file
View file

@ -0,0 +1,5 @@
export function trimTag(tagStr: string) {
return tagStr
.trim()
.replaceAll(/\s{2,}/g, " ");
}