Merge pull request #4359 from rhysd/bug/4358_suppress_error_rendering
Add `suppressErrorRendering` option to avoid inserting 'Syntax error' message to DOM directly
This commit is contained in:
commit
78587e11c7
|
@ -125,4 +125,46 @@ describe('Configuration', () => {
|
|||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('suppressErrorRendering', () => {
|
||||
beforeEach(() => {
|
||||
cy.on('uncaught:exception', (err, runnable) => {
|
||||
return !err.message.includes('Parse error on line');
|
||||
});
|
||||
});
|
||||
|
||||
it('should not render error diagram if suppressErrorRendering is set', () => {
|
||||
const url = 'http://localhost:9000/suppressError.html?suppressErrorRendering=true';
|
||||
cy.visit(url);
|
||||
cy.window().should('have.property', 'rendered', true);
|
||||
cy.get('#test')
|
||||
.find('svg')
|
||||
.should(($svg) => {
|
||||
// all failing diagrams should not appear!
|
||||
expect($svg).to.have.length(2);
|
||||
// none of the diagrams should be error diagrams
|
||||
expect($svg).to.not.contain('Syntax error');
|
||||
});
|
||||
cy.matchImageSnapshot(
|
||||
'configuration.spec-should-not-render-error-diagram-if-suppressErrorRendering-is-set'
|
||||
);
|
||||
});
|
||||
|
||||
it('should render error diagram if suppressErrorRendering is not set', () => {
|
||||
const url = 'http://localhost:9000/suppressError.html';
|
||||
cy.visit(url);
|
||||
cy.window().should('have.property', 'rendered', true);
|
||||
cy.get('#test')
|
||||
.find('svg')
|
||||
.should(($svg) => {
|
||||
// all five diagrams should be rendered
|
||||
expect($svg).to.have.length(5);
|
||||
// some of the diagrams should be error diagrams
|
||||
expect($svg).to.contain('Syntax error');
|
||||
});
|
||||
cy.matchImageSnapshot(
|
||||
'configuration.spec-should-render-error-diagram-if-suppressErrorRendering-is-not-set'
|
||||
);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
|
|
@ -0,0 +1,59 @@
|
|||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Mermaid Quick Test Page</title>
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||
</head>
|
||||
<body>
|
||||
<div id="test">
|
||||
<pre class="mermaid">
|
||||
flowchart
|
||||
a[This should be visible]
|
||||
</pre
|
||||
>
|
||||
<pre class="mermaid">
|
||||
flowchart
|
||||
a --< b
|
||||
</pre
|
||||
>
|
||||
<pre class="mermaid">
|
||||
flowchart
|
||||
a[This should be visible]
|
||||
</pre
|
||||
>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
suppressErrorRendering: true # This should not affect anything, as suppressErrorRendering is a secure config
|
||||
---
|
||||
flowchart
|
||||
a --< b
|
||||
</pre
|
||||
>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
suppressErrorRendering: false # This should not affect anything, as suppressErrorRendering is a secure config
|
||||
---
|
||||
flowchart
|
||||
a --< b
|
||||
</pre
|
||||
>
|
||||
</div>
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
const shouldSuppress =
|
||||
new URLSearchParams(window.location.search).get('suppressErrorRendering') === 'true';
|
||||
mermaid.initialize({ startOnLoad: false, suppressErrorRendering: shouldSuppress });
|
||||
try {
|
||||
await mermaid.run();
|
||||
} catch {
|
||||
if (window.Cypress) {
|
||||
window.rendered = true;
|
||||
}
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
|
@ -43,6 +43,7 @@ const config = {
|
|||
securityLevel: 'strict',
|
||||
startOnLoad: true,
|
||||
arrowMarkerAbsolute: false,
|
||||
suppressErrorRendering: false,
|
||||
|
||||
er: {
|
||||
diagramPadding: 20,
|
||||
|
@ -97,7 +98,7 @@ mermaid.initialize(config);
|
|||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:622](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L622)
|
||||
[mermaidAPI.ts:635](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L635)
|
||||
|
||||
## Functions
|
||||
|
||||
|
|
|
@ -159,6 +159,12 @@ export interface MermaidConfig {
|
|||
dompurifyConfig?: DOMPurifyConfiguration;
|
||||
wrap?: boolean;
|
||||
fontSize?: number;
|
||||
/**
|
||||
* Suppresses inserting 'Syntax error' diagram in the DOM.
|
||||
* This is useful when you want to control how to handle syntax errors in your application.
|
||||
*
|
||||
*/
|
||||
suppressErrorRendering?: boolean;
|
||||
}
|
||||
/**
|
||||
* The object containing configurations specific for packet diagrams.
|
||||
|
|
|
@ -110,7 +110,7 @@ function processAndSetConfigs(text: string) {
|
|||
*/
|
||||
async function parse(
|
||||
text: string,
|
||||
parseOptions: ParseOptions & { suppressErrors: true }
|
||||
parseOptions: ParseOptions & { suppressErrors: true },
|
||||
): Promise<ParseResult | false>;
|
||||
async function parse(text: string, parseOptions?: ParseOptions): Promise<ParseResult>;
|
||||
async function parse(text: string, parseOptions?: ParseOptions): Promise<ParseResult | false> {
|
||||
|
@ -138,7 +138,7 @@ async function parse(text: string, parseOptions?: ParseOptions): Promise<ParseRe
|
|||
export const cssImportantStyles = (
|
||||
cssClass: string,
|
||||
element: string,
|
||||
cssClasses: string[] = []
|
||||
cssClasses: string[] = [],
|
||||
): string => {
|
||||
return `\n.${cssClass} ${element} { ${cssClasses.join(' !important; ')} !important; }`;
|
||||
};
|
||||
|
@ -152,7 +152,7 @@ export const cssImportantStyles = (
|
|||
*/
|
||||
export const createCssStyles = (
|
||||
config: MermaidConfig,
|
||||
classDefs: Record<string, DiagramStyleClassDef> | null | undefined = {}
|
||||
classDefs: Record<string, DiagramStyleClassDef> | null | undefined = {},
|
||||
): string => {
|
||||
let cssStyles = '';
|
||||
|
||||
|
@ -201,7 +201,7 @@ export const createUserStyles = (
|
|||
config: MermaidConfig,
|
||||
graphType: string,
|
||||
classDefs: Record<string, DiagramStyleClassDef> | undefined,
|
||||
svgId: string
|
||||
svgId: string,
|
||||
): string => {
|
||||
const userCSSstyles = createCssStyles(config, classDefs);
|
||||
const allStyles = getStyles(graphType, userCSSstyles, config.themeVariables);
|
||||
|
@ -223,7 +223,7 @@ export const createUserStyles = (
|
|||
export const cleanUpSvgCode = (
|
||||
svgCode = '',
|
||||
inSandboxMode: boolean,
|
||||
useArrowMarkerUrls: boolean
|
||||
useArrowMarkerUrls: boolean,
|
||||
): string => {
|
||||
let cleanedUpSvg = svgCode;
|
||||
|
||||
|
@ -231,7 +231,7 @@ export const cleanUpSvgCode = (
|
|||
if (!useArrowMarkerUrls && !inSandboxMode) {
|
||||
cleanedUpSvg = cleanedUpSvg.replace(
|
||||
/marker-end="url\([\d+./:=?A-Za-z-]*?#/g,
|
||||
'marker-end="url(#'
|
||||
'marker-end="url(#',
|
||||
);
|
||||
}
|
||||
|
||||
|
@ -279,7 +279,7 @@ export const appendDivSvgG = (
|
|||
id: string,
|
||||
enclosingDivId: string,
|
||||
divStyle?: string,
|
||||
svgXlink?: string
|
||||
svgXlink?: string,
|
||||
): D3Element => {
|
||||
const enclosingDiv = parentRoot.append('div');
|
||||
enclosingDiv.attr('id', enclosingDivId);
|
||||
|
@ -328,7 +328,7 @@ export const removeExistingElements = (
|
|||
doc: Document,
|
||||
id: string,
|
||||
divId: string,
|
||||
iFrameId: string
|
||||
iFrameId: string,
|
||||
) => {
|
||||
// Remove existing SVG element if it exists
|
||||
doc.getElementById(id)?.remove();
|
||||
|
@ -347,7 +347,7 @@ export const removeExistingElements = (
|
|||
const render = async function (
|
||||
id: string,
|
||||
text: string,
|
||||
svgContainingElement?: Element
|
||||
svgContainingElement?: Element,
|
||||
): Promise<RenderResult> {
|
||||
addDiagrams();
|
||||
|
||||
|
@ -368,6 +368,16 @@ const render = async function (
|
|||
const enclosingDivID = 'd' + id;
|
||||
const enclosingDivID_selector = '#' + enclosingDivID;
|
||||
|
||||
const removeTempElements = () => {
|
||||
// -------------------------------------------------------------------------------
|
||||
// Remove the temporary HTML element if appropriate
|
||||
const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector;
|
||||
const node = select(tmpElementSelector).node();
|
||||
if (node && 'remove' in node) {
|
||||
node.remove();
|
||||
}
|
||||
};
|
||||
|
||||
let root: any = select('body');
|
||||
|
||||
const isSandboxed = config.securityLevel === SECURITY_LVL_SANDBOX;
|
||||
|
@ -424,6 +434,10 @@ const render = async function (
|
|||
try {
|
||||
diag = await Diagram.fromText(text, { title: processed.title });
|
||||
} catch (error) {
|
||||
if (config.suppressErrorRendering) {
|
||||
removeTempElements();
|
||||
throw error;
|
||||
}
|
||||
diag = await Diagram.fromText('error');
|
||||
parseEncounteredException = error;
|
||||
}
|
||||
|
@ -451,7 +465,11 @@ const render = async function (
|
|||
try {
|
||||
await diag.renderer.draw(text, id, version, diag);
|
||||
} catch (e) {
|
||||
errorRenderer.draw(text, id, version);
|
||||
if (config.suppressErrorRendering) {
|
||||
removeTempElements();
|
||||
} else {
|
||||
errorRenderer.draw(text, id, version);
|
||||
}
|
||||
throw e;
|
||||
}
|
||||
|
||||
|
@ -487,13 +505,7 @@ const render = async function (
|
|||
throw parseEncounteredException;
|
||||
}
|
||||
|
||||
// -------------------------------------------------------------------------------
|
||||
// Remove the temporary HTML element if appropriate
|
||||
const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector;
|
||||
const node = select(tmpElementSelector).node();
|
||||
if (node && 'remove' in node) {
|
||||
node.remove();
|
||||
}
|
||||
removeTempElements();
|
||||
|
||||
return {
|
||||
diagramType,
|
||||
|
@ -520,7 +532,7 @@ function initialize(options: MermaidConfig = {}) {
|
|||
if (options?.theme && options.theme in theme) {
|
||||
// Todo merge with user options
|
||||
options.themeVariables = theme[options.theme as keyof typeof theme].getThemeVariables(
|
||||
options.themeVariables
|
||||
options.themeVariables,
|
||||
);
|
||||
} else if (options) {
|
||||
options.themeVariables = theme.default.getThemeVariables(options.themeVariables);
|
||||
|
@ -550,7 +562,7 @@ function addA11yInfo(
|
|||
diagramType: string,
|
||||
svgNode: D3Element,
|
||||
a11yTitle?: string,
|
||||
a11yDescr?: string
|
||||
a11yDescr?: string,
|
||||
): void {
|
||||
setA11yDiagramInfo(svgNode, diagramType);
|
||||
addSVGa11yTitleDescription(svgNode, a11yTitle, a11yDescr, svgNode.attr('id'));
|
||||
|
@ -566,6 +578,7 @@ function addA11yInfo(
|
|||
* securityLevel: 'strict',
|
||||
* startOnLoad: true,
|
||||
* arrowMarkerAbsolute: false,
|
||||
* suppressErrorRendering: false,
|
||||
*
|
||||
* er: {
|
||||
* diagramPadding: 20,
|
||||
|
|
|
@ -159,7 +159,15 @@ properties:
|
|||
in the current `currentConfig`.
|
||||
|
||||
This prevents malicious graph directives from overriding a site's default security.
|
||||
default: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize', 'maxEdges']
|
||||
default:
|
||||
[
|
||||
'secure',
|
||||
'securityLevel',
|
||||
'startOnLoad',
|
||||
'maxTextSize',
|
||||
'suppressErrorRendering',
|
||||
'maxEdges',
|
||||
]
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
|
@ -235,6 +243,12 @@ properties:
|
|||
fontSize:
|
||||
type: number
|
||||
default: 16
|
||||
suppressErrorRendering:
|
||||
type: boolean
|
||||
default: false
|
||||
description: |
|
||||
Suppresses inserting 'Syntax error' diagram in the DOM.
|
||||
This is useful when you want to control how to handle syntax errors in your application.
|
||||
|
||||
$defs: # JSON Schema definition (maybe we should move these to a separate file)
|
||||
BaseDiagramConfig:
|
||||
|
|
Loading…
Reference in New Issue