diff --git a/server/models/helpers/DocumentHelper.tsx b/server/models/helpers/DocumentHelper.tsx index 3aca06544..91a0e422c 100644 --- a/server/models/helpers/DocumentHelper.tsx +++ b/server/models/helpers/DocumentHelper.tsx @@ -32,6 +32,8 @@ type HTMLOptions = { includeTitle?: boolean; /** Whether to include style tags in the generated HTML (defaults to true) */ 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) */ centered?: boolean; /** @@ -107,6 +109,7 @@ export default class DocumentHelper { let output = ProsemirrorHelper.toHTML(node, { title: options?.includeTitle !== false ? document.title : undefined, includeStyles: options?.includeStyles, + includeMermaid: options?.includeMermaid, centered: options?.centered, }); diff --git a/server/models/helpers/ProsemirrorHelper.tsx b/server/models/helpers/ProsemirrorHelper.tsx index a60969c9d..4d3793137 100644 --- a/server/models/helpers/ProsemirrorHelper.tsx +++ b/server/models/helpers/ProsemirrorHelper.tsx @@ -19,6 +19,8 @@ export type HTMLOptions = { title?: string; /** Whether to include style tags in the generated HTML (defaults to true) */ 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) */ centered?: boolean; }; @@ -144,7 +146,8 @@ export default class ProsemirrorHelper { */ static toHTML(node: Node, options?: HTMLOptions) { const sheet = new ServerStyleSheet(); - let html, styleTags; + let html = ""; + let styleTags = ""; const Centered = options?.centered ? styled.article` @@ -160,7 +163,7 @@ export default class ProsemirrorHelper { <> {options?.title &&

{options.title}

} {options?.includeStyles !== false ? ( - + {content} ) : ( @@ -214,6 +217,40 @@ export default class ProsemirrorHelper { 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
 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();
   }
 }
diff --git a/server/routes/api/documents/documents.ts b/server/routes/api/documents/documents.ts
index 051670c45..8654c3b54 100644
--- a/server/routes/api/documents/documents.ts
+++ b/server/routes/api/documents/documents.ts
@@ -527,6 +527,7 @@ router.post(
       content = await DocumentHelper.toHTML(document, {
         signedUrls: true,
         centered: true,
+        includeMermaid: true,
       });
     } else if (accept?.includes("application/pdf")) {
       throw IncorrectEditionError(
diff --git a/shared/editor/components/Styles.ts b/shared/editor/components/Styles.ts
index aa2582a26..4ec253b83 100644
--- a/shared/editor/components/Styles.ts
+++ b/shared/editor/components/Styles.ts
@@ -6,6 +6,7 @@ export type Props = {
   rtl: boolean;
   readOnly?: boolean;
   readOnlyWriteCheckboxes?: boolean;
+  staticHTML?: boolean;
   editorStyle?: React.CSSProperties;
   grow?: boolean;
   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) => `
 flex-grow: ${props.grow ? 1 : 0};
 justify-content: start;
@@ -1128,7 +1139,7 @@ mark {
 
   /* Hide code without display none so toolbar can still be positioned against it */
   &:not(.code-active) {
-    height: 0;
+    height: ${props.staticHTML ? "auto" : "0"};
     margin: -0.5em 0;
     overflow: hidden;
   }
@@ -1136,7 +1147,7 @@ mark {
 
 /* Hide code without display none so toolbar can still be positioned against it */
 .ProseMirror[contenteditable="false"] .code-block[data-language=mermaidjs] {
-  height: 0;
+  height: ${props.staticHTML ? "auto" : "0"};
   margin: -0.5em 0;
   overflow: hidden;
 }
@@ -1559,6 +1570,7 @@ const EditorContainer = styled.div`
   ${codeBlockStyle}
   ${findAndReplaceStyle}
   ${emailStyle}
+  ${printStyle}
 `;
 
 export default EditorContainer;