@frontlit/text-editor 0.1.0-alpha.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.
- package/LICENSE +21 -0
- package/README.md +60 -0
- package/dist/index.d.ts +135 -0
- package/dist/index.js +1474 -0
- package/dist/index.js.map +1 -0
- package/dist/prose.css +403 -0
- package/dist/styles.css +1058 -0
- package/dist/tokens.css +58 -0
- package/package.json +91 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,1474 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
import React5, { createContext, useState, useRef, useMemo, useEffect, useCallback, useContext } from 'react';
|
|
3
|
+
import { Node, generateText } from '@tiptap/core';
|
|
4
|
+
import { useEditor, EditorContent } from '@tiptap/react';
|
|
5
|
+
import StarterKit from '@tiptap/starter-kit';
|
|
6
|
+
import Placeholder from '@tiptap/extension-placeholder';
|
|
7
|
+
import Link from '@tiptap/extension-link';
|
|
8
|
+
import { TableRow, TableHeader, TableCell, Table } from '@tiptap/extension-table';
|
|
9
|
+
import Image from '@tiptap/extension-image';
|
|
10
|
+
import Dropcursor from '@tiptap/extension-dropcursor';
|
|
11
|
+
import Gapcursor from '@tiptap/extension-gapcursor';
|
|
12
|
+
import CodeBlockLowlight from '@tiptap/extension-code-block-lowlight';
|
|
13
|
+
import Heading from '@tiptap/extension-heading';
|
|
14
|
+
import Highlight from '@tiptap/extension-highlight';
|
|
15
|
+
import { lowlight } from 'lowlight';
|
|
16
|
+
import javascript from 'highlight.js/lib/languages/javascript';
|
|
17
|
+
import typescript from 'highlight.js/lib/languages/typescript';
|
|
18
|
+
import json from 'highlight.js/lib/languages/json';
|
|
19
|
+
import css from 'highlight.js/lib/languages/css';
|
|
20
|
+
import xml from 'highlight.js/lib/languages/xml';
|
|
21
|
+
import { Undo2, Redo2, ChevronDown, Bold, Italic, Underline, Code, Link as Link$1, List, ListOrdered, Quote, Plus, Minus, Table as Table$1, Image as Image$1, Trash2, Strikethrough, X, Check, Copy } from 'lucide-react';
|
|
22
|
+
import { jsx, Fragment, jsxs } from 'react/jsx-runtime';
|
|
23
|
+
import { BubbleMenuPlugin } from '@tiptap/extension-bubble-menu';
|
|
24
|
+
import { clsx } from 'clsx';
|
|
25
|
+
import { twMerge } from 'tailwind-merge';
|
|
26
|
+
import { renderToReactElement } from '@tiptap/static-renderer';
|
|
27
|
+
|
|
28
|
+
// src/editor.tsx
|
|
29
|
+
|
|
30
|
+
// src/empty-doc.ts
|
|
31
|
+
var emptyDoc = {
|
|
32
|
+
type: "doc",
|
|
33
|
+
content: []
|
|
34
|
+
};
|
|
35
|
+
var empty_doc_default = emptyDoc;
|
|
36
|
+
|
|
37
|
+
// src/create-id.ts
|
|
38
|
+
function createId(textContent) {
|
|
39
|
+
return textContent.replace(/\s/g, "-").toLowerCase();
|
|
40
|
+
}
|
|
41
|
+
var CodeMirrorNode = Node.create({
|
|
42
|
+
name: "codeMirror",
|
|
43
|
+
content: "block+",
|
|
44
|
+
code: true,
|
|
45
|
+
addOptions() {
|
|
46
|
+
return {
|
|
47
|
+
HTMLAttributes: {}
|
|
48
|
+
};
|
|
49
|
+
},
|
|
50
|
+
parseHTML() {
|
|
51
|
+
return [
|
|
52
|
+
{
|
|
53
|
+
tag: "pre"
|
|
54
|
+
}
|
|
55
|
+
];
|
|
56
|
+
},
|
|
57
|
+
renderHTML({ HTMLAttributes }) {
|
|
58
|
+
return ["pre", HTMLAttributes, 0];
|
|
59
|
+
}
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
// src/extensions.ts
|
|
63
|
+
lowlight.registerLanguage("javascript", javascript);
|
|
64
|
+
lowlight.registerLanguage("typescript", typescript);
|
|
65
|
+
lowlight.registerLanguage("json", json);
|
|
66
|
+
lowlight.registerLanguage("css", css);
|
|
67
|
+
lowlight.registerLanguage("html", xml);
|
|
68
|
+
var createExtensions = ({
|
|
69
|
+
placeholder
|
|
70
|
+
} = {}) => [
|
|
71
|
+
StarterKit.configure({
|
|
72
|
+
codeBlock: false,
|
|
73
|
+
// Configured/replaced explicitly below (custom options or a
|
|
74
|
+
// `.extend()`) — disabled here to avoid tiptap's duplicate-extension
|
|
75
|
+
// warning and constructing throwaway instances of each.
|
|
76
|
+
link: false,
|
|
77
|
+
dropcursor: false,
|
|
78
|
+
gapcursor: false,
|
|
79
|
+
heading: false
|
|
80
|
+
}),
|
|
81
|
+
Placeholder.configure({
|
|
82
|
+
placeholder: placeholder || "Write something\u2026"
|
|
83
|
+
}),
|
|
84
|
+
Link.configure({
|
|
85
|
+
openOnClick: false,
|
|
86
|
+
autolink: true,
|
|
87
|
+
linkOnPaste: true
|
|
88
|
+
}),
|
|
89
|
+
Table.configure({
|
|
90
|
+
resizable: true
|
|
91
|
+
}),
|
|
92
|
+
TableRow,
|
|
93
|
+
TableHeader,
|
|
94
|
+
TableCell,
|
|
95
|
+
Image.configure({
|
|
96
|
+
// We embed uploaded images as data URLs (no backend/media library in
|
|
97
|
+
// this package), so base64 sources must be allowed.
|
|
98
|
+
allowBase64: true,
|
|
99
|
+
HTMLAttributes: {
|
|
100
|
+
class: "max-w-full h-auto rounded-md"
|
|
101
|
+
},
|
|
102
|
+
resize: {
|
|
103
|
+
enabled: true,
|
|
104
|
+
directions: ["top", "bottom", "left", "right"],
|
|
105
|
+
alwaysPreserveAspectRatio: true
|
|
106
|
+
}
|
|
107
|
+
}),
|
|
108
|
+
Dropcursor.configure({
|
|
109
|
+
color: "hsl(var(--foreground))",
|
|
110
|
+
width: 2
|
|
111
|
+
}),
|
|
112
|
+
Gapcursor,
|
|
113
|
+
CodeBlockLowlight.configure({
|
|
114
|
+
lowlight
|
|
115
|
+
}),
|
|
116
|
+
Heading.extend({
|
|
117
|
+
renderHTML({ node, HTMLAttributes }) {
|
|
118
|
+
const level = this.options.levels.includes(node.attrs.level) ? node.attrs.level : this.options.levels[0];
|
|
119
|
+
const id = typeof node.textContent === "string" ? createId(node.textContent) : void 0;
|
|
120
|
+
return [
|
|
121
|
+
`h${level}`,
|
|
122
|
+
{
|
|
123
|
+
...HTMLAttributes,
|
|
124
|
+
id
|
|
125
|
+
},
|
|
126
|
+
0
|
|
127
|
+
];
|
|
128
|
+
}
|
|
129
|
+
}),
|
|
130
|
+
Highlight,
|
|
131
|
+
CodeMirrorNode
|
|
132
|
+
];
|
|
133
|
+
var LinkEditorPopover = ({
|
|
134
|
+
editor,
|
|
135
|
+
onClose,
|
|
136
|
+
variant = "panel",
|
|
137
|
+
containerRef
|
|
138
|
+
}) => {
|
|
139
|
+
const [linkValue, setLinkValue] = useState("");
|
|
140
|
+
const inputRef = useRef(null);
|
|
141
|
+
const localContainerRef = useRef(null);
|
|
142
|
+
const assignContainerRef = (node) => {
|
|
143
|
+
localContainerRef.current = node;
|
|
144
|
+
if (containerRef) {
|
|
145
|
+
containerRef.current = node;
|
|
146
|
+
}
|
|
147
|
+
};
|
|
148
|
+
useEffect(() => {
|
|
149
|
+
const current = editor.getAttributes("link").href;
|
|
150
|
+
setLinkValue(current ?? "");
|
|
151
|
+
}, [editor]);
|
|
152
|
+
useEffect(() => {
|
|
153
|
+
const timer = window.setTimeout(() => {
|
|
154
|
+
inputRef.current?.focus();
|
|
155
|
+
}, 20);
|
|
156
|
+
return () => window.clearTimeout(timer);
|
|
157
|
+
}, []);
|
|
158
|
+
const applyLink = () => {
|
|
159
|
+
const value = linkValue.trim();
|
|
160
|
+
if (!value) {
|
|
161
|
+
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
162
|
+
} else {
|
|
163
|
+
editor.chain().focus().extendMarkRange("link").setLink({ href: value }).run();
|
|
164
|
+
}
|
|
165
|
+
onClose();
|
|
166
|
+
};
|
|
167
|
+
const removeLink = () => {
|
|
168
|
+
editor.chain().focus().extendMarkRange("link").unsetLink().run();
|
|
169
|
+
onClose();
|
|
170
|
+
};
|
|
171
|
+
const handleKeyDown = (event) => {
|
|
172
|
+
if (event.key === "Enter") {
|
|
173
|
+
event.preventDefault();
|
|
174
|
+
applyLink();
|
|
175
|
+
}
|
|
176
|
+
if (event.key === "Escape") {
|
|
177
|
+
event.preventDefault();
|
|
178
|
+
onClose();
|
|
179
|
+
}
|
|
180
|
+
};
|
|
181
|
+
if (variant === "inline") {
|
|
182
|
+
return /* @__PURE__ */ jsxs(
|
|
183
|
+
"div",
|
|
184
|
+
{
|
|
185
|
+
ref: assignContainerRef,
|
|
186
|
+
className: "flex items-center gap-2 rounded-md border border-border bg-background p-2 shadow",
|
|
187
|
+
onMouseDown: (event) => event.stopPropagation(),
|
|
188
|
+
children: [
|
|
189
|
+
/* @__PURE__ */ jsx(
|
|
190
|
+
"input",
|
|
191
|
+
{
|
|
192
|
+
ref: inputRef,
|
|
193
|
+
type: "url",
|
|
194
|
+
placeholder: "https://example.com",
|
|
195
|
+
value: linkValue,
|
|
196
|
+
onChange: (event) => setLinkValue(event.target.value),
|
|
197
|
+
onKeyDown: handleKeyDown,
|
|
198
|
+
className: "w-48 rounded border border-border bg-background px-2 py-1 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
|
199
|
+
}
|
|
200
|
+
),
|
|
201
|
+
/* @__PURE__ */ jsx(
|
|
202
|
+
"button",
|
|
203
|
+
{
|
|
204
|
+
type: "button",
|
|
205
|
+
"aria-label": "Cancel link editing",
|
|
206
|
+
className: "inline-flex h-8 w-8 items-center justify-center rounded border border-border text-foreground transition-colors hover:bg-muted",
|
|
207
|
+
onClick: onClose,
|
|
208
|
+
children: /* @__PURE__ */ jsx(X, { className: "h-4 w-4" })
|
|
209
|
+
}
|
|
210
|
+
),
|
|
211
|
+
/* @__PURE__ */ jsx(
|
|
212
|
+
"button",
|
|
213
|
+
{
|
|
214
|
+
type: "button",
|
|
215
|
+
"aria-label": "Remove link",
|
|
216
|
+
className: "inline-flex h-8 w-8 items-center justify-center rounded border border-border text-foreground transition-colors hover:bg-muted",
|
|
217
|
+
onClick: removeLink,
|
|
218
|
+
children: /* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" })
|
|
219
|
+
}
|
|
220
|
+
),
|
|
221
|
+
/* @__PURE__ */ jsx(
|
|
222
|
+
"button",
|
|
223
|
+
{
|
|
224
|
+
type: "button",
|
|
225
|
+
"aria-label": "Apply link",
|
|
226
|
+
className: "inline-flex h-8 w-8 items-center justify-center rounded bg-primary text-primary-foreground transition-colors hover:bg-primary/90",
|
|
227
|
+
onClick: applyLink,
|
|
228
|
+
children: /* @__PURE__ */ jsx(Check, { className: "h-4 w-4" })
|
|
229
|
+
}
|
|
230
|
+
)
|
|
231
|
+
]
|
|
232
|
+
}
|
|
233
|
+
);
|
|
234
|
+
}
|
|
235
|
+
return /* @__PURE__ */ jsx(
|
|
236
|
+
"div",
|
|
237
|
+
{
|
|
238
|
+
ref: assignContainerRef,
|
|
239
|
+
className: "absolute left-2 top-full z-20 mt-2 w-full max-w-sm rounded-md border border-border bg-background p-3 shadow-lg",
|
|
240
|
+
children: /* @__PURE__ */ jsxs("div", { className: "flex flex-col gap-2", children: [
|
|
241
|
+
/* @__PURE__ */ jsx("label", { className: "text-xs font-medium text-muted-foreground", children: "Link URL" }),
|
|
242
|
+
/* @__PURE__ */ jsx(
|
|
243
|
+
"input",
|
|
244
|
+
{
|
|
245
|
+
ref: inputRef,
|
|
246
|
+
type: "url",
|
|
247
|
+
placeholder: "https://example.com",
|
|
248
|
+
value: linkValue,
|
|
249
|
+
onChange: (event) => setLinkValue(event.target.value),
|
|
250
|
+
onKeyDown: handleKeyDown,
|
|
251
|
+
className: "w-full rounded border border-border bg-background px-2 py-1 text-sm text-foreground focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary"
|
|
252
|
+
}
|
|
253
|
+
),
|
|
254
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center justify-end gap-2", children: [
|
|
255
|
+
/* @__PURE__ */ jsxs(
|
|
256
|
+
"button",
|
|
257
|
+
{
|
|
258
|
+
type: "button",
|
|
259
|
+
className: "inline-flex items-center gap-1 rounded border border-border px-2 py-1 text-sm text-foreground transition-colors hover:bg-muted",
|
|
260
|
+
onClick: onClose,
|
|
261
|
+
children: [
|
|
262
|
+
/* @__PURE__ */ jsx(X, { className: "h-4 w-4" }),
|
|
263
|
+
"Cancel"
|
|
264
|
+
]
|
|
265
|
+
}
|
|
266
|
+
),
|
|
267
|
+
/* @__PURE__ */ jsxs(
|
|
268
|
+
"button",
|
|
269
|
+
{
|
|
270
|
+
type: "button",
|
|
271
|
+
className: "inline-flex items-center gap-1 rounded border border-border px-2 py-1 text-sm text-foreground transition-colors hover:bg-muted",
|
|
272
|
+
onClick: removeLink,
|
|
273
|
+
children: [
|
|
274
|
+
/* @__PURE__ */ jsx(Trash2, { className: "h-4 w-4" }),
|
|
275
|
+
"Remove"
|
|
276
|
+
]
|
|
277
|
+
}
|
|
278
|
+
),
|
|
279
|
+
/* @__PURE__ */ jsxs(
|
|
280
|
+
"button",
|
|
281
|
+
{
|
|
282
|
+
type: "button",
|
|
283
|
+
className: "inline-flex items-center gap-1 rounded bg-primary px-2 py-1 text-sm font-medium text-primary-foreground transition-colors hover:bg-primary/90",
|
|
284
|
+
onClick: applyLink,
|
|
285
|
+
children: [
|
|
286
|
+
/* @__PURE__ */ jsx(Check, { className: "h-4 w-4" }),
|
|
287
|
+
"Apply"
|
|
288
|
+
]
|
|
289
|
+
}
|
|
290
|
+
)
|
|
291
|
+
] })
|
|
292
|
+
] })
|
|
293
|
+
}
|
|
294
|
+
);
|
|
295
|
+
};
|
|
296
|
+
var link_editor_popover_default = LinkEditorPopover;
|
|
297
|
+
var iconButtonBase = "inline-flex h-8 w-8 items-center justify-center rounded border border-transparent text-foreground transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary disabled:cursor-not-allowed disabled:opacity-50";
|
|
298
|
+
function IconButton({
|
|
299
|
+
icon: Icon,
|
|
300
|
+
label,
|
|
301
|
+
active,
|
|
302
|
+
disabled,
|
|
303
|
+
onClick
|
|
304
|
+
}) {
|
|
305
|
+
return /* @__PURE__ */ jsx(
|
|
306
|
+
"button",
|
|
307
|
+
{
|
|
308
|
+
type: "button",
|
|
309
|
+
"aria-label": label,
|
|
310
|
+
title: label,
|
|
311
|
+
className: `${iconButtonBase} ${active ? "bg-primary text-primary-foreground hover:bg-primary/90" : "bg-transparent hover:bg-muted"}`,
|
|
312
|
+
disabled,
|
|
313
|
+
onClick,
|
|
314
|
+
children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" })
|
|
315
|
+
}
|
|
316
|
+
);
|
|
317
|
+
}
|
|
318
|
+
var headingOptions = [
|
|
319
|
+
{ label: "Paragraph", value: "paragraph" },
|
|
320
|
+
{ label: "Heading 1", value: "h1", level: 1 },
|
|
321
|
+
{ label: "Heading 2", value: "h2", level: 2 },
|
|
322
|
+
{ label: "Heading 3", value: "h3", level: 3 }
|
|
323
|
+
];
|
|
324
|
+
function Divider() {
|
|
325
|
+
return /* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "mx-1 h-6 w-px shrink-0 bg-border" });
|
|
326
|
+
}
|
|
327
|
+
var Toolbar = ({
|
|
328
|
+
editor,
|
|
329
|
+
onRequestImageUpload,
|
|
330
|
+
isUploadingImage
|
|
331
|
+
}) => {
|
|
332
|
+
const [activePopover, setActivePopover] = useState(null);
|
|
333
|
+
const [wordCount, setWordCount] = useState(0);
|
|
334
|
+
const popoverRef = useRef(null);
|
|
335
|
+
useEffect(() => {
|
|
336
|
+
if (!activePopover) return void 0;
|
|
337
|
+
const handleClick = (event) => {
|
|
338
|
+
if (popoverRef.current && !popoverRef.current.contains(event.target)) {
|
|
339
|
+
setActivePopover(null);
|
|
340
|
+
}
|
|
341
|
+
};
|
|
342
|
+
const handleKeyDown = (event) => {
|
|
343
|
+
if (event.key === "Escape") setActivePopover(null);
|
|
344
|
+
};
|
|
345
|
+
document.addEventListener("mousedown", handleClick);
|
|
346
|
+
document.addEventListener("keydown", handleKeyDown);
|
|
347
|
+
return () => {
|
|
348
|
+
document.removeEventListener("mousedown", handleClick);
|
|
349
|
+
document.removeEventListener("keydown", handleKeyDown);
|
|
350
|
+
};
|
|
351
|
+
}, [activePopover]);
|
|
352
|
+
useEffect(() => {
|
|
353
|
+
if (!editor) return void 0;
|
|
354
|
+
const updateStats = () => {
|
|
355
|
+
const text = editor.getText().trim();
|
|
356
|
+
setWordCount(text ? text.split(/\s+/).length : 0);
|
|
357
|
+
};
|
|
358
|
+
updateStats();
|
|
359
|
+
editor.on("update", updateStats);
|
|
360
|
+
return () => {
|
|
361
|
+
editor.off("update", updateStats);
|
|
362
|
+
};
|
|
363
|
+
}, [editor]);
|
|
364
|
+
if (!editor) return null;
|
|
365
|
+
const activeHeading = headingOptions.find(
|
|
366
|
+
(option) => option.level ? editor.isActive("heading", { level: option.level }) : editor.isActive("paragraph")
|
|
367
|
+
) ?? headingOptions[0];
|
|
368
|
+
const readTime = wordCount === 0 ? 0 : Math.max(1, Math.ceil(wordCount / 200));
|
|
369
|
+
const selectHeading = (level) => {
|
|
370
|
+
const chain = editor.chain().focus();
|
|
371
|
+
if (level) chain.setHeading({ level }).run();
|
|
372
|
+
else chain.setParagraph().run();
|
|
373
|
+
setActivePopover(null);
|
|
374
|
+
};
|
|
375
|
+
const insertTable = () => {
|
|
376
|
+
editor.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run();
|
|
377
|
+
setActivePopover(null);
|
|
378
|
+
};
|
|
379
|
+
return /* @__PURE__ */ jsxs("div", { className: "relative w-full", children: [
|
|
380
|
+
/* @__PURE__ */ jsx("div", { className: "overflow-visible border-b border-border bg-card", children: /* @__PURE__ */ jsxs("div", { className: "flex min-h-11 flex-wrap items-center gap-1 px-4 py-1", children: [
|
|
381
|
+
/* @__PURE__ */ jsx(
|
|
382
|
+
IconButton,
|
|
383
|
+
{
|
|
384
|
+
icon: Undo2,
|
|
385
|
+
label: "Undo",
|
|
386
|
+
disabled: !editor.can().undo(),
|
|
387
|
+
onClick: () => editor.chain().focus().undo().run()
|
|
388
|
+
}
|
|
389
|
+
),
|
|
390
|
+
/* @__PURE__ */ jsx(
|
|
391
|
+
IconButton,
|
|
392
|
+
{
|
|
393
|
+
icon: Redo2,
|
|
394
|
+
label: "Redo",
|
|
395
|
+
disabled: !editor.can().redo(),
|
|
396
|
+
onClick: () => editor.chain().focus().redo().run()
|
|
397
|
+
}
|
|
398
|
+
),
|
|
399
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
400
|
+
/* @__PURE__ */ jsxs(
|
|
401
|
+
"div",
|
|
402
|
+
{
|
|
403
|
+
ref: activePopover === "heading" ? popoverRef : void 0,
|
|
404
|
+
className: "relative",
|
|
405
|
+
children: [
|
|
406
|
+
/* @__PURE__ */ jsxs(
|
|
407
|
+
"button",
|
|
408
|
+
{
|
|
409
|
+
type: "button",
|
|
410
|
+
className: "flex h-8 min-w-28 items-center justify-between gap-3 rounded border border-input bg-card px-3 text-sm font-medium hover:bg-muted",
|
|
411
|
+
"aria-haspopup": "menu",
|
|
412
|
+
"aria-expanded": activePopover === "heading",
|
|
413
|
+
onClick: () => setActivePopover(
|
|
414
|
+
(current) => current === "heading" ? null : "heading"
|
|
415
|
+
),
|
|
416
|
+
children: [
|
|
417
|
+
activeHeading.label,
|
|
418
|
+
/* @__PURE__ */ jsx(ChevronDown, { className: "size-4 text-muted-foreground" })
|
|
419
|
+
]
|
|
420
|
+
}
|
|
421
|
+
),
|
|
422
|
+
activePopover === "heading" && /* @__PURE__ */ jsx("div", { className: "te-popover absolute left-0 top-full z-30 mt-1 w-40 rounded-md p-1 shadow-lg", children: headingOptions.map((option) => /* @__PURE__ */ jsx(
|
|
423
|
+
"button",
|
|
424
|
+
{
|
|
425
|
+
type: "button",
|
|
426
|
+
role: "menuitem",
|
|
427
|
+
className: "flex w-full rounded px-2.5 py-2 text-left text-sm hover:bg-muted",
|
|
428
|
+
onClick: () => selectHeading(option.level),
|
|
429
|
+
children: option.label
|
|
430
|
+
},
|
|
431
|
+
option.value
|
|
432
|
+
)) })
|
|
433
|
+
]
|
|
434
|
+
}
|
|
435
|
+
),
|
|
436
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
437
|
+
/* @__PURE__ */ jsx(
|
|
438
|
+
IconButton,
|
|
439
|
+
{
|
|
440
|
+
icon: Bold,
|
|
441
|
+
label: "Bold",
|
|
442
|
+
active: editor.isActive("bold"),
|
|
443
|
+
onClick: () => editor.chain().focus().toggleBold().run()
|
|
444
|
+
}
|
|
445
|
+
),
|
|
446
|
+
/* @__PURE__ */ jsx(
|
|
447
|
+
IconButton,
|
|
448
|
+
{
|
|
449
|
+
icon: Italic,
|
|
450
|
+
label: "Italic",
|
|
451
|
+
active: editor.isActive("italic"),
|
|
452
|
+
onClick: () => editor.chain().focus().toggleItalic().run()
|
|
453
|
+
}
|
|
454
|
+
),
|
|
455
|
+
/* @__PURE__ */ jsx(
|
|
456
|
+
IconButton,
|
|
457
|
+
{
|
|
458
|
+
icon: Underline,
|
|
459
|
+
label: "Underline",
|
|
460
|
+
active: editor.isActive("underline"),
|
|
461
|
+
onClick: () => editor.chain().focus().toggleMark("underline").run()
|
|
462
|
+
}
|
|
463
|
+
),
|
|
464
|
+
/* @__PURE__ */ jsx(
|
|
465
|
+
IconButton,
|
|
466
|
+
{
|
|
467
|
+
icon: Code,
|
|
468
|
+
label: "Inline code",
|
|
469
|
+
active: editor.isActive("code"),
|
|
470
|
+
onClick: () => editor.chain().focus().toggleCode().run()
|
|
471
|
+
}
|
|
472
|
+
),
|
|
473
|
+
/* @__PURE__ */ jsx(
|
|
474
|
+
IconButton,
|
|
475
|
+
{
|
|
476
|
+
icon: Link$1,
|
|
477
|
+
label: "Insert link",
|
|
478
|
+
active: editor.isActive("link"),
|
|
479
|
+
onClick: () => setActivePopover(
|
|
480
|
+
(current) => current === "link" ? null : "link"
|
|
481
|
+
)
|
|
482
|
+
}
|
|
483
|
+
),
|
|
484
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
485
|
+
/* @__PURE__ */ jsx(
|
|
486
|
+
IconButton,
|
|
487
|
+
{
|
|
488
|
+
icon: List,
|
|
489
|
+
label: "Bullet list",
|
|
490
|
+
active: editor.isActive("bulletList"),
|
|
491
|
+
onClick: () => editor.chain().focus().toggleBulletList().run()
|
|
492
|
+
}
|
|
493
|
+
),
|
|
494
|
+
/* @__PURE__ */ jsx(
|
|
495
|
+
IconButton,
|
|
496
|
+
{
|
|
497
|
+
icon: ListOrdered,
|
|
498
|
+
label: "Numbered list",
|
|
499
|
+
active: editor.isActive("orderedList"),
|
|
500
|
+
onClick: () => editor.chain().focus().toggleOrderedList().run()
|
|
501
|
+
}
|
|
502
|
+
),
|
|
503
|
+
/* @__PURE__ */ jsx(
|
|
504
|
+
IconButton,
|
|
505
|
+
{
|
|
506
|
+
icon: Quote,
|
|
507
|
+
label: "Blockquote",
|
|
508
|
+
active: editor.isActive("blockquote"),
|
|
509
|
+
onClick: () => editor.chain().focus().toggleBlockquote().run()
|
|
510
|
+
}
|
|
511
|
+
),
|
|
512
|
+
/* @__PURE__ */ jsx(Divider, {}),
|
|
513
|
+
/* @__PURE__ */ jsxs(
|
|
514
|
+
"div",
|
|
515
|
+
{
|
|
516
|
+
ref: activePopover === "insert" ? popoverRef : void 0,
|
|
517
|
+
className: "relative",
|
|
518
|
+
children: [
|
|
519
|
+
/* @__PURE__ */ jsxs(
|
|
520
|
+
"button",
|
|
521
|
+
{
|
|
522
|
+
type: "button",
|
|
523
|
+
className: "flex h-8 items-center gap-1.5 rounded border border-primary px-2.5 text-sm font-semibold text-primary hover:bg-[var(--primary-soft)]",
|
|
524
|
+
"aria-haspopup": "menu",
|
|
525
|
+
"aria-expanded": activePopover === "insert",
|
|
526
|
+
onClick: () => setActivePopover(
|
|
527
|
+
(current) => current === "insert" ? null : "insert"
|
|
528
|
+
),
|
|
529
|
+
children: [
|
|
530
|
+
/* @__PURE__ */ jsx(Plus, { className: "size-4" }),
|
|
531
|
+
"Insert"
|
|
532
|
+
]
|
|
533
|
+
}
|
|
534
|
+
),
|
|
535
|
+
activePopover === "insert" && /* @__PURE__ */ jsxs("div", { className: "te-popover absolute right-0 top-full z-30 mt-1 w-52 rounded-md p-1 shadow-lg", children: [
|
|
536
|
+
/* @__PURE__ */ jsxs(
|
|
537
|
+
"button",
|
|
538
|
+
{
|
|
539
|
+
type: "button",
|
|
540
|
+
className: "flex w-full items-center gap-2 rounded px-2.5 py-2 text-sm hover:bg-muted",
|
|
541
|
+
onClick: () => {
|
|
542
|
+
editor.chain().focus().setHorizontalRule().run();
|
|
543
|
+
setActivePopover(null);
|
|
544
|
+
},
|
|
545
|
+
children: [
|
|
546
|
+
/* @__PURE__ */ jsx(Minus, { className: "size-4" }),
|
|
547
|
+
"Horizontal rule"
|
|
548
|
+
]
|
|
549
|
+
}
|
|
550
|
+
),
|
|
551
|
+
/* @__PURE__ */ jsxs(
|
|
552
|
+
"button",
|
|
553
|
+
{
|
|
554
|
+
type: "button",
|
|
555
|
+
className: "flex w-full items-center gap-2 rounded px-2.5 py-2 text-sm hover:bg-muted",
|
|
556
|
+
onClick: () => {
|
|
557
|
+
editor.chain().focus().toggleCodeBlock().run();
|
|
558
|
+
setActivePopover(null);
|
|
559
|
+
},
|
|
560
|
+
children: [
|
|
561
|
+
/* @__PURE__ */ jsx(Code, { className: "size-4" }),
|
|
562
|
+
"Code block"
|
|
563
|
+
]
|
|
564
|
+
}
|
|
565
|
+
),
|
|
566
|
+
/* @__PURE__ */ jsxs(
|
|
567
|
+
"button",
|
|
568
|
+
{
|
|
569
|
+
type: "button",
|
|
570
|
+
className: "flex w-full items-center gap-2 rounded px-2.5 py-2 text-sm hover:bg-muted disabled:opacity-50",
|
|
571
|
+
onClick: insertTable,
|
|
572
|
+
disabled: !editor.can().chain().focus().insertTable({
|
|
573
|
+
rows: 3,
|
|
574
|
+
cols: 3,
|
|
575
|
+
withHeaderRow: true
|
|
576
|
+
}).run(),
|
|
577
|
+
children: [
|
|
578
|
+
/* @__PURE__ */ jsx(Table$1, { className: "size-4" }),
|
|
579
|
+
"Table"
|
|
580
|
+
]
|
|
581
|
+
}
|
|
582
|
+
),
|
|
583
|
+
/* @__PURE__ */ jsxs(
|
|
584
|
+
"button",
|
|
585
|
+
{
|
|
586
|
+
type: "button",
|
|
587
|
+
className: "flex w-full items-center gap-2 rounded px-2.5 py-2 text-sm hover:bg-muted disabled:opacity-50",
|
|
588
|
+
disabled: isUploadingImage,
|
|
589
|
+
onClick: () => {
|
|
590
|
+
onRequestImageUpload();
|
|
591
|
+
setActivePopover(null);
|
|
592
|
+
},
|
|
593
|
+
children: [
|
|
594
|
+
/* @__PURE__ */ jsx(Image$1, { className: "size-4" }),
|
|
595
|
+
isUploadingImage ? "Uploading image\u2026" : "Image"
|
|
596
|
+
]
|
|
597
|
+
}
|
|
598
|
+
),
|
|
599
|
+
editor.isActive("table") && /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
600
|
+
/* @__PURE__ */ jsx("div", { className: "my-1 h-px bg-border" }),
|
|
601
|
+
/* @__PURE__ */ jsx(
|
|
602
|
+
"button",
|
|
603
|
+
{
|
|
604
|
+
type: "button",
|
|
605
|
+
className: "flex w-full rounded px-2.5 py-2 text-sm hover:bg-muted",
|
|
606
|
+
onClick: () => {
|
|
607
|
+
editor.chain().focus().addRowAfter().run();
|
|
608
|
+
setActivePopover(null);
|
|
609
|
+
},
|
|
610
|
+
children: "Add table row"
|
|
611
|
+
}
|
|
612
|
+
),
|
|
613
|
+
/* @__PURE__ */ jsx(
|
|
614
|
+
"button",
|
|
615
|
+
{
|
|
616
|
+
type: "button",
|
|
617
|
+
className: "flex w-full rounded px-2.5 py-2 text-sm hover:bg-muted",
|
|
618
|
+
onClick: () => {
|
|
619
|
+
editor.chain().focus().addColumnAfter().run();
|
|
620
|
+
setActivePopover(null);
|
|
621
|
+
},
|
|
622
|
+
children: "Add table column"
|
|
623
|
+
}
|
|
624
|
+
),
|
|
625
|
+
/* @__PURE__ */ jsxs(
|
|
626
|
+
"button",
|
|
627
|
+
{
|
|
628
|
+
type: "button",
|
|
629
|
+
className: "flex w-full items-center gap-2 rounded px-2.5 py-2 text-sm text-destructive hover:bg-destructive/10",
|
|
630
|
+
onClick: () => {
|
|
631
|
+
editor.chain().focus().deleteTable().run();
|
|
632
|
+
setActivePopover(null);
|
|
633
|
+
},
|
|
634
|
+
children: [
|
|
635
|
+
/* @__PURE__ */ jsx(Trash2, { className: "size-4" }),
|
|
636
|
+
"Remove table"
|
|
637
|
+
]
|
|
638
|
+
}
|
|
639
|
+
)
|
|
640
|
+
] })
|
|
641
|
+
] })
|
|
642
|
+
]
|
|
643
|
+
}
|
|
644
|
+
),
|
|
645
|
+
/* @__PURE__ */ jsxs("span", { className: "ml-auto hidden pl-8 text-xs tabular-nums text-muted-foreground xl:inline", children: [
|
|
646
|
+
wordCount.toLocaleString(),
|
|
647
|
+
" ",
|
|
648
|
+
wordCount === 1 ? "word" : "words",
|
|
649
|
+
/* @__PURE__ */ jsx("span", { "aria-hidden": true, className: "mx-2", children: "\xB7" }),
|
|
650
|
+
readTime,
|
|
651
|
+
" min"
|
|
652
|
+
] })
|
|
653
|
+
] }) }),
|
|
654
|
+
activePopover === "link" && /* @__PURE__ */ jsx(
|
|
655
|
+
link_editor_popover_default,
|
|
656
|
+
{
|
|
657
|
+
editor,
|
|
658
|
+
onClose: () => setActivePopover(null),
|
|
659
|
+
containerRef: popoverRef
|
|
660
|
+
}
|
|
661
|
+
)
|
|
662
|
+
] });
|
|
663
|
+
};
|
|
664
|
+
var toolbar_default = Toolbar;
|
|
665
|
+
var buttonBase = "inline-flex h-8 w-8 items-center justify-center rounded text-foreground transition-colors focus-visible:outline focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-primary";
|
|
666
|
+
var TipTapBubbleMenu = ({
|
|
667
|
+
editor,
|
|
668
|
+
className,
|
|
669
|
+
children,
|
|
670
|
+
pluginKey = "inlineBubbleMenu",
|
|
671
|
+
updateDelay,
|
|
672
|
+
resizeDelay,
|
|
673
|
+
shouldShow,
|
|
674
|
+
appendTo,
|
|
675
|
+
getReferencedVirtualElement,
|
|
676
|
+
options
|
|
677
|
+
}) => {
|
|
678
|
+
const menuRef = useRef(null);
|
|
679
|
+
useEffect(() => {
|
|
680
|
+
const element = menuRef.current;
|
|
681
|
+
if (!editor || editor.isDestroyed || !element) {
|
|
682
|
+
return;
|
|
683
|
+
}
|
|
684
|
+
const plugin = BubbleMenuPlugin({
|
|
685
|
+
editor,
|
|
686
|
+
element,
|
|
687
|
+
pluginKey,
|
|
688
|
+
updateDelay,
|
|
689
|
+
resizeDelay,
|
|
690
|
+
shouldShow: shouldShow ?? null,
|
|
691
|
+
appendTo,
|
|
692
|
+
getReferencedVirtualElement,
|
|
693
|
+
options
|
|
694
|
+
});
|
|
695
|
+
editor.registerPlugin(plugin);
|
|
696
|
+
return () => {
|
|
697
|
+
editor.unregisterPlugin(pluginKey);
|
|
698
|
+
};
|
|
699
|
+
}, [
|
|
700
|
+
editor,
|
|
701
|
+
pluginKey,
|
|
702
|
+
updateDelay,
|
|
703
|
+
resizeDelay,
|
|
704
|
+
shouldShow,
|
|
705
|
+
appendTo,
|
|
706
|
+
getReferencedVirtualElement,
|
|
707
|
+
options
|
|
708
|
+
]);
|
|
709
|
+
return /* @__PURE__ */ jsx(
|
|
710
|
+
"div",
|
|
711
|
+
{
|
|
712
|
+
ref: menuRef,
|
|
713
|
+
className,
|
|
714
|
+
style: { visibility: "hidden" },
|
|
715
|
+
children
|
|
716
|
+
}
|
|
717
|
+
);
|
|
718
|
+
};
|
|
719
|
+
function InlineButton({
|
|
720
|
+
icon: Icon,
|
|
721
|
+
label,
|
|
722
|
+
active,
|
|
723
|
+
onClick
|
|
724
|
+
}) {
|
|
725
|
+
return /* @__PURE__ */ jsx(
|
|
726
|
+
"button",
|
|
727
|
+
{
|
|
728
|
+
type: "button",
|
|
729
|
+
"aria-label": label,
|
|
730
|
+
title: label,
|
|
731
|
+
className: `${buttonBase} ${active ? "bg-primary text-primary-foreground hover:bg-primary/90" : "bg-transparent hover:bg-muted"}`,
|
|
732
|
+
onClick,
|
|
733
|
+
children: /* @__PURE__ */ jsx(Icon, { className: "h-4 w-4" })
|
|
734
|
+
}
|
|
735
|
+
);
|
|
736
|
+
}
|
|
737
|
+
var InlineBubbleMenu = ({
|
|
738
|
+
editor
|
|
739
|
+
}) => {
|
|
740
|
+
const [isLinkEditing, setIsLinkEditing] = useState(false);
|
|
741
|
+
const openLinkEditor = useCallback(() => {
|
|
742
|
+
setIsLinkEditing((prev) => !prev);
|
|
743
|
+
}, []);
|
|
744
|
+
useEffect(() => {
|
|
745
|
+
const handleSelectionUpdate = () => {
|
|
746
|
+
setIsLinkEditing(false);
|
|
747
|
+
};
|
|
748
|
+
editor.on("selectionUpdate", handleSelectionUpdate);
|
|
749
|
+
return () => {
|
|
750
|
+
editor.off("selectionUpdate", handleSelectionUpdate);
|
|
751
|
+
};
|
|
752
|
+
}, [editor]);
|
|
753
|
+
if (!editor) {
|
|
754
|
+
return null;
|
|
755
|
+
}
|
|
756
|
+
return /* @__PURE__ */ jsxs(
|
|
757
|
+
TipTapBubbleMenu,
|
|
758
|
+
{
|
|
759
|
+
editor,
|
|
760
|
+
className: "bubble-menu flex flex-col gap-1 rounded-md border border-border bg-popover p-2 text-popover-foreground shadow-xl",
|
|
761
|
+
children: [
|
|
762
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-1", children: [
|
|
763
|
+
/* @__PURE__ */ jsx(
|
|
764
|
+
InlineButton,
|
|
765
|
+
{
|
|
766
|
+
icon: Bold,
|
|
767
|
+
label: "Bold",
|
|
768
|
+
active: editor.isActive("bold"),
|
|
769
|
+
onClick: () => editor.chain().focus().toggleBold().run()
|
|
770
|
+
}
|
|
771
|
+
),
|
|
772
|
+
/* @__PURE__ */ jsx(
|
|
773
|
+
InlineButton,
|
|
774
|
+
{
|
|
775
|
+
icon: Italic,
|
|
776
|
+
label: "Italic",
|
|
777
|
+
active: editor.isActive("italic"),
|
|
778
|
+
onClick: () => editor.chain().focus().toggleItalic().run()
|
|
779
|
+
}
|
|
780
|
+
),
|
|
781
|
+
/* @__PURE__ */ jsx(
|
|
782
|
+
InlineButton,
|
|
783
|
+
{
|
|
784
|
+
icon: Underline,
|
|
785
|
+
label: "Underline",
|
|
786
|
+
active: editor.isActive("underline"),
|
|
787
|
+
onClick: () => editor.chain().focus().toggleMark("underline").run()
|
|
788
|
+
}
|
|
789
|
+
),
|
|
790
|
+
/* @__PURE__ */ jsx(
|
|
791
|
+
InlineButton,
|
|
792
|
+
{
|
|
793
|
+
icon: Strikethrough,
|
|
794
|
+
label: "Strikethrough",
|
|
795
|
+
active: editor.isActive("strike"),
|
|
796
|
+
onClick: () => editor.chain().focus().toggleStrike().run()
|
|
797
|
+
}
|
|
798
|
+
),
|
|
799
|
+
/* @__PURE__ */ jsx(
|
|
800
|
+
InlineButton,
|
|
801
|
+
{
|
|
802
|
+
icon: Code,
|
|
803
|
+
label: "Inline code",
|
|
804
|
+
active: editor.isActive("code"),
|
|
805
|
+
onClick: () => editor.chain().focus().toggleCode().run()
|
|
806
|
+
}
|
|
807
|
+
),
|
|
808
|
+
/* @__PURE__ */ jsx(
|
|
809
|
+
InlineButton,
|
|
810
|
+
{
|
|
811
|
+
icon: Link$1,
|
|
812
|
+
label: "Insert link",
|
|
813
|
+
active: editor.isActive("link"),
|
|
814
|
+
onClick: openLinkEditor
|
|
815
|
+
}
|
|
816
|
+
)
|
|
817
|
+
] }),
|
|
818
|
+
isLinkEditing && /* @__PURE__ */ jsx(
|
|
819
|
+
link_editor_popover_default,
|
|
820
|
+
{
|
|
821
|
+
editor,
|
|
822
|
+
onClose: () => setIsLinkEditing(false),
|
|
823
|
+
variant: "inline"
|
|
824
|
+
}
|
|
825
|
+
)
|
|
826
|
+
]
|
|
827
|
+
}
|
|
828
|
+
);
|
|
829
|
+
};
|
|
830
|
+
var bubble_menu_default = InlineBubbleMenu;
|
|
831
|
+
function cn(...inputs) {
|
|
832
|
+
return twMerge(clsx(inputs));
|
|
833
|
+
}
|
|
834
|
+
var ImagePickerContext = createContext(null);
|
|
835
|
+
function ImagePickerContextProvider({
|
|
836
|
+
pickImage,
|
|
837
|
+
children
|
|
838
|
+
}) {
|
|
839
|
+
return /* @__PURE__ */ jsx(ImagePickerContext.Provider, { value: pickImage, children });
|
|
840
|
+
}
|
|
841
|
+
function useImagePicker() {
|
|
842
|
+
return useContext(ImagePickerContext);
|
|
843
|
+
}
|
|
844
|
+
var Editor = Object.assign(
|
|
845
|
+
({
|
|
846
|
+
initialContent,
|
|
847
|
+
onChange,
|
|
848
|
+
placeholder,
|
|
849
|
+
children,
|
|
850
|
+
showToolbar = true,
|
|
851
|
+
editable = true,
|
|
852
|
+
refresh,
|
|
853
|
+
url,
|
|
854
|
+
autoFocus,
|
|
855
|
+
imageSizeLimit,
|
|
856
|
+
onError,
|
|
857
|
+
className,
|
|
858
|
+
editorClassName,
|
|
859
|
+
contentClassName,
|
|
860
|
+
beforeContent
|
|
861
|
+
}) => {
|
|
862
|
+
const [isUploading, setIsUploading] = useState(false);
|
|
863
|
+
const fileInputRef = useRef(null);
|
|
864
|
+
const pickImage = useImagePicker();
|
|
865
|
+
const hasInteractedRef = useRef(false);
|
|
866
|
+
const lastSerializedContentRef = useRef(
|
|
867
|
+
JSON.stringify(initialContent ?? empty_doc_default)
|
|
868
|
+
);
|
|
869
|
+
const refreshRef = useRef(refresh);
|
|
870
|
+
const extensions = useMemo(
|
|
871
|
+
() => createExtensions({ placeholder }),
|
|
872
|
+
[placeholder]
|
|
873
|
+
);
|
|
874
|
+
const changeTimeoutRef = useRef(
|
|
875
|
+
null
|
|
876
|
+
);
|
|
877
|
+
const editor = useEditor(
|
|
878
|
+
{
|
|
879
|
+
immediatelyRender: false,
|
|
880
|
+
extensions,
|
|
881
|
+
content: initialContent ?? empty_doc_default,
|
|
882
|
+
autofocus: autoFocus ? "end" : false,
|
|
883
|
+
editable,
|
|
884
|
+
editorProps: {
|
|
885
|
+
attributes: {
|
|
886
|
+
class: `tiptap-content ${editorClassName ?? ""}`,
|
|
887
|
+
"data-placeholder": placeholder ?? ""
|
|
888
|
+
}
|
|
889
|
+
},
|
|
890
|
+
onUpdate: ({ editor: instance }) => {
|
|
891
|
+
if (!hasInteractedRef.current) {
|
|
892
|
+
return;
|
|
893
|
+
}
|
|
894
|
+
if (changeTimeoutRef.current) {
|
|
895
|
+
clearTimeout(changeTimeoutRef.current);
|
|
896
|
+
}
|
|
897
|
+
changeTimeoutRef.current = setTimeout(() => {
|
|
898
|
+
onChange(instance.getJSON());
|
|
899
|
+
changeTimeoutRef.current = null;
|
|
900
|
+
}, 1e3);
|
|
901
|
+
},
|
|
902
|
+
onSelectionUpdate: ({ editor: instance }) => {
|
|
903
|
+
hasInteractedRef.current = true;
|
|
904
|
+
}
|
|
905
|
+
},
|
|
906
|
+
[extensions]
|
|
907
|
+
);
|
|
908
|
+
useEffect(() => {
|
|
909
|
+
return () => {
|
|
910
|
+
if (changeTimeoutRef.current) {
|
|
911
|
+
clearTimeout(changeTimeoutRef.current);
|
|
912
|
+
}
|
|
913
|
+
};
|
|
914
|
+
}, []);
|
|
915
|
+
useEffect(() => {
|
|
916
|
+
if (!editor) {
|
|
917
|
+
return;
|
|
918
|
+
}
|
|
919
|
+
editor.setEditable(editable);
|
|
920
|
+
}, [editor, editable]);
|
|
921
|
+
useEffect(() => {
|
|
922
|
+
if (!editor) {
|
|
923
|
+
return;
|
|
924
|
+
}
|
|
925
|
+
const nextContent = initialContent ?? empty_doc_default;
|
|
926
|
+
const nextSerialized = JSON.stringify(nextContent);
|
|
927
|
+
const refreshChanged = refreshRef.current !== refresh;
|
|
928
|
+
if (refreshChanged) {
|
|
929
|
+
refreshRef.current = refresh;
|
|
930
|
+
hasInteractedRef.current = false;
|
|
931
|
+
lastSerializedContentRef.current = nextSerialized;
|
|
932
|
+
editor.commands.setContent(nextContent, {
|
|
933
|
+
emitUpdate: false
|
|
934
|
+
});
|
|
935
|
+
return;
|
|
936
|
+
}
|
|
937
|
+
if (!hasInteractedRef.current) {
|
|
938
|
+
if (lastSerializedContentRef.current !== nextSerialized) {
|
|
939
|
+
lastSerializedContentRef.current = nextSerialized;
|
|
940
|
+
editor.commands.setContent(nextContent, {
|
|
941
|
+
emitUpdate: false
|
|
942
|
+
});
|
|
943
|
+
}
|
|
944
|
+
return;
|
|
945
|
+
}
|
|
946
|
+
lastSerializedContentRef.current = nextSerialized;
|
|
947
|
+
}, [editor, initialContent, refresh]);
|
|
948
|
+
const handleImageUpload = useCallback(
|
|
949
|
+
async (file) => {
|
|
950
|
+
const sizeLimit = imageSizeLimit ?? 2097152;
|
|
951
|
+
if (file.size > sizeLimit) {
|
|
952
|
+
onError?.("File is larger than the allowed size");
|
|
953
|
+
return;
|
|
954
|
+
}
|
|
955
|
+
try {
|
|
956
|
+
setIsUploading(true);
|
|
957
|
+
const dataUrl = await new Promise(
|
|
958
|
+
(resolve, reject) => {
|
|
959
|
+
const reader = new FileReader();
|
|
960
|
+
reader.onload = () => resolve(reader.result);
|
|
961
|
+
reader.onerror = reject;
|
|
962
|
+
reader.readAsDataURL(file);
|
|
963
|
+
}
|
|
964
|
+
);
|
|
965
|
+
editor?.chain().focus().setImage({
|
|
966
|
+
src: dataUrl,
|
|
967
|
+
alt: file.name,
|
|
968
|
+
title: file.name
|
|
969
|
+
}).run();
|
|
970
|
+
} catch (error) {
|
|
971
|
+
console.error("Image upload failed", error);
|
|
972
|
+
} finally {
|
|
973
|
+
setIsUploading(false);
|
|
974
|
+
}
|
|
975
|
+
},
|
|
976
|
+
[editor]
|
|
977
|
+
);
|
|
978
|
+
const handleSelectFile = useCallback(
|
|
979
|
+
(event) => {
|
|
980
|
+
const files = event.target.files;
|
|
981
|
+
if (!files || files.length === 0) {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
const file = files[0];
|
|
985
|
+
void handleImageUpload(file);
|
|
986
|
+
event.target.value = "";
|
|
987
|
+
},
|
|
988
|
+
[handleImageUpload]
|
|
989
|
+
);
|
|
990
|
+
const insertPickedImage = useCallback(
|
|
991
|
+
(image) => {
|
|
992
|
+
editor?.chain().focus().setImage({
|
|
993
|
+
src: image.src,
|
|
994
|
+
alt: image.alt,
|
|
995
|
+
title: image.alt
|
|
996
|
+
}).run();
|
|
997
|
+
},
|
|
998
|
+
[editor]
|
|
999
|
+
);
|
|
1000
|
+
const openFileDialog = useCallback(() => {
|
|
1001
|
+
if (pickImage) {
|
|
1002
|
+
void pickImage().then((image) => {
|
|
1003
|
+
if (image) insertPickedImage(image);
|
|
1004
|
+
});
|
|
1005
|
+
return;
|
|
1006
|
+
}
|
|
1007
|
+
fileInputRef.current?.click();
|
|
1008
|
+
}, [insertPickedImage, pickImage]);
|
|
1009
|
+
if (typeof window === "undefined") {
|
|
1010
|
+
return /* @__PURE__ */ jsx(Fragment, {});
|
|
1011
|
+
}
|
|
1012
|
+
return /* @__PURE__ */ jsxs(
|
|
1013
|
+
"div",
|
|
1014
|
+
{
|
|
1015
|
+
className: cn(
|
|
1016
|
+
"tiptap-editor flex flex-col gap-4 border",
|
|
1017
|
+
className
|
|
1018
|
+
),
|
|
1019
|
+
children: [
|
|
1020
|
+
editable && showToolbar && /* @__PURE__ */ jsx(
|
|
1021
|
+
"div",
|
|
1022
|
+
{
|
|
1023
|
+
className: "sticky top-0 bg-background",
|
|
1024
|
+
style: {
|
|
1025
|
+
zIndex: 1
|
|
1026
|
+
},
|
|
1027
|
+
children: /* @__PURE__ */ jsx(
|
|
1028
|
+
toolbar_default,
|
|
1029
|
+
{
|
|
1030
|
+
editor,
|
|
1031
|
+
onRequestImageUpload: openFileDialog,
|
|
1032
|
+
isUploadingImage: isUploading
|
|
1033
|
+
}
|
|
1034
|
+
)
|
|
1035
|
+
}
|
|
1036
|
+
),
|
|
1037
|
+
/* @__PURE__ */ jsx("div", { className: "flex w-full justify-center", children: /* @__PURE__ */ jsxs(
|
|
1038
|
+
"div",
|
|
1039
|
+
{
|
|
1040
|
+
className: cn(
|
|
1041
|
+
"flex w-full max-w-3xl flex-col",
|
|
1042
|
+
contentClassName
|
|
1043
|
+
),
|
|
1044
|
+
children: [
|
|
1045
|
+
beforeContent,
|
|
1046
|
+
/* @__PURE__ */ jsx(
|
|
1047
|
+
EditorContent,
|
|
1048
|
+
{
|
|
1049
|
+
editor,
|
|
1050
|
+
className: `${editorClassName ?? ""} ${editorClassName?.includes("min-h-") ? "" : "min-h-[200px]"}`
|
|
1051
|
+
}
|
|
1052
|
+
),
|
|
1053
|
+
pickImage ? null : /* @__PURE__ */ jsx(
|
|
1054
|
+
"input",
|
|
1055
|
+
{
|
|
1056
|
+
ref: fileInputRef,
|
|
1057
|
+
type: "file",
|
|
1058
|
+
accept: "image/*",
|
|
1059
|
+
className: "hidden",
|
|
1060
|
+
onChange: handleSelectFile
|
|
1061
|
+
}
|
|
1062
|
+
),
|
|
1063
|
+
children,
|
|
1064
|
+
editor && /* @__PURE__ */ jsx(bubble_menu_default, { editor })
|
|
1065
|
+
]
|
|
1066
|
+
}
|
|
1067
|
+
) })
|
|
1068
|
+
]
|
|
1069
|
+
}
|
|
1070
|
+
);
|
|
1071
|
+
},
|
|
1072
|
+
{
|
|
1073
|
+
getPlainText: (doc) => generateText(doc || empty_doc_default, createExtensions()),
|
|
1074
|
+
emptyDoc: empty_doc_default
|
|
1075
|
+
}
|
|
1076
|
+
);
|
|
1077
|
+
var editor_default = Editor;
|
|
1078
|
+
|
|
1079
|
+
// src/extract-headings.ts
|
|
1080
|
+
function extractHeadings(json2) {
|
|
1081
|
+
const headings = [];
|
|
1082
|
+
const collectHeadings = (node) => {
|
|
1083
|
+
if (!node) {
|
|
1084
|
+
return;
|
|
1085
|
+
}
|
|
1086
|
+
if (node.type === "heading") {
|
|
1087
|
+
const level = typeof node.attrs?.level === "number" ? node.attrs.level : 1;
|
|
1088
|
+
const collectText = (current) => {
|
|
1089
|
+
if (!current) {
|
|
1090
|
+
return "";
|
|
1091
|
+
}
|
|
1092
|
+
if (typeof current.text === "string") {
|
|
1093
|
+
return current.text;
|
|
1094
|
+
}
|
|
1095
|
+
if (!current.content) {
|
|
1096
|
+
return "";
|
|
1097
|
+
}
|
|
1098
|
+
return current.content.map(collectText).join("");
|
|
1099
|
+
};
|
|
1100
|
+
const textContent = collectText(node);
|
|
1101
|
+
headings.push({
|
|
1102
|
+
level,
|
|
1103
|
+
text: textContent,
|
|
1104
|
+
id: createId(textContent)
|
|
1105
|
+
});
|
|
1106
|
+
}
|
|
1107
|
+
if (Array.isArray(node.content)) {
|
|
1108
|
+
node.content.forEach((child) => {
|
|
1109
|
+
collectHeadings(child);
|
|
1110
|
+
});
|
|
1111
|
+
}
|
|
1112
|
+
};
|
|
1113
|
+
collectHeadings(json2 || void 0);
|
|
1114
|
+
return headings;
|
|
1115
|
+
}
|
|
1116
|
+
|
|
1117
|
+
// src/extract-text-from-node.ts
|
|
1118
|
+
function extractTextFromNode(node) {
|
|
1119
|
+
if (!node) {
|
|
1120
|
+
return "";
|
|
1121
|
+
}
|
|
1122
|
+
if (typeof node.textContent === "string") {
|
|
1123
|
+
return node.textContent;
|
|
1124
|
+
}
|
|
1125
|
+
if (node.content && node.content.content && Array.isArray(node.content.content)) {
|
|
1126
|
+
return node.content.content.map((child) => {
|
|
1127
|
+
if (child.text) {
|
|
1128
|
+
return child.text;
|
|
1129
|
+
}
|
|
1130
|
+
return extractTextFromNode(child);
|
|
1131
|
+
}).join("");
|
|
1132
|
+
}
|
|
1133
|
+
if (Array.isArray(node.content)) {
|
|
1134
|
+
return node.content.map((child) => {
|
|
1135
|
+
if (child.text) {
|
|
1136
|
+
return child.text;
|
|
1137
|
+
}
|
|
1138
|
+
return extractTextFromNode(child);
|
|
1139
|
+
}).join("");
|
|
1140
|
+
}
|
|
1141
|
+
return "";
|
|
1142
|
+
}
|
|
1143
|
+
function textFromChildren(node) {
|
|
1144
|
+
if (node == null || typeof node === "boolean") return "";
|
|
1145
|
+
if (typeof node === "string" || typeof node === "number") {
|
|
1146
|
+
return String(node);
|
|
1147
|
+
}
|
|
1148
|
+
if (Array.isArray(node)) return node.map(textFromChildren).join("");
|
|
1149
|
+
if (React5.isValidElement(node)) {
|
|
1150
|
+
return textFromChildren(node.props.children);
|
|
1151
|
+
}
|
|
1152
|
+
return "";
|
|
1153
|
+
}
|
|
1154
|
+
async function writeClipboard(text) {
|
|
1155
|
+
if (navigator.clipboard?.writeText) {
|
|
1156
|
+
await navigator.clipboard.writeText(text);
|
|
1157
|
+
return;
|
|
1158
|
+
}
|
|
1159
|
+
const area = document.createElement("textarea");
|
|
1160
|
+
area.value = text;
|
|
1161
|
+
area.setAttribute("readonly", "");
|
|
1162
|
+
area.style.position = "fixed";
|
|
1163
|
+
area.style.left = "-9999px";
|
|
1164
|
+
document.body.appendChild(area);
|
|
1165
|
+
area.select();
|
|
1166
|
+
document.execCommand("copy");
|
|
1167
|
+
area.remove();
|
|
1168
|
+
}
|
|
1169
|
+
function RenderedCodeBlock({
|
|
1170
|
+
node,
|
|
1171
|
+
children,
|
|
1172
|
+
language
|
|
1173
|
+
}) {
|
|
1174
|
+
const [copied, setCopied] = useState(false);
|
|
1175
|
+
const source = extractTextFromNode(node) || textFromChildren(children);
|
|
1176
|
+
useEffect(() => {
|
|
1177
|
+
if (!copied) return;
|
|
1178
|
+
const timer = window.setTimeout(() => setCopied(false), 1500);
|
|
1179
|
+
return () => window.clearTimeout(timer);
|
|
1180
|
+
}, [copied]);
|
|
1181
|
+
async function handleCopy() {
|
|
1182
|
+
if (!source) return;
|
|
1183
|
+
try {
|
|
1184
|
+
await writeClipboard(source);
|
|
1185
|
+
setCopied(true);
|
|
1186
|
+
} catch {
|
|
1187
|
+
setCopied(false);
|
|
1188
|
+
}
|
|
1189
|
+
}
|
|
1190
|
+
return /* @__PURE__ */ jsxs("div", { className: "te-code-block", children: [
|
|
1191
|
+
/* @__PURE__ */ jsx("pre", { children: /* @__PURE__ */ jsx("code", { className: language ? `language-${language}` : void 0, children }) }),
|
|
1192
|
+
/* @__PURE__ */ jsx(
|
|
1193
|
+
"button",
|
|
1194
|
+
{
|
|
1195
|
+
type: "button",
|
|
1196
|
+
className: "te-code-copy",
|
|
1197
|
+
"aria-label": copied ? "Copied" : "Copy code",
|
|
1198
|
+
title: copied ? "Copied" : "Copy",
|
|
1199
|
+
onClick: () => {
|
|
1200
|
+
void handleCopy();
|
|
1201
|
+
},
|
|
1202
|
+
children: copied ? /* @__PURE__ */ jsx(Check, { className: "te-code-copy__icon", "aria-hidden": true }) : /* @__PURE__ */ jsx(Copy, { className: "te-code-copy__icon", "aria-hidden": true })
|
|
1203
|
+
}
|
|
1204
|
+
)
|
|
1205
|
+
] });
|
|
1206
|
+
}
|
|
1207
|
+
var typographyClasses = (typography) => [
|
|
1208
|
+
typography?.fontFamily,
|
|
1209
|
+
typography?.fontSize,
|
|
1210
|
+
typography?.fontWeight,
|
|
1211
|
+
typography?.lineHeight,
|
|
1212
|
+
typography?.letterSpacing,
|
|
1213
|
+
typography?.textTransform,
|
|
1214
|
+
typography?.textDecoration,
|
|
1215
|
+
typography?.textOverflow
|
|
1216
|
+
];
|
|
1217
|
+
function removeEmptyTextNodes(node) {
|
|
1218
|
+
if (!node || typeof node !== "object") {
|
|
1219
|
+
return node;
|
|
1220
|
+
}
|
|
1221
|
+
if (node.type === "text" && node.text === "") {
|
|
1222
|
+
return void 0;
|
|
1223
|
+
}
|
|
1224
|
+
if (!Array.isArray(node.content)) {
|
|
1225
|
+
return node;
|
|
1226
|
+
}
|
|
1227
|
+
const content = node.content.map(removeEmptyTextNodes).filter((child) => child !== void 0);
|
|
1228
|
+
return {
|
|
1229
|
+
...node,
|
|
1230
|
+
content
|
|
1231
|
+
};
|
|
1232
|
+
}
|
|
1233
|
+
function TextRenderer({ json: json2, className, theme }) {
|
|
1234
|
+
const extensions = createExtensions();
|
|
1235
|
+
const content = removeEmptyTextNodes(
|
|
1236
|
+
json2 ?? empty_doc_default
|
|
1237
|
+
);
|
|
1238
|
+
const rendered = renderToReactElement({
|
|
1239
|
+
extensions,
|
|
1240
|
+
content,
|
|
1241
|
+
options: {
|
|
1242
|
+
nodeMapping: {
|
|
1243
|
+
paragraph: ({ children }) => {
|
|
1244
|
+
const classes = cn(
|
|
1245
|
+
"text-base",
|
|
1246
|
+
...typographyClasses(theme?.typography?.text1)
|
|
1247
|
+
);
|
|
1248
|
+
return /* @__PURE__ */ jsx("p", { className: classes, children });
|
|
1249
|
+
},
|
|
1250
|
+
hardBreak: () => /* @__PURE__ */ jsx("br", {}),
|
|
1251
|
+
heading: ({ node, children }) => {
|
|
1252
|
+
const level = node?.attrs?.level ?? 1;
|
|
1253
|
+
const textContent = extractTextFromNode(node);
|
|
1254
|
+
const id = createId(textContent);
|
|
1255
|
+
if (!theme) {
|
|
1256
|
+
const Tag = `h${level}`;
|
|
1257
|
+
return /* @__PURE__ */ jsx(Tag, { id, children });
|
|
1258
|
+
}
|
|
1259
|
+
if (level === 1) {
|
|
1260
|
+
const classes2 = cn(
|
|
1261
|
+
"scroll-m-20 text-4xl font-bold tracking-tight",
|
|
1262
|
+
...typographyClasses(theme?.typography?.header1)
|
|
1263
|
+
);
|
|
1264
|
+
return /* @__PURE__ */ jsx("h1", { id, className: classes2, children });
|
|
1265
|
+
}
|
|
1266
|
+
if (level === 2) {
|
|
1267
|
+
const classes2 = cn(
|
|
1268
|
+
"scroll-m-20 text-3xl font-bold tracking-tight",
|
|
1269
|
+
...typographyClasses(theme?.typography?.header2)
|
|
1270
|
+
);
|
|
1271
|
+
return /* @__PURE__ */ jsx("h2", { id, className: classes2, children });
|
|
1272
|
+
}
|
|
1273
|
+
const classes = cn(
|
|
1274
|
+
"scroll-m-20 text-2xl font-semibold tracking-normal",
|
|
1275
|
+
...typographyClasses(theme?.typography?.header3)
|
|
1276
|
+
);
|
|
1277
|
+
return /* @__PURE__ */ jsx("h3", { id, className: classes, children });
|
|
1278
|
+
},
|
|
1279
|
+
codeBlock: ({ node, children }) => /* @__PURE__ */ jsx(
|
|
1280
|
+
RenderedCodeBlock,
|
|
1281
|
+
{
|
|
1282
|
+
node,
|
|
1283
|
+
language: node?.attrs?.language,
|
|
1284
|
+
children
|
|
1285
|
+
}
|
|
1286
|
+
),
|
|
1287
|
+
codeMirror: ({ node, children }) => /* @__PURE__ */ jsx(RenderedCodeBlock, { node, children })
|
|
1288
|
+
},
|
|
1289
|
+
markMapping: {
|
|
1290
|
+
link: ({ mark, children }) => {
|
|
1291
|
+
const href = mark?.attrs?.href;
|
|
1292
|
+
const linkStyle = theme?.interactives?.link;
|
|
1293
|
+
const classes = cn(
|
|
1294
|
+
"font-medium cursor-pointer",
|
|
1295
|
+
...typographyClasses(theme?.typography?.link),
|
|
1296
|
+
linkStyle?.padding?.x,
|
|
1297
|
+
linkStyle?.padding?.y,
|
|
1298
|
+
linkStyle?.border?.width,
|
|
1299
|
+
linkStyle?.border?.radius,
|
|
1300
|
+
linkStyle?.border?.style,
|
|
1301
|
+
linkStyle?.custom
|
|
1302
|
+
);
|
|
1303
|
+
return /* @__PURE__ */ jsx(
|
|
1304
|
+
"a",
|
|
1305
|
+
{
|
|
1306
|
+
href,
|
|
1307
|
+
target: "_blank",
|
|
1308
|
+
rel: "noopener noreferrer",
|
|
1309
|
+
className: classes,
|
|
1310
|
+
style: {
|
|
1311
|
+
textShadow: linkStyle?.textShadow || void 0
|
|
1312
|
+
},
|
|
1313
|
+
children
|
|
1314
|
+
}
|
|
1315
|
+
);
|
|
1316
|
+
},
|
|
1317
|
+
highlight: ({ children }) => /* @__PURE__ */ jsx("mark", { className: "bg-accent text-accent-foreground", children })
|
|
1318
|
+
}
|
|
1319
|
+
}
|
|
1320
|
+
});
|
|
1321
|
+
const combinedClassName = ["tiptap-renderer", className].filter(Boolean).join(" ");
|
|
1322
|
+
return /* @__PURE__ */ jsx("div", { className: "text-editor flex flex-col gap-4", children: /* @__PURE__ */ jsx("div", { className: combinedClassName, children: rendered }) });
|
|
1323
|
+
}
|
|
1324
|
+
|
|
1325
|
+
// src/normalize-text-editor-content.ts
|
|
1326
|
+
function createParagraphNode(text) {
|
|
1327
|
+
const lines = text.split("\n");
|
|
1328
|
+
const content = [];
|
|
1329
|
+
lines.forEach((line, index) => {
|
|
1330
|
+
if (line) {
|
|
1331
|
+
content.push({
|
|
1332
|
+
type: "text",
|
|
1333
|
+
text: line
|
|
1334
|
+
});
|
|
1335
|
+
}
|
|
1336
|
+
if (index < lines.length - 1) {
|
|
1337
|
+
content.push({
|
|
1338
|
+
type: "hardBreak"
|
|
1339
|
+
});
|
|
1340
|
+
}
|
|
1341
|
+
});
|
|
1342
|
+
return {
|
|
1343
|
+
type: "paragraph",
|
|
1344
|
+
...content.length ? { content } : {}
|
|
1345
|
+
};
|
|
1346
|
+
}
|
|
1347
|
+
function normalizeTextEditorContent(json2) {
|
|
1348
|
+
if (json2 && typeof json2 === "object" && "type" in json2 && json2.type === "doc") {
|
|
1349
|
+
return json2;
|
|
1350
|
+
}
|
|
1351
|
+
const text = typeof json2 === "string" ? json2.replace(/\r\n?/g, "\n") : "";
|
|
1352
|
+
return {
|
|
1353
|
+
type: "doc",
|
|
1354
|
+
content: text ? text.split(/\n{2,}/).map((paragraph) => createParagraphNode(paragraph)) : []
|
|
1355
|
+
};
|
|
1356
|
+
}
|
|
1357
|
+
|
|
1358
|
+
// src/extract-text-from-text-editor-content.ts
|
|
1359
|
+
function extractTextFromTextEditorContent(content) {
|
|
1360
|
+
if (typeof content === "string") {
|
|
1361
|
+
return content;
|
|
1362
|
+
}
|
|
1363
|
+
if (!content || typeof content !== "object") {
|
|
1364
|
+
return "";
|
|
1365
|
+
}
|
|
1366
|
+
const record = content;
|
|
1367
|
+
const ownText = typeof record.text === "string" ? record.text : "";
|
|
1368
|
+
const children = Array.isArray(record.content) ? record.content.map(extractTextFromTextEditorContent).join("") : "";
|
|
1369
|
+
return `${ownText}${children}`;
|
|
1370
|
+
}
|
|
1371
|
+
|
|
1372
|
+
// src/truncate-text-editor-content.ts
|
|
1373
|
+
function appendEllipsisToLastTextNode(nodes) {
|
|
1374
|
+
for (let i = nodes.length - 1; i >= 0; i--) {
|
|
1375
|
+
const node = nodes[i];
|
|
1376
|
+
if (!node) {
|
|
1377
|
+
continue;
|
|
1378
|
+
}
|
|
1379
|
+
if (node.type === "text" && typeof node.text === "string") {
|
|
1380
|
+
if (!node.text.endsWith("...")) {
|
|
1381
|
+
nodes[i] = {
|
|
1382
|
+
...node,
|
|
1383
|
+
text: `${node.text.trimEnd()}...`
|
|
1384
|
+
};
|
|
1385
|
+
}
|
|
1386
|
+
return nodes;
|
|
1387
|
+
}
|
|
1388
|
+
if (Array.isArray(node.content) && node.content.length) {
|
|
1389
|
+
nodes[i] = {
|
|
1390
|
+
...node,
|
|
1391
|
+
content: appendEllipsisToLastTextNode([...node.content])
|
|
1392
|
+
};
|
|
1393
|
+
return nodes;
|
|
1394
|
+
}
|
|
1395
|
+
}
|
|
1396
|
+
return nodes;
|
|
1397
|
+
}
|
|
1398
|
+
function truncateNodes(nodes, remaining) {
|
|
1399
|
+
const truncatedNodes = [];
|
|
1400
|
+
let truncated = false;
|
|
1401
|
+
for (let index = 0; index < nodes.length; index++) {
|
|
1402
|
+
const node = nodes[index];
|
|
1403
|
+
if (!node) {
|
|
1404
|
+
continue;
|
|
1405
|
+
}
|
|
1406
|
+
if (remaining <= 0) {
|
|
1407
|
+
truncated = true;
|
|
1408
|
+
break;
|
|
1409
|
+
}
|
|
1410
|
+
if (node.type === "text" && typeof node.text === "string") {
|
|
1411
|
+
if (node.text.length <= remaining) {
|
|
1412
|
+
truncatedNodes.push(node);
|
|
1413
|
+
remaining -= node.text.length;
|
|
1414
|
+
} else {
|
|
1415
|
+
truncatedNodes.push({
|
|
1416
|
+
...node,
|
|
1417
|
+
text: node.text.slice(0, remaining).trimEnd()
|
|
1418
|
+
});
|
|
1419
|
+
remaining = 0;
|
|
1420
|
+
truncated = true;
|
|
1421
|
+
break;
|
|
1422
|
+
}
|
|
1423
|
+
continue;
|
|
1424
|
+
}
|
|
1425
|
+
if (node.type === "hardBreak") {
|
|
1426
|
+
truncatedNodes.push(node);
|
|
1427
|
+
remaining -= 1;
|
|
1428
|
+
continue;
|
|
1429
|
+
}
|
|
1430
|
+
if (Array.isArray(node.content)) {
|
|
1431
|
+
const result = truncateNodes(node.content, remaining);
|
|
1432
|
+
remaining = result.remaining;
|
|
1433
|
+
if (result.nodes.length > 0) {
|
|
1434
|
+
truncatedNodes.push({
|
|
1435
|
+
...node,
|
|
1436
|
+
content: result.nodes
|
|
1437
|
+
});
|
|
1438
|
+
}
|
|
1439
|
+
if (result.truncated) {
|
|
1440
|
+
truncated = true;
|
|
1441
|
+
break;
|
|
1442
|
+
}
|
|
1443
|
+
continue;
|
|
1444
|
+
}
|
|
1445
|
+
truncatedNodes.push(node);
|
|
1446
|
+
}
|
|
1447
|
+
if (truncated) {
|
|
1448
|
+
return {
|
|
1449
|
+
nodes: appendEllipsisToLastTextNode(truncatedNodes),
|
|
1450
|
+
remaining,
|
|
1451
|
+
truncated
|
|
1452
|
+
};
|
|
1453
|
+
}
|
|
1454
|
+
return {
|
|
1455
|
+
nodes: truncatedNodes,
|
|
1456
|
+
remaining,
|
|
1457
|
+
truncated
|
|
1458
|
+
};
|
|
1459
|
+
}
|
|
1460
|
+
function truncateTextEditorContent(json2, characterLimit) {
|
|
1461
|
+
if (!characterLimit || characterLimit <= 0) {
|
|
1462
|
+
return json2;
|
|
1463
|
+
}
|
|
1464
|
+
const content = Array.isArray(json2?.content) ? json2.content : [];
|
|
1465
|
+
const result = truncateNodes(content, characterLimit);
|
|
1466
|
+
return {
|
|
1467
|
+
...json2,
|
|
1468
|
+
content: result.nodes
|
|
1469
|
+
};
|
|
1470
|
+
}
|
|
1471
|
+
|
|
1472
|
+
export { editor_default as Editor, ImagePickerContextProvider, TextRenderer, createExtensions, createId, empty_doc_default as emptyDoc, extractHeadings, extractTextFromNode, extractTextFromTextEditorContent, normalizeTextEditorContent, truncateTextEditorContent, useImagePicker };
|
|
1473
|
+
//# sourceMappingURL=index.js.map
|
|
1474
|
+
//# sourceMappingURL=index.js.map
|