@sofereditor/core 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.d.cts CHANGED
@@ -600,6 +600,20 @@ interface ClipboardSlice {
600
600
  openStart: boolean;
601
601
  /** último bloco é fragmento parcial → conteúdo após o caret continua nele. */
602
602
  openEnd: boolean;
603
+ /**
604
+ * Marca explícita: quando presente (`true`) e `blocks.length === 1`, esse
605
+ * único bloco é uma unidade INTEIRA (ex.: um `<h1>`/`<li>`/`<blockquote>`
606
+ * inteiro colado de fora) — `insertSlice` deve adotar o `type`/`attrs` dele
607
+ * em vez de tratá-lo como fragmento de uma seleção.
608
+ *
609
+ * `serializeSelection` NUNCA define este campo — uma cópia interna é sempre
610
+ * um fragmento de seleção, mesmo quando por coincidência cobre o bloco
611
+ * inteiro (nesse caso `openStart`/`openEnd` também saem `false`, mas isso
612
+ * não significa "bloco externo inteiro": significa apenas que a seleção
613
+ * tocou as bordas do bloco). Só produtores de blocos externos inteiros
614
+ * (hoje: `htmlToSlice`) devem marcar `blockLevel: true`.
615
+ */
616
+ blockLevel?: true;
603
617
  }
604
618
  /**
605
619
  * Lê a seleção e produz um ClipboardSlice, ou null se:
@@ -678,7 +692,18 @@ declare function setCellAttr<K extends keyof CellAttrs>(ctx: CommandContext, key
678
692
  declare function deleteSelection(ctx: CommandContext): void;
679
693
  /**
680
694
  * Replace the current selection with a clipboard slice (A1).
681
- * - Single-block slice → inline splice (Dhead + S0 + Dtail), target keeps its type.
695
+ * - Single-block slice WITHOUT `blockLevel` → always an inline splice
696
+ * (Dhead + S0 + Dtail), target keeps its type. This is the internal
697
+ * copy/paste shape, including a full-block internal copy (selecting an
698
+ * entire line and copying it also yields `openStart:false, openEnd:false`
699
+ * with one block — that is NOT the same thing as an external whole block;
700
+ * `openStart`/`openEnd` do not carry that distinction, so branching on them
701
+ * here regressed plain in-editor copy/paste. See `ClipboardSlice.blockLevel`.
702
+ * - Single-block slice WITH `blockLevel: true` (e.g. one <h1>/<li>/<blockquote>
703
+ * from an HTML paste via `htmlToSlice`) → adopts S0's type/attrs instead,
704
+ * same rules as the multi-block case below (reuse target in place when the
705
+ * caret is at offset 0, otherwise insert S0 as its own block; any tail
706
+ * becomes a separate block with the target's original type).
682
707
  * - Multi-block slice → openStart merges S0 inline; middle/last blocks inserted
683
708
  * discretely; openEnd appends the post-caret tail onto the last pasted block.
684
709
  * - Target inside a table cell → slice flattened to inline (no block structure).
package/dist/index.d.ts CHANGED
@@ -600,6 +600,20 @@ interface ClipboardSlice {
600
600
  openStart: boolean;
601
601
  /** último bloco é fragmento parcial → conteúdo após o caret continua nele. */
602
602
  openEnd: boolean;
603
+ /**
604
+ * Marca explícita: quando presente (`true`) e `blocks.length === 1`, esse
605
+ * único bloco é uma unidade INTEIRA (ex.: um `<h1>`/`<li>`/`<blockquote>`
606
+ * inteiro colado de fora) — `insertSlice` deve adotar o `type`/`attrs` dele
607
+ * em vez de tratá-lo como fragmento de uma seleção.
608
+ *
609
+ * `serializeSelection` NUNCA define este campo — uma cópia interna é sempre
610
+ * um fragmento de seleção, mesmo quando por coincidência cobre o bloco
611
+ * inteiro (nesse caso `openStart`/`openEnd` também saem `false`, mas isso
612
+ * não significa "bloco externo inteiro": significa apenas que a seleção
613
+ * tocou as bordas do bloco). Só produtores de blocos externos inteiros
614
+ * (hoje: `htmlToSlice`) devem marcar `blockLevel: true`.
615
+ */
616
+ blockLevel?: true;
603
617
  }
