@sofereditor/react 0.5.0 → 0.6.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.js CHANGED
@@ -17,7 +17,7 @@ import {
17
17
  insertText,
18
18
  serializeSelection,
19
19
  sliceToText,
20
- SOFER_MIME
20
+ SOFER_MIME as SOFER_MIME2
21
21
  } from "@sofereditor/core";
22
22
 
23
23
  // src/dom-bridge.ts
@@ -293,6 +293,615 @@ function applyDomSelection(root, modelSel) {
293
293
  }
294
294
  }
295
295
 
296
+ // src/pastePlan.ts
297
+ import { SOFER_MIME } from "@sofereditor/core";
298
+
299
+ // src/htmlToSlice.ts
300
+ import { MAX_LIST_LEVEL } from "@sofereditor/core";
301
+ function htmlToSlice(html) {
302
+ if (!html || html.trim().length === 0) return null;
303
+ let doc;
304
+ try {
305
+ doc = new DOMParser().parseFromString(html, "text/html");
306
+ } catch {
307
+ return null;
308
+ }
309
+ const body = doc.body;
310
+ if (!body) return null;
311
+ const blocks = [];
312
+ try {
313
+ walkBlockLevel(body, {}, blocks);
314
+ } catch {
315
+ return null;
316
+ }
317
+ if (blocks.length === 0) return null;
318
+ normalizeListLevels(blocks);
319
+ const temTexto = blocks.some((b) => b.text.trim().length > 0);
320
+ if (!temTexto) return null;
321
+ const blockLevel = blocks.length > 1 || blocks[0].type !== "paragraph";
322
+ return { blocks, openStart: false, openEnd: false, ...blockLevel ? { blockLevel: true } : {} };
323
+ }
324
+ var IGNORED_TAGS = /* @__PURE__ */ new Set(["script", "style", "meta", "link", "head", "title", "xml"]);
325
+ var BLOCK_LEVEL_TAGS = /* @__PURE__ */ new Set([
326
+ "p",
327
+ "div",
328
+ "section",
329
+ "article",
330
+ "header",
331
+ "footer",
332
+ "main",
333
+ "figure",
334
+ "figcaption",
335
+ "pre",
336
+ "address"
337
+ ]);
338
+ var BLOCK_TRIGGER_TAGS = /* @__PURE__ */ new Set([
339
+ "p",
340
+ "div",
341
+ "ul",
342
+ "ol",
343
+ "li",
344
+ "h1",
345
+ "h2",
346
+ "h3",
347
+ "h4",
348
+ "h5",
349
+ "h6",
350
+ "blockquote",
351
+ "table",
352
+ "tr",
353
+ "td",
354
+ "th"
355
+ ]);
356
+ function isNamespacedTag(tag) {
357
+ return tag.includes(":");
358
+ }
359
+ function hasBlockDescendant(el) {
360
+ const all = el.querySelectorAll("*");
361
+ for (const e of Array.from(all)) {
362
+ if (BLOCK_TRIGGER_TAGS.has(e.tagName.toLowerCase())) return true;
363
+ }
364
+ return false;
365
+ }
366
+ function walkBlockLevel(container, marks, blocks) {
367
+ let loose = [];
368
+ const flushLoose = () => {
369
+ if (loose.length === 0) return;
370
+ const nodes = loose;
371
+ loose = [];
372
+ const delta = inlineDeltaFromNodes(nodes, marks);
373
+ if (delta.length > 0) blocks.push(makeBlock("paragraph", delta, {}));
374
+ };
375
+ for (const node of Array.from(container.childNodes)) {
376
+ if (node.nodeType === Node.COMMENT_NODE) continue;
377
+ if (node.nodeType === Node.TEXT_NODE) {
378
+ loose.push(node);
379
+ continue;
380
+ }
381
+ if (node.nodeType !== Node.ELEMENT_NODE) continue;
382
+ const el = node;
383
+ const tag = el.tagName.toLowerCase();
384
+ if (IGNORED_TAGS.has(tag) || isNamespacedTag(tag)) continue;
385
+ switch (tag) {
386
+ case "ul":
387
+ case "ol": {
388
+ flushLoose();
389
+ handleList(el, tag === "ol" ? "ordered" : "bullet", 0, marks, blocks);
390
+ break;
391
+ }
392
+ case "h1":
393
+ case "h2":
394
+ case "h3":
395
+ case "h4":
396
+ case "h5":
397
+ case "h6": {
398
+ flushLoose();
399
+ const level = Number(tag[1]);
400
+ const attrs = { level };
401
+ const align = readAlign(el);
402
+ if (align) attrs.align = align;
403
+ const delta = inlineDeltaFromNodes(Array.from(el.childNodes), marks);
404
+ blocks.push(makeBlock("heading", delta, attrs));
405
+ break;
406
+ }
407
+ case "blockquote": {
408
+ flushLoose();
409
+ handleBlockquote(el, marks, blocks);
410
+ break;
411
+ }
412
+ case "table": {
413
+ flushLoose();
414
+ handleTable(el, marks, blocks);
415
+ break;
416
+ }
417
+ case "li": {
418
+ flushLoose();
419
+ const nestedLists = topLevelListDescendants(el);
420
+ const clone = el.cloneNode(true);
421
+ for (const l of topLevelListDescendants(clone)) l.remove();
422
+ const delta = inlineDeltaFromNodes(Array.from(clone.childNodes), marks);
423
+ blocks.push(makeBlock("listItem", delta, { listKind: "bullet", listLevel: 0 }));
424
+ for (const nested of nestedLists) {
425
+ const nestedKind = nested.tagName.toLowerCase() === "ol" ? "ordered" : "bullet";
426
+ handleList(nested, nestedKind, 1, marks, blocks);
427
+ }
428
+ break;
429
+ }
430
+ case "img":
431
+ break;
432
+ case "br":
433
+ loose.push(node);
434
+ break;
435
+ default: {
436
+ const styleAttr = el.getAttribute("style") ?? "";
437
+ const wordListLevel = parseWordListLevel(styleAttr);
438
+ if (wordListLevel != null && !hasBlockDescendant(el)) {
439
+ flushLoose();
440
+ const marker = findWordListMarker(el);
441
+ const listKind = marker ? detectWordListKind(marker) : "bullet";
442
+ const attrs = { listKind, listLevel: wordListLevel };
443
+ const align = readAlign(el);
444
+ if (align) attrs.align = align;
445
+ const delta = inlineDeltaFromNodes(Array.from(el.childNodes), marks);
446
+ blocks.push(makeBlock("listItem", delta, attrs));
447
+ break;
448
+ }
449
+ if (hasBlockDescendant(el)) {
450
+ flushLoose();
451
+ let nextMarks = applyTagMarks(tag, marks);
452
+ nextMarks = applyStyleMarks(el.getAttribute("style"), nextMarks);
453
+ walkBlockLevel(el, nextMarks, blocks);
454
+ break;
455
+ }
456
+ if (BLOCK_LEVEL_TAGS.has(tag)) {
457
+ flushLoose();
458
+ const attrs = {};
459
+ const align = readAlign(el);
460
+ if (align) attrs.align = align;
461
+ const delta = inlineDeltaFromNodes(Array.from(el.childNodes), marks);
462
+ blocks.push(makeBlock("paragraph", delta, attrs));
463
+ break;
464
+ }
465
+ loose.push(node);
466
+ }
467
+ }
468
+ }
469
+ flushLoose();
470
+ }
471
+ function handleList(listEl, kind, level, marks, blocks) {
472
+ for (const child of Array.from(listEl.children)) {
473
+ const tag = child.tagName.toLowerCase();
474
+ if (tag === "ul" || tag === "ol") {
475
+ const siblingKind = tag === "ol" ? "ordered" : "bullet";
476
+ handleList(child, siblingKind, level + 1, marks, blocks);
477
+ continue;
478
+ }
479
+ if (tag !== "li") continue;
480
+ const nestedLists = topLevelListDescendants(child);
481
+ const clone = child.cloneNode(true);
482
+ for (const l of topLevelListDescendants(clone)) l.remove();
483
+ const delta = inlineDeltaFromNodes(Array.from(clone.childNodes), marks);
484
+ const attrs = { listKind: kind, listLevel: clampLevel(level) };
485
+ const align = readAlign(child);
486
+ if (align) attrs.align = align;
487
+ blocks.push(makeBlock("listItem", delta, attrs));
488
+ for (const nested of nestedLists) {
489
+ const nestedKind = nested.tagName.toLowerCase() === "ol" ? "ordered" : "bullet";
490
+ handleList(nested, nestedKind, level + 1, marks, blocks);
491
+ }
492
+ }
493
+ }
494
+ function topLevelListDescendants(root) {
495
+ const all = Array.from(root.querySelectorAll("ul, ol"));
496
+ return all.filter((el) => {
497
+ let p = el.parentElement;
498
+ while (p && p !== root) {
499
+ const t = p.tagName.toLowerCase();
500
+ if (t === "ul" || t === "ol" || t === "table" || t === "td" || t === "th") return false;
501
+ p = p.parentElement;
502
+ }
503
+ return true;
504
+ });
505
+ }
506
+ function handleBlockquote(el, marks, blocks) {
507
+ if (!hasBlockDescendant(el)) {
508
+ const attrs = {};
509
+ const align = readAlign(el);
510
+ if (align) attrs.align = align;
511
+ const delta = inlineDeltaFromNodes(Array.from(el.childNodes), marks);
512
+ blocks.push(makeBlock("blockquote", delta, attrs));
513
+ return;
514
+ }
515
+ const nested = [];
516
+ walkBlockLevel(el, marks, nested);
517
+ for (const b of nested) {
518
+ blocks.push(b.type === "paragraph" ? { ...b, type: "blockquote" } : b);
519
+ }
520
+ }
521
+ function handleTable(table, marks, blocks) {
522
+ for (const row of directRows(table)) {
523
+ const cells = Array.from(row.children).filter(
524
+ (c) => ["td", "th"].includes(c.tagName.toLowerCase())
525
+ );
526
+ for (const cell of cells) {
527
+ const cellBlocks = [];
528
+ walkBlockLevel(cell, marks, cellBlocks);
529
+ for (const b of cellBlocks) {
530
+ const attrs = {};
531
+ if (b.attrs.align) attrs.align = b.attrs.align;
532
+ blocks.push(makeBlock("paragraph", b.delta, attrs));
533
+ }
534
+ }
535
+ }
536
+ }
537
+ function directRows(table) {
538
+ const rows = [];
539
+ for (const child of Array.from(table.children)) {
540
+ const tag = child.tagName.toLowerCase();
541
+ if (tag === "tr") {
542
+ rows.push(child);
543
+ } else if (tag === "tbody" || tag === "thead" || tag === "tfoot") {
544
+ for (const r of Array.from(child.children)) {
545
+ if (r.tagName.toLowerCase() === "tr") rows.push(r);
546
+ }
547
+ }
548
+ }
549
+ return rows;
550
+ }
551
+ function parseWordListLevel(styleAttr) {
552
+ const m = /mso-list\s*:\s*l\d+\s+level(\d+)/i.exec(styleAttr);
553
+ if (!m) return null;
554
+ const n = Number.parseInt(m[1], 10);
555
+ const fromLevelN = Number.isFinite(n) ? Math.max(0, n - 1) : 0;
556
+ const fromMargin = wordListLevelFromMarginLeft(marginLeftPt(styleAttr));
557
+ return clampLevel(Math.max(fromLevelN, fromMargin));
558
+ }
559
+ function marginLeftPt(styleAttr) {
560
+ const raw = parseStyle(styleAttr)["margin-left"];
561
+ if (!raw) return 0;
562
+ const m = /^(-?[\d.]+)\s*(pt|cm|in|px)$/i.exec(raw.trim());
563
+ if (!m) return 0;
564
+ const val = Number.parseFloat(m[1]);
565
+ if (!Number.isFinite(val) || val < 0) return 0;
566
+ switch (m[2].toLowerCase()) {
567
+ case "pt":
568
+ return val;
569
+ case "cm":
570
+ return val * 28.3465;
571
+ case "in":
572
+ return val * 72;
573
+ case "px":
574
+ return val * 0.75;
575
+ default:
576
+ return 0;
577
+ }
578
+ }
579
+ function wordListLevelFromMarginLeft(pt) {
580
+ if (pt <= 0) return 0;
581
+ return Math.max(0, Math.round(pt / 36) - 1);
582
+ }
583
+ function findWordListMarker(el) {
584
+ const all = el.querySelectorAll("*");
585
+ for (const e of Array.from(all)) {
586
+ const style = (e.getAttribute("style") ?? "").toLowerCase().replace(/\s+/g, "");
587
+ if (style.includes("mso-list:ignore")) return e;
588
+ }
589
+ return null;
590
+ }
591
+ function detectWordListKind(markerEl) {
592
+ const all = [markerEl, ...Array.from(markerEl.querySelectorAll("*"))];
593
+ for (const e of all) {
594
+ const style = (e.getAttribute("style") ?? "").toLowerCase();
595
+ if (style.includes("symbol") || style.includes("wingdings")) return "bullet";
596
+ }
597
+ const text = (markerEl.textContent ?? "").replace(/ /g, " ").trim();
598
+ if (/^[0-9]+[.)]/.test(text)) return "ordered";
599
+ if (/^[a-zA-Z][.)]/.test(text)) return "ordered";
600
+ return "bullet";
601
+ }
602
+ function inlineDeltaFromNodes(nodes, marks) {
603
+ const ops = [];
604
+ for (const n of nodes) walkInlineNode(n, marks, ops);
605
+ return trimEdgeWhitespace(collapseRepeatedSpaces(mergeAdjacentOps(ops)));
606
+ }
607
+ function collapseRepeatedSpaces(ops) {
608
+ return ops.map(
609
+ (op) => typeof op.insert === "string" ? { ...op, insert: op.insert.replace(/ {2,}/g, " ") } : op
610
+ );
611
+ }
612
+ function walkInlineNode(node, marks, ops) {
613
+ if (node.nodeType === Node.COMMENT_NODE) return;
614
+ if (node.nodeType === Node.TEXT_NODE) {
615
+ const text = normalizeWhitespace(node.textContent ?? "");
616
+ if (text.length > 0) pushOp(ops, text, marks);
617
+ return;
618
+ }
619
+ if (node.nodeType !== Node.ELEMENT_NODE) return;
620
+ const el = node;
621
+ const tag = el.tagName.toLowerCase();
622
+ if (IGNORED_TAGS.has(tag) || isNamespacedTag(tag)) return;
623
+ if (tag === "img") return;
624
+ if (tag === "br") {
625
+ ops.push({ insert: " " });
626
+ return;
627
+ }
628
+ const styleAttr = (el.getAttribute("style") ?? "").toLowerCase().replace(/\s+/g, "");
629
+ if (styleAttr.includes("mso-list:ignore")) return;
630
+ let nextMarks = applyTagMarks(tag, marks);
631
+ nextMarks = applyStyleMarks(el.getAttribute("style"), nextMarks);
632
+ if (tag === "a") {
633
+ const href = el.getAttribute("href");
634
+ if (href) nextMarks = { ...nextMarks, link: { href } };
635
+ }
636
+ for (const child of Array.from(el.childNodes)) walkInlineNode(child, nextMarks, ops);
637
+ }
638
+ function applyTagMarks(tag, marks) {
639
+ switch (tag) {
640
+ case "b":
641
+ case "strong":
642
+ return { ...marks, bold: true };
643
+ case "i":
644
+ case "em":
645
+ return { ...marks, italic: true };
646
+ case "u":
647
+ return { ...marks, underline: true };
648
+ case "s":
649
+ case "strike":
650
+ case "del":
651
+ return { ...marks, strike: true };
652
+ default:
653
+ return marks;
654
+ }
655
+ }
656
+ function parseStyle(styleAttr) {
657
+ const out = {};
658
+ if (!styleAttr) return out;
659
+ for (const decl of styleAttr.split(";")) {
660
+ const idx = decl.indexOf(":");
661
+ if (idx === -1) continue;
662
+ const prop = decl.slice(0, idx).trim().toLowerCase();
663
+ const val = decl.slice(idx + 1).trim();
664
+ if (prop) out[prop] = val;
665
+ }
666
+ return out;
667
+ }
668
+ function applyStyleMarks(styleAttr, marks) {
669
+ const decls = parseStyle(styleAttr);
670
+ if (Object.keys(decls).length === 0) return marks;
671
+ const next = { ...marks };
672
+ if ("font-weight" in decls) {
673
+ const v = decls["font-weight"].toLowerCase();
674
+ if (v === "bold" || /^[5-9]00$/.test(v)) next.bold = true;
675
+ else if (v === "normal" || /^[1-4]00$/.test(v)) delete next.bold;
676
+ }
677
+ if ("font-style" in decls) {
678
+ const v = decls["font-style"].toLowerCase();
679
+ if (v === "italic" || v === "oblique") next.italic = true;
680
+ else if (v === "normal") delete next.italic;
681
+ }
682
+ const deco = decls["text-decoration-line"] ?? decls["text-decoration"];
683
+ if (deco !== void 0) {
684
+ const v = deco.toLowerCase();
685
+ delete next.underline;
686
+ delete next.strike;
687
+ if (v.includes("underline")) next.underline = true;
688
+ if (v.includes("line-through")) next.strike = true;
689
+ }
690
+ if ("color" in decls) {
691
+ const css = normalizeCssColor(decls.color);
692
+ if (css && !isBlack(css)) next.color = css;
693
+ else delete next.color;
694
+ }
695
+ const bg = decls["background-color"] ?? decls.background;
696
+ if (bg !== void 0) {
697
+ const css = normalizeCssColor(bg);
698
+ if (css && css !== "#ffffff") next.highlight = css;
699
+ else delete next.highlight;
700
+ }
701
+ if ("font-size" in decls) {
702
+ const pt = parseFontSizePt(decls["font-size"]);
703
+ if (pt != null) next.fontSize = formatFontSizePt(pt);
704
+ }
705
+ return next;
706
+ }
707
+ function parseFontSizePt(raw) {
708
+ const v = raw.trim().toLowerCase();
709
+ const m = /^(-?[\d.]+)\s*(pt|px|in)$/.exec(v);
710
+ if (!m) return void 0;
711
+ const num = Number.parseFloat(m[1]);
712
+ if (!Number.isFinite(num)) return void 0;
713
+ let pt;
714
+ switch (m[2]) {
715
+ case "pt":
716
+ pt = num;
717
+ break;
718
+ case "px":
719
+ pt = num * 0.75;
720
+ break;
721
+ case "in":
722
+ pt = num * 72;
723
+ break;
724
+ default:
725
+ return void 0;
726
+ }
727
+ const rounded = Math.round(pt * 10) / 10;
728
+ if (rounded <= 0 || rounded > 200) return void 0;
729
+ return rounded;
730
+ }
731
+ function formatFontSizePt(pt) {
732
+ const rounded = Math.round(pt * 10) / 10;
733
+ const str = Number.isInteger(rounded) ? String(rounded) : rounded.toFixed(1);
734
+ return `${str}pt`;
735
+ }
736
+ var NAMED_COLORS = {
737
+ black: "#000000",
738
+ white: "#ffffff",
739
+ red: "#ff0000",
740
+ green: "#008000",
741
+ blue: "#0000ff",
742
+ yellow: "#ffff00",
743
+ cyan: "#00ffff",
744
+ magenta: "#ff00ff",
745
+ gray: "#808080",
746
+ grey: "#808080",
747
+ orange: "#ffa500",
748
+ purple: "#800080"
749
+ };
750
+ function normalizeCssColor(raw) {
751
+ if (!raw) return void 0;
752
+ const v = raw.trim().toLowerCase();
753
+ if (!v || v === "transparent" || v === "none" || v === "inherit" || v === "initial") return void 0;
754
+ if (v.startsWith("#")) {
755
+ let hex = v.slice(1);
756
+ if (hex.length === 3) hex = hex.split("").map((c) => c + c).join("");
757
+ return /^[0-9a-f]{6}$/.test(hex) ? `#${hex}` : void 0;
758
+ }
759
+ const rgbMatch = /^rgba?\(([^)]+)\)$/.exec(v);
760
+ if (rgbMatch) {
761
+ const parts = rgbMatch[1].split(",").map((s) => s.trim());
762
+ if (parts.length < 3) return void 0;
763
+ const alpha = parts[3] !== void 0 ? Number.parseFloat(parts[3]) : 1;
764
+ if (alpha === 0) return void 0;
765
+ const toHex = (n) => {
766
+ const num = Math.max(0, Math.min(255, Number.parseInt(n, 10) || 0));
767
+ return num.toString(16).padStart(2, "0");
768
+ };
769
+ return `#${toHex(parts[0])}${toHex(parts[1])}${toHex(parts[2])}`;
770
+ }
771
+ return NAMED_COLORS[v];
772
+ }
773
+ function isBlack(hex) {
774
+ return hex.toLowerCase() === "#000000";
775
+ }
776
+ function readAlign(el) {
777
+ const style = parseStyle(el.getAttribute("style"));
778
+ const v = style["text-align"];
779
+ if (!v) return void 0;
780
+ switch (v.toLowerCase()) {
781
+ case "left":
782
+ case "start":
783
+ return "left";
784
+ case "center":
785
+ return "center";
786
+ case "right":
787
+ case "end":
788
+ return "right";
789
+ case "justify":
790
+ return "justify";
791
+ default:
792
+ return void 0;
793
+ }
794
+ }
795
+ function clampLevel(n) {
796
+ if (!Number.isFinite(n)) return 0;
797
+ return Math.max(0, Math.min(MAX_LIST_LEVEL, Math.trunc(n)));
798
+ }
799
+ function normalizeListLevels(blocks) {
800
+ let i = 0;
801
+ while (i < blocks.length) {
802
+ if (blocks[i].type !== "listItem") {
803
+ i++;
804
+ continue;
805
+ }
806
+ let j = i + 1;
807
+ while (j < blocks.length && blocks[j].type === "listItem") j++;
808
+ if (j - i > 1) {
809
+ let min = Number.POSITIVE_INFINITY;
810
+ for (let k = i; k < j; k++) {
811
+ const lvl = blocks[k].attrs.listLevel ?? 0;
812
+ if (lvl < min) min = lvl;
813
+ }
814
+ if (min > 0) {
815
+ for (let k = i; k < j; k++) {
816
+ const lvl = blocks[k].attrs.listLevel ?? 0;
817
+ blocks[k].attrs.listLevel = clampLevel(lvl - min);
818
+ }
819
+ }
820
+ }
821
+ i = j;
822
+ }
823
+ }
824
+ function pushOp(ops, text, marks) {
825
+ const hasMarks = Object.keys(marks).length > 0;
826
+ ops.push(hasMarks ? { insert: text, attributes: { ...marks } } : { insert: text });
827
+ }
828
+ function normalizeWhitespace(raw) {
829
+ return raw.replace(/[ \t\r\n]+/g, " ");
830
+ }
831
+ function trimEdgeWhitespace(ops) {
832
+ let out = ops;
833
+ if (out.length > 0 && typeof out[0].insert === "string") {
834
+ const trimmed = out[0].insert.replace(/^ +/, "");
835
+ if (trimmed.length === 0) out = out.slice(1);
836
+ else if (trimmed !== out[0].insert) out = [{ ...out[0], insert: trimmed }, ...out.slice(1)];
837
+ }
838
+ if (out.length > 0 && typeof out[out.length - 1].insert === "string") {
839
+ const last = out[out.length - 1];
840
+ const trimmed = last.insert.replace(/ +$/, "");
841
+ if (trimmed.length === 0) out = out.slice(0, -1);
842
+ else if (trimmed !== last.insert) out = [...out.slice(0, -1), { ...last, insert: trimmed }];
843
+ }
844
+ return out;
845
+ }
846
+ function mergeAdjacentOps(ops) {
847
+ const out = [];
848
+ for (const op of ops) {
849
+ const prev = out[out.length - 1];
850
+ if (prev && typeof prev.insert === "string" && typeof op.insert === "string" && sameMarks(prev.attributes, op.attributes)) {
851
+ prev.insert = prev.insert + op.insert;
852
+ continue;
853
+ }
854
+ out.push({ ...op });
855
+ }
856
+ return out;
857
+ }
858
+ function sameMarks(a, b) {
859
+ const ak = a ? Object.keys(a).sort() : [];
860
+ const bk = b ? Object.keys(b).sort() : [];
861
+ if (ak.length !== bk.length) return false;
862
+ for (let i = 0; i < ak.length; i++) {
863
+ if (ak[i] !== bk[i]) return false;
864
+ const av = a[ak[i]];
865
+ const bv = b[bk[i]];
866
+ if (typeof av === "object" && av !== null && typeof bv === "object" && bv !== null) {
867
+ if (JSON.stringify(av) !== JSON.stringify(bv)) return false;
868
+ } else if (av !== bv) {
869
+ return false;
870
+ }
871
+ }
872
+ return true;
873
+ }
874
+ function deltaToText(delta) {
875
+ let s = "";
876
+ for (const op of delta) if (typeof op.insert === "string") s += op.insert;
877
+ return s;
878
+ }
879
+ function makeBlock(type, delta, attrs) {
880
+ return { type, text: deltaToText(delta), delta, attrs };
881
+ }
882
+
883
+ // src/pastePlan.ts
884
+ function planPaste(cd) {
885
+ const raw = cd.getData(SOFER_MIME);
886
+ if (raw) {
887
+ try {
888
+ const slice = JSON.parse(raw);
889
+ if (slice && Array.isArray(slice.blocks)) return { kind: "sofer", slice };
890
+ } catch {
891
+ }
892
+ }
893
+ const html = cd.getData("text/html");
894
+ if (html) {
895
+ const slice = htmlToSlice(html);
896
+ if (slice) return { kind: "html", slice };
897
+ }
898
+ const files = Array.from(cd.files ?? []).filter((f) => f.type.startsWith("image/"));
899
+ if (files.length > 0) return { kind: "images", files };
900
+ const text = cd.getData("text/plain");
901
+ if (text.length > 0) return { kind: "text", text };
902
+ return { kind: "none" };
903
+ }
904
+
296
905
  // src/BehindImageSelectAffordance.tsx
