mirror of
https://github.com/kotaindah55/extended-markdown-syntax.git
synced 2026-07-22 05:38:06 +00:00
refactor: reconfigure some types for tokens settings
This commit is contained in:
parent
784532b1d2
commit
16c84ea898
10 changed files with 293 additions and 45 deletions
|
|
@ -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>[];
|
||||
|
|
@ -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
3
src/utils/decToHex.ts
Normal 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
3
src/utils/deepCopy.ts
Normal 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
5
src/utils/getTheme.ts
Normal 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;
|
||||
}
|
||||
|
|
@ -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";
|
||||
6
src/utils/isInlineFormat.ts
Normal file
6
src/utils/isInlineFormat.ts
Normal 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
11
src/utils/moveElement.ts
Normal 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);
|
||||
}
|
||||
}
|
||||
14
src/utils/reconfigureDelimLookup.ts
Normal file
14
src/utils/reconfigureDelimLookup.ts
Normal 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
5
src/utils/trimTag.ts
Normal file
|
|
@ -0,0 +1,5 @@
|
|||
export function trimTag(tagStr: string) {
|
||||
return tagStr
|
||||
.trim()
|
||||
.replaceAll(/\s{2,}/g, " ");
|
||||
}
|
||||
Loading…
Reference in a new issue