fix: Render Mermaid diagrams in HTML export, towards #6205
This commit is contained in:
@@ -32,6 +32,8 @@ type HTMLOptions = {
|
|||||||
includeTitle?: boolean;
|
includeTitle?: boolean;
|
||||||
/** Whether to include style tags in the generated HTML (defaults to true) */
|
/** Whether to include style tags in the generated HTML (defaults to true) */
|
||||||
includeStyles?: boolean;
|
includeStyles?: boolean;
|
||||||
|
/** Whether to include the Mermaid script in the generated HTML (defaults to false) */
|
||||||
|
includeMermaid?: boolean;
|
||||||
/** Whether to include styles to center diff (defaults to true) */
|
/** Whether to include styles to center diff (defaults to true) */
|
||||||
centered?: boolean;
|
centered?: boolean;
|
||||||
/**
|
/**
|
||||||
@@ -107,6 +109,7 @@ export default class DocumentHelper {
|
|||||||
let output = ProsemirrorHelper.toHTML(node, {
|
let output = ProsemirrorHelper.toHTML(node, {
|
||||||
title: options?.includeTitle !== false ? document.title : undefined,
|
title: options?.includeTitle !== false ? document.title : undefined,
|
||||||
includeStyles: options?.includeStyles,
|
includeStyles: options?.includeStyles,
|
||||||
|
includeMermaid: options?.includeMermaid,
|
||||||
centered: options?.centered,
|
centered: options?.centered,
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
@@ -19,6 +19,8 @@ export type HTMLOptions = {
|
|||||||
title?: string;
|
title?: string;
|
||||||
/** Whether to include style tags in the generated HTML (defaults to true) */
|
/** Whether to include style tags in the generated HTML (defaults to true) */
|
||||||
includeStyles?: boolean;
|
includeStyles?: boolean;
|
||||||
|
/** Whether to include mermaidjs scripts in the generated HTML (defaults to false) */
|
||||||
|
includeMermaid?: boolean;
|
||||||
/** Whether to include styles to center diff (defaults to true) */
|
/** Whether to include styles to center diff (defaults to true) */
|
||||||
centered?: boolean;
|
centered?: boolean;
|
||||||
};
|
};
|
||||||
@@ -144,7 +146,8 @@ export default class ProsemirrorHelper {
|
|||||||
*/
|
*/
|
||||||
static toHTML(node: Node, options?: HTMLOptions) {
|
static toHTML(node: Node, options?: HTMLOptions) {
|
||||||
const sheet = new ServerStyleSheet();
|
const sheet = new ServerStyleSheet();
|
||||||
let html, styleTags;
|
let html = "";
|
||||||
|
let styleTags = "";
|
||||||
|
|
||||||
const Centered = options?.centered
|
const Centered = options?.centered
|
||||||
? styled.article`
|
? styled.article`
|
||||||
@@ -160,7 +163,7 @@ export default class ProsemirrorHelper {
|
|||||||
<>
|
<>
|
||||||
{options?.title && <h1 dir={rtl ? "rtl" : "ltr"}>{options.title}</h1>}
|
{options?.title && <h1 dir={rtl ? "rtl" : "ltr"}>{options.title}</h1>}
|
||||||
{options?.includeStyles !== false ? (
|
{options?.includeStyles !== false ? (
|
||||||
<EditorContainer dir={rtl ? "rtl" : "ltr"} rtl={rtl}>
|
<EditorContainer dir={rtl ? "rtl" : "ltr"} rtl={rtl} staticHTML>
|
||||||
{content}
|
{content}
|
||||||
</EditorContainer>
|
</EditorContainer>
|
||||||
) : (
|
) : (
|
||||||
@@ -214,6 +217,40 @@ export default class ProsemirrorHelper {
|
|||||||
target
|
target
|
||||||
);
|
);
|
||||||
|
|
||||||
|
// Inject mermaidjs scripts if the document contains mermaid diagrams
|
||||||
|
if (options?.includeMermaid) {
|
||||||
|
const mermaidElements = dom.window.document.querySelectorAll(
|
||||||
|
`[data-language="mermaidjs"] pre code`
|
||||||
|
);
|
||||||
|
|
||||||
|
// Unwrap <pre> tags to enable Mermaid script to correctly render inner content
|
||||||
|
for (const el of mermaidElements) {
|
||||||
|
const parent = el.parentNode as HTMLElement;
|
||||||
|
if (parent) {
|
||||||
|
while (el.firstChild) {
|
||||||
|
parent.insertBefore(el.firstChild, el);
|
||||||
|
}
|
||||||
|
parent.removeChild(el);
|
||||||
|
parent.setAttribute("class", "mermaid");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Inject Mermaid script
|
||||||
|
if (mermaidElements.length) {
|
||||||
|
const element = dom.window.document.createElement("script");
|
||||||
|
element.setAttribute("type", "module");
|
||||||
|
element.innerHTML = `
|
||||||
|
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs';
|
||||||
|
mermaid.initialize({
|
||||||
|
startOnLoad: true,
|
||||||
|
fontFamily: "inherit",
|
||||||
|
});
|
||||||
|
window.status = "ready";
|
||||||
|
`;
|
||||||
|
dom.window.document.body.appendChild(element);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return dom.serialize();
|
return dom.serialize();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -527,6 +527,7 @@ router.post(
|
|||||||
content = await DocumentHelper.toHTML(document, {
|
content = await DocumentHelper.toHTML(document, {
|
||||||
signedUrls: true,
|
signedUrls: true,
|
||||||
centered: true,
|
centered: true,
|
||||||
|
includeMermaid: true,
|
||||||
});
|
});
|
||||||
} else if (accept?.includes("application/pdf")) {
|
} else if (accept?.includes("application/pdf")) {
|
||||||
throw IncorrectEditionError(
|
throw IncorrectEditionError(
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ export type Props = {
|
|||||||
rtl: boolean;
|
rtl: boolean;
|
||||||
readOnly?: boolean;
|
readOnly?: boolean;
|
||||||
readOnlyWriteCheckboxes?: boolean;
|
readOnlyWriteCheckboxes?: boolean;
|
||||||
|
staticHTML?: boolean;
|
||||||
editorStyle?: React.CSSProperties;
|
editorStyle?: React.CSSProperties;
|
||||||
grow?: boolean;
|
grow?: boolean;
|
||||||
theme: DefaultTheme;
|
theme: DefaultTheme;
|
||||||
@@ -260,6 +261,16 @@ const emailStyle = (props: Props) => css`
|
|||||||
}
|
}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
|
const printStyle = (props: Props) => css`
|
||||||
|
${props.staticHTML &&
|
||||||
|
`
|
||||||
|
body {
|
||||||
|
height: auto;
|
||||||
|
min-height: 0;
|
||||||
|
}
|
||||||
|
`}
|
||||||
|
`;
|
||||||
|
|
||||||
const style = (props: Props) => `
|
const style = (props: Props) => `
|
||||||
flex-grow: ${props.grow ? 1 : 0};
|
flex-grow: ${props.grow ? 1 : 0};
|
||||||
justify-content: start;
|
justify-content: start;
|
||||||
@@ -1128,7 +1139,7 @@ mark {
|
|||||||
|
|
||||||
/* Hide code without display none so toolbar can still be positioned against it */
|
/* Hide code without display none so toolbar can still be positioned against it */
|
||||||
&:not(.code-active) {
|
&:not(.code-active) {
|
||||||
height: 0;
|
height: ${props.staticHTML ? "auto" : "0"};
|
||||||
margin: -0.5em 0;
|
margin: -0.5em 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -1136,7 +1147,7 @@ mark {
|
|||||||
|
|
||||||
/* Hide code without display none so toolbar can still be positioned against it */
|
/* Hide code without display none so toolbar can still be positioned against it */
|
||||||
.ProseMirror[contenteditable="false"] .code-block[data-language=mermaidjs] {
|
.ProseMirror[contenteditable="false"] .code-block[data-language=mermaidjs] {
|
||||||
height: 0;
|
height: ${props.staticHTML ? "auto" : "0"};
|
||||||
margin: -0.5em 0;
|
margin: -0.5em 0;
|
||||||
overflow: hidden;
|
overflow: hidden;
|
||||||
}
|
}
|
||||||
@@ -1559,6 +1570,7 @@ const EditorContainer = styled.div<Props>`
|
|||||||
${codeBlockStyle}
|
${codeBlockStyle}
|
||||||
${findAndReplaceStyle}
|
${findAndReplaceStyle}
|
||||||
${emailStyle}
|
${emailStyle}
|
||||||
|
${printStyle}
|
||||||
`;
|
`;
|
||||||
|
|
||||||
export default EditorContainer;
|
export default EditorContainer;
|
||||||
|
|||||||
Reference in New Issue
Block a user