@llui/markdown-editor 0.3.0 → 0.4.0

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 (54) hide show
  1. package/dist/editor.d.ts +15 -2
  2. package/dist/editor.d.ts.map +1 -1
  3. package/dist/editor.js +12 -2
  4. package/dist/editor.js.map +1 -1
  5. package/dist/index.d.ts +6 -1
  6. package/dist/index.d.ts.map +1 -1
  7. package/dist/index.js +15 -1
  8. package/dist/index.js.map +1 -1
  9. package/dist/plugins/block-drag.d.ts +73 -0
  10. package/dist/plugins/block-drag.d.ts.map +1 -0
  11. package/dist/plugins/block-drag.js +760 -0
  12. package/dist/plugins/block-drag.js.map +1 -0
  13. package/dist/plugins/code-language.d.ts +60 -0
  14. package/dist/plugins/code-language.d.ts.map +1 -0
  15. package/dist/plugins/code-language.js +308 -0
  16. package/dist/plugins/code-language.js.map +1 -0
  17. package/dist/plugins/frontmatter.d.ts +45 -0
  18. package/dist/plugins/frontmatter.d.ts.map +1 -0
  19. package/dist/plugins/frontmatter.js +281 -0
  20. package/dist/plugins/frontmatter.js.map +1 -0
  21. package/dist/plugins/overlay.d.ts +1 -0
  22. package/dist/plugins/overlay.d.ts.map +1 -1
  23. package/dist/plugins/overlay.js +7 -0
  24. package/dist/plugins/overlay.js.map +1 -1
  25. package/dist/plugins/wikilink.d.ts +91 -0
  26. package/dist/plugins/wikilink.d.ts.map +1 -0
  27. package/dist/plugins/wikilink.js +467 -0
  28. package/dist/plugins/wikilink.js.map +1 -0
  29. package/dist/styles/block-drag.css +63 -0
  30. package/dist/styles/editor.css +64 -0
  31. package/dist/transformers/code.d.ts +24 -0
  32. package/dist/transformers/code.d.ts.map +1 -0
  33. package/dist/transformers/code.js +167 -0
  34. package/dist/transformers/code.js.map +1 -0
  35. package/dist/transformers/gfm.d.ts.map +1 -1
  36. package/dist/transformers/gfm.js +7 -2
  37. package/dist/transformers/gfm.js.map +1 -1
  38. package/dist/transformers/registry.d.ts +3 -0
  39. package/dist/transformers/registry.d.ts.map +1 -1
  40. package/dist/transformers/registry.js +26 -0
  41. package/dist/transformers/registry.js.map +1 -1
  42. package/package.json +40 -26
  43. package/src/editor.ts +27 -4
  44. package/src/index.ts +58 -1
  45. package/src/plugins/block-drag.ts +912 -0
  46. package/src/plugins/code-language.ts +378 -0
  47. package/src/plugins/frontmatter.ts +324 -0
  48. package/src/plugins/overlay.ts +7 -0
  49. package/src/plugins/wikilink.ts +553 -0
  50. package/src/styles/block-drag.css +63 -0
  51. package/src/styles/editor.css +64 -0
  52. package/src/transformers/code.ts +174 -0
  53. package/src/transformers/gfm.ts +6 -2
  54. package/src/transformers/registry.ts +27 -0
