@sofereditor/core 0.9.0 → 0.10.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.d.cts CHANGED
@@ -191,6 +191,20 @@ interface CellAttrs {
191
191
  align?: AlignValue;
192
192
  /** Cor de fundo da célula (CSS color, ex. `#ffe58f`). Ausente = sem fundo. */
193
193
  bgColor?: string;
194
+ /**
195
+ * Quando presente, a célula renderiza como lista e cada linha separada por
196
+ * `\n` vira um item. Ausente = texto normal com quebras visuais.
197
+ *
198
+ * Não existe `listLevel` aqui de propósito: `Y.Text` plano não guarda
199
+ * atributo por linha, então recuo seria um nível para a célula inteira, não
200
+ * por item. Aninhamento dentro de célula exigiria blocos de verdade dentro
201
+ * dela — ver o spec de 2026-08-24.
202
+ */
203
+ listKind?: ListKind;
204
+ /** Só relevante com `listKind === "ordered"`. Primeiro número da lista. */
205
+ listStart?: number;
206
+ /** Só relevante com `listKind === "ordered"`. Sobrepõe o marcador padrão. */
207
+ listStyle?: ListStyleType;
194
208
  }
195
209
  interface Position {
196
210
  blockIndex: number;
@@ -531,6 +545,19 @@ declare function deltaLength(delta: DeltaOp[]): number;
531
545
  * Embeds are atomic length-1 ops: included whole if their slot intersects the range.
532
546
  */
533
547
  declare function sliceDelta(delta: DeltaOp[], start: number, end: number): DeltaOp[];
548
+ /**
549
+ * Fatia um delta em uma lista por linha, quebrando nos `\n`.
550
+ *
551
+ * Usado pelo render de célula-lista: a célula é um `Y.Text` plano, e quando ela
552
+ * carrega `listKind` cada linha vira um `<li>`. As marcas de cada trecho são
553
+ * preservadas; embeds ficam na linha corrente e nunca separam.
554
+ *
555
+ * Devolve SEMPRE ao menos uma linha — delta vazio vira `[[]]`, não `[]`.
556
+ * Linha vazia no meio (`"a\n\nb"`) é preservada como delta vazio, senão o
557
+ * número de itens da lista não bateria com o número de `\n` do modelo, e o
558
+ * mapeamento de cursor do `dom-bridge` sairia do lugar.
559
+ */
560
+ declare function splitDeltaByLines(delta: DeltaOp[]): DeltaOp[][];
534
561
 
535
562
  /**
536
563
  * Helpers puros de decoração visual, compartilhados pelos DOIS renderizadores
@@ -1055,4 +1082,24 @@ declare class EditorHistory {
1055
1082
  destroy(): void;
1056
1083
  }
1057
1084
 
1058
- export { ANSWER_LINE_COLOR, ANSWER_LINE_MAX, type AlignValue, type AnswerLineSpacing, BLANK_MIN_RUN, BLANK_STYLE, BLOCK_SPECS, type BlockAttrs, type BlockType, CLEAR_ALL_MARKS, COMMAND_ORIGIN, type CellAttrs, type CellBorderColors, type CellBorderPos, type ClipboardSlice, type CommandContext, type CommentAttr, DEFAULT_PAGE_SETTINGS, type DeltaOp, type DirValue, EditorDocument, EditorHistory, type EmbedLoc, type EncodedCursor, type EncodedCursorPoint, type ImageEmbed, type ImageLayout, type InsertContent, type LegacySerializedDocument, type LinkAttr, type ListKind, type ListStyleType, MARGIN_PRESETS, MAX_LIST_LEVEL, MAX_TABELA_PCT, MIN_LINHA_PX, MIN_TABELA_PCT, type MarkAttrs, type MarkName, type MarkValue, PAGE_PRESETS, type PagePreset, type PageSettings, type Position, SOFER_MIME, type Selection, type SerializedBlock, type SerializedCell, type SerializedDocument, type StyleRecord, TABLE_BORDER_COLOR, TABLE_GUIDE_COLOR, TRACKED_ORIGINS, type TableBorderPreset, type TableLocation, type TableRect, answerLineStyle, cellBorderColors, cellBorderStyle, cellsInRect, clearRectCells, collapsedSelection, comparePositions, createBlock, createCell, createTableBlock, decodeSelection, dedentList, defaultAttrsFor, deleteBackward, deleteForward, deleteSelection, deleteTable, deleteTableColumn, deleteTableRow, deltaLength, detectPreset, encodeSelection, expandTableRect, getBlockId, getMarksInRange, indentList, insertAnswerLines, insertImage, insertParagraph, insertSlice, insertTable, insertTableColumn, insertTableRow, insertText, isCollapsed, isCommandOrigin, isEmbedAdjacentToCaret, isFormulaEmbed, isImageEmbed, isKnownBlockType, isLegacySerializedDocument, isMarkUniformInRange, mergeDown, mergeRight, mergeSelection, mmToPx, moveEmbedAnchor, moveToNextCell, moveToPrevCell, newBlockId, normalizarLarguras, orderedRange, positionsEqual, pxToMm, removeMark, sameTextRun, serializeSelection, setBlockAttr, setBlockAttrAtIndex, setBlockType, setCellAttr, setColumnBoundary, setImageAttrs, setMark, setRowHeight, setRowHeights, setTableWidth, sliceDelta, sliceToInlineDelta, sliceToText, spanOf, splitCell, splitListItem, splitUnderscoreRuns, styleToCssText, tableLocationOf, tableRectSelection, toggleList, toggleMark, travaLarguraTabela, withDefaults };
1085
+ /**
1086
+ * Remove toda marca `fontFamily` do documento — de blocos e de células de
1087
+ * tabela. Remove **todas**, não só as de valor conhecido: com o dropdown de
1088
+ * fonte fora da toolbar, nenhuma marca legítima pode existir.
1089
+ *
1090
+ * Idempotente e observável: conta antes de escrever e só abre transação se
1091
+ * houver o que limpar. Documento já migrado não gera update no Y.Doc, logo não
1092
+ * dispara o autosave e não regrava o `htmlSnapshot`.
1093
+ *
1094
+ * A transação usa origin `"migration"`, seguindo a convenção de `"pageSettings"`
1095
+ * e `"import"` (`document.ts:106`) — origens que o `UndoManager` não rastreia.
1096
+ * Sem isso, o primeiro Ctrl+Z do professor desfaria a migração em vez da edição
1097
+ * dele.
1098
+ *
1099
+ * @returns número de runs que carregavam a marca. 0 = nenhuma escrita.
1100
+ */
1101
+ declare function stripFontFamilyMarks(doc: EditorDocument, opts?: {
1102
+ dryRun?: boolean;
1103
+ }): number;
1104
+
1105
+ export { ANSWER_LINE_COLOR, ANSWER_LINE_MAX, type AlignValue, type AnswerLineSpacing, BLANK_MIN_RUN, BLANK_STYLE, BLOCK_SPECS, type BlockAttrs, type BlockType, CLEAR_ALL_MARKS, COMMAND_ORIGIN, type CellAttrs, type CellBorderColors, type CellBorderPos, type ClipboardSlice, type CommandContext, type CommentAttr, DEFAULT_PAGE_SETTINGS, type DeltaOp, type DirValue, EditorDocument, EditorHistory, type EmbedLoc, type EncodedCursor, type EncodedCursorPoint, type ImageEmbed, type ImageLayout, type InsertContent, type LegacySerializedDocument, type LinkAttr, type ListKind, type ListStyleType, MARGIN_PRESETS, MAX_LIST_LEVEL, MAX_TABELA_PCT, MIN_LINHA_PX, MIN_TABELA_PCT, type MarkAttrs, type MarkName, type MarkValue, PAGE_PRESETS, type PagePreset, type PageSettings, type Position, SOFER_MIME, type Selection, type SerializedBlock, type SerializedCell, type SerializedDocument, type StyleRecord, TABLE_BORDER_COLOR, TABLE_GUIDE_COLOR, TRACKED_ORIGINS, type TableBorderPreset, type TableLocation, type TableRect, answerLineStyle, cellBorderColors, cellBorderStyle, cellsInRect, clearRectCells, collapsedSelection, comparePositions, createBlock, createCell, createTableBlock, decodeSelection, dedentList, defaultAttrsFor, deleteBackward, deleteForward, deleteSelection, deleteTable, deleteTableColumn, deleteTableRow, deltaLength, detectPreset, encodeSelection, expandTableRect, getBlockId, getMarksInRange, indentList, insertAnswerLines, insertImage, insertParagraph, insertSlice, insertTable, insertTableColumn, insertTableRow, insertText, isCollapsed, isCommandOrigin, isEmbedAdjacentToCaret, isFormulaEmbed, isImageEmbed, isKnownBlockType, isLegacySerializedDocument, isMarkUniformInRange, mergeDown, mergeRight, mergeSelection, mmToPx, moveEmbedAnchor, moveToNextCell, moveToPrevCell, newBlockId, normalizarLarguras, orderedRange, positionsEqual, pxToMm, removeMark, sameTextRun, serializeSelection, setBlockAttr, setBlockAttrAtIndex, setBlockType, setCellAttr, setColumnBoundary, setImageAttrs, setMark, setRowHeight, setRowHeights, setTableWidth, sliceDelta, sliceToInlineDelta, sliceToText, spanOf, splitCell, splitDeltaByLines, splitListItem, splitUnderscoreRuns, stripFontFamilyMarks, styleToCssText, tableLocationOf, tableRectSelection, toggleList, toggleMark, travaLarguraTabela, withDefaults };
package/dist/index.d.ts CHANGED
@@ -191,6 +191,20 @@ interface CellAttrs {
191
191
  align?: AlignValue;
192
192
  /** Cor de fundo da célula (CSS color, ex. `#ffe58f`). Ausente = sem fundo. */
193
193
  bgColor?: string;
194
+ /**
195
+ * Quando presente, a célula renderiza como lista e cada linha separada por
196
+ * `\n` vira um item. Ausente = texto normal com quebras visuais.
197
+ *
198
+ * Não existe `listLevel` aqui de propósito: `Y.Text` plano não guarda
199
+ * atributo por linha, então recuo seria um nível para a célula inteira, não
200
+ * por item. Aninhamento dentro de célula exigiria blocos de verdade dentro
201
+ * dela — ver o spec de 2026-08-24.
202
+ */
203
+ listKind?: ListKind;
204
+ /** Só relevante com `listKind === "ordered"`. Primeiro número da lista. */
205
+ listStart?: number;
206
+ /** Só relevante com `listKind === "ordered"`. Sobrepõe o marcador padrão. */
207
+ listStyle?: ListStyleType;
194
208
  }
195
209
  interface Position {
196
210
  blockIndex: number;
@@ -531,6 +545,19 @@ declare function deltaLength(delta: DeltaOp[]): number;
531
545
  * Embeds are atomic length-1 ops: included whole if their slot intersects the range.
532
546
  */
533
547
  declare function sliceDelta(delta: DeltaOp[], start: number, end: number): DeltaOp[];
548
+ /**
549
+ * Fatia um delta em uma lista por linha, quebrando nos `\n`.
550
+ *
551
+ * Usado pelo render de célula-lista: a célula é um `Y.Text` plano, e quando ela
552
+ * carrega `listKind` cada linha vira um `<li>`. As marcas de cada trecho são
553
+ * preservadas; embeds ficam na linha corrente e nunca separam.
554
+ *
555
+ * Devolve SEMPRE ao menos uma linha — delta vazio vira `[[]]`, não `[]`.
556
+ * Linha vazia no meio (`"a\n\nb"`) é preservada como delta vazio, senão o
557
+ * número de itens da lista não bateria com o número de `\n` do modelo, e o
558
+ * mapeamento de cursor do `dom-bridge` sairia do lugar.
559
+ */
560
+ declare function splitDeltaByLines(delta: DeltaOp[]): DeltaOp[][];
534
561
 
535
562
  /**
536
563
  * Helpers puros de decoração visual, compartilhados pelos DOIS renderizadores
@@ -1055,4 +1082,24 @@ declare class EditorHistory {
1055
1082
  destroy(): void;
1056
1083
  }
1057
1084
 
1058
- export { ANSWER_LINE_COLOR, ANSWER_LINE_MAX, type AlignValue, type AnswerLineSpacing, BLANK_MIN_RUN, BLANK_STYLE, BLOCK_SPECS, type BlockAttrs, type BlockType, CLEAR_ALL_MARKS, COMMAND_ORIGIN, type CellAttrs, type CellBorderColors, type CellBorderPos, type ClipboardSlice, type CommandContext, type CommentAttr, DEFAULT_PAGE_SETTINGS, type DeltaOp, type DirValue, EditorDocument, EditorHistory, type EmbedLoc, type EncodedCursor, type EncodedCursorPoint, type ImageEmbed, type ImageLayout, type InsertContent, type LegacySerializedDocument, type LinkAttr, type ListKind, type ListStyleType, MARGIN_PRESETS, MAX_LIST_LEVEL, MAX_TABELA_PCT, MIN_LINHA_PX, MIN_TABELA_PCT, type MarkAttrs, type MarkName, type MarkValue, PAGE_PRESETS, type PagePreset, type PageSettings, type Position, SOFER_MIME, type Selection, type SerializedBlock, type SerializedCell, type SerializedDocument, type StyleRecord, TABLE_BORDER_COLOR, TABLE_GUIDE_COLOR, TRACKED_ORIGINS, type TableBorderPreset, type TableLocation, type TableRect, answerLineStyle, cellBorderColors, cellBorderStyle, cellsInRect, clearRectCells, collapsedSelection, comparePositions, createBlock, createCell, createTableBlock, decodeSelection, dedentList, defaultAttrsFor, deleteBackward, deleteForward, deleteSelection, deleteTable, deleteTableColumn, deleteTableRow, deltaLength, detectPreset, encodeSelection, expandTableRect, getBlockId, getMarksInRange, indentList, insertAnswerLines, insertImage, insertParagraph, insertSlice, insertTable, insertTableColumn, insertTableRow, insertText, isCollapsed, isCommandOrigin, isEmbedAdjacentToCaret, isFormulaEmbed, isImageEmbed, isKnownBlockType, isLegacySerializedDocument, isMarkUniformInRange, mergeDown, mergeRight, mergeSelection, mmToPx, moveEmbedAnchor, moveToNextCell, moveToPrevCell, newBlockId, normalizarLarguras, orderedRange, positionsEqual, pxToMm, removeMark, sameTextRun, serializeSelection, setBlockAttr, setBlockAttrAtIndex, setBlockType, setCellAttr, setColumnBoundary, setImageAttrs, setMark, setRowHeight, setRowHeights, setTableWidth, sliceDelta, sliceToInlineDelta, sliceToText, spanOf, splitCell, splitListItem, splitUnderscoreRuns, styleToCssText, tableLocationOf, tableRectSelection, toggleList, toggleMark, travaLarguraTabela, withDefaults };
1085
+ /**
1086
+ * Remove toda marca `fontFamily` do documento — de blocos e de células de
1087
+ * tabela. Remove **todas**, não só as de valor conhecido: com o dropdown de
1088
+ * fonte fora da toolbar, nenhuma marca legítima pode existir.
1089
+ *
1090
+ * Idempotente e observável: conta antes de escrever e só abre transação se
1091
+ * houver o que limpar. Documento já migrado não gera update no Y.Doc, logo não
1092
+ * dispara o autosave e não regrava o `htmlSnapshot`.
1093
+ *
1094
+ * A transação usa origin `"migration"`, seguindo a convenção de `"pageSettings"`
1095
+ * e `"import"` (`document.ts:106`) — origens que o `UndoManager` não rastreia.
1096
+ * Sem isso, o primeiro Ctrl+Z do professor desfaria a migração em vez da edição
1097
+ * dele.
1098
+ *
1099
+ * @returns número de runs que carregavam a marca. 0 = nenhuma escrita.
1100
+ */
1101
+ declare function stripFontFamilyMarks(doc: EditorDocument, opts?: {
1102
+ dryRun?: boolean;
1103
+ }): number;
1104
+
1105
+ export { ANSWER_LINE_COLOR, ANSWER_LINE_MAX, type AlignValue, type AnswerLineSpacing, BLANK_MIN_RUN, BLANK_STYLE, BLOCK_SPECS, type BlockAttrs, type BlockType, CLEAR_ALL_MARKS, COMMAND_ORIGIN, type CellAttrs, type CellBorderColors, type CellBorderPos, type ClipboardSlice, type CommandContext, type CommentAttr, DEFAULT_PAGE_SETTINGS, type DeltaOp, type DirValue, EditorDocument, EditorHistory, type EmbedLoc, type EncodedCursor, type EncodedCursorPoint, type ImageEmbed, type ImageLayout, type InsertContent, type LegacySerializedDocument, type LinkAttr, type ListKind, type ListStyleType, MARGIN_PRESETS, MAX_LIST_LEVEL, MAX_TABELA_PCT, MIN_LINHA_PX, MIN_TABELA_PCT, type MarkAttrs, type MarkName, type MarkValue, PAGE_PRESETS, type PagePreset, type PageSettings, type Position, SOFER_MIME, type Selection, type SerializedBlock, type SerializedCell, type SerializedDocument, type StyleRecord, TABLE_BORDER_COLOR, TABLE_GUIDE_COLOR, TRACKED_ORIGINS, type TableBorderPreset, type TableLocation, type TableRect, answerLineStyle, cellBorderColors, cellBorderStyle, cellsInRect, clearRectCells, collapsedSelection, comparePositions, createBlock, createCell, createTableBlock, decodeSelection, dedentList, defaultAttrsFor, deleteBackward, deleteForward, deleteSelection, deleteTable, deleteTableColumn, deleteTableRow, deltaLength, detectPreset, encodeSelection, expandTableRect, getBlockId, getMarksInRange, indentList, insertAnswerLines, insertImage, insertParagraph, insertSlice, insertTable, insertTableColumn, insertTableRow, insertText, isCollapsed, isCommandOrigin, isEmbedAdjacentToCaret, isFormulaEmbed, isImageEmbed, isKnownBlockType, isLegacySerializedDocument, isMarkUniformInRange, mergeDown, mergeRight, mergeSelection, mmToPx, moveEmbedAnchor, moveToNextCell, moveToPrevCell, newBlockId, normalizarLarguras, orderedRange, positionsEqual, pxToMm, removeMark, sameTextRun, serializeSelection, setBlockAttr, setBlockAttrAtIndex, setBlockType, setCellAttr, setColumnBoundary, setImageAttrs, setMark, setRowHeight, setRowHeights, setTableWidth, sliceDelta, sliceToInlineDelta, sliceToText, spanOf, splitCell, splitDeltaByLines, splitListItem, splitUnderscoreRuns, stripFontFamilyMarks, styleToCssText, tableLocationOf, tableRectSelection, toggleList, toggleMark, travaLarguraTabela, withDefaults };
package/dist/index.js CHANGED
@@ -725,6 +725,25 @@ function sameMarkValue(a, b) {
725
725
  }
726
726
  return false;
727
727
  }
728
+ function splitDeltaByLines(delta) {
729
+ const lines = [[]];
730
+ for (const op of delta) {
731
+ if (typeof op.insert !== "string") {
732
+ lines[lines.length - 1].push(op);
733
+ continue;
734
+ }
735
+ const parts = op.insert.split("\n");
736
+ for (let i = 0; i < parts.length; i++) {
737
+ if (i > 0) lines.push([]);
738
+ const part = parts[i];
739
+ if (part.length === 0) continue;
740
+ lines[lines.length - 1].push(
741
+ op.attributes ? { insert: part, attributes: op.attributes } : { insert: part }
742
+ );
743
+ }
744
+ }
745
+ return lines;
746
+ }
728
747
 
729
748
  // src/decorations.ts
730
749
  var BLANK_MIN_RUN = 3;
@@ -1348,7 +1367,12 @@ function mergeWithPrevious(ctx, blockIndex) {
1348
1367
  function toggleList(ctx, kind) {
1349
1368
  transact(ctx.doc, () => {
1350
1369
  const sel = ctx.getSelection();
1351
- if (sel.anchor.cellIndex != null || sel.focus.cellIndex != null) return;
1370
+ if (sel.anchor.cellIndex != null || sel.focus.cellIndex != null) {
1371
+ const real = sel.focus.cellIndex != null ? ctx.doc.realCellIndex(sel.focus.blockIndex, sel.focus.cellIndex) : void 0;
1372
+ const atual = real != null ? ctx.doc.getCellAttrs(sel.focus.blockIndex, real).listKind : void 0;
1373
+ setCellAttr(ctx, "listKind", atual === kind ? null : kind);
1374
+ return;
1375
+ }
1352
1376
  const indices = selectedBlockIndices(sel, ctx.doc.blockCount());
1353
1377
  const allMatching = indices.every((i) => {
1354
1378
  const block = ctx.doc.getBlock(i);
@@ -2291,6 +2315,45 @@ var EditorHistory = class {
2291
2315
  this.undoManager.destroy();
2292
2316
  }
2293
2317
  };
2318
+
2319
+ // src/migrations.ts
2320
+ function coletar(yText, alvos) {
2321
+ if (!yText) return 0;
2322
+ const delta = yText.toDelta();
2323
+ const ranges = [];
2324
+ let index = 0;
2325
+ for (const op of delta) {
2326
+ const len = typeof op.insert === "string" ? op.insert.length : 1;
2327
+ if (typeof op.insert === "string" && op.attributes && "fontFamily" in op.attributes) {
2328
+ ranges.push([index, len]);
2329
+ }
2330
+ index += len;
2331
+ }
2332
+ if (ranges.length > 0) alvos.push({ yText, ranges });
2333
+ return ranges.length;
2334
+ }
2335
+ function stripFontFamilyMarks(doc, opts) {
2336
+ const alvos = [];
2337
+ let total = 0;
2338
+ for (let i = 0; i < doc.blockCount(); i++) {
2339
+ total += coletar(doc.getBlockText(i), alvos);
2340
+ const cells = doc.getCells(i);
2341
+ if (cells) {
2342
+ for (let c = 0; c < cells.length; c++) {
2343
+ total += coletar(doc.getCellText(i, c), alvos);
2344
+ }
2345
+ }
2346
+ }
2347
+ if (total === 0 || opts?.dryRun) return total;
2348
+ doc.ydoc.transact(() => {
2349
+ for (const { yText, ranges } of alvos) {
2350
+ for (const [index, length] of ranges) {
2351
+ yText.format(index, length, { fontFamily: null });
2352
+ }
2353
+ }
2354
+ }, "migration");
2355
+ return total;
2356
+ }
2294
2357
  export {
2295
2358
  ANSWER_LINE_COLOR,
2296
2359
  ANSWER_LINE_MAX,
@@ -2385,8 +2448,10 @@ export {
2385
2448
  sliceToText,
2386
2449
  spanOf,
2387
2450
  splitCell,
2451
+ splitDeltaByLines,
2388
2452
  splitListItem,
2389
2453
  splitUnderscoreRuns,
2454
+ stripFontFamilyMarks,
2390
2455
  styleToCssText,
2391
2456
  tableLocationOf,
2392
2457
  tableRectSelection,