mirror of
https://github.com/kotaindah55/extended-markdown-syntax.git
synced 2026-07-22 05:38:06 +00:00
413 lines
No EOL
12 KiB
TypeScript
413 lines
No EOL
12 KiB
TypeScript
import type { SyntaxNode, Tree } from "@lezer/common";
|
|
import type { Format, MarkdownViewMode, TokenLevel, Delimiter, TokenStatus, Field, DisplayBehaviour } from "src/enums";
|
|
import { Line, RangeSet, RangeValue, Text, Range, SelectionRange, EditorState } from "@codemirror/state";
|
|
import {} from "@codemirror/language";
|
|
import { Decoration, DecorationSet } from "@codemirror/view";
|
|
import { ColorComponent, Command, DropdownComponent, Editor, ExtraButtonComponent, IconName, MarkdownFileInfo, MarkdownView, Setting, SliderComponent, TextAreaComponent, TextComponent, ToggleComponent } from "obsidian";
|
|
import ExtendedMarkdownSyntax from "main";
|
|
import { TagMenu } from "src/editor-mode/ui-components";
|
|
import { ExtendedSettingTab } from "src/settings/ui/setting-tab";
|
|
|
|
declare module "@codemirror/state" {
|
|
/**
|
|
* MIT licensed, copyright (c) by Marijn Haverbeke and others at
|
|
* CodeMirror.
|
|
*
|
|
* @see https://github.com/codemirror/state/blob/main/src/text.ts
|
|
*/
|
|
interface TextNode extends Text {
|
|
readonly children: readonly Text[];
|
|
}
|
|
|
|
/**
|
|
* MIT licensed, copyright (c) by Marijn Haverbeke and others at
|
|
* CodeMirror.
|
|
*
|
|
* @see https://github.com/codemirror/state/blob/main/src/text.ts
|
|
*/
|
|
interface TextLeaf extends Text {
|
|
readonly children: null;
|
|
text: string[];
|
|
}
|
|
}
|
|
|
|
declare module "@codemirror/language" {
|
|
/**
|
|
* MIT licensed, copyright (c) by Marijn Haverbeke and others at
|
|
* CodeMirror.
|
|
*
|
|
* @see https://github.com/codemirror/language/blob/main/src/language.ts
|
|
*/
|
|
class LanguageState {
|
|
readonly tree: Tree;
|
|
readonly context: ParseContext;
|
|
}
|
|
|
|
/**
|
|
* MIT licensed, copyright (c) by Marijn Haverbeke and others at
|
|
* CodeMirror.
|
|
*
|
|
* @see https://github.com/codemirror/language/blob/main/src/language.ts
|
|
*/
|
|
interface ParseContext {
|
|
tree: Tree;
|
|
treeLen: number;
|
|
}
|
|
}
|
|
|
|
declare module "obsidian" {
|
|
interface MarkdownPostProcessorContext {
|
|
el: HTMLElement;
|
|
containerEl: HTMLElement;
|
|
}
|
|
}
|
|
|
|
declare global {
|
|
interface Window {
|
|
globalThis: typeof globalThis;
|
|
}
|
|
|
|
type Writable<T> = {
|
|
-readonly [P in keyof T]: T[P];
|
|
}
|
|
}
|
|
|
|
/** 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
|
|
/** 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;
|
|
/**
|
|
* 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`).
|
|
*/
|
|
closedByBlankLine: boolean;
|
|
}
|
|
|
|
/** 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.
|
|
*/
|
|
from: number,
|
|
/** 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. */
|
|
changedTo: number;
|
|
/**
|
|
* Length difference. Can be negative value, indicating that replaced text
|
|
* is longer than the substitute.
|
|
*/
|
|
length: number;
|
|
}
|
|
|
|
/** Describe the rule that has to be satisfied by the delimiter */
|
|
export type DelimSpec = {
|
|
/** Should be single character */
|
|
char: string,
|
|
/** Delimiter length */
|
|
length: number,
|
|
/**
|
|
* If true, then delimiter length must be the same as
|
|
* in the predetermined rule. Otherwise, the defined length
|
|
* act as minimum length.
|
|
*/
|
|
exactLen: boolean,
|
|
/** 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
|
|
}
|
|
|
|
/**
|
|
* Used for (re)configuring the state, especially in
|
|
* the case of document or tree change
|
|
*/
|
|
export type StateConfig = {
|
|
doc: Text,
|
|
tree: Tree,
|
|
offset: number,
|
|
settings: PluginSettings,
|
|
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 InlineFormat = Extract<Format, Format.INSERTION | Format.SPOILER | Format.SUPERSCRIPT | Format.SUBSCRIPT | Format.HIGHLIGHT | Format.CUSTOM_SPAN>;
|
|
|
|
export type BlockFormat = Exclude<Format, InlineFormat>;
|
|
|
|
export type NonBuiltinInlineFormat = Exclude<InlineFormat, Format.HIGHLIGHT>;
|
|
|
|
export type TagSupportFormat = Format.HIGHLIGHT | Format.CUSTOM_SPAN | Format.FENCED_DIV;
|
|
|
|
export type TokenGroup = Token[];
|
|
|
|
export type ColorConfig = {
|
|
tag: string,
|
|
name: string,
|
|
color: string,
|
|
showInMenu: boolean
|
|
};
|
|
|
|
export type TagConfig = {
|
|
tag: string,
|
|
name: string,
|
|
showInMenu: boolean
|
|
};
|
|
|
|
export type PluginSettings = {
|
|
// General
|
|
insertion: MarkdownViewMode;
|
|
spoiler: MarkdownViewMode;
|
|
superscript: MarkdownViewMode;
|
|
subscript: MarkdownViewMode;
|
|
customHighlight: MarkdownViewMode;
|
|
customSpan: MarkdownViewMode;
|
|
fencedDiv: MarkdownViewMode;
|
|
|
|
// Tag behavior
|
|
hlTagDisplayBehaviour: DisplayBehaviour;
|
|
spanTagDisplayBehaviour: DisplayBehaviour;
|
|
showHlTagInPreviewMode: boolean;
|
|
showSpanTagInPreviewMode: boolean;
|
|
alwaysShowFencedDivTag: MarkdownViewMode;
|
|
|
|
// Formatting
|
|
tidyFormatting: boolean;
|
|
openTagMenuAfterFormat: boolean;
|
|
noStyledDivInSourceMode: boolean;
|
|
|
|
// Custom highlight
|
|
colorButton: boolean;
|
|
showAccentColor: boolean;
|
|
showDefaultColor: boolean;
|
|
showRemoveColor: boolean;
|
|
lightModeHlOpacity: number;
|
|
darkModeHlOpacity: number;
|
|
colorConfigs: ColorConfig[];
|
|
|
|
// Custom span
|
|
showDefaultSpanTag: boolean;
|
|
showRemoveSpanTag: boolean;
|
|
predefinedSpanTag: TagConfig[];
|
|
|
|
// Fenced div
|
|
showDefaultDivTag: boolean;
|
|
showRemoveDivTag: boolean;
|
|
predefinedDivTag: TagConfig[];
|
|
|
|
// Others
|
|
editorEscape: boolean;
|
|
decoratePDF: boolean;
|
|
}
|
|
|
|
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, filter?: RegExp },
|
|
[Field.TEXT_AREA]: { placeholder?: string, filter?: RegExp, resizable?: 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?: {
|
|
deep?: 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>,
|
|
preservedForTagSettings?: Format.HIGHLIGHT | Format.CUSTOM_SPAN | Format.FENCED_DIV
|
|
}
|
|
|
|
export type SettingGroup<TRecord> = {
|
|
heading?: string,
|
|
desc?: string,
|
|
collapsible?: boolean,
|
|
id: string,
|
|
items: SettingItem<TRecord>[]
|
|
}
|
|
|
|
export type SettingRoot<TRecord> = SettingGroup<TRecord>[];
|
|
|
|
export type TagSettingsSpec = {
|
|
type: TagSupportFormat;
|
|
addBtnPlaceholder: string;
|
|
nameFieldPlaceholder: string;
|
|
tagFieldPlaceholder: string;
|
|
tagFilter: RegExp;
|
|
onAdd?: (settingTab: ExtendedSettingTab, tagSettingItem: Setting, newConfig: TagConfig) => unknown;
|
|
onMove?: (settingTab: ExtendedSettingTab, oldIndex: number, newIndex: number) => unknown;
|
|
onResetted?: (settingTab: ExtendedSettingTab) => unknown;
|
|
onDelete?: (settingTab: ExtendedSettingTab, deletedIndex: number) => unknown;
|
|
onTagChange?: (settingTab: ExtendedSettingTab, changedConfig: TagConfig, index: number) => unknown;
|
|
}
|
|
|
|
export type FormattingSpec = {
|
|
range: SelectionRange,
|
|
type: Format,
|
|
state: EditorState,
|
|
tokenIndexMap: number[],
|
|
tokens: TokenGroup,
|
|
remadeTokenIndexes: Partial<Record<number, boolean>>,
|
|
preventMenu?: boolean,
|
|
precise?: boolean
|
|
}
|
|
|
|
export interface CtxMenuCommand extends Command {
|
|
icon: string;
|
|
ctxMenuTitle: string;
|
|
editorCallback: (editor: Editor, ctx: MarkdownView | MarkdownFileInfo) => unknown;
|
|
[data: string]: unknown;
|
|
}
|
|
|
|
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
export interface ITagMenu extends TagMenu {} |