@stll/folio-core 0.28.1 → 0.30.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/controller/folioEditor.d.ts +28 -1
- package/dist/controller/folioEditor.js +37 -26
- package/dist/controller/fontReadiness.js +28 -24
- package/dist/controller/hiddenEditorManager.d.ts +2 -7
- package/dist/controller/layoutPipeline.js +33 -3
- package/dist/controller/noteEditorManager.d.ts +3 -6
- package/dist/docx/metafileSvg.d.ts +12 -0
- package/dist/docx/metafileSvg.js +462 -0
- package/dist/docx/paragraphTextBoxEnrichment.js +55 -4
- package/dist/docx/parser.d.ts +2 -2
- package/dist/docx/parser.js +26 -0
- package/dist/docx/runParser.js +2 -1
- package/dist/docx/serializer/runSerializer.js +14 -2
- package/dist/docx/styleParser.js +2 -1
- package/dist/docx/textBoxParser.js +14 -0
- package/dist/docx/vmlImageParser.js +72 -152
- package/dist/docx/vmlPreview.d.ts +31 -0
- package/dist/docx/vmlPreview.js +535 -0
- package/dist/docx/wrapTypes.d.ts +2 -36
- package/dist/docx/wrapTypes.js +1 -39
- package/dist/fonts/fontAlternates.d.ts +8 -0
- package/dist/fonts/fontAlternates.js +16 -0
- package/dist/layout-bridge/convert/footnoteLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/footnoteLayout.js +34 -3
- package/dist/layout-bridge/convert/headerFooterLayout.d.ts +1 -0
- package/dist/layout-bridge/convert/headerFooterLayout.js +5 -0
- package/dist/layout-bridge/convert/toFlowBlocks.d.ts +4 -2
- package/dist/layout-bridge/convert/toFlowBlocks.js +136 -120
- package/dist/layout-bridge/dom/noteStoryDom.d.ts +1 -1
- package/dist/layout-bridge/engine/selectionRects.js +3 -1
- package/dist/layout-engine/index.js +65 -32
- package/dist/layout-engine/justifiedLineFit.d.ts +7 -0
- package/dist/layout-engine/justifiedLineFit.js +6 -0
- package/dist/layout-engine/keep-together.js +15 -10
- package/dist/layout-engine/measure/complexScriptFormatting.d.ts +6 -1
- package/dist/layout-engine/measure/complexScriptFormatting.js +11 -2
- package/dist/layout-engine/measure/floatingTablePosition.d.ts +16 -1
- package/dist/layout-engine/measure/floatingTablePosition.js +29 -6
- package/dist/layout-engine/measure/lineBreakProvider.js +67 -8
- package/dist/layout-engine/measure/listMarkerWidth.d.ts +1 -0
- package/dist/layout-engine/measure/listMarkerWidth.js +23 -10
- package/dist/layout-engine/measure/measureBlocks.d.ts +15 -0
- package/dist/layout-engine/measure/measureBlocks.js +197 -30
- package/dist/layout-engine/measure/measureContainer.js +20 -24
- package/dist/layout-engine/measure/measureHelpers.d.ts +1 -1
- package/dist/layout-engine/measure/measureHelpers.js +14 -8
- package/dist/layout-engine/measure/measureParagraph.js +111 -37
- package/dist/layout-engine/measure/measureTypes.d.ts +3 -0
- package/dist/layout-engine/measure/tabCalculator.d.ts +123 -0
- package/dist/layout-engine/measure/tabCalculator.js +144 -0
- package/dist/layout-engine/measure/tableCellFlow.d.ts +21 -1
- package/dist/layout-engine/measure/tableCellFlow.js +45 -8
- package/dist/layout-engine/measure/tableInlinePlacement.d.ts +10 -2
- package/dist/layout-engine/measure/tableInlinePlacement.js +17 -15
- package/dist/layout-engine/paginator.d.ts +26 -2
- package/dist/layout-engine/paginator.js +76 -14
- package/dist/layout-engine/paragraphSpacing.d.ts +3 -1
- package/dist/layout-engine/paragraphSpacing.js +4 -3
- package/dist/layout-engine/renderedBreakReconciliation.d.ts +14 -3
- package/dist/layout-engine/renderedBreakReconciliation.js +45 -14
- package/dist/layout-engine/section-breaks.d.ts +3 -1
- package/dist/layout-engine/section-breaks.js +6 -2
- package/dist/layout-engine/types.d.ts +18 -1
- package/dist/layout-engine/types.js +1 -1
- package/dist/layout-painter/imageLayout.d.ts +1 -1
- package/dist/layout-painter/renderPage.js +6 -1
- package/dist/layout-painter/renderParagraph.js +169 -82
- package/dist/layout-painter/renderTable.d.ts +1 -1
- package/dist/layout-painter/renderTable.js +18 -7
- package/dist/layout-painter/renderTextBox.d.ts +1 -1
- package/dist/layout-painter/renderTextBox.js +6 -1
- package/dist/markdown/renderParagraph.js +7 -2
- package/dist/model.d.ts +3 -3
- package/dist/model.js +2 -2
- package/dist/paged-layout/sectionBlockWidths.d.ts +14 -0
- package/dist/paged-layout/sectionBlockWidths.js +103 -14
- package/dist/prosemirror/attrs/index.js +14 -2
- package/dist/prosemirror/bookmarkBoundaryAttrs.d.ts +8 -0
- package/dist/prosemirror/bookmarkBoundaryAttrs.js +66 -0
- package/dist/prosemirror/conversion/fromProseDoc.js +176 -54
- package/dist/prosemirror/conversion/toProseDoc.js +179 -64
- 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/CharacterSpacingExtension.js +9 -5
- 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/TableExtension.js +2 -0
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.d.ts +4 -1
- package/dist/prosemirror/extensions/nodes/TextBoxAnchorExtension.js +4 -3
- package/dist/prosemirror/extensions/nodes/TextBoxExtension.js +6 -2
- 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 +17 -1
- package/dist/prosemirror/utils/tabCalculator.d.ts +2 -123
- package/dist/prosemirror/utils/tabCalculator.js +1 -140
- package/dist/prosemirror/validation.js +93 -0
- package/dist/render-dom/RemoteSelectionOverlay.d.ts +4 -4
- package/dist/types/documentEnumValues.d.ts +4 -1
- package/dist/types/documentEnumValues.js +12 -1
- package/dist/types/editor-story.d.ts +8 -0
- package/dist/types/editor-story.js +0 -0
- package/dist/types/remote-selection.d.ts +11 -0
- package/dist/types/remote-selection.js +0 -0
- package/dist/types/wrap.d.ts +36 -0
- package/dist/types/wrap.js +40 -0
- package/dist/utils/fontResolver.d.ts +1 -1
- package/dist/utils/fontResolver.js +42 -2
- package/dist/utils/formatToStyle.js +4 -2
- package/dist/utils/horizontalScale.d.ts +28 -0
- package/dist/utils/horizontalScale.js +42 -0
- package/package.json +2 -2
|
@@ -1,4 +1,6 @@
|
|
|
1
1
|
import { stripXmlDeclarations } from "../../../utils/stripXmlDeclarations.js";
|
|
2
|
+
import { readBookmarkBoundaryAttrs } from "../../bookmarkBoundaryAttrs.js";
|
|
3
|
+
import { Fragment, Slice } from "prosemirror-model";
|
|
2
4
|
//#region src/prosemirror/extensions/features/pasteCleanup.ts
|
|
3
5
|
/**
|
|
4
6
|
* Office / web paste cleanup
|
|
@@ -117,56 +119,109 @@ const STRAY_XML_TAG = new RegExp(`<\\/?xml${TAG_TAIL}>`, "gi");
|
|
|
117
119
|
const NOISE_TAG = new RegExp(`<\\/?(?:font|meta|link)${TAG_TAIL}>`, "gi");
|
|
118
120
|
const EMPTY_SPAN = new RegExp(`<span(?:\\s${TAG_TAIL})?><\\/span>`, "gi");
|
|
119
121
|
const MAX_EMPTY_SPAN_PASSES = 5;
|
|
120
|
-
const
|
|
122
|
+
const INTERNAL_CLIPBOARD_ATTR_PATTERN = /\s+data-docx-internal-clipboard(?:="([^"]*)"|='([^']*)'|=([^\s>]+))?/gi;
|
|
121
123
|
const TEXTBOX_ANCHOR_ATTR = /\s+data-docx-textbox-anchor(?:="[^"]*"|='[^']*')?/gi;
|
|
124
|
+
const BOOKMARK_BOUNDARY_ATTR = /\s+data-docx-bookmark-(?:boundary|id|name|col-first|col-last)(?:="[^"]*"|='[^']*'|=[^\s>]+)?/gi;
|
|
125
|
+
const STRUCTURED_FIELD_ATTR = /\s+data-field-structured(?:="[^"]*"|='[^']*'|=[^\s>]+)?/gi;
|
|
122
126
|
/**
|
|
123
|
-
*
|
|
124
|
-
*
|
|
125
|
-
*
|
|
126
|
-
* keeps working; anything else has the marker stripped defensively.
|
|
127
|
+
* Treat reconstruction attributes as internal only when the HTML carries the
|
|
128
|
+
* unguessable capability created with this editor schema. `data-pm-slice`
|
|
129
|
+
* cannot establish trust because arbitrary external HTML can forge it.
|
|
127
130
|
*/
|
|
128
|
-
function
|
|
129
|
-
if (
|
|
131
|
+
function hasInternalClipboardCapability(html, token) {
|
|
132
|
+
if (!token) return false;
|
|
133
|
+
INTERNAL_CLIPBOARD_ATTR_PATTERN.lastIndex = 0;
|
|
134
|
+
for (const match of html.matchAll(INTERNAL_CLIPBOARD_ATTR_PATTERN)) if ((match[1] ?? match[2] ?? match[3]) === token) return true;
|
|
135
|
+
return false;
|
|
136
|
+
}
|
|
137
|
+
function stripForeignTextBoxAnchors(html, internal) {
|
|
138
|
+
if (internal) return html;
|
|
130
139
|
return html.replace(TEXTBOX_ANCHOR_ATTR, "");
|
|
131
140
|
}
|
|
141
|
+
function stripForeignBookmarkBoundaries(html, internal) {
|
|
142
|
+
if (internal) return html;
|
|
143
|
+
return html.replace(BOOKMARK_BOUNDARY_ATTR, "");
|
|
144
|
+
}
|
|
145
|
+
function stripForeignStructuredFields(html, internal) {
|
|
146
|
+
if (internal) return html;
|
|
147
|
+
return html.replace(STRUCTURED_FIELD_ATTR, "");
|
|
148
|
+
}
|
|
132
149
|
/**
|
|
133
150
|
* Remove empty spans left behind after stripping `mso-*` styles. Only truly
|
|
134
151
|
* empty spans are removed (whitespace-only spans are kept so word-separating
|
|
135
152
|
* spacer runs never collapse two words together). Repeated a bounded number of
|
|
136
153
|
* times to unwrap nested empties (`<span><span></span></span>`).
|
|
137
154
|
*/
|
|
138
|
-
function stripEmptySpans(html) {
|
|
155
|
+
function stripEmptySpans(html, preserveInternalAtoms) {
|
|
139
156
|
let current = html;
|
|
140
157
|
for (let pass = 0; pass < MAX_EMPTY_SPAN_PASSES; pass++) {
|
|
141
|
-
const next = current.replace(EMPTY_SPAN,
|
|
158
|
+
const next = current.replace(EMPTY_SPAN, (span) => {
|
|
159
|
+
if (preserveInternalAtoms && /\bdata-docx-(?:bookmark-boundary|textbox-anchor)\s*=/i.test(span)) return span;
|
|
160
|
+
return "";
|
|
161
|
+
});
|
|
142
162
|
if (next === current) break;
|
|
143
163
|
current = next;
|
|
144
164
|
}
|
|
145
165
|
return current;
|
|
146
166
|
}
|
|
147
|
-
|
|
148
|
-
* Strip Office/web producer cruft from a raw clipboard HTML string.
|
|
149
|
-
*
|
|
150
|
-
* Best-effort and non-throwing: any unexpected failure returns the original
|
|
151
|
-
* markup so a paste degrades to the browser default rather than losing content.
|
|
152
|
-
* `<style>` blocks are intentionally left in place for the downstream style
|
|
153
|
-
* inliner, which resolves class-based CSS before the schema parser runs.
|
|
154
|
-
*/
|
|
155
|
-
function cleanPastedHtml(html) {
|
|
167
|
+
function cleanPastedHtml(html, options = {}) {
|
|
156
168
|
if (!html) return html;
|
|
157
169
|
try {
|
|
170
|
+
const internal = hasInternalClipboardCapability(html, options.internalClipboardToken);
|
|
158
171
|
let cleaned = stripHtmlComments(html);
|
|
159
172
|
cleaned = stripXmlDeclarations(cleaned);
|
|
160
173
|
cleaned = cleaned.replace(NAMESPACED_TAG, "");
|
|
161
174
|
cleaned = cleaned.replace(STRAY_XML_TAG, "");
|
|
162
175
|
cleaned = cleaned.replace(NOISE_TAG, "");
|
|
163
176
|
cleaned = stripMsoStyles(cleaned);
|
|
164
|
-
cleaned = stripEmptySpans(cleaned);
|
|
165
|
-
cleaned = stripForeignTextBoxAnchors(cleaned,
|
|
177
|
+
cleaned = stripEmptySpans(cleaned, internal);
|
|
178
|
+
cleaned = stripForeignTextBoxAnchors(cleaned, internal);
|
|
179
|
+
cleaned = stripForeignBookmarkBoundaries(cleaned, internal);
|
|
180
|
+
cleaned = stripForeignStructuredFields(cleaned, internal);
|
|
181
|
+
cleaned = cleaned.replace(INTERNAL_CLIPBOARD_ATTR_PATTERN, "");
|
|
166
182
|
return cleaned.trim();
|
|
167
183
|
} catch {
|
|
168
|
-
return
|
|
184
|
+
return "";
|
|
169
185
|
}
|
|
170
186
|
}
|
|
187
|
+
/** Remove incomplete or duplicate bookmark pairs at copied slice edges. */
|
|
188
|
+
function removeUnpairedBookmarkBoundaries(slice) {
|
|
189
|
+
const counts = /* @__PURE__ */ new Map();
|
|
190
|
+
let boundaryIndex = 0;
|
|
191
|
+
slice.content.descendants((node) => {
|
|
192
|
+
if (node.type.name !== "bookmarkBoundary") return true;
|
|
193
|
+
const result = readBookmarkBoundaryAttrs(node);
|
|
194
|
+
if (!result.ok) return false;
|
|
195
|
+
const attrs = result.value;
|
|
196
|
+
const count = counts.get(attrs.id) ?? {
|
|
197
|
+
starts: 0,
|
|
198
|
+
ends: 0
|
|
199
|
+
};
|
|
200
|
+
if (attrs.type === "start") {
|
|
201
|
+
count.starts += 1;
|
|
202
|
+
count.firstStart ??= boundaryIndex;
|
|
203
|
+
} else {
|
|
204
|
+
count.ends += 1;
|
|
205
|
+
count.firstEnd ??= boundaryIndex;
|
|
206
|
+
}
|
|
207
|
+
boundaryIndex += 1;
|
|
208
|
+
counts.set(attrs.id, count);
|
|
209
|
+
return false;
|
|
210
|
+
});
|
|
211
|
+
const pairedIds = new Set([...counts].flatMap(([id, count]) => count.starts === 1 && count.ends === 1 && count.firstStart !== void 0 && count.firstEnd !== void 0 && count.firstStart < count.firstEnd ? [id] : []));
|
|
212
|
+
const filterFragment = (fragment) => {
|
|
213
|
+
const children = [];
|
|
214
|
+
fragment.forEach((node) => {
|
|
215
|
+
if (node.type.name === "bookmarkBoundary") {
|
|
216
|
+
const result = readBookmarkBoundaryAttrs(node);
|
|
217
|
+
if (result.ok && pairedIds.has(result.value.id)) children.push(node);
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
children.push(node.childCount === 0 ? node : node.copy(filterFragment(node.content)));
|
|
221
|
+
});
|
|
222
|
+
return Fragment.fromArray(children);
|
|
223
|
+
};
|
|
224
|
+
return new Slice(filterFragment(slice.content), slice.openStart, slice.openEnd);
|
|
225
|
+
}
|
|
171
226
|
//#endregion
|
|
172
|
-
export { cleanPastedHtml };
|
|
227
|
+
export { cleanPastedHtml, removeUnpairedBookmarkBoundaries };
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { getHorizontalScaleFactor, normalizeHorizontalScalePercent, parseHorizontalScalePercent } from "../../../utils/horizontalScale.js";
|
|
1
2
|
import { formatPx, twipsToPixels } from "../../../utils/units.js";
|
|
2
3
|
import { expectCharacterSpacingMarkAttrs } from "../../attrs/index.js";
|
|
3
4
|
import { createMarkExtension } from "../create.js";
|
|
@@ -28,12 +29,13 @@ const CharacterSpacingExtension = createMarkExtension({
|
|
|
28
29
|
getAttrs: (dom) => ({
|
|
29
30
|
spacing: dom.dataset["spacing"] ? Number(dom.dataset["spacing"]) : null,
|
|
30
31
|
position: dom.dataset["position"] ? Number(dom.dataset["position"]) : null,
|
|
31
|
-
scale:
|
|
32
|
+
scale: parseHorizontalScalePercent(dom.dataset["scale"]),
|
|
32
33
|
kerning: dom.dataset["kerning"] ? Number(dom.dataset["kerning"]) : null
|
|
33
34
|
})
|
|
34
35
|
}],
|
|
35
36
|
toDOM(mark) {
|
|
36
|
-
const { spacing, position, scale, kerning } = expectCharacterSpacingMarkAttrs(mark);
|
|
37
|
+
const { spacing, position, scale: authoredScale, kerning } = expectCharacterSpacingMarkAttrs(mark);
|
|
38
|
+
const scale = normalizeHorizontalScalePercent(authoredScale);
|
|
37
39
|
const styles = [];
|
|
38
40
|
const dataAttrs = { class: "docx-char-spacing" };
|
|
39
41
|
if (spacing !== void 0 && spacing !== 0) {
|
|
@@ -45,10 +47,12 @@ const CharacterSpacingExtension = createMarkExtension({
|
|
|
45
47
|
styles.push(`vertical-align: ${formatPx(px)}`);
|
|
46
48
|
dataAttrs["data-position"] = String(position);
|
|
47
49
|
}
|
|
48
|
-
if (scale !== void 0
|
|
49
|
-
styles.push(`transform: scaleX(${scale / 100})`);
|
|
50
|
-
styles.push("display: inline-block");
|
|
50
|
+
if (scale !== void 0) {
|
|
51
51
|
dataAttrs["data-scale"] = String(scale);
|
|
52
|
+
if (scale !== 100) {
|
|
53
|
+
styles.push(`transform: scaleX(${getHorizontalScaleFactor(scale)})`);
|
|
54
|
+
styles.push("display: inline-block");
|
|
55
|
+
}
|
|
52
56
|
}
|
|
53
57
|
if (kerning !== void 0) dataAttrs["data-kerning"] = String(kerning);
|
|
54
58
|
if (styles.length > 0) dataAttrs["style"] = styles.join("; ");
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import { NodeExtension } from "../types.js";
|
|
2
|
+
//#region src/prosemirror/extensions/nodes/BookmarkBoundaryExtension.d.ts
|
|
3
|
+
/** Zero-width bookmark boundary that preserves its position through ProseMirror edits. */
|
|
4
|
+
type BookmarkBoundaryOptions = {
|
|
5
|
+
getInternalClipboardToken?: () => string;
|
|
6
|
+
};
|
|
7
|
+
declare const BookmarkBoundaryExtension: (options?: Partial<BookmarkBoundaryOptions> | undefined) => NodeExtension;
|
|
8
|
+
//#endregion
|
|
9
|
+
export { BookmarkBoundaryExtension };
|
|
@@ -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 };
|
|
@@ -154,6 +154,7 @@ const tableSpec = {
|
|
|
154
154
|
look: { default: null },
|
|
155
155
|
borders: { default: null },
|
|
156
156
|
_resolvedIndent: { default: null },
|
|
157
|
+
_resolvedJustification: { default: null },
|
|
157
158
|
_resolvedBidi: { default: null },
|
|
158
159
|
_originalFormatting: { default: null },
|
|
159
160
|
tblPrChange: { default: null },
|
|
@@ -200,6 +201,7 @@ const tableRowSpec = {
|
|
|
200
201
|
heightRule: { default: null },
|
|
201
202
|
isHeader: { default: false },
|
|
202
203
|
hidden: { default: false },
|
|
204
|
+
_resolvedJustification: { default: null },
|
|
203
205
|
_originalFormatting: { default: null },
|
|
204
206
|
trPrChange: { default: null },
|
|
205
207
|
trIns: { default: null },
|
|
@@ -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 };
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { IMAGE_WRAP_TYPE_VALUES } from "../../../types/documentEnumValues.js";
|
|
1
|
+
import { IMAGE_WRAP_TYPE_VALUES, normalizeShapeTextAnchor } from "../../../types/documentEnumValues.js";
|
|
2
2
|
import { expectTextBoxAttrs } from "../../attrs/index.js";
|
|
3
3
|
import { createNodeExtension } from "../create.js";
|
|
4
4
|
//#region src/prosemirror/extensions/nodes/TextBoxExtension.ts
|
|
@@ -21,6 +21,9 @@ function parseTextBoxAutoFit(raw) {
|
|
|
21
21
|
function parseTextBoxTextWrap(raw) {
|
|
22
22
|
if (raw === "none" || raw === "square") return raw;
|
|
23
23
|
}
|
|
24
|
+
function parseTextBoxVerticalAlign(raw) {
|
|
25
|
+
return normalizeShapeTextAnchor(raw);
|
|
26
|
+
}
|
|
24
27
|
const TextBoxExtension = createNodeExtension({
|
|
25
28
|
name: "textBox",
|
|
26
29
|
schemaNodeName: "textBox",
|
|
@@ -68,6 +71,7 @@ const TextBoxExtension = createNodeExtension({
|
|
|
68
71
|
const wrapType = parseTextBoxWrapType(d["wrapType"]);
|
|
69
72
|
const autoFit = parseTextBoxAutoFit(d["autoFit"]);
|
|
70
73
|
const textWrap = parseTextBoxTextWrap(d["textWrap"]);
|
|
74
|
+
const verticalAlign = parseTextBoxVerticalAlign(d["verticalAlign"]);
|
|
71
75
|
return {
|
|
72
76
|
...d["width"] ? { width: Number(d["width"]) } : {},
|
|
73
77
|
...d["height"] ? { height: Number(d["height"]) } : {},
|
|
@@ -82,7 +86,7 @@ const TextBoxExtension = createNodeExtension({
|
|
|
82
86
|
...d["marginBottom"] ? { marginBottom: Number(d["marginBottom"]) } : {},
|
|
83
87
|
...d["marginLeft"] ? { marginLeft: Number(d["marginLeft"]) } : {},
|
|
84
88
|
...d["marginRight"] ? { marginRight: Number(d["marginRight"]) } : {},
|
|
85
|
-
...
|
|
89
|
+
...verticalAlign ? { verticalAlign } : {},
|
|
86
90
|
...d["displayMode"] ? { displayMode: d["displayMode"] } : {},
|
|
87
91
|
...d["cssFloat"] ? { cssFloat: d["cssFloat"] } : {},
|
|
88
92
|
...wrapType ? { wrapType } : {},
|
|
@@ -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 };
|