@stll/folio-core 0.28.0 → 0.29.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/styleParser.js +15 -14
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +1 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +135 -118
- package/dist/layout-engine/footnoteColumnReflow.d.ts +15 -0
- package/dist/layout-engine/footnoteColumnReflow.js +74 -0
- package/dist/layout-engine/index.d.ts +3 -5
- package/dist/layout-engine/index.js +36 -15
- package/dist/layout-engine/measure/cache.d.ts +1 -1
- package/dist/layout-engine/measure/cache.js +11 -2
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +3 -1
- package/dist/layout-engine/measure/listMarkerWidth.js +6 -4
- package/dist/layout-engine/paginator.d.ts +6 -0
- package/dist/layout-engine/paginator.js +30 -11
- package/dist/layout-engine/types.d.ts +11 -6
- package/dist/markdown/renderParagraph.js +7 -2
- package/dist/prosemirror/attrs/index.js +5 -3
- package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +8 -0
- package/dist/prosemirror/bookmarkBoundaryAttrs.js +66 -0
- package/dist/prosemirror/commands/formatPainter.js +45 -1
- package/dist/prosemirror/conversion/fromProseDoc.d.ts +4 -1
- package/dist/prosemirror/conversion/fromProseDoc.js +224 -91
- package/dist/prosemirror/conversion/toProseDoc.d.ts +4 -1
- package/dist/prosemirror/conversion/toProseDoc.js +325 -94
- package/dist/prosemirror/extensions/StarterKit.js +11 -3
- package/dist/prosemirror/extensions/features/AutoBidiDetectionExtension.js +8 -4
- package/dist/prosemirror/extensions/features/PasteCleanupExtension.d.ts +4 -1
- package/dist/prosemirror/extensions/features/PasteCleanupExtension.js +9 -5
- package/dist/prosemirror/extensions/features/pasteCleanup.d.ts +10 -23
- package/dist/prosemirror/extensions/features/pasteCleanup.js +77 -22
- package/dist/prosemirror/extensions/marks/RunFormattingOverrideExtension.js +22 -22
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts +9 -0
- package/dist/prosemirror/extensions/nodes/BookmarkBoundaryExtension.js +67 -0
- package/dist/prosemirror/extensions/nodes/FieldExtension.d.ts +10 -4
- package/dist/prosemirror/extensions/nodes/FieldExtension.js +77 -59
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +4 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -3
- package/dist/prosemirror/listMarker.d.ts +58 -0
- package/dist/prosemirror/listMarker.js +185 -0
- package/dist/prosemirror/numberedRefFields.d.ts +24 -0
- package/dist/prosemirror/numberedRefFields.js +276 -0
- package/dist/prosemirror/paraText.js +56 -17
- package/dist/prosemirror/plugins/anonymizationDecorations.js +1 -1
- package/dist/prosemirror/plugins/pmTextScan.d.ts +3 -1
- package/dist/prosemirror/plugins/pmTextScan.js +28 -7
- package/dist/prosemirror/plugins/templateDirectives.js +2 -2
- package/dist/prosemirror/schema/index.d.ts +2 -2
- package/dist/prosemirror/schema/marks.d.ts +3 -1
- package/dist/prosemirror/schema/nodes.d.ts +13 -1
- package/dist/prosemirror/styles/styleResolver.d.ts +0 -1
- package/dist/prosemirror/styles/styleResolver.js +22 -29
- package/dist/prosemirror/styles/styleToggleCascade.d.ts +37 -0
- package/dist/prosemirror/styles/styleToggleCascade.js +52 -0
- package/dist/prosemirror/validation.js +93 -0
- package/dist/utils/textFormattingMerge.d.ts +9 -1
- package/dist/utils/textFormattingMerge.js +32 -1
- package/package.json +1 -1
|
@@ -10,10 +10,24 @@ const collectBlockChunks = (doc) => {
|
|
|
10
10
|
if (node.isTextblock) {
|
|
11
11
|
const chunks = [];
|
|
12
12
|
node.descendants((child, offset) => {
|
|
13
|
-
if (child.isText && child.text !== void 0)
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
13
|
+
if (child.isText && child.text !== void 0) {
|
|
14
|
+
const start = pos + 1 + offset;
|
|
15
|
+
chunks.push({
|
|
16
|
+
text: child.text,
|
|
17
|
+
start,
|
|
18
|
+
end: start + child.text.length
|
|
19
|
+
});
|
|
20
|
+
return false;
|
|
21
|
+
}
|
|
22
|
+
if (child.isLeaf && child.textContent) {
|
|
23
|
+
const start = pos + 1 + offset;
|
|
24
|
+
chunks.push({
|
|
25
|
+
text: child.textContent,
|
|
26
|
+
start,
|
|
27
|
+
end: start + child.nodeSize
|
|
28
|
+
});
|
|
29
|
+
return false;
|
|
30
|
+
}
|
|
17
31
|
return true;
|
|
18
32
|
});
|
|
19
33
|
if (chunks.length > 0) blocks.push(chunks);
|
|
@@ -24,14 +38,21 @@ const collectBlockChunks = (doc) => {
|
|
|
24
38
|
return blocks;
|
|
25
39
|
};
|
|
26
40
|
/** Map a joined-string offset back to its PM doc position. */
|
|
27
|
-
const offsetToDocPos = (chunks, offset) => {
|
|
41
|
+
const offsetToDocPos = (chunks, offset, bias = "start") => {
|
|
28
42
|
let consumed = 0;
|
|
29
43
|
for (const chunk of chunks) {
|
|
30
|
-
if (offset <= consumed + chunk.text.length)
|
|
44
|
+
if (offset <= consumed + chunk.text.length) {
|
|
45
|
+
const localOffset = offset - consumed;
|
|
46
|
+
const end = chunk.end ?? chunk.start + chunk.text.length;
|
|
47
|
+
if (end - chunk.start === chunk.text.length) return chunk.start + localOffset;
|
|
48
|
+
if (localOffset === 0) return chunk.start;
|
|
49
|
+
if (localOffset === chunk.text.length || bias === "end") return end;
|
|
50
|
+
return chunk.start;
|
|
51
|
+
}
|
|
31
52
|
consumed += chunk.text.length;
|
|
32
53
|
}
|
|
33
54
|
const last = chunks.at(-1);
|
|
34
|
-
return last ? last.start + last.text.length : 0;
|
|
55
|
+
return last ? last.end ?? last.start + last.text.length : 0;
|
|
35
56
|
};
|
|
36
57
|
/** Join a block's chunks into the single string callers scan. */
|
|
37
58
|
const joinChunks = (chunks) => chunks.map((c) => c.text).join("");
|
|
@@ -71,7 +71,7 @@ const scanDirectives = (doc) => {
|
|
|
71
71
|
const last = chunks.at(-1);
|
|
72
72
|
ranges.push({
|
|
73
73
|
from: chunks[0]?.start ?? 0,
|
|
74
|
-
to: last ? last.start + last.text.length : 0,
|
|
74
|
+
to: last ? last.end ?? last.start + last.text.length : 0,
|
|
75
75
|
kind: sole.meta.kind,
|
|
76
76
|
expr: directiveExpr(sole.meta),
|
|
77
77
|
block: true
|
|
@@ -82,7 +82,7 @@ const scanDirectives = (doc) => {
|
|
|
82
82
|
const clauseVersion = marker.meta.kind === "clause" ? marker.meta.version : void 0;
|
|
83
83
|
ranges.push({
|
|
84
84
|
from: offsetToDocPos(chunks, marker.start),
|
|
85
|
-
to: offsetToDocPos(chunks, marker.end),
|
|
85
|
+
to: offsetToDocPos(chunks, marker.end, "end"),
|
|
86
86
|
kind: marker.meta.kind,
|
|
87
87
|
expr: directiveExpr(marker.meta),
|
|
88
88
|
block: false,
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { CharacterSpacingAttrs, CharacterStyleAttrs, CommentAttrs, EmphasisMarkAttrs, FontFamilyAttrs, FontSizeAttrs, FootnoteRefAttrs, HighlightAttrs, HyperlinkAttrs, LanguageAttrs, RunFormattingOverrideAttrs, RunPropertyChangeMarkAttrs, RunShadingAttrs, StrikeAttrs, TextColorAttrs, TextEffectAttrs, TrackedChangeMarkAttrs, UnderlineAttrs } from "./marks.js";
|
|
2
|
-
import { BlockSdtAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, SdtAttrs, ShapeAttrs, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAttrs } from "./nodes.js";
|
|
2
|
+
import { BlockSdtAttrs, BookmarkBoundaryAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, SdtAttrs, ShapeAttrs, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAttrs } from "./nodes.js";
|
|
3
3
|
import { ExtensionManager } from "../extensions/ExtensionManager.js";
|
|
4
4
|
//#region src/prosemirror/schema/index.d.ts
|
|
5
5
|
declare const singletonManager: ExtensionManager;
|
|
@@ -11,4 +11,4 @@ type DocxSchema = typeof schema;
|
|
|
11
11
|
type DocxNode = ReturnType<typeof schema.node>;
|
|
12
12
|
type DocxMark = ReturnType<typeof schema.mark>;
|
|
13
13
|
//#endregion
|
|
14
|
-
export { type BlockSdtAttrs, type CharacterSpacingAttrs, type CharacterStyleAttrs, type CommentAttrs, DocxMark, DocxNode, DocxSchema, type EmphasisMarkAttrs, type FieldAttrs, type FontFamilyAttrs, type FontSizeAttrs, type FootnoteRefAttrs, type HardBreakAttrs, type HighlightAttrs, type HyperlinkAttrs, type ImageAttrs, type ImagePositionAttrs, type LanguageAttrs, type MathAttrs, type ParagraphAttrs, type ParagraphPropertyChangeAttrs, type RunFormattingOverrideAttrs, type RunPropertyChangeMarkAttrs, type RunShadingAttrs, type SdtAttrs, type ShapeAttrs, type StrikeAttrs, type SymbolAttrs, type TabAttrs, type TableAttrs, type TableCellAttrs, type TableRowAttrs, type TextBoxAttrs, type TextColorAttrs, type TextEffectAttrs, type TrackedChangeMarkAttrs, type UnderlineAttrs, schema, singletonManager };
|
|
14
|
+
export { type BlockSdtAttrs, type BookmarkBoundaryAttrs, type CharacterSpacingAttrs, type CharacterStyleAttrs, type CommentAttrs, DocxMark, DocxNode, DocxSchema, type EmphasisMarkAttrs, type FieldAttrs, type FontFamilyAttrs, type FontSizeAttrs, type FootnoteRefAttrs, type HardBreakAttrs, type HighlightAttrs, type HyperlinkAttrs, type ImageAttrs, type ImagePositionAttrs, type LanguageAttrs, type MathAttrs, type ParagraphAttrs, type ParagraphPropertyChangeAttrs, type RunFormattingOverrideAttrs, type RunPropertyChangeMarkAttrs, type RunShadingAttrs, type SdtAttrs, type ShapeAttrs, type StrikeAttrs, type SymbolAttrs, type TabAttrs, type TableAttrs, type TableCellAttrs, type TableRowAttrs, type TextBoxAttrs, type TextColorAttrs, type TextEffectAttrs, type TrackedChangeMarkAttrs, type UnderlineAttrs, schema, singletonManager };
|
|
@@ -121,7 +121,9 @@ type RunPropertyChangeMarkAttrs = {
|
|
|
121
121
|
provenance: TrackedChangeProvenance;
|
|
122
122
|
suggestionId?: string;
|
|
123
123
|
};
|
|
124
|
-
type RunFormattingOverrideAttrs = { [K in keyof Pick<document_d_exports.TextFormatting, "bold" | "italic" | "strike" | "
|
|
124
|
+
type RunFormattingOverrideAttrs = { [K in keyof Pick<document_d_exports.TextFormatting, "bold" | "italic" | "strike" | "allCaps" | "smallCaps" | "hidden" | "emboss" | "imprint" | "shadow" | "outline">]?: boolean; } & {
|
|
125
|
+
doubleStrike?: false;
|
|
126
|
+
rtl?: false;
|
|
125
127
|
/** Independent complex-script weight (`w:bCs`). */
|
|
126
128
|
boldCs?: boolean;
|
|
127
129
|
/** Force complex-script formatting for the full run (`w:cs`). */
|
|
@@ -15,6 +15,16 @@ type SymbolAttrs = {
|
|
|
15
15
|
font: string;
|
|
16
16
|
char: string;
|
|
17
17
|
};
|
|
18
|
+
type BookmarkBoundaryAttrs = {
|
|
19
|
+
type: "start";
|
|
20
|
+
id: number;
|
|
21
|
+
name: string;
|
|
22
|
+
colFirst?: number;
|
|
23
|
+
colLast?: number;
|
|
24
|
+
} | {
|
|
25
|
+
type: "end";
|
|
26
|
+
id: number;
|
|
27
|
+
};
|
|
18
28
|
/**
|
|
19
29
|
* Paragraph node attributes - maps to ParagraphFormatting
|
|
20
30
|
*/
|
|
@@ -292,6 +302,8 @@ type FieldAttrs = {
|
|
|
292
302
|
instruction: string;
|
|
293
303
|
/** Current/cached display text */
|
|
294
304
|
displayText: string;
|
|
305
|
+
/** Imported cache that proved numbered REF resolution for this field. */
|
|
306
|
+
_numberedRefBaseline?: string;
|
|
295
307
|
/** Whether the field came from w:fldSimple or a complex fldChar range */
|
|
296
308
|
fieldKind: "simple" | "complex";
|
|
297
309
|
/** Field is locked */
|
|
@@ -706,4 +718,4 @@ type TableCellAttrs = {
|
|
|
706
718
|
_docxVMergeContinuationCells?: document_d_exports.TableCell[];
|
|
707
719
|
};
|
|
708
720
|
//#endregion
|
|
709
|
-
export { BlockSdtAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, SdtAttrs, ShapeAttrs, SuggestedStructuralMarker, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAnchorAttrs, TextBoxAttrs };
|
|
721
|
+
export { BlockSdtAttrs, BookmarkBoundaryAttrs, FieldAttrs, HardBreakAttrs, ImageAttrs, ImagePositionAttrs, MathAttrs, ParagraphAttrs, ParagraphPropertyChangeAttrs, SdtAttrs, ShapeAttrs, SuggestedStructuralMarker, SymbolAttrs, TabAttrs, TableAttrs, TableCellAttrs, TableRowAttrs, TextBoxAnchorAttrs, TextBoxAttrs };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { mergeParagraphFormatting } from "../../utils/paragraphFormattingMerge.js";
|
|
2
|
-
import {
|
|
2
|
+
import { cascadeStyleTextFormatting } from "./styleToggleCascade.js";
|
|
3
3
|
//#region src/prosemirror/styles/styleResolver.ts
|
|
4
4
|
/**
|
|
5
5
|
* Word's default-template Normal style, used as a last-resort fallback for a
|
|
@@ -102,21 +102,23 @@ var StyleResolver = class {
|
|
|
102
102
|
resolveParagraphStyleCascade(styleId, tableParagraphOverlay) {
|
|
103
103
|
const result = {};
|
|
104
104
|
if (this.docDefaults?.pPr) result.paragraphFormatting = { ...this.docDefaults.pPr };
|
|
105
|
-
if (this.docDefaults?.rPr) result.runFormatting = { ...this.docDefaults.rPr };
|
|
106
105
|
if (tableParagraphOverlay) {
|
|
107
106
|
const merged = mergeParagraphFormatting(result.paragraphFormatting, tableParagraphOverlay);
|
|
108
107
|
if (merged !== void 0) result.paragraphFormatting = merged;
|
|
109
108
|
}
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
const style = this.stylesById.get(styleId);
|
|
115
|
-
if (!style) {
|
|
116
|
-
if (this.defaultParagraphStyle) this.mergeStyleIntoResult(result, this.defaultParagraphStyle);
|
|
117
|
-
return result;
|
|
109
|
+
const style = styleId ? this.stylesById.get(styleId) ?? this.defaultParagraphStyle : this.defaultParagraphStyle;
|
|
110
|
+
if (style?.pPr) {
|
|
111
|
+
const merged = mergeParagraphFormatting(result.paragraphFormatting, style.pPr);
|
|
112
|
+
if (merged) result.paragraphFormatting = merged;
|
|
118
113
|
}
|
|
119
|
-
|
|
114
|
+
const runFormatting = cascadeStyleTextFormatting([{
|
|
115
|
+
formatting: this.docDefaults?.rPr,
|
|
116
|
+
type: "defaults"
|
|
117
|
+
}, {
|
|
118
|
+
formatting: style?.rPr,
|
|
119
|
+
type: "style"
|
|
120
|
+
}]).formatting;
|
|
121
|
+
if (runFormatting) result.runFormatting = runFormatting;
|
|
120
122
|
return result;
|
|
121
123
|
}
|
|
122
124
|
/**
|
|
@@ -159,14 +161,15 @@ var StyleResolver = class {
|
|
|
159
161
|
* @returns Resolved text formatting
|
|
160
162
|
*/
|
|
161
163
|
resolveRunStyle(styleId) {
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
164
|
+
const style = styleId ? this.stylesById.get(styleId) : this.defaultCharacterStyle;
|
|
165
|
+
const result = cascadeStyleTextFormatting([{
|
|
166
|
+
formatting: this.docDefaults?.rPr,
|
|
167
|
+
type: "defaults"
|
|
168
|
+
}, {
|
|
169
|
+
formatting: style?.rPr,
|
|
170
|
+
type: "style"
|
|
171
|
+
}]).formatting;
|
|
172
|
+
return result && Object.keys(result).length > 0 ? result : void 0;
|
|
170
173
|
}
|
|
171
174
|
/**
|
|
172
175
|
* Get a character style's own properties WITHOUT docDefaults.
|
|
@@ -213,16 +216,6 @@ var StyleResolver = class {
|
|
|
213
216
|
for (const style of this.stylesById.values()) if (style.type === type && style.default) return style;
|
|
214
217
|
if (type === "paragraph") return this.stylesById.get("Normal") ?? (this.docDefaults ? void 0 : BUILTIN_NORMAL_STYLE);
|
|
215
218
|
}
|
|
216
|
-
mergeStyleIntoResult(result, style) {
|
|
217
|
-
if (style.pPr) {
|
|
218
|
-
const merged = mergeParagraphFormatting(result.paragraphFormatting, style.pPr);
|
|
219
|
-
if (merged !== void 0) result.paragraphFormatting = merged;
|
|
220
|
-
}
|
|
221
|
-
if (style.rPr) {
|
|
222
|
-
const merged = mergeTextFormatting(result.runFormatting, style.rPr);
|
|
223
|
-
if (merged !== void 0) result.runFormatting = merged;
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
219
|
};
|
|
227
220
|
/**
|
|
228
221
|
* Create a style resolver from document's style definitions
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { document_d_exports } from "../../types/document.js";
|
|
2
|
+
import { STYLE_TOGGLE_KEYS } from "../../utils/textFormattingMerge.js";
|
|
3
|
+
//#region src/prosemirror/styles/styleToggleCascade.d.ts
|
|
4
|
+
type StyleToggleKey = (typeof STYLE_TOGGLE_KEYS)[number];
|
|
5
|
+
type ToggleState = {
|
|
6
|
+
value: boolean;
|
|
7
|
+
defaultsActive: boolean;
|
|
8
|
+
};
|
|
9
|
+
/** Internal formatting value whose toggle history survives copying and separate cascade passes. */
|
|
10
|
+
type StyleTextFormattingCascade = {
|
|
11
|
+
formatting: document_d_exports.TextFormatting | undefined;
|
|
12
|
+
toggleStates: ReadonlyMap<StyleToggleKey, ToggleState>;
|
|
13
|
+
type: "styleToggleCascade";
|
|
14
|
+
};
|
|
15
|
+
type StyleToggleLevel = {
|
|
16
|
+
formatting: document_d_exports.TextFormatting | undefined;
|
|
17
|
+
type: "defaults" | "style" | "direct";
|
|
18
|
+
} | {
|
|
19
|
+
cascade: StyleTextFormattingCascade;
|
|
20
|
+
type: "carried";
|
|
21
|
+
};
|
|
22
|
+
type StyleToggleCascadeOptions = {
|
|
23
|
+
/** Existing ordinary-property merge whose toggle fields this cascade replaces. */
|
|
24
|
+
ordinaryFormatting: document_d_exports.TextFormatting | undefined;
|
|
25
|
+
};
|
|
26
|
+
/**
|
|
27
|
+
* Resolve run formatting across OOXML style hierarchy levels.
|
|
28
|
+
*
|
|
29
|
+
* Ordinary properties retain Folio's existing low-to-high, last-defined merge. Toggle
|
|
30
|
+
* properties reverse inherited state when a style level states `true`; `false` leaves inherited
|
|
31
|
+
* style state unchanged, while direct formatting remains an absolute value. The returned value
|
|
32
|
+
* carries its toggle history explicitly, so paragraph and character style resolution may happen
|
|
33
|
+
* in separate passes without an object-identity contract.
|
|
34
|
+
*/
|
|
35
|
+
declare function cascadeStyleTextFormatting(levels: readonly StyleToggleLevel[], options?: StyleToggleCascadeOptions): StyleTextFormattingCascade;
|
|
36
|
+
//#endregion
|
|
37
|
+
export { cascadeStyleTextFormatting };
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import { STYLE_TOGGLE_KEYS, mergeTextFormatting } from "../../utils/textFormattingMerge.js";
|
|
2
|
+
//#region src/prosemirror/styles/styleToggleCascade.ts
|
|
3
|
+
/**
|
|
4
|
+
* Resolve run formatting across OOXML style hierarchy levels.
|
|
5
|
+
*
|
|
6
|
+
* Ordinary properties retain Folio's existing low-to-high, last-defined merge. Toggle
|
|
7
|
+
* properties reverse inherited state when a style level states `true`; `false` leaves inherited
|
|
8
|
+
* style state unchanged, while direct formatting remains an absolute value. The returned value
|
|
9
|
+
* carries its toggle history explicitly, so paragraph and character style resolution may happen
|
|
10
|
+
* in separate passes without an object-identity contract.
|
|
11
|
+
*/
|
|
12
|
+
function cascadeStyleTextFormatting(levels, options) {
|
|
13
|
+
let formatting = options ? mergeTextFormatting(void 0, options.ordinaryFormatting) : void 0;
|
|
14
|
+
const mergeOrdinaryFormatting = options === void 0;
|
|
15
|
+
const toggleStates = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const level of levels) {
|
|
17
|
+
if (level.type === "carried") {
|
|
18
|
+
if (mergeOrdinaryFormatting) formatting = mergeTextFormatting(formatting, level.cascade.formatting);
|
|
19
|
+
for (const [key, state] of level.cascade.toggleStates) toggleStates.set(key, state);
|
|
20
|
+
continue;
|
|
21
|
+
}
|
|
22
|
+
if (!level.formatting) continue;
|
|
23
|
+
if (mergeOrdinaryFormatting) formatting = mergeTextFormatting(formatting, level.formatting);
|
|
24
|
+
for (const key of STYLE_TOGGLE_KEYS) {
|
|
25
|
+
const value = level.formatting[key];
|
|
26
|
+
if (value === void 0) continue;
|
|
27
|
+
const inherited = toggleStates.get(key);
|
|
28
|
+
if (level.type === "direct") toggleStates.set(key, {
|
|
29
|
+
value,
|
|
30
|
+
defaultsActive: false
|
|
31
|
+
});
|
|
32
|
+
else if (!value) continue;
|
|
33
|
+
else if (level.type === "defaults") toggleStates.set(key, {
|
|
34
|
+
value: true,
|
|
35
|
+
defaultsActive: true
|
|
36
|
+
});
|
|
37
|
+
else toggleStates.set(key, {
|
|
38
|
+
value: inherited?.defaultsActive === true ? true : !(inherited?.value ?? false),
|
|
39
|
+
defaultsActive: inherited?.defaultsActive === true
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
if (!formatting && toggleStates.size > 0) formatting = {};
|
|
44
|
+
for (const [key, state] of toggleStates) if (formatting) formatting[key] = state.value;
|
|
45
|
+
return {
|
|
46
|
+
formatting,
|
|
47
|
+
toggleStates,
|
|
48
|
+
type: "styleToggleCascade"
|
|
49
|
+
};
|
|
50
|
+
}
|
|
51
|
+
//#endregion
|
|
52
|
+
export { cascadeStyleTextFormatting };
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { readBlockSdtAttrs, readCharacterSpacingMarkAttrs, readCharacterStyleMarkAttrs, readCommentMarkAttrs, readEmphasisMarkAttrs, readFieldAttrs, readFontFamilyMarkAttrs, readFontSizeMarkAttrs, readFootnoteRefMarkAttrs, readHardBreakAttrs, readHighlightMarkAttrs, readHyperlinkMarkAttrs, readImageAttrs, readLanguageMarkAttrs, readMathAttrs, readParagraphAttrs, readRunFormattingOverrideMarkAttrs, readRunPropertyChangeMarkAttrs, readRunShadingMarkAttrs, readSdtAttrs, readShapeAttrs, readStrikeMarkAttrs, readSymbolAttrs, readTabAttrs, readTableAttrs, readTableCellAttrs, readTableRowAttrs, readTextBoxAttrs, readTextColorMarkAttrs, readTextEffectMarkAttrs, readTrackedChangeMarkAttrs, readUnderlineMarkAttrs } from "./attrs/index.js";
|
|
2
|
+
import { readBookmarkBoundaryAttrs } from "./bookmarkBoundaryAttrs.js";
|
|
2
3
|
import { readTextBoxAnchorAttrs } from "./textBoxAnchorAttrs.js";
|
|
3
4
|
//#region src/prosemirror/validation.ts
|
|
4
5
|
var ProseMirrorDocumentValidationError = class extends Error {
|
|
@@ -18,11 +19,71 @@ const validateProseMirrorDocument = (doc) => {
|
|
|
18
19
|
message: `Expected doc, got ${doc.type.name}.`
|
|
19
20
|
});
|
|
20
21
|
validateNode(doc, "doc", issues);
|
|
22
|
+
validateBookmarkBoundaryStructure(doc, issues);
|
|
21
23
|
return {
|
|
22
24
|
valid: issues.length === 0,
|
|
23
25
|
issues
|
|
24
26
|
};
|
|
25
27
|
};
|
|
28
|
+
const validateBookmarkBoundaryStructure = (doc, issues) => {
|
|
29
|
+
const open = /* @__PURE__ */ new Map();
|
|
30
|
+
const startedIds = /* @__PURE__ */ new Set();
|
|
31
|
+
const registerStart = (id, path) => {
|
|
32
|
+
if (startedIds.has(id)) {
|
|
33
|
+
issues.push({
|
|
34
|
+
path,
|
|
35
|
+
message: `Bookmark id ${id} has more than one start boundary.`
|
|
36
|
+
});
|
|
37
|
+
return;
|
|
38
|
+
}
|
|
39
|
+
startedIds.add(id);
|
|
40
|
+
open.set(id, {
|
|
41
|
+
id,
|
|
42
|
+
path
|
|
43
|
+
});
|
|
44
|
+
};
|
|
45
|
+
const registerEnd = (id, path) => {
|
|
46
|
+
if (!open.get(id)) {
|
|
47
|
+
issues.push({
|
|
48
|
+
path,
|
|
49
|
+
message: `Bookmark id ${id} has no open start boundary.`
|
|
50
|
+
});
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
open.delete(id);
|
|
54
|
+
};
|
|
55
|
+
const visit = (node, path) => {
|
|
56
|
+
const paragraphAttrs = node.type.name === "paragraph" ? readParagraphAttrs(node) : null;
|
|
57
|
+
if (paragraphAttrs?.ok) for (const [index, bookmark] of (paragraphAttrs.value.bookmarks ?? []).entries()) registerStart(bookmark.id, `${path}.paragraph.attrs.bookmarks[${index}]`);
|
|
58
|
+
if (node.type.name === "bookmarkBoundary") {
|
|
59
|
+
const result = readBookmarkBoundaryAttrs(node);
|
|
60
|
+
if (result.ok) {
|
|
61
|
+
const attrs = result.value;
|
|
62
|
+
const hasHyperlink = node.marks.some((mark) => mark.type.name === "hyperlink");
|
|
63
|
+
const trackedChanges = node.marks.filter((mark) => mark.type.name === "insertion" || mark.type.name === "deletion");
|
|
64
|
+
if (trackedChanges.length > 1) issues.push({
|
|
65
|
+
path,
|
|
66
|
+
message: "Bookmark boundaries cannot carry multiple tracked-change parents."
|
|
67
|
+
});
|
|
68
|
+
else if (trackedChanges.length === 1 && !hasHyperlink) issues.push({
|
|
69
|
+
path,
|
|
70
|
+
message: "Bookmark boundaries inside tracked changes require a hyperlink serialization parent."
|
|
71
|
+
});
|
|
72
|
+
if (attrs.type === "start") registerStart(attrs.id, path);
|
|
73
|
+
else registerEnd(attrs.id, path);
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
node.forEach((child, _offset, index) => {
|
|
77
|
+
visit(child, `${path}.content[${index}]`);
|
|
78
|
+
});
|
|
79
|
+
if (paragraphAttrs?.ok) for (const [index, bookmark] of (paragraphAttrs.value.bookmarks ?? []).entries()) registerEnd(bookmark.id, `${path}.paragraph.attrs.bookmarks[${index}]`);
|
|
80
|
+
};
|
|
81
|
+
visit(doc, "doc");
|
|
82
|
+
for (const boundary of open.values()) issues.push({
|
|
83
|
+
path: boundary.path,
|
|
84
|
+
message: `Bookmark id ${boundary.id} has no matching end boundary.`
|
|
85
|
+
});
|
|
86
|
+
};
|
|
26
87
|
const assertValidProseMirrorDocument = (doc, context) => {
|
|
27
88
|
if (validDocumentCache.has(doc)) return;
|
|
28
89
|
const validation = validateProseMirrorDocument(doc);
|
|
@@ -50,6 +111,9 @@ const validateNodeAttrs = (node, path, issues) => {
|
|
|
50
111
|
case "horizontalRule":
|
|
51
112
|
case "pageBreak":
|
|
52
113
|
case "renderedPageBreak": return;
|
|
114
|
+
case "bookmarkBoundary":
|
|
115
|
+
appendAttrIssues(path, readBookmarkBoundaryAttrs(node), issues);
|
|
116
|
+
return;
|
|
53
117
|
case "tab":
|
|
54
118
|
appendAttrIssues(path, readTabAttrs(node), issues);
|
|
55
119
|
return;
|
|
@@ -77,6 +141,35 @@ const validateNodeAttrs = (node, path, issues) => {
|
|
|
77
141
|
return;
|
|
78
142
|
case "field":
|
|
79
143
|
appendAttrIssues(path, readFieldAttrs(node), issues);
|
|
144
|
+
if (node.childCount > 0) issues.push({
|
|
145
|
+
path: `${path}.content`,
|
|
146
|
+
message: "Ordinary fields cannot contain structured result children."
|
|
147
|
+
});
|
|
148
|
+
return;
|
|
149
|
+
case "structuredField":
|
|
150
|
+
{
|
|
151
|
+
const fieldAttrs = readFieldAttrs(node);
|
|
152
|
+
appendAttrIssues(path, fieldAttrs, issues);
|
|
153
|
+
if (fieldAttrs.ok) {
|
|
154
|
+
const hasStructuredHyperlink = node.content.content.some((child) => child.marks.some((mark) => mark.type.name === "hyperlink"));
|
|
155
|
+
if (fieldAttrs.value.fieldKind === "complex") issues.push({
|
|
156
|
+
path: `${path}.content`,
|
|
157
|
+
message: "Complex fields cannot contain structured result children."
|
|
158
|
+
});
|
|
159
|
+
else if (!hasStructuredHyperlink) issues.push({
|
|
160
|
+
path: `${path}.content`,
|
|
161
|
+
message: "Structured simple fields require hyperlink content."
|
|
162
|
+
});
|
|
163
|
+
node.forEach((child, _offset, index) => {
|
|
164
|
+
const childPath = `${path}.content[${index}]`;
|
|
165
|
+
const hasHyperlink = child.marks.some((mark) => mark.type.name === "hyperlink");
|
|
166
|
+
if (child.type.name === "bookmarkBoundary" && !hasHyperlink) issues.push({
|
|
167
|
+
path: childPath,
|
|
168
|
+
message: "Bookmark boundaries inside fields require a hyperlink parent."
|
|
169
|
+
});
|
|
170
|
+
});
|
|
171
|
+
}
|
|
172
|
+
}
|
|
80
173
|
return;
|
|
81
174
|
case "math":
|
|
82
175
|
appendAttrIssues(path, readMathAttrs(node), issues);
|
|
@@ -1,5 +1,13 @@
|
|
|
1
1
|
import { document_d_exports } from "../types/document.js";
|
|
2
2
|
//#region src/utils/textFormattingMerge.d.ts
|
|
3
|
+
declare const STYLE_TOGGLE_KEYS: readonly ["bold", "boldCs", "italic", "italicCs", "allCaps", "emboss", "imprint", "outline", "shadow", "smallCaps", "strike", "hidden"];
|
|
4
|
+
/**
|
|
5
|
+
* Merge the properties exposed by one resolved style definition.
|
|
6
|
+
*
|
|
7
|
+
* A false toggle at a child style level does not cancel the inherited state. The
|
|
8
|
+
* ordinary merge remains unchanged for direct formatting and non-toggle properties.
|
|
9
|
+
*/
|
|
10
|
+
declare function mergeStyleTextFormatting(target: document_d_exports.TextFormatting | undefined, source: document_d_exports.TextFormatting | undefined): document_d_exports.TextFormatting | undefined;
|
|
3
11
|
declare function mergeTextFormatting(target: document_d_exports.TextFormatting | undefined, source: document_d_exports.TextFormatting | undefined): document_d_exports.TextFormatting | undefined;
|
|
4
12
|
//#endregion
|
|
5
|
-
export { mergeTextFormatting };
|
|
13
|
+
export { STYLE_TOGGLE_KEYS, mergeStyleTextFormatting, mergeTextFormatting };
|
|
@@ -1,5 +1,36 @@
|
|
|
1
1
|
import { mergeFontFamily } from "./fontFamilyMerge.js";
|
|
2
2
|
//#region src/utils/textFormattingMerge.ts
|
|
3
|
+
const STYLE_TOGGLE_KEYS = [
|
|
4
|
+
"bold",
|
|
5
|
+
"boldCs",
|
|
6
|
+
"italic",
|
|
7
|
+
"italicCs",
|
|
8
|
+
"allCaps",
|
|
9
|
+
"emboss",
|
|
10
|
+
"imprint",
|
|
11
|
+
"outline",
|
|
12
|
+
"shadow",
|
|
13
|
+
"smallCaps",
|
|
14
|
+
"strike",
|
|
15
|
+
"hidden"
|
|
16
|
+
];
|
|
17
|
+
/**
|
|
18
|
+
* Merge the properties exposed by one resolved style definition.
|
|
19
|
+
*
|
|
20
|
+
* A false toggle at a child style level does not cancel the inherited state. The
|
|
21
|
+
* ordinary merge remains unchanged for direct formatting and non-toggle properties.
|
|
22
|
+
*/
|
|
23
|
+
function mergeStyleTextFormatting(target, source) {
|
|
24
|
+
const result = mergeTextFormatting(target, source);
|
|
25
|
+
if (!result || !source) return result;
|
|
26
|
+
for (const key of STYLE_TOGGLE_KEYS) {
|
|
27
|
+
if (source[key] !== false) continue;
|
|
28
|
+
const inherited = target?.[key];
|
|
29
|
+
if (inherited === void 0) Reflect.deleteProperty(result, key);
|
|
30
|
+
else result[key] = inherited;
|
|
31
|
+
}
|
|
32
|
+
return result;
|
|
33
|
+
}
|
|
3
34
|
function mergeTextFormatting(target, source) {
|
|
4
35
|
if (!source && !target) return;
|
|
5
36
|
if (!source) return target;
|
|
@@ -28,4 +59,4 @@ function mergeTextFormatting(target, source) {
|
|
|
28
59
|
return result;
|
|
29
60
|
}
|
|
30
61
|
//#endregion
|
|
31
|
-
export { mergeTextFormatting };
|
|
62
|
+
export { STYLE_TOGGLE_KEYS, mergeStyleTextFormatting, mergeTextFormatting };
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stll/folio-core",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.29.0",
|
|
4
4
|
"description": "Headless, framework-neutral core of folio: the OOXML (.docx) parser, document model, ProseMirror integration, and page-layout engine. No React.",
|
|
5
5
|
"keywords": [
|
|
6
6
|
"document-model",
|