@stll/folio-core 0.61.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/dist/ai-edits/headless.js +13 -12
- package/dist/controller/noteEditorManager.d.ts +1 -1
- package/dist/display-list/build/imagePrimitives.js +26 -7
- 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/groupDrawingParser.js +22 -12
- 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/layout-bridge/convert/endnoteLayout.d.ts +1 -1
- package/dist/layout-bridge/convert/endnoteLayout.js +1 -3
- package/dist/layout-engine/index.js +2 -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/FindReplaceManager.d.ts +13 -6
- package/dist/managers/FindReplaceManager.js +15 -9
- package/dist/paged-layout/editorScrollRoot.d.ts +3 -1
- package/dist/paged-layout/editorScrollRoot.js +2 -2
- package/dist/pdf/images.js +1 -0
- package/dist/prosemirror/commentRangeInlineAtoms.d.ts +40 -0
- package/dist/prosemirror/commentRangeInlineAtoms.js +33 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +5 -4
- package/dist/prosemirror/conversion/toProseDoc.js +12 -19
- package/dist/prosemirror/extensions/nodes/CommentReferenceExtension.js +4 -4
- package/dist/prosemirror/fieldRepresentation.js +4 -2
- package/dist/prosemirror/findReplaceSelection.d.ts +38 -2
- package/dist/prosemirror/findReplaceSelection.js +143 -42
- package/dist/prosemirror/inlineDisplayText.d.ts +10 -0
- package/dist/prosemirror/inlineDisplayText.js +29 -0
- package/dist/prosemirror/trackedRunInlineAtoms.d.ts +1 -1
- package/dist/prosemirror/trackedRunInlineAtoms.js +1 -1
- package/dist/utils/findMatchView.d.ts +32 -0
- package/dist/utils/findMatchView.js +406 -0
- package/dist/utils/findReplace.d.ts +62 -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 -111
- package/package.json +1 -1
|
@@ -362,6 +362,7 @@ const messages = { "folio": {
|
|
|
362
362
|
"previous": "ההתאמה הקודמת",
|
|
363
363
|
"previousShortcut": "ההתאמה הקודמת (Shift+Enter)",
|
|
364
364
|
"replaceAll": "החלף הכל",
|
|
365
|
+
"replaceAllSummary": "הוחלפו {replacedCount}, דולגו {skippedCount}",
|
|
365
366
|
"replaceCurrent": "החלף את התוצאה הנוכחית",
|
|
366
367
|
"replacePlaceholder": "טקסט חלופי...",
|
|
367
368
|
"replaceText": "טקסט חלופי",
|
|
@@ -362,6 +362,7 @@ const messages = { "folio": {
|
|
|
362
362
|
"previous": "पिछला मिलान",
|
|
363
363
|
"previousShortcut": "पिछला मिलान (Shift+Enter)",
|
|
364
364
|
"replaceAll": "सभी बदलें",
|
|
365
|
+
"replaceAllSummary": "{replacedCount} बदले गए, {skippedCount} छोड़े गए",
|
|
365
366
|
"replaceCurrent": "वर्तमान मिलान को बदलें",
|
|
366
367
|
"replacePlaceholder": "बदलने वाला पाठ...",
|
|
367
368
|
"replaceText": "बदलने वाला पाठ",
|
|
@@ -362,6 +362,7 @@ const messages = { "folio": {
|
|
|
362
362
|
"previous": "Előző találat",
|
|
363
363
|
"previousShortcut": "Előző találat (Shift+Enter)",
|
|
364
364
|
"replaceAll": "Összes cseréje",
|
|
365
|
+
"replaceAllSummary": "Cserélve: {replacedCount}, kihagyva: {skippedCount}",
|
|
365
366
|
"replaceCurrent": "Jelenlegi találat cseréje",
|
|
366
367
|
"replacePlaceholder": "Csereszöveg…",
|
|
367
368
|
"replaceText": "Csereszöveg",
|
|
@@ -362,6 +362,7 @@ const messages = { "folio": {
|
|
|
362
362
|
"previous": "Ankstesnis atitikmuo",
|
|
363
363
|
"previousShortcut": "Ankstesnis atitikmuo (Shift+Enter)",
|
|
364
364
|
"replaceAll": "Pakeisti viską",
|
|
365
|
+
"replaceAllSummary": "Pakeista: {replacedCount}, praleista: {skippedCount}",
|
|
365
366
|
"replaceCurrent": "Pakeisti dabartinį atitikmenį",
|
|
366
367
|
"replacePlaceholder": "Pakeičiantysis tekstas…",
|
|
367
368
|
"replaceText": "Pakeičiantysis tekstas",
|
|
@@ -362,6 +362,7 @@ const messages = { "folio": {
|
|
|
362
362
|
"previous": "Iepriekšējā atbilstība",
|
|
363
363
|
"previousShortcut": "Iepriekšējā atbilstība (Shift+Enter)",
|
|
364
364
|
"replaceAll": "Aizstāt visu",
|
|
365
|
+
"replaceAllSummary": "Aizstāti: {replacedCount}, izlaisti: {skippedCount}",
|
|
365
366
|
"replaceCurrent": "Aizstāt pašreizējo atbilstību",
|
|
366
367
|
"replacePlaceholder": "Aizstājējteksts…",
|
|
367
368
|
"replaceText": "Aizstājējteksts",
|
|
@@ -363,6 +363,7 @@ type Messages = {
|
|
|
363
363
|
"previous": "Previous match";
|
|
364
364
|
"previousShortcut": "Previous match (Shift+Enter)";
|
|
365
365
|
"replaceAll": "Replace all";
|
|
366
|
+
"replaceAllSummary": "{replacedCount} replaced, {skippedCount} skipped";
|
|
366
367
|
"replaceCurrent": "Replace current match";
|
|
367
368
|
"replacePlaceholder": "Replacement text...";
|
|
368
369
|
"replaceText": "Replacement text";
|
|
@@ -362,6 +362,7 @@ const messages = { "folio": {
|
|
|
362
362
|
"previous": "Poprzednie wystąpienie",
|
|
363
363
|
"previousShortcut": "Poprzednie wystąpienie (Shift+Enter)",
|
|
364
364
|
"replaceAll": "Zamień wszystko",
|
|
365
|
+
"replaceAllSummary": "Zastąpiono: {replacedCount}, pominięto: {skippedCount}",
|
|
365
366
|
"replaceCurrent": "Zamień bieżące wystąpienie",
|
|
366
367
|
"replacePlaceholder": "Tekst zamienny...",
|
|
367
368
|
"replaceText": "Tekst zamienny",
|
|
@@ -362,6 +362,7 @@ const messages = { "folio": {
|
|
|
362
362
|
"previous": "Ocorrência anterior",
|
|
363
363
|
"previousShortcut": "Ocorrência anterior (Shift+Enter)",
|
|
364
364
|
"replaceAll": "Substituir tudo",
|
|
365
|
+
"replaceAllSummary": "{replacedCount} substituídos, {skippedCount} ignorados",
|
|
365
366
|
"replaceCurrent": "Substituir correspondência atual",
|
|
366
367
|
"replacePlaceholder": "Texto de substituição...",
|
|
367
368
|
"replaceText": "Texto de substituição",
|
|
@@ -362,6 +362,7 @@ const messages = { "folio": {
|
|
|
362
362
|
"previous": "Predchádzajúci výskyt",
|
|
363
363
|
"previousShortcut": "Predchádzajúci výskyt (Shift+Enter)",
|
|
364
364
|
"replaceAll": "Nahradiť všetko",
|
|
365
|
+
"replaceAllSummary": "Nahradené: {replacedCount}, preskočené: {skippedCount}",
|
|
365
366
|
"replaceCurrent": "Nahradiť aktuálny výskyt",
|
|
366
367
|
"replacePlaceholder": "Text náhrady...",
|
|
367
368
|
"replaceText": "Text náhrady",
|
|
@@ -362,6 +362,7 @@ const messages = { "folio": {
|
|
|
362
362
|
"previous": "Önceki eşleşme",
|
|
363
363
|
"previousShortcut": "Önceki eşleşme (Shift+Enter)",
|
|
364
364
|
"replaceAll": "Tümünü değiştir",
|
|
365
|
+
"replaceAllSummary": "{replacedCount} değiştirildi, {skippedCount} atlandı",
|
|
365
366
|
"replaceCurrent": "Mevcut eşleşmeyi değiştir",
|
|
366
367
|
"replacePlaceholder": "Yeni metin...",
|
|
367
368
|
"replaceText": "Yeni metin",
|
|
@@ -362,6 +362,7 @@ const messages = { "folio": {
|
|
|
362
362
|
"previous": "上一个匹配项",
|
|
363
363
|
"previousShortcut": "上一个匹配项 (Shift+Enter)",
|
|
364
364
|
"replaceAll": "全部替换",
|
|
365
|
+
"replaceAllSummary": "已替换 {replacedCount} 项,已跳过 {skippedCount} 项",
|
|
365
366
|
"replaceCurrent": "替换当前匹配项",
|
|
366
367
|
"replacePlaceholder": "替换为...",
|
|
367
368
|
"replaceText": "替换文本",
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { document_d_exports } from "../../types/document.js";
|
|
2
|
+
import { NoteStoryKey } from "../../types/editor-story.js";
|
|
2
3
|
import { FlowBlock, Measure, NoteAreaLayout } from "../../layout-engine/types.js";
|
|
3
4
|
import { ConvertFootnoteOptions, MeasureBlocksFn } from "./footnoteLayout.js";
|
|
4
|
-
import { NoteStoryKey } from "../../types/editor-story.js";
|
|
5
5
|
//#region src/layout-bridge/convert/endnoteLayout.d.ts
|
|
6
6
|
/** One endnote area and the body position it follows. */
|
|
7
7
|
type EndnoteArea = {
|
|
@@ -58,7 +58,6 @@ function prepareEndnoteAreas(input) {
|
|
|
58
58
|
notesBySection.set(section, noteIds);
|
|
59
59
|
}
|
|
60
60
|
const separatorBlockIds = /* @__PURE__ */ new Map();
|
|
61
|
-
const contentBlockIds = /* @__PURE__ */ new Set();
|
|
62
61
|
const noteStoryByBlockId = /* @__PURE__ */ new Map();
|
|
63
62
|
const areas = [];
|
|
64
63
|
for (const [section, noteIds] of [...notesBySection].sort(([left], [right]) => left - right)) {
|
|
@@ -82,7 +81,6 @@ function prepareEndnoteAreas(input) {
|
|
|
82
81
|
const id = `endnote-${String(noteId)}-${String(index)}`;
|
|
83
82
|
blocks.push(withId(stripFlowBlockPmAnchors(block), id));
|
|
84
83
|
measures.push(content.measures[index]);
|
|
85
|
-
contentBlockIds.add(id);
|
|
86
84
|
noteStoryByBlockId.set(id, {
|
|
87
85
|
kind: "endnote",
|
|
88
86
|
noteId
|
|
@@ -101,7 +99,7 @@ function prepareEndnoteAreas(input) {
|
|
|
101
99
|
areas,
|
|
102
100
|
noteAreas: {
|
|
103
101
|
separatorBlockIds,
|
|
104
|
-
|
|
102
|
+
noteStoryByBlockId,
|
|
105
103
|
continuationSeparator
|
|
106
104
|
},
|
|
107
105
|
noteStoryByBlockId
|
|
@@ -4,7 +4,7 @@ import { layoutImage } from "./imageLayout.js";
|
|
|
4
4
|
import { calculateChainHeight, computeKeepNextChains, getMidChainIndices, hasKeepLines, hasPageBreakBefore } from "./keep-together.js";
|
|
5
5
|
import "./layoutFlowShared.js";
|
|
6
6
|
import { measuredLineAdvance } from "./lineFlow.js";
|
|
7
|
-
import {
|
|
7
|
+
import { annotateNoteAreas, continuesNoteArea, createNoteAreaContinuation } from "./noteAreaFlow.js";
|
|
8
8
|
import { createPaginator } from "./paginator.js";
|
|
9
9
|
import { layoutParagraph } from "./paragraphLayout.js";
|
|
10
10
|
import { paragraphsShareStyle, resolveEffectiveParagraphSpacingTree } from "./paragraphSpacing.js";
|
|
@@ -63,7 +63,7 @@ function applyContextualSpacing(blocks) {
|
|
|
63
63
|
*/
|
|
64
64
|
function layoutDocument(blocks, measures, options) {
|
|
65
65
|
const initialLayout = layoutDocumentPass(blocks, measures, options);
|
|
66
|
-
return
|
|
66
|
+
return annotateNoteAreas(applySectionVerticalAlignment(options.footnoteHeightById ? reflowFootnoteColumns({
|
|
67
67
|
initialLayout,
|
|
68
68
|
...options.footnoteReservedHeights ? { initialReserveFloors: options.footnoteReservedHeights } : {},
|
|
69
69
|
runLayout: (reserveFloors) => layoutDocumentPass(blocks, measures, {
|
|
@@ -10,10 +10,10 @@ declare function createNoteAreaContinuation(noteAreas: NoteAreaLayout | undefine
|
|
|
10
10
|
/** Whether laying out `block` continues a note area onto any page it opens. */
|
|
11
11
|
declare function continuesNoteArea(noteAreas: NoteAreaLayout | undefined, block: FlowBlock): boolean;
|
|
12
12
|
/**
|
|
13
|
-
* Record where each separator mark landed. A rule is centred
|
|
14
|
-
* of the separator paragraph: a `w:separator` spans part of the column and a
|
|
13
|
+
* Record note identities and where each separator mark landed. A rule is centred
|
|
14
|
+
* on the first line of the separator paragraph: a `w:separator` spans part of the column and a
|
|
15
15
|
* `w:continuationSeparator` all of it.
|
|
16
16
|
*/
|
|
17
|
-
declare function
|
|
17
|
+
declare function annotateNoteAreas(layout: Layout, blocks: readonly FlowBlock[], measures: readonly Measure[], noteAreas: NoteAreaLayout | undefined): Layout;
|
|
18
18
|
//#endregion
|
|
19
|
-
export {
|
|
19
|
+
export { annotateNoteAreas, continuesNoteArea, createNoteAreaContinuation };
|
|
@@ -39,15 +39,15 @@ function createNoteAreaContinuation(noteAreas, paginator) {
|
|
|
39
39
|
}
|
|
40
40
|
/** Whether laying out `block` continues a note area onto any page it opens. */
|
|
41
41
|
function continuesNoteArea(noteAreas, block) {
|
|
42
|
-
return noteAreas?.
|
|
42
|
+
return noteAreas?.noteStoryByBlockId.has(String(block.id)) === true;
|
|
43
43
|
}
|
|
44
44
|
/**
|
|
45
|
-
* Record where each separator mark landed. A rule is centred
|
|
46
|
-
* of the separator paragraph: a `w:separator` spans part of the column and a
|
|
45
|
+
* Record note identities and where each separator mark landed. A rule is centred
|
|
46
|
+
* on the first line of the separator paragraph: a `w:separator` spans part of the column and a
|
|
47
47
|
* `w:continuationSeparator` all of it.
|
|
48
48
|
*/
|
|
49
|
-
function
|
|
50
|
-
if (noteAreas === void 0
|
|
49
|
+
function annotateNoteAreas(layout, blocks, measures, noteAreas) {
|
|
50
|
+
if (noteAreas === void 0) return layout;
|
|
51
51
|
const measureById = /* @__PURE__ */ new Map();
|
|
52
52
|
for (const [index, block] of blocks.entries()) {
|
|
53
53
|
const measure = measures[index];
|
|
@@ -56,6 +56,12 @@ function annotateNoteSeparators(layout, blocks, measures, noteAreas) {
|
|
|
56
56
|
const continuation = noteAreas.continuationSeparator;
|
|
57
57
|
if (continuation !== void 0) measureById.set(String(continuation.block.id), continuation.measure);
|
|
58
58
|
for (const page of layout.pages) {
|
|
59
|
+
const endnoteIds = /* @__PURE__ */ new Set();
|
|
60
|
+
for (const fragment of page.fragments) {
|
|
61
|
+
const story = noteAreas.noteStoryByBlockId.get(String(fragment.blockId));
|
|
62
|
+
if (story?.kind === "endnote") endnoteIds.add(story.noteId);
|
|
63
|
+
}
|
|
64
|
+
if (endnoteIds.size > 0) page.endnoteIds = [...endnoteIds];
|
|
59
65
|
const rules = [];
|
|
60
66
|
for (const fragment of page.fragments) {
|
|
61
67
|
if (fragment.kind !== "paragraph" || fragment.fromLine !== 0) continue;
|
|
@@ -80,4 +86,4 @@ function separatorWidthFraction(kind) {
|
|
|
80
86
|
}
|
|
81
87
|
}
|
|
82
88
|
//#endregion
|
|
83
|
-
export {
|
|
89
|
+
export { annotateNoteAreas, continuesNoteArea, createNoteAreaContinuation };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { OutlineStyleAttr } from "../types/documentEnumValues.js";
|
|
2
|
+
import { NoteStoryKey } from "../types/editor-story.js";
|
|
2
3
|
import { CssBorderStyle } from "../utils/borderCss.js";
|
|
3
4
|
import { ImagePosition, ImageWrap, InlineWrapper, NumberFormat, OutlineLevel, ParagraphNumberingOverride, PreviewDescriptor, SdtProperties, SdtType, SectionStart, ShapeTextBody, TableCellFormatting, TableWidthType, UnderlineStyle, VerticalAlign } from "@stll/docx-core/model";
|
|
4
5
|
declare namespace types_d_exports {
|
|
@@ -1268,6 +1269,8 @@ type Page = {
|
|
|
1268
1269
|
headerFooterRefs?: PageHeaderFooterRefs;
|
|
1269
1270
|
/** Footnote IDs that appear on this page (for rendering). */
|
|
1270
1271
|
footnoteIds?: number[];
|
|
1272
|
+
/** Endnote IDs whose content fragments appear on this page. */
|
|
1273
|
+
endnoteIds?: number[];
|
|
1271
1274
|
/** Height reserved for the footnote area at page bottom (pixels). */
|
|
1272
1275
|
footnoteReservedHeight?: number;
|
|
1273
1276
|
/**
|
|
@@ -1436,10 +1439,10 @@ type NoteAreaLayout = {
|
|
|
1436
1439
|
*/
|
|
1437
1440
|
separatorBlockIds: ReadonlyMap<string, NoteSeparatorKind>;
|
|
1438
1441
|
/**
|
|
1439
|
-
* Note content
|
|
1442
|
+
* Note identities keyed by content block. A page opened while one is laid out
|
|
1440
1443
|
* continues the area, so it starts with the continuation separator.
|
|
1441
1444
|
*/
|
|
1442
|
-
|
|
1445
|
+
noteStoryByBlockId: ReadonlyMap<string, NoteStoryKey>;
|
|
1443
1446
|
/** The `w:continuationSeparator` story's first paragraph, already measured. */
|
|
1444
1447
|
continuationSeparator?: {
|
|
1445
1448
|
block: ParagraphBlock;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
|
-
import { FlowBlock, Layout, Measure } from "../layout-engine/types.js";
|
|
2
1
|
import { NoteStoryKey } from "../types/editor-story.js";
|
|
2
|
+
import { FlowBlock, Layout, Measure } from "../layout-engine/types.js";
|
|
3
3
|
import { RenderContext } from "./renderUtils.js";
|
|
4
4
|
import { BlockFor, FeatureDispatchInput, FeatureFallback, FeatureModule, FeatureRenderInput, FragmentFor, MeasureFor } from "./registry/types.js";
|
|
5
5
|
import { FeatureRegistry, createFeatureRegistry } from "./registry/registry.js";
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { ReplaceAllSummary, ReplaceEligibility } from "../utils/findReplace.js";
|
|
2
3
|
import { FindMatchPosition } from "../prosemirror/findReplaceSelection.js";
|
|
4
|
+
import { OpStory } from "@stll/docx-core/ops";
|
|
3
5
|
//#region src/managers/FindReplaceManager.d.ts
|
|
4
6
|
type FindDirection = "next" | "previous";
|
|
5
7
|
type FindReplaceResult<TMatch> = {
|
|
@@ -7,16 +9,21 @@ type FindReplaceResult<TMatch> = {
|
|
|
7
9
|
totalCount: number;
|
|
8
10
|
currentIndex: number;
|
|
9
11
|
};
|
|
10
|
-
type ReplaceAllOutcome = {
|
|
12
|
+
type ReplaceAllOutcome = ReplaceAllSummary & {
|
|
11
13
|
document: document_d_exports.Document;
|
|
12
|
-
replacedCount: number;
|
|
13
14
|
};
|
|
14
15
|
/**
|
|
15
16
|
* The cursor index after stepping `direction` over `totalCount` matches,
|
|
16
17
|
* wrapping at both ends. Returns 0 when there are no matches.
|
|
17
18
|
*/
|
|
18
19
|
declare const getAdjacentFindIndex: (currentIndex: number, totalCount: number, direction: FindDirection) => number;
|
|
19
|
-
|
|
20
|
+
type ReplaceableFindPosition = FindMatchPosition & {
|
|
21
|
+
story?: OpStory;
|
|
22
|
+
textBoxIndex?: number;
|
|
23
|
+
commentId?: number;
|
|
24
|
+
replacement: ReplaceEligibility;
|
|
25
|
+
};
|
|
26
|
+
declare class FindReplaceManager<TMatch extends ReplaceableFindPosition> {
|
|
20
27
|
private result;
|
|
21
28
|
/** The active find result, or null when there is no active search. */
|
|
22
29
|
getResult(): FindReplaceResult<TMatch> | null;
|
|
@@ -27,7 +34,7 @@ declare class FindReplaceManager<TMatch extends FindMatchPosition> {
|
|
|
27
34
|
* first match. The result is kept even when `matches` is empty so callers can
|
|
28
35
|
* surface a "no results" state.
|
|
29
36
|
*/
|
|
30
|
-
setMatches(matches: TMatch[]): FindReplaceResult<TMatch>;
|
|
37
|
+
setMatches(matches: TMatch[], currentIndex?: number): FindReplaceResult<TMatch>;
|
|
31
38
|
/**
|
|
32
39
|
* Step the cursor and return the now-current match with its index, or null
|
|
33
40
|
* when there are no matches to navigate.
|
|
@@ -51,8 +58,8 @@ declare class FindReplaceManager<TMatch extends FindMatchPosition> {
|
|
|
51
58
|
replaceCurrent(document: document_d_exports.Document, replaceText: string): document_d_exports.Document | null;
|
|
52
59
|
/**
|
|
53
60
|
* Replace every match. Matches are replaced from the end of the document
|
|
54
|
-
* backwards so earlier offsets stay valid as the text shifts.
|
|
55
|
-
*
|
|
61
|
+
* backwards so earlier offsets stay valid as the text shifts. Ineligible
|
|
62
|
+
* matches are skipped and counted. Clears the active result, or returns null
|
|
56
63
|
* when there is nothing to replace.
|
|
57
64
|
*/
|
|
58
65
|
replaceAll(document: document_d_exports.Document, matches: readonly TMatch[], replaceText: string): ReplaceAllOutcome | null;
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { replaceEligibility } from "../utils/findReplace.js";
|
|
1
2
|
import { replaceTextInDocument } from "../utils/replaceText.js";
|
|
2
3
|
//#region src/managers/FindReplaceManager.ts
|
|
3
4
|
/**
|
|
@@ -34,11 +35,11 @@ var FindReplaceManager = class {
|
|
|
34
35
|
* first match. The result is kept even when `matches` is empty so callers can
|
|
35
36
|
* surface a "no results" state.
|
|
36
37
|
*/
|
|
37
|
-
setMatches(matches) {
|
|
38
|
+
setMatches(matches, currentIndex = 0) {
|
|
38
39
|
this.result = {
|
|
39
40
|
matches,
|
|
40
41
|
totalCount: matches.length,
|
|
41
|
-
currentIndex: 0
|
|
42
|
+
currentIndex: Math.max(0, Math.min(currentIndex, matches.length - 1))
|
|
42
43
|
};
|
|
43
44
|
return this.result;
|
|
44
45
|
}
|
|
@@ -86,7 +87,7 @@ var FindReplaceManager = class {
|
|
|
86
87
|
const result = this.result;
|
|
87
88
|
if (!result || result.matches.length === 0) return null;
|
|
88
89
|
const match = result.matches[result.currentIndex];
|
|
89
|
-
if (!match) return null;
|
|
90
|
+
if (!match || replaceEligibility(match).status === "blocked") return null;
|
|
90
91
|
try {
|
|
91
92
|
return replaceTextInDocument(document, toReplaceRange(match), replaceText);
|
|
92
93
|
} catch {
|
|
@@ -95,26 +96,31 @@ var FindReplaceManager = class {
|
|
|
95
96
|
}
|
|
96
97
|
/**
|
|
97
98
|
* Replace every match. Matches are replaced from the end of the document
|
|
98
|
-
* backwards so earlier offsets stay valid as the text shifts.
|
|
99
|
-
*
|
|
99
|
+
* backwards so earlier offsets stay valid as the text shifts. Ineligible
|
|
100
|
+
* matches are skipped and counted. Clears the active result, or returns null
|
|
100
101
|
* when there is nothing to replace.
|
|
101
102
|
*/
|
|
102
103
|
replaceAll(document, matches, replaceText) {
|
|
103
104
|
if (matches.length === 0) return null;
|
|
104
|
-
const
|
|
105
|
+
const eligibleMatches = matches.filter((match) => replaceEligibility(match).status === "allowed");
|
|
106
|
+
let skippedCount = matches.length - eligibleMatches.length;
|
|
107
|
+
let replacedCount = 0;
|
|
108
|
+
const sortedMatches = [...eligibleMatches].toSorted((a, b) => {
|
|
105
109
|
if (a.paragraphIndex !== b.paragraphIndex) return b.paragraphIndex - a.paragraphIndex;
|
|
106
110
|
return b.startOffset - a.startOffset;
|
|
107
111
|
});
|
|
108
112
|
let nextDocument = document;
|
|
109
113
|
for (const match of sortedMatches) try {
|
|
110
114
|
nextDocument = replaceTextInDocument(nextDocument, toReplaceRange(match), replaceText);
|
|
115
|
+
replacedCount++;
|
|
111
116
|
} catch {
|
|
112
|
-
|
|
117
|
+
skippedCount++;
|
|
113
118
|
}
|
|
114
|
-
this.result = null;
|
|
119
|
+
if (replacedCount > 0) this.result = null;
|
|
115
120
|
return {
|
|
116
121
|
document: nextDocument,
|
|
117
|
-
replacedCount
|
|
122
|
+
replacedCount,
|
|
123
|
+
skippedCount
|
|
118
124
|
};
|
|
119
125
|
}
|
|
120
126
|
};
|
|
@@ -8,8 +8,10 @@ type ScrollEditorElementOptions = {
|
|
|
8
8
|
behavior?: ScrollBehavior;
|
|
9
9
|
/** Space to leave between the target and the visible root edge. */
|
|
10
10
|
margin?: number;
|
|
11
|
+
/** Measured target range when only part of the element should be revealed. */
|
|
12
|
+
rect?: DOMRectReadOnly;
|
|
11
13
|
};
|
|
12
14
|
/** Scroll only the marked root, including when the target is inside a scaled viewport. */
|
|
13
|
-
declare const scrollEditorElementIntoView: (element: HTMLElement | null, { block, behavior, margin }?: ScrollEditorElementOptions) => void;
|
|
15
|
+
declare const scrollEditorElementIntoView: (element: HTMLElement | null, { block, behavior, margin, rect }?: ScrollEditorElementOptions) => void;
|
|
14
16
|
//#endregion
|
|
15
17
|
export { getEditorScrollRoot, scrollEditorBy, scrollEditorElementIntoView, scrollEditorTo };
|
|
@@ -25,10 +25,10 @@ const nearestScrollOffset = ({ targetStart, targetEnd, viewportStart, viewportEn
|
|
|
25
25
|
return 0;
|
|
26
26
|
};
|
|
27
27
|
/** Scroll only the marked root, including when the target is inside a scaled viewport. */
|
|
28
|
-
const scrollEditorElementIntoView = (element, { block = "center", behavior = prefersReducedMotionBehavior(), margin = 0 } = {}) => {
|
|
28
|
+
const scrollEditorElementIntoView = (element, { block = "center", behavior = prefersReducedMotionBehavior(), margin = 0, rect } = {}) => {
|
|
29
29
|
const root = getEditorScrollRoot(element);
|
|
30
30
|
if (!root || !element) return;
|
|
31
|
-
const targetRect = element.getBoundingClientRect();
|
|
31
|
+
const targetRect = rect ?? element.getBoundingClientRect();
|
|
32
32
|
const rootRect = root.getBoundingClientRect();
|
|
33
33
|
const visibleTop = rootRect.top + root.clientTop;
|
|
34
34
|
const targetTop = targetRect.top - visibleTop + root.scrollTop;
|
package/dist/pdf/images.js
CHANGED
|
@@ -566,6 +566,7 @@ const decodeImage = (source) => {
|
|
|
566
566
|
switch (source.format) {
|
|
567
567
|
case "png": return decodePng(source.bytes);
|
|
568
568
|
case "jpeg": return decodeJpeg(source.bytes);
|
|
569
|
+
case "svg": return Result.err(new PdfImageError({ message: "SVG images are not supported in PDF" }));
|
|
569
570
|
default: {
|
|
570
571
|
const unreachable = source.format;
|
|
571
572
|
return Result.err(new PdfImageError({ message: `unsupported image format ${String(unreachable)}` }));
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import { Node } from "prosemirror-model";
|
|
2
|
+
//#region src/prosemirror/commentRangeInlineAtoms.d.ts
|
|
3
|
+
/**
|
|
4
|
+
* Whether an inline atom extends a comment range it sits in.
|
|
5
|
+
*
|
|
6
|
+
* - `extends`: a comment mark on the atom keeps the comment's range alive.
|
|
7
|
+
* - `inert`: the atom is invisible to the reader, so a comment mark on it would
|
|
8
|
+
* be a ghost range nobody can see or select. Comment integrity strips the mark
|
|
9
|
+
* and never counts the atom as range content.
|
|
10
|
+
*
|
|
11
|
+
* This is separate from revision ownership: a cached rendered page break must
|
|
12
|
+
* stay inside its tracked-change wrapper, but it can never hold a comment on
|
|
13
|
+
* its own.
|
|
14
|
+
*/
|
|
15
|
+
type CommentRangeInlineAtomParticipation = "extends" | "inert";
|
|
16
|
+
/** Keyed by the tracked-run atom set, which the fixed-point test binds to the schema. */
|
|
17
|
+
declare const COMMENT_RANGE_INLINE_ATOM_PARTICIPATION: Readonly<{
|
|
18
|
+
readonly bookmarkBoundary: "extends";
|
|
19
|
+
readonly commentReference: "inert";
|
|
20
|
+
readonly field: "extends";
|
|
21
|
+
readonly hardBreak: "extends";
|
|
22
|
+
readonly image: "extends";
|
|
23
|
+
readonly math: "extends";
|
|
24
|
+
readonly moveRangeBoundary: "extends";
|
|
25
|
+
readonly noteMarker: "extends";
|
|
26
|
+
readonly pageBreakRun: "extends";
|
|
27
|
+
readonly preservedXml: "extends";
|
|
28
|
+
readonly rangeAnchor: "inert";
|
|
29
|
+
readonly renderedPageBreak: "inert";
|
|
30
|
+
readonly shape: "extends";
|
|
31
|
+
readonly structuredField: "extends";
|
|
32
|
+
readonly symbol: "extends";
|
|
33
|
+
readonly tab: "extends";
|
|
34
|
+
readonly text: "extends";
|
|
35
|
+
readonly textBoxAnchor: "extends";
|
|
36
|
+
}>;
|
|
37
|
+
/** Whether a node is an inline atom that must never carry or extend a comment range. */
|
|
38
|
+
declare const isCommentRangeInertAtom: (node: Node) => boolean;
|
|
39
|
+
//#endregion
|
|
40
|
+
export { COMMENT_RANGE_INLINE_ATOM_PARTICIPATION, CommentRangeInlineAtomParticipation, isCommentRangeInertAtom };
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
import { panic } from "better-result";
|
|
2
|
+
//#region src/prosemirror/commentRangeInlineAtoms.ts
|
|
3
|
+
/** Keyed by the tracked-run atom set, which the fixed-point test binds to the schema. */
|
|
4
|
+
const COMMENT_RANGE_INLINE_ATOM_PARTICIPATION = Object.freeze({
|
|
5
|
+
bookmarkBoundary: "extends",
|
|
6
|
+
commentReference: "inert",
|
|
7
|
+
field: "extends",
|
|
8
|
+
hardBreak: "extends",
|
|
9
|
+
image: "extends",
|
|
10
|
+
math: "extends",
|
|
11
|
+
moveRangeBoundary: "extends",
|
|
12
|
+
noteMarker: "extends",
|
|
13
|
+
pageBreakRun: "extends",
|
|
14
|
+
preservedXml: "extends",
|
|
15
|
+
rangeAnchor: "inert",
|
|
16
|
+
renderedPageBreak: "inert",
|
|
17
|
+
shape: "extends",
|
|
18
|
+
structuredField: "extends",
|
|
19
|
+
symbol: "extends",
|
|
20
|
+
tab: "extends",
|
|
21
|
+
text: "extends",
|
|
22
|
+
textBoxAnchor: "extends"
|
|
23
|
+
});
|
|
24
|
+
const participationByName = new Map(Object.entries(COMMENT_RANGE_INLINE_ATOM_PARTICIPATION));
|
|
25
|
+
/** Whether a node is an inline atom that must never carry or extend a comment range. */
|
|
26
|
+
const isCommentRangeInertAtom = (node) => {
|
|
27
|
+
if (!node.isInline || !node.isAtom) return false;
|
|
28
|
+
const participation = participationByName.get(node.type.name);
|
|
29
|
+
if (!participation) panic(`Inline atom ${JSON.stringify(node.type.name)} has no comment-range participation`);
|
|
30
|
+
return participation === "inert";
|
|
31
|
+
};
|
|
32
|
+
//#endregion
|
|
33
|
+
export { COMMENT_RANGE_INLINE_ATOM_PARTICIPATION, isCommentRangeInertAtom };
|
|
@@ -17,6 +17,7 @@ import { createStyleEngine } from "../../style-engine/styleEngine.js";
|
|
|
17
17
|
import { normalizeShapeTextAnchor, presetDashForOutlineAttr } from "../../types/documentEnumValues.js";
|
|
18
18
|
import { presetDashForCssBorderStyle } from "../../utils/borderCss.js";
|
|
19
19
|
import { canonicalJson } from "../../utils/canonicalJson.js";
|
|
20
|
+
import { getFindParagraphText } from "../../utils/findReplace.js";
|
|
20
21
|
import { mergeTextFormatting } from "../../utils/textFormattingMerge.js";
|
|
21
22
|
import { emuToPixels, emuToStrokePixels, pixelsToEmu } from "../../utils/units.js";
|
|
22
23
|
import { unchangedAlternateContentXml } from "../alternateContentAttrs.js";
|
|
@@ -2615,7 +2616,10 @@ const reconcileEmptyFieldResultRuns = (runs, displayRun) => {
|
|
|
2615
2616
|
return reconciled;
|
|
2616
2617
|
};
|
|
2617
2618
|
const synchronizeFieldDisplayText = (content, displayText, fallbackRun) => {
|
|
2618
|
-
|
|
2619
|
+
const currentText = getFindParagraphText({
|
|
2620
|
+
type: "paragraph",
|
|
2621
|
+
content
|
|
2622
|
+
});
|
|
2619
2623
|
const visitRunsIn = (items, visit) => {
|
|
2620
2624
|
for (const child of items) {
|
|
2621
2625
|
if (child.type === "run") {
|
|
@@ -2628,9 +2632,6 @@ const synchronizeFieldDisplayText = (content, displayText, fallbackRun) => {
|
|
|
2628
2632
|
const visitRuns = (visit) => {
|
|
2629
2633
|
visitRunsIn(content, visit);
|
|
2630
2634
|
};
|
|
2631
|
-
visitRuns((run) => {
|
|
2632
|
-
for (const runContent of run.content) if (runContent.type === "text") currentText += runContent.text;
|
|
2633
|
-
});
|
|
2634
2635
|
if (currentText === displayText) return content;
|
|
2635
2636
|
let replacedText = false;
|
|
2636
2637
|
visitRuns((run) => {
|
|
@@ -31,6 +31,7 @@ import { MOVE_RANGE_BOUNDARY_NODE_NAME } from "../extensions/nodes/MoveRangeBoun
|
|
|
31
31
|
import { RANGE_ANCHOR_NODE_NAME } from "../extensions/nodes/RangeAnchorExtension.js";
|
|
32
32
|
import "../extensions/nodes/SdtExtension.js";
|
|
33
33
|
import { fieldRequiresStructuredContent } from "../fieldRepresentation.js";
|
|
34
|
+
import { getInlineDisplayText } from "../inlineDisplayText.js";
|
|
34
35
|
import { inlineWrapperLayer } from "../inlineWrapperStack.js";
|
|
35
36
|
import { CLEARED_LIST_RENDERING_ATTRS } from "../listMarker.js";
|
|
36
37
|
import { listRenderingFor } from "../listRendering.js";
|
|
@@ -1368,27 +1369,24 @@ function standaloneTableCellToProseMirror(cell, nodeType) {
|
|
|
1368
1369
|
});
|
|
1369
1370
|
}
|
|
1370
1371
|
function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyperlinkInstanceIndex, runScope, textBoxAnchors }) {
|
|
1371
|
-
let displayText = "";
|
|
1372
1372
|
let fieldFormatting;
|
|
1373
1373
|
let fieldPropertyChanges;
|
|
1374
1374
|
const inlineNodes = [];
|
|
1375
|
-
const
|
|
1375
|
+
const hasPageBreakContent = fieldResultHasPageBreakContent(field);
|
|
1376
|
+
const resultRuns = field.type === "complexField" ? field.fieldResult : field.content.flatMap((content) => content.type === "run" ? [content] : []);
|
|
1376
1377
|
const appendRun = (run, into) => {
|
|
1377
|
-
for (const content of run.content) if (content.type === "text") displayText += content.text;
|
|
1378
1378
|
fieldFormatting ??= run.formatting;
|
|
1379
1379
|
fieldPropertyChanges ??= run.propertyChanges;
|
|
1380
|
-
if (field.type !== "simpleField" && !hasStructuredSourceContent) return;
|
|
1381
1380
|
into.push(...convertRun(run, getInheritedRunFormatting(run.formatting, field.fieldType), runScope, styleResolver, textBoxAnchors));
|
|
1382
1381
|
};
|
|
1383
|
-
const
|
|
1382
|
+
const collectFormatting = (items) => {
|
|
1384
1383
|
for (const item of items) switch (item.type) {
|
|
1385
1384
|
case "run":
|
|
1386
|
-
for (const child of item.content) if (child.type === "text") displayText += child.text;
|
|
1387
1385
|
fieldFormatting ??= item.formatting;
|
|
1388
1386
|
fieldPropertyChanges ??= item.propertyChanges;
|
|
1389
1387
|
break;
|
|
1390
1388
|
case "hyperlink":
|
|
1391
|
-
|
|
1389
|
+
collectFormatting(item.children);
|
|
1392
1390
|
break;
|
|
1393
1391
|
case "inlineWrapper":
|
|
1394
1392
|
case "inlineSdt":
|
|
@@ -1396,20 +1394,16 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
|
|
|
1396
1394
|
case "deletion":
|
|
1397
1395
|
case "moveFrom":
|
|
1398
1396
|
case "moveTo":
|
|
1399
|
-
|
|
1397
|
+
collectFormatting(item.content);
|
|
1400
1398
|
break;
|
|
1401
1399
|
case "simpleField":
|
|
1402
|
-
|
|
1400
|
+
collectFormatting(item.content);
|
|
1403
1401
|
break;
|
|
1404
1402
|
case "complexField":
|
|
1405
|
-
|
|
1403
|
+
collectFormatting(item.fieldResult);
|
|
1406
1404
|
break;
|
|
1407
1405
|
case "preservedInline":
|
|
1408
|
-
|
|
1409
|
-
break;
|
|
1410
|
-
case "mathEquation":
|
|
1411
|
-
displayText += item.plainText ?? "";
|
|
1412
|
-
break;
|
|
1406
|
+
case "mathEquation": break;
|
|
1413
1407
|
case "bookmarkStart":
|
|
1414
1408
|
case "bookmarkEnd":
|
|
1415
1409
|
case "commentRangeStart":
|
|
@@ -1433,7 +1427,6 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
|
|
|
1433
1427
|
break;
|
|
1434
1428
|
case "hyperlink":
|
|
1435
1429
|
for (const { content: child } of withInlineWrapperStacks(content.children)) if (child.type === "run") {
|
|
1436
|
-
for (const runContent of child.content) if (runContent.type === "text") displayText += runContent.text;
|
|
1437
1430
|
fieldFormatting ??= child.formatting;
|
|
1438
1431
|
fieldPropertyChanges ??= child.propertyChanges;
|
|
1439
1432
|
}
|
|
@@ -1449,7 +1442,7 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
|
|
|
1449
1442
|
case "deletion":
|
|
1450
1443
|
case "moveFrom":
|
|
1451
1444
|
case "moveTo":
|
|
1452
|
-
|
|
1445
|
+
collectFormatting(content.content);
|
|
1453
1446
|
itemNodes.push(...convertTrackedChange(content, content.type === "insertion" || content.type === "moveTo" ? "insertion" : "deletion", nextHyperlinkInstanceIndex, runScope, {
|
|
1454
1447
|
current: getInheritedRunFormatting,
|
|
1455
1448
|
historical: getInheritedRunFormatting
|
|
@@ -1468,8 +1461,8 @@ function convertField(field, { getInheritedRunFormatting, styleResolver, nextHyp
|
|
|
1468
1461
|
styleResolver,
|
|
1469
1462
|
createMark: runScope.createMark
|
|
1470
1463
|
});
|
|
1471
|
-
const createStructuredField = fieldRequiresStructuredContent(inlineNodes);
|
|
1472
|
-
const
|
|
1464
|
+
const createStructuredField = (field.type === "simpleField" || hasPageBreakContent) && fieldRequiresStructuredContent(inlineNodes);
|
|
1465
|
+
const displayText = getInlineDisplayText(schema.node("paragraph", null, inlineNodes));
|
|
1473
1466
|
const emptyResultRuns = displayText === "" && resultRuns.length > 0 && (field.type === "complexField" || field.content.length === resultRuns.length) && resultRuns.every((run) => run.content.every((item) => item.type === "text" && item.text === "")) ? resultRuns : void 0;
|
|
1474
1467
|
const resultPropertyChanges = emptyResultRuns ? emptyFieldRunPropertyChanges(emptyResultRuns) : fieldPropertyChanges;
|
|
1475
1468
|
if (!createStructuredField && resultPropertyChanges && resultPropertyChanges.length > 0) marks.push(schema.mark("runPropertyChange", { changes: [...resultPropertyChanges] }));
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
+
import { isCommentRangeInertAtom } from "../../commentRangeInlineAtoms.js";
|
|
1
2
|
import { readCommentReferenceAttrs } from "../../commentReferenceAttrs.js";
|
|
2
3
|
import { planCommentReferenceRepairs } from "../../commentReferenceIntegrity.js";
|
|
3
4
|
import { readRangeAnchorAttrs } from "../../rangeAnchorAttrs.js";
|
|
4
|
-
import { trackedRunInlineAtomDisposition } from "../../trackedRunInlineAtoms.js";
|
|
5
5
|
import { createNodeExtension } from "../create.js";
|
|
6
6
|
import "./RangeAnchorExtension.js";
|
|
7
7
|
import { Plugin } from "prosemirror-state";
|
|
@@ -13,8 +13,8 @@ const surveyCommentReferences = (doc) => {
|
|
|
13
13
|
const markEnds = /* @__PURE__ */ new Map();
|
|
14
14
|
const unanchoredMarks = [];
|
|
15
15
|
doc.descendants((node, position) => {
|
|
16
|
-
const
|
|
17
|
-
if (
|
|
16
|
+
const inert = isCommentRangeInertAtom(node);
|
|
17
|
+
if (inert) {
|
|
18
18
|
for (const mark of node.marks) if (mark.type.name === "comment") unanchoredMarks.push({
|
|
19
19
|
position,
|
|
20
20
|
size: node.nodeSize,
|
|
@@ -43,7 +43,7 @@ const surveyCommentReferences = (doc) => {
|
|
|
43
43
|
}
|
|
44
44
|
return false;
|
|
45
45
|
}
|
|
46
|
-
if (
|
|
46
|
+
if (inert) return false;
|
|
47
47
|
if (!node.isInline) return true;
|
|
48
48
|
for (const mark of node.marks) {
|
|
49
49
|
if (mark.type.name !== "comment") continue;
|