@llui/markdown-editor 0.1.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 (104) hide show
  1. package/LICENSE +21 -0
  2. package/dist/__llui_deps.json +252 -0
  3. package/dist/editor.d.ts +42 -0
  4. package/dist/editor.d.ts.map +1 -0
  5. package/dist/editor.js +157 -0
  6. package/dist/editor.js.map +1 -0
  7. package/dist/effects.d.ts +17 -0
  8. package/dist/effects.d.ts.map +1 -0
  9. package/dist/effects.js +33 -0
  10. package/dist/effects.js.map +1 -0
  11. package/dist/format.d.ts +6 -0
  12. package/dist/format.d.ts.map +1 -0
  13. package/dist/format.js +51 -0
  14. package/dist/format.js.map +1 -0
  15. package/dist/index.d.ts +23 -0
  16. package/dist/index.d.ts.map +1 -0
  17. package/dist/index.js +24 -0
  18. package/dist/index.js.map +1 -0
  19. package/dist/plugins/callout.d.ts +15 -0
  20. package/dist/plugins/callout.d.ts.map +1 -0
  21. package/dist/plugins/callout.js +151 -0
  22. package/dist/plugins/callout.js.map +1 -0
  23. package/dist/plugins/context-menu.d.ts +3 -0
  24. package/dist/plugins/context-menu.d.ts.map +1 -0
  25. package/dist/plugins/context-menu.js +93 -0
  26. package/dist/plugins/context-menu.js.map +1 -0
  27. package/dist/plugins/core.d.ts +7 -0
  28. package/dist/plugins/core.d.ts.map +1 -0
  29. package/dist/plugins/core.js +189 -0
  30. package/dist/plugins/core.js.map +1 -0
  31. package/dist/plugins/emoji.d.ts +9 -0
  32. package/dist/plugins/emoji.d.ts.map +1 -0
  33. package/dist/plugins/emoji.js +50 -0
  34. package/dist/plugins/emoji.js.map +1 -0
  35. package/dist/plugins/floating-toolbar.d.ts +3 -0
  36. package/dist/plugins/floating-toolbar.d.ts.map +1 -0
  37. package/dist/plugins/floating-toolbar.js +137 -0
  38. package/dist/plugins/floating-toolbar.js.map +1 -0
  39. package/dist/plugins/hr.d.ts +5 -0
  40. package/dist/plugins/hr.d.ts.map +1 -0
  41. package/dist/plugins/hr.js +46 -0
  42. package/dist/plugins/hr.js.map +1 -0
  43. package/dist/plugins/image.d.ts +8 -0
  44. package/dist/plugins/image.d.ts.map +1 -0
  45. package/dist/plugins/image.js +173 -0
  46. package/dist/plugins/image.js.map +1 -0
  47. package/dist/plugins/link.d.ts +7 -0
  48. package/dist/plugins/link.d.ts.map +1 -0
  49. package/dist/plugins/link.js +100 -0
  50. package/dist/plugins/link.js.map +1 -0
  51. package/dist/plugins/math.d.ts +8 -0
  52. package/dist/plugins/math.d.ts.map +1 -0
  53. package/dist/plugins/math.js +81 -0
  54. package/dist/plugins/math.js.map +1 -0
  55. package/dist/plugins/mention.d.ts +11 -0
  56. package/dist/plugins/mention.d.ts.map +1 -0
  57. package/dist/plugins/mention.js +163 -0
  58. package/dist/plugins/mention.js.map +1 -0
  59. package/dist/plugins/mermaid.d.ts +8 -0
  60. package/dist/plugins/mermaid.d.ts.map +1 -0
  61. package/dist/plugins/mermaid.js +92 -0
  62. package/dist/plugins/mermaid.js.map +1 -0
  63. package/dist/plugins/overlay.d.ts +46 -0
  64. package/dist/plugins/overlay.d.ts.map +1 -0
  65. package/dist/plugins/overlay.js +83 -0
  66. package/dist/plugins/overlay.js.map +1 -0
  67. package/dist/plugins/slash.d.ts +3 -0
  68. package/dist/plugins/slash.d.ts.map +1 -0
  69. package/dist/plugins/slash.js +167 -0
  70. package/dist/plugins/slash.js.map +1 -0
  71. package/dist/plugins/table.d.ts +3 -0
  72. package/dist/plugins/table.d.ts.map +1 -0
  73. package/dist/plugins/table.js +227 -0
  74. package/dist/plugins/table.js.map +1 -0
  75. package/dist/plugins/types.d.ts +44 -0
  76. package/dist/plugins/types.d.ts.map +1 -0
  77. package/dist/plugins/types.js +4 -0
  78. package/dist/plugins/types.js.map +1 -0
  79. package/dist/plugins/ui.d.ts +44 -0
  80. package/dist/plugins/ui.d.ts.map +1 -0
  81. package/dist/plugins/ui.js +34 -0
  82. package/dist/plugins/ui.js.map +1 -0
  83. package/dist/state.d.ts +105 -0
  84. package/dist/state.d.ts.map +1 -0
  85. package/dist/state.js +100 -0
  86. package/dist/state.js.map +1 -0
  87. package/dist/styles/editor.css +517 -0
  88. package/dist/surfaces/link-dialog.d.ts +19 -0
  89. package/dist/surfaces/link-dialog.d.ts.map +1 -0
  90. package/dist/surfaces/link-dialog.js +45 -0
  91. package/dist/surfaces/link-dialog.js.map +1 -0
  92. package/dist/surfaces/toolbar.d.ts +48 -0
  93. package/dist/surfaces/toolbar.d.ts.map +1 -0
  94. package/dist/surfaces/toolbar.js +134 -0
  95. package/dist/surfaces/toolbar.js.map +1 -0
  96. package/dist/transformers/gfm.d.ts +7 -0
  97. package/dist/transformers/gfm.d.ts.map +1 -0
  98. package/dist/transformers/gfm.js +41 -0
  99. package/dist/transformers/gfm.js.map +1 -0
  100. package/dist/transformers/registry.d.ts +9 -0
  101. package/dist/transformers/registry.d.ts.map +1 -0
  102. package/dist/transformers/registry.js +43 -0
  103. package/dist/transformers/registry.js.map +1 -0
  104. package/package.json +89 -0
