xiaodao-editor 0.1.19 → 0.1.21

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (36) hide show
  1. package/README.ZH.md +191 -68
  2. package/README.md +230 -78
  3. package/dist/block-editor.js +10444 -20908
  4. package/dist/block-editor.umd.cjs +30 -285
  5. package/dist/core/Editor.d.ts +32 -0
  6. package/dist/core/command/primitiveCommands.d.ts +1 -1
  7. package/dist/core/plugin/Plugin.d.ts +45 -5
  8. package/dist/core/state/EditorState.d.ts +5 -2
  9. package/dist/core/state/invert.d.ts +2 -2
  10. package/dist/core/types.d.ts +1 -1
  11. package/dist/extensions/Equation.d.ts +73 -8
  12. package/dist/extensions/Image.d.ts +54 -17
  13. package/dist/extensions/OrderedList.d.ts +1 -1
  14. package/dist/extensions/Paragraph.d.ts +1 -1
  15. package/dist/extensions/Table.d.ts +1 -1
  16. package/dist/extensions/math/ast.d.ts +140 -0
  17. package/dist/extensions/math/index.d.ts +18 -0
  18. package/dist/extensions/math/parser.d.ts +5 -0
  19. package/dist/extensions/math/renderHtml.d.ts +5 -0
  20. package/dist/extensions/math/renderTree.d.ts +16 -0
  21. package/dist/extensions/math/renderVNode.d.ts +4 -0
  22. package/dist/extensions/math/symbols.d.ts +37 -0
  23. package/dist/extensions/math/tokens.d.ts +22 -0
  24. package/dist/extensions/tableModel.d.ts +6 -6
  25. package/dist/i18n.d.ts +2 -2
  26. package/dist/index.d.ts +6 -3
  27. package/dist/style.css +1 -1
  28. package/dist/view/BlockEditor.vue.d.ts +0 -30
  29. package/dist/view/BlockList.vue.d.ts +2 -2
  30. package/dist/view/context.d.ts +11 -2
  31. package/dist/view/imageUpload.d.ts +20 -14
  32. package/dist/view/ui/icons.d.ts +22 -22
  33. package/dist/view/ui/inputRulesEngine.d.ts +2 -2
  34. package/dist/view/ui/popup.d.ts +1 -1
  35. package/dist/view/urlUtils.d.ts +2 -2
  36. package/package.json +2 -3
