@stll/folio-core 0.60.0 → 0.62.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/README.md +13 -0
- package/dist/ai-edits/apply.js +139 -32
- package/dist/ai-edits/headless.d.ts +3 -0
- package/dist/ai-edits/headless.js +70 -75
- package/dist/ai-edits/note-story-inventory.d.ts +7 -0
- package/dist/ai-edits/note-story-inventory.js +11 -0
- package/dist/ai-edits/read.js +16 -9
- package/dist/ai-edits/revisionStretches.js +52 -1
- package/dist/ai-edits/table-row-column-mutations.js +6 -1
- package/dist/compare/reproducible-package.js +2 -1
- package/dist/compare/verification.js +1 -0
- package/dist/compat/eigenpal.d.ts +3 -1
- package/dist/controller/canonicalClipboard.js +2 -1
- package/dist/controller/canonicalListRendering.d.ts +4 -6
- package/dist/controller/canonicalListRendering.js +3 -157
- package/dist/controller/headerFooterEditorManager.d.ts +1 -0
- package/dist/controller/headerFooterEditorManager.js +11 -0
- package/dist/controller/hiddenEditorManager.d.ts +1 -1
- package/dist/controller/hiddenEditorManager.js +3 -1
- package/dist/controller/layoutPipeline.d.ts +3 -1
- package/dist/controller/layoutPipeline.js +13 -4
- package/dist/controller/layoutSession.d.ts +2 -1
- package/dist/controller/noteEditorManager.d.ts +1 -1
- package/dist/display-list/build/imagePrimitives.js +28 -12
- package/dist/display-list/build/paragraphPrimitives.js +15 -2
- package/dist/display-list/dom/renderDisplayListToDom.js +8 -5
- package/dist/display-list/types.d.ts +7 -3
- package/dist/docx/canonicalStoryRepack.js +3 -2
- package/dist/docx/commentTextParagraph.d.ts +6 -0
- package/dist/docx/commentTextParagraph.js +15 -0
- package/dist/docx/conformance.js +6 -1
- package/dist/docx/diagramPreview.js +2 -1
- package/dist/docx/documentParser.d.ts +13 -1
- package/dist/docx/documentParser.js +1 -1
- package/dist/docx/encryption/compoundFile.d.ts +0 -8
- package/dist/docx/encryption/compoundFile.js +2 -1
- package/dist/docx/encryption/cryptoBytes.d.ts +2 -2
- package/dist/docx/encryption/cryptoBytes.js +3 -18
- package/dist/docx/encryption/encryptionInfo.d.ts +0 -5
- package/dist/docx/encryption/encryptionInfo.js +2 -1
- package/dist/docx/ensureParaIds.js +2 -1
- package/dist/docx/groupDrawingParser.js +28 -13
- package/dist/docx/listModelNormalization.d.ts +10 -0
- package/dist/docx/listModelNormalization.js +166 -0
- package/dist/docx/metadataPrivacy.js +3 -2
- package/dist/docx/metafileRaster.js +2 -1
- package/dist/docx/packageParts.js +6 -5
- package/dist/docx/paragraphParser.js +6 -4
- package/dist/docx/parser.js +5 -4
- package/dist/docx/preservedRunContent.js +11 -2
- package/dist/docx/removeHeaderFooterParts.js +8 -7
- package/dist/docx/replyToComment.js +3 -13
- package/dist/docx/rezip.js +88 -89
- package/dist/docx/runParser.js +12 -6
- package/dist/docx/runPropertyPresence.d.ts +11 -0
- package/dist/docx/runPropertyPresence.js +14 -0
- package/dist/docx/selectiveSave.js +15 -14
- package/dist/docx/serializer/runSerializer.js +3 -2
- package/dist/docx/serializer/tableSerializer.js +2 -1
- package/dist/docx/server/applyDocxXmlPatchProposal.js +2 -2
- package/dist/docx/server/boundedArchive.js +2 -1
- package/dist/docx/server/evaluateDocxXmlPatchProposal.js +2 -1
- package/dist/docx/server/inspectDocxPackage.js +5 -1
- package/dist/docx/server/validateDocxConformance.js +2 -1
- package/dist/docx/tableParser.js +7 -4
- package/dist/docx/tableRowPropertyPresence.d.ts +9 -0
- package/dist/docx/tableRowPropertyPresence.js +15 -0
- package/dist/docx/unzip.js +2 -1
- package/dist/docx/xmlEncoding.js +6 -5
- package/dist/fonts/fontsourceFaces.js +2 -1
- package/dist/fonts/sfnt/parse.js +6 -9
- package/dist/fonts/sfnt/subset.js +2 -1
- package/dist/fonts/sfnt/tables.js +2 -3
- package/dist/fonts/sfnt/woff.js +2 -1
- package/dist/i18n/messages/ar.gen.js +1 -0
- package/dist/i18n/messages/catalogs.gen.d.ts +17 -0
- package/dist/i18n/messages/catalogs.gen.js +17 -0
- package/dist/i18n/messages/cs.gen.js +1 -0
- package/dist/i18n/messages/de.gen.js +1 -0
- package/dist/i18n/messages/en.gen.js +1 -0
- package/dist/i18n/messages/es.gen.js +1 -0
- package/dist/i18n/messages/et.gen.js +1 -0
- package/dist/i18n/messages/fr.gen.js +1 -0
- package/dist/i18n/messages/he.gen.js +1 -0
- package/dist/i18n/messages/hi.gen.js +1 -0
- package/dist/i18n/messages/hu.gen.js +1 -0
- package/dist/i18n/messages/lt.gen.js +1 -0
- package/dist/i18n/messages/lv.gen.js +1 -0
- package/dist/i18n/messages/messages.gen.d.ts +1 -0
- package/dist/i18n/messages/pl.gen.js +1 -0
- package/dist/i18n/messages/pt-BR.gen.js +1 -0
- package/dist/i18n/messages/sk.gen.js +1 -0
- package/dist/i18n/messages/tr.gen.js +1 -0
- package/dist/i18n/messages/zh-CN.gen.js +1 -0
- package/dist/index.d.ts +3 -1
- package/dist/internal/revisionResolutionInline.d.ts +2 -1
- package/dist/internal/revisionResolutionInline.js +22 -1
- package/dist/internal/wholeStoryRevisionResolution.js +24 -7
- package/dist/layout-bridge/convert/endnoteLayout.d.ts +1 -1
- package/dist/layout-bridge/convert/endnoteLayout.js +1 -3
- package/dist/layout-bridge/convert/flowConversionShared.d.ts +2 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +4 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +13 -3
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +5 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +12 -15
- package/dist/layout-bridge/convert/paragraphRuns.js +2 -1
- package/dist/layout-engine/index.js +2 -2
- package/dist/layout-engine/measure/crossRunWords.js +12 -2
- package/dist/layout-engine/noteAreaFlow.d.ts +4 -4
- package/dist/layout-engine/noteAreaFlow.js +12 -6
- package/dist/layout-engine/types.d.ts +5 -2
- package/dist/layout-painter/index.d.ts +1 -1
- package/dist/managers/AutoSaveManager.js +2 -1
- package/dist/managers/EditorModeManager.d.ts +10 -1
- package/dist/managers/EditorModeManager.js +15 -1
- package/dist/managers/FindReplaceManager.d.ts +13 -6
- package/dist/managers/FindReplaceManager.js +15 -9
- package/dist/managers/TableSelectionManager.d.ts +1 -1
- package/dist/managers/autoSaveCodec.js +2 -6
- package/dist/managers/reviewDisplayMode.d.ts +8 -0
- package/dist/managers/reviewDisplayMode.js +15 -0
- package/dist/paged-layout/editorScrollRoot.d.ts +3 -1
- package/dist/paged-layout/editorScrollRoot.js +2 -2
- package/dist/paged-layout/pageText.d.ts +11 -2
- package/dist/paged-layout/pageText.js +8 -3
- package/dist/pdf/fonts.js +5 -14
- package/dist/pdf/images.js +4 -2
- package/dist/pdf/objects.d.ts +0 -11
- package/dist/pdf/objects.js +7 -9
- package/dist/pdf/writePdf.js +2 -1
- package/dist/prosemirror/commands/comments.js +51 -13
- package/dist/prosemirror/commentRangeInlineAtoms.d.ts +40 -0
- package/dist/prosemirror/commentRangeInlineAtoms.js +33 -0
- package/dist/prosemirror/containerFinalParagraph.d.ts +8 -8
- package/dist/prosemirror/containerFinalParagraph.js +8 -8
- package/dist/prosemirror/conversion/fromProseDoc.js +14 -8
- package/dist/prosemirror/conversion/toProseDoc.js +16 -23
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +16 -2
- package/dist/prosemirror/fieldRepresentation.js +4 -2
- package/dist/prosemirror/findReplaceSelection.d.ts +48 -2
- package/dist/prosemirror/findReplaceSelection.js +153 -38
- package/dist/prosemirror/inlineDisplayText.d.ts +10 -0
- package/dist/prosemirror/inlineDisplayText.js +29 -0
- package/dist/prosemirror/markupViewProjection.d.ts +10 -1
- package/dist/prosemirror/markupViewProjection.js +21 -3
- package/dist/prosemirror/plugins/transactionInvariants.js +6 -1
- package/dist/prosemirror/replacedAnnotations.d.ts +15 -1
- package/dist/prosemirror/replacedAnnotations.js +71 -12
- package/dist/prosemirror/runFormattingReconciliation.d.ts +3 -1
- package/dist/prosemirror/runFormattingReconciliation.js +10 -2
- package/dist/prosemirror/runIdentityAcrossRevisions.d.ts +5 -7
- package/dist/prosemirror/runIdentityAcrossRevisions.js +66 -38
- package/dist/prosemirror/runPropertyChangeResolution.js +13 -1
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
- package/dist/utils/base64.d.ts +1 -31
- package/dist/utils/base64.js +19 -33
- package/dist/utils/clipboard.js +5 -4
- package/dist/utils/colorResolver.js +11 -2
- package/dist/utils/findMatchView.d.ts +32 -0
- package/dist/utils/findMatchView.js +406 -0
- package/dist/utils/findReplace.d.ts +67 -4
- package/dist/utils/findReplace.js +359 -37
- package/dist/utils/findSearchKey.d.ts +29 -0
- package/dist/utils/findSearchKey.js +34 -0
- package/dist/utils/replaceText.js +138 -94
- package/dist/utils/sliceText.d.ts +2 -0
- package/dist/utils/sliceText.js +2 -0
- package/dist/utils/textEncoding.d.ts +2 -0
- package/dist/utils/textEncoding.js +2 -0
- package/dist/utils/tiffConverter.d.ts +0 -9
- package/dist/utils/tiffConverter.js +2 -4
- package/package.json +51 -25
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { createTextStreamRange, measurePaintedSpanRange } from "../layout-bridge/dom/textStreamDom.js";
|
|
2
|
+
import { selectSectionHeaderFooterRIds } from "../layout-painter/renderPage.js";
|
|
3
|
+
import { getEditorScrollRoot, scrollEditorElementIntoView } from "../paged-layout/editorScrollRoot.js";
|
|
4
|
+
import { headerFooterToProseDoc, toProseDoc } from "../prosemirror/conversion/toProseDoc.js";
|
|
5
|
+
import { resolveFindMatchRanges } from "../prosemirror/findReplaceSelection.js";
|
|
6
|
+
import { OP_STORIES, documentStories, storyBody } from "@stll/docx-core/ops";
|
|
7
|
+
//#region src/utils/findMatchView.ts
|
|
8
|
+
const storyKey = (story) => {
|
|
9
|
+
if (story === OP_STORIES.MAIN) return "body";
|
|
10
|
+
switch (story.kind) {
|
|
11
|
+
case "header":
|
|
12
|
+
case "footer": return `${story.kind}:${story.rId}`;
|
|
13
|
+
case "footnote":
|
|
14
|
+
case "endnote": return `${story.kind}:${story.id}`;
|
|
15
|
+
default: return story;
|
|
16
|
+
}
|
|
17
|
+
};
|
|
18
|
+
/** The same page variant selection used by the painter, including unmounted pages. */
|
|
19
|
+
const getRenderedSearchStories = (layout) => {
|
|
20
|
+
const stories = /* @__PURE__ */ new Map();
|
|
21
|
+
for (const page of layout?.pages ?? []) {
|
|
22
|
+
const { headerRId, footerRId } = selectSectionHeaderFooterRIds(page);
|
|
23
|
+
if (headerRId !== void 0) stories.set(`header:${headerRId}`, {
|
|
24
|
+
kind: "header",
|
|
25
|
+
rId: headerRId
|
|
26
|
+
});
|
|
27
|
+
if (footerRId !== void 0) stories.set(`footer:${footerRId}`, {
|
|
28
|
+
kind: "footer",
|
|
29
|
+
rId: footerRId
|
|
30
|
+
});
|
|
31
|
+
}
|
|
32
|
+
return [...stories.values()];
|
|
33
|
+
};
|
|
34
|
+
/** Resolve each story once; box positions are local to their parent story. */
|
|
35
|
+
const resolveDocumentFindRanges = ({ document, bodyDoc, matches }) => {
|
|
36
|
+
const resolved = [];
|
|
37
|
+
const { styles, theme, numbering } = document.package;
|
|
38
|
+
const conversionOptions = {
|
|
39
|
+
...styles !== void 0 && { styles },
|
|
40
|
+
...theme !== void 0 && { theme },
|
|
41
|
+
...numbering !== void 0 && { numbering }
|
|
42
|
+
};
|
|
43
|
+
const byStory = /* @__PURE__ */ new Map();
|
|
44
|
+
for (const [index, match] of matches.entries()) {
|
|
45
|
+
if (match.commentId !== void 0) continue;
|
|
46
|
+
if (match.authoredRange && match.textBoxIndex === void 0) {
|
|
47
|
+
resolved.push({
|
|
48
|
+
...match.authoredRange,
|
|
49
|
+
index,
|
|
50
|
+
story: OP_STORIES.MAIN
|
|
51
|
+
});
|
|
52
|
+
continue;
|
|
53
|
+
}
|
|
54
|
+
const key = storyKey(match.story ?? OP_STORIES.MAIN);
|
|
55
|
+
const group = byStory.get(key);
|
|
56
|
+
if (group) group.push({
|
|
57
|
+
match,
|
|
58
|
+
index
|
|
59
|
+
});
|
|
60
|
+
else byStory.set(key, [{
|
|
61
|
+
match,
|
|
62
|
+
index
|
|
63
|
+
}]);
|
|
64
|
+
}
|
|
65
|
+
for (const story of documentStories(document)) {
|
|
66
|
+
const entries = byStory.get(storyKey(story)) ?? [];
|
|
67
|
+
if (entries.length === 0) continue;
|
|
68
|
+
const doc = story === OP_STORIES.MAIN ? bodyDoc ?? toProseDoc(document) : headerFooterToProseDoc(storyBody(document, story).content, conversionOptions);
|
|
69
|
+
const groups = /* @__PURE__ */ new Map();
|
|
70
|
+
for (const entry of entries) {
|
|
71
|
+
const group = groups.get(entry.match.textBoxIndex);
|
|
72
|
+
if (group) group.push(entry);
|
|
73
|
+
else groups.set(entry.match.textBoxIndex, [entry]);
|
|
74
|
+
}
|
|
75
|
+
const boxes = [];
|
|
76
|
+
doc.descendants((node, pos) => {
|
|
77
|
+
if (node.type.name === "textBox") boxes.push({
|
|
78
|
+
doc: node,
|
|
79
|
+
offset: pos + 1
|
|
80
|
+
});
|
|
81
|
+
return true;
|
|
82
|
+
});
|
|
83
|
+
for (const [boxIndex, group] of groups) {
|
|
84
|
+
const source = boxIndex === void 0 ? {
|
|
85
|
+
doc,
|
|
86
|
+
offset: 0
|
|
87
|
+
} : boxes.at(boxIndex);
|
|
88
|
+
if (!source) continue;
|
|
89
|
+
const ranges = resolveFindMatchRanges(source.doc, group.map(({ match }) => match));
|
|
90
|
+
for (const [index, range] of ranges.entries()) {
|
|
91
|
+
const entry = group.at(index);
|
|
92
|
+
if (range && entry) resolved.push({
|
|
93
|
+
from: range.from + source.offset,
|
|
94
|
+
to: range.to + source.offset,
|
|
95
|
+
...range.excludedRanges && { excludedRanges: range.excludedRanges.map((anchor) => ({
|
|
96
|
+
from: anchor.from + source.offset,
|
|
97
|
+
to: anchor.to + source.offset
|
|
98
|
+
})) },
|
|
99
|
+
...range.atomicTextRanges && { atomicTextRanges: range.atomicTextRanges.map((atom) => ({
|
|
100
|
+
from: atom.from + source.offset,
|
|
101
|
+
to: atom.to + source.offset,
|
|
102
|
+
startOffset: atom.startOffset,
|
|
103
|
+
endOffset: atom.endOffset
|
|
104
|
+
})) },
|
|
105
|
+
index: entry.index,
|
|
106
|
+
story
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
return resolved;
|
|
112
|
+
};
|
|
113
|
+
const spanStoryKey = (span) => {
|
|
114
|
+
const story = span.dataset["story"];
|
|
115
|
+
if (story === "header" || story === "footer") return `${story}:${span.dataset["hfRid"]}`;
|
|
116
|
+
if (story === "footnote" || story === "endnote") return `${story}:${span.dataset["noteId"]}`;
|
|
117
|
+
const slot = span.closest("[data-hf-slot-kind][data-hf-rid], [data-note-kind][data-note-id]");
|
|
118
|
+
if (slot?.dataset["hfSlotKind"]) return `${slot.dataset["hfSlotKind"]}:${slot.dataset["hfRid"]}`;
|
|
119
|
+
if (slot?.dataset["noteKind"]) return `${slot.dataset["noteKind"]}:${slot.dataset["noteId"]}`;
|
|
120
|
+
return "body";
|
|
121
|
+
};
|
|
122
|
+
/** CSS clipping can hide a nonzero range; scroll roots stay revealable off-screen. */
|
|
123
|
+
const clipPaintedRects = (source, rects) => {
|
|
124
|
+
const root = getEditorScrollRoot(source);
|
|
125
|
+
const clips = [];
|
|
126
|
+
for (let parent = source.parentElement; parent && parent !== root; parent = parent.parentElement) {
|
|
127
|
+
const style = getComputedStyle(parent);
|
|
128
|
+
const x = style.overflowX === "hidden" || style.overflowX === "clip";
|
|
129
|
+
const y = style.overflowY === "hidden" || style.overflowY === "clip";
|
|
130
|
+
if (x || y) clips.push({
|
|
131
|
+
box: parent.getBoundingClientRect(),
|
|
132
|
+
x,
|
|
133
|
+
y
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
return rects.flatMap((rect) => {
|
|
137
|
+
let { left, right, top, bottom } = rect;
|
|
138
|
+
for (const { box, x, y } of clips) {
|
|
139
|
+
if (x) {
|
|
140
|
+
left = Math.max(left, box.left);
|
|
141
|
+
right = Math.min(right, box.right);
|
|
142
|
+
}
|
|
143
|
+
if (y) {
|
|
144
|
+
top = Math.max(top, box.top);
|
|
145
|
+
bottom = Math.min(bottom, box.bottom);
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
return right > left && bottom > top ? [new DOMRect(left, top, right - left, bottom - top)] : [];
|
|
149
|
+
});
|
|
150
|
+
};
|
|
151
|
+
/** Paint search geometry over the existing page DOM, without changing editor state. */
|
|
152
|
+
const createFindMatchView = (options) => {
|
|
153
|
+
const { container } = options;
|
|
154
|
+
const ranges = resolveDocumentFindRanges(options);
|
|
155
|
+
const overlay = container.ownerDocument.createElement("div");
|
|
156
|
+
overlay.dataset["folioFindOverlay"] = "";
|
|
157
|
+
overlay.setAttribute("aria-hidden", "true");
|
|
158
|
+
Object.assign(overlay.style, {
|
|
159
|
+
position: "absolute",
|
|
160
|
+
inset: "0",
|
|
161
|
+
pointerEvents: "none",
|
|
162
|
+
zIndex: "2"
|
|
163
|
+
});
|
|
164
|
+
container.append(overlay);
|
|
165
|
+
const commentRoot = options.commentRoot ?? container;
|
|
166
|
+
const commentOverlay = options.matches.some((match) => match.commentId !== void 0) ? container.ownerDocument.createElement("div") : null;
|
|
167
|
+
if (commentOverlay) {
|
|
168
|
+
commentOverlay.dataset["folioFindCommentOverlay"] = "";
|
|
169
|
+
commentOverlay.setAttribute("aria-hidden", "true");
|
|
170
|
+
Object.assign(commentOverlay.style, {
|
|
171
|
+
position: "absolute",
|
|
172
|
+
inset: "0",
|
|
173
|
+
pointerEvents: "none",
|
|
174
|
+
zIndex: "110"
|
|
175
|
+
});
|
|
176
|
+
commentRoot.append(commentOverlay);
|
|
177
|
+
}
|
|
178
|
+
const marks = /* @__PURE__ */ new Map();
|
|
179
|
+
const targets = /* @__PURE__ */ new Map();
|
|
180
|
+
let currentIndex = 0;
|
|
181
|
+
let pendingReveal = null;
|
|
182
|
+
let frame = null;
|
|
183
|
+
const window = container.ownerDocument.defaultView;
|
|
184
|
+
const emphasize = () => {
|
|
185
|
+
for (const [index, elements] of marks) for (const element of elements) {
|
|
186
|
+
const active = index === currentIndex;
|
|
187
|
+
element.dataset["current"] = String(active);
|
|
188
|
+
element.style.background = active ? "var(--doc-find-current)" : "var(--doc-find-highlight)";
|
|
189
|
+
element.style.outlineWidth = active ? "2px" : "0px";
|
|
190
|
+
element.style.outlineStyle = "solid";
|
|
191
|
+
element.style.outlineColor = "var(--doc-find-current)";
|
|
192
|
+
}
|
|
193
|
+
};
|
|
194
|
+
const repaint = () => {
|
|
195
|
+
marks.clear();
|
|
196
|
+
targets.clear();
|
|
197
|
+
const spans = /* @__PURE__ */ new Map();
|
|
198
|
+
for (const span of container.querySelectorAll(".layout-page span[data-pm-start][data-pm-end], .layout-page span[data-decoration-start][data-decoration-end]")) {
|
|
199
|
+
const key = spanStoryKey(span);
|
|
200
|
+
const group = spans.get(key);
|
|
201
|
+
if (group) group.push(span);
|
|
202
|
+
else spans.set(key, [span]);
|
|
203
|
+
}
|
|
204
|
+
for (const group of spans.values()) group.sort((a, b) => spanEnd(a) - spanEnd(b));
|
|
205
|
+
const suffixStarts = /* @__PURE__ */ new Map();
|
|
206
|
+
for (const [key, group] of spans) {
|
|
207
|
+
const starts = Array.from({ length: group.length }, () => Number.POSITIVE_INFINITY);
|
|
208
|
+
let minimum = Number.POSITIVE_INFINITY;
|
|
209
|
+
for (let index = group.length - 1; index >= 0; index--) {
|
|
210
|
+
const span = group.at(index);
|
|
211
|
+
if (span) minimum = Math.min(minimum, spanStart(span));
|
|
212
|
+
starts[index] = minimum;
|
|
213
|
+
}
|
|
214
|
+
suffixStarts.set(key, starts);
|
|
215
|
+
}
|
|
216
|
+
const fragment = container.ownerDocument.createDocumentFragment();
|
|
217
|
+
const commentFragment = container.ownerDocument.createDocumentFragment();
|
|
218
|
+
const appendRects = ({ index, rects, source, surface }) => {
|
|
219
|
+
const visibleRects = clipPaintedRects(source, rects);
|
|
220
|
+
if (visibleRects.length === 0) return;
|
|
221
|
+
const previousTarget = targets.get(index);
|
|
222
|
+
const firstRect = visibleRects.at(0);
|
|
223
|
+
if (firstRect && (!previousTarget || (previousTarget.source.compareDocumentPosition(source) & 2) !== 0)) targets.set(index, {
|
|
224
|
+
source,
|
|
225
|
+
rect: firstRect
|
|
226
|
+
});
|
|
227
|
+
const origin = surface.getBoundingClientRect();
|
|
228
|
+
const scaleX = surface.offsetWidth > 0 ? origin.width / surface.offsetWidth : 1;
|
|
229
|
+
const scaleY = surface.offsetHeight > 0 ? origin.height / surface.offsetHeight : 1;
|
|
230
|
+
const elements = marks.get(index) ?? [];
|
|
231
|
+
const viewport = getEditorScrollRoot(source)?.getBoundingClientRect();
|
|
232
|
+
for (const rect of visibleRects) {
|
|
233
|
+
const mark = container.ownerDocument.createElement("span");
|
|
234
|
+
mark.dataset["findMatchIndex"] = String(index);
|
|
235
|
+
Object.assign(mark.style, {
|
|
236
|
+
position: "absolute",
|
|
237
|
+
left: `${(rect.left - origin.left) / scaleX}px`,
|
|
238
|
+
top: `${(rect.top - origin.top) / scaleY}px`,
|
|
239
|
+
width: `${rect.width / scaleX}px`,
|
|
240
|
+
height: `${rect.height / scaleY}px`,
|
|
241
|
+
borderRadius: "2px",
|
|
242
|
+
...viewport && { clipPath: `inset(${Math.max(0, viewport.top - rect.top) / scaleY}px ${Math.max(0, rect.right - viewport.right) / scaleX}px ${Math.max(0, rect.bottom - viewport.bottom) / scaleY}px ${Math.max(0, viewport.left - rect.left) / scaleX}px)` }
|
|
243
|
+
});
|
|
244
|
+
elements.push(mark);
|
|
245
|
+
(surface === overlay ? fragment : commentFragment).append(mark);
|
|
246
|
+
}
|
|
247
|
+
marks.set(index, elements);
|
|
248
|
+
};
|
|
249
|
+
for (const range of ranges) {
|
|
250
|
+
const group = spans.get(storyKey(range.story)) ?? [];
|
|
251
|
+
let low = 0;
|
|
252
|
+
let high = group.length;
|
|
253
|
+
while (low < high) {
|
|
254
|
+
const middle = low + high >>> 1;
|
|
255
|
+
const span = group.at(middle);
|
|
256
|
+
if (span && spanEnd(span) <= range.from) low = middle + 1;
|
|
257
|
+
else high = middle;
|
|
258
|
+
}
|
|
259
|
+
for (let index = low; index < group.length; index++) {
|
|
260
|
+
if ((suffixStarts.get(storyKey(range.story))?.at(index) ?? Number.POSITIVE_INFINITY) >= range.to) break;
|
|
261
|
+
const span = group.at(index);
|
|
262
|
+
if (!span) continue;
|
|
263
|
+
const atom = range.atomicTextRanges?.find(({ from, to }) => from === spanStart(span) && to === spanEnd(span));
|
|
264
|
+
const textOffset = Number(span.dataset["decorationTextOffset"] ?? 0);
|
|
265
|
+
if (atom && (atom.endOffset <= textOffset || atom.startOffset >= textOffset + (span.textContent?.length ?? 0))) continue;
|
|
266
|
+
const atomRange = atom ? createTextStreamRange(span, Math.max(0, atom.startOffset - textOffset), Math.min(span.textContent?.length ?? 0, atom.endOffset - textOffset)) : null;
|
|
267
|
+
let intervals = [{
|
|
268
|
+
from: range.from,
|
|
269
|
+
to: range.to
|
|
270
|
+
}];
|
|
271
|
+
for (const anchor of range.excludedRanges ?? []) intervals = intervals.flatMap((interval) => {
|
|
272
|
+
if (anchor.to <= interval.from || anchor.from >= interval.to) return [interval];
|
|
273
|
+
const retained = [];
|
|
274
|
+
if (interval.from < anchor.from) retained.push({
|
|
275
|
+
from: interval.from,
|
|
276
|
+
to: anchor.from
|
|
277
|
+
});
|
|
278
|
+
if (anchor.to < interval.to) retained.push({
|
|
279
|
+
from: anchor.to,
|
|
280
|
+
to: interval.to
|
|
281
|
+
});
|
|
282
|
+
return retained;
|
|
283
|
+
});
|
|
284
|
+
const measurements = atomRange ? [{
|
|
285
|
+
type: "text",
|
|
286
|
+
rects: [...atomRange.getClientRects()]
|
|
287
|
+
}] : intervals.map(({ from, to }) => measurePaintedSpanRange(span, from, to));
|
|
288
|
+
for (const measured of measurements) {
|
|
289
|
+
if (measured.type === "outside") continue;
|
|
290
|
+
const rects = measured.type === "element" ? [measured.rect] : measured.rects;
|
|
291
|
+
appendRects({
|
|
292
|
+
index: range.index,
|
|
293
|
+
rects,
|
|
294
|
+
source: span,
|
|
295
|
+
surface: overlay
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
const commentParagraphs = [...commentRoot.querySelectorAll("[data-find-comment-id][data-find-comment-paragraph]")];
|
|
301
|
+
for (const [index, match] of options.matches.entries()) {
|
|
302
|
+
if (match.commentId === void 0) continue;
|
|
303
|
+
const paragraph = commentParagraphs.find((element) => Number(element.dataset["findCommentId"]) === match.commentId && Number(element.dataset["findCommentParagraph"]) === match.paragraphIndex);
|
|
304
|
+
if (!paragraph) continue;
|
|
305
|
+
const range = createTextStreamRange(paragraph, match.startOffset, match.endOffset);
|
|
306
|
+
if (!range) continue;
|
|
307
|
+
if (commentOverlay) appendRects({
|
|
308
|
+
index,
|
|
309
|
+
rects: [...range.getClientRects()],
|
|
310
|
+
source: paragraph,
|
|
311
|
+
surface: commentOverlay
|
|
312
|
+
});
|
|
313
|
+
}
|
|
314
|
+
overlay.replaceChildren(fragment);
|
|
315
|
+
commentOverlay?.replaceChildren(commentFragment);
|
|
316
|
+
emphasize();
|
|
317
|
+
if (pendingReveal !== null) {
|
|
318
|
+
const target = targets.get(pendingReveal);
|
|
319
|
+
if (target) {
|
|
320
|
+
pendingReveal = null;
|
|
321
|
+
scrollEditorElementIntoView(target.source, { rect: target.rect });
|
|
322
|
+
}
|
|
323
|
+
}
|
|
324
|
+
};
|
|
325
|
+
const schedule = () => {
|
|
326
|
+
if (frame !== null || !window) return;
|
|
327
|
+
frame = window.requestAnimationFrame(() => {
|
|
328
|
+
frame = null;
|
|
329
|
+
repaint();
|
|
330
|
+
});
|
|
331
|
+
};
|
|
332
|
+
const observer = new MutationObserver((mutations) => {
|
|
333
|
+
if (mutations.some(({ target }) => target !== overlay && !overlay.contains(target) && target !== commentOverlay && !commentOverlay?.contains(target))) schedule();
|
|
334
|
+
});
|
|
335
|
+
observer.observe(container, {
|
|
336
|
+
childList: true,
|
|
337
|
+
subtree: true,
|
|
338
|
+
attributes: true
|
|
339
|
+
});
|
|
340
|
+
if (options.commentRoot && options.commentRoot !== container) observer.observe(options.commentRoot, {
|
|
341
|
+
childList: true,
|
|
342
|
+
subtree: true,
|
|
343
|
+
attributes: true
|
|
344
|
+
});
|
|
345
|
+
const resize = new ResizeObserver(schedule);
|
|
346
|
+
resize.observe(container);
|
|
347
|
+
const scrollTargets = new Set([container, options.commentRoot].flatMap((surface) => surface ? [surface, getEditorScrollRoot(surface) ?? surface] : []));
|
|
348
|
+
for (const target of scrollTargets) target.addEventListener("scroll", schedule, true);
|
|
349
|
+
repaint();
|
|
350
|
+
const paint = (index) => {
|
|
351
|
+
currentIndex = index;
|
|
352
|
+
if (pendingReveal !== index) pendingReveal = null;
|
|
353
|
+
emphasize();
|
|
354
|
+
};
|
|
355
|
+
const reveal = (index) => {
|
|
356
|
+
pendingReveal = null;
|
|
357
|
+
repaint();
|
|
358
|
+
const target = targets.get(index);
|
|
359
|
+
pendingReveal = target ? null : index;
|
|
360
|
+
if (!target) return false;
|
|
361
|
+
scrollEditorElementIntoView(target.source, { rect: target.rect });
|
|
362
|
+
return true;
|
|
363
|
+
};
|
|
364
|
+
return {
|
|
365
|
+
paint,
|
|
366
|
+
reveal,
|
|
367
|
+
navigate: (index, activateComment) => {
|
|
368
|
+
if (!Number.isInteger(index) || index < 0) return;
|
|
369
|
+
const match = options.matches.at(index);
|
|
370
|
+
if (!match) return;
|
|
371
|
+
paint(index);
|
|
372
|
+
if (match.commentId !== void 0) activateComment?.(match.commentId);
|
|
373
|
+
if (reveal(index) || match.commentId !== void 0) return;
|
|
374
|
+
const range = ranges.find((candidate) => candidate.index === index);
|
|
375
|
+
if (!range) return;
|
|
376
|
+
const page = options.layout?.pages.find((candidate) => {
|
|
377
|
+
const story = range.story;
|
|
378
|
+
if (story === OP_STORIES.MAIN) return candidate.fragments.some(({ pmStart, pmEnd }) => pmStart !== void 0 && pmEnd !== void 0 && pmStart < range.to && pmEnd > range.from);
|
|
379
|
+
switch (story.kind) {
|
|
380
|
+
case "header":
|
|
381
|
+
case "footer": {
|
|
382
|
+
const refs = selectSectionHeaderFooterRIds(candidate);
|
|
383
|
+
return (story.kind === "header" ? refs.headerRId : refs.footerRId) === story.rId;
|
|
384
|
+
}
|
|
385
|
+
case "footnote": return candidate.footnoteIds?.includes(story.id) ?? false;
|
|
386
|
+
case "endnote": return candidate.endnoteIds?.includes(story.id) ?? false;
|
|
387
|
+
default: return story;
|
|
388
|
+
}
|
|
389
|
+
});
|
|
390
|
+
if (!page) return;
|
|
391
|
+
scrollEditorElementIntoView(container.querySelector(`[data-page-number="${page.number}"]`));
|
|
392
|
+
},
|
|
393
|
+
destroy: () => {
|
|
394
|
+
observer.disconnect();
|
|
395
|
+
resize.disconnect();
|
|
396
|
+
for (const target of scrollTargets) target.removeEventListener("scroll", schedule, true);
|
|
397
|
+
if (frame !== null) window?.cancelAnimationFrame(frame);
|
|
398
|
+
overlay.remove();
|
|
399
|
+
commentOverlay?.remove();
|
|
400
|
+
}
|
|
401
|
+
};
|
|
402
|
+
};
|
|
403
|
+
const spanStart = (span) => Number(span.dataset["pmStart"] ?? span.dataset["decorationStart"]);
|
|
404
|
+
const spanEnd = (span) => Number(span.dataset["pmEnd"] ?? span.dataset["decorationEnd"]);
|
|
405
|
+
//#endregion
|
|
406
|
+
export { createFindMatchView, getRenderedSearchStories, resolveDocumentFindRanges };
|
|
@@ -1,23 +1,81 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
2
|
import { Paragraph } from "../types/content.js";
|
|
3
|
+
import { DisplayMode } from "../managers/EditorModeManager.js";
|
|
4
|
+
import { OpStory } from "@stll/docx-core/ops";
|
|
3
5
|
//#region src/utils/findReplace.d.ts
|
|
4
|
-
type
|
|
6
|
+
type ReplaceEligibility = {
|
|
7
|
+
status: "allowed";
|
|
8
|
+
} | {
|
|
9
|
+
status: "blocked";
|
|
10
|
+
reason: "field-result" | "atomic-content" | "hidden-content" | "non-body-story";
|
|
11
|
+
};
|
|
12
|
+
type ReplaceAllSummary = {
|
|
13
|
+
replacedCount: number;
|
|
14
|
+
skippedCount: number;
|
|
15
|
+
};
|
|
16
|
+
type ParagraphFindMatch = {
|
|
5
17
|
paragraphIndex: number;
|
|
6
18
|
contentIndex: number;
|
|
19
|
+
replacement: ReplaceEligibility;
|
|
7
20
|
startOffset: number;
|
|
8
21
|
endOffset: number;
|
|
9
22
|
text: string;
|
|
23
|
+
/** Authored editor range when the match was found in a resolved markup view. */
|
|
24
|
+
authoredRange?: {
|
|
25
|
+
from: number;
|
|
26
|
+
to: number;
|
|
27
|
+
/** Navigation anchors inside the logical match, excluded from decorations. */
|
|
28
|
+
excludedRanges?: {
|
|
29
|
+
from: number;
|
|
30
|
+
to: number;
|
|
31
|
+
}[];
|
|
32
|
+
/** Character subranges inside source atoms, whose text exceeds their editor size. */
|
|
33
|
+
atomicTextRanges?: {
|
|
34
|
+
from: number;
|
|
35
|
+
to: number;
|
|
36
|
+
startOffset: number;
|
|
37
|
+
endOffset: number;
|
|
38
|
+
}[];
|
|
39
|
+
};
|
|
10
40
|
};
|
|
41
|
+
type FindMatch = ParagraphFindMatch & ({
|
|
42
|
+
/** Package story; omitted only by paragraph-local search. */
|
|
43
|
+
story?: OpStory;
|
|
44
|
+
textBoxIndex?: never;
|
|
45
|
+
anchorParagraphIndex?: never;
|
|
46
|
+
commentId?: never;
|
|
47
|
+
} | {
|
|
48
|
+
story: OpStory;
|
|
49
|
+
/** Zero-based authored shape text-body index within the package story. */
|
|
50
|
+
textBoxIndex: number;
|
|
51
|
+
/** Parent-story paragraph containing the outer shape anchor. */
|
|
52
|
+
anchorParagraphIndex: number;
|
|
53
|
+
commentId?: never;
|
|
54
|
+
} | {
|
|
55
|
+
story: "main";
|
|
56
|
+
/** Comment sidebar address; present only for opt-in comment searching. */
|
|
57
|
+
commentId: number;
|
|
58
|
+
textBoxIndex?: never;
|
|
59
|
+
anchorParagraphIndex?: never;
|
|
60
|
+
});
|
|
61
|
+
/** One replacement policy for every host; searchability does not imply editability. */
|
|
62
|
+
declare const replaceEligibility: (match: Pick<FindMatch, "story" | "textBoxIndex" | "commentId" | "replacement">) => ReplaceEligibility;
|
|
11
63
|
type FindOptions = {
|
|
12
64
|
matchCase: boolean;
|
|
13
65
|
matchWholeWord: boolean;
|
|
14
66
|
useRegex?: boolean;
|
|
67
|
+
displayMode?: DisplayMode;
|
|
68
|
+
includeComments?: boolean;
|
|
15
69
|
};
|
|
16
70
|
type FindResult = {
|
|
17
71
|
matches: FindMatch[];
|
|
18
72
|
totalCount: number;
|
|
19
73
|
currentIndex: number;
|
|
20
74
|
};
|
|
75
|
+
/** Rendering eligibility supplied by the view; matching remains owned here. */
|
|
76
|
+
type FindSearchContext = {
|
|
77
|
+
renderedStories?: readonly OpStory[];
|
|
78
|
+
};
|
|
21
79
|
declare const createDefaultFindOptions: () => FindOptions;
|
|
22
80
|
declare const escapeRegexString: (value: string) => string;
|
|
23
81
|
declare const createSearchPattern: (searchText: string, options: FindOptions) => RegExp | null;
|
|
@@ -25,7 +83,12 @@ declare const findAllMatches: (content: string, searchText: string, options: Fin
|
|
|
25
83
|
start: number;
|
|
26
84
|
end: number;
|
|
27
85
|
}>;
|
|
28
|
-
declare const findInDocument: (document: document_d_exports.Document | null | undefined, searchText: string, options: FindOptions) => FindMatch
|
|
29
|
-
|
|
86
|
+
declare const findInDocument: (document: document_d_exports.Document | null | undefined, searchText: string, options: FindOptions, context?: FindSearchContext) => Array<FindMatch & {
|
|
87
|
+
story: OpStory;
|
|
88
|
+
}>;
|
|
89
|
+
/** Authored paragraph text displayed by comment UI, using the search owner. */
|
|
90
|
+
declare const getFindParagraphText: (paragraph: Paragraph) => string;
|
|
91
|
+
declare const findInParagraph: (paragraph: Paragraph, searchText: string, options: FindOptions, paragraphIndex: number) => ParagraphFindMatch[];
|
|
92
|
+
declare const isPageDependentSearchField: (instruction: string) => boolean;
|
|
30
93
|
//#endregion
|
|
31
|
-
export { FindMatch, FindOptions, FindResult, createDefaultFindOptions, createSearchPattern, escapeRegexString, findAllMatches, findInDocument, findInParagraph };
|
|
94
|
+
export { FindMatch, FindOptions, FindResult, FindSearchContext, ReplaceAllSummary, ReplaceEligibility, createDefaultFindOptions, createSearchPattern, escapeRegexString, findAllMatches, findInDocument, findInParagraph, getFindParagraphText, isPageDependentSearchField, replaceEligibility };
|