@sofereditor/react 0.5.0 → 0.7.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/dist/index.cjs +939 -147
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +45 -2
- package/dist/index.d.ts +45 -2
- package/dist/index.js +831 -38
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
package/dist/index.js
CHANGED
|
@@ -8,6 +8,7 @@ import {
|
|
|
8
8
|
useState as useState10
|
|
9
9
|
} from "react";
|
|
10
10
|
import {
|
|
11
|
+
collapsedSelection as collapsedSelection2,
|
|
11
12
|
deleteBackward,
|
|
12
13
|
deleteForward,
|
|
13
14
|
deleteSelection,
|
|
@@ -15,9 +16,10 @@ import {
|
|
|
15
16
|
insertParagraph,
|
|
16
17
|
insertSlice,
|
|
17
18
|
insertText,
|
|
19
|
+
isEmbedAdjacentToCaret,
|
|
18
20
|
serializeSelection,
|
|
19
21
|
sliceToText,
|
|
20
|
-
SOFER_MIME
|
|
22
|
+
SOFER_MIME as SOFER_MIME2
|
|
21
23
|
} from "@sofereditor/core";
|
|
22
24
|
|
|
23
25
|
// src/dom-bridge.ts
|
|
@@ -293,6 +295,746 @@ function applyDomSelection(root, modelSel) {
|
|
|
293
295
|
}
|
|
294
296
|
}
|
|
295
297
|
|
|
298
|
+
// src/pastePlan.ts
|
|
299
|
+
import { SOFER_MIME } from "@sofereditor/core";
|
|
300
|
+
|
|
301
|
+
// src/htmlToSlice.ts
|
|
302
|
+
import { isImageEmbed, MAX_LIST_LEVEL } from "@sofereditor/core";
|
|
303
|
+
|
|
304
|
+
// src/imageConstraints.ts
|
|
305
|
+
var MAX_INSERT_WIDTH = 600;
|
|
306
|
+
|
|
307
|
+
// src/htmlToSlice.ts
|
|
308
|
+
function htmlToSlice(html) {
|
|
309
|
+
if (!html || html.trim().length === 0) return null;
|
|
310
|
+
let doc;
|
|
311
|
+
try {
|
|
312
|
+
doc = new DOMParser().parseFromString(html, "text/html");
|
|
313
|
+
} catch {
|
|
314
|
+
return null;
|
|
315
|
+
}
|
|
316
|
+
const body = doc.body;
|
|
317
|
+
if (!body) return null;
|
|
318
|
+
const blocks = [];
|
|
319
|
+
try {
|
|
320
|
+
walkBlockLevel(body, {}, blocks);
|
|
321
|
+
} catch {
|
|
322
|
+
return null;
|
|
323
|
+
}
|
|
324
|
+
if (blocks.length === 0) return null;
|
|
325
|
+
normalizeListLevels(blocks);
|
|
326
|
+
const temConteudo = blocks.some(
|
|
327
|
+
(b) => b.text.trim().length > 0 || b.delta.some((op) => isImageEmbed(op.insert))
|
|
328
|
+
);
|
|
329
|
+
if (!temConteudo) return null;
|
|
330
|
+
const blockLevel = blocks.length > 1 || blocks[0].type !== "paragraph";
|
|
331
|
+
return { blocks, openStart: false, openEnd: false, ...blockLevel ? { blockLevel: true } : {} };
|
|
332
|
+
}
|
|
333
|
+
var IGNORED_TAGS = /* @__PURE__ */ new Set(["script", "style", "meta", "link", "head", "title", "xml"]);
|
|
334
|
+
var BLOCK_LEVEL_TAGS = /* @__PURE__ */ new Set([
|
|
335
|
+
"p",
|
|
336
|
+
"div",
|
|
337
|
+
"section",
|
|
338
|
+
"article",
|
|
339
|
+
"header",
|
|
340
|
+
"footer",
|
|
341
|
+
"main",
|
|
342
|
+
"figure",
|
|
343
|
+
"figcaption",
|
|
344
|
+
"pre",
|
|
345
|
+
"address"
|
|
346
|
+
]);
|
|
347
|
+
var BLOCK_TRIGGER_TAGS = /* @__PURE__ */ new Set([
|
|
348
|
+
"p",
|
|
349
|
+
"div",
|
|
350
|
+
"ul",
|
|
351
|
+
"ol",
|
|
352
|
+
"li",
|
|
353
|
+
"h1",
|
|
354
|
+
"h2",
|
|
355
|
+
"h3",
|
|
356
|
+
"h4",
|
|
357
|
+
"h5",
|
|
358
|
+
"h6",
|
|
359
|
+
"blockquote",
|
|
360
|
+
"table",
|
|
361
|
+
"tr",
|
|
362
|
+
"td",
|
|
363
|
+
"th"
|
|
364
|
+
]);
|
|
365
|
+
function isNamespacedTag(tag) {
|
|
366
|
+
return tag.includes(":");
|
|
367
|
+
}
|
|
368
|
+
function hasBlockDescendant(el) {
|
|
369
|
+
const all = el.querySelectorAll("*");
|
|
370
|
+
for (const e of Array.from(all)) {
|
|
371
|
+
if (BLOCK_TRIGGER_TAGS.has(e.tagName.toLowerCase())) return true;
|
|
372
|
+
}
|
|
373
|
+
return false;
|
|
374
|
+
}
|
|
375
|
+
function walkBlockLevel(container, marks, blocks) {
|
|
376
|
+
let loose = [];
|
|
377
|
+
const flushLoose = () => {
|
|
378
|
+
if (loose.length === 0) return;
|
|
379
|
+
const nodes = loose;
|
|
380
|
+
loose = [];
|
|
381
|
+
const delta = inlineDeltaFromNodes(nodes, marks);
|
|
382
|
+
if (delta.length > 0) blocks.push(makeBlock("paragraph", delta, {}));
|
|
383
|
+
};
|
|
384
|
+
for (const node of Array.from(container.childNodes)) {
|
|
385
|
+
if (node.nodeType === Node.COMMENT_NODE) continue;
|
|
386
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
387
|
+
loose.push(node);
|
|
388
|
+
continue;
|
|
389
|
+
}
|
|
390
|
+
if (node.nodeType !== Node.ELEMENT_NODE) continue;
|
|
391
|
+
const el = node;
|
|
392
|
+
const tag = el.tagName.toLowerCase();
|
|
393
|
+
if (IGNORED_TAGS.has(tag) || isNamespacedTag(tag)) continue;
|
|
394
|
+
switch (tag) {
|
|
395
|
+
case "ul":
|
|
396
|
+
case "ol": {
|
|
397
|
+
flushLoose();
|
|
398
|
+
handleList(el, tag === "ol" ? "ordered" : "bullet", 0, marks, blocks);
|
|
399
|
+
break;
|
|
400
|
+
}
|
|
401
|
+
case "h1":
|
|
402
|
+
case "h2":
|
|
403
|
+
case "h3":
|
|
404
|
+
case "h4":
|
|
405
|
+
case "h5":
|
|
406
|
+
case "h6": {
|
|
407
|
+
flushLoose();
|
|
408
|
+
const level = Number(tag[1]);
|
|
409
|
+
const attrs = { level };
|
|
410
|
+
const align = readAlign(el);
|
|
411
|
+
if (align) attrs.align = align;
|
|
412
|
+
const delta = inlineDeltaFromNodes(Array.from(el.childNodes), marks);
|
|
413
|
+
blocks.push(makeBlock("heading", delta, attrs));
|
|
414
|
+
break;
|
|
415
|
+
}
|
|
416
|
+
case "blockquote": {
|
|
417
|
+
flushLoose();
|
|
418
|
+
handleBlockquote(el, marks, blocks);
|
|
419
|
+
break;
|
|
420
|
+
}
|
|
421
|
+
case "table": {
|
|
422
|
+
flushLoose();
|
|
423
|
+
handleTable(el, marks, blocks);
|
|
424
|
+
break;
|
|
425
|
+
}
|
|
426
|
+
case "li": {
|
|
427
|
+
flushLoose();
|
|
428
|
+
const nestedLists = topLevelListDescendants(el);
|
|
429
|
+
const clone = el.cloneNode(true);
|
|
430
|
+
for (const l of topLevelListDescendants(clone)) l.remove();
|
|
431
|
+
const delta = inlineDeltaFromNodes(Array.from(clone.childNodes), marks);
|
|
432
|
+
blocks.push(makeBlock("listItem", delta, { listKind: "bullet", listLevel: 0 }));
|
|
433
|
+
for (const nested of nestedLists) {
|
|
434
|
+
const nestedKind = nested.tagName.toLowerCase() === "ol" ? "ordered" : "bullet";
|
|
435
|
+
handleList(nested, nestedKind, 1, marks, blocks);
|
|
436
|
+
}
|
|
437
|
+
break;
|
|
438
|
+
}
|
|
439
|
+
case "img":
|
|
440
|
+
loose.push(node);
|
|
441
|
+
break;
|
|
442
|
+
case "br":
|
|
443
|
+
loose.push(node);
|
|
444
|
+
break;
|
|
445
|
+
default: {
|
|
446
|
+
const styleAttr = el.getAttribute("style") ?? "";
|
|
447
|
+
const wordListLevel = parseWordListLevel(styleAttr);
|
|
448
|
+
if (wordListLevel != null && !hasBlockDescendant(el)) {
|
|
449
|
+
flushLoose();
|
|
450
|
+
const marker = findWordListMarker(el);
|
|
451
|
+
const listKind = marker ? detectWordListKind(marker) : "bullet";
|
|
452
|
+
const attrs = { listKind, listLevel: wordListLevel };
|
|
453
|
+
const align = readAlign(el);
|
|
454
|
+
if (align) attrs.align = align;
|
|
455
|
+
const delta = inlineDeltaFromNodes(Array.from(el.childNodes), marks);
|
|
456
|
+
blocks.push(makeBlock("listItem", delta, attrs));
|
|
457
|
+
break;
|
|
458
|
+
}
|
|
459
|
+
if (hasBlockDescendant(el)) {
|
|
460
|
+
flushLoose();
|
|
461
|
+
let nextMarks = applyTagMarks(tag, marks);
|
|
462
|
+
nextMarks = applyStyleMarks(el.getAttribute("style"), nextMarks);
|
|
463
|
+
walkBlockLevel(el, nextMarks, blocks);
|
|
464
|
+
break;
|
|
465
|
+
}
|
|
466
|
+
if (BLOCK_LEVEL_TAGS.has(tag)) {
|
|
467
|
+
flushLoose();
|
|
468
|
+
const attrs = {};
|
|
469
|
+
const align = readAlign(el);
|
|
470
|
+
if (align) attrs.align = align;
|
|
471
|
+
const delta = inlineDeltaFromNodes(Array.from(el.childNodes), marks);
|
|
472
|
+
blocks.push(makeBlock("paragraph", delta, attrs));
|
|
473
|
+
break;
|
|
474
|
+
}
|
|
475
|
+
loose.push(node);
|
|
476
|
+
}
|
|
477
|
+
}
|
|
478
|
+
}
|
|
479
|
+
flushLoose();
|
|
480
|
+
}
|
|
481
|
+
function handleList(listEl, kind, level, marks, blocks) {
|
|
482
|
+
for (const child of Array.from(listEl.children)) {
|
|
483
|
+
const tag = child.tagName.toLowerCase();
|
|
484
|
+
if (tag === "ul" || tag === "ol") {
|
|
485
|
+
const siblingKind = tag === "ol" ? "ordered" : "bullet";
|
|
486
|
+
handleList(child, siblingKind, level + 1, marks, blocks);
|
|
487
|
+
continue;
|
|
488
|
+
}
|
|
489
|
+
if (tag !== "li") continue;
|
|
490
|
+
const nestedLists = topLevelListDescendants(child);
|
|
491
|
+
const clone = child.cloneNode(true);
|
|
492
|
+
for (const l of topLevelListDescendants(clone)) l.remove();
|
|
493
|
+
const delta = inlineDeltaFromNodes(Array.from(clone.childNodes), marks);
|
|
494
|
+
const attrs = { listKind: kind, listLevel: clampLevel(level) };
|
|
495
|
+
const align = readAlign(child);
|
|
496
|
+
if (align) attrs.align = align;
|
|
497
|
+
blocks.push(makeBlock("listItem", delta, attrs));
|
|
498
|
+
for (const nested of nestedLists) {
|
|
499
|
+
const nestedKind = nested.tagName.toLowerCase() === "ol" ? "ordered" : "bullet";
|
|
500
|
+
handleList(nested, nestedKind, level + 1, marks, blocks);
|
|
501
|
+
}
|
|
502
|
+
}
|
|
503
|
+
}
|
|
504
|
+
function topLevelListDescendants(root) {
|
|
505
|
+
const all = Array.from(root.querySelectorAll("ul, ol"));
|
|
506
|
+
return all.filter((el) => {
|
|
507
|
+
let p = el.parentElement;
|
|
508
|
+
while (p && p !== root) {
|
|
509
|
+
const t = p.tagName.toLowerCase();
|
|
510
|
+
if (t === "ul" || t === "ol" || t === "table" || t === "td" || t === "th") return false;
|
|
511
|
+
p = p.parentElement;
|
|
512
|
+
}
|
|
513
|
+
return true;
|
|
514
|
+
});
|
|
515
|
+
}
|
|
516
|
+
function handleBlockquote(el, marks, blocks) {
|
|
517
|
+
if (!hasBlockDescendant(el)) {
|
|
518
|
+
const attrs = {};
|
|
519
|
+
const align = readAlign(el);
|
|
520
|
+
if (align) attrs.align = align;
|
|
521
|
+
const delta = inlineDeltaFromNodes(Array.from(el.childNodes), marks);
|
|
522
|
+
blocks.push(makeBlock("blockquote", delta, attrs));
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const nested = [];
|
|
526
|
+
walkBlockLevel(el, marks, nested);
|
|
527
|
+
for (const b of nested) {
|
|
528
|
+
blocks.push(b.type === "paragraph" ? { ...b, type: "blockquote" } : b);
|
|
529
|
+
}
|
|
530
|
+
}
|
|
531
|
+
function handleTable(table, marks, blocks) {
|
|
532
|
+
for (const row of directRows(table)) {
|
|
533
|
+
const cells = Array.from(row.children).filter(
|
|
534
|
+
(c) => ["td", "th"].includes(c.tagName.toLowerCase())
|
|
535
|
+
);
|
|
536
|
+
for (const cell of cells) {
|
|
537
|
+
const cellBlocks = [];
|
|
538
|
+
walkBlockLevel(cell, marks, cellBlocks);
|
|
539
|
+
for (const b of cellBlocks) {
|
|
540
|
+
const attrs = {};
|
|
541
|
+
if (b.attrs.align) attrs.align = b.attrs.align;
|
|
542
|
+
blocks.push(makeBlock("paragraph", b.delta, attrs));
|
|
543
|
+
}
|
|
544
|
+
}
|
|
545
|
+
}
|
|
546
|
+
}
|
|
547
|
+
function directRows(table) {
|
|
548
|
+
const rows = [];
|
|
549
|
+
for (const child of Array.from(table.children)) {
|
|
550
|
+
const tag = child.tagName.toLowerCase();
|
|
551
|
+
if (tag === "tr") {
|
|
552
|
+
rows.push(child);
|
|
553
|
+
} else if (tag === "tbody" || tag === "thead" || tag === "tfoot") {
|
|
554
|
+
for (const r of Array.from(child.children)) {
|
|
555
|
+
if (r.tagName.toLowerCase() === "tr") rows.push(r);
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
}
|
|
559
|
+
return rows;
|
|
560
|
+
}
|
|
561
|
+
function parseWordListLevel(styleAttr) {
|
|
562
|
+
const m = /mso-list\s*:\s*l\d+\s+level(\d+)/i.exec(styleAttr);
|
|
563
|
+
if (!m) return null;
|
|
564
|
+
const n = Number.parseInt(m[1], 10);
|
|
565
|
+
const fromLevelN = Number.isFinite(n) ? Math.max(0, n - 1) : 0;
|
|
566
|
+
const fromMargin = wordListLevelFromMarginLeft(marginLeftPt(styleAttr));
|
|
567
|
+
return clampLevel(Math.max(fromLevelN, fromMargin));
|
|
568
|
+
}
|
|
569
|
+
function marginLeftPt(styleAttr) {
|
|
570
|
+
const raw = parseStyle(styleAttr)["margin-left"];
|
|
571
|
+
if (!raw) return 0;
|
|
572
|
+
const m = /^(-?[\d.]+)\s*(pt|cm|in|px)$/i.exec(raw.trim());
|
|
573
|
+
if (!m) return 0;
|
|
574
|
+
const val = Number.parseFloat(m[1]);
|
|
575
|
+
if (!Number.isFinite(val) || val < 0) return 0;
|
|
576
|
+
switch (m[2].toLowerCase()) {
|
|
577
|
+
case "pt":
|
|
578
|
+
return val;
|
|
579
|
+
case "cm":
|
|
580
|
+
return val * 28.3465;
|
|
581
|
+
case "in":
|
|
582
|
+
return val * 72;
|
|
583
|
+
case "px":
|
|
584
|
+
return val * 0.75;
|
|
585
|
+
default:
|
|
586
|
+
return 0;
|
|
587
|
+
}
|
|
588
|
+
}
|
|
589
|
+
function wordListLevelFromMarginLeft(pt) {
|
|
590
|
+
if (pt <= 0) return 0;
|
|
591
|
+
return Math.max(0, Math.round(pt / 36) - 1);
|
|
592
|
+
}
|
|
593
|
+
function findWordListMarker(el) {
|
|
594
|
+
const all = el.querySelectorAll("*");
|
|
595
|
+
for (const e of Array.from(all)) {
|
|
596
|
+
const style = (e.getAttribute("style") ?? "").toLowerCase().replace(/\s+/g, "");
|
|
597
|
+
if (style.includes("mso-list:ignore")) return e;
|
|
598
|
+
}
|
|
599
|
+
return null;
|
|
600
|
+
}
|
|
601
|
+
function detectWordListKind(markerEl) {
|
|
602
|
+
const all = [markerEl, ...Array.from(markerEl.querySelectorAll("*"))];
|
|
603
|
+
for (const e of all) {
|
|
604
|
+
const style = (e.getAttribute("style") ?? "").toLowerCase();
|
|
605
|
+
if (style.includes("symbol") || style.includes("wingdings")) return "bullet";
|
|
606
|
+
}
|
|
607
|
+
const text = (markerEl.textContent ?? "").replace(/ /g, " ").trim();
|
|
608
|
+
if (/^[0-9]+[.)]/.test(text)) return "ordered";
|
|
609
|
+
if (/^[a-zA-Z][.)]/.test(text)) return "ordered";
|
|
610
|
+
return "bullet";
|
|
611
|
+
}
|
|
612
|
+
function inlineDeltaFromNodes(nodes, marks) {
|
|
613
|
+
const ops = [];
|
|
614
|
+
for (const n of nodes) walkInlineNode(n, marks, ops);
|
|
615
|
+
return trimEdgeWhitespace(collapseRepeatedSpaces(mergeAdjacentOps(ops)));
|
|
616
|
+
}
|
|
617
|
+
function collapseRepeatedSpaces(ops) {
|
|
618
|
+
return ops.map(
|
|
619
|
+
(op) => typeof op.insert === "string" ? { ...op, insert: op.insert.replace(/ {2,}/g, " ") } : op
|
|
620
|
+
);
|
|
621
|
+
}
|
|
622
|
+
function walkInlineNode(node, marks, ops) {
|
|
623
|
+
if (node.nodeType === Node.COMMENT_NODE) return;
|
|
624
|
+
if (node.nodeType === Node.TEXT_NODE) {
|
|
625
|
+
const text = normalizeWhitespace(node.textContent ?? "");
|
|
626
|
+
if (text.length > 0) pushOp(ops, text, marks);
|
|
627
|
+
return;
|
|
628
|
+
}
|
|
629
|
+
if (node.nodeType !== Node.ELEMENT_NODE) return;
|
|
630
|
+
const el = node;
|
|
631
|
+
const tag = el.tagName.toLowerCase();
|
|
632
|
+
if (IGNORED_TAGS.has(tag) || isNamespacedTag(tag)) return;
|
|
633
|
+
if (tag === "img") {
|
|
634
|
+
const op = imageEmbedFromElement(el);
|
|
635
|
+
if (op) ops.push(op);
|
|
636
|
+
return;
|
|
637
|
+
}
|
|
638
|
+
if (tag === "br") {
|
|
639
|
+
ops.push({ insert: " " });
|
|
640
|
+
return;
|
|
641
|
+
}
|
|
642
|
+
const styleAttr = (el.getAttribute("style") ?? "").toLowerCase().replace(/\s+/g, "");
|
|
643
|
+
if (styleAttr.includes("mso-list:ignore")) return;
|
|
644
|
+
let nextMarks = applyTagMarks(tag, marks);
|
|
645
|
+
nextMarks = applyStyleMarks(el.getAttribute("style"), nextMarks);
|
|
646
|
+
if (tag === "a") {
|
|
647
|
+
const href = el.getAttribute("href");
|
|
648
|
+
if (href) nextMarks = { ...nextMarks, link: { href } };
|
|
649
|
+
}
|
|
650
|
+
for (const child of Array.from(el.childNodes)) walkInlineNode(child, nextMarks, ops);
|
|
651
|
+
}
|
|
652
|
+
var MIN_EMBED_BYTES = 1024;
|
|
653
|
+
function decodedDataUrlByteLength(dataUrl) {
|
|
654
|
+
const commaIndex = dataUrl.indexOf(",");
|
|
655
|
+
if (commaIndex === -1) return 0;
|
|
656
|
+
const meta = dataUrl.slice(0, commaIndex);
|
|
657
|
+
const payload = dataUrl.slice(commaIndex + 1);
|
|
658
|
+
if (/;base64$/i.test(meta)) {
|
|
659
|
+
const padding = payload.endsWith("==") ? 2 : payload.endsWith("=") ? 1 : 0;
|
|
660
|
+
return Math.max(0, Math.floor(payload.length * 3 / 4) - padding);
|
|
661
|
+
}
|
|
662
|
+
try {
|
|
663
|
+
return decodeURIComponent(payload).length;
|
|
664
|
+
} catch {
|
|
665
|
+
return payload.length;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
function imageEmbedFromElement(el) {
|
|
669
|
+
const src = el.getAttribute("src");
|
|
670
|
+
if (!src || !src.startsWith("data:image/")) return null;
|
|
671
|
+
if (decodedDataUrlByteLength(src) < MIN_EMBED_BYTES) return null;
|
|
672
|
+
const style = parseStyle(el.getAttribute("style"));
|
|
673
|
+
const width = readImageDimension(el, "width", style);
|
|
674
|
+
const height = readImageDimension(el, "height", style);
|
|
675
|
+
if (width == null || height == null) return null;
|
|
676
|
+
let w = width;
|
|
677
|
+
let h = height;
|
|
678
|
+
if (w > MAX_INSERT_WIDTH) {
|
|
679
|
+
h = h * MAX_INSERT_WIDTH / w;
|
|
680
|
+
w = MAX_INSERT_WIDTH;
|
|
681
|
+
}
|
|
682
|
+
return { insert: { type: "image", src, width: Math.round(w), height: Math.round(h) } };
|
|
683
|
+
}
|
|
684
|
+
function readImageDimension(el, attr, style) {
|
|
685
|
+
const raw = el.getAttribute(attr);
|
|
686
|
+
if (raw && /^\d+(\.\d+)?$/.test(raw.trim())) {
|
|
687
|
+
const n = Number.parseFloat(raw);
|
|
688
|
+
if (Number.isFinite(n) && n > 0) return n;
|
|
689
|
+
}
|
|
690
|
+
const fromStyle = style[attr];
|
|
691
|
+
if (fromStyle) {
|
|
692
|
+
const n = cssLengthToPx(fromStyle);
|
|
693
|
+
if (n != null) return n;
|
|
694
|
+
}
|
|
695
|
+
return void 0;
|
|
696
|
+
}
|
|
697
|
+
function cssLengthToPx(raw) {
|
|
698
|
+
const v = raw.trim();
|
|
699
|
+
const m = /^(-?[\d.]+)\s*(px|pt|in|cm|mm)?$/i.exec(v);
|
|
700
|
+
if (!m) return void 0;
|
|
701
|
+
const num = Number.parseFloat(m[1]);
|
|
702
|
+
if (!Number.isFinite(num) || num <= 0) return void 0;
|
|
703
|
+
const unit = (m[2] ?? "px").toLowerCase();
|
|
704
|
+
switch (unit) {
|
|
705
|
+
case "px":
|
|
706
|
+
return num;
|
|
707
|
+
case "pt":
|
|
708
|
+
return num * (96 / 72);
|
|
709
|
+
case "in":
|
|
710
|
+
return num * 96;
|
|
711
|
+
case "cm":
|
|
712
|
+
return num * (96 / 2.54);
|
|
713
|
+
case "mm":
|
|
714
|
+
return num * (96 / 25.4);
|
|
715
|
+
default:
|
|
716
|
+
return void 0;
|
|
717
|
+
}
|
|
718
|
+
}
|
|
719
|
+
function applyTagMarks(tag, marks) {
|
|
720
|
+
switch (tag) {
|
|
721
|
+
case "b":
|
|
722
|
+
case "strong":
|
|
723
|
+
return { ...marks, bold: true };
|
|
724
|
+
case "i":
|
|
725
|
+
case "em":
|
|
726
|
+
return { ...marks, italic: true };
|
|
727
|
+
case "u":
|
|
728
|
+
return { ...marks, underline: true };
|
|
729
|
+
case "s":
|
|
730
|
+
case "strike":
|
|
731
|
+
case "del":
|
|
732
|
+
return { ...marks, strike: true };
|
|
733
|
+
default:
|
|
734
|
+
return marks;
|
|
735
|
+
}
|
|
736
|
+
}
|
|
737
|
+
function parseStyle(styleAttr) {
|
|
738
|
+
const out = {};
|
|
739
|
+
if (!styleAttr) return out;
|
|
740
|
+
for (const decl of styleAttr.split(";")) {
|
|
741
|
+
const idx = decl.indexOf(":");
|
|
742
|
+
if (idx === -1) continue;
|
|
743
|
+
const prop = decl.slice(0, idx).trim().toLowerCase();
|
|
744
|
+
const val = decl.slice(idx + 1).trim();
|
|
745
|
+
if (prop) out[prop] = val;
|
|
746
|
+
}
|
|
747
|
+
return out;
|
|
748
|
+
}
|
|
749
|
+
function applyStyleMarks(styleAttr, marks) {
|
|
750
|
+
const decls = parseStyle(styleAttr);
|
|
751
|
+
if (Object.keys(decls).length === 0) return marks;
|
|
752
|
+
const next = { ...marks };
|
|
753
|
+
if ("font-weight" in decls) {
|
|
754
|
+
const v = decls["font-weight"].toLowerCase();
|
|
755
|
+
if (v === "bold" || /^[5-9]00$/.test(v)) next.bold = true;
|
|
756
|
+
else if (v === "normal" || /^[1-4]00$/.test(v)) delete next.bold;
|
|
757
|
+
}
|
|
758
|
+
if ("font-style" in decls) {
|
|
759
|
+
const v = decls["font-style"].toLowerCase();
|
|
760
|
+
if (v === "italic" || v === "oblique") next.italic = true;
|
|
761
|
+
else if (v === "normal") delete next.italic;
|
|
762
|
+
}
|
|
763
|
+
const deco = decls["text-decoration-line"] ?? decls["text-decoration"];
|
|
764
|
+
if (deco !== void 0) {
|
|
765
|
+
const v = deco.toLowerCase();
|
|
766
|
+
delete next.underline;
|
|
767
|
+
delete next.strike;
|
|
768
|
+
if (v.includes("underline")) next.underline = true;
|
|
769
|
+
if (v.includes("line-through")) next.strike = true;
|
|
770
|
+
}
|
|
771
|
+
if ("color" in decls) {
|
|
772
|
+
const css = normalizeCssColor(decls.color);
|
|
773
|
+
if (css && !isBlack(css)) next.color = css;
|
|
774
|
+
else delete next.color;
|
|
775
|
+
}
|
|
776
|
+
const bg = decls["background-color"] ?? decls.background;
|
|
777
|
+
if (bg !== void 0) {
|
|
778
|
+
const css = normalizeCssColor(bg);
|
|
779
|
+
if (css && css !== "#ffffff") next.highlight = css;
|
|
780
|
+
else delete next.highlight;
|
|
781
|
+
}
|
|
782
|
+
if ("font-size" in decls) {
|
|
783
|
+
const pt = parseFontSizePt(decls["font-size"]);
|
|
784
|
+
if (pt != null) next.fontSize = formatFontSizePt(pt);
|
|
785
|
+
}
|
|
786
|
+
return next;
|
|
787
|
+
}
|
|
788
|
+
function parseFontSizePt(raw) {
|
|
789
|
+
const v = raw.trim().toLowerCase();
|
|
790
|
+
const m = /^(-?[\d.]+)\s*(pt|px|in)$/.exec(v);
|
|
791
|
+
if (!m) return void 0;
|
|
792
|
+
const num = Number.parseFloat(m[1]);
|
|
793
|
+
if (!Number.isFinite(num)) return void 0;
|
|
794
|
+
let pt;
|
|
795
|
+
switch (m[2]) {
|
|
796
|
+
case "pt":
|
|
797
|
+
pt = num;
|
|
798
|
+
break;
|
|
799
|
+
case "px":
|
|
800
|
+
pt = num * 0.75;
|
|
801
|
+
break;
|
|
802
|
+
case "in":
|
|
803
|
+
pt = num * 72;
|
|
804
|
+
break;
|
|
805
|
+
default:
|
|
806
|
+
return void 0;
|
|
807
|
+
}
|
|
808
|
+
const rounded = Math.round(pt * 10) / 10;
|
|
809
|
+
if (rounded <= 0 || rounded > 200) return void 0;
|
|
810
|
+
return rounded;
|
|
811
|
+
}
|
|
812
|
+
function formatFontSizePt(pt) {
|
|
813
|
+
const rounded = Math.round(pt * 10) / 10;
|
|
814
|
+
const str = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
|
|
815
|
+
return `${str}pt`;
|
|
816
|
+
}
|
|
817
|
+
var NAMED_COLORS = {
|
|
818
|
+
black: "#000000",
|
|
819
|
+
white: "#ffffff",
|
|
820
|
+
red: "#ff0000",
|
|
821
|
+
green: "#008000",
|
|
822
|
+
blue: "#0000ff",
|
|
823
|
+
yellow: "#ffff00",
|
|
824
|
+
cyan: "#00ffff",
|
|
825
|
+
magenta: "#ff00ff",
|
|
826
|
+
gray: "#808080",
|
|
827
|
+
grey: "#808080",
|
|
828
|
+
orange: "#ffa500",
|
|
829
|
+
purple: "#800080"
|
|
830
|
+
};
|
|
831
|
+
function normalizeCssColor(raw) {
|
|
832
|
+
if (!raw) return void 0;
|
|
833
|
+
const v = raw.trim().toLowerCase();
|
|
834
|
+
if (!v || v === "transparent" || v === "none" || v === "inherit" || v === "initial") return void 0;
|
|
835
|
+
if (v.startsWith("#")) {
|
|
836
|
+
let hex = v.slice(1);
|
|
837
|
+
if (hex.length === 3) hex = hex.split("").map((c) => c + c).join("");
|
|
838
|
+
return /^[0-9a-f]{6}$/.test(hex) ? `#${hex}` : void 0;
|
|
839
|
+
}
|
|
840
|
+
const rgbMatch = /^rgba?\(([^)]+)\)$/.exec(v);
|
|
841
|
+
if (rgbMatch) {
|
|
842
|
+
const parts = rgbMatch[1].split(",").map((s) => s.trim());
|
|
843
|
+
if (parts.length < 3) return void 0;
|
|
844
|
+
const alpha = parts[3] !== void 0 ? Number.parseFloat(parts[3]) : 1;
|
|
845
|
+
if (alpha === 0) return void 0;
|
|
846
|
+
const toHex = (n) => {
|
|
847
|
+
const num = Math.max(0, Math.min(255, Number.parseInt(n, 10) || 0));
|
|
848
|
+
return num.toString(16).padStart(2, "0");
|
|
849
|
+
};
|
|
850
|
+
return `#${toHex(parts[0])}${toHex(parts[1])}${toHex(parts[2])}`;
|
|
851
|
+
}
|
|
852
|
+
return NAMED_COLORS[v];
|
|
853
|
+
}
|
|
854
|
+
function isBlack(hex) {
|
|
855
|
+
return hex.toLowerCase() === "#000000";
|
|
856
|
+
}
|
|
857
|
+
function readAlign(el) {
|
|
858
|
+
const style = parseStyle(el.getAttribute("style"));
|
|
859
|
+
const v = style["text-align"];
|
|
860
|
+
if (!v) return void 0;
|
|
861
|
+
switch (v.toLowerCase()) {
|
|
862
|
+
case "left":
|
|
863
|
+
case "start":
|
|
864
|
+
return "left";
|
|
865
|
+
case "center":
|
|
866
|
+
return "center";
|
|
867
|
+
case "right":
|
|
868
|
+
case "end":
|
|
869
|
+
return "right";
|
|
870
|
+
case "justify":
|
|
871
|
+
return "justify";
|
|
872
|
+
default:
|
|
873
|
+
return void 0;
|
|
874
|
+
}
|
|
875
|
+
}
|
|
876
|
+
function clampLevel(n) {
|
|
877
|
+
if (!Number.isFinite(n)) return 0;
|
|
878
|
+
return Math.max(0, Math.min(MAX_LIST_LEVEL, Math.trunc(n)));
|
|
879
|
+
}
|
|
880
|
+
function normalizeListLevels(blocks) {
|
|
881
|
+
let i = 0;
|
|
882
|
+
while (i < blocks.length) {
|
|
883
|
+
if (blocks[i].type !== "listItem") {
|
|
884
|
+
i++;
|
|
885
|
+
continue;
|
|
886
|
+
}
|
|
887
|
+
let j = i + 1;
|
|
888
|
+
while (j < blocks.length && blocks[j].type === "listItem") j++;
|
|
889
|
+
if (j - i > 1) {
|
|
890
|
+
let min = Number.POSITIVE_INFINITY;
|
|
891
|
+
for (let k = i; k < j; k++) {
|
|
892
|
+
const lvl = blocks[k].attrs.listLevel ?? 0;
|
|
893
|
+
if (lvl < min) min = lvl;
|
|
894
|
+
}
|
|
895
|
+
if (min > 0) {
|
|
896
|
+
for (let k = i; k < j; k++) {
|
|
897
|
+
const lvl = blocks[k].attrs.listLevel ?? 0;
|
|
898
|
+
blocks[k].attrs.listLevel = clampLevel(lvl - min);
|
|
899
|
+
}
|
|
900
|
+
}
|
|
901
|
+
}
|
|
902
|
+
i = j;
|
|
903
|
+
}
|
|
904
|
+
}
|
|
905
|
+
function pushOp(ops, text, marks) {
|
|
906
|
+
const hasMarks = Object.keys(marks).length > 0;
|
|
907
|
+
ops.push(hasMarks ? { insert: text, attributes: { ...marks } } : { insert: text });
|
|
908
|
+
}
|
|
909
|
+
function normalizeWhitespace(raw) {
|
|
910
|
+
return raw.replace(/[ \t\r\n]+/g, " ");
|
|
911
|
+
}
|
|
912
|
+
function trimEdgeWhitespace(ops) {
|
|
913
|
+
let out = ops;
|
|
914
|
+
if (out.length > 0 && typeof out[0].insert === "string") {
|
|
915
|
+
const trimmed = out[0].insert.replace(/^ +/, "");
|
|
916
|
+
if (trimmed.length === 0) out = out.slice(1);
|
|
917
|
+
else if (trimmed !== out[0].insert) out = [{ ...out[0], insert: trimmed }, ...out.slice(1)];
|
|
918
|
+
}
|
|
919
|
+
if (out.length > 0 && typeof out[out.length - 1].insert === "string") {
|
|
920
|
+
const last = out[out.length - 1];
|
|
921
|
+
const trimmed = last.insert.replace(/ +$/, "");
|
|
922
|
+
if (trimmed.length === 0) out = out.slice(0, -1);
|
|
923
|
+
else if (trimmed !== last.insert) out = [...out.slice(0, -1), { ...last, insert: trimmed }];
|
|
924
|
+
}
|
|
925
|
+
return out;
|
|
926
|
+
}
|
|
927
|
+
function mergeAdjacentOps(ops) {
|
|
928
|
+
const out = [];
|
|
929
|
+
for (const op of ops) {
|
|
930
|
+
const prev = out[out.length - 1];
|
|
931
|
+
if (prev && typeof prev.insert === "string" && typeof op.insert === "string" && sameMarks(prev.attributes, op.attributes)) {
|
|
932
|
+
prev.insert = prev.insert + op.insert;
|
|
933
|
+
continue;
|
|
934
|
+
}
|
|
935
|
+
out.push({ ...op });
|
|
936
|
+
}
|
|
937
|
+
return out;
|
|
938
|
+
}
|
|
939
|
+
function sameMarks(a, b) {
|
|
940
|
+
const ak = a ? Object.keys(a).sort() : [];
|
|
941
|
+
const bk = b ? Object.keys(b).sort() : [];
|
|
942
|
+
if (ak.length !== bk.length) return false;
|
|
943
|
+
for (let i = 0; i < ak.length; i++) {
|
|
944
|
+
if (ak[i] !== bk[i]) return false;
|
|
945
|
+
const av = a[ak[i]];
|
|
946
|
+
const bv = b[bk[i]];
|
|
947
|
+
if (typeof av === "object" && av !== null && typeof bv === "object" && bv !== null) {
|
|
948
|
+
if (JSON.stringify(av) !== JSON.stringify(bv)) return false;
|
|
949
|
+
} else if (av !== bv) {
|
|
950
|
+
return false;
|
|
951
|
+
}
|
|
952
|
+
}
|
|
953
|
+
return true;
|
|
954
|
+
}
|
|
955
|
+
function deltaToText(delta) {
|
|
956
|
+
let s = "";
|
|
957
|
+
for (const op of delta) if (typeof op.insert === "string") s += op.insert;
|
|
958
|
+
return s;
|
|
959
|
+
}
|
|
960
|
+
function makeBlock(type, delta, attrs) {
|
|
961
|
+
return { type, text: deltaToText(delta), delta, attrs };
|
|
962
|
+
}
|
|
963
|
+
|
|
964
|
+
// src/pastePlan.ts
|
|
965
|
+
function planPaste(cd) {
|
|
966
|
+
const raw = cd.getData(SOFER_MIME);
|
|
967
|
+
if (raw) {
|
|
968
|
+
try {
|
|
969
|
+
const slice = JSON.parse(raw);
|
|
970
|
+
if (slice && Array.isArray(slice.blocks)) return { kind: "sofer", slice };
|
|
971
|
+
} catch {
|
|
972
|
+
}
|
|
973
|
+
}
|
|
974
|
+
const html = cd.getData("text/html");
|
|
975
|
+
if (html) {
|
|
976
|
+
const slice = htmlToSlice(html);
|
|
977
|
+
if (slice) return { kind: "html", slice };
|
|
978
|
+
}
|
|
979
|
+
const files = Array.from(cd.files ?? []).filter((f) => f.type.startsWith("image/"));
|
|
980
|
+
if (files.length > 0) return { kind: "images", files };
|
|
981
|
+
const text = cd.getData("text/plain");
|
|
982
|
+
if (text.length > 0) return { kind: "text", text };
|
|
983
|
+
return { kind: "none" };
|
|
984
|
+
}
|
|
985
|
+
|
|
986
|
+
// src/resolvePastedImages.ts
|
|
987
|
+
import { isImageEmbed as isImageEmbed2 } from "@sofereditor/core";
|
|
988
|
+
function sliceHasDataImageEmbeds(slice) {
|
|
989
|
+
return slice.blocks.some(
|
|
990
|
+
(b) => b.delta.some((op) => isImageEmbed2(op.insert) && op.insert.src.startsWith("data:image/"))
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
async function resolvePastedImageUploads(slice, upload) {
|
|
994
|
+
if (!upload) return slice;
|
|
995
|
+
const blocks = await Promise.all(
|
|
996
|
+
slice.blocks.map(async (block) => {
|
|
997
|
+
let changed = false;
|
|
998
|
+
const delta = await Promise.all(
|
|
999
|
+
block.delta.map(async (op) => {
|
|
1000
|
+
if (!isImageEmbed2(op.insert) || !op.insert.src.startsWith("data:image/")) return op;
|
|
1001
|
+
try {
|
|
1002
|
+
const file = await dataUrlToFile(op.insert.src);
|
|
1003
|
+
const newSrc = await upload(file);
|
|
1004
|
+
changed = true;
|
|
1005
|
+
return { ...op, insert: { ...op.insert, src: newSrc } };
|
|
1006
|
+
} catch (err) {
|
|
1007
|
+
console.error(
|
|
1008
|
+
"[sofereditor] falha ao subir imagem colada para o storage \u2014 mantendo a imagem embutida (data:) nesta colagem",
|
|
1009
|
+
err
|
|
1010
|
+
);
|
|
1011
|
+
return op;
|
|
1012
|
+
}
|
|
1013
|
+
})
|
|
1014
|
+
);
|
|
1015
|
+
return changed ? { ...block, delta } : block;
|
|
1016
|
+
})
|
|
1017
|
+
);
|
|
1018
|
+
return { ...slice, blocks };
|
|
1019
|
+
}
|
|
1020
|
+
async function dataUrlToFile(dataUrl) {
|
|
1021
|
+
const blob = await fetch(dataUrl).then((r) => r.blob());
|
|
1022
|
+
const mime = blob.type;
|
|
1023
|
+
return new File([blob], `imagem-colada-${Date.now()}.${extensionForMime(mime)}`, { type: mime });
|
|
1024
|
+
}
|
|
1025
|
+
var MIME_EXTENSIONS = {
|
|
1026
|
+
"image/png": "png",
|
|
1027
|
+
"image/jpeg": "jpg",
|
|
1028
|
+
"image/jpg": "jpg",
|
|
1029
|
+
"image/gif": "gif",
|
|
1030
|
+
"image/webp": "webp",
|
|
1031
|
+
"image/bmp": "bmp",
|
|
1032
|
+
"image/svg+xml": "svg"
|
|
1033
|
+
};
|
|
1034
|
+
function extensionForMime(mime) {
|
|
1035
|
+
return MIME_EXTENSIONS[mime.toLowerCase()] ?? "png";
|
|
1036
|
+
}
|
|
1037
|
+
|
|
296
1038
|
// src/BehindImageSelectAffordance.tsx
|
|
297
1039
|
import {
|
|
298
1040
|
useCallback,
|
|
@@ -1718,7 +2460,7 @@ import {
|
|
|
1718
2460
|
import { Fragment as Fragment3, memo } from "react";
|
|
1719
2461
|
import {
|
|
1720
2462
|
BLANK_STYLE,
|
|
1721
|
-
isImageEmbed,
|
|
2463
|
+
isImageEmbed as isImageEmbed3,
|
|
1722
2464
|
splitUnderscoreRuns
|
|
1723
2465
|
} from "@sofereditor/core";
|
|
1724
2466
|
import { jsx as jsx9, jsxs as jsxs9 } from "react/jsx-runtime";
|
|
@@ -1736,7 +2478,7 @@ function renderInline(delta, keyPrefix) {
|
|
|
1736
2478
|
const key = `${keyPrefix}-${offset}`;
|
|
1737
2479
|
body.push(/* @__PURE__ */ jsx9(Fragment3, { children: wrap(text, op.attributes) }, key));
|
|
1738
2480
|
offset += text.length;
|
|
1739
|
-
} else if (
|
|
2481
|
+
} else if (isImageEmbed3(op.insert)) {
|
|
1740
2482
|
const key = `${keyPrefix}-${offset}`;
|
|
1741
2483
|
const layout = op.insert.layout ?? "inline";
|
|
1742
2484
|
const commentMarkId = op.attributes?.comment?.markId;
|
|
@@ -2107,7 +2849,7 @@ function NodeView({ block, index, children, fragment, tableFragment }) {
|
|
|
2107
2849
|
const common = commonBlockProps(block.attrs, index, block.type, fragment);
|
|
2108
2850
|
switch (block.type) {
|
|
2109
2851
|
case "heading": {
|
|
2110
|
-
const level =
|
|
2852
|
+
const level = clampLevel2(block.attrs.level);
|
|
2111
2853
|
const Tag = `h${level}`;
|
|
2112
2854
|
return /* @__PURE__ */ jsx11(
|
|
2113
2855
|
Tag,
|
|
@@ -2194,7 +2936,7 @@ function cellStyle(attrs, border) {
|
|
|
2194
2936
|
if (attrs?.bgColor) style.backgroundColor = attrs.bgColor;
|
|
2195
2937
|
return Object.keys(style).length > 0 ? style : void 0;
|
|
2196
2938
|
}
|
|
2197
|
-
function
|
|
2939
|
+
function clampLevel2(l) {
|
|
2198
2940
|
if (l === 2 || l === 3 || l === 4 || l === 5 || l === 6) return l;
|
|
2199
2941
|
return 1;
|
|
2200
2942
|
}
|
|
@@ -2851,7 +3593,7 @@ import {
|
|
|
2851
3593
|
insertTable as cmdInsertTable,
|
|
2852
3594
|
insertTableColumn as cmdInsertColumn,
|
|
2853
3595
|
insertTableRow as cmdInsertRow,
|
|
2854
|
-
isImageEmbed as
|
|
3596
|
+
isImageEmbed as isImageEmbed4,
|
|
2855
3597
|
isMarkUniformInRange,
|
|
2856
3598
|
moveToNextCell as cmdMoveNextCell,
|
|
2857
3599
|
moveToPrevCell as cmdMovePrevCell,
|
|
@@ -2866,7 +3608,6 @@ import {
|
|
|
2866
3608
|
toggleList as cmdToggleList,
|
|
2867
3609
|
toggleMark as cmdToggleMark
|
|
2868
3610
|
} from "@sofereditor/core";
|
|
2869
|
-
var MAX_INSERT_WIDTH = 600;
|
|
2870
3611
|
function useEditor(opts = {}) {
|
|
2871
3612
|
const doc = useMemo3(() => opts.document ?? new EditorDocument(), [opts.document]);
|
|
2872
3613
|
const history = useMemo3(() => new EditorHistory(doc), [doc]);
|
|
@@ -3253,7 +3994,7 @@ function useEditor(opts = {}) {
|
|
|
3253
3994
|
cursor += op.insert.length;
|
|
3254
3995
|
continue;
|
|
3255
3996
|
}
|
|
3256
|
-
if (cursor === start &&
|
|
3997
|
+
if (cursor === start && isImageEmbed4(op.insert)) {
|
|
3257
3998
|
return {
|
|
3258
3999
|
blockIndex: sel.anchor.blockIndex,
|
|
3259
4000
|
cellIndex: sel.anchor.cellIndex,
|
|
@@ -3386,7 +4127,8 @@ function useEditor(opts = {}) {
|
|
|
3386
4127
|
resolvePageConfigRequest,
|
|
3387
4128
|
imageCaptionRequest,
|
|
3388
4129
|
requestImageCaption,
|
|
3389
|
-
resolveImageCaptionRequest
|
|
4130
|
+
resolveImageCaptionRequest,
|
|
4131
|
+
uploadImage: opts.uploadImage
|
|
3390
4132
|
};
|
|
3391
4133
|
}
|
|
3392
4134
|
function readAsDataURL(file) {
|
|
@@ -3433,6 +4175,7 @@ function Editor({
|
|
|
3433
4175
|
const { doc, history, snapshot, getSelection, setSelection } = editor;
|
|
3434
4176
|
const rootRef = useRef11(null);
|
|
3435
4177
|
const isComposingRef = useRef11(false);
|
|
4178
|
+
const pasteInFlightRef = useRef11(false);
|
|
3436
4179
|
const [hoveredBehind, setHoveredBehind] = useState10(null);
|
|
3437
4180
|
const hoveredBehindRef = useRef11(null);
|
|
3438
4181
|
const [hoveredLink, setHoveredLink] = useState10(null);
|
|
@@ -3855,7 +4598,7 @@ function Editor({
|
|
|
3855
4598
|
const ctx = ctxRef.current;
|
|
3856
4599
|
const slice = serializeSelection(ctx.doc, ctx.getSelection());
|
|
3857
4600
|
if (!slice || !e.clipboardData) return false;
|
|
3858
|
-
e.clipboardData.setData(
|
|
4601
|
+
e.clipboardData.setData(SOFER_MIME2, JSON.stringify(slice));
|
|
3859
4602
|
e.clipboardData.setData("text/plain", sliceToText(slice));
|
|
3860
4603
|
return true;
|
|
3861
4604
|
};
|
|
@@ -3870,34 +4613,67 @@ function Editor({
|
|
|
3870
4613
|
const ctx = ctxRef.current;
|
|
3871
4614
|
const cd = e.clipboardData;
|
|
3872
4615
|
if (!cd) return;
|
|
3873
|
-
const
|
|
3874
|
-
if (
|
|
3875
|
-
|
|
3876
|
-
|
|
3877
|
-
|
|
3878
|
-
|
|
3879
|
-
|
|
4616
|
+
const plan = planPaste(cd);
|
|
4617
|
+
if (plan.kind === "none") return;
|
|
4618
|
+
e.preventDefault();
|
|
4619
|
+
switch (plan.kind) {
|
|
4620
|
+
case "sofer":
|
|
4621
|
+
insertSlice(ctx, plan.slice);
|
|
4622
|
+
return;
|
|
4623
|
+
case "html": {
|
|
4624
|
+
if (!sliceHasDataImageEmbeds(plan.slice)) {
|
|
4625
|
+
insertSlice(ctx, plan.slice);
|
|
3880
4626
|
return;
|
|
3881
4627
|
}
|
|
3882
|
-
|
|
4628
|
+
if (pasteInFlightRef.current) {
|
|
4629
|
+
console.warn(
|
|
4630
|
+
"[sofereditor] colagem com imagem ainda subindo \u2014 descartando esta colagem concorrente para n\xE3o corromper a posi\xE7\xE3o de inser\xE7\xE3o"
|
|
4631
|
+
);
|
|
4632
|
+
return;
|
|
4633
|
+
}
|
|
4634
|
+
pasteInFlightRef.current = true;
|
|
4635
|
+
const capturedSelection = ctx.getSelection();
|
|
4636
|
+
void (async () => {
|
|
4637
|
+
try {
|
|
4638
|
+
const resolved = await resolvePastedImageUploads(plan.slice, editorRef.current.uploadImage);
|
|
4639
|
+
const liveSelection = ctxRef.current.getSelection();
|
|
4640
|
+
const userMovedAway = !selectionsEqual(liveSelection, capturedSelection);
|
|
4641
|
+
const { blockIndex, cellIndex } = capturedSelection.focus;
|
|
4642
|
+
const targetText = ctxRef.current.doc.textAt(blockIndex, cellIndex);
|
|
4643
|
+
let insertAt = capturedSelection;
|
|
4644
|
+
if (!targetText) {
|
|
4645
|
+
console.error(
|
|
4646
|
+
"[sofereditor] bloco de destino da colagem sumiu durante o upload da imagem \u2014 inserindo no fim do documento em vez de descartar a colagem"
|
|
4647
|
+
);
|
|
4648
|
+
const lastIndex = Math.max(0, ctxRef.current.doc.blockCount() - 1);
|
|
4649
|
+
const lastText = ctxRef.current.doc.getBlockText(lastIndex);
|
|
4650
|
+
insertAt = collapsedSelection2({ blockIndex: lastIndex, offset: lastText ? lastText.length : 0 });
|
|
4651
|
+
}
|
|
4652
|
+
ctxRef.current.setSelection(insertAt);
|
|
4653
|
+
insertSlice(ctxRef.current, resolved);
|
|
4654
|
+
if (userMovedAway) {
|
|
4655
|
+
ctxRef.current.setSelection(liveSelection);
|
|
4656
|
+
}
|
|
4657
|
+
} finally {
|
|
4658
|
+
pasteInFlightRef.current = false;
|
|
4659
|
+
}
|
|
4660
|
+
})();
|
|
4661
|
+
return;
|
|
4662
|
+
}
|
|
4663
|
+
case "images":
|
|
4664
|
+
void (async () => {
|
|
4665
|
+
for (const f of plan.files) await editorRef.current.insertImageFromFile(f);
|
|
4666
|
+
})();
|
|
4667
|
+
return;
|
|
4668
|
+
case "text": {
|
|
4669
|
+
const lines = plan.text.split(/\r\n|\r|\n/);
|
|
4670
|
+
lines.forEach((line, i) => {
|
|
4671
|
+
if (i > 0) insertParagraph(ctx);
|
|
4672
|
+
if (line.length > 0) insertText(ctx, line);
|
|
4673
|
+
});
|
|
4674
|
+
return;
|
|
3883
4675
|
}
|
|
3884
4676
|
}
|
|
3885
|
-
const images = Array.from(cd.files ?? []).filter((f) => f.type.startsWith("image/"));
|
|
3886
|
-
if (images.length > 0) {
|
|
3887
|
-
e.preventDefault();
|
|
3888
|
-
void (async () => {
|
|
3889
|
-
for (const f of images) await editorRef.current.insertImageFromFile(f);
|
|
3890
|
-
})();
|
|
3891
|
-
return;
|
|
3892
|
-
}
|
|
3893
|
-
const text = cd.getData("text/plain");
|
|
3894
|
-
if (text.length === 0) return;
|
|
3895
|
-
e.preventDefault();
|
|
3896
|
-
const lines = text.split(/\r\n|\r|\n/);
|
|
3897
|
-
lines.forEach((line, i) => {
|
|
3898
|
-
if (i > 0) insertParagraph(ctx);
|
|
3899
|
-
if (line.length > 0) insertText(ctx, line);
|
|
3900
|
-
});
|
|
3901
4677
|
};
|
|
3902
4678
|
root.addEventListener("copy", onCopy);
|
|
3903
4679
|
root.addEventListener("cut", onCut);
|
|
@@ -3936,6 +4712,17 @@ function Editor({
|
|
|
3936
4712
|
}
|
|
3937
4713
|
return;
|
|
3938
4714
|
}
|
|
4715
|
+
if (!mod && (e.key === "Backspace" || e.key === "Delete")) {
|
|
4716
|
+
if (isComposingRef.current) return;
|
|
4717
|
+
const direction = e.key === "Backspace" ? "backward" : "forward";
|
|
4718
|
+
const shouldIntercept = ed.getSelectedEmbed() != null || isEmbedAdjacentToCaret(ctxRef.current, direction);
|
|
4719
|
+
if (shouldIntercept) {
|
|
4720
|
+
e.preventDefault();
|
|
4721
|
+
if (direction === "backward") deleteBackward(ctxRef.current);
|
|
4722
|
+
else deleteForward(ctxRef.current);
|
|
4723
|
+
}
|
|
4724
|
+
return;
|
|
4725
|
+
}
|
|
3939
4726
|
if (!mod) return;
|
|
3940
4727
|
const key = e.key.toLowerCase();
|
|
3941
4728
|
if (!e.shiftKey && !e.altKey && key === "z") {
|
|
@@ -4152,11 +4939,15 @@ function groupTopLevels(snapshot) {
|
|
|
4152
4939
|
const groupStart = i;
|
|
4153
4940
|
const items = [];
|
|
4154
4941
|
const kind = block.attrs.listKind === "ordered" ? "ordered" : "bullet";
|
|
4942
|
+
const leaderLevel = clampListLevel2(block.attrs.listLevel);
|
|
4155
4943
|
const groupStyle = block.attrs.listStyle;
|
|
4156
4944
|
while (i < snapshot.length) {
|
|
4157
4945
|
const b = snapshot[i];
|
|
4158
4946
|
if (b.type !== "listItem") break;
|
|
4159
|
-
|
|
4947
|
+
const bLevel = clampListLevel2(b.attrs.listLevel);
|
|
4948
|
+
if (bLevel <= leaderLevel && (b.attrs.listKind ?? "bullet") !== kind) {
|
|
4949
|
+
break;
|
|
4950
|
+
}
|
|
4160
4951
|
if (i > groupStart) {
|
|
4161
4952
|
if (typeof b.attrs.listStart === "number") break;
|
|
4162
4953
|
const itemStyle = b.attrs.listStyle;
|
|
@@ -4264,7 +5055,7 @@ function buildListTree(items) {
|
|
|
4264
5055
|
function renderListTree(tree, top, start, key) {
|
|
4265
5056
|
const className = `ed-list ed-list-${top.listKind}`;
|
|
4266
5057
|
const style = top.listStyle ? { listStyleType: top.listStyle } : void 0;
|
|
4267
|
-
const items = tree.map((node) => /* @__PURE__ */ jsx12(NodeView, { block: node.entry.block, index: node.entry.index, children: node.children.length > 0 ? renderNestedListTree(node.children,
|
|
5058
|
+
const items = tree.map((node) => /* @__PURE__ */ jsx12(NodeView, { block: node.entry.block, index: node.entry.index, children: node.children.length > 0 ? renderNestedListTree(node.children, `nested-${node.entry.index}`) : null }, node.entry.index));
|
|
4268
5059
|
if (top.listKind === "ordered") {
|
|
4269
5060
|
return /* @__PURE__ */ jsx12(
|
|
4270
5061
|
"ol",
|
|
@@ -4280,9 +5071,10 @@ function renderListTree(tree, top, start, key) {
|
|
|
4280
5071
|
}
|
|
4281
5072
|
return /* @__PURE__ */ jsx12("ul", { className, "data-list-kind": "bullet", style, children: items }, key);
|
|
4282
5073
|
}
|
|
4283
|
-
function renderNestedListTree(tree,
|
|
5074
|
+
function renderNestedListTree(tree, key) {
|
|
5075
|
+
const kind = tree[0]?.entry.block.attrs.listKind === "ordered" ? "ordered" : "bullet";
|
|
4284
5076
|
const Tag = kind === "ordered" ? "ol" : "ul";
|
|
4285
|
-
return /* @__PURE__ */ jsx12(Tag, { className: `ed-list ed-list-${kind}`, "data-list-kind": kind, children: tree.map((node) => /* @__PURE__ */ jsx12(NodeView, { block: node.entry.block, index: node.entry.index, children: node.children.length > 0 ? renderNestedListTree(node.children,
|
|
5077
|
+
return /* @__PURE__ */ jsx12(Tag, { className: `ed-list ed-list-${kind}`, "data-list-kind": kind, children: tree.map((node) => /* @__PURE__ */ jsx12(NodeView, { block: node.entry.block, index: node.entry.index, children: node.children.length > 0 ? renderNestedListTree(node.children, `nested-${node.entry.index}`) : null }, node.entry.index)) }, key);
|
|
4286
5078
|
}
|
|
4287
5079
|
function clampListLevel2(l) {
|
|
4288
5080
|
if (typeof l !== "number" || !Number.isFinite(l)) return 0;
|
|
@@ -5143,6 +5935,7 @@ export {
|
|
|
5143
5935
|
getBlockElement,
|
|
5144
5936
|
getCellElement,
|
|
5145
5937
|
getFragmentForOffset,
|
|
5938
|
+
htmlToSlice,
|
|
5146
5939
|
isTableRectSelection,
|
|
5147
5940
|
locatePoint,
|
|
5148
5941
|
readDomSelection,
|