@stll/folio-core 0.28.1 → 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/layout-bridge/convert/toFlowBlocks.d.ts +1 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +61 -95
- package/dist/markdown/renderParagraph.js +7 -2
- package/dist/prosemirror/attrs/index.js +2 -1
- package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +8 -0
- package/dist/prosemirror/bookmarkBoundaryAttrs.js +66 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +170 -52
- package/dist/prosemirror/conversion/toProseDoc.js +169 -61
- 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/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/nodes.d.ts +13 -1
- package/dist/prosemirror/validation.js +93 -0
- package/package.json +1 -1
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { expectBookmarkBoundaryAttrs } from "../../bookmarkBoundaryAttrs.js";
|
|
2
|
+
import { createNodeExtension } from "../create.js";
|
|
3
|
+
//#region src/prosemirror/extensions/nodes/BookmarkBoundaryExtension.ts
|
|
4
|
+
/** Zero-width bookmark boundary that preserves its position through ProseMirror edits. */
|
|
5
|
+
function readNonnegativeInteger(value) {
|
|
6
|
+
if (value === null || !/^(?:0|[1-9]\d*)$/.test(value)) return false;
|
|
7
|
+
const parsed = Number(value);
|
|
8
|
+
return Number.isSafeInteger(parsed) ? parsed : false;
|
|
9
|
+
}
|
|
10
|
+
function readOptionalColumn(dom, attribute) {
|
|
11
|
+
const value = dom.getAttribute(attribute);
|
|
12
|
+
return value === null ? void 0 : readNonnegativeInteger(value);
|
|
13
|
+
}
|
|
14
|
+
const BookmarkBoundaryExtension = createNodeExtension({
|
|
15
|
+
name: "bookmarkBoundary",
|
|
16
|
+
schemaNodeName: "bookmarkBoundary",
|
|
17
|
+
nodeSpec: (options) => ({
|
|
18
|
+
inline: true,
|
|
19
|
+
group: "inline",
|
|
20
|
+
marks: "_",
|
|
21
|
+
atom: true,
|
|
22
|
+
selectable: false,
|
|
23
|
+
attrs: {
|
|
24
|
+
type: {},
|
|
25
|
+
id: {},
|
|
26
|
+
name: { default: null },
|
|
27
|
+
colFirst: { default: null },
|
|
28
|
+
colLast: { default: null }
|
|
29
|
+
},
|
|
30
|
+
parseDOM: [{
|
|
31
|
+
tag: "span[data-docx-bookmark-boundary]",
|
|
32
|
+
getAttrs(dom) {
|
|
33
|
+
const type = dom.getAttribute("data-docx-bookmark-boundary");
|
|
34
|
+
const id = readNonnegativeInteger(dom.getAttribute("data-docx-bookmark-id"));
|
|
35
|
+
if (type !== "start" && type !== "end" || id === false) return false;
|
|
36
|
+
const name = dom.getAttribute("data-docx-bookmark-name");
|
|
37
|
+
if (type === "start" && !name) return false;
|
|
38
|
+
const colFirst = readOptionalColumn(dom, "data-docx-bookmark-col-first");
|
|
39
|
+
const colLast = readOptionalColumn(dom, "data-docx-bookmark-col-last");
|
|
40
|
+
if (colFirst === false || colLast === false) return false;
|
|
41
|
+
return {
|
|
42
|
+
type,
|
|
43
|
+
id,
|
|
44
|
+
...name ? { name } : {},
|
|
45
|
+
...colFirst !== void 0 ? { colFirst } : {},
|
|
46
|
+
...colLast !== void 0 ? { colLast } : {}
|
|
47
|
+
};
|
|
48
|
+
}
|
|
49
|
+
}],
|
|
50
|
+
toDOM(node) {
|
|
51
|
+
const attrs = expectBookmarkBoundaryAttrs(node);
|
|
52
|
+
return ["span", {
|
|
53
|
+
"data-docx-bookmark-boundary": attrs.type,
|
|
54
|
+
"data-docx-bookmark-id": String(attrs.id),
|
|
55
|
+
...attrs.type === "start" ? { "data-docx-bookmark-name": attrs.name } : {},
|
|
56
|
+
...attrs.type === "start" && attrs.colFirst !== void 0 ? { "data-docx-bookmark-col-first": String(attrs.colFirst) } : {},
|
|
57
|
+
...attrs.type === "start" && attrs.colLast !== void 0 ? { "data-docx-bookmark-col-last": String(attrs.colLast) } : {},
|
|
58
|
+
"aria-hidden": "true",
|
|
59
|
+
contenteditable: "false",
|
|
60
|
+
style: "display: none;",
|
|
61
|
+
...options.getInternalClipboardToken ? { "data-docx-internal-clipboard": options.getInternalClipboardToken() } : {}
|
|
62
|
+
}];
|
|
63
|
+
}
|
|
64
|
+
})
|
|
65
|
+
});
|
|
66
|
+
//#endregion
|
|
67
|
+
export { BookmarkBoundaryExtension };
|
|
@@ -1,11 +1,17 @@
|
|
|
1
1
|
import { NodeExtension } from "../types.js";
|
|
2
2
|
//#region src/prosemirror/extensions/nodes/FieldExtension.d.ts
|
|
3
3
|
/**
|
|
4
|
-
* Field
|
|
4
|
+
* Field extensions — atomic inline fields (PAGE, NUMPAGES, REF, etc.).
|
|
5
5
|
*
|
|
6
|
-
*
|
|
7
|
-
*
|
|
6
|
+
* Ordinary fields are true ProseMirror leaves, so `textContent`, `textBetween`,
|
|
7
|
+
* and the default plain-text clipboard serializer can use `leafText`.
|
|
8
|
+
* Simple fields that must preserve hyperlink structure use a distinct atomic
|
|
9
|
+
* node with inline children; their visible text comes from those children.
|
|
8
10
|
*/
|
|
11
|
+
type StructuredFieldOptions = {
|
|
12
|
+
getInternalClipboardToken?: () => string;
|
|
13
|
+
};
|
|
9
14
|
declare const FieldExtension: (options?: Partial<Record<string, unknown>> | undefined) => NodeExtension;
|
|
15
|
+
declare const StructuredFieldExtension: (options?: Partial<StructuredFieldOptions> | undefined) => NodeExtension;
|
|
10
16
|
//#endregion
|
|
11
|
-
export { FieldExtension };
|
|
17
|
+
export { FieldExtension, StructuredFieldExtension };
|
|
@@ -2,12 +2,49 @@ import { parseFieldInstruction } from "../../../docx/fieldParser.js";
|
|
|
2
2
|
import { expectFieldAttrs } from "../../attrs/index.js";
|
|
3
3
|
import { createNodeExtension } from "../create.js";
|
|
4
4
|
//#region src/prosemirror/extensions/nodes/FieldExtension.ts
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
5
|
+
const createFieldAttrs = () => ({
|
|
6
|
+
fieldType: { default: "UNKNOWN" },
|
|
7
|
+
instruction: { default: "" },
|
|
8
|
+
displayText: { default: "" },
|
|
9
|
+
_numberedRefBaseline: { default: void 0 },
|
|
10
|
+
fieldKind: { default: "simple" },
|
|
11
|
+
fldLock: { default: false },
|
|
12
|
+
dirty: { default: false }
|
|
13
|
+
});
|
|
14
|
+
const readFieldDomAttrs = (dom) => ({
|
|
15
|
+
fieldType: dom.dataset["fieldType"] ?? "UNKNOWN",
|
|
16
|
+
instruction: dom.dataset["instruction"] ?? "",
|
|
17
|
+
displayText: dom.textContent ?? "",
|
|
18
|
+
fieldKind: dom.dataset["fieldKind"] ?? "simple",
|
|
19
|
+
fldLock: dom.dataset["fldLock"] === "true",
|
|
20
|
+
dirty: dom.dataset["dirty"] === "true"
|
|
21
|
+
});
|
|
22
|
+
const getFieldDomAttrs = (node) => {
|
|
23
|
+
const { fieldType, instruction, fieldKind, fldLock, dirty } = expectFieldAttrs(node);
|
|
24
|
+
return {
|
|
25
|
+
class: `docx-field docx-field-${fieldType.toLowerCase()}`,
|
|
26
|
+
"data-field-type": fieldType,
|
|
27
|
+
"data-instruction": instruction,
|
|
28
|
+
"data-field-kind": fieldKind,
|
|
29
|
+
...fldLock ? { "data-fld-lock": "true" } : {},
|
|
30
|
+
...dirty ? { "data-dirty": "true" } : {},
|
|
31
|
+
style: "outline: 1px solid var(--doc-field-outline, rgba(200,200,200,0.4)); padding: 0 1px; border-radius: 2px;"
|
|
32
|
+
};
|
|
33
|
+
};
|
|
34
|
+
const getFieldVisibleText = (node) => {
|
|
35
|
+
const { fieldType, instruction, displayText } = expectFieldAttrs(node);
|
|
36
|
+
if (displayText) return displayText;
|
|
37
|
+
switch (fieldType) {
|
|
38
|
+
case "PAGE": return "{page}";
|
|
39
|
+
case "NUMPAGES": return "{pages}";
|
|
40
|
+
case "DATE":
|
|
41
|
+
case "TIME":
|
|
42
|
+
case "CREATEDATE":
|
|
43
|
+
case "SAVEDATE": return (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
44
|
+
case "MERGEFIELD": return `«${getMergeFieldName(instruction)}»`;
|
|
45
|
+
default: return `{${fieldType}}`;
|
|
46
|
+
}
|
|
47
|
+
};
|
|
11
48
|
const FieldExtension = createNodeExtension({
|
|
12
49
|
name: "field",
|
|
13
50
|
schemaNodeName: "field",
|
|
@@ -16,72 +53,53 @@ const FieldExtension = createNodeExtension({
|
|
|
16
53
|
group: "inline",
|
|
17
54
|
atom: true,
|
|
18
55
|
selectable: true,
|
|
19
|
-
attrs:
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
56
|
+
attrs: createFieldAttrs(),
|
|
57
|
+
leafText: getFieldVisibleText,
|
|
58
|
+
parseDOM: [{
|
|
59
|
+
tag: "span.docx-field:not([data-field-structured])",
|
|
60
|
+
getAttrs: readFieldDomAttrs
|
|
61
|
+
}],
|
|
62
|
+
toDOM(node) {
|
|
63
|
+
return [
|
|
64
|
+
"span",
|
|
65
|
+
getFieldDomAttrs(node),
|
|
66
|
+
getFieldVisibleText(node)
|
|
67
|
+
];
|
|
68
|
+
}
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
const StructuredFieldExtension = createNodeExtension({
|
|
72
|
+
name: "structuredField",
|
|
73
|
+
schemaNodeName: "structuredField",
|
|
74
|
+
nodeSpec: (options) => ({
|
|
75
|
+
inline: true,
|
|
76
|
+
group: "inline",
|
|
77
|
+
content: "(text | bookmarkBoundary | tab | symbol | hardBreak | image | shape | renderedPageBreak | textBoxAnchor)+",
|
|
78
|
+
atom: true,
|
|
79
|
+
selectable: true,
|
|
80
|
+
attrs: createFieldAttrs(),
|
|
33
81
|
parseDOM: [{
|
|
34
|
-
tag: "span.docx-field",
|
|
82
|
+
tag: "span.docx-field[data-field-structured=\"true\"]",
|
|
35
83
|
getAttrs(dom) {
|
|
36
|
-
return
|
|
37
|
-
|
|
38
|
-
instruction: dom.dataset["instruction"] ?? "",
|
|
39
|
-
displayText: dom.textContent,
|
|
40
|
-
fieldKind: dom.dataset["fieldKind"] ?? "simple",
|
|
41
|
-
fldLock: dom.dataset["fldLock"] === "true",
|
|
42
|
-
dirty: dom.dataset["dirty"] === "true"
|
|
43
|
-
};
|
|
84
|
+
if (dom.dataset["fieldKind"] !== "simple" || !dom.querySelector("a[href]")) return false;
|
|
85
|
+
return readFieldDomAttrs(dom);
|
|
44
86
|
}
|
|
45
87
|
}],
|
|
46
88
|
toDOM(node) {
|
|
47
|
-
const { fieldType, instruction, displayText, fieldKind, fldLock, dirty } = expectFieldAttrs(node);
|
|
48
|
-
let text = displayText || "";
|
|
49
|
-
if (!text) switch (fieldType) {
|
|
50
|
-
case "PAGE":
|
|
51
|
-
text = "{page}";
|
|
52
|
-
break;
|
|
53
|
-
case "NUMPAGES":
|
|
54
|
-
text = "{pages}";
|
|
55
|
-
break;
|
|
56
|
-
case "DATE":
|
|
57
|
-
case "TIME":
|
|
58
|
-
case "CREATEDATE":
|
|
59
|
-
case "SAVEDATE":
|
|
60
|
-
text = (/* @__PURE__ */ new Date()).toLocaleDateString();
|
|
61
|
-
break;
|
|
62
|
-
case "MERGEFIELD":
|
|
63
|
-
text = `«${getMergeFieldName(instruction)}»`;
|
|
64
|
-
break;
|
|
65
|
-
default: text = `{${fieldType}}`;
|
|
66
|
-
}
|
|
67
89
|
return [
|
|
68
90
|
"span",
|
|
69
91
|
{
|
|
70
|
-
|
|
71
|
-
"data-field-
|
|
72
|
-
"data-
|
|
73
|
-
"data-field-kind": fieldKind,
|
|
74
|
-
...fldLock ? { "data-fld-lock": "true" } : {},
|
|
75
|
-
...dirty ? { "data-dirty": "true" } : {},
|
|
76
|
-
style: "outline: 1px solid var(--doc-field-outline, rgba(200,200,200,0.4)); padding: 0 1px; border-radius: 2px;"
|
|
92
|
+
...getFieldDomAttrs(node),
|
|
93
|
+
"data-field-structured": "true",
|
|
94
|
+
...options.getInternalClipboardToken ? { "data-docx-internal-clipboard": options.getInternalClipboardToken() } : {}
|
|
77
95
|
},
|
|
78
|
-
|
|
96
|
+
0
|
|
79
97
|
];
|
|
80
98
|
}
|
|
81
|
-
}
|
|
99
|
+
})
|
|
82
100
|
});
|
|
83
101
|
function getMergeFieldName(instruction) {
|
|
84
102
|
return parseFieldInstruction(instruction).argument ?? "";
|
|
85
103
|
}
|
|
86
104
|
//#endregion
|
|
87
|
-
export { FieldExtension };
|
|
105
|
+
export { FieldExtension, StructuredFieldExtension };
|
|
@@ -7,6 +7,9 @@ import { NodeExtension } from "../types.js";
|
|
|
7
7
|
* map its position as surrounding text is edited. The save conversion consumes
|
|
8
8
|
* it when the sibling text box is restored to the DOCX run stream.
|
|
9
9
|
*/
|
|
10
|
-
|
|
10
|
+
type TextBoxAnchorOptions = {
|
|
11
|
+
getInternalClipboardToken?: () => string;
|
|
12
|
+
};
|
|
13
|
+
declare const TextBoxAnchorExtension: (options?: Partial<TextBoxAnchorOptions> | undefined) => NodeExtension;
|
|
11
14
|
//#endregion
|
|
12
15
|
export { TextBoxAnchorExtension };
|
|
@@ -11,7 +11,7 @@ import { createNodeExtension } from "../create.js";
|
|
|
11
11
|
const TextBoxAnchorExtension = createNodeExtension({
|
|
12
12
|
name: "textBoxAnchor",
|
|
13
13
|
schemaNodeName: "textBoxAnchor",
|
|
14
|
-
nodeSpec: {
|
|
14
|
+
nodeSpec: (options) => ({
|
|
15
15
|
inline: true,
|
|
16
16
|
group: "inline",
|
|
17
17
|
marks: "_",
|
|
@@ -32,10 +32,11 @@ const TextBoxAnchorExtension = createNodeExtension({
|
|
|
32
32
|
"data-docx-textbox-anchor": anchorId,
|
|
33
33
|
"aria-hidden": "true",
|
|
34
34
|
contenteditable: "false",
|
|
35
|
-
style: "display: inline-block; width: 0; height: 0; overflow: hidden; pointer-events: none;"
|
|
35
|
+
style: "display: inline-block; width: 0; height: 0; overflow: hidden; pointer-events: none;",
|
|
36
|
+
...options.getInternalClipboardToken ? { "data-docx-internal-clipboard": options.getInternalClipboardToken() } : {}
|
|
36
37
|
}];
|
|
37
38
|
}
|
|
38
|
-
}
|
|
39
|
+
})
|
|
39
40
|
});
|
|
40
41
|
//#endregion
|
|
41
42
|
export { TextBoxAnchorExtension };
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
import { document_d_exports } from "../types/document.js";
|
|
2
|
+
import { ParagraphAttrs } from "./schema/nodes.js";
|
|
3
|
+
//#region src/prosemirror/listMarker.d.ts
|
|
4
|
+
type ListCounterState = {
|
|
5
|
+
counters: Map<number, number[]>;
|
|
6
|
+
abstractCounters: Map<number, number[]>;
|
|
7
|
+
seenLevels: Set<string>;
|
|
8
|
+
lastAdvancedNumId?: number;
|
|
9
|
+
};
|
|
10
|
+
type ListCounterStreams = {
|
|
11
|
+
final: ListCounterState;
|
|
12
|
+
original: ListCounterState;
|
|
13
|
+
};
|
|
14
|
+
type ListCounterStream = keyof ListCounterStreams;
|
|
15
|
+
type AdvancedListMarker = {
|
|
16
|
+
marker: string | null;
|
|
17
|
+
counterAttrs: ParagraphAttrs;
|
|
18
|
+
counterState: ListCounterState;
|
|
19
|
+
stream: ListCounterStream;
|
|
20
|
+
};
|
|
21
|
+
type VisibleListMarker = {
|
|
22
|
+
marker: string | null;
|
|
23
|
+
counterAttrs: ParagraphAttrs;
|
|
24
|
+
counterState: ListCounterState;
|
|
25
|
+
stream: ListCounterStream;
|
|
26
|
+
advances: readonly AdvancedListMarker[];
|
|
27
|
+
};
|
|
28
|
+
type ResolvedListComponent = {
|
|
29
|
+
level: number;
|
|
30
|
+
relativeStart: number;
|
|
31
|
+
start: number;
|
|
32
|
+
end: number;
|
|
33
|
+
};
|
|
34
|
+
type ResolvedListTemplate = {
|
|
35
|
+
value: string;
|
|
36
|
+
components: readonly ResolvedListComponent[];
|
|
37
|
+
};
|
|
38
|
+
declare const MAX_LIST_LEVEL = 8;
|
|
39
|
+
declare function createListCounterState(): ListCounterState;
|
|
40
|
+
declare function cloneListCounterState(state: ListCounterState): ListCounterState;
|
|
41
|
+
/**
|
|
42
|
+
* Advance the counter stream that paints this paragraph's marker. Normal
|
|
43
|
+
* paragraphs advance both final and original streams; inserted/deleted list
|
|
44
|
+
* paragraphs advance only the document view in which they exist.
|
|
45
|
+
*/
|
|
46
|
+
declare function advanceVisibleListMarker(attrs: ParagraphAttrs, streams: ListCounterStreams): VisibleListMarker;
|
|
47
|
+
declare function formatCounter(value: number, format: document_d_exports.NumberFormat | undefined): string;
|
|
48
|
+
type ResolveListTemplateOptions = {
|
|
49
|
+
template: string;
|
|
50
|
+
counters: number[];
|
|
51
|
+
levelFormats?: document_d_exports.NumberFormat[] | undefined;
|
|
52
|
+
forceDecimal?: boolean | undefined;
|
|
53
|
+
};
|
|
54
|
+
declare function resolveListTemplate(options: ResolveListTemplateOptions): string;
|
|
55
|
+
declare function resolveListTemplateWithComponents({ template, counters, levelFormats, forceDecimal }: ResolveListTemplateOptions): ResolvedListTemplate;
|
|
56
|
+
declare function advanceListMarker(attrs: ParagraphAttrs, state: ListCounterState): string | null;
|
|
57
|
+
//#endregion
|
|
58
|
+
export { AdvancedListMarker, ListCounterState, ListCounterStream, ListCounterStreams, MAX_LIST_LEVEL, ResolvedListComponent, ResolvedListTemplate, VisibleListMarker, advanceListMarker, advanceVisibleListMarker, cloneListCounterState, createListCounterState, formatCounter, resolveListTemplate, resolveListTemplateWithComponents };
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
import { convertBulletToUnicode } from "../docx/bulletMarkers.js";
|
|
2
|
+
import { formatOoxmlCounter } from "../docx/ooxmlCounterFormatter.js";
|
|
3
|
+
//#region src/prosemirror/listMarker.ts
|
|
4
|
+
const MAX_LIST_LEVEL = 8;
|
|
5
|
+
function createListCounterState() {
|
|
6
|
+
return {
|
|
7
|
+
counters: /* @__PURE__ */ new Map(),
|
|
8
|
+
abstractCounters: /* @__PURE__ */ new Map(),
|
|
9
|
+
seenLevels: /* @__PURE__ */ new Set()
|
|
10
|
+
};
|
|
11
|
+
}
|
|
12
|
+
function cloneListCounterState(state) {
|
|
13
|
+
const counters = /* @__PURE__ */ new Map();
|
|
14
|
+
for (const [numId, values] of state.counters) counters.set(numId, [...values]);
|
|
15
|
+
const abstractCounters = /* @__PURE__ */ new Map();
|
|
16
|
+
for (const [abstractNumId, values] of state.abstractCounters) abstractCounters.set(abstractNumId, [...values]);
|
|
17
|
+
return {
|
|
18
|
+
counters,
|
|
19
|
+
abstractCounters,
|
|
20
|
+
seenLevels: new Set(state.seenLevels),
|
|
21
|
+
...state.lastAdvancedNumId !== void 0 ? { lastAdvancedNumId: state.lastAdvancedNumId } : {}
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
const isListNumPr = (value) => value !== void 0 && value !== null;
|
|
25
|
+
const sameListNumPr = (left, right) => left.numId === right.numId && left.ilvl === right.ilvl;
|
|
26
|
+
function previousListAttrs(attrs) {
|
|
27
|
+
const previous = (attrs._propertyChanges?.find(({ previousFormatting }) => {
|
|
28
|
+
const previousNumPr = previousFormatting?.numPr;
|
|
29
|
+
return isListNumPr(previousNumPr) && (!attrs.numPr || !sameListNumPr(previousNumPr, attrs.numPr));
|
|
30
|
+
}))?.previousFormatting;
|
|
31
|
+
if (!previous || !isListNumPr(previous.numPr)) return null;
|
|
32
|
+
return {
|
|
33
|
+
numPr: previous.numPr,
|
|
34
|
+
...previous.listIsBullet !== void 0 ? { listIsBullet: previous.listIsBullet } : {},
|
|
35
|
+
...previous.listIsLegal !== void 0 ? { listIsLegal: previous.listIsLegal } : {},
|
|
36
|
+
...previous.listNumFmt !== void 0 ? { listNumFmt: previous.listNumFmt } : {},
|
|
37
|
+
...previous.listMarker !== void 0 ? { listMarker: previous.listMarker } : {},
|
|
38
|
+
...previous.listMarkerHidden !== void 0 ? { listMarkerHidden: previous.listMarkerHidden } : {},
|
|
39
|
+
...previous.listLevelNumFmts !== void 0 ? { listLevelNumFmts: previous.listLevelNumFmts } : {},
|
|
40
|
+
...previous.listLevelStarts !== void 0 ? { listLevelStarts: previous.listLevelStarts } : {},
|
|
41
|
+
...previous.listAbstractNumId !== void 0 ? { listAbstractNumId: previous.listAbstractNumId } : {},
|
|
42
|
+
...previous.listStartOverride !== void 0 ? { listStartOverride: previous.listStartOverride } : {}
|
|
43
|
+
};
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Advance the counter stream that paints this paragraph's marker. Normal
|
|
47
|
+
* paragraphs advance both final and original streams; inserted/deleted list
|
|
48
|
+
* paragraphs advance only the document view in which they exist.
|
|
49
|
+
*/
|
|
50
|
+
function advanceVisibleListMarker(attrs, streams) {
|
|
51
|
+
const advances = [];
|
|
52
|
+
const advance = (counterAttrs, stream) => {
|
|
53
|
+
const counterState = streams[stream];
|
|
54
|
+
const advanced = {
|
|
55
|
+
marker: advanceListMarker(counterAttrs, counterState),
|
|
56
|
+
counterAttrs,
|
|
57
|
+
counterState,
|
|
58
|
+
stream
|
|
59
|
+
};
|
|
60
|
+
advances.push(advanced);
|
|
61
|
+
return advanced;
|
|
62
|
+
};
|
|
63
|
+
const previous = previousListAttrs(attrs);
|
|
64
|
+
if (!attrs.numPr) return {
|
|
65
|
+
...advance(previous ?? attrs, previous ? "original" : "final"),
|
|
66
|
+
advances
|
|
67
|
+
};
|
|
68
|
+
if (attrs.pPrMark?.kind === "del") return {
|
|
69
|
+
...advance(attrs, "original"),
|
|
70
|
+
advances
|
|
71
|
+
};
|
|
72
|
+
const numberingWasAdded = attrs._propertyChanges?.some(({ previousFormatting }) => previousFormatting && Object.hasOwn(previousFormatting, "numPr") && previousFormatting.numPr == null);
|
|
73
|
+
const numberingChanged = previous?.numPr !== void 0 && !sameListNumPr(previous.numPr, attrs.numPr);
|
|
74
|
+
const visible = advance(attrs, "final");
|
|
75
|
+
if (attrs.pPrMark?.kind !== "ins" && !numberingWasAdded && !numberingChanged) advance(attrs, "original");
|
|
76
|
+
else if (numberingChanged && previous) advance(previous, "original");
|
|
77
|
+
return {
|
|
78
|
+
...visible,
|
|
79
|
+
advances
|
|
80
|
+
};
|
|
81
|
+
}
|
|
82
|
+
function formatCounter(value, format) {
|
|
83
|
+
return formatOoxmlCounter(value, format);
|
|
84
|
+
}
|
|
85
|
+
function resolveListTemplate(options) {
|
|
86
|
+
return resolveListTemplateWithComponents(options).value;
|
|
87
|
+
}
|
|
88
|
+
function resolveListTemplateWithComponents({ template, counters, levelFormats, forceDecimal = false }) {
|
|
89
|
+
const components = [];
|
|
90
|
+
let value = "";
|
|
91
|
+
let sourceEnd = 0;
|
|
92
|
+
for (const match of template.matchAll(/%(?<digit>\d)(?<punct>[.):\]])?/gu)) {
|
|
93
|
+
const sourceStart = match.index;
|
|
94
|
+
const relativeStart = value.length;
|
|
95
|
+
value += template.slice(sourceEnd, sourceStart);
|
|
96
|
+
sourceEnd = sourceStart + match[0].length;
|
|
97
|
+
const index = Number.parseInt(match.groups?.["digit"] ?? "", 10) - 1;
|
|
98
|
+
if (index < 0) continue;
|
|
99
|
+
const counter = counters[index];
|
|
100
|
+
if (counter === void 0 || Number.isNaN(counter)) continue;
|
|
101
|
+
const formatted = formatCounter(counter, forceDecimal ? "decimal" : levelFormats?.[index]);
|
|
102
|
+
if (!formatted) continue;
|
|
103
|
+
const start = value.length;
|
|
104
|
+
value += formatted;
|
|
105
|
+
components.push({
|
|
106
|
+
level: index,
|
|
107
|
+
relativeStart,
|
|
108
|
+
start,
|
|
109
|
+
end: value.length
|
|
110
|
+
});
|
|
111
|
+
value += match.groups?.["punct"] ?? "";
|
|
112
|
+
}
|
|
113
|
+
value += template.slice(sourceEnd);
|
|
114
|
+
return {
|
|
115
|
+
value,
|
|
116
|
+
components
|
|
117
|
+
};
|
|
118
|
+
}
|
|
119
|
+
function getLastListCounters(state) {
|
|
120
|
+
return state.lastAdvancedNumId === void 0 ? void 0 : state.counters.get(state.lastAdvancedNumId);
|
|
121
|
+
}
|
|
122
|
+
function formatNumberedMarker(counters, level) {
|
|
123
|
+
const parts = [];
|
|
124
|
+
for (let index = 0; index <= level; index += 1) {
|
|
125
|
+
const value = counters[index] ?? 0;
|
|
126
|
+
if (!Number.isFinite(value) || value <= 0) break;
|
|
127
|
+
parts.push(value);
|
|
128
|
+
}
|
|
129
|
+
return parts.length === 0 ? "1." : `${parts.join(".")}.`;
|
|
130
|
+
}
|
|
131
|
+
function advanceListMarker(attrs, state) {
|
|
132
|
+
const level = attrs.numPr?.ilvl ?? 0;
|
|
133
|
+
if (!Number.isInteger(level) || level < 0 || level > 8) return null;
|
|
134
|
+
const numId = attrs.numPr?.numId;
|
|
135
|
+
if (numId === void 0 || numId === 0) {
|
|
136
|
+
if (attrs.listMarker?.includes("%") && !attrs.listIsBullet) {
|
|
137
|
+
const counters = getLastListCounters(state);
|
|
138
|
+
if (counters) return resolveListTemplate({
|
|
139
|
+
template: attrs.listMarker,
|
|
140
|
+
counters,
|
|
141
|
+
levelFormats: attrs.listLevelNumFmts,
|
|
142
|
+
forceDecimal: attrs.listIsLegal
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
return null;
|
|
146
|
+
}
|
|
147
|
+
if (attrs.listIsBullet) return convertBulletToUnicode(attrs.listMarker ?? "");
|
|
148
|
+
const counters = state.counters.get(numId) ?? Array.from({ length: 9 }, () => NaN);
|
|
149
|
+
const abstractNumId = attrs.listAbstractNumId;
|
|
150
|
+
if (level > 0) {
|
|
151
|
+
const latestAbstractCounters = abstractNumId === void 0 ? void 0 : state.abstractCounters.get(abstractNumId);
|
|
152
|
+
if (counters.slice(0, level).every((counter) => !Number.isFinite(counter))) for (let index = 0; index < level; index += 1) {
|
|
153
|
+
const latestCounter = latestAbstractCounters?.[index];
|
|
154
|
+
counters[index] = latestCounter !== void 0 && Number.isFinite(latestCounter) ? latestCounter : attrs.listLevelStarts?.[index] ?? 1;
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
const seenKey = `${numId}:${level}`;
|
|
158
|
+
if (!state.seenLevels.has(seenKey)) {
|
|
159
|
+
state.seenLevels.add(seenKey);
|
|
160
|
+
if (attrs.listStartOverride != null) counters[level] = attrs.listStartOverride - 1;
|
|
161
|
+
}
|
|
162
|
+
if (!Number.isFinite(counters[level])) counters[level] = (attrs.listLevelStarts?.[level] ?? 1) - 1;
|
|
163
|
+
counters[level] = (counters[level] ?? 0) + 1;
|
|
164
|
+
for (let index = level + 1; index < counters.length; index += 1) counters[index] = NaN;
|
|
165
|
+
const childAdvances = attrs.listImplicitChildLevelAdvances ?? 0;
|
|
166
|
+
if (childAdvances > 0 && level + 1 < counters.length) {
|
|
167
|
+
const childCounter = counters[level + 1];
|
|
168
|
+
counters[level + 1] = (childCounter === void 0 || !Number.isFinite(childCounter) ? 0 : childCounter) + childAdvances;
|
|
169
|
+
}
|
|
170
|
+
state.counters.set(numId, counters);
|
|
171
|
+
state.lastAdvancedNumId = numId;
|
|
172
|
+
if (abstractNumId !== void 0) state.abstractCounters.set(abstractNumId, [...counters]);
|
|
173
|
+
const levelFormats = attrs.listLevelNumFmts ?? (attrs.listNumFmt ? [attrs.listNumFmt] : void 0);
|
|
174
|
+
if (attrs.listMarker?.includes("%")) return resolveListTemplate({
|
|
175
|
+
template: attrs.listMarker,
|
|
176
|
+
counters,
|
|
177
|
+
levelFormats,
|
|
178
|
+
forceDecimal: attrs.listIsLegal
|
|
179
|
+
});
|
|
180
|
+
if (attrs.listMarker) return attrs.listMarker;
|
|
181
|
+
if ((levelFormats?.[level] ?? attrs.listNumFmt) === "none" || attrs.listMarker === "") return null;
|
|
182
|
+
return formatNumberedMarker(counters, level);
|
|
183
|
+
}
|
|
184
|
+
//#endregion
|
|
185
|
+
export { MAX_LIST_LEVEL, advanceListMarker, advanceVisibleListMarker, cloneListCounterState, createListCounterState, formatCounter, resolveListTemplate, resolveListTemplateWithComponents };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { ListCounterState } from "./listMarker.js";
|
|
2
|
+
import { Node } from "prosemirror-model";
|
|
3
|
+
//#region src/prosemirror/numberedRefFields.d.ts
|
|
4
|
+
type NumberSwitch = "n" | "r" | "w";
|
|
5
|
+
type NumberedRefSpec = {
|
|
6
|
+
bookmark: string;
|
|
7
|
+
numberSwitch: NumberSwitch;
|
|
8
|
+
};
|
|
9
|
+
type NumberedRefResolutionOptions = {
|
|
10
|
+
/** Counter snapshot at the start of this body story. The resolver consumes it in place. */
|
|
11
|
+
listCounterState?: ListCounterState;
|
|
12
|
+
/** Counter snapshot for tracked deletions in the original document view. */
|
|
13
|
+
originalListCounterState?: ListCounterState;
|
|
14
|
+
};
|
|
15
|
+
declare function parseNumberedRefInstruction(instruction: string): NumberedRefSpec | null;
|
|
16
|
+
/** Stamp only proven numbered REF fields with clone-safe calibration state. */
|
|
17
|
+
declare function stampNumberedRefFieldBaselines(doc: Node): Node;
|
|
18
|
+
/**
|
|
19
|
+
* Resolve bounded, numbered REF fields against bookmarks in the ProseMirror body story.
|
|
20
|
+
* Unsupported or uncalibrated fields are absent from the returned map and retain their cache.
|
|
21
|
+
*/
|
|
22
|
+
declare function resolveNumberedRefFields(doc: Node, options?: NumberedRefResolutionOptions): ReadonlyMap<Node, string>;
|
|
23
|
+
//#endregion
|
|
24
|
+
export { NumberedRefResolutionOptions, parseNumberedRefInstruction, resolveNumberedRefFields, stampNumberedRefFieldBaselines };
|