@bendyline/squisq-editor-react 1.0.0 → 1.0.1

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 (45) hide show
  1. package/dist/EditorContext.d.ts +75 -0
  2. package/dist/EditorContext.d.ts.map +1 -0
  3. package/dist/EditorContext.js +158 -0
  4. package/dist/EditorContext.js.map +1 -0
  5. package/dist/EditorShell.d.ts +34 -0
  6. package/dist/EditorShell.d.ts.map +1 -0
  7. package/dist/EditorShell.js +59 -0
  8. package/dist/EditorShell.js.map +1 -0
  9. package/dist/PreviewPanel.d.ts +33 -0
  10. package/dist/PreviewPanel.d.ts.map +1 -0
  11. package/dist/PreviewPanel.js +385 -0
  12. package/dist/PreviewPanel.js.map +1 -0
  13. package/dist/RawEditor.d.ts +25 -0
  14. package/dist/RawEditor.d.ts.map +1 -0
  15. package/dist/RawEditor.js +100 -0
  16. package/dist/RawEditor.js.map +1 -0
  17. package/dist/StatusBar.d.ts +15 -0
  18. package/dist/StatusBar.d.ts.map +1 -0
  19. package/dist/StatusBar.js +24 -0
  20. package/dist/StatusBar.js.map +1 -0
  21. package/dist/TemplateAnnotation.d.ts +20 -0
  22. package/dist/TemplateAnnotation.d.ts.map +1 -0
  23. package/dist/TemplateAnnotation.js +69 -0
  24. package/dist/TemplateAnnotation.js.map +1 -0
  25. package/dist/Toolbar.d.ts +19 -0
  26. package/dist/Toolbar.d.ts.map +1 -0
  27. package/dist/Toolbar.js +350 -0
  28. package/dist/Toolbar.js.map +1 -0
  29. package/dist/ViewSwitcher.d.ts +14 -0
  30. package/dist/ViewSwitcher.d.ts.map +1 -0
  31. package/dist/ViewSwitcher.js +20 -0
  32. package/dist/ViewSwitcher.js.map +1 -0
  33. package/dist/WysiwygEditor.d.ts +28 -0
  34. package/dist/WysiwygEditor.d.ts.map +1 -0
  35. package/dist/WysiwygEditor.js +111 -0
  36. package/dist/WysiwygEditor.js.map +1 -0
  37. package/dist/index.d.ts +36 -268
  38. package/dist/index.d.ts.map +1 -0
  39. package/dist/index.js +34 -3832
  40. package/dist/index.js.map +1 -1
  41. package/dist/tiptapBridge.d.ts +24 -0
  42. package/dist/tiptapBridge.d.ts.map +1 -0
  43. package/dist/tiptapBridge.js +358 -0
  44. package/dist/tiptapBridge.js.map +1 -0
  45. package/package.json +4 -5
