@docen/docx 0.3.0 → 0.3.1
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/README.md +2 -3
- package/dist/blockquote-Ky9xD_Q-.mjs +31 -0
- package/dist/code-block-BgNjhADy.mjs +23 -0
- package/dist/converters/docx.d.mts +1 -109
- package/dist/converters/docx.mjs +1 -623
- package/dist/converters/html.d.mts +1 -13
- package/dist/converters/html.mjs +1 -17
- package/dist/converters/markdown.d.mts +1 -12
- package/dist/converters/markdown.mjs +1 -17
- package/dist/converters/patch.d.mts +1 -52
- package/dist/converters/patch.mjs +1 -36
- package/dist/converters/prepare.d.mts +2 -51
- package/dist/converters/prepare.mjs +2 -76
- package/dist/converters/styles.d.mts +2 -0
- package/dist/converters/styles.mjs +2 -0
- package/dist/core-DEqBHRzE.mjs +406 -0
- package/dist/core-fBMCCj4D.d.mts +97 -0
- package/dist/core.d.mts +1 -1
- package/dist/core.mjs +1 -1
- package/dist/details-Dh8luWb2.mjs +18 -0
- package/dist/dist-B6B3HnVZ.mjs +1838 -0
- package/dist/document-BH1y4qHM.d.mts +6 -0
- package/dist/document-CvTym-FM.mjs +50 -0
- package/dist/docx-C5wyjI-0.d.mts +200 -0
- package/dist/docx-CONyAedl.mjs +1231 -0
- package/dist/editor-Bf3CxQnY.mjs +21 -0
- package/dist/editor-DReY3Rb9.d.mts +21 -0
- package/dist/editor.d.mts +1 -20
- package/dist/editor.mjs +1 -19
- package/dist/emoji-BZ1dJD7d.d.mts +17 -0
- package/dist/emoji-C2OsTWR7.mjs +27 -0
- package/dist/extensions/blockquote.d.mts +23 -0
- package/dist/extensions/blockquote.mjs +3 -0
- package/dist/extensions/code-block.d.mts +19 -0
- package/dist/extensions/code-block.mjs +2 -0
- package/dist/extensions/column-break.d.mts +31 -0
- package/dist/extensions/column-break.mjs +2 -0
- package/dist/extensions/details.d.mts +19 -0
- package/dist/extensions/details.mjs +3 -0
- package/dist/extensions/document.d.mts +2 -0
- package/dist/extensions/document.mjs +2 -0
- package/dist/extensions/emoji.d.mts +2 -0
- package/dist/extensions/emoji.mjs +2 -0
- package/dist/extensions/extensions.d.mts +6 -5
- package/dist/extensions/extensions.mjs +14 -12
- package/dist/extensions/formatting-marks.d.mts +32 -0
- package/dist/extensions/formatting-marks.mjs +2 -0
- package/dist/extensions/heading.mjs +1 -144
- package/dist/extensions/image-group.d.mts +6 -0
- package/dist/extensions/image-group.mjs +2 -0
- package/dist/extensions/image.d.mts +2 -2
- package/dist/extensions/image.mjs +1 -200
- package/dist/extensions/index.d.mts +3 -2
- package/dist/extensions/index.mjs +3 -2
- package/dist/extensions/mention.d.mts +25 -0
- package/dist/extensions/mention.mjs +3 -0
- package/dist/extensions/ordered-list.d.mts +25 -0
- package/dist/extensions/ordered-list.mjs +3 -0
- package/dist/extensions/page-break.d.mts +34 -0
- package/dist/extensions/page-break.mjs +2 -0
- package/dist/extensions/paragraph.d.mts +1 -1
- package/dist/extensions/paragraph.mjs +1 -115
- package/dist/extensions/passthrough.d.mts +2 -0
- package/dist/extensions/passthrough.mjs +2 -0
- package/dist/extensions/section-break.d.mts +27 -0
- package/dist/extensions/section-break.mjs +2 -0
- package/dist/extensions/strike.mjs +1 -49
- package/dist/extensions/table-cell.mjs +1 -111
- package/dist/extensions/table-header.mjs +1 -111
- package/dist/extensions/table-row.mjs +1 -83
- package/dist/extensions/table.mjs +1 -133
- package/dist/extensions/task-item.d.mts +27 -0
- package/dist/extensions/task-item.mjs +3 -0
- package/dist/extensions/text-style.mjs +1 -133
- package/dist/extensions/tiptap.mjs +1 -32
- package/dist/extensions/types.d.mts +7 -5
- package/dist/extensions/utils.d.mts +16 -3
- package/dist/extensions/utils.mjs +2 -289
- package/dist/heading-DAubgJJN.mjs +166 -0
- package/dist/html-DCC_DSHv.d.mts +18 -0
- package/dist/html-DedDWPoO.mjs +66 -0
- package/dist/image-CuEIXoZl.mjs +334 -0
- package/dist/index.d.mts +68 -18
- package/dist/index.mjs +10 -8
- package/dist/markdown-BzJmRsBs.mjs +18 -0
- package/dist/markdown-D0k0sRYS.d.mts +13 -0
- package/dist/mention-C-dTR6MK.mjs +44 -0
- package/dist/ordered-list-V-3xFQmV.mjs +41 -0
- package/dist/paragraph-BrWH-BOV.mjs +147 -0
- package/dist/paragraph-D8mpHo_o.d.mts +8 -0
- package/dist/patch-Cdp5WKJz.d.mts +53 -0
- package/dist/patch-IZ13tXTu.mjs +36 -0
- package/dist/prepare-Bdk8Cjkf.mjs +122 -0
- package/dist/prepare-CPAvcv6q.d.mts +63 -0
- package/dist/strike-hWxShOgb.mjs +50 -0
- package/dist/styles-QSX3RhxK.mjs +285 -0
- package/dist/styles-ugspkrxz.d.mts +85 -0
- package/dist/table-DrA12lxX.mjs +159 -0
- package/dist/table-cell-DmkfYwDc.mjs +112 -0
- package/dist/table-header-Bwr5GKWg.mjs +112 -0
- package/dist/table-row-vew1y7WH.mjs +84 -0
- package/dist/task-item-BnlXGrIG.mjs +37 -0
- package/dist/text-style-tMeBq5XQ.mjs +151 -0
- package/dist/tiptap-D7pl4I5U.mjs +33 -0
- package/dist/utils-BPT4tjcn.mjs +333 -0
- package/package.json +3 -2
- package/dist/core-CFIQVRfx.mjs +0 -88
- package/dist/core-omBKMRtl.d.mts +0 -34
- package/dist/image-Ge1y6uam.d.mts +0 -47
- package/dist/paragraph-fhEXtAN2.d.mts +0 -16
|
@@ -1,53 +1,2 @@
|
|
|
1
|
-
import {
|
|
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
|
|
1
|
+
import { n as DocxPatchOptions, r as patchDOCX, t as DocxPatchContent } from "../patch-Cdp5WKJz.mjs";
|
|
53
2
|
export { DocxPatchContent, DocxPatchOptions, patchDOCX };
|
|
@@ -1,37 +1,2 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { compileDocument } from "./docx.mjs";
|
|
3
|
-
import { PatchType, 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
|
-
const children = compileDocument(patchContent.content).sections?.[0]?.children ?? [];
|
|
19
|
-
patchesObject[key] = {
|
|
20
|
-
type: PatchType.DOCUMENT,
|
|
21
|
-
children
|
|
22
|
-
};
|
|
23
|
-
}
|
|
24
|
-
return patchDocument({
|
|
25
|
-
outputType,
|
|
26
|
-
data: template,
|
|
27
|
-
patches: patchesObject,
|
|
28
|
-
...keepOriginalStyles !== void 0 && { keepOriginalStyles },
|
|
29
|
-
...recursive !== void 0 && { recursive },
|
|
30
|
-
...placeholderDelimiters && { placeholderDelimiters: {
|
|
31
|
-
start: placeholderDelimiters.start ?? "{{",
|
|
32
|
-
end: placeholderDelimiters.end ?? "}}"
|
|
33
|
-
} }
|
|
34
|
-
});
|
|
35
|
-
}
|
|
36
|
-
//#endregion
|
|
1
|
+
import { t as patchDOCX } from "../patch-IZ13tXTu.mjs";
|
|
37
2
|
export { patchDOCX };
|
|
@@ -1,51 +1,2 @@
|
|
|
1
|
-
import { a as
|
|
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
|
-
* Run prepare steps on a Tiptap JSON document before compilation.
|
|
36
|
-
*
|
|
37
|
-
* Each step receives the JSON and may mutate it in place (e.g. replace external
|
|
38
|
-
* URLs with embedded data). Steps run sequentially in order.
|
|
39
|
-
*
|
|
40
|
-
* Defaults to `[prepareImages()]` when no steps are provided.
|
|
41
|
-
*
|
|
42
|
-
* @example
|
|
43
|
-
* ```ts
|
|
44
|
-
* const json = parseHTML(html);
|
|
45
|
-
* await prepareDocument(json); // default: fetch images
|
|
46
|
-
* const docOpts = compileDocument(json);
|
|
47
|
-
* ```
|
|
48
|
-
*/
|
|
49
|
-
declare function prepareDocument(json: JSONContent, steps?: readonly PrepareStep[]): Promise<void>;
|
|
50
|
-
//#endregion
|
|
51
|
-
export { ImageFetchHandler, PrepareStep, fetchImageHandler, prepareDocument, prepareImages };
|
|
1
|
+
import { a as prepareImageSizes, i as prepareDocument, n as PrepareStep, o as prepareImages, r as fetchImageHandler, t as ImageFetchHandler } from "../prepare-CPAvcv6q.mjs";
|
|
2
|
+
export { ImageFetchHandler, PrepareStep, fetchImageHandler, prepareDocument, prepareImageSizes, prepareImages };
|
|
@@ -1,76 +1,2 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
* Default fetch handler using the global `fetch` API (Node 18+ and browsers).
|
|
4
|
-
*/
|
|
5
|
-
async function fetchImageHandler(url) {
|
|
6
|
-
const response = await fetch(url);
|
|
7
|
-
if (!response.ok) throw new Error(`Failed to fetch image from ${url}: ${response.status} ${response.statusText}`);
|
|
8
|
-
const buffer = await response.arrayBuffer();
|
|
9
|
-
return new Uint8Array(buffer);
|
|
10
|
-
}
|
|
11
|
-
/**
|
|
12
|
-
* Create a prepare step that fetches external image URLs and converts them to data URLs.
|
|
13
|
-
*
|
|
14
|
-
* @param handler - Custom fetch handler (defaults to `fetchImageHandler`)
|
|
15
|
-
*
|
|
16
|
-
* @example
|
|
17
|
-
* ```ts
|
|
18
|
-
* // Use with defaults
|
|
19
|
-
* await prepareDocument(json);
|
|
20
|
-
*
|
|
21
|
-
* // Custom handler
|
|
22
|
-
* await prepareDocument(json, [prepareImages(myHandler)]);
|
|
23
|
-
* ```
|
|
24
|
-
*/
|
|
25
|
-
function prepareImages(handler = fetchImageHandler) {
|
|
26
|
-
return async (json) => {
|
|
27
|
-
await walkImages(json, handler);
|
|
28
|
-
};
|
|
29
|
-
}
|
|
30
|
-
/** Built-in prepare steps, run when no custom steps are provided. */
|
|
31
|
-
const DEFAULT_STEPS = [prepareImages()];
|
|
32
|
-
/**
|
|
33
|
-
* Run prepare steps on a Tiptap JSON document before compilation.
|
|
34
|
-
*
|
|
35
|
-
* Each step receives the JSON and may mutate it in place (e.g. replace external
|
|
36
|
-
* URLs with embedded data). Steps run sequentially in order.
|
|
37
|
-
*
|
|
38
|
-
* Defaults to `[prepareImages()]` when no steps are provided.
|
|
39
|
-
*
|
|
40
|
-
* @example
|
|
41
|
-
* ```ts
|
|
42
|
-
* const json = parseHTML(html);
|
|
43
|
-
* await prepareDocument(json); // default: fetch images
|
|
44
|
-
* const docOpts = compileDocument(json);
|
|
45
|
-
* ```
|
|
46
|
-
*/
|
|
47
|
-
async function prepareDocument(json, steps = DEFAULT_STEPS) {
|
|
48
|
-
for (const step of steps) await step(json);
|
|
49
|
-
}
|
|
50
|
-
async function toDataUrl(src, handler) {
|
|
51
|
-
const data = await handler(src);
|
|
52
|
-
return `data:${{
|
|
53
|
-
jpg: "image/jpeg",
|
|
54
|
-
jpeg: "image/jpeg",
|
|
55
|
-
png: "image/png",
|
|
56
|
-
gif: "image/gif",
|
|
57
|
-
bmp: "image/bmp",
|
|
58
|
-
svg: "image/svg+xml",
|
|
59
|
-
webp: "image/webp"
|
|
60
|
-
}[src.split(".").pop()?.toLowerCase() ?? "png"] ?? "image/png"};base64,${typeof btoa !== "undefined" ? btoa(String.fromCharCode(...data)) : Buffer.from(data).toString("base64")}`;
|
|
61
|
-
}
|
|
62
|
-
async function walkImages(node, handler) {
|
|
63
|
-
if (node.type === "image" && node.attrs) {
|
|
64
|
-
const src = node.attrs.src;
|
|
65
|
-
if (src && (src.startsWith("http://") || src.startsWith("https://"))) try {
|
|
66
|
-
node.attrs.src = await toDataUrl(src, handler);
|
|
67
|
-
} catch (error) {
|
|
68
|
-
console.warn(`Failed to fetch image: ${src}`, error instanceof Error ? error.message : error);
|
|
69
|
-
}
|
|
70
|
-
}
|
|
71
|
-
const tasks = [];
|
|
72
|
-
for (const child of node.content ?? []) tasks.push(walkImages(child, handler));
|
|
73
|
-
await Promise.all(tasks);
|
|
74
|
-
}
|
|
75
|
-
//#endregion
|
|
76
|
-
export { fetchImageHandler, prepareDocument, prepareImages };
|
|
1
|
+
import { i as prepareImages, n as prepareDocument, r as prepareImageSizes, t as fetchImageHandler } from "../prepare-Bdk8Cjkf.mjs";
|
|
2
|
+
export { fetchImageHandler, prepareDocument, prepareImageSizes, prepareImages };
|
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import { a as inlineStyles, i as effectiveRunProps, n as StylesOptions, o as quickStyles, r as defaultParagraphStyleId, s as stylesToCss, t as QuickStyleEntry } from "../styles-ugspkrxz.mjs";
|
|
2
|
+
export { QuickStyleEntry, type StylesOptions, defaultParagraphStyleId, effectiveRunProps, inlineStyles, quickStyles, stylesToCss };
|
|
@@ -0,0 +1,406 @@
|
|
|
1
|
+
import { B as UndoRedo, C as Mention, D as Subscript, F as Text, I as TextAlign$1, N as TaskItem, O as Superscript, P as TaskList, R as TrailingNode, S as Mathematics, a as CodeBlockLowlight, b as ListItem, c as DetailsSummary, f as Gapcursor, g as HorizontalRule, h as Highlight, i as Code, n as Bold, o as Details, p as HardBreak, r as BulletList, s as DetailsContent, t as Blockquote, u as Dropcursor, v as Italic, w as OrderedList, x as ListKeymap, y as Link, z as Underline } from "./tiptap-D7pl4I5U.mjs";
|
|
2
|
+
import "./blockquote-Ky9xD_Q-.mjs";
|
|
3
|
+
import { t as CodeBlock } from "./code-block-BgNjhADy.mjs";
|
|
4
|
+
import "./details-Dh8luWb2.mjs";
|
|
5
|
+
import { t as Document } from "./document-CvTym-FM.mjs";
|
|
6
|
+
import { t as Emoji } from "./emoji-C2OsTWR7.mjs";
|
|
7
|
+
import { t as Heading } from "./heading-DAubgJJN.mjs";
|
|
8
|
+
import { t as Image } from "./image-CuEIXoZl.mjs";
|
|
9
|
+
import { t as encodeBase64 } from "./dist-B6B3HnVZ.mjs";
|
|
10
|
+
import "./mention-C-dTR6MK.mjs";
|
|
11
|
+
import "./ordered-list-V-3xFQmV.mjs";
|
|
12
|
+
import { t as Paragraph } from "./paragraph-BrWH-BOV.mjs";
|
|
13
|
+
import { t as Strike } from "./strike-hWxShOgb.mjs";
|
|
14
|
+
import { t as Table } from "./table-DrA12lxX.mjs";
|
|
15
|
+
import { t as TableCell } from "./table-cell-DmkfYwDc.mjs";
|
|
16
|
+
import { t as TableHeader } from "./table-header-Bwr5GKWg.mjs";
|
|
17
|
+
import { t as TableRow } from "./table-row-vew1y7WH.mjs";
|
|
18
|
+
import "./task-item-BnlXGrIG.mjs";
|
|
19
|
+
import { t as TextStyle } from "./text-style-tMeBq5XQ.mjs";
|
|
20
|
+
import { Editor, Extension, Mark, Node } from "@tiptap/core";
|
|
21
|
+
import { all, createLowlight } from "lowlight";
|
|
22
|
+
import { TextAlign } from "@tiptap/extension-text-align";
|
|
23
|
+
import { Plugin, PluginKey } from "@tiptap/pm/state";
|
|
24
|
+
import { Decoration, DecorationSet } from "@tiptap/pm/view";
|
|
25
|
+
//#region src/extensions/column-break.ts
|
|
26
|
+
/**
|
|
27
|
+
* ColumnBreak — inline atom node for DOCX column breaks (`<w:br w:type="column"/>`).
|
|
28
|
+
*
|
|
29
|
+
* Sibling to PageBreak and HardBreak: all three render `<w:br>`, differing only
|
|
30
|
+
* by type. Inline (group "inline") to match the run-internal OOXML position,
|
|
31
|
+
* keeping a column break inside its paragraph on round-trip. Visual column
|
|
32
|
+
* layout (paged.js multi-column) is a future concern; the node preserves the
|
|
33
|
+
* break losslessly regardless.
|
|
34
|
+
*
|
|
35
|
+
* The DOCX payload (`{ columnBreak: true }`) is inlined in DocxManager — a
|
|
36
|
+
* one-liner with no per-node variance, so no extension helper for it.
|
|
37
|
+
*
|
|
38
|
+
* `setColumnBreak` only inserts the atom (no paragraph split): a column break
|
|
39
|
+
* does not start a new page, and there is no column layout to reflow yet, so
|
|
40
|
+
* the node is purely for round-trip fidelity until multi-column lands.
|
|
41
|
+
*/
|
|
42
|
+
const ColumnBreak = Node.create({
|
|
43
|
+
name: "columnBreak",
|
|
44
|
+
inline: true,
|
|
45
|
+
group: "inline",
|
|
46
|
+
atom: true,
|
|
47
|
+
parseHTML() {
|
|
48
|
+
return [{ tag: "span[data-type=\"columnBreak\"]" }, { tag: "br[data-type=\"columnBreak\"]" }];
|
|
49
|
+
},
|
|
50
|
+
renderHTML() {
|
|
51
|
+
return ["span", {
|
|
52
|
+
"data-type": "columnBreak",
|
|
53
|
+
style: "break-after:column"
|
|
54
|
+
}];
|
|
55
|
+
},
|
|
56
|
+
addCommands() {
|
|
57
|
+
return { setColumnBreak: () => ({ commands }) => commands.insertContent({ type: "columnBreak" }) };
|
|
58
|
+
}
|
|
59
|
+
});
|
|
60
|
+
//#endregion
|
|
61
|
+
//#region src/extensions/formatting-marks.ts
|
|
62
|
+
/** Plugin key holding the on/off boolean for formatting marks. Toggled via a
|
|
63
|
+
* transaction meta so `decorations` recomputes when the user flips the Show/
|
|
64
|
+
* Hide button. */
|
|
65
|
+
const FORMAT_MARKS_KEY = new PluginKey("docen-formatting-marks");
|
|
66
|
+
/**
|
|
67
|
+
* FormattingMarks — paints the non-printing paragraph mark via ProseMirror
|
|
68
|
+
* widget decorations.
|
|
69
|
+
*
|
|
70
|
+
* CSS `p::after { content: "…" }` does NOT work on a ProseMirror-managed
|
|
71
|
+
* paragraph: the view owns the DOM and the trailing-break kludge pushes the
|
|
72
|
+
* pseudo-element off the visible line, so the mark never appears (verified).
|
|
73
|
+
* Marijn Haverbeke endorses widget decorations for exactly this use case
|
|
74
|
+
* (https://discuss.prosemirror.net/t/1442). Each textblock gets a widget at its
|
|
75
|
+
* closing position (inside the textblock, so it stays on the content's line)
|
|
76
|
+
* holding a non-editable `<span class="docen-para-mark">`. `side: 1` keeps the
|
|
77
|
+
* mark to the right of a cursor resting at the paragraph end (the cursor always
|
|
78
|
+
* precedes its own paragraph mark).
|
|
79
|
+
*
|
|
80
|
+
* Glyph: a down-then-left return arrow (↲, U+21B2 "downwards arrow with tip
|
|
81
|
+
* leftwards" — the Enter-key glyph). Word/WPS use the pilcrow ¶ for paragraph
|
|
82
|
+
* marks; that down-then-left arrow is Word's line-break glyph — adopted here as
|
|
83
|
+
* the paragraph mark per the project's visual preference.
|
|
84
|
+
*/
|
|
85
|
+
const FormattingMarks = Extension.create({
|
|
86
|
+
name: "formattingMarks",
|
|
87
|
+
addProseMirrorPlugins() {
|
|
88
|
+
return [new Plugin({
|
|
89
|
+
key: FORMAT_MARKS_KEY,
|
|
90
|
+
state: {
|
|
91
|
+
init: () => false,
|
|
92
|
+
apply: (tr, value) => {
|
|
93
|
+
if (tr.getMeta(FORMAT_MARKS_KEY) === "toggle") return !value;
|
|
94
|
+
return value;
|
|
95
|
+
}
|
|
96
|
+
},
|
|
97
|
+
props: { decorations(state) {
|
|
98
|
+
if (!FORMAT_MARKS_KEY.getState(state)) return DecorationSet.empty;
|
|
99
|
+
const decos = [];
|
|
100
|
+
state.doc.descendants((node, pos) => {
|
|
101
|
+
if (!node.isTextblock) return;
|
|
102
|
+
decos.push(Decoration.widget(pos + node.nodeSize - 1, () => {
|
|
103
|
+
const span = document.createElement("span");
|
|
104
|
+
span.className = "docen-para-mark";
|
|
105
|
+
span.contentEditable = "false";
|
|
106
|
+
span.textContent = "↲";
|
|
107
|
+
return span;
|
|
108
|
+
}, { side: 1 }));
|
|
109
|
+
});
|
|
110
|
+
return DecorationSet.create(state.doc, decos);
|
|
111
|
+
} }
|
|
112
|
+
})];
|
|
113
|
+
},
|
|
114
|
+
addCommands() {
|
|
115
|
+
return { toggleFormattingMarks: () => ({ tr, dispatch }) => {
|
|
116
|
+
if (dispatch) dispatch(tr.setMeta(FORMAT_MARKS_KEY, "toggle"));
|
|
117
|
+
return true;
|
|
118
|
+
} };
|
|
119
|
+
}
|
|
120
|
+
});
|
|
121
|
+
//#endregion
|
|
122
|
+
//#region src/extensions/image-group.ts
|
|
123
|
+
/**
|
|
124
|
+
* ImageGroup — inline atom node carrying a DOCX drawing group (wpg:
|
|
125
|
+
* wordprocessingGroup) as an opaque blob. A group bundles pictures, shapes, and
|
|
126
|
+
* nested groups behind a shared transform; the editor doesn't model its
|
|
127
|
+
* interior — the full WpgGroupRunOptions round-trips verbatim in attrs.wpgGroup.
|
|
128
|
+
*
|
|
129
|
+
* renderHTML paints the first picture (common case: a single-logo header group)
|
|
130
|
+
* so the group is visible; shapes/nested groups fall back to a placeholder.
|
|
131
|
+
* Structured group/shape editing is a separate phase.
|
|
132
|
+
*/
|
|
133
|
+
const EMU_PER_PX = 9525;
|
|
134
|
+
/** Extract the first picture (pic/MediaData) from a group for display. */
|
|
135
|
+
function firstPicture(group) {
|
|
136
|
+
const pic = group?.children?.find((c) => {
|
|
137
|
+
const t = c.type;
|
|
138
|
+
return typeof t === "string" && t !== "wps" && t !== "wpg";
|
|
139
|
+
});
|
|
140
|
+
if (!pic?.data) return null;
|
|
141
|
+
const bytes = pic.data instanceof Uint8Array ? pic.data : new Uint8Array(Object.values(pic.data));
|
|
142
|
+
const width = pic.transformation?.width;
|
|
143
|
+
const height = pic.transformation?.height;
|
|
144
|
+
return {
|
|
145
|
+
src: `data:image/${pic.type};base64,${encodeBase64(bytes)}`,
|
|
146
|
+
width: typeof width === "number" ? Math.round(width / EMU_PER_PX) : void 0,
|
|
147
|
+
height: typeof height === "number" ? Math.round(height / EMU_PER_PX) : void 0
|
|
148
|
+
};
|
|
149
|
+
}
|
|
150
|
+
const attrWpgGroup = () => ({
|
|
151
|
+
default: null,
|
|
152
|
+
rendered: false,
|
|
153
|
+
parseHTML: (element) => {
|
|
154
|
+
const raw = element.getAttribute("data-wpg-group");
|
|
155
|
+
if (!raw) return null;
|
|
156
|
+
try {
|
|
157
|
+
return JSON.parse(raw);
|
|
158
|
+
} catch {
|
|
159
|
+
return null;
|
|
160
|
+
}
|
|
161
|
+
}
|
|
162
|
+
});
|
|
163
|
+
const ImageGroup = Node.create({
|
|
164
|
+
name: "imageGroup",
|
|
165
|
+
group: "inline",
|
|
166
|
+
inline: true,
|
|
167
|
+
atom: true,
|
|
168
|
+
addAttributes() {
|
|
169
|
+
return { wpgGroup: attrWpgGroup() };
|
|
170
|
+
},
|
|
171
|
+
parseHTML() {
|
|
172
|
+
return [{ tag: "span[data-image-group]" }];
|
|
173
|
+
},
|
|
174
|
+
renderHTML({ node }) {
|
|
175
|
+
const attrs = {
|
|
176
|
+
"data-image-group": "",
|
|
177
|
+
contenteditable: "false"
|
|
178
|
+
};
|
|
179
|
+
const pic = firstPicture(node.attrs.wpgGroup);
|
|
180
|
+
if (pic) {
|
|
181
|
+
const imgAttrs = {
|
|
182
|
+
src: pic.src,
|
|
183
|
+
alt: ""
|
|
184
|
+
};
|
|
185
|
+
if (pic.width && pic.height) imgAttrs.style = `width:${pic.width}px;height:${pic.height}px`;
|
|
186
|
+
return [
|
|
187
|
+
"span",
|
|
188
|
+
attrs,
|
|
189
|
+
["img", imgAttrs]
|
|
190
|
+
];
|
|
191
|
+
}
|
|
192
|
+
return [
|
|
193
|
+
"span",
|
|
194
|
+
attrs,
|
|
195
|
+
"♢"
|
|
196
|
+
];
|
|
197
|
+
}
|
|
198
|
+
});
|
|
199
|
+
//#endregion
|
|
200
|
+
//#region src/extensions/page-break.ts
|
|
201
|
+
/**
|
|
202
|
+
* PageBreak — inline atom node for DOCX page breaks (`<w:br w:type="page"/>`).
|
|
203
|
+
*
|
|
204
|
+
* OOXML page breaks live inside a run (inline), so the Tiptap node is inline
|
|
205
|
+
* too (group "inline"). This keeps a break inside its paragraph on round-trip
|
|
206
|
+
* — a break mid-paragraph stays mid-paragraph instead of splitting the
|
|
207
|
+
* paragraph into separate blocks (which would lose the original structure).
|
|
208
|
+
* Sibling to HardBreak: both render `<w:br>`, differing only by type.
|
|
209
|
+
*
|
|
210
|
+
* The DOCX payload (`{ pageBreak: true }`) is inlined in DocxManager — a
|
|
211
|
+
* one-liner with no per-node variance, so no extension helper for it.
|
|
212
|
+
*
|
|
213
|
+
* Inserting a page break must also visually reflow (the C-route paginator only
|
|
214
|
+
* forces a new page after a block *containing* a pageBreak atom). So
|
|
215
|
+
* `setPageBreak` inserts the atom then splits the paragraph — the atom lands at
|
|
216
|
+
* the end of the first half, the trailing text becomes a fresh paragraph, and
|
|
217
|
+
* the paginator's `forcesPageBreakAfter` moves it to the next page (matching
|
|
218
|
+
* Word's Ctrl+Enter behavior).
|
|
219
|
+
*/
|
|
220
|
+
const PageBreak = Node.create({
|
|
221
|
+
name: "pageBreak",
|
|
222
|
+
inline: true,
|
|
223
|
+
group: "inline",
|
|
224
|
+
atom: true,
|
|
225
|
+
parseHTML() {
|
|
226
|
+
return [{ tag: "span[data-type=\"pageBreak\"]" }, { tag: "br[data-type=\"pageBreak\"]" }];
|
|
227
|
+
},
|
|
228
|
+
renderHTML() {
|
|
229
|
+
return ["span", {
|
|
230
|
+
"data-type": "pageBreak",
|
|
231
|
+
style: "break-after:page"
|
|
232
|
+
}];
|
|
233
|
+
},
|
|
234
|
+
addCommands() {
|
|
235
|
+
return { setPageBreak: () => ({ tr, state, dispatch }) => {
|
|
236
|
+
if (!dispatch) return true;
|
|
237
|
+
tr.replaceSelectionWith(state.schema.nodes.pageBreak.create());
|
|
238
|
+
tr.split(tr.selection.from, 1);
|
|
239
|
+
tr.scrollIntoView();
|
|
240
|
+
return true;
|
|
241
|
+
} };
|
|
242
|
+
}
|
|
243
|
+
});
|
|
244
|
+
//#endregion
|
|
245
|
+
//#region src/extensions/passthrough.ts
|
|
246
|
+
/**
|
|
247
|
+
* Passthrough — block atom carrying an opaque {@link SectionChild} that has
|
|
248
|
+
* no native Tiptap representation (rawXml, bookmarkStart/End, toc, textbox,
|
|
249
|
+
* altChunk, subDoc, customXml).
|
|
250
|
+
*
|
|
251
|
+
* The full SectionChild is stored as JSON in `attrs.data` so the DOCX→JSON→DOCX
|
|
252
|
+
* round-trip stays byte-faithful: office-open's stringify handles the inner
|
|
253
|
+
* structure verbatim (including a textbox's nested children, which remain as
|
|
254
|
+
* structured ParagraphOptions inside the blob rather than editable Tiptap
|
|
255
|
+
* nodes). The node is not editable — it renders a read-only placeholder in HTML.
|
|
256
|
+
*
|
|
257
|
+
* DOCX serialization is inlined in DocxManager (compile/resolve read/write
|
|
258
|
+
* `attrs.data` directly), so no renderDocx/parseDocx is needed here.
|
|
259
|
+
*/
|
|
260
|
+
const Passthrough = Node.create({
|
|
261
|
+
name: "passthrough",
|
|
262
|
+
group: "block",
|
|
263
|
+
atom: true,
|
|
264
|
+
addAttributes() {
|
|
265
|
+
return { data: {
|
|
266
|
+
default: "{}",
|
|
267
|
+
rendered: false
|
|
268
|
+
} };
|
|
269
|
+
},
|
|
270
|
+
parseHTML() {
|
|
271
|
+
return [{ tag: "div[data-passthrough]" }];
|
|
272
|
+
},
|
|
273
|
+
renderHTML({ node }) {
|
|
274
|
+
let label = "DOCX";
|
|
275
|
+
try {
|
|
276
|
+
const parsed = JSON.parse(node.attrs.data || "{}");
|
|
277
|
+
const key = Object.keys(parsed)[0];
|
|
278
|
+
if (key) label = key;
|
|
279
|
+
} catch {}
|
|
280
|
+
return [
|
|
281
|
+
"div",
|
|
282
|
+
{
|
|
283
|
+
"data-passthrough": label,
|
|
284
|
+
contenteditable: "false",
|
|
285
|
+
style: "display:block;padding:0.5em 0.75em;margin:0.5em 0;border:1px dashed #bbb;border-radius:4px;color:#888;font-size:0.85em;background:#fafafa"
|
|
286
|
+
},
|
|
287
|
+
[
|
|
288
|
+
"span",
|
|
289
|
+
{},
|
|
290
|
+
`[${label}]`
|
|
291
|
+
]
|
|
292
|
+
];
|
|
293
|
+
}
|
|
294
|
+
});
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region src/extensions/section-break.ts
|
|
297
|
+
/**
|
|
298
|
+
* SectionBreak — command extension that marks a paragraph as a section boundary.
|
|
299
|
+
*
|
|
300
|
+
* OOXML sections (sectPr) attach to a section's LAST paragraph's pPr, NOT a
|
|
301
|
+
* standalone node. So this extension provides only the `setSectionBreak`
|
|
302
|
+
* command (stamps sectionProperties on the current paragraph); the paragraph
|
|
303
|
+
* extension carries the sectionProperties/sectionHeaders/sectionFooters attrs,
|
|
304
|
+
* and DocxManager splits/merges sections in compile/resolve by reading them off
|
|
305
|
+
* the paragraph.
|
|
306
|
+
*
|
|
307
|
+
* The final section's sectPr rides on doc.attrs.sectionProperties (it lives at
|
|
308
|
+
* <w:body>'s end in OOXML). Single-section documents have no section-carrying
|
|
309
|
+
* paragraph at all.
|
|
310
|
+
*/
|
|
311
|
+
const SectionBreak = Extension.create({
|
|
312
|
+
name: "sectionBreak",
|
|
313
|
+
addCommands() {
|
|
314
|
+
return { setSectionBreak: () => ({ commands }) => commands.updateAttributes("paragraph", { sectionProperties: {} }) };
|
|
315
|
+
}
|
|
316
|
+
});
|
|
317
|
+
//#endregion
|
|
318
|
+
//#region src/extensions/extensions.ts
|
|
319
|
+
const tiptapNodeExtensions = [
|
|
320
|
+
Document,
|
|
321
|
+
Paragraph,
|
|
322
|
+
Text,
|
|
323
|
+
HardBreak,
|
|
324
|
+
PageBreak,
|
|
325
|
+
ColumnBreak,
|
|
326
|
+
SectionBreak,
|
|
327
|
+
Passthrough,
|
|
328
|
+
Blockquote,
|
|
329
|
+
OrderedList,
|
|
330
|
+
BulletList,
|
|
331
|
+
ListItem,
|
|
332
|
+
CodeBlock.configure({ lowlight: createLowlight(all) }),
|
|
333
|
+
Details,
|
|
334
|
+
DetailsSummary,
|
|
335
|
+
DetailsContent,
|
|
336
|
+
Emoji,
|
|
337
|
+
HorizontalRule,
|
|
338
|
+
Image.configure({ inline: true }),
|
|
339
|
+
ImageGroup,
|
|
340
|
+
Mathematics,
|
|
341
|
+
Mention,
|
|
342
|
+
Table,
|
|
343
|
+
TableRow,
|
|
344
|
+
TableCell,
|
|
345
|
+
TableHeader,
|
|
346
|
+
TaskList,
|
|
347
|
+
TaskItem,
|
|
348
|
+
Heading,
|
|
349
|
+
TextAlign$1.configure({ types: ["heading", "paragraph"] })
|
|
350
|
+
];
|
|
351
|
+
const tiptapMarkExtensions = [
|
|
352
|
+
Bold,
|
|
353
|
+
Code,
|
|
354
|
+
Highlight,
|
|
355
|
+
Italic,
|
|
356
|
+
Link,
|
|
357
|
+
Strike,
|
|
358
|
+
Subscript,
|
|
359
|
+
Superscript,
|
|
360
|
+
TextStyle,
|
|
361
|
+
Underline
|
|
362
|
+
];
|
|
363
|
+
const docxExtensions = [
|
|
364
|
+
...tiptapNodeExtensions,
|
|
365
|
+
...tiptapMarkExtensions,
|
|
366
|
+
FormattingMarks,
|
|
367
|
+
UndoRedo,
|
|
368
|
+
Dropcursor,
|
|
369
|
+
Gapcursor,
|
|
370
|
+
TrailingNode,
|
|
371
|
+
ListKeymap
|
|
372
|
+
];
|
|
373
|
+
const DocxKit = Extension.create({
|
|
374
|
+
name: "docxKit",
|
|
375
|
+
addExtensions() {
|
|
376
|
+
const extensions = [];
|
|
377
|
+
if (this.options.bold !== false) extensions.push(Bold.configure(this.options.bold));
|
|
378
|
+
if (this.options.blockquote !== false) extensions.push(Blockquote.configure(this.options.blockquote));
|
|
379
|
+
if (this.options.bulletList !== false) extensions.push(BulletList.configure(this.options.bulletList));
|
|
380
|
+
if (this.options.code !== false) extensions.push(Code.configure(this.options.code));
|
|
381
|
+
if (this.options.codeBlock !== false) extensions.push(CodeBlockLowlight.configure({
|
|
382
|
+
lowlight: createLowlight(all),
|
|
383
|
+
...this.options.codeBlock
|
|
384
|
+
}));
|
|
385
|
+
if (this.options.document !== false) extensions.push(Document);
|
|
386
|
+
if (this.options.dropcursor !== false) extensions.push(Dropcursor.configure(this.options.dropcursor));
|
|
387
|
+
if (this.options.gapcursor !== false) extensions.push(Gapcursor);
|
|
388
|
+
if (this.options.hardBreak !== false) extensions.push(HardBreak.configure(this.options.hardBreak));
|
|
389
|
+
if (this.options.heading !== false) extensions.push(Heading.configure(this.options.heading));
|
|
390
|
+
if (this.options.undoRedo !== false) extensions.push(UndoRedo.configure(this.options.undoRedo));
|
|
391
|
+
if (this.options.horizontalRule !== false) extensions.push(HorizontalRule.configure(this.options.horizontalRule));
|
|
392
|
+
if (this.options.italic !== false) extensions.push(Italic.configure(this.options.italic));
|
|
393
|
+
if (this.options.listItem !== false) extensions.push(ListItem.configure(this.options.listItem));
|
|
394
|
+
if (this.options.listKeymap !== false) extensions.push(ListKeymap.configure(this.options.listKeymap));
|
|
395
|
+
if (this.options.link !== false) extensions.push(Link.configure(this.options.link));
|
|
396
|
+
if (this.options.orderedList !== false) extensions.push(OrderedList.configure(this.options.orderedList));
|
|
397
|
+
if (this.options.paragraph !== false) extensions.push(Paragraph.configure(this.options.paragraph));
|
|
398
|
+
if (this.options.strike !== false) extensions.push(Strike.configure(this.options.strike));
|
|
399
|
+
if (this.options.text !== false) extensions.push(Text);
|
|
400
|
+
if (this.options.underline !== false) extensions.push(Underline.configure(this.options.underline));
|
|
401
|
+
if (this.options.trailingNode !== false) extensions.push(TrailingNode.configure(this.options.trailingNode));
|
|
402
|
+
return extensions;
|
|
403
|
+
}
|
|
404
|
+
});
|
|
405
|
+
//#endregion
|
|
406
|
+
export { DocxKit as a, tiptapMarkExtensions as c, Passthrough as d, PageBreak as f, ColumnBreak as h, Node as i, tiptapNodeExtensions as l, FormattingMarks as m, Extension as n, TextAlign as o, ImageGroup as p, Mark as r, docxExtensions as s, Editor as t, SectionBreak as u };
|