@sofereditor/react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +661 -0
- package/README.md +27 -0
- package/dist/index.cjs +3759 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +353 -0
- package/dist/index.d.ts +353 -0
- package/dist/index.js +3778 -0
- package/dist/index.js.map +1 -0
- package/package.json +62 -0
package/dist/index.js
ADDED
|
@@ -0,0 +1,3778 @@
|
|
|
1
|
+
// src/Editor.tsx
|
|
2
|
+
import {
|
|
3
|
+
useCallback as useCallback5,
|
|
4
|
+
useEffect as useEffect7,
|
|
5
|
+
useLayoutEffect as useLayoutEffect4,
|
|
6
|
+
useMemo as useMemo4,
|
|
7
|
+
useRef as useRef10
|
|
8
|
+
} from "react";
|
|
9
|
+
import {
|
|
10
|
+
deleteBackward,
|
|
11
|
+
deleteForward,
|
|
12
|
+
insertParagraph,
|
|
13
|
+
insertText
|
|
14
|
+
} from "@sofereditor/core";
|
|
15
|
+
|
|
16
|
+
// src/dom-bridge.ts
|
|
17
|
+
function findContainingBlockElement(node, root) {
|
|
18
|
+
let cur = node;
|
|
19
|
+
while (cur && cur !== root) {
|
|
20
|
+
if (cur.nodeType === Node.ELEMENT_NODE) {
|
|
21
|
+
const el = cur;
|
|
22
|
+
if (el.dataset.blockIndex != null) return el;
|
|
23
|
+
}
|
|
24
|
+
cur = cur.parentNode;
|
|
25
|
+
}
|
|
26
|
+
return null;
|
|
27
|
+
}
|
|
28
|
+
function findContainingCellElement(node, blockEl) {
|
|
29
|
+
let cur = node;
|
|
30
|
+
while (cur && cur !== blockEl) {
|
|
31
|
+
if (cur.nodeType === Node.ELEMENT_NODE) {
|
|
32
|
+
const el = cur;
|
|
33
|
+
if (el.dataset.cellIndex != null) return el;
|
|
34
|
+
}
|
|
35
|
+
cur = cur.parentNode;
|
|
36
|
+
}
|
|
37
|
+
return null;
|
|
38
|
+
}
|
|
39
|
+
function findBlockIndex(node, root) {
|
|
40
|
+
const el = findContainingBlockElement(node, root);
|
|
41
|
+
if (!el) return null;
|
|
42
|
+
return Number.parseInt(el.dataset.blockIndex, 10);
|
|
43
|
+
}
|
|
44
|
+
function getBlockElement(root, blockIndex) {
|
|
45
|
+
return root.querySelector(`[data-block-index="${blockIndex}"]`);
|
|
46
|
+
}
|
|
47
|
+
function readFragmentStart(el) {
|
|
48
|
+
const s = el.dataset.fragmentStart;
|
|
49
|
+
if (s == null) return 0;
|
|
50
|
+
const n = Number.parseInt(s, 10);
|
|
51
|
+
return Number.isFinite(n) ? n : 0;
|
|
52
|
+
}
|
|
53
|
+
function readFragmentEnd(el) {
|
|
54
|
+
const s = el.dataset.fragmentEnd;
|
|
55
|
+
if (s == null) return void 0;
|
|
56
|
+
const n = Number.parseInt(s, 10);
|
|
57
|
+
return Number.isFinite(n) ? n : void 0;
|
|
58
|
+
}
|
|
59
|
+
function getFragmentForOffset(root, blockIndex, offset) {
|
|
60
|
+
const candidates = root.querySelectorAll(`[data-block-index="${blockIndex}"]`);
|
|
61
|
+
if (candidates.length === 0) return null;
|
|
62
|
+
if (candidates.length === 1) return candidates[0];
|
|
63
|
+
let last = null;
|
|
64
|
+
for (const el of candidates) {
|
|
65
|
+
last = el;
|
|
66
|
+
const start = readFragmentStart(el);
|
|
67
|
+
const end = readFragmentEnd(el);
|
|
68
|
+
if (end == null) return el;
|
|
69
|
+
if (offset >= start && offset < end) return el;
|
|
70
|
+
}
|
|
71
|
+
return last;
|
|
72
|
+
}
|
|
73
|
+
function getCellElement(root, blockIndex, cellIndex) {
|
|
74
|
+
const blockEl = getBlockElement(root, blockIndex);
|
|
75
|
+
if (!blockEl) return null;
|
|
76
|
+
return blockEl.querySelector(`[data-cell-index="${cellIndex}"]`);
|
|
77
|
+
}
|
|
78
|
+
function isImgEmbed(n) {
|
|
79
|
+
return n.nodeType === Node.ELEMENT_NODE && n.dataset.embed === "image";
|
|
80
|
+
}
|
|
81
|
+
function isWrapAnchorImg(n) {
|
|
82
|
+
return n.nodeType === Node.ELEMENT_NODE && n.dataset.wrapAnchor === "true";
|
|
83
|
+
}
|
|
84
|
+
function isPhantomEmbed(n) {
|
|
85
|
+
return n.nodeType === Node.ELEMENT_NODE && n.dataset.embedPhantom === "true";
|
|
86
|
+
}
|
|
87
|
+
function indexInParent(n) {
|
|
88
|
+
const parent = n.parentNode;
|
|
89
|
+
if (!parent) return 0;
|
|
90
|
+
let i = 0;
|
|
91
|
+
for (const c of Array.from(parent.childNodes)) {
|
|
92
|
+
if (c === n) return i;
|
|
93
|
+
i++;
|
|
94
|
+
}
|
|
95
|
+
return 0;
|
|
96
|
+
}
|
|
97
|
+
function isRejectedSubtree(el, container) {
|
|
98
|
+
if (el === container) return false;
|
|
99
|
+
if (el.dataset.blockIndex != null) return true;
|
|
100
|
+
if (container.dataset.cellIndex != null && el.dataset.cellIndex != null) return true;
|
|
101
|
+
return false;
|
|
102
|
+
}
|
|
103
|
+
function textOffsetWithin(container, node, offsetInNode) {
|
|
104
|
+
let target = node;
|
|
105
|
+
let targetOff = offsetInNode;
|
|
106
|
+
if (isImgEmbed(target) && target.parentNode) {
|
|
107
|
+
targetOff = indexInParent(target);
|
|
108
|
+
target = target.parentNode;
|
|
109
|
+
}
|
|
110
|
+
let offset = 0;
|
|
111
|
+
let done = false;
|
|
112
|
+
function visit(n) {
|
|
113
|
+
if (done) return;
|
|
114
|
+
if (n === target) {
|
|
115
|
+
if (n.nodeType === Node.TEXT_NODE) {
|
|
116
|
+
offset += targetOff;
|
|
117
|
+
done = true;
|
|
118
|
+
return;
|
|
119
|
+
}
|
|
120
|
+
const children = n.childNodes;
|
|
121
|
+
for (let i = 0; i < targetOff && i < children.length; i++) {
|
|
122
|
+
visit(children[i]);
|
|
123
|
+
if (done) return;
|
|
124
|
+
}
|
|
125
|
+
done = true;
|
|
126
|
+
return;
|
|
127
|
+
}
|
|
128
|
+
if (n.nodeType === Node.TEXT_NODE) {
|
|
129
|
+
offset += (n.textContent ?? "").length;
|
|
130
|
+
return;
|
|
131
|
+
}
|
|
132
|
+
if (n.nodeType !== Node.ELEMENT_NODE) return;
|
|
133
|
+
const el = n;
|
|
134
|
+
if (isRejectedSubtree(el, container)) return;
|
|
135
|
+
if (isImgEmbed(el) && isWrapAnchorImg(el)) return;
|
|
136
|
+
if (isImgEmbed(el) || isPhantomEmbed(el)) {
|
|
137
|
+
offset += 1;
|
|
138
|
+
return;
|
|
139
|
+
}
|
|
140
|
+
for (const child of Array.from(el.childNodes)) {
|
|
141
|
+
visit(child);
|
|
142
|
+
if (done) return;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
visit(container);
|
|
146
|
+
return offset;
|
|
147
|
+
}
|
|
148
|
+
function resolveAnchorElement(node, root) {
|
|
149
|
+
const blockEl = findContainingBlockElement(node, root);
|
|
150
|
+
if (!blockEl) return null;
|
|
151
|
+
const blockIdx = Number.parseInt(blockEl.dataset.blockIndex, 10);
|
|
152
|
+
const blockType = blockEl.dataset.blockType;
|
|
153
|
+
if (blockType === "table") {
|
|
154
|
+
const cellEl = findContainingCellElement(node, blockEl);
|
|
155
|
+
if (!cellEl) {
|
|
156
|
+
const firstCell = blockEl.querySelector("[data-cell-index]");
|
|
157
|
+
if (!firstCell) return null;
|
|
158
|
+
return {
|
|
159
|
+
blockIdx,
|
|
160
|
+
cellIdx: 0,
|
|
161
|
+
container: firstCell,
|
|
162
|
+
containerStart: 0
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
const cellIdx = Number.parseInt(cellEl.dataset.cellIndex, 10);
|
|
166
|
+
return {
|
|
167
|
+
blockIdx,
|
|
168
|
+
cellIdx,
|
|
169
|
+
container: cellEl,
|
|
170
|
+
containerStart: 0
|
|
171
|
+
};
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
blockIdx,
|
|
175
|
+
cellIdx: void 0,
|
|
176
|
+
container: blockEl,
|
|
177
|
+
containerStart: readFragmentStart(blockEl)
|
|
178
|
+
};
|
|
179
|
+
}
|
|
180
|
+
function readDomSelection(root) {
|
|
181
|
+
const sel = window.getSelection();
|
|
182
|
+
if (!sel || sel.rangeCount === 0) return null;
|
|
183
|
+
if (!sel.anchorNode || !sel.focusNode) return null;
|
|
184
|
+
if (!root.contains(sel.anchorNode) || !root.contains(sel.focusNode)) return null;
|
|
185
|
+
const a = resolveAnchorElement(sel.anchorNode, root);
|
|
186
|
+
const f = resolveAnchorElement(sel.focusNode, root);
|
|
187
|
+
if (!a || !f) return null;
|
|
188
|
+
const anchor = {
|
|
189
|
+
blockIndex: a.blockIdx,
|
|
190
|
+
cellIndex: a.cellIdx,
|
|
191
|
+
offset: a.containerStart + textOffsetWithin(a.container, sel.anchorNode, sel.anchorOffset)
|
|
192
|
+
};
|
|
193
|
+
const focus = {
|
|
194
|
+
blockIndex: f.blockIdx,
|
|
195
|
+
cellIndex: f.cellIdx,
|
|
196
|
+
offset: f.containerStart + textOffsetWithin(f.container, sel.focusNode, sel.focusOffset)
|
|
197
|
+
};
|
|
198
|
+
return { anchor, focus };
|
|
199
|
+
}
|
|
200
|
+
function locatePoint(root, pos) {
|
|
201
|
+
let container;
|
|
202
|
+
let containerStart = 0;
|
|
203
|
+
if (pos.cellIndex != null) {
|
|
204
|
+
container = getCellElement(root, pos.blockIndex, pos.cellIndex);
|
|
205
|
+
if (!container) return null;
|
|
206
|
+
} else {
|
|
207
|
+
container = getFragmentForOffset(root, pos.blockIndex, pos.offset);
|
|
208
|
+
if (!container) return null;
|
|
209
|
+
containerStart = readFragmentStart(container);
|
|
210
|
+
}
|
|
211
|
+
let remaining = Math.max(0, pos.offset - containerStart);
|
|
212
|
+
let result = null;
|
|
213
|
+
let lastImg = null;
|
|
214
|
+
function visit(n) {
|
|
215
|
+
if (result) return;
|
|
216
|
+
if (n.nodeType === Node.TEXT_NODE) {
|
|
217
|
+
const len = (n.textContent ?? "").length;
|
|
218
|
+
if (remaining <= len) {
|
|
219
|
+
result = { node: n, offset: remaining };
|
|
220
|
+
return;
|
|
221
|
+
}
|
|
222
|
+
remaining -= len;
|
|
223
|
+
lastImg = null;
|
|
224
|
+
return;
|
|
225
|
+
}
|
|
226
|
+
if (n.nodeType !== Node.ELEMENT_NODE) return;
|
|
227
|
+
const el = n;
|
|
228
|
+
if (isRejectedSubtree(el, container)) return;
|
|
229
|
+
if (isImgEmbed(el) && isWrapAnchorImg(el)) return;
|
|
230
|
+
if (isImgEmbed(el) || isPhantomEmbed(el)) {
|
|
231
|
+
if (remaining === 0) {
|
|
232
|
+
const parent = el.parentNode;
|
|
233
|
+
if (parent) {
|
|
234
|
+
result = { node: parent, offset: indexInParent(el) };
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
remaining -= 1;
|
|
239
|
+
lastImg = el;
|
|
240
|
+
return;
|
|
241
|
+
}
|
|
242
|
+
for (const child of Array.from(el.childNodes)) {
|
|
243
|
+
visit(child);
|
|
244
|
+
if (result) return;
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
visit(container);
|
|
248
|
+
if (result) return result;
|
|
249
|
+
if (lastImg && remaining === 0) {
|
|
250
|
+
const parent = lastImg.parentNode;
|
|
251
|
+
if (parent) return { node: parent, offset: indexInParent(lastImg) + 1 };
|
|
252
|
+
}
|
|
253
|
+
return { node: container, offset: 0 };
|
|
254
|
+
}
|
|
255
|
+
function selectionsEqual(a, b) {
|
|
256
|
+
return a.anchor.blockIndex === b.anchor.blockIndex && (a.anchor.cellIndex ?? -1) === (b.anchor.cellIndex ?? -1) && a.anchor.offset === b.anchor.offset && a.focus.blockIndex === b.focus.blockIndex && (a.focus.cellIndex ?? -1) === (b.focus.cellIndex ?? -1) && a.focus.offset === b.focus.offset;
|
|
257
|
+
}
|
|
258
|
+
function isTableRectSelection(sel) {
|
|
259
|
+
if (sel.anchor.blockIndex !== sel.focus.blockIndex) return false;
|
|
260
|
+
if (sel.anchor.cellIndex == null || sel.focus.cellIndex == null) return false;
|
|
261
|
+
return sel.anchor.cellIndex !== sel.focus.cellIndex;
|
|
262
|
+
}
|
|
263
|
+
function applyDomSelection(root, modelSel) {
|
|
264
|
+
const domSel = window.getSelection();
|
|
265
|
+
if (!domSel) return;
|
|
266
|
+
if (isTableRectSelection(modelSel)) {
|
|
267
|
+
domSel.removeAllRanges();
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
const anchorPt = locatePoint(root, modelSel.anchor);
|
|
271
|
+
const focusPt = locatePoint(root, modelSel.focus);
|
|
272
|
+
if (!anchorPt || !focusPt) return;
|
|
273
|
+
try {
|
|
274
|
+
domSel.removeAllRanges();
|
|
275
|
+
domSel.setBaseAndExtent(anchorPt.node, anchorPt.offset, focusPt.node, focusPt.offset);
|
|
276
|
+
} catch {
|
|
277
|
+
}
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/EditorContext.tsx
|
|
281
|
+
import { createContext, useContext } from "react";
|
|
282
|
+
|
|
283
|
+
// src/LinkDialog.tsx
|
|
284
|
+
import {
|
|
285
|
+
useEffect,
|
|
286
|
+
useRef,
|
|
287
|
+
useState
|
|
288
|
+
} from "react";
|
|
289
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
290
|
+
function LinkDialog() {
|
|
291
|
+
const { linkRequest, resolveLinkRequest } = useEditorContext();
|
|
292
|
+
const dialogRef = useRef(null);
|
|
293
|
+
const inputRef = useRef(null);
|
|
294
|
+
const [href, setHref] = useState("");
|
|
295
|
+
useEffect(() => {
|
|
296
|
+
const dialog = dialogRef.current;
|
|
297
|
+
if (!dialog) return;
|
|
298
|
+
if (linkRequest) {
|
|
299
|
+
setHref(linkRequest.initialHref);
|
|
300
|
+
if (!dialog.open) dialog.showModal();
|
|
301
|
+
queueMicrotask(() => {
|
|
302
|
+
const input = inputRef.current;
|
|
303
|
+
if (input) {
|
|
304
|
+
input.focus();
|
|
305
|
+
input.select();
|
|
306
|
+
}
|
|
307
|
+
});
|
|
308
|
+
} else if (dialog.open) {
|
|
309
|
+
dialog.close();
|
|
310
|
+
}
|
|
311
|
+
}, [linkRequest]);
|
|
312
|
+
if (!linkRequest) return null;
|
|
313
|
+
const onSubmit = (e) => {
|
|
314
|
+
e.preventDefault();
|
|
315
|
+
resolveLinkRequest(href.trim());
|
|
316
|
+
};
|
|
317
|
+
const onRemove = () => {
|
|
318
|
+
resolveLinkRequest("");
|
|
319
|
+
};
|
|
320
|
+
const onCancel = () => {
|
|
321
|
+
resolveLinkRequest(null);
|
|
322
|
+
};
|
|
323
|
+
return /* @__PURE__ */ jsx(
|
|
324
|
+
"dialog",
|
|
325
|
+
{
|
|
326
|
+
ref: dialogRef,
|
|
327
|
+
className: "ed-link-dialog",
|
|
328
|
+
onClose: onCancel,
|
|
329
|
+
onCancel: (e) => {
|
|
330
|
+
e.preventDefault();
|
|
331
|
+
onCancel();
|
|
332
|
+
},
|
|
333
|
+
children: /* @__PURE__ */ jsxs("form", { onSubmit, className: "ed-link-dialog-form", children: [
|
|
334
|
+
/* @__PURE__ */ jsxs("div", { className: "ed-link-dialog-row", children: [
|
|
335
|
+
/* @__PURE__ */ jsx("label", { htmlFor: "ed-link-href", children: "URL" }),
|
|
336
|
+
/* @__PURE__ */ jsx(
|
|
337
|
+
"input",
|
|
338
|
+
{
|
|
339
|
+
id: "ed-link-href",
|
|
340
|
+
ref: inputRef,
|
|
341
|
+
type: "url",
|
|
342
|
+
inputMode: "url",
|
|
343
|
+
value: href,
|
|
344
|
+
onChange: (e) => setHref(e.target.value),
|
|
345
|
+
placeholder: "https://exemplo.com",
|
|
346
|
+
autoComplete: "off"
|
|
347
|
+
}
|
|
348
|
+
)
|
|
349
|
+
] }),
|
|
350
|
+
/* @__PURE__ */ jsxs("div", { className: "ed-link-dialog-actions", children: [
|
|
351
|
+
linkRequest.initialHref ? /* @__PURE__ */ jsx("button", { type: "button", onClick: onRemove, className: "ed-link-dialog-remove", children: "Remover link" }) : /* @__PURE__ */ jsx("span", {}),
|
|
352
|
+
/* @__PURE__ */ jsxs("div", { className: "ed-link-dialog-confirm", children: [
|
|
353
|
+
/* @__PURE__ */ jsx("button", { type: "button", onClick: onCancel, children: "Cancelar" }),
|
|
354
|
+
/* @__PURE__ */ jsx("button", { type: "submit", className: "ed-link-dialog-apply", children: "Aplicar" })
|
|
355
|
+
] })
|
|
356
|
+
] })
|
|
357
|
+
] })
|
|
358
|
+
}
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// src/PageConfigDialog.tsx
|
|
363
|
+
import { useEffect as useEffect2, useMemo, useRef as useRef2, useState as useState2 } from "react";
|
|
364
|
+
import {
|
|
365
|
+
MARGIN_PRESETS,
|
|
366
|
+
PAGE_PRESETS,
|
|
367
|
+
detectPreset,
|
|
368
|
+
mmToPx,
|
|
369
|
+
pxToMm
|
|
370
|
+
} from "@sofereditor/core";
|
|
371
|
+
import { jsx as jsx2, jsxs as jsxs2 } from "react/jsx-runtime";
|
|
372
|
+
function PageConfigDialog() {
|
|
373
|
+
const { pageConfigRequest, resolvePageConfigRequest, setPageSettings } = useEditorContext();
|
|
374
|
+
const dialogRef = useRef2(null);
|
|
375
|
+
const [sizePreset, setSizePreset] = useState2("a4");
|
|
376
|
+
const [widthMm, setWidthMm] = useState2(210);
|
|
377
|
+
const [heightMm, setHeightMm] = useState2(297);
|
|
378
|
+
const [marginPreset, setMarginPreset] = useState2("normal");
|
|
379
|
+
const [marginTopMm, setMarginTopMm] = useState2(25.4);
|
|
380
|
+
const [marginBottomMm, setMarginBottomMm] = useState2(25.4);
|
|
381
|
+
const [marginLeftMm, setMarginLeftMm] = useState2(25.4);
|
|
382
|
+
const [marginRightMm, setMarginRightMm] = useState2(25.4);
|
|
383
|
+
useEffect2(() => {
|
|
384
|
+
const dialog = dialogRef.current;
|
|
385
|
+
if (!dialog) return;
|
|
386
|
+
if (pageConfigRequest) {
|
|
387
|
+
hydrateFromSettings(pageConfigRequest.initial, {
|
|
388
|
+
setSizePreset,
|
|
389
|
+
setWidthMm,
|
|
390
|
+
setHeightMm,
|
|
391
|
+
setMarginPreset,
|
|
392
|
+
setMarginTopMm,
|
|
393
|
+
setMarginBottomMm,
|
|
394
|
+
setMarginLeftMm,
|
|
395
|
+
setMarginRightMm
|
|
396
|
+
});
|
|
397
|
+
if (!dialog.open) dialog.showModal();
|
|
398
|
+
} else if (dialog.open) {
|
|
399
|
+
dialog.close();
|
|
400
|
+
}
|
|
401
|
+
}, [pageConfigRequest]);
|
|
402
|
+
useEffect2(() => {
|
|
403
|
+
if (sizePreset === "custom") return;
|
|
404
|
+
const p = PAGE_PRESETS[sizePreset];
|
|
405
|
+
setWidthMm(roundMm(pxToMm(p.width)));
|
|
406
|
+
setHeightMm(roundMm(pxToMm(p.height)));
|
|
407
|
+
}, [sizePreset]);
|
|
408
|
+
useEffect2(() => {
|
|
409
|
+
if (marginPreset === "custom") return;
|
|
410
|
+
const px = MARGIN_PRESETS[marginPreset];
|
|
411
|
+
const mm = roundMm(pxToMm(px));
|
|
412
|
+
setMarginTopMm(mm);
|
|
413
|
+
setMarginBottomMm(mm);
|
|
414
|
+
setMarginLeftMm(mm);
|
|
415
|
+
setMarginRightMm(mm);
|
|
416
|
+
}, [marginPreset]);
|
|
417
|
+
const customWidth = sizePreset === "custom";
|
|
418
|
+
const previewPx = useMemo(
|
|
419
|
+
() => ({
|
|
420
|
+
width: Math.round(mmToPx(widthMm)),
|
|
421
|
+
height: Math.round(mmToPx(heightMm))
|
|
422
|
+
}),
|
|
423
|
+
[widthMm, heightMm]
|
|
424
|
+
);
|
|
425
|
+
if (!pageConfigRequest) return null;
|
|
426
|
+
const onApply = (e) => {
|
|
427
|
+
e.preventDefault();
|
|
428
|
+
const next = {
|
|
429
|
+
width: mmToPx(widthMm),
|
|
430
|
+
height: mmToPx(heightMm),
|
|
431
|
+
marginTop: mmToPx(marginTopMm),
|
|
432
|
+
marginBottom: mmToPx(marginBottomMm),
|
|
433
|
+
marginLeft: mmToPx(marginLeftMm),
|
|
434
|
+
marginRight: mmToPx(marginRightMm)
|
|
435
|
+
};
|
|
436
|
+
setPageSettings(next);
|
|
437
|
+
resolvePageConfigRequest(true);
|
|
438
|
+
};
|
|
439
|
+
const onCancel = () => resolvePageConfigRequest(false);
|
|
440
|
+
return /* @__PURE__ */ jsx2(
|
|
441
|
+
"dialog",
|
|
442
|
+
{
|
|
443
|
+
ref: dialogRef,
|
|
444
|
+
className: "ed-page-config-dialog",
|
|
445
|
+
onClose: onCancel,
|
|
446
|
+
onCancel: (e) => {
|
|
447
|
+
e.preventDefault();
|
|
448
|
+
onCancel();
|
|
449
|
+
},
|
|
450
|
+
children: /* @__PURE__ */ jsxs2("form", { onSubmit: onApply, className: "ed-page-config-form", children: [
|
|
451
|
+
/* @__PURE__ */ jsx2("h2", { children: "Configurar p\xE1gina" }),
|
|
452
|
+
/* @__PURE__ */ jsxs2("fieldset", { children: [
|
|
453
|
+
/* @__PURE__ */ jsx2("legend", { children: "Tamanho" }),
|
|
454
|
+
/* @__PURE__ */ jsxs2("div", { className: "ed-page-config-row", children: [
|
|
455
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
456
|
+
/* @__PURE__ */ jsx2(
|
|
457
|
+
"input",
|
|
458
|
+
{
|
|
459
|
+
type: "radio",
|
|
460
|
+
name: "size",
|
|
461
|
+
checked: sizePreset === "a4",
|
|
462
|
+
onChange: () => setSizePreset("a4")
|
|
463
|
+
}
|
|
464
|
+
),
|
|
465
|
+
" ",
|
|
466
|
+
"A4 (210 \xD7 297 mm)"
|
|
467
|
+
] }),
|
|
468
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
469
|
+
/* @__PURE__ */ jsx2(
|
|
470
|
+
"input",
|
|
471
|
+
{
|
|
472
|
+
type: "radio",
|
|
473
|
+
name: "size",
|
|
474
|
+
checked: sizePreset === "letter",
|
|
475
|
+
onChange: () => setSizePreset("letter")
|
|
476
|
+
}
|
|
477
|
+
),
|
|
478
|
+
" ",
|
|
479
|
+
"Carta (216 \xD7 279 mm)"
|
|
480
|
+
] }),
|
|
481
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
482
|
+
/* @__PURE__ */ jsx2(
|
|
483
|
+
"input",
|
|
484
|
+
{
|
|
485
|
+
type: "radio",
|
|
486
|
+
name: "size",
|
|
487
|
+
checked: sizePreset === "oficio",
|
|
488
|
+
onChange: () => setSizePreset("oficio")
|
|
489
|
+
}
|
|
490
|
+
),
|
|
491
|
+
" ",
|
|
492
|
+
"Of\xEDcio (216 \xD7 330 mm)"
|
|
493
|
+
] }),
|
|
494
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
495
|
+
/* @__PURE__ */ jsx2(
|
|
496
|
+
"input",
|
|
497
|
+
{
|
|
498
|
+
type: "radio",
|
|
499
|
+
name: "size",
|
|
500
|
+
checked: sizePreset === "custom",
|
|
501
|
+
onChange: () => setSizePreset("custom")
|
|
502
|
+
}
|
|
503
|
+
),
|
|
504
|
+
" ",
|
|
505
|
+
"Personalizado"
|
|
506
|
+
] })
|
|
507
|
+
] }),
|
|
508
|
+
/* @__PURE__ */ jsxs2("div", { className: "ed-page-config-row", children: [
|
|
509
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
510
|
+
"Largura (mm)",
|
|
511
|
+
" ",
|
|
512
|
+
/* @__PURE__ */ jsx2(
|
|
513
|
+
"input",
|
|
514
|
+
{
|
|
515
|
+
type: "number",
|
|
516
|
+
step: 0.1,
|
|
517
|
+
min: 50,
|
|
518
|
+
max: 1e3,
|
|
519
|
+
value: widthMm,
|
|
520
|
+
onChange: (e) => setWidthMm(Number(e.target.value)),
|
|
521
|
+
disabled: !customWidth
|
|
522
|
+
}
|
|
523
|
+
)
|
|
524
|
+
] }),
|
|
525
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
526
|
+
"Altura (mm)",
|
|
527
|
+
" ",
|
|
528
|
+
/* @__PURE__ */ jsx2(
|
|
529
|
+
"input",
|
|
530
|
+
{
|
|
531
|
+
type: "number",
|
|
532
|
+
step: 0.1,
|
|
533
|
+
min: 50,
|
|
534
|
+
max: 1e3,
|
|
535
|
+
value: heightMm,
|
|
536
|
+
onChange: (e) => setHeightMm(Number(e.target.value)),
|
|
537
|
+
disabled: !customWidth
|
|
538
|
+
}
|
|
539
|
+
)
|
|
540
|
+
] }),
|
|
541
|
+
/* @__PURE__ */ jsxs2("span", { className: "ed-page-config-preview", children: [
|
|
542
|
+
previewPx.width,
|
|
543
|
+
" \xD7 ",
|
|
544
|
+
previewPx.height,
|
|
545
|
+
" px"
|
|
546
|
+
] })
|
|
547
|
+
] })
|
|
548
|
+
] }),
|
|
549
|
+
/* @__PURE__ */ jsxs2("fieldset", { children: [
|
|
550
|
+
/* @__PURE__ */ jsx2("legend", { children: "Margens" }),
|
|
551
|
+
/* @__PURE__ */ jsxs2("div", { className: "ed-page-config-row", children: [
|
|
552
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
553
|
+
/* @__PURE__ */ jsx2(
|
|
554
|
+
"input",
|
|
555
|
+
{
|
|
556
|
+
type: "radio",
|
|
557
|
+
name: "margin",
|
|
558
|
+
checked: marginPreset === "normal",
|
|
559
|
+
onChange: () => setMarginPreset("normal")
|
|
560
|
+
}
|
|
561
|
+
),
|
|
562
|
+
" ",
|
|
563
|
+
"Normal (25,4 mm)"
|
|
564
|
+
] }),
|
|
565
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
566
|
+
/* @__PURE__ */ jsx2(
|
|
567
|
+
"input",
|
|
568
|
+
{
|
|
569
|
+
type: "radio",
|
|
570
|
+
name: "margin",
|
|
571
|
+
checked: marginPreset === "narrow",
|
|
572
|
+
onChange: () => setMarginPreset("narrow")
|
|
573
|
+
}
|
|
574
|
+
),
|
|
575
|
+
" ",
|
|
576
|
+
"Estreita (12,7 mm)"
|
|
577
|
+
] }),
|
|
578
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
579
|
+
/* @__PURE__ */ jsx2(
|
|
580
|
+
"input",
|
|
581
|
+
{
|
|
582
|
+
type: "radio",
|
|
583
|
+
name: "margin",
|
|
584
|
+
checked: marginPreset === "wide",
|
|
585
|
+
onChange: () => setMarginPreset("wide")
|
|
586
|
+
}
|
|
587
|
+
),
|
|
588
|
+
" ",
|
|
589
|
+
"Larga (50,8 mm)"
|
|
590
|
+
] }),
|
|
591
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
592
|
+
/* @__PURE__ */ jsx2(
|
|
593
|
+
"input",
|
|
594
|
+
{
|
|
595
|
+
type: "radio",
|
|
596
|
+
name: "margin",
|
|
597
|
+
checked: marginPreset === "custom",
|
|
598
|
+
onChange: () => setMarginPreset("custom")
|
|
599
|
+
}
|
|
600
|
+
),
|
|
601
|
+
" ",
|
|
602
|
+
"Personalizada"
|
|
603
|
+
] })
|
|
604
|
+
] }),
|
|
605
|
+
/* @__PURE__ */ jsxs2("div", { className: "ed-page-config-row", children: [
|
|
606
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
607
|
+
"Topo",
|
|
608
|
+
" ",
|
|
609
|
+
/* @__PURE__ */ jsx2(
|
|
610
|
+
"input",
|
|
611
|
+
{
|
|
612
|
+
type: "number",
|
|
613
|
+
step: 0.1,
|
|
614
|
+
min: 0,
|
|
615
|
+
max: 200,
|
|
616
|
+
value: marginTopMm,
|
|
617
|
+
onChange: (e) => {
|
|
618
|
+
setMarginTopMm(Number(e.target.value));
|
|
619
|
+
setMarginPreset("custom");
|
|
620
|
+
}
|
|
621
|
+
}
|
|
622
|
+
)
|
|
623
|
+
] }),
|
|
624
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
625
|
+
"Inferior",
|
|
626
|
+
" ",
|
|
627
|
+
/* @__PURE__ */ jsx2(
|
|
628
|
+
"input",
|
|
629
|
+
{
|
|
630
|
+
type: "number",
|
|
631
|
+
step: 0.1,
|
|
632
|
+
min: 0,
|
|
633
|
+
max: 200,
|
|
634
|
+
value: marginBottomMm,
|
|
635
|
+
onChange: (e) => {
|
|
636
|
+
setMarginBottomMm(Number(e.target.value));
|
|
637
|
+
setMarginPreset("custom");
|
|
638
|
+
}
|
|
639
|
+
}
|
|
640
|
+
)
|
|
641
|
+
] }),
|
|
642
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
643
|
+
"Esquerda",
|
|
644
|
+
" ",
|
|
645
|
+
/* @__PURE__ */ jsx2(
|
|
646
|
+
"input",
|
|
647
|
+
{
|
|
648
|
+
type: "number",
|
|
649
|
+
step: 0.1,
|
|
650
|
+
min: 0,
|
|
651
|
+
max: 200,
|
|
652
|
+
value: marginLeftMm,
|
|
653
|
+
onChange: (e) => {
|
|
654
|
+
setMarginLeftMm(Number(e.target.value));
|
|
655
|
+
setMarginPreset("custom");
|
|
656
|
+
}
|
|
657
|
+
}
|
|
658
|
+
)
|
|
659
|
+
] }),
|
|
660
|
+
/* @__PURE__ */ jsxs2("label", { children: [
|
|
661
|
+
"Direita",
|
|
662
|
+
" ",
|
|
663
|
+
/* @__PURE__ */ jsx2(
|
|
664
|
+
"input",
|
|
665
|
+
{
|
|
666
|
+
type: "number",
|
|
667
|
+
step: 0.1,
|
|
668
|
+
min: 0,
|
|
669
|
+
max: 200,
|
|
670
|
+
value: marginRightMm,
|
|
671
|
+
onChange: (e) => {
|
|
672
|
+
setMarginRightMm(Number(e.target.value));
|
|
673
|
+
setMarginPreset("custom");
|
|
674
|
+
}
|
|
675
|
+
}
|
|
676
|
+
)
|
|
677
|
+
] })
|
|
678
|
+
] })
|
|
679
|
+
] }),
|
|
680
|
+
/* @__PURE__ */ jsxs2("div", { className: "ed-page-config-actions", children: [
|
|
681
|
+
/* @__PURE__ */ jsx2("button", { type: "button", onClick: onCancel, children: "Cancelar" }),
|
|
682
|
+
/* @__PURE__ */ jsx2("button", { type: "submit", className: "ed-page-config-apply", children: "Aplicar" })
|
|
683
|
+
] })
|
|
684
|
+
] })
|
|
685
|
+
}
|
|
686
|
+
);
|
|
687
|
+
}
|
|
688
|
+
function roundMm(mm) {
|
|
689
|
+
return Math.round(mm * 10) / 10;
|
|
690
|
+
}
|
|
691
|
+
function hydrateFromSettings(s, setters) {
|
|
692
|
+
const preset = detectPreset(s.width, s.height);
|
|
693
|
+
setters.setSizePreset(preset);
|
|
694
|
+
setters.setWidthMm(roundMm(pxToMm(s.width)));
|
|
695
|
+
setters.setHeightMm(roundMm(pxToMm(s.height)));
|
|
696
|
+
const top = roundMm(pxToMm(s.marginTop));
|
|
697
|
+
const bottom = roundMm(pxToMm(s.marginBottom));
|
|
698
|
+
const left = roundMm(pxToMm(s.marginLeft));
|
|
699
|
+
const right = roundMm(pxToMm(s.marginRight));
|
|
700
|
+
setters.setMarginTopMm(top);
|
|
701
|
+
setters.setMarginBottomMm(bottom);
|
|
702
|
+
setters.setMarginLeftMm(left);
|
|
703
|
+
setters.setMarginRightMm(right);
|
|
704
|
+
setters.setMarginPreset(detectMarginPreset(s));
|
|
705
|
+
}
|
|
706
|
+
function detectMarginPreset(s) {
|
|
707
|
+
const TOL = 1;
|
|
708
|
+
const allEqual = Math.abs(s.marginTop - s.marginBottom) <= TOL && Math.abs(s.marginTop - s.marginLeft) <= TOL && Math.abs(s.marginTop - s.marginRight) <= TOL;
|
|
709
|
+
if (!allEqual) return "custom";
|
|
710
|
+
for (const [name, px] of Object.entries(MARGIN_PRESETS)) {
|
|
711
|
+
if (Math.abs(s.marginTop - px) <= TOL) return name;
|
|
712
|
+
}
|
|
713
|
+
return "custom";
|
|
714
|
+
}
|
|
715
|
+
|
|
716
|
+
// src/ImageCaptionDialog.tsx
|
|
717
|
+
import { useEffect as useEffect3, useRef as useRef3, useState as useState3 } from "react";
|
|
718
|
+
import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
|
|
719
|
+
function ImageCaptionDialog() {
|
|
720
|
+
const { imageCaptionRequest, resolveImageCaptionRequest } = useEditorContext();
|
|
721
|
+
const dialogRef = useRef3(null);
|
|
722
|
+
const inputRef = useRef3(null);
|
|
723
|
+
const [caption, setCaption] = useState3("");
|
|
724
|
+
const [align, setAlign] = useState3("center");
|
|
725
|
+
useEffect3(() => {
|
|
726
|
+
const dialog = dialogRef.current;
|
|
727
|
+
if (!dialog) return;
|
|
728
|
+
if (imageCaptionRequest) {
|
|
729
|
+
setCaption(imageCaptionRequest.initialCaption);
|
|
730
|
+
setAlign(imageCaptionRequest.initialAlign);
|
|
731
|
+
if (!dialog.open) dialog.showModal();
|
|
732
|
+
queueMicrotask(() => {
|
|
733
|
+
const input = inputRef.current;
|
|
734
|
+
if (input) {
|
|
735
|
+
input.focus();
|
|
736
|
+
input.select();
|
|
737
|
+
}
|
|
738
|
+
});
|
|
739
|
+
} else if (dialog.open) {
|
|
740
|
+
dialog.close();
|
|
741
|
+
}
|
|
742
|
+
}, [imageCaptionRequest]);
|
|
743
|
+
if (!imageCaptionRequest) return null;
|
|
744
|
+
const onSubmit = (e) => {
|
|
745
|
+
e.preventDefault();
|
|
746
|
+
resolveImageCaptionRequest({ caption, align });
|
|
747
|
+
};
|
|
748
|
+
const onRemove = () => resolveImageCaptionRequest({ caption: "", align });
|
|
749
|
+
const onCancel = () => resolveImageCaptionRequest(null);
|
|
750
|
+
return /* @__PURE__ */ jsx3(
|
|
751
|
+
"dialog",
|
|
752
|
+
{
|
|
753
|
+
ref: dialogRef,
|
|
754
|
+
className: "ed-caption-dialog ed-link-dialog",
|
|
755
|
+
onClose: onCancel,
|
|
756
|
+
onCancel: (e) => {
|
|
757
|
+
e.preventDefault();
|
|
758
|
+
onCancel();
|
|
759
|
+
},
|
|
760
|
+
children: /* @__PURE__ */ jsxs3("form", { onSubmit, className: "ed-link-dialog-form", children: [
|
|
761
|
+
/* @__PURE__ */ jsxs3("div", { className: "ed-link-dialog-row", children: [
|
|
762
|
+
/* @__PURE__ */ jsx3("label", { htmlFor: "ed-caption-input", children: "Legenda" }),
|
|
763
|
+
/* @__PURE__ */ jsx3(
|
|
764
|
+
"input",
|
|
765
|
+
{
|
|
766
|
+
id: "ed-caption-input",
|
|
767
|
+
ref: inputRef,
|
|
768
|
+
type: "text",
|
|
769
|
+
value: caption,
|
|
770
|
+
onChange: (e) => setCaption(e.target.value),
|
|
771
|
+
placeholder: "Ex.: Figura 1 \u2014 Mapa do Brasil",
|
|
772
|
+
autoComplete: "off"
|
|
773
|
+
}
|
|
774
|
+
)
|
|
775
|
+
] }),
|
|
776
|
+
/* @__PURE__ */ jsxs3("div", { className: "ed-link-dialog-row", children: [
|
|
777
|
+
/* @__PURE__ */ jsx3("span", { children: "Alinhamento" }),
|
|
778
|
+
/* @__PURE__ */ jsx3("div", { className: "ed-caption-align-group", role: "radiogroup", "aria-label": "Alinhamento da legenda", children: ["left", "center", "right"].map((value) => /* @__PURE__ */ jsxs3("label", { className: "ed-caption-align-option", children: [
|
|
779
|
+
/* @__PURE__ */ jsx3(
|
|
780
|
+
"input",
|
|
781
|
+
{
|
|
782
|
+
type: "radio",
|
|
783
|
+
name: "caption-align",
|
|
784
|
+
value,
|
|
785
|
+
checked: align === value,
|
|
786
|
+
onChange: () => setAlign(value)
|
|
787
|
+
}
|
|
788
|
+
),
|
|
789
|
+
value === "left" ? "Esquerda" : value === "center" ? "Centro" : "Direita"
|
|
790
|
+
] }, value)) })
|
|
791
|
+
] }),
|
|
792
|
+
/* @__PURE__ */ jsxs3("div", { className: "ed-link-dialog-actions", children: [
|
|
793
|
+
imageCaptionRequest.initialCaption ? /* @__PURE__ */ jsx3("button", { type: "button", onClick: onRemove, className: "ed-link-dialog-remove", children: "Remover legenda" }) : /* @__PURE__ */ jsx3("span", {}),
|
|
794
|
+
/* @__PURE__ */ jsxs3("div", { className: "ed-link-dialog-confirm", children: [
|
|
795
|
+
/* @__PURE__ */ jsx3("button", { type: "button", onClick: onCancel, children: "Cancelar" }),
|
|
796
|
+
/* @__PURE__ */ jsx3("button", { type: "submit", className: "ed-link-dialog-apply", children: "Aplicar" })
|
|
797
|
+
] })
|
|
798
|
+
] })
|
|
799
|
+
] })
|
|
800
|
+
}
|
|
801
|
+
);
|
|
802
|
+
}
|
|
803
|
+
|
|
804
|
+
// src/EditorContext.tsx
|
|
805
|
+
import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
|
|
806
|
+
var EditorContext = createContext(null);
|
|
807
|
+
function EditorProvider({ editor, children }) {
|
|
808
|
+
return /* @__PURE__ */ jsxs4(EditorContext.Provider, { value: editor, children: [
|
|
809
|
+
children,
|
|
810
|
+
/* @__PURE__ */ jsx4(LinkDialog, {}),
|
|
811
|
+
/* @__PURE__ */ jsx4(PageConfigDialog, {}),
|
|
812
|
+
/* @__PURE__ */ jsx4(ImageCaptionDialog, {})
|
|
813
|
+
] });
|
|
814
|
+
}
|
|
815
|
+
function useEditorContext() {
|
|
816
|
+
const ctx = useContext(EditorContext);
|
|
817
|
+
if (!ctx) {
|
|
818
|
+
throw new Error("useEditorContext must be used inside an <EditorProvider>");
|
|
819
|
+
}
|
|
820
|
+
return ctx;
|
|
821
|
+
}
|
|
822
|
+
|
|
823
|
+
// src/ImageResizeOverlay.tsx
|
|
824
|
+
import {
|
|
825
|
+
useCallback,
|
|
826
|
+
useEffect as useEffect4,
|
|
827
|
+
useLayoutEffect,
|
|
828
|
+
useRef as useRef4,
|
|
829
|
+
useState as useState4
|
|
830
|
+
} from "react";
|
|
831
|
+
import { jsx as jsx5, jsxs as jsxs5 } from "react/jsx-runtime";
|
|
832
|
+
function ImageResizeOverlay({
|
|
833
|
+
rootRef,
|
|
834
|
+
blockIndex,
|
|
835
|
+
offset,
|
|
836
|
+
cellIndex,
|
|
837
|
+
embed,
|
|
838
|
+
onCommit,
|
|
839
|
+
onCommitMove
|
|
840
|
+
}) {
|
|
841
|
+
const [rect, setRect] = useState4(null);
|
|
842
|
+
const imgRef = useRef4(null);
|
|
843
|
+
const locateImage = useCallback(() => {
|
|
844
|
+
const root = rootRef.current;
|
|
845
|
+
if (!root) return null;
|
|
846
|
+
let container = null;
|
|
847
|
+
if (cellIndex != null) {
|
|
848
|
+
container = root.querySelector(
|
|
849
|
+
`[data-block-index="${blockIndex}"] [data-cell-index="${cellIndex}"]`
|
|
850
|
+
);
|
|
851
|
+
} else {
|
|
852
|
+
container = getFragmentForOffset(root, blockIndex, offset);
|
|
853
|
+
}
|
|
854
|
+
if (!container) return null;
|
|
855
|
+
const fragmentStart = container.dataset.fragmentStart ? Number.parseInt(container.dataset.fragmentStart, 10) || 0 : 0;
|
|
856
|
+
const localOffset = offset - fragmentStart;
|
|
857
|
+
return container.querySelector(
|
|
858
|
+
`img[data-embed="image"][data-embed-offset="${localOffset}"]`
|
|
859
|
+
);
|
|
860
|
+
}, [rootRef, blockIndex, offset, cellIndex]);
|
|
861
|
+
const measure = useCallback(() => {
|
|
862
|
+
const img = locateImage();
|
|
863
|
+
if (!img) {
|
|
864
|
+
setRect((prev) => prev === null ? prev : null);
|
|
865
|
+
return;
|
|
866
|
+
}
|
|
867
|
+
imgRef.current = img;
|
|
868
|
+
const root = rootRef.current;
|
|
869
|
+
if (!root) return;
|
|
870
|
+
const r = img.getBoundingClientRect();
|
|
871
|
+
const rr = root.getBoundingClientRect();
|
|
872
|
+
const next = {
|
|
873
|
+
top: r.top - rr.top + root.scrollTop,
|
|
874
|
+
left: r.left - rr.left + root.scrollLeft,
|
|
875
|
+
width: r.width,
|
|
876
|
+
height: r.height
|
|
877
|
+
};
|
|
878
|
+
setRect(
|
|
879
|
+
(prev) => prev && prev.top === next.top && prev.left === next.left && prev.width === next.width && prev.height === next.height ? prev : next
|
|
880
|
+
);
|
|
881
|
+
}, [locateImage, rootRef]);
|
|
882
|
+
useLayoutEffect(() => {
|
|
883
|
+
measure();
|
|
884
|
+
let cancelled = false;
|
|
885
|
+
const remeasure = () => {
|
|
886
|
+
if (cancelled) return;
|
|
887
|
+
measure();
|
|
888
|
+
};
|
|
889
|
+
const id = typeof requestAnimationFrame === "function" ? requestAnimationFrame(remeasure) : (queueMicrotask(remeasure), 0);
|
|
890
|
+
return () => {
|
|
891
|
+
cancelled = true;
|
|
892
|
+
if (typeof cancelAnimationFrame === "function" && typeof id === "number" && id > 0) {
|
|
893
|
+
cancelAnimationFrame(id);
|
|
894
|
+
}
|
|
895
|
+
};
|
|
896
|
+
}, [
|
|
897
|
+
measure,
|
|
898
|
+
embed.width,
|
|
899
|
+
embed.height,
|
|
900
|
+
embed.align,
|
|
901
|
+
embed.src,
|
|
902
|
+
embed.layout,
|
|
903
|
+
embed.offsetX,
|
|
904
|
+
embed.offsetY
|
|
905
|
+
]);
|
|
906
|
+
useEffect4(() => {
|
|
907
|
+
const root = rootRef.current;
|
|
908
|
+
if (!root) return;
|
|
909
|
+
const onScroll = () => measure();
|
|
910
|
+
window.addEventListener("resize", onScroll);
|
|
911
|
+
root.addEventListener("scroll", onScroll);
|
|
912
|
+
return () => {
|
|
913
|
+
window.removeEventListener("resize", onScroll);
|
|
914
|
+
root.removeEventListener("scroll", onScroll);
|
|
915
|
+
};
|
|
916
|
+
}, [measure, rootRef]);
|
|
917
|
+
useEffect4(() => {
|
|
918
|
+
if (typeof ResizeObserver === "undefined") return;
|
|
919
|
+
const img = locateImage();
|
|
920
|
+
const root = rootRef.current;
|
|
921
|
+
if (!img || !root) return;
|
|
922
|
+
const ro = new ResizeObserver(() => measure());
|
|
923
|
+
ro.observe(img);
|
|
924
|
+
ro.observe(root);
|
|
925
|
+
return () => ro.disconnect();
|
|
926
|
+
}, [locateImage, measure, rootRef, embed.layout, embed.align, embed.src]);
|
|
927
|
+
if (!rect) return null;
|
|
928
|
+
const layout = embed.layout ?? "inline";
|
|
929
|
+
const movable = layout !== "inline" && onCommitMove !== void 0;
|
|
930
|
+
return /* @__PURE__ */ jsxs5(
|
|
931
|
+
"div",
|
|
932
|
+
{
|
|
933
|
+
className: "ed-image-overlay",
|
|
934
|
+
style: { top: rect.top, left: rect.left, width: rect.width, height: rect.height },
|
|
935
|
+
contentEditable: false,
|
|
936
|
+
children: [
|
|
937
|
+
movable && /* @__PURE__ */ jsx5(
|
|
938
|
+
MoveHandle,
|
|
939
|
+
{
|
|
940
|
+
layout,
|
|
941
|
+
startOffsetX: embed.offsetX ?? 0,
|
|
942
|
+
startOffsetY: embed.offsetY ?? 0,
|
|
943
|
+
imgRef,
|
|
944
|
+
onCommit: onCommitMove,
|
|
945
|
+
onLiveChange: measure
|
|
946
|
+
}
|
|
947
|
+
),
|
|
948
|
+
/* @__PURE__ */ jsx5(
|
|
949
|
+
Handle,
|
|
950
|
+
{
|
|
951
|
+
corner: "nw",
|
|
952
|
+
startW: embed.width,
|
|
953
|
+
startH: embed.height,
|
|
954
|
+
imgRef,
|
|
955
|
+
onCommit,
|
|
956
|
+
onLiveChange: measure
|
|
957
|
+
}
|
|
958
|
+
),
|
|
959
|
+
/* @__PURE__ */ jsx5(
|
|
960
|
+
Handle,
|
|
961
|
+
{
|
|
962
|
+
corner: "ne",
|
|
963
|
+
startW: embed.width,
|
|
964
|
+
startH: embed.height,
|
|
965
|
+
imgRef,
|
|
966
|
+
onCommit,
|
|
967
|
+
onLiveChange: measure
|
|
968
|
+
}
|
|
969
|
+
),
|
|
970
|
+
/* @__PURE__ */ jsx5(
|
|
971
|
+
Handle,
|
|
972
|
+
{
|
|
973
|
+
corner: "sw",
|
|
974
|
+
startW: embed.width,
|
|
975
|
+
startH: embed.height,
|
|
976
|
+
imgRef,
|
|
977
|
+
onCommit,
|
|
978
|
+
onLiveChange: measure
|
|
979
|
+
}
|
|
980
|
+
),
|
|
981
|
+
/* @__PURE__ */ jsx5(
|
|
982
|
+
Handle,
|
|
983
|
+
{
|
|
984
|
+
corner: "se",
|
|
985
|
+
startW: embed.width,
|
|
986
|
+
startH: embed.height,
|
|
987
|
+
imgRef,
|
|
988
|
+
onCommit,
|
|
989
|
+
onLiveChange: measure
|
|
990
|
+
}
|
|
991
|
+
)
|
|
992
|
+
]
|
|
993
|
+
}
|
|
994
|
+
);
|
|
995
|
+
}
|
|
996
|
+
function MoveHandle({
|
|
997
|
+
layout,
|
|
998
|
+
startOffsetX,
|
|
999
|
+
startOffsetY,
|
|
1000
|
+
imgRef,
|
|
1001
|
+
onCommit,
|
|
1002
|
+
onLiveChange
|
|
1003
|
+
}) {
|
|
1004
|
+
const dragRef = useRef4(null);
|
|
1005
|
+
const onPointerDown = (e) => {
|
|
1006
|
+
if (e.button !== 0) return;
|
|
1007
|
+
if (!imgRef.current) return;
|
|
1008
|
+
e.preventDefault();
|
|
1009
|
+
e.stopPropagation();
|
|
1010
|
+
e.target.setPointerCapture(e.pointerId);
|
|
1011
|
+
dragRef.current = {
|
|
1012
|
+
startX: e.clientX,
|
|
1013
|
+
startY: e.clientY,
|
|
1014
|
+
liveOX: startOffsetX,
|
|
1015
|
+
liveOY: startOffsetY
|
|
1016
|
+
};
|
|
1017
|
+
};
|
|
1018
|
+
const onPointerMove = (e) => {
|
|
1019
|
+
const d = dragRef.current;
|
|
1020
|
+
if (!d || !imgRef.current) return;
|
|
1021
|
+
const dx = e.clientX - d.startX;
|
|
1022
|
+
const dy = e.clientY - d.startY;
|
|
1023
|
+
const img = imgRef.current;
|
|
1024
|
+
let nox = startOffsetX;
|
|
1025
|
+
let noy = startOffsetY;
|
|
1026
|
+
if (layout === "wrap-left") {
|
|
1027
|
+
nox = Math.max(8, startOffsetX + dx);
|
|
1028
|
+
noy = Math.max(0, startOffsetY + dy);
|
|
1029
|
+
img.style.marginRight = `${nox}px`;
|
|
1030
|
+
img.style.marginTop = `${noy}px`;
|
|
1031
|
+
} else if (layout === "wrap-right") {
|
|
1032
|
+
nox = Math.max(8, startOffsetX - dx);
|
|
1033
|
+
noy = Math.max(0, startOffsetY + dy);
|
|
1034
|
+
img.style.marginLeft = `${nox}px`;
|
|
1035
|
+
img.style.marginTop = `${noy}px`;
|
|
1036
|
+
} else if (layout === "behind" || layout === "front") {
|
|
1037
|
+
nox = startOffsetX + dx;
|
|
1038
|
+
noy = startOffsetY + dy;
|
|
1039
|
+
img.style.left = `${nox}px`;
|
|
1040
|
+
img.style.top = `${noy}px`;
|
|
1041
|
+
}
|
|
1042
|
+
d.liveOX = nox;
|
|
1043
|
+
d.liveOY = noy;
|
|
1044
|
+
onLiveChange();
|
|
1045
|
+
};
|
|
1046
|
+
const finish = (e) => {
|
|
1047
|
+
const d = dragRef.current;
|
|
1048
|
+
if (!d) return;
|
|
1049
|
+
dragRef.current = null;
|
|
1050
|
+
try {
|
|
1051
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
1052
|
+
} catch {
|
|
1053
|
+
}
|
|
1054
|
+
onCommit(Math.round(d.liveOX), Math.round(d.liveOY));
|
|
1055
|
+
};
|
|
1056
|
+
return /* @__PURE__ */ jsx5(
|
|
1057
|
+
"div",
|
|
1058
|
+
{
|
|
1059
|
+
className: "ed-image-move",
|
|
1060
|
+
onPointerDown,
|
|
1061
|
+
onPointerMove,
|
|
1062
|
+
onPointerUp: finish,
|
|
1063
|
+
onPointerCancel: finish
|
|
1064
|
+
}
|
|
1065
|
+
);
|
|
1066
|
+
}
|
|
1067
|
+
var MIN_W = 20;
|
|
1068
|
+
function Handle({
|
|
1069
|
+
corner,
|
|
1070
|
+
startW,
|
|
1071
|
+
startH,
|
|
1072
|
+
imgRef,
|
|
1073
|
+
onCommit,
|
|
1074
|
+
onLiveChange
|
|
1075
|
+
}) {
|
|
1076
|
+
const dragRef = useRef4(null);
|
|
1077
|
+
const onPointerDown = (e) => {
|
|
1078
|
+
if (e.button !== 0) return;
|
|
1079
|
+
if (!imgRef.current) return;
|
|
1080
|
+
e.preventDefault();
|
|
1081
|
+
e.stopPropagation();
|
|
1082
|
+
e.target.setPointerCapture(e.pointerId);
|
|
1083
|
+
const aspect = startW > 0 && startH > 0 ? startW / startH : 1;
|
|
1084
|
+
dragRef.current = {
|
|
1085
|
+
startX: e.clientX,
|
|
1086
|
+
startY: e.clientY,
|
|
1087
|
+
startW,
|
|
1088
|
+
startH,
|
|
1089
|
+
aspect,
|
|
1090
|
+
liveW: startW,
|
|
1091
|
+
liveH: startH
|
|
1092
|
+
};
|
|
1093
|
+
};
|
|
1094
|
+
const onPointerMove = (e) => {
|
|
1095
|
+
const d = dragRef.current;
|
|
1096
|
+
if (!d || !imgRef.current) return;
|
|
1097
|
+
const dxRaw = e.clientX - d.startX;
|
|
1098
|
+
const dx = corner === "se" || corner === "ne" ? dxRaw : -dxRaw;
|
|
1099
|
+
let w = Math.max(MIN_W, d.startW + dx);
|
|
1100
|
+
let h = w / d.aspect;
|
|
1101
|
+
imgRef.current.style.width = `${w}px`;
|
|
1102
|
+
imgRef.current.style.height = `${h}px`;
|
|
1103
|
+
imgRef.current.width = Math.round(w);
|
|
1104
|
+
imgRef.current.height = Math.round(h);
|
|
1105
|
+
d.liveW = w;
|
|
1106
|
+
d.liveH = h;
|
|
1107
|
+
onLiveChange();
|
|
1108
|
+
};
|
|
1109
|
+
const finish = (e) => {
|
|
1110
|
+
const d = dragRef.current;
|
|
1111
|
+
if (!d) return;
|
|
1112
|
+
dragRef.current = null;
|
|
1113
|
+
try {
|
|
1114
|
+
e.target.releasePointerCapture(e.pointerId);
|
|
1115
|
+
} catch {
|
|
1116
|
+
}
|
|
1117
|
+
onCommit(Math.round(d.liveW), Math.round(d.liveH));
|
|
1118
|
+
};
|
|
1119
|
+
return /* @__PURE__ */ jsx5(
|
|
1120
|
+
"div",
|
|
1121
|
+
{
|
|
1122
|
+
className: `ed-image-handle ${corner}`,
|
|
1123
|
+
onPointerDown,
|
|
1124
|
+
onPointerMove,
|
|
1125
|
+
onPointerUp: finish,
|
|
1126
|
+
onPointerCancel: finish
|
|
1127
|
+
}
|
|
1128
|
+
);
|
|
1129
|
+
}
|
|
1130
|
+
|
|
1131
|
+
// src/NodeView.tsx
|
|
1132
|
+
import { useMemo as useMemo2, useRef as useRef6 } from "react";
|
|
1133
|
+
import { tableRectSelection } from "@sofereditor/core";
|
|
1134
|
+
|
|
1135
|
+
// src/renderInline.tsx
|
|
1136
|
+
import { Fragment, memo } from "react";
|
|
1137
|
+
import { isImageEmbed } from "@sofereditor/core";
|
|
1138
|
+
import { jsx as jsx6, jsxs as jsxs6 } from "react/jsx-runtime";
|
|
1139
|
+
function renderInline(delta, keyPrefix) {
|
|
1140
|
+
if (delta.length === 0 || delta.length === 1 && typeof delta[0].insert === "string" && delta[0].insert === "") {
|
|
1141
|
+
return /* @__PURE__ */ jsx6("br", { "data-empty": "true" });
|
|
1142
|
+
}
|
|
1143
|
+
let offset = 0;
|
|
1144
|
+
const wrapAnchors = [];
|
|
1145
|
+
const body = [];
|
|
1146
|
+
for (const op of delta) {
|
|
1147
|
+
if (typeof op.insert === "string") {
|
|
1148
|
+
const text = op.insert;
|
|
1149
|
+
if (text.length === 0) continue;
|
|
1150
|
+
const key = `${keyPrefix}-${offset}`;
|
|
1151
|
+
body.push(/* @__PURE__ */ jsx6(Fragment, { children: wrap(text, op.attributes) }, key));
|
|
1152
|
+
offset += text.length;
|
|
1153
|
+
} else if (isImageEmbed(op.insert)) {
|
|
1154
|
+
const key = `${keyPrefix}-${offset}`;
|
|
1155
|
+
const layout = op.insert.layout ?? "inline";
|
|
1156
|
+
if (layout === "wrap-left" || layout === "wrap-right") {
|
|
1157
|
+
wrapAnchors.push(
|
|
1158
|
+
/* @__PURE__ */ jsx6(ImageEmbedView, { embed: op.insert, offset, wrapAnchor: true }, `${key}-anchor`)
|
|
1159
|
+
);
|
|
1160
|
+
body.push(
|
|
1161
|
+
/* @__PURE__ */ jsx6(
|
|
1162
|
+
"span",
|
|
1163
|
+
{
|
|
1164
|
+
"data-embed-phantom": "true",
|
|
1165
|
+
"data-embed-offset": offset,
|
|
1166
|
+
contentEditable: false,
|
|
1167
|
+
style: PHANTOM_STYLE
|
|
1168
|
+
},
|
|
1169
|
+
`${key}-phantom`
|
|
1170
|
+
)
|
|
1171
|
+
);
|
|
1172
|
+
} else {
|
|
1173
|
+
body.push(/* @__PURE__ */ jsx6(ImageEmbedView, { embed: op.insert, offset }, key));
|
|
1174
|
+
}
|
|
1175
|
+
offset += 1;
|
|
1176
|
+
} else if (op.insert != null) {
|
|
1177
|
+
offset += 1;
|
|
1178
|
+
}
|
|
1179
|
+
}
|
|
1180
|
+
if (wrapAnchors.length === 0 && body.length === 0) return /* @__PURE__ */ jsx6("br", { "data-empty": "true" });
|
|
1181
|
+
return [...wrapAnchors, ...body];
|
|
1182
|
+
}
|
|
1183
|
+
var PHANTOM_STYLE = {
|
|
1184
|
+
display: "inline-block",
|
|
1185
|
+
width: 0,
|
|
1186
|
+
height: 0,
|
|
1187
|
+
overflow: "hidden",
|
|
1188
|
+
verticalAlign: "baseline"
|
|
1189
|
+
};
|
|
1190
|
+
function imageEmbedPropsEqual(a, b) {
|
|
1191
|
+
if (a.offset !== b.offset || a.wrapAnchor !== b.wrapAnchor) return false;
|
|
1192
|
+
const x = a.embed;
|
|
1193
|
+
const y = b.embed;
|
|
1194
|
+
if (x === y) return true;
|
|
1195
|
+
return x.width === y.width && x.height === y.height && x.layout === y.layout && x.align === y.align && x.offsetX === y.offsetX && x.offsetY === y.offsetY && x.caption === y.caption && x.captionAlign === y.captionAlign && // src is the expensive compare — only fall back to ref equality. Fresh
|
|
1196
|
+
// toDelta() objects share the same string reference for embed payloads
|
|
1197
|
+
// because Y.js stores the embed in its content map and re-exposes it.
|
|
1198
|
+
x.src === y.src;
|
|
1199
|
+
}
|
|
1200
|
+
var ImageEmbedView = memo(ImageEmbedViewImpl, imageEmbedPropsEqual);
|
|
1201
|
+
function ImageEmbedViewImpl({
|
|
1202
|
+
embed,
|
|
1203
|
+
offset,
|
|
1204
|
+
wrapAnchor
|
|
1205
|
+
}) {
|
|
1206
|
+
const layout = embed.layout ?? "inline";
|
|
1207
|
+
const align = embed.align;
|
|
1208
|
+
const ox = embed.offsetX ?? 0;
|
|
1209
|
+
const oy = embed.offsetY ?? 0;
|
|
1210
|
+
const hasCaption = !!embed.caption && embed.caption.length > 0;
|
|
1211
|
+
let outerStyle;
|
|
1212
|
+
switch (layout) {
|
|
1213
|
+
case "wrap-left":
|
|
1214
|
+
outerStyle = {
|
|
1215
|
+
float: "left",
|
|
1216
|
+
width: embed.width,
|
|
1217
|
+
marginRight: Math.max(8, ox),
|
|
1218
|
+
marginTop: Math.max(0, oy),
|
|
1219
|
+
marginBottom: 4
|
|
1220
|
+
};
|
|
1221
|
+
break;
|
|
1222
|
+
case "wrap-right":
|
|
1223
|
+
outerStyle = {
|
|
1224
|
+
float: "right",
|
|
1225
|
+
width: embed.width,
|
|
1226
|
+
marginLeft: Math.max(8, ox),
|
|
1227
|
+
marginTop: Math.max(0, oy),
|
|
1228
|
+
marginBottom: 4
|
|
1229
|
+
};
|
|
1230
|
+
break;
|
|
1231
|
+
case "behind":
|
|
1232
|
+
case "front":
|
|
1233
|
+
outerStyle = {
|
|
1234
|
+
position: "absolute",
|
|
1235
|
+
left: ox,
|
|
1236
|
+
top: oy,
|
|
1237
|
+
width: embed.width,
|
|
1238
|
+
zIndex: layout === "behind" ? -1 : 2
|
|
1239
|
+
};
|
|
1240
|
+
break;
|
|
1241
|
+
case "inline":
|
|
1242
|
+
default:
|
|
1243
|
+
outerStyle = align ? {
|
|
1244
|
+
display: "block",
|
|
1245
|
+
width: embed.width,
|
|
1246
|
+
marginLeft: align === "left" ? 0 : "auto",
|
|
1247
|
+
marginRight: align === "right" ? 0 : "auto"
|
|
1248
|
+
} : {
|
|
1249
|
+
display: "inline-block",
|
|
1250
|
+
verticalAlign: "text-bottom",
|
|
1251
|
+
width: embed.width
|
|
1252
|
+
};
|
|
1253
|
+
break;
|
|
1254
|
+
}
|
|
1255
|
+
const imgStyle = hasCaption ? { width: embed.width, height: embed.height, display: "block" } : outerStyle;
|
|
1256
|
+
const img = /* @__PURE__ */ jsx6(
|
|
1257
|
+
"img",
|
|
1258
|
+
{
|
|
1259
|
+
src: embed.src,
|
|
1260
|
+
width: embed.width,
|
|
1261
|
+
height: embed.height,
|
|
1262
|
+
contentEditable: false,
|
|
1263
|
+
draggable: false,
|
|
1264
|
+
"data-embed": "image",
|
|
1265
|
+
"data-embed-offset": offset,
|
|
1266
|
+
"data-embed-align": align ?? "",
|
|
1267
|
+
"data-embed-layout": layout,
|
|
1268
|
+
"data-wrap-anchor": wrapAnchor ? "true" : void 0,
|
|
1269
|
+
style: imgStyle,
|
|
1270
|
+
alt: ""
|
|
1271
|
+
}
|
|
1272
|
+
);
|
|
1273
|
+
if (!hasCaption) return img;
|
|
1274
|
+
const captionAlign = embed.captionAlign ?? "center";
|
|
1275
|
+
return /* @__PURE__ */ jsxs6(
|
|
1276
|
+
"figure",
|
|
1277
|
+
{
|
|
1278
|
+
className: "ed-figure",
|
|
1279
|
+
contentEditable: false,
|
|
1280
|
+
"data-embed-figure": "true",
|
|
1281
|
+
"data-embed-layout": layout,
|
|
1282
|
+
style: outerStyle,
|
|
1283
|
+
children: [
|
|
1284
|
+
img,
|
|
1285
|
+
/* @__PURE__ */ jsx6(
|
|
1286
|
+
"figcaption",
|
|
1287
|
+
{
|
|
1288
|
+
className: "ed-figcaption",
|
|
1289
|
+
"data-caption-align": captionAlign,
|
|
1290
|
+
style: { textAlign: captionAlign },
|
|
1291
|
+
children: embed.caption
|
|
1292
|
+
}
|
|
1293
|
+
)
|
|
1294
|
+
]
|
|
1295
|
+
}
|
|
1296
|
+
);
|
|
1297
|
+
}
|
|
1298
|
+
function wrap(text, attrs) {
|
|
1299
|
+
let node = text;
|
|
1300
|
+
if (!attrs) return node;
|
|
1301
|
+
if (attrs.bold) node = /* @__PURE__ */ jsx6("strong", { children: node });
|
|
1302
|
+
if (attrs.italic) node = /* @__PURE__ */ jsx6("em", { children: node });
|
|
1303
|
+
if (attrs.underline) node = /* @__PURE__ */ jsx6("u", { children: node });
|
|
1304
|
+
if (attrs.strike) node = /* @__PURE__ */ jsx6("s", { children: node });
|
|
1305
|
+
const styleParts = {};
|
|
1306
|
+
if (attrs.color) styleParts.color = attrs.color;
|
|
1307
|
+
if (attrs.fontFamily) styleParts.fontFamily = attrs.fontFamily;
|
|
1308
|
+
if (attrs.fontSize) styleParts.fontSize = attrs.fontSize;
|
|
1309
|
+
if (Object.keys(styleParts).length > 0) {
|
|
1310
|
+
node = /* @__PURE__ */ jsx6("span", { style: styleParts, children: node });
|
|
1311
|
+
}
|
|
1312
|
+
if (attrs.link?.href) {
|
|
1313
|
+
node = /* @__PURE__ */ jsx6(
|
|
1314
|
+
"a",
|
|
1315
|
+
{
|
|
1316
|
+
href: attrs.link.href,
|
|
1317
|
+
title: attrs.link.title,
|
|
1318
|
+
rel: "noopener noreferrer",
|
|
1319
|
+
target: "_blank",
|
|
1320
|
+
onClick: (e) => {
|
|
1321
|
+
if (!(e.metaKey || e.ctrlKey)) e.preventDefault();
|
|
1322
|
+
},
|
|
1323
|
+
children: node
|
|
1324
|
+
}
|
|
1325
|
+
);
|
|
1326
|
+
}
|
|
1327
|
+
if (attrs.comment?.markId) {
|
|
1328
|
+
node = /* @__PURE__ */ jsx6("span", { "data-comment-id": attrs.comment.markId, className: "ed-comment", children: node });
|
|
1329
|
+
}
|
|
1330
|
+
return node;
|
|
1331
|
+
}
|
|
1332
|
+
|
|
1333
|
+
// src/sliceDelta.ts
|
|
1334
|
+
function deltaLength(delta) {
|
|
1335
|
+
let sum = 0;
|
|
1336
|
+
for (const op of delta) {
|
|
1337
|
+
if (typeof op.insert === "string") sum += op.insert.length;
|
|
1338
|
+
else if (op.insert != null) sum += 1;
|
|
1339
|
+
}
|
|
1340
|
+
return sum;
|
|
1341
|
+
}
|
|
1342
|
+
function sliceDelta(delta, start, end) {
|
|
1343
|
+
if (end <= start) return [];
|
|
1344
|
+
const out = [];
|
|
1345
|
+
let cursor = 0;
|
|
1346
|
+
for (const op of delta) {
|
|
1347
|
+
if (typeof op.insert === "string") {
|
|
1348
|
+
const opStart = cursor;
|
|
1349
|
+
const opEnd = cursor + op.insert.length;
|
|
1350
|
+
cursor = opEnd;
|
|
1351
|
+
if (opEnd <= start) continue;
|
|
1352
|
+
if (opStart >= end) break;
|
|
1353
|
+
const sliceFrom = Math.max(opStart, start) - opStart;
|
|
1354
|
+
const sliceTo = Math.min(opEnd, end) - opStart;
|
|
1355
|
+
out.push({
|
|
1356
|
+
insert: op.insert.slice(sliceFrom, sliceTo),
|
|
1357
|
+
attributes: op.attributes
|
|
1358
|
+
});
|
|
1359
|
+
} else if (op.insert != null) {
|
|
1360
|
+
const opStart = cursor;
|
|
1361
|
+
const opEnd = cursor + 1;
|
|
1362
|
+
cursor = opEnd;
|
|
1363
|
+
if (opEnd <= start) continue;
|
|
1364
|
+
if (opStart >= end) break;
|
|
1365
|
+
out.push({ insert: op.insert, attributes: op.attributes });
|
|
1366
|
+
}
|
|
1367
|
+
}
|
|
1368
|
+
return out;
|
|
1369
|
+
}
|
|
1370
|
+
|
|
1371
|
+
// src/TableResizeOverlay.tsx
|
|
1372
|
+
import {
|
|
1373
|
+
useCallback as useCallback2,
|
|
1374
|
+
useEffect as useEffect5,
|
|
1375
|
+
useLayoutEffect as useLayoutEffect2,
|
|
1376
|
+
useRef as useRef5,
|
|
1377
|
+
useState as useState5
|
|
1378
|
+
} from "react";
|
|
1379
|
+
import { Fragment as Fragment2, jsx as jsx7 } from "react/jsx-runtime";
|
|
1380
|
+
function TableResizeOverlay({ tableRef, blockIndex, cols }) {
|
|
1381
|
+
const editor = useEditorContext();
|
|
1382
|
+
const [positions, setPositions] = useState5(null);
|
|
1383
|
+
const [layout, setLayout] = useState5(null);
|
|
1384
|
+
const draggingRef = useRef5(null);
|
|
1385
|
+
const measure = useCallback2(() => {
|
|
1386
|
+
const table = tableRef.current;
|
|
1387
|
+
if (!table) return;
|
|
1388
|
+
const tableRect = table.getBoundingClientRect();
|
|
1389
|
+
const cgCols = table.querySelectorAll("colgroup > col");
|
|
1390
|
+
let rights = [];
|
|
1391
|
+
if (cgCols.length === cols) {
|
|
1392
|
+
let acc = 0;
|
|
1393
|
+
cgCols.forEach((c) => {
|
|
1394
|
+
acc += c.getBoundingClientRect().width;
|
|
1395
|
+
rights.push(acc);
|
|
1396
|
+
});
|
|
1397
|
+
} else {
|
|
1398
|
+
const firstRowCells = table.querySelectorAll("tbody > tr:first-child > td");
|
|
1399
|
+
let acc = 0;
|
|
1400
|
+
firstRowCells.forEach((td) => {
|
|
1401
|
+
const cs = Math.max(1, td.colSpan || 1);
|
|
1402
|
+
const w = td.getBoundingClientRect().width / cs;
|
|
1403
|
+
for (let i = 0; i < cs; i++) {
|
|
1404
|
+
acc += w;
|
|
1405
|
+
rights.push(acc);
|
|
1406
|
+
}
|
|
1407
|
+
});
|
|
1408
|
+
}
|
|
1409
|
+
if (rights.length !== cols) {
|
|
1410
|
+
setLayout(null);
|
|
1411
|
+
return;
|
|
1412
|
+
}
|
|
1413
|
+
setLayout({ rights, height: tableRect.height });
|
|
1414
|
+
}, [cols, tableRef]);
|
|
1415
|
+
useLayoutEffect2(() => {
|
|
1416
|
+
measure();
|
|
1417
|
+
}, [measure]);
|
|
1418
|
+
useEffect5(() => {
|
|
1419
|
+
const table = tableRef.current;
|
|
1420
|
+
if (!table) return;
|
|
1421
|
+
const ro = new ResizeObserver(() => measure());
|
|
1422
|
+
ro.observe(table);
|
|
1423
|
+
return () => ro.disconnect();
|
|
1424
|
+
}, [measure, tableRef]);
|
|
1425
|
+
const onPointerDown = useCallback2(
|
|
1426
|
+
(col) => (e) => {
|
|
1427
|
+
e.preventDefault();
|
|
1428
|
+
e.stopPropagation();
|
|
1429
|
+
const table = tableRef.current;
|
|
1430
|
+
if (!table) return;
|
|
1431
|
+
const cgCols = table.querySelectorAll("colgroup > col");
|
|
1432
|
+
const startWidth = cgCols[col]?.getBoundingClientRect().width ?? 100;
|
|
1433
|
+
draggingRef.current = { col, startX: e.clientX, startWidth };
|
|
1434
|
+
setPositions({ x: e.clientX, height: layout?.height ?? 0 });
|
|
1435
|
+
e.currentTarget.setPointerCapture(e.pointerId);
|
|
1436
|
+
},
|
|
1437
|
+
[layout, tableRef]
|
|
1438
|
+
);
|
|
1439
|
+
const onPointerMove = useCallback2(
|
|
1440
|
+
(e) => {
|
|
1441
|
+
const d = draggingRef.current;
|
|
1442
|
+
if (!d) return;
|
|
1443
|
+
const dx = e.clientX - d.startX;
|
|
1444
|
+
const next = Math.max(40, d.startWidth + dx);
|
|
1445
|
+
editor.setColumnWidth(blockIndex, d.col, next);
|
|
1446
|
+
},
|
|
1447
|
+
[blockIndex, editor]
|
|
1448
|
+
);
|
|
1449
|
+
const onPointerUp = useCallback2((e) => {
|
|
1450
|
+
draggingRef.current = null;
|
|
1451
|
+
setPositions(null);
|
|
1452
|
+
try {
|
|
1453
|
+
e.currentTarget.releasePointerCapture(e.pointerId);
|
|
1454
|
+
} catch {
|
|
1455
|
+
}
|
|
1456
|
+
}, []);
|
|
1457
|
+
if (!layout) return null;
|
|
1458
|
+
const handleStyle = (x) => ({
|
|
1459
|
+
position: "absolute",
|
|
1460
|
+
top: 0,
|
|
1461
|
+
left: `${x - 3}px`,
|
|
1462
|
+
width: "6px",
|
|
1463
|
+
height: `${layout.height}px`,
|
|
1464
|
+
cursor: "col-resize",
|
|
1465
|
+
zIndex: 5
|
|
1466
|
+
});
|
|
1467
|
+
void positions;
|
|
1468
|
+
return /* @__PURE__ */ jsx7(Fragment2, { children: layout.rights.map((x, c) => /* @__PURE__ */ jsx7(
|
|
1469
|
+
"div",
|
|
1470
|
+
{
|
|
1471
|
+
contentEditable: false,
|
|
1472
|
+
className: "ed-col-resize-handle",
|
|
1473
|
+
style: handleStyle(x),
|
|
1474
|
+
onPointerDown: onPointerDown(c),
|
|
1475
|
+
onPointerMove,
|
|
1476
|
+
onPointerUp,
|
|
1477
|
+
"aria-label": `Redimensionar coluna ${c + 1}`,
|
|
1478
|
+
role: "separator"
|
|
1479
|
+
},
|
|
1480
|
+
c
|
|
1481
|
+
)) });
|
|
1482
|
+
}
|
|
1483
|
+
|
|
1484
|
+
// src/NodeView.tsx
|
|
1485
|
+
import { jsx as jsx8, jsxs as jsxs7 } from "react/jsx-runtime";
|
|
1486
|
+
function NodeView({ block, index, children, fragment, tableFragment }) {
|
|
1487
|
+
if (block.type === "table") {
|
|
1488
|
+
return /* @__PURE__ */ jsx8(TableView, { block, index, tableFragment });
|
|
1489
|
+
}
|
|
1490
|
+
const inline = fragment ? renderInline(sliceDelta(block.delta, fragment.start, fragment.end), index) : renderInline(block.delta, index);
|
|
1491
|
+
const common = commonBlockProps(block.attrs, index, block.type, fragment);
|
|
1492
|
+
switch (block.type) {
|
|
1493
|
+
case "heading": {
|
|
1494
|
+
const level = clampLevel(block.attrs.level);
|
|
1495
|
+
const Tag = `h${level}`;
|
|
1496
|
+
return /* @__PURE__ */ jsx8(
|
|
1497
|
+
Tag,
|
|
1498
|
+
{
|
|
1499
|
+
...common,
|
|
1500
|
+
className: `ed-block ed-heading ed-heading-${level}${alignClass(block.attrs.align)}${fragmentClass(fragment)}`,
|
|
1501
|
+
children: inline
|
|
1502
|
+
}
|
|
1503
|
+
);
|
|
1504
|
+
}
|
|
1505
|
+
case "blockquote":
|
|
1506
|
+
return /* @__PURE__ */ jsx8(
|
|
1507
|
+
"blockquote",
|
|
1508
|
+
{
|
|
1509
|
+
...common,
|
|
1510
|
+
className: `ed-block ed-blockquote${alignClass(block.attrs.align)}${fragmentClass(fragment)}`,
|
|
1511
|
+
children: inline
|
|
1512
|
+
}
|
|
1513
|
+
);
|
|
1514
|
+
case "codeBlock":
|
|
1515
|
+
return /* @__PURE__ */ jsx8(
|
|
1516
|
+
"pre",
|
|
1517
|
+
{
|
|
1518
|
+
...common,
|
|
1519
|
+
"data-language": block.attrs.language ?? void 0,
|
|
1520
|
+
className: `ed-block ed-codeblock${alignClass(block.attrs.align)}`,
|
|
1521
|
+
children: /* @__PURE__ */ jsx8("code", { children: inline })
|
|
1522
|
+
}
|
|
1523
|
+
);
|
|
1524
|
+
case "listItem": {
|
|
1525
|
+
const level = clampListLevel(block.attrs.listLevel);
|
|
1526
|
+
return /* @__PURE__ */ jsxs7(
|
|
1527
|
+
"li",
|
|
1528
|
+
{
|
|
1529
|
+
...common,
|
|
1530
|
+
"data-list-kind": block.attrs.listKind ?? "bullet",
|
|
1531
|
+
"data-list-level": level,
|
|
1532
|
+
className: `ed-block ed-listitem${alignClass(block.attrs.align)}`,
|
|
1533
|
+
children: [
|
|
1534
|
+
inline,
|
|
1535
|
+
children
|
|
1536
|
+
]
|
|
1537
|
+
}
|
|
1538
|
+
);
|
|
1539
|
+
}
|
|
1540
|
+
case "paragraph":
|
|
1541
|
+
default:
|
|
1542
|
+
return /* @__PURE__ */ jsx8(
|
|
1543
|
+
"div",
|
|
1544
|
+
{
|
|
1545
|
+
...common,
|
|
1546
|
+
className: `ed-block ed-paragraph${alignClass(block.attrs.align)}${fragmentClass(fragment)}`,
|
|
1547
|
+
children: inline
|
|
1548
|
+
}
|
|
1549
|
+
);
|
|
1550
|
+
}
|
|
1551
|
+
}
|
|
1552
|
+
function commonBlockProps(attrs, index, type, fragment) {
|
|
1553
|
+
const style = {};
|
|
1554
|
+
if (attrs.align) style.textAlign = attrs.align;
|
|
1555
|
+
const base = {
|
|
1556
|
+
"data-block-index": index,
|
|
1557
|
+
"data-block-type": type,
|
|
1558
|
+
dir: attrs.dir,
|
|
1559
|
+
style: Object.keys(style).length > 0 ? style : void 0
|
|
1560
|
+
};
|
|
1561
|
+
if (!fragment) return base;
|
|
1562
|
+
base["data-fragment-index"] = fragment.index;
|
|
1563
|
+
base["data-fragment-start"] = fragment.start;
|
|
1564
|
+
base["data-fragment-end"] = fragment.end;
|
|
1565
|
+
return base;
|
|
1566
|
+
}
|
|
1567
|
+
function fragmentClass(f) {
|
|
1568
|
+
if (!f) return "";
|
|
1569
|
+
return ` ed-fragment ed-fragment-${f.index}`;
|
|
1570
|
+
}
|
|
1571
|
+
function alignClass(a) {
|
|
1572
|
+
return a ? ` ed-align-${a}` : "";
|
|
1573
|
+
}
|
|
1574
|
+
function clampLevel(l) {
|
|
1575
|
+
if (l === 2 || l === 3 || l === 4 || l === 5 || l === 6) return l;
|
|
1576
|
+
return 1;
|
|
1577
|
+
}
|
|
1578
|
+
function clampListLevel(l) {
|
|
1579
|
+
if (typeof l !== "number" || !Number.isFinite(l)) return 0;
|
|
1580
|
+
return Math.max(0, Math.min(5, Math.trunc(l)));
|
|
1581
|
+
}
|
|
1582
|
+
function TableView({ block, index, tableFragment }) {
|
|
1583
|
+
const rows = clampPositive(block.attrs.rows);
|
|
1584
|
+
const cols = clampPositive(block.attrs.cols);
|
|
1585
|
+
const cells = block.cells ?? [];
|
|
1586
|
+
const widths = readColWidths(block.attrs.colWidths, cols);
|
|
1587
|
+
const tableRef = useRef6(null);
|
|
1588
|
+
const rowStart = tableFragment?.rowStart ?? 0;
|
|
1589
|
+
const rowEnd = tableFragment?.rowEnd ?? rows;
|
|
1590
|
+
const isFirstFragment = !tableFragment || tableFragment.index === 0;
|
|
1591
|
+
const editor = useEditorContext();
|
|
1592
|
+
const rect = useMemo2(() => {
|
|
1593
|
+
if (editor.selection.anchor.blockIndex !== index) return null;
|
|
1594
|
+
if (editor.selection.focus.blockIndex !== index) return null;
|
|
1595
|
+
return tableRectSelection(editor.doc, editor.selection);
|
|
1596
|
+
}, [editor.doc, editor.selection, index]);
|
|
1597
|
+
return /* @__PURE__ */ jsxs7("div", { className: "ed-table-wrap", children: [
|
|
1598
|
+
/* @__PURE__ */ jsxs7(
|
|
1599
|
+
"table",
|
|
1600
|
+
{
|
|
1601
|
+
ref: tableRef,
|
|
1602
|
+
"data-block-index": index,
|
|
1603
|
+
"data-block-type": "table",
|
|
1604
|
+
className: "ed-block ed-table",
|
|
1605
|
+
children: [
|
|
1606
|
+
widths && /* @__PURE__ */ jsx8("colgroup", { children: widths.map((w, c) => /* @__PURE__ */ jsx8("col", { style: w != null ? { width: `${w}px` } : void 0 }, c)) }),
|
|
1607
|
+
/* @__PURE__ */ jsx8("tbody", { children: Array.from({ length: rowEnd - rowStart }, (_, i) => {
|
|
1608
|
+
const r = rowStart + i;
|
|
1609
|
+
return /* @__PURE__ */ jsx8("tr", { "data-cell-row": r, children: Array.from({ length: cols }, (_2, c) => {
|
|
1610
|
+
const flat = r * cols + c;
|
|
1611
|
+
const cell = cells[flat];
|
|
1612
|
+
if (cell?.attrs.covered === true) return null;
|
|
1613
|
+
const rowspan = spanAttr(cell?.attrs.rowspan);
|
|
1614
|
+
const colspan = spanAttr(cell?.attrs.colspan);
|
|
1615
|
+
const delta = cell?.delta ?? [];
|
|
1616
|
+
const inRect = rect !== null && r >= rect.top && r <= rect.bottom && c >= rect.left && c <= rect.right;
|
|
1617
|
+
return /* @__PURE__ */ jsx8(
|
|
1618
|
+
"td",
|
|
1619
|
+
{
|
|
1620
|
+
"data-cell-index": flat,
|
|
1621
|
+
"data-cell-row": r,
|
|
1622
|
+
"data-cell-col": c,
|
|
1623
|
+
"data-cell-rowspan": rowspan,
|
|
1624
|
+
"data-cell-colspan": colspan,
|
|
1625
|
+
rowSpan: rowspan > 1 ? rowspan : void 0,
|
|
1626
|
+
colSpan: colspan > 1 ? colspan : void 0,
|
|
1627
|
+
className: inRect ? "ed-cell ed-cell--selected" : "ed-cell",
|
|
1628
|
+
children: renderInline(delta, `t${index}-c${flat}`)
|
|
1629
|
+
},
|
|
1630
|
+
c
|
|
1631
|
+
);
|
|
1632
|
+
}) }, r);
|
|
1633
|
+
}) })
|
|
1634
|
+
]
|
|
1635
|
+
}
|
|
1636
|
+
),
|
|
1637
|
+
isFirstFragment && /* @__PURE__ */ jsx8(TableResizeOverlay, { tableRef, blockIndex: index, cols })
|
|
1638
|
+
] });
|
|
1639
|
+
}
|
|
1640
|
+
function readColWidths(raw, cols) {
|
|
1641
|
+
if (!Array.isArray(raw)) return null;
|
|
1642
|
+
const out = [];
|
|
1643
|
+
for (let i = 0; i < cols; i++) {
|
|
1644
|
+
const v = raw[i];
|
|
1645
|
+
out.push(typeof v === "number" && Number.isFinite(v) && v > 0 ? v : null);
|
|
1646
|
+
}
|
|
1647
|
+
return out;
|
|
1648
|
+
}
|
|
1649
|
+
function spanAttr(v) {
|
|
1650
|
+
if (typeof v !== "number" || !Number.isFinite(v) || v < 1) return 1;
|
|
1651
|
+
return Math.trunc(v);
|
|
1652
|
+
}
|
|
1653
|
+
function clampPositive(n) {
|
|
1654
|
+
if (typeof n !== "number" || !Number.isFinite(n) || n <= 0) return 1;
|
|
1655
|
+
return Math.trunc(n);
|
|
1656
|
+
}
|
|
1657
|
+
|
|
1658
|
+
// src/usePagination.ts
|
|
1659
|
+
import { useLayoutEffect as useLayoutEffect3, useRef as useRef7, useState as useState6 } from "react";
|
|
1660
|
+
var A4_PAGE = {
|
|
1661
|
+
width: 794,
|
|
1662
|
+
height: 1123,
|
|
1663
|
+
marginTop: 96,
|
|
1664
|
+
marginBottom: 96,
|
|
1665
|
+
marginLeft: 96,
|
|
1666
|
+
marginRight: 96
|
|
1667
|
+
};
|
|
1668
|
+
function contentHeight(g) {
|
|
1669
|
+
return g.height - g.marginTop - g.marginBottom;
|
|
1670
|
+
}
|
|
1671
|
+
function contentWidth(g) {
|
|
1672
|
+
return g.width - g.marginLeft - g.marginRight;
|
|
1673
|
+
}
|
|
1674
|
+
function defaultPageLayout(topLevelCount) {
|
|
1675
|
+
return {
|
|
1676
|
+
pages: [
|
|
1677
|
+
{
|
|
1678
|
+
slots: Array.from({ length: topLevelCount }, (_, i) => ({ topLevelIndex: i }))
|
|
1679
|
+
}
|
|
1680
|
+
]
|
|
1681
|
+
};
|
|
1682
|
+
}
|
|
1683
|
+
function usePageLayout(rootRef, geometry, topLevelCount, deps) {
|
|
1684
|
+
const [phase, setPhase] = useState6("measure");
|
|
1685
|
+
const [layout, setLayout] = useState6(() => defaultPageLayout(topLevelCount));
|
|
1686
|
+
const lastDepsRef = useRef7([]);
|
|
1687
|
+
const currentDeps = [...deps, topLevelCount, geometry];
|
|
1688
|
+
if (!shallowArrayEqual(lastDepsRef.current, currentDeps)) {
|
|
1689
|
+
lastDepsRef.current = currentDeps;
|
|
1690
|
+
if (phase !== "measure") setPhase("measure");
|
|
1691
|
+
const next = defaultPageLayout(topLevelCount);
|
|
1692
|
+
if (!layoutsDeepEqual(layout, next)) setLayout(next);
|
|
1693
|
+
}
|
|
1694
|
+
useLayoutEffect3(() => {
|
|
1695
|
+
if (phase !== "measure") return;
|
|
1696
|
+
const root = rootRef.current;
|
|
1697
|
+
if (!root) return;
|
|
1698
|
+
if (!isUnfragmented(layout)) return;
|
|
1699
|
+
const next = computeLayout(root, geometry, topLevelCount);
|
|
1700
|
+
if (!layoutsDeepEqual(layout, next)) setLayout(next);
|
|
1701
|
+
setPhase("stable");
|
|
1702
|
+
}, [phase, layout, geometry, topLevelCount, rootRef]);
|
|
1703
|
+
return layout;
|
|
1704
|
+
}
|
|
1705
|
+
function isUnfragmented(layout) {
|
|
1706
|
+
return layout.pages.length === 1 && layout.pages[0].slots.every(
|
|
1707
|
+
(s) => s.fragment === void 0 && s.tableFragment === void 0
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
function computeLayout(root, geometry, topLevelCount) {
|
|
1711
|
+
const tops = collectTopLevelElements(root);
|
|
1712
|
+
if (tops.length === 0) return defaultPageLayout(topLevelCount);
|
|
1713
|
+
const limit = contentHeight(geometry);
|
|
1714
|
+
const pages = [{ slots: [] }];
|
|
1715
|
+
let currentPage = pages[0];
|
|
1716
|
+
let used = 0;
|
|
1717
|
+
const cap = Math.min(tops.length, topLevelCount);
|
|
1718
|
+
for (let i = 0; i < cap; i++) {
|
|
1719
|
+
const el = tops[i];
|
|
1720
|
+
const h = elTotalHeight(el);
|
|
1721
|
+
if (used + h <= limit) {
|
|
1722
|
+
currentPage.slots.push({ topLevelIndex: i });
|
|
1723
|
+
used += h;
|
|
1724
|
+
continue;
|
|
1725
|
+
}
|
|
1726
|
+
if (isFragmentable(el)) {
|
|
1727
|
+
const result = placeFragmentedBlock(el, i, pages, used, limit);
|
|
1728
|
+
currentPage = pages[pages.length - 1];
|
|
1729
|
+
used = result.lastUsed;
|
|
1730
|
+
continue;
|
|
1731
|
+
}
|
|
1732
|
+
if (isTableTopLevel(el)) {
|
|
1733
|
+
const result = placeFragmentedTable(el, i, pages, used, limit);
|
|
1734
|
+
currentPage = pages[pages.length - 1];
|
|
1735
|
+
used = result.lastUsed;
|
|
1736
|
+
continue;
|
|
1737
|
+
}
|
|
1738
|
+
if (currentPage.slots.length === 0) {
|
|
1739
|
+
currentPage.slots.push({ topLevelIndex: i });
|
|
1740
|
+
pages.push({ slots: [] });
|
|
1741
|
+
currentPage = pages[pages.length - 1];
|
|
1742
|
+
used = 0;
|
|
1743
|
+
} else {
|
|
1744
|
+
pages.push({ slots: [] });
|
|
1745
|
+
currentPage = pages[pages.length - 1];
|
|
1746
|
+
currentPage.slots.push({ topLevelIndex: i });
|
|
1747
|
+
used = h;
|
|
1748
|
+
}
|
|
1749
|
+
}
|
|
1750
|
+
if (pages.length > 1 && pages[pages.length - 1].slots.length === 0) pages.pop();
|
|
1751
|
+
return { pages };
|
|
1752
|
+
}
|
|
1753
|
+
function placeFragmentedBlock(el, topLevelIndex, pages, startUsed, limit) {
|
|
1754
|
+
const totalChars = getBlockTextLength(el);
|
|
1755
|
+
if (totalChars === 0) {
|
|
1756
|
+
const h = elTotalHeight(el);
|
|
1757
|
+
let currentPage2 = pages[pages.length - 1];
|
|
1758
|
+
if (startUsed + h > limit && currentPage2.slots.length > 0) {
|
|
1759
|
+
pages.push({ slots: [] });
|
|
1760
|
+
currentPage2 = pages[pages.length - 1];
|
|
1761
|
+
startUsed = 0;
|
|
1762
|
+
}
|
|
1763
|
+
currentPage2.slots.push({ topLevelIndex });
|
|
1764
|
+
return { lastUsed: startUsed + h };
|
|
1765
|
+
}
|
|
1766
|
+
const lines = measureLines(el);
|
|
1767
|
+
if (lines.length === 0) {
|
|
1768
|
+
const h = elTotalHeight(el);
|
|
1769
|
+
let currentPage2 = pages[pages.length - 1];
|
|
1770
|
+
if (startUsed + h > limit && currentPage2.slots.length > 0) {
|
|
1771
|
+
pages.push({ slots: [] });
|
|
1772
|
+
currentPage2 = pages[pages.length - 1];
|
|
1773
|
+
startUsed = 0;
|
|
1774
|
+
}
|
|
1775
|
+
currentPage2.slots.push({ topLevelIndex });
|
|
1776
|
+
return { lastUsed: startUsed + h };
|
|
1777
|
+
}
|
|
1778
|
+
let lineIdx = 0;
|
|
1779
|
+
let currentPage = pages[pages.length - 1];
|
|
1780
|
+
let used = startUsed;
|
|
1781
|
+
let fragIndex = 0;
|
|
1782
|
+
while (lineIdx < lines.length) {
|
|
1783
|
+
const available = Math.max(0, limit - used);
|
|
1784
|
+
if (available <= 0) {
|
|
1785
|
+
pages.push({ slots: [] });
|
|
1786
|
+
currentPage = pages[pages.length - 1];
|
|
1787
|
+
used = 0;
|
|
1788
|
+
continue;
|
|
1789
|
+
}
|
|
1790
|
+
const startTop = lines[lineIdx].top;
|
|
1791
|
+
let bestK = -1;
|
|
1792
|
+
for (let k = lineIdx; k < lines.length; k++) {
|
|
1793
|
+
const h = lines[k].bottom - startTop;
|
|
1794
|
+
if (h <= available) bestK = k;
|
|
1795
|
+
else break;
|
|
1796
|
+
}
|
|
1797
|
+
if (bestK < 0) {
|
|
1798
|
+
if (currentPage.slots.length === 0) {
|
|
1799
|
+
bestK = lineIdx;
|
|
1800
|
+
} else {
|
|
1801
|
+
pages.push({ slots: [] });
|
|
1802
|
+
currentPage = pages[pages.length - 1];
|
|
1803
|
+
used = 0;
|
|
1804
|
+
continue;
|
|
1805
|
+
}
|
|
1806
|
+
}
|
|
1807
|
+
const fragStart = lines[lineIdx].start;
|
|
1808
|
+
const fragEnd = bestK + 1 < lines.length ? lines[bestK + 1].start : totalChars;
|
|
1809
|
+
const fragHeight = lines[bestK].bottom - startTop;
|
|
1810
|
+
currentPage.slots.push({
|
|
1811
|
+
topLevelIndex,
|
|
1812
|
+
fragment: { index: fragIndex, start: fragStart, end: fragEnd }
|
|
1813
|
+
});
|
|
1814
|
+
used += fragHeight;
|
|
1815
|
+
fragIndex++;
|
|
1816
|
+
lineIdx = bestK + 1;
|
|
1817
|
+
if (lineIdx < lines.length) {
|
|
1818
|
+
pages.push({ slots: [] });
|
|
1819
|
+
currentPage = pages[pages.length - 1];
|
|
1820
|
+
used = 0;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
return { lastUsed: used };
|
|
1824
|
+
}
|
|
1825
|
+
var lineMetricsCache = /* @__PURE__ */ new Map();
|
|
1826
|
+
var LINE_METRICS_CACHE_LIMIT = 200;
|
|
1827
|
+
function measureLines(el) {
|
|
1828
|
+
const text = el.textContent ?? "";
|
|
1829
|
+
const cacheKey = `${el.offsetWidth}|${text}`;
|
|
1830
|
+
const cached = lineMetricsCache.get(cacheKey);
|
|
1831
|
+
if (cached) {
|
|
1832
|
+
lineMetricsCache.delete(cacheKey);
|
|
1833
|
+
lineMetricsCache.set(cacheKey, cached);
|
|
1834
|
+
return cached;
|
|
1835
|
+
}
|
|
1836
|
+
const fresh = measureLinesUncached(el);
|
|
1837
|
+
lineMetricsCache.set(cacheKey, fresh);
|
|
1838
|
+
if (lineMetricsCache.size > LINE_METRICS_CACHE_LIMIT) {
|
|
1839
|
+
const oldest = lineMetricsCache.keys().next().value;
|
|
1840
|
+
if (oldest !== void 0) lineMetricsCache.delete(oldest);
|
|
1841
|
+
}
|
|
1842
|
+
return fresh;
|
|
1843
|
+
}
|
|
1844
|
+
function measureLinesUncached(el) {
|
|
1845
|
+
const range = document.createRange();
|
|
1846
|
+
const lines = [];
|
|
1847
|
+
const walker = document.createTreeWalker(
|
|
1848
|
+
el,
|
|
1849
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
|
|
1850
|
+
blockBoundaryFilter(el)
|
|
1851
|
+
);
|
|
1852
|
+
let n;
|
|
1853
|
+
let charOffset = 0;
|
|
1854
|
+
while (n = walker.nextNode()) {
|
|
1855
|
+
if (n.nodeType === Node.TEXT_NODE) {
|
|
1856
|
+
const text = n.textContent ?? "";
|
|
1857
|
+
for (let i = 0; i < text.length; i++) {
|
|
1858
|
+
try {
|
|
1859
|
+
range.setStart(n, i);
|
|
1860
|
+
range.setEnd(n, i + 1);
|
|
1861
|
+
} catch {
|
|
1862
|
+
continue;
|
|
1863
|
+
}
|
|
1864
|
+
const rect = range.getBoundingClientRect();
|
|
1865
|
+
if (rect.width === 0 && rect.height === 0) continue;
|
|
1866
|
+
const last = lines[lines.length - 1];
|
|
1867
|
+
if (!last || rect.top >= last.bottom) {
|
|
1868
|
+
lines.push({ start: charOffset + i, top: rect.top, bottom: rect.bottom });
|
|
1869
|
+
} else {
|
|
1870
|
+
last.top = Math.min(last.top, rect.top);
|
|
1871
|
+
last.bottom = Math.max(last.bottom, rect.bottom);
|
|
1872
|
+
}
|
|
1873
|
+
}
|
|
1874
|
+
charOffset += text.length;
|
|
1875
|
+
continue;
|
|
1876
|
+
}
|
|
1877
|
+
if (n.nodeType === Node.ELEMENT_NODE && n.dataset.embed === "image") {
|
|
1878
|
+
const rect = n.getBoundingClientRect();
|
|
1879
|
+
if (rect.height > 0) {
|
|
1880
|
+
const last = lines[lines.length - 1];
|
|
1881
|
+
if (!last || rect.top >= last.bottom) {
|
|
1882
|
+
lines.push({ start: charOffset, top: rect.top, bottom: rect.bottom });
|
|
1883
|
+
} else {
|
|
1884
|
+
last.top = Math.min(last.top, rect.top);
|
|
1885
|
+
last.bottom = Math.max(last.bottom, rect.bottom);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
charOffset += 1;
|
|
1889
|
+
}
|
|
1890
|
+
}
|
|
1891
|
+
return lines;
|
|
1892
|
+
}
|
|
1893
|
+
function elTotalHeight(el) {
|
|
1894
|
+
const cs = getComputedStyle(el);
|
|
1895
|
+
const m = parseFloat(cs.marginTop || "0") + parseFloat(cs.marginBottom || "0");
|
|
1896
|
+
return el.offsetHeight + m;
|
|
1897
|
+
}
|
|
1898
|
+
function isFragmentable(el) {
|
|
1899
|
+
const type = el.getAttribute("data-block-type");
|
|
1900
|
+
if (type === "paragraph") return true;
|
|
1901
|
+
if (type === "heading") return true;
|
|
1902
|
+
if (type === "blockquote") return true;
|
|
1903
|
+
return false;
|
|
1904
|
+
}
|
|
1905
|
+
function isTableTopLevel(el) {
|
|
1906
|
+
return el.querySelector(':scope > table[data-block-type="table"]') !== null;
|
|
1907
|
+
}
|
|
1908
|
+
function placeFragmentedTable(el, topLevelIndex, pages, startUsed, limit) {
|
|
1909
|
+
const table = el.querySelector(':scope > table[data-block-type="table"]');
|
|
1910
|
+
const tbody = table?.querySelector(":scope > tbody");
|
|
1911
|
+
const trs = tbody ? Array.from(tbody.children) : [];
|
|
1912
|
+
if (trs.length === 0) {
|
|
1913
|
+
const h = elTotalHeight(el);
|
|
1914
|
+
let currentPage2 = pages[pages.length - 1];
|
|
1915
|
+
if (startUsed + h > limit && currentPage2.slots.length > 0) {
|
|
1916
|
+
pages.push({ slots: [] });
|
|
1917
|
+
currentPage2 = pages[pages.length - 1];
|
|
1918
|
+
startUsed = 0;
|
|
1919
|
+
}
|
|
1920
|
+
currentPage2.slots.push({ topLevelIndex });
|
|
1921
|
+
return { lastUsed: startUsed + h };
|
|
1922
|
+
}
|
|
1923
|
+
const rowHeights = trs.map((tr) => tr.getBoundingClientRect().height);
|
|
1924
|
+
const invalid = /* @__PURE__ */ new Set();
|
|
1925
|
+
for (let r = 0; r < trs.length; r++) {
|
|
1926
|
+
const tds = Array.from(trs[r].children);
|
|
1927
|
+
for (const td of tds) {
|
|
1928
|
+
const rs = td.rowSpan || 1;
|
|
1929
|
+
if (rs > 1) {
|
|
1930
|
+
for (let k = r; k <= r + rs - 2 && k < trs.length - 1; k++) {
|
|
1931
|
+
invalid.add(k);
|
|
1932
|
+
}
|
|
1933
|
+
}
|
|
1934
|
+
}
|
|
1935
|
+
}
|
|
1936
|
+
const wrapHeight = elTotalHeight(el);
|
|
1937
|
+
const tableHeight = table ? table.getBoundingClientRect().height : 0;
|
|
1938
|
+
const chrome = Math.max(0, wrapHeight - tableHeight);
|
|
1939
|
+
let rowIdx = 0;
|
|
1940
|
+
let used = startUsed;
|
|
1941
|
+
let fragIndex = 0;
|
|
1942
|
+
let currentPage = pages[pages.length - 1];
|
|
1943
|
+
while (rowIdx < trs.length) {
|
|
1944
|
+
const overhead = fragIndex === 0 ? chrome : 0;
|
|
1945
|
+
const available = Math.max(0, limit - used - overhead);
|
|
1946
|
+
if (available <= 0) {
|
|
1947
|
+
pages.push({ slots: [] });
|
|
1948
|
+
currentPage = pages[pages.length - 1];
|
|
1949
|
+
used = 0;
|
|
1950
|
+
continue;
|
|
1951
|
+
}
|
|
1952
|
+
let bestEnd = -1;
|
|
1953
|
+
let accH = 0;
|
|
1954
|
+
for (let k = rowIdx; k < trs.length; k++) {
|
|
1955
|
+
accH += rowHeights[k];
|
|
1956
|
+
if (accH > available) break;
|
|
1957
|
+
if (k === trs.length - 1 || !invalid.has(k)) bestEnd = k + 1;
|
|
1958
|
+
}
|
|
1959
|
+
if (bestEnd < 0) {
|
|
1960
|
+
if (currentPage.slots.length === 0) {
|
|
1961
|
+
let k = rowIdx;
|
|
1962
|
+
while (k < trs.length - 1 && invalid.has(k)) k++;
|
|
1963
|
+
bestEnd = k + 1;
|
|
1964
|
+
} else {
|
|
1965
|
+
pages.push({ slots: [] });
|
|
1966
|
+
currentPage = pages[pages.length - 1];
|
|
1967
|
+
used = 0;
|
|
1968
|
+
continue;
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
let fragHeight = 0;
|
|
1972
|
+
for (let k = rowIdx; k < bestEnd; k++) fragHeight += rowHeights[k];
|
|
1973
|
+
if (fragIndex === 0) fragHeight += chrome;
|
|
1974
|
+
currentPage.slots.push({
|
|
1975
|
+
topLevelIndex,
|
|
1976
|
+
tableFragment: { index: fragIndex, rowStart: rowIdx, rowEnd: bestEnd }
|
|
1977
|
+
});
|
|
1978
|
+
used += fragHeight;
|
|
1979
|
+
fragIndex++;
|
|
1980
|
+
rowIdx = bestEnd;
|
|
1981
|
+
if (rowIdx < trs.length) {
|
|
1982
|
+
pages.push({ slots: [] });
|
|
1983
|
+
currentPage = pages[pages.length - 1];
|
|
1984
|
+
used = 0;
|
|
1985
|
+
}
|
|
1986
|
+
}
|
|
1987
|
+
return { lastUsed: used };
|
|
1988
|
+
}
|
|
1989
|
+
function collectTopLevelElements(root) {
|
|
1990
|
+
const pageContents = root.querySelectorAll(":scope > .ed-page > .ed-page-content");
|
|
1991
|
+
if (pageContents.length > 0) {
|
|
1992
|
+
const out = [];
|
|
1993
|
+
pageContents.forEach((pc) => {
|
|
1994
|
+
Array.from(pc.children).forEach((c) => out.push(c));
|
|
1995
|
+
});
|
|
1996
|
+
return out;
|
|
1997
|
+
}
|
|
1998
|
+
return Array.from(root.children);
|
|
1999
|
+
}
|
|
2000
|
+
function getBlockTextLength(el) {
|
|
2001
|
+
const walker = document.createTreeWalker(
|
|
2002
|
+
el,
|
|
2003
|
+
NodeFilter.SHOW_TEXT | NodeFilter.SHOW_ELEMENT,
|
|
2004
|
+
blockBoundaryFilter(el)
|
|
2005
|
+
);
|
|
2006
|
+
let n;
|
|
2007
|
+
let sum = 0;
|
|
2008
|
+
while (n = walker.nextNode()) {
|
|
2009
|
+
if (n.nodeType === Node.TEXT_NODE) {
|
|
2010
|
+
sum += (n.textContent ?? "").length;
|
|
2011
|
+
} else if (n.nodeType === Node.ELEMENT_NODE && n.dataset.embed === "image") {
|
|
2012
|
+
sum += 1;
|
|
2013
|
+
}
|
|
2014
|
+
}
|
|
2015
|
+
return sum;
|
|
2016
|
+
}
|
|
2017
|
+
function blockBoundaryFilter(blockEl) {
|
|
2018
|
+
return {
|
|
2019
|
+
acceptNode(n) {
|
|
2020
|
+
let p = n.parentNode;
|
|
2021
|
+
while (p && p !== blockEl) {
|
|
2022
|
+
if (p.nodeType === Node.ELEMENT_NODE) {
|
|
2023
|
+
const e = p;
|
|
2024
|
+
if (e.dataset.blockIndex != null) return NodeFilter.FILTER_REJECT;
|
|
2025
|
+
}
|
|
2026
|
+
p = p.parentNode;
|
|
2027
|
+
}
|
|
2028
|
+
return NodeFilter.FILTER_ACCEPT;
|
|
2029
|
+
}
|
|
2030
|
+
};
|
|
2031
|
+
}
|
|
2032
|
+
function shallowArrayEqual(a, b) {
|
|
2033
|
+
if (a.length !== b.length) return false;
|
|
2034
|
+
for (let i = 0; i < a.length; i++) if (a[i] !== b[i]) return false;
|
|
2035
|
+
return true;
|
|
2036
|
+
}
|
|
2037
|
+
function layoutsDeepEqual(a, b) {
|
|
2038
|
+
if (a.pages.length !== b.pages.length) return false;
|
|
2039
|
+
for (let i = 0; i < a.pages.length; i++) {
|
|
2040
|
+
const sA = a.pages[i].slots;
|
|
2041
|
+
const sB = b.pages[i].slots;
|
|
2042
|
+
if (sA.length !== sB.length) return false;
|
|
2043
|
+
for (let j = 0; j < sA.length; j++) {
|
|
2044
|
+
if (sA[j].topLevelIndex !== sB[j].topLevelIndex) return false;
|
|
2045
|
+
const fA = sA[j].fragment;
|
|
2046
|
+
const fB = sB[j].fragment;
|
|
2047
|
+
if (fA || fB) {
|
|
2048
|
+
if (!fA || !fB) return false;
|
|
2049
|
+
if (fA.index !== fB.index || fA.start !== fB.start || fA.end !== fB.end) return false;
|
|
2050
|
+
}
|
|
2051
|
+
const tA = sA[j].tableFragment;
|
|
2052
|
+
const tB = sB[j].tableFragment;
|
|
2053
|
+
if (tA || tB) {
|
|
2054
|
+
if (!tA || !tB) return false;
|
|
2055
|
+
if (tA.index !== tB.index || tA.rowStart !== tB.rowStart || tA.rowEnd !== tB.rowEnd) {
|
|
2056
|
+
return false;
|
|
2057
|
+
}
|
|
2058
|
+
}
|
|
2059
|
+
}
|
|
2060
|
+
}
|
|
2061
|
+
return true;
|
|
2062
|
+
}
|
|
2063
|
+
|
|
2064
|
+
// src/useEditor.ts
|
|
2065
|
+
import { useCallback as useCallback4, useEffect as useEffect6, useMemo as useMemo3, useRef as useRef9, useState as useState7 } from "react";
|
|
2066
|
+
|
|
2067
|
+
// src/usePageSettings.ts
|
|
2068
|
+
import { useCallback as useCallback3, useRef as useRef8, useSyncExternalStore } from "react";
|
|
2069
|
+
function usePageSettings(doc) {
|
|
2070
|
+
const cacheRef = useRef8(null);
|
|
2071
|
+
const subscribe = useCallback3(
|
|
2072
|
+
(notify) => doc.observePageSettings(notify),
|
|
2073
|
+
[doc]
|
|
2074
|
+
);
|
|
2075
|
+
const getSnapshot = useCallback3(() => {
|
|
2076
|
+
const next = doc.getPageSettings();
|
|
2077
|
+
const prev = cacheRef.current;
|
|
2078
|
+
if (prev && shallowEqual(prev, next)) return prev;
|
|
2079
|
+
cacheRef.current = next;
|
|
2080
|
+
return next;
|
|
2081
|
+
}, [doc]);
|
|
2082
|
+
return useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
|
|
2083
|
+
}
|
|
2084
|
+
function shallowEqual(a, b) {
|
|
2085
|
+
return a.width === b.width && a.height === b.height && a.marginTop === b.marginTop && a.marginBottom === b.marginBottom && a.marginLeft === b.marginLeft && a.marginRight === b.marginRight && a.preset === b.preset;
|
|
2086
|
+
}
|
|
2087
|
+
|
|
2088
|
+
// src/useEditor.ts
|
|
2089
|
+
import {
|
|
2090
|
+
EditorDocument,
|
|
2091
|
+
EditorHistory,
|
|
2092
|
+
collapsedSelection,
|
|
2093
|
+
dedentList as cmdDedentList,
|
|
2094
|
+
deleteTable as cmdDeleteTable,
|
|
2095
|
+
deleteTableColumn as cmdDeleteColumn,
|
|
2096
|
+
deleteTableRow as cmdDeleteRow,
|
|
2097
|
+
insertImage as cmdInsertImage,
|
|
2098
|
+
mergeDown as cmdMergeDown,
|
|
2099
|
+
mergeRight as cmdMergeRight,
|
|
2100
|
+
mergeSelection as cmdMergeSelection,
|
|
2101
|
+
setColumnWidth as cmdSetColumnWidth,
|
|
2102
|
+
setImageAttrs as cmdSetImageAttrs,
|
|
2103
|
+
splitCell as cmdSplitCell,
|
|
2104
|
+
tableRectSelection as tableRectSelection2,
|
|
2105
|
+
getMarksInRange,
|
|
2106
|
+
indentList as cmdIndentList,
|
|
2107
|
+
insertTable as cmdInsertTable,
|
|
2108
|
+
insertTableColumn as cmdInsertColumn,
|
|
2109
|
+
insertTableRow as cmdInsertRow,
|
|
2110
|
+
isImageEmbed as isImageEmbed2,
|
|
2111
|
+
isMarkUniformInRange,
|
|
2112
|
+
moveToNextCell as cmdMoveNextCell,
|
|
2113
|
+
moveToPrevCell as cmdMovePrevCell,
|
|
2114
|
+
orderedRange,
|
|
2115
|
+
removeMark as cmdRemoveMark,
|
|
2116
|
+
setBlockAttr as cmdSetBlockAttr,
|
|
2117
|
+
setBlockType as cmdSetBlockType,
|
|
2118
|
+
setMark as cmdSetMark,
|
|
2119
|
+
splitListItem as cmdSplitListItem,
|
|
2120
|
+
tableLocationOf,
|
|
2121
|
+
toggleList as cmdToggleList,
|
|
2122
|
+
toggleMark as cmdToggleMark
|
|
2123
|
+
} from "@sofereditor/core";
|
|
2124
|
+
var MAX_INSERT_WIDTH = 600;
|
|
2125
|
+
function useEditor(opts = {}) {
|
|
2126
|
+
const doc = useMemo3(() => opts.document ?? new EditorDocument(), [opts.document]);
|
|
2127
|
+
const history = useMemo3(() => new EditorHistory(doc), [doc]);
|
|
2128
|
+
const [snapshot, setSnapshot] = useState7(() => doc.toJSON());
|
|
2129
|
+
const [selection, setSelectionState] = useState7(
|
|
2130
|
+
() => collapsedSelection({ blockIndex: 0, offset: 0 })
|
|
2131
|
+
);
|
|
2132
|
+
const selectionRef = useRef9(selection);
|
|
2133
|
+
selectionRef.current = selection;
|
|
2134
|
+
const pendingMarksRef = useRef9({});
|
|
2135
|
+
const [pendingMarks, setPendingMarksState] = useState7({});
|
|
2136
|
+
const setPendingMarks = useCallback4((next) => {
|
|
2137
|
+
pendingMarksRef.current = next;
|
|
2138
|
+
setPendingMarksState(next);
|
|
2139
|
+
}, []);
|
|
2140
|
+
useEffect6(() => {
|
|
2141
|
+
let scheduled = false;
|
|
2142
|
+
let cancelled = false;
|
|
2143
|
+
let frame = 0;
|
|
2144
|
+
const flush = () => {
|
|
2145
|
+
scheduled = false;
|
|
2146
|
+
if (cancelled) return;
|
|
2147
|
+
setSnapshot(doc.toJSON());
|
|
2148
|
+
};
|
|
2149
|
+
const schedule = () => {
|
|
2150
|
+
if (scheduled) return;
|
|
2151
|
+
scheduled = true;
|
|
2152
|
+
if (typeof requestAnimationFrame === "function") {
|
|
2153
|
+
frame = requestAnimationFrame(flush);
|
|
2154
|
+
} else {
|
|
2155
|
+
queueMicrotask(flush);
|
|
2156
|
+
}
|
|
2157
|
+
};
|
|
2158
|
+
doc.ydoc.on("afterTransaction", schedule);
|
|
2159
|
+
return () => {
|
|
2160
|
+
cancelled = true;
|
|
2161
|
+
if (scheduled && typeof requestAnimationFrame === "function" && typeof cancelAnimationFrame === "function") {
|
|
2162
|
+
cancelAnimationFrame(frame);
|
|
2163
|
+
}
|
|
2164
|
+
doc.ydoc.off("afterTransaction", schedule);
|
|
2165
|
+
};
|
|
2166
|
+
}, [doc]);
|
|
2167
|
+
const getSelection = useCallback4(() => selectionRef.current, []);
|
|
2168
|
+
const setSelection = useCallback4(
|
|
2169
|
+
(sel) => {
|
|
2170
|
+
const prev = selectionRef.current;
|
|
2171
|
+
selectionRef.current = sel;
|
|
2172
|
+
setSelectionState(sel);
|
|
2173
|
+
if (Object.keys(pendingMarksRef.current).length > 0 && (prev.focus.blockIndex !== sel.focus.blockIndex || prev.focus.offset !== sel.focus.offset)) {
|
|
2174
|
+
const advancedByOne = prev.focus.blockIndex === sel.focus.blockIndex && sel.focus.offset === prev.focus.offset + 1;
|
|
2175
|
+
if (!advancedByOne) {
|
|
2176
|
+
pendingMarksRef.current = {};
|
|
2177
|
+
setPendingMarksState({});
|
|
2178
|
+
}
|
|
2179
|
+
}
|
|
2180
|
+
},
|
|
2181
|
+
[]
|
|
2182
|
+
);
|
|
2183
|
+
const ctxRef = useRef9({ doc, getSelection, setSelection });
|
|
2184
|
+
ctxRef.current = { doc, getSelection, setSelection };
|
|
2185
|
+
const consumePendingMarks = useCallback4(() => {
|
|
2186
|
+
const current = pendingMarksRef.current;
|
|
2187
|
+
if (Object.keys(current).length === 0) return void 0;
|
|
2188
|
+
pendingMarksRef.current = {};
|
|
2189
|
+
setPendingMarksState({});
|
|
2190
|
+
return current;
|
|
2191
|
+
}, []);
|
|
2192
|
+
const isMarkActive = useCallback4(
|
|
2193
|
+
(name, value) => {
|
|
2194
|
+
const sel = selectionRef.current;
|
|
2195
|
+
const { start, end } = orderedRange(sel);
|
|
2196
|
+
if (start.blockIndex === end.blockIndex && start.offset === end.offset) {
|
|
2197
|
+
const pending = pendingMarksRef.current[name];
|
|
2198
|
+
if (pending !== void 0) {
|
|
2199
|
+
if (pending === null) return false;
|
|
2200
|
+
return value === void 0 ? true : sameValue(pending, value);
|
|
2201
|
+
}
|
|
2202
|
+
const yText2 = doc.getBlockText(start.blockIndex);
|
|
2203
|
+
if (!yText2 || start.offset === 0) return false;
|
|
2204
|
+
return isMarkUniformInRange(yText2, start.offset - 1, start.offset, name, value);
|
|
2205
|
+
}
|
|
2206
|
+
if (start.blockIndex !== end.blockIndex) {
|
|
2207
|
+
for (let i = start.blockIndex; i <= end.blockIndex; i++) {
|
|
2208
|
+
const yText2 = doc.getBlockText(i);
|
|
2209
|
+
if (!yText2) return false;
|
|
2210
|
+
const s = i === start.blockIndex ? start.offset : 0;
|
|
2211
|
+
const e = i === end.blockIndex ? end.offset : yText2.length;
|
|
2212
|
+
if (s >= e) continue;
|
|
2213
|
+
if (!isMarkUniformInRange(yText2, s, e, name, value)) return false;
|
|
2214
|
+
}
|
|
2215
|
+
return true;
|
|
2216
|
+
}
|
|
2217
|
+
const yText = doc.getBlockText(start.blockIndex);
|
|
2218
|
+
if (!yText) return false;
|
|
2219
|
+
return isMarkUniformInRange(yText, start.offset, end.offset, name, value);
|
|
2220
|
+
},
|
|
2221
|
+
[doc]
|
|
2222
|
+
);
|
|
2223
|
+
const getActiveMarks = useCallback4(() => {
|
|
2224
|
+
const sel = selectionRef.current;
|
|
2225
|
+
const { start, end } = orderedRange(sel);
|
|
2226
|
+
if (start.blockIndex !== end.blockIndex) {
|
|
2227
|
+
return {};
|
|
2228
|
+
}
|
|
2229
|
+
const yText = doc.getBlockText(start.blockIndex);
|
|
2230
|
+
if (!yText) return {};
|
|
2231
|
+
if (start.offset === end.offset) {
|
|
2232
|
+
if (start.offset === 0) return {};
|
|
2233
|
+
return getMarksInRange(yText, start.offset - 1, start.offset);
|
|
2234
|
+
}
|
|
2235
|
+
return getMarksInRange(yText, start.offset, end.offset);
|
|
2236
|
+
}, [doc]);
|
|
2237
|
+
const toggleMark = useCallback4(
|
|
2238
|
+
(name, value) => {
|
|
2239
|
+
const sel = selectionRef.current;
|
|
2240
|
+
const { start, end } = orderedRange(sel);
|
|
2241
|
+
const isCollapsed = start.blockIndex === end.blockIndex && start.offset === end.offset;
|
|
2242
|
+
if (isCollapsed) {
|
|
2243
|
+
const next = { ...pendingMarksRef.current };
|
|
2244
|
+
const currentlyActive = isMarkActive(name, value);
|
|
2245
|
+
if (currentlyActive) {
|
|
2246
|
+
next[name] = null;
|
|
2247
|
+
} else {
|
|
2248
|
+
next[name] = value ?? (booleanMarks.has(name) ? true : null);
|
|
2249
|
+
}
|
|
2250
|
+
setPendingMarks(next);
|
|
2251
|
+
return;
|
|
2252
|
+
}
|
|
2253
|
+
cmdToggleMark(ctxRef.current, name, value);
|
|
2254
|
+
},
|
|
2255
|
+
[isMarkActive, setPendingMarks]
|
|
2256
|
+
);
|
|
2257
|
+
const setMark = useCallback4(
|
|
2258
|
+
(name, value) => {
|
|
2259
|
+
const sel = selectionRef.current;
|
|
2260
|
+
const { start, end } = orderedRange(sel);
|
|
2261
|
+
const isCollapsed = start.blockIndex === end.blockIndex && start.offset === end.offset;
|
|
2262
|
+
if (isCollapsed) {
|
|
2263
|
+
setPendingMarks({ ...pendingMarksRef.current, [name]: value });
|
|
2264
|
+
return;
|
|
2265
|
+
}
|
|
2266
|
+
cmdSetMark(ctxRef.current, name, value);
|
|
2267
|
+
},
|
|
2268
|
+
[setPendingMarks]
|
|
2269
|
+
);
|
|
2270
|
+
const removeMark = useCallback4(
|
|
2271
|
+
(name) => {
|
|
2272
|
+
const sel = selectionRef.current;
|
|
2273
|
+
const { start, end } = orderedRange(sel);
|
|
2274
|
+
const isCollapsed = start.blockIndex === end.blockIndex && start.offset === end.offset;
|
|
2275
|
+
if (isCollapsed) {
|
|
2276
|
+
setPendingMarks({ ...pendingMarksRef.current, [name]: null });
|
|
2277
|
+
return;
|
|
2278
|
+
}
|
|
2279
|
+
cmdRemoveMark(ctxRef.current, name);
|
|
2280
|
+
},
|
|
2281
|
+
[setPendingMarks]
|
|
2282
|
+
);
|
|
2283
|
+
const getBlockType = useCallback4(() => {
|
|
2284
|
+
const sel = selectionRef.current;
|
|
2285
|
+
const { start, end } = orderedRange(sel);
|
|
2286
|
+
const first = doc.getBlockType(start.blockIndex);
|
|
2287
|
+
if (first === void 0) return "paragraph";
|
|
2288
|
+
for (let i = start.blockIndex + 1; i <= end.blockIndex; i++) {
|
|
2289
|
+
if (doc.getBlockType(i) !== first) return "mixed";
|
|
2290
|
+
}
|
|
2291
|
+
return first;
|
|
2292
|
+
}, [doc]);
|
|
2293
|
+
const getBlockAttr = useCallback4(
|
|
2294
|
+
(key) => {
|
|
2295
|
+
const sel = selectionRef.current;
|
|
2296
|
+
const { start, end } = orderedRange(sel);
|
|
2297
|
+
const first = doc.getBlockAttrs(start.blockIndex)[key];
|
|
2298
|
+
for (let i = start.blockIndex + 1; i <= end.blockIndex; i++) {
|
|
2299
|
+
const next = doc.getBlockAttrs(i)[key];
|
|
2300
|
+
if (next !== first) return "mixed";
|
|
2301
|
+
}
|
|
2302
|
+
return first;
|
|
2303
|
+
},
|
|
2304
|
+
[doc]
|
|
2305
|
+
);
|
|
2306
|
+
const setBlockType = useCallback4((type, attrs) => {
|
|
2307
|
+
cmdSetBlockType(ctxRef.current, type, attrs);
|
|
2308
|
+
}, []);
|
|
2309
|
+
const setBlockAttr = useCallback4(
|
|
2310
|
+
(key, value) => {
|
|
2311
|
+
cmdSetBlockAttr(ctxRef.current, key, value);
|
|
2312
|
+
},
|
|
2313
|
+
[]
|
|
2314
|
+
);
|
|
2315
|
+
const isListActive = useCallback4(
|
|
2316
|
+
(kind) => {
|
|
2317
|
+
const sel = selectionRef.current;
|
|
2318
|
+
const { start, end } = orderedRange(sel);
|
|
2319
|
+
for (let i = start.blockIndex; i <= end.blockIndex; i++) {
|
|
2320
|
+
if (doc.getBlockType(i) !== "listItem") return false;
|
|
2321
|
+
if (doc.getBlockAttrs(i).listKind !== kind) return false;
|
|
2322
|
+
}
|
|
2323
|
+
return true;
|
|
2324
|
+
},
|
|
2325
|
+
[doc]
|
|
2326
|
+
);
|
|
2327
|
+
const toggleList = useCallback4((kind) => {
|
|
2328
|
+
cmdToggleList(ctxRef.current, kind);
|
|
2329
|
+
}, []);
|
|
2330
|
+
const indentList = useCallback4(() => {
|
|
2331
|
+
cmdIndentList(ctxRef.current);
|
|
2332
|
+
}, []);
|
|
2333
|
+
const dedentList = useCallback4(() => {
|
|
2334
|
+
cmdDedentList(ctxRef.current);
|
|
2335
|
+
}, []);
|
|
2336
|
+
const splitListItem = useCallback4(() => {
|
|
2337
|
+
return cmdSplitListItem(ctxRef.current);
|
|
2338
|
+
}, []);
|
|
2339
|
+
const isInTable = useCallback4(() => {
|
|
2340
|
+
return selectionRef.current.focus.cellIndex != null;
|
|
2341
|
+
}, []);
|
|
2342
|
+
const getTableLocation = useCallback4(() => {
|
|
2343
|
+
return tableLocationOf(doc, selectionRef.current.focus);
|
|
2344
|
+
}, [doc]);
|
|
2345
|
+
const insertTable = useCallback4((rows, cols) => {
|
|
2346
|
+
cmdInsertTable(ctxRef.current, rows, cols);
|
|
2347
|
+
}, []);
|
|
2348
|
+
const deleteTable = useCallback4((blockIndex) => {
|
|
2349
|
+
const idx = blockIndex ?? selectionRef.current.focus.blockIndex;
|
|
2350
|
+
cmdDeleteTable(ctxRef.current, idx);
|
|
2351
|
+
}, []);
|
|
2352
|
+
const insertRowAbove = useCallback4(() => {
|
|
2353
|
+
const loc = tableLocationOf(doc, selectionRef.current.focus);
|
|
2354
|
+
if (!loc) return;
|
|
2355
|
+
cmdInsertRow(ctxRef.current, loc.blockIndex, loc.row, "before");
|
|
2356
|
+
}, [doc]);
|
|
2357
|
+
const insertRowBelow = useCallback4(() => {
|
|
2358
|
+
const loc = tableLocationOf(doc, selectionRef.current.focus);
|
|
2359
|
+
if (!loc) return;
|
|
2360
|
+
cmdInsertRow(ctxRef.current, loc.blockIndex, loc.row, "after");
|
|
2361
|
+
}, [doc]);
|
|
2362
|
+
const deleteCurrentRow = useCallback4(() => {
|
|
2363
|
+
const loc = tableLocationOf(doc, selectionRef.current.focus);
|
|
2364
|
+
if (!loc) return;
|
|
2365
|
+
cmdDeleteRow(ctxRef.current, loc.blockIndex, loc.row);
|
|
2366
|
+
}, [doc]);
|
|
2367
|
+
const insertColumnLeft = useCallback4(() => {
|
|
2368
|
+
const loc = tableLocationOf(doc, selectionRef.current.focus);
|
|
2369
|
+
if (!loc) return;
|
|
2370
|
+
cmdInsertColumn(ctxRef.current, loc.blockIndex, loc.col, "before");
|
|
2371
|
+
}, [doc]);
|
|
2372
|
+
const insertColumnRight = useCallback4(() => {
|
|
2373
|
+
const loc = tableLocationOf(doc, selectionRef.current.focus);
|
|
2374
|
+
if (!loc) return;
|
|
2375
|
+
cmdInsertColumn(ctxRef.current, loc.blockIndex, loc.col, "after");
|
|
2376
|
+
}, [doc]);
|
|
2377
|
+
const deleteCurrentColumn = useCallback4(() => {
|
|
2378
|
+
const loc = tableLocationOf(doc, selectionRef.current.focus);
|
|
2379
|
+
if (!loc) return;
|
|
2380
|
+
cmdDeleteColumn(ctxRef.current, loc.blockIndex, loc.col);
|
|
2381
|
+
}, [doc]);
|
|
2382
|
+
const moveToNextCell = useCallback4(() => {
|
|
2383
|
+
cmdMoveNextCell(ctxRef.current);
|
|
2384
|
+
}, []);
|
|
2385
|
+
const moveToPrevCell = useCallback4(() => {
|
|
2386
|
+
cmdMovePrevCell(ctxRef.current);
|
|
2387
|
+
}, []);
|
|
2388
|
+
const mergeRight = useCallback4(() => {
|
|
2389
|
+
const loc = tableLocationOf(doc, selectionRef.current.focus);
|
|
2390
|
+
if (!loc) return false;
|
|
2391
|
+
return cmdMergeRight(ctxRef.current, loc.blockIndex, loc.row, loc.col);
|
|
2392
|
+
}, [doc]);
|
|
2393
|
+
const mergeDown = useCallback4(() => {
|
|
2394
|
+
const loc = tableLocationOf(doc, selectionRef.current.focus);
|
|
2395
|
+
if (!loc) return false;
|
|
2396
|
+
return cmdMergeDown(ctxRef.current, loc.blockIndex, loc.row, loc.col);
|
|
2397
|
+
}, [doc]);
|
|
2398
|
+
const splitCell = useCallback4(() => {
|
|
2399
|
+
const loc = tableLocationOf(doc, selectionRef.current.focus);
|
|
2400
|
+
if (!loc) return false;
|
|
2401
|
+
return cmdSplitCell(ctxRef.current, loc.blockIndex, loc.row, loc.col);
|
|
2402
|
+
}, [doc]);
|
|
2403
|
+
const setColumnWidth = useCallback4(
|
|
2404
|
+
(blockIndex, col, widthPx) => {
|
|
2405
|
+
cmdSetColumnWidth(ctxRef.current, blockIndex, col, widthPx);
|
|
2406
|
+
},
|
|
2407
|
+
[]
|
|
2408
|
+
);
|
|
2409
|
+
const getTableRect = useCallback4(
|
|
2410
|
+
() => tableRectSelection2(doc, selectionRef.current),
|
|
2411
|
+
[doc]
|
|
2412
|
+
);
|
|
2413
|
+
const hasTableRectSelection = useCallback4(() => {
|
|
2414
|
+
return tableRectSelection2(doc, selectionRef.current) !== null;
|
|
2415
|
+
}, [doc]);
|
|
2416
|
+
const mergeSelection = useCallback4(() => {
|
|
2417
|
+
return cmdMergeSelection(ctxRef.current);
|
|
2418
|
+
}, []);
|
|
2419
|
+
const insertImage = useCallback4((embed) => {
|
|
2420
|
+
cmdInsertImage(ctxRef.current, embed);
|
|
2421
|
+
}, []);
|
|
2422
|
+
const setImageAttrs = useCallback4(
|
|
2423
|
+
(blockIndex, offset, partial, cellIndex, newOffset) => {
|
|
2424
|
+
cmdSetImageAttrs(ctxRef.current, blockIndex, offset, partial, cellIndex, newOffset);
|
|
2425
|
+
},
|
|
2426
|
+
[]
|
|
2427
|
+
);
|
|
2428
|
+
const uploadImageRef = useRef9(opts.uploadImage);
|
|
2429
|
+
uploadImageRef.current = opts.uploadImage;
|
|
2430
|
+
const insertImageFromFile = useCallback4(async (file) => {
|
|
2431
|
+
if (!file.type.startsWith("image/")) return;
|
|
2432
|
+
const dataUrl = await readAsDataURL(file);
|
|
2433
|
+
const dims = await readImageDimensions(dataUrl);
|
|
2434
|
+
let w = dims.width;
|
|
2435
|
+
let h = dims.height;
|
|
2436
|
+
if (w > MAX_INSERT_WIDTH) {
|
|
2437
|
+
h = Math.round(h * MAX_INSERT_WIDTH / w);
|
|
2438
|
+
w = MAX_INSERT_WIDTH;
|
|
2439
|
+
}
|
|
2440
|
+
const upload = uploadImageRef.current;
|
|
2441
|
+
const src = upload ? await upload(file) : dataUrl;
|
|
2442
|
+
cmdInsertImage(ctxRef.current, { type: "image", src, width: w, height: h });
|
|
2443
|
+
}, []);
|
|
2444
|
+
const getSelectedEmbed = useCallback4(() => {
|
|
2445
|
+
const sel = selectionRef.current;
|
|
2446
|
+
if (sel.anchor.blockIndex !== sel.focus.blockIndex) return null;
|
|
2447
|
+
if ((sel.anchor.cellIndex ?? -1) !== (sel.focus.cellIndex ?? -1)) return null;
|
|
2448
|
+
const a = sel.anchor.offset;
|
|
2449
|
+
const b = sel.focus.offset;
|
|
2450
|
+
if (Math.abs(a - b) !== 1) return null;
|
|
2451
|
+
const start = Math.min(a, b);
|
|
2452
|
+
const yText = doc.textAt(sel.anchor.blockIndex, sel.anchor.cellIndex);
|
|
2453
|
+
if (!yText) return null;
|
|
2454
|
+
const delta = yText.toDelta();
|
|
2455
|
+
let cursor = 0;
|
|
2456
|
+
for (const op of delta) {
|
|
2457
|
+
if (typeof op.insert === "string") {
|
|
2458
|
+
cursor += op.insert.length;
|
|
2459
|
+
continue;
|
|
2460
|
+
}
|
|
2461
|
+
if (cursor === start && isImageEmbed2(op.insert)) {
|
|
2462
|
+
return {
|
|
2463
|
+
blockIndex: sel.anchor.blockIndex,
|
|
2464
|
+
cellIndex: sel.anchor.cellIndex,
|
|
2465
|
+
offset: start,
|
|
2466
|
+
embed: op.insert
|
|
2467
|
+
};
|
|
2468
|
+
}
|
|
2469
|
+
cursor += 1;
|
|
2470
|
+
}
|
|
2471
|
+
return null;
|
|
2472
|
+
}, [doc]);
|
|
2473
|
+
const [linkRequest, setLinkRequest] = useState7(null);
|
|
2474
|
+
const requestLink = useCallback4((initialHref = "") => {
|
|
2475
|
+
return new Promise((resolve) => {
|
|
2476
|
+
setLinkRequest({ initialHref, resolve });
|
|
2477
|
+
});
|
|
2478
|
+
}, []);
|
|
2479
|
+
const resolveLinkRequest = useCallback4((href) => {
|
|
2480
|
+
setLinkRequest((prev) => {
|
|
2481
|
+
if (prev) prev.resolve(href);
|
|
2482
|
+
return null;
|
|
2483
|
+
});
|
|
2484
|
+
}, []);
|
|
2485
|
+
const pageSettings = usePageSettings(doc);
|
|
2486
|
+
const setPageSettings = useCallback4(
|
|
2487
|
+
(partial) => {
|
|
2488
|
+
doc.setPageSettings(partial);
|
|
2489
|
+
},
|
|
2490
|
+
[doc]
|
|
2491
|
+
);
|
|
2492
|
+
const [pageConfigRequest, setPageConfigRequest] = useState7(null);
|
|
2493
|
+
const requestPageConfig = useCallback4(() => {
|
|
2494
|
+
return new Promise((resolve) => {
|
|
2495
|
+
setPageConfigRequest({ initial: doc.getPageSettings(), resolve });
|
|
2496
|
+
});
|
|
2497
|
+
}, [doc]);
|
|
2498
|
+
const resolvePageConfigRequest = useCallback4((applied) => {
|
|
2499
|
+
setPageConfigRequest((prev) => {
|
|
2500
|
+
if (prev) prev.resolve(applied);
|
|
2501
|
+
return null;
|
|
2502
|
+
});
|
|
2503
|
+
}, []);
|
|
2504
|
+
const [imageCaptionRequest, setImageCaptionRequest] = useState7(null);
|
|
2505
|
+
const requestImageCaption = useCallback4(
|
|
2506
|
+
(initialCaption = "", initialAlign = "center") => {
|
|
2507
|
+
return new Promise((resolve) => {
|
|
2508
|
+
setImageCaptionRequest({ initialCaption, initialAlign, resolve });
|
|
2509
|
+
});
|
|
2510
|
+
},
|
|
2511
|
+
[]
|
|
2512
|
+
);
|
|
2513
|
+
const resolveImageCaptionRequest = useCallback4((result) => {
|
|
2514
|
+
setImageCaptionRequest((prev) => {
|
|
2515
|
+
if (prev) prev.resolve(result);
|
|
2516
|
+
return null;
|
|
2517
|
+
});
|
|
2518
|
+
}, []);
|
|
2519
|
+
return {
|
|
2520
|
+
doc,
|
|
2521
|
+
history,
|
|
2522
|
+
snapshot,
|
|
2523
|
+
selection,
|
|
2524
|
+
setSelection,
|
|
2525
|
+
getSelection,
|
|
2526
|
+
isMarkActive,
|
|
2527
|
+
getActiveMarks,
|
|
2528
|
+
toggleMark,
|
|
2529
|
+
setMark,
|
|
2530
|
+
removeMark,
|
|
2531
|
+
pendingMarks,
|
|
2532
|
+
consumePendingMarks,
|
|
2533
|
+
getBlockType,
|
|
2534
|
+
getBlockAttr,
|
|
2535
|
+
setBlockType,
|
|
2536
|
+
setBlockAttr,
|
|
2537
|
+
isListActive,
|
|
2538
|
+
toggleList,
|
|
2539
|
+
indentList,
|
|
2540
|
+
dedentList,
|
|
2541
|
+
splitListItem,
|
|
2542
|
+
linkRequest,
|
|
2543
|
+
requestLink,
|
|
2544
|
+
resolveLinkRequest,
|
|
2545
|
+
isInTable,
|
|
2546
|
+
getTableLocation,
|
|
2547
|
+
insertTable,
|
|
2548
|
+
deleteTable,
|
|
2549
|
+
insertRowAbove,
|
|
2550
|
+
insertRowBelow,
|
|
2551
|
+
deleteCurrentRow,
|
|
2552
|
+
insertColumnLeft,
|
|
2553
|
+
insertColumnRight,
|
|
2554
|
+
deleteCurrentColumn,
|
|
2555
|
+
moveToNextCell,
|
|
2556
|
+
moveToPrevCell,
|
|
2557
|
+
mergeRight,
|
|
2558
|
+
mergeDown,
|
|
2559
|
+
splitCell,
|
|
2560
|
+
setColumnWidth,
|
|
2561
|
+
getTableRect,
|
|
2562
|
+
hasTableRectSelection,
|
|
2563
|
+
mergeSelection,
|
|
2564
|
+
insertImage,
|
|
2565
|
+
setImageAttrs,
|
|
2566
|
+
insertImageFromFile,
|
|
2567
|
+
getSelectedEmbed,
|
|
2568
|
+
pageSettings,
|
|
2569
|
+
setPageSettings,
|
|
2570
|
+
pageConfigRequest,
|
|
2571
|
+
requestPageConfig,
|
|
2572
|
+
resolvePageConfigRequest,
|
|
2573
|
+
imageCaptionRequest,
|
|
2574
|
+
requestImageCaption,
|
|
2575
|
+
resolveImageCaptionRequest
|
|
2576
|
+
};
|
|
2577
|
+
}
|
|
2578
|
+
function readAsDataURL(file) {
|
|
2579
|
+
return new Promise((resolve, reject) => {
|
|
2580
|
+
const reader = new FileReader();
|
|
2581
|
+
reader.onerror = () => reject(reader.error ?? new Error("FileReader error"));
|
|
2582
|
+
reader.onload = () => resolve(reader.result);
|
|
2583
|
+
reader.readAsDataURL(file);
|
|
2584
|
+
});
|
|
2585
|
+
}
|
|
2586
|
+
function readImageDimensions(src) {
|
|
2587
|
+
return new Promise((resolve, reject) => {
|
|
2588
|
+
const img = new Image();
|
|
2589
|
+
img.onerror = () => reject(new Error("image load failed"));
|
|
2590
|
+
img.onload = () => resolve({ width: img.naturalWidth, height: img.naturalHeight });
|
|
2591
|
+
img.src = src;
|
|
2592
|
+
});
|
|
2593
|
+
}
|
|
2594
|
+
var booleanMarks = /* @__PURE__ */ new Set(["bold", "italic", "underline", "strike"]);
|
|
2595
|
+
function sameValue(a, b) {
|
|
2596
|
+
if (a === b) return true;
|
|
2597
|
+
if (typeof a === "object" && a && b && typeof b === "object") {
|
|
2598
|
+
const ak = Object.keys(a);
|
|
2599
|
+
const bk = Object.keys(b);
|
|
2600
|
+
if (ak.length !== bk.length) return false;
|
|
2601
|
+
return ak.every((k) => a[k] === b[k]);
|
|
2602
|
+
}
|
|
2603
|
+
return false;
|
|
2604
|
+
}
|
|
2605
|
+
|
|
2606
|
+
// src/Editor.tsx
|
|
2607
|
+
import { jsx as jsx9, jsxs as jsxs8 } from "react/jsx-runtime";
|
|
2608
|
+
var defaultFooter = ({ pageNumber, pageCount }) => `${pageNumber} / ${pageCount}`;
|
|
2609
|
+
function Editor({
|
|
2610
|
+
editor: providedEditor,
|
|
2611
|
+
className,
|
|
2612
|
+
pageGeometry,
|
|
2613
|
+
renderPageHeader,
|
|
2614
|
+
renderPageFooter = defaultFooter
|
|
2615
|
+
}) {
|
|
2616
|
+
const internal = useEditor();
|
|
2617
|
+
const editor = providedEditor ?? internal;
|
|
2618
|
+
const { doc, history, snapshot, getSelection, setSelection } = editor;
|
|
2619
|
+
const rootRef = useRef10(null);
|
|
2620
|
+
const isComposingRef = useRef10(false);
|
|
2621
|
+
const suppressSelectionSyncRef = useRef10(false);
|
|
2622
|
+
const ctxRef = useRef10({ doc, getSelection, setSelection });
|
|
2623
|
+
ctxRef.current = { doc, getSelection, setSelection };
|
|
2624
|
+
const historyRef = useRef10(history);
|
|
2625
|
+
historyRef.current = history;
|
|
2626
|
+
const editorRef = useRef10(editor);
|
|
2627
|
+
editorRef.current = editor;
|
|
2628
|
+
useLayoutEffect4(() => {
|
|
2629
|
+
const root = rootRef.current;
|
|
2630
|
+
if (!root) return;
|
|
2631
|
+
if (isComposingRef.current) return;
|
|
2632
|
+
if (document.activeElement !== root) return;
|
|
2633
|
+
const current = readDomSelection(root);
|
|
2634
|
+
if (current && selectionsEqual(current, editor.selection)) return;
|
|
2635
|
+
suppressSelectionSyncRef.current = true;
|
|
2636
|
+
applyDomSelection(root, editor.selection);
|
|
2637
|
+
queueMicrotask(() => {
|
|
2638
|
+
suppressSelectionSyncRef.current = false;
|
|
2639
|
+
});
|
|
2640
|
+
});
|
|
2641
|
+
useEffect7(() => {
|
|
2642
|
+
const root = rootRef.current;
|
|
2643
|
+
if (!root) return;
|
|
2644
|
+
const handler = () => {
|
|
2645
|
+
if (suppressSelectionSyncRef.current) return;
|
|
2646
|
+
if (isComposingRef.current) return;
|
|
2647
|
+
if (document.activeElement !== root) return;
|
|
2648
|
+
const modelSel = readDomSelection(root);
|
|
2649
|
+
if (modelSel) setSelection(modelSel);
|
|
2650
|
+
};
|
|
2651
|
+
document.addEventListener("selectionchange", handler);
|
|
2652
|
+
return () => document.removeEventListener("selectionchange", handler);
|
|
2653
|
+
}, [setSelection]);
|
|
2654
|
+
useEffect7(() => {
|
|
2655
|
+
const root = rootRef.current;
|
|
2656
|
+
if (!root) return;
|
|
2657
|
+
let drag = null;
|
|
2658
|
+
function cellFromTarget(target) {
|
|
2659
|
+
if (!(target instanceof Node)) return null;
|
|
2660
|
+
let n = target;
|
|
2661
|
+
let cellEl = null;
|
|
2662
|
+
let tableEl = null;
|
|
2663
|
+
while (n && n !== root) {
|
|
2664
|
+
if (n.nodeType === Node.ELEMENT_NODE) {
|
|
2665
|
+
const e = n;
|
|
2666
|
+
if (!cellEl && e.dataset.cellIndex != null) cellEl = e;
|
|
2667
|
+
if (e.tagName === "TABLE" && e.dataset.blockIndex != null) {
|
|
2668
|
+
tableEl = e;
|
|
2669
|
+
break;
|
|
2670
|
+
}
|
|
2671
|
+
}
|
|
2672
|
+
n = n.parentNode;
|
|
2673
|
+
}
|
|
2674
|
+
if (!cellEl || !tableEl) return null;
|
|
2675
|
+
const cellIndex = Number.parseInt(cellEl.dataset.cellIndex, 10);
|
|
2676
|
+
const blockIndex = Number.parseInt(tableEl.dataset.blockIndex, 10);
|
|
2677
|
+
return { blockIndex, cellIndex, tableEl };
|
|
2678
|
+
}
|
|
2679
|
+
const onPointerDown = (e) => {
|
|
2680
|
+
if (e.button !== 0) return;
|
|
2681
|
+
const hit = cellFromTarget(e.target);
|
|
2682
|
+
if (!hit) {
|
|
2683
|
+
drag = null;
|
|
2684
|
+
return;
|
|
2685
|
+
}
|
|
2686
|
+
drag = {
|
|
2687
|
+
blockIndex: hit.blockIndex,
|
|
2688
|
+
anchorCellIndex: hit.cellIndex,
|
|
2689
|
+
anchorTableEl: hit.tableEl,
|
|
2690
|
+
rectActive: false
|
|
2691
|
+
};
|
|
2692
|
+
};
|
|
2693
|
+
const onPointerMove = (e) => {
|
|
2694
|
+
if (!drag) return;
|
|
2695
|
+
if ((e.buttons & 1) === 0) {
|
|
2696
|
+
drag = null;
|
|
2697
|
+
return;
|
|
2698
|
+
}
|
|
2699
|
+
const hit = cellFromTarget(e.target);
|
|
2700
|
+
if (!hit) return;
|
|
2701
|
+
if (hit.blockIndex !== drag.blockIndex) return;
|
|
2702
|
+
if (hit.cellIndex === drag.anchorCellIndex && !drag.rectActive) return;
|
|
2703
|
+
if (!drag.rectActive) {
|
|
2704
|
+
drag.rectActive = true;
|
|
2705
|
+
window.getSelection()?.removeAllRanges();
|
|
2706
|
+
}
|
|
2707
|
+
e.preventDefault();
|
|
2708
|
+
ctxRef.current.setSelection({
|
|
2709
|
+
anchor: { blockIndex: drag.blockIndex, cellIndex: drag.anchorCellIndex, offset: 0 },
|
|
2710
|
+
focus: { blockIndex: drag.blockIndex, cellIndex: hit.cellIndex, offset: 0 }
|
|
2711
|
+
});
|
|
2712
|
+
};
|
|
2713
|
+
const onPointerUp = () => {
|
|
2714
|
+
drag = null;
|
|
2715
|
+
};
|
|
2716
|
+
root.addEventListener("pointerdown", onPointerDown);
|
|
2717
|
+
document.addEventListener("pointermove", onPointerMove);
|
|
2718
|
+
document.addEventListener("pointerup", onPointerUp);
|
|
2719
|
+
return () => {
|
|
2720
|
+
root.removeEventListener("pointerdown", onPointerDown);
|
|
2721
|
+
document.removeEventListener("pointermove", onPointerMove);
|
|
2722
|
+
document.removeEventListener("pointerup", onPointerUp);
|
|
2723
|
+
};
|
|
2724
|
+
}, []);
|
|
2725
|
+
useEffect7(() => {
|
|
2726
|
+
const root = rootRef.current;
|
|
2727
|
+
if (!root) return;
|
|
2728
|
+
const onDragOver = (e) => {
|
|
2729
|
+
if (!e.dataTransfer) return;
|
|
2730
|
+
const hasFiles = Array.from(e.dataTransfer.items ?? []).some(
|
|
2731
|
+
(i) => i.kind === "file"
|
|
2732
|
+
);
|
|
2733
|
+
if (!hasFiles) return;
|
|
2734
|
+
e.preventDefault();
|
|
2735
|
+
e.dataTransfer.dropEffect = "copy";
|
|
2736
|
+
};
|
|
2737
|
+
const onDrop = (e) => {
|
|
2738
|
+
if (!e.dataTransfer) return;
|
|
2739
|
+
const images = Array.from(e.dataTransfer.files).filter(
|
|
2740
|
+
(f) => f.type.startsWith("image/")
|
|
2741
|
+
);
|
|
2742
|
+
if (images.length === 0) return;
|
|
2743
|
+
e.preventDefault();
|
|
2744
|
+
const range = caretRangeFromPoint(e.clientX, e.clientY);
|
|
2745
|
+
if (range) {
|
|
2746
|
+
const dom = window.getSelection();
|
|
2747
|
+
dom?.removeAllRanges();
|
|
2748
|
+
dom?.addRange(range);
|
|
2749
|
+
const sel = readDomSelection(root);
|
|
2750
|
+
if (sel) ctxRef.current.setSelection(sel);
|
|
2751
|
+
}
|
|
2752
|
+
void (async () => {
|
|
2753
|
+
for (const f of images) await editorRef.current.insertImageFromFile(f);
|
|
2754
|
+
})();
|
|
2755
|
+
};
|
|
2756
|
+
root.addEventListener("dragover", onDragOver);
|
|
2757
|
+
root.addEventListener("drop", onDrop);
|
|
2758
|
+
return () => {
|
|
2759
|
+
root.removeEventListener("dragover", onDragOver);
|
|
2760
|
+
root.removeEventListener("drop", onDrop);
|
|
2761
|
+
};
|
|
2762
|
+
}, []);
|
|
2763
|
+
useEffect7(() => {
|
|
2764
|
+
const root = rootRef.current;
|
|
2765
|
+
if (!root) return;
|
|
2766
|
+
const onPointerDown = (e) => {
|
|
2767
|
+
if (e.button !== 0) return;
|
|
2768
|
+
const target = e.target;
|
|
2769
|
+
if (!(target instanceof Element)) return;
|
|
2770
|
+
const img = target.closest('img[data-embed="image"]');
|
|
2771
|
+
if (!img) return;
|
|
2772
|
+
const blockEl = img.closest("[data-block-index]");
|
|
2773
|
+
if (!blockEl) return;
|
|
2774
|
+
const blockIndex = Number.parseInt(blockEl.dataset.blockIndex, 10);
|
|
2775
|
+
const offsetAttr = img.dataset.embedOffset;
|
|
2776
|
+
if (offsetAttr == null) return;
|
|
2777
|
+
const localOffset = Number.parseInt(offsetAttr, 10);
|
|
2778
|
+
const fragmentStart = Number.parseInt(blockEl.dataset.fragmentStart ?? "0", 10) || 0;
|
|
2779
|
+
const cellEl = img.closest("[data-cell-index]");
|
|
2780
|
+
const cellIndex = cellEl ? Number.parseInt(cellEl.dataset.cellIndex, 10) : void 0;
|
|
2781
|
+
const offset = fragmentStart + localOffset;
|
|
2782
|
+
e.preventDefault();
|
|
2783
|
+
ctxRef.current.setSelection({
|
|
2784
|
+
anchor: { blockIndex, cellIndex, offset },
|
|
2785
|
+
focus: { blockIndex, cellIndex, offset: offset + 1 }
|
|
2786
|
+
});
|
|
2787
|
+
};
|
|
2788
|
+
root.addEventListener("pointerdown", onPointerDown);
|
|
2789
|
+
return () => root.removeEventListener("pointerdown", onPointerDown);
|
|
2790
|
+
}, []);
|
|
2791
|
+
useEffect7(() => {
|
|
2792
|
+
const root = rootRef.current;
|
|
2793
|
+
if (!root) return;
|
|
2794
|
+
const handler = (e) => {
|
|
2795
|
+
const ctx = ctxRef.current;
|
|
2796
|
+
const hist = historyRef.current;
|
|
2797
|
+
const ed = editorRef.current;
|
|
2798
|
+
const type = e.inputType;
|
|
2799
|
+
if (isComposingRef.current && type === "insertCompositionText") return;
|
|
2800
|
+
switch (type) {
|
|
2801
|
+
case "insertText": {
|
|
2802
|
+
const data = e.data ?? "";
|
|
2803
|
+
if (data.length > 0) {
|
|
2804
|
+
e.preventDefault();
|
|
2805
|
+
insertText(ctx, data, ed.consumePendingMarks());
|
|
2806
|
+
}
|
|
2807
|
+
return;
|
|
2808
|
+
}
|
|
2809
|
+
case "insertParagraph":
|
|
2810
|
+
case "insertLineBreak": {
|
|
2811
|
+
e.preventDefault();
|
|
2812
|
+
const selectedEmbed2 = ed.getSelectedEmbed();
|
|
2813
|
+
if (selectedEmbed2) {
|
|
2814
|
+
const off = selectedEmbed2.offset + 1;
|
|
2815
|
+
ctx.setSelection({
|
|
2816
|
+
anchor: {
|
|
2817
|
+
blockIndex: selectedEmbed2.blockIndex,
|
|
2818
|
+
cellIndex: selectedEmbed2.cellIndex,
|
|
2819
|
+
offset: off
|
|
2820
|
+
},
|
|
2821
|
+
focus: {
|
|
2822
|
+
blockIndex: selectedEmbed2.blockIndex,
|
|
2823
|
+
cellIndex: selectedEmbed2.cellIndex,
|
|
2824
|
+
offset: off
|
|
2825
|
+
}
|
|
2826
|
+
});
|
|
2827
|
+
}
|
|
2828
|
+
if (ed.splitListItem()) return;
|
|
2829
|
+
insertParagraph(ctx);
|
|
2830
|
+
return;
|
|
2831
|
+
}
|
|
2832
|
+
case "insertReplacementText": {
|
|
2833
|
+
const text = e.data ?? e.dataTransfer?.getData("text/plain") ?? "";
|
|
2834
|
+
if (text.length > 0) {
|
|
2835
|
+
e.preventDefault();
|
|
2836
|
+
insertText(ctx, text, ed.consumePendingMarks());
|
|
2837
|
+
}
|
|
2838
|
+
return;
|
|
2839
|
+
}
|
|
2840
|
+
case "insertFromPaste": {
|
|
2841
|
+
e.preventDefault();
|
|
2842
|
+
const images = Array.from(e.dataTransfer?.files ?? []).filter(
|
|
2843
|
+
(f) => f.type.startsWith("image/")
|
|
2844
|
+
);
|
|
2845
|
+
if (images.length > 0) {
|
|
2846
|
+
void (async () => {
|
|
2847
|
+
for (const f of images) await editorRef.current.insertImageFromFile(f);
|
|
2848
|
+
})();
|
|
2849
|
+
return;
|
|
2850
|
+
}
|
|
2851
|
+
const text = e.dataTransfer?.getData("text/plain") ?? "";
|
|
2852
|
+
if (text.length === 0) return;
|
|
2853
|
+
const lines = text.split(/\r\n|\r|\n/);
|
|
2854
|
+
lines.forEach((line, i) => {
|
|
2855
|
+
if (i > 0) insertParagraph(ctx);
|
|
2856
|
+
if (line.length > 0) insertText(ctx, line);
|
|
2857
|
+
});
|
|
2858
|
+
return;
|
|
2859
|
+
}
|
|
2860
|
+
case "deleteContentBackward":
|
|
2861
|
+
case "deleteWordBackward":
|
|
2862
|
+
case "deleteSoftLineBackward":
|
|
2863
|
+
case "deleteHardLineBackward": {
|
|
2864
|
+
e.preventDefault();
|
|
2865
|
+
deleteBackward(ctx);
|
|
2866
|
+
return;
|
|
2867
|
+
}
|
|
2868
|
+
case "deleteContentForward":
|
|
2869
|
+
case "deleteWordForward":
|
|
2870
|
+
case "deleteSoftLineForward":
|
|
2871
|
+
case "deleteHardLineForward": {
|
|
2872
|
+
e.preventDefault();
|
|
2873
|
+
deleteForward(ctx);
|
|
2874
|
+
return;
|
|
2875
|
+
}
|
|
2876
|
+
case "historyUndo": {
|
|
2877
|
+
e.preventDefault();
|
|
2878
|
+
hist.undo();
|
|
2879
|
+
return;
|
|
2880
|
+
}
|
|
2881
|
+
case "historyRedo": {
|
|
2882
|
+
e.preventDefault();
|
|
2883
|
+
hist.redo();
|
|
2884
|
+
return;
|
|
2885
|
+
}
|
|
2886
|
+
default: {
|
|
2887
|
+
e.preventDefault();
|
|
2888
|
+
return;
|
|
2889
|
+
}
|
|
2890
|
+
}
|
|
2891
|
+
};
|
|
2892
|
+
root.addEventListener("beforeinput", handler);
|
|
2893
|
+
return () => root.removeEventListener("beforeinput", handler);
|
|
2894
|
+
}, []);
|
|
2895
|
+
const onKeyDown = useCallback5((e) => {
|
|
2896
|
+
const ed = editorRef.current;
|
|
2897
|
+
const mod = e.metaKey || e.ctrlKey;
|
|
2898
|
+
if (e.key === "Tab") {
|
|
2899
|
+
e.preventDefault();
|
|
2900
|
+
if (ed.isInTable()) {
|
|
2901
|
+
if (e.shiftKey) ed.moveToPrevCell();
|
|
2902
|
+
else ed.moveToNextCell();
|
|
2903
|
+
return;
|
|
2904
|
+
}
|
|
2905
|
+
if (e.shiftKey) ed.dedentList();
|
|
2906
|
+
else ed.indentList();
|
|
2907
|
+
return;
|
|
2908
|
+
}
|
|
2909
|
+
if (!mod) return;
|
|
2910
|
+
const key = e.key.toLowerCase();
|
|
2911
|
+
if (!e.shiftKey && !e.altKey && key === "z") {
|
|
2912
|
+
e.preventDefault();
|
|
2913
|
+
historyRef.current.undo();
|
|
2914
|
+
return;
|
|
2915
|
+
}
|
|
2916
|
+
if (!e.altKey && (key === "y" || e.shiftKey && key === "z")) {
|
|
2917
|
+
e.preventDefault();
|
|
2918
|
+
historyRef.current.redo();
|
|
2919
|
+
return;
|
|
2920
|
+
}
|
|
2921
|
+
if (e.altKey && !e.shiftKey && /^[0-6]$/.test(key)) {
|
|
2922
|
+
e.preventDefault();
|
|
2923
|
+
const n = Number(key);
|
|
2924
|
+
if (n === 0) ed.setBlockType("paragraph");
|
|
2925
|
+
else ed.setBlockType("heading", { level: n });
|
|
2926
|
+
return;
|
|
2927
|
+
}
|
|
2928
|
+
if (e.shiftKey && !e.altKey && (key === "7" || key === "8")) {
|
|
2929
|
+
e.preventDefault();
|
|
2930
|
+
ed.toggleList(key === "7" ? "ordered" : "bullet");
|
|
2931
|
+
return;
|
|
2932
|
+
}
|
|
2933
|
+
const markToggle = !e.shiftKey && !e.altKey ? BOOLEAN_SHORTCUTS[key] : void 0;
|
|
2934
|
+
if (markToggle) {
|
|
2935
|
+
e.preventDefault();
|
|
2936
|
+
ed.toggleMark(markToggle);
|
|
2937
|
+
return;
|
|
2938
|
+
}
|
|
2939
|
+
if (e.shiftKey && !e.altKey && key === "x") {
|
|
2940
|
+
e.preventDefault();
|
|
2941
|
+
ed.toggleMark("strike");
|
|
2942
|
+
return;
|
|
2943
|
+
}
|
|
2944
|
+
if (!e.altKey && !e.shiftKey && key === "k") {
|
|
2945
|
+
e.preventDefault();
|
|
2946
|
+
const active = ed.getActiveMarks();
|
|
2947
|
+
const currentHref = active.link && typeof active.link === "object" && "href" in active.link ? active.link.href : "";
|
|
2948
|
+
void ed.requestLink(currentHref).then((href) => {
|
|
2949
|
+
if (href === null) return;
|
|
2950
|
+
if (href === "") ed.removeMark("link");
|
|
2951
|
+
else ed.setMark("link", { href });
|
|
2952
|
+
});
|
|
2953
|
+
}
|
|
2954
|
+
}, []);
|
|
2955
|
+
const onCompositionStart = useCallback5(() => {
|
|
2956
|
+
isComposingRef.current = true;
|
|
2957
|
+
}, []);
|
|
2958
|
+
const onCompositionEnd = useCallback5((e) => {
|
|
2959
|
+
isComposingRef.current = false;
|
|
2960
|
+
if (e.data && e.data.length > 0) {
|
|
2961
|
+
insertText(ctxRef.current, e.data);
|
|
2962
|
+
}
|
|
2963
|
+
}, []);
|
|
2964
|
+
const topLevels = useMemo4(() => groupTopLevels(snapshot.blocks), [snapshot]);
|
|
2965
|
+
const docSettings = usePageSettings(editor.doc);
|
|
2966
|
+
const propIsSet = pageGeometry !== void 0;
|
|
2967
|
+
const geom = propIsSet ? pageGeometry ?? A4_PAGE : docSettings;
|
|
2968
|
+
const paginated = pageGeometry !== null;
|
|
2969
|
+
const selectedEmbed = editor.getSelectedEmbed();
|
|
2970
|
+
const layout = usePageLayout(
|
|
2971
|
+
rootRef,
|
|
2972
|
+
geom,
|
|
2973
|
+
topLevels.length,
|
|
2974
|
+
[topLevels, pageGeometry]
|
|
2975
|
+
);
|
|
2976
|
+
const effectiveLayout = paginated ? layout : defaultPageLayout(topLevels.length);
|
|
2977
|
+
const pageStyle = paginated ? {
|
|
2978
|
+
"--ed-page-width": `${geom.width}px`,
|
|
2979
|
+
"--ed-page-height": `${geom.height}px`,
|
|
2980
|
+
"--ed-page-margin-top": `${geom.marginTop}px`,
|
|
2981
|
+
"--ed-page-margin-bottom": `${geom.marginBottom}px`,
|
|
2982
|
+
"--ed-page-margin-left": `${geom.marginLeft}px`,
|
|
2983
|
+
"--ed-page-margin-right": `${geom.marginRight}px`
|
|
2984
|
+
} : void 0;
|
|
2985
|
+
const body = /* @__PURE__ */ jsxs8(
|
|
2986
|
+
"div",
|
|
2987
|
+
{
|
|
2988
|
+
ref: rootRef,
|
|
2989
|
+
contentEditable: true,
|
|
2990
|
+
suppressContentEditableWarning: true,
|
|
2991
|
+
spellCheck: true,
|
|
2992
|
+
autoCorrect: "off",
|
|
2993
|
+
autoCapitalize: "off",
|
|
2994
|
+
role: "textbox",
|
|
2995
|
+
"aria-multiline": "true",
|
|
2996
|
+
className: (className ?? "ed-root") + (paginated ? " ed-root--paged" : " ed-root--flat"),
|
|
2997
|
+
style: pageStyle,
|
|
2998
|
+
onKeyDown,
|
|
2999
|
+
onCompositionStart,
|
|
3000
|
+
onCompositionEnd,
|
|
3001
|
+
children: [
|
|
3002
|
+
paginated ? effectiveLayout.pages.map((page, pi) => {
|
|
3003
|
+
const ctx = {
|
|
3004
|
+
pageNumber: pi + 1,
|
|
3005
|
+
pageCount: effectiveLayout.pages.length
|
|
3006
|
+
};
|
|
3007
|
+
return /* @__PURE__ */ jsxs8(
|
|
3008
|
+
"div",
|
|
3009
|
+
{
|
|
3010
|
+
className: "ed-page",
|
|
3011
|
+
"data-page-number": ctx.pageNumber,
|
|
3012
|
+
"data-page-total": ctx.pageCount,
|
|
3013
|
+
children: [
|
|
3014
|
+
renderPageHeader && /* @__PURE__ */ jsx9("div", { className: "ed-page-header", contentEditable: false, children: renderPageHeader(ctx) }),
|
|
3015
|
+
/* @__PURE__ */ jsx9("div", { className: "ed-page-content", children: page.slots.map((slot) => renderSlot(slot, topLevels)) }),
|
|
3016
|
+
renderPageFooter && /* @__PURE__ */ jsx9("div", { className: "ed-page-footer", contentEditable: false, children: renderPageFooter(ctx) })
|
|
3017
|
+
]
|
|
3018
|
+
},
|
|
3019
|
+
`page-${pi}`
|
|
3020
|
+
);
|
|
3021
|
+
}) : topLevels.map((t) => renderTopLevel(t)),
|
|
3022
|
+
selectedEmbed && /* @__PURE__ */ jsx9(
|
|
3023
|
+
ImageResizeOverlay,
|
|
3024
|
+
{
|
|
3025
|
+
rootRef,
|
|
3026
|
+
blockIndex: selectedEmbed.blockIndex,
|
|
3027
|
+
offset: selectedEmbed.offset,
|
|
3028
|
+
cellIndex: selectedEmbed.cellIndex,
|
|
3029
|
+
embed: selectedEmbed.embed,
|
|
3030
|
+
onCommit: (w, h) => editor.setImageAttrs(
|
|
3031
|
+
selectedEmbed.blockIndex,
|
|
3032
|
+
selectedEmbed.offset,
|
|
3033
|
+
{ width: w, height: h },
|
|
3034
|
+
selectedEmbed.cellIndex
|
|
3035
|
+
),
|
|
3036
|
+
onCommitMove: (ox, oy) => editor.setImageAttrs(
|
|
3037
|
+
selectedEmbed.blockIndex,
|
|
3038
|
+
selectedEmbed.offset,
|
|
3039
|
+
{ offsetX: ox, offsetY: oy },
|
|
3040
|
+
selectedEmbed.cellIndex
|
|
3041
|
+
)
|
|
3042
|
+
}
|
|
3043
|
+
)
|
|
3044
|
+
]
|
|
3045
|
+
}
|
|
3046
|
+
);
|
|
3047
|
+
return /* @__PURE__ */ jsx9(EditorProvider, { editor, children: body });
|
|
3048
|
+
}
|
|
3049
|
+
function groupTopLevels(snapshot) {
|
|
3050
|
+
const out = [];
|
|
3051
|
+
let i = 0;
|
|
3052
|
+
while (i < snapshot.length) {
|
|
3053
|
+
const block = snapshot[i];
|
|
3054
|
+
if (block.type === "listItem") {
|
|
3055
|
+
const groupStart = i;
|
|
3056
|
+
const items = [];
|
|
3057
|
+
const kind = block.attrs.listKind === "ordered" ? "ordered" : "bullet";
|
|
3058
|
+
while (i < snapshot.length && snapshot[i].type === "listItem" && (snapshot[i].attrs.listKind ?? "bullet") === kind) {
|
|
3059
|
+
items.push({ block: snapshot[i], index: i });
|
|
3060
|
+
i++;
|
|
3061
|
+
}
|
|
3062
|
+
out.push({ kind: "list", items, listKind: kind, key: groupStart });
|
|
3063
|
+
continue;
|
|
3064
|
+
}
|
|
3065
|
+
out.push({ kind: "block", block, blockIndex: i, key: i });
|
|
3066
|
+
i++;
|
|
3067
|
+
}
|
|
3068
|
+
return out;
|
|
3069
|
+
}
|
|
3070
|
+
function renderSlot(slot, topLevels) {
|
|
3071
|
+
const top = topLevels[slot.topLevelIndex];
|
|
3072
|
+
if (!top) return null;
|
|
3073
|
+
if (slot.fragment && top.kind === "block") {
|
|
3074
|
+
const f = {
|
|
3075
|
+
index: slot.fragment.index,
|
|
3076
|
+
start: slot.fragment.start,
|
|
3077
|
+
end: slot.fragment.end
|
|
3078
|
+
};
|
|
3079
|
+
return /* @__PURE__ */ jsx9(
|
|
3080
|
+
NodeView,
|
|
3081
|
+
{
|
|
3082
|
+
block: top.block,
|
|
3083
|
+
index: top.blockIndex,
|
|
3084
|
+
fragment: f
|
|
3085
|
+
},
|
|
3086
|
+
`${top.key}-f${f.index}`
|
|
3087
|
+
);
|
|
3088
|
+
}
|
|
3089
|
+
if (slot.tableFragment && top.kind === "block" && top.block.type === "table") {
|
|
3090
|
+
return /* @__PURE__ */ jsx9(
|
|
3091
|
+
NodeView,
|
|
3092
|
+
{
|
|
3093
|
+
block: top.block,
|
|
3094
|
+
index: top.blockIndex,
|
|
3095
|
+
tableFragment: slot.tableFragment
|
|
3096
|
+
},
|
|
3097
|
+
`${top.key}-tf${slot.tableFragment.index}`
|
|
3098
|
+
);
|
|
3099
|
+
}
|
|
3100
|
+
return renderTopLevel(top);
|
|
3101
|
+
}
|
|
3102
|
+
function renderTopLevel(top) {
|
|
3103
|
+
if (top.kind === "list") {
|
|
3104
|
+
const tree = buildListTree(top.items);
|
|
3105
|
+
return renderListTree(tree, top.listKind, `list-${top.key}`);
|
|
3106
|
+
}
|
|
3107
|
+
return /* @__PURE__ */ jsx9(NodeView, { block: top.block, index: top.blockIndex }, top.key);
|
|
3108
|
+
}
|
|
3109
|
+
function buildListTree(items) {
|
|
3110
|
+
const roots = [];
|
|
3111
|
+
const stack = [{ children: roots, level: -1 }];
|
|
3112
|
+
for (const it of items) {
|
|
3113
|
+
const level = clampListLevel2(it.block.attrs.listLevel);
|
|
3114
|
+
while (stack.length > 1 && stack[stack.length - 1].level >= level) stack.pop();
|
|
3115
|
+
const node = { entry: it, children: [] };
|
|
3116
|
+
stack[stack.length - 1].children.push(node);
|
|
3117
|
+
stack.push({ children: node.children, level });
|
|
3118
|
+
}
|
|
3119
|
+
return roots;
|
|
3120
|
+
}
|
|
3121
|
+
function renderListTree(tree, kind, key) {
|
|
3122
|
+
const Tag = kind === "ordered" ? "ol" : "ul";
|
|
3123
|
+
return /* @__PURE__ */ jsx9(Tag, { className: `ed-list ed-list-${kind}`, "data-list-kind": kind, children: tree.map((node) => /* @__PURE__ */ jsx9(NodeView, { block: node.entry.block, index: node.entry.index, children: node.children.length > 0 ? renderListTree(node.children, kind, `nested-${node.entry.index}`) : null }, node.entry.index)) }, key);
|
|
3124
|
+
}
|
|
3125
|
+
function clampListLevel2(l) {
|
|
3126
|
+
if (typeof l !== "number" || !Number.isFinite(l)) return 0;
|
|
3127
|
+
return Math.max(0, Math.min(5, Math.trunc(l)));
|
|
3128
|
+
}
|
|
3129
|
+
var BOOLEAN_SHORTCUTS = {
|
|
3130
|
+
b: "bold",
|
|
3131
|
+
i: "italic",
|
|
3132
|
+
u: "underline"
|
|
3133
|
+
};
|
|
3134
|
+
function caretRangeFromPoint(x, y) {
|
|
3135
|
+
const doc = document;
|
|
3136
|
+
if (typeof doc.caretRangeFromPoint === "function") {
|
|
3137
|
+
return doc.caretRangeFromPoint(x, y);
|
|
3138
|
+
}
|
|
3139
|
+
if (typeof doc.caretPositionFromPoint === "function") {
|
|
3140
|
+
const pos = doc.caretPositionFromPoint(x, y);
|
|
3141
|
+
if (!pos) return null;
|
|
3142
|
+
const range = document.createRange();
|
|
3143
|
+
range.setStart(pos.offsetNode, pos.offset);
|
|
3144
|
+
range.collapse(true);
|
|
3145
|
+
return range;
|
|
3146
|
+
}
|
|
3147
|
+
return null;
|
|
3148
|
+
}
|
|
3149
|
+
|
|
3150
|
+
// src/Toolbar.tsx
|
|
3151
|
+
import {
|
|
3152
|
+
useCallback as useCallback6,
|
|
3153
|
+
useEffect as useEffect8,
|
|
3154
|
+
useRef as useRef11,
|
|
3155
|
+
useState as useState8
|
|
3156
|
+
} from "react";
|
|
3157
|
+
import { jsx as jsx10, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
3158
|
+
var FONT_FAMILIES = ["Arial"];
|
|
3159
|
+
var FONT_SIZES = ["8pt", "10pt", "11pt", "12pt", "14pt", "16pt", "18pt", "24pt", "32pt", "48pt"];
|
|
3160
|
+
var BLOCK_OPTIONS = [
|
|
3161
|
+
{ value: "paragraph", label: "Par\xE1grafo", type: "paragraph" },
|
|
3162
|
+
{ value: "h1", label: "T\xEDtulo 1", type: "heading", level: 1 },
|
|
3163
|
+
{ value: "h2", label: "T\xEDtulo 2", type: "heading", level: 2 },
|
|
3164
|
+
{ value: "h3", label: "T\xEDtulo 3", type: "heading", level: 3 },
|
|
3165
|
+
{ value: "h4", label: "T\xEDtulo 4", type: "heading", level: 4 },
|
|
3166
|
+
{ value: "h5", label: "T\xEDtulo 5", type: "heading", level: 5 },
|
|
3167
|
+
{ value: "h6", label: "T\xEDtulo 6", type: "heading", level: 6 },
|
|
3168
|
+
{ value: "blockquote", label: "Cita\xE7\xE3o", type: "blockquote" },
|
|
3169
|
+
{ value: "codeBlock", label: "C\xF3digo", type: "codeBlock" }
|
|
3170
|
+
];
|
|
3171
|
+
var ALIGN_OPTIONS = [
|
|
3172
|
+
{ value: "left", label: "\u21E4", title: "Alinhar \xE0 esquerda" },
|
|
3173
|
+
{ value: "center", label: "\u2261", title: "Centralizar" },
|
|
3174
|
+
{ value: "right", label: "\u21E5", title: "Alinhar \xE0 direita" },
|
|
3175
|
+
{ value: "justify", label: "\u2630", title: "Justificar" }
|
|
3176
|
+
];
|
|
3177
|
+
var IMAGE_ALIGN_VALUES = /* @__PURE__ */ new Set(["left", "center", "right"]);
|
|
3178
|
+
var IMAGE_LAYOUT_OPTIONS = [
|
|
3179
|
+
{ value: "inline", label: "\u2194", title: "Inline (no fluxo do texto)" },
|
|
3180
|
+
{ value: "wrap-left", label: "\u25E7", title: "Texto contorna \xE0 direita" },
|
|
3181
|
+
{ value: "wrap-right", label: "\u25E8", title: "Texto contorna \xE0 esquerda" },
|
|
3182
|
+
{ value: "behind", label: "\u25A2\u2193", title: "Atr\xE1s do texto" },
|
|
3183
|
+
{ value: "front", label: "\u25A2\u2191", title: "Na frente do texto" }
|
|
3184
|
+
];
|
|
3185
|
+
function Toolbar({ className }) {
|
|
3186
|
+
const editor = useEditorContext();
|
|
3187
|
+
const {
|
|
3188
|
+
isMarkActive,
|
|
3189
|
+
getActiveMarks,
|
|
3190
|
+
toggleMark,
|
|
3191
|
+
setMark,
|
|
3192
|
+
removeMark,
|
|
3193
|
+
getBlockType,
|
|
3194
|
+
getBlockAttr,
|
|
3195
|
+
setBlockType,
|
|
3196
|
+
setBlockAttr,
|
|
3197
|
+
isListActive,
|
|
3198
|
+
toggleList,
|
|
3199
|
+
indentList,
|
|
3200
|
+
dedentList
|
|
3201
|
+
} = editor;
|
|
3202
|
+
const active = getActiveMarks();
|
|
3203
|
+
const blockType = getBlockType();
|
|
3204
|
+
const blockLevel = getBlockAttr("level");
|
|
3205
|
+
const blockAlign = getBlockAttr("align");
|
|
3206
|
+
const blockDir = getBlockAttr("dir");
|
|
3207
|
+
const stop = (e) => {
|
|
3208
|
+
const t = e.target;
|
|
3209
|
+
if (t && (t.tagName === "SELECT" || t.tagName === "INPUT" || t.tagName === "OPTION")) {
|
|
3210
|
+
return;
|
|
3211
|
+
}
|
|
3212
|
+
e.preventDefault();
|
|
3213
|
+
};
|
|
3214
|
+
const onToggle = (name) => (e) => {
|
|
3215
|
+
e.preventDefault();
|
|
3216
|
+
toggleMark(name);
|
|
3217
|
+
};
|
|
3218
|
+
const onColorChange = (e) => {
|
|
3219
|
+
setMark("color", e.target.value);
|
|
3220
|
+
};
|
|
3221
|
+
const onFontFamilyChange = (e) => {
|
|
3222
|
+
const v = e.target.value;
|
|
3223
|
+
if (v === "") removeMark("fontFamily");
|
|
3224
|
+
else setMark("fontFamily", v);
|
|
3225
|
+
};
|
|
3226
|
+
const onFontSizeChange = (e) => {
|
|
3227
|
+
const v = e.target.value;
|
|
3228
|
+
if (v === "") removeMark("fontSize");
|
|
3229
|
+
else setMark("fontSize", v);
|
|
3230
|
+
};
|
|
3231
|
+
const onBlockChange = (e) => {
|
|
3232
|
+
const opt = BLOCK_OPTIONS.find((o) => o.value === e.target.value);
|
|
3233
|
+
if (!opt) return;
|
|
3234
|
+
setBlockType(opt.type, opt.level !== void 0 ? { level: opt.level } : void 0);
|
|
3235
|
+
};
|
|
3236
|
+
const onAlign = (value) => (e) => {
|
|
3237
|
+
e.preventDefault();
|
|
3238
|
+
const selectedEmbed2 = editor.getSelectedEmbed();
|
|
3239
|
+
if (selectedEmbed2 && IMAGE_ALIGN_VALUES.has(value)) {
|
|
3240
|
+
editor.setImageAttrs(
|
|
3241
|
+
selectedEmbed2.blockIndex,
|
|
3242
|
+
selectedEmbed2.offset,
|
|
3243
|
+
{ align: value },
|
|
3244
|
+
selectedEmbed2.cellIndex
|
|
3245
|
+
);
|
|
3246
|
+
return;
|
|
3247
|
+
}
|
|
3248
|
+
setBlockAttr("align", value);
|
|
3249
|
+
};
|
|
3250
|
+
const onInsertImageClick = useCallback6(
|
|
3251
|
+
(e) => {
|
|
3252
|
+
e.preventDefault();
|
|
3253
|
+
imageInputRef.current?.click();
|
|
3254
|
+
},
|
|
3255
|
+
[]
|
|
3256
|
+
);
|
|
3257
|
+
const onImageInputChange = useCallback6(
|
|
3258
|
+
(e) => {
|
|
3259
|
+
const file = e.target.files?.[0];
|
|
3260
|
+
if (file) void editor.insertImageFromFile(file);
|
|
3261
|
+
e.target.value = "";
|
|
3262
|
+
},
|
|
3263
|
+
[editor]
|
|
3264
|
+
);
|
|
3265
|
+
const imageInputRef = useRef11(null);
|
|
3266
|
+
const onToggleDir = (e) => {
|
|
3267
|
+
e.preventDefault();
|
|
3268
|
+
const next = blockDir === "rtl" ? "ltr" : "rtl";
|
|
3269
|
+
setBlockAttr("dir", next);
|
|
3270
|
+
};
|
|
3271
|
+
const linkActive = isMarkActive("link");
|
|
3272
|
+
const onLinkClick = useCallback6(
|
|
3273
|
+
(e) => {
|
|
3274
|
+
e.preventDefault();
|
|
3275
|
+
const currentHref = active.link && typeof active.link === "object" && "href" in active.link ? active.link.href : "";
|
|
3276
|
+
void editor.requestLink(currentHref).then((href) => {
|
|
3277
|
+
if (href === null) return;
|
|
3278
|
+
if (href === "") removeMark("link");
|
|
3279
|
+
else setMark("link", { href });
|
|
3280
|
+
});
|
|
3281
|
+
},
|
|
3282
|
+
[active.link, editor, removeMark, setMark]
|
|
3283
|
+
);
|
|
3284
|
+
const colorValue = typeof active.color === "string" ? active.color : "#000000";
|
|
3285
|
+
const fontFamilyValue = typeof active.fontFamily === "string" ? active.fontFamily : "";
|
|
3286
|
+
const fontSizeValue = typeof active.fontSize === "string" ? active.fontSize : "";
|
|
3287
|
+
const blockValue = blockSelectValue(blockType, blockLevel);
|
|
3288
|
+
const selectedEmbed = editor.getSelectedEmbed();
|
|
3289
|
+
const currentImageLayout = selectedEmbed ? selectedEmbed.embed.layout ?? "inline" : "inline";
|
|
3290
|
+
const onImageLayoutClick = (layout) => (e) => {
|
|
3291
|
+
e.preventDefault();
|
|
3292
|
+
const sel = editor.getSelectedEmbed();
|
|
3293
|
+
if (!sel) return;
|
|
3294
|
+
const reset = layout !== (sel.embed.layout ?? "inline") ? { offsetX: 0, offsetY: 0 } : {};
|
|
3295
|
+
editor.setImageAttrs(
|
|
3296
|
+
sel.blockIndex,
|
|
3297
|
+
sel.offset,
|
|
3298
|
+
{ layout, ...reset },
|
|
3299
|
+
sel.cellIndex
|
|
3300
|
+
);
|
|
3301
|
+
};
|
|
3302
|
+
return /* @__PURE__ */ jsxs9(
|
|
3303
|
+
"div",
|
|
3304
|
+
{
|
|
3305
|
+
className: className ?? "ed-toolbar",
|
|
3306
|
+
role: "toolbar",
|
|
3307
|
+
"aria-label": "Editor toolbar",
|
|
3308
|
+
onMouseDown: stop,
|
|
3309
|
+
children: [
|
|
3310
|
+
/* @__PURE__ */ jsx10(Group, { children: /* @__PURE__ */ jsxs9(
|
|
3311
|
+
"select",
|
|
3312
|
+
{
|
|
3313
|
+
className: "ed-toolbar-select",
|
|
3314
|
+
value: blockValue,
|
|
3315
|
+
onChange: onBlockChange,
|
|
3316
|
+
onMouseDown: stop,
|
|
3317
|
+
"aria-label": "Tipo de bloco",
|
|
3318
|
+
children: [
|
|
3319
|
+
blockValue === "" && /* @__PURE__ */ jsx10("option", { value: "", children: "\u2014 (misto)" }),
|
|
3320
|
+
BLOCK_OPTIONS.map((o) => /* @__PURE__ */ jsx10("option", { value: o.value, children: o.label }, o.value))
|
|
3321
|
+
]
|
|
3322
|
+
}
|
|
3323
|
+
) }),
|
|
3324
|
+
/* @__PURE__ */ jsxs9(Group, { children: [
|
|
3325
|
+
/* @__PURE__ */ jsx10(MarkButton, { title: "Negrito (\u2318B)", pressed: isMarkActive("bold"), onClick: onToggle("bold"), children: /* @__PURE__ */ jsx10("strong", { children: "B" }) }),
|
|
3326
|
+
/* @__PURE__ */ jsx10(MarkButton, { title: "It\xE1lico (\u2318I)", pressed: isMarkActive("italic"), onClick: onToggle("italic"), children: /* @__PURE__ */ jsx10("em", { children: "I" }) }),
|
|
3327
|
+
/* @__PURE__ */ jsx10(MarkButton, { title: "Sublinhado (\u2318U)", pressed: isMarkActive("underline"), onClick: onToggle("underline"), children: /* @__PURE__ */ jsx10("u", { children: "U" }) }),
|
|
3328
|
+
/* @__PURE__ */ jsx10(MarkButton, { title: "Tachado (\u2318\u21E7X)", pressed: isMarkActive("strike"), onClick: onToggle("strike"), children: /* @__PURE__ */ jsx10("s", { children: "S" }) })
|
|
3329
|
+
] }),
|
|
3330
|
+
/* @__PURE__ */ jsxs9(Group, { children: [
|
|
3331
|
+
/* @__PURE__ */ jsxs9("label", { className: "ed-toolbar-label", children: [
|
|
3332
|
+
/* @__PURE__ */ jsx10("span", { className: "ed-toolbar-swatch", "aria-hidden": true, style: { background: colorValue } }),
|
|
3333
|
+
/* @__PURE__ */ jsx10("input", { type: "color", value: colorValue, onChange: onColorChange, onMouseDown: stop, "aria-label": "Cor do texto" })
|
|
3334
|
+
] }),
|
|
3335
|
+
/* @__PURE__ */ jsx10(
|
|
3336
|
+
"button",
|
|
3337
|
+
{
|
|
3338
|
+
type: "button",
|
|
3339
|
+
className: "ed-toolbar-btn",
|
|
3340
|
+
title: "Remover cor",
|
|
3341
|
+
onMouseDown: stop,
|
|
3342
|
+
onClick: (e) => {
|
|
3343
|
+
e.preventDefault();
|
|
3344
|
+
removeMark("color");
|
|
3345
|
+
},
|
|
3346
|
+
children: "\u232B"
|
|
3347
|
+
}
|
|
3348
|
+
)
|
|
3349
|
+
] }),
|
|
3350
|
+
/* @__PURE__ */ jsxs9(Group, { children: [
|
|
3351
|
+
/* @__PURE__ */ jsxs9(
|
|
3352
|
+
"select",
|
|
3353
|
+
{
|
|
3354
|
+
className: "ed-toolbar-select",
|
|
3355
|
+
value: fontFamilyValue,
|
|
3356
|
+
onChange: onFontFamilyChange,
|
|
3357
|
+
onMouseDown: stop,
|
|
3358
|
+
"aria-label": "Fam\xEDlia da fonte",
|
|
3359
|
+
children: [
|
|
3360
|
+
/* @__PURE__ */ jsx10("option", { value: "", children: "Fonte\u2026" }),
|
|
3361
|
+
FONT_FAMILIES.map((f) => /* @__PURE__ */ jsx10("option", { value: f, children: f }, f))
|
|
3362
|
+
]
|
|
3363
|
+
}
|
|
3364
|
+
),
|
|
3365
|
+
/* @__PURE__ */ jsxs9(
|
|
3366
|
+
"select",
|
|
3367
|
+
{
|
|
3368
|
+
className: "ed-toolbar-select",
|
|
3369
|
+
value: fontSizeValue,
|
|
3370
|
+
onChange: onFontSizeChange,
|
|
3371
|
+
onMouseDown: stop,
|
|
3372
|
+
"aria-label": "Tamanho da fonte",
|
|
3373
|
+
children: [
|
|
3374
|
+
/* @__PURE__ */ jsx10("option", { value: "", children: "Tamanho\u2026" }),
|
|
3375
|
+
FONT_SIZES.map((s) => /* @__PURE__ */ jsx10("option", { value: s, children: s }, s))
|
|
3376
|
+
]
|
|
3377
|
+
}
|
|
3378
|
+
)
|
|
3379
|
+
] }),
|
|
3380
|
+
/* @__PURE__ */ jsx10(Group, { children: ALIGN_OPTIONS.map((a) => /* @__PURE__ */ jsx10(
|
|
3381
|
+
"button",
|
|
3382
|
+
{
|
|
3383
|
+
type: "button",
|
|
3384
|
+
className: "ed-toolbar-btn",
|
|
3385
|
+
title: a.title,
|
|
3386
|
+
"aria-pressed": blockAlign === a.value,
|
|
3387
|
+
onMouseDown: stop,
|
|
3388
|
+
onClick: onAlign(a.value),
|
|
3389
|
+
children: a.label
|
|
3390
|
+
},
|
|
3391
|
+
a.value
|
|
3392
|
+
)) }),
|
|
3393
|
+
/* @__PURE__ */ jsx10(Group, { children: /* @__PURE__ */ jsx10(
|
|
3394
|
+
"button",
|
|
3395
|
+
{
|
|
3396
|
+
type: "button",
|
|
3397
|
+
className: "ed-toolbar-btn",
|
|
3398
|
+
"aria-pressed": blockDir === "rtl",
|
|
3399
|
+
title: blockDir === "rtl" ? "Mudar para LTR" : "Mudar para RTL (hebraico)",
|
|
3400
|
+
onMouseDown: stop,
|
|
3401
|
+
onClick: onToggleDir,
|
|
3402
|
+
children: blockDir === "rtl" ? "RTL" : "LTR"
|
|
3403
|
+
}
|
|
3404
|
+
) }),
|
|
3405
|
+
/* @__PURE__ */ jsxs9(Group, { children: [
|
|
3406
|
+
/* @__PURE__ */ jsx10(
|
|
3407
|
+
"button",
|
|
3408
|
+
{
|
|
3409
|
+
type: "button",
|
|
3410
|
+
className: "ed-toolbar-btn",
|
|
3411
|
+
"aria-pressed": isListActive("bullet"),
|
|
3412
|
+
title: "Lista com marcadores (\u2318\u21E78)",
|
|
3413
|
+
onMouseDown: stop,
|
|
3414
|
+
onClick: (e) => {
|
|
3415
|
+
e.preventDefault();
|
|
3416
|
+
toggleList("bullet");
|
|
3417
|
+
},
|
|
3418
|
+
children: "\u2022"
|
|
3419
|
+
}
|
|
3420
|
+
),
|
|
3421
|
+
/* @__PURE__ */ jsx10(
|
|
3422
|
+
"button",
|
|
3423
|
+
{
|
|
3424
|
+
type: "button",
|
|
3425
|
+
className: "ed-toolbar-btn",
|
|
3426
|
+
"aria-pressed": isListActive("ordered"),
|
|
3427
|
+
title: "Lista numerada (\u2318\u21E77)",
|
|
3428
|
+
onMouseDown: stop,
|
|
3429
|
+
onClick: (e) => {
|
|
3430
|
+
e.preventDefault();
|
|
3431
|
+
toggleList("ordered");
|
|
3432
|
+
},
|
|
3433
|
+
children: "1."
|
|
3434
|
+
}
|
|
3435
|
+
),
|
|
3436
|
+
/* @__PURE__ */ jsx10(
|
|
3437
|
+
"button",
|
|
3438
|
+
{
|
|
3439
|
+
type: "button",
|
|
3440
|
+
className: "ed-toolbar-btn",
|
|
3441
|
+
title: "Diminuir recuo (\u21E7Tab)",
|
|
3442
|
+
onMouseDown: stop,
|
|
3443
|
+
onClick: (e) => {
|
|
3444
|
+
e.preventDefault();
|
|
3445
|
+
dedentList();
|
|
3446
|
+
},
|
|
3447
|
+
children: "\u21E4"
|
|
3448
|
+
}
|
|
3449
|
+
),
|
|
3450
|
+
/* @__PURE__ */ jsx10(
|
|
3451
|
+
"button",
|
|
3452
|
+
{
|
|
3453
|
+
type: "button",
|
|
3454
|
+
className: "ed-toolbar-btn",
|
|
3455
|
+
title: "Aumentar recuo (Tab)",
|
|
3456
|
+
onMouseDown: stop,
|
|
3457
|
+
onClick: (e) => {
|
|
3458
|
+
e.preventDefault();
|
|
3459
|
+
indentList();
|
|
3460
|
+
},
|
|
3461
|
+
children: "\u21E5"
|
|
3462
|
+
}
|
|
3463
|
+
)
|
|
3464
|
+
] }),
|
|
3465
|
+
/* @__PURE__ */ jsx10(Group, { children: /* @__PURE__ */ jsx10(
|
|
3466
|
+
"button",
|
|
3467
|
+
{
|
|
3468
|
+
type: "button",
|
|
3469
|
+
className: "ed-toolbar-btn",
|
|
3470
|
+
"aria-pressed": linkActive,
|
|
3471
|
+
title: linkActive ? "Remover link (\u2318K)" : "Adicionar link (\u2318K)",
|
|
3472
|
+
onMouseDown: stop,
|
|
3473
|
+
onClick: onLinkClick,
|
|
3474
|
+
children: "\u{1F517}"
|
|
3475
|
+
}
|
|
3476
|
+
) }),
|
|
3477
|
+
/* @__PURE__ */ jsx10(Group, { children: /* @__PURE__ */ jsx10(TableMenu, {}) }),
|
|
3478
|
+
/* @__PURE__ */ jsxs9(Group, { children: [
|
|
3479
|
+
/* @__PURE__ */ jsx10(
|
|
3480
|
+
"button",
|
|
3481
|
+
{
|
|
3482
|
+
type: "button",
|
|
3483
|
+
className: "ed-toolbar-btn",
|
|
3484
|
+
title: "Inserir imagem",
|
|
3485
|
+
onMouseDown: stop,
|
|
3486
|
+
onClick: onInsertImageClick,
|
|
3487
|
+
children: "\u{1F5BC}"
|
|
3488
|
+
}
|
|
3489
|
+
),
|
|
3490
|
+
/* @__PURE__ */ jsx10(
|
|
3491
|
+
"input",
|
|
3492
|
+
{
|
|
3493
|
+
ref: imageInputRef,
|
|
3494
|
+
type: "file",
|
|
3495
|
+
accept: "image/*",
|
|
3496
|
+
hidden: true,
|
|
3497
|
+
onChange: onImageInputChange
|
|
3498
|
+
}
|
|
3499
|
+
)
|
|
3500
|
+
] }),
|
|
3501
|
+
selectedEmbed && /* @__PURE__ */ jsxs9(Group, { children: [
|
|
3502
|
+
IMAGE_LAYOUT_OPTIONS.map((opt) => /* @__PURE__ */ jsx10(
|
|
3503
|
+
"button",
|
|
3504
|
+
{
|
|
3505
|
+
type: "button",
|
|
3506
|
+
className: "ed-toolbar-btn",
|
|
3507
|
+
title: opt.title,
|
|
3508
|
+
"aria-pressed": currentImageLayout === opt.value,
|
|
3509
|
+
onMouseDown: stop,
|
|
3510
|
+
onClick: onImageLayoutClick(opt.value),
|
|
3511
|
+
children: opt.label
|
|
3512
|
+
},
|
|
3513
|
+
opt.value
|
|
3514
|
+
)),
|
|
3515
|
+
/* @__PURE__ */ jsx10(
|
|
3516
|
+
"button",
|
|
3517
|
+
{
|
|
3518
|
+
type: "button",
|
|
3519
|
+
className: "ed-toolbar-btn",
|
|
3520
|
+
title: "Legenda da imagem",
|
|
3521
|
+
"aria-pressed": !!selectedEmbed.embed.caption,
|
|
3522
|
+
onMouseDown: stop,
|
|
3523
|
+
onClick: (e) => {
|
|
3524
|
+
e.preventDefault();
|
|
3525
|
+
const initialCaption = selectedEmbed.embed.caption ?? "";
|
|
3526
|
+
const initialAlign = selectedEmbed.embed.captionAlign ?? "center";
|
|
3527
|
+
void editor.requestImageCaption(initialCaption, initialAlign).then((result) => {
|
|
3528
|
+
if (result === null) return;
|
|
3529
|
+
const empty = result.caption.length === 0;
|
|
3530
|
+
editor.setImageAttrs(
|
|
3531
|
+
selectedEmbed.blockIndex,
|
|
3532
|
+
selectedEmbed.offset,
|
|
3533
|
+
{
|
|
3534
|
+
caption: empty ? void 0 : result.caption,
|
|
3535
|
+
captionAlign: empty ? void 0 : result.align
|
|
3536
|
+
},
|
|
3537
|
+
selectedEmbed.cellIndex
|
|
3538
|
+
);
|
|
3539
|
+
});
|
|
3540
|
+
},
|
|
3541
|
+
children: "Legenda"
|
|
3542
|
+
}
|
|
3543
|
+
)
|
|
3544
|
+
] }),
|
|
3545
|
+
/* @__PURE__ */ jsx10(Group, { children: /* @__PURE__ */ jsx10(
|
|
3546
|
+
"button",
|
|
3547
|
+
{
|
|
3548
|
+
type: "button",
|
|
3549
|
+
className: "ed-toolbar-btn",
|
|
3550
|
+
title: "Configurar p\xE1gina (tamanho, margens)",
|
|
3551
|
+
onMouseDown: stop,
|
|
3552
|
+
onClick: (e) => {
|
|
3553
|
+
e.preventDefault();
|
|
3554
|
+
void editor.requestPageConfig();
|
|
3555
|
+
},
|
|
3556
|
+
children: "\u2699"
|
|
3557
|
+
}
|
|
3558
|
+
) })
|
|
3559
|
+
]
|
|
3560
|
+
}
|
|
3561
|
+
);
|
|
3562
|
+
}
|
|
3563
|
+
function TableMenu() {
|
|
3564
|
+
const editor = useEditorContext();
|
|
3565
|
+
const inTable = editor.isInTable();
|
|
3566
|
+
const [open, setOpen] = useState8(false);
|
|
3567
|
+
const rootRef = useRef11(null);
|
|
3568
|
+
useEffect8(() => {
|
|
3569
|
+
if (!open) return;
|
|
3570
|
+
const onDown = (e) => {
|
|
3571
|
+
if (!rootRef.current) return;
|
|
3572
|
+
if (rootRef.current.contains(e.target)) return;
|
|
3573
|
+
setOpen(false);
|
|
3574
|
+
};
|
|
3575
|
+
document.addEventListener("mousedown", onDown);
|
|
3576
|
+
return () => document.removeEventListener("mousedown", onDown);
|
|
3577
|
+
}, [open]);
|
|
3578
|
+
return /* @__PURE__ */ jsxs9("div", { ref: rootRef, className: "ed-toolbar-tablemenu", children: [
|
|
3579
|
+
/* @__PURE__ */ jsx10(
|
|
3580
|
+
"button",
|
|
3581
|
+
{
|
|
3582
|
+
type: "button",
|
|
3583
|
+
className: "ed-toolbar-btn",
|
|
3584
|
+
title: "Tabela",
|
|
3585
|
+
"aria-pressed": open,
|
|
3586
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
3587
|
+
onClick: (e) => {
|
|
3588
|
+
e.preventDefault();
|
|
3589
|
+
setOpen((v) => !v);
|
|
3590
|
+
},
|
|
3591
|
+
children: "\u25A6"
|
|
3592
|
+
}
|
|
3593
|
+
),
|
|
3594
|
+
open && /* @__PURE__ */ jsxs9(
|
|
3595
|
+
"div",
|
|
3596
|
+
{
|
|
3597
|
+
className: "ed-table-popover",
|
|
3598
|
+
role: "menu",
|
|
3599
|
+
onMouseDown: (e) => {
|
|
3600
|
+
const t = e.target;
|
|
3601
|
+
if (!t || t.tagName !== "INPUT" && t.tagName !== "BUTTON") {
|
|
3602
|
+
e.preventDefault();
|
|
3603
|
+
}
|
|
3604
|
+
},
|
|
3605
|
+
children: [
|
|
3606
|
+
/* @__PURE__ */ jsx10(
|
|
3607
|
+
TableSizePicker,
|
|
3608
|
+
{
|
|
3609
|
+
onPick: (rows, cols) => {
|
|
3610
|
+
setOpen(false);
|
|
3611
|
+
editor.insertTable(rows, cols);
|
|
3612
|
+
}
|
|
3613
|
+
}
|
|
3614
|
+
),
|
|
3615
|
+
inTable && /* @__PURE__ */ jsxs9("div", { className: "ed-table-actions", children: [
|
|
3616
|
+
/* @__PURE__ */ jsx10("hr", {}),
|
|
3617
|
+
/* @__PURE__ */ jsx10("button", { type: "button", onClick: () => {
|
|
3618
|
+
setOpen(false);
|
|
3619
|
+
editor.insertRowAbove();
|
|
3620
|
+
}, children: "Inserir linha acima" }),
|
|
3621
|
+
/* @__PURE__ */ jsx10("button", { type: "button", onClick: () => {
|
|
3622
|
+
setOpen(false);
|
|
3623
|
+
editor.insertRowBelow();
|
|
3624
|
+
}, children: "Inserir linha abaixo" }),
|
|
3625
|
+
/* @__PURE__ */ jsx10("button", { type: "button", onClick: () => {
|
|
3626
|
+
setOpen(false);
|
|
3627
|
+
editor.insertColumnLeft();
|
|
3628
|
+
}, children: "Inserir coluna \xE0 esquerda" }),
|
|
3629
|
+
/* @__PURE__ */ jsx10("button", { type: "button", onClick: () => {
|
|
3630
|
+
setOpen(false);
|
|
3631
|
+
editor.insertColumnRight();
|
|
3632
|
+
}, children: "Inserir coluna \xE0 direita" }),
|
|
3633
|
+
/* @__PURE__ */ jsx10("button", { type: "button", onClick: () => {
|
|
3634
|
+
setOpen(false);
|
|
3635
|
+
editor.deleteCurrentRow();
|
|
3636
|
+
}, children: "Excluir linha" }),
|
|
3637
|
+
/* @__PURE__ */ jsx10("button", { type: "button", onClick: () => {
|
|
3638
|
+
setOpen(false);
|
|
3639
|
+
editor.deleteCurrentColumn();
|
|
3640
|
+
}, children: "Excluir coluna" }),
|
|
3641
|
+
/* @__PURE__ */ jsx10("hr", {}),
|
|
3642
|
+
editor.hasTableRectSelection() && /* @__PURE__ */ jsx10(
|
|
3643
|
+
"button",
|
|
3644
|
+
{
|
|
3645
|
+
type: "button",
|
|
3646
|
+
onClick: () => {
|
|
3647
|
+
setOpen(false);
|
|
3648
|
+
editor.mergeSelection();
|
|
3649
|
+
},
|
|
3650
|
+
children: "Mesclar c\xE9lulas selecionadas"
|
|
3651
|
+
}
|
|
3652
|
+
),
|
|
3653
|
+
/* @__PURE__ */ jsx10("button", { type: "button", onClick: () => {
|
|
3654
|
+
setOpen(false);
|
|
3655
|
+
editor.mergeRight();
|
|
3656
|
+
}, children: "Mesclar com c\xE9lula \xE0 direita" }),
|
|
3657
|
+
/* @__PURE__ */ jsx10("button", { type: "button", onClick: () => {
|
|
3658
|
+
setOpen(false);
|
|
3659
|
+
editor.mergeDown();
|
|
3660
|
+
}, children: "Mesclar com c\xE9lula abaixo" }),
|
|
3661
|
+
/* @__PURE__ */ jsx10("button", { type: "button", onClick: () => {
|
|
3662
|
+
setOpen(false);
|
|
3663
|
+
editor.splitCell();
|
|
3664
|
+
}, children: "Dividir c\xE9lulas mescladas" }),
|
|
3665
|
+
/* @__PURE__ */ jsx10(
|
|
3666
|
+
"button",
|
|
3667
|
+
{
|
|
3668
|
+
type: "button",
|
|
3669
|
+
className: "ed-table-delete",
|
|
3670
|
+
onClick: () => {
|
|
3671
|
+
setOpen(false);
|
|
3672
|
+
editor.deleteTable();
|
|
3673
|
+
},
|
|
3674
|
+
children: "Excluir tabela"
|
|
3675
|
+
}
|
|
3676
|
+
)
|
|
3677
|
+
] })
|
|
3678
|
+
]
|
|
3679
|
+
}
|
|
3680
|
+
)
|
|
3681
|
+
] });
|
|
3682
|
+
}
|
|
3683
|
+
var PICKER_MAX_ROWS = 8;
|
|
3684
|
+
var PICKER_MAX_COLS = 10;
|
|
3685
|
+
function TableSizePicker({ onPick }) {
|
|
3686
|
+
const [hover, setHover] = useState8(null);
|
|
3687
|
+
const label = hover ? `${hover.r + 1} \xD7 ${hover.c + 1}` : "Inserir tabela";
|
|
3688
|
+
return /* @__PURE__ */ jsxs9("div", { className: "ed-table-picker", children: [
|
|
3689
|
+
/* @__PURE__ */ jsx10("div", { className: "ed-table-picker-label", children: label }),
|
|
3690
|
+
/* @__PURE__ */ jsx10(
|
|
3691
|
+
"div",
|
|
3692
|
+
{
|
|
3693
|
+
className: "ed-table-picker-grid",
|
|
3694
|
+
style: {
|
|
3695
|
+
gridTemplateColumns: `repeat(${PICKER_MAX_COLS}, 18px)`,
|
|
3696
|
+
gridTemplateRows: `repeat(${PICKER_MAX_ROWS}, 18px)`
|
|
3697
|
+
},
|
|
3698
|
+
onMouseLeave: () => setHover(null),
|
|
3699
|
+
children: Array.from(
|
|
3700
|
+
{ length: PICKER_MAX_ROWS },
|
|
3701
|
+
(_, r) => Array.from({ length: PICKER_MAX_COLS }, (_2, c) => {
|
|
3702
|
+
const filled = hover != null && r <= hover.r && c <= hover.c;
|
|
3703
|
+
return /* @__PURE__ */ jsx10(
|
|
3704
|
+
"button",
|
|
3705
|
+
{
|
|
3706
|
+
type: "button",
|
|
3707
|
+
className: `ed-table-picker-cell${filled ? " filled" : ""}`,
|
|
3708
|
+
onMouseEnter: () => setHover({ r, c }),
|
|
3709
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
3710
|
+
onClick: (e) => {
|
|
3711
|
+
e.preventDefault();
|
|
3712
|
+
onPick(r + 1, c + 1);
|
|
3713
|
+
},
|
|
3714
|
+
"aria-label": `${r + 1} \xD7 ${c + 1}`
|
|
3715
|
+
},
|
|
3716
|
+
`${r}-${c}`
|
|
3717
|
+
);
|
|
3718
|
+
})
|
|
3719
|
+
)
|
|
3720
|
+
}
|
|
3721
|
+
)
|
|
3722
|
+
] });
|
|
3723
|
+
}
|
|
3724
|
+
function blockSelectValue(type, level) {
|
|
3725
|
+
if (type === "mixed") return "";
|
|
3726
|
+
if (type === "heading") {
|
|
3727
|
+
if (typeof level === "number" && level >= 1 && level <= 6) return `h${level}`;
|
|
3728
|
+
return "h1";
|
|
3729
|
+
}
|
|
3730
|
+
return type;
|
|
3731
|
+
}
|
|
3732
|
+
function Group({ children }) {
|
|
3733
|
+
return /* @__PURE__ */ jsx10("div", { className: "ed-toolbar-group", children });
|
|
3734
|
+
}
|
|
3735
|
+
function MarkButton({ title, pressed, onClick, children }) {
|
|
3736
|
+
return /* @__PURE__ */ jsx10(
|
|
3737
|
+
"button",
|
|
3738
|
+
{
|
|
3739
|
+
type: "button",
|
|
3740
|
+
className: "ed-toolbar-btn",
|
|
3741
|
+
"aria-pressed": pressed,
|
|
3742
|
+
title,
|
|
3743
|
+
onMouseDown: (e) => e.preventDefault(),
|
|
3744
|
+
onClick,
|
|
3745
|
+
children
|
|
3746
|
+
}
|
|
3747
|
+
);
|
|
3748
|
+
}
|
|
3749
|
+
export {
|
|
3750
|
+
A4_PAGE,
|
|
3751
|
+
Editor,
|
|
3752
|
+
EditorProvider,
|
|
3753
|
+
ImageCaptionDialog,
|
|
3754
|
+
LinkDialog,
|
|
3755
|
+
NodeView,
|
|
3756
|
+
PageConfigDialog,
|
|
3757
|
+
Toolbar,
|
|
3758
|
+
applyDomSelection,
|
|
3759
|
+
contentHeight,
|
|
3760
|
+
contentWidth,
|
|
3761
|
+
defaultPageLayout,
|
|
3762
|
+
deltaLength,
|
|
3763
|
+
findBlockIndex,
|
|
3764
|
+
getBlockElement,
|
|
3765
|
+
getCellElement,
|
|
3766
|
+
getFragmentForOffset,
|
|
3767
|
+
isTableRectSelection,
|
|
3768
|
+
readDomSelection,
|
|
3769
|
+
renderInline,
|
|
3770
|
+
selectionsEqual,
|
|
3771
|
+
sliceDelta,
|
|
3772
|
+
textOffsetWithin,
|
|
3773
|
+
useEditor,
|
|
3774
|
+
useEditorContext,
|
|
3775
|
+
usePageLayout,
|
|
3776
|
+
usePageSettings
|
|
3777
|
+
};
|
|
3778
|
+
//# sourceMappingURL=index.js.map
|