604
618
  /**
605
619
  * Lê a seleção e produz um ClipboardSlice, ou null se:
@@ -678,7 +692,18 @@ declare function setCellAttr<K extends keyof CellAttrs>(ctx: CommandContext, key
678
692
  declare function deleteSelection(ctx: CommandContext): void;
679
693
  /**
680
694
  * Replace the current selection with a clipboard slice (A1).
681
- * - Single-block slice → inline splice (Dhead + S0 + Dtail), target keeps its type.
695
+ * - Single-block slice WITHOUT `blockLevel` → always an inline splice
696
+ * (Dhead + S0 + Dtail), target keeps its type. This is the internal
697
+ * copy/paste shape, including a full-block internal copy (selecting an
698
+ * entire line and copying it also yields `openStart:false, openEnd:false`
699
+ * with one block — that is NOT the same thing as an external whole block;
700
+ * `openStart`/`openEnd` do not carry that distinction, so branching on them
701
+ * here regressed plain in-editor copy/paste. See `ClipboardSlice.blockLevel`.
702
+ * - Single-block slice WITH `blockLevel: true` (e.g. one <h1>/<li>/<blockquote>
703
+ * from an HTML paste via `htmlToSlice`) → adopts S0's type/attrs instead,
704
+ * same rules as the multi-block case below (reuse target in place when the
705
+ * caret is at offset 0, otherwise insert S0 as its own block; any tail
706
+ * becomes a separate block with the target's original type).
682
707
  * - Multi-block slice → openStart merges S0 inline; middle/last blocks inserted
683
708
  * discretely; openEnd appends the post-caret tail onto the last pasted block.
684
709
  * - Target inside a table cell → slice flattened to inline (no block structure).
package/dist/index.js CHANGED
@@ -1190,6 +1190,34 @@ function insertSlice(ctx, slice) {
1190
1190
  const tailDelta = sliceDelta(full, offset, deltaLength(full));
1191
1191
  targetText.delete(offset, targetText.length - offset);
1192
1192
  const blocks = slice.blocks;
1193
+ if (blocks.length === 1 && slice.blockLevel) {
1194
+ const originalType2 = ctx.doc.getBlockType(blockIndex);
1195
+ const originalAttrs2 = ctx.doc.getBlockAttrs(blockIndex);
1196
+ let hostIndex = blockIndex;
1197
+ if (offset === 0) {
1198
+ const blk = ctx.doc.blocks.get(blockIndex);
1199
+ blk.set("type", blocks[0].type);
1200
+ const am = blk.get("attrs");
1201
+ am.clear();
1202
+ for (const [k, v] of Object.entries(blocks[0].attrs)) {
1203
+ if (v !== void 0) am.set(k, v);
1204
+ }
1205
+ writeDeltaInto(targetText, 0, blocks[0].delta);
1206
+ } else {
1207
+ hostIndex = blockIndex + 1;
1208
+ const b = createBlock(blocks[0].type, "", blocks[0].attrs);
1209
+ ctx.doc.blocks.insert(hostIndex, [b]);
1210
+ writeDeltaInto(b.get("text"), 0, blocks[0].delta);
1211
+ }
1212
+ const caretPos = ctx.doc.getBlockText(hostIndex).length;
1213
+ if (tailDelta.length > 0) {
1214
+ const tb = createBlock(originalType2 ?? "paragraph", "", originalAttrs2);
1215
+ ctx.doc.blocks.insert(hostIndex + 1, [tb]);
1216
+ writeDeltaInto(tb.get("text"), 0, tailDelta);
1217
+ }
1218
+ ctx.setSelection(collapsedSelection({ blockIndex: hostIndex, offset: caretPos }));
1219
+ return;
1220
+ }
1193
1221
  if (blocks.length === 1) {
1194
1222
  const afterS0 = writeDeltaInto(targetText, offset, blocks[0].delta);
1195
1223
  writeDeltaInto(targetText, afterS0, tailDelta);