package/dist/index.js CHANGED
@@ -1,3833 +1,35 @@
1
- // src/EditorShell.tsx
2
- import { useEffect as useEffect6 } from "react";
3
-
4
- // src/EditorContext.tsx
5
- import {
6
- createContext,
7
- useContext,
8
- useState,
9
- useCallback,
10
- useMemo,
11
- useRef,
12
- useEffect
13
- } from "react";
14
- import { parseMarkdown, stringifyMarkdown } from "@bendyline/squisq/markdown";
15
- import { markdownToDoc } from "@bendyline/squisq/doc";
16
- import { jsx } from "react/jsx-runtime";
17
- var EditorContext = createContext(null);
18
- function useEditorContext() {
19
- const ctx = useContext(EditorContext);
20
- if (!ctx) {
21
- throw new Error("useEditorContext must be used within an <EditorProvider>");
22
- }
23
- return ctx;
24
- }
25
- function EditorProvider({
26
- initialMarkdown = "",
27
- initialView = "raw",
28
- articleId = "untitled",
29
- theme: initialTheme = "light",
30
- children
31
- }) {
32
- const [markdownSource, setMarkdownSourceRaw] = useState(initialMarkdown);
33
- const [markdownDoc, setMarkdownDocState] = useState(null);
34
- const [doc, setDoc] = useState(null);
35
- const [activeView, setActiveView] = useState(initialView);
36
- const [parseError, setParseError] = useState(null);
37
- const [isParsing, setIsParsing] = useState(false);
38
- const [theme, setTheme] = useState(initialTheme);
39
- const [tiptapEditor, setTiptapEditor] = useState(null);
40
- const [monacoEditor, setMonacoEditor] = useState(null);
41
- const articleIdRef = useRef(articleId);
42
- articleIdRef.current = articleId;
43
- useEffect(() => {
44
- setTheme(initialTheme);
45
- }, [initialTheme]);
46
- const parseTimeoutRef = useRef(null);
47
- const doParse = useCallback((source) => {
48
- setIsParsing(true);
49
- try {
50
- const parsed = parseMarkdown(source);
51
- setMarkdownDocState(parsed);
52
- setParseError(null);
53
- try {
54
- const generatedDoc = markdownToDoc(parsed, {
55
- articleId: articleIdRef.current
56
- });
57
- setDoc(generatedDoc);
58
- } catch (docErr) {
59
- setDoc(null);
60
- console.warn("Doc generation failed:", docErr instanceof Error ? docErr.message : docErr);
61
- }
62
- } catch (err) {
63
- setParseError(err instanceof Error ? err.message : "Parse error");
64
- setMarkdownDocState(null);
65
- setDoc(null);
66
- } finally {
67
- setIsParsing(false);
68
- }
69
- }, []);
70
- useEffect(() => {
71
- if (parseTimeoutRef.current) {
72
- clearTimeout(parseTimeoutRef.current);
73
- }
74
- parseTimeoutRef.current = setTimeout(() => {
75
- doParse(markdownSource);
76
- }, 150);
77
- return () => {
78
- if (parseTimeoutRef.current) {
79
- clearTimeout(parseTimeoutRef.current);
80
- }
81
- };
82
- }, [markdownSource, doParse]);
83
- useEffect(() => {
84
- if (initialMarkdown) {
85
- doParse(initialMarkdown);
86
- }
87
- }, []);
88
- const setMarkdownSource = useCallback((source) => {
89
- setMarkdownSourceRaw(source);
90
- }, []);
91
- const setMarkdownDoc = useCallback((newDoc) => {
92
- setMarkdownDocState(newDoc);
93
- try {
94
- const newSource = stringifyMarkdown(newDoc);
95
- setMarkdownSourceRaw(newSource);
96
- setParseError(null);
97
- try {
98
- const generatedDoc = markdownToDoc(newDoc, {
99
- articleId: articleIdRef.current
100
- });
101
- setDoc(generatedDoc);
102
- } catch (docErr) {
103
- setDoc(null);
104
- console.warn("Doc generation failed:", docErr instanceof Error ? docErr.message : docErr);
105
- }
106
- } catch (err) {
107
- setParseError(err instanceof Error ? err.message : "Stringify error");
108
- }
109
- }, []);
110
- const value = useMemo(
111
- () => ({
112
- markdownSource,
113
- markdownDoc,
114
- doc,
115
- activeView,
116
- parseError,
117
- isParsing,
118
- theme,
119
- tiptapEditor,
120
- monacoEditor,
121
- setMarkdownSource,
122
- setMarkdownDoc,
123
- setActiveView,
124
- setTiptapEditor,
125
- setMonacoEditor,
126
- setTheme
127
- }),
128
- [
129
- markdownSource,
130
- markdownDoc,
131
- doc,
132
- activeView,
133
- parseError,
134
- isParsing,
135
- theme,
136
- tiptapEditor,
137
- monacoEditor,
138
- setMarkdownSource,
139
- setMarkdownDoc,
140
- setActiveView,
141
- setTiptapEditor,
142
- setMonacoEditor,
143
- setTheme
144
- ]
145
- );
146
- return /* @__PURE__ */ jsx(EditorContext.Provider, { value, children });
147
- }
148
-
149
- // src/Toolbar.tsx
150
- import { useCallback as useCallback2, useEffect as useEffect2, useReducer } from "react";
151
- import { getAvailableTemplates } from "@bendyline/squisq/doc";
152
- import { Fragment, jsx as jsx2, jsxs } from "react/jsx-runtime";
153
- var VIEWS = [
154
- { id: "wysiwyg", label: "Editor", shortcut: "\u23181" },
155
- { id: "raw", label: "Raw", shortcut: "\u23182" },
156
- { id: "preview", label: "Preview", shortcut: "\u23183" }
157
- ];
158
- var BUTTONS = [
159
- // Format group
160
- {
161
- id: "bold",
162
- label: "B",
163
- icon: "B",
164
- title: "Bold (Ctrl+B)",
165
- group: "format",
166
- iconStyle: { fontWeight: 700 }
167
- },
168
- {
169
- id: "italic",
170
- label: "I",
171
- icon: "I",
172
- title: "Italic (Ctrl+I)",
173
- group: "format",
174
- iconStyle: { fontStyle: "italic" }
175
- },
176
- {
177
- id: "strikethrough",
178
- label: "S",
179
- icon: "S",
180
- title: "Strikethrough",
181
- group: "format",
182
- iconStyle: { textDecoration: "line-through" }
183
- },
184
- { id: "code", label: "<>", icon: "`", title: "Inline code", group: "format" },
185
- // Structure group
186
- { id: "h1", label: "H1", icon: "H1", title: "Heading 1", group: "structure" },
187
- { id: "h2", label: "H2", icon: "H2", title: "Heading 2", group: "structure" },
188
- { id: "h3", label: "H3", icon: "H3", title: "Heading 3", group: "structure" },
189
- { id: "quote", label: "\u275D", icon: "\u275D", title: "Blockquote", group: "structure" },
190
- // Insert group
191
- { id: "ul", label: "\u2022", icon: "\u2022", title: "Bullet list", group: "insert" },
192
- { id: "ol", label: "1.", icon: "1.", title: "Numbered list", group: "insert" },
193
- { id: "codeblock", label: "{ }", icon: "{ }", title: "Code block", group: "insert" },
194
- { id: "hr", label: "\u2014", icon: "\u2014", title: "Horizontal rule", group: "insert" },
195
- { id: "link", label: "\u{1F517}", icon: "\u{1F517}", title: "Insert link", group: "insert" }
196
- ];
197
- function isTiptapActive(editor, id) {
198
- if (!editor) return false;
199
- switch (id) {
200
- case "bold":
201
- return editor.isActive("bold");
202
- case "italic":
203
- return editor.isActive("italic");
204
- case "strikethrough":
205
- return editor.isActive("strike");
206
- case "code":
207
- return editor.isActive("code");
208
- case "h1":
209
- return editor.isActive("heading", { level: 1 });
210
- case "h2":
211
- return editor.isActive("heading", { level: 2 });
212
- case "h3":
213
- return editor.isActive("heading", { level: 3 });
214
- case "quote":
215
- return editor.isActive("blockquote");
216
- case "ul":
217
- return editor.isActive("bulletList");
218
- case "ol":
219
- return editor.isActive("orderedList");
220
- case "codeblock":
221
- return editor.isActive("codeBlock");
222
- default:
223
- return false;
224
- }
225
- }
226
- function Toolbar({ className }) {
227
- const {
228
- activeView,
229
- setActiveView,
230
- markdownSource,
231
- setMarkdownSource,
232
- tiptapEditor,
233
- monacoEditor
234
- } = useEditorContext();
235
- const [, forceUpdate] = useReducer((c) => c + 1, 0);
236
- useEffect2(() => {
237
- if (!tiptapEditor) return;
238
- tiptapEditor.on("transaction", forceUpdate);
239
- return () => {
240
- tiptapEditor.off("transaction", forceUpdate);
241
- };
242
- }, [tiptapEditor]);
243
- const handleTiptap = useCallback2(
244
- (id) => {
245
- if (!tiptapEditor) return;
246
- const chain = tiptapEditor.chain().focus();
247
- switch (id) {
248
- case "bold":
249
- chain.toggleBold().run();
250
- break;
251
- case "italic":
252
- chain.toggleItalic().run();
253
- break;
254
- case "strikethrough":
255
- chain.toggleStrike().run();
256
- break;
257
- case "code":
258
- chain.toggleCode().run();
259
- break;
260
- case "h1":
261
- chain.toggleHeading({ level: 1 }).run();
262
- break;
263
- case "h2":
264
- chain.toggleHeading({ level: 2 }).run();
265
- break;
266
- case "h3":
267
- chain.toggleHeading({ level: 3 }).run();
268
- break;
269
- case "quote":
270
- chain.toggleBlockquote().run();
271
- break;
272
- case "ul":
273
- chain.toggleBulletList().run();
274
- break;
275
- case "ol":
276
- chain.toggleOrderedList().run();
277
- break;
278
- case "codeblock":
279
- chain.toggleCodeBlock().run();
280
- break;
281
- case "hr":
282
- chain.setHorizontalRule().run();
283
- break;
284
- case "link": {
285
- const url = window.prompt("URL:");
286
- if (url) {
287
- chain.setLink?.({ href: url }).run();
288
- }
289
- break;
290
- }
291
- }
292
- },
293
- [tiptapEditor]
294
- );
295
- const handleRaw = useCallback2(
296
- (id) => {
297
- if (monacoEditor) {
298
- const selection = monacoEditor.getSelection();
299
- const model = monacoEditor.getModel();
300
- if (!selection || !model) return;
301
- const selectedText = model.getValueInRange(selection);
302
- const hasSelection = selectedText.length > 0;
303
- let replacement = "";
304
- let newCursorOffset = 0;
305
- const wrapInline = (before, after, placeholder) => {
306
- if (hasSelection) {
307
- replacement = before + selectedText + after;
308
- } else {
309
- replacement = before + placeholder + after;
310
- newCursorOffset = before.length;
311
- }
312
- };
313
- const prefixLines = (prefix, placeholder) => {
314
- if (hasSelection) {
315
- replacement = selectedText.split("\n").map((line) => prefix + line).join("\n");
316
- } else {
317
- replacement = prefix + placeholder;
318
- newCursorOffset = prefix.length;
319
- }
320
- };
321
- switch (id) {
322
- case "bold":
323
- wrapInline("**", "**", "bold text");
324
- break;
325
- case "italic":
326
- wrapInline("*", "*", "italic text");
327
- break;
328
- case "strikethrough":
329
- wrapInline("~~", "~~", "strikethrough");
330
- break;
331
- case "code":
332
- wrapInline("`", "`", "code");
333
- break;
334
- case "h1":
335
- prefixLines("# ", "Heading 1");
336
- break;
337
- case "h2":
338
- prefixLines("## ", "Heading 2");
339
- break;
340
- case "h3":
341
- prefixLines("### ", "Heading 3");
342
- break;
343
- case "quote":
344
- prefixLines("> ", "Quote");
345
- break;
346
- case "ul":
347
- prefixLines("- ", "Item");
348
- break;
349
- case "ol":
350
- prefixLines("1. ", "Item");
351
- break;
352
- case "codeblock": {
353
- const inner = hasSelection ? selectedText : "code";
354
- replacement = "```\n" + inner + "\n```";
355
- if (!hasSelection) newCursorOffset = 4;
356
- break;
357
- }
358
- case "hr": {
359
- replacement = "\n---\n";
360
- break;
361
- }
362
- case "link": {
363
- if (hasSelection) {
364
- replacement = "[" + selectedText + "](url)";
365
- } else {
366
- replacement = "[link text](url)";
367
- newCursorOffset = 1;
368
- }
369
- break;
370
- }
371
- }
372
- const range = selection;
373
- monacoEditor.executeEdits("toolbar", [{ range, text: replacement }]);
374
- if (!hasSelection && newCursorOffset > 0) {
375
- const startPos = model.getPositionAt(
376
- model.getOffsetAt(range.getStartPosition()) + newCursorOffset
377
- );
378
- const _placeholderLen = replacement.length - newCursorOffset - (replacement.length - replacement.lastIndexOf(replacement.charAt(replacement.length - 1)));
379
- monacoEditor.setPosition(startPos);
380
- }
381
- monacoEditor.focus();
382
- } else {
383
- let insertion = "";
384
- switch (id) {
385
- case "bold":
386
- insertion = "**bold text**";
387
- break;
388
- case "italic":
389
- insertion = "*italic text*";
390
- break;
391
- case "strikethrough":
392
- insertion = "~~strikethrough~~";
393
- break;
394
- case "code":
395
- insertion = "`code`";
396
- break;
397
- case "h1":
398
- insertion = "\n# Heading 1\n";
399
- break;
400
- case "h2":
401
- insertion = "\n## Heading 2\n";
402
- break;
403
- case "h3":
404
- insertion = "\n### Heading 3\n";
405
- break;
406
- case "quote":
407
- insertion = "\n> Quote\n";
408
- break;
409
- case "ul":
410
- insertion = "\n- Item\n";
411
- break;
412
- case "ol":
413
- insertion = "\n1. Item\n";
414
- break;
415
- case "codeblock":
416
- insertion = "\n```\ncode\n```\n";
417
- break;
418
- case "hr":
419
- insertion = "\n---\n";
420
- break;
421
- case "link":
422
- insertion = "[link text](url)";
423
- break;
424
- }
425
- if (insertion) {
426
- setMarkdownSource(markdownSource + insertion);
427
- }
428
- }
429
- },
430
- [monacoEditor, markdownSource, setMarkdownSource]
431
- );
432
- const handleAction = useCallback2(
433
- (id) => {
434
- if (activeView === "wysiwyg" && tiptapEditor) {
435
- handleTiptap(id);
436
- } else {
437
- handleRaw(id);
438
- }
439
- },
440
- [activeView, tiptapEditor, handleTiptap, handleRaw]
441
- );
442
- const groups = ["format", "structure", "insert"];
443
- const isWysiwyg = activeView === "wysiwyg" && tiptapEditor;
444
- const isPreview = activeView === "preview";
445
- const currentTemplate = isWysiwyg ? tiptapEditor.isActive("heading") ? tiptapEditor.getAttributes("heading")?.dataTemplate ?? "" : null : null;
446
- const handleTemplatePick = (value) => {
447
- if (!tiptapEditor) return;
448
- if (value === "") {
449
- tiptapEditor.chain().focus().updateAttributes("heading", { dataTemplate: null, dataTemplateParams: null }).run();
450
- } else {
451
- tiptapEditor.chain().focus().updateAttributes("heading", { dataTemplate: value }).run();
452
- }
453
- };
454
- const templateNames = getAvailableTemplates();
455
- return /* @__PURE__ */ jsxs(
456
- "div",
457
- {
458
- className: `squisq-toolbar ${className || ""}`,
459
- role: "toolbar",
460
- "aria-label": "Formatting toolbar",
461
- children: [
462
- /* @__PURE__ */ jsx2("div", { className: "squisq-toolbar-view-tabs", role: "tablist", "aria-label": "Editor view", children: VIEWS.map((view) => /* @__PURE__ */ jsx2(
463
- "button",
464
- {
465
- role: "tab",
466
- "data-view": view.id,
467
- "aria-selected": activeView === view.id,
468
- className: `squisq-toolbar-view-tab${activeView === view.id ? " squisq-toolbar-view-tab--active" : ""}`,
469
- onClick: () => setActiveView(view.id),
470
- title: `${view.label} (${view.shortcut})`,
471
- children: view.label
472
- },
473
- view.id
474
- )) }),
475
- !isPreview && /* @__PURE__ */ jsxs("div", { className: "squisq-toolbar-actions", children: [
476
- groups.map((group, gi) => /* @__PURE__ */ jsxs("div", { className: "squisq-toolbar-group", children: [
477
- gi > 0 && /* @__PURE__ */ jsx2("div", { className: "squisq-toolbar-separator" }),
478
- BUTTONS.filter((b) => b.group === group).map((btn) => {
479
- const active = isWysiwyg ? isTiptapActive(tiptapEditor, btn.id) : false;
480
- return /* @__PURE__ */ jsx2(
481
- "button",
482
- {
483
- className: `squisq-toolbar-button${active ? " squisq-toolbar-button--active" : ""}`,
484
- title: btn.title,
485
- onClick: () => handleAction(btn.id),
486
- "aria-label": btn.title,
487
- "aria-pressed": active,
488
- style: btn.iconStyle,
489
- children: btn.icon
490
- },
491
- btn.id
492
- );
493
- })
494
- ] }, group)),
495
- currentTemplate !== null && /* @__PURE__ */ jsxs(Fragment, { children: [
496
- /* @__PURE__ */ jsx2("div", { className: "squisq-toolbar-separator" }),
497
- /* @__PURE__ */ jsx2("div", { className: "squisq-toolbar-group squisq-template-picker", children: /* @__PURE__ */ jsxs(
498
- "label",
499
- {
500
- className: "squisq-template-picker-label",
501
- title: "Block template for this heading",
502
- children: [
503
- "Template:",
504
- /* @__PURE__ */ jsxs(
505
- "select",
506
- {
507
- className: "squisq-template-picker-select",
508
- value: currentTemplate,
509
- onChange: (e) => handleTemplatePick(e.target.value),
510
- children: [
511
- /* @__PURE__ */ jsx2("option", { value: "", children: "\u2014 none \u2014" }),
512
- templateNames.map((name) => /* @__PURE__ */ jsx2("option", { value: name, children: name }, name))
513
- ]
514
- }
515
- )
516
- ]
517
- }
518
- ) })
519
- ] })
520
- ] })
521
- ]
522
- }
523
- );
524
- }
525
-
526
- // src/StatusBar.tsx
527
- import { useMemo as useMemo2 } from "react";
528
- import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
529
- function StatusBar({ className }) {
530
- const { markdownSource, doc, parseError, isParsing } = useEditorContext();
531
- const stats = useMemo2(() => {
532
- const chars = markdownSource.length;
533
- const words = markdownSource.trim() ? markdownSource.trim().split(/\s+/).length : 0;
534
- const lines = markdownSource.split("\n").length;
535
- const blocks = doc?.blocks.length ?? 0;
536
- return { chars, words, lines, blocks };
537
- }, [markdownSource, doc]);
538
- return /* @__PURE__ */ jsxs2("div", { className: `squisq-status-bar ${className || ""}`, children: [
539
- /* @__PURE__ */ jsxs2("span", { className: "squisq-status-item", children: [
540
- stats.words,
541
- " words"
542
- ] }),
543
- /* @__PURE__ */ jsxs2("span", { className: "squisq-status-item", children: [
544
- stats.chars,
545
- " chars"
546
- ] }),
547
- /* @__PURE__ */ jsxs2("span", { className: "squisq-status-item", children: [
548
- stats.lines,
549
- " lines"
550
- ] }),
551
- /* @__PURE__ */ jsxs2("span", { className: "squisq-status-item", children: [
552
- stats.blocks,
553
- " blocks"
554
- ] }),
555
- /* @__PURE__ */ jsx3("span", { className: "squisq-status-spacer" }),
556
- isParsing && /* @__PURE__ */ jsx3("span", { className: "squisq-status-item squisq-status-parsing", children: "Parsing\u2026" }),
557
- parseError && /* @__PURE__ */ jsx3("span", { className: "squisq-status-item squisq-status-error", title: parseError, children: "\u26A0 Error" }),
558
- !isParsing && !parseError && /* @__PURE__ */ jsx3("span", { className: "squisq-status-item squisq-status-ok", children: "\u2713 OK" })
559
- ] });
560
- }
561
-
562
- // src/RawEditor.tsx
563
- import { useRef as useRef2, useCallback as useCallback3, useEffect as useEffect3 } from "react";
564
- import Editor, { loader } from "@monaco-editor/react";
565
- import * as monaco from "monaco-editor";
566
- import { getAvailableTemplates as getAvailableTemplates2 } from "@bendyline/squisq/doc";
567
- import { jsx as jsx4 } from "react/jsx-runtime";
568
- loader.config({ monaco });
569
- function RawEditor({
570
- theme = "vs",
571
- minimap = false,
572
- fontSize = 14,
573
- wordWrap = "on",
574
- className
575
- }) {
576
- const { markdownSource, setMarkdownSource, setMonacoEditor } = useEditorContext();
577
- const editorRef = useRef2(null);
578
- const isExternalUpdate = useRef2(false);
579
- const completionDisposable = useRef2(null);
580
- const handleMount = useCallback3(
581
- (editor, monaco2) => {
582
- editorRef.current = editor;
583
- setMonacoEditor(editor);
584
- editor.focus();
585
- completionDisposable.current?.dispose();
586
- const templates = getAvailableTemplates2();
587
- completionDisposable.current = monaco2.languages.registerCompletionItemProvider("markdown", {
588
- triggerCharacters: ["["],
589
- provideCompletionItems(model, position) {
590
- const lineContent = model.getLineContent(position.lineNumber);
591
- if (!/^#{1,6}\s/.test(lineContent)) return { suggestions: [] };
592
- const textBeforeCursor = lineContent.substring(0, position.column - 1);
593
- const bracketIdx = textBeforeCursor.lastIndexOf("{[");
594
- if (bracketIdx === -1) return { suggestions: [] };
595
- const startCol = bracketIdx + 3;
596
- const range = new monaco2.Range(
597
- position.lineNumber,
598
- startCol,
599
- position.lineNumber,
600
- position.column
601
- );
602
- const suggestions = templates.map((name) => ({
603
- label: name,
604
- kind: monaco2.languages.CompletionItemKind.Value,
605
- insertText: name + "]}",
606
- range,
607
- detail: "Block template",
608
- sortText: name
609
- }));
610
- return { suggestions };
611
- }
612
- });
613
- },
614
- [setMonacoEditor]
615
- );
616
- useEffect3(() => {
617
- return () => {
618
- setMonacoEditor(null);
619
- completionDisposable.current?.dispose();
620
- completionDisposable.current = null;
621
- };
622
- }, [setMonacoEditor]);
623
- const handleChange = useCallback3(
624
- (value) => {
625
- if (isExternalUpdate.current) return;
626
- if (value !== void 0) {
627
- setMarkdownSource(value);
628
- }
629
- },
630
- [setMarkdownSource]
631
- );
632
- useEffect3(() => {
633
- const editor = editorRef.current;
634
- if (editor) {
635
- const currentValue = editor.getValue();
636
- if (currentValue !== markdownSource) {
637
- isExternalUpdate.current = true;
638
- editor.setValue(markdownSource);
639
- isExternalUpdate.current = false;
640
- }
641
- }
642
- }, [markdownSource]);
643
- return /* @__PURE__ */ jsx4("div", { className, style: { width: "100%", height: "100%" }, "data-testid": "raw-editor", children: /* @__PURE__ */ jsx4(
644
- Editor,
645
- {
646
- defaultLanguage: "markdown",
647
- value: markdownSource,
648
- theme,
649
- onMount: handleMount,
650
- onChange: handleChange,
651
- options: {
652
- fontSize,
653
- wordWrap,
654
- minimap: { enabled: minimap },
655
- lineNumbers: "on",
656
- scrollBeyondLastLine: false,
657
- automaticLayout: true,
658
- tabSize: 2,
659
- renderWhitespace: "selection",
660
- bracketPairColorization: { enabled: true },
661
- guides: { indentation: true },
662
- padding: { top: 12, bottom: 12 }
663
- }
664
- }
665
- ) });
666
- }
667
-
668
- // src/WysiwygEditor.tsx
669
- import { useEffect as useEffect4, useRef as useRef3 } from "react";
670
- import { useEditor, EditorContent } from "@tiptap/react";
671
- import StarterKit from "@tiptap/starter-kit";
672
- import Table from "@tiptap/extension-table";
673
- import TableRow from "@tiptap/extension-table-row";
674
- import TableCell from "@tiptap/extension-table-cell";
675
- import TableHeader from "@tiptap/extension-table-header";
676
- import TaskList from "@tiptap/extension-task-list";
677
- import TaskItem from "@tiptap/extension-task-item";
678
- import Placeholder from "@tiptap/extension-placeholder";
679
-
680
- // ../../node_modules/@tiptap/core/dist/index.js
681
- import { Plugin, PluginKey, TextSelection, Selection, AllSelection, NodeSelection, EditorState } from "@tiptap/pm/state";
682
- import { EditorView } from "@tiptap/pm/view";
683
- import { keymap } from "@tiptap/pm/keymap";
684
- import { Schema, DOMSerializer, Fragment as Fragment2, Node as Node$1, DOMParser, Slice } from "@tiptap/pm/model";
685
- import { liftTarget, ReplaceStep, ReplaceAroundStep, joinPoint, Transform, canSplit, canJoin, findWrapping } from "@tiptap/pm/transform";
686
- import { createParagraphNear as createParagraphNear$1, deleteSelection as deleteSelection$1, exitCode as exitCode$1, joinUp as joinUp$1, joinDown as joinDown$1, joinBackward as joinBackward$1, joinForward as joinForward$1, joinTextblockBackward as joinTextblockBackward$1, joinTextblockForward as joinTextblockForward$1, lift as lift$1, liftEmptyBlock as liftEmptyBlock$1, newlineInCode as newlineInCode$1, selectNodeBackward as selectNodeBackward$1, selectNodeForward as selectNodeForward$1, selectParentNode as selectParentNode$1, selectTextblockEnd as selectTextblockEnd$1, selectTextblockStart as selectTextblockStart$1, setBlockType, wrapIn as wrapIn$1 } from "@tiptap/pm/commands";
687
- import { liftListItem as liftListItem$1, sinkListItem as sinkListItem$1, wrapInList as wrapInList$1 } from "@tiptap/pm/schema-list";
688
- function createChainableState(config) {
689
- const { state, transaction } = config;
690
- let { selection } = transaction;
691
- let { doc } = transaction;
692
- let { storedMarks } = transaction;
693
- return {
694
- ...state,
695
- apply: state.apply.bind(state),
696
- applyTransaction: state.applyTransaction.bind(state),
697
- plugins: state.plugins,
698
- schema: state.schema,
699
- reconfigure: state.reconfigure.bind(state),
700
- toJSON: state.toJSON.bind(state),
701
- get storedMarks() {
702
- return storedMarks;
703
- },
704
- get selection() {
705
- return selection;
706
- },
707
- get doc() {
708
- return doc;
709
- },
710
- get tr() {
711
- selection = transaction.selection;
712
- doc = transaction.doc;
713
- storedMarks = transaction.storedMarks;
714
- return transaction;
715
- }
716
- };
717
- }
718
- var CommandManager = class {
719
- constructor(props) {
720
- this.editor = props.editor;
721
- this.rawCommands = this.editor.extensionManager.commands;
722
- this.customState = props.state;
723
- }
724
- get hasCustomState() {
725
- return !!this.customState;
726
- }
727
- get state() {
728
- return this.customState || this.editor.state;
729
- }
730
- get commands() {
731
- const { rawCommands, editor, state } = this;
732
- const { view } = editor;
733
- const { tr } = state;
734
- const props = this.buildProps(tr);
735
- return Object.fromEntries(Object.entries(rawCommands).map(([name, command2]) => {
736
- const method = (...args) => {
737
- const callback = command2(...args)(props);
738
- if (!tr.getMeta("preventDispatch") && !this.hasCustomState) {
739
- view.dispatch(tr);
740
- }
741
- return callback;
742
- };
743
- return [name, method];
744
- }));
745
- }
746
- get chain() {
747
- return () => this.createChain();
748
- }
749
- get can() {
750
- return () => this.createCan();
751
- }
752
- createChain(startTr, shouldDispatch = true) {
753
- const { rawCommands, editor, state } = this;
754
- const { view } = editor;
755
- const callbacks = [];
756
- const hasStartTransaction = !!startTr;
757
- const tr = startTr || state.tr;
758
- const run = () => {
759
- if (!hasStartTransaction && shouldDispatch && !tr.getMeta("preventDispatch") && !this.hasCustomState) {
760
- view.dispatch(tr);
761
- }
762
- return callbacks.every((callback) => callback === true);
763
- };
764
- const chain = {
765
- ...Object.fromEntries(Object.entries(rawCommands).map(([name, command2]) => {
766
- const chainedCommand = (...args) => {
767
- const props = this.buildProps(tr, shouldDispatch);
768
- const callback = command2(...args)(props);
769
- callbacks.push(callback);
770
- return chain;
771
- };
772
- return [name, chainedCommand];
773
- })),
774
- run
775
- };
776
- return chain;
777
- }
778
- createCan(startTr) {
779
- const { rawCommands, state } = this;
780
- const dispatch = false;
781
- const tr = startTr || state.tr;
782
- const props = this.buildProps(tr, dispatch);
783
- const formattedCommands = Object.fromEntries(Object.entries(rawCommands).map(([name, command2]) => {
784
- return [name, (...args) => command2(...args)({ ...props, dispatch: void 0 })];
785
- }));
786
- return {
787
- ...formattedCommands,
788
- chain: () => this.createChain(tr, dispatch)
789
- };
790
- }
791
- buildProps(tr, shouldDispatch = true) {
792
- const { rawCommands, editor, state } = this;
793
- const { view } = editor;
794
- const props = {
795
- tr,
796
- editor,
797
- view,
798
- state: createChainableState({
799
- state,
800
- transaction: tr
801
- }),
802
- dispatch: shouldDispatch ? () => void 0 : void 0,
803
- chain: () => this.createChain(tr, shouldDispatch),
804
- can: () => this.createCan(tr),
805
- get commands() {
806
- return Object.fromEntries(Object.entries(rawCommands).map(([name, command2]) => {
807
- return [name, (...args) => command2(...args)(props)];
808
- }));
809
- }
810
- };
811
- return props;
812
- }
813
- };
814
- function getExtensionField(extension, field, context) {
815
- if (extension.config[field] === void 0 && extension.parent) {
816
- return getExtensionField(extension.parent, field, context);
817
- }
818
- if (typeof extension.config[field] === "function") {
819
- const value = extension.config[field].bind({
820
- ...context,
821
- parent: extension.parent ? getExtensionField(extension.parent, field, context) : null
822
- });
823
- return value;
824
- }
825
- return extension.config[field];
826
- }
827
- function splitExtensions(extensions) {
828
- const baseExtensions = extensions.filter((extension) => extension.type === "extension");
829
- const nodeExtensions = extensions.filter((extension) => extension.type === "node");
830
- const markExtensions = extensions.filter((extension) => extension.type === "mark");
831
- return {
832
- baseExtensions,
833
- nodeExtensions,
834
- markExtensions
835
- };
836
- }
837
- function getNodeType(nameOrType, schema) {
838
- if (typeof nameOrType === "string") {
839
- if (!schema.nodes[nameOrType]) {
840
- throw Error(`There is no node type named '${nameOrType}'. Maybe you forgot to add the extension?`);
841
- }
842
- return schema.nodes[nameOrType];
843
- }
844
- return nameOrType;
845
- }
846
- function mergeAttributes(...objects) {
847
- return objects.filter((item) => !!item).reduce((items, item) => {
848
- const mergedAttributes = { ...items };
849
- Object.entries(item).forEach(([key, value]) => {
850
- const exists = mergedAttributes[key];
851
- if (!exists) {
852
- mergedAttributes[key] = value;
853
- return;
854
- }
855
- if (key === "class") {
856
- const valueClasses = value ? String(value).split(" ") : [];
857
- const existingClasses = mergedAttributes[key] ? mergedAttributes[key].split(" ") : [];
858
- const insertClasses = valueClasses.filter((valueClass) => !existingClasses.includes(valueClass));
859
- mergedAttributes[key] = [...existingClasses, ...insertClasses].join(" ");
860
- } else if (key === "style") {
861
- const newStyles = value ? value.split(";").map((style) => style.trim()).filter(Boolean) : [];
862
- const existingStyles = mergedAttributes[key] ? mergedAttributes[key].split(";").map((style) => style.trim()).filter(Boolean) : [];
863
- const styleMap = /* @__PURE__ */ new Map();
864
- existingStyles.forEach((style) => {
865
- const [property, val] = style.split(":").map((part) => part.trim());
866
- styleMap.set(property, val);
867
- });
868
- newStyles.forEach((style) => {
869
- const [property, val] = style.split(":").map((part) => part.trim());
870
- styleMap.set(property, val);
871
- });
872
- mergedAttributes[key] = Array.from(styleMap.entries()).map(([property, val]) => `${property}: ${val}`).join("; ");
873
- } else {
874
- mergedAttributes[key] = value;
875
- }
876
- });
877
- return mergedAttributes;
878
- }, {});
879
- }
880
- function isFunction(value) {
881
- return typeof value === "function";
882
- }
883
- function callOrReturn(value, context = void 0, ...props) {
884
- if (isFunction(value)) {
885
- if (context) {
886
- return value.bind(context)(...props);
887
- }
888
- return value(...props);
889
- }
890
- return value;
891
- }
892
- function isRegExp(value) {
893
- return Object.prototype.toString.call(value) === "[object RegExp]";
894
- }
895
- var InputRule = class {
896
- constructor(config) {
897
- this.find = config.find;
898
- this.handler = config.handler;
899
- }
900
- };
901
- function getType(value) {
902
- return Object.prototype.toString.call(value).slice(8, -1);
903
- }
904
- function isPlainObject(value) {
905
- if (getType(value) !== "Object") {
906
- return false;
907
- }
908
- return value.constructor === Object && Object.getPrototypeOf(value) === Object.prototype;
909
- }
910
- function mergeDeep(target, source) {
911
- const output = { ...target };
912
- if (isPlainObject(target) && isPlainObject(source)) {
913
- Object.keys(source).forEach((key) => {
914
- if (isPlainObject(source[key]) && isPlainObject(target[key])) {
915
- output[key] = mergeDeep(target[key], source[key]);
916
- } else {
917
- output[key] = source[key];
918
- }
919
- });
920
- }
921
- return output;
922
- }
923
- var Extension = class _Extension {
924
- constructor(config = {}) {
925
- this.type = "extension";
926
- this.name = "extension";
927
- this.parent = null;
928
- this.child = null;
929
- this.config = {
930
- name: this.name,
931
- defaultOptions: {}
932
- };
933
- this.config = {
934
- ...this.config,
935
- ...config
936
- };
937
- this.name = this.config.name;
938
- if (config.defaultOptions && Object.keys(config.defaultOptions).length > 0) {
939
- console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`);
940
- }
941
- this.options = this.config.defaultOptions;
942
- if (this.config.addOptions) {
943
- this.options = callOrReturn(getExtensionField(this, "addOptions", {
944
- name: this.name
945
- }));
946
- }
947
- this.storage = callOrReturn(getExtensionField(this, "addStorage", {
948
- name: this.name,
949
- options: this.options
950
- })) || {};
951
- }
952
- static create(config = {}) {
953
- return new _Extension(config);
954
- }
955
- configure(options = {}) {
956
- const extension = this.extend({
957
- ...this.config,
958
- addOptions: () => {
959
- return mergeDeep(this.options, options);
960
- }
961
- });
962
- extension.name = this.name;
963
- extension.parent = this.parent;
964
- return extension;
965
- }
966
- extend(extendedConfig = {}) {
967
- const extension = new _Extension({ ...this.config, ...extendedConfig });
968
- extension.parent = this;
969
- this.child = extension;
970
- extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name;
971
- if (extendedConfig.defaultOptions && Object.keys(extendedConfig.defaultOptions).length > 0) {
972
- console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`);
973
- }
974
- extension.options = callOrReturn(getExtensionField(extension, "addOptions", {
975
- name: extension.name
976
- }));
977
- extension.storage = callOrReturn(getExtensionField(extension, "addStorage", {
978
- name: extension.name,
979
- options: extension.options
980
- }));
981
- return extension;
982
- }
983
- };
984
- function getTextBetween(startNode, range, options) {
985
- const { from, to } = range;
986
- const { blockSeparator = "\n\n", textSerializers = {} } = options || {};
987
- let text = "";
988
- startNode.nodesBetween(from, to, (node, pos, parent, index) => {
989
- var _a;
990
- if (node.isBlock && pos > from) {
991
- text += blockSeparator;
992
- }
993
- const textSerializer = textSerializers === null || textSerializers === void 0 ? void 0 : textSerializers[node.type.name];
994
- if (textSerializer) {
995
- if (parent) {
996
- text += textSerializer({
997
- node,
998
- pos,
999
- parent,
1000
- index,
1001
- range
1002
- });
1003
- }
1004
- return false;
1005
- }
1006
- if (node.isText) {
1007
- text += (_a = node === null || node === void 0 ? void 0 : node.text) === null || _a === void 0 ? void 0 : _a.slice(Math.max(from, pos) - pos, to - pos);
1008
- }
1009
- });
1010
- return text;
1011
- }
1012
- function getTextSerializersFromSchema(schema) {
1013
- return Object.fromEntries(Object.entries(schema.nodes).filter(([, node]) => node.spec.toText).map(([name, node]) => [name, node.spec.toText]));
1014
- }
1015
- var ClipboardTextSerializer = Extension.create({
1016
- name: "clipboardTextSerializer",
1017
- addOptions() {
1018
- return {
1019
- blockSeparator: void 0
1020
- };
1021
- },
1022
- addProseMirrorPlugins() {
1023
- return [
1024
- new Plugin({
1025
- key: new PluginKey("clipboardTextSerializer"),
1026
- props: {
1027
- clipboardTextSerializer: () => {
1028
- const { editor } = this;
1029
- const { state, schema } = editor;
1030
- const { doc, selection } = state;
1031
- const { ranges } = selection;
1032
- const from = Math.min(...ranges.map((range2) => range2.$from.pos));
1033
- const to = Math.max(...ranges.map((range2) => range2.$to.pos));
1034
- const textSerializers = getTextSerializersFromSchema(schema);
1035
- const range = { from, to };
1036
- return getTextBetween(doc, range, {
1037
- ...this.options.blockSeparator !== void 0 ? { blockSeparator: this.options.blockSeparator } : {},
1038
- textSerializers
1039
- });
1040
- }
1041
- }
1042
- })
1043
- ];
1044
- }
1045
- });
1046
- var blur = () => ({ editor, view }) => {
1047
- requestAnimationFrame(() => {
1048
- var _a;
1049
- if (!editor.isDestroyed) {
1050
- view.dom.blur();
1051
- (_a = window === null || window === void 0 ? void 0 : window.getSelection()) === null || _a === void 0 ? void 0 : _a.removeAllRanges();
1052
- }
1053
- });
1054
- return true;
1055
- };
1056
- var clearContent = (emitUpdate = false) => ({ commands: commands2 }) => {
1057
- return commands2.setContent("", emitUpdate);
1058
- };
1059
- var clearNodes = () => ({ state, tr, dispatch }) => {
1060
- const { selection } = tr;
1061
- const { ranges } = selection;
1062
- if (!dispatch) {
1063
- return true;
1064
- }
1065
- ranges.forEach(({ $from, $to }) => {
1066
- state.doc.nodesBetween($from.pos, $to.pos, (node, pos) => {
1067
- if (node.type.isText) {
1068
- return;
1069
- }
1070
- const { doc, mapping } = tr;
1071
- const $mappedFrom = doc.resolve(mapping.map(pos));
1072
- const $mappedTo = doc.resolve(mapping.map(pos + node.nodeSize));
1073
- const nodeRange = $mappedFrom.blockRange($mappedTo);
1074
- if (!nodeRange) {
1075
- return;
1076
- }
1077
- const targetLiftDepth = liftTarget(nodeRange);
1078
- if (node.type.isTextblock) {
1079
- const { defaultType } = $mappedFrom.parent.contentMatchAt($mappedFrom.index());
1080
- tr.setNodeMarkup(nodeRange.start, defaultType);
1081
- }
1082
- if (targetLiftDepth || targetLiftDepth === 0) {
1083
- tr.lift(nodeRange, targetLiftDepth);
1084
- }
1085
- });
1086
- });
1087
- return true;
1088
- };
1089
- var command = (fn) => (props) => {
1090
- return fn(props);
1091
- };
1092
- var createParagraphNear = () => ({ state, dispatch }) => {
1093
- return createParagraphNear$1(state, dispatch);
1094
- };
1095
- var cut = (originRange, targetPos) => ({ editor, tr }) => {
1096
- const { state } = editor;
1097
- const contentSlice = state.doc.slice(originRange.from, originRange.to);
1098
- tr.deleteRange(originRange.from, originRange.to);
1099
- const newPos = tr.mapping.map(targetPos);
1100
- tr.insert(newPos, contentSlice.content);
1101
- tr.setSelection(new TextSelection(tr.doc.resolve(Math.max(newPos - 1, 0))));
1102
- return true;
1103
- };
1104
- var deleteCurrentNode = () => ({ tr, dispatch }) => {
1105
- const { selection } = tr;
1106
- const currentNode = selection.$anchor.node();
1107
- if (currentNode.content.size > 0) {
1108
- return false;
1109
- }
1110
- const $pos = tr.selection.$anchor;
1111
- for (let depth = $pos.depth; depth > 0; depth -= 1) {
1112
- const node = $pos.node(depth);
1113
- if (node.type === currentNode.type) {
1114
- if (dispatch) {
1115
- const from = $pos.before(depth);
1116
- const to = $pos.after(depth);
1117
- tr.delete(from, to).scrollIntoView();
1118
- }
1119
- return true;
1120
- }
1121
- }
1122
- return false;
1123
- };
1124
- var deleteNode = (typeOrName) => ({ tr, state, dispatch }) => {
1125
- const type = getNodeType(typeOrName, state.schema);
1126
- const $pos = tr.selection.$anchor;
1127
- for (let depth = $pos.depth; depth > 0; depth -= 1) {
1128
- const node = $pos.node(depth);
1129
- if (node.type === type) {
1130
- if (dispatch) {
1131
- const from = $pos.before(depth);
1132
- const to = $pos.after(depth);
1133
- tr.delete(from, to).scrollIntoView();
1134
- }
1135
- return true;
1136
- }
1137
- }
1138
- return false;
1139
- };
1140
- var deleteRange = (range) => ({ tr, dispatch }) => {
1141
- const { from, to } = range;
1142
- if (dispatch) {
1143
- tr.delete(from, to);
1144
- }
1145
- return true;
1146
- };
1147
- var deleteSelection = () => ({ state, dispatch }) => {
1148
- return deleteSelection$1(state, dispatch);
1149
- };
1150
- var enter = () => ({ commands: commands2 }) => {
1151
- return commands2.keyboardShortcut("Enter");
1152
- };
1153
- var exitCode = () => ({ state, dispatch }) => {
1154
- return exitCode$1(state, dispatch);
1155
- };
1156
- function objectIncludes(object1, object2, options = { strict: true }) {
1157
- const keys = Object.keys(object2);
1158
- if (!keys.length) {
1159
- return true;
1160
- }
1161
- return keys.every((key) => {
1162
- if (options.strict) {
1163
- return object2[key] === object1[key];
1164
- }
1165
- if (isRegExp(object2[key])) {
1166
- return object2[key].test(object1[key]);
1167
- }
1168
- return object2[key] === object1[key];
1169
- });
1170
- }
1171
- function findMarkInSet(marks, type, attributes = {}) {
1172
- return marks.find((item) => {
1173
- return item.type === type && objectIncludes(
1174
- // Only check equality for the attributes that are provided
1175
- Object.fromEntries(Object.keys(attributes).map((k) => [k, item.attrs[k]])),
1176
- attributes
1177
- );
1178
- });
1179
- }
1180
- function isMarkInSet(marks, type, attributes = {}) {
1181
- return !!findMarkInSet(marks, type, attributes);
1182
- }
1183
- function getMarkRange($pos, type, attributes) {
1184
- var _a;
1185
- if (!$pos || !type) {
1186
- return;
1187
- }
1188
- let start = $pos.parent.childAfter($pos.parentOffset);
1189
- if (!start.node || !start.node.marks.some((mark2) => mark2.type === type)) {
1190
- start = $pos.parent.childBefore($pos.parentOffset);
1191
- }
1192
- if (!start.node || !start.node.marks.some((mark2) => mark2.type === type)) {
1193
- return;
1194
- }
1195
- attributes = attributes || ((_a = start.node.marks[0]) === null || _a === void 0 ? void 0 : _a.attrs);
1196
- const mark = findMarkInSet([...start.node.marks], type, attributes);
1197
- if (!mark) {
1198
- return;
1199
- }
1200
- let startIndex = start.index;
1201
- let startPos = $pos.start() + start.offset;
1202
- let endIndex = startIndex + 1;
1203
- let endPos = startPos + start.node.nodeSize;
1204
- while (startIndex > 0 && isMarkInSet([...$pos.parent.child(startIndex - 1).marks], type, attributes)) {
1205
- startIndex -= 1;
1206
- startPos -= $pos.parent.child(startIndex).nodeSize;
1207
- }
1208
- while (endIndex < $pos.parent.childCount && isMarkInSet([...$pos.parent.child(endIndex).marks], type, attributes)) {
1209
- endPos += $pos.parent.child(endIndex).nodeSize;
1210
- endIndex += 1;
1211
- }
1212
- return {
1213
- from: startPos,
1214
- to: endPos
1215
- };
1216
- }
1217
- function getMarkType(nameOrType, schema) {
1218
- if (typeof nameOrType === "string") {
1219
- if (!schema.marks[nameOrType]) {
1220
- throw Error(`There is no mark type named '${nameOrType}'. Maybe you forgot to add the extension?`);
1221
- }
1222
- return schema.marks[nameOrType];
1223
- }
1224
- return nameOrType;
1225
- }
1226
- var extendMarkRange = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => {
1227
- const type = getMarkType(typeOrName, state.schema);
1228
- const { doc, selection } = tr;
1229
- const { $from, from, to } = selection;
1230
- if (dispatch) {
1231
- const range = getMarkRange($from, type, attributes);
1232
- if (range && range.from <= from && range.to >= to) {
1233
- const newSelection = TextSelection.create(doc, range.from, range.to);
1234
- tr.setSelection(newSelection);
1235
- }
1236
- }
1237
- return true;
1238
- };
1239
- var first = (commands2) => (props) => {
1240
- const items = typeof commands2 === "function" ? commands2(props) : commands2;
1241
- for (let i = 0; i < items.length; i += 1) {
1242
- if (items[i](props)) {
1243
- return true;
1244
- }
1245
- }
1246
- return false;
1247
- };
1248
- function isTextSelection(value) {
1249
- return value instanceof TextSelection;
1250
- }
1251
- function minMax(value = 0, min = 0, max = 0) {
1252
- return Math.min(Math.max(value, min), max);
1253
- }
1254
- function resolveFocusPosition(doc, position = null) {
1255
- if (!position) {
1256
- return null;
1257
- }
1258
- const selectionAtStart = Selection.atStart(doc);
1259
- const selectionAtEnd = Selection.atEnd(doc);
1260
- if (position === "start" || position === true) {
1261
- return selectionAtStart;
1262
- }
1263
- if (position === "end") {
1264
- return selectionAtEnd;
1265
- }
1266
- const minPos = selectionAtStart.from;
1267
- const maxPos = selectionAtEnd.to;
1268
- if (position === "all") {
1269
- return TextSelection.create(doc, minMax(0, minPos, maxPos), minMax(doc.content.size, minPos, maxPos));
1270
- }
1271
- return TextSelection.create(doc, minMax(position, minPos, maxPos), minMax(position, minPos, maxPos));
1272
- }
1273
- function isAndroid() {
1274
- return navigator.platform === "Android" || /android/i.test(navigator.userAgent);
1275
- }
1276
- function isiOS() {
1277
- return [
1278
- "iPad Simulator",
1279
- "iPhone Simulator",
1280
- "iPod Simulator",
1281
- "iPad",
1282
- "iPhone",
1283
- "iPod"
1284
- ].includes(navigator.platform) || navigator.userAgent.includes("Mac") && "ontouchend" in document;
1285
- }
1286
- function isSafari() {
1287
- return typeof navigator !== "undefined" ? /^((?!chrome|android).)*safari/i.test(navigator.userAgent) : false;
1288
- }
1289
- var focus = (position = null, options = {}) => ({ editor, view, tr, dispatch }) => {
1290
- options = {
1291
- scrollIntoView: true,
1292
- ...options
1293
- };
1294
- const delayedFocus = () => {
1295
- if (isiOS() || isAndroid()) {
1296
- view.dom.focus();
1297
- }
1298
- requestAnimationFrame(() => {
1299
- if (!editor.isDestroyed) {
1300
- view.focus();
1301
- if (isSafari() && !isiOS() && !isAndroid()) {
1302
- view.dom.focus({ preventScroll: true });
1303
- }
1304
- }
1305
- });
1306
- };
1307
- if (view.hasFocus() && position === null || position === false) {
1308
- return true;
1309
- }
1310
- if (dispatch && position === null && !isTextSelection(editor.state.selection)) {
1311
- delayedFocus();
1312
- return true;
1313
- }
1314
- const selection = resolveFocusPosition(tr.doc, position) || editor.state.selection;
1315
- const isSameSelection = editor.state.selection.eq(selection);
1316
- if (dispatch) {
1317
- if (!isSameSelection) {
1318
- tr.setSelection(selection);
1319
- }
1320
- if (isSameSelection && tr.storedMarks) {
1321
- tr.setStoredMarks(tr.storedMarks);
1322
- }
1323
- delayedFocus();
1324
- }
1325
- return true;
1326
- };
1327
- var forEach = (items, fn) => (props) => {
1328
- return items.every((item, index) => fn(item, { ...props, index }));
1329
- };
1330
- var insertContent = (value, options) => ({ tr, commands: commands2 }) => {
1331
- return commands2.insertContentAt({ from: tr.selection.from, to: tr.selection.to }, value, options);
1332
- };
1333
- var removeWhitespaces = (node) => {
1334
- const children = node.childNodes;
1335
- for (let i = children.length - 1; i >= 0; i -= 1) {
1336
- const child = children[i];
1337
- if (child.nodeType === 3 && child.nodeValue && /^(\n\s\s|\n)$/.test(child.nodeValue)) {
1338
- node.removeChild(child);
1339
- } else if (child.nodeType === 1) {
1340
- removeWhitespaces(child);
1341
- }
1342
- }
1343
- return node;
1344
- };
1345
- function elementFromString(value) {
1346
- const wrappedValue = `<body>${value}</body>`;
1347
- const html = new window.DOMParser().parseFromString(wrappedValue, "text/html").body;
1348
- return removeWhitespaces(html);
1349
- }
1350
- function createNodeFromContent(content, schema, options) {
1351
- if (content instanceof Node$1 || content instanceof Fragment2) {
1352
- return content;
1353
- }
1354
- options = {
1355
- slice: true,
1356
- parseOptions: {},
1357
- ...options
1358
- };
1359
- const isJSONContent = typeof content === "object" && content !== null;
1360
- const isTextContent = typeof content === "string";
1361
- if (isJSONContent) {
1362
- try {
1363
- const isArrayContent = Array.isArray(content) && content.length > 0;
1364
- if (isArrayContent) {
1365
- return Fragment2.fromArray(content.map((item) => schema.nodeFromJSON(item)));
1366
- }
1367
- const node = schema.nodeFromJSON(content);
1368
- if (options.errorOnInvalidContent) {
1369
- node.check();
1370
- }
1371
- return node;
1372
- } catch (error) {
1373
- if (options.errorOnInvalidContent) {
1374
- throw new Error("[tiptap error]: Invalid JSON content", { cause: error });
1375
- }
1376
- console.warn("[tiptap warn]: Invalid content.", "Passed value:", content, "Error:", error);
1377
- return createNodeFromContent("", schema, options);
1378
- }
1379
- }
1380
- if (isTextContent) {
1381
- if (options.errorOnInvalidContent) {
1382
- let hasInvalidContent = false;
1383
- let invalidContent = "";
1384
- const contentCheckSchema = new Schema({
1385
- topNode: schema.spec.topNode,
1386
- marks: schema.spec.marks,
1387
- // Prosemirror's schemas are executed such that: the last to execute, matches last
1388
- // This means that we can add a catch-all node at the end of the schema to catch any content that we don't know how to handle
1389
- nodes: schema.spec.nodes.append({
1390
- __tiptap__private__unknown__catch__all__node: {
1391
- content: "inline*",
1392
- group: "block",
1393
- parseDOM: [
1394
- {
1395
- tag: "*",
1396
- getAttrs: (e) => {
1397
- hasInvalidContent = true;
1398
- invalidContent = typeof e === "string" ? e : e.outerHTML;
1399
- return null;
1400
- }
1401
- }
1402
- ]
1403
- }
1404
- })
1405
- });
1406
- if (options.slice) {
1407
- DOMParser.fromSchema(contentCheckSchema).parseSlice(elementFromString(content), options.parseOptions);
1408
- } else {
1409
- DOMParser.fromSchema(contentCheckSchema).parse(elementFromString(content), options.parseOptions);
1410
- }
1411
- if (options.errorOnInvalidContent && hasInvalidContent) {
1412
- throw new Error("[tiptap error]: Invalid HTML content", { cause: new Error(`Invalid element found: ${invalidContent}`) });
1413
- }
1414
- }
1415
- const parser = DOMParser.fromSchema(schema);
1416
- if (options.slice) {
1417
- return parser.parseSlice(elementFromString(content), options.parseOptions).content;
1418
- }
1419
- return parser.parse(elementFromString(content), options.parseOptions);
1420
- }
1421
- return createNodeFromContent("", schema, options);
1422
- }
1423
- function selectionToInsertionEnd(tr, startLen, bias) {
1424
- const last = tr.steps.length - 1;
1425
- if (last < startLen) {
1426
- return;
1427
- }
1428
- const step = tr.steps[last];
1429
- if (!(step instanceof ReplaceStep || step instanceof ReplaceAroundStep)) {
1430
- return;
1431
- }
1432
- const map = tr.mapping.maps[last];
1433
- let end = 0;
1434
- map.forEach((_from, _to, _newFrom, newTo) => {
1435
- if (end === 0) {
1436
- end = newTo;
1437
- }
1438
- });
1439
- tr.setSelection(Selection.near(tr.doc.resolve(end), bias));
1440
- }
1441
- var isFragment = (nodeOrFragment) => {
1442
- return !("type" in nodeOrFragment);
1443
- };
1444
- var insertContentAt = (position, value, options) => ({ tr, dispatch, editor }) => {
1445
- var _a;
1446
- if (dispatch) {
1447
- options = {
1448
- parseOptions: editor.options.parseOptions,
1449
- updateSelection: true,
1450
- applyInputRules: false,
1451
- applyPasteRules: false,
1452
- ...options
1453
- };
1454
- let content;
1455
- const emitContentError = (error) => {
1456
- editor.emit("contentError", {
1457
- editor,
1458
- error,
1459
- disableCollaboration: () => {
1460
- if (editor.storage.collaboration) {
1461
- editor.storage.collaboration.isDisabled = true;
1462
- }
1463
- }
1464
- });
1465
- };
1466
- const parseOptions = {
1467
- preserveWhitespace: "full",
1468
- ...options.parseOptions
1469
- };
1470
- if (!options.errorOnInvalidContent && !editor.options.enableContentCheck && editor.options.emitContentError) {
1471
- try {
1472
- createNodeFromContent(value, editor.schema, {
1473
- parseOptions,
1474
- errorOnInvalidContent: true
1475
- });
1476
- } catch (e) {
1477
- emitContentError(e);
1478
- }
1479
- }
1480
- try {
1481
- content = createNodeFromContent(value, editor.schema, {
1482
- parseOptions,
1483
- errorOnInvalidContent: (_a = options.errorOnInvalidContent) !== null && _a !== void 0 ? _a : editor.options.enableContentCheck
1484
- });
1485
- } catch (e) {
1486
- emitContentError(e);
1487
- return false;
1488
- }
1489
- let { from, to } = typeof position === "number" ? { from: position, to: position } : { from: position.from, to: position.to };
1490
- let isOnlyTextContent = true;
1491
- let isOnlyBlockContent = true;
1492
- const nodes = isFragment(content) ? content : [content];
1493
- nodes.forEach((node) => {
1494
- node.check();
1495
- isOnlyTextContent = isOnlyTextContent ? node.isText && node.marks.length === 0 : false;
1496
- isOnlyBlockContent = isOnlyBlockContent ? node.isBlock : false;
1497
- });
1498
- if (from === to && isOnlyBlockContent) {
1499
- const { parent } = tr.doc.resolve(from);
1500
- const isEmptyTextBlock = parent.isTextblock && !parent.type.spec.code && !parent.childCount;
1501
- if (isEmptyTextBlock) {
1502
- from -= 1;
1503
- to += 1;
1504
- }
1505
- }
1506
- let newContent;
1507
- if (isOnlyTextContent) {
1508
- if (Array.isArray(value)) {
1509
- newContent = value.map((v) => v.text || "").join("");
1510
- } else if (value instanceof Fragment2) {
1511
- let text = "";
1512
- value.forEach((node) => {
1513
- if (node.text) {
1514
- text += node.text;
1515
- }
1516
- });
1517
- newContent = text;
1518
- } else if (typeof value === "object" && !!value && !!value.text) {
1519
- newContent = value.text;
1520
- } else {
1521
- newContent = value;
1522
- }
1523
- tr.insertText(newContent, from, to);
1524
- } else {
1525
- newContent = content;
1526
- tr.replaceWith(from, to, newContent);
1527
- }
1528
- if (options.updateSelection) {
1529
- selectionToInsertionEnd(tr, tr.steps.length - 1, -1);
1530
- }
1531
- if (options.applyInputRules) {
1532
- tr.setMeta("applyInputRules", { from, text: newContent });
1533
- }
1534
- if (options.applyPasteRules) {
1535
- tr.setMeta("applyPasteRules", { from, text: newContent });
1536
- }
1537
- }
1538
- return true;
1539
- };
1540
- var joinUp = () => ({ state, dispatch }) => {
1541
- return joinUp$1(state, dispatch);
1542
- };
1543
- var joinDown = () => ({ state, dispatch }) => {
1544
- return joinDown$1(state, dispatch);
1545
- };
1546
- var joinBackward = () => ({ state, dispatch }) => {
1547
- return joinBackward$1(state, dispatch);
1548
- };
1549
- var joinForward = () => ({ state, dispatch }) => {
1550
- return joinForward$1(state, dispatch);
1551
- };
1552
- var joinItemBackward = () => ({ state, dispatch, tr }) => {
1553
- try {
1554
- const point = joinPoint(state.doc, state.selection.$from.pos, -1);
1555
- if (point === null || point === void 0) {
1556
- return false;
1557
- }
1558
- tr.join(point, 2);
1559
- if (dispatch) {
1560
- dispatch(tr);
1561
- }
1562
- return true;
1563
- } catch {
1564
- return false;
1565
- }
1566
- };
1567
- var joinItemForward = () => ({ state, dispatch, tr }) => {
1568
- try {
1569
- const point = joinPoint(state.doc, state.selection.$from.pos, 1);
1570
- if (point === null || point === void 0) {
1571
- return false;
1572
- }
1573
- tr.join(point, 2);
1574
- if (dispatch) {
1575
- dispatch(tr);
1576
- }
1577
- return true;
1578
- } catch {
1579
- return false;
1580
- }
1581
- };
1582
- var joinTextblockBackward = () => ({ state, dispatch }) => {
1583
- return joinTextblockBackward$1(state, dispatch);
1584
- };
1585
- var joinTextblockForward = () => ({ state, dispatch }) => {
1586
- return joinTextblockForward$1(state, dispatch);
1587
- };
1588
- function isMacOS() {
1589
- return typeof navigator !== "undefined" ? /Mac/.test(navigator.platform) : false;
1590
- }
1591
- function normalizeKeyName(name) {
1592
- const parts = name.split(/-(?!$)/);
1593
- let result = parts[parts.length - 1];
1594
- if (result === "Space") {
1595
- result = " ";
1596
- }
1597
- let alt;
1598
- let ctrl;
1599
- let shift;
1600
- let meta;
1601
- for (let i = 0; i < parts.length - 1; i += 1) {
1602
- const mod = parts[i];
1603
- if (/^(cmd|meta|m)$/i.test(mod)) {
1604
- meta = true;
1605
- } else if (/^a(lt)?$/i.test(mod)) {
1606
- alt = true;
1607
- } else if (/^(c|ctrl|control)$/i.test(mod)) {
1608
- ctrl = true;
1609
- } else if (/^s(hift)?$/i.test(mod)) {
1610
- shift = true;
1611
- } else if (/^mod$/i.test(mod)) {
1612
- if (isiOS() || isMacOS()) {
1613
- meta = true;
1614
- } else {
1615
- ctrl = true;
1616
- }
1617
- } else {
1618
- throw new Error(`Unrecognized modifier name: ${mod}`);
1619
- }
1620
- }
1621
- if (alt) {
1622
- result = `Alt-${result}`;
1623
- }
1624
- if (ctrl) {
1625
- result = `Ctrl-${result}`;
1626
- }
1627
- if (meta) {
1628
- result = `Meta-${result}`;
1629
- }
1630
- if (shift) {
1631
- result = `Shift-${result}`;
1632
- }
1633
- return result;
1634
- }
1635
- var keyboardShortcut = (name) => ({ editor, view, tr, dispatch }) => {
1636
- const keys = normalizeKeyName(name).split(/-(?!$)/);
1637
- const key = keys.find((item) => !["Alt", "Ctrl", "Meta", "Shift"].includes(item));
1638
- const event = new KeyboardEvent("keydown", {
1639
- key: key === "Space" ? " " : key,
1640
- altKey: keys.includes("Alt"),
1641
- ctrlKey: keys.includes("Ctrl"),
1642
- metaKey: keys.includes("Meta"),
1643
- shiftKey: keys.includes("Shift"),
1644
- bubbles: true,
1645
- cancelable: true
1646
- });
1647
- const capturedTransaction = editor.captureTransaction(() => {
1648
- view.someProp("handleKeyDown", (f) => f(view, event));
1649
- });
1650
- capturedTransaction === null || capturedTransaction === void 0 ? void 0 : capturedTransaction.steps.forEach((step) => {
1651
- const newStep = step.map(tr.mapping);
1652
- if (newStep && dispatch) {
1653
- tr.maybeStep(newStep);
1654
- }
1655
- });
1656
- return true;
1657
- };
1658
- function isNodeActive(state, typeOrName, attributes = {}) {
1659
- const { from, to, empty } = state.selection;
1660
- const type = typeOrName ? getNodeType(typeOrName, state.schema) : null;
1661
- const nodeRanges = [];
1662
- state.doc.nodesBetween(from, to, (node, pos) => {
1663
- if (node.isText) {
1664
- return;
1665
- }
1666
- const relativeFrom = Math.max(from, pos);
1667
- const relativeTo = Math.min(to, pos + node.nodeSize);
1668
- nodeRanges.push({
1669
- node,
1670
- from: relativeFrom,
1671
- to: relativeTo
1672
- });
1673
- });
1674
- const selectionRange = to - from;
1675
- const matchedNodeRanges = nodeRanges.filter((nodeRange) => {
1676
- if (!type) {
1677
- return true;
1678
- }
1679
- return type.name === nodeRange.node.type.name;
1680
- }).filter((nodeRange) => objectIncludes(nodeRange.node.attrs, attributes, { strict: false }));
1681
- if (empty) {
1682
- return !!matchedNodeRanges.length;
1683
- }
1684
- const range = matchedNodeRanges.reduce((sum, nodeRange) => sum + nodeRange.to - nodeRange.from, 0);
1685
- return range >= selectionRange;
1686
- }
1687
- var lift = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
1688
- const type = getNodeType(typeOrName, state.schema);
1689
- const isActive = isNodeActive(state, type, attributes);
1690
- if (!isActive) {
1691
- return false;
1692
- }
1693
- return lift$1(state, dispatch);
1694
- };
1695
- var liftEmptyBlock = () => ({ state, dispatch }) => {
1696
- return liftEmptyBlock$1(state, dispatch);
1697
- };
1698
- var liftListItem = (typeOrName) => ({ state, dispatch }) => {
1699
- const type = getNodeType(typeOrName, state.schema);
1700
- return liftListItem$1(type)(state, dispatch);
1701
- };
1702
- var newlineInCode = () => ({ state, dispatch }) => {
1703
- return newlineInCode$1(state, dispatch);
1704
- };
1705
- function getSchemaTypeNameByName(name, schema) {
1706
- if (schema.nodes[name]) {
1707
- return "node";
1708
- }
1709
- if (schema.marks[name]) {
1710
- return "mark";
1711
- }
1712
- return null;
1713
- }
1714
- function deleteProps(obj, propOrProps) {
1715
- const props = typeof propOrProps === "string" ? [propOrProps] : propOrProps;
1716
- return Object.keys(obj).reduce((newObj, prop) => {
1717
- if (!props.includes(prop)) {
1718
- newObj[prop] = obj[prop];
1719
- }
1720
- return newObj;
1721
- }, {});
1722
- }
1723
- var resetAttributes = (typeOrName, attributes) => ({ tr, state, dispatch }) => {
1724
- let nodeType = null;
1725
- let markType = null;
1726
- const schemaType = getSchemaTypeNameByName(typeof typeOrName === "string" ? typeOrName : typeOrName.name, state.schema);
1727
- if (!schemaType) {
1728
- return false;
1729
- }
1730
- if (schemaType === "node") {
1731
- nodeType = getNodeType(typeOrName, state.schema);
1732
- }
1733
- if (schemaType === "mark") {
1734
- markType = getMarkType(typeOrName, state.schema);
1735
- }
1736
- if (dispatch) {
1737
- tr.selection.ranges.forEach((range) => {
1738
- state.doc.nodesBetween(range.$from.pos, range.$to.pos, (node, pos) => {
1739
- if (nodeType && nodeType === node.type) {
1740
- tr.setNodeMarkup(pos, void 0, deleteProps(node.attrs, attributes));
1741
- }
1742
- if (markType && node.marks.length) {
1743
- node.marks.forEach((mark) => {
1744
- if (markType === mark.type) {
1745
- tr.addMark(pos, pos + node.nodeSize, markType.create(deleteProps(mark.attrs, attributes)));
1746
- }
1747
- });
1748
- }
1749
- });
1750
- });
1751
- }
1752
- return true;
1753
- };
1754
- var scrollIntoView = () => ({ tr, dispatch }) => {
1755
- if (dispatch) {
1756
- tr.scrollIntoView();
1757
- }
1758
- return true;
1759
- };
1760
- var selectAll = () => ({ tr, dispatch }) => {
1761
- if (dispatch) {
1762
- const selection = new AllSelection(tr.doc);
1763
- tr.setSelection(selection);
1764
- }
1765
- return true;
1766
- };
1767
- var selectNodeBackward = () => ({ state, dispatch }) => {
1768
- return selectNodeBackward$1(state, dispatch);
1769
- };
1770
- var selectNodeForward = () => ({ state, dispatch }) => {
1771
- return selectNodeForward$1(state, dispatch);
1772
- };
1773
- var selectParentNode = () => ({ state, dispatch }) => {
1774
- return selectParentNode$1(state, dispatch);
1775
- };
1776
- var selectTextblockEnd = () => ({ state, dispatch }) => {
1777
- return selectTextblockEnd$1(state, dispatch);
1778
- };
1779
- var selectTextblockStart = () => ({ state, dispatch }) => {
1780
- return selectTextblockStart$1(state, dispatch);
1781
- };
1782
- function createDocument(content, schema, parseOptions = {}, options = {}) {
1783
- return createNodeFromContent(content, schema, {
1784
- slice: false,
1785
- parseOptions,
1786
- errorOnInvalidContent: options.errorOnInvalidContent
1787
- });
1788
- }
1789
- var setContent = (content, emitUpdate = false, parseOptions = {}, options = {}) => ({ editor, tr, dispatch, commands: commands2 }) => {
1790
- var _a, _b;
1791
- const { doc } = tr;
1792
- if (parseOptions.preserveWhitespace !== "full") {
1793
- const document2 = createDocument(content, editor.schema, parseOptions, {
1794
- errorOnInvalidContent: (_a = options.errorOnInvalidContent) !== null && _a !== void 0 ? _a : editor.options.enableContentCheck
1795
- });
1796
- if (dispatch) {
1797
- tr.replaceWith(0, doc.content.size, document2).setMeta("preventUpdate", !emitUpdate);
1798
- }
1799
- return true;
1800
- }
1801
- if (dispatch) {
1802
- tr.setMeta("preventUpdate", !emitUpdate);
1803
- }
1804
- return commands2.insertContentAt({ from: 0, to: doc.content.size }, content, {
1805
- parseOptions,
1806
- errorOnInvalidContent: (_b = options.errorOnInvalidContent) !== null && _b !== void 0 ? _b : editor.options.enableContentCheck
1807
- });
1808
- };
1809
- function getMarkAttributes(state, typeOrName) {
1810
- const type = getMarkType(typeOrName, state.schema);
1811
- const { from, to, empty } = state.selection;
1812
- const marks = [];
1813
- if (empty) {
1814
- if (state.storedMarks) {
1815
- marks.push(...state.storedMarks);
1816
- }
1817
- marks.push(...state.selection.$head.marks());
1818
- } else {
1819
- state.doc.nodesBetween(from, to, (node) => {
1820
- marks.push(...node.marks);
1821
- });
1822
- }
1823
- const mark = marks.find((markItem) => markItem.type.name === type.name);
1824
- if (!mark) {
1825
- return {};
1826
- }
1827
- return { ...mark.attrs };
1828
- }
1829
- function defaultBlockAt(match) {
1830
- for (let i = 0; i < match.edgeCount; i += 1) {
1831
- const { type } = match.edge(i);
1832
- if (type.isTextblock && !type.hasRequiredAttrs()) {
1833
- return type;
1834
- }
1835
- }
1836
- return null;
1837
- }
1838
- function findParentNodeClosestToPos($pos, predicate) {
1839
- for (let i = $pos.depth; i > 0; i -= 1) {
1840
- const node = $pos.node(i);
1841
- if (predicate(node)) {
1842
- return {
1843
- pos: i > 0 ? $pos.before(i) : 0,
1844
- start: $pos.start(i),
1845
- depth: i,
1846
- node
1847
- };
1848
- }
1849
- }
1850
- }
1851
- function findParentNode(predicate) {
1852
- return (selection) => findParentNodeClosestToPos(selection.$from, predicate);
1853
- }
1854
- function getSplittedAttributes(extensionAttributes, typeName, attributes) {
1855
- return Object.fromEntries(Object.entries(attributes).filter(([name]) => {
1856
- const extensionAttribute = extensionAttributes.find((item) => {
1857
- return item.type === typeName && item.name === name;
1858
- });
1859
- if (!extensionAttribute) {
1860
- return false;
1861
- }
1862
- return extensionAttribute.attribute.keepOnSplit;
1863
- }));
1864
- }
1865
- function isMarkActive(state, typeOrName, attributes = {}) {
1866
- const { empty, ranges } = state.selection;
1867
- const type = typeOrName ? getMarkType(typeOrName, state.schema) : null;
1868
- if (empty) {
1869
- return !!(state.storedMarks || state.selection.$from.marks()).filter((mark) => {
1870
- if (!type) {
1871
- return true;
1872
- }
1873
- return type.name === mark.type.name;
1874
- }).find((mark) => objectIncludes(mark.attrs, attributes, { strict: false }));
1875
- }
1876
- let selectionRange = 0;
1877
- const markRanges = [];
1878
- ranges.forEach(({ $from, $to }) => {
1879
- const from = $from.pos;
1880
- const to = $to.pos;
1881
- state.doc.nodesBetween(from, to, (node, pos) => {
1882
- if (!node.isText && !node.marks.length) {
1883
- return;
1884
- }
1885
- const relativeFrom = Math.max(from, pos);
1886
- const relativeTo = Math.min(to, pos + node.nodeSize);
1887
- const range2 = relativeTo - relativeFrom;
1888
- selectionRange += range2;
1889
- markRanges.push(...node.marks.map((mark) => ({
1890
- mark,
1891
- from: relativeFrom,
1892
- to: relativeTo
1893
- })));
1894
- });
1895
- });
1896
- if (selectionRange === 0) {
1897
- return false;
1898
- }
1899
- const matchedRange = markRanges.filter((markRange) => {
1900
- if (!type) {
1901
- return true;
1902
- }
1903
- return type.name === markRange.mark.type.name;
1904
- }).filter((markRange) => objectIncludes(markRange.mark.attrs, attributes, { strict: false })).reduce((sum, markRange) => sum + markRange.to - markRange.from, 0);
1905
- const excludedRange = markRanges.filter((markRange) => {
1906
- if (!type) {
1907
- return true;
1908
- }
1909
- return markRange.mark.type !== type && markRange.mark.type.excludes(type);
1910
- }).reduce((sum, markRange) => sum + markRange.to - markRange.from, 0);
1911
- const range = matchedRange > 0 ? matchedRange + excludedRange : matchedRange;
1912
- return range >= selectionRange;
1913
- }
1914
- function isList(name, extensions) {
1915
- const { nodeExtensions } = splitExtensions(extensions);
1916
- const extension = nodeExtensions.find((item) => item.name === name);
1917
- if (!extension) {
1918
- return false;
1919
- }
1920
- const context = {
1921
- name: extension.name,
1922
- options: extension.options,
1923
- storage: extension.storage
1924
- };
1925
- const group = callOrReturn(getExtensionField(extension, "group", context));
1926
- if (typeof group !== "string") {
1927
- return false;
1928
- }
1929
- return group.split(" ").includes("list");
1930
- }
1931
- function isNodeEmpty(node, { checkChildren = true, ignoreWhitespace = false } = {}) {
1932
- var _a;
1933
- if (ignoreWhitespace) {
1934
- if (node.type.name === "hardBreak") {
1935
- return true;
1936
- }
1937
- if (node.isText) {
1938
- return /^\s*$/m.test((_a = node.text) !== null && _a !== void 0 ? _a : "");
1939
- }
1940
- }
1941
- if (node.isText) {
1942
- return !node.text;
1943
- }
1944
- if (node.isAtom || node.isLeaf) {
1945
- return false;
1946
- }
1947
- if (node.content.childCount === 0) {
1948
- return true;
1949
- }
1950
- if (checkChildren) {
1951
- let isContentEmpty = true;
1952
- node.content.forEach((childNode) => {
1953
- if (isContentEmpty === false) {
1954
- return;
1955
- }
1956
- if (!isNodeEmpty(childNode, { ignoreWhitespace, checkChildren })) {
1957
- isContentEmpty = false;
1958
- }
1959
- });
1960
- return isContentEmpty;
1961
- }
1962
- return false;
1963
- }
1964
- function canSetMark(state, tr, newMarkType) {
1965
- var _a;
1966
- const { selection } = tr;
1967
- let cursor = null;
1968
- if (isTextSelection(selection)) {
1969
- cursor = selection.$cursor;
1970
- }
1971
- if (cursor) {
1972
- const currentMarks = (_a = state.storedMarks) !== null && _a !== void 0 ? _a : cursor.marks();
1973
- return !!newMarkType.isInSet(currentMarks) || !currentMarks.some((mark) => mark.type.excludes(newMarkType));
1974
- }
1975
- const { ranges } = selection;
1976
- return ranges.some(({ $from, $to }) => {
1977
- let someNodeSupportsMark = $from.depth === 0 ? state.doc.inlineContent && state.doc.type.allowsMarkType(newMarkType) : false;
1978
- state.doc.nodesBetween($from.pos, $to.pos, (node, _pos, parent) => {
1979
- if (someNodeSupportsMark) {
1980
- return false;
1981
- }
1982
- if (node.isInline) {
1983
- const parentAllowsMarkType = !parent || parent.type.allowsMarkType(newMarkType);
1984
- const currentMarksAllowMarkType = !!newMarkType.isInSet(node.marks) || !node.marks.some((otherMark) => otherMark.type.excludes(newMarkType));
1985
- someNodeSupportsMark = parentAllowsMarkType && currentMarksAllowMarkType;
1986
- }
1987
- return !someNodeSupportsMark;
1988
- });
1989
- return someNodeSupportsMark;
1990
- });
1991
- }
1992
- var setMark = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => {
1993
- const { selection } = tr;
1994
- const { empty, ranges } = selection;
1995
- const type = getMarkType(typeOrName, state.schema);
1996
- if (dispatch) {
1997
- if (empty) {
1998
- const oldAttributes = getMarkAttributes(state, type);
1999
- tr.addStoredMark(type.create({
2000
- ...oldAttributes,
2001
- ...attributes
2002
- }));
2003
- } else {
2004
- ranges.forEach((range) => {
2005
- const from = range.$from.pos;
2006
- const to = range.$to.pos;
2007
- state.doc.nodesBetween(from, to, (node, pos) => {
2008
- const trimmedFrom = Math.max(pos, from);
2009
- const trimmedTo = Math.min(pos + node.nodeSize, to);
2010
- const someHasMark = node.marks.find((mark) => mark.type === type);
2011
- if (someHasMark) {
2012
- node.marks.forEach((mark) => {
2013
- if (type === mark.type) {
2014
- tr.addMark(trimmedFrom, trimmedTo, type.create({
2015
- ...mark.attrs,
2016
- ...attributes
2017
- }));
2018
- }
2019
- });
2020
- } else {
2021
- tr.addMark(trimmedFrom, trimmedTo, type.create(attributes));
2022
- }
2023
- });
2024
- });
2025
- }
2026
- }
2027
- return canSetMark(state, tr, type);
2028
- };
2029
- var setMeta = (key, value) => ({ tr }) => {
2030
- tr.setMeta(key, value);
2031
- return true;
2032
- };
2033
- var setNode = (typeOrName, attributes = {}) => ({ state, dispatch, chain }) => {
2034
- const type = getNodeType(typeOrName, state.schema);
2035
- let attributesToCopy;
2036
- if (state.selection.$anchor.sameParent(state.selection.$head)) {
2037
- attributesToCopy = state.selection.$anchor.parent.attrs;
2038
- }
2039
- if (!type.isTextblock) {
2040
- console.warn('[tiptap warn]: Currently "setNode()" only supports text block nodes.');
2041
- return false;
2042
- }
2043
- return chain().command(({ commands: commands2 }) => {
2044
- const canSetBlock = setBlockType(type, { ...attributesToCopy, ...attributes })(state);
2045
- if (canSetBlock) {
2046
- return true;
2047
- }
2048
- return commands2.clearNodes();
2049
- }).command(({ state: updatedState }) => {
2050
- return setBlockType(type, { ...attributesToCopy, ...attributes })(updatedState, dispatch);
2051
- }).run();
2052
- };
2053
- var setNodeSelection = (position) => ({ tr, dispatch }) => {
2054
- if (dispatch) {
2055
- const { doc } = tr;
2056
- const from = minMax(position, 0, doc.content.size);
2057
- const selection = NodeSelection.create(doc, from);
2058
- tr.setSelection(selection);
2059
- }
2060
- return true;
2061
- };
2062
- var setTextSelection = (position) => ({ tr, dispatch }) => {
2063
- if (dispatch) {
2064
- const { doc } = tr;
2065
- const { from, to } = typeof position === "number" ? { from: position, to: position } : position;
2066
- const minPos = TextSelection.atStart(doc).from;
2067
- const maxPos = TextSelection.atEnd(doc).to;
2068
- const resolvedFrom = minMax(from, minPos, maxPos);
2069
- const resolvedEnd = minMax(to, minPos, maxPos);
2070
- const selection = TextSelection.create(doc, resolvedFrom, resolvedEnd);
2071
- tr.setSelection(selection);
2072
- }
2073
- return true;
2074
- };
2075
- var sinkListItem = (typeOrName) => ({ state, dispatch }) => {
2076
- const type = getNodeType(typeOrName, state.schema);
2077
- return sinkListItem$1(type)(state, dispatch);
2078
- };
2079
- function ensureMarks(state, splittableMarks) {
2080
- const marks = state.storedMarks || state.selection.$to.parentOffset && state.selection.$from.marks();
2081
- if (marks) {
2082
- const filteredMarks = marks.filter((mark) => splittableMarks === null || splittableMarks === void 0 ? void 0 : splittableMarks.includes(mark.type.name));
2083
- state.tr.ensureMarks(filteredMarks);
2084
- }
2085
- }
2086
- var splitBlock = ({ keepMarks = true } = {}) => ({ tr, state, dispatch, editor }) => {
2087
- const { selection, doc } = tr;
2088
- const { $from, $to } = selection;
2089
- const extensionAttributes = editor.extensionManager.attributes;
2090
- const newAttributes = getSplittedAttributes(extensionAttributes, $from.node().type.name, $from.node().attrs);
2091
- if (selection instanceof NodeSelection && selection.node.isBlock) {
2092
- if (!$from.parentOffset || !canSplit(doc, $from.pos)) {
2093
- return false;
2094
- }
2095
- if (dispatch) {
2096
- if (keepMarks) {
2097
- ensureMarks(state, editor.extensionManager.splittableMarks);
2098
- }
2099
- tr.split($from.pos).scrollIntoView();
2100
- }
2101
- return true;
2102
- }
2103
- if (!$from.parent.isBlock) {
2104
- return false;
2105
- }
2106
- const atEnd = $to.parentOffset === $to.parent.content.size;
2107
- const deflt = $from.depth === 0 ? void 0 : defaultBlockAt($from.node(-1).contentMatchAt($from.indexAfter(-1)));
2108
- let types = atEnd && deflt ? [
2109
- {
2110
- type: deflt,
2111
- attrs: newAttributes
2112
- }
2113
- ] : void 0;
2114
- let can = canSplit(tr.doc, tr.mapping.map($from.pos), 1, types);
2115
- if (!types && !can && canSplit(tr.doc, tr.mapping.map($from.pos), 1, deflt ? [{ type: deflt }] : void 0)) {
2116
- can = true;
2117
- types = deflt ? [
2118
- {
2119
- type: deflt,
2120
- attrs: newAttributes
2121
- }
2122
- ] : void 0;
2123
- }
2124
- if (dispatch) {
2125
- if (can) {
2126
- if (selection instanceof TextSelection) {
2127
- tr.deleteSelection();
2128
- }
2129
- tr.split(tr.mapping.map($from.pos), 1, types);
2130
- if (deflt && !atEnd && !$from.parentOffset && $from.parent.type !== deflt) {
2131
- const first2 = tr.mapping.map($from.before());
2132
- const $first = tr.doc.resolve(first2);
2133
- if ($from.node(-1).canReplaceWith($first.index(), $first.index() + 1, deflt)) {
2134
- tr.setNodeMarkup(tr.mapping.map($from.before()), deflt);
2135
- }
2136
- }
2137
- }
2138
- if (keepMarks) {
2139
- ensureMarks(state, editor.extensionManager.splittableMarks);
2140
- }
2141
- tr.scrollIntoView();
2142
- }
2143
- return can;
2144
- };
2145
- var splitListItem = (typeOrName, overrideAttrs = {}) => ({ tr, state, dispatch, editor }) => {
2146
- var _a;
2147
- const type = getNodeType(typeOrName, state.schema);
2148
- const { $from, $to } = state.selection;
2149
- const node = state.selection.node;
2150
- if (node && node.isBlock || $from.depth < 2 || !$from.sameParent($to)) {
2151
- return false;
2152
- }
2153
- const grandParent = $from.node(-1);
2154
- if (grandParent.type !== type) {
2155
- return false;
2156
- }
2157
- const extensionAttributes = editor.extensionManager.attributes;
2158
- if ($from.parent.content.size === 0 && $from.node(-1).childCount === $from.indexAfter(-1)) {
2159
- if ($from.depth === 2 || $from.node(-3).type !== type || $from.index(-2) !== $from.node(-2).childCount - 1) {
2160
- return false;
2161
- }
2162
- if (dispatch) {
2163
- let wrap = Fragment2.empty;
2164
- const depthBefore = $from.index(-1) ? 1 : $from.index(-2) ? 2 : 3;
2165
- for (let d = $from.depth - depthBefore; d >= $from.depth - 3; d -= 1) {
2166
- wrap = Fragment2.from($from.node(d).copy(wrap));
2167
- }
2168
- const depthAfter = $from.indexAfter(-1) < $from.node(-2).childCount ? 1 : $from.indexAfter(-2) < $from.node(-3).childCount ? 2 : 3;
2169
- const newNextTypeAttributes2 = {
2170
- ...getSplittedAttributes(extensionAttributes, $from.node().type.name, $from.node().attrs),
2171
- ...overrideAttrs
2172
- };
2173
- const nextType2 = ((_a = type.contentMatch.defaultType) === null || _a === void 0 ? void 0 : _a.createAndFill(newNextTypeAttributes2)) || void 0;
2174
- wrap = wrap.append(Fragment2.from(type.createAndFill(null, nextType2) || void 0));
2175
- const start = $from.before($from.depth - (depthBefore - 1));
2176
- tr.replace(start, $from.after(-depthAfter), new Slice(wrap, 4 - depthBefore, 0));
2177
- let sel = -1;
2178
- tr.doc.nodesBetween(start, tr.doc.content.size, (n, pos) => {
2179
- if (sel > -1) {
2180
- return false;
2181
- }
2182
- if (n.isTextblock && n.content.size === 0) {
2183
- sel = pos + 1;
2184
- }
2185
- });
2186
- if (sel > -1) {
2187
- tr.setSelection(TextSelection.near(tr.doc.resolve(sel)));
2188
- }
2189
- tr.scrollIntoView();
2190
- }
2191
- return true;
2192
- }
2193
- const nextType = $to.pos === $from.end() ? grandParent.contentMatchAt(0).defaultType : null;
2194
- const newTypeAttributes = {
2195
- ...getSplittedAttributes(extensionAttributes, grandParent.type.name, grandParent.attrs),
2196
- ...overrideAttrs
2197
- };
2198
- const newNextTypeAttributes = {
2199
- ...getSplittedAttributes(extensionAttributes, $from.node().type.name, $from.node().attrs),
2200
- ...overrideAttrs
2201
- };
2202
- tr.delete($from.pos, $to.pos);
2203
- const types = nextType ? [
2204
- { type, attrs: newTypeAttributes },
2205
- { type: nextType, attrs: newNextTypeAttributes }
2206
- ] : [{ type, attrs: newTypeAttributes }];
2207
- if (!canSplit(tr.doc, $from.pos, 2)) {
2208
- return false;
2209
- }
2210
- if (dispatch) {
2211
- const { selection, storedMarks } = state;
2212
- const { splittableMarks } = editor.extensionManager;
2213
- const marks = storedMarks || selection.$to.parentOffset && selection.$from.marks();
2214
- tr.split($from.pos, 2, types).scrollIntoView();
2215
- if (!marks || !dispatch) {
2216
- return true;
2217
- }
2218
- const filteredMarks = marks.filter((mark) => splittableMarks.includes(mark.type.name));
2219
- tr.ensureMarks(filteredMarks);
2220
- }
2221
- return true;
2222
- };
2223
- var joinListBackwards = (tr, listType) => {
2224
- const list = findParentNode((node) => node.type === listType)(tr.selection);
2225
- if (!list) {
2226
- return true;
2227
- }
2228
- const before = tr.doc.resolve(Math.max(0, list.pos - 1)).before(list.depth);
2229
- if (before === void 0) {
2230
- return true;
2231
- }
2232
- const nodeBefore = tr.doc.nodeAt(before);
2233
- const canJoinBackwards = list.node.type === (nodeBefore === null || nodeBefore === void 0 ? void 0 : nodeBefore.type) && canJoin(tr.doc, list.pos);
2234
- if (!canJoinBackwards) {
2235
- return true;
2236
- }
2237
- tr.join(list.pos);
2238
- return true;
2239
- };
2240
- var joinListForwards = (tr, listType) => {
2241
- const list = findParentNode((node) => node.type === listType)(tr.selection);
2242
- if (!list) {
2243
- return true;
2244
- }
2245
- const after = tr.doc.resolve(list.start).after(list.depth);
2246
- if (after === void 0) {
2247
- return true;
2248
- }
2249
- const nodeAfter = tr.doc.nodeAt(after);
2250
- const canJoinForwards = list.node.type === (nodeAfter === null || nodeAfter === void 0 ? void 0 : nodeAfter.type) && canJoin(tr.doc, after);
2251
- if (!canJoinForwards) {
2252
- return true;
2253
- }
2254
- tr.join(after);
2255
- return true;
2256
- };
2257
- var toggleList = (listTypeOrName, itemTypeOrName, keepMarks, attributes = {}) => ({ editor, tr, state, dispatch, chain, commands: commands2, can }) => {
2258
- const { extensions, splittableMarks } = editor.extensionManager;
2259
- const listType = getNodeType(listTypeOrName, state.schema);
2260
- const itemType = getNodeType(itemTypeOrName, state.schema);
2261
- const { selection, storedMarks } = state;
2262
- const { $from, $to } = selection;
2263
- const range = $from.blockRange($to);
2264
- const marks = storedMarks || selection.$to.parentOffset && selection.$from.marks();
2265
- if (!range) {
2266
- return false;
2267
- }
2268
- const parentList = findParentNode((node) => isList(node.type.name, extensions))(selection);
2269
- if (range.depth >= 1 && parentList && range.depth - parentList.depth <= 1) {
2270
- if (parentList.node.type === listType) {
2271
- return commands2.liftListItem(itemType);
2272
- }
2273
- if (isList(parentList.node.type.name, extensions) && listType.validContent(parentList.node.content) && dispatch) {
2274
- return chain().command(() => {
2275
- tr.setNodeMarkup(parentList.pos, listType);
2276
- return true;
2277
- }).command(() => joinListBackwards(tr, listType)).command(() => joinListForwards(tr, listType)).run();
2278
- }
2279
- }
2280
- if (!keepMarks || !marks || !dispatch) {
2281
- return chain().command(() => {
2282
- const canWrapInList = can().wrapInList(listType, attributes);
2283
- if (canWrapInList) {
2284
- return true;
2285
- }
2286
- return commands2.clearNodes();
2287
- }).wrapInList(listType, attributes).command(() => joinListBackwards(tr, listType)).command(() => joinListForwards(tr, listType)).run();
2288
- }
2289
- return chain().command(() => {
2290
- const canWrapInList = can().wrapInList(listType, attributes);
2291
- const filteredMarks = marks.filter((mark) => splittableMarks.includes(mark.type.name));
2292
- tr.ensureMarks(filteredMarks);
2293
- if (canWrapInList) {
2294
- return true;
2295
- }
2296
- return commands2.clearNodes();
2297
- }).wrapInList(listType, attributes).command(() => joinListBackwards(tr, listType)).command(() => joinListForwards(tr, listType)).run();
2298
- };
2299
- var toggleMark = (typeOrName, attributes = {}, options = {}) => ({ state, commands: commands2 }) => {
2300
- const { extendEmptyMarkRange = false } = options;
2301
- const type = getMarkType(typeOrName, state.schema);
2302
- const isActive = isMarkActive(state, type, attributes);
2303
- if (isActive) {
2304
- return commands2.unsetMark(type, { extendEmptyMarkRange });
2305
- }
2306
- return commands2.setMark(type, attributes);
2307
- };
2308
- var toggleNode = (typeOrName, toggleTypeOrName, attributes = {}) => ({ state, commands: commands2 }) => {
2309
- const type = getNodeType(typeOrName, state.schema);
2310
- const toggleType = getNodeType(toggleTypeOrName, state.schema);
2311
- const isActive = isNodeActive(state, type, attributes);
2312
- let attributesToCopy;
2313
- if (state.selection.$anchor.sameParent(state.selection.$head)) {
2314
- attributesToCopy = state.selection.$anchor.parent.attrs;
2315
- }
2316
- if (isActive) {
2317
- return commands2.setNode(toggleType, attributesToCopy);
2318
- }
2319
- return commands2.setNode(type, { ...attributesToCopy, ...attributes });
2320
- };
2321
- var toggleWrap = (typeOrName, attributes = {}) => ({ state, commands: commands2 }) => {
2322
- const type = getNodeType(typeOrName, state.schema);
2323
- const isActive = isNodeActive(state, type, attributes);
2324
- if (isActive) {
2325
- return commands2.lift(type);
2326
- }
2327
- return commands2.wrapIn(type, attributes);
2328
- };
2329
- var undoInputRule = () => ({ state, dispatch }) => {
2330
- const plugins = state.plugins;
2331
- for (let i = 0; i < plugins.length; i += 1) {
2332
- const plugin = plugins[i];
2333
- let undoable;
2334
- if (plugin.spec.isInputRules && (undoable = plugin.getState(state))) {
2335
- if (dispatch) {
2336
- const tr = state.tr;
2337
- const toUndo = undoable.transform;
2338
- for (let j = toUndo.steps.length - 1; j >= 0; j -= 1) {
2339
- tr.step(toUndo.steps[j].invert(toUndo.docs[j]));
2340
- }
2341
- if (undoable.text) {
2342
- const marks = tr.doc.resolve(undoable.from).marks();
2343
- tr.replaceWith(undoable.from, undoable.to, state.schema.text(undoable.text, marks));
2344
- } else {
2345
- tr.delete(undoable.from, undoable.to);
2346
- }
2347
- }
2348
- return true;
2349
- }
2350
- }
2351
- return false;
2352
- };
2353
- var unsetAllMarks = () => ({ tr, dispatch }) => {
2354
- const { selection } = tr;
2355
- const { empty, ranges } = selection;
2356
- if (empty) {
2357
- return true;
2358
- }
2359
- if (dispatch) {
2360
- ranges.forEach((range) => {
2361
- tr.removeMark(range.$from.pos, range.$to.pos);
2362
- });
2363
- }
2364
- return true;
2365
- };
2366
- var unsetMark = (typeOrName, options = {}) => ({ tr, state, dispatch }) => {
2367
- var _a;
2368
- const { extendEmptyMarkRange = false } = options;
2369
- const { selection } = tr;
2370
- const type = getMarkType(typeOrName, state.schema);
2371
- const { $from, empty, ranges } = selection;
2372
- if (!dispatch) {
2373
- return true;
2374
- }
2375
- if (empty && extendEmptyMarkRange) {
2376
- let { from, to } = selection;
2377
- const attrs = (_a = $from.marks().find((mark) => mark.type === type)) === null || _a === void 0 ? void 0 : _a.attrs;
2378
- const range = getMarkRange($from, type, attrs);
2379
- if (range) {
2380
- from = range.from;
2381
- to = range.to;
2382
- }
2383
- tr.removeMark(from, to, type);
2384
- } else {
2385
- ranges.forEach((range) => {
2386
- tr.removeMark(range.$from.pos, range.$to.pos, type);
2387
- });
2388
- }
2389
- tr.removeStoredMark(type);
2390
- return true;
2391
- };
2392
- var updateAttributes = (typeOrName, attributes = {}) => ({ tr, state, dispatch }) => {
2393
- let nodeType = null;
2394
- let markType = null;
2395
- const schemaType = getSchemaTypeNameByName(typeof typeOrName === "string" ? typeOrName : typeOrName.name, state.schema);
2396
- if (!schemaType) {
2397
- return false;
2398
- }
2399
- if (schemaType === "node") {
2400
- nodeType = getNodeType(typeOrName, state.schema);
2401
- }
2402
- if (schemaType === "mark") {
2403
- markType = getMarkType(typeOrName, state.schema);
2404
- }
2405
- if (dispatch) {
2406
- tr.selection.ranges.forEach((range) => {
2407
- const from = range.$from.pos;
2408
- const to = range.$to.pos;
2409
- let lastPos;
2410
- let lastNode;
2411
- let trimmedFrom;
2412
- let trimmedTo;
2413
- if (tr.selection.empty) {
2414
- state.doc.nodesBetween(from, to, (node, pos) => {
2415
- if (nodeType && nodeType === node.type) {
2416
- trimmedFrom = Math.max(pos, from);
2417
- trimmedTo = Math.min(pos + node.nodeSize, to);
2418
- lastPos = pos;
2419
- lastNode = node;
2420
- }
2421
- });
2422
- } else {
2423
- state.doc.nodesBetween(from, to, (node, pos) => {
2424
- if (pos < from && nodeType && nodeType === node.type) {
2425
- trimmedFrom = Math.max(pos, from);
2426
- trimmedTo = Math.min(pos + node.nodeSize, to);
2427
- lastPos = pos;
2428
- lastNode = node;
2429
- }
2430
- if (pos >= from && pos <= to) {
2431
- if (nodeType && nodeType === node.type) {
2432
- tr.setNodeMarkup(pos, void 0, {
2433
- ...node.attrs,
2434
- ...attributes
2435
- });
2436
- }
2437
- if (markType && node.marks.length) {
2438
- node.marks.forEach((mark) => {
2439
- if (markType === mark.type) {
2440
- const trimmedFrom2 = Math.max(pos, from);
2441
- const trimmedTo2 = Math.min(pos + node.nodeSize, to);
2442
- tr.addMark(trimmedFrom2, trimmedTo2, markType.create({
2443
- ...mark.attrs,
2444
- ...attributes
2445
- }));
2446
- }
2447
- });
2448
- }
2449
- }
2450
- });
2451
- }
2452
- if (lastNode) {
2453
- if (lastPos !== void 0) {
2454
- tr.setNodeMarkup(lastPos, void 0, {
2455
- ...lastNode.attrs,
2456
- ...attributes
2457
- });
2458
- }
2459
- if (markType && lastNode.marks.length) {
2460
- lastNode.marks.forEach((mark) => {
2461
- if (markType === mark.type) {
2462
- tr.addMark(trimmedFrom, trimmedTo, markType.create({
2463
- ...mark.attrs,
2464
- ...attributes
2465
- }));
2466
- }
2467
- });
2468
- }
2469
- }
2470
- });
2471
- }
2472
- return true;
2473
- };
2474
- var wrapIn = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
2475
- const type = getNodeType(typeOrName, state.schema);
2476
- return wrapIn$1(type, attributes)(state, dispatch);
2477
- };
2478
- var wrapInList = (typeOrName, attributes = {}) => ({ state, dispatch }) => {
2479
- const type = getNodeType(typeOrName, state.schema);
2480
- return wrapInList$1(type, attributes)(state, dispatch);
2481
- };
2482
- var commands = /* @__PURE__ */ Object.freeze({
2483
- __proto__: null,
2484
- blur,
2485
- clearContent,
2486
- clearNodes,
2487
- command,
2488
- createParagraphNear,
2489
- cut,
2490
- deleteCurrentNode,
2491
- deleteNode,
2492
- deleteRange,
2493
- deleteSelection,
2494
- enter,
2495
- exitCode,
2496
- extendMarkRange,
2497
- first,
2498
- focus,
2499
- forEach,
2500
- insertContent,
2501
- insertContentAt,
2502
- joinBackward,
2503
- joinDown,
2504
- joinForward,
2505
- joinItemBackward,
2506
- joinItemForward,
2507
- joinTextblockBackward,
2508
- joinTextblockForward,
2509
- joinUp,
2510
- keyboardShortcut,
2511
- lift,
2512
- liftEmptyBlock,
2513
- liftListItem,
2514
- newlineInCode,
2515
- resetAttributes,
2516
- scrollIntoView,
2517
- selectAll,
2518
- selectNodeBackward,
2519
- selectNodeForward,
2520
- selectParentNode,
2521
- selectTextblockEnd,
2522
- selectTextblockStart,
2523
- setContent,
2524
- setMark,
2525
- setMeta,
2526
- setNode,
2527
- setNodeSelection,
2528
- setTextSelection,
2529
- sinkListItem,
2530
- splitBlock,
2531
- splitListItem,
2532
- toggleList,
2533
- toggleMark,
2534
- toggleNode,
2535
- toggleWrap,
2536
- undoInputRule,
2537
- unsetAllMarks,
2538
- unsetMark,
2539
- updateAttributes,
2540
- wrapIn,
2541
- wrapInList
2542
- });
2543
- var Commands = Extension.create({
2544
- name: "commands",
2545
- addCommands() {
2546
- return {
2547
- ...commands
2548
- };
2549
- }
2550
- });
2551
- var Drop = Extension.create({
2552
- name: "drop",
2553
- addProseMirrorPlugins() {
2554
- return [
2555
- new Plugin({
2556
- key: new PluginKey("tiptapDrop"),
2557
- props: {
2558
- handleDrop: (_, e, slice, moved) => {
2559
- this.editor.emit("drop", {
2560
- editor: this.editor,
2561
- event: e,
2562
- slice,
2563
- moved
2564
- });
2565
- }
2566
- }
2567
- })
2568
- ];
2569
- }
2570
- });
2571
- var Editable = Extension.create({
2572
- name: "editable",
2573
- addProseMirrorPlugins() {
2574
- return [
2575
- new Plugin({
2576
- key: new PluginKey("editable"),
2577
- props: {
2578
- editable: () => this.editor.options.editable
2579
- }
2580
- })
2581
- ];
2582
- }
2583
- });
2584
- var focusEventsPluginKey = new PluginKey("focusEvents");
2585
- var FocusEvents = Extension.create({
2586
- name: "focusEvents",
2587
- addProseMirrorPlugins() {
2588
- const { editor } = this;
2589
- return [
2590
- new Plugin({
2591
- key: focusEventsPluginKey,
2592
- props: {
2593
- handleDOMEvents: {
2594
- focus: (view, event) => {
2595
- editor.isFocused = true;
2596
- const transaction = editor.state.tr.setMeta("focus", { event }).setMeta("addToHistory", false);
2597
- view.dispatch(transaction);
2598
- return false;
2599
- },
2600
- blur: (view, event) => {
2601
- editor.isFocused = false;
2602
- const transaction = editor.state.tr.setMeta("blur", { event }).setMeta("addToHistory", false);
2603
- view.dispatch(transaction);
2604
- return false;
2605
- }
2606
- }
2607
- }
2608
- })
2609
- ];
2610
- }
2611
- });
2612
- var Keymap = Extension.create({
2613
- name: "keymap",
2614
- addKeyboardShortcuts() {
2615
- const handleBackspace = () => this.editor.commands.first(({ commands: commands2 }) => [
2616
- () => commands2.undoInputRule(),
2617
- // maybe convert first text block node to default node
2618
- () => commands2.command(({ tr }) => {
2619
- const { selection, doc } = tr;
2620
- const { empty, $anchor } = selection;
2621
- const { pos, parent } = $anchor;
2622
- const $parentPos = $anchor.parent.isTextblock && pos > 0 ? tr.doc.resolve(pos - 1) : $anchor;
2623
- const parentIsIsolating = $parentPos.parent.type.spec.isolating;
2624
- const parentPos = $anchor.pos - $anchor.parentOffset;
2625
- const isAtStart = parentIsIsolating && $parentPos.parent.childCount === 1 ? parentPos === $anchor.pos : Selection.atStart(doc).from === pos;
2626
- if (!empty || !parent.type.isTextblock || parent.textContent.length || !isAtStart || isAtStart && $anchor.parent.type.name === "paragraph") {
2627
- return false;
2628
- }
2629
- return commands2.clearNodes();
2630
- }),
2631
- () => commands2.deleteSelection(),
2632
- () => commands2.joinBackward(),
2633
- () => commands2.selectNodeBackward()
2634
- ]);
2635
- const handleDelete = () => this.editor.commands.first(({ commands: commands2 }) => [
2636
- () => commands2.deleteSelection(),
2637
- () => commands2.deleteCurrentNode(),
2638
- () => commands2.joinForward(),
2639
- () => commands2.selectNodeForward()
2640
- ]);
2641
- const handleEnter = () => this.editor.commands.first(({ commands: commands2 }) => [
2642
- () => commands2.newlineInCode(),
2643
- () => commands2.createParagraphNear(),
2644
- () => commands2.liftEmptyBlock(),
2645
- () => commands2.splitBlock()
2646
- ]);
2647
- const baseKeymap = {
2648
- Enter: handleEnter,
2649
- "Mod-Enter": () => this.editor.commands.exitCode(),
2650
- Backspace: handleBackspace,
2651
- "Mod-Backspace": handleBackspace,
2652
- "Shift-Backspace": handleBackspace,
2653
- Delete: handleDelete,
2654
- "Mod-Delete": handleDelete,
2655
- "Mod-a": () => this.editor.commands.selectAll()
2656
- };
2657
- const pcKeymap = {
2658
- ...baseKeymap
2659
- };
2660
- const macKeymap = {
2661
- ...baseKeymap,
2662
- "Ctrl-h": handleBackspace,
2663
- "Alt-Backspace": handleBackspace,
2664
- "Ctrl-d": handleDelete,
2665
- "Ctrl-Alt-Backspace": handleDelete,
2666
- "Alt-Delete": handleDelete,
2667
- "Alt-d": handleDelete,
2668
- "Ctrl-a": () => this.editor.commands.selectTextblockStart(),
2669
- "Ctrl-e": () => this.editor.commands.selectTextblockEnd()
2670
- };
2671
- if (isiOS() || isMacOS()) {
2672
- return macKeymap;
2673
- }
2674
- return pcKeymap;
2675
- },
2676
- addProseMirrorPlugins() {
2677
- return [
2678
- // With this plugin we check if the whole document was selected and deleted.
2679
- // In this case we will additionally call `clearNodes()` to convert e.g. a heading
2680
- // to a paragraph if necessary.
2681
- // This is an alternative to ProseMirror's `AllSelection`, which doesn’t work well
2682
- // with many other commands.
2683
- new Plugin({
2684
- key: new PluginKey("clearDocument"),
2685
- appendTransaction: (transactions, oldState, newState) => {
2686
- if (transactions.some((tr2) => tr2.getMeta("composition"))) {
2687
- return;
2688
- }
2689
- const docChanges = transactions.some((transaction) => transaction.docChanged) && !oldState.doc.eq(newState.doc);
2690
- const ignoreTr = transactions.some((transaction) => transaction.getMeta("preventClearDocument"));
2691
- if (!docChanges || ignoreTr) {
2692
- return;
2693
- }
2694
- const { empty, from, to } = oldState.selection;
2695
- const allFrom = Selection.atStart(oldState.doc).from;
2696
- const allEnd = Selection.atEnd(oldState.doc).to;
2697
- const allWasSelected = from === allFrom && to === allEnd;
2698
- if (empty || !allWasSelected) {
2699
- return;
2700
- }
2701
- const isEmpty = isNodeEmpty(newState.doc);
2702
- if (!isEmpty) {
2703
- return;
2704
- }
2705
- const tr = newState.tr;
2706
- const state = createChainableState({
2707
- state: newState,
2708
- transaction: tr
2709
- });
2710
- const { commands: commands2 } = new CommandManager({
2711
- editor: this.editor,
2712
- state
2713
- });
2714
- commands2.clearNodes();
2715
- if (!tr.steps.length) {
2716
- return;
2717
- }
2718
- return tr;
2719
- }
2720
- })
2721
- ];
2722
- }
2723
- });
2724
- var Paste = Extension.create({
2725
- name: "paste",
2726
- addProseMirrorPlugins() {
2727
- return [
2728
- new Plugin({
2729
- key: new PluginKey("tiptapPaste"),
2730
- props: {
2731
- handlePaste: (_view, e, slice) => {
2732
- this.editor.emit("paste", {
2733
- editor: this.editor,
2734
- event: e,
2735
- slice
2736
- });
2737
- }
2738
- }
2739
- })
2740
- ];
2741
- }
2742
- });
2743
- var Tabindex = Extension.create({
2744
- name: "tabindex",
2745
- addProseMirrorPlugins() {
2746
- return [
2747
- new Plugin({
2748
- key: new PluginKey("tabindex"),
2749
- props: {
2750
- attributes: () => this.editor.isEditable ? { tabindex: "0" } : {}
2751
- }
2752
- })
2753
- ];
2754
- }
2755
- });
2756
- function textblockTypeInputRule(config) {
2757
- return new InputRule({
2758
- find: config.find,
2759
- handler: ({ state, range, match }) => {
2760
- const $start = state.doc.resolve(range.from);
2761
- const attributes = callOrReturn(config.getAttributes, void 0, match) || {};
2762
- if (!$start.node(-1).canReplaceWith($start.index(-1), $start.indexAfter(-1), config.type)) {
2763
- return null;
2764
- }
2765
- state.tr.delete(range.from, range.to).setBlockType(range.from, range.from, config.type, attributes);
2766
- }
2767
- });
2768
- }
2769
- var Node = class _Node {
2770
- constructor(config = {}) {
2771
- this.type = "node";
2772
- this.name = "node";
2773
- this.parent = null;
2774
- this.child = null;
2775
- this.config = {
2776
- name: this.name,
2777
- defaultOptions: {}
2778
- };
2779
- this.config = {
2780
- ...this.config,
2781
- ...config
2782
- };
2783
- this.name = this.config.name;
2784
- if (config.defaultOptions && Object.keys(config.defaultOptions).length > 0) {
2785
- console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${this.name}".`);
2786
- }
2787
- this.options = this.config.defaultOptions;
2788
- if (this.config.addOptions) {
2789
- this.options = callOrReturn(getExtensionField(this, "addOptions", {
2790
- name: this.name
2791
- }));
2792
- }
2793
- this.storage = callOrReturn(getExtensionField(this, "addStorage", {
2794
- name: this.name,
2795
- options: this.options
2796
- })) || {};
2797
- }
2798
- static create(config = {}) {
2799
- return new _Node(config);
2800
- }
2801
- configure(options = {}) {
2802
- const extension = this.extend({
2803
- ...this.config,
2804
- addOptions: () => {
2805
- return mergeDeep(this.options, options);
2806
- }
2807
- });
2808
- extension.name = this.name;
2809
- extension.parent = this.parent;
2810
- return extension;
2811
- }
2812
- extend(extendedConfig = {}) {
2813
- const extension = new _Node(extendedConfig);
2814
- extension.parent = this;
2815
- this.child = extension;
2816
- extension.name = extendedConfig.name ? extendedConfig.name : extension.parent.name;
2817
- if (extendedConfig.defaultOptions && Object.keys(extendedConfig.defaultOptions).length > 0) {
2818
- console.warn(`[tiptap warn]: BREAKING CHANGE: "defaultOptions" is deprecated. Please use "addOptions" instead. Found in extension: "${extension.name}".`);
2819
- }
2820
- extension.options = callOrReturn(getExtensionField(extension, "addOptions", {
2821
- name: extension.name
2822
- }));
2823
- extension.storage = callOrReturn(getExtensionField(extension, "addStorage", {
2824
- name: extension.name,
2825
- options: extension.options
2826
- }));
2827
- return extension;
2828
- }
2829
- };
2830
-
2831
- // ../../node_modules/@tiptap/extension-heading/dist/index.js
2832
- var Heading = Node.create({
2833
- name: "heading",
2834
- addOptions() {
2835
- return {
2836
- levels: [1, 2, 3, 4, 5, 6],
2837
- HTMLAttributes: {}
2838
- };
2839
- },
2840
- content: "inline*",
2841
- group: "block",
2842
- defining: true,
2843
- addAttributes() {
2844
- return {
2845
- level: {
2846
- default: 1,
2847
- rendered: false
2848
- }
2849
- };
2850
- },
2851
- parseHTML() {
2852
- return this.options.levels.map((level) => ({
2853
- tag: `h${level}`,
2854
- attrs: { level }
2855
- }));
2856
- },
2857
- renderHTML({ node, HTMLAttributes }) {
2858
- const hasLevel = this.options.levels.includes(node.attrs.level);
2859
- const level = hasLevel ? node.attrs.level : this.options.levels[0];
2860
- return [`h${level}`, mergeAttributes(this.options.HTMLAttributes, HTMLAttributes), 0];
2861
- },
2862
- addCommands() {
2863
- return {
2864
- setHeading: (attributes) => ({ commands: commands2 }) => {
2865
- if (!this.options.levels.includes(attributes.level)) {
2866
- return false;
2867
- }
2868
- return commands2.setNode(this.name, attributes);
2869
- },
2870
- toggleHeading: (attributes) => ({ commands: commands2 }) => {
2871
- if (!this.options.levels.includes(attributes.level)) {
2872
- return false;
2873
- }
2874
- return commands2.toggleNode(this.name, "paragraph", attributes);
2875
- }
2876
- };
2877
- },
2878
- addKeyboardShortcuts() {
2879
- return this.options.levels.reduce((items, level) => ({
2880
- ...items,
2881
- ...{
2882
- [`Mod-Alt-${level}`]: () => this.editor.commands.toggleHeading({ level })
2883
- }
2884
- }), {});
2885
- },
2886
- addInputRules() {
2887
- return this.options.levels.map((level) => {
2888
- return textblockTypeInputRule({
2889
- find: new RegExp(`^(#{${Math.min(...this.options.levels)},${level}})\\s$`),
2890
- type: this.type,
2891
- getAttributes: {
2892
- level
2893
- }
2894
- });
2895
- });
2896
- }
2897
- });
2898
-
2899
- // src/TemplateAnnotation.ts
2900
- var HeadingWithTemplate = Heading.extend({
2901
- addAttributes() {
2902
- return {
2903
- ...this.parent?.(),
2904
- dataTemplate: {
2905
- default: null,
2906
- parseHTML: (element) => element.getAttribute("data-template") || null,
2907
- renderHTML: (attributes) => {
2908
- if (!attributes.dataTemplate) return {};
2909
- return { "data-template": attributes.dataTemplate };
2910
- }
2911
- },
2912
- dataTemplateParams: {
2913
- default: null,
2914
- parseHTML: (element) => element.getAttribute("data-template-params") || null,
2915
- renderHTML: (attributes) => {
2916
- if (!attributes.dataTemplateParams) return {};
2917
- return { "data-template-params": attributes.dataTemplateParams };
2918
- }
2919
- }
2920
- };
2921
- },
2922
- renderHTML({ node, HTMLAttributes }) {
2923
- const level = node.attrs.level;
2924
- const tag = `h${level}`;
2925
- const templateName = HTMLAttributes["data-template"];
2926
- if (templateName) {
2927
- return [
2928
- tag,
2929
- HTMLAttributes,
2930
- ["span", { class: "squisq-heading-content" }, 0],
2931
- [
2932
- "span",
2933
- {
2934
- class: "squisq-template-badge",
2935
- contenteditable: "false",
2936
- "data-template": templateName
2937
- },
2938
- templateName
2939
- ]
2940
- ];
2941
- }
2942
- return [tag, HTMLAttributes, 0];
2943
- }
2944
- });
2945
-
2946
- // src/tiptapBridge.ts
2947
- var RE_BOLD_STAR = /\*\*(.+?)\*\*/g;
2948
- var RE_BOLD_UNDER = /__(.+?)__/g;
2949
- var RE_ITALIC_STAR = /\*(.+?)\*/g;
2950
- var RE_ITALIC_UNDER = /_(.+?)_/g;
2951
- var RE_STRIKETHROUGH = /~~(.+?)~~/g;
2952
- var RE_INLINE_CODE = /`(.+?)`/g;
2953
- var RE_LINK = /\[(.+?)\]\((.+?)\)/g;
2954
- var RE_IMAGE = /!\[(.+?)\]\((.+?)\)/g;
2955
- var RE_STRONG_TAG = /<strong>(.*?)<\/strong>/g;
2956
- var RE_B_TAG = /<b>(.*?)<\/b>/g;
2957
- var RE_EM_TAG = /<em>(.*?)<\/em>/g;
2958
- var RE_I_TAG = /<i>(.*?)<\/i>/g;
2959
- var RE_S_TAG = /<s>(.*?)<\/s>/g;
2960
- var RE_DEL_TAG = /<del>(.*?)<\/del>/g;
2961
- var RE_CODE_TAG = /<code>(.*?)<\/code>/g;
2962
- var RE_A_TAG = /<a[^>]+href="([^"]*)"[^>]*>(.*?)<\/a>/g;
2963
- var RE_IMG_TAG = /<img[^>]+alt="([^"]*)"[^>]+src="([^"]*)"[^>]*>/g;
2964
- var RE_STRIP_TAGS = /<[^>]+>/g;
2965
- function markdownToTiptap(markdown) {
2966
- if (!markdown.trim()) return "<p></p>";
2967
- const html = markdown;
2968
- const lines = html.split("\n");
2969
- const outputBlocks = [];
2970
- let inCodeBlock = false;
2971
- let codeBlockLang = "";
2972
- let codeBlockLines = [];
2973
- let inList = false;
2974
- let listItems = [];
2975
- let listType = "ul";
2976
- const flushList = () => {
2977
- if (inList && listItems.length > 0) {
2978
- const tag = listType === "ol" ? "ol" : "ul";
2979
- const attr = listType === "task" ? ' data-type="taskList"' : "";
2980
- outputBlocks.push(`<${tag}${attr}>${listItems.join("")}</${tag}>`);
2981
- listItems = [];
2982
- inList = false;
2983
- }
2984
- };
2985
- for (let i = 0; i < lines.length; i++) {
2986
- const line = lines[i];
2987
- if (line.startsWith("```")) {
2988
- if (!inCodeBlock) {
2989
- flushList();
2990
- inCodeBlock = true;
2991
- codeBlockLang = line.slice(3).trim();
2992
- codeBlockLines = [];
2993
- continue;
2994
- } else {
2995
- const langAttr = codeBlockLang ? ` class="language-${escapeHtml(codeBlockLang)}"` : "";
2996
- outputBlocks.push(
2997
- `<pre><code${langAttr}>${escapeHtml(codeBlockLines.join("\n"))}</code></pre>`
2998
- );
2999
- inCodeBlock = false;
3000
- codeBlockLang = "";
3001
- codeBlockLines = [];
3002
- continue;
3003
- }
3004
- }
3005
- if (inCodeBlock) {
3006
- codeBlockLines.push(line);
3007
- continue;
3008
- }
3009
- if (line.trim() === "") {
3010
- flushList();
3011
- continue;
3012
- }
3013
- const headingMatch = line.match(/^(#{1,6})\s+(.+)$/);
3014
- if (headingMatch) {
3015
- flushList();
3016
- const level = headingMatch[1].length;
3017
- let text = headingMatch[2];
3018
- let attrs = "";
3019
- const annotMatch = text.match(/\s*\{\[([^\]]+)\]\}\s*$/);
3020
- if (annotMatch) {
3021
- text = text.slice(0, annotMatch.index).trimEnd();
3022
- const tokens = annotMatch[1].trim().split(/\s+/);
3023
- attrs = ` data-template="${escapeHtml(tokens[0])}"`;
3024
- const params = tokens.slice(1).filter((t) => t.includes("="));
3025
- if (params.length > 0) {
3026
- attrs += ` data-template-params="${escapeHtml(params.join(" "))}"`;
3027
- }
3028
- }
3029
- outputBlocks.push(`<h${level}${attrs}>${inlineToHtml(text)}</h${level}>`);
3030
- continue;
3031
- }
3032
- if (/^(---|\*\*\*|___)(\s*)$/.test(line.trim())) {
3033
- flushList();
3034
- outputBlocks.push("<hr>");
3035
- continue;
3036
- }
3037
- if (line.startsWith("> ")) {
3038
- flushList();
3039
- outputBlocks.push(`<blockquote><p>${inlineToHtml(line.slice(2))}</p></blockquote>`);
3040
- continue;
3041
- }
3042
- const taskMatch = line.match(/^[-*+]\s+\[([xX ])\]\s+(.+)$/);
3043
- if (taskMatch) {
3044
- if (!inList || listType !== "task") {
3045
- flushList();
3046
- inList = true;
3047
- listType = "task";
3048
- }
3049
- const checked = taskMatch[1].toLowerCase() === "x" ? ' data-checked="true"' : "";
3050
- listItems.push(
3051
- `<li data-type="taskItem"${checked}><label><input type="checkbox"${checked ? " checked" : ""}>${inlineToHtml(taskMatch[2])}</label></li>`
3052
- );
3053
- continue;
3054
- }
3055
- const ulMatch = line.match(/^[-*+]\s+(.+)$/);
3056
- if (ulMatch) {
3057
- if (!inList || listType !== "ul") {
3058
- flushList();
3059
- inList = true;
3060
- listType = "ul";
3061
- }
3062
- listItems.push(`<li><p>${inlineToHtml(ulMatch[1])}</p></li>`);
3063
- continue;
3064
- }
3065
- const olMatch = line.match(/^\d+\.\s+(.+)$/);
3066
- if (olMatch) {
3067
- if (!inList || listType !== "ol") {
3068
- flushList();
3069
- inList = true;
3070
- listType = "ol";
3071
- }
3072
- listItems.push(`<li><p>${inlineToHtml(olMatch[1])}</p></li>`);
3073
- continue;
3074
- }
3075
- flushList();
3076
- outputBlocks.push(`<p>${inlineToHtml(line)}</p>`);
3077
- }
3078
- if (inCodeBlock) {
3079
- const langAttr = codeBlockLang ? ` class="language-${escapeHtml(codeBlockLang)}"` : "";
3080
- outputBlocks.push(
3081
- `<pre><code${langAttr}>${escapeHtml(codeBlockLines.join("\n"))}</code></pre>`
3082
- );
3083
- }
3084
- flushList();
3085
- return outputBlocks.join("") || "<p></p>";
3086
- }
3087
- function tiptapToMarkdown(html) {
3088
- if (!html || html === "<p></p>") return "";
3089
- const lines = [];
3090
- let remaining = html;
3091
- while (remaining.length > 0) {
3092
- const headingMatch = remaining.match(/^<h([1-6])([^>]*)>(.*?)<\/h\1>/s);
3093
- if (headingMatch) {
3094
- const level = parseInt(headingMatch[1], 10);
3095
- const attrs = headingMatch[2];
3096
- let text = htmlToInline(headingMatch[3]);
3097
- const tmplMatch = attrs.match(/data-template="([^"]+)"/);
3098
- if (tmplMatch) {
3099
- let annotation = tmplMatch[1];
3100
- const paramsMatch = attrs.match(/data-template-params="([^"]+)"/);
3101
- if (paramsMatch) {
3102
- annotation += " " + unescapeHtml(paramsMatch[1]);
3103
- }
3104
- text += ` {[${annotation}]}`;
3105
- }
3106
- lines.push("#".repeat(level) + " " + text);
3107
- lines.push("");
3108
- remaining = remaining.slice(headingMatch[0].length);
3109
- continue;
3110
- }
3111
- const codeMatch = remaining.match(
3112
- /^<pre><code(?:\s+class="language-([^"]*)")?>(.*?)<\/code><\/pre>/s
3113
- );
3114
- if (codeMatch) {
3115
- const lang = codeMatch[1] || "";
3116
- const code = unescapeHtml(codeMatch[2]);
3117
- lines.push("```" + lang);
3118
- lines.push(code);
3119
- lines.push("```");
3120
- lines.push("");
3121
- remaining = remaining.slice(codeMatch[0].length);
3122
- continue;
3123
- }
3124
- const bqMatch = remaining.match(/^<blockquote>(.*?)<\/blockquote>/s);
3125
- if (bqMatch) {
3126
- const inner = htmlToInline(bqMatch[1].replace(/<\/?p>/g, ""));
3127
- lines.push("> " + inner);
3128
- lines.push("");
3129
- remaining = remaining.slice(bqMatch[0].length);
3130
- continue;
3131
- }
3132
- if (remaining.startsWith("<hr>") || remaining.startsWith("<hr/>") || remaining.startsWith("<hr />")) {
3133
- const hrMatch = remaining.match(/^<hr\s*\/?>/);
3134
- lines.push("---");
3135
- lines.push("");
3136
- remaining = remaining.slice(hrMatch[0].length);
3137
- continue;
3138
- }
3139
- const taskListMatch = remaining.match(/^<ul[^>]*data-type="taskList"[^>]*>(.*?)<\/ul>/s);
3140
- if (taskListMatch) {
3141
- const items = taskListMatch[1].matchAll(
3142
- /<li[^>]*data-type="taskItem"[^>]*(data-checked="true")?[^>]*>.*?<\/li>/gs
3143
- );
3144
- for (const item of items) {
3145
- const checked = item[0].includes('data-checked="true"') || item[0].includes("checked");
3146
- const textMatch2 = item[0].match(/<label>.*?<\/label>|<p>(.*?)<\/p>/s);
3147
- const text = textMatch2 ? htmlToInline(textMatch2[0].replace(/<[^>]+>/g, "")) : "";
3148
- lines.push(`- [${checked ? "x" : " "}] ${text}`);
3149
- }
3150
- lines.push("");
3151
- remaining = remaining.slice(taskListMatch[0].length);
3152
- continue;
3153
- }
3154
- const ulMatch = remaining.match(/^<ul>(.*?)<\/ul>/s);
3155
- if (ulMatch) {
3156
- const items = ulMatch[1].matchAll(/<li>(.*?)<\/li>/gs);
3157
- for (const item of items) {
3158
- lines.push("- " + htmlToInline(item[1].replace(/<\/?p>/g, "")));
3159
- }
3160
- lines.push("");
3161
- remaining = remaining.slice(ulMatch[0].length);
3162
- continue;
3163
- }
3164
- const olMatch = remaining.match(/^<ol[^>]*>(.*?)<\/ol>/s);
3165
- if (olMatch) {
3166
- const items = [...olMatch[1].matchAll(/<li>(.*?)<\/li>/gs)];
3167
- items.forEach((item, idx) => {
3168
- lines.push(`${idx + 1}. ` + htmlToInline(item[1].replace(/<\/?p>/g, "")));
3169
- });
3170
- lines.push("");
3171
- remaining = remaining.slice(olMatch[0].length);
3172
- continue;
3173
- }
3174
- const pMatch = remaining.match(/^<p>(.*?)<\/p>/s);
3175
- if (pMatch) {
3176
- const text = htmlToInline(pMatch[1]);
3177
- if (text.trim()) {
3178
- lines.push(text);
3179
- lines.push("");
3180
- }
3181
- remaining = remaining.slice(pMatch[0].length);
3182
- continue;
3183
- }
3184
- const skipMatch = remaining.match(/^(<[^>]+>|\s+)/);
3185
- if (skipMatch) {
3186
- remaining = remaining.slice(skipMatch[0].length);
3187
- continue;
3188
- }
3189
- const textMatch = remaining.match(/^([^<]+)/);
3190
- if (textMatch) {
3191
- lines.push(unescapeHtml(textMatch[1]));
3192
- remaining = remaining.slice(textMatch[0].length);
3193
- continue;
3194
- }
3195
- remaining = remaining.slice(1);
3196
- }
3197
- return lines.join("\n").replace(/\n{3,}/g, "\n\n").trim() + "\n";
3198
- }
3199
- function escapeHtml(text) {
3200
- return text.replace(/&/g, "&amp;").replace(/</g, "&lt;").replace(/>/g, "&gt;").replace(/"/g, "&quot;");
3201
- }
3202
- function unescapeHtml(text) {
3203
- return text.replace(/&lt;/g, "<").replace(/&gt;/g, ">").replace(/&quot;/g, '"').replace(/&amp;/g, "&");
3204
- }
3205
- function inlineToHtml(text) {
3206
- let result = escapeHtml(text);
3207
- result = result.replace(RE_BOLD_STAR, "<strong>$1</strong>");
3208
- result = result.replace(RE_BOLD_UNDER, "<strong>$1</strong>");
3209
- result = result.replace(RE_ITALIC_STAR, "<em>$1</em>");
3210
- result = result.replace(RE_ITALIC_UNDER, "<em>$1</em>");
3211
- result = result.replace(RE_STRIKETHROUGH, "<s>$1</s>");
3212
- result = result.replace(RE_INLINE_CODE, "<code>$1</code>");
3213
- result = result.replace(RE_LINK, '<a href="$2">$1</a>');
3214
- result = result.replace(RE_IMAGE, '<img alt="$1" src="$2">');
3215
- return result;
3216
- }
3217
- function htmlToInline(html) {
3218
- let result = html;
3219
- result = result.replace(RE_STRONG_TAG, "**$1**");
3220
- result = result.replace(RE_B_TAG, "**$1**");
3221
- result = result.replace(RE_EM_TAG, "*$1*");
3222
- result = result.replace(RE_I_TAG, "*$1*");
3223
- result = result.replace(RE_S_TAG, "~~$1~~");
3224
- result = result.replace(RE_DEL_TAG, "~~$1~~");
3225
- result = result.replace(RE_CODE_TAG, "`$1`");
3226
- result = result.replace(RE_A_TAG, "[$2]($1)");
3227
- result = result.replace(RE_IMG_TAG, "![$1]($2)");
3228
- result = result.replace(RE_STRIP_TAGS, "");
3229
- return unescapeHtml(result);
3230
- }
3231
-
3232
- // src/WysiwygEditor.tsx
3233
- import { useEditor as useEditor2 } from "@tiptap/react";
3234
- import { jsx as jsx5 } from "react/jsx-runtime";
3235
- var FRONTMATTER_RE = /^---\r?\n([\s\S]*?)\r?\n---\r?\n?/;
3236
- function stripFrontmatter(md) {
3237
- const m = md.match(FRONTMATTER_RE);
3238
- if (!m) return { body: md, frontmatter: "" };
3239
- return { body: md.slice(m[0].length), frontmatter: m[0] };
3240
- }
3241
- function WysiwygEditor({
3242
- placeholder = "Start typing your markdown\u2026",
3243
- className
3244
- }) {
3245
- const { markdownSource, setMarkdownSource, setTiptapEditor } = useEditorContext();
3246
- const isExternalUpdate = useRef3(false);
3247
- const lastSourceRef = useRef3(markdownSource);
3248
- const frontmatterRef = useRef3(stripFrontmatter(markdownSource).frontmatter);
3249
- const editor = useEditor({
3250
- extensions: [
3251
- StarterKit.configure({
3252
- // Disable built-in heading; we use HeadingWithTemplate instead
3253
- heading: false,
3254
- codeBlock: {
3255
- HTMLAttributes: { class: "squisq-code-block" }
3256
- }
3257
- }),
3258
- HeadingWithTemplate.configure({ levels: [1, 2, 3, 4, 5, 6] }),
3259
- Table.configure({ resizable: true }),
3260
- TableRow,
3261
- TableCell,
3262
- TableHeader,
3263
- TaskList,
3264
- TaskItem.configure({ nested: true }),
3265
- Placeholder.configure({ placeholder })
3266
- ],
3267
- content: markdownToTiptap(stripFrontmatter(markdownSource).body),
3268
- onUpdate: ({ editor: ed }) => {
3269
- if (isExternalUpdate.current) return;
3270
- const html = ed.getHTML();
3271
- const bodyMd = tiptapToMarkdown(html);
3272
- const newSource = frontmatterRef.current + bodyMd;
3273
- lastSourceRef.current = newSource;
3274
- setMarkdownSource(newSource);
3275
- },
3276
- editorProps: {
3277
- attributes: {
3278
- class: "squisq-wysiwyg-editor",
3279
- "data-testid": "wysiwyg-editor"
3280
- }
3281
- }
3282
- });
3283
- useEffect4(() => {
3284
- if (editor) {
3285
- setTiptapEditor(editor);
3286
- }
3287
- return () => setTiptapEditor(null);
3288
- }, [editor, setTiptapEditor]);
3289
- useEffect4(() => {
3290
- if (!editor) return;
3291
- if (markdownSource !== lastSourceRef.current) {
3292
- isExternalUpdate.current = true;
3293
- const { body, frontmatter } = stripFrontmatter(markdownSource);
3294
- frontmatterRef.current = frontmatter;
3295
- const content = markdownToTiptap(body);
3296
- editor.commands.setContent(content);
3297
- lastSourceRef.current = markdownSource;
3298
- isExternalUpdate.current = false;
3299
- }
3300
- }, [markdownSource, editor]);
3301
- return /* @__PURE__ */ jsx5(
3302
- "div",
3303
- {
3304
- className,
3305
- style: { width: "100%", height: "100%", overflow: "auto" },
3306
- "data-testid": "wysiwyg-container",
3307
- children: /* @__PURE__ */ jsx5(EditorContent, { editor, style: { height: "100%" } })
3308
- }
3309
- );
3310
- }
3311
-
3312
- // src/PreviewPanel.tsx
3313
- import { useMemo as useMemo3, useState as useState2, useEffect as useEffect5 } from "react";
3314
- import { DocPlayer, LinearDocView } from "@bendyline/squisq-react";
3315
- import { flattenBlocks } from "@bendyline/squisq/doc";
3316
- import { hasTemplate } from "@bendyline/squisq/doc";
3317
- import { extractPlainText } from "@bendyline/squisq/markdown";
3318
- import { VIEWPORT_PRESETS } from "@bendyline/squisq/schemas";
3319
- import { getThemeSummaries, resolveTheme } from "@bendyline/squisq/schemas";
3320
- import { jsx as jsx6, jsxs as jsxs3 } from "react/jsx-runtime";
3321
- function extractBodyText(contents) {
3322
- if (!contents || contents.length === 0) return "";
3323
- const parts = [];
3324
- for (const node of contents) {
3325
- parts.push(extractPlainText(node));
3326
- }
3327
- return parts.join("\n").trim();
3328
- }
3329
- function extractListItems(contents) {
3330
- if (!contents) return [];
3331
- const items = [];
3332
- for (const node of contents) {
3333
- if (node.type === "list") {
3334
- for (const item of node.children) {
3335
- const text = extractPlainText(item).trim();
3336
- if (text) items.push(text);
3337
- }
3338
- }
3339
- }
3340
- return items;
3341
- }
3342
- function getTemplateDefaults(templateName, headingText, block) {
3343
- const body = extractBodyText(block.contents);
3344
- switch (templateName) {
3345
- case "statHighlight":
3346
- return {
3347
- stat: headingText,
3348
- description: body || headingText
3349
- };
3350
- case "quoteBlock":
3351
- case "fullBleedQuote":
3352
- case "pullQuote":
3353
- return {
3354
- quote: body || headingText
3355
- };
3356
- case "factCard":
3357
- return {
3358
- fact: headingText,
3359
- explanation: body || headingText
3360
- };
3361
- case "comparisonBar":
3362
- return {
3363
- leftLabel: "A",
3364
- leftValue: 60,
3365
- rightLabel: "B",
3366
- rightValue: 40
3367
- };
3368
- case "listBlock":
3369
- return {
3370
- items: extractListItems(block.contents) || ["Item 1", "Item 2", "Item 3"]
3371
- };
3372
- case "definitionCard":
3373
- return {
3374
- term: headingText,
3375
- definition: body || headingText
3376
- };
3377
- case "dateEvent":
3378
- return {
3379
- date: headingText,
3380
- description: body || headingText
3381
- };
3382
- default:
3383
- return {};
3384
- }
3385
- }
3386
- function blockToSlide(block, index) {
3387
- const headingText = block.sourceHeading ? extractPlainText(block.sourceHeading) : block.id || `Slide ${index + 1}`;
3388
- const requestedTemplate = block.template || "sectionHeader";
3389
- const template = hasTemplate(requestedTemplate) ? requestedTemplate : "sectionHeader";
3390
- const defaults = getTemplateDefaults(template, headingText, block);
3391
- return {
3392
- id: block.id,
3393
- template,
3394
- duration: block.duration,
3395
- audioSegment: 0,
3396
- transition: index > 0 ? { type: "fade", duration: 0.5 } : void 0,
3397
- // Provide heading text as title — consumed by sectionHeader, titleBlock, etc.
3398
- title: headingText,
3399
- // Template-specific defaults (safe fallbacks for required fields)
3400
- ...defaults,
3401
- // Spread annotation overrides last so explicit values win
3402
- ...block.templateOverrides
3403
- };
3404
- }
3405
- function buildPreviewDoc(doc) {
3406
- const flat = flattenBlocks(doc.blocks);
3407
- const slides = flat.map(blockToSlide);
3408
- let t = 0;
3409
- for (const slide of slides) {
3410
- slide.startTime = t;
3411
- t += slide.duration;
3412
- }
3413
- return {
3414
- articleId: doc.articleId,
3415
- duration: t,
3416
- blocks: slides,
3417
- audio: {
3418
- // Synthetic segment — audio will fail to load and DocPlayer will use
3419
- // its fallback timer to advance currentTime via requestAnimationFrame.
3420
- segments: t > 0 ? [{ src: "", name: "preview", duration: t, startTime: 0 }] : []
3421
- }
3422
- };
3423
- }
3424
- var VIEWPORT_OPTIONS = [
3425
- { key: "landscape", label: "16:9 Landscape" },
3426
- { key: "portrait", label: "9:16 Portrait" },
3427
- { key: "square", label: "1:1 Square" },
3428
- { key: "standard", label: "4:3 Standard" }
3429
- ];
3430
- function resolveRenderAs(value) {
3431
- if (typeof value !== "string") return null;
3432
- const v = value.trim().toLowerCase();
3433
- const mapping = {
3434
- landscape: "landscape",
3435
- "16:9": "landscape",
3436
- widescreen: "landscape",
3437
- portrait: "portrait",
3438
- "9:16": "portrait",
3439
- vertical: "portrait",
3440
- stories: "portrait",
3441
- square: "square",
3442
- "1:1": "square",
3443
- standard: "standard",
3444
- "4:3": "standard"
3445
- };
3446
- return mapping[v] ?? null;
3447
- }
3448
- var DISPLAY_MODE_OPTIONS = [
3449
- { key: "video", label: "Video" },
3450
- { key: "slideshow", label: "Slideshow" },
3451
- { key: "linear", label: "Document" }
3452
- ];
3453
- var THEME_OPTIONS = getThemeSummaries().map((s) => ({ key: s.id, label: s.name }));
3454
- var VALID_THEME_IDS = new Set(THEME_OPTIONS.map((o) => o.key));
3455
- function resolveFrontmatterTheme(value) {
3456
- if (typeof value !== "string") return null;
3457
- const v = value.trim().toLowerCase();
3458
- if (VALID_THEME_IDS.has(v)) return v;
3459
- const normalized = v.replace(/\s+/g, "-");
3460
- if (VALID_THEME_IDS.has(normalized)) return normalized;
3461
- return null;
3462
- }
3463
- function resolveDisplayMode(value) {
3464
- if (typeof value !== "string") return null;
3465
- const v = value.trim().toLowerCase();
3466
- if (v === "video" || v === "slideshow" || v === "linear") return v;
3467
- if (v === "slides" || v === "presentation" || v === "deck") return "slideshow";
3468
- if (v === "document" || v === "scroll" || v === "page") return "linear";
3469
- return null;
3470
- }
3471
- function PreviewPanel({ basePath = "/", className }) {
3472
- const { doc, parseError, isParsing } = useEditorContext();
3473
- const frontmatterPreset = useMemo3(() => {
3474
- if (!doc?.frontmatter) return null;
3475
- return resolveRenderAs(doc.frontmatter["document-render-as"]);
3476
- }, [doc?.frontmatter]);
3477
- const [selectedPreset, setSelectedPreset] = useState2(null);
3478
- useEffect5(() => {
3479
- setSelectedPreset(null);
3480
- }, [frontmatterPreset]);
3481
- const activePreset = selectedPreset ?? frontmatterPreset ?? "landscape";
3482
- const activeViewport = VIEWPORT_PRESETS[activePreset];
3483
- const frontmatterDisplayMode = useMemo3(() => {
3484
- if (!doc?.frontmatter) return null;
3485
- return resolveDisplayMode(doc.frontmatter["display-mode"]);
3486
- }, [doc?.frontmatter]);
3487
- const [selectedDisplayMode, setSelectedDisplayMode] = useState2(null);
3488
- useEffect5(() => {
3489
- setSelectedDisplayMode(null);
3490
- }, [frontmatterDisplayMode]);
3491
- const activeDisplayMode = selectedDisplayMode ?? frontmatterDisplayMode ?? "video";
3492
- const frontmatterThemeId = useMemo3(() => {
3493
- if (!doc?.frontmatter) return null;
3494
- return resolveFrontmatterTheme(doc.frontmatter["theme"]);
3495
- }, [doc?.frontmatter]);
3496
- const [selectedThemeId, setSelectedThemeId] = useState2(null);
3497
- useEffect5(() => {
3498
- setSelectedThemeId(null);
3499
- }, [frontmatterThemeId]);
3500
- const activeThemeId = selectedThemeId ?? frontmatterThemeId ?? "documentary";
3501
- const activeTheme = useMemo3(() => resolveTheme(activeThemeId), [activeThemeId]);
3502
- const previewDoc = useMemo3(() => {
3503
- if (!doc || !doc.blocks.length) return null;
3504
- return buildPreviewDoc(doc);
3505
- }, [doc]);
3506
- if (isParsing) {
3507
- return /* @__PURE__ */ jsx6("div", { className: `squisq-preview-status ${className || ""}`, "data-testid": "preview-panel", children: /* @__PURE__ */ jsx6("p", { children: "Parsing\u2026" }) });
3508
- }
3509
- if (parseError) {
3510
- return /* @__PURE__ */ jsxs3("div", { className: `squisq-preview-status ${className || ""}`, "data-testid": "preview-panel", children: [
3511
- /* @__PURE__ */ jsx6("h3", { children: "Parse Error" }),
3512
- /* @__PURE__ */ jsx6("pre", { children: parseError })
3513
- ] });
3514
- }
3515
- if (!previewDoc) {
3516
- return /* @__PURE__ */ jsx6("div", { className: `squisq-preview-status ${className || ""}`, "data-testid": "preview-panel", children: /* @__PURE__ */ jsx6("p", { children: "No content to preview. Start typing in the editor." }) });
3517
- }
3518
- return /* @__PURE__ */ jsxs3(
3519
- "div",
3520
- {
3521
- className: `squisq-preview-container ${className || ""}`,
3522
- "data-testid": "preview-panel",
3523
- style: {
3524
- width: "100%",
3525
- height: "100%",
3526
- display: "flex",
3527
- flexDirection: "column",
3528
- overflow: "hidden",
3529
- background: "var(--squisq-bg, #f5f5f5)"
3530
- },
3531
- children: [
3532
- /* @__PURE__ */ jsxs3(
3533
- "div",
3534
- {
3535
- className: "squisq-preview-toolbar",
3536
- style: {
3537
- display: "flex",
3538
- alignItems: "center",
3539
- gap: "8px",
3540
- padding: "6px 12px",
3541
- borderBottom: "1px solid var(--squisq-border, #e0e0e0)",
3542
- flexShrink: 0,
3543
- fontSize: "13px"
3544
- },
3545
- children: [
3546
- /* @__PURE__ */ jsx6("label", { htmlFor: "viewport-preset", style: { color: "var(--squisq-text-muted, #6b7280)" }, children: "Format:" }),
3547
- /* @__PURE__ */ jsx6(
3548
- "select",
3549
- {
3550
- id: "viewport-preset",
3551
- value: activePreset,
3552
- onChange: (e) => setSelectedPreset(e.target.value),
3553
- style: {
3554
- padding: "3px 8px",
3555
- borderRadius: "4px",
3556
- border: "1px solid var(--squisq-border, #d1d5db)",
3557
- background: "var(--squisq-input-bg, #fff)",
3558
- color: "var(--squisq-text, #1f2937)",
3559
- fontSize: "13px",
3560
- cursor: "pointer"
3561
- },
3562
- children: VIEWPORT_OPTIONS.map((opt) => /* @__PURE__ */ jsx6("option", { value: opt.key, children: opt.label }, opt.key))
3563
- }
3564
- ),
3565
- frontmatterPreset && selectedPreset === null && /* @__PURE__ */ jsx6(
3566
- "span",
3567
- {
3568
- style: {
3569
- fontSize: "11px",
3570
- color: "var(--squisq-text-muted, #9ca3af)",
3571
- fontStyle: "italic"
3572
- },
3573
- children: "(from frontmatter)"
3574
- }
3575
- ),
3576
- /* @__PURE__ */ jsx6(
3577
- "span",
3578
- {
3579
- style: {
3580
- width: "1px",
3581
- height: "18px",
3582
- background: "var(--squisq-border, #d1d5db)",
3583
- margin: "0 4px"
3584
- }
3585
- }
3586
- ),
3587
- /* @__PURE__ */ jsx6("label", { htmlFor: "display-mode", style: { color: "var(--squisq-text-muted, #6b7280)" }, children: "Mode:" }),
3588
- /* @__PURE__ */ jsx6(
3589
- "select",
3590
- {
3591
- id: "display-mode",
3592
- value: activeDisplayMode,
3593
- onChange: (e) => setSelectedDisplayMode(e.target.value),
3594
- style: {
3595
- padding: "3px 8px",
3596
- borderRadius: "4px",
3597
- border: "1px solid var(--squisq-border, #d1d5db)",
3598
- background: "var(--squisq-input-bg, #fff)",
3599
- color: "var(--squisq-text, #1f2937)",
3600
- fontSize: "13px",
3601
- cursor: "pointer"
3602
- },
3603
- children: DISPLAY_MODE_OPTIONS.map((opt) => /* @__PURE__ */ jsx6("option", { value: opt.key, children: opt.label }, opt.key))
3604
- }
3605
- ),
3606
- frontmatterDisplayMode && selectedDisplayMode === null && /* @__PURE__ */ jsx6(
3607
- "span",
3608
- {
3609
- style: {
3610
- fontSize: "11px",
3611
- color: "var(--squisq-text-muted, #9ca3af)",
3612
- fontStyle: "italic"
3613
- },
3614
- children: "(from frontmatter)"
3615
- }
3616
- ),
3617
- /* @__PURE__ */ jsx6(
3618
- "span",
3619
- {
3620
- style: {
3621
- width: "1px",
3622
- height: "18px",
3623
- background: "var(--squisq-border, #d1d5db)",
3624
- margin: "0 4px"
3625
- }
3626
- }
3627
- ),
3628
- /* @__PURE__ */ jsx6("label", { htmlFor: "theme-select", style: { color: "var(--squisq-text-muted, #6b7280)" }, children: "Theme:" }),
3629
- /* @__PURE__ */ jsx6(
3630
- "select",
3631
- {
3632
- id: "theme-select",
3633
- value: activeThemeId,
3634
- onChange: (e) => setSelectedThemeId(e.target.value),
3635
- style: {
3636
- padding: "3px 8px",
3637
- borderRadius: "4px",
3638
- border: "1px solid var(--squisq-border, #d1d5db)",
3639
- background: "var(--squisq-input-bg, #fff)",
3640
- color: "var(--squisq-text, #1f2937)",
3641
- fontSize: "13px",
3642
- cursor: "pointer"
3643
- },
3644
- children: THEME_OPTIONS.map((opt) => /* @__PURE__ */ jsx6("option", { value: opt.key, children: opt.label }, opt.key))
3645
- }
3646
- ),
3647
- frontmatterThemeId && selectedThemeId === null && /* @__PURE__ */ jsx6(
3648
- "span",
3649
- {
3650
- style: {
3651
- fontSize: "11px",
3652
- color: "var(--squisq-text-muted, #9ca3af)",
3653
- fontStyle: "italic"
3654
- },
3655
- children: "(from frontmatter)"
3656
- }
3657
- )
3658
- ]
3659
- }
3660
- ),
3661
- /* @__PURE__ */ jsx6(
3662
- "div",
3663
- {
3664
- className: "squisq-preview-player",
3665
- style: {
3666
- flex: 1,
3667
- display: "flex",
3668
- alignItems: activeDisplayMode === "linear" ? "stretch" : "center",
3669
- justifyContent: "center",
3670
- overflow: "hidden",
3671
- minHeight: 0
3672
- },
3673
- children: activeDisplayMode === "linear" ? /* @__PURE__ */ jsx6(
3674
- LinearDocView,
3675
- {
3676
- doc,
3677
- basePath,
3678
- viewport: activeViewport,
3679
- theme: activeTheme
3680
- }
3681
- ) : /* @__PURE__ */ jsx6(
3682
- DocPlayer,
3683
- {
3684
- script: previewDoc,
3685
- basePath,
3686
- showControls: true,
3687
- muted: true,
3688
- forceViewport: activeViewport,
3689
- displayMode: activeDisplayMode,
3690
- theme: activeTheme
3691
- }
3692
- )
3693
- }
3694
- )
3695
- ]
3696
- }
3697
- );
3698
- }
3699
-
3700
- // src/EditorShell.tsx
3701
- import { jsx as jsx7, jsxs as jsxs4 } from "react/jsx-runtime";
3702
- function EditorShell({
3703
- initialMarkdown = "",
3704
- initialView = "wysiwyg",
3705
- articleId = "untitled",
3706
- basePath = "/",
3707
- onChange,
3708
- theme = "light",
3709
- className,
3710
- height = "100vh"
3711
- }) {
3712
- return /* @__PURE__ */ jsx7(
3713
- EditorProvider,
3714
- {
3715
- initialMarkdown,
3716
- initialView,
3717
- articleId,
3718
- theme,
3719
- children: /* @__PURE__ */ jsx7(
3720
- EditorShellInner,
3721
- {
3722
- basePath,
3723
- onChange,
3724
- className,
3725
- height
3726
- }
3727
- )
3728
- }
3729
- );
3730
- }
3731
- function EditorShellInner({ basePath, onChange, className, height }) {
3732
- const { activeView, markdownSource, theme } = useEditorContext();
3733
- useEffect6(() => {
3734
- onChange?.(markdownSource);
3735
- }, [markdownSource, onChange]);
3736
- useEffect6(() => {
3737
- const handler = (e) => {
3738
- if (e.ctrlKey || e.metaKey) {
3739
- switch (e.key) {
3740
- case "1":
3741
- e.preventDefault();
3742
- document.querySelector('[data-view="wysiwyg"]')?.click();
3743
- break;
3744
- case "2":
3745
- e.preventDefault();
3746
- document.querySelector('[data-view="raw"]')?.click();
3747
- break;
3748
- case "3":
3749
- e.preventDefault();
3750
- document.querySelector('[data-view="preview"]')?.click();
3751
- break;
3752
- }
3753
- }
3754
- };
3755
- window.addEventListener("keydown", handler);
3756
- return () => window.removeEventListener("keydown", handler);
3757
- }, []);
3758
- return /* @__PURE__ */ jsxs4(
3759
- "div",
3760
- {
3761
- className: `squisq-editor-shell ${className || ""}`,
3762
- "data-theme": theme,
3763
- style: {
3764
- display: "flex",
3765
- flexDirection: "column",
3766
- height,
3767
- overflow: "hidden"
3768
- },
3769
- children: [
3770
- /* @__PURE__ */ jsx7("div", { className: "squisq-editor-header", children: /* @__PURE__ */ jsx7(Toolbar, {}) }),
3771
- /* @__PURE__ */ jsxs4(
3772
- "div",
3773
- {
3774
- className: "squisq-editor-content",
3775
- style: { flex: 1, overflow: "hidden", position: "relative" },
3776
- children: [
3777
- activeView === "raw" && /* @__PURE__ */ jsx7(RawEditor, { theme: theme === "dark" ? "vs-dark" : "vs" }),
3778
- activeView === "wysiwyg" && /* @__PURE__ */ jsx7(WysiwygEditor, {}),
3779
- activeView === "preview" && /* @__PURE__ */ jsx7(PreviewPanel, { basePath })
3780
- ]
3781
- }
3782
- ),
3783
- /* @__PURE__ */ jsx7(StatusBar, {})
3784
- ]
3785
- }
3786
- );
3787
- }
3788
-
3789
- // src/ViewSwitcher.tsx
3790
- import { jsx as jsx8 } from "react/jsx-runtime";
3791
- var VIEWS2 = [
3792
- { id: "raw", label: "Raw", shortcut: "\u23181" },
3793
- { id: "wysiwyg", label: "Editor", shortcut: "\u23182" },
3794
- { id: "preview", label: "Preview", shortcut: "\u23183" }
3795
- ];
3796
- function ViewSwitcher({ className }) {
3797
- const { activeView, setActiveView } = useEditorContext();
3798
- return /* @__PURE__ */ jsx8(
3799
- "div",
3800
- {
3801
- className: `squisq-view-switcher ${className || ""}`,
3802
- role: "tablist",
3803
- "aria-label": "Editor view",
3804
- children: VIEWS2.map((view) => /* @__PURE__ */ jsx8(
3805
- "button",
3806
- {
3807
- role: "tab",
3808
- "aria-selected": activeView === view.id,
3809
- className: `squisq-view-tab ${activeView === view.id ? "squisq-view-tab--active" : ""}`,
3810
- onClick: () => setActiveView(view.id),
3811
- title: `${view.label} (${view.shortcut})`,
3812
- children: view.label
3813
- },
3814
- view.id
3815
- ))
3816
- }
3817
- );
3818
- }
3819
- export {
3820
- EditorProvider,
3821
- EditorShell,
3822
- HeadingWithTemplate,
3823
- PreviewPanel,
3824
- RawEditor,
3825
- StatusBar,
3826
- Toolbar,
3827
- ViewSwitcher,
3828
- WysiwygEditor,
3829
- markdownToTiptap,
3830
- tiptapToMarkdown,
3831
- useEditorContext
3832
- };
1
+ /**
2
+ * @bendyline/squisq-editor-react
3
+ *
4
+ * React component library for editing markdown content with three views:
5
+ * - Raw (Monaco) — Full markdown source editing
6
+ * - WYSIWYG (Tiptap) — Rich text editing
7
+ * - Preview — Rendered block preview
8
+ *
9
+ * @example
10
+ * ```tsx
11
+ * import { EditorShell } from '@bendyline/squisq-editor-react';
12
+ * import '@bendyline/squisq-editor-react/styles';
13
+ *
14
+ * function App() {
15
+ * return <EditorShell initialMarkdown="# Hello World" />;
16
+ * }
17
+ * ```
18
+ */
19
+ // Shell (top-level component)
20
+ export { EditorShell } from './EditorShell.js';
21
+ // Context
22
+ export { EditorProvider, useEditorContext } from './EditorContext.js';
23
+ // Individual editors (for custom layouts)
24
+ export { RawEditor } from './RawEditor.js';
25
+ export { WysiwygEditor } from './WysiwygEditor.js';
26
+ export { PreviewPanel } from './PreviewPanel.js';
27
+ // Chrome (for custom layouts)
28
+ export { ViewSwitcher } from './ViewSwitcher.js';
29
+ export { Toolbar } from './Toolbar.js';
30
+ export { StatusBar } from './StatusBar.js';
31
+ // Bridge utilities
32
+ export { markdownToTiptap, tiptapToMarkdown } from './tiptapBridge.js';
33
+ // Tiptap extension: Heading with template annotation support
34
+ export { HeadingWithTemplate } from './TemplateAnnotation.js';
3833
35
  //# sourceMappingURL=index.js.map