@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.
- package/LICENSE +21 -0
- package/dist/__llui_deps.json +252 -0
- package/dist/editor.d.ts +42 -0
- package/dist/editor.d.ts.map +1 -0
- package/dist/editor.js +157 -0
- package/dist/editor.js.map +1 -0
- package/dist/effects.d.ts +17 -0
- package/dist/effects.d.ts.map +1 -0
- package/dist/effects.js +33 -0
- package/dist/effects.js.map +1 -0
- package/dist/format.d.ts +6 -0
- package/dist/format.d.ts.map +1 -0
- package/dist/format.js +51 -0
- package/dist/format.js.map +1 -0
- package/dist/index.d.ts +23 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +24 -0
- package/dist/index.js.map +1 -0
- package/dist/plugins/callout.d.ts +15 -0
- package/dist/plugins/callout.d.ts.map +1 -0
- package/dist/plugins/callout.js +151 -0
- package/dist/plugins/callout.js.map +1 -0
- package/dist/plugins/context-menu.d.ts +3 -0
- package/dist/plugins/context-menu.d.ts.map +1 -0
- package/dist/plugins/context-menu.js +93 -0
- package/dist/plugins/context-menu.js.map +1 -0
- package/dist/plugins/core.d.ts +7 -0
- package/dist/plugins/core.d.ts.map +1 -0
- package/dist/plugins/core.js +189 -0
- package/dist/plugins/core.js.map +1 -0
- package/dist/plugins/emoji.d.ts +9 -0
- package/dist/plugins/emoji.d.ts.map +1 -0
- package/dist/plugins/emoji.js +50 -0
- package/dist/plugins/emoji.js.map +1 -0
- package/dist/plugins/floating-toolbar.d.ts +3 -0
- package/dist/plugins/floating-toolbar.d.ts.map +1 -0
- package/dist/plugins/floating-toolbar.js +137 -0
- package/dist/plugins/floating-toolbar.js.map +1 -0
- package/dist/plugins/hr.d.ts +5 -0
- package/dist/plugins/hr.d.ts.map +1 -0
- package/dist/plugins/hr.js +46 -0
- package/dist/plugins/hr.js.map +1 -0
- package/dist/plugins/image.d.ts +8 -0
- package/dist/plugins/image.d.ts.map +1 -0
- package/dist/plugins/image.js +173 -0
- package/dist/plugins/image.js.map +1 -0
- package/dist/plugins/link.d.ts +7 -0
- package/dist/plugins/link.d.ts.map +1 -0
- package/dist/plugins/link.js +100 -0
- package/dist/plugins/link.js.map +1 -0
- package/dist/plugins/math.d.ts +8 -0
- package/dist/plugins/math.d.ts.map +1 -0
- package/dist/plugins/math.js +81 -0
- package/dist/plugins/math.js.map +1 -0
- package/dist/plugins/mention.d.ts +11 -0
- package/dist/plugins/mention.d.ts.map +1 -0
- package/dist/plugins/mention.js +163 -0
- package/dist/plugins/mention.js.map +1 -0
- package/dist/plugins/mermaid.d.ts +8 -0
- package/dist/plugins/mermaid.d.ts.map +1 -0
- package/dist/plugins/mermaid.js +92 -0
- package/dist/plugins/mermaid.js.map +1 -0
- package/dist/plugins/overlay.d.ts +46 -0
- package/dist/plugins/overlay.d.ts.map +1 -0
- package/dist/plugins/overlay.js +83 -0
- package/dist/plugins/overlay.js.map +1 -0
- package/dist/plugins/slash.d.ts +3 -0
- package/dist/plugins/slash.d.ts.map +1 -0
- package/dist/plugins/slash.js +167 -0
- package/dist/plugins/slash.js.map +1 -0
- package/dist/plugins/table.d.ts +3 -0
- package/dist/plugins/table.d.ts.map +1 -0
- package/dist/plugins/table.js +227 -0
- package/dist/plugins/table.js.map +1 -0
- package/dist/plugins/types.d.ts +44 -0
- package/dist/plugins/types.d.ts.map +1 -0
- package/dist/plugins/types.js +4 -0
- package/dist/plugins/types.js.map +1 -0
- package/dist/plugins/ui.d.ts +44 -0
- package/dist/plugins/ui.d.ts.map +1 -0
- package/dist/plugins/ui.js +34 -0
- package/dist/plugins/ui.js.map +1 -0
- package/dist/state.d.ts +105 -0
- package/dist/state.d.ts.map +1 -0
- package/dist/state.js +100 -0
- package/dist/state.js.map +1 -0
- package/dist/styles/editor.css +517 -0
- package/dist/surfaces/link-dialog.d.ts +19 -0
- package/dist/surfaces/link-dialog.d.ts.map +1 -0
- package/dist/surfaces/link-dialog.js +45 -0
- package/dist/surfaces/link-dialog.js.map +1 -0
- package/dist/surfaces/toolbar.d.ts +48 -0
- package/dist/surfaces/toolbar.d.ts.map +1 -0
- package/dist/surfaces/toolbar.js +134 -0
- package/dist/surfaces/toolbar.js.map +1 -0
- package/dist/transformers/gfm.d.ts +7 -0
- package/dist/transformers/gfm.d.ts.map +1 -0
- package/dist/transformers/gfm.js +41 -0
- package/dist/transformers/gfm.js.map +1 -0
- package/dist/transformers/registry.d.ts +9 -0
- package/dist/transformers/registry.d.ts.map +1 -0
- package/dist/transformers/registry.js +43 -0
- package/dist/transformers/registry.js.map +1 -0
- package/package.json +89 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slash.d.ts","sourceRoot":"","sources":["../../src/plugins/slash.ts"],"names":[],"mappings":"AAoBA,OAAO,KAAK,EAAe,cAAc,EAAE,MAAM,YAAY,CAAA;AAkE7D,wBAAgB,WAAW,IAAI,cAAc,CAyI5C"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
// Slash command menu — a `/` command palette built as a plugin. It uses both the
|
|
2
|
+
// engine `register` hook (to detect the `/query` trigger and drive keyboard nav
|
|
3
|
+
// from editor events) and the plugin-UI extension (state + the floating menu
|
|
4
|
+
// view + command execution). The flagship demonstration that the plugin-UI seam
|
|
5
|
+
// handles complex, stateful overlays.
|
|
6
|
+
import { $getSelection, $isRangeSelection, $isTextNode, COMMAND_PRIORITY_HIGH, KEY_ARROW_DOWN_COMMAND, KEY_ARROW_UP_COMMAND, KEY_ENTER_COMMAND, KEY_ESCAPE_COMMAND, } from 'lexical';
|
|
7
|
+
import { mergeRegister } from '@lexical/utils';
|
|
8
|
+
import { div, each, text } from '@llui/dom';
|
|
9
|
+
import { definePluginUI } from './ui.js';
|
|
10
|
+
import { OVERLAY_Z, hideOverlay, overlayRoot } from './overlay.js';
|
|
11
|
+
function withActive(items, index) {
|
|
12
|
+
return items.map((it, i) => ({ id: it.id, label: it.label, active: i === index }));
|
|
13
|
+
}
|
|
14
|
+
const TRIGGER = /(?:^|\s)\/([\w-]*)$/;
|
|
15
|
+
/** Read the active slash query before the collapsed caret, or null. */
|
|
16
|
+
function $readSlashQuery() {
|
|
17
|
+
const selection = $getSelection();
|
|
18
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
|
19
|
+
return null;
|
|
20
|
+
const node = selection.anchor.getNode();
|
|
21
|
+
if (!$isTextNode(node))
|
|
22
|
+
return null;
|
|
23
|
+
const before = node.getTextContent().slice(0, selection.anchor.offset);
|
|
24
|
+
const match = before.match(TRIGGER);
|
|
25
|
+
return match ? (match[1] ?? '') : null;
|
|
26
|
+
}
|
|
27
|
+
function caretXY() {
|
|
28
|
+
if (typeof window === 'undefined')
|
|
29
|
+
return { x: 0, y: 0 };
|
|
30
|
+
const sel = window.getSelection();
|
|
31
|
+
if (sel && sel.rangeCount > 0) {
|
|
32
|
+
const rect = sel.getRangeAt(0).getBoundingClientRect();
|
|
33
|
+
return { x: rect.left, y: rect.bottom + 4 };
|
|
34
|
+
}
|
|
35
|
+
return { x: 0, y: 0 };
|
|
36
|
+
}
|
|
37
|
+
function matches(item, query) {
|
|
38
|
+
if (query === '')
|
|
39
|
+
return true;
|
|
40
|
+
const q = query.toLowerCase();
|
|
41
|
+
if (item.label.toLowerCase().includes(q) || item.id.toLowerCase().includes(q))
|
|
42
|
+
return true;
|
|
43
|
+
return (item.keywords ?? []).some((k) => k.toLowerCase().includes(q));
|
|
44
|
+
}
|
|
45
|
+
export function slashPlugin() {
|
|
46
|
+
let slashItems = [];
|
|
47
|
+
const filtered = (query) => slashItems.filter((i) => matches(i, query)).map((i) => ({ id: i.id, label: i.label }));
|
|
48
|
+
return {
|
|
49
|
+
name: 'slash',
|
|
50
|
+
onItems: (items) => {
|
|
51
|
+
slashItems = items.filter((i) => i.surfaces
|
|
52
|
+
? i.surfaces.includes('slash')
|
|
53
|
+
: ['block', 'list', 'insert'].includes(i.group ?? ''));
|
|
54
|
+
},
|
|
55
|
+
register: (editor, ctx) => {
|
|
56
|
+
const isActive = () => editor.getEditorState().read(() => $readSlashQuery() !== null);
|
|
57
|
+
const refresh = () => {
|
|
58
|
+
const query = editor.getEditorState().read(() => $readSlashQuery());
|
|
59
|
+
if (query === null) {
|
|
60
|
+
ctx.emit({ type: 'plugin', name: 'slash', msg: { type: 'hide' } });
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const items = filtered(query);
|
|
64
|
+
const { x, y } = caretXY();
|
|
65
|
+
ctx.emit({ type: 'plugin', name: 'slash', msg: { type: 'show', query, items, x, y } });
|
|
66
|
+
};
|
|
67
|
+
const nav = (delta, e) => {
|
|
68
|
+
if (!isActive())
|
|
69
|
+
return false;
|
|
70
|
+
e?.preventDefault();
|
|
71
|
+
ctx.emit({ type: 'plugin', name: 'slash', msg: { type: 'move', delta } });
|
|
72
|
+
return true;
|
|
73
|
+
};
|
|
74
|
+
return mergeRegister(editor.registerUpdateListener(() => refresh()), editor.registerCommand(KEY_ARROW_DOWN_COMMAND, (e) => nav(1, e), COMMAND_PRIORITY_HIGH), editor.registerCommand(KEY_ARROW_UP_COMMAND, (e) => nav(-1, e), COMMAND_PRIORITY_HIGH), editor.registerCommand(KEY_ENTER_COMMAND, (e) => {
|
|
75
|
+
if (!isActive())
|
|
76
|
+
return false;
|
|
77
|
+
e?.preventDefault();
|
|
78
|
+
ctx.emit({ type: 'plugin', name: 'slash', msg: { type: 'choose' } });
|
|
79
|
+
return true;
|
|
80
|
+
}, COMMAND_PRIORITY_HIGH), editor.registerCommand(KEY_ESCAPE_COMMAND, () => {
|
|
81
|
+
if (!isActive())
|
|
82
|
+
return false;
|
|
83
|
+
ctx.emit({ type: 'plugin', name: 'slash', msg: { type: 'hide' } });
|
|
84
|
+
return true;
|
|
85
|
+
}, COMMAND_PRIORITY_HIGH));
|
|
86
|
+
},
|
|
87
|
+
ui: definePluginUI({
|
|
88
|
+
init: () => ({ open: false, query: '', items: [], index: 0, x: 0, y: 0 }),
|
|
89
|
+
update: (state, msg) => {
|
|
90
|
+
switch (msg.type) {
|
|
91
|
+
case 'show':
|
|
92
|
+
return {
|
|
93
|
+
open: msg.items.length > 0,
|
|
94
|
+
query: msg.query,
|
|
95
|
+
items: withActive(msg.items, 0),
|
|
96
|
+
index: 0,
|
|
97
|
+
x: msg.x,
|
|
98
|
+
y: msg.y,
|
|
99
|
+
};
|
|
100
|
+
case 'hide':
|
|
101
|
+
return hideOverlay(state);
|
|
102
|
+
case 'move': {
|
|
103
|
+
if (!state.open || state.items.length === 0)
|
|
104
|
+
return state;
|
|
105
|
+
const index = (state.index + msg.delta + state.items.length) % state.items.length;
|
|
106
|
+
return { ...state, index, items: withActive(state.items, index) };
|
|
107
|
+
}
|
|
108
|
+
case 'choose': {
|
|
109
|
+
const item = state.items[state.index];
|
|
110
|
+
if (!state.open || !item)
|
|
111
|
+
return hideOverlay(state);
|
|
112
|
+
return [{ ...state, open: false }, [{ type: 'run', id: item.id, query: state.query }]];
|
|
113
|
+
}
|
|
114
|
+
case 'click': {
|
|
115
|
+
const item = state.items[msg.index];
|
|
116
|
+
if (!item)
|
|
117
|
+
return state;
|
|
118
|
+
return [{ ...state, open: false }, [{ type: 'run', id: item.id, query: state.query }]];
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
},
|
|
122
|
+
onEffect: (effect, ctx) => {
|
|
123
|
+
const editor = ctx.editor();
|
|
124
|
+
if (!editor)
|
|
125
|
+
return;
|
|
126
|
+
// Remove the typed "/query" before running the command.
|
|
127
|
+
editor.update(() => {
|
|
128
|
+
const selection = $getSelection();
|
|
129
|
+
if (!$isRangeSelection(selection) || !selection.isCollapsed())
|
|
130
|
+
return;
|
|
131
|
+
const node = selection.anchor.getNode();
|
|
132
|
+
if (!$isTextNode(node))
|
|
133
|
+
return;
|
|
134
|
+
const offset = selection.anchor.offset;
|
|
135
|
+
const start = offset - effect.query.length - 1;
|
|
136
|
+
if (start >= 0)
|
|
137
|
+
node.spliceText(start, effect.query.length + 1, '', true);
|
|
138
|
+
});
|
|
139
|
+
ctx.emit({ type: 'runCommand', id: effect.id });
|
|
140
|
+
},
|
|
141
|
+
view: ({ state, send }) => overlayRoot({
|
|
142
|
+
open: state.at('open'),
|
|
143
|
+
x: state.at('x'),
|
|
144
|
+
y: state.at('y'),
|
|
145
|
+
zIndex: OVERLAY_Z.typeahead,
|
|
146
|
+
attrs: { 'data-scope': 'md-slash', 'data-part': 'root' },
|
|
147
|
+
children: () => [
|
|
148
|
+
each(state.at('items'), {
|
|
149
|
+
key: (it) => it.id,
|
|
150
|
+
render: (item, index) => [
|
|
151
|
+
div({
|
|
152
|
+
'data-scope': 'md-slash',
|
|
153
|
+
'data-part': 'option',
|
|
154
|
+
'data-active': item.map((it) => (it.active ? '' : undefined)),
|
|
155
|
+
onMouseDown: (e) => {
|
|
156
|
+
e.preventDefault();
|
|
157
|
+
send({ type: 'click', index: index.peek() });
|
|
158
|
+
},
|
|
159
|
+
}, [text(item.map((it) => it.label))]),
|
|
160
|
+
],
|
|
161
|
+
}),
|
|
162
|
+
],
|
|
163
|
+
}),
|
|
164
|
+
}),
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=slash.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"slash.js","sourceRoot":"","sources":["../../src/plugins/slash.ts"],"names":[],"mappings":"AAAA,iFAAiF;AACjF,gFAAgF;AAChF,6EAA6E;AAC7E,gFAAgF;AAChF,sCAAsC;AAEtC,OAAO,EACL,aAAa,EACb,iBAAiB,EACjB,WAAW,EACX,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,EACpB,iBAAiB,EACjB,kBAAkB,GACnB,MAAM,SAAS,CAAA;AAChB,OAAO,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAA;AAC9C,OAAO,EAAE,GAAG,EAAE,IAAI,EAAE,IAAI,EAAe,MAAM,WAAW,CAAA;AACxD,OAAO,EAAE,cAAc,EAAE,MAAM,SAAS,CAAA;AACxC,OAAO,EAAE,SAAS,EAAE,WAAW,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAwBlE,SAAS,UAAU,CAAC,KAA0B,EAAE,KAAa;IAC3D,OAAO,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,EAAE,CAAC,EAAE,EAAE,KAAK,EAAE,EAAE,CAAC,KAAK,EAAE,MAAM,EAAE,CAAC,KAAK,KAAK,EAAE,CAAC,CAAC,CAAA;AACpF,CAAC;AAWD,MAAM,OAAO,GAAG,qBAAqB,CAAA;AAErC,uEAAuE;AACvE,SAAS,eAAe;IACtB,MAAM,SAAS,GAAG,aAAa,EAAE,CAAA;IACjC,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE;QAAE,OAAO,IAAI,CAAA;IAC1E,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,CAAA;IACvC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;QAAE,OAAO,IAAI,CAAA;IACnC,MAAM,MAAM,GAAG,IAAI,CAAC,cAAc,EAAE,CAAC,KAAK,CAAC,CAAC,EAAE,SAAS,CAAC,MAAM,CAAC,MAAM,CAAC,CAAA;IACtE,MAAM,KAAK,GAAG,MAAM,CAAC,KAAK,CAAC,OAAO,CAAC,CAAA;IACnC,OAAO,KAAK,CAAC,CAAC,CAAC,CAAC,KAAK,CAAC,CAAC,CAAC,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC,IAAI,CAAA;AACxC,CAAC;AAED,SAAS,OAAO;IACd,IAAI,OAAO,MAAM,KAAK,WAAW;QAAE,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA;IACxD,MAAM,GAAG,GAAG,MAAM,CAAC,YAAY,EAAE,CAAA;IACjC,IAAI,GAAG,IAAI,GAAG,CAAC,UAAU,GAAG,CAAC,EAAE,CAAC;QAC9B,MAAM,IAAI,GAAG,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,CAAC,qBAAqB,EAAE,CAAA;QACtD,OAAO,EAAE,CAAC,EAAE,IAAI,CAAC,IAAI,EAAE,CAAC,EAAE,IAAI,CAAC,MAAM,GAAG,CAAC,EAAE,CAAA;IAC7C,CAAC;IACD,OAAO,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAA;AACvB,CAAC;AAED,SAAS,OAAO,CAAC,IAAiB,EAAE,KAAa;IAC/C,IAAI,KAAK,KAAK,EAAE;QAAE,OAAO,IAAI,CAAA;IAC7B,MAAM,CAAC,GAAG,KAAK,CAAC,WAAW,EAAE,CAAA;IAC7B,IAAI,IAAI,CAAC,KAAK,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,IAAI,IAAI,CAAC,EAAE,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC;QAAE,OAAO,IAAI,CAAA;IAC1F,OAAO,CAAC,IAAI,CAAC,QAAQ,IAAI,EAAE,CAAC,CAAC,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,WAAW,EAAE,CAAC,QAAQ,CAAC,CAAC,CAAC,CAAC,CAAA;AACvE,CAAC;AAED,MAAM,UAAU,WAAW;IACzB,IAAI,UAAU,GAAkB,EAAE,CAAA;IAElC,MAAM,QAAQ,GAAG,CAAC,KAAa,EAAc,EAAE,CAC7C,UAAU,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,OAAO,CAAC,CAAC,EAAE,KAAK,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,EAAE,KAAK,EAAE,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;IAExF,OAAO;QACL,IAAI,EAAE,OAAO;QACb,OAAO,EAAE,CAAC,KAAK,EAAE,EAAE;YACjB,UAAU,GAAG,KAAK,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,EAAE,CAC9B,CAAC,CAAC,QAAQ;gBACR,CAAC,CAAC,CAAC,CAAC,QAAQ,CAAC,QAAQ,CAAC,OAAO,CAAC;gBAC9B,CAAC,CAAC,CAAC,OAAO,EAAE,MAAM,EAAE,QAAQ,CAAC,CAAC,QAAQ,CAAC,CAAC,CAAC,KAAK,IAAI,EAAE,CAAC,CACxD,CAAA;QACH,CAAC;QACD,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;YACxB,MAAM,QAAQ,GAAG,GAAY,EAAE,CAAC,MAAM,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,eAAe,EAAE,KAAK,IAAI,CAAC,CAAA;YAE9F,MAAM,OAAO,GAAG,GAAS,EAAE;gBACzB,MAAM,KAAK,GAAG,MAAM,CAAC,cAAc,EAAE,CAAC,IAAI,CAAC,GAAG,EAAE,CAAC,eAAe,EAAE,CAAC,CAAA;gBACnE,IAAI,KAAK,KAAK,IAAI,EAAE,CAAC;oBACnB,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;oBAClE,OAAM;gBACR,CAAC;gBACD,MAAM,KAAK,GAAG,QAAQ,CAAC,KAAK,CAAC,CAAA;gBAC7B,MAAM,EAAE,CAAC,EAAE,CAAC,EAAE,GAAG,OAAO,EAAE,CAAA;gBAC1B,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,EAAE,CAAC,CAAA;YACxF,CAAC,CAAA;YAED,MAAM,GAAG,GAAG,CAAC,KAAa,EAAE,CAAuB,EAAW,EAAE;gBAC9D,IAAI,CAAC,QAAQ,EAAE;oBAAE,OAAO,KAAK,CAAA;gBAC7B,CAAC,EAAE,cAAc,EAAE,CAAA;gBACnB,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,EAAE,CAAC,CAAA;gBACzE,OAAO,IAAI,CAAA;YACb,CAAC,CAAA;YAED,OAAO,aAAa,CAClB,MAAM,CAAC,sBAAsB,CAAC,GAAG,EAAE,CAAC,OAAO,EAAE,CAAC,EAC9C,MAAM,CAAC,eAAe,CAAC,sBAAsB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,qBAAqB,CAAC,EACvF,MAAM,CAAC,eAAe,CAAC,oBAAoB,EAAE,CAAC,CAAC,EAAE,EAAE,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,EAAE,qBAAqB,CAAC,EACtF,MAAM,CAAC,eAAe,CACpB,iBAAiB,EACjB,CAAC,CAAC,EAAE,EAAE;gBACJ,IAAI,CAAC,QAAQ,EAAE;oBAAE,OAAO,KAAK,CAAA;gBAC7B,CAAC,EAAE,cAAc,EAAE,CAAA;gBACnB,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,QAAQ,EAAE,EAAE,CAAC,CAAA;gBACpE,OAAO,IAAI,CAAA;YACb,CAAC,EACD,qBAAqB,CACtB,EACD,MAAM,CAAC,eAAe,CACpB,kBAAkB,EAClB,GAAG,EAAE;gBACH,IAAI,CAAC,QAAQ,EAAE;oBAAE,OAAO,KAAK,CAAA;gBAC7B,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,OAAO,EAAE,GAAG,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE,EAAE,CAAC,CAAA;gBAClE,OAAO,IAAI,CAAA;YACb,CAAC,EACD,qBAAqB,CACtB,CACF,CAAA;QACH,CAAC;QACD,EAAE,EAAE,cAAc,CAAoC;YACpD,IAAI,EAAE,GAAG,EAAE,CAAC,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,EAAE,EAAE,KAAK,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC,EAAE,CAAC;YACzE,MAAM,EAAE,CAAC,KAAK,EAAE,GAAG,EAAE,EAAE;gBACrB,QAAQ,GAAG,CAAC,IAAI,EAAE,CAAC;oBACjB,KAAK,MAAM;wBACT,OAAO;4BACL,IAAI,EAAE,GAAG,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC;4BAC1B,KAAK,EAAE,GAAG,CAAC,KAAK;4BAChB,KAAK,EAAE,UAAU,CAAC,GAAG,CAAC,KAAK,EAAE,CAAC,CAAC;4BAC/B,KAAK,EAAE,CAAC;4BACR,CAAC,EAAE,GAAG,CAAC,CAAC;4BACR,CAAC,EAAE,GAAG,CAAC,CAAC;yBACT,CAAA;oBACH,KAAK,MAAM;wBACT,OAAO,WAAW,CAAC,KAAK,CAAC,CAAA;oBAC3B,KAAK,MAAM,CAAC,CAAC,CAAC;wBACZ,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;4BAAE,OAAO,KAAK,CAAA;wBACzD,MAAM,KAAK,GAAG,CAAC,KAAK,CAAC,KAAK,GAAG,GAAG,CAAC,KAAK,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAC,GAAG,KAAK,CAAC,KAAK,CAAC,MAAM,CAAA;wBACjF,OAAO,EAAE,GAAG,KAAK,EAAE,KAAK,EAAE,KAAK,EAAE,UAAU,CAAC,KAAK,CAAC,KAAK,EAAE,KAAK,CAAC,EAAE,CAAA;oBACnE,CAAC;oBACD,KAAK,QAAQ,CAAC,CAAC,CAAC;wBACd,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,KAAK,CAAC,CAAA;wBACrC,IAAI,CAAC,KAAK,CAAC,IAAI,IAAI,CAAC,IAAI;4BAAE,OAAO,WAAW,CAAC,KAAK,CAAC,CAAA;wBACnD,OAAO,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;oBACxF,CAAC;oBACD,KAAK,OAAO,CAAC,CAAC,CAAC;wBACb,MAAM,IAAI,GAAG,KAAK,CAAC,KAAK,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;wBACnC,IAAI,CAAC,IAAI;4BAAE,OAAO,KAAK,CAAA;wBACvB,OAAO,CAAC,EAAE,GAAG,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC,EAAE,IAAI,EAAE,KAAK,EAAE,EAAE,EAAE,IAAI,CAAC,EAAE,EAAE,KAAK,EAAE,KAAK,CAAC,KAAK,EAAE,CAAC,CAAC,CAAA;oBACxF,CAAC;gBACH,CAAC;YACH,CAAC;YACD,QAAQ,EAAE,CAAC,MAAM,EAAE,GAAG,EAAE,EAAE;gBACxB,MAAM,MAAM,GAAG,GAAG,CAAC,MAAM,EAAE,CAAA;gBAC3B,IAAI,CAAC,MAAM;oBAAE,OAAM;gBACnB,wDAAwD;gBACxD,MAAM,CAAC,MAAM,CAAC,GAAG,EAAE;oBACjB,MAAM,SAAS,GAAG,aAAa,EAAE,CAAA;oBACjC,IAAI,CAAC,iBAAiB,CAAC,SAAS,CAAC,IAAI,CAAC,SAAS,CAAC,WAAW,EAAE;wBAAE,OAAM;oBACrE,MAAM,IAAI,GAAG,SAAS,CAAC,MAAM,CAAC,OAAO,EAAE,CAAA;oBACvC,IAAI,CAAC,WAAW,CAAC,IAAI,CAAC;wBAAE,OAAM;oBAC9B,MAAM,MAAM,GAAG,SAAS,CAAC,MAAM,CAAC,MAAM,CAAA;oBACtC,MAAM,KAAK,GAAG,MAAM,GAAG,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,CAAA;oBAC9C,IAAI,KAAK,IAAI,CAAC;wBAAE,IAAI,CAAC,UAAU,CAAC,KAAK,EAAE,MAAM,CAAC,KAAK,CAAC,MAAM,GAAG,CAAC,EAAE,EAAE,EAAE,IAAI,CAAC,CAAA;gBAC3E,CAAC,CAAC,CAAA;gBACF,GAAG,CAAC,IAAI,CAAC,EAAE,IAAI,EAAE,YAAY,EAAE,EAAE,EAAE,MAAM,CAAC,EAAE,EAAE,CAAC,CAAA;YACjD,CAAC;YACD,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,IAAI,EAAE,EAAE,EAAE,CACxB,WAAW,CAAC;gBACV,IAAI,EAAE,KAAK,CAAC,EAAE,CAAC,MAAM,CAAC;gBACtB,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC;gBAChB,CAAC,EAAE,KAAK,CAAC,EAAE,CAAC,GAAG,CAAC;gBAChB,MAAM,EAAE,SAAS,CAAC,SAAS;gBAC3B,KAAK,EAAE,EAAE,YAAY,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,EAAE;gBACxD,QAAQ,EAAE,GAAG,EAAE,CAAC;oBACd,IAAI,CAAC,KAAK,CAAC,EAAE,CAAC,OAAO,CAAsB,EAAE;wBAC3C,GAAG,EAAE,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,EAAE;wBAClB,MAAM,EAAE,CAAC,IAAI,EAAE,KAAK,EAAE,EAAE,CAAC;4BACvB,GAAG,CACD;gCACE,YAAY,EAAE,UAAU;gCACxB,WAAW,EAAE,QAAQ;gCACrB,aAAa,EAAE,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,CAAC,EAAE,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,CAAC,CAAC,CAAC,SAAS,CAAC,CAAC;gCAC7D,WAAW,EAAE,CAAC,CAAa,EAAE,EAAE;oCAC7B,CAAC,CAAC,cAAc,EAAE,CAAA;oCAClB,IAAI,CAAC,EAAE,IAAI,EAAE,OAAO,EAAE,KAAK,EAAE,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC,CAAA;gCAC9C,CAAC;6BACF,EACD,CAAC,IAAI,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,EAAE,EAAE,EAAE,CAAC,EAAE,CAAC,KAAK,CAAC,CAAC,CAAC,CACnC;yBACF;qBACF,CAAC;iBACH;aACF,CAAC;SACL,CAAC;KACH,CAAA;AACH,CAAC","sourcesContent":["// Slash command menu — a `/` command palette built as a plugin. It uses both the\n// engine `register` hook (to detect the `/query` trigger and drive keyboard nav\n// from editor events) and the plugin-UI extension (state + the floating menu\n// view + command execution). The flagship demonstration that the plugin-UI seam\n// handles complex, stateful overlays.\n\nimport {\n $getSelection,\n $isRangeSelection,\n $isTextNode,\n COMMAND_PRIORITY_HIGH,\n KEY_ARROW_DOWN_COMMAND,\n KEY_ARROW_UP_COMMAND,\n KEY_ENTER_COMMAND,\n KEY_ESCAPE_COMMAND,\n} from 'lexical'\nimport { mergeRegister } from '@lexical/utils'\nimport { div, each, text, type Signal } from '@llui/dom'\nimport { definePluginUI } from './ui.js'\nimport { OVERLAY_Z, hideOverlay, overlayRoot } from './overlay.js'\nimport type { CommandItem, MarkdownPlugin } from './types.js'\n\ninterface MenuItem {\n id: string\n label: string\n}\n\n/** A rendered row — the `active` flag is row-local so `each` highlights reliably. */\ninterface MenuRow {\n id: string\n label: string\n active: boolean\n}\n\ninterface SlashState {\n open: boolean\n query: string\n items: MenuRow[]\n index: number\n x: number\n y: number\n}\n\nfunction withActive(items: readonly MenuItem[], index: number): MenuRow[] {\n return items.map((it, i) => ({ id: it.id, label: it.label, active: i === index }))\n}\n\ntype SlashMsg =\n | { type: 'show'; query: string; items: MenuItem[]; x: number; y: number }\n | { type: 'hide' }\n | { type: 'move'; delta: number }\n | { type: 'choose' }\n | { type: 'click'; index: number }\n\ntype SlashEffect = { type: 'run'; id: string; query: string }\n\nconst TRIGGER = /(?:^|\\s)\\/([\\w-]*)$/\n\n/** Read the active slash query before the collapsed caret, or null. */\nfunction $readSlashQuery(): string | null {\n const selection = $getSelection()\n if (!$isRangeSelection(selection) || !selection.isCollapsed()) return null\n const node = selection.anchor.getNode()\n if (!$isTextNode(node)) return null\n const before = node.getTextContent().slice(0, selection.anchor.offset)\n const match = before.match(TRIGGER)\n return match ? (match[1] ?? '') : null\n}\n\nfunction caretXY(): { x: number; y: number } {\n if (typeof window === 'undefined') return { x: 0, y: 0 }\n const sel = window.getSelection()\n if (sel && sel.rangeCount > 0) {\n const rect = sel.getRangeAt(0).getBoundingClientRect()\n return { x: rect.left, y: rect.bottom + 4 }\n }\n return { x: 0, y: 0 }\n}\n\nfunction matches(item: CommandItem, query: string): boolean {\n if (query === '') return true\n const q = query.toLowerCase()\n if (item.label.toLowerCase().includes(q) || item.id.toLowerCase().includes(q)) return true\n return (item.keywords ?? []).some((k) => k.toLowerCase().includes(q))\n}\n\nexport function slashPlugin(): MarkdownPlugin {\n let slashItems: CommandItem[] = []\n\n const filtered = (query: string): MenuItem[] =>\n slashItems.filter((i) => matches(i, query)).map((i) => ({ id: i.id, label: i.label }))\n\n return {\n name: 'slash',\n onItems: (items) => {\n slashItems = items.filter((i) =>\n i.surfaces\n ? i.surfaces.includes('slash')\n : ['block', 'list', 'insert'].includes(i.group ?? ''),\n )\n },\n register: (editor, ctx) => {\n const isActive = (): boolean => editor.getEditorState().read(() => $readSlashQuery() !== null)\n\n const refresh = (): void => {\n const query = editor.getEditorState().read(() => $readSlashQuery())\n if (query === null) {\n ctx.emit({ type: 'plugin', name: 'slash', msg: { type: 'hide' } })\n return\n }\n const items = filtered(query)\n const { x, y } = caretXY()\n ctx.emit({ type: 'plugin', name: 'slash', msg: { type: 'show', query, items, x, y } })\n }\n\n const nav = (delta: number, e: KeyboardEvent | null): boolean => {\n if (!isActive()) return false\n e?.preventDefault()\n ctx.emit({ type: 'plugin', name: 'slash', msg: { type: 'move', delta } })\n return true\n }\n\n return mergeRegister(\n editor.registerUpdateListener(() => refresh()),\n editor.registerCommand(KEY_ARROW_DOWN_COMMAND, (e) => nav(1, e), COMMAND_PRIORITY_HIGH),\n editor.registerCommand(KEY_ARROW_UP_COMMAND, (e) => nav(-1, e), COMMAND_PRIORITY_HIGH),\n editor.registerCommand(\n KEY_ENTER_COMMAND,\n (e) => {\n if (!isActive()) return false\n e?.preventDefault()\n ctx.emit({ type: 'plugin', name: 'slash', msg: { type: 'choose' } })\n return true\n },\n COMMAND_PRIORITY_HIGH,\n ),\n editor.registerCommand(\n KEY_ESCAPE_COMMAND,\n () => {\n if (!isActive()) return false\n ctx.emit({ type: 'plugin', name: 'slash', msg: { type: 'hide' } })\n return true\n },\n COMMAND_PRIORITY_HIGH,\n ),\n )\n },\n ui: definePluginUI<SlashState, SlashMsg, SlashEffect>({\n init: () => ({ open: false, query: '', items: [], index: 0, x: 0, y: 0 }),\n update: (state, msg) => {\n switch (msg.type) {\n case 'show':\n return {\n open: msg.items.length > 0,\n query: msg.query,\n items: withActive(msg.items, 0),\n index: 0,\n x: msg.x,\n y: msg.y,\n }\n case 'hide':\n return hideOverlay(state)\n case 'move': {\n if (!state.open || state.items.length === 0) return state\n const index = (state.index + msg.delta + state.items.length) % state.items.length\n return { ...state, index, items: withActive(state.items, index) }\n }\n case 'choose': {\n const item = state.items[state.index]\n if (!state.open || !item) return hideOverlay(state)\n return [{ ...state, open: false }, [{ type: 'run', id: item.id, query: state.query }]]\n }\n case 'click': {\n const item = state.items[msg.index]\n if (!item) return state\n return [{ ...state, open: false }, [{ type: 'run', id: item.id, query: state.query }]]\n }\n }\n },\n onEffect: (effect, ctx) => {\n const editor = ctx.editor()\n if (!editor) return\n // Remove the typed \"/query\" before running the command.\n editor.update(() => {\n const selection = $getSelection()\n if (!$isRangeSelection(selection) || !selection.isCollapsed()) return\n const node = selection.anchor.getNode()\n if (!$isTextNode(node)) return\n const offset = selection.anchor.offset\n const start = offset - effect.query.length - 1\n if (start >= 0) node.spliceText(start, effect.query.length + 1, '', true)\n })\n ctx.emit({ type: 'runCommand', id: effect.id })\n },\n view: ({ state, send }) =>\n overlayRoot({\n open: state.at('open'),\n x: state.at('x'),\n y: state.at('y'),\n zIndex: OVERLAY_Z.typeahead,\n attrs: { 'data-scope': 'md-slash', 'data-part': 'root' },\n children: () => [\n each(state.at('items') as Signal<MenuRow[]>, {\n key: (it) => it.id,\n render: (item, index) => [\n div(\n {\n 'data-scope': 'md-slash',\n 'data-part': 'option',\n 'data-active': item.map((it) => (it.active ? '' : undefined)),\n onMouseDown: (e: MouseEvent) => {\n e.preventDefault()\n send({ type: 'click', index: index.peek() })\n },\n },\n [text(item.map((it) => it.label))],\n ),\n ],\n }),\n ],\n }),\n }),\n }\n}\n"]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table.d.ts","sourceRoot":"","sources":["../../src/plugins/table.ts"],"names":[],"mappings":"AAoCA,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,YAAY,CAAA;AA+HhD,wBAAgB,WAAW,IAAI,cAAc,CAqH5C"}
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
// Table plugin — GFM tables backed by @lexical/table nodes, with a hand-written
|
|
2
|
+
// multiline transformer that imports `| a | b |` syntax to a TableNode and
|
|
3
|
+
// exports it back. Cells are ordinary editable paragraphs.
|
|
4
|
+
//
|
|
5
|
+
// Place `tablePlugin()` before `corePlugin()` so its multiline transformer is
|
|
6
|
+
// tried ahead of the generic code-block one.
|
|
7
|
+
import { $createParagraphNode, $createTextNode, $getSelection, $isRangeSelection, } from 'lexical';
|
|
8
|
+
import { $insertNodeToNearestRoot, mergeRegister } from '@lexical/utils';
|
|
9
|
+
import { $createTableCellNode, $createTableNode, $createTableRowNode, $deleteTableColumnAtSelection, $deleteTableRowAtSelection, $getTableCellNodeFromLexicalNode, $getTableNodeFromLexicalNodeOrThrow, $insertTableColumnAtSelection, $insertTableRowAtSelection, $isTableNode, $isTableSelection, TableCellHeaderStates, TableCellNode, TableNode, TableRowNode, } from '@lexical/table';
|
|
10
|
+
import { button, text } from '@llui/dom';
|
|
11
|
+
import { definePluginUI } from './ui.js';
|
|
12
|
+
import { OVERLAY_Z, hideOverlay, onViewportChange, overlayRoot } from './overlay.js';
|
|
13
|
+
const TABLE_TOOLS = [
|
|
14
|
+
{ id: 'rowAbove', label: '↑+', title: 'Insert row above' },
|
|
15
|
+
{ id: 'rowBelow', label: '↓+', title: 'Insert row below' },
|
|
16
|
+
{ id: 'colLeft', label: '←+', title: 'Insert column left' },
|
|
17
|
+
{ id: 'colRight', label: '→+', title: 'Insert column right' },
|
|
18
|
+
{ id: 'delRow', label: '✕R', title: 'Delete row' },
|
|
19
|
+
{ id: 'delCol', label: '✕C', title: 'Delete column' },
|
|
20
|
+
{ id: 'delTable', label: '🗑', title: 'Delete table' },
|
|
21
|
+
];
|
|
22
|
+
/** Run a table editing operation against the current cell selection. */
|
|
23
|
+
function $runTableOp(id) {
|
|
24
|
+
switch (id) {
|
|
25
|
+
case 'rowAbove':
|
|
26
|
+
$insertTableRowAtSelection(false);
|
|
27
|
+
return;
|
|
28
|
+
case 'rowBelow':
|
|
29
|
+
$insertTableRowAtSelection(true);
|
|
30
|
+
return;
|
|
31
|
+
case 'colLeft':
|
|
32
|
+
$insertTableColumnAtSelection(false);
|
|
33
|
+
return;
|
|
34
|
+
case 'colRight':
|
|
35
|
+
$insertTableColumnAtSelection(true);
|
|
36
|
+
return;
|
|
37
|
+
case 'delRow':
|
|
38
|
+
$deleteTableRowAtSelection();
|
|
39
|
+
return;
|
|
40
|
+
case 'delCol':
|
|
41
|
+
$deleteTableColumnAtSelection();
|
|
42
|
+
return;
|
|
43
|
+
case 'delTable': {
|
|
44
|
+
const selection = $getSelection();
|
|
45
|
+
// A focused cell is a RangeSelection; a multi-cell drag is a TableSelection.
|
|
46
|
+
// Both carry an anchor pointing into a cell — accept either.
|
|
47
|
+
if (!$isRangeSelection(selection) && !$isTableSelection(selection))
|
|
48
|
+
return;
|
|
49
|
+
const cell = $getTableCellNodeFromLexicalNode(selection.anchor.getNode());
|
|
50
|
+
if (cell)
|
|
51
|
+
$getTableNodeFromLexicalNodeOrThrow(cell).remove();
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function splitRow(line) {
|
|
57
|
+
return line
|
|
58
|
+
.trim()
|
|
59
|
+
.replace(/^\|/, '')
|
|
60
|
+
.replace(/\|$/, '')
|
|
61
|
+
.split('|')
|
|
62
|
+
.map((c) => c.trim().replace(/\\\|/g, '|'));
|
|
63
|
+
}
|
|
64
|
+
function isSeparator(line) {
|
|
65
|
+
return !!line && /^\s*\|?[\s:|-]*-[\s:|-]*\|?\s*$/.test(line) && line.includes('-');
|
|
66
|
+
}
|
|
67
|
+
function isRow(line) {
|
|
68
|
+
return !!line && /^\s*\|.*\|\s*$/.test(line.trim());
|
|
69
|
+
}
|
|
70
|
+
function makeCell(textValue, header) {
|
|
71
|
+
const cell = $createTableCellNode(header ? TableCellHeaderStates.COLUMN : TableCellHeaderStates.NO_STATUS);
|
|
72
|
+
cell.append($createParagraphNode().append($createTextNode(textValue)));
|
|
73
|
+
return cell;
|
|
74
|
+
}
|
|
75
|
+
function buildTable(header, body) {
|
|
76
|
+
const table = $createTableNode();
|
|
77
|
+
const headerRow = $createTableRowNode();
|
|
78
|
+
for (const h of header)
|
|
79
|
+
headerRow.append(makeCell(h, true));
|
|
80
|
+
table.append(headerRow);
|
|
81
|
+
for (const row of body) {
|
|
82
|
+
const tr = $createTableRowNode();
|
|
83
|
+
for (let i = 0; i < header.length; i++)
|
|
84
|
+
tr.append(makeCell(row[i] ?? '', false));
|
|
85
|
+
table.append(tr);
|
|
86
|
+
}
|
|
87
|
+
return table;
|
|
88
|
+
}
|
|
89
|
+
const TABLE_TRANSFORMER = {
|
|
90
|
+
dependencies: [TableNode, TableRowNode, TableCellNode],
|
|
91
|
+
export: (node) => {
|
|
92
|
+
if (!$isTableNode(node))
|
|
93
|
+
return null;
|
|
94
|
+
const rows = node.getChildren();
|
|
95
|
+
if (rows.length === 0)
|
|
96
|
+
return null;
|
|
97
|
+
const lines = [];
|
|
98
|
+
rows.forEach((row, ri) => {
|
|
99
|
+
const cells = row.getChildren();
|
|
100
|
+
const texts = cells.map((c) => c.getTextContent().replace(/\|/g, '\\|').replace(/\n/g, ' '));
|
|
101
|
+
lines.push('| ' + texts.join(' | ') + ' |');
|
|
102
|
+
if (ri === 0)
|
|
103
|
+
lines.push('| ' + texts.map(() => '---').join(' | ') + ' |');
|
|
104
|
+
});
|
|
105
|
+
return lines.join('\n');
|
|
106
|
+
},
|
|
107
|
+
regExpStart: /^\s*\|(.+)\|\s*$/,
|
|
108
|
+
handleImportAfterStartMatch: ({ lines, rootNode, startLineIndex }) => {
|
|
109
|
+
if (!isSeparator(lines[startLineIndex + 1]))
|
|
110
|
+
return null; // header must be followed by a separator
|
|
111
|
+
let end = startLineIndex + 2;
|
|
112
|
+
const body = [];
|
|
113
|
+
while (end < lines.length && isRow(lines[end]) && !isSeparator(lines[end])) {
|
|
114
|
+
body.push(splitRow(lines[end]));
|
|
115
|
+
end++;
|
|
116
|
+
}
|
|
117
|
+
rootNode.append(buildTable(splitRow(lines[startLineIndex]), body));
|
|
118
|
+
return [true, end - 1];
|
|
119
|
+
},
|
|
120
|
+
// Import is handled manually above; a multiline transformer still needs replace.
|
|
121
|
+
replace: () => false,
|
|
122
|
+
type: 'multiline-element',
|
|
123
|
+
};
|
|
124
|
+
export function tablePlugin() {
|
|
125
|
+
return {
|
|
126
|
+
name: 'table',
|
|
127
|
+
nodes: [TableNode, TableRowNode, TableCellNode],
|
|
128
|
+
transformers: [TABLE_TRANSFORMER],
|
|
129
|
+
// A contextual toolbar appears above the table whenever the selection is in a
|
|
130
|
+
// cell. It tracks the table's viewport rect, so it repositions on scroll/resize
|
|
131
|
+
// (not just on editor updates) and only re-emits when the rect actually moves.
|
|
132
|
+
register: (editor, ctx) => {
|
|
133
|
+
let lastKey = null;
|
|
134
|
+
let lastX = NaN;
|
|
135
|
+
let lastY = NaN;
|
|
136
|
+
const refresh = () => {
|
|
137
|
+
const tableKey = editor.getEditorState().read(() => {
|
|
138
|
+
const selection = $getSelection();
|
|
139
|
+
// RangeSelection = caret in a cell; TableSelection = multi-cell drag.
|
|
140
|
+
if (!$isRangeSelection(selection) && !$isTableSelection(selection))
|
|
141
|
+
return null;
|
|
142
|
+
const cell = $getTableCellNodeFromLexicalNode(selection.anchor.getNode());
|
|
143
|
+
return cell ? $getTableNodeFromLexicalNodeOrThrow(cell).getKey() : null;
|
|
144
|
+
});
|
|
145
|
+
const el = tableKey ? editor.getElementByKey(tableKey) : null;
|
|
146
|
+
if (!el) {
|
|
147
|
+
if (lastKey !== null) {
|
|
148
|
+
lastKey = null;
|
|
149
|
+
lastX = NaN;
|
|
150
|
+
lastY = NaN;
|
|
151
|
+
ctx.emit({ type: 'plugin', name: 'table', msg: { type: 'hide' } });
|
|
152
|
+
}
|
|
153
|
+
return;
|
|
154
|
+
}
|
|
155
|
+
const rect = el.getBoundingClientRect();
|
|
156
|
+
// Typing inside a cell never moves the table's top-left; skip the redundant
|
|
157
|
+
// emit so the overlay isn't reconciled on every keystroke.
|
|
158
|
+
if (tableKey === lastKey && rect.left === lastX && rect.top === lastY)
|
|
159
|
+
return;
|
|
160
|
+
lastKey = tableKey;
|
|
161
|
+
lastX = rect.left;
|
|
162
|
+
lastY = rect.top;
|
|
163
|
+
ctx.emit({
|
|
164
|
+
type: 'plugin',
|
|
165
|
+
name: 'table',
|
|
166
|
+
msg: { type: 'show', x: rect.left, y: rect.top },
|
|
167
|
+
});
|
|
168
|
+
};
|
|
169
|
+
return mergeRegister(editor.registerUpdateListener(() => refresh()), onViewportChange(refresh));
|
|
170
|
+
},
|
|
171
|
+
ui: definePluginUI({
|
|
172
|
+
init: () => ({ open: false, x: 0, y: 0 }),
|
|
173
|
+
update: (state, msg) => {
|
|
174
|
+
switch (msg.type) {
|
|
175
|
+
case 'show':
|
|
176
|
+
return state.open && state.x === msg.x && state.y === msg.y
|
|
177
|
+
? state
|
|
178
|
+
: { open: true, x: msg.x, y: msg.y };
|
|
179
|
+
case 'hide':
|
|
180
|
+
return hideOverlay(state);
|
|
181
|
+
case 'op':
|
|
182
|
+
return [state, [{ type: 'run', id: msg.id }]];
|
|
183
|
+
}
|
|
184
|
+
},
|
|
185
|
+
onEffect: (effect, ctx) => {
|
|
186
|
+
const editor = ctx.editor();
|
|
187
|
+
if (editor)
|
|
188
|
+
editor.update(() => $runTableOp(effect.id));
|
|
189
|
+
},
|
|
190
|
+
view: ({ state, send }) => overlayRoot({
|
|
191
|
+
open: state.at('open'),
|
|
192
|
+
x: state.at('x'),
|
|
193
|
+
y: state.at('y'),
|
|
194
|
+
zIndex: OVERLAY_Z.tableTools,
|
|
195
|
+
// Lift the bar above the table's top edge.
|
|
196
|
+
transform: 'transform:translateY(-118%)',
|
|
197
|
+
attrs: { 'data-scope': 'md-table-tools', 'data-part': 'bar' },
|
|
198
|
+
children: () => TABLE_TOOLS.map((tool) => button({
|
|
199
|
+
type: 'button',
|
|
200
|
+
'data-scope': 'md-table-tools',
|
|
201
|
+
'data-part': 'tool',
|
|
202
|
+
title: tool.title,
|
|
203
|
+
'aria-label': tool.title,
|
|
204
|
+
onMouseDown: (e) => {
|
|
205
|
+
e.preventDefault();
|
|
206
|
+
send({ type: 'op', id: tool.id });
|
|
207
|
+
},
|
|
208
|
+
}, [text(tool.label)])),
|
|
209
|
+
}),
|
|
210
|
+
}),
|
|
211
|
+
items: [
|
|
212
|
+
{
|
|
213
|
+
id: 'table',
|
|
214
|
+
label: 'Table',
|
|
215
|
+
icon: 'table',
|
|
216
|
+
group: 'insert',
|
|
217
|
+
keywords: ['grid', 'rows', 'columns'],
|
|
218
|
+
run: (editor) => editor.update(() => $insertNodeToNearestRoot(buildTable(['Column 1', 'Column 2'], [
|
|
219
|
+
['', ''],
|
|
220
|
+
['', ''],
|
|
221
|
+
]))),
|
|
222
|
+
surfaces: ['toolbar', 'slash', 'context'],
|
|
223
|
+
},
|
|
224
|
+
],
|
|
225
|
+
};
|
|
226
|
+
}
|
|
227
|
+
//# sourceMappingURL=table.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"table.js","sourceRoot":"","sources":["../../src/plugins/table.ts"],"names":[],"mappings":"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","sourcesContent":["// Table plugin — GFM tables backed by @lexical/table nodes, with a hand-written\n// multiline transformer that imports `| a | b |` syntax to a TableNode and\n// exports it back. Cells are ordinary editable paragraphs.\n//\n// Place `tablePlugin()` before `corePlugin()` so its multiline transformer is\n// tried ahead of the generic code-block one.\n\nimport {\n $createParagraphNode,\n $createTextNode,\n $getSelection,\n $isRangeSelection,\n type LexicalNode,\n} from 'lexical'\nimport { $insertNodeToNearestRoot, mergeRegister } from '@lexical/utils'\nimport {\n $createTableCellNode,\n $createTableNode,\n $createTableRowNode,\n $deleteTableColumnAtSelection,\n $deleteTableRowAtSelection,\n $getTableCellNodeFromLexicalNode,\n $getTableNodeFromLexicalNodeOrThrow,\n $insertTableColumnAtSelection,\n $insertTableRowAtSelection,\n $isTableNode,\n $isTableSelection,\n TableCellHeaderStates,\n TableCellNode,\n TableNode,\n TableRowNode,\n} from '@lexical/table'\nimport type { MultilineElementTransformer } from '@lexical/markdown'\nimport { button, text } from '@llui/dom'\nimport { definePluginUI } from './ui.js'\nimport { OVERLAY_Z, hideOverlay, onViewportChange, overlayRoot } from './overlay.js'\nimport type { MarkdownPlugin } from './types.js'\n\ninterface TableToolsState {\n open: boolean\n x: number\n y: number\n}\n\ntype TableToolsMsg =\n | { type: 'show'; x: number; y: number }\n | { type: 'hide' }\n | { type: 'op'; id: string }\ntype TableToolsEffect = { type: 'run'; id: string }\n\nconst TABLE_TOOLS: ReadonlyArray<{ id: string; label: string; title: string }> = [\n { id: 'rowAbove', label: '↑+', title: 'Insert row above' },\n { id: 'rowBelow', label: '↓+', title: 'Insert row below' },\n { id: 'colLeft', label: '←+', title: 'Insert column left' },\n { id: 'colRight', label: '→+', title: 'Insert column right' },\n { id: 'delRow', label: '✕R', title: 'Delete row' },\n { id: 'delCol', label: '✕C', title: 'Delete column' },\n { id: 'delTable', label: '🗑', title: 'Delete table' },\n]\n\n/** Run a table editing operation against the current cell selection. */\nfunction $runTableOp(id: string): void {\n switch (id) {\n case 'rowAbove':\n $insertTableRowAtSelection(false)\n return\n case 'rowBelow':\n $insertTableRowAtSelection(true)\n return\n case 'colLeft':\n $insertTableColumnAtSelection(false)\n return\n case 'colRight':\n $insertTableColumnAtSelection(true)\n return\n case 'delRow':\n $deleteTableRowAtSelection()\n return\n case 'delCol':\n $deleteTableColumnAtSelection()\n return\n case 'delTable': {\n const selection = $getSelection()\n // A focused cell is a RangeSelection; a multi-cell drag is a TableSelection.\n // Both carry an anchor pointing into a cell — accept either.\n if (!$isRangeSelection(selection) && !$isTableSelection(selection)) return\n const cell = $getTableCellNodeFromLexicalNode(selection.anchor.getNode())\n if (cell) $getTableNodeFromLexicalNodeOrThrow(cell).remove()\n return\n }\n }\n}\n\nfunction splitRow(line: string): string[] {\n return line\n .trim()\n .replace(/^\\|/, '')\n .replace(/\\|$/, '')\n .split('|')\n .map((c) => c.trim().replace(/\\\\\\|/g, '|'))\n}\n\nfunction isSeparator(line: string | undefined): boolean {\n return !!line && /^\\s*\\|?[\\s:|-]*-[\\s:|-]*\\|?\\s*$/.test(line) && line.includes('-')\n}\n\nfunction isRow(line: string | undefined): boolean {\n return !!line && /^\\s*\\|.*\\|\\s*$/.test(line.trim())\n}\n\nfunction makeCell(textValue: string, header: boolean): TableCellNode {\n const cell = $createTableCellNode(\n header ? TableCellHeaderStates.COLUMN : TableCellHeaderStates.NO_STATUS,\n )\n cell.append($createParagraphNode().append($createTextNode(textValue)))\n return cell\n}\n\nfunction buildTable(header: string[], body: string[][]): TableNode {\n const table = $createTableNode()\n const headerRow = $createTableRowNode()\n for (const h of header) headerRow.append(makeCell(h, true))\n table.append(headerRow)\n for (const row of body) {\n const tr = $createTableRowNode()\n for (let i = 0; i < header.length; i++) tr.append(makeCell(row[i] ?? '', false))\n table.append(tr)\n }\n return table\n}\n\nconst TABLE_TRANSFORMER: MultilineElementTransformer = {\n dependencies: [TableNode, TableRowNode, TableCellNode],\n export: (node: LexicalNode): string | null => {\n if (!$isTableNode(node)) return null\n const rows = node.getChildren() as TableRowNode[]\n if (rows.length === 0) return null\n const lines: string[] = []\n rows.forEach((row, ri) => {\n const cells = row.getChildren() as TableCellNode[]\n const texts = cells.map((c) => c.getTextContent().replace(/\\|/g, '\\\\|').replace(/\\n/g, ' '))\n lines.push('| ' + texts.join(' | ') + ' |')\n if (ri === 0) lines.push('| ' + texts.map(() => '---').join(' | ') + ' |')\n })\n return lines.join('\\n')\n },\n regExpStart: /^\\s*\\|(.+)\\|\\s*$/,\n handleImportAfterStartMatch: ({ lines, rootNode, startLineIndex }) => {\n if (!isSeparator(lines[startLineIndex + 1])) return null // header must be followed by a separator\n let end = startLineIndex + 2\n const body: string[][] = []\n while (end < lines.length && isRow(lines[end]) && !isSeparator(lines[end])) {\n body.push(splitRow(lines[end]!))\n end++\n }\n rootNode.append(buildTable(splitRow(lines[startLineIndex]!), body))\n return [true, end - 1]\n },\n // Import is handled manually above; a multiline transformer still needs replace.\n replace: () => false,\n type: 'multiline-element',\n}\n\nexport function tablePlugin(): MarkdownPlugin {\n return {\n name: 'table',\n nodes: [TableNode, TableRowNode, TableCellNode],\n transformers: [TABLE_TRANSFORMER],\n // A contextual toolbar appears above the table whenever the selection is in a\n // cell. It tracks the table's viewport rect, so it repositions on scroll/resize\n // (not just on editor updates) and only re-emits when the rect actually moves.\n register: (editor, ctx) => {\n let lastKey: string | null = null\n let lastX = NaN\n let lastY = NaN\n const refresh = (): void => {\n const tableKey = editor.getEditorState().read(() => {\n const selection = $getSelection()\n // RangeSelection = caret in a cell; TableSelection = multi-cell drag.\n if (!$isRangeSelection(selection) && !$isTableSelection(selection)) return null\n const cell = $getTableCellNodeFromLexicalNode(selection.anchor.getNode())\n return cell ? $getTableNodeFromLexicalNodeOrThrow(cell).getKey() : null\n })\n const el = tableKey ? editor.getElementByKey(tableKey) : null\n if (!el) {\n if (lastKey !== null) {\n lastKey = null\n lastX = NaN\n lastY = NaN\n ctx.emit({ type: 'plugin', name: 'table', msg: { type: 'hide' } })\n }\n return\n }\n const rect = el.getBoundingClientRect()\n // Typing inside a cell never moves the table's top-left; skip the redundant\n // emit so the overlay isn't reconciled on every keystroke.\n if (tableKey === lastKey && rect.left === lastX && rect.top === lastY) return\n lastKey = tableKey\n lastX = rect.left\n lastY = rect.top\n ctx.emit({\n type: 'plugin',\n name: 'table',\n msg: { type: 'show', x: rect.left, y: rect.top },\n })\n }\n return mergeRegister(\n editor.registerUpdateListener(() => refresh()),\n onViewportChange(refresh),\n )\n },\n ui: definePluginUI<TableToolsState, TableToolsMsg, TableToolsEffect>({\n init: () => ({ open: false, x: 0, y: 0 }),\n update: (state, msg) => {\n switch (msg.type) {\n case 'show':\n return state.open && state.x === msg.x && state.y === msg.y\n ? state\n : { open: true, x: msg.x, y: msg.y }\n case 'hide':\n return hideOverlay(state)\n case 'op':\n return [state, [{ type: 'run', id: msg.id }]]\n }\n },\n onEffect: (effect, ctx) => {\n const editor = ctx.editor()\n if (editor) editor.update(() => $runTableOp(effect.id))\n },\n view: ({ state, send }) =>\n overlayRoot({\n open: state.at('open'),\n x: state.at('x'),\n y: state.at('y'),\n zIndex: OVERLAY_Z.tableTools,\n // Lift the bar above the table's top edge.\n transform: 'transform:translateY(-118%)',\n attrs: { 'data-scope': 'md-table-tools', 'data-part': 'bar' },\n children: () =>\n TABLE_TOOLS.map((tool) =>\n button(\n {\n type: 'button',\n 'data-scope': 'md-table-tools',\n 'data-part': 'tool',\n title: tool.title,\n 'aria-label': tool.title,\n onMouseDown: (e: MouseEvent) => {\n e.preventDefault()\n send({ type: 'op', id: tool.id })\n },\n },\n [text(tool.label)],\n ),\n ),\n }),\n }),\n items: [\n {\n id: 'table',\n label: 'Table',\n icon: 'table',\n group: 'insert',\n keywords: ['grid', 'rows', 'columns'],\n run: (editor) =>\n editor.update(() =>\n $insertNodeToNearestRoot(\n buildTable(\n ['Column 1', 'Column 2'],\n [\n ['', ''],\n ['', ''],\n ],\n ),\n ),\n ),\n surfaces: ['toolbar', 'slash', 'context'],\n },\n ],\n }\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { LexicalEditor } from 'lexical';
|
|
2
|
+
import type { Transformer } from '@lexical/markdown';
|
|
3
|
+
import type { LexicalPlugin } from '@llui/lexical';
|
|
4
|
+
import type { EditorMsg, EditorOutMsg, FormatState } from '../state.js';
|
|
5
|
+
import type { PluginUI } from './ui.js';
|
|
6
|
+
/** Which surfaces a command item appears in (default: all). */
|
|
7
|
+
export type ItemSurface = 'toolbar' | 'floating' | 'slash' | 'context';
|
|
8
|
+
/** Handed to a command item's `run` so it can talk back to the host (e.g. open
|
|
9
|
+
* the link dialog) instead of only mutating the editor. */
|
|
10
|
+
export interface CommandContext {
|
|
11
|
+
send: (msg: EditorMsg) => void;
|
|
12
|
+
}
|
|
13
|
+
/** A user-invokable editor command surfaced to the chrome. Its reactive
|
|
14
|
+
* active/disabled state is read from {@link FormatState}; `run` mutates the
|
|
15
|
+
* live editor. */
|
|
16
|
+
export interface CommandItem {
|
|
17
|
+
/** Stable id (also the `runCommand` payload). */
|
|
18
|
+
id: string;
|
|
19
|
+
label: string;
|
|
20
|
+
/** Optional icon hint (class / svg id); rendering is the consumer's CSS. */
|
|
21
|
+
icon?: string;
|
|
22
|
+
/** Grouping key for menu sectioning. */
|
|
23
|
+
group?: string;
|
|
24
|
+
/** Keyword aliases for slash/command-palette filtering. */
|
|
25
|
+
keywords?: readonly string[];
|
|
26
|
+
isActive?: (format: FormatState) => boolean;
|
|
27
|
+
isDisabled?: (format: FormatState) => boolean;
|
|
28
|
+
run: (editor: LexicalEditor, ctx: CommandContext) => void;
|
|
29
|
+
surfaces?: readonly ItemSurface[];
|
|
30
|
+
}
|
|
31
|
+
/** A markdown editor plugin: engine wiring + transformers + UI items + an
|
|
32
|
+
* optional stateful UI extension (its own state slice, reducer, view, effects). */
|
|
33
|
+
export interface MarkdownPlugin extends LexicalPlugin<EditorOutMsg> {
|
|
34
|
+
/** Markdown ↔ node transformers contributed to the registry. */
|
|
35
|
+
transformers?: readonly Transformer[];
|
|
36
|
+
/** Command items surfaced to the toolbar / slash / context menus. */
|
|
37
|
+
items?: readonly CommandItem[];
|
|
38
|
+
/** A stateful UI extension keyed by this plugin's `name` (see {@link definePluginUI}). */
|
|
39
|
+
ui?: PluginUI;
|
|
40
|
+
/** Receive the merged command items from all plugins (e.g. a slash menu lists
|
|
41
|
+
* every plugin's items). Called once at editor construction. */
|
|
42
|
+
onItems?: (items: readonly CommandItem[]) => void;
|
|
43
|
+
}
|
|
44
|
+
//# sourceMappingURL=types.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/plugins/types.ts"],"names":[],"mappings":"AAGA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,mBAAmB,CAAA;AACpD,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,eAAe,CAAA;AAClD,OAAO,KAAK,EAAE,SAAS,EAAE,YAAY,EAAE,WAAW,EAAE,MAAM,aAAa,CAAA;AACvE,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAA;AAEvC,+DAA+D;AAC/D,MAAM,MAAM,WAAW,GAAG,SAAS,GAAG,UAAU,GAAG,OAAO,GAAG,SAAS,CAAA;AAEtE;2DAC2D;AAC3D,MAAM,WAAW,cAAc;IAC7B,IAAI,EAAE,CAAC,GAAG,EAAE,SAAS,KAAK,IAAI,CAAA;CAC/B;AAED;;kBAEkB;AAClB,MAAM,WAAW,WAAW;IAC1B,iDAAiD;IACjD,EAAE,EAAE,MAAM,CAAA;IACV,KAAK,EAAE,MAAM,CAAA;IACb,4EAA4E;IAC5E,IAAI,CAAC,EAAE,MAAM,CAAA;IACb,wCAAwC;IACxC,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2DAA2D;IAC3D,QAAQ,CAAC,EAAE,SAAS,MAAM,EAAE,CAAA;IAC5B,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAA;IAC3C,UAAU,CAAC,EAAE,CAAC,MAAM,EAAE,WAAW,KAAK,OAAO,CAAA;IAC7C,GAAG,EAAE,CAAC,MAAM,EAAE,aAAa,EAAE,GAAG,EAAE,cAAc,KAAK,IAAI,CAAA;IACzD,QAAQ,CAAC,EAAE,SAAS,WAAW,EAAE,CAAA;CAClC;AAED;mFACmF;AACnF,MAAM,WAAW,cAAe,SAAQ,aAAa,CAAC,YAAY,CAAC;IACjE,gEAAgE;IAChE,YAAY,CAAC,EAAE,SAAS,WAAW,EAAE,CAAA;IACrC,qEAAqE;IACrE,KAAK,CAAC,EAAE,SAAS,WAAW,EAAE,CAAA;IAC9B,0FAA0F;IAC1F,EAAE,CAAC,EAAE,QAAQ,CAAA;IACb;oEACgE;IAChE,OAAO,CAAC,EAAE,CAAC,KAAK,EAAE,SAAS,WAAW,EAAE,KAAK,IAAI,CAAA;CAClD"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/plugins/types.ts"],"names":[],"mappings":"AAAA,4EAA4E;AAC5E,0EAA0E","sourcesContent":["// The markdown plugin contract: extends the engine-level LexicalPlugin with\n// markdown transformers and UI command items (toolbar / slash / context).\n\nimport type { LexicalEditor } from 'lexical'\nimport type { Transformer } from '@lexical/markdown'\nimport type { LexicalPlugin } from '@llui/lexical'\nimport type { EditorMsg, EditorOutMsg, FormatState } from '../state.js'\nimport type { PluginUI } from './ui.js'\n\n/** Which surfaces a command item appears in (default: all). */\nexport type ItemSurface = 'toolbar' | 'floating' | 'slash' | 'context'\n\n/** Handed to a command item's `run` so it can talk back to the host (e.g. open\n * the link dialog) instead of only mutating the editor. */\nexport interface CommandContext {\n send: (msg: EditorMsg) => void\n}\n\n/** A user-invokable editor command surfaced to the chrome. Its reactive\n * active/disabled state is read from {@link FormatState}; `run` mutates the\n * live editor. */\nexport interface CommandItem {\n /** Stable id (also the `runCommand` payload). */\n id: string\n label: string\n /** Optional icon hint (class / svg id); rendering is the consumer's CSS. */\n icon?: string\n /** Grouping key for menu sectioning. */\n group?: string\n /** Keyword aliases for slash/command-palette filtering. */\n keywords?: readonly string[]\n isActive?: (format: FormatState) => boolean\n isDisabled?: (format: FormatState) => boolean\n run: (editor: LexicalEditor, ctx: CommandContext) => void\n surfaces?: readonly ItemSurface[]\n}\n\n/** A markdown editor plugin: engine wiring + transformers + UI items + an\n * optional stateful UI extension (its own state slice, reducer, view, effects). */\nexport interface MarkdownPlugin extends LexicalPlugin<EditorOutMsg> {\n /** Markdown ↔ node transformers contributed to the registry. */\n transformers?: readonly Transformer[]\n /** Command items surfaced to the toolbar / slash / context menus. */\n items?: readonly CommandItem[]\n /** A stateful UI extension keyed by this plugin's `name` (see {@link definePluginUI}). */\n ui?: PluginUI\n /** Receive the merged command items from all plugins (e.g. a slash menu lists\n * every plugin's items). Called once at editor construction. */\n onItems?: (items: readonly CommandItem[]) => void\n}\n"]}
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
import type { LexicalEditor } from 'lexical';
|
|
2
|
+
import type { Renderable, Signal } from '@llui/dom';
|
|
3
|
+
/** Context for a plugin's `onEffect` — reach the live editor and dispatch back. */
|
|
4
|
+
export interface PluginEffectContext<M> {
|
|
5
|
+
/** The live Lexical editor (null before mount). */
|
|
6
|
+
editor: () => LexicalEditor | null;
|
|
7
|
+
/** Dispatch a message back into this plugin. */
|
|
8
|
+
send: (msg: M) => void;
|
|
9
|
+
/** Dispatch a host editor message (e.g. `{type:'runCommand', id}`). */
|
|
10
|
+
emit: (msg: unknown) => void;
|
|
11
|
+
}
|
|
12
|
+
/** Args for a plugin's `view` — its reactive state slice + a scoped dispatcher. */
|
|
13
|
+
export interface PluginViewArgs<S, M> {
|
|
14
|
+
state: Signal<S>;
|
|
15
|
+
send: (msg: M) => void;
|
|
16
|
+
editor: () => LexicalEditor | null;
|
|
17
|
+
}
|
|
18
|
+
/** A typed plugin UI module (authored via {@link definePluginUI}). */
|
|
19
|
+
export interface PluginUISpec<S, M, E = never> {
|
|
20
|
+
/** Initial slice state (JSON-serializable). */
|
|
21
|
+
init: () => S;
|
|
22
|
+
/** Pure reducer over the slice; may return effects. */
|
|
23
|
+
update?: (state: S, msg: M) => S | [S, E[]];
|
|
24
|
+
/** View contribution (overlays/panels), rendered by the host. */
|
|
25
|
+
view?: (args: PluginViewArgs<S, M>) => Renderable;
|
|
26
|
+
/** Effect handler with live-editor access + host dispatch. */
|
|
27
|
+
onEffect?: (effect: E, ctx: PluginEffectContext<M>) => void;
|
|
28
|
+
}
|
|
29
|
+
/** The host message type a plugin effect may emit (the editor's full Msg). */
|
|
30
|
+
export type HostEmit = (msg: unknown) => void;
|
|
31
|
+
/** The type-erased form stored on a plugin and consumed by the host. */
|
|
32
|
+
export interface PluginUI {
|
|
33
|
+
init: () => unknown;
|
|
34
|
+
update?: (state: unknown, msg: unknown) => unknown | [unknown, unknown[]];
|
|
35
|
+
view?: (args: PluginViewArgs<unknown, unknown>) => Renderable;
|
|
36
|
+
onEffect?: (effect: unknown, ctx: PluginEffectContext<unknown>) => void;
|
|
37
|
+
}
|
|
38
|
+
/**
|
|
39
|
+
* Author a plugin UI module with full `State`/`Msg`/`Effect` types, erased for
|
|
40
|
+
* storage. The casts are confined to this boundary (the host only knows
|
|
41
|
+
* `unknown`), exactly like the decorator bridge.
|
|
42
|
+
*/
|
|
43
|
+
export declare function definePluginUI<S, M, E = never>(spec: PluginUISpec<S, M, E>): PluginUI;
|
|
44
|
+
//# sourceMappingURL=ui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"ui.d.ts","sourceRoot":"","sources":["../../src/plugins/ui.ts"],"names":[],"mappings":"AASA,OAAO,KAAK,EAAE,aAAa,EAAE,MAAM,SAAS,CAAA;AAC5C,OAAO,KAAK,EAAE,UAAU,EAAE,MAAM,EAAE,MAAM,WAAW,CAAA;AAEnD,mFAAmF;AACnF,MAAM,WAAW,mBAAmB,CAAC,CAAC;IACpC,mDAAmD;IACnD,MAAM,EAAE,MAAM,aAAa,GAAG,IAAI,CAAA;IAClC,gDAAgD;IAChD,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAA;IACtB,uEAAuE;IACvE,IAAI,EAAE,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;CAC7B;AAED,mFAAmF;AACnF,MAAM,WAAW,cAAc,CAAC,CAAC,EAAE,CAAC;IAClC,KAAK,EAAE,MAAM,CAAC,CAAC,CAAC,CAAA;IAChB,IAAI,EAAE,CAAC,GAAG,EAAE,CAAC,KAAK,IAAI,CAAA;IACtB,MAAM,EAAE,MAAM,aAAa,GAAG,IAAI,CAAA;CACnC;AAED,sEAAsE;AACtE,MAAM,WAAW,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK;IAC3C,+CAA+C;IAC/C,IAAI,EAAE,MAAM,CAAC,CAAA;IACb,uDAAuD;IACvD,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,CAAC,EAAE,GAAG,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAA;IAC3C,iEAAiE;IACjE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,CAAC,EAAE,CAAC,CAAC,KAAK,UAAU,CAAA;IACjD,8DAA8D;IAC9D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,GAAG,EAAE,mBAAmB,CAAC,CAAC,CAAC,KAAK,IAAI,CAAA;CAC5D;AAED,8EAA8E;AAC9E,MAAM,MAAM,QAAQ,GAAG,CAAC,GAAG,EAAE,OAAO,KAAK,IAAI,CAAA;AAE7C,wEAAwE;AACxE,MAAM,WAAW,QAAQ;IACvB,IAAI,EAAE,MAAM,OAAO,CAAA;IACnB,MAAM,CAAC,EAAE,CAAC,KAAK,EAAE,OAAO,EAAE,GAAG,EAAE,OAAO,KAAK,OAAO,GAAG,CAAC,OAAO,EAAE,OAAO,EAAE,CAAC,CAAA;IACzE,IAAI,CAAC,EAAE,CAAC,IAAI,EAAE,cAAc,CAAC,OAAO,EAAE,OAAO,CAAC,KAAK,UAAU,CAAA;IAC7D,QAAQ,CAAC,EAAE,CAAC,MAAM,EAAE,OAAO,EAAE,GAAG,EAAE,mBAAmB,CAAC,OAAO,CAAC,KAAK,IAAI,CAAA;CACxE;AAED;;;;GAIG;AACH,wBAAgB,cAAc,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,GAAG,KAAK,EAAE,IAAI,EAAE,YAAY,CAAC,CAAC,EAAE,CAAC,EAAE,CAAC,CAAC,GAAG,QAAQ,CAqBrF"}
|