297
906
  import {
298
907
  useCallback,
@@ -2107,7 +2716,7 @@ function NodeView({ block, index, children, fragment, tableFragment }) {
2107
2716
  const common = commonBlockProps(block.attrs, index, block.type, fragment);
2108
2717
  switch (block.type) {
2109
2718
  case "heading": {
2110
- const level = clampLevel(block.attrs.level);
2719
+ const level = clampLevel2(block.attrs.level);
2111
2720
  const Tag = `h${level}`;
2112
2721
  return /* @__PURE__ */ jsx11(
2113
2722
  Tag,
@@ -2194,7 +2803,7 @@ function cellStyle(attrs, border) {
2194
2803
  if (attrs?.bgColor) style.backgroundColor = attrs.bgColor;
2195
2804
  return Object.keys(style).length > 0 ? style : void 0;
2196
2805
  }
2197
- function clampLevel(l) {
2806
+ function clampLevel2(l) {
2198
2807
  if (l === 2 || l === 3 || l === 4 || l === 5 || l === 6) return l;
2199
2808
  return 1;
2200
2809
  }
@@ -3855,7 +4464,7 @@ function Editor({
3855
4464
  const ctx = ctxRef.current;
3856
4465
  const slice = serializeSelection(ctx.doc, ctx.getSelection());
3857
4466
  if (!slice || !e.clipboardData) return false;
3858
- e.clipboardData.setData(SOFER_MIME, JSON.stringify(slice));
4467
+ e.clipboardData.setData(SOFER_MIME2, JSON.stringify(slice));
3859
4468
  e.clipboardData.setData("text/plain", sliceToText(slice));
3860
4469
  return true;
3861
4470
  };
@@ -3870,34 +4479,28 @@ function Editor({
3870
4479
  const ctx = ctxRef.current;
3871
4480
  const cd = e.clipboardData;
3872
4481
  if (!cd) return;
3873
- const raw = cd.getData(SOFER_MIME);
3874
- if (raw) {
3875
- try {
3876
- const slice = JSON.parse(raw);
3877
- if (slice && Array.isArray(slice.blocks)) {
3878
- e.preventDefault();
3879
- insertSlice(ctx, slice);
3880
- return;
3881
- }
3882
- } catch {
4482
+ const plan = planPaste(cd);
4483
+ if (plan.kind === "none") return;
4484
+ e.preventDefault();
4485
+ switch (plan.kind) {
4486
+ case "sofer":
4487
+ case "html":
4488
+ insertSlice(ctx, plan.slice);
4489
+ return;
4490
+ case "images":
4491
+ void (async () => {
4492
+ for (const f of plan.files) await editorRef.current.insertImageFromFile(f);
4493
+ })();
4494
+ return;
4495
+ case "text": {
4496
+ const lines = plan.text.split(/\r\n|\r|\n/);
4497
+ lines.forEach((line, i) => {
4498
+ if (i > 0) insertParagraph(ctx);
4499
+ if (line.length > 0) insertText(ctx, line);
4500
+ });
4501
+ return;
3883
4502
  }
3884
4503
  }
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
4504
  };
3902
4505
  root.addEventListener("copy", onCopy);
3903
4506
  root.addEventListener("cut", onCut);
@@ -4152,11 +4755,15 @@ function groupTopLevels(snapshot) {
4152
4755
  const groupStart = i;
4153
4756
  const items = [];
4154
4757
  const kind = block.attrs.listKind === "ordered" ? "ordered" : "bullet";
4758
+ const leaderLevel = clampListLevel2(block.attrs.listLevel);
4155
4759
  const groupStyle = block.attrs.listStyle;
4156
4760
  while (i < snapshot.length) {
4157
4761
  const b = snapshot[i];
4158
4762
  if (b.type !== "listItem") break;
4159
- if ((b.attrs.listKind ?? "bullet") !== kind) break;
4763
+ const bLevel = clampListLevel2(b.attrs.listLevel);
4764
+ if (bLevel <= leaderLevel && (b.attrs.listKind ?? "bullet") !== kind) {
4765
+ break;
4766
+ }
4160
4767
  if (i > groupStart) {
4161
4768
  if (typeof b.attrs.listStart === "number") break;
4162
4769
  const itemStyle = b.attrs.listStyle;
@@ -4264,7 +4871,7 @@ function buildListTree(items) {
4264
4871
  function renderListTree(tree, top, start, key) {
4265
4872
  const className = `ed-list ed-list-${top.listKind}`;
4266
4873
  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, top.listKind, `nested-${node.entry.index}`) : null }, node.entry.index));
4874
+ 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
4875
  if (top.listKind === "ordered") {
4269
4876
  return /* @__PURE__ */ jsx12(
4270
4877
  "ol",
@@ -4280,9 +4887,10 @@ function renderListTree(tree, top, start, key) {
4280
4887
  }
4281
4888
  return /* @__PURE__ */ jsx12("ul", { className, "data-list-kind": "bullet", style, children: items }, key);
4282
4889
  }
4283
- function renderNestedListTree(tree, kind, key) {
4890
+ function renderNestedListTree(tree, key) {
4891
+ const kind = tree[0]?.entry.block.attrs.listKind === "ordered" ? "ordered" : "bullet";
4284
4892
  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, kind, `nested-${node.entry.index}`) : null }, node.entry.index)) }, key);
4893
+ 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
4894
  }
4287
4895
  function clampListLevel2(l) {
4288
4896
  if (typeof l !== "number" || !Number.isFinite(l)) return 0;
@@ -5143,6 +5751,7 @@ export {
5143
5751
  getBlockElement,
5144
5752
  getCellElement,
5145
5753
  getFragmentForOffset,
5754
+ htmlToSlice,
5146
5755
  isTableRectSelection,
5147
5756
  locatePoint,
5148
5757
  readDomSelection,