@stll/folio-core 0.31.1 → 0.31.2

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.
@@ -1,5 +1,21 @@
1
1
  //#region src/ai-edits/word-diff.ts
2
- const tokenize = (s) => s.match(/\s+|\S+/gu) ?? [];
2
+ const WHITESPACE = /\s/u;
3
+ const tokenize = (s) => {
4
+ const tokens = [];
5
+ let tokenStart = 0;
6
+ let cursor = 0;
7
+ while (cursor < s.length) {
8
+ while (cursor < s.length && WHITESPACE.test(s.charAt(cursor))) cursor++;
9
+ if (cursor === s.length) break;
10
+ while (cursor < s.length && !WHITESPACE.test(s.charAt(cursor))) cursor++;
11
+ tokens.push(s.slice(tokenStart, cursor));
12
+ tokenStart = cursor;
13
+ }
14
+ const last = tokens.at(-1);
15
+ if (last === void 0) return s.length === 0 ? [] : [s];
16
+ if (tokenStart < s.length) tokens[tokens.length - 1] = last + s.slice(tokenStart);
17
+ return tokens;
18
+ };
3
19
  /**
4
20
  * Cell budget for the O(n*m) word-diff DP table below, mirroring
5
21
  * `MAX_LCS_CELLS` in `version-comparison.ts`. `before`/`after` come from
@@ -86,6 +86,7 @@ function createBilingualDocument(source, options) {
86
86
  cloner
87
87
  });
88
88
  const paraIds = createParaIdMinter(collectPackageParaIds(source.package));
89
+ const bookmarkIds = createBookmarkIdMinter(source.package);
89
90
  const rows = [];
90
91
  const content = [];
91
92
  let sectionRows = [];
@@ -97,7 +98,7 @@ function createBilingualDocument(source, options) {
97
98
  const copyParagraph = (paragraph) => {
98
99
  const targetParaId = paraIds.mint(paragraph.paraId);
99
100
  return {
100
- copy: cloneParagraphForTarget(paragraph, targetParaId, styleCloner, cloner),
101
+ copy: cloneParagraphForTarget(paragraph, targetParaId, styleCloner, cloner, bookmarkIds),
101
102
  ref: {
102
103
  sourceParaId: paragraph.paraId,
103
104
  targetParaId,
@@ -152,7 +153,8 @@ function createBilingualDocument(source, options) {
152
153
  editableParagraphIds: options.editableParagraphIds,
153
154
  paraIds,
154
155
  styleCloner,
155
- cloner
156
+ cloner,
157
+ bookmarkIds
156
158
  });
157
159
  rows.push({
158
160
  kind: "table",
@@ -362,7 +364,7 @@ const createStyleCloner = ({ styleById, suffix, cloner }) => {
362
364
  })
363
365
  };
364
366
  };
365
- const cloneParagraphForTarget = (paragraph, targetParaId, styleCloner, cloner) => {
367
+ const cloneParagraphForTarget = (paragraph, targetParaId, styleCloner, cloner, bookmarkIds) => {
366
368
  const { textId: _textId, sectionProperties: _sectionProperties, ...rest } = paragraph;
367
369
  const formatting = paragraph.formatting;
368
370
  const nextFormatting = formatting && {
@@ -379,12 +381,56 @@ const cloneParagraphForTarget = (paragraph, targetParaId, styleCloner, cloner) =
379
381
  };
380
382
  return {
381
383
  ...rest,
382
- content: structuredClone(paragraph.content),
384
+ content: remapClonedBookmarkIds(structuredClone(paragraph.content), bookmarkIds),
383
385
  paraId: targetParaId,
384
386
  ...nextFormatting && { formatting: nextFormatting },
385
387
  ...paragraph.listRendering && { listRendering: remapListRendering(paragraph.listRendering, cloner) }
386
388
  };
387
389
  };
390
+ const createBookmarkIdMinter = (source) => {
391
+ let nextId = 0;
392
+ const remapped = /* @__PURE__ */ new Map();
393
+ const visit = (value, seen) => {
394
+ if (typeof value !== "object" || value === null || seen.has(value)) return;
395
+ seen.add(value);
396
+ if (Array.isArray(value)) {
397
+ value.forEach((item) => visit(item, seen));
398
+ return;
399
+ }
400
+ const record = value;
401
+ if (record["type"] === "bookmarkStart" || record["type"] === "bookmarkEnd") {
402
+ const id = record["id"];
403
+ if (typeof id === "number") nextId = Math.max(nextId, id + 1);
404
+ }
405
+ Object.values(record).forEach((item) => visit(item, seen));
406
+ };
407
+ visit(source, /* @__PURE__ */ new Set());
408
+ return { mint: (sourceId) => {
409
+ const existing = remapped.get(sourceId);
410
+ if (existing !== void 0) return existing;
411
+ const id = nextId++;
412
+ remapped.set(sourceId, id);
413
+ return id;
414
+ } };
415
+ };
416
+ const remapClonedBookmarkIds = (value, bookmarkIds) => {
417
+ const visit = (item, seen) => {
418
+ if (typeof item !== "object" || item === null || seen.has(item)) return;
419
+ seen.add(item);
420
+ if (Array.isArray(item)) {
421
+ item.forEach((child) => visit(child, seen));
422
+ return;
423
+ }
424
+ const record = item;
425
+ if (record["type"] === "bookmarkStart" || record["type"] === "bookmarkEnd") {
426
+ const id = record["id"];
427
+ if (typeof id === "number") record["id"] = bookmarkIds.mint(id);
428
+ }
429
+ Object.values(record).forEach((child) => visit(child, seen));
430
+ };
431
+ visit(value, /* @__PURE__ */ new Set());
432
+ return value;
433
+ };
388
434
  const remapListRendering = (rendering, cloner) => {
389
435
  const clonedAbstract = rendering.abstractNumId === void 0 ? void 0 : cloner.clonedAbstractNumId(rendering.abstractNumId);
390
436
  return {
@@ -399,7 +445,7 @@ const collectTableParagraphs = (table) => {
399
445
  else out.push(...collectTableParagraphs(item));
400
446
  return out;
401
447
  };
402
- const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner, cloner }) => {
448
+ const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner, cloner, bookmarkIds }) => {
403
449
  const paragraphs = [];
404
450
  const cloneTable = (source) => ({
405
451
  ...structuredClone(source),
@@ -410,7 +456,7 @@ const cloneTableForTarget = ({ table, editableParagraphIds, paraIds, styleCloner
410
456
  content: cell.content.map((item) => {
411
457
  if (item.type === "table") return cloneTable(item);
412
458
  const targetParaId = paraIds.mint(item.paraId);
413
- const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner);
459
+ const copy = cloneParagraphForTarget(item, targetParaId, styleCloner, cloner, bookmarkIds);
414
460
  if (item.paraId !== void 0 && editableParagraphIds.has(item.paraId)) paragraphs.push({
415
461
  sourceParaId: item.paraId,
416
462
  targetParaId,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@stll/folio-core",
3
- "version": "0.31.1",
3
+ "version": "0.31.2",
4
4
  "description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
5
5
  "keywords": [
6
6
  "document-model",