@@ -0,0 +1,134 @@
1
+ // Toolbar surface. `connectToolbar` returns a parts bag (the @llui/components
2
+ // idiom) driven reactively by the format signal; `toolbar` is a ready-made
3
+ // grouped toolbar Mountable for consumers who don't want to hand-render buttons.
4
+ import { button, div, option, select, span, text, tagSend, unsafeHtml, } from '@llui/dom';
5
+ /** Build reactive toolbar parts from the format signal. Spread `item(id)` onto a
6
+ * `<button>`; `aria-pressed` / `data-active` / `disabled` track the format. */
7
+ export function connectToolbar(format, send, items) {
8
+ const byId = new Map(items.map((i) => [i.id, i]));
9
+ return {
10
+ root: {
11
+ role: 'toolbar',
12
+ 'aria-label': 'Formatting',
13
+ 'data-scope': 'md-toolbar',
14
+ 'data-part': 'root',
15
+ },
16
+ item: (id) => {
17
+ const item = byId.get(id);
18
+ const label = item?.label ?? id;
19
+ return {
20
+ type: 'button',
21
+ 'data-scope': 'md-toolbar',
22
+ 'data-part': 'item',
23
+ 'data-id': id,
24
+ 'aria-label': label,
25
+ title: label,
26
+ 'aria-pressed': format.map((f) => (item?.isActive?.(f) ? 'true' : 'false')),
27
+ 'aria-disabled': format.map((f) => (item?.isDisabled?.(f) ? 'true' : undefined)),
28
+ disabled: format.map((f) => item?.isDisabled?.(f) ?? false),
29
+ 'data-active': format.map((f) => (item?.isActive?.(f) ? '' : undefined)),
30
+ onClick: tagSend(send, ['runCommand'], () => send({ type: 'runCommand', id })),
31
+ };
32
+ },
33
+ };
34
+ }
35
+ /** Inline SVG icon (monochrome, inherits `currentColor`) used where a glyph
36
+ * reads poorly. A glyph string starting with `<svg` is rendered as markup. */
37
+ const LINK_ICON = '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><path d="M9.5 13.5a3.5 3.5 0 0 0 5 0l3-3a3.5 3.5 0 1 0-5-5l-1 1"/><path d="M14.5 10.5a3.5 3.5 0 0 0-5 0l-3 3a3.5 3.5 0 1 0 5 5l1-1"/></svg>';
38
+ const IMAGE_ICON = '<svg viewBox="0 0 24 24" width="15" height="15" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><circle cx="9" cy="9.5" r="1.6"/><path d="m4 18 5-5 4 4 3-3 4 4"/></svg>';
39
+ /** Compact glyphs so the default toolbar reads as a real toolbar without icon
40
+ * assets. SVG strings render as icons; everything else as text. Override via
41
+ * `ToolbarOptions.glyphs`. */
42
+ export const DEFAULT_GLYPHS = {
43
+ bold: 'B',
44
+ italic: 'I',
45
+ strikethrough: 'S',
46
+ code: '</>',
47
+ link: LINK_ICON,
48
+ image: IMAGE_ICON,
49
+ hr: '—',
50
+ table: '▦',
51
+ paragraph: '¶',
52
+ h1: 'H1',
53
+ h2: 'H2',
54
+ h3: 'H3',
55
+ quote: '❝',
56
+ codeBlock: '{ }',
57
+ bulletList: '•',
58
+ numberList: '1.',
59
+ checkList: '☑',
60
+ undo: '↺',
61
+ redo: '↻',
62
+ };
63
+ const GROUP_ORDER = ['inline', 'block', 'list', 'insert', 'history'];
64
+ function groupItems(items) {
65
+ const groups = new Map();
66
+ for (const item of items) {
67
+ const key = item.group ?? 'other';
68
+ const list = groups.get(key) ?? [];
69
+ list.push(item.id);
70
+ groups.set(key, list);
71
+ }
72
+ const ordered = [];
73
+ for (const key of GROUP_ORDER) {
74
+ const list = groups.get(key);
75
+ if (list)
76
+ ordered.push(list);
77
+ }
78
+ for (const [key, list] of groups) {
79
+ if (!GROUP_ORDER.includes(key))
80
+ ordered.push(list);
81
+ }
82
+ return ordered;
83
+ }
84
+ /** Render the `block` group as a native `<select>` of block types. */
85
+ function blockTypeSelect(format, send, blockItems) {
86
+ return select({
87
+ 'data-scope': 'md-toolbar',
88
+ 'data-part': 'block-select',
89
+ 'aria-label': 'Block type',
90
+ value: format.map((f) => blockItems.find((i) => i.isActive?.(f))?.id ?? ''),
91
+ onChange: (e) => {
92
+ const id = e.currentTarget.value;
93
+ if (id)
94
+ send({ type: 'runCommand', id });
95
+ },
96
+ }, [
97
+ option({ value: '', hidden: true }, [text('—')]),
98
+ ...blockItems.map((i) => option({ value: i.id }, [text(i.label)])),
99
+ ]);
100
+ }
101
+ /** A ready-made grouped toolbar. Items not surfaced to `'toolbar'` are dropped. */
102
+ export function toolbar(opts) {
103
+ const surfaceItems = opts.items.filter((i) => i.surfaces?.includes('toolbar') ?? true);
104
+ const byId = new Map(surfaceItems.map((i) => [i.id, i]));
105
+ const parts = connectToolbar(opts.format, opts.send, surfaceItems);
106
+ const glyphs = { ...DEFAULT_GLYPHS, ...(opts.glyphs ?? {}) };
107
+ const useBlockSelect = opts.blockSelect !== false && !opts.groups;
108
+ const blockItems = useBlockSelect ? surfaceItems.filter((i) => i.group === 'block') : [];
109
+ const buttonItems = useBlockSelect
110
+ ? surfaceItems.filter((i) => i.group !== 'block')
111
+ : surfaceItems;
112
+ const groups = opts.groups ?? groupItems(buttonItems);
113
+ const children = [];
114
+ if (blockItems.length > 0) {
115
+ children.push(div({ 'data-scope': 'md-toolbar', 'data-part': 'group' }, [
116
+ blockTypeSelect(opts.format, opts.send, blockItems),
117
+ ]));
118
+ }
119
+ for (const ids of groups) {
120
+ const present = ids.filter((id) => byId.has(id));
121
+ if (present.length === 0)
122
+ continue;
123
+ children.push(div({ 'data-scope': 'md-toolbar', 'data-part': 'group' }, present.map((id) => {
124
+ const item = byId.get(id);
125
+ const glyph = glyphs[id] ?? item.label;
126
+ const glyphNode = glyph.trimStart().startsWith('<svg') ? unsafeHtml(glyph) : text(glyph);
127
+ return button({ ...parts.item(id) }, [
128
+ span({ 'data-part': 'glyph', 'aria-hidden': 'true' }, [glyphNode]),
129
+ ]);
130
+ })));
131
+ }
132
+ return div({ ...parts.root, ...(opts['aria-label'] ? { 'aria-label': opts['aria-label'] } : {}) }, children);
133
+ }
134
+ //# sourceMappingURL=toolbar.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"toolbar.js","sourceRoot":"","sources":["../../src/surfaces/toolbar.ts"],"names":[],"mappings":"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","sourcesContent":["// Toolbar surface. `connectToolbar` returns a parts bag (the @llui/components\n// idiom) driven reactively by the format signal; `toolbar` is a ready-made\n// grouped toolbar Mountable for consumers who don't want to hand-render buttons.\n\nimport {\n button,\n div,\n option,\n select,\n span,\n text,\n tagSend,\n unsafeHtml,\n type Mountable,\n type Send,\n type Signal,\n} from '@llui/dom'\nimport type { CommandItem } from '../plugins/types.js'\nimport type { EditorMsg, FormatState } from '../state.js'\n\nexport interface ToolbarItemParts {\n type: 'button'\n 'data-scope': 'md-toolbar'\n 'data-part': 'item'\n 'data-id': string\n 'aria-label': string\n title: string\n 'aria-pressed': Signal<'true' | 'false'>\n 'aria-disabled': Signal<'true' | undefined>\n disabled: Signal<boolean>\n 'data-active': Signal<'' | undefined>\n onClick: (e: MouseEvent) => void\n}\n\nexport interface ToolbarParts {\n root: {\n role: 'toolbar'\n 'aria-label': string\n 'data-scope': 'md-toolbar'\n 'data-part': 'root'\n }\n item: (id: string) => ToolbarItemParts\n}\n\n/** Build reactive toolbar parts from the format signal. Spread `item(id)` onto a\n * `<button>`; `aria-pressed` / `data-active` / `disabled` track the format. */\nexport function connectToolbar(\n format: Signal<FormatState>,\n send: Send<EditorMsg>,\n items: readonly CommandItem[],\n): ToolbarParts {\n const byId = new Map(items.map((i) => [i.id, i]))\n return {\n root: {\n role: 'toolbar',\n 'aria-label': 'Formatting',\n 'data-scope': 'md-toolbar',\n 'data-part': 'root',\n },\n item: (id) => {\n const item = byId.get(id)\n const label = item?.label ?? id\n return {\n type: 'button',\n 'data-scope': 'md-toolbar',\n 'data-part': 'item',\n 'data-id': id,\n 'aria-label': label,\n title: label,\n 'aria-pressed': format.map((f) => (item?.isActive?.(f) ? 'true' : 'false')),\n 'aria-disabled': format.map((f) => (item?.isDisabled?.(f) ? 'true' : undefined)),\n disabled: format.map((f) => item?.isDisabled?.(f) ?? false),\n 'data-active': format.map((f) => (item?.isActive?.(f) ? '' : undefined)),\n onClick: tagSend(send, ['runCommand'], () => send({ type: 'runCommand', id })),\n }\n },\n }\n}\n\n/** Inline SVG icon (monochrome, inherits `currentColor`) used where a glyph\n * reads poorly. A glyph string starting with `<svg` is rendered as markup. */\nconst LINK_ICON =\n '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><path d=\"M9.5 13.5a3.5 3.5 0 0 0 5 0l3-3a3.5 3.5 0 1 0-5-5l-1 1\"/><path d=\"M14.5 10.5a3.5 3.5 0 0 0-5 0l-3 3a3.5 3.5 0 1 0 5 5l1-1\"/></svg>'\n\nconst IMAGE_ICON =\n '<svg viewBox=\"0 0 24 24\" width=\"15\" height=\"15\" fill=\"none\" stroke=\"currentColor\" stroke-width=\"2\" stroke-linecap=\"round\" stroke-linejoin=\"round\" aria-hidden=\"true\"><rect x=\"3\" y=\"4\" width=\"18\" height=\"16\" rx=\"2\"/><circle cx=\"9\" cy=\"9.5\" r=\"1.6\"/><path d=\"m4 18 5-5 4 4 3-3 4 4\"/></svg>'\n\n/** Compact glyphs so the default toolbar reads as a real toolbar without icon\n * assets. SVG strings render as icons; everything else as text. Override via\n * `ToolbarOptions.glyphs`. */\nexport const DEFAULT_GLYPHS: Readonly<Record<string, string>> = {\n bold: 'B',\n italic: 'I',\n strikethrough: 'S',\n code: '</>',\n link: LINK_ICON,\n image: IMAGE_ICON,\n hr: '—',\n table: '▦',\n paragraph: '¶',\n h1: 'H1',\n h2: 'H2',\n h3: 'H3',\n quote: '❝',\n codeBlock: '{ }',\n bulletList: '•',\n numberList: '1.',\n checkList: '☑',\n undo: '↺',\n redo: '↻',\n}\n\nconst GROUP_ORDER = ['inline', 'block', 'list', 'insert', 'history']\n\nfunction groupItems(items: readonly CommandItem[]): string[][] {\n const groups = new Map<string, string[]>()\n for (const item of items) {\n const key = item.group ?? 'other'\n const list = groups.get(key) ?? []\n list.push(item.id)\n groups.set(key, list)\n }\n const ordered: string[][] = []\n for (const key of GROUP_ORDER) {\n const list = groups.get(key)\n if (list) ordered.push(list)\n }\n for (const [key, list] of groups) {\n if (!GROUP_ORDER.includes(key)) ordered.push(list)\n }\n return ordered\n}\n\nexport interface ToolbarOptions {\n format: Signal<FormatState>\n send: Send<EditorMsg>\n items: readonly CommandItem[]\n /** Explicit grouped layout of ids; defaults to grouping by `item.group`. */\n groups?: readonly (readonly string[])[]\n /** Glyph overrides (id → text/emoji). Merged over {@link DEFAULT_GLYPHS}. */\n glyphs?: Readonly<Record<string, string>>\n /** Render the `block` group as a `<select>` dropdown instead of buttons\n * (default true). */\n blockSelect?: boolean\n 'aria-label'?: string\n}\n\n/** Render the `block` group as a native `<select>` of block types. */\nfunction blockTypeSelect(\n format: Signal<FormatState>,\n send: Send<EditorMsg>,\n blockItems: readonly CommandItem[],\n): Mountable {\n return select(\n {\n 'data-scope': 'md-toolbar',\n 'data-part': 'block-select',\n 'aria-label': 'Block type',\n value: format.map((f) => blockItems.find((i) => i.isActive?.(f))?.id ?? ''),\n onChange: (e: Event) => {\n const id = (e.currentTarget as HTMLSelectElement).value\n if (id) send({ type: 'runCommand', id })\n },\n },\n [\n option({ value: '', hidden: true }, [text('—')]),\n ...blockItems.map((i) => option({ value: i.id }, [text(i.label)])),\n ],\n )\n}\n\n/** A ready-made grouped toolbar. Items not surfaced to `'toolbar'` are dropped. */\nexport function toolbar(opts: ToolbarOptions): Mountable {\n const surfaceItems = opts.items.filter((i) => i.surfaces?.includes('toolbar') ?? true)\n const byId = new Map(surfaceItems.map((i) => [i.id, i]))\n const parts = connectToolbar(opts.format, opts.send, surfaceItems)\n const glyphs = { ...DEFAULT_GLYPHS, ...(opts.glyphs ?? {}) }\n\n const useBlockSelect = opts.blockSelect !== false && !opts.groups\n const blockItems = useBlockSelect ? surfaceItems.filter((i) => i.group === 'block') : []\n const buttonItems = useBlockSelect\n ? surfaceItems.filter((i) => i.group !== 'block')\n : surfaceItems\n const groups = opts.groups ?? groupItems(buttonItems)\n\n const children: Mountable[] = []\n if (blockItems.length > 0) {\n children.push(\n div({ 'data-scope': 'md-toolbar', 'data-part': 'group' }, [\n blockTypeSelect(opts.format, opts.send, blockItems),\n ]),\n )\n }\n for (const ids of groups) {\n const present = ids.filter((id) => byId.has(id))\n if (present.length === 0) continue\n children.push(\n div(\n { 'data-scope': 'md-toolbar', 'data-part': 'group' },\n present.map((id) => {\n const item = byId.get(id)!\n const glyph = glyphs[id] ?? item.label\n const glyphNode = glyph.trimStart().startsWith('<svg') ? unsafeHtml(glyph) : text(glyph)\n return button({ ...parts.item(id) }, [\n span({ 'data-part': 'glyph', 'aria-hidden': 'true' }, [glyphNode]),\n ])\n }),\n ),\n )\n }\n\n return div(\n { ...parts.root, ...(opts['aria-label'] ? { 'aria-label': opts['aria-label'] } : {}) },\n children,\n )\n}\n"]}
@@ -0,0 +1,7 @@
1
+ import type { Klass, LexicalNode } from 'lexical';
2
+ import { type Transformer } from '@lexical/markdown';
3
+ /** Node classes required to render the GFM superset. */
4
+ export declare const GFM_NODES: ReadonlyArray<Klass<LexicalNode>>;
5
+ /** Markdown ↔ node transformers for the GFM superset. */
6
+ export declare const GFM_TRANSFORMERS: readonly Transformer[];
7
+ //# sourceMappingURL=gfm.d.ts.map
@@ -0,0 +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,yDAAyD;AACzD,eAAO,MAAM,gBAAgB,EAAE,SAAS,WAAW,EAmBlD,CAAA"}
@@ -0,0 +1,41 @@
1
+ // The built-in GFM superset: node classes + the explicit transformer set that
2
+ // maps exactly to those nodes (no surprise nodes — HR/tables are opt-in plugins).
3
+ import { HeadingNode, QuoteNode } from '@lexical/rich-text';
4
+ import { ListNode, ListItemNode } from '@lexical/list';
5
+ import { LinkNode } from '@lexical/link';
6
+ // `@lexical/code-core` (not `@lexical/code`) keeps Prism out of the bundle — we
7
+ // never register syntax highlighting, so plain CodeNode is all we need.
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';
10
+ /** Node classes required to render the GFM superset. */
11
+ export const GFM_NODES = [
12
+ HeadingNode,
13
+ QuoteNode,
14
+ ListNode,
15
+ ListItemNode,
16
+ CodeNode,
17
+ CodeHighlightNode,
18
+ LinkNode,
19
+ ];
20
+ /** Markdown ↔ node transformers for the GFM superset. */
21
+ export const GFM_TRANSFORMERS = [
22
+ HEADING,
23
+ QUOTE,
24
+ // CHECK_LIST must precede the plain list transformers: `- [ ]`/`- [x]` also
25
+ // match `- `, so UNORDERED_LIST would otherwise swallow it as bullet text.
26
+ CHECK_LIST,
27
+ UNORDERED_LIST,
28
+ ORDERED_LIST,
29
+ CODE,
30
+ BOLD_ITALIC_STAR,
31
+ BOLD_ITALIC_UNDERSCORE,
32
+ BOLD_STAR,
33
+ BOLD_UNDERSCORE,
34
+ ITALIC_STAR,
35
+ ITALIC_UNDERSCORE,
36
+ STRIKETHROUGH,
37
+ INLINE_CODE,
38
+ HIGHLIGHT,
39
+ LINK,
40
+ ];
41
+ //# sourceMappingURL=gfm.js.map
@@ -0,0 +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,yDAAyD;AACzD,MAAM,CAAC,MAAM,gBAAgB,GAA2B;IACtD,OAAO;IACP,KAAK;IACL,4EAA4E;IAC5E,2EAA2E;IAC3E,UAAU;IACV,cAAc;IACd,YAAY;IACZ,IAAI;IACJ,gBAAgB;IAChB,sBAAsB;IACtB,SAAS;IACT,eAAe;IACf,WAAW;IACX,iBAAiB;IACjB,aAAa;IACb,WAAW;IACX,SAAS;IACT,IAAI;CACL,CAAA","sourcesContent":["// The built-in GFM superset: node classes + the explicit transformer set that\n// maps exactly to those nodes (no surprise nodes — HR/tables are opt-in plugins).\n\nimport type { Klass, LexicalNode } from 'lexical'\nimport { HeadingNode, QuoteNode } from '@lexical/rich-text'\nimport { ListNode, ListItemNode } from '@lexical/list'\nimport { LinkNode } from '@lexical/link'\n// `@lexical/code-core` (not `@lexical/code`) keeps Prism out of the bundle — we\n// never register syntax highlighting, so plain CodeNode is all we need.\nimport { CodeNode, CodeHighlightNode } from '@lexical/code-core'\nimport {\n type Transformer,\n HEADING,\n QUOTE,\n UNORDERED_LIST,\n ORDERED_LIST,\n CHECK_LIST,\n CODE,\n BOLD_ITALIC_STAR,\n BOLD_ITALIC_UNDERSCORE,\n BOLD_STAR,\n BOLD_UNDERSCORE,\n ITALIC_STAR,\n ITALIC_UNDERSCORE,\n STRIKETHROUGH,\n INLINE_CODE,\n HIGHLIGHT,\n LINK,\n} from '@lexical/markdown'\n\n/** Node classes required to render the GFM superset. */\nexport const GFM_NODES: ReadonlyArray<Klass<LexicalNode>> = [\n HeadingNode,\n QuoteNode,\n ListNode,\n ListItemNode,\n CodeNode,\n CodeHighlightNode,\n LinkNode,\n]\n\n/** Markdown ↔ node transformers for the GFM superset. */\nexport const GFM_TRANSFORMERS: readonly Transformer[] = [\n HEADING,\n QUOTE,\n // CHECK_LIST must precede the plain list transformers: `- [ ]`/`- [x]` also\n // match `- `, so UNORDERED_LIST would otherwise swallow it as bullet text.\n CHECK_LIST,\n UNORDERED_LIST,\n ORDERED_LIST,\n CODE,\n BOLD_ITALIC_STAR,\n BOLD_ITALIC_UNDERSCORE,\n BOLD_STAR,\n BOLD_UNDERSCORE,\n ITALIC_STAR,\n ITALIC_UNDERSCORE,\n STRIKETHROUGH,\n INLINE_CODE,\n HIGHLIGHT,\n LINK,\n]\n"]}
@@ -0,0 +1,9 @@
1
+ import type { Transformer } from '@lexical/markdown';
2
+ import type { MarkdownPlugin } from '../plugins/types.js';
3
+ /** Stable-sort transformers into the order Lexical expects. */
4
+ export declare function orderTransformers(transformers: readonly Transformer[]): Transformer[];
5
+ /** Collect every plugin's transformers (de-duplicated by reference) and order
6
+ * them. The result is passed to `$convertTo/FromMarkdownString` and
7
+ * `registerMarkdownShortcuts`. */
8
+ export declare function buildTransformers(plugins: readonly MarkdownPlugin[]): Transformer[];
9
+ //# sourceMappingURL=registry.d.ts.map
@@ -0,0 +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"}
@@ -0,0 +1,43 @@
1
+ // The transformer registry: flatten plugin transformer contributions into a
2
+ // single, correctly-ordered Lexical TRANSFORMERS array. Ordering is the single
3
+ // place markdown round-trip fidelity is governed.
4
+ // Lexical resolves transformers in array order. Element/multiline constructs
5
+ // must be tried before inline text matching, and longer text-format patterns
6
+ // (e.g. `***`) before shorter (`**`, `*`) so they aren't shadowed.
7
+ const TYPE_RANK = {
8
+ 'multiline-element': 0,
9
+ element: 1,
10
+ 'text-format': 2,
11
+ 'text-match': 3,
12
+ };
13
+ /** Stable-sort transformers into the order Lexical expects. */
14
+ export function orderTransformers(transformers) {
15
+ return [...transformers].sort((a, b) => {
16
+ const ra = TYPE_RANK[a.type] ?? 9;
17
+ const rb = TYPE_RANK[b.type] ?? 9;
18
+ if (ra !== rb)
19
+ return ra - rb;
20
+ if (a.type === 'text-format' && b.type === 'text-format') {
21
+ // Longer trigger (`***`) before shorter (`**`, `*`).
22
+ return b.tag.length - a.tag.length;
23
+ }
24
+ return 0;
25
+ });
26
+ }
27
+ /** Collect every plugin's transformers (de-duplicated by reference) and order
28
+ * them. The result is passed to `$convertTo/FromMarkdownString` and
29
+ * `registerMarkdownShortcuts`. */
30
+ export function buildTransformers(plugins) {
31
+ const seen = new Set();
32
+ const collected = [];
33
+ for (const plugin of plugins) {
34
+ for (const transformer of plugin.transformers ?? []) {
35
+ if (!seen.has(transformer)) {
36
+ seen.add(transformer);
37
+ collected.push(transformer);
38
+ }
39
+ }
40
+ }
41
+ return orderTransformers(collected);
42
+ }
43
+ //# sourceMappingURL=registry.js.map
@@ -0,0 +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","sourcesContent":["// The transformer registry: flatten plugin transformer contributions into a\n// single, correctly-ordered Lexical TRANSFORMERS array. Ordering is the single\n// place markdown round-trip fidelity is governed.\n\nimport type { Transformer } from '@lexical/markdown'\nimport type { MarkdownPlugin } from '../plugins/types.js'\n\n// Lexical resolves transformers in array order. Element/multiline constructs\n// must be tried before inline text matching, and longer text-format patterns\n// (e.g. `***`) before shorter (`**`, `*`) so they aren't shadowed.\nconst TYPE_RANK: Record<string, number> = {\n 'multiline-element': 0,\n element: 1,\n 'text-format': 2,\n 'text-match': 3,\n}\n\n/** Stable-sort transformers into the order Lexical expects. */\nexport function orderTransformers(transformers: readonly Transformer[]): Transformer[] {\n return [...transformers].sort((a, b) => {\n const ra = TYPE_RANK[a.type] ?? 9\n const rb = TYPE_RANK[b.type] ?? 9\n if (ra !== rb) return ra - rb\n if (a.type === 'text-format' && b.type === 'text-format') {\n // Longer trigger (`***`) before shorter (`**`, `*`).\n return b.tag.length - a.tag.length\n }\n return 0\n })\n}\n\n/** Collect every plugin's transformers (de-duplicated by reference) and order\n * them. The result is passed to `$convertTo/FromMarkdownString` and\n * `registerMarkdownShortcuts`. */\nexport function buildTransformers(plugins: readonly MarkdownPlugin[]): Transformer[] {\n const seen = new Set<Transformer>()\n const collected: Transformer[] = []\n for (const plugin of plugins) {\n for (const transformer of plugin.transformers ?? []) {\n if (!seen.has(transformer)) {\n seen.add(transformer)\n collected.push(transformer)\n }\n }\n }\n return orderTransformers(collected)\n}\n"]}
package/package.json ADDED
@@ -0,0 +1,89 @@
1
+ {
2
+ "name": "@llui/markdown-editor",
3
+ "version": "0.1.0",
4
+ "type": "module",
5
+ "main": "dist/index.js",
6
+ "types": "dist/index.d.ts",
7
+ "exports": {
8
+ ".": {
9
+ "import": "./dist/index.js",
10
+ "types": "./dist/index.d.ts"
11
+ },
12
+ "./plugins/core": {
13
+ "import": "./dist/plugins/core.js",
14
+ "types": "./dist/plugins/core.d.ts"
15
+ },
16
+ "./plugins/callout": {
17
+ "import": "./dist/plugins/callout.js",
18
+ "types": "./dist/plugins/callout.d.ts"
19
+ },
20
+ "./surfaces/toolbar": {
21
+ "import": "./dist/surfaces/toolbar.js",
22
+ "types": "./dist/surfaces/toolbar.d.ts"
23
+ },
24
+ "./styles/editor.css": "./dist/styles/editor.css"
25
+ },
26
+ "peerDependencies": {
27
+ "@llui/dom": "^0.9.0",
28
+ "@llui/lexical": "^0.1.0",
29
+ "@llui/components": "^0.10.0",
30
+ "lexical": "^0.45.0",
31
+ "@lexical/markdown": "^0.45.0",
32
+ "@lexical/rich-text": "^0.45.0",
33
+ "@lexical/list": "^0.45.0",
34
+ "@lexical/link": "^0.45.0",
35
+ "@lexical/table": "^0.45.0",
36
+ "@lexical/selection": "^0.45.0",
37
+ "@lexical/utils": "^0.45.0",
38
+ "@lexical/code-core": "^0.45.0"
39
+ },
40
+ "devDependencies": {
41
+ "lexical": "^0.45.0",
42
+ "@lexical/headless": "^0.45.0",
43
+ "@lexical/markdown": "^0.45.0",
44
+ "@lexical/rich-text": "^0.45.0",
45
+ "@lexical/list": "^0.45.0",
46
+ "@lexical/link": "^0.45.0",
47
+ "@lexical/table": "^0.45.0",
48
+ "@lexical/selection": "^0.45.0",
49
+ "@lexical/utils": "^0.45.0",
50
+ "typescript": "^6.0.0",
51
+ "vitest": "^4.1.2",
52
+ "@lexical/code-core": "^0.45.0",
53
+ "@llui/dom": "0.9.0",
54
+ "@llui/lexical": "0.1.0",
55
+ "@llui/components": "0.10.0"
56
+ },
57
+ "sideEffects": [
58
+ "./dist/styles/editor.css"
59
+ ],
60
+ "description": "WYSIWYG Markdown editor for LLui — hides Markdown behind a rich, pluggable editing widget built on Lexical",
61
+ "keywords": [
62
+ "llui",
63
+ "markdown",
64
+ "editor",
65
+ "wysiwyg",
66
+ "lexical",
67
+ "rich-text"
68
+ ],
69
+ "author": "Franco Ponticelli <franco.ponticelli@gmail.com>",
70
+ "license": "MIT",
71
+ "repository": {
72
+ "type": "git",
73
+ "url": "git+https://github.com/fponticelli/llui.git",
74
+ "directory": "packages/markdown-editor"
75
+ },
76
+ "bugs": {
77
+ "url": "https://github.com/fponticelli/llui/issues"
78
+ },
79
+ "homepage": "https://github.com/fponticelli/llui/tree/main/packages/markdown-editor#readme",
80
+ "files": [
81
+ "dist"
82
+ ],
83
+ "scripts": {
84
+ "build": "tsc -p tsconfig.build.json && mkdir -p dist/styles && cp src/styles/editor.css dist/styles/",
85
+ "check": "tsc --noEmit -p tsconfig.check.json",
86
+ "lint": "eslint src",
87
+ "test": "vitest run"
88
+ }
89
+ }