Various Mermaid improvements (#18776) (#18780)

* Various Mermaid improvments

- Render into iframe for improved security
- Use built-in dark theme instead of color inversion
- Remove flexbox attributes, resulting in more consistent size rendering
- Update API usage and update to latest version

* restart ci

* misc tweaks

* remove unneccesary declaration

* make it work without allow-same-origin, add loading=lazy

* remove loading attribute, does not seem to work

* rename variable

* skip roundtrip to DOM for rendering

* don't guess chart height

* update comment to make it clear it's intentional

* tweak

* replace deprecated 'scrolling' property

* remove unused css file

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>

Co-authored-by: Lunny Xiao <xiaolunwen@gmail.com>
This commit is contained in:
silverwind 2022-02-16 10:26:53 +01:00 committed by GitHub
parent c876124efe
commit 1f7802db97
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 50 additions and 50 deletions

30
package-lock.json generated
View file

@ -23,7 +23,7 @@
"less": "4.1.2", "less": "4.1.2",
"less-loader": "10.2.0", "less-loader": "10.2.0",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"mermaid": "8.13.10", "mermaid": "8.14.0",
"mini-css-extract-plugin": "2.5.2", "mini-css-extract-plugin": "2.5.2",
"monaco-editor": "0.31.1", "monaco-editor": "0.31.1",
"monaco-editor-webpack-plugin": "7.0.1", "monaco-editor-webpack-plugin": "7.0.1",
@ -3435,9 +3435,9 @@
} }
}, },
"node_modules/dompurify": { "node_modules/dompurify": {
"version": "2.3.4", "version": "2.3.5",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.4.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.5.tgz",
"integrity": "sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ==" "integrity": "sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ=="
}, },
"node_modules/domutils": { "node_modules/domutils": {
"version": "2.8.0", "version": "2.8.0",
@ -6797,15 +6797,15 @@
} }
}, },
"node_modules/mermaid": { "node_modules/mermaid": {
"version": "8.13.10", "version": "8.14.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.13.10.tgz", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.14.0.tgz",
"integrity": "sha512-2ANep359uML87+wiYaWSu83eg9Qc0xCLnNJdCh100m4v0orS3fp8SScsZLcDSElRGHi+1zuVJsEEVEWH05+COQ==", "integrity": "sha512-ITSHjwVaby1Li738sxhF48sLTxcNyUAoWfoqyztL1f7J6JOLpHOuQPNLBb6lxGPUA0u7xP9IRULgvod0dKu35A==",
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^3.1.0", "@braintree/sanitize-url": "^3.1.0",
"d3": "^7.0.0", "d3": "^7.0.0",
"dagre": "^0.8.5", "dagre": "^0.8.5",
"dagre-d3": "^0.6.4", "dagre-d3": "^0.6.4",
"dompurify": "2.3.4", "dompurify": "2.3.5",
"graphlib": "^2.1.8", "graphlib": "^2.1.8",
"khroma": "^1.4.1", "khroma": "^1.4.1",
"moment-mini": "^2.24.0", "moment-mini": "^2.24.0",
@ -12547,9 +12547,9 @@
} }
}, },
"dompurify": { "dompurify": {
"version": "2.3.4", "version": "2.3.5",
"resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.4.tgz", "resolved": "https://registry.npmjs.org/dompurify/-/dompurify-2.3.5.tgz",
"integrity": "sha512-6BVcgOAVFXjI0JTjEvZy901Rghm+7fDQOrNIcxB4+gdhj6Kwp6T9VBhBY/AbagKHJocRkDYGd6wvI+p4/10xtQ==" "integrity": "sha512-kD+f8qEaa42+mjdOpKeztu9Mfx5bv9gVLO6K9jRx4uGvh6Wv06Srn4jr1wPNY2OOUGGSKHNFN+A8MA3v0E0QAQ=="
}, },
"domutils": { "domutils": {
"version": "2.8.0", "version": "2.8.0",
@ -15033,15 +15033,15 @@
"integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==" "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg=="
}, },
"mermaid": { "mermaid": {
"version": "8.13.10", "version": "8.14.0",
"resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.13.10.tgz", "resolved": "https://registry.npmjs.org/mermaid/-/mermaid-8.14.0.tgz",
"integrity": "sha512-2ANep359uML87+wiYaWSu83eg9Qc0xCLnNJdCh100m4v0orS3fp8SScsZLcDSElRGHi+1zuVJsEEVEWH05+COQ==", "integrity": "sha512-ITSHjwVaby1Li738sxhF48sLTxcNyUAoWfoqyztL1f7J6JOLpHOuQPNLBb6lxGPUA0u7xP9IRULgvod0dKu35A==",
"requires": { "requires": {
"@braintree/sanitize-url": "^3.1.0", "@braintree/sanitize-url": "^3.1.0",
"d3": "^7.0.0", "d3": "^7.0.0",
"dagre": "^0.8.5", "dagre": "^0.8.5",
"dagre-d3": "^0.6.4", "dagre-d3": "^0.6.4",
"dompurify": "2.3.4", "dompurify": "2.3.5",
"graphlib": "^2.1.8", "graphlib": "^2.1.8",
"khroma": "^1.4.1", "khroma": "^1.4.1",
"moment-mini": "^2.24.0", "moment-mini": "^2.24.0",

View file

@ -23,7 +23,7 @@
"less": "4.1.2", "less": "4.1.2",
"less-loader": "10.2.0", "less-loader": "10.2.0",
"license-checker-webpack-plugin": "0.2.1", "license-checker-webpack-plugin": "0.2.1",
"mermaid": "8.13.10", "mermaid": "8.14.0",
"mini-css-extract-plugin": "2.5.2", "mini-css-extract-plugin": "2.5.2",
"monaco-editor": "0.31.1", "monaco-editor": "0.31.1",
"monaco-editor-webpack-plugin": "7.0.1", "monaco-editor-webpack-plugin": "7.0.1",

View file

@ -1,5 +1,11 @@
import {isDarkTheme} from '../utils.js';
const {mermaidMaxSourceCharacters} = window.config; const {mermaidMaxSourceCharacters} = window.config;
const iframeCss = `
body {margin: 0; padding: 0}
#mermaid {display: block; margin: 0 auto}
`;
function displayError(el, err) { function displayError(el, err) {
el.closest('pre').classList.remove('is-loading'); el.closest('pre').classList.remove('is-loading');
const errorNode = document.createElement('div'); const errorNode = document.createElement('div');
@ -15,26 +21,22 @@ export async function renderMermaid() {
const {default: mermaid} = await import(/* webpackChunkName: "mermaid" */'mermaid'); const {default: mermaid} = await import(/* webpackChunkName: "mermaid" */'mermaid');
mermaid.initialize({ mermaid.initialize({
mermaid: {
startOnLoad: false, startOnLoad: false,
}, theme: isDarkTheme() ? 'dark' : 'neutral',
flowchart: {
useMaxWidth: true,
htmlLabels: false,
},
theme: 'neutral',
securityLevel: 'strict', securityLevel: 'strict',
}); });
for (const el of els) { for (const el of els) {
if (mermaidMaxSourceCharacters >= 0 && el.textContent.length > mermaidMaxSourceCharacters) { const source = el.textContent;
displayError(el, new Error(`Mermaid source of ${el.textContent.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`));
if (mermaidMaxSourceCharacters >= 0 && source.length > mermaidMaxSourceCharacters) {
displayError(el, new Error(`Mermaid source of ${source.length} characters exceeds the maximum allowed length of ${mermaidMaxSourceCharacters}.`));
continue; continue;
} }
let valid; let valid;
try { try {
valid = mermaid.parse(el.textContent); valid = mermaid.parse(source);
} catch (err) { } catch (err) {
displayError(el, err); displayError(el, err);
} }
@ -45,10 +47,17 @@ export async function renderMermaid() {
} }
try { try {
mermaid.init(undefined, el, (id) => { // can't use bindFunctions here because we can't cross the iframe boundary. This
const svg = document.getElementById(id); // means js-based interactions won't work but they aren't intended to work either
svg.classList.add('mermaid-chart'); mermaid.mermaidAPI.render('mermaid', source, (svgStr) => {
svg.closest('pre').replaceWith(svg); const heightStr = (svgStr.match(/height="(.+?)"/) || [])[1];
if (!heightStr) return displayError(el, new Error('Could not determine chart height'));
const iframe = document.createElement('iframe');
iframe.classList.add('markup-render');
iframe.sandbox = 'allow-scripts';
iframe.style.height = `${Math.ceil(parseFloat(heightStr))}px`;
iframe.srcdoc = `<html><head><style>${iframeCss}</style></head><body>${svgStr}</body></html>`;
el.closest('pre').replaceWith(iframe);
}); });
} catch (err) { } catch (err) {
displayError(el, err); displayError(el, err);

View file

@ -6,6 +6,7 @@
/* other variables */ /* other variables */
--border-radius: .28571429rem; --border-radius: .28571429rem;
--opacity-disabled: .55; --opacity-disabled: .55;
--height-loading: 12rem;
--color-primary: #4183c4; --color-primary: #4183c4;
--color-primary-dark-1: #3876b3; --color-primary-dark-1: #3876b3;
--color-primary-dark-2: #31699f; --color-primary-dark-2: #31699f;

View file

@ -30,7 +30,7 @@
.markup pre.is-loading, .markup pre.is-loading,
.editor-loading.is-loading { .editor-loading.is-loading {
height: 12rem; height: var(--height-loading);
} }
@keyframes fadein { @keyframes fadein {

View file

@ -10,7 +10,6 @@
@import "./features/codeeditor.less"; @import "./features/codeeditor.less";
@import "./features/projects.less"; @import "./features/projects.less";
@import "./markup/content.less"; @import "./markup/content.less";
@import "./markup/mermaid.less";
@import "./markup/codecopy.less"; @import "./markup/codecopy.less";
@import "./code/linebutton.less"; @import "./code/linebutton.less";

View file

@ -536,6 +536,14 @@
} }
} }
.markup-render {
display: block;
border: none;
width: 100%;
height: var(--height-loading); // actual height is set in JS after loading
overflow: hidden;
}
.markup-block-error { .markup-block-error {
margin-bottom: 0 !important; margin-bottom: 0 !important;
border-bottom-left-radius: 0 !important; border-bottom-left-radius: 0 !important;

View file

@ -1,13 +0,0 @@
.mermaid-chart {
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
margin: 1rem auto;
height: auto;
}
/* mermaid's errorRenderer seems to unavoidably spew stuff into <body>, hide it */
body > div[id*="mermaid-"] {
display: none !important;
}

View file

@ -455,10 +455,6 @@ img[src$="/img/matrix.svg"] {
filter: invert(80%); filter: invert(80%);
} }
.mermaid-chart {
filter: invert(84%) hue-rotate(180deg);
}
.is-loading::after { .is-loading::after {
border-color: #4a4c58 #4a4c58 #d7d7da #d7d7da; border-color: #4a4c58 #4a4c58 #d7d7da #d7d7da;
} }