@@ -0,0 +1,167 @@
1
+ // The fenced-code-block transformer.
2
+ //
3
+ // ## Why this package does not use `@lexical/markdown`'s `CODE`
4
+ //
5
+ // Upstream's `CODE_START_REGEX` captures the info string with `([\w-]+)?`, i.e.
6
+ // a SINGLE word-ish token. Everything after that token stays on the line and is
7
+ // pushed into the code block's CONTENT:
8
+ //
9
+ // ```lance table → language 'lance', body 'table\nsum(x)' (corrupt)
10
+ // ```c++ → language 'c', body '++\nint main()' (corrupt)
11
+ //
12
+ // That is silent data loss on perfectly ordinary markdown. CommonMark defines
13
+ // the info string as *the rest of the line*, trimmed — so
14
+ // {@link CODE_INFO_TRANSFORMER} captures exactly that and hands it to
15
+ // `CodeNode.setLanguage` untouched.
16
+ //
17
+ // This lives in `transformers/` (not in `plugins/code-language.ts`) so that it
18
+ // is the DEFAULT in `GFM_TRANSFORMERS` rather than an opt-in a consumer has to
19
+ // remember to order ahead of `corePlugin()`. `codeLanguagePlugin()` contributes
20
+ // this same object reference, so the registry's reference de-duplication
21
+ // collapses the two contributions into one and plugin order cannot change the
22
+ // parse. See `test/composition.test.ts`.
23
+ //
24
+ // ## The language is an opaque label
25
+ //
26
+ // The package depends on `@lexical/code-core`, not `@lexical/code`, to keep
27
+ // Prism out of the bundle. Nothing interprets the info string — it is stored,
28
+ // shown, edited, and re-emitted verbatim. That opacity is precisely what lets an
29
+ // arbitrary token like `lance table` survive a round-trip.
30
+ import { $createTextNode } from 'lexical';
31
+ import { $createCodeNode, $isCodeNode, CodeNode } from '@lexical/code-core';
32
+ /**
33
+ * Canonicalize a fence info string.
34
+ *
35
+ * CommonMark's info string is the remainder of the opening-fence line with the
36
+ * surrounding whitespace stripped; a blank one means "no language". Two
37
+ * characters are removed rather than preserved, because keeping them would emit
38
+ * markdown that no longer re-imports to the same block:
39
+ *
40
+ * - a backtick — illegal in a backtick-fenced info string (it would terminate
41
+ * or corrupt the fence);
42
+ * - a newline — it would end the fence line entirely.
43
+ *
44
+ * Everything else survives verbatim, including spaces (`'lance table'`) and
45
+ * punctuation (`'c++'`, `'objective-c'`).
46
+ */
47
+ export function normalizeCodeInfo(raw) {
48
+ if (raw === null || raw === undefined)
49
+ return null;
50
+ const cleaned = raw
51
+ .replace(/`/g, '')
52
+ .replace(/[\r\n]+/g, ' ')
53
+ .trim();
54
+ return cleaned === '' ? null : cleaned;
55
+ }
56
+ /** The opening fence: its backtick run, then the raw info string (rest of line). */
57
+ const FENCE_START = /^([ \t]*`{3,})(.*)$/;
58
+ /** A closing fence of at least `length` backticks on a line of its own. */
59
+ const fenceEndFor = (length) => new RegExp(`^[ \\t]*\`{${length},}[ \\t]*$`);
60
+ /**
61
+ * The narrowest fence that can enclose `code` without being terminated early:
62
+ * one backtick longer than the longest fence-like run inside it.
63
+ */
64
+ function fenceFor(code) {
65
+ const runs = code.match(/`{3,}/g);
66
+ const longest = runs ? Math.max(...runs.map((run) => run.length)) : 0;
67
+ return '`'.repeat(Math.max(3, longest + 1));
68
+ }
69
+ /**
70
+ * A self-closing opening fence (```` ```code``` ````): the closing run sits on
71
+ * the SAME line, so the span between the fences is CONTENT and there is no info
72
+ * string. Returns that content, or `null` when the line is an ordinary opening
73
+ * fence.
74
+ *
75
+ * Shared by BOTH directions on purpose. It used to live only in
76
+ * `handleImportAfterStartMatch`, so the import path and the typed-shortcut
77
+ * `replace` path parsed the same text two different ways: importing
78
+ * '```inline```' gave a language-less block containing `inline`, while TYPING it
79
+ * and pressing Enter gave an EMPTY block labelled 'inline' — the word was gone.
80
+ * The divergence was reachable because `FENCE_START`'s `(.*)$` consumes the
81
+ * whole line, which always satisfies the shortcut engine's `match[0].length ===
82
+ * matchLength` gate. (Upstream's narrower `CODE_START_REGEX` never matched this
83
+ * shape, so widening the capture is what introduced it.)
84
+ */
85
+ function singleLineFenceContent(fence, rest) {
86
+ const closing = rest.match(new RegExp(`\`{${fence.length},}[ \t]*$`));
87
+ return closing && closing.index !== undefined ? rest.slice(0, closing.index) : null;
88
+ }
89
+ /** Append a code block carrying `language` and `lines` to `parent`. */
90
+ function $appendCodeNode(parent, language, lines) {
91
+ const node = $createCodeNode(language ?? undefined);
92
+ const code = lines.join('\n');
93
+ if (code !== '')
94
+ node.append($createTextNode(code));
95
+ parent.append(node);
96
+ }
97
+ /**
98
+ * A drop-in replacement for `@lexical/markdown`'s `CODE` that treats the whole
99
+ * remainder of the opening-fence line as the info string (CommonMark's rule)
100
+ * instead of a single `[\w-]+` token.
101
+ */
102
+ export const CODE_INFO_TRANSFORMER = {
103
+ dependencies: [CodeNode],
104
+ export: (node) => {
105
+ if (!$isCodeNode(node))
106
+ return null;
107
+ const code = node.getTextContent();
108
+ const fence = fenceFor(code);
109
+ const info = normalizeCodeInfo(node.getLanguage()) ?? '';
110
+ return fence + info + (code === '' ? '' : '\n' + code) + '\n' + fence;
111
+ },
112
+ handleImportAfterStartMatch: ({ lines, rootNode, startLineIndex, startMatch }) => {
113
+ const fence = (startMatch[1] ?? '```').trim();
114
+ const rest = startMatch[2] ?? '';
115
+ // Single-line form (```code```) — see `singleLineFenceContent`. Matches
116
+ // `@lexical/markdown`, and matches CommonMark, which forbids a backtick
117
+ // inside the info string.
118
+ const inline = singleLineFenceContent(fence, rest);
119
+ if (inline !== null) {
120
+ $appendCodeNode(rootNode, null, [inline]);
121
+ return [true, startLineIndex];
122
+ }
123
+ const language = normalizeCodeInfo(rest);
124
+ const fenceEnd = fenceEndFor(fence.length);
125
+ for (let i = startLineIndex + 1; i < lines.length; i++) {
126
+ if (fenceEnd.test(lines[i])) {
127
+ $appendCodeNode(rootNode, language, lines.slice(startLineIndex + 1, i));
128
+ return [true, i];
129
+ }
130
+ }
131
+ // Unterminated fence: consume to the end of the document.
132
+ $appendCodeNode(rootNode, language, lines.slice(startLineIndex + 1));
133
+ return [true, lines.length - 1];
134
+ },
135
+ regExpEnd: { optional: true, regExp: /^[ \t]*`{3,}[ \t]*$/ },
136
+ regExpStart: FENCE_START,
137
+ // Import is fully handled above; `replace` serves the typed-shortcut path
138
+ // (`registerMarkdownShortcuts` calls it with the trailing siblings) and the
139
+ // paste path (`children === null`, lines supplied).
140
+ replace: (rootNode, children, startMatch, _endMatch, linesInBetween, isImport) => {
141
+ const fence = (startMatch[1] ?? '```').trim();
142
+ const rest = startMatch[2] ?? '';
143
+ // The SAME single-line rule the import path applies, so the two agree.
144
+ const inline = singleLineFenceContent(fence, rest);
145
+ if (inline !== null) {
146
+ const node = $createCodeNode();
147
+ if (inline !== '')
148
+ node.append($createTextNode(inline));
149
+ rootNode.replace(node);
150
+ if (!isImport)
151
+ node.select(0, 0);
152
+ return;
153
+ }
154
+ const language = normalizeCodeInfo(rest);
155
+ if (!children) {
156
+ $appendCodeNode(rootNode, language, linesInBetween ?? []);
157
+ return;
158
+ }
159
+ const node = $createCodeNode(language ?? undefined);
160
+ node.append(...children);
161
+ rootNode.replace(node);
162
+ if (!isImport)
163
+ node.select(0, 0);
164
+ },
165
+ type: 'multiline-element',
166
+ };
167
+ //# sourceMappingURL=code.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"code.js","sourceRoot":"","sources":["../../src/transformers/code.ts"],"names":[],"mappings":"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"}
@@ -1 +1 @@
1
- {"version":3,"file":"gfm.d.ts","sourceRoot":"","sources":["../../src/transformers/gfm.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAOjD,OAAO,EACL,KAAK,WAAW,EAiBjB,MAAM,mBAAmB,CAAA;AAE1B,wDAAwD;AACxD,eAAO,MAAM,SAAS,EAAE,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,CAQvD,CAAA;AAED;;qFAEqF;AACrF,eAAO,MAAM,qBAAqB,EAAE,WAAuB,CAAA;AAE3D;;;;;;gDAMgD;AAChD,eAAO,MAAM,wBAAwB,EAAE,SAAS,WAAW,EAS1D,CAAA;AAED,yDAAyD;AACzD,eAAO,MAAM,gBAAgB,EAAE,SAAS,WAAW,EAWlD,CAAA"}
1
+ {"version":3,"file":"gfm.d.ts","sourceRoot":"","sources":["../../src/transformers/gfm.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAA;AAOjD,OAAO,EACL,KAAK,WAAW,EAgBjB,MAAM,mBAAmB,CAAA;AAG1B,wDAAwD;AACxD,eAAO,MAAM,SAAS,EAAE,aAAa,CAAC,KAAK,CAAC,WAAW,CAAC,CAQvD,CAAA;AAED;;qFAEqF;AACrF,eAAO,MAAM,qBAAqB,EAAE,WAAuB,CAAA;AAE3D;;;;;;gDAMgD;AAChD,eAAO,MAAM,wBAAwB,EAAE,SAAS,WAAW,EAS1D,CAAA;AAED,yDAAyD;AACzD,eAAO,MAAM,gBAAgB,EAAE,SAAS,WAAW,EAelD,CAAA"}
@@ -6,7 +6,8 @@ import { LinkNode } from '@lexical/link';
6
6
  // `@lexical/code-core` (not `@lexical/code`) keeps Prism out of the bundle — we
7
7
  // never register syntax highlighting, so plain CodeNode is all we need.
8
8
  import { CodeNode, CodeHighlightNode } from '@lexical/code-core';
9
- import { HEADING, QUOTE, UNORDERED_LIST, ORDERED_LIST, CHECK_LIST, CODE, BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, BOLD_STAR, BOLD_UNDERSCORE, ITALIC_STAR, ITALIC_UNDERSCORE, STRIKETHROUGH, INLINE_CODE, HIGHLIGHT, LINK, } from '@lexical/markdown';
9
+ import { HEADING, QUOTE, UNORDERED_LIST, ORDERED_LIST, CHECK_LIST, BOLD_ITALIC_STAR, BOLD_ITALIC_UNDERSCORE, BOLD_STAR, BOLD_UNDERSCORE, ITALIC_STAR, ITALIC_UNDERSCORE, STRIKETHROUGH, INLINE_CODE, HIGHLIGHT, LINK, } from '@lexical/markdown';
10
+ import { CODE_INFO_TRANSFORMER } from './code.js';
10
11
  /** Node classes required to render the GFM superset. */
11
12
  export const GFM_NODES = [
12
13
  HeadingNode,
@@ -47,7 +48,11 @@ export const GFM_TRANSFORMERS = [
47
48
  CHECK_LIST,
48
49
  UNORDERED_LIST,
49
50
  ORDERED_LIST,
50
- CODE,
51
+ // NOT `@lexical/markdown`'s `CODE`: that one captures the info string as a
52
+ // single `[\w-]+` token and pushes the remainder of the fence line into the
53
+ // code body, silently corrupting ```c++ and ```lance table. See
54
+ // `transformers/code.ts`.
55
+ CODE_INFO_TRANSFORMER,
51
56
  ...INLINE_TEXT_TRANSFORMERS,
52
57
  LINK,
53
58
  ];
@@ -1 +1 @@
1
- {"version":3,"file":"gfm.js","sourceRoot":"","sources":["../../src/transformers/gfm.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,kFAAkF;AAGlF,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,gFAAgF;AAChF,wEAAwE;AACxE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,EAEL,OAAO,EACP,KAAK,EACL,cAAc,EACd,YAAY,EACZ,UAAU,EACV,IAAI,EACJ,gBAAgB,EAChB,sBAAsB,EACtB,SAAS,EACT,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,SAAS,EACT,IAAI,GACL,MAAM,mBAAmB,CAAA;AAE1B,wDAAwD;AACxD,MAAM,CAAC,MAAM,SAAS,GAAsC;IAC1D,WAAW;IACX,SAAS;IACT,QAAQ;IACR,YAAY;IACZ,QAAQ;IACR,iBAAiB;IACjB,QAAQ;CACT,CAAA;AAED;;qFAEqF;AACrF,MAAM,CAAC,MAAM,qBAAqB,GAAgB,SAAS,CAAA;AAE3D;;;;;;gDAMgD;AAChD,MAAM,CAAC,MAAM,wBAAwB,GAA2B;IAC9D,gBAAgB;IAChB,sBAAsB;IACtB,SAAS;IACT,eAAe;IACf,WAAW;IACX,iBAAiB;IACjB,aAAa;IACb,WAAW;CACZ,CAAA;AAED,yDAAyD;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAA2B;IACtD,OAAO;IACP,KAAK;IACL,4EAA4E;IAC5E,2EAA2E;IAC3E,UAAU;IACV,cAAc;IACd,YAAY;IACZ,IAAI;IACJ,GAAG,wBAAwB;IAC3B,IAAI;CACL,CAAA"}
1
+ {"version":3,"file":"gfm.js","sourceRoot":"","sources":["../../src/transformers/gfm.ts"],"names":[],"mappings":"AAAA,8EAA8E;AAC9E,kFAAkF;AAGlF,OAAO,EAAE,WAAW,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAA;AAC3D,OAAO,EAAE,QAAQ,EAAE,YAAY,EAAE,MAAM,eAAe,CAAA;AACtD,OAAO,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAA;AACxC,gFAAgF;AAChF,wEAAwE;AACxE,OAAO,EAAE,QAAQ,EAAE,iBAAiB,EAAE,MAAM,oBAAoB,CAAA;AAChE,OAAO,EAEL,OAAO,EACP,KAAK,EACL,cAAc,EACd,YAAY,EACZ,UAAU,EACV,gBAAgB,EAChB,sBAAsB,EACtB,SAAS,EACT,eAAe,EACf,WAAW,EACX,iBAAiB,EACjB,aAAa,EACb,WAAW,EACX,SAAS,EACT,IAAI,GACL,MAAM,mBAAmB,CAAA;AAC1B,OAAO,EAAE,qBAAqB,EAAE,MAAM,WAAW,CAAA;AAEjD,wDAAwD;AACxD,MAAM,CAAC,MAAM,SAAS,GAAsC;IAC1D,WAAW;IACX,SAAS;IACT,QAAQ;IACR,YAAY;IACZ,QAAQ;IACR,iBAAiB;IACjB,QAAQ;CACT,CAAA;AAED;;qFAEqF;AACrF,MAAM,CAAC,MAAM,qBAAqB,GAAgB,SAAS,CAAA;AAE3D;;;;;;gDAMgD;AAChD,MAAM,CAAC,MAAM,wBAAwB,GAA2B;IAC9D,gBAAgB;IAChB,sBAAsB;IACtB,SAAS;IACT,eAAe;IACf,WAAW;IACX,iBAAiB;IACjB,aAAa;IACb,WAAW;CACZ,CAAA;AAED,yDAAyD;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAA2B;IACtD,OAAO;IACP,KAAK;IACL,4EAA4E;IAC5E,2EAA2E;IAC3E,UAAU;IACV,cAAc;IACd,YAAY;IACZ,2EAA2E;IAC3E,4EAA4E;IAC5E,gEAAgE;IAChE,0BAA0B;IAC1B,qBAAqB;IACrB,GAAG,wBAAwB;IAC3B,IAAI;CACL,CAAA"}
@@ -1,5 +1,8 @@
1
1
  import type { Transformer } from '@lexical/markdown';
2
2
  import type { MarkdownPlugin } from '../plugins/types.js';
3
+ /** Declare that `transformer` must be consulted before same-rank peers with a
4
+ * higher value. Call at module scope, beside the transformer's definition. */
5
+ export declare function setTransformerPrecedence(transformer: Transformer, value: number): void;
3
6
  /** Stable-sort transformers into the order Lexical expects. */
4
7
  export declare function orderTransformers(transformers: readonly Transformer[]): Transformer[];
5
8
  /** Collect every plugin's transformers (de-duplicated by reference) and order
@@ -1 +1 @@
1
- {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/transformers/registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AAYzD,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAWrF;AAED;;kCAEkC;AAClC,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,GAAG,WAAW,EAAE,CAYnF"}
1
+ {"version":3,"file":"registry.d.ts","sourceRoot":"","sources":["../../src/transformers/registry.ts"],"names":[],"mappings":"AAIA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAA;AA4BzD;8EAC8E;AAC9E,wBAAgB,wBAAwB,CAAC,WAAW,EAAE,WAAW,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAEtF;AAED,+DAA+D;AAC/D,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,SAAS,WAAW,EAAE,GAAG,WAAW,EAAE,CAgBrF;AAED;;kCAEkC;AAClC,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,SAAS,cAAc,EAAE,GAAG,WAAW,EAAE,CAYnF"}
@@ -10,6 +10,26 @@ const TYPE_RANK = {
10
10
  'text-format': 2,
11
11
  'text-match': 3,
12
12
  };
13
+ /**
14
+ * Explicit ordering between transformers of the SAME rank.
15
+ *
16
+ * Within a rank Lexical falls back to array position, which for text-match
17
+ * transformers means `findOutermostTextMatchTransformer` breaks a tie — two
18
+ * transformers matching at the SAME start index — by plugin array order. That
19
+ * makes round-trip fidelity depend on the order a consumer happens to list
20
+ * plugins in, which is exactly the failure mode the registry exists to prevent.
21
+ *
22
+ * A transformer that must be consulted ahead of a same-rank peer declares a
23
+ * LOWER precedence here (default 0). This is a side table rather than a field on
24
+ * the transformer because the colliding peers include upstream's `LINK`, which
25
+ * this package does not own and cannot annotate.
26
+ */
27
+ const precedence = new WeakMap();
28
+ /** Declare that `transformer` must be consulted before same-rank peers with a
29
+ * higher value. Call at module scope, beside the transformer's definition. */
30
+ export function setTransformerPrecedence(transformer, value) {
31
+ precedence.set(transformer, value);
32
+ }
13
33
  /** Stable-sort transformers into the order Lexical expects. */
14
34
  export function orderTransformers(transformers) {
15
35
  return [...transformers].sort((a, b) => {
@@ -21,6 +41,12 @@ export function orderTransformers(transformers) {
21
41
  // Longer trigger (`***`) before shorter (`**`, `*`).
22
42
  return b.tag.length - a.tag.length;
23
43
  }
44
+ const pa = precedence.get(a) ?? 0;
45
+ const pb = precedence.get(b) ?? 0;
46
+ if (pa !== pb)
47
+ return pa - pb;
48
+ // Equal precedence: `Array.prototype.sort` is specified stable, so the
49
+ // plugin array order is preserved — the documented default.
24
50
  return 0;
25
51
  });
26
52
  }
@@ -1 +1 @@
1
- {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/transformers/registry.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,+EAA+E;AAC/E,kDAAkD;AAKlD,6EAA6E;AAC7E,6EAA6E;AAC7E,mEAAmE;AACnE,MAAM,SAAS,GAA2B;IACxC,mBAAmB,EAAE,CAAC;IACtB,OAAO,EAAE,CAAC;IACV,aAAa,EAAE,CAAC;IAChB,YAAY,EAAE,CAAC;CAChB,CAAA;AAED,+DAA+D;AAC/D,MAAM,UAAU,iBAAiB,CAAC,YAAoC;IACpE,OAAO,CAAC,GAAG,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACrC,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,GAAG,EAAE,CAAA;QAC7B,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACzD,qDAAqD;YACrD,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAA;QACpC,CAAC;QACD,OAAO,CAAC,CAAA;IACV,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;kCAEkC;AAClC,MAAM,UAAU,iBAAiB,CAAC,OAAkC;IAClE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAe,CAAA;IACnC,MAAM,SAAS,GAAkB,EAAE,CAAA;IACnC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;YACpD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;gBACrB,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAA;AACrC,CAAC"}
1
+ {"version":3,"file":"registry.js","sourceRoot":"","sources":["../../src/transformers/registry.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,+EAA+E;AAC/E,kDAAkD;AAKlD,6EAA6E;AAC7E,6EAA6E;AAC7E,mEAAmE;AACnE,MAAM,SAAS,GAA2B;IACxC,mBAAmB,EAAE,CAAC;IACtB,OAAO,EAAE,CAAC;IACV,aAAa,EAAE,CAAC;IAChB,YAAY,EAAE,CAAC;CAChB,CAAA;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,GAAG,IAAI,OAAO,EAAuB,CAAA;AAErD;8EAC8E;AAC9E,MAAM,UAAU,wBAAwB,CAAC,WAAwB,EAAE,KAAa;IAC9E,UAAU,CAAC,GAAG,CAAC,WAAW,EAAE,KAAK,CAAC,CAAA;AACpC,CAAC;AAED,+DAA+D;AAC/D,MAAM,UAAU,iBAAiB,CAAC,YAAoC;IACpE,OAAO,CAAC,GAAG,YAAY,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,CAAC,EAAE,EAAE;QACrC,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,MAAM,EAAE,GAAG,SAAS,CAAC,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,GAAG,EAAE,CAAA;QAC7B,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,IAAI,CAAC,CAAC,IAAI,KAAK,aAAa,EAAE,CAAC;YACzD,qDAAqD;YACrD,OAAO,CAAC,CAAC,GAAG,CAAC,MAAM,GAAG,CAAC,CAAC,GAAG,CAAC,MAAM,CAAA;QACpC,CAAC;QACD,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QACjC,MAAM,EAAE,GAAG,UAAU,CAAC,GAAG,CAAC,CAAC,CAAC,IAAI,CAAC,CAAA;QACjC,IAAI,EAAE,KAAK,EAAE;YAAE,OAAO,EAAE,GAAG,EAAE,CAAA;QAC7B,uEAAuE;QACvE,4DAA4D;QAC5D,OAAO,CAAC,CAAA;IACV,CAAC,CAAC,CAAA;AACJ,CAAC;AAED;;kCAEkC;AAClC,MAAM,UAAU,iBAAiB,CAAC,OAAkC;IAClE,MAAM,IAAI,GAAG,IAAI,GAAG,EAAe,CAAA;IACnC,MAAM,SAAS,GAAkB,EAAE,CAAA;IACnC,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;QAC7B,KAAK,MAAM,WAAW,IAAI,MAAM,CAAC,YAAY,IAAI,EAAE,EAAE,CAAC;YACpD,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,EAAE,CAAC;gBAC3B,IAAI,CAAC,GAAG,CAAC,WAAW,CAAC,CAAA;gBACrB,SAAS,CAAC,IAAI,CAAC,WAAW,CAAC,CAAA;YAC7B,CAAC;QACH,CAAC;IACH,CAAC;IACD,OAAO,iBAAiB,CAAC,SAAS,CAAC,CAAA;AACrC,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@llui/markdown-editor",
3
- "version": "0.3.0",
3
+ "version": "0.4.0",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -21,43 +21,57 @@
21
21
  "types": "./dist/surfaces/toolbar.d.ts",
22
22
  "import": "./dist/surfaces/toolbar.js"
23
23
  },
24
- "./styles/editor.css": "./dist/styles/editor.css"
24
+ "./plugins/block-drag": {
25
+ "types": "./dist/plugins/block-drag.d.ts",
26
+ "import": "./dist/plugins/block-drag.js"
27
+ },
28
+ "./plugins/code-language": {
29
+ "types": "./dist/plugins/code-language.d.ts",
30
+ "import": "./dist/plugins/code-language.js"
31
+ },
32
+ "./plugins/wikilink": {
33
+ "types": "./dist/plugins/wikilink.d.ts",
34
+ "import": "./dist/plugins/wikilink.js"
35
+ },
36
+ "./styles/editor.css": "./dist/styles/editor.css",
37
+ "./styles/block-drag.css": "./dist/styles/block-drag.css"
25
38
  },
26
39
  "peerDependencies": {
27
- "lexical": "^0.46.0",
28
- "@lexical/markdown": "^0.46.0",
29
- "@lexical/rich-text": "^0.46.0",
30
- "@lexical/list": "^0.46.0",
31
- "@lexical/link": "^0.46.0",
32
- "@lexical/table": "^0.46.0",
33
- "@lexical/selection": "^0.46.0",
34
- "@lexical/utils": "^0.46.0",
35
- "@lexical/code-core": "^0.46.0",
40
+ "lexical": "^0.48.0",
41
+ "@lexical/markdown": "^0.48.0",
42
+ "@lexical/rich-text": "^0.48.0",
43
+ "@lexical/list": "^0.48.0",
44
+ "@lexical/link": "^0.48.0",
45
+ "@lexical/table": "^0.48.0",
46
+ "@lexical/selection": "^0.48.0",
47
+ "@lexical/utils": "^0.48.0",
48
+ "@lexical/code-core": "^0.48.0",
36
49
  "@llui/dom": "^0.12.0",
50
+ "@llui/lexical": "^0.4.0",
37
51
  "@llui/markdown": "^0.12.0",
38
- "@llui/lexical": "^0.3.0",
39
52
  "@llui/components": "^0.13.0"
40
53
  },
41
54
  "devDependencies": {
42
- "lexical": "^0.46.0",
43
- "@lexical/headless": "^0.46.0",
44
- "@lexical/markdown": "^0.46.0",
45
- "@lexical/rich-text": "^0.46.0",
46
- "@lexical/list": "^0.46.0",
47
- "@lexical/link": "^0.46.0",
48
- "@lexical/table": "^0.46.0",
49
- "@lexical/selection": "^0.46.0",
50
- "@lexical/utils": "^0.46.0",
55
+ "lexical": "^0.48.0",
56
+ "@lexical/headless": "^0.48.0",
57
+ "@lexical/markdown": "^0.48.0",
58
+ "@lexical/rich-text": "^0.48.0",
59
+ "@lexical/list": "^0.48.0",
60
+ "@lexical/link": "^0.48.0",
61
+ "@lexical/table": "^0.48.0",
62
+ "@lexical/selection": "^0.48.0",
63
+ "@lexical/utils": "^0.48.0",
51
64
  "typescript": "^6.0.0",
52
65
  "vitest": "^4.1.2",
53
- "@lexical/code-core": "^0.46.0",
66
+ "@lexical/code-core": "^0.48.0",
54
67
  "@llui/dom": "0.12.0",
55
- "@llui/markdown": "0.12.0",
56
68
  "@llui/components": "0.13.0",
57
- "@llui/lexical": "0.3.0"
69
+ "@llui/markdown": "0.12.0",
70
+ "@llui/lexical": "0.4.0"
58
71
  },
59
72
  "sideEffects": [
60
- "./dist/styles/editor.css"
73
+ "./dist/styles/editor.css",
74
+ "./dist/styles/block-drag.css"
61
75
  ],
62
76
  "description": "WYSIWYG Markdown editor for LLui — hides Markdown behind a rich, pluggable editing widget built on Lexical",
63
77
  "keywords": [
@@ -87,7 +101,7 @@
87
101
  "access": "public"
88
102
  },
89
103
  "scripts": {
90
- "build": "tsc -p tsconfig.build.json && mkdir -p dist/styles && cp src/styles/editor.css dist/styles/",
104
+ "build": "tsc -p tsconfig.build.json && mkdir -p dist/styles && cp src/styles/*.css dist/styles/",
91
105
  "check": "tsc --noEmit -p tsconfig.check.json",
92
106
  "lint": "eslint src",
93
107
  "test": "vitest run"
package/src/editor.ts CHANGED
@@ -83,10 +83,23 @@ export interface EditorConfig {
83
83
  }
84
84
 
85
85
  /** Disposer-returning binding the collab layer installs on the live editor.
86
- * `@llui/lexical-collab`'s `YjsCollab` satisfies this structurally, so
87
- * `@llui/markdown-editor` needs no Yjs dependency of its own. */
86
+ * `@llui/lexical-collab`'s `YjsCollab` and `@llui/lexical-loro`'s `LoroCollab`
87
+ * both satisfy this structurally, so `@llui/markdown-editor` needs neither a Yjs
88
+ * nor a Loro dependency of its own. */
88
89
  export interface CollabBinding {
89
90
  register: (editor: LexicalEditor) => () => void
91
+ /**
92
+ * A CRDT-aware undo owner, if the binding provides one SEPARATELY from
93
+ * `register`. When present it is handed to `lexicalForeign({ externalUndo })`,
94
+ * which forces the built-in `@lexical/history` stack off so the two can never
95
+ * both be live — this is what gives collab mode real, peer-scoped undo.
96
+ *
97
+ * Optional because not every binding splits undo out this way: `yjsCollab`
98
+ * installs its own undo commands INSIDE `register`, so it leaves this unset and
99
+ * still owns undo. A binding that sets neither would leave the editor with no
100
+ * undo at all — see `@llui/lexical-loro`, which sets this.
101
+ */
102
+ externalUndo?: (editor: LexicalEditor) => () => void
90
103
  }
91
104
 
92
105
  /** Hooks the editor injects into the {@link CollabFactory}: a markdown `seed`
@@ -291,8 +304,18 @@ export function markdownEditor(
291
304
  },
292
305
  // In collab mode the shared CRDT owns the document: the local undo stack
293
306
  // and the boot-time seed are disabled — the binding supplies a scoped undo
294
- // manager and a sync-gated bootstrap instead.
295
- ...(collabBinding ? { history: false, seedMode: 'deferred' as const } : {}),
307
+ // manager and a sync-gated bootstrap instead. A binding that owns undo via
308
+ // a SEPARATE `externalUndo` (Loro) has it wired here; one that owns undo
309
+ // inside its own `register` (Yjs) leaves the field unset. Either way the
310
+ // built-in history is off, so a binding that forgot both would show as "no
311
+ // undo" rather than fighting a local stack.
312
+ ...(collabBinding
313
+ ? {
314
+ history: false as const,
315
+ seedMode: 'deferred' as const,
316
+ ...(collabBinding.externalUndo ? { externalUndo: collabBinding.externalUndo } : {}),
317
+ }
318
+ : {}),
296
319
  defaultValue: collabBinding || config.value ? undefined : (config.defaultValue ?? ''),
297
320
  ...(config.value && !collabBinding ? { value: config.value } : {}),
298
321
  readonly: state.at('readonly'),
package/src/index.ts CHANGED
@@ -57,6 +57,17 @@ export {
57
57
  $insertCallout,
58
58
  } from './plugins/callout.js'
59
59
  export { hrPlugin, $insertHorizontalRule } from './plugins/hr.js'
60
+ export {
61
+ type FrontmatterData,
62
+ type FrontmatterPluginOptions,
63
+ FRONTMATTER_BRIDGE_TYPE,
64
+ FRONTMATTER_TRANSFORMER,
65
+ frontmatterPlugin,
66
+ serializeFrontmatter,
67
+ splitFrontmatter,
68
+ $getFrontmatter,
69
+ $setFrontmatter,
70
+ } from './plugins/frontmatter.js'
60
71
  export { slashPlugin } from './plugins/slash.js'
61
72
  export { contextMenuPlugin } from './plugins/context-menu.js'
62
73
  export { floatingToolbarPlugin } from './plugins/floating-toolbar.js'
@@ -66,11 +77,57 @@ export { type Mention, type MentionPluginOptions, mentionPlugin } from './plugin
66
77
  export { type EmojiPluginOptions, DEFAULT_EMOJI, emojiPlugin } from './plugins/emoji.js'
67
78
  export { type ImagePluginOptions, imagePlugin } from './plugins/image.js'
68
79
  export { tablePlugin } from './plugins/table.js'
80
+ export {
81
+ type CodeLanguagePluginOptions,
82
+ type CodeLanguageState,
83
+ type CodeLanguageMsg,
84
+ type CodeLanguageEffect,
85
+ CODE_LANGUAGE_PLUGIN,
86
+ codeLanguagePlugin,
87
+ } from './plugins/code-language.js'
88
+ export {
89
+ type WikiLink,
90
+ type WikiLinkPluginOptions,
91
+ type SerializedWikiLinkNode,
92
+ WikiLinkNode,
93
+ $createWikiLinkNode,
94
+ $isWikiLinkNode,
95
+ parseWikiLinkInner,
96
+ formatWikiLink,
97
+ // The representability guards that make `formatWikiLink` the true inverse of
98
+ // `parseWikiLinkInner`; exported so a host resolving/creating targets can
99
+ // apply the same normalization before it hands one to `$createWikiLinkNode`.
100
+ sanitizeWikiLinkTarget,
101
+ sanitizeWikiLinkAlias,
102
+ wikilinkPlugin,
103
+ } from './plugins/wikilink.js'
104
+ export {
105
+ type BlockDragOptions,
106
+ type BlockRect,
107
+ type DropTarget,
108
+ type IndicatorRect,
109
+ type Place,
110
+ BLOCK_DRAG_Z,
111
+ blockAtPoint,
112
+ findDropTarget,
113
+ indicatorRect,
114
+ blockDragPlugin,
115
+ } from './plugins/block-drag.js'
69
116
 
70
117
  export { $insertMarkdownAtSelection, registerMarkdownPaste } from './paste.js'
71
118
 
72
119
  export { GFM_NODES, GFM_TRANSFORMERS, HIGHLIGHT_TRANSFORMER } from './transformers/gfm.js'
73
- export { buildTransformers, orderTransformers } from './transformers/registry.js'
120
+ // The CommonMark-correct fenced-code transformer. Already part of
121
+ // `GFM_TRANSFORMERS`; exported for consumers assembling a transformer set by hand.
122
+ export { CODE_INFO_TRANSFORMER, normalizeCodeInfo } from './transformers/code.js'
123
+ // `setTransformerPrecedence` breaks ties between SAME-rank transformers, so a
124
+ // collision (e.g. wikilink vs upstream LINK, which both match at the same index)
125
+ // is resolved structurally instead of by the order a consumer lists plugins in.
126
+ export {
127
+ buildTransformers,
128
+ orderTransformers,
129
+ setTransformerPrecedence,
130
+ } from './transformers/registry.js'
74
131
 
75
132
  export { computeFormatState } from './format.js'
76
133