@@ -42,6 +42,23 @@ export declare class Editor {
42
42
  editable: boolean;
43
43
  /** The block id currently owning the focused contenteditable (set by the view). */
44
44
  focusBlockId: BlockId | null;
45
+ /**
46
+ * Extension-attached callables (e.g. async commands like
47
+ * `startImageUpload`). Plugins register these during `init` and look
48
+ * them up by name. Distinct from the synchronous `commands` proxy and
49
+ * from `Editor`'s own public API so extensions can advertise any
50
+ * callable without polluting the core surface.
51
+ *
52
+ * Invariant: every value stored here MUST be a callable. The map's
53
+ * value type is `unknown` (so we don't need a single vararg signature
54
+ * that every concrete function has to be assignable to); the
55
+ * `getExtensionMethod<T>` API is the only way to retrieve one, and
56
+ * it requires callers to assert the concrete function type at the
57
+ * use site (which is also where they actually know the signature).
58
+ */
59
+ private readonly extensionMethods;
60
+ /** Context handed to plugin `init` and `applyTransaction` hooks. */
61
+ private readonly pluginCtx;
45
62
  /** Public history API: canUndo/canRedo plus grouping helpers. */
46
63
  readonly history: EditorHistory;
47
64
  constructor(config: EditorConfig);
@@ -64,6 +81,21 @@ export declare class Editor {
64
81
  setDocument(json: DocumentData): void;
65
82
  /** Take ownership of a normalized document and reset the editor state. */
66
83
  private adoptDoc;
84
+ /**
85
+ * Register a callable under a string key. Throws if the key is already
86
+ * taken: there is no implicit override because extension authors
87
+ * should be explicit about who wins when two extensions want the same
88
+ * name. Returns an unregister function.
89
+ *
90
+ * `T` is inferred from the call site so concrete functions with
91
+ * specific parameter / return types (e.g. `(file: File) => Promise<…>`)
92
+ * are stored verbatim. The runtime map's value type is `unknown`
93
+ * (any value is structurally assignable to it); callers retrieve a
94
+ * typed function via `getExtensionMethod<T>(name)`.
95
+ */
96
+ registerExtensionMethod<T>(name: string, fn: T): () => void;
97
+ /** Look up a previously-registered extension method. */
98
+ getExtensionMethod<T = unknown>(name: string): T | undefined;
67
99
  dispatch(tr: Transaction): void;
68
100
  undo(): boolean;
69
101
  redo(): boolean;
@@ -58,7 +58,7 @@ export interface SetStartNumberArgs {
58
58
  readonly id: BlockId;
59
59
  /**
60
60
  * The explicit starting number for this ordered-list item.
61
- * Pass `null` / `undefined` to clear the override the item will then
61
+ * Pass `null` / `undefined` to clear the override: the item will then
62
62
  * follow the previous block's numbering (standard "continue" behavior).
63
63
  */
64
64
  readonly startNumber: number | null;
@@ -1,23 +1,63 @@
1
1
  import { EditorState } from '../state/EditorState';
2
2
  import { Transaction } from '../state/Transaction';
3
+ import { EditorRegistries } from '../extension/Registry';
3
4
  /** Opaque per-plugin state slice. Each plugin owns its concrete type. */
4
5
  export type PluginState = unknown;
6
+ /**
7
+ * Minimal Editor surface exposed to plugins. Defined here (rather than
8
+ * importing the `Editor` class) to avoid a circular type dependency:
9
+ * `plugin/Plugin.ts` is imported by `Editor.ts`, which would otherwise
10
+ * import `Editor.ts` back. Any method added to the live Editor that
11
+ * plugins should be able to call needs to be declared here too.
12
+ *
13
+ * The interface intentionally mirrors what plugins actually use today
14
+ * (registries for schema reads, commands for transaction dispatch,
15
+ * dispatch for plugin-internal transactions, subscribe for side-channel
16
+ * cleanup tracking). It can grow as new plugins need more access.
17
+ */
18
+ export interface PluginEditor {
19
+ readonly registries: EditorRegistries;
20
+ readonly commands: Record<string, (...args: unknown[]) => boolean>;
21
+ getState(): EditorState;
22
+ subscribe(listener: (update: {
23
+ readonly state: EditorState;
24
+ }) => void): () => void;
25
+ /**
26
+ * Register a callable under a string key. `T` is inferred from the
27
+ * call site so concrete function types are preserved. The returned
28
+ * function unregisters the method. Plugins use this to expose async
29
+ * commands (e.g. `startImageUpload`) or any other callable surface
30
+ * that the view layer needs to invoke through the editor.
31
+ */
32
+ registerExtensionMethod<T>(name: string, fn: T): () => void;
33
+ /** Look up a previously-registered extension method. */
34
+ getExtensionMethod<T = unknown>(name: string): T | undefined;
35
+ }
36
+ /** Context passed to `Plugin.init`. Provided once when the editor is built. */
37
+ export interface PluginInitContext {
38
+ readonly editor: PluginEditor;
39
+ }
5
40
  export interface EventContext {
6
41
  readonly state: EditorState;
7
42
  readonly dispatch: (tr: Transaction) => void;
8
43
  /** The focused block id, if any (the block owning the active contenteditable). */
9
44
  readonly focusBlockId: () => string | null;
45
+ /** The editor instance, exposing the same surface as `PluginInitContext.editor`. */
46
+ readonly editor: PluginEditor;
10
47
  }
11
48
  export interface Plugin {
12
49
  readonly name: string;
13
- /** Called once when the editor is created. Returns the initial plugin state. */
14
- init?(state: EditorState): PluginState;
50
+ /** Called once when the editor is created. Returns the initial plugin state.
51
+ * `ctx` carries a handle to the editor so the plugin can register
52
+ * extension methods on it. */
53
+ init?(state: EditorState, ctx: PluginInitContext): PluginState;
15
54
  /**
16
55
  * Called for every applied transaction. Receives the transaction, the
17
- * *previous* state, and the new (doc/selection) state being assembled.
18
- * Returns the plugin's new state slice.
56
+ * *previous* state, and the new (doc/selection) state being assembled,
57
+ * plus the same context as the event hooks. Returns the plugin's new
58
+ * state slice.
19
59
  */
20
- applyTransaction?(tr: Transaction, prevState: EditorState, nextDoc: EditorState['doc'], nextSelection: EditorState['selection']): PluginState;
60
+ applyTransaction?(tr: Transaction, prevState: EditorState, nextDoc: EditorState['doc'], nextSelection: EditorState['selection'], ctx: PluginInitContext): PluginState;
21
61
  onKeyDown?(event: KeyboardEvent, ctx: EventContext): boolean;
22
62
  onInput?(event: InputEvent, ctx: EventContext): boolean;
23
63
  onCompositionStart?(event: CompositionEvent, ctx: EventContext): void;
@@ -1,5 +1,5 @@
1
1
  import { DocState, Selection } from '../types';
2
- import { Plugin, PluginState } from '../plugin/Plugin';
2
+ import { Plugin, PluginInitContext, PluginState } from '../plugin/Plugin';
3
3
  import { Transaction } from './Transaction';
4
4
  import { ApplyResult } from './Step';
5
5
  export interface EditorState {
@@ -19,8 +19,11 @@ interface TransactionApplier {
19
19
  /**
20
20
  * Apply a transaction to a state, producing a new state plus a diff
21
21
  * (changed / removed) that the view bridge consumes.
22
+ *
23
+ * `ctx` is forwarded to every plugin's `applyTransaction` hook so plugins
24
+ * can register / look up extension methods on the editor.
22
25
  */
23
- export declare function applyTransaction(state: EditorState, tr: Transaction, plugins: readonly TransactionApplier[]): ApplyTransactionResult;
26
+ export declare function applyTransaction(state: EditorState, tr: Transaction, plugins: readonly TransactionApplier[], ctx: PluginInitContext): ApplyTransactionResult;
24
27
  /** Create the initial state from a document and a (possibly null) selection. */
25
28
  export declare function createState(doc: DocState, selection: Selection, pluginState?: Readonly<Record<string, PluginState>>): EditorState;
26
29
  export {};
@@ -5,7 +5,7 @@ import { Step } from './Step';
5
5
  * that, when applied to the post-state, restores the pre-state. Steps are
6
6
  * inverted in reverse order so the last-applied change is undone first.
7
7
  *
8
- * Important forward-step is a sequential program:
8
+ * Important: forward-step is a sequential program:
9
9
  * [ setAttrs(A, x'), insertBlock(B), replaceBlock(B), setAttrs(B) ]
10
10
  * When reversing, we may see `replaceBlock(B)` / `setAttrs(B)` BEFORE we see
11
11
  * the `insertBlock(B)` that actually added B to the document. In that case
@@ -13,7 +13,7 @@ import { Step } from './Step';
13
13
  * not the mid-transaction intermediate). Inverting B's attribute/replace
14
14
  * changes is redundant because the final `insertBlock(B)` → inverse
15
15
  * `removeBlock(B)` already erases B entirely. So these "unknown id" cases
16
- * are skipped the block is handled by its matching insertBlock inverse
16
+ * are skipped: the block is handled by its matching insertBlock inverse
17
17
  * further up the step list.
18
18
  */
19
19
  export declare function invertSteps(steps: readonly Step[], prevDoc: DocState): Step[];
@@ -9,7 +9,7 @@
9
9
  */
10
10
  /**
11
11
  * A stable, opaque identifier for a block. Branded so that a plain `string`
12
- * cannot be passed where a `BlockId` is expected this catches an entire
12
+ * cannot be passed where a `BlockId` is expected: this catches an entire
13
13
  * class of bugs at compile time.
14
14
  */
15
15
  export type BlockId = string & {
@@ -2,29 +2,85 @@ import { PropType, VNode } from 'vue';
2
2
  import { Extension } from '../core/extension/Extension';
3
3
  import { Block, BlockId } from '../core/types';
4
4
  import { Editor } from '../core/Editor';
5
- export interface RenderResult {
6
- /** KaTeX HTML output (safe to insert via v-html because trust:false). */
5
+ export interface EquationRenderOptions {
6
+ /** Block-level formula (centered, limits above/below). Default: true. */
7
+ readonly displayMode?: boolean;
8
+ }
9
+ export interface EquationDiagnostic {
10
+ /** `error` surfaces a visible problem; `warning` is recoverable (e.g. unknown command). */
11
+ readonly severity: 'error' | 'warning';
12
+ readonly message: string;
13
+ readonly start: number;
14
+ readonly end: number;
15
+ }
16
+ export interface EquationRenderResult {
17
+ /** Safe HTML string, always available (export / SSR / non-Vue consumers). */
7
18
  readonly html: string;
8
- /** True when the source could not be parsed by KaTeX. */
19
+ /**
20
+ * Vue VNode tree. When present the view renders it directly, so no HTML
21
+ * string is ever assigned to innerHTML. Adapters for string-only engines
22
+ * (KaTeX, MathJax) leave this `null` and fill `html` instead.
23
+ */
24
+ readonly vnode: VNode | VNode[] | null;
25
+ /** True when the expression has at least one `error` diagnostic. */
9
26
  readonly error: boolean;
27
+ readonly diagnostics: readonly EquationDiagnostic[];
28
+ }
29
+ export interface EquationRenderer {
30
+ render(expression: string, options?: EquationRenderOptions): EquationRenderResult;
10
31
  }
11
32
  /**
12
- * Render a LaTeX expression to HTML. Always returns a result parse failures
13
- * surface as `error: true` with a KaTeX error span rather than throwing.
14
- * Safe to call on the server (no `document` access).
33
+ * Default renderer: Tokenizer -> Parser -> AST -> render tree -> VNode + HTML.
34
+ * Supports a lightweight subset of LaTeX math (see `SUPPORTED_COMMANDS`).
15
35
  */
36
+ export declare const builtinEquationRenderer: EquationRenderer;
37
+ /**
38
+ * Legacy convenience wrapper retained for backward compatibility: render with
39
+ * the built-in renderer and return the historical `{ html, error }` shape.
40
+ */
41
+ export interface RenderResult {
42
+ /** Renderer HTML output (safe to insert via v-html: it is never raw input). */
43
+ readonly html: string;
44
+ /** True when the source could not be parsed. */
45
+ readonly error: boolean;
46
+ }
16
47
  export declare function renderEquation(expression: string): RenderResult;
17
48
  export interface EquationAttrs {
18
49
  readonly expression: string;
19
50
  }
20
- /** Paragraph/text block Equation: the block's plain text becomes the LaTeX source. */
51
+ /** Paragraph/text block -> Equation: the block's plain text becomes the LaTeX source. */
21
52
  export declare function turnIntoEquation(editor: Editor, id: BlockId, expression: string): void;
22
53
  /**
23
- * Equation Paragraph: the LaTeX source becomes the paragraph's text.
54
+ * Equation -> Paragraph: the LaTeX source becomes the paragraph's text.
24
55
  * (A plain convertBlock would drop the expression because the paragraph schema
25
56
  * has no `expression` attr, so we restore it as text explicitly.)
26
57
  */
27
58
  export declare function turnEquationIntoParagraph(editor: Editor, id: BlockId): void;
59
+ /** Build the equation block component bound to a specific renderer. */
60
+ export declare function createEquationBlock(renderer: EquationRenderer): import('vue').DefineComponent<import('vue').ExtractPropTypes<{
61
+ block: {
62
+ type: PropType<Block>;
63
+ required: true;
64
+ };
65
+ placeholder: {
66
+ type: StringConstructor;
67
+ default: undefined;
68
+ };
69
+ }>, () => VNode<import('vue').RendererNode, import('vue').RendererElement, {
70
+ [key: string]: any;
71
+ }>, {}, {}, {}, import('vue').ComponentOptionsMixin, import('vue').ComponentOptionsMixin, {}, string, import('vue').PublicProps, Readonly<import('vue').ExtractPropTypes<{
72
+ block: {
73
+ type: PropType<Block>;
74
+ required: true;
75
+ };
76
+ placeholder: {
77
+ type: StringConstructor;
78
+ default: undefined;
79
+ };
80
+ }>> & Readonly<{}>, {
81
+ placeholder: string;
82
+ }, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
83
+ /** The equation block bound to the built-in (zero-dependency) renderer. */
28
84
  export declare const EquationBlock: import('vue').DefineComponent<import('vue').ExtractPropTypes<{
29
85
  block: {
30
86
  type: PropType<Block>;
@@ -48,4 +104,13 @@ export declare const EquationBlock: import('vue').DefineComponent<import('vue').
48
104
  }>> & Readonly<{}>, {
49
105
  placeholder: string;
50
106
  }, {}, {}, {}, string, import('vue').ComponentProvideOptions, true, {}, any>;
107
+ export interface EquationExtensionOptions {
108
+ /**
109
+ * Custom renderer (KaTeX / MathJax / ...). When omitted the built-in
110
+ * zero-dependency renderer is used. The renderer is captured at extension
111
+ * creation time, so the component and `serialize.toHTML` always agree.
112
+ */
113
+ readonly renderer?: EquationRenderer;
114
+ }
115
+ export declare function createEquationExtension(options?: EquationExtensionOptions): Extension;
51
116
  export declare const EquationExtension: Extension;
@@ -1,5 +1,8 @@
1
1
  import { Extension } from '../core/extension/Extension';
2
2
  import { BlockId } from '../core/types';
3
+ import { UploadImageHandler } from '../view/imageUpload';
4
+ /** Synchronous command that aborts an in-flight upload for a block. */
5
+ export declare const CANCEL_IMAGE_UPLOAD_COMMAND = "cancelImageUpload";
3
6
  export interface ImageAttrs {
4
7
  readonly align: string;
5
8
  readonly src: string;
@@ -9,26 +12,60 @@ export interface ImageAttrs {
9
12
  readonly height: number;
10
13
  readonly caption: string;
11
14
  /**
12
- * Optional server-side file identifier (integer). Set by the external
13
- * `uploadImage` handler when it returns a result. The editor tracks
14
- * reference counts for each fileId and emits `cleanup:image-file` when
15
- * the last block referencing a fileId is removed, so the consumer can
16
- * reclaim cloud storage.
15
+ * Optional server-side file identifier (integer). Set by the
16
+ * `UploadImageHandler` passed to `createImageExtension({ upload })`
17
+ * when it resolves. The `image-upload` plugin tracks reference counts
18
+ * for each fileId and invokes `onFileCleanup` (also passed to
19
+ * `createImageExtension`) when the last block referencing a fileId is
20
+ * removed, so the consumer can reclaim cloud storage.
17
21
  * 0 (the default) means "no file id" / not uploaded yet / no managed file.
18
22
  */
19
23
  readonly fileId: number;
20
24
  }
21
- export interface InsertImageBlockArgs {
22
- readonly after?: BlockId;
23
- readonly parent?: BlockId | null;
24
- readonly index?: number;
25
- readonly attrs?: {
26
- src?: string;
27
- alt?: string;
28
- title?: string;
29
- width?: number;
30
- height?: number;
31
- caption?: string;
32
- };
25
+ export interface ImageExtensionOptions {
26
+ /**
27
+ * Real upload handler. When omitted the extension falls back to an
28
+ * in-memory mock upload that stores the file as an object URL: fine
29
+ * for demos but NOT for persisted documents (object URLs do not
30
+ * survive reload).
31
+ */
32
+ readonly upload?: UploadImageHandler;
33
+ /**
34
+ * Invoked when the LAST image block referencing a given fileId is
35
+ * removed/replaced. Hosts can use this to reclaim cloud storage.
36
+ */
37
+ readonly onFileCleanup?: (fileId: number) => void;
38
+ }
39
+ export interface BeginImageUploadOpts {
40
+ readonly relativeToBlockId?: BlockId | null;
41
+ readonly position?: 'after' | 'before' | 'replace';
42
+ readonly convertIfEmpty?: boolean;
33
43
  }
44
+ export type BeginImageUploadFn = (fileOrSrc: File | string, opts?: BeginImageUploadOpts) => Promise<BlockId | null>;
45
+ /** Key under which the async command is registered on the Editor. */
46
+ export declare const START_IMAGE_UPLOAD_METHOD = "startImageUpload";
47
+ /**
48
+ * Create the Image block extension with optional upload + cleanup hooks.
49
+ *
50
+ * Usage:
51
+ * ```ts
52
+ * const myExt = createImageExtension({
53
+ * upload: async (name, file, controller, onProgress) => {
54
+ * // upload `file` to your cloud; return { url, width, height, fileId }
55
+ * },
56
+ * onFileCleanup: (fileId) => api.deleteCloudFile(fileId),
57
+ * });
58
+ * new Editor({ extensions: [...BuiltinExtensions.filter(e => e.name !== 'image'), myExt] });
59
+ * ```
60
+ *
61
+ * `BlockEditor.vue` itself does NOT know about upload or file cleanup:
62
+ * it only forwards the existing `useBeginImageUpload()` Vue injection to
63
+ * the async command this extension registers on the editor.
64
+ */
65
+ export declare function createImageExtension(options?: ImageExtensionOptions): Extension;
66
+ /**
67
+ * The default Image extension bundled with the editor. Uses an in-memory
68
+ * mock upload (object URLs) and never invokes a cleanup callback. Suitable
69
+ * for demos; persisted documents need `createImageExtension({ upload, onFileCleanup })`.
70
+ */
34
71
  export declare const ImageExtension: Extension;
@@ -10,7 +10,7 @@ import { siblingList } from '../core/state/store';
10
10
  * chain; an explicit `attrs.startNumber` acts as a reset anchor.
11
11
  *
12
12
  * This is strictly narrower than the old flat-indent model: blocks under a
13
- * DIFFERENT parent (even at the same depthOf) never share a counter
13
+ * DIFFERENT parent (even at the same depthOf) never share a counter:
14
14
  * crossing any parent boundary resets numbering by design. This matches the
15
15
  * rendering (BlockList nests children) and user intuition.
16
16
  */
@@ -1,5 +1,5 @@
1
1
  import { Extension } from '../core/extension/Extension';
2
2
  import { BlockId } from '../core/types';
3
- /** Slash-menu icon SVG string from shared icons module. */
3
+ /** Slash-menu icon: SVG string from shared icons module. */
4
4
  export declare const ParagraphExtension: Extension;
5
5
  export declare const CURRENT_BLOCK_PLACEHOLDER: BlockId;
@@ -55,7 +55,7 @@ export interface InsertTableArgs {
55
55
  readonly replaceCurrent?: boolean;
56
56
  }
57
57
  /** Build all table commands. The registry context is accepted for parity with
58
- * other factories but currently unused kept for future callers that need
58
+ * other factories but currently unused: kept for future callers that need
59
59
  * schema-level coordination. */
60
60
  export declare function createTableCommands(_registries: EditorRegistries): AnyCommandEntry[];
61
61
  export declare const TableExtension: Extension;
@@ -0,0 +1,140 @@
1
+ /**
2
+ * Math AST for xiaodao-editor's built-in (zero-dependency) equation renderer.
3
+ *
4
+ * The AST is a pure data structure: it knows nothing about the DOM, Vue, or the
5
+ * editor. Two independent backends consume it:
6
+ * - `renderVNode.ts` -> Vue VNode tree (live editing, no innerHTML)
7
+ * - `renderHtml.ts` -> escaped HTML string (export / SSR / non-Vue callers)
8
+ *
9
+ * Every node carries the source span it was parsed from, so future work can
10
+ * highlight the exact offending range without re-parsing.
11
+ */
12
+ /** Source span, in UTF-16 code units, half-open: [start, end). */
13
+ export interface MathSpan {
14
+ readonly start: number;
15
+ readonly end: number;
16
+ }
17
+ /** Environments understood by the built-in renderer. */
18
+ export type MathEnv = 'matrix' | 'aligned';
19
+ /** Delimiters produced by `(`/`)`/`[`/`]`/`\{`/`|` and `\left`/`\right`. */
20
+ export type MathDelimiter = '(' | ')' | '[' | ']' | '{' | '}' | '|' | '.';
21
+ /** An ordered run of nodes (an argument, a numerator, a script, a cell, ...). */
22
+ export type MathContent = readonly MathNode[];
23
+ /** One cell of a matrix/aligned row. */
24
+ export type MathCell = MathContent;
25
+ /** One row of a matrix/aligned environment. */
26
+ export type MathRow = readonly MathCell[];
27
+ /** A run of ordinary characters that is not a single identifier (e.g. `abc`). */
28
+ export interface MathTextNode extends MathSpan {
29
+ readonly type: 'text';
30
+ readonly value: string;
31
+ }
32
+ /** A numeric literal such as `2` or `3.14`. */
33
+ export interface MathNumberNode extends MathSpan {
34
+ readonly type: 'number';
35
+ readonly value: string;
36
+ }
37
+ /** A single-letter variable such as `x`. Rendered italic. */
38
+ export interface MathIdentifierNode extends MathSpan {
39
+ readonly type: 'identifier';
40
+ readonly value: string;
41
+ }
42
+ /** `+`, `-`, `=`, `<`, `>`, or a mapped command such as `\times`. */
43
+ export interface MathOperatorNode extends MathSpan {
44
+ readonly type: 'operator';
45
+ readonly value: string;
46
+ /** The originating command name (without backslash) when it came from one. */
47
+ readonly command: string | null;
48
+ /**
49
+ * True for a unary (sign) usage such as `-b` or `x = -1`, where the minus
50
+ * hugs the operand that follows it. Absent/false for the binary minus.
51
+ */
52
+ readonly unary?: boolean;
53
+ }
54
+ /** A delimited group: `{...}`, `(...)`, `[...]`, or `\left(...\right)`. */
55
+ export interface MathGroupNode extends MathSpan {
56
+ readonly type: 'group';
57
+ readonly body: MathContent;
58
+ /**
59
+ * `null` for a plain `{...}` grouping (braces stay invisible, as in TeX).
60
+ * A non-null value renders that glyph on the left/right of the body.
61
+ */
62
+ readonly open: MathDelimiter | null;
63
+ readonly close: MathDelimiter | null;
64
+ }
65
+ /** `\frac{num}{den}`. */
66
+ export interface MathFractionNode extends MathSpan {
67
+ readonly type: 'fraction';
68
+ readonly numerator: MathContent;
69
+ readonly denominator: MathContent;
70
+ }
71
+ /** `\sqrt{x}` or `\sqrt[3]{x}`. */
72
+ export interface MathRootNode extends MathSpan {
73
+ readonly type: 'root';
74
+ readonly radicand: MathContent;
75
+ /** Degree for `\sqrt[n]{}`; null for a plain square root. */
76
+ readonly index: MathContent | null;
77
+ }
78
+ /**
79
+ * `x^2`, `x_1`, and `x_1^2` all collapse into a SINGLE node so the renderer
80
+ * never has to deal with a nested/incorrect shape for combined scripts.
81
+ */
82
+ export interface MathScriptsNode extends MathSpan {
83
+ readonly type: 'scripts';
84
+ readonly base: MathNode;
85
+ readonly sup: MathContent | null;
86
+ readonly sub: MathContent | null;
87
+ }
88
+ /** `\sin`, `\log`, `\lim`, ... rendered upright (`sin`, not `s i n`). */
89
+ export interface MathFunctionNode extends MathSpan {
90
+ readonly type: 'function';
91
+ readonly name: string;
92
+ readonly command: string;
93
+ }
94
+ /** A mapped symbol such as `\alpha` -> alpha. */
95
+ export interface MathSymbolNode extends MathSpan {
96
+ readonly type: 'symbol';
97
+ readonly char: string;
98
+ readonly command: string;
99
+ }
100
+ /** `\sum`, `\prod`, `\int`: carries its own limits for above/below layout. */
101
+ export interface MathLargeOperatorNode extends MathSpan {
102
+ readonly type: 'largeOperator';
103
+ readonly symbol: string;
104
+ readonly command: string;
105
+ readonly sup: MathContent | null;
106
+ readonly sub: MathContent | null;
107
+ }
108
+ /** `\begin{matrix}...\end{matrix}` / `\begin{aligned}...\end{aligned}`. */
109
+ export interface MathMatrixNode extends MathSpan {
110
+ readonly type: 'matrix';
111
+ readonly env: MathEnv;
112
+ readonly rows: readonly MathRow[];
113
+ }
114
+ /** A command the built-in renderer does not implement (graceful degradation). */
115
+ export interface MathUnknownNode extends MathSpan {
116
+ readonly type: 'unknown';
117
+ readonly command: string;
118
+ }
119
+ /** A placeholder for a construct that could not be parsed. */
120
+ export interface MathErrorNode extends MathSpan {
121
+ readonly type: 'error';
122
+ readonly message: string;
123
+ }
124
+ export type MathNode = MathTextNode | MathNumberNode | MathIdentifierNode | MathOperatorNode | MathGroupNode | MathFractionNode | MathRootNode | MathScriptsNode | MathFunctionNode | MathSymbolNode | MathLargeOperatorNode | MathMatrixNode | MathUnknownNode | MathErrorNode;
125
+ export type MathNodeType = MathNode['type'];
126
+ /**
127
+ * A parse outcome. `severity: 'error'` means the renderer should surface a
128
+ * visible error; `'warning'` means the input was recoverable (e.g. an unknown
129
+ * command that is rendered literally) and is NOT an error state.
130
+ */
131
+ export interface MathDiagnostic {
132
+ readonly severity: 'error' | 'warning';
133
+ readonly message: string;
134
+ readonly start: number;
135
+ readonly end: number;
136
+ }
137
+ export interface ParseResult {
138
+ readonly nodes: MathContent;
139
+ readonly diagnostics: readonly MathDiagnostic[];
140
+ }
@@ -0,0 +1,18 @@
1
+ /**
2
+ * Built-in, zero-dependency math engine for xiaodao-editor.
3
+ *
4
+ * Pipeline: expression -> tokenize -> parse (AST) -> render tree -> VNode / HTML
5
+ *
6
+ * Nothing in this folder depends on KaTeX, MathJax, the DOM, or the editor:
7
+ * it is a pure, dependency-free LaTeX-subset renderer.
8
+ */
9
+ export type { MathCell, MathContent, MathDelimiter, MathDiagnostic, MathEnv, MathNode, MathNodeType, MathRow, MathSpan, ParseResult, } from './ast';
10
+ export type { MathErrorNode, MathFractionNode, MathFunctionNode, MathGroupNode, MathIdentifierNode, MathLargeOperatorNode, MathMatrixNode, MathNumberNode, MathOperatorNode, MathRootNode, MathScriptsNode, MathSymbolNode, MathTextNode, MathUnknownNode, } from './ast';
11
+ export { tokenize } from './tokens';
12
+ export type { Token, TokenKind } from './tokens';
13
+ export { FUNCTION_COMMANDS, GREEK_LETTERS, LARGE_OPERATORS, OPERATOR_COMMANDS, STRUCTURAL_COMMANDS, SUPPORTED_COMMANDS, isSupportedCommand, } from './symbols';
14
+ export { parseMath } from './parser';
15
+ export { buildRenderTree } from './renderTree';
16
+ export type { MathElement, RenderContext } from './renderTree';
17
+ export { renderMathToVNode } from './renderVNode';
18
+ export { escapeHtmlText, renderMathToHtml } from './renderHtml';
@@ -0,0 +1,5 @@
1
+ import { ParseResult } from './ast';
2
+ /**
3
+ * Parse a LaTeX-ish math expression. Always returns a result: never throws.
4
+ */
5
+ export declare function parseMath(source: string): ParseResult;
@@ -0,0 +1,5 @@
1
+ import { MathContent } from './ast';
2
+ import { RenderContext } from './renderTree';
3
+ export declare function renderMathToHtml(nodes: MathContent, ctx: RenderContext): string;
4
+ /** Escape for element text content. */
5
+ export declare function escapeHtmlText(value: string): string;
@@ -0,0 +1,16 @@
1
+ import { MathContent } from './ast';
2
+ export interface RenderContext {
3
+ readonly displayMode: boolean;
4
+ }
5
+ export interface MathElement {
6
+ readonly tag: string;
7
+ /** Class name(s), or null. */
8
+ readonly class: string | null;
9
+ /** Inline style, or null. */
10
+ readonly style: Readonly<Record<string, string>> | null;
11
+ /** Text content for a leaf element; null when the element has children. */
12
+ readonly text: string | null;
13
+ readonly children: readonly MathElement[] | null;
14
+ }
15
+ /** Build the full render tree for a parsed expression. */
16
+ export declare function buildRenderTree(nodes: MathContent, ctx: RenderContext): MathElement;
@@ -0,0 +1,4 @@
1
+ import { VNode } from 'vue';
2
+ import { MathContent } from './ast';
3
+ import { RenderContext } from './renderTree';
4
+ export declare function renderMathToVNode(nodes: MathContent, ctx: RenderContext): VNode;
@@ -0,0 +1,37 @@
1
+ /**
2
+ * Symbol tables for the built-in math renderer.
3
+ *
4
+ * This file is the single source of truth for "what does the built-in renderer
5
+ * understand". Everything listed here is rendered as a real math glyph; every
6
+ * other command degrades gracefully to an `unknown` node.
7
+ *
8
+ * Glyphs are plain Unicode characters: no font download, no SVG, no MathML.
9
+ */
10
+ /** Greek letters, lowercase + the uppercase variants the spec requires. */
11
+ export declare const GREEK_LETTERS: Readonly<Record<string, string>>;
12
+ /** Binary relations / operators expressed as LaTeX commands. */
13
+ export declare const OPERATOR_COMMANDS: Readonly<Record<string, string>>;
14
+ /** Named functions, rendered upright (so `\sin x` never looks like `s i n x`). */
15
+ export declare const FUNCTION_COMMANDS: Readonly<Record<string, string>>;
16
+ /** Operators whose limits are placed above/below in display mode. */
17
+ export declare const LARGE_OPERATORS: Readonly<Record<string, string>>;
18
+ /** Structural commands handled directly by the parser. */
19
+ export declare const STRUCTURAL_COMMANDS: readonly string[];
20
+ /**
21
+ * Spacing commands spelled as letter commands: `\quad` (1 em) and `\qquad`
22
+ * (2 em), mapped to Unicode em spaces.
23
+ */
24
+ export declare const SPACING_COMMANDS: Readonly<Record<string, string>>;
25
+ /**
26
+ * TeX spacing commands that arrive from the tokenizer as escaped literal
27
+ * characters (`\,` `\:` `\;` `\!` `\ `), mapped to Unicode spaces:
28
+ * thin / medium math / thick / zero-width / no-break. (A negative space has
29
+ * no Unicode glyph; `\!` degrades to a zero-width, visually neutral space.)
30
+ */
31
+ export declare const SPACING_ESCAPES: Readonly<Record<string, string>>;
32
+ /**
33
+ * Every command name the built-in renderer understands. Exported so docs and
34
+ * tests can assert the supported surface without duplicating the tables.
35
+ */
36
+ export declare const SUPPORTED_COMMANDS: readonly string[];
37
+ export declare function isSupportedCommand(name: string): boolean;