fix: Render Mermaid diagrams in HTML export, towards #6205

This commit is contained in:
Tom Moor
2023-11-23 09:05:40 -05:00
parent ea8ebc3b2a
commit 8b68ee404a
4 changed files with 57 additions and 4 deletions

View File

@@ -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,
}); });

View File

@@ -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();
} }
} }

View File

@@ -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(

View File

@@ -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;