Merge branch 'master' into develop

* master: (23 commits)
  Update vitepress
  fix: Add icon css
  fix Top level await
  v9.3.0
  bump dagre-es 7.0.6
  Bump mermaid version
  Update dagre-es
  Bump mermaid version
  fix: Incorrect removal of existing elements
  fix: add .js to external imports.
  fix: add .js to external imports.
  Bump mermaid version
  fix: add .js to external imports.
  Bump mermaid version
  fix: Throw correct errors when parsing diagrams with errors
  Update url snapshot test for external diagrams
  Update url snapshot test for external diagrams
  Updated package number
  Updated package number
  Updated package number to 9.3.0-rc1
  ...
This commit is contained in:
Sidharth Vinod
2022-12-16 00:51:16 +05:30
36 changed files with 578 additions and 488 deletions

View File

@@ -1,7 +1,7 @@
{
"name": "@mermaid-js/mermaid-mindmap",
"version": "9.2.2",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"version": "9.3.0",
"description": "Mindmap diagram module for MermaidJS.",
"module": "dist/mermaid-mindmap.core.mjs",
"types": "dist/detector.d.ts",
"type": "module",

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "9.2.3-rc.1",
"version": "9.3.0",
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "./dist/mermaid.min.js",
"module": "./dist/mermaid.core.mjs",
@@ -54,7 +54,7 @@
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"d3": "^7.0.0",
"dagre-d3-es": "7.0.4",
"dagre-d3-es": "7.0.6",
"dompurify": "2.4.1",
"khroma": "^2.0.0",
"lodash-es": "^4.17.21",
@@ -93,8 +93,8 @@
"typedoc-plugin-markdown": "^3.13.6",
"typescript": "^4.8.4",
"unist-util-flatmap": "^1.0.0",
"vitepress": "^1.0.0-alpha.28",
"vitepress-plugin-search": "^1.0.4-alpha.15"
"vitepress": "^1.0.0-alpha.31",
"vitepress-plugin-search": "^1.0.4-alpha.16"
},
"files": [
"dist",

View File

@@ -102,7 +102,6 @@ export const getDiagramFromText = (
try {
// Trying to find the diagram
getDiagram(type);
return new Diagram(txt, parseError);
} catch (error) {
const loader = getDiagramLoader(type);
if (!loader) {
@@ -118,6 +117,7 @@ export const getDiagramFromText = (
return new Diagram(txt, parseError);
});
}
return new Diagram(txt, parseError);
};
export default Diagram;

View File

@@ -7,7 +7,7 @@
*/
import { D3Element } from './mermaidAPI';
import isEmpty from 'lodash-es/isEmpty';
import isEmpty from 'lodash-es/isEmpty.js';
/**
* SVG element role:

View File

@@ -1,5 +1,5 @@
import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js';
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json';
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
import insertMarkers from './markers';
import { updateNodeBounds } from './shapes/util';
import {

View File

@@ -1,7 +1,7 @@
/** Decorates with functions required by mermaids dagre-wrapper. */
import { log } from '../logger';
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json';
import * as graphlib from 'dagre-d3-es/src/graphlib';
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
export let clusterDb = {};
let descendants = {};

View File

@@ -1,5 +1,5 @@
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json';
import * as graphlib from 'dagre-d3-es/src/graphlib';
import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import {
validate,
adjustClustersAndEdges,

View File

@@ -0,0 +1,67 @@
import { describe, test, expect } from 'vitest';
import Diagram, { getDiagramFromText } from './Diagram';
import { addDetector } from './diagram-api/detectType';
import { addDiagrams } from './diagram-api/diagram-orchestration';
addDiagrams();
describe('diagram detection', () => {
test('should detect inbuilt diagrams', () => {
const graph = getDiagramFromText('graph TD; A-->B') as Diagram;
expect(graph).toBeInstanceOf(Diagram);
expect(graph.type).toBe('flowchart-v2');
const sequence = getDiagramFromText(
'sequenceDiagram; Alice->>+John: Hello John, how are you?'
) as Diagram;
expect(sequence).toBeInstanceOf(Diagram);
expect(sequence.type).toBe('sequence');
});
test('should detect external diagrams', async () => {
addDetector(
'loki',
(str) => str.startsWith('loki'),
() =>
Promise.resolve({
id: 'loki',
diagram: {
db: {},
parser: {
parse: () => {
// no-op
},
parser: {
yy: {},
},
},
renderer: {},
styles: {},
},
})
);
const diagram = (await getDiagramFromText('loki TD; A-->B')) as Diagram;
expect(diagram).toBeInstanceOf(Diagram);
expect(diagram.type).toBe('loki');
});
test('should throw the right error for incorrect diagram', () => {
expect(() => getDiagramFromText('graph TD; A-->')).toThrowErrorMatchingInlineSnapshot(`
"Parse error on line 3:
graph TD; A-->
--------------^
Expecting 'AMP', 'ALPHA', 'COLON', 'PIPE', 'TESTSTR', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'MINUS', 'BRKT', 'DOT', 'PUNCTUATION', 'UNICODE_TEXT', 'PLUS', 'EQUALS', 'MULT', 'UNDERSCORE', got 'EOF'"
`);
expect(() => getDiagramFromText('sequenceDiagram; A-->B')).toThrowErrorMatchingInlineSnapshot(`
"Parse error on line 1:
...quenceDiagram; A-->B
-----------------------^
Expecting 'TXT', got 'NEWLINE'"
`);
});
test('should throw the right error for unregistered diagrams', () => {
expect(() => getDiagramFromText('thor TD; A-->B')).toThrowError(
'No diagram type detected for text: thor TD; A-->B'
);
});
});

View File

@@ -1,5 +1,5 @@
import { select } from 'd3';
import * as graphlib from 'dagre-d3-es/src/graphlib';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { log } from '../../logger';
import { getConfig } from '../../config';
import { render } from '../../dagre-wrapper/index.js';

View File

@@ -47,7 +47,9 @@ export const sanitizeText = (text: string, config: MermaidConfig): string => {
if (config.dompurifyConfig) {
text = DOMPurify.sanitize(sanitizeMore(text, config), config.dompurifyConfig).toString();
} else {
text = DOMPurify.sanitize(sanitizeMore(text, config));
text = DOMPurify.sanitize(sanitizeMore(text, config), {
FORBID_TAGS: ['style'],
}).toString();
}
return text;
};

View File

@@ -1,4 +1,4 @@
import * as graphlib from 'dagre-d3-es/src/graphlib';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { line, curveBasis, select } from 'd3';
import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js';
import { getConfig } from '../../config';

View File

@@ -1,4 +1,4 @@
import * as graphlib from 'dagre-d3-es/src/graphlib';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { select, curveLinear, selectAll } from 'd3';
import flowDb from './flowDb';

View File

@@ -1,4 +1,4 @@
import * as graphlib from 'dagre-d3-es/src/graphlib';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { select, curveLinear, selectAll } from 'd3';
import { getConfig } from '../../config';
import { render as Render } from 'dagre-d3-es';

View File

@@ -1,6 +1,5 @@
import flowDb from '../flowDb';
import flow from './flow';
import filter from 'lodash-es/filter';
import { setConfig } from '../../../config';
setConfig({

View File

@@ -1,6 +1,5 @@
import flowDb from '../flowDb';
import flow from './flow';
import filter from 'lodash-es/filter';
import { setConfig } from '../../../config';
setConfig({

View File

@@ -1,4 +1,4 @@
import * as graphlib from 'dagre-d3-es/src/graphlib';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { select } from 'd3';
import { getConfig } from '../../config';
import { render } from '../../dagre-wrapper/index.js';

View File

@@ -1,3 +1,6 @@
@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css');
@import url('https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css');
:root {
--vp-c-brand: #ff3670;
--vp-c-brand-light: #ff5e8c;

View File

@@ -1,13 +1,16 @@
import mermaid, { type MermaidConfig } from 'mermaid';
import mindmap from '@mermaid-js/mermaid-mindmap';
try {
await mermaid.registerExternalDiagrams([mindmap]);
} catch (e) {
console.error(e);
}
const init = (async () => {
try {
await mermaid.registerExternalDiagrams([mindmap]);
} catch (e) {
console.error(e);
}
})();
export const render = async (id: string, code: string, config: MermaidConfig): Promise<string> => {
await init;
mermaid.initialize(config);
const svg = await mermaid.renderAsync(id, code);
return svg;

View File

@@ -112,7 +112,7 @@ More shapes will be added, beginning with the shapes available in flowcharts.
# Icons and classes
## icons
## Icons
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and fontawesome 4 are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.

View File

@@ -470,61 +470,48 @@ describe('mermaidAPI', function () {
svgElement.id = svgId;
const tempDivElement = givenDocument.createElement('div'); // doesn't matter what the tag is in the test
tempDivElement.id = tempDivId;
const tempiFrameElement = givenDocument.createElement('div'); // doesn't matter what the tag is in the test
const tempiFrameElement = givenDocument.createElement('iframe'); // doesn't matter what the tag is in the test
tempiFrameElement.id = tempIframeId;
it('removes an existing element with given id', () => {
rootHtml.appendChild(svgElement);
rootHtml.append(tempDivElement);
rootHtml.append(tempiFrameElement);
expect(givenDocument.getElementById(svgElement.id)).toEqual(svgElement);
removeExistingElements(givenDocument, false, svgId, tempDivId, tempIframeId);
expect(givenDocument.getElementById(tempDivElement.id)).toEqual(tempDivElement);
expect(givenDocument.getElementById(tempiFrameElement.id)).toEqual(tempiFrameElement);
removeExistingElements(givenDocument, svgId, tempDivId, tempIframeId);
expect(givenDocument.getElementById(svgElement.id)).toBeNull();
expect(givenDocument.getElementById(tempDivElement.id)).toBeNull();
expect(givenDocument.getElementById(tempiFrameElement.id)).toBeNull();
});
describe('is in sandboxed mode', () => {
const inSandboxedMode = true;
it('removes an existing iframe element even if div element is absent', () => {
tempiFrameElement.append(svgElement);
rootHtml.append(tempiFrameElement);
it('removes an existing element with the given iFrame selector', () => {
tempiFrameElement.append(svgElement);
rootHtml.append(tempiFrameElement);
rootHtml.append(tempDivElement);
expect(givenDocument.getElementById(tempIframeId)).toEqual(tempiFrameElement);
expect(givenDocument.getElementById(tempDivId)).toEqual(tempDivElement);
expect(givenDocument.getElementById(svgId)).toEqual(svgElement);
removeExistingElements(
givenDocument,
inSandboxedMode,
svgId,
'#' + tempDivId,
'#' + tempIframeId
);
expect(givenDocument.getElementById(tempDivId)).toEqual(tempDivElement);
expect(givenDocument.getElementById(tempIframeId)).toBeNull();
expect(givenDocument.getElementById(svgId)).toBeNull();
});
expect(givenDocument.getElementById(tempIframeId)).toEqual(tempiFrameElement);
expect(givenDocument.getElementById(tempDivId)).toBeNull();
expect(givenDocument.getElementById(svgId)).toEqual(svgElement);
removeExistingElements(givenDocument, svgId, tempDivId, tempIframeId);
expect(givenDocument.getElementById(tempDivId)).toBeNull();
expect(givenDocument.getElementById(tempIframeId)).toBeNull();
expect(givenDocument.getElementById(svgId)).toBeNull();
});
describe('not in sandboxed mode', () => {
const inSandboxedMode = false;
it('removes an existing element with the given enclosing div selector', () => {
tempDivElement.append(svgElement);
rootHtml.append(tempDivElement);
rootHtml.append(tempiFrameElement);
it('removes both existing div and iframe elements when both are present', () => {
tempDivElement.append(svgElement);
rootHtml.append(tempDivElement);
rootHtml.append(tempiFrameElement);
expect(givenDocument.getElementById(tempIframeId)).toEqual(tempiFrameElement);
expect(givenDocument.getElementById(tempDivId)).toEqual(tempDivElement);
expect(givenDocument.getElementById(svgId)).toEqual(svgElement);
removeExistingElements(
givenDocument,
inSandboxedMode,
svgId,
'#' + tempDivId,
'#' + tempIframeId
);
expect(givenDocument.getElementById(tempIframeId)).toEqual(tempiFrameElement);
expect(givenDocument.getElementById(tempDivId)).toBeNull();
expect(givenDocument.getElementById(svgId)).toBeNull();
});
expect(givenDocument.getElementById(tempIframeId)).toEqual(tempiFrameElement);
expect(givenDocument.getElementById(tempDivId)).toEqual(tempDivElement);
expect(givenDocument.getElementById(svgId)).toEqual(svgElement);
removeExistingElements(givenDocument, svgId, tempDivId, tempIframeId);
expect(givenDocument.getElementById(tempIframeId)).toBeNull();
expect(givenDocument.getElementById(tempDivId)).toBeNull();
expect(givenDocument.getElementById(svgId)).toBeNull();
});
});

View File

@@ -29,7 +29,7 @@ import utils, { directiveSanitizer } from './utils';
import DOMPurify from 'dompurify';
import { MermaidConfig } from './config.type';
import { evaluate } from './diagrams/common/common';
import isEmpty from 'lodash-es/isEmpty';
import isEmpty from 'lodash-es/isEmpty.js';
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility';
// diagram names that support classDef statements
@@ -55,8 +55,8 @@ const IFRAME_SANDBOX_OPTS = 'allow-top-navigation-by-user-activation allow-popup
const IFRAME_NOT_SUPPORTED_MSG = 'The "iframe" tag is not supported by your browser.';
// DOMPurify settings for svgCode
const DOMPURE_TAGS = ['foreignobject'];
const DOMPURE_ATTR = ['dominant-baseline'];
const DOMPURIFY_TAGS = ['foreignobject'];
const DOMPURIFY_ATTR = ['dominant-baseline'];
// This is what is returned from getClasses(...) methods.
// It is slightly renamed to ..StyleClassDef instead of just ClassDef because "class" is a greatly ambiguous and overloaded word.
@@ -328,29 +328,22 @@ function sandboxedIframe(parentNode: D3Element, iFrameId: string): D3Element {
* Remove any existing elements from the given document
*
* @param doc - the document to removed elements from
* @param isSandboxed - whether or not we are in sandboxed mode
* @param id - id for any existing SVG element
* @param divSelector - selector for any existing enclosing div element
* @param iFrameSelector - selector for any existing iFrame element
*/
export const removeExistingElements = (
doc: Document,
isSandboxed: boolean,
id: string,
divSelector: string,
iFrameSelector: string
divId: string,
iFrameId: string
) => {
// Remove existing SVG element if it exists
const existingSvg = doc.getElementById(id);
if (existingSvg) {
existingSvg.remove();
}
doc.getElementById(id)?.remove();
// Remove previous temporary element if it exists
const element = isSandboxed ? doc.querySelector(iFrameSelector) : doc.querySelector(divSelector);
if (element) {
element.remove();
}
// Both div and iframe needs to be cleared in case there is a config change happening between renders.
doc.getElementById(divId)?.remove();
doc.getElementById(iFrameId)?.remove();
};
/**
@@ -443,7 +436,7 @@ const render = function (
// No svgContainingElement was provided
// If there is an existing element with the id, we remove it. This likely a previously rendered diagram
removeExistingElements(document, isSandboxed, id, iFrameID_selector, enclosingDivID_selector);
removeExistingElements(document, id, enclosingDivID, iFrameID);
// Add the temporary div used for rendering with the enclosingDivID.
// This temporary div will contain a svg with the id == id
@@ -536,11 +529,11 @@ const render = function (
if (isSandboxed) {
const svgEl = root.select(enclosingDivID_selector + ' svg').node();
svgCode = putIntoIFrame(svgCode, svgEl);
} else if (isLooseSecurityLevel) {
} else if (!isLooseSecurityLevel) {
// Sanitize the svgCode using DOMPurify
svgCode = DOMPurify.sanitize(svgCode, {
ADD_TAGS: DOMPURE_TAGS,
ADD_ATTR: DOMPURE_ATTR,
ADD_TAGS: DOMPURIFY_TAGS,
ADD_ATTR: DOMPURIFY_ATTR,
});
}
@@ -650,7 +643,7 @@ const renderAsync = async function (
// No svgContainingElement was provided
// If there is an existing element with the id, we remove it. This likely a previously rendered diagram
removeExistingElements(document, isSandboxed, id, iFrameID_selector, enclosingDivID_selector);
removeExistingElements(document, id, enclosingDivID, iFrameID);
// Add the temporary div used for rendering with the enclosingDivID.
// This temporary div will contain a svg with the id == id
@@ -738,11 +731,11 @@ const renderAsync = async function (
if (isSandboxed) {
const svgEl = root.select(enclosingDivID_selector + ' svg').node();
svgCode = putIntoIFrame(svgCode, svgEl);
} else if (isLooseSecurityLevel) {
} else if (!isLooseSecurityLevel) {
// Sanitize the svgCode using DOMPurify
svgCode = DOMPurify.sanitize(svgCode, {
ADD_TAGS: DOMPURE_TAGS,
ADD_ATTR: DOMPURE_ATTR,
ADD_TAGS: DOMPURIFY_TAGS,
ADD_ATTR: DOMPURIFY_ATTR,
});
}

View File

@@ -3,7 +3,7 @@ import utils from './utils';
import assignWithDepth from './assignWithDepth';
import { detectType } from './diagram-api/detectType';
import { addDiagrams } from './diagram-api/diagram-orchestration';
import memoize from 'lodash-es/memoize';
import memoize from 'lodash-es/memoize.js';
import { MockedD3 } from './tests/MockedD3';
addDiagrams();

View File

@@ -21,7 +21,7 @@ import { log } from './logger';
import { detectType } from './diagram-api/detectType';
import assignWithDepth from './assignWithDepth';
import { MermaidConfig } from './config.type';
import memoize from 'lodash-es/memoize';
import memoize from 'lodash-es/memoize.js';
// Effectively an enum of the supported curve types, accessible by name
const d3CurveTypes = {