@haklex/rich-editor 0.1.1 → 0.3.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 (65) hide show
  1. package/dist/AlertQuoteEditNode-C55sxsR3.js +267 -0
  2. package/dist/KaTeXRenderer-CQQT3BMw.js +215 -0
  3. package/dist/LinkCardRenderer-CigqFwCv.js +45 -0
  4. package/dist/MermaidPlugin-BrOr-wQi.js +67 -0
  5. package/dist/RubyRenderer-jOkydJHg.js +15 -0
  6. package/dist/SubmitShortcutPlugin-DhyVFzoj.js +2186 -0
  7. package/dist/commands-entry.mjs +54 -74
  8. package/dist/components/decorators/PollEditDecorator.d.ts +13 -0
  9. package/dist/components/decorators/PollEditDecorator.d.ts.map +1 -0
  10. package/dist/components/renderers/PollRenderer.d.ts +3 -0
  11. package/dist/components/renderers/PollRenderer.d.ts.map +1 -0
  12. package/dist/config-B5BuLljq.js +1633 -0
  13. package/dist/config-edit.d.ts.map +1 -1
  14. package/dist/config.d.ts.map +1 -1
  15. package/dist/context/PollDataContext.d.ts +11 -0
  16. package/dist/context/PollDataContext.d.ts.map +1 -0
  17. package/dist/extractPolls-DO31LNrp.js +116 -0
  18. package/dist/grid.css-CJCkLTZc.js +44 -0
  19. package/dist/index.d.ts +5 -1
  20. package/dist/index.d.ts.map +1 -1
  21. package/dist/index.mjs +121 -180
  22. package/dist/katex.css-CIOEOXyd.js +145 -0
  23. package/dist/node-registry-Dz5OTkh4.js +946 -0
  24. package/dist/nodes/PollEditNode.d.ts +14 -0
  25. package/dist/nodes/PollEditNode.d.ts.map +1 -0
  26. package/dist/nodes/PollNode.d.ts +52 -0
  27. package/dist/nodes/PollNode.d.ts.map +1 -0
  28. package/dist/nodes-entry.d.ts +3 -0
  29. package/dist/nodes-entry.d.ts.map +1 -1
  30. package/dist/nodes-entry.mjs +5 -50
  31. package/dist/normalizeSerializedEditorState-B-1wmGzd.js +78 -0
  32. package/dist/plugins-entry.mjs +3 -28
  33. package/dist/renderers-entry.mjs +41 -61
  34. package/dist/rich-editor.css +2 -1
  35. package/dist/static-entry.d.ts +5 -0
  36. package/dist/static-entry.d.ts.map +1 -1
  37. package/dist/static-entry.mjs +16 -66
  38. package/dist/styles/index.d.ts +2 -0
  39. package/dist/styles/index.d.ts.map +1 -1
  40. package/dist/styles/poll-edit.css.d.ts +35 -0
  41. package/dist/styles/poll-edit.css.d.ts.map +1 -0
  42. package/dist/styles/poll.css.d.ts +43 -0
  43. package/dist/styles/poll.css.d.ts.map +1 -0
  44. package/dist/styles-entry.mjs +3 -21
  45. package/dist/theme-B5B2EOWM.js +1099 -0
  46. package/dist/types/poll.d.ts +36 -0
  47. package/dist/types/poll.d.ts.map +1 -0
  48. package/dist/types/renderer-config.d.ts +3 -0
  49. package/dist/types/renderer-config.d.ts.map +1 -1
  50. package/dist/utils/extractPolls.d.ts +4 -0
  51. package/dist/utils/extractPolls.d.ts.map +1 -0
  52. package/package.json +30 -30
  53. package/dist/AlertQuoteEditNode-sPNf3_7P.js +0 -293
  54. package/dist/KaTeXRenderer-CQyQzNTJ.js +0 -218
  55. package/dist/LinkCardRenderer-QmkOlyXb.js +0 -36
  56. package/dist/MermaidPlugin-DKuGUcCG.js +0 -101
  57. package/dist/PresentDialogContext-DRroMIoK.js +0 -71
  58. package/dist/RubyRenderer-CJQmODir.js +0 -14
  59. package/dist/SubmitShortcutPlugin-D9uKYHda.js +0 -2427
  60. package/dist/config-Dl3ZkytB.js +0 -1362
  61. package/dist/grid.css-Md5-Cfx_.js +0 -11
  62. package/dist/katex.css-Csc-7N7u.js +0 -28
  63. package/dist/node-registry-CovhHUB6.js +0 -824
  64. package/dist/normalizeSerializedEditorState-k5G4xSi9.js +0 -85
  65. package/dist/theme-lEwScxEX.js +0 -1113
@@ -0,0 +1,946 @@
1
+ import { C as ImageNode, E as FootnoteNode, _ as KaTeXInlineNode, b as KaTeXBlockNode, c as SpoilerNode, d as MermaidNode, i as TagNode, j as _defineProperty, m as MentionNode, t as editorTheme } from "./theme-B5B2EOWM.js";
2
+ import { l as RendererWrapper, s as useFootnoteDefinitions } from "./KaTeXRenderer-CQQT3BMw.js";
3
+ import { l as semanticClassNames, r as clsx, u as sharedStyles } from "./katex.css-CIOEOXyd.js";
4
+ import { C as GridContainerNode, E as FootnoteSectionNode, F as CodeBlockRenderer, H as normalizeBannerType, M as CommentNode, N as $isCodeBlockNode, O as DetailsNode, P as CodeBlockNode, R as $isBannerNode, S as $isGridContainerNode, U as BannerRenderer, V as BannerNode, b as LinkCardNode, i as VideoNode, l as $isPollNode, n as builtinNodes, s as RubyNode, u as PollNode } from "./config-B5BuLljq.js";
5
+ import { n as gridStyles, t as gridClassNames } from "./grid.css-CJCkLTZc.js";
6
+ import { n as AlertQuoteEditNode, r as NESTED_EDITOR_NODES } from "./AlertQuoteEditNode-C55sxsR3.js";
7
+ import { LinkPlugin } from "@lexical/react/LexicalLinkPlugin";
8
+ import { ListPlugin } from "@lexical/react/LexicalListPlugin";
9
+ import { useLexicalComposerContext } from "@lexical/react/LexicalComposerContext";
10
+ import { createElement, useCallback, useEffect, useState } from "react";
11
+ import { $createNodeSelection, $createParagraphNode, $getNodeByKey, $getRoot, $getSelection, $insertNodes, $isDecoratorNode, $isElementNode, $isNodeSelection, $nodesOfType, $setSelection, createEditor } from "lexical";
12
+ import { Fragment, jsx, jsxs } from "react/jsx-runtime";
13
+ import { Flag, LayoutGrid, Minus, Plus, Vote } from "lucide-react";
14
+ import { customAlphabet } from "nanoid";
15
+ import { ContentEditable } from "@lexical/react/LexicalContentEditable";
16
+ import { LexicalErrorBoundary } from "@lexical/react/LexicalErrorBoundary";
17
+ import { LexicalNestedComposer } from "@lexical/react/LexicalNestedComposer";
18
+ import { RichTextPlugin } from "@lexical/react/LexicalRichTextPlugin";
19
+ import { useLexicalNodeSelection } from "@lexical/react/useLexicalNodeSelection";
20
+ //#region src/utils/codeBlockSelectionIntent.ts
21
+ var codeBlockCursorIntentMap = /* @__PURE__ */ new Map();
22
+ function setCodeBlockCursorIntent(nodeKey, placement) {
23
+ codeBlockCursorIntentMap.set(nodeKey, placement);
24
+ }
25
+ function consumeCodeBlockCursorIntent(nodeKey) {
26
+ const placement = codeBlockCursorIntentMap.get(nodeKey);
27
+ if (!placement) return null;
28
+ codeBlockCursorIntentMap.delete(nodeKey);
29
+ return placement;
30
+ }
31
+ //#endregion
32
+ //#region src/components/decorators/BannerEditDecorator.tsx
33
+ function BannerEditDecorator({ nodeKey, bannerType, contentEditor }) {
34
+ const [editor] = useLexicalComposerContext();
35
+ const editable = editor.isEditable();
36
+ const handleTypeChange = useCallback((newType) => {
37
+ editor.update(() => {
38
+ const node = $getNodeByKey(nodeKey);
39
+ if ($isBannerNode(node)) node.setBannerType(newType);
40
+ });
41
+ }, [editor, nodeKey]);
42
+ return /* @__PURE__ */ jsxs("div", {
43
+ className: "rich-banner-inner",
44
+ children: [/* @__PURE__ */ jsx(RendererWrapper, {
45
+ defaultRenderer: BannerRenderer,
46
+ rendererKey: "Banner",
47
+ props: {
48
+ type: bannerType,
49
+ editable,
50
+ onTypeChange: editable ? handleTypeChange : void 0
51
+ }
52
+ }), /* @__PURE__ */ jsx("div", {
53
+ className: "rich-banner-content",
54
+ children: /* @__PURE__ */ jsxs(LexicalNestedComposer, {
55
+ initialEditor: contentEditor,
56
+ children: [
57
+ /* @__PURE__ */ jsx(RichTextPlugin, {
58
+ ErrorBoundary: LexicalErrorBoundary,
59
+ contentEditable: /* @__PURE__ */ jsx(ContentEditable, {
60
+ "aria-placeholder": "",
61
+ className: "rich-banner-content-editable",
62
+ placeholder: /* @__PURE__ */ jsx("span", { style: { display: "none" } }),
63
+ style: { outline: "none" }
64
+ })
65
+ }),
66
+ /* @__PURE__ */ jsx(ListPlugin, {}),
67
+ /* @__PURE__ */ jsx(LinkPlugin, {})
68
+ ]
69
+ })
70
+ })]
71
+ });
72
+ }
73
+ //#endregion
74
+ //#region src/nodes/BannerEditNode.ts
75
+ function createContentEditor() {
76
+ return createEditor({
77
+ namespace: "BannerContent",
78
+ nodes: NESTED_EDITOR_NODES,
79
+ theme: editorTheme,
80
+ onError: (error) => {
81
+ console.error("[BannerContent]", error);
82
+ }
83
+ });
84
+ }
85
+ var BannerEditNode = class BannerEditNode extends BannerNode {
86
+ static clone(node) {
87
+ const cloned = new BannerEditNode(node.__bannerType, node.__contentState, node.__key);
88
+ cloned.__contentEditor = node.__contentEditor;
89
+ return cloned;
90
+ }
91
+ constructor(bannerType, contentState, key) {
92
+ super(bannerType, contentState, key);
93
+ _defineProperty(this, "__contentEditor", void 0);
94
+ this.__contentEditor = createContentEditor();
95
+ if (contentState) {
96
+ const editorState = this.__contentEditor.parseEditorState(contentState);
97
+ this.__contentEditor.setEditorState(editorState);
98
+ }
99
+ }
100
+ getContentEditor() {
101
+ return this.__contentEditor;
102
+ }
103
+ static importJSON(serializedNode) {
104
+ const legacy = serializedNode;
105
+ const bannerType = normalizeBannerType(serializedNode.bannerType);
106
+ if (serializedNode.content) return new BannerEditNode(bannerType, serializedNode.content);
107
+ if (legacy.children) return new BannerEditNode(bannerType, { root: {
108
+ children: legacy.children,
109
+ direction: null,
110
+ format: "",
111
+ indent: 0,
112
+ type: "root",
113
+ version: 1
114
+ } });
115
+ return new BannerEditNode(bannerType);
116
+ }
117
+ exportJSON() {
118
+ return {
119
+ ...super.exportJSON(),
120
+ type: "banner",
121
+ bannerType: this.__bannerType,
122
+ content: this.__contentEditor.getEditorState().toJSON(),
123
+ version: 1
124
+ };
125
+ }
126
+ decorate(_editor, _config) {
127
+ return createElement(BannerEditDecorator, {
128
+ nodeKey: this.__key,
129
+ bannerType: this.__bannerType,
130
+ contentEditor: this.__contentEditor
131
+ });
132
+ }
133
+ };
134
+ _defineProperty(BannerEditNode, "commandItems", [{
135
+ title: "Banner",
136
+ icon: createElement(Flag, { size: 20 }),
137
+ description: "Highlighted banner block",
138
+ keywords: [
139
+ "banner",
140
+ "notice",
141
+ "announcement"
142
+ ],
143
+ section: "ADVANCED",
144
+ placement: ["slash", "toolbar"],
145
+ group: "insert",
146
+ onSelect: (editor) => {
147
+ editor.update(() => {
148
+ $insertNodes([$createBannerEditNode("note")]);
149
+ });
150
+ }
151
+ }]);
152
+ function $createBannerEditNode(bannerType, contentState) {
153
+ return new BannerEditNode(bannerType, contentState);
154
+ }
155
+ //#endregion
156
+ //#region src/components/decorators/CodeBlockEditDecorator.tsx
157
+ function CodeBlockEditDecorator({ nodeKey, code, language }) {
158
+ const [editor] = useLexicalComposerContext();
159
+ const [isSelected] = useLexicalNodeSelection(nodeKey);
160
+ const [shouldFocusEditor, setShouldFocusEditor] = useState(false);
161
+ const [cursorPlacement, setCursorPlacement] = useState("start");
162
+ const editable = editor.isEditable();
163
+ useEffect(() => {
164
+ if (!editable || !isSelected) {
165
+ setShouldFocusEditor(false);
166
+ return;
167
+ }
168
+ let nextShouldFocus = false;
169
+ let nextCursorPlacement = "start";
170
+ editor.getEditorState().read(() => {
171
+ const selection = $getSelection();
172
+ const selectedNodes = $isNodeSelection(selection) ? selection.getNodes() : null;
173
+ nextShouldFocus = selectedNodes !== null && selectedNodes.length === 1 && selectedNodes[0]?.getKey() === nodeKey;
174
+ if (nextShouldFocus) nextCursorPlacement = consumeCodeBlockCursorIntent(nodeKey) ?? "start";
175
+ });
176
+ setShouldFocusEditor(nextShouldFocus);
177
+ if (nextShouldFocus) setCursorPlacement(nextCursorPlacement);
178
+ }, [
179
+ editable,
180
+ editor,
181
+ isSelected,
182
+ nodeKey
183
+ ]);
184
+ const handleCodeChange = useCallback((newCode) => {
185
+ editor.update(() => {
186
+ const node = $getNodeByKey(nodeKey);
187
+ if ($isCodeBlockNode(node)) node.setCode(newCode);
188
+ });
189
+ }, [editor, nodeKey]);
190
+ const handleLanguageChange = useCallback((newLanguage) => {
191
+ editor.update(() => {
192
+ const node = $getNodeByKey(nodeKey);
193
+ if ($isCodeBlockNode(node)) node.setLanguage(newLanguage);
194
+ });
195
+ }, [editor, nodeKey]);
196
+ const handleDelete = useCallback(() => {
197
+ editor.getRootElement()?.focus({ preventScroll: true });
198
+ editor.update(() => {
199
+ const node = $getNodeByKey(nodeKey);
200
+ if (!node) return;
201
+ const prev = node.getPreviousSibling();
202
+ const next = node.getNextSibling();
203
+ const parent = node.getParent();
204
+ node.remove();
205
+ if (prev) {
206
+ if ($isElementNode(prev)) prev.selectEnd();
207
+ else {
208
+ if ($isDecoratorNode(prev) && prev.getType() === "code-block") setCodeBlockCursorIntent(prev.getKey(), "end");
209
+ const selection = $createNodeSelection();
210
+ selection.add(prev.getKey());
211
+ $setSelection(selection);
212
+ }
213
+ return;
214
+ }
215
+ if (next) {
216
+ if ($isElementNode(next)) next.selectStart();
217
+ else {
218
+ if ($isDecoratorNode(next) && next.getType() === "code-block") setCodeBlockCursorIntent(next.getKey(), "start");
219
+ const selection = $createNodeSelection();
220
+ selection.add(next.getKey());
221
+ $setSelection(selection);
222
+ }
223
+ return;
224
+ }
225
+ if (parent) {
226
+ const p = $createParagraphNode();
227
+ parent.append(p);
228
+ p.selectStart();
229
+ }
230
+ });
231
+ }, [editor, nodeKey]);
232
+ const handleExitBlock = useCallback((direction) => {
233
+ editor.getRootElement()?.focus({ preventScroll: true });
234
+ editor.update(() => {
235
+ const node = $getNodeByKey(nodeKey);
236
+ if (!node) return;
237
+ if (direction === "before") {
238
+ const prev = node.getPreviousSibling();
239
+ if (!prev) return;
240
+ if ($isElementNode(prev)) prev.selectEnd();
241
+ else {
242
+ if ($isDecoratorNode(prev) && prev.getType() === "code-block") setCodeBlockCursorIntent(prev.getKey(), "end");
243
+ const selection = $createNodeSelection();
244
+ selection.add(prev.getKey());
245
+ $setSelection(selection);
246
+ }
247
+ return;
248
+ }
249
+ let next = node.getNextSibling();
250
+ if (!next) {
251
+ const p = $createParagraphNode();
252
+ node.insertAfter(p);
253
+ next = p;
254
+ }
255
+ if ($isElementNode(next)) next.selectStart();
256
+ else {
257
+ if ($isDecoratorNode(next) && next.getType() === "code-block") setCodeBlockCursorIntent(next.getKey(), "start");
258
+ const selection = $createNodeSelection();
259
+ selection.add(next.getKey());
260
+ $setSelection(selection);
261
+ }
262
+ });
263
+ }, [editor, nodeKey]);
264
+ return /* @__PURE__ */ jsx(RendererWrapper, {
265
+ defaultRenderer: CodeBlockRenderer,
266
+ rendererKey: "CodeBlock",
267
+ props: {
268
+ code,
269
+ language,
270
+ editable,
271
+ onCodeChange: editable ? handleCodeChange : void 0,
272
+ onLanguageChange: editable ? handleLanguageChange : void 0,
273
+ onDelete: editable ? handleDelete : void 0,
274
+ onExitBlock: editable ? handleExitBlock : void 0,
275
+ selected: editable ? shouldFocusEditor : false,
276
+ cursorPlacement: editable ? cursorPlacement : "start"
277
+ }
278
+ });
279
+ }
280
+ //#endregion
281
+ //#region src/nodes/CodeBlockEditNode.ts
282
+ var _CodeBlockEditNode;
283
+ var CodeBlockEditNode = class CodeBlockEditNode extends CodeBlockNode {
284
+ static clone(node) {
285
+ return new CodeBlockEditNode(node.__code, node.__language, node.__key);
286
+ }
287
+ static importJSON(serializedNode) {
288
+ return new CodeBlockEditNode(serializedNode.code, serializedNode.language);
289
+ }
290
+ decorate(_editor, _config) {
291
+ return createElement(CodeBlockEditDecorator, {
292
+ nodeKey: this.__key,
293
+ code: this.__code,
294
+ language: this.__language
295
+ });
296
+ }
297
+ };
298
+ _CodeBlockEditNode = CodeBlockEditNode;
299
+ _defineProperty(CodeBlockEditNode, "commandItems", CodeBlockNode.commandItems.map((item) => ({
300
+ ...item,
301
+ onSelect: (editor) => {
302
+ editor.update(() => {
303
+ $insertNodes([new _CodeBlockEditNode("", "text")]);
304
+ });
305
+ }
306
+ })));
307
+ function $createCodeBlockEditNode(code, language) {
308
+ return new CodeBlockEditNode(code, language);
309
+ }
310
+ //#endregion
311
+ //#region src/components/renderers/FootnoteSectionEditRenderer.tsx
312
+ function FootnoteSectionEditRenderer({ definitions }) {
313
+ const [editor] = useLexicalComposerContext();
314
+ const { displayNumberMap } = useFootnoteDefinitions();
315
+ const sortedEntries = Object.entries(definitions).sort(([a], [b]) => (displayNumberMap[a] ?? 0) - (displayNumberMap[b] ?? 0));
316
+ const handleContentChange = useCallback((identifier, newContent) => {
317
+ editor.update(() => {
318
+ const sectionNodes = $nodesOfType(FootnoteSectionNode);
319
+ if (sectionNodes.length > 0) sectionNodes[0].setDefinition(identifier, newContent);
320
+ });
321
+ }, [editor]);
322
+ const handleRemove = useCallback((identifier) => {
323
+ editor.update(() => {
324
+ const sectionNodes = $nodesOfType(FootnoteSectionNode);
325
+ if (sectionNodes.length > 0) sectionNodes[0].removeDefinition(identifier);
326
+ });
327
+ }, [editor]);
328
+ if (sortedEntries.length === 0) return null;
329
+ return /* @__PURE__ */ jsxs("div", {
330
+ role: "doc-endnotes",
331
+ className: clsx("rich-footnote-section-content", "rich-footnote-section-edit", semanticClassNames.footnoteSection, sharedStyles.footnoteSection),
332
+ children: [/* @__PURE__ */ jsx("hr", { className: clsx(semanticClassNames.footnoteSectionDivider, sharedStyles.footnoteSectionDivider) }), /* @__PURE__ */ jsx("ol", {
333
+ className: clsx(semanticClassNames.footnoteSectionList, sharedStyles.footnoteSectionList),
334
+ children: sortedEntries.map(([identifier, content]) => {
335
+ const displayNum = displayNumberMap[identifier] ?? identifier;
336
+ return /* @__PURE__ */ jsxs("li", {
337
+ id: `footnote-${identifier}`,
338
+ className: clsx(semanticClassNames.footnoteSectionItem, sharedStyles.footnoteSectionItem, semanticClassNames.footnoteSectionItemEdit, sharedStyles.footnoteSectionItemEdit),
339
+ children: [
340
+ /* @__PURE__ */ jsxs("span", {
341
+ className: clsx(semanticClassNames.footnoteSectionItemNum, sharedStyles.footnoteSectionItemNum),
342
+ children: [displayNum, "."]
343
+ }),
344
+ /* @__PURE__ */ jsx("input", {
345
+ placeholder: `Footnote content for [^${identifier}]`,
346
+ type: "text",
347
+ value: content,
348
+ className: clsx(semanticClassNames.footnoteSectionItemInput, sharedStyles.footnoteSectionItemInput),
349
+ onChange: (e) => handleContentChange(identifier, e.target.value)
350
+ }),
351
+ /* @__PURE__ */ jsx("button", {
352
+ "aria-label": `Remove footnote ${identifier}`,
353
+ type: "button",
354
+ className: clsx(semanticClassNames.footnoteSectionItemRemove, sharedStyles.footnoteSectionItemRemove),
355
+ onClick: () => handleRemove(identifier),
356
+ children: "×"
357
+ })
358
+ ]
359
+ }, identifier);
360
+ })
361
+ })]
362
+ });
363
+ }
364
+ //#endregion
365
+ //#region src/nodes/FootnoteSectionEditNode.ts
366
+ var FootnoteSectionEditNode = class FootnoteSectionEditNode extends FootnoteSectionNode {
367
+ static getType() {
368
+ return "footnote-section";
369
+ }
370
+ static clone(node) {
371
+ return new FootnoteSectionEditNode({ ...node.__definitions }, node.__key);
372
+ }
373
+ static importJSON(serializedNode) {
374
+ return new FootnoteSectionEditNode(serializedNode.definitions);
375
+ }
376
+ decorate(_editor, _config) {
377
+ return createElement(FootnoteSectionEditRenderer, {
378
+ definitions: this.__definitions,
379
+ nodeKey: this.__key
380
+ });
381
+ }
382
+ };
383
+ //#endregion
384
+ //#region src/components/decorators/GridEditDecorator.tsx
385
+ var COL_OPTIONS = [
386
+ 1,
387
+ 2,
388
+ 3,
389
+ 4
390
+ ];
391
+ function GridEditDecorator({ nodeKey, cols: initialCols, gap, cellEditors }) {
392
+ const [editor] = useLexicalComposerContext();
393
+ const [currentCols, setCurrentCols] = useState(initialCols);
394
+ useEffect(() => {
395
+ return editor.registerUpdateListener(({ editorState }) => {
396
+ editorState.read(() => {
397
+ const node = $getNodeByKey(nodeKey);
398
+ if ($isGridContainerNode(node)) setCurrentCols(node.getCols());
399
+ });
400
+ });
401
+ }, [editor, nodeKey]);
402
+ const handleSetCols = useCallback((cols) => {
403
+ editor.update(() => {
404
+ const node = $getNodeByKey(nodeKey);
405
+ if ($isGridContainerNode(node)) node.setCols(cols);
406
+ });
407
+ }, [editor, nodeKey]);
408
+ const handleAddRow = useCallback(() => {
409
+ editor.update(() => {
410
+ const node = $getNodeByKey(nodeKey);
411
+ if ($isGridContainerNode(node)) node.addCells(node.getCols());
412
+ });
413
+ }, [editor, nodeKey]);
414
+ const handleRemoveRow = useCallback(() => {
415
+ editor.update(() => {
416
+ const node = $getNodeByKey(nodeKey);
417
+ if (!$isGridEditNode(node)) return;
418
+ const cols = node.getCols();
419
+ const editors = node.getCellEditors();
420
+ if (editors.length <= cols) return;
421
+ if (!editors.slice(-cols).every((cellEditor) => cellEditor.getEditorState().read(() => $getRoot().getTextContentSize() === 0))) return;
422
+ node.removeCells(cols);
423
+ });
424
+ }, [editor, nodeKey]);
425
+ return /* @__PURE__ */ jsxs(Fragment, { children: [/* @__PURE__ */ jsxs("div", {
426
+ className: clsx(gridClassNames.toolbar, gridStyles.toolbar),
427
+ onMouseDown: (e) => e.preventDefault(),
428
+ children: [
429
+ /* @__PURE__ */ jsx("span", {
430
+ className: clsx(gridClassNames.toolbarIcon, gridStyles.toolbarIcon),
431
+ children: /* @__PURE__ */ jsx(LayoutGrid, { size: 14 })
432
+ }),
433
+ COL_OPTIONS.map((n) => /* @__PURE__ */ jsx("button", {
434
+ "aria-label": `${n} columns`,
435
+ type: "button",
436
+ className: clsx(gridClassNames.colButton, gridStyles.colButton, n === currentCols && gridClassNames.colButtonActive, n === currentCols && gridStyles.colButtonActive),
437
+ onClick: () => handleSetCols(n),
438
+ children: n
439
+ }, n)),
440
+ /* @__PURE__ */ jsx("div", { className: clsx(gridClassNames.toolbarDivider, gridStyles.toolbarDivider) }),
441
+ /* @__PURE__ */ jsx("button", {
442
+ "aria-label": "Add row",
443
+ className: clsx(gridClassNames.actionButton, gridStyles.actionButton),
444
+ type: "button",
445
+ onClick: handleAddRow,
446
+ children: /* @__PURE__ */ jsx(Plus, { size: 14 })
447
+ }),
448
+ /* @__PURE__ */ jsx("button", {
449
+ "aria-label": "Remove row",
450
+ className: clsx(gridClassNames.actionButton, gridStyles.actionButton),
451
+ type: "button",
452
+ onClick: handleRemoveRow,
453
+ children: /* @__PURE__ */ jsx(Minus, { size: 14 })
454
+ })
455
+ ]
456
+ }), /* @__PURE__ */ jsx("div", {
457
+ className: clsx(gridClassNames.inner, gridStyles.inner),
458
+ style: {
459
+ gridTemplateColumns: `repeat(${currentCols}, 1fr)`,
460
+ gap
461
+ },
462
+ children: cellEditors.map((cellEditor, i) => /* @__PURE__ */ jsx("div", {
463
+ className: clsx(gridClassNames.cell, gridStyles.cell),
464
+ children: /* @__PURE__ */ jsxs(LexicalNestedComposer, {
465
+ initialEditor: cellEditor,
466
+ children: [
467
+ /* @__PURE__ */ jsx(RichTextPlugin, {
468
+ ErrorBoundary: LexicalErrorBoundary,
469
+ contentEditable: /* @__PURE__ */ jsx(ContentEditable, {
470
+ "aria-placeholder": "",
471
+ className: clsx(gridClassNames.cellEditable, gridStyles.cellEditable),
472
+ placeholder: /* @__PURE__ */ jsx("span", { style: { display: "none" } })
473
+ })
474
+ }),
475
+ /* @__PURE__ */ jsx(ListPlugin, {}),
476
+ /* @__PURE__ */ jsx(LinkPlugin, {})
477
+ ]
478
+ })
479
+ }, i))
480
+ })] });
481
+ }
482
+ //#endregion
483
+ //#region src/nodes/GridEditNode.ts
484
+ function createCellEditor() {
485
+ return createEditor({
486
+ namespace: "GridCell",
487
+ nodes: NESTED_EDITOR_NODES,
488
+ theme: editorTheme,
489
+ onError: (error) => {
490
+ console.error("[GridCell]", error);
491
+ }
492
+ });
493
+ }
494
+ var GridEditNode = class GridEditNode extends GridContainerNode {
495
+ static clone(node) {
496
+ const cloned = new GridEditNode(node.__cols, node.__gap, node.__cellStates, node.__key);
497
+ cloned.__cellEditors = [...node.__cellEditors];
498
+ return cloned;
499
+ }
500
+ constructor(cols = 2, gap, cellStates, key) {
501
+ super(cols, gap, cellStates, key);
502
+ _defineProperty(this, "__cellEditors", void 0);
503
+ this.__cellEditors = this.__cellStates.map((state) => {
504
+ const editor = createCellEditor();
505
+ const editorState = editor.parseEditorState(state);
506
+ editor.setEditorState(editorState);
507
+ return editor;
508
+ });
509
+ }
510
+ getCellEditors() {
511
+ return this.getLatest().__cellEditors;
512
+ }
513
+ setCols(cols) {
514
+ const writable = this.getWritable();
515
+ const prev = writable.__cellEditors.length;
516
+ writable.__cols = cols;
517
+ if (cols > prev) for (let i = prev; i < cols; i++) {
518
+ const editor = createCellEditor();
519
+ writable.__cellEditors.push(editor);
520
+ writable.__cellStates.push({ root: {
521
+ children: [{
522
+ type: "paragraph",
523
+ children: [],
524
+ direction: null,
525
+ format: "",
526
+ indent: 0,
527
+ textFormat: 0,
528
+ textStyle: "",
529
+ version: 1
530
+ }],
531
+ direction: null,
532
+ format: "",
533
+ indent: 0,
534
+ type: "root",
535
+ version: 1
536
+ } });
537
+ }
538
+ }
539
+ addCells(count) {
540
+ const writable = this.getWritable();
541
+ for (let i = 0; i < count; i++) {
542
+ const editor = createCellEditor();
543
+ writable.__cellEditors.push(editor);
544
+ writable.__cellStates.push({ root: {
545
+ children: [{
546
+ type: "paragraph",
547
+ children: [],
548
+ direction: null,
549
+ format: "",
550
+ indent: 0,
551
+ textFormat: 0,
552
+ textStyle: "",
553
+ version: 1
554
+ }],
555
+ direction: null,
556
+ format: "",
557
+ indent: 0,
558
+ type: "root",
559
+ version: 1
560
+ } });
561
+ }
562
+ }
563
+ removeCells(count) {
564
+ const writable = this.getWritable();
565
+ const editors = writable.__cellEditors;
566
+ const states = writable.__cellStates;
567
+ const toRemove = Math.min(count, editors.length);
568
+ for (let i = 0; i < toRemove; i++) {
569
+ const editor = editors.at(-1);
570
+ if (!editor) break;
571
+ if (!editor.getEditorState().read(() => {
572
+ return $getRoot().getTextContentSize() === 0;
573
+ })) break;
574
+ editors.pop();
575
+ states.pop();
576
+ }
577
+ }
578
+ static importJSON(serializedNode) {
579
+ const legacy = serializedNode;
580
+ const cols = legacy.cols || 2;
581
+ const rawGap = legacy.gap;
582
+ const gap = typeof rawGap === "number" ? `${rawGap}px` : rawGap;
583
+ if (legacy.cells && legacy.cells.length > 0) return new GridEditNode(cols, gap, legacy.cells);
584
+ if (legacy.children) return new GridEditNode(cols, gap, legacy.children.map((child) => {
585
+ return { root: {
586
+ children: [child],
587
+ direction: null,
588
+ format: "",
589
+ indent: 0,
590
+ type: "root",
591
+ version: 1
592
+ } };
593
+ }));
594
+ return new GridEditNode(cols, gap);
595
+ }
596
+ exportJSON() {
597
+ return {
598
+ ...super.exportJSON(),
599
+ type: "grid-container",
600
+ cols: this.__cols,
601
+ gap: this.__gap,
602
+ cells: this.__cellEditors.map((editor) => editor.getEditorState().toJSON()),
603
+ version: 1
604
+ };
605
+ }
606
+ decorate(_editor, _config) {
607
+ return createElement(GridEditDecorator, {
608
+ nodeKey: this.__key,
609
+ cols: this.__cols,
610
+ gap: this.__gap,
611
+ cellEditors: this.__cellEditors
612
+ });
613
+ }
614
+ };
615
+ _defineProperty(GridEditNode, "slashMenuItems", [{
616
+ title: "Grid",
617
+ icon: createElement(LayoutGrid, { size: 20 }),
618
+ description: "Grid layout container",
619
+ keywords: [
620
+ "grid",
621
+ "columns",
622
+ "layout"
623
+ ],
624
+ section: "LAYOUT",
625
+ onSelect: (editor) => {
626
+ editor.update(() => {
627
+ $insertNodes([$createGridEditNode(2)]);
628
+ });
629
+ }
630
+ }]);
631
+ function $createGridEditNode(cols = 2, gap) {
632
+ return new GridEditNode(cols, gap);
633
+ }
634
+ function $isGridEditNode(node) {
635
+ return node instanceof GridEditNode;
636
+ }
637
+ //#endregion
638
+ //#region src/styles/poll-edit.css.ts
639
+ var pollEditClasses = {
640
+ container: "_15l32q50",
641
+ meta: "_15l32q51",
642
+ question: "_15l32q52",
643
+ optionList: "_15l32q53",
644
+ optionRow: "_15l32q54",
645
+ reorderColumn: "_15l32q55",
646
+ reorderButton: "_15l32q56",
647
+ optionInput: "_15l32q57",
648
+ removeButton: "_15l32q58",
649
+ addOption: "_15l32q59",
650
+ advancedSummary: "_15l32q5a",
651
+ advancedGrid: "_15l32q5b",
652
+ advancedLabel: "_15l32q5c",
653
+ advancedInput: "_15l32q5d",
654
+ modeRow: "_15l32q5e",
655
+ modeLabel: "_15l32q5f"
656
+ };
657
+ //#endregion
658
+ //#region src/components/decorators/PollEditDecorator.tsx
659
+ var makeOptionId = customAlphabet("abcdefghijklmnopqrstuvwxyz0123456789", 6);
660
+ function moveItem(arr, from, to) {
661
+ if (to < 0 || to >= arr.length) return arr;
662
+ const next = arr.slice();
663
+ const [item] = next.splice(from, 1);
664
+ next.splice(to, 0, item);
665
+ return next;
666
+ }
667
+ function PollEditDecorator({ closeAt, mode, nodeKey, options, pollId, question, showResults }) {
668
+ const [editor] = useLexicalComposerContext();
669
+ const editable = editor.isEditable();
670
+ const updatePoll = useCallback((mutate) => {
671
+ editor.update(() => {
672
+ const node = $getNodeByKey(nodeKey);
673
+ if ($isPollNode(node)) mutate(node);
674
+ });
675
+ }, [editor, nodeKey]);
676
+ const handleQuestionChange = useCallback((value) => {
677
+ updatePoll((node) => node.setQuestion(value));
678
+ }, [updatePoll]);
679
+ const handleOptionLabelChange = useCallback((id, label) => {
680
+ updatePoll((node) => node.setOptions(node.getOptions().map((o) => o.id === id ? {
681
+ ...o,
682
+ label
683
+ } : o)));
684
+ }, [updatePoll]);
685
+ const handleOptionRemove = useCallback((id) => {
686
+ updatePoll((node) => node.setOptions(node.getOptions().filter((o) => o.id !== id)));
687
+ }, [updatePoll]);
688
+ const handleOptionAdd = useCallback(() => {
689
+ updatePoll((node) => node.setOptions([...node.getOptions(), {
690
+ id: `o_${makeOptionId()}`,
691
+ label: ""
692
+ }]));
693
+ }, [updatePoll]);
694
+ const handleOptionMove = useCallback((index, direction) => {
695
+ updatePoll((node) => {
696
+ const current = node.getOptions();
697
+ const next = moveItem(current, index, index + direction);
698
+ if (next !== current) node.setOptions(next);
699
+ });
700
+ }, [updatePoll]);
701
+ const handleModeChange = useCallback((next) => {
702
+ updatePoll((node) => node.setMode(next));
703
+ }, [updatePoll]);
704
+ const handleCloseAtChange = useCallback((value) => {
705
+ updatePoll((node) => node.setCloseAt(value || void 0));
706
+ }, [updatePoll]);
707
+ const handleShowResultsChange = useCallback((value) => {
708
+ updatePoll((node) => {
709
+ if (value === "always" || value === "after-vote" || value === "after-close") node.setShowResults(value);
710
+ else node.setShowResults(void 0);
711
+ });
712
+ }, [updatePoll]);
713
+ const handleOptionKeyDown = useCallback((event, index) => {
714
+ if (event.key === "Enter") {
715
+ event.preventDefault();
716
+ if (index === options.length - 1) handleOptionAdd();
717
+ }
718
+ }, [handleOptionAdd, options.length]);
719
+ return /* @__PURE__ */ jsxs("div", {
720
+ className: pollEditClasses.container,
721
+ "data-poll-id": pollId,
722
+ children: [
723
+ /* @__PURE__ */ jsxs("div", {
724
+ className: pollEditClasses.meta,
725
+ children: ["Poll · ", mode === "single" ? "Single choice" : "Multiple choice"]
726
+ }),
727
+ /* @__PURE__ */ jsx("input", {
728
+ "aria-label": "Poll question",
729
+ className: pollEditClasses.question,
730
+ disabled: !editable,
731
+ placeholder: "Question",
732
+ value: question,
733
+ onChange: (event) => handleQuestionChange(event.target.value)
734
+ }),
735
+ /* @__PURE__ */ jsx("ul", {
736
+ className: pollEditClasses.optionList,
737
+ children: options.map((option, index) => /* @__PURE__ */ jsxs("li", {
738
+ className: pollEditClasses.optionRow,
739
+ children: [
740
+ editable && options.length > 1 && /* @__PURE__ */ jsxs("span", {
741
+ className: pollEditClasses.reorderColumn,
742
+ children: [/* @__PURE__ */ jsx("button", {
743
+ "aria-label": "Move option up",
744
+ className: pollEditClasses.reorderButton,
745
+ disabled: index === 0,
746
+ type: "button",
747
+ onClick: () => handleOptionMove(index, -1),
748
+ children: "▲"
749
+ }), /* @__PURE__ */ jsx("button", {
750
+ "aria-label": "Move option down",
751
+ className: pollEditClasses.reorderButton,
752
+ disabled: index === options.length - 1,
753
+ type: "button",
754
+ onClick: () => handleOptionMove(index, 1),
755
+ children: "▼"
756
+ })]
757
+ }),
758
+ /* @__PURE__ */ jsx("input", {
759
+ "aria-label": "Option label",
760
+ className: pollEditClasses.optionInput,
761
+ disabled: !editable,
762
+ placeholder: "Option",
763
+ value: option.label,
764
+ onChange: (event) => handleOptionLabelChange(option.id, event.target.value),
765
+ onKeyDown: (event) => handleOptionKeyDown(event, index)
766
+ }),
767
+ editable && options.length > 2 && /* @__PURE__ */ jsx("button", {
768
+ "aria-label": "Remove option",
769
+ className: pollEditClasses.removeButton,
770
+ type: "button",
771
+ onClick: () => handleOptionRemove(option.id),
772
+ children: "×"
773
+ })
774
+ ]
775
+ }, option.id))
776
+ }),
777
+ editable && /* @__PURE__ */ jsx("button", {
778
+ className: pollEditClasses.addOption,
779
+ type: "button",
780
+ onClick: handleOptionAdd,
781
+ children: "+ Add option"
782
+ }),
783
+ editable && /* @__PURE__ */ jsxs("div", {
784
+ className: pollEditClasses.modeRow,
785
+ children: [/* @__PURE__ */ jsxs("label", {
786
+ className: pollEditClasses.modeLabel,
787
+ children: [/* @__PURE__ */ jsx("input", {
788
+ checked: mode === "single",
789
+ name: `poll-mode-${nodeKey}`,
790
+ type: "radio",
791
+ onChange: () => handleModeChange("single")
792
+ }), "Single"]
793
+ }), /* @__PURE__ */ jsxs("label", {
794
+ className: pollEditClasses.modeLabel,
795
+ children: [/* @__PURE__ */ jsx("input", {
796
+ checked: mode === "multiple",
797
+ name: `poll-mode-${nodeKey}`,
798
+ type: "radio",
799
+ onChange: () => handleModeChange("multiple")
800
+ }), "Multiple"]
801
+ })]
802
+ }),
803
+ editable && /* @__PURE__ */ jsxs("details", { children: [/* @__PURE__ */ jsx("summary", {
804
+ className: pollEditClasses.advancedSummary,
805
+ children: "Advanced"
806
+ }), /* @__PURE__ */ jsxs("div", {
807
+ className: pollEditClasses.advancedGrid,
808
+ children: [
809
+ /* @__PURE__ */ jsx("span", {
810
+ className: pollEditClasses.advancedLabel,
811
+ children: "Close at"
812
+ }),
813
+ /* @__PURE__ */ jsx("input", {
814
+ "aria-label": "Closing time",
815
+ className: pollEditClasses.advancedInput,
816
+ type: "datetime-local",
817
+ value: closeAt ?? "",
818
+ onChange: (event) => handleCloseAtChange(event.target.value)
819
+ }),
820
+ /* @__PURE__ */ jsx("span", {
821
+ className: pollEditClasses.advancedLabel,
822
+ children: "Show results"
823
+ }),
824
+ /* @__PURE__ */ jsxs("select", {
825
+ "aria-label": "Show results policy",
826
+ className: pollEditClasses.advancedInput,
827
+ value: showResults ?? "always",
828
+ onChange: (event) => handleShowResultsChange(event.target.value),
829
+ children: [
830
+ /* @__PURE__ */ jsx("option", {
831
+ value: "always",
832
+ children: "Always"
833
+ }),
834
+ /* @__PURE__ */ jsx("option", {
835
+ value: "after-vote",
836
+ children: "After vote"
837
+ }),
838
+ /* @__PURE__ */ jsx("option", {
839
+ value: "after-close",
840
+ children: "After close"
841
+ })
842
+ ]
843
+ })
844
+ ]
845
+ })] })
846
+ ]
847
+ });
848
+ }
849
+ //#endregion
850
+ //#region src/nodes/PollEditNode.ts
851
+ var PollEditNode = class PollEditNode extends PollNode {
852
+ static getType() {
853
+ return "poll";
854
+ }
855
+ static clone(node) {
856
+ return new PollEditNode({
857
+ pollId: node.__pollId,
858
+ question: node.__question,
859
+ options: node.__options,
860
+ mode: node.__mode,
861
+ closeAt: node.__closeAt,
862
+ showResults: node.__showResults
863
+ }, node.__key);
864
+ }
865
+ constructor(payload, key) {
866
+ super(payload, key);
867
+ }
868
+ static importJSON(serializedNode) {
869
+ return new PollEditNode({
870
+ pollId: serializedNode.pollId,
871
+ question: serializedNode.question,
872
+ options: serializedNode.options,
873
+ mode: serializedNode.mode,
874
+ closeAt: serializedNode.closeAt,
875
+ showResults: serializedNode.showResults
876
+ });
877
+ }
878
+ decorate(_editor, _config) {
879
+ return createElement(PollEditDecorator, {
880
+ nodeKey: this.__key,
881
+ pollId: this.__pollId,
882
+ question: this.__question,
883
+ options: this.__options,
884
+ mode: this.__mode,
885
+ closeAt: this.__closeAt,
886
+ showResults: this.__showResults
887
+ });
888
+ }
889
+ };
890
+ _defineProperty(PollEditNode, "commandItems", [{
891
+ title: "Poll",
892
+ icon: createElement(Vote, { size: 20 }),
893
+ description: "Reader-facing vote widget",
894
+ keywords: [
895
+ "poll",
896
+ "vote",
897
+ "survey",
898
+ "choice"
899
+ ],
900
+ section: "ADVANCED",
901
+ placement: ["slash", "toolbar"],
902
+ group: "insert",
903
+ onSelect: (editor) => {
904
+ editor.update(() => {
905
+ $insertNodes([$createPollEditNode()]);
906
+ });
907
+ }
908
+ }]);
909
+ function $createPollEditNode(payload) {
910
+ return new PollEditNode(payload);
911
+ }
912
+ //#endregion
913
+ //#region src/config-edit.ts
914
+ var customEditNodes = [
915
+ SpoilerNode,
916
+ MentionNode,
917
+ KaTeXInlineNode,
918
+ KaTeXBlockNode,
919
+ ImageNode,
920
+ AlertQuoteEditNode,
921
+ CodeBlockEditNode,
922
+ FootnoteNode,
923
+ FootnoteSectionEditNode,
924
+ VideoNode,
925
+ LinkCardNode,
926
+ CommentNode,
927
+ DetailsNode,
928
+ GridEditNode,
929
+ BannerEditNode,
930
+ MermaidNode,
931
+ RubyNode,
932
+ TagNode,
933
+ PollEditNode
934
+ ];
935
+ var allEditNodes = [...builtinNodes, ...customEditNodes];
936
+ //#endregion
937
+ //#region src/node-registry.ts
938
+ var _resolvedEditNodes = null;
939
+ function setResolvedEditNodes(nodes) {
940
+ _resolvedEditNodes = nodes;
941
+ }
942
+ function getResolvedEditNodes() {
943
+ return _resolvedEditNodes ?? allEditNodes;
944
+ }
945
+ //#endregion
946
+ export { $createPollEditNode as a, FootnoteSectionEditNode as c, setCodeBlockCursorIntent as d, customEditNodes as i, $createCodeBlockEditNode as l, setResolvedEditNodes as n, PollEditNode as o, allEditNodes as r, pollEditClasses as s, getResolvedEditNodes as t, $createBannerEditNode as u };