@stll/folio-core 0.1.3 → 0.2.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/docx/index.d.ts +2 -0
- package/dist/docx/index.js +2 -0
- package/dist/docx/wrapTypes.d.ts +8 -1
- package/dist/docx/wrapTypes.js +0 -8
- package/dist/i18n/messages/catalogs.gen.d.ts +2776 -0
- package/dist/i18n/messages/catalogs.gen.js +2742 -0
- package/dist/i18n/messages/messages.gen.d.ts +166 -0
- package/dist/i18n/messages/messages.gen.js +0 -0
- package/dist/i18n/messages.d.ts +13 -0
- package/dist/i18n/messages.js +39 -0
- package/dist/layout-bridge/cellSelectionHighlight.d.ts +18 -0
- package/dist/layout-bridge/cellSelectionHighlight.js +36 -0
- package/dist/layout-bridge/headerFooterLayout.d.ts +50 -0
- package/dist/layout-bridge/headerFooterLayout.js +216 -0
- package/dist/layout-bridge/measuring/measureBlocksPipeline.d.ts +33 -0
- package/dist/layout-bridge/measuring/measureBlocksPipeline.js +0 -0
- package/dist/layout-bridge/sectionColumns.d.ts +11 -0
- package/dist/layout-bridge/sectionColumns.js +21 -0
- package/dist/layout-bridge/tableInsertHover.d.ts +42 -0
- package/dist/layout-bridge/tableInsertHover.js +102 -0
- package/dist/layout-engine/index.d.ts +2 -1
- package/dist/layout-engine/index.js +2 -1
- package/dist/layout-engine/measure/measureParagraph.js +40 -5
- package/dist/layout-engine/pmPageIndex.d.ts +24 -0
- package/dist/layout-engine/pmPageIndex.js +37 -0
- package/dist/layout-painter/imageLayout.d.ts +101 -0
- package/dist/layout-painter/imageLayout.js +161 -0
- package/dist/layout-painter/renderPage.d.ts +16 -1
- package/dist/layout-painter/renderPage.js +28 -1
- package/dist/layout-painter/renderParagraph.js +1 -1
- package/dist/layout-painter/sdtBoundary.d.ts +24 -1
- package/dist/layout-painter/sdtBoundary.js +40 -1
- package/dist/managers/AutoSaveManager.d.ts +89 -0
- package/dist/managers/AutoSaveManager.js +279 -0
- package/dist/managers/ClipboardManager.d.ts +31 -0
- package/dist/managers/ClipboardManager.js +147 -0
- package/dist/paged-layout/rangeProjection.d.ts +23 -1
- package/dist/paged-layout/rangeProjection.js +34 -1
- package/dist/prosemirror/cellDragSelection.d.ts +34 -0
- package/dist/prosemirror/cellDragSelection.js +69 -0
- package/dist/prosemirror/commands/sectionBreak.d.ts +15 -0
- package/dist/prosemirror/commands/sectionBreak.js +54 -0
- package/dist/prosemirror/commentIdAllocator.d.ts +32 -0
- package/dist/prosemirror/commentIdAllocator.js +35 -0
- package/dist/prosemirror/commentOps.d.ts +35 -0
- package/dist/prosemirror/commentOps.js +104 -0
- package/dist/prosemirror/conversion/toProseDoc.js +29 -6
- package/dist/prosemirror/extensions/index.d.ts +3 -0
- package/dist/prosemirror/extensions/index.js +3 -0
- package/dist/prosemirror/imageCommit.d.ts +42 -0
- package/dist/prosemirror/imageCommit.js +118 -0
- package/dist/prosemirror/paraText.d.ts +26 -0
- package/dist/prosemirror/paraText.js +73 -0
- package/dist/prosemirror/plugins/templateDirectives.d.ts +21 -1
- package/dist/prosemirror/plugins/templateDirectives.js +40 -1
- package/dist/prosemirror/queries.d.ts +46 -0
- package/dist/prosemirror/queries.js +74 -0
- package/dist/prosemirror/styles/styleResolver.d.ts +43 -1
- package/dist/prosemirror/styles/styleResolver.js +34 -0
- package/dist/prosemirror/tableResize.d.ts +49 -0
- package/dist/prosemirror/tableResize.js +162 -0
- package/dist/prosemirror/utils/extractTrackedChanges.d.ts +106 -0
- package/dist/prosemirror/utils/extractTrackedChanges.js +379 -0
- package/dist/prosemirror/utils/visualLineNavigation.d.ts +27 -0
- package/dist/prosemirror/utils/visualLineNavigation.js +217 -0
- package/dist/style-engine/index.d.ts +2 -2
- package/dist/style-engine/styleEngine.d.ts +14 -1
- package/dist/style-engine/styleEngine.js +15 -0
- package/dist/utils/colorResolver.d.ts +8 -1
- package/dist/utils/colorResolver.js +12 -1
- package/dist/utils/findVerticalScrollParent.d.ts +20 -0
- package/dist/utils/findVerticalScrollParent.js +30 -0
- package/dist/utils/fontResolver.d.ts +22 -3
- package/dist/utils/fontResolver.js +216 -31
- package/package.json +9 -1
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
//#region src/prosemirror/tableResize.ts
|
|
2
|
+
/** 1px ≈ 15 twips at 96dpi (20 twips/pt × 72pt/in ÷ 96px/in). */
|
|
3
|
+
const TWIPS_PER_PIXEL = 15;
|
|
4
|
+
/** Minimum column width (~0.2"). */
|
|
5
|
+
const MIN_CELL_WIDTH_TWIPS = 300;
|
|
6
|
+
/** Minimum row height (~0.14"). */
|
|
7
|
+
const MIN_ROW_HEIGHT_TWIPS = 200;
|
|
8
|
+
/** Walk up from `pmStart + 1` to find the enclosing table node. */
|
|
9
|
+
function findTableAt(view, pmStart) {
|
|
10
|
+
try {
|
|
11
|
+
const $pos = view.state.doc.resolve(pmStart + 1);
|
|
12
|
+
for (let d = $pos.depth; d >= 0; d--) {
|
|
13
|
+
const node = $pos.node(d);
|
|
14
|
+
if (node.type.name === "table") return {
|
|
15
|
+
node,
|
|
16
|
+
tablePos: $pos.before(d)
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
} catch {}
|
|
20
|
+
return null;
|
|
21
|
+
}
|
|
22
|
+
/** Read the [left, right] column widths at `colIndex` in the table starting at `pmStart`. */
|
|
23
|
+
function readColumnWidths(view, pmStart, colIndex) {
|
|
24
|
+
const found = findTableAt(view, pmStart);
|
|
25
|
+
if (!found) return null;
|
|
26
|
+
const widths = found.node.attrs["columnWidths"];
|
|
27
|
+
if (!widths) return null;
|
|
28
|
+
const left = widths[colIndex];
|
|
29
|
+
const right = widths[colIndex + 1];
|
|
30
|
+
if (left === void 0 || right === void 0) return null;
|
|
31
|
+
return {
|
|
32
|
+
left,
|
|
33
|
+
right
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* Read the row height (in twips) for `rowIndex` in the table starting at
|
|
38
|
+
* `pmStart`. Returns null if the row has no explicit height — the caller
|
|
39
|
+
* can fall back to measuring the rendered DOM cell.
|
|
40
|
+
*/
|
|
41
|
+
function readRowHeight(view, pmStart, rowIndex) {
|
|
42
|
+
const found = findTableAt(view, pmStart);
|
|
43
|
+
if (!found) return null;
|
|
44
|
+
let rowHeight = null;
|
|
45
|
+
let idx = 0;
|
|
46
|
+
found.node.forEach((child) => {
|
|
47
|
+
if (idx === rowIndex) {
|
|
48
|
+
const h = child.attrs["height"];
|
|
49
|
+
if (h) rowHeight = h;
|
|
50
|
+
}
|
|
51
|
+
idx++;
|
|
52
|
+
});
|
|
53
|
+
return rowHeight;
|
|
54
|
+
}
|
|
55
|
+
/** Read the last-column width (the one being resized from the table's right edge). */
|
|
56
|
+
function readColumnWidthAt(view, pmStart, colIndex) {
|
|
57
|
+
const found = findTableAt(view, pmStart);
|
|
58
|
+
if (!found) return null;
|
|
59
|
+
const widths = found.node.attrs["columnWidths"];
|
|
60
|
+
if (!widths) return null;
|
|
61
|
+
const width = widths[colIndex];
|
|
62
|
+
if (width === void 0) return null;
|
|
63
|
+
return width;
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
66
|
+
* Bake a column-resize into the PM doc: update the table's `columnWidths`
|
|
67
|
+
* attr and every cell at `colIdx` / `colIdx + 1` in every row.
|
|
68
|
+
*/
|
|
69
|
+
function commitColumnResize(view, opts) {
|
|
70
|
+
const { pmStart, colIdx, newLeft, newRight } = opts;
|
|
71
|
+
const found = findTableAt(view, pmStart);
|
|
72
|
+
if (!found) return;
|
|
73
|
+
const { node, tablePos } = found;
|
|
74
|
+
const tr = view.state.tr;
|
|
75
|
+
const widths = [...node.attrs["columnWidths"] ?? []];
|
|
76
|
+
widths[colIdx] = newLeft;
|
|
77
|
+
widths[colIdx + 1] = newRight;
|
|
78
|
+
tr.setNodeMarkup(tablePos, void 0, {
|
|
79
|
+
...node.attrs,
|
|
80
|
+
columnWidths: widths,
|
|
81
|
+
tableLayout: "fixed"
|
|
82
|
+
});
|
|
83
|
+
let rowOffset = tablePos + 1;
|
|
84
|
+
node.forEach((row) => {
|
|
85
|
+
let cellOffset = rowOffset + 1;
|
|
86
|
+
let cellColIdx = 0;
|
|
87
|
+
row.forEach((cell) => {
|
|
88
|
+
const colspan = cell.attrs["colspan"] || 1;
|
|
89
|
+
if (cellColIdx === colIdx || cellColIdx === colIdx + 1) {
|
|
90
|
+
const newWidth = cellColIdx === colIdx ? newLeft : newRight;
|
|
91
|
+
tr.setNodeMarkup(tr.mapping.map(cellOffset), void 0, {
|
|
92
|
+
...cell.attrs,
|
|
93
|
+
width: newWidth,
|
|
94
|
+
widthType: "dxa",
|
|
95
|
+
colwidth: null
|
|
96
|
+
});
|
|
97
|
+
}
|
|
98
|
+
cellOffset += cell.nodeSize;
|
|
99
|
+
cellColIdx += colspan;
|
|
100
|
+
});
|
|
101
|
+
rowOffset += row.nodeSize;
|
|
102
|
+
});
|
|
103
|
+
view.dispatch(tr);
|
|
104
|
+
}
|
|
105
|
+
/** Bake a row-resize into the PM doc: update the target row's `height` + `heightRule`. */
|
|
106
|
+
function commitRowResize(view, opts) {
|
|
107
|
+
const { pmStart, rowIdx, newHeight } = opts;
|
|
108
|
+
const found = findTableAt(view, pmStart);
|
|
109
|
+
if (!found) return;
|
|
110
|
+
const { node, tablePos } = found;
|
|
111
|
+
const tr = view.state.tr;
|
|
112
|
+
let rowOffset = tablePos + 1;
|
|
113
|
+
let idx = 0;
|
|
114
|
+
node.forEach((row) => {
|
|
115
|
+
if (idx === rowIdx) tr.setNodeMarkup(tr.mapping.map(rowOffset), void 0, {
|
|
116
|
+
...row.attrs,
|
|
117
|
+
height: newHeight,
|
|
118
|
+
heightRule: "atLeast"
|
|
119
|
+
});
|
|
120
|
+
rowOffset += row.nodeSize;
|
|
121
|
+
idx++;
|
|
122
|
+
});
|
|
123
|
+
view.dispatch(tr);
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Bake a right-edge resize into the PM doc: grow only the last column.
|
|
127
|
+
* Updates `columnWidths[colIdx]` and the cell at `colIdx` in every row.
|
|
128
|
+
*/
|
|
129
|
+
function commitRightEdgeResize(view, opts) {
|
|
130
|
+
const { pmStart, colIdx, newWidth } = opts;
|
|
131
|
+
const found = findTableAt(view, pmStart);
|
|
132
|
+
if (!found) return;
|
|
133
|
+
const { node, tablePos } = found;
|
|
134
|
+
const tr = view.state.tr;
|
|
135
|
+
const widths = [...node.attrs["columnWidths"] ?? []];
|
|
136
|
+
widths[colIdx] = newWidth;
|
|
137
|
+
tr.setNodeMarkup(tablePos, void 0, {
|
|
138
|
+
...node.attrs,
|
|
139
|
+
columnWidths: widths,
|
|
140
|
+
tableLayout: "fixed"
|
|
141
|
+
});
|
|
142
|
+
let rowOffset = tablePos + 1;
|
|
143
|
+
node.forEach((row) => {
|
|
144
|
+
let cellOffset = rowOffset + 1;
|
|
145
|
+
let cellColIdx = 0;
|
|
146
|
+
row.forEach((cell) => {
|
|
147
|
+
const colspan = cell.attrs["colspan"] || 1;
|
|
148
|
+
if (cellColIdx === colIdx) tr.setNodeMarkup(tr.mapping.map(cellOffset), void 0, {
|
|
149
|
+
...cell.attrs,
|
|
150
|
+
width: newWidth,
|
|
151
|
+
widthType: "dxa",
|
|
152
|
+
colwidth: null
|
|
153
|
+
});
|
|
154
|
+
cellOffset += cell.nodeSize;
|
|
155
|
+
cellColIdx += colspan;
|
|
156
|
+
});
|
|
157
|
+
rowOffset += row.nodeSize;
|
|
158
|
+
});
|
|
159
|
+
view.dispatch(tr);
|
|
160
|
+
}
|
|
161
|
+
//#endregion
|
|
162
|
+
export { MIN_CELL_WIDTH_TWIPS, MIN_ROW_HEIGHT_TWIPS, TWIPS_PER_PIXEL, commitColumnResize, commitRightEdgeResize, commitRowResize, readColumnWidthAt, readColumnWidths, readRowHeight };
|
|
@@ -0,0 +1,106 @@
|
|
|
1
|
+
import { EditorState } from "prosemirror-state";
|
|
2
|
+
|
|
3
|
+
//#region src/prosemirror/utils/extractTrackedChanges.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* One tracked change surfaced by {@link extractTrackedChanges}. Each entry
|
|
6
|
+
* groups all sites of one revision into a single row that the sidebar
|
|
7
|
+
* renders as one card.
|
|
8
|
+
*
|
|
9
|
+
* @public
|
|
10
|
+
*/
|
|
11
|
+
type TrackedChangeEntry = {
|
|
12
|
+
/**
|
|
13
|
+
* Revision shape. Inline shapes (`insertion`, `deletion`, `replacement`)
|
|
14
|
+
* wrap text runs; the rest are structural revisions on node attrs.
|
|
15
|
+
*
|
|
16
|
+
* - `insertion` — text was added (`<w:ins>`).
|
|
17
|
+
* - `deletion` — text was struck through but not removed (`<w:del>`).
|
|
18
|
+
* - `replacement` — a deletion + insertion by the same author at the
|
|
19
|
+
* same position+time; sidebar shows one combined card. `deletedText`
|
|
20
|
+
* and `insertionRevisionId` are set on this variant.
|
|
21
|
+
* - `paragraphMarkInsertion` / `paragraphMarkDeletion` — Enter /
|
|
22
|
+
* Backspace produced a tracked paragraph break (`<w:pPr><w:rPr><w:ins/>` /
|
|
23
|
+
* `<w:del/>`).
|
|
24
|
+
* - `paragraphPropertiesChanged` — formatting (alignment, spacing,
|
|
25
|
+
* etc.) on the paragraph was changed (`<w:pPrChange>`).
|
|
26
|
+
* - `rowInserted` / `rowDeleted` / `rowPropertiesChanged` — table
|
|
27
|
+
* row authored / removed / formatted (`<w:trPr><w:ins/>` / `<w:del/>`
|
|
28
|
+
* / `<w:trPrChange>`).
|
|
29
|
+
* - `cellInserted` / `cellDeleted` / `cellMerged` /
|
|
30
|
+
* `cellPropertiesChanged` — per-cell revisions
|
|
31
|
+
* (`<w:cellIns>` / `<w:cellDel>` / `<w:cellMerge>` / `<w:tcPrChange>`).
|
|
32
|
+
* - `tablePropertiesChanged` — table-level formatting
|
|
33
|
+
* (`<w:tblPrChange>`).
|
|
34
|
+
*/
|
|
35
|
+
type: "insertion" | "deletion" | "replacement" | "paragraphMarkInsertion" | "paragraphMarkDeletion" | "paragraphPropertiesChanged" | "rowInserted" | "rowDeleted" | "rowPropertiesChanged" | "cellInserted" | "cellDeleted" | "cellMerged" | "cellPropertiesChanged" | "tableInserted" | "tableDeleted" | "tablePropertiesChanged";
|
|
36
|
+
/**
|
|
37
|
+
* Affected text. For inline types this is the run's text; for
|
|
38
|
+
* structural types it's the surrounding paragraph / cell content
|
|
39
|
+
* (truncated by the sidebar before display).
|
|
40
|
+
*/
|
|
41
|
+
text: string;
|
|
42
|
+
/**
|
|
43
|
+
* Only set when `type === 'replacement'` — the text the user removed.
|
|
44
|
+
* The inserted text lives in {@link TrackedChangeEntry.text}.
|
|
45
|
+
*/
|
|
46
|
+
deletedText?: string | undefined; /** Author that minted the revision (`w:author`). */
|
|
47
|
+
author: string; /** ISO timestamp the revision was minted (`w:date`). May be undefined for legacy imports. */
|
|
48
|
+
date?: string | undefined;
|
|
49
|
+
/**
|
|
50
|
+
* Document position where the revision starts. For inline types this
|
|
51
|
+
* is the start of the marked text run; for structural types it's the
|
|
52
|
+
* containing paragraph / row / cell / table node's start position.
|
|
53
|
+
*/
|
|
54
|
+
from: number;
|
|
55
|
+
/**
|
|
56
|
+
* Document position where the revision ends. For inline coalesced
|
|
57
|
+
* runs that span multiple paragraphs, this is the END position of the
|
|
58
|
+
* LAST run in the group; the intervening structural positions are not
|
|
59
|
+
* preserved.
|
|
60
|
+
*/
|
|
61
|
+
to: number; /** The `w:id` of the revision. */
|
|
62
|
+
revisionId: number;
|
|
63
|
+
/**
|
|
64
|
+
* Only set when `type === 'replacement'` — the insertion half carries
|
|
65
|
+
* a DIFFERENT `w:id` from the deletion (sharing would trip the OOXML
|
|
66
|
+
* move-pair serializer).
|
|
67
|
+
*/
|
|
68
|
+
insertionRevisionId?: number | undefined;
|
|
69
|
+
/**
|
|
70
|
+
* Extra `w:id`s that map to the same logical revision as this card.
|
|
71
|
+
* Populated when the extractor coalesces a burst of distinct ids by
|
|
72
|
+
* (author, date) — e.g. a foreign document where the source editor
|
|
73
|
+
* minted a fresh id per atomic edit.
|
|
74
|
+
*/
|
|
75
|
+
coalescedRevisionIds?: number[] | undefined;
|
|
76
|
+
};
|
|
77
|
+
/**
|
|
78
|
+
* Output of {@link extractTrackedChanges}.
|
|
79
|
+
*
|
|
80
|
+
* @public
|
|
81
|
+
*/
|
|
82
|
+
type TrackedChangesResult = {
|
|
83
|
+
/** Tracked-change entries, sorted by document position, with adjacent same-revision entries merged. */entries: TrackedChangeEntry[];
|
|
84
|
+
/**
|
|
85
|
+
* Map of `commentId -> revisionId` for comments whose range overlaps a tracked-change mark.
|
|
86
|
+
* Consumers (DocxEditor's threading effect) use this to thread comments under their tracked change.
|
|
87
|
+
*/
|
|
88
|
+
commentToRevision: Map<number, number>;
|
|
89
|
+
};
|
|
90
|
+
/**
|
|
91
|
+
* Walk the PM doc and extract every tracked change as a flat list of
|
|
92
|
+
* `TrackedChangeEntry` plus a comment→revision overlap map. Adjacent
|
|
93
|
+
* inline marks coalesce by `(type, revisionId, author, date)`; a
|
|
94
|
+
* deletion immediately followed by an insertion (same author + same
|
|
95
|
+
* date) collapses into a single `replacement` entry; paragraph-mark
|
|
96
|
+
* cards (`paragraphMarkInsertion` / `paragraphMarkDeletion`) are
|
|
97
|
+
* hidden when an inline entry already covers their revision triple
|
|
98
|
+
* (one Accept clears every site of one conceptual change).
|
|
99
|
+
*
|
|
100
|
+
* Pure and deterministic. Returns `EMPTY_RESULT` on null state.
|
|
101
|
+
*
|
|
102
|
+
* @public
|
|
103
|
+
*/
|
|
104
|
+
declare function extractTrackedChanges(state: EditorState | null): TrackedChangesResult;
|
|
105
|
+
//#endregion
|
|
106
|
+
export { TrackedChangeEntry, TrackedChangesResult, extractTrackedChanges };
|
|
@@ -0,0 +1,379 @@
|
|
|
1
|
+
//#region src/prosemirror/utils/extractTrackedChanges.ts
|
|
2
|
+
const EMPTY_RESULT = {
|
|
3
|
+
entries: [],
|
|
4
|
+
commentToRevision: /* @__PURE__ */ new Map()
|
|
5
|
+
};
|
|
6
|
+
/**
|
|
7
|
+
* Walk the PM doc and extract every tracked change as a flat list of
|
|
8
|
+
* `TrackedChangeEntry` plus a comment→revision overlap map. Adjacent
|
|
9
|
+
* inline marks coalesce by `(type, revisionId, author, date)`; a
|
|
10
|
+
* deletion immediately followed by an insertion (same author + same
|
|
11
|
+
* date) collapses into a single `replacement` entry; paragraph-mark
|
|
12
|
+
* cards (`paragraphMarkInsertion` / `paragraphMarkDeletion`) are
|
|
13
|
+
* hidden when an inline entry already covers their revision triple
|
|
14
|
+
* (one Accept clears every site of one conceptual change).
|
|
15
|
+
*
|
|
16
|
+
* Pure and deterministic. Returns `EMPTY_RESULT` on null state.
|
|
17
|
+
*
|
|
18
|
+
* @public
|
|
19
|
+
*/
|
|
20
|
+
function extractTrackedChanges(state) {
|
|
21
|
+
if (!state) return EMPTY_RESULT;
|
|
22
|
+
const { doc, schema } = state;
|
|
23
|
+
const insertionType = schema.marks["insertion"];
|
|
24
|
+
const deletionType = schema.marks["deletion"];
|
|
25
|
+
const commentType = schema.marks["comment"];
|
|
26
|
+
if (!insertionType && !deletionType) return EMPTY_RESULT;
|
|
27
|
+
const raw = [];
|
|
28
|
+
const commentToRevision = /* @__PURE__ */ new Map();
|
|
29
|
+
const commentMetadata = /* @__PURE__ */ new Map();
|
|
30
|
+
doc.descendants((node, pos) => {
|
|
31
|
+
if (node.type.name === "paragraph") {
|
|
32
|
+
const ins = node.attrs["pPrIns"];
|
|
33
|
+
const del = node.attrs["pPrDel"];
|
|
34
|
+
if (ins) raw.push({
|
|
35
|
+
type: "paragraphMarkInsertion",
|
|
36
|
+
text: node.textContent || "",
|
|
37
|
+
author: ins.author || "",
|
|
38
|
+
date: ins.date ?? void 0,
|
|
39
|
+
from: pos,
|
|
40
|
+
to: pos + node.nodeSize,
|
|
41
|
+
revisionId: ins.revisionId
|
|
42
|
+
});
|
|
43
|
+
if (del) raw.push({
|
|
44
|
+
type: "paragraphMarkDeletion",
|
|
45
|
+
text: node.textContent || "",
|
|
46
|
+
author: del.author || "",
|
|
47
|
+
date: del.date ?? void 0,
|
|
48
|
+
from: pos,
|
|
49
|
+
to: pos + node.nodeSize,
|
|
50
|
+
revisionId: del.revisionId
|
|
51
|
+
});
|
|
52
|
+
const pPrChange = node.attrs["pPrChange"];
|
|
53
|
+
if (Array.isArray(pPrChange)) for (const entry of pPrChange) {
|
|
54
|
+
if (!entry?.info || typeof entry.info.id !== "number") continue;
|
|
55
|
+
raw.push({
|
|
56
|
+
type: "paragraphPropertiesChanged",
|
|
57
|
+
text: node.textContent || "",
|
|
58
|
+
author: entry.info.author || "",
|
|
59
|
+
date: entry.info.date ?? void 0,
|
|
60
|
+
from: pos,
|
|
61
|
+
to: pos + node.nodeSize,
|
|
62
|
+
revisionId: entry.info.id
|
|
63
|
+
});
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
if (node.type.name === "tableRow") {
|
|
67
|
+
const trIns = node.attrs["trIns"];
|
|
68
|
+
const trDel = node.attrs["trDel"];
|
|
69
|
+
if (trIns) raw.push({
|
|
70
|
+
type: "rowInserted",
|
|
71
|
+
text: node.textContent || "",
|
|
72
|
+
author: trIns.author || "",
|
|
73
|
+
date: trIns.date ?? void 0,
|
|
74
|
+
from: pos,
|
|
75
|
+
to: pos + node.nodeSize,
|
|
76
|
+
revisionId: trIns.revisionId
|
|
77
|
+
});
|
|
78
|
+
if (trDel) raw.push({
|
|
79
|
+
type: "rowDeleted",
|
|
80
|
+
text: node.textContent || "",
|
|
81
|
+
author: trDel.author || "",
|
|
82
|
+
date: trDel.date ?? void 0,
|
|
83
|
+
from: pos,
|
|
84
|
+
to: pos + node.nodeSize,
|
|
85
|
+
revisionId: trDel.revisionId
|
|
86
|
+
});
|
|
87
|
+
const trPrChange = node.attrs["trPrChange"];
|
|
88
|
+
if (Array.isArray(trPrChange)) for (const entry of trPrChange) {
|
|
89
|
+
if (!entry?.info || typeof entry.info.id !== "number") continue;
|
|
90
|
+
raw.push({
|
|
91
|
+
type: "rowPropertiesChanged",
|
|
92
|
+
text: node.textContent || "",
|
|
93
|
+
author: entry.info.author || "",
|
|
94
|
+
date: entry.info.date ?? void 0,
|
|
95
|
+
from: pos,
|
|
96
|
+
to: pos + node.nodeSize,
|
|
97
|
+
revisionId: entry.info.id
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
if (node.type.name === "tableCell" || node.type.name === "tableHeader") {
|
|
102
|
+
const cellMarker = node.attrs["cellMarker"];
|
|
103
|
+
if (cellMarker?.info && typeof cellMarker.info.revisionId === "number") {
|
|
104
|
+
const resolvedType = {
|
|
105
|
+
ins: "cellInserted",
|
|
106
|
+
del: "cellDeleted",
|
|
107
|
+
merge: "cellMerged"
|
|
108
|
+
}[cellMarker.kind];
|
|
109
|
+
if (resolvedType) raw.push({
|
|
110
|
+
type: resolvedType,
|
|
111
|
+
text: node.textContent || "",
|
|
112
|
+
author: cellMarker.info.author || "",
|
|
113
|
+
date: cellMarker.info.date ?? void 0,
|
|
114
|
+
from: pos,
|
|
115
|
+
to: pos + node.nodeSize,
|
|
116
|
+
revisionId: cellMarker.info.revisionId
|
|
117
|
+
});
|
|
118
|
+
}
|
|
119
|
+
const tcPrChange = node.attrs["tcPrChange"];
|
|
120
|
+
if (Array.isArray(tcPrChange)) for (const entry of tcPrChange) {
|
|
121
|
+
if (!entry?.info || typeof entry.info.id !== "number") continue;
|
|
122
|
+
raw.push({
|
|
123
|
+
type: "cellPropertiesChanged",
|
|
124
|
+
text: node.textContent || "",
|
|
125
|
+
author: entry.info.author || "",
|
|
126
|
+
date: entry.info.date ?? void 0,
|
|
127
|
+
from: pos,
|
|
128
|
+
to: pos + node.nodeSize,
|
|
129
|
+
revisionId: entry.info.id
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
if (node.type.name === "table") {
|
|
134
|
+
const tblPrChange = node.attrs["tblPrChange"];
|
|
135
|
+
if (Array.isArray(tblPrChange)) for (const entry of tblPrChange) {
|
|
136
|
+
if (!entry?.info || typeof entry.info.id !== "number") continue;
|
|
137
|
+
raw.push({
|
|
138
|
+
type: "tablePropertiesChanged",
|
|
139
|
+
text: "",
|
|
140
|
+
author: entry.info.author || "",
|
|
141
|
+
date: entry.info.date ?? void 0,
|
|
142
|
+
from: pos,
|
|
143
|
+
to: pos + node.nodeSize,
|
|
144
|
+
revisionId: entry.info.id
|
|
145
|
+
});
|
|
146
|
+
}
|
|
147
|
+
const firstRow = node.firstChild;
|
|
148
|
+
const firstIns = firstRow?.attrs["trIns"];
|
|
149
|
+
const firstDel = firstRow?.attrs["trDel"];
|
|
150
|
+
let sharedAttr = null;
|
|
151
|
+
if (firstIns) sharedAttr = "trIns";
|
|
152
|
+
else if (firstDel) sharedAttr = "trDel";
|
|
153
|
+
if (sharedAttr) {
|
|
154
|
+
const sharedRev = firstIns ?? firstDel;
|
|
155
|
+
let allShare = true;
|
|
156
|
+
const rowRevIds = [];
|
|
157
|
+
node.forEach((row) => {
|
|
158
|
+
if (row.type.name !== "tableRow") {
|
|
159
|
+
allShare = false;
|
|
160
|
+
return;
|
|
161
|
+
}
|
|
162
|
+
const v = row.attrs[sharedAttr];
|
|
163
|
+
if (!v || (v.author ?? "") !== (sharedRev.author ?? "") || (v.date ?? null) !== (sharedRev.date ?? null)) {
|
|
164
|
+
allShare = false;
|
|
165
|
+
return;
|
|
166
|
+
}
|
|
167
|
+
rowRevIds.push(v.revisionId);
|
|
168
|
+
});
|
|
169
|
+
if (allShare) {
|
|
170
|
+
let visibleText = "";
|
|
171
|
+
if (deletionType) node.descendants((child) => {
|
|
172
|
+
if (child.isText && !child.marks.some((m) => m.type === deletionType)) visibleText += child.text || "";
|
|
173
|
+
});
|
|
174
|
+
else visibleText = node.textContent || "";
|
|
175
|
+
const primaryId = sharedRev.revisionId;
|
|
176
|
+
const extraIds = rowRevIds.filter((id, idx) => id !== primaryId && rowRevIds.indexOf(id) === idx);
|
|
177
|
+
raw.push({
|
|
178
|
+
type: sharedAttr === "trIns" ? "tableInserted" : "tableDeleted",
|
|
179
|
+
text: visibleText,
|
|
180
|
+
author: sharedRev.author || "",
|
|
181
|
+
date: sharedRev.date ?? void 0,
|
|
182
|
+
from: pos,
|
|
183
|
+
to: pos + node.nodeSize,
|
|
184
|
+
revisionId: primaryId,
|
|
185
|
+
...extraIds.length > 0 ? { coalescedRevisionIds: extraIds } : {}
|
|
186
|
+
});
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
if (!node.isInline) return;
|
|
191
|
+
let inlineText;
|
|
192
|
+
if (node.isText) inlineText = node.text || "";
|
|
193
|
+
else if (node.type.name === "image") inlineText = node.attrs["alt"] || "image";
|
|
194
|
+
else inlineText = node.type.name;
|
|
195
|
+
let tcMark = null;
|
|
196
|
+
for (const mark of node.marks) if (mark.type === insertionType || mark.type === deletionType) {
|
|
197
|
+
raw.push({
|
|
198
|
+
type: mark.type === insertionType ? "insertion" : "deletion",
|
|
199
|
+
text: inlineText,
|
|
200
|
+
author: mark.attrs["author"] || "",
|
|
201
|
+
date: mark.attrs["date"],
|
|
202
|
+
from: pos,
|
|
203
|
+
to: pos + node.nodeSize,
|
|
204
|
+
revisionId: mark.attrs["revisionId"]
|
|
205
|
+
});
|
|
206
|
+
tcMark = mark;
|
|
207
|
+
}
|
|
208
|
+
if (commentType) {
|
|
209
|
+
const commentMark = node.marks.find((m) => m.type === commentType);
|
|
210
|
+
if (commentMark) {
|
|
211
|
+
const cid = commentMark.attrs["commentId"];
|
|
212
|
+
let meta = commentMetadata.get(cid);
|
|
213
|
+
if (!meta) {
|
|
214
|
+
meta = {
|
|
215
|
+
revisions: /* @__PURE__ */ new Set(),
|
|
216
|
+
hasCleanText: false
|
|
217
|
+
};
|
|
218
|
+
commentMetadata.set(cid, meta);
|
|
219
|
+
}
|
|
220
|
+
if (tcMark) meta.revisions.add(tcMark.attrs["revisionId"]);
|
|
221
|
+
else meta.hasCleanText = true;
|
|
222
|
+
}
|
|
223
|
+
}
|
|
224
|
+
});
|
|
225
|
+
for (const [cid, meta] of commentMetadata.entries()) if (!meta.hasCleanText && meta.revisions.size === 1) {
|
|
226
|
+
const rid = meta.revisions.values().next().value;
|
|
227
|
+
commentToRevision.set(cid, rid);
|
|
228
|
+
}
|
|
229
|
+
const STRUCTURAL_PRIORITY = {
|
|
230
|
+
tableInserted: 6,
|
|
231
|
+
tableDeleted: 6,
|
|
232
|
+
tablePropertiesChanged: 5,
|
|
233
|
+
rowInserted: 4,
|
|
234
|
+
rowDeleted: 4,
|
|
235
|
+
rowPropertiesChanged: 4,
|
|
236
|
+
cellInserted: 3,
|
|
237
|
+
cellDeleted: 3,
|
|
238
|
+
cellMerged: 3,
|
|
239
|
+
cellPropertiesChanged: 3,
|
|
240
|
+
paragraphMarkInsertion: 2,
|
|
241
|
+
paragraphMarkDeletion: 2,
|
|
242
|
+
paragraphPropertiesChanged: 2
|
|
243
|
+
};
|
|
244
|
+
const isStructuralType = (t) => t in STRUCTURAL_PRIORITY;
|
|
245
|
+
const slotByKey = /* @__PURE__ */ new Map();
|
|
246
|
+
const ordered = [];
|
|
247
|
+
const mergeIds = (survivor, dropped) => {
|
|
248
|
+
const ids = new Set(survivor.coalescedRevisionIds ?? []);
|
|
249
|
+
for (const id of dropped.coalescedRevisionIds ?? []) ids.add(id);
|
|
250
|
+
ids.add(dropped.revisionId);
|
|
251
|
+
ids.delete(survivor.revisionId);
|
|
252
|
+
return [...ids];
|
|
253
|
+
};
|
|
254
|
+
for (const entry of raw) {
|
|
255
|
+
if (!isStructuralType(entry.type)) {
|
|
256
|
+
ordered.push(entry);
|
|
257
|
+
continue;
|
|
258
|
+
}
|
|
259
|
+
const key = `${entry.author}|${entry.date ?? ""}`;
|
|
260
|
+
const slot = slotByKey.get(key);
|
|
261
|
+
if (slot === void 0) {
|
|
262
|
+
slotByKey.set(key, ordered.push(entry) - 1);
|
|
263
|
+
continue;
|
|
264
|
+
}
|
|
265
|
+
const existing = ordered[slot];
|
|
266
|
+
if ((STRUCTURAL_PRIORITY[entry.type] ?? 0) > (STRUCTURAL_PRIORITY[existing.type] ?? 0)) ordered[slot] = {
|
|
267
|
+
...entry,
|
|
268
|
+
coalescedRevisionIds: mergeIds(entry, existing)
|
|
269
|
+
};
|
|
270
|
+
else ordered[slot] = {
|
|
271
|
+
...existing,
|
|
272
|
+
coalescedRevisionIds: mergeIds(existing, entry)
|
|
273
|
+
};
|
|
274
|
+
}
|
|
275
|
+
const inlineGroups = /* @__PURE__ */ new Map();
|
|
276
|
+
const merged = [];
|
|
277
|
+
for (const entry of ordered) {
|
|
278
|
+
if (!(entry.type === "insertion" || entry.type === "deletion")) {
|
|
279
|
+
merged.push({ ...entry });
|
|
280
|
+
continue;
|
|
281
|
+
}
|
|
282
|
+
const key = `${entry.type}|${entry.author}|${entry.date ?? ""}`;
|
|
283
|
+
const group = inlineGroups.get(key);
|
|
284
|
+
if (group) {
|
|
285
|
+
const sep = group.to === entry.from ? "" : " ";
|
|
286
|
+
group.text += sep + entry.text;
|
|
287
|
+
group.to = entry.to;
|
|
288
|
+
if (entry.revisionId !== group.revisionId) {
|
|
289
|
+
const ids = new Set(group.coalescedRevisionIds ?? []);
|
|
290
|
+
for (const id of entry.coalescedRevisionIds ?? []) ids.add(id);
|
|
291
|
+
ids.add(entry.revisionId);
|
|
292
|
+
ids.delete(group.revisionId);
|
|
293
|
+
group.coalescedRevisionIds = ids.size > 0 ? [...ids] : void 0;
|
|
294
|
+
}
|
|
295
|
+
} else {
|
|
296
|
+
const copy = { ...entry };
|
|
297
|
+
inlineGroups.set(key, copy);
|
|
298
|
+
merged.push(copy);
|
|
299
|
+
}
|
|
300
|
+
}
|
|
301
|
+
const final = [];
|
|
302
|
+
for (let i = 0; i < merged.length; i++) {
|
|
303
|
+
const curr = merged[i];
|
|
304
|
+
const next = merged[i + 1];
|
|
305
|
+
if (curr.type === "deletion" && next && next.type === "insertion" && curr.author === next.author && curr.date === next.date && curr.to === next.from) {
|
|
306
|
+
final.push({
|
|
307
|
+
type: "replacement",
|
|
308
|
+
text: next.text,
|
|
309
|
+
deletedText: curr.text,
|
|
310
|
+
author: curr.author,
|
|
311
|
+
date: curr.date,
|
|
312
|
+
from: curr.from,
|
|
313
|
+
to: next.to,
|
|
314
|
+
revisionId: curr.revisionId,
|
|
315
|
+
insertionRevisionId: next.revisionId
|
|
316
|
+
});
|
|
317
|
+
i++;
|
|
318
|
+
} else final.push(curr);
|
|
319
|
+
}
|
|
320
|
+
const inlineByKey = /* @__PURE__ */ new Map();
|
|
321
|
+
for (const e of final) if (e.type === "insertion" || e.type === "deletion" || e.type === "replacement") {
|
|
322
|
+
const k = `${e.author}|${e.date ?? ""}`;
|
|
323
|
+
if (!inlineByKey.has(k)) inlineByKey.set(k, e);
|
|
324
|
+
}
|
|
325
|
+
for (const e of final) {
|
|
326
|
+
if (e.type !== "paragraphMarkInsertion" && e.type !== "paragraphMarkDeletion") continue;
|
|
327
|
+
const survivor = inlineByKey.get(`${e.author}|${e.date ?? ""}`);
|
|
328
|
+
if (!survivor) continue;
|
|
329
|
+
const ids = new Set(survivor.coalescedRevisionIds ?? []);
|
|
330
|
+
for (const id of e.coalescedRevisionIds ?? []) ids.add(id);
|
|
331
|
+
ids.add(e.revisionId);
|
|
332
|
+
ids.delete(survivor.revisionId);
|
|
333
|
+
if (survivor.type === "replacement" && survivor.insertionRevisionId != null) ids.delete(survivor.insertionRevisionId);
|
|
334
|
+
survivor.coalescedRevisionIds = ids.size > 0 ? [...ids] : void 0;
|
|
335
|
+
}
|
|
336
|
+
const emptyTableEntries = /* @__PURE__ */ new Set();
|
|
337
|
+
const tableByKey = /* @__PURE__ */ new Map();
|
|
338
|
+
for (const e of final) {
|
|
339
|
+
if (e.type !== "tableInserted" && e.type !== "tableDeleted") continue;
|
|
340
|
+
const key = `${e.author}|${e.date ?? ""}`;
|
|
341
|
+
if (e.text.trim().length > 0) continue;
|
|
342
|
+
emptyTableEntries.add(e);
|
|
343
|
+
if (!tableByKey.has(key)) tableByKey.set(key, e);
|
|
344
|
+
}
|
|
345
|
+
for (const e of final) {
|
|
346
|
+
if (e.type !== "paragraphMarkInsertion" && e.type !== "paragraphMarkDeletion") continue;
|
|
347
|
+
const survivor = tableByKey.get(`${e.author}|${e.date ?? ""}`);
|
|
348
|
+
if (!survivor) continue;
|
|
349
|
+
const ids = new Set(survivor.coalescedRevisionIds ?? []);
|
|
350
|
+
for (const id of e.coalescedRevisionIds ?? []) ids.add(id);
|
|
351
|
+
ids.add(e.revisionId);
|
|
352
|
+
ids.delete(survivor.revisionId);
|
|
353
|
+
survivor.coalescedRevisionIds = ids.size > 0 ? [...ids] : void 0;
|
|
354
|
+
}
|
|
355
|
+
const inlineHosts = final.filter((e) => e.type === "insertion" || e.type === "replacement");
|
|
356
|
+
const foldedPropChanges = /* @__PURE__ */ new Set();
|
|
357
|
+
for (const e of final) {
|
|
358
|
+
if (e.type !== "paragraphPropertiesChanged") continue;
|
|
359
|
+
const host = inlineHosts.find((c) => c.author === e.author && c.from < e.to && e.from < c.to);
|
|
360
|
+
if (!host) continue;
|
|
361
|
+
const ids = new Set(mergeIds(host, e));
|
|
362
|
+
if (host.type === "replacement" && host.insertionRevisionId != null) ids.delete(host.insertionRevisionId);
|
|
363
|
+
host.coalescedRevisionIds = ids.size > 0 ? [...ids] : void 0;
|
|
364
|
+
foldedPropChanges.add(e);
|
|
365
|
+
}
|
|
366
|
+
return {
|
|
367
|
+
entries: final.filter((e) => {
|
|
368
|
+
const key = `${e.author}|${e.date ?? ""}`;
|
|
369
|
+
if (e.type === "tableInserted" || e.type === "tableDeleted") return emptyTableEntries.has(e);
|
|
370
|
+
if (e.type === "paragraphMarkInsertion" || e.type === "paragraphMarkDeletion") return !inlineByKey.has(key) && !tableByKey.has(key);
|
|
371
|
+
if (e.type === "paragraphPropertiesChanged") return !foldedPropChanges.has(e);
|
|
372
|
+
if (e.type === "insertion" || e.type === "deletion" || e.type === "replacement") return !tableByKey.has(key);
|
|
373
|
+
return true;
|
|
374
|
+
}),
|
|
375
|
+
commentToRevision
|
|
376
|
+
};
|
|
377
|
+
}
|
|
378
|
+
//#endregion
|
|
379
|
+
export { extractTrackedChanges };
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { EditorView } from "prosemirror-view";
|
|
2
|
+
|
|
3
|
+
//#region src/prosemirror/utils/visualLineNavigation.d.ts
|
|
4
|
+
/** @internal */
|
|
5
|
+
type VisualLineState = {
|
|
6
|
+
stickyX: number | null;
|
|
7
|
+
lastVisualLineIndex: number;
|
|
8
|
+
};
|
|
9
|
+
/** @internal */
|
|
10
|
+
declare function createVisualLineState(): VisualLineState;
|
|
11
|
+
/** @internal */
|
|
12
|
+
declare function getCaretClientX(container: HTMLElement, pmPos: number): number | null;
|
|
13
|
+
/** @internal */
|
|
14
|
+
declare function findLineElementAtPosition(container: HTMLElement, pmPos: number): HTMLElement | null;
|
|
15
|
+
/** @internal */
|
|
16
|
+
declare function findPositionOnLineAtClientX(lineEl: HTMLElement, clientX: number): number | null;
|
|
17
|
+
/**
|
|
18
|
+
* Handle PM ArrowUp / ArrowDown with visual-line awareness + sticky
|
|
19
|
+
* X. Returns true if the event was handled and PM should not run
|
|
20
|
+
* its default behaviour. Mutates `state` so consecutive presses
|
|
21
|
+
* keep the same sticky X.
|
|
22
|
+
*
|
|
23
|
+
* @internal
|
|
24
|
+
*/
|
|
25
|
+
declare function handleVisualLineKeyDown(state: VisualLineState, view: EditorView, event: KeyboardEvent, container: HTMLElement | null): boolean;
|
|
26
|
+
//#endregion
|
|
27
|
+
export { VisualLineState, createVisualLineState, findLineElementAtPosition, findPositionOnLineAtClientX, getCaretClientX, handleVisualLineKeyDown };
|