@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
|
|
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.
|
|
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",
|