@blocknote/xl-docx-exporter
Version:
A "Notion-style" block-based extensible text editor built on top of Prosemirror and Tiptap.
475 lines (474 loc) • 13.1 kB
JavaScript
import { mapTableCell as k, UnreachableCaseError as T, Exporter as v, COLORS_DEFAULT as F } from "@blocknote/core";
import { Table as D, TableRow as I, TableCell as g, Paragraph as l, ShadingType as w, ImageRun as S, PageBreak as _, TextRun as p, CheckBox as M, ExternalHyperlink as L, Tab as E, AlignmentType as C, LevelFormat as A, Packer as U, Document as R } from "docx";
async function H(e) {
if (typeof window < "u") {
const t = await createImageBitmap(e), { width: n, height: r } = t;
return t.close(), { width: n, height: r };
} else {
const t = (await import("image-meta")).imageMeta, n = new Uint8Array(await e.arrayBuffer()), r = t(n);
if (!r.width || !r.height)
throw new Error("Image dimensions not found");
return { width: r.width, height: r.height };
}
}
const W = (e, t) => {
const r = new Array(e.headerRows ?? 0).fill(!0), o = new Array(e.headerCols ?? 0).fill(!0);
return new D({
layout: "autofit",
columnWidths: e.columnWidths.map(
(i) => (i ?? 120) * /* to points */
0.75 * /* to twips */
20
),
rows: e.rows.map((i, u) => {
const c = r[u];
return new I({
tableHeader: c,
children: i.cells.map((d, y) => {
var b;
const x = (b = e.columnWidths) == null ? void 0 : b[y], s = k(d), O = o[y];
return new g({
width: x ? {
size: `${x * 0.75}pt`,
type: "dxa"
} : void 0,
columnSpan: s.props.colspan,
rowSpan: s.props.rowspan,
shading: s.props.backgroundColor === "default" || !s.props.backgroundColor ? void 0 : {
type: w.SOLID,
color: t.options.colors[s.props.backgroundColor].background.slice(1)
},
children: [
new l({
children: t.transformInlineContent(s.content),
alignment: !s.props.textAlignment || s.props.textAlignment === "left" ? void 0 : s.props.textAlignment === "center" ? "center" : s.props.textAlignment === "right" ? "right" : s.props.textAlignment === "justify" ? "distribute" : (() => {
throw new T(
s.props.textAlignment
);
})(),
run: {
// TODO add support for table headers exporting, bolding seems to not be working at the moment
bold: c || O,
// TODO table paragraph color seems to not be working at the moment
// Probably because the runs are setting their own color
color: s.props.textColor === "default" || !s.props.textColor ? void 0 : t.options.colors[s.props.textColor].text.slice(1)
}
})
]
});
})
});
})
});
};
function a(e, t) {
return {
shading: e.backgroundColor === "default" || !e.backgroundColor ? void 0 : {
type: w.SOLID,
color: t[e.backgroundColor].background.slice(1)
},
run: e.textColor === "default" || !e.textColor ? void 0 : {
color: t[e.textColor].text.slice(1)
},
alignment: !e.textAlignment || e.textAlignment === "left" ? void 0 : e.textAlignment === "center" ? "center" : e.textAlignment === "right" ? "right" : e.textAlignment === "justify" ? "distribute" : (() => {
throw new T(e.textAlignment);
})()
};
}
const $ = {
paragraph: (e, t) => new l({
...a(e.props, t.options.colors),
children: t.transformInlineContent(e.content),
style: "Normal",
run: {
font: "Inter"
}
}),
toggleListItem: (e, t) => new l({
...a(e.props, t.options.colors),
children: [
new p({
children: ["> "]
}),
...t.transformInlineContent(e.content)
]
}),
numberedListItem: (e, t, n) => new l({
...a(e.props, t.options.colors),
children: t.transformInlineContent(e.content),
numbering: {
reference: "blocknote-numbered-list",
level: n
}
}),
bulletListItem: (e, t, n) => new l({
...a(e.props, t.options.colors),
children: t.transformInlineContent(e.content),
numbering: {
reference: "blocknote-bullet-list",
level: n
}
}),
checkListItem: (e, t) => new l({
...a(e.props, t.options.colors),
children: [
new M({ checked: e.props.checked }),
new p({
children: [" "]
}),
...t.transformInlineContent(e.content)
]
}),
heading: (e, t) => new l({
...a(e.props, t.options.colors),
children: t.transformInlineContent(e.content),
heading: `Heading${e.props.level}`
}),
quote: (e, t) => new l({
shading: {
color: "#7D797A"
},
border: {
left: {
color: "#7D797A",
space: 100,
style: "single",
size: 8
}
},
...a(e.props, t.options.colors),
children: t.transformInlineContent(e.content)
}),
audio: (e, t) => [
m(e.props, "Open audio", t),
...f(e.props, t)
],
video: (e, t) => [
m(e.props, "Open video", t),
...f(e.props, t)
],
file: (e, t) => [
m(e.props, "Open file", t),
...f(e.props, t)
],
codeBlock: (e) => {
var n;
const t = ((n = e.content[0]) == null ? void 0 : n.text) || "";
return new l({
style: "Codeblock",
shading: {
type: w.SOLID,
fill: "161616",
color: "161616"
},
children: [
...t.split(`
`).map((r, o) => new p({
text: r,
break: o > 0 ? 1 : 0
}))
]
});
},
pageBreak: () => new l({
children: [new _()]
}),
column: (e, t, n, r, o) => new g({
width: {
size: `${e.props.width * 100}%`,
type: "pct"
},
children: (o || []).flatMap((i) => Array.isArray(i) ? i : [i])
}),
columnList: (e, t, n, r, o) => new D({
layout: "autofit",
borders: {
bottom: { style: "nil" },
top: { style: "nil" },
left: { style: "nil" },
right: { style: "nil" },
insideHorizontal: { style: "nil" },
insideVertical: { style: "nil" }
},
rows: [
new I({
children: o.map(
(i, u, c) => {
var d;
return new g({
width: {
size: `${parseFloat(`${((d = i.options.width) == null ? void 0 : d.size) || "100%"}`) / (c.length * 100) * 100}%`,
type: "pct"
},
children: i.options.children
});
}
)
})
]
}),
image: async (e, t) => {
const n = await t.resolveFile(e.props.url), { width: r, height: o } = await H(n);
return [
new l({
...a(e.props, t.options.colors),
children: [
new S({
data: await n.arrayBuffer(),
// it would be nicer to set the actual data type here, but then we'd need to use a mime type / image type
// detector. atm passing gif does not seem to be causing issues as the "type" is mainly used by docxjs internally
// (i.e.: to make sure it's not svg)
type: "gif",
altText: e.props.caption ? {
description: e.props.caption,
name: e.props.caption,
title: e.props.caption
} : void 0,
transformation: {
width: e.props.previewWidth || r,
height: (e.props.previewWidth || r) / r * o
}
})
]
}),
...f(e.props, t)
];
},
table: (e, t) => W(e.content, t)
};
function m(e, t, n) {
return new l({
...a(e, n.options.colors),
children: [
new L({
children: [
new p({
text: e.name || t,
style: "Hyperlink"
})
],
link: e.url
})
]
});
}
function f(e, t) {
return e.caption ? [
new l({
...a(e, t.options.colors),
children: [
new p({
text: e.caption
})
],
style: "Caption"
})
] : [];
}
const z = {
link: (e, t) => new L({
children: e.content.map((n) => t.transformStyledText(
n,
!0
)),
link: e.href
}),
text: (e, t) => t.transformStyledText(e)
}, P = {
bold: (e) => e ? {
bold: e
} : {},
italic: (e) => e ? {
italics: e
} : {},
underline: (e) => e ? {
underline: {
type: "single"
}
} : {},
strike: (e) => e ? {
strike: e
} : {},
backgroundColor: (e, t) => e ? {
shading: {
fill: t.options.colors[e].background.slice(1)
}
} : {},
textColor: (e, t) => e ? {
color: t.options.colors[e].text.slice(1)
} : {},
code: (e) => e ? {
font: "GeistMono"
} : {}
}, J = {
blockMapping: $,
inlineContentMapping: z,
styleMapping: P
};
async function j(e) {
return "https://corsproxy.api.blocknotejs.org/corsproxy/?url=" + encodeURIComponent(e);
}
async function B(e) {
{
const t = e.default;
return await (await fetch(t)).arrayBuffer();
}
}
const h = (
/* default font size */
16 * /* 1 pixel is 0.75 points */
0.75 * /* 1.5em*/
1.5 * /* 1 point is 20 twips */
20
);
class q extends v {
constructor(t, n, r) {
const i = {
...{
colors: F,
resolveFileUrl: j
},
...r
};
super(t, n, i), this.schema = t, this.mappings = n;
}
/**
* Mostly for internal use, you probably want to use `toBlob` or `toDocxJsDocument` instead.
*/
transformStyledText(t, n) {
const r = this.mapStyles(t.styles), o = Object.assign(
{},
...r
);
return new p({
...o,
style: n ? "Hyperlink" : void 0,
text: t.text
});
}
/**
* Mostly for internal use, you probably want to use `toBlob` or `toDocxJsDocument` instead.
*/
async transformBlocks(t, n = 0) {
const r = [];
for (const o of t) {
let i = await this.transformBlocks(o.children, n + 1);
["columnList", "column"].includes(o.type) || (i = i.map((c, d) => (c instanceof l && !c.properties.numberingReferences.length && c.addRunToFront(
new p({
children: [new E()]
})
), c)));
const u = await this.mapBlock(
o,
n,
0,
i
);
["columnList", "column"].includes(o.type) ? r.push(u) : Array.isArray(u) ? r.push(...u, ...i) : r.push(u, ...i);
}
return r;
}
async getFonts() {
let t = await B(
await import("./Inter_18pt-Regular-byxnNS-8.js")
), n = await B(
await import("./GeistMono-Regular-D4rKXxwr.js")
);
if (t instanceof ArrayBuffer || n instanceof ArrayBuffer) {
const r = (await import("./index-cA_PmnZy.js").then((o) => o.i)).Buffer;
t instanceof ArrayBuffer && (t = r.from(t)), n instanceof ArrayBuffer && (n = r.from(n));
}
return [
{ name: "Inter", data: t },
{
name: "GeistMono",
data: n
}
];
}
async createDefaultDocumentOptions() {
const t = (await import("./styles-CujW8HHo.js")).default, n = ["•"];
return {
numbering: {
config: [
{
reference: "blocknote-numbered-list",
levels: Array.from({ length: 9 }, (r, o) => ({
start: 1,
level: o,
format: A.DECIMAL,
text: `%${o + 1}.`,
alignment: C.LEFT,
style: {
paragraph: {
indent: {
left: h * (o + 1),
hanging: h
}
}
}
}))
},
{
reference: "blocknote-bullet-list",
levels: Array.from({ length: 9 }, (r, o) => ({
start: 1,
level: o,
format: A.BULLET,
text: n[o % n.length],
alignment: C.LEFT,
style: {
paragraph: {
indent: {
left: h * (o + 1),
hanging: h
}
}
}
}))
}
]
},
fonts: await this.getFonts(),
defaultTabStop: 200,
externalStyles: t
};
}
/**
* Convert a document (array of Blocks to a Blob representing a .docx file)
*/
async toBlob(t, n = {
sectionOptions: {},
documentOptions: {}
}) {
const r = await this.toDocxJsDocument(t, n), o = globalThis.Buffer;
try {
return globalThis.Buffer || (globalThis.Buffer = (await import("buffer")).default.Buffer), U.toBlob(r);
} finally {
globalThis.Buffer = o;
}
}
/**
* Convert a document (array of Blocks to a docxjs Document)
*/
async toDocxJsDocument(t, n = {
sectionOptions: {},
documentOptions: {}
}) {
return new R({
...await this.createDefaultDocumentOptions(),
...n.documentOptions,
sections: [
{
children: await this.transformBlocks(t),
...n.sectionOptions
}
]
});
}
}
export {
q as DOCXExporter,
J as docxDefaultSchemaMappings
};
//# sourceMappingURL=blocknote-xl-docx-exporter.js.map