@docen/docx 0.3.1 → 0.3.2
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/converters/docx.d.mts +199 -1
- package/dist/converters/docx.mjs +1273 -1
- package/dist/converters/html.d.mts +28 -1
- package/dist/converters/html.mjs +103 -1
- package/dist/converters/markdown.d.mts +12 -1
- package/dist/converters/markdown.mjs +17 -1
- package/dist/converters/patch.d.mts +52 -1
- package/dist/converters/patch.mjs +35 -1
- package/dist/converters/prepare.d.mts +62 -1
- package/dist/converters/prepare.mjs +121 -1
- package/dist/converters/styles.d.mts +84 -1
- package/dist/converters/styles.mjs +284 -1
- package/dist/core-C9VunJ8o.d.mts +174 -0
- package/dist/core-DT9IrTzN.mjs +670 -0
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +1 -1
- package/dist/editor.d.mts +20 -1
- package/dist/editor.mjs +20 -1
- package/dist/extensions/blockquote.d.mts +1 -1
- package/dist/extensions/blockquote.mjs +30 -2
- package/dist/extensions/code-block.d.mts +1 -1
- package/dist/extensions/code-block.mjs +22 -1
- package/dist/extensions/column-break.d.mts +1 -1
- package/dist/extensions/column-break.mjs +1 -1
- package/dist/extensions/details.d.mts +1 -1
- package/dist/extensions/details.mjs +17 -2
- package/dist/extensions/document.mjs +49 -1
- package/dist/extensions/extensions.d.mts +3 -4
- package/dist/extensions/extensions.mjs +13 -14
- package/dist/extensions/formatting-marks.d.mts +7 -1
- package/dist/extensions/formatting-marks.mjs +1 -1
- package/dist/extensions/heading.mjs +165 -1
- package/dist/extensions/image.d.mts +1 -1
- package/dist/extensions/image.mjs +395 -1
- package/dist/extensions/index.d.mts +3 -2
- package/dist/extensions/index.mjs +4 -3
- package/dist/extensions/mention.d.mts +1 -1
- package/dist/extensions/mention.mjs +43 -2
- package/dist/extensions/ordered-list.d.mts +1 -1
- package/dist/extensions/ordered-list.mjs +40 -2
- package/dist/extensions/page-break.d.mts +1 -33
- package/dist/extensions/page-break.mjs +1 -1
- package/dist/extensions/paragraph.mjs +150 -1
- package/dist/extensions/passthrough.d.mts +1 -1
- package/dist/extensions/passthrough.mjs +1 -1
- package/dist/extensions/section-break.d.mts +19 -2
- package/dist/extensions/section-break.mjs +1 -1
- package/dist/extensions/strike.mjs +49 -1
- package/dist/extensions/table-cell.mjs +111 -1
- package/dist/extensions/table-header.mjs +111 -1
- package/dist/extensions/table-row.mjs +83 -1
- package/dist/extensions/table.mjs +159 -1
- package/dist/extensions/task-item.d.mts +1 -1
- package/dist/extensions/task-item.mjs +36 -2
- package/dist/extensions/text-style.mjs +150 -1
- package/dist/extensions/tiptap.d.mts +2 -2
- package/dist/extensions/tiptap.mjs +31 -2
- package/dist/extensions/utils.d.mts +2 -62
- package/dist/extensions/utils.mjs +499 -2
- package/dist/extensions/wpg-group.d.mts +2 -0
- package/dist/extensions/wpg-group.mjs +2 -0
- package/dist/extensions/wps-shape.d.mts +2 -0
- package/dist/extensions/wps-shape.mjs +2 -0
- package/dist/index.d.mts +86 -27
- package/dist/index.mjs +13 -10
- package/dist/{tiptap-TErPjuNJ.d.mts → tiptap-pZsNPsvV.d.mts} +1 -3
- package/dist/utils-D87vukzp.d.mts +128 -0
- package/package.json +39 -40
- package/dist/blockquote-Ky9xD_Q-.mjs +0 -31
- package/dist/code-block-BgNjhADy.mjs +0 -23
- package/dist/core-DEqBHRzE.mjs +0 -406
- package/dist/core-fBMCCj4D.d.mts +0 -97
- package/dist/details-Dh8luWb2.mjs +0 -18
- package/dist/dist-B6B3HnVZ.mjs +0 -1838
- package/dist/document-CvTym-FM.mjs +0 -50
- package/dist/docx-C5wyjI-0.d.mts +0 -200
- package/dist/docx-CONyAedl.mjs +0 -1231
- package/dist/editor-Bf3CxQnY.mjs +0 -21
- package/dist/editor-DReY3Rb9.d.mts +0 -21
- package/dist/emoji-BZ1dJD7d.d.mts +0 -17
- package/dist/emoji-C2OsTWR7.mjs +0 -27
- package/dist/extensions/emoji.d.mts +0 -2
- package/dist/extensions/emoji.mjs +0 -2
- package/dist/extensions/image-group.d.mts +0 -6
- package/dist/extensions/image-group.mjs +0 -2
- package/dist/heading-DAubgJJN.mjs +0 -166
- package/dist/html-DCC_DSHv.d.mts +0 -18
- package/dist/html-DedDWPoO.mjs +0 -66
- package/dist/image-CuEIXoZl.mjs +0 -334
- package/dist/markdown-BzJmRsBs.mjs +0 -18
- package/dist/markdown-D0k0sRYS.d.mts +0 -13
- package/dist/mention-C-dTR6MK.mjs +0 -44
- package/dist/ordered-list-V-3xFQmV.mjs +0 -41
- package/dist/paragraph-BrWH-BOV.mjs +0 -147
- package/dist/patch-Cdp5WKJz.d.mts +0 -53
- package/dist/patch-IZ13tXTu.mjs +0 -36
- package/dist/prepare-Bdk8Cjkf.mjs +0 -122
- package/dist/prepare-CPAvcv6q.d.mts +0 -63
- package/dist/strike-hWxShOgb.mjs +0 -50
- package/dist/styles-QSX3RhxK.mjs +0 -285
- package/dist/styles-ugspkrxz.d.mts +0 -85
- package/dist/table-DrA12lxX.mjs +0 -159
- package/dist/table-cell-DmkfYwDc.mjs +0 -112
- package/dist/table-header-Bwr5GKWg.mjs +0 -112
- package/dist/table-row-vew1y7WH.mjs +0 -84
- package/dist/task-item-BnlXGrIG.mjs +0 -37
- package/dist/text-style-tMeBq5XQ.mjs +0 -151
- package/dist/tiptap-D7pl4I5U.mjs +0 -33
- package/dist/utils-BPT4tjcn.mjs +0 -333
|
@@ -1,2 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as JSONContent, i as Extensions } from "../core-C9VunJ8o.mjs";
|
|
2
|
+
import { ParseOptions } from "@tiptap/pm/model";
|
|
3
|
+
|
|
4
|
+
//#region src/converters/html.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* Serialize Tiptap JSON to an HTML string. Renders per-section: each OOXML
|
|
7
|
+
* section (CT_SectPr) becomes a `<section>` carrying its own page margin
|
|
8
|
+
* (padding) and a document-grid line-height (the font's `normal` metric —
|
|
9
|
+
* Word does not add the grid pitch to rendered line height), so paragraph
|
|
10
|
+
* line-spacing multiples resolve against the section's font, not a fallback.
|
|
11
|
+
* The document background (CT_DocumentBase, single for the whole doc) wraps all
|
|
12
|
+
* sections.
|
|
13
|
+
*
|
|
14
|
+
* Same ProseMirror DOMSerializer pipeline as @tiptap/html, on a linkedom
|
|
15
|
+
* document: happy-dom drops calc(var(…)) when re-serializing the style
|
|
16
|
+
* attribute, so DOCX line-spacing survives only with linkedom.
|
|
17
|
+
*/
|
|
18
|
+
declare function generateHTML(doc: JSONContent, extensions?: Extensions): string;
|
|
19
|
+
/**
|
|
20
|
+
* Parse an HTML string into Tiptap JSON. Same ProseMirror DOMParser pipeline as
|
|
21
|
+
* @tiptap/html on a linkedom document. The background wrapper and section
|
|
22
|
+
* containers are unknown elements (no doc/div/section node in the schema), so
|
|
23
|
+
* the parser ignores their tags and extracts the content. Section geometry
|
|
24
|
+
* (linePitch/margins) and the page background are section/doc-level metadata,
|
|
25
|
+
* not content — they round-trip losslessly via DOCX, not HTML.
|
|
26
|
+
*/
|
|
27
|
+
declare function parseHTML(html: string, extensions?: Extensions, options?: ParseOptions): JSONContent;
|
|
28
|
+
//#endregion
|
|
2
29
|
export { generateHTML, parseHTML };
|
package/dist/converters/html.mjs
CHANGED
|
@@ -1,2 +1,104 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { s as docxExtensions } from "../core-DT9IrTzN.mjs";
|
|
2
|
+
import { sectionLinePitchCss, sectionMarginCss } from "../extensions/utils.mjs";
|
|
3
|
+
import { getSchema } from "@tiptap/core";
|
|
4
|
+
import { encodeBase64 } from "@office-open/core";
|
|
5
|
+
import { DOMParser, DOMSerializer, Node } from "@tiptap/pm/model";
|
|
6
|
+
import { parseHTML as parseHTML$1 } from "linkedom";
|
|
7
|
+
//#region src/converters/html.ts
|
|
8
|
+
const defaultExtensions = docxExtensions;
|
|
9
|
+
/** JSON round-trips byte arrays as plain objects ({0:byte,…}); rebuild here. */
|
|
10
|
+
function toBytes(data) {
|
|
11
|
+
if (!data) return null;
|
|
12
|
+
return data instanceof Uint8Array ? data : new Uint8Array(Object.values(data));
|
|
13
|
+
}
|
|
14
|
+
/** Page background → CSS for the root wrapper. Color renders directly; a VML
|
|
15
|
+
* pattern's first media item tiles as an image. OOXML patterns have no CSS
|
|
16
|
+
* equivalent, so DOCX (not HTML) is the fidelity source. */
|
|
17
|
+
function backgroundToCss(bg) {
|
|
18
|
+
const styles = [];
|
|
19
|
+
if (bg?.color) styles.push(`background-color:#${bg.color}`);
|
|
20
|
+
const media = bg?.rawMedia?.[0];
|
|
21
|
+
const bytes = toBytes(media?.data);
|
|
22
|
+
if (bytes) styles.push(`background-image:url(data:image/${media?.type ?? "png"};base64,${encodeBase64(bytes)})`, "background-repeat:repeat");
|
|
23
|
+
return styles.length ? styles.join(";") : void 0;
|
|
24
|
+
}
|
|
25
|
+
/** Split flat `doc > block+` into sections by section-carrying paragraphs.
|
|
26
|
+
* Mirrors DocxManager's compile-time split (converters/docx.ts): a paragraph
|
|
27
|
+
* with `sectionProperties` closes its section; trailing blocks form the final
|
|
28
|
+
* section under doc.attrs.sectionProperties. No section-carrying paragraph →
|
|
29
|
+
* a single section (backward compatible). */
|
|
30
|
+
function splitJsonSections(doc) {
|
|
31
|
+
const sections = [];
|
|
32
|
+
let current = [];
|
|
33
|
+
for (const node of doc.content ?? []) {
|
|
34
|
+
current.push(node);
|
|
35
|
+
const sp = node.attrs?.sectionProperties;
|
|
36
|
+
if (sp != null) {
|
|
37
|
+
sections.push({
|
|
38
|
+
properties: sp,
|
|
39
|
+
blocks: current
|
|
40
|
+
});
|
|
41
|
+
current = [];
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
const tailProps = doc.attrs?.sectionProperties;
|
|
45
|
+
sections.push({
|
|
46
|
+
properties: tailProps ?? null,
|
|
47
|
+
blocks: current
|
|
48
|
+
});
|
|
49
|
+
return sections;
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Serialize Tiptap JSON to an HTML string. Renders per-section: each OOXML
|
|
53
|
+
* section (CT_SectPr) becomes a `<section>` carrying its own page margin
|
|
54
|
+
* (padding) and a document-grid line-height (the font's `normal` metric —
|
|
55
|
+
* Word does not add the grid pitch to rendered line height), so paragraph
|
|
56
|
+
* line-spacing multiples resolve against the section's font, not a fallback.
|
|
57
|
+
* The document background (CT_DocumentBase, single for the whole doc) wraps all
|
|
58
|
+
* sections.
|
|
59
|
+
*
|
|
60
|
+
* Same ProseMirror DOMSerializer pipeline as @tiptap/html, on a linkedom
|
|
61
|
+
* document: happy-dom drops calc(var(…)) when re-serializing the style
|
|
62
|
+
* attribute, so DOCX line-spacing survives only with linkedom.
|
|
63
|
+
*/
|
|
64
|
+
function generateHTML(doc, extensions = defaultExtensions) {
|
|
65
|
+
const schema = getSchema(extensions);
|
|
66
|
+
const { document } = parseHTML$1("<!DOCTYPE html><html><body></body></html>");
|
|
67
|
+
const serializer = DOMSerializer.fromSchema(schema);
|
|
68
|
+
const parts = [];
|
|
69
|
+
for (const section of splitJsonSections(doc)) {
|
|
70
|
+
const sp = section.properties;
|
|
71
|
+
const styles = [];
|
|
72
|
+
const padding = sectionMarginCss(sp?.page?.margin);
|
|
73
|
+
if (padding) styles.push(padding);
|
|
74
|
+
styles.push(...sectionLinePitchCss(sp?.grid));
|
|
75
|
+
const sec = document.createElement("section");
|
|
76
|
+
if (styles.length) sec.setAttribute("style", styles.join(";"));
|
|
77
|
+
if (section.blocks.length) {
|
|
78
|
+
const fragment = Node.fromJSON(schema, {
|
|
79
|
+
type: "doc",
|
|
80
|
+
content: section.blocks
|
|
81
|
+
}).content;
|
|
82
|
+
serializer.serializeFragment(fragment, { document }, sec);
|
|
83
|
+
}
|
|
84
|
+
parts.push(sec.outerHTML);
|
|
85
|
+
}
|
|
86
|
+
const body = parts.join("");
|
|
87
|
+
const bgCss = backgroundToCss(doc.attrs?.background);
|
|
88
|
+
return bgCss ? `<div style="${bgCss}">${body}</div>` : body;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Parse an HTML string into Tiptap JSON. Same ProseMirror DOMParser pipeline as
|
|
92
|
+
* @tiptap/html on a linkedom document. The background wrapper and section
|
|
93
|
+
* containers are unknown elements (no doc/div/section node in the schema), so
|
|
94
|
+
* the parser ignores their tags and extracts the content. Section geometry
|
|
95
|
+
* (linePitch/margins) and the page background are section/doc-level metadata,
|
|
96
|
+
* not content — they round-trip losslessly via DOCX, not HTML.
|
|
97
|
+
*/
|
|
98
|
+
function parseHTML(html, extensions = defaultExtensions, options) {
|
|
99
|
+
const schema = getSchema(extensions);
|
|
100
|
+
const { document } = parseHTML$1(`<!DOCTYPE html><html><body>${html}</body></html>`);
|
|
101
|
+
return DOMParser.fromSchema(schema).parse(document.body, options).toJSON();
|
|
102
|
+
}
|
|
103
|
+
//#endregion
|
|
2
104
|
export { generateHTML, parseHTML };
|
|
@@ -1,2 +1,13 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as JSONContent } from "../core-C9VunJ8o.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/converters/markdown.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* Parse Markdown string to Tiptap JSON.
|
|
6
|
+
*/
|
|
7
|
+
declare function parseMarkdown(markdown: string): JSONContent;
|
|
8
|
+
/**
|
|
9
|
+
* Generate Markdown string from Tiptap JSON.
|
|
10
|
+
*/
|
|
11
|
+
declare function generateMarkdown(doc: JSONContent): string;
|
|
12
|
+
//#endregion
|
|
2
13
|
export { generateMarkdown, parseMarkdown };
|
|
@@ -1,2 +1,18 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { s as docxExtensions } from "../core-DT9IrTzN.mjs";
|
|
2
|
+
import { Markdown, MarkdownManager } from "@tiptap/markdown";
|
|
3
|
+
//#region src/converters/markdown.ts
|
|
4
|
+
const markdownManager = new MarkdownManager({ extensions: [...docxExtensions, Markdown] });
|
|
5
|
+
/**
|
|
6
|
+
* Parse Markdown string to Tiptap JSON.
|
|
7
|
+
*/
|
|
8
|
+
function parseMarkdown(markdown) {
|
|
9
|
+
return markdownManager.parse(markdown);
|
|
10
|
+
}
|
|
11
|
+
/**
|
|
12
|
+
* Generate Markdown string from Tiptap JSON.
|
|
13
|
+
*/
|
|
14
|
+
function generateMarkdown(doc) {
|
|
15
|
+
return markdownManager.serialize(doc);
|
|
16
|
+
}
|
|
17
|
+
//#endregion
|
|
2
18
|
export { generateMarkdown, parseMarkdown };
|
|
@@ -1,2 +1,53 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { a as JSONContent } from "../core-C9VunJ8o.mjs";
|
|
2
|
+
import { PrepareStep } from "./prepare.mjs";
|
|
3
|
+
import { OutputByType, OutputType, patchDocument } from "@office-open/docx";
|
|
4
|
+
|
|
5
|
+
//#region src/converters/patch.d.ts
|
|
6
|
+
/**
|
|
7
|
+
* A patch's replacement content, expressed as Tiptap JSON. `compileDocument`
|
|
8
|
+
* converts it to office-open `SectionChild[]` before applying.
|
|
9
|
+
*/
|
|
10
|
+
interface DocxPatchContent {
|
|
11
|
+
content: JSONContent;
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Options for patching a DOCX template.
|
|
15
|
+
*
|
|
16
|
+
* Mirrors the legacy `@docen/export-docx` DocxPatchOptions minus `exportOptions`
|
|
17
|
+
* — `compileDocument` already derives all styling from the Tiptap attrs, so no
|
|
18
|
+
* separate export configuration is needed.
|
|
19
|
+
*/
|
|
20
|
+
interface DocxPatchOptions<T extends OutputType = OutputType> {
|
|
21
|
+
/** Template DOCX to patch. */
|
|
22
|
+
template: Parameters<typeof patchDocument>[0]["data"];
|
|
23
|
+
/** Placeholder name → replacement content. */
|
|
24
|
+
patches: Record<string, DocxPatchContent>;
|
|
25
|
+
/**
|
|
26
|
+
* Pre-compilation steps run on each patch's content in place (default:
|
|
27
|
+
* `prepareImages()`). `false` skips; `PrepareStep[]` runs custom steps.
|
|
28
|
+
* Required when patch content references http image URLs.
|
|
29
|
+
*/
|
|
30
|
+
prepare?: boolean | PrepareStep[];
|
|
31
|
+
/** Custom placeholder delimiters (default `{{` / `}}`). */
|
|
32
|
+
placeholderDelimiters?: {
|
|
33
|
+
start?: string;
|
|
34
|
+
end?: string;
|
|
35
|
+
};
|
|
36
|
+
/** Keep the template's paragraph styles on patched content. */
|
|
37
|
+
keepOriginalStyles?: boolean;
|
|
38
|
+
/** Recurse into nested patches. */
|
|
39
|
+
recursive?: boolean;
|
|
40
|
+
/** Output container type. */
|
|
41
|
+
outputType: T;
|
|
42
|
+
}
|
|
43
|
+
/**
|
|
44
|
+
* Patch a DOCX template by replacing placeholders with Tiptap-JSON content.
|
|
45
|
+
*
|
|
46
|
+
* Each patch's `content` is prepared (default: `prepareImages`, in place) then
|
|
47
|
+
* compiled (`compileDocument` → `DocumentOptions`); its first section's
|
|
48
|
+
* `children` become the replacement. Patching is delegated to
|
|
49
|
+
* `@office-open/docx`'s `patchDocument`.
|
|
50
|
+
*/
|
|
51
|
+
declare function patchDOCX<T extends OutputType>(options: DocxPatchOptions<T>): Promise<OutputByType[T]>;
|
|
52
|
+
//#endregion
|
|
2
53
|
export { DocxPatchContent, DocxPatchOptions, patchDOCX };
|
|
@@ -1,2 +1,36 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { prepareDocument } from "./prepare.mjs";
|
|
2
|
+
import { compileDocument } from "./docx.mjs";
|
|
3
|
+
import { patchDocument } from "@office-open/docx";
|
|
4
|
+
//#region src/converters/patch.ts
|
|
5
|
+
/**
|
|
6
|
+
* Patch a DOCX template by replacing placeholders with Tiptap-JSON content.
|
|
7
|
+
*
|
|
8
|
+
* Each patch's `content` is prepared (default: `prepareImages`, in place) then
|
|
9
|
+
* compiled (`compileDocument` → `DocumentOptions`); its first section's
|
|
10
|
+
* `children` become the replacement. Patching is delegated to
|
|
11
|
+
* `@office-open/docx`'s `patchDocument`.
|
|
12
|
+
*/
|
|
13
|
+
async function patchDOCX(options) {
|
|
14
|
+
const { template, patches, prepare = true, placeholderDelimiters, keepOriginalStyles, recursive, outputType } = options;
|
|
15
|
+
const patchesObject = {};
|
|
16
|
+
for (const [key, patchContent] of Object.entries(patches)) {
|
|
17
|
+
if (prepare !== false) await prepareDocument(patchContent.content, prepare === true ? void 0 : prepare);
|
|
18
|
+
patchesObject[key] = {
|
|
19
|
+
type: "document",
|
|
20
|
+
children: compileDocument(patchContent.content).sections?.[0]?.children ?? []
|
|
21
|
+
};
|
|
22
|
+
}
|
|
23
|
+
return patchDocument({
|
|
24
|
+
outputType,
|
|
25
|
+
data: template,
|
|
26
|
+
placeholders: patchesObject,
|
|
27
|
+
...keepOriginalStyles !== void 0 && { keepOriginalStyles },
|
|
28
|
+
...recursive !== void 0 && { recursive },
|
|
29
|
+
...placeholderDelimiters && { placeholderDelimiters: {
|
|
30
|
+
start: placeholderDelimiters.start ?? "{{",
|
|
31
|
+
end: placeholderDelimiters.end ?? "}}"
|
|
32
|
+
} }
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
//#endregion
|
|
2
36
|
export { patchDOCX };
|
|
@@ -1,2 +1,63 @@
|
|
|
1
|
-
import { a as
|
|
1
|
+
import { a as JSONContent } from "../core-C9VunJ8o.mjs";
|
|
2
|
+
|
|
3
|
+
//#region src/converters/prepare.d.ts
|
|
4
|
+
/**
|
|
5
|
+
* A prepare step that transforms Tiptap JSON in place (e.g. fetch external resources).
|
|
6
|
+
*/
|
|
7
|
+
type PrepareStep = (json: JSONContent) => Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Fetch handler for external image URLs.
|
|
10
|
+
*
|
|
11
|
+
* Receives the URL, returns the image binary data.
|
|
12
|
+
* Override to customize fetching (proxy, auth, caching, etc.).
|
|
13
|
+
*/
|
|
14
|
+
type ImageFetchHandler = (url: string) => Promise<Uint8Array>;
|
|
15
|
+
/**
|
|
16
|
+
* Default fetch handler using the global `fetch` API (Node 18+ and browsers).
|
|
17
|
+
*/
|
|
18
|
+
declare function fetchImageHandler(url: string): Promise<Uint8Array>;
|
|
19
|
+
/**
|
|
20
|
+
* Create a prepare step that fetches external image URLs and converts them to data URLs.
|
|
21
|
+
*
|
|
22
|
+
* @param handler - Custom fetch handler (defaults to `fetchImageHandler`)
|
|
23
|
+
*
|
|
24
|
+
* @example
|
|
25
|
+
* ```ts
|
|
26
|
+
* // Use with defaults
|
|
27
|
+
* await prepareDocument(json);
|
|
28
|
+
*
|
|
29
|
+
* // Custom handler
|
|
30
|
+
* await prepareDocument(json, [prepareImages(myHandler)]);
|
|
31
|
+
* ```
|
|
32
|
+
*/
|
|
33
|
+
declare function prepareImages(handler?: ImageFetchHandler): PrepareStep;
|
|
34
|
+
/**
|
|
35
|
+
* Create a prepare step that fills in missing image dimensions by reading image
|
|
36
|
+
* metadata. Intended to run after {@link prepareImages} so HTTP sources are
|
|
37
|
+
* already embedded as data URLs.
|
|
38
|
+
*
|
|
39
|
+
* Only images lacking `width` or `height` are probed — the DOCX round-trip path
|
|
40
|
+
* already carries `transformation.width/height`, so this mainly serves images
|
|
41
|
+
* entering via HTML/Markdown (which carry no intrinsic size). Images whose
|
|
42
|
+
* metadata can't be read are left untouched; `renderDocx` falls back to 600×400
|
|
43
|
+
* (see extensions/image.ts).
|
|
44
|
+
*/
|
|
45
|
+
declare function prepareImageSizes(): PrepareStep;
|
|
46
|
+
/**
|
|
47
|
+
* Run prepare steps on a Tiptap JSON document before compilation.
|
|
48
|
+
*
|
|
49
|
+
* Each step receives the JSON and may mutate it in place (e.g. replace external
|
|
50
|
+
* URLs with embedded data). Steps run sequentially in order.
|
|
51
|
+
*
|
|
52
|
+
* Defaults to `[prepareImages(), prepareImageSizes()]` when no steps are provided.
|
|
53
|
+
*
|
|
54
|
+
* @example
|
|
55
|
+
* ```ts
|
|
56
|
+
* const json = parseHTML(html);
|
|
57
|
+
* await prepareDocument(json); // default: fetch images
|
|
58
|
+
* const docOpts = compileDocument(json);
|
|
59
|
+
* ```
|
|
60
|
+
*/
|
|
61
|
+
declare function prepareDocument(json: JSONContent, steps?: readonly PrepareStep[]): Promise<void>;
|
|
62
|
+
//#endregion
|
|
2
63
|
export { ImageFetchHandler, PrepareStep, fetchImageHandler, prepareDocument, prepareImageSizes, prepareImages };
|
|
@@ -1,2 +1,122 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { encodeBase64 } from "@office-open/core";
|
|
2
|
+
import { imageMeta } from "image-meta";
|
|
3
|
+
//#region src/converters/prepare.ts
|
|
4
|
+
/**
|
|
5
|
+
* Default fetch handler using the global `fetch` API (Node 18+ and browsers).
|
|
6
|
+
*/
|
|
7
|
+
async function fetchImageHandler(url) {
|
|
8
|
+
const response = await fetch(url);
|
|
9
|
+
if (!response.ok) throw new Error(`Failed to fetch image from ${url}: ${response.status} ${response.statusText}`);
|
|
10
|
+
const buffer = await response.arrayBuffer();
|
|
11
|
+
return new Uint8Array(buffer);
|
|
12
|
+
}
|
|
13
|
+
/**
|
|
14
|
+
* Create a prepare step that fetches external image URLs and converts them to data URLs.
|
|
15
|
+
*
|
|
16
|
+
* @param handler - Custom fetch handler (defaults to `fetchImageHandler`)
|
|
17
|
+
*
|
|
18
|
+
* @example
|
|
19
|
+
* ```ts
|
|
20
|
+
* // Use with defaults
|
|
21
|
+
* await prepareDocument(json);
|
|
22
|
+
*
|
|
23
|
+
* // Custom handler
|
|
24
|
+
* await prepareDocument(json, [prepareImages(myHandler)]);
|
|
25
|
+
* ```
|
|
26
|
+
*/
|
|
27
|
+
function prepareImages(handler = fetchImageHandler) {
|
|
28
|
+
return async (json) => {
|
|
29
|
+
await walkImages(json, handler);
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/**
|
|
33
|
+
* Create a prepare step that fills in missing image dimensions by reading image
|
|
34
|
+
* metadata. Intended to run after {@link prepareImages} so HTTP sources are
|
|
35
|
+
* already embedded as data URLs.
|
|
36
|
+
*
|
|
37
|
+
* Only images lacking `width` or `height` are probed — the DOCX round-trip path
|
|
38
|
+
* already carries `transformation.width/height`, so this mainly serves images
|
|
39
|
+
* entering via HTML/Markdown (which carry no intrinsic size). Images whose
|
|
40
|
+
* metadata can't be read are left untouched; `renderDocx` falls back to 600×400
|
|
41
|
+
* (see extensions/image.ts).
|
|
42
|
+
*/
|
|
43
|
+
function prepareImageSizes() {
|
|
44
|
+
return async (json) => {
|
|
45
|
+
await walkImageSizes(json);
|
|
46
|
+
};
|
|
47
|
+
}
|
|
48
|
+
/** Built-in prepare steps, run when no custom steps are provided. */
|
|
49
|
+
const DEFAULT_STEPS = [prepareImages(), prepareImageSizes()];
|
|
50
|
+
/**
|
|
51
|
+
* Run prepare steps on a Tiptap JSON document before compilation.
|
|
52
|
+
*
|
|
53
|
+
* Each step receives the JSON and may mutate it in place (e.g. replace external
|
|
54
|
+
* URLs with embedded data). Steps run sequentially in order.
|
|
55
|
+
*
|
|
56
|
+
* Defaults to `[prepareImages(), prepareImageSizes()]` when no steps are provided.
|
|
57
|
+
*
|
|
58
|
+
* @example
|
|
59
|
+
* ```ts
|
|
60
|
+
* const json = parseHTML(html);
|
|
61
|
+
* await prepareDocument(json); // default: fetch images
|
|
62
|
+
* const docOpts = compileDocument(json);
|
|
63
|
+
* ```
|
|
64
|
+
*/
|
|
65
|
+
async function prepareDocument(json, steps = DEFAULT_STEPS) {
|
|
66
|
+
for (const step of steps) await step(json);
|
|
67
|
+
}
|
|
68
|
+
async function toDataUrl(src, handler) {
|
|
69
|
+
const data = await handler(src);
|
|
70
|
+
return `data:${{
|
|
71
|
+
jpg: "image/jpeg",
|
|
72
|
+
jpeg: "image/jpeg",
|
|
73
|
+
png: "image/png",
|
|
74
|
+
gif: "image/gif",
|
|
75
|
+
bmp: "image/bmp",
|
|
76
|
+
svg: "image/svg+xml",
|
|
77
|
+
webp: "image/webp"
|
|
78
|
+
}[src.split(".").pop()?.toLowerCase() ?? "png"] ?? "image/png"};base64,${encodeBase64(data)}`;
|
|
79
|
+
}
|
|
80
|
+
async function walkImages(node, handler) {
|
|
81
|
+
if (node.type === "image" && node.attrs) {
|
|
82
|
+
const src = node.attrs.src;
|
|
83
|
+
if (src && (src.startsWith("http://") || src.startsWith("https://"))) try {
|
|
84
|
+
node.attrs.src = await toDataUrl(src, handler);
|
|
85
|
+
} catch (error) {
|
|
86
|
+
console.warn(`Failed to fetch image: ${src}`, error instanceof Error ? error.message : error);
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
const tasks = [];
|
|
90
|
+
for (const child of node.content ?? []) tasks.push(walkImages(child, handler));
|
|
91
|
+
await Promise.all(tasks);
|
|
92
|
+
}
|
|
93
|
+
async function walkImageSizes(node) {
|
|
94
|
+
if (node.type === "image" && node.attrs) {
|
|
95
|
+
const attrs = node.attrs;
|
|
96
|
+
const needsWidth = attrs.width == null;
|
|
97
|
+
const needsHeight = attrs.height == null;
|
|
98
|
+
if (needsWidth || needsHeight) {
|
|
99
|
+
const bytes = decodeDataUrl(attrs.src);
|
|
100
|
+
if (bytes) try {
|
|
101
|
+
const meta = imageMeta(bytes);
|
|
102
|
+
if (needsWidth && typeof meta.width === "number") attrs.width = meta.width;
|
|
103
|
+
if (needsHeight && typeof meta.height === "number") attrs.height = meta.height;
|
|
104
|
+
} catch {}
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
const tasks = [];
|
|
108
|
+
for (const child of node.content ?? []) tasks.push(walkImageSizes(child));
|
|
109
|
+
await Promise.all(tasks);
|
|
110
|
+
}
|
|
111
|
+
/** Decode a `data:image/...;base64,...` URL to bytes; null if not a data URL. */
|
|
112
|
+
function decodeDataUrl(src) {
|
|
113
|
+
if (!src) return null;
|
|
114
|
+
const match = src.match(/^data:image\/[\w.+-]+;base64,(.+)$/);
|
|
115
|
+
if (!match) return null;
|
|
116
|
+
const binary = atob(match[1]);
|
|
117
|
+
const bytes = new Uint8Array(binary.length);
|
|
118
|
+
for (let i = 0; i < binary.length; i++) bytes[i] = binary.charCodeAt(i);
|
|
119
|
+
return bytes;
|
|
120
|
+
}
|
|
121
|
+
//#endregion
|
|
2
122
|
export { fetchImageHandler, prepareDocument, prepareImageSizes, prepareImages };
|
|
@@ -1,2 +1,85 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { JSONContent } from "@tiptap/core";
|
|
2
|
+
import { StylesOptions } from "@office-open/docx";
|
|
3
|
+
|
|
4
|
+
//#region src/converters/styles.d.ts
|
|
5
|
+
/** The styleId of the document's default paragraph style (`w:default="1"`
|
|
6
|
+
* type="paragraph") — the implicit style applied to every paragraph WITHOUT an
|
|
7
|
+
* explicit pStyle. OOXML renders a pStyle-less paragraph as this style (usually
|
|
8
|
+
* "Normal"), so the editor's `.docx-default` class — emitted by Paragraph/
|
|
9
|
+
* Heading renderHTML on a styleId-less node — targets it. Searched in
|
|
10
|
+
* `paragraphStyles` and the built-in named styles nested under `default`
|
|
11
|
+
* (key → pStyle id). null when the document declares none. */
|
|
12
|
+
declare function defaultParagraphStyleId(styles: StylesOptions | null | undefined): string | null;
|
|
13
|
+
/**
|
|
14
|
+
* Generate scoped CSS from a document's `styles.xml` model (`StylesOptions`) so
|
|
15
|
+
* named paragraph/character styles and the document defaults render correctly
|
|
16
|
+
* in the editor. All rules are scoped to `.docen-page` (the editor's page
|
|
17
|
+
* surface); the editor injects the result into a `<style>` after load.
|
|
18
|
+
*
|
|
19
|
+
* - `default.document` → `.docen-page` base run + paragraph defaults (the doc's
|
|
20
|
+
* default font/size, line spacing, …).
|
|
21
|
+
* - `paragraphStyles[]` → `.docen-page .docx-style-{id}` (run + paragraph CSS).
|
|
22
|
+
* Paragraph nodes carry `class="docx-style-{styleId}"` (see the Paragraph /
|
|
23
|
+
* Heading extensions' pStyle ↔ styleId round-trip).
|
|
24
|
+
* - `characterStyles[]` → `.docen-page .docx-char-{id}` (run CSS).
|
|
25
|
+
*
|
|
26
|
+
* `basedOn` inheritance is deep-merged per-property into each named style's
|
|
27
|
+
* rule (root first, child overrides) — see mergeStyleChain. CSS inheritance
|
|
28
|
+
* follows the DOM tree, not a style's basedOn, so each `.docx-style-{id}` rule
|
|
29
|
+
* carries its full ancestor chain rather than relying on source order.
|
|
30
|
+
*/
|
|
31
|
+
declare function stylesToCss(styles: StylesOptions | null | undefined, scope: string): string;
|
|
32
|
+
/** A gallery-ready paragraph-style entry for the Styles combobox. */
|
|
33
|
+
interface QuickStyleEntry {
|
|
34
|
+
id: string;
|
|
35
|
+
name: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The paragraph styles to list in the Quick Styles gallery, matching Word's
|
|
39
|
+
* default behavior: the gallery is a *paragraph-style* selector (it applies a
|
|
40
|
+
* pStyle), so only paragraph styles appear — never character styles, even
|
|
41
|
+
* those flagged `quickFormat` (those live in the Styles task pane). Among
|
|
42
|
+
* paragraph styles, only those flagged `quickFormat` are listed, ordered by
|
|
43
|
+
* `uiPriority` (Word orders the gallery this way).
|
|
44
|
+
*
|
|
45
|
+
* Reads `quickFormat`/`uiPriority`/`name` straight from office-open's styles
|
|
46
|
+
* model (`StylesOptions`): `paragraphStyles` (Normal + custom) and the built-in
|
|
47
|
+
* named paragraph styles nested under `default` (title/heading1-9/quote/…). The
|
|
48
|
+
* `default` keys that hold character styles are excluded via
|
|
49
|
+
* `CHARACTER_DEFAULT_KEYS`. When a document carries no quickFormat flags at all
|
|
50
|
+
* (e.g. some LibreOffice-generated files), fall back to all paragraph styles so
|
|
51
|
+
* the gallery is never empty.
|
|
52
|
+
*/
|
|
53
|
+
declare function quickStyles(styles: StylesOptions | null | undefined): QuickStyleEntry[];
|
|
54
|
+
/** Resolve the effective run-level properties (font name, size in points) at the
|
|
55
|
+
* caret, staying in the document's own units — no px conversion. Priority:
|
|
56
|
+
* direct run props (the textStyle mark) → the paragraph style (`styleId`) →
|
|
57
|
+
* its `basedOn` chain → the document defaults. `font` is resolved to a single
|
|
58
|
+
* display name (ascii/hAnsi/eastAsia). Returns null where nothing in the chain
|
|
59
|
+
* sets a property, so the caller can leave the box empty. */
|
|
60
|
+
declare function effectiveRunProps(styles: StylesOptions | null | undefined, styleId: string | null | undefined, direct?: {
|
|
61
|
+
font?: unknown;
|
|
62
|
+
size?: unknown;
|
|
63
|
+
}): {
|
|
64
|
+
font: string | null;
|
|
65
|
+
size: number | null;
|
|
66
|
+
};
|
|
67
|
+
/**
|
|
68
|
+
* Inline a document's styles into each node's attrs — a self-contained
|
|
69
|
+
* JSONContent that round-trips and renders without the styles context. A
|
|
70
|
+
* paragraph/heading absorbs its styleId's `basedOn` chain (paragraph properties
|
|
71
|
+
* + the paragraph's default `run`); a textStyle mark absorbs its character
|
|
72
|
+
* style's run. Explicit attrs/marks always override the inherited values;
|
|
73
|
+
* styleId is preserved (the semantic reference is kept). The style definitions
|
|
74
|
+
* themselves are untouched — only properties are copied onto nodes.
|
|
75
|
+
*
|
|
76
|
+
* Styles default to the document's own `attrs.styles` (the round-tripped
|
|
77
|
+
* styles.xml model carried on the doc node), so `inlineStyles(doc)` needs no
|
|
78
|
+
* second argument; pass `styles` explicitly to override. Use cases:
|
|
79
|
+
* cross-document paste (the snippet carries its styling) and self-contained
|
|
80
|
+
* export. The editor still renders via global styles — this only merges JSON
|
|
81
|
+
* properties.
|
|
82
|
+
*/
|
|
83
|
+
declare function inlineStyles(json: JSONContent, styles?: StylesOptions | null): JSONContent;
|
|
84
|
+
//#endregion
|
|
2
85
|
export { QuickStyleEntry, type StylesOptions, defaultParagraphStyleId, effectiveRunProps, inlineStyles, quickStyles, stylesToCss };
|