diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 41d9c4cff8..3574c35997 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,4 +14,5 @@ Make sure you - [ ] :book: have read the [contribution guidelines](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) - [ ] :computer: have added unit/e2e tests (if appropriate) +- [ ] :notebook: have added documentation (if appropriate) - [ ] :bookmark: targeted `develop` branch diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8ba06989d0..95e4256b1f 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,9 +7,10 @@ on: - opened - synchronize - ready_for_review + workflow_dispatch: permissions: - contents: read + contents: write jobs: lint: @@ -39,5 +40,19 @@ jobs: run: pnpm run lint - name: Verify Docs + id: verifyDocs working-directory: ./packages/mermaid + continue-on-error: ${{ github.event_name == 'push' }} run: pnpm run docs:verify + + - name: Rebuild Docs + if: ${{ steps.verifyDocs.outcome == 'failure' && github.event_name == 'push' }} + working-directory: ./packages/mermaid + run: pnpm run docs:build + + - name: Commit changes + uses: EndBug/add-and-commit@v9 + if: ${{ steps.verifyDocs.outcome == 'failure' && github.event_name == 'push' }} + with: + message: 'Update docs' + add: 'docs/*' diff --git a/.github/workflows/release-preview-publish.yml b/.github/workflows/release-preview-publish.yml index 2b2ff559b2..5f4936ab68 100644 --- a/.github/workflows/release-preview-publish.yml +++ b/.github/workflows/release-preview-publish.yml @@ -10,22 +10,30 @@ jobs: runs-on: ubuntu-latest steps: - uses: actions/checkout@v3 + with: + fetch-depth: 0 + + - uses: pnpm/action-setup@v2 + - name: Setup Node.js uses: actions/setup-node@v3 with: + cache: pnpm node-version: 18.x - - name: Install Yarn - run: npm i yarn --global + + - name: Install Packages + run: | + pnpm install --frozen-lockfile + env: + CYPRESS_CACHE_FOLDER: .cache/Cypress - name: Install Json run: npm i json --global - - name: Install Packages - run: yarn install --frozen-lockfile - - name: Publish + working-directory: ./packages/mermaid run: | - PREVIEW_VERSION=8 + PREVIEW_VERSION=$(git log --oneline "origin/$GITHUB_REF_NAME" ^"origin/master" | wc -l) VERSION=$(echo ${{github.ref}} | tail -c +20)-preview.$PREVIEW_VERSION echo $VERSION npm version --no-git-tag-version --allow-same-version $VERSION diff --git a/.vite/build.ts b/.vite/build.ts index 1be46ad5ad..1261e375ba 100644 --- a/.vite/build.ts +++ b/.vite/build.ts @@ -146,7 +146,7 @@ const main = async () => { }; if (watch) { - build(getBuildConfig({ minify: false, watch, core: true, entryName: 'mermaid' })); + build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' })); if (!mermaidOnly) { build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' })); // build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' })); diff --git a/README.md b/README.md index 059940a028..9a500283ce 100644 --- a/README.md +++ b/README.md @@ -10,7 +10,7 @@ English | [简体中文](./README.zh-CN.md) **Thanks to all involved, people committing pull requests, people answering questions! 🙏** -Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! +Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! ## About diff --git a/README.zh-CN.md b/README.zh-CN.md index 4bdbc4ae7d..6b3e28b192 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -10,7 +10,7 @@ **感谢所有参与进来提交 PR,解答疑问的人们! 🙏** -Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! +Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! ## 关于 Mermaid diff --git a/__mocks__/c4Renderer.js b/__mocks__/c4Renderer.js new file mode 100644 index 0000000000..576d5d8634 --- /dev/null +++ b/__mocks__/c4Renderer.js @@ -0,0 +1,21 @@ +/** + * Mocked C4Context diagram renderer + */ + +import { vi } from 'vitest'; + +export const drawPersonOrSystemArray = vi.fn(); +export const drawBoundary = vi.fn(); + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + drawPersonOrSystemArray, + drawBoundary, + setConf, + draw, +}; diff --git a/__mocks__/classRenderer-v2.js b/__mocks__/classRenderer-v2.js new file mode 100644 index 0000000000..1ad95806fc --- /dev/null +++ b/__mocks__/classRenderer-v2.js @@ -0,0 +1,16 @@ +/** + * Mocked class diagram v2 renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/classRenderer.js b/__mocks__/classRenderer.js new file mode 100644 index 0000000000..1c20de4b18 --- /dev/null +++ b/__mocks__/classRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked class diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/d3.ts b/__mocks__/d3.ts index f90d93557b..af35020c57 100644 --- a/__mocks__/d3.ts +++ b/__mocks__/d3.ts @@ -1,79 +1,14 @@ // @ts-nocheck TODO: Fix TS -import { vi } from 'vitest'; - -const NewD3 = function () { - /** - * - */ - function returnThis() { - return this; - } - return { - append: function () { - return NewD3(); - }, - lower: returnThis, - attr: returnThis, - style: returnThis, - text: returnThis, - 0: { - 0: { - getBBox: function () { - return { - height: 10, - width: 20, - }; - }, - }, - }, - }; -}; +import { MockedD3 } from '../packages/mermaid/src/tests/MockedD3'; export const select = function () { - return new NewD3(); + return new MockedD3(); }; export const selectAll = function () { - return new NewD3(); + return new MockedD3(); }; export const curveBasis = 'basis'; export const curveLinear = 'linear'; export const curveCardinal = 'cardinal'; - -export const MockD3 = (name, parent) => { - const children = []; - const elem = { - get __children() { - return children; - }, - get __name() { - return name; - }, - get __parent() { - return parent; - }, - node() { - return { - getBBox() { - return { - x: 5, - y: 10, - height: 15, - width: 20, - }; - }, - }; - }, - }; - elem.append = (name) => { - const mockElem = MockD3(name, elem); - children.push(mockElem); - return mockElem; - }; - elem.lower = vi.fn(() => elem); - elem.attr = vi.fn(() => elem); - elem.text = vi.fn(() => elem); - elem.style = vi.fn(() => elem); - return elem; -}; diff --git a/__mocks__/erRenderer.js b/__mocks__/erRenderer.js new file mode 100644 index 0000000000..845d641f75 --- /dev/null +++ b/__mocks__/erRenderer.js @@ -0,0 +1,16 @@ +/** + * Mocked er diagram renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/flowRenderer-v2.js b/__mocks__/flowRenderer-v2.js new file mode 100644 index 0000000000..89cc86031e --- /dev/null +++ b/__mocks__/flowRenderer-v2.js @@ -0,0 +1,24 @@ +/** + * Mocked flow (flowchart) diagram v2 renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); +export const addVertices = vi.fn(); +export const addEdges = vi.fn(); +export const getClasses = vi.fn().mockImplementation(() => { + return {}; +}); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + addVertices, + addEdges, + getClasses, + draw, +}; diff --git a/__mocks__/ganttRenderer.js b/__mocks__/ganttRenderer.js new file mode 100644 index 0000000000..9572498321 --- /dev/null +++ b/__mocks__/ganttRenderer.js @@ -0,0 +1,16 @@ +/** + * Mocked gantt diagram renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/gitGraphRenderer.js b/__mocks__/gitGraphRenderer.js new file mode 100644 index 0000000000..1daa82ca4c --- /dev/null +++ b/__mocks__/gitGraphRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked git (graph) diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/journeyRenderer.js b/__mocks__/journeyRenderer.js new file mode 100644 index 0000000000..2bc77c0b10 --- /dev/null +++ b/__mocks__/journeyRenderer.js @@ -0,0 +1,15 @@ +/** + * Mocked pie (picChart) diagram renderer + */ + +import { vi } from 'vitest'; +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/pieRenderer.js b/__mocks__/pieRenderer.js new file mode 100644 index 0000000000..317c69901d --- /dev/null +++ b/__mocks__/pieRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked pie (picChart) diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/requirementRenderer.js b/__mocks__/requirementRenderer.js new file mode 100644 index 0000000000..48d8997ac1 --- /dev/null +++ b/__mocks__/requirementRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked requirement diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/sequenceRenderer.js b/__mocks__/sequenceRenderer.js new file mode 100644 index 0000000000..11080c6bbf --- /dev/null +++ b/__mocks__/sequenceRenderer.js @@ -0,0 +1,23 @@ +/** + * Mocked sequence diagram renderer + */ + +import { vi } from 'vitest'; + +export const bounds = vi.fn(); +export const drawActors = vi.fn(); +export const drawActorsPopup = vi.fn(); + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + bounds, + drawActors, + drawActorsPopup, + setConf, + draw, +}; diff --git a/__mocks__/stateRenderer-v2.js b/__mocks__/stateRenderer-v2.js new file mode 100644 index 0000000000..a2d103b50e --- /dev/null +++ b/__mocks__/stateRenderer-v2.js @@ -0,0 +1,22 @@ +/** + * Mocked state diagram v2 renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); +export const getClasses = vi.fn().mockImplementation(() => { + return {}; +}); +export const stateDomId = vi.fn().mockImplementation(() => { + return 'mocked-stateDiagram-stateDomId'; +}); +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + getClasses, + draw, +}; diff --git a/cSpell.json b/cSpell.json index 64187e1ca5..49cb8ada14 100644 --- a/cSpell.json +++ b/cSpell.json @@ -14,6 +14,7 @@ "bilkent", "bisheng", "braintree", + "brkt", "brolin", "brotli", "classdef", @@ -60,13 +61,19 @@ "mindmaps", "mitigations", "mkdocs", + "mult", "orlandoni", "phpbb", "plantuml", "playfair", + "pnpm", "podlite", + "quence", "ranksep", + "rect", + "rects", "redmine", + "roledescription", "sandboxed", "setupgraphviewbox", "shiki", @@ -76,7 +83,10 @@ "stylis", "substate", "sveidqvist", + "swimm", "techn", + "teststr", + "textlength", "treemap", "ts-nocheck", "tuleap", diff --git a/cypress/integration/other/external-diagrams.spec.js b/cypress/integration/other/external-diagrams.spec.js index 3a6c37e881..be69dfc982 100644 --- a/cypress/integration/other/external-diagrams.spec.js +++ b/cypress/integration/other/external-diagrams.spec.js @@ -1,13 +1,10 @@ +import { urlSnapshotTest } from '../../helpers/util'; + describe('mermaid', () => { describe('registerDiagram', () => { it('should work on @mermaid-js/mermaid-mindmap and mermaid-example-diagram', () => { const url = 'http://localhost:9000/external-diagrams-mindmap.html'; - cy.visit(url); - - cy.get('svg', { - // may be a bit slower than normal, since vite might need to re-compile mermaid/mermaid-mindmap/mermaid-example-diagram - timeout: 10000, - }).matchImageSnapshot(); + urlSnapshotTest(url, {}, false, false); }); }); }); diff --git a/cypress/integration/other/ghsa.spec.js b/cypress/integration/other/ghsa.spec.js index 5b168a8a8f..4fadc78554 100644 --- a/cypress/integration/other/ghsa.spec.js +++ b/cypress/integration/other/ghsa.spec.js @@ -7,4 +7,10 @@ describe('CSS injections', () => { flowchart: { htmlLabels: false }, }); }); + it('should not allow adding styletags affecting the page', () => { + urlSnapshotTest('http://localhost:9000/ghsa3.html', { + logLevel: 1, + flowchart: { htmlLabels: false }, + }); + }); }); diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js index f97458857e..9536a074dc 100644 --- a/cypress/integration/rendering/classDiagram-v2.spec.js +++ b/cypress/integration/rendering/classDiagram-v2.spec.js @@ -485,8 +485,7 @@ describe('Class diagram V2', () => { classDiagram-v2 note "I love this diagram!\nDo you love it?" class Class10 { - <> - int id + int id size() } note for Class10 "Cool class\nI said it's very cool class!" diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index 16601652d5..e21be67ec4 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -414,7 +414,6 @@ describe('Class diagram', () => { classDiagram note "I love this diagram!\nDo you love it?" class Class10 { - <> int id size() } diff --git a/cypress/integration/rendering/current.spec.js b/cypress/integration/rendering/current.spec.js index 56b5f774b3..033752c668 100644 --- a/cypress/integration/rendering/current.spec.js +++ b/cypress/integration/rendering/current.spec.js @@ -1,6 +1,6 @@ import { imgSnapshotTest } from '../../helpers/util'; -describe('State diagram', () => { +describe('Current diagram', () => { it('should render a state with states in it', () => { imgSnapshotTest( ` diff --git a/cypress/integration/rendering/erDiagram.spec.js b/cypress/integration/rendering/erDiagram.spec.js index 8e8946170b..c72df49b68 100644 --- a/cypress/integration/rendering/erDiagram.spec.js +++ b/cypress/integration/rendering/erDiagram.spec.js @@ -182,6 +182,20 @@ describe('Entity Relationship Diagram', () => { cy.get('svg'); }); + it('should render entities with length in attributes type', () => { + renderGraph( + ` + erDiagram + CLUSTER { + varchar(99) name + string(255) description + } + `, + { logLevel: 1 } + ); + cy.get('svg'); + }); + it('should render entities and attributes with big and small entity names', () => { renderGraph( ` diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index 325cca0658..c0156eee36 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -310,38 +310,6 @@ describe('Gantt diagram', () => { ); }); - it('should render accessibility tags', function () { - const expectedTitle = 'Gantt Diagram'; - const expectedAccDescription = 'Tasks for Q4'; - renderGraph( - ` - gantt - accTitle: ${expectedTitle} - accDescr: ${expectedAccDescription} - dateFormat YYYY-MM-DD - section Section - A task :a1, 2014-01-01, 30d - `, - {} - ); - cy.get('svg').should((svg) => { - const el = svg.get(0); - const children = [...el.children]; - - const titleEl = children.find(function (node) { - return node.tagName === 'title'; - }); - const descriptionEl = children.find(function (node) { - return node.tagName === 'desc'; - }); - - expect(titleEl).to.exist; - expect(titleEl.textContent).to.equal(expectedTitle); - expect(descriptionEl).to.exist; - expect(descriptionEl.textContent).to.equal(expectedAccDescription); - }); - }); - it('should render a gantt diagram with tick is 15 minutes', () => { imgSnapshotTest( ` diff --git a/cypress/integration/rendering/requirement.spec.js b/cypress/integration/rendering/requirement.spec.js index 8a8d188ffd..0bf9014bf9 100644 --- a/cypress/integration/rendering/requirement.spec.js +++ b/cypress/integration/rendering/requirement.spec.js @@ -46,69 +46,4 @@ describe('Requirement diagram', () => { ); cy.get('svg'); }); - - it('should render accessibility tags', function () { - const expectedTitle = 'Gantt Diagram'; - const expectedAccDescription = 'Tasks for Q4'; - renderGraph( - ` - requirementDiagram - accTitle: ${expectedTitle} - accDescr: ${expectedAccDescription} - - requirement test_req { - id: 1 - text: the test text. - risk: high - verifymethod: test - } - - functionalRequirement test_req2 { - id: 1.1 - text: the second test text. - risk: low - verifymethod: inspection - } - - performanceRequirement test_req3 { - id: 1.2 - text: the third test text. - risk: medium - verifymethod: demonstration - } - - element test_entity { - type: simulation - } - - element test_entity2 { - type: word doc - docRef: reqs/test_entity - } - - - test_entity - satisfies -> test_req2 - test_req - traces -> test_req2 - test_req - contains -> test_req3 - test_req <- copies - test_entity2 - `, - {} - ); - cy.get('svg').should((svg) => { - const el = svg.get(0); - const children = [...el.children]; - - const titleEl = children.find(function (node) { - return node.tagName === 'title'; - }); - const descriptionEl = children.find(function (node) { - return node.tagName === 'desc'; - }); - - expect(titleEl).to.exist; - expect(titleEl.textContent).to.equal(expectedTitle); - expect(descriptionEl).to.exist; - expect(descriptionEl.textContent).to.equal(expectedAccDescription); - }); - }); }); diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js index 0eca018739..047e240fc8 100644 --- a/cypress/integration/rendering/stateDiagram-v2.spec.js +++ b/cypress/integration/rendering/stateDiagram-v2.spec.js @@ -328,7 +328,7 @@ describe('State diagram', () => { } ); }); - it('v2 it should be possibel to use a choice', () => { + it('v2 it should be possible to use a choice', () => { imgSnapshotTest( ` stateDiagram-v2 diff --git a/cypress/platform/ghsa1.html b/cypress/platform/ghsa1.html index c543588621..890a8e0dd2 100644 --- a/cypress/platform/ghsa1.html +++ b/cypress/platform/ghsa1.html @@ -4,7 +4,7 @@
-

This element does not belong to the SVG but we can style it

+

Background should be yellow!!!

diff --git a/cypress/platform/ghsa3.html b/cypress/platform/ghsa3.html new file mode 100644 index 0000000000..63dfa0d01e --- /dev/null +++ b/cypress/platform/ghsa3.html @@ -0,0 +1,100 @@ + + + + + + + + + +

PAGE SHOULD NOT BE RED

+
+
+
+
+ + + + diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index ba7f416011..83472039df 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -56,29 +56,30 @@
Security check
-graph LR
-    subgraph external
-        inside
+flowchart LR
+    %% Actors
+    A
+    subgraph Sub
+        B --> C
     end
-    outside --> external
+
+    %% Accusations
+    A --L --> Sub
+
+    %% Offense
+    B --> A
+
     
-mindmap
-  root
-    child1((Circle))
-        grandchild 1
-        grandchild 2
-    child2(Round rectangle)
-        grandchild 3
-        grandchild 4
-    child3[Square]
-        grandchild 5
-        ::icon(mdi mdi-fire)
-        gc6((grand
child 6)) - ::icon(mdi mdi-fire) - gc7((grand
grand
child 8)) -
-
+      stateDiagram-v2
+
+      [*] --> S1
+      S1 --> S2: long line using
should work + S1 --> S3: long line using
should work + S1 --> S4: long line using \\nshould work + +
+
       gantt
         title Style today marker (vertical line should be 5px wide and half-transparent blue)
         dateFormat YYYY-MM-DD
@@ -103,7 +104,7 @@
         // console.error('Mermaid error: ', err);
       };
       mermaid.initialize({
-        theme: 'base',
+        theme: 'default',
         startOnLoad: true,
         logLevel: 0,
         flowchart: {
@@ -114,10 +115,6 @@
           useMaxWidth: false,
         },
         useMaxWidth: false,
-        lazyLoadedDiagrams: [
-          './mermaid-mindmap-detector.esm.mjs',
-          './mermaid-example-diagram-detector.esm.mjs',
-        ],
       });
       function callback() {
         alert('It worked');
diff --git a/cypress/platform/knsv3.html b/cypress/platform/knsv3.html
index 0c1afadb72..e5ca66c87a 100644
--- a/cypress/platform/knsv3.html
+++ b/cypress/platform/knsv3.html
@@ -6,6 +6,10 @@
       rel="stylesheet"
       href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
     />
+    
     
   
   
-    
info below
-
-
-flowchart TB;subgraph "number as labels";1;end;
-      
-
-flowchart TB;a[APA];
-      
-
-graph TD
-      work --> sleep
-      sleep --> work
-      eat --> sleep
-      work --> eat
-      
-
-flowchart TD
-      work --> sleep
-      sleep --> work
-      eat --> sleep
-      work --> eat
-      
-
- graph TB
-      A
-      B
-      subgraph foo[Foo SubGraph]
-        C
-        D
-      end
-      subgraph bar[Bar SubGraph]
-        E
-        F
-      end
-      G
-
-      A-->B
-      B-->C
-      C-->D
-      B-->D
-      D-->E
-      E-->A
-      E-->F
-      F-->D
-      F-->G
-      B-->G
-      G-->D
-
-      style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred
-      style bar fill:#999,stroke-width:2px,stroke:#0F0,color:blue
-      
-
-      graph TB
-%%{init: { "logLevel": 1, "flowchart": {"htmlLabels":true }} }%%
-      A
-      B
-      subgraph foo[Foo SubGraph]
-        C
-        D
-      end
-      subgraph bar[Bar SubGraph]
-        E
-        F
-      end
-      G
+    
Security check
+
+      graph TD
+        A["test"] --"

test

"--> B +
- A-->B - B-->C - C-->D - B-->D - D-->E - E-->A - E-->F - F-->D - F-->G - B-->G - G-->D + + + + + - style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred - style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue -
-
-      graph TD
-        A[Christmas] ==> D
-        A[Christmas] -->|Get money| B(Go shopping)
-        A[Christmas] ==> C
-      
-
-      graph TD
-%%{init: { "logLevel": 1, "flowchart": {"htmlLabels":true }} }%%
-        A[Christmas] ==> D
-        A[Christmas] -->|Get money| B(Go shopping)
-        A[Christmas] ==> C
-      
-
-      flowchart TD
-        A[Christmas] ==> D
-        A[Christmas] -->|Get money| B(Go shopping)
-        A[Christmas] ==> C
-      
-
-      flowchart TD
-%%{init: { "logLevel": 1, "flowchart": {"htmlLabels":true }} }%%
-        A[Christmas] ==> D
-        A[Christmas] -->|Get money| B(Go shopping)
-        A[Christmas] ==> C
-      
-
-flowchart LR
-        a["Haiya"]---->b
-      
-
-flowchart LR
-%%{init: { "logLevel": 1, "flowchart": {"htmlLabels":true }} }%%
-        a["Haiya"]---->b
-      
-
-      flowchart TD
-        A[Christmas] ==> D
-        A[Christmas] -->|Get money| B(Go shopping)
-        A[Christmas] ==> C
-      
-
-      flowchart TD
-%%{init: { "logLevel": 1, "flowchart": {"htmlLabels":true }} }%%
-        A[Christmas] ==> D
-        A[Christmas] -->|Get money| B(Go shopping)
-        A[Christmas] ==> C
-      
-
-      %%{init: { "logLevel": 1, "flowchart": {"htmlLabels":true }} }%%
-classDiagram-v2
-      Class01 <|-- AveryLongClass : Cool
-      <<interface>> Class01
-      Class03 *-- Class04
-      Class05 o-- Class06
-      Class07 .. Class08
-      Class09 --> C2 : Where am i?
-      Class09 --* C3
-      Class09 --|> Class07
-      Class12 <|.. Class08
-      Class11 ..>Class12
-      Class07 : equals()
-      Class07 : Object[] elementData
-      Class01 : size()
-      Class01 : int chimp
-      Class01 : int gorilla
-      Class01 : -int privateChimp
-      Class01 : +int publicGorilla
-      Class01 : #int protectedMarmoset
-      Class08 <--> C2: Cool label
-      class Class10 {
-        <<service>>
-        int id
-        test()
-      }
-      
-
-classDiagram-v2
-      Class01 <|-- AveryLongClass : Cool
-      <<interface>> Class01
-      Class03 *-- Class04
-      Class05 o-- Class06
-      Class07 .. Class08
-      Class09 --> C2 : Where am i?
-      Class09 --* C3
-      Class09 --|> Class07
-      Class12 <|.. Class08
-      Class11 ..>Class12
-      Class07 : equals()
-      Class07 : Object[] elementData
-      Class01 : size()
-      Class01 : int chimp
-      Class01 : int gorilla
-      Class01 : -int privateChimp
-      Class01 : +int publicGorilla
-      Class01 : #int protectedMarmoset
-      Class08 <--> C2: Cool label
-      class Class10 {
-        <<service>>
-        int id
-        test()
-      }
-      
-
-flowchart BT
-   subgraph S1
-    sub1 -->sub2
-   end
-  subgraph S2
-    sub4
-   end
-   S1 --> S2
-   sub1 --> sub4
-      
-
- - diff --git a/demos/er.html b/demos/er.html index b5d1966f72..34e06acf85 100644 --- a/demos/er.html +++ b/demos/er.html @@ -33,29 +33,43 @@ "Person . CUSTOMER" }|..|{ "Address//StreetAddress::[DELIVERY ADDRESS]" : uses "Address//StreetAddress::[DELIVERY ADDRESS]" { - int customerID FK - string line1 "this is the first address line comment" - string line2 - string city - string region - string state - string postal_code - string country - } + int customerID FK + string line1 "this is the first address line comment" + string line2 + string city + string region + string state + string(5) postal_code + string country + } - "a_~`!@#$^&*()-_=+[]{}|/;:'.?¡⁄™€£‹¢›∞fi§‡•°ª·º‚≠±œŒ∑„®†ˇ¥Á¨ˆˆØπ∏“«»åÅßÍ∂΃ϩ˙Ó∆Ô˚¬Ò…ÚæÆΩ¸≈π˛çÇ√◊∫ı˜µÂ≤¯≥˘÷¿" { - string name "this is an entity with an absurd name just to show characters that are now acceptable as long as the name is in double quotes" - } + "a_~`!@#$^&*()-_=+[]{}|/;:'.?¡⁄™€£‹¢›∞fi§‡•°ª·º‚≠±œŒ∑„®†ˇ¥Á¨ˆˆØπ∏“«»åÅßÍ∂΃ϩ˙Ó∆Ô˚¬Ò…ÚæÆΩ¸≈π˛çÇ√◊∫ı˜µÂ≤¯≥˘÷¿" { + string name "this is an entity with an absurd name just to show characters that are now acceptable as long as the name is in double quotes" + } - "€£LINE_ITEM ¥" { - int orderID FK - int currencyId FK - number price - number quantity - number adjustment - number final_price - } + "€£LINE_ITEM ¥" { + int orderID FK + int currencyId FK + number price + number quantity + number adjustment + number final_price + }
+
+ +
+    erDiagram
+      "HOSPITAL" {
+        int id PK
+        int doctor_id FK
+        string address UK
+        string name
+        string phone_number
+        string fax_number
+      }
+    
+
``` @@ -85,7 +85,7 @@ Example: B-->D(fa:fa-spinner); diff --git a/docs/intro/index.md b/docs/intro/index.md index b8a27acff2..5aa068e27c 100644 --- a/docs/intro/index.md +++ b/docs/intro/index.md @@ -267,7 +267,7 @@ To Deploy Mermaid: ```html ``` diff --git a/docs/intro/n00b-gettingStarted.md b/docs/intro/n00b-gettingStarted.md index 498aa15955..2a05a1fdd0 100644 --- a/docs/intro/n00b-gettingStarted.md +++ b/docs/intro/n00b-gettingStarted.md @@ -128,7 +128,7 @@ b. The importing of mermaid library through the `mermaid.esm.js` or `mermaid.esm ```html @@ -143,6 +143,10 @@ Rendering in Mermaid is initialized by `mermaid.initialize()` call. You can plac | ----------- | --------------------------------- | ------- | ----------- | | startOnLoad | Toggle for Rendering upon loading | Boolean | true, false | +### Adding external diagrams to mermaid + +Please refer to the [Mindmap](../syntax/mindmap.md?id=integrating-with-your-librarywebsite) section for more information. + ### Working Examples **Here is a full working example of the mermaidAPI being called through the CDN:** @@ -168,7 +172,7 @@ Rendering in Mermaid is initialized by `mermaid.initialize()` call. You can plac diff --git a/docs/misc/integrations.md b/docs/misc/integrations.md index 007b9e7780..50f3237cd3 100644 --- a/docs/misc/integrations.md +++ b/docs/misc/integrations.md @@ -21,6 +21,7 @@ They also serve as proof of concept, for the variety of things that can be built - [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) (**Native support**) - [Tuleap](https://docs.tuleap.org/user-guide/writing-in-tuleap.html#graphs) (**Native support**) - [Joplin](https://joplinapp.org) (**Native support**) +- [Swimm](https://swimm.io) (**Native support**) - [Notion](https://notion.so) (**Native support**) - [Observable](https://observablehq.com/@observablehq/mermaid) (**Native support**) - [Obsidian](https://help.obsidian.md/How+to/Format+your+notes#Diagram) (**Native support**) @@ -109,10 +110,10 @@ They also serve as proof of concept, for the variety of things that can be built - [md-it-mermaid](https://github.com/iamcco/md-it-mermaid) - [markdown-it-mermaid-fence-new](https://github.com/Revomatico/markdown-it-mermaid-fence-new) - [markdown-it-mermaid-less](https://github.com/searKing/markdown-it-mermaid-less) -- [Atom](https://atom.io) - - [Markdown Preview Enhanced](https://atom.io/packages/markdown-preview-enhanced) - - [Atom Mermaid](https://atom.io/packages/atom-mermaid) - - [Language Mermaid Syntax Highlighter](https://atom.io/packages/language-mermaid) +- Atom _(Atom has been [archived.](https://github.blog/2022-06-08-sunsetting-atom/))_ + - [Markdown Preview Enhanced](https://github.com/shd101wyy/markdown-preview-enhanced) + - [Atom Mermaid](https://github.com/y-takey/atom-mermaid) + - [Language Mermaid Syntax Highlighter](https://github.com/ytisf/language-mermaid) - [Sublime Text 3](https://sublimetext.com) - [Mermaid Package](https://packagecontrol.io/packages/Mermaid) - [Astah](https://astah.net) diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index 5870d07433..97032ff56c 100644 --- a/docs/syntax/classDiagram.md +++ b/docs/syntax/classDiagram.md @@ -7,7 +7,8 @@ # Class diagrams > "In software engineering, a class diagram in the Unified Modeling Language (UML) is a type of static structure diagram that describes the structure of a system by showing the system's classes, their attributes, operations (or methods), and the relationships among objects." -> Wikipedia +> +> \-Wikipedia The class diagram is the main building block of object-oriented modeling. It is used for general conceptual modeling of the structure of the application, and for detailed modeling to translate the models into programming code. Class diagrams can also be used for data modeling. The classes in a class diagram represent both the main elements, interactions in the application, and the classes to be programmed. @@ -237,10 +238,6 @@ Square : +setMessages(List~string~ messages) Square : +getMessages() List~string~ ``` -#### Return Type - -Optionally you can end the method/function definition with the data type that will be returned. - #### Visibility To describe the visibility (or encapsulation) of an attribute or method/function that is a part of a class (i.e. a class member), optional notation may be placed before that members' name: @@ -270,7 +267,7 @@ There are eight different types of relations defined for classes under UML which | Type | Description | | ------- | ------------- | | `<\|--` | Inheritance | -| `\*--` | Composition | +| `*--` | Composition | | `o--` | Aggregation | | `-->` | Association | | `--` | Link (Solid) | diff --git a/docs/syntax/entityRelationshipDiagram.md b/docs/syntax/entityRelationshipDiagram.md index 9b938bc368..447b8d3227 100644 --- a/docs/syntax/entityRelationshipDiagram.md +++ b/docs/syntax/entityRelationshipDiagram.md @@ -230,25 +230,26 @@ erDiagram } ``` -The `type` and `name` values must begin with an alphabetic character and may contain digits, hyphens or underscores. Other than that, there are no restrictions, and there is no implicit set of valid data types. +The `type` and `name` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. Other than that, there are no restrictions, and there is no implicit set of valid data types. #### Attribute Keys and Comments -Attributes may also have a `key` or comment defined. Keys can be "PK" or "FK", for Primary Key or Foreign Key. And a `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them. +Attributes may also have a `key` or comment defined. Keys can be "PK", "FK" or "UK", for Primary Key, Foreign Key or Unique Key. And a `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them. ```mermaid-example erDiagram CAR ||--o{ NAMED-DRIVER : allows CAR { string allowedDriver FK "The license of the allowed driver" - string registrationNumber + string registrationNumber UK string make string model + string[] parts } PERSON ||--o{ NAMED-DRIVER : is PERSON { string driversLicense PK "The license #" - string firstName + string(99) firstName "Only 99 characters are allowed" string lastName int age } @@ -260,14 +261,15 @@ erDiagram CAR ||--o{ NAMED-DRIVER : allows CAR { string allowedDriver FK "The license of the allowed driver" - string registrationNumber + string registrationNumber UK string make string model + string[] parts } PERSON ||--o{ NAMED-DRIVER : is PERSON { string driversLicense PK "The license #" - string firstName + string(99) firstName "Only 99 characters are allowed" string lastName int age } diff --git a/docs/syntax/mindmap.md b/docs/syntax/mindmap.md index 628461c4f5..7e1d9c0808 100644 --- a/docs/syntax/mindmap.md +++ b/docs/syntax/mindmap.md @@ -180,7 +180,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. @@ -253,3 +253,17 @@ Root B C ``` + +## Integrating with your library/website. + +Mindmap uses the experimental lazy loading & async rendering features which could change in the future. + +```html + +``` + +You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/fcf53c98c25604c90a218104268c339be53035a6/src/lib/util/mermaid.ts) to see how the async loading is done. diff --git a/package.json b/package.json index 1faa1628dc..596cac22bc 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "mermaid-monorepo", "private": true, - "version": "9.2.2", + "version": "9.3.0-rc1", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", - "packageManager": "pnpm@7.17.1", + "packageManager": "pnpm@7.18.2", "keywords": [ "diagram", "markdown", @@ -75,7 +75,7 @@ "coveralls": "^3.1.1", "cypress": "^10.11.0", "cypress-image-snapshot": "^4.0.1", - "esbuild": "^0.15.13", + "esbuild": "^0.16.0", "eslint": "^8.27.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-cypress": "^2.12.1", diff --git a/packages/mermaid-mindmap/package.json b/packages/mermaid-mindmap/package.json index 852c0871b8..0f1a98303b 100644 --- a/packages/mermaid-mindmap/package.json +++ b/packages/mermaid-mindmap/package.json @@ -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", diff --git a/packages/mermaid/README.md b/packages/mermaid/README.md index 91c2d16401..e6c7db6085 100644 --- a/packages/mermaid/README.md +++ b/packages/mermaid/README.md @@ -10,7 +10,7 @@ English | [简体中文](./README.zh-CN.md) **Thanks to all involved, people committing pull requests, people answering questions! 🙏** -Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! +Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! ## About diff --git a/packages/mermaid/README.zh-CN.md b/packages/mermaid/README.zh-CN.md index 0ccef27e48..f34c7a6471 100644 --- a/packages/mermaid/README.zh-CN.md +++ b/packages/mermaid/README.zh-CN.md @@ -10,7 +10,7 @@ **感谢所有参与进来提交 PR,解答疑问的人们! 🙏** -Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! +Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! ## 关于 Mermaid diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index d74de25d44..802ffd237c 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -1,11 +1,10 @@ { "name": "mermaid", - "version": "9.2.2", + "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", "types": "./dist/mermaid.d.ts", - "type": "commonjs", "exports": { ".": { "require": "./dist/mermaid.min.js", @@ -29,7 +28,7 @@ "docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts", "docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify", "docs:pre:vitepress": "rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress", - "docs:build:vitepress": "pnpm docs:pre:vitepress && vitepress build src/vitepress", + "docs:build:vitepress": "pnpm docs:pre:vitepress && vitepress build src/vitepress && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing", "docs:dev": "pnpm docs:pre:vitepress && concurrently \"vitepress dev src/vitepress\" \"ts-node-esm src/docs.mts --watch --vitepress\"", "docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress", "docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"", @@ -55,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", @@ -79,6 +78,7 @@ "chokidar": "^3.5.3", "concurrently": "^7.5.0", "coveralls": "^3.1.1", + "cpy-cli": "^4.2.0", "cspell": "^6.14.3", "globby": "^13.1.2", "jison": "^0.4.18", @@ -95,8 +95,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", diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index a2349c2556..83412e4aac 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -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; diff --git a/packages/mermaid/src/accessibility.js b/packages/mermaid/src/accessibility.js deleted file mode 100644 index 4d4837fff2..0000000000 --- a/packages/mermaid/src/accessibility.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * This method will add a basic title and description element to a chart. The yy parser will need to - * respond to getAccTitle and getAccDescription, where the title is the title element on the chart, - * which is generally not displayed and the accDescription is the description element on the chart, - * which is never displayed. - * - * The following charts display their title as a visual and accessibility element: gantt - * - * @param yy_parser - * @param svg - * @param id - */ -export default function addSVGAccessibilityFields(yy_parser, svg, id) { - if (svg.insert === undefined) { - return; - } - - let title_string = yy_parser.getAccTitle(); - let description = yy_parser.getAccDescription(); - svg.attr('role', 'img').attr('aria-labelledby', 'chart-title-' + id + ' chart-desc-' + id); - svg - .insert('desc', ':first-child') - .attr('id', 'chart-desc-' + id) - .text(description); - svg - .insert('title', ':first-child') - .attr('id', 'chart-title-' + id) - .text(title_string); -} diff --git a/packages/mermaid/src/accessibility.spec.ts b/packages/mermaid/src/accessibility.spec.ts new file mode 100644 index 0000000000..60415ea374 --- /dev/null +++ b/packages/mermaid/src/accessibility.spec.ts @@ -0,0 +1,227 @@ +import { MockedD3 } from './tests/MockedD3'; +import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility'; +import { D3Element } from './mermaidAPI'; + +describe('accessibility', () => { + const fauxSvgNode = new MockedD3(); + + describe('setA11yDiagramInfo', () => { + it('sets the svg element role to "graphics-document document"', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + setA11yDiagramInfo(fauxSvgNode, 'flowchart'); + expect(svgAttrSpy).toHaveBeenCalledWith('role', 'graphics-document document'); + }); + + it('sets the aria-roledescription to the diagram type', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + setA11yDiagramInfo(fauxSvgNode, 'flowchart'); + expect(svgAttrSpy).toHaveBeenCalledWith('aria-roledescription', 'flowchart'); + }); + + it('does not set the aria-roledescription if the diagram type is empty', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + setA11yDiagramInfo(fauxSvgNode, ''); + expect(svgAttrSpy).toHaveBeenCalledTimes(1); + expect(svgAttrSpy).toHaveBeenCalledWith('role', expect.anything()); // only called to set the role + }); + }); + + describe('addSVGa11yTitleDescription', () => { + const givenId = 'theBaseId'; + + describe('with the given svg d3 object:', () => { + it('does nothing if there is no insert defined', () => { + const noInsertSvg = { + attr: vi.fn(), + }; + const noInsertAttrSpy = vi.spyOn(noInsertSvg, 'attr').mockReturnValue(noInsertSvg); + addSVGa11yTitleDescription(noInsertSvg, 'some title', 'some desc', givenId); + expect(noInsertAttrSpy).not.toHaveBeenCalled(); + }); + + // ---------------- + // Convenience functions to DRY up the spec + + function expectAriaLabelledByIsTitleId( + svgD3Node: D3Element, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string + ) { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node); + addSVGa11yTitleDescription(svgD3Node, title, desc, givenId); + expect(svgAttrSpy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`); + } + + function expectAriaDescribedByIsDescId( + svgD3Node: D3Element, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string + ) { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node); + addSVGa11yTitleDescription(svgD3Node, title, desc, givenId); + expect(svgAttrSpy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`); + } + + function a11yTitleTagInserted( + svgD3Node: D3Element, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string, + callNumber: number + ) { + a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'title', title); + } + + function a11yDescTagInserted( + svgD3Node: D3Element, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string, + callNumber: number + ) { + a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'desc', desc); + } + + function a11yTagInserted( + svgD3Node: D3Element, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string, + callNumber: number, + expectedPrefix: string, + expectedText: string | null | undefined + ) { + const fauxInsertedD3 = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxInsertedD3); + // @ts-ignore Required to easily handle the d3 select types + const titleAttrSpy = vi.spyOn(fauxInsertedD3, 'attr').mockReturnValue(fauxInsertedD3); + const titleTextSpy = vi.spyOn(fauxInsertedD3, 'text'); + + addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId); + expect(svgInsertSpy).toHaveBeenCalledWith(expectedPrefix, ':first-child'); + expect(titleAttrSpy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`); + expect(titleTextSpy).toHaveBeenNthCalledWith(callNumber, expectedText); + } + // ---------------- + + describe('given an a11y title', () => { + const a11yTitle = 'a11y title'; + + describe('given an a11y description', () => { + const a11yDesc = 'a11y description'; + + it('sets aria-labelledby to the title id inserted as a child', () => { + expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId); + }); + + it('sets aria-describedby to the description id inserted as a child', () => { + expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId); + }); + + it('inserts a title tag as the first child with the text set to the accTitle given', () => { + a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 2); + }); + + it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => { + a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1); + }); + }); + + describe(`no a11y description`, () => { + const a11yDesc = undefined; + + it('sets aria-labelledby to the title id inserted as a child', () => { + expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId); + }); + + it('no aria-describedby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything()); + }); + + it('inserts a title tag as the first child with the text set to the accTitle given', () => { + a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1); + }); + + it('no description tag is inserted', () => { + const fauxTitle = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child'); + }); + }); + }); + + describe('no a11y title', () => { + const a11yTitle = undefined; + + describe('given an a11y description', () => { + const a11yDesc = 'a11y description'; + + it('no aria-labelledby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything()); + }); + + it('no title tag inserted', () => { + const fauxTitle = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child'); + }); + + it('sets aria-describedby to the description id inserted as a child', () => { + expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId); + }); + + it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => { + a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1); + }); + }); + + describe('no a11y description', () => { + const a11yDesc = undefined; + + it('no aria-labelledby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything()); + }); + + it('no aria-describedby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything()); + }); + + it('no title tag inserted', () => { + const fauxTitle = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child'); + }); + + it('no description tag inserted', () => { + const fauxDesc = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxDesc); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child'); + }); + }); + }); + }); + }); +}); diff --git a/packages/mermaid/src/accessibility.ts b/packages/mermaid/src/accessibility.ts new file mode 100644 index 0000000000..8e073aa76f --- /dev/null +++ b/packages/mermaid/src/accessibility.ts @@ -0,0 +1,70 @@ +/** + * Accessibility (a11y) functions, types, helpers + * @see https://www.w3.org/WAI/ + * @see https://www.w3.org/TR/wai-aria-1.1/ + * @see https://www.w3.org/TR/svg-aam-1.0/ + * + */ +import { D3Element } from './mermaidAPI'; + +import isEmpty from 'lodash-es/isEmpty.js'; + +/** + * SVG element role: + * The SVG element role _should_ be set to 'graphics-document' per SVG standard + * but in practice is not always done by browsers, etc. (As of 2022-12-08). + * A fallback role of 'document' should be set for those browsers, etc., that only support ARIA 1.0. + * + * @see https://www.w3.org/TR/svg-aam-1.0/#roleMappingGeneralRules + * @see https://www.w3.org/TR/graphics-aria-1.0/#graphics-document + */ +const SVG_ROLE = 'graphics-document document'; + +/** + * Add role and aria-roledescription to the svg element + * + * @param svg - d3 object that contains the SVG HTML element + * @param diagramType - diagram name for to the aria-roledescription + */ +export function setA11yDiagramInfo(svg: D3Element, diagramType: string | null | undefined) { + svg.attr('role', SVG_ROLE); + if (!isEmpty(diagramType)) { + svg.attr('aria-roledescription', diagramType); + } +} +/** + * Add an accessible title and/or description element to a chart. + * The title is usually not displayed and the description is never displayed. + * + * The following charts display their title as a visual and accessibility element: gantt + * + * @param svg - d3 node to insert the a11y title and desc info + * @param a11yTitle - a11y title. null and undefined are meaningful: means to skip it + * @param a11yDesc - a11y description. null and undefined are meaningful: means to skip it + * @param baseId - id used to construct the a11y title and description id + */ +export function addSVGa11yTitleDescription( + svg: D3Element, + a11yTitle: string | null | undefined, + a11yDesc: string | null | undefined, + baseId: string +) { + if (svg.insert === undefined) { + return; + } + + if (a11yTitle || a11yDesc) { + if (a11yDesc) { + const descId = 'chart-desc-' + baseId; + svg.attr('aria-describedby', descId); + svg.insert('desc', ':first-child').attr('id', descId).text(a11yDesc); + } + if (a11yTitle) { + const titleId = 'chart-title-' + baseId; + svg.attr('aria-labelledby', titleId); + svg.insert('title', ':first-child').attr('id', titleId).text(a11yTitle); + } + } else { + return; + } +} diff --git a/packages/mermaid/src/dagre-wrapper/clusters.js b/packages/mermaid/src/dagre-wrapper/clusters.js index 40729deadc..57c3ff5138 100644 --- a/packages/mermaid/src/dagre-wrapper/clusters.js +++ b/packages/mermaid/src/dagre-wrapper/clusters.js @@ -59,11 +59,9 @@ const rect = (parent, node) => { // Center the label label.attr( 'transform', - 'translate(' + - (node.x - bbox.width / 2) + - ', ' + - (node.y - node.height / 2 + node.padding / 3) + - ')' + // This puts the labal on top of the box instead of inside it + // 'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2 - bbox.height) + ')' + 'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2) + ')' ); const rectBox = rect.node().getBBox(); diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index 5213d06846..bb22cee838 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -130,9 +130,21 @@ export const positionEdgeLabel = (edge, paths) => { if (path) { // // debugger; const pos = utils.calcLabelPosition(path); - log.info('Moving label from (', x, ',', y, ') to (', pos.x, ',', pos.y, ') abc78'); - // x = pos.x; - // y = pos.y; + log.info( + 'Moving label ' + edge.label + ' from (', + x, + ',', + y, + ') to (', + pos.x, + ',', + pos.y, + ') abc78' + ); + if (paths.updatedPath) { + x = pos.x; + y = pos.y; + } } el.attr('transform', 'translate(' + x + ', ' + y + ')'); } @@ -463,7 +475,7 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph .attr('style', edge.style); // DEBUG code, adds a red circle at each edge coordinate - // edge.points.forEach(point => { + // edge.points.forEach((point) => { // elem // .append('circle') // .style('stroke', 'red') diff --git a/packages/mermaid/src/dagre-wrapper/index.js b/packages/mermaid/src/dagre-wrapper/index.js index e2d7d51f46..ce3ef6014a 100644 --- a/packages/mermaid/src/dagre-wrapper/index.js +++ b/packages/mermaid/src/dagre-wrapper/index.js @@ -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 { diff --git a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js index 5722f7cc08..875ac4deff 100644 --- a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js +++ b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js @@ -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 = {}; diff --git a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js index f594e34309..25fb75d643 100644 --- a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js +++ b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js @@ -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, diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index 23810d1330..3449782e20 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -14,6 +14,8 @@ export interface InjectUtils { export interface DiagramDb { clear?: () => void; setDiagramTitle?: (title: string) => void; + getAccTitle?: () => string; + getAccDescription?: () => string; } export interface DiagramDefinition { diff --git a/packages/mermaid/src/diagram.spec.ts b/packages/mermaid/src/diagram.spec.ts new file mode 100644 index 0000000000..ebe088a868 --- /dev/null +++ b/packages/mermaid/src/diagram.spec.ts @@ -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' + ); + }); +}); diff --git a/packages/mermaid/src/diagrams/c4/c4Renderer.js b/packages/mermaid/src/diagrams/c4/c4Renderer.js index 6490a8e199..a51fe0b6af 100644 --- a/packages/mermaid/src/diagrams/c4/c4Renderer.js +++ b/packages/mermaid/src/diagrams/c4/c4Renderer.js @@ -8,7 +8,6 @@ import * as configApi from '../../config'; import assignWithDepth from '../../assignWithDepth'; import { wrapLabel, calculateTextWidth, calculateTextHeight } from '../../utils'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; let globalBoundaryMaxX = 0, globalBoundaryMaxY = 0; @@ -675,7 +674,6 @@ export const draw = function (_text, id, _version, diagObj) { (height + extraVertForTitle) ); - addSVGAccessibilityFields(parser.yy, diagram, id); log.debug(`models:`, box); }; diff --git a/packages/mermaid/src/diagrams/c4/svgDraw.js b/packages/mermaid/src/diagrams/c4/svgDraw.js index d9727f0747..d3d66a80d3 100644 --- a/packages/mermaid/src/diagrams/c4/svgDraw.js +++ b/packages/mermaid/src/diagrams/c4/svgDraw.js @@ -35,183 +35,6 @@ export const drawImage = function (elem, width, height, x, y, link) { imageElem.attr('xlink:href', sanitizedLink); }; -export const drawEmbeddedImage = function (elem, x, y, link) { - const imageElem = elem.append('use'); - imageElem.attr('x', x); - imageElem.attr('y', y); - var sanitizedLink = sanitizeUrl(link); - imageElem.attr('xlink:href', '#' + sanitizedLink); -}; - -export const drawText = function (elem, textData) { - let prevTextHeight = 0, - textHeight = 0; - const lines = textData.text.split(common.lineBreakRegex); - - let textElems = []; - let dy = 0; - let yfunc = () => textData.y; - if ( - textData.valign !== undefined && - textData.textMargin !== undefined && - textData.textMargin > 0 - ) { - switch (textData.valign) { - case 'top': - case 'start': - yfunc = () => Math.round(textData.y + textData.textMargin); - break; - case 'middle': - case 'center': - yfunc = () => - Math.round(textData.y + (prevTextHeight + textHeight + textData.textMargin) / 2); - break; - case 'bottom': - case 'end': - yfunc = () => - Math.round( - textData.y + - (prevTextHeight + textHeight + 2 * textData.textMargin) - - textData.textMargin - ); - break; - } - } - if ( - textData.anchor !== undefined && - textData.textMargin !== undefined && - textData.width !== undefined - ) { - switch (textData.anchor) { - case 'left': - case 'start': - textData.x = Math.round(textData.x + textData.textMargin); - textData.anchor = 'start'; - textData.dominantBaseline = 'text-after-edge'; - textData.alignmentBaseline = 'middle'; - break; - case 'middle': - case 'center': - textData.x = Math.round(textData.x + textData.width / 2); - textData.anchor = 'middle'; - textData.dominantBaseline = 'middle'; - textData.alignmentBaseline = 'middle'; - break; - case 'right': - case 'end': - textData.x = Math.round(textData.x + textData.width - textData.textMargin); - textData.anchor = 'end'; - textData.dominantBaseline = 'text-before-edge'; - textData.alignmentBaseline = 'middle'; - break; - } - } - for (let [i, line] of lines.entries()) { - if ( - textData.textMargin !== undefined && - textData.textMargin === 0 && - textData.fontSize !== undefined - ) { - dy = i * textData.fontSize; - } - - const textElem = elem.append('text'); - textElem.attr('x', textData.x); - textElem.attr('y', yfunc()); - if (textData.anchor !== undefined) { - textElem - .attr('text-anchor', textData.anchor) - .attr('dominant-baseline', textData.dominantBaseline) - .attr('alignment-baseline', textData.alignmentBaseline); - } - if (textData.fontFamily !== undefined) { - textElem.style('font-family', textData.fontFamily); - } - if (textData.fontSize !== undefined) { - textElem.style('font-size', textData.fontSize); - } - if (textData.fontWeight !== undefined) { - textElem.style('font-weight', textData.fontWeight); - } - if (textData.fill !== undefined) { - textElem.attr('fill', textData.fill); - } - if (textData.class !== undefined) { - textElem.attr('class', textData.class); - } - if (textData.dy !== undefined) { - textElem.attr('dy', textData.dy); - } else if (dy !== 0) { - textElem.attr('dy', dy); - } - - if (textData.tspan) { - const span = textElem.append('tspan'); - span.attr('x', textData.x); - if (textData.fill !== undefined) { - span.attr('fill', textData.fill); - } - span.text(line); - } else { - textElem.text(line); - } - if ( - textData.valign !== undefined && - textData.textMargin !== undefined && - textData.textMargin > 0 - ) { - textHeight += (textElem._groups || textElem)[0][0].getBBox().height; - prevTextHeight = textHeight; - } - - textElems.push(textElem); - } - - return textElems; -}; - -export const drawLabel = function (elem, txtObject) { - /** - * @param {any} x - * @param {any} y - * @param {any} width - * @param {any} height - * @param {any} cut - * @returns {any} - */ - function genPoints(x, y, width, height, cut) { - return ( - x + - ',' + - y + - ' ' + - (x + width) + - ',' + - y + - ' ' + - (x + width) + - ',' + - (y + height - cut) + - ' ' + - (x + width - cut * 1.2) + - ',' + - (y + height) + - ' ' + - x + - ',' + - (y + height) - ); - } - const polygon = elem.append('polygon'); - polygon.attr('points', genPoints(txtObject.x, txtObject.y, txtObject.width, txtObject.height, 7)); - polygon.attr('class', 'labelBox'); - - txtObject.y = txtObject.y + txtObject.height / 2; - - drawText(elem, txtObject); - return polygon; -}; - export const drawRels = (elem, rels, conf) => { const relsElem = elem.append('g'); let i = 0; @@ -744,23 +567,6 @@ export const insertArrowCrossHead = function (elem) { // this is actual shape for arrowhead }; -export const getTextObj = function () { - return { - x: 0, - y: 0, - fill: undefined, - anchor: undefined, - style: '#666', - width: undefined, - height: undefined, - textMargin: 0, - rx: 0, - ry: 0, - tspan: true, - valign: undefined, - }; -}; - export const getNoteRect = function () { return { x: 0, @@ -895,13 +701,10 @@ const _drawTextCandidateFunc = (function () { export default { drawRect, - drawText, - drawLabel, drawBoundary, drawC4Shape, drawRels, drawImage, - drawEmbeddedImage, insertArrowHead, insertArrowEnd, insertArrowFilledHead, @@ -910,7 +713,6 @@ export default { insertDatabaseIcon, insertComputerIcon, insertClockIcon, - getTextObj, getNoteRect, - sanitizeUrl, + sanitizeUrl, // TODO why is this exported? }; diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.js b/packages/mermaid/src/diagrams/class/classRenderer-v2.js index c4e7e02913..d95c29fd5f 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.js +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.js @@ -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'; @@ -8,7 +8,6 @@ import { curveLinear } from 'd3'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import common from '../common/common'; -import addSVGAccessibilityFields from '../../accessibility'; const sanitizeText = (txt) => common.sanitizeText(txt, getConfig()); @@ -451,7 +450,6 @@ export const draw = function (text, id, _version, diagObj) { } } - addSVGAccessibilityFields(diagObj.db, svg, id); // If node has a link, wrap it in an anchor SVG object. // const keys = Object.keys(classes); // keys.forEach(function(key) { diff --git a/packages/mermaid/src/diagrams/class/classRenderer.js b/packages/mermaid/src/diagrams/class/classRenderer.js index c500a73a70..80a7f26e46 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer.js +++ b/packages/mermaid/src/diagrams/class/classRenderer.js @@ -5,7 +5,6 @@ import { log } from '../../logger'; import svgDraw from './svgDraw'; import { configureSvgSize } from '../../setupGraphViewbox'; import { getConfig } from '../../config'; -import addSVGAccessibilityFields from '../../accessibility'; let idCache = {}; const padding = 20; @@ -272,7 +271,6 @@ export const draw = function (text, id, _version, diagObj) { const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; log.debug(`viewBox ${vBox}`); diagram.attr('viewBox', vBox); - addSVGAccessibilityFields(diagObj.db, diagram, id); }; export default { diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 194a9a4c05..628908aab6 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -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; }; diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js index 101beebb96..e3b12d087c 100644 --- a/packages/mermaid/src/diagrams/er/erRenderer.js +++ b/packages/mermaid/src/diagrams/er/erRenderer.js @@ -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'; @@ -6,9 +6,8 @@ import { log } from '../../logger'; import utils from '../../utils'; import erMarkers from './erMarkers'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; import { parseGenericTypes } from '../common/common'; -import { v4 as uuid4 } from 'uuid'; +import { v5 as uuid5 } from 'uuid'; /** Regex used to remove chars from the entity name so the result can be used in an id */ const BAD_ID_CHARS_REGEXP = /[^\dA-Za-z](\W)*/g; @@ -642,13 +641,26 @@ export const draw = function (text, id, _version, diagObj) { configureSvgSize(svg, height, width, conf.useMaxWidth); svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`); - - addSVGAccessibilityFields(diagObj.db, svg, id); }; // draw +/** + * UUID namespace for ER diagram IDs + * + * This can be generated via running: + * + * ```js + * const { v5: uuid5 } = await import('uuid'); + * uuid5( + * 'https://mermaid-js.github.io/mermaid/syntax/entityRelationshipDiagram.html', + * uuid5.URL + * ); + * ``` + */ +const MERMAID_ERDIAGRAM_UUID = '28e9f9db-3c8d-5aa5-9faf-44286ae5937c'; + /** * Return a unique id based on the given string. Start with the prefix, then a hyphen, then the - * simplified str, then a hyphen, then a unique uuid. (Hyphens are only included if needed.) + * simplified str, then a hyphen, then a unique uuid based on the str. (Hyphens are only included if needed.) * Although the official XML standard for ids says that many more characters are valid in the id, * this keeps things simple by accepting only A-Za-z0-9. * @@ -659,7 +671,11 @@ export const draw = function (text, id, _version, diagObj) { */ export function generateId(str = '', prefix = '') { const simplifiedStr = str.replace(BAD_ID_CHARS_REGEXP, ''); - return `${strWithHyphen(prefix)}${strWithHyphen(simplifiedStr)}${uuid4()}`; + // we use `uuid v5` so that UUIDs are consistent given a string. + return `${strWithHyphen(prefix)}${strWithHyphen(simplifiedStr)}${uuid5( + str, + MERMAID_ERDIAGRAM_UUID + )}`; } /** diff --git a/packages/mermaid/src/diagrams/er/erRenderer.spec.ts b/packages/mermaid/src/diagrams/er/erRenderer.spec.ts new file mode 100644 index 0000000000..ca0f62bd28 --- /dev/null +++ b/packages/mermaid/src/diagrams/er/erRenderer.spec.ts @@ -0,0 +1,12 @@ +import { generateId } from './erRenderer'; + +describe('erRenderer', () => { + describe('generateId', () => { + it('should be deterministic', () => { + const id1 = generateId('hello world', 'my-prefix'); + const id2 = generateId('hello world', 'my-prefix'); + + expect(id1).toBe(id2); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison index f0411fd724..ada176a8d4 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison @@ -29,9 +29,9 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili "erDiagram" return 'ER_DIAGRAM'; "{" { this.begin("block"); return 'BLOCK_START'; } \s+ /* skip whitespace in block */ -\b((?:PK)|(?:FK))\b return 'ATTRIBUTE_KEY' +\b((?:PK)|(?:FK)|(?:UK))\b return 'ATTRIBUTE_KEY' (.*?)[~](.*?)*[~] return 'ATTRIBUTE_WORD'; -[A-Za-z][A-Za-z0-9\-_\[\]]* return 'ATTRIBUTE_WORD' +[A-Za-z][A-Za-z0-9\-_\[\]\(\)]* return 'ATTRIBUTE_WORD' \"[^"]*\" return 'COMMENT'; [\n]+ /* nothing */ "}" { this.popState(); return 'BLOCK_STOP'; } diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js index eb738fe4b2..ba00c602e3 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js @@ -176,17 +176,18 @@ describe('when parsing ER diagram it...', function () { expect(entities[entity].attributes.length).toBe(1); }); - it('should allow an entity with attribute starting with fk or pk and a comment', function () { + it('should allow an entity with attribute starting with fk, pk or uk and a comment', function () { const entity = 'BOOK'; const attribute1 = 'int fk_title FK'; const attribute2 = 'string pk_author PK'; - const attribute3 = 'float pk_price PK "comment"'; + const attribute3 = 'string uk_address UK'; + const attribute4 = 'float pk_price PK "comment"'; erDiagram.parser.parse( - `erDiagram\n${entity} {\n${attribute1} \n\n${attribute2}\n${attribute3}\n}` + `erDiagram\n${entity} {\n${attribute1} \n\n${attribute2}\n${attribute3}\n${attribute4}\n}` ); const entities = erDb.getEntities(); - expect(entities[entity].attributes.length).toBe(3); + expect(entities[entity].attributes.length).toBe(4); }); it('should allow an entity with attribute that has a generic type', function () { @@ -214,6 +215,19 @@ describe('when parsing ER diagram it...', function () { expect(entities[entity].attributes.length).toBe(2); }); + it('should allow an entity with attribute that is a limited length string', function () { + const entity = 'BOOK'; + const attribute1 = 'character(10) isbn FK'; + const attribute2 = 'varchar(5) postal_code "Five digits"'; + + erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute1}\n${attribute2}\n}`); + const entities = erDb.getEntities(); + expect(Object.keys(entities).length).toBe(1); + expect(entities[entity].attributes.length).toBe(2); + expect(entities[entity].attributes[0].attributeType).toBe('character(10)'); + expect(entities[entity].attributes[1].attributeType).toBe('varchar(5)'); + }); + it('should allow an entity with multiple attributes to be defined', function () { const entity = 'BOOK'; const attribute1 = 'string title'; @@ -323,34 +337,34 @@ describe('when parsing ER diagram it...', function () { expect(Object.keys(erDb.getEntities()).length).toBe(1); }); - it('should allow for a accessibility title and description (accDescr)', function () { + describe('accessible title and description', () => { const teacherRole = 'is teacher of'; const line1 = `TEACHER }o--o{ STUDENT : "${teacherRole}"`; - erDiagram.parser.parse( - `erDiagram + it('should allow for a accessibility title and description (accDescr)', function () { + erDiagram.parser.parse( + `erDiagram accTitle: graph title accDescr: this graph is about stuff ${line1}` - ); - expect(erDb.getAccTitle()).toBe('graph title'); - expect(erDb.getAccDescription()).toBe('this graph is about stuff'); - }); - - it('should allow for a accessibility title and multi line description (accDescr)', function () { - const teacherRole = 'is teacher of'; - const line1 = `TEACHER }o--o{ STUDENT : "${teacherRole}"`; + ); + expect(erDb.getAccTitle()).toBe('graph title'); + expect(erDb.getAccDescription()).toBe('this graph is about stuff'); + }); - erDiagram.parser.parse( - `erDiagram + it('parses a multi line description (accDescr)', function () { + erDiagram.parser.parse( + `erDiagram accTitle: graph title - accDescr { - this graph is about stuff - }\n + accDescr { this graph is + about + stuff + }\n ${line1}` - ); - expect(erDb.getAccTitle()).toBe('graph title'); - expect(erDb.getAccDescription()).toBe('this graph is about stuff'); + ); + expect(erDb.getAccTitle()).toEqual('graph title'); + expect(erDb.getAccDescription()).toEqual('this graph is\nabout\nstuff'); + }); }); it('should allow more than one relationship between the same two entities', function () { diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js index be3fffa0c8..29c7ba07a9 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -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'; @@ -11,7 +11,6 @@ import { log } from '../../logger'; import common, { evaluate } from '../common/common'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; const conf = {}; export const setConf = function (cnf) { @@ -431,9 +430,6 @@ export const draw = function (text, id, _version, diagObj) { // Set up an SVG group so that we can translate the final graph. const svg = root.select(`[id="${id}"]`); - // Adds title and description to the flow chart - addSVGAccessibilityFields(diagObj.db, svg, id); - // Run the renderer. This is what draws the final graph. const element = root.select('#' + id + ' g'); render(element, g, ['point', 'circle', 'cross'], 'flowchart', id); diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index 4b3232189e..63234b57cc 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -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'; @@ -9,7 +9,6 @@ import common, { evaluate } from '../common/common'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import flowChartShapes from './flowChartShapes'; -import addSVGAccessibilityFields from '../../accessibility'; const conf = {}; export const setConf = function (cnf) { @@ -417,9 +416,6 @@ export const draw = function (text, id, _version, diagObj) { // Set up an SVG group so that we can translate the final graph. const svg = root.select(`[id="${id}"]`); - // Adds title and description to the flow chart - addSVGAccessibilityFields(diagObj.db, svg, id); - // Run the renderer. This is what draws the final graph. const element = root.select('#' + id + ' g'); render(element, g); diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js index 5c20947376..7726ce0f79 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js @@ -1,6 +1,5 @@ import flowDb from '../flowDb'; import flow from './flow'; -import filter from 'lodash-es/filter'; import { setConfig } from '../../../config'; setConfig({ diff --git a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js index 5ba6a5361e..ae6f178b89 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js @@ -1,6 +1,5 @@ import flowDb from '../flowDb'; import flow from './flow'; -import filter from 'lodash-es/filter'; import { setConfig } from '../../../config'; setConfig({ diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js index ab2407ecdc..faec35a862 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js +++ b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js @@ -19,7 +19,6 @@ import { import common from '../common/common'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; export const setConf = function () { log.debug('Something is calling, setConf, remove the call'); @@ -116,8 +115,6 @@ export const draw = function (text, id, version, diagObj) { .attr('y', conf.titleTopMargin) .attr('class', 'titleText'); - addSVGAccessibilityFields(diagObj.db, svg, id); - /** * @param tasks * @param pageWidth diff --git a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js index 6874363ad2..787eb24901 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js +++ b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js @@ -2,7 +2,6 @@ import { select } from 'd3'; import { getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI'; import { log } from '../../logger'; import utils from '../../utils'; -import addSVGAccessibilityFields from '../../accessibility'; let allCommitsDict = {}; @@ -506,9 +505,6 @@ export const draw = function (txt, id, ver, diagObj) { const diagram = select(`[id="${id}"]`); - // Adds title and description to the flow chart - addSVGAccessibilityFields(diagObj.db, diagram, id); - drawCommits(diagram, allCommitsDict, false); if (gitGraphConfig.showBranches) { drawBranches(diagram, branches); diff --git a/packages/mermaid/src/diagrams/pie/pieRenderer.js b/packages/mermaid/src/diagrams/pie/pieRenderer.js index c5d86ad65c..83f301207a 100644 --- a/packages/mermaid/src/diagrams/pie/pieRenderer.js +++ b/packages/mermaid/src/diagrams/pie/pieRenderer.js @@ -3,7 +3,6 @@ import { select, scaleOrdinal, pie as d3pie, arc } from 'd3'; import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; import * as configApi from '../../config'; -import addSVGAccessibilityFields from '../../accessibility'; let conf = configApi.getConfig(); @@ -53,7 +52,6 @@ export const draw = (txt, id, _version, diagObj) => { const diagram = root.select('#' + id); configureSvgSize(diagram, height, width, conf.pie.useMaxWidth); - addSVGAccessibilityFields(diagObj.db, diagram, id); // Set viewBox elem.setAttribute('viewBox', '0 0 ' + width + ' ' + height); diff --git a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js index a0019f46bd..9fd746bd1a 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js +++ b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js @@ -6,7 +6,6 @@ import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; import markers from './requirementMarkers'; import { getConfig } from '../../config'; -import addSVGAccessibilityFields from '../../accessibility'; let conf = {}; let relCnt = 0; @@ -363,8 +362,6 @@ export const draw = (text, id, _version, diagObj) => { configureSvgSize(svg, height, width, conf.useMaxWidth); svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`); - // Adds title and description to the requirements diagram - addSVGAccessibilityFields(diagObj.db, svg, id); }; export default { diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 9422a5f37f..6395940b05 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -1,8 +1,62 @@ +import { vi } from 'vitest'; + import * as configApi from '../../config'; import mermaidAPI from '../../mermaidAPI'; import Diagram from '../../Diagram'; import { addDiagrams } from '../../diagram-api/diagram-orchestration'; + +/** + * Sequence diagrams require their own very special version of a mocked d3 module + * diagrams/sequence/svgDraw uses statements like this with d3 nodes: (note the [0][0]) + * + * // in drawText(...) + * textHeight += (textElem._groups || textElem)[0][0].getBBox().height; + */ +vi.mock('d3', () => { + const NewD3 = function () { + function returnThis() { + return this; + } + return { + append: function () { + return NewD3(); + }, + lower: returnThis, + attr: returnThis, + style: returnThis, + text: returnThis, + // [0][0] (below) is required by drawText() in packages/mermaid/src/diagrams/sequence/svgDraw.js + 0: { + 0: { + getBBox: function () { + return { + height: 10, + width: 20, + }; + }, + }, + }, + }; + }; + + return { + select: function () { + return new NewD3(); + }, + + selectAll: function () { + return new NewD3(); + }, + + curveBasis: 'basis', + curveLinear: 'linear', + curveCardinal: 'cardinal', + }; +}); +// ------------------------------- + addDiagrams(); + /** * @param conf * @param key diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 738b865409..1f6164b92a 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -9,7 +9,6 @@ import * as configApi from '../../config'; import assignWithDepth from '../../assignWithDepth'; import utils from '../../utils'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; import Diagram from '../../Diagram'; let conf = {}; @@ -904,7 +903,6 @@ export const draw = function (_text: string, id: string, _version: string, diagO (height + extraVertForTitle) ); - addSVGAccessibilityFields(diagObj.db, diagram, id); log.debug(`models:`, bounds.models); }; diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js index 580dafe896..8e5f5f32bc 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js @@ -1,5 +1,31 @@ +import { vi } from 'vitest'; import svgDraw from './svgDraw'; -import { MockD3 } from 'd3'; + +// This is the only place that uses this mock +export const MockD3 = (name, parent) => { + const children = []; + const elem = { + get __children() { + return children; + }, + get __name() { + return name; + }, + get __parent() { + return parent; + }, + }; + elem.append = (name) => { + const mockElem = MockD3(name, elem); + children.push(mockElem); + return mockElem; + }; + elem.lower = vi.fn(() => elem); + elem.attr = vi.fn(() => elem); + elem.text = vi.fn(() => elem); + elem.style = vi.fn(() => elem); + return elem; +}; describe('svgDraw', function () { describe('drawRect', function () { diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index 78e38726eb..ebe18535d9 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js @@ -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'; @@ -6,7 +6,7 @@ import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; import utils from '../../utils'; -import addSVGAccessibilityFields from '../../accessibility'; + import { DEFAULT_DIAGRAM_DIRECTION, DEFAULT_NESTED_DOC_DIR, @@ -470,7 +470,6 @@ export const draw = function (text, id, _version, diag) { label.insertBefore(rect, label.firstChild); // } } - addSVGAccessibilityFields(diag.db, svg, id); }; export default { diff --git a/packages/mermaid/src/diagrams/state/stateRenderer.js b/packages/mermaid/src/diagrams/state/stateRenderer.js index 4eeede12e3..8d410fdd98 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer.js @@ -6,7 +6,6 @@ import common from '../common/common'; import { drawState, addTitleAndBox, drawEdge } from './shapes'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; // TODO Move conf object to main conf in mermaidAPI let conf; @@ -97,7 +96,6 @@ export const draw = function (text, id, _version, diagObj) { 'viewBox', `${bounds.x - conf.padding} ${bounds.y - conf.padding} ` + width + ' ' + height ); - addSVGAccessibilityFields(diagObj.db, diagram, id); }; const getLabelWidth = (text) => { return text ? text.length * conf.fontSizeFactor : 1; diff --git a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts index b22192101e..df46fc9c6f 100644 --- a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts +++ b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts @@ -3,7 +3,6 @@ import { select } from 'd3'; import svgDraw from './svgDraw'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; export const setConf = function (cnf) { const keys = Object.keys(cnf); @@ -121,8 +120,6 @@ export const draw = function (text, id, version, diagObj) { diagram.attr('viewBox', `${box.startx} -25 ${width} ${height + extraVertForTitle}`); diagram.attr('preserveAspectRatio', 'xMinYMin meet'); diagram.attr('height', height + extraVertForTitle + 25); - - addSVGAccessibilityFields(diagObj.db, diagram, id); }; export const bounds = { diff --git a/packages/mermaid/src/docs.mts b/packages/mermaid/src/docs.mts index 846e92212d..17505b6b9e 100644 --- a/packages/mermaid/src/docs.mts +++ b/packages/mermaid/src/docs.mts @@ -46,7 +46,7 @@ import flatmap from 'unist-util-flatmap'; const MERMAID_MAJOR_VERSION = ( JSON.parse(readFileSync('../mermaid/package.json', 'utf8')).version as string ).split('.')[0]; -const CDN_URL = 'https://unpkg.com'; // https://cdn.jsdelivr.net/npm +const CDN_URL = 'https://cdn.jsdelivr.net/npm'; // 'https://unpkg.com'; const verifyOnly: boolean = process.argv.includes('--verify'); const git: boolean = process.argv.includes('--git'); @@ -260,7 +260,7 @@ const transformHtml = (filename: string) => { }; const getGlobs = (globs: string[]): string[] => { - globs.push('!**/dist', '!**/redirect.spec.ts'); + globs.push('!**/dist', '!**/redirect.spec.ts', '!**/landing'); if (!vitepress) { globs.push('!**/.vitepress', '!**/vite.config.ts', '!src/docs/index.md'); } diff --git a/packages/mermaid/src/docs/.vitepress/config.ts b/packages/mermaid/src/docs/.vitepress/config.ts index 216541d52e..9b5f1547e8 100644 --- a/packages/mermaid/src/docs/.vitepress/config.ts +++ b/packages/mermaid/src/docs/.vitepress/config.ts @@ -14,37 +14,34 @@ export default defineConfig({ lang: 'en-US', title: 'Mermaid', description: 'Create diagrams and visualizations using text and code.', - base: '/mermaid/', + base: '/', markdown: allMarkdownTransformers, - head: [['link', { rel: 'icon', type: 'image/x-icon', href: '/mermaid/favicon.ico' }]], + head: [['link', { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]], themeConfig: { nav: nav(), editLink: { pattern: 'https://github.com/mermaid-js/mermaid/edit/develop/packages/mermaid/src/docs/:path', text: 'Edit this page on GitHub', }, - sidebar: { '/': sidebarAll(), }, + socialLinks: [ + { icon: 'github', link: 'https://github.com/mermaid-js/mermaid' }, + { icon: 'slack', link: 'https://mermaid-talk.slack.com' }, + ], }, }); function nav() { return [ - { text: 'Intro', link: '/intro/', activeMatch: '/intro/' }, + { text: 'Docs', link: '/intro/', activeMatch: '/intro/' }, { - text: 'Configuration', - link: '/config/configuration', + text: 'Tutorials', + link: '/config/Tutorials', activeMatch: '/config/', }, - { text: 'Syntax', link: '/syntax/classDiagram', activeMatch: '/syntax/' }, - { text: 'Misc', link: '/misc/integrations', activeMatch: '/misc/' }, - { - text: 'Community', - link: '/community/n00b-overview', - activeMatch: '/community/', - }, + { text: 'Integrations', link: '/misc/integrations', activeMatch: '/misc/' }, { text: version, items: [ diff --git a/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue b/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue index 9ae9c9f3b0..85c13393c2 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue +++ b/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue @@ -20,11 +20,6 @@ const props = defineProps({ const svg = ref(null); let mut = null; -const mermaidConfig = { - securityLevel: 'loose', - startOnLoad: false, -}; - onMounted(async () => { mut = new MutationObserver(() => renderChart()); mut.observe(document.documentElement, { attributes: true }); @@ -58,9 +53,20 @@ onUnmounted(() => mut.disconnect()); const renderChart = async () => { console.log('rendering chart' + props.id + props.graph); const hasDarkClass = document.documentElement.classList.contains('dark'); - mermaidConfig.theme = hasDarkClass ? 'dark' : 'default'; + const mermaidConfig = { + securityLevel: 'loose', + startOnLoad: false, + theme: hasDarkClass ? 'dark' : 'default', + }; console.log({ mermaidConfig }); - svg.value = await render(props.id, decodeURIComponent(props.graph), mermaidConfig); + let svgCode = await render(props.id, decodeURIComponent(props.graph), mermaidConfig); + // This is a hack to force v-html to re-render, otherwise the diagram disappears + // when **switching themes** or **reloading the page**. + // The cause is that the diagram is deleted during rendering (out of Vue's knowledge). + // Because svgCode does NOT change, v-html does not re-render. + // This is not required for all diagrams, but it is required for c4c, mindmap and zenuml. + const salt = Math.random().toString(36).substring(7); + svg.value = `${svgCode} ${salt}`; }; diff --git a/packages/mermaid/src/docs/.vitepress/theme/custom.css b/packages/mermaid/src/docs/.vitepress/theme/custom.css index e1ef049cdf..28ef8d3385 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/custom.css +++ b/packages/mermaid/src/docs/.vitepress/theme/custom.css @@ -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; diff --git a/packages/mermaid/src/docs/.vitepress/theme/index.ts b/packages/mermaid/src/docs/.vitepress/theme/index.ts index efb065feaa..ef929aa5db 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/index.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/index.ts @@ -18,7 +18,7 @@ export default { if (newPath) { console.log(`Redirecting to ${newPath} from ${window.location}`); // router.go isn't loading the ID properly. - window.location.href = `/mermaid/${newPath}`; + window.location.href = `/${newPath}`; } } catch (e) {} }; diff --git a/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts b/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts index b287346f94..e9a038ec4c 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts @@ -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 => { + await init; mermaid.initialize(config); const svg = await mermaid.renderAsync(id, code); return svg; diff --git a/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts b/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts index c263641082..6070abee44 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts @@ -8,6 +8,7 @@ test.each([ ['http://localhost:1234/mermaid/#/flowchart.md', 'syntax/flowchart.html'], ['http://localhost/mermaid/#/flowchart.md', 'syntax/flowchart.html'], ['https://mermaid-js.github.io/mermaid/#/flowchart.md', 'syntax/flowchart.html'], + ['https://mermaid.js.org/#/flowchart.md', 'syntax/flowchart.html'], ['https://mermaid-js.github.io/mermaid/#/./flowchart', 'syntax/flowchart.html'], ['https://mermaid-js.github.io/mermaid/#/flowchart', 'syntax/flowchart.html'], ['https://mermaid-js.github.io/mermaid/#flowchart', 'syntax/flowchart.html'], @@ -31,7 +32,4 @@ test.each([ test('should throw for invalid URL', () => { // Not mermaid domain expect(() => getRedirect('https://www.google.com')).toThrowError(); - - // Not `/mermaid/` path - expect(() => getRedirect('http://localhost/#/flowchart.md')).toThrowError(); }); diff --git a/packages/mermaid/src/docs/.vitepress/theme/redirect.ts b/packages/mermaid/src/docs/.vitepress/theme/redirect.ts index ca4606be0d..58537b0ef5 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/redirect.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/redirect.ts @@ -10,8 +10,9 @@ export interface Redirect { const getBaseFile = (link: string): Redirect => { const url = new URL(link); if ( - (url.hostname !== 'mermaid-js.github.io' && url.hostname !== 'localhost') || - url.pathname !== '/mermaid/' + url.hostname !== 'mermaid-js.github.io' && + url.hostname !== 'mermaid.js.org' && + url.hostname !== 'localhost' ) { throw new Error('Not mermaidjs url'); } diff --git a/packages/mermaid/src/docs/community/newDiagram.md b/packages/mermaid/src/docs/community/newDiagram.md index 74026b3ff3..75e17e4c9b 100644 --- a/packages/mermaid/src/docs/community/newDiagram.md +++ b/packages/mermaid/src/docs/community/newDiagram.md @@ -8,8 +8,8 @@ This would be to define a jison grammar for the new diagram type. That should st For instance: -- the flowchart starts with the keyword graph. -- the sequence diagram starts with the keyword sequenceDiagram +- the flowchart starts with the keyword _graph_ +- the sequence diagram starts with the keyword _sequenceDiagram_ #### Store data found during parsing @@ -55,7 +55,12 @@ Place the renderer in the diagram folder. ### Step 3: Detection of the new diagram type -The second thing to do is to add the capability to detect the new new diagram to type to the detectType in utils.js. The detection should return a key for the new diagram type. +The second thing to do is to add the capability to detect the new diagram to type to the detectType in utils.js. The detection should return a key for the new diagram type. +[This key will be used to as the aria roledescription](#aria-roledescription), so it should be a word that clearly describes the diagram type. +For example, if your new diagram use a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader +would voice that as "U-M-L Deployment diagram." Another good key would be "deploymentDiagram" because that would be voiced as "Deployment Diagram." A bad key would be "deployment" because that would not sufficiently describe the diagram. + +Note that the diagram type key does not have to be the same as the diagram keyword chosen for the [grammar](#grammar), but it is helpful if they are the same. ### Step 4: The final piece - triggering the rendering @@ -163,19 +168,23 @@ It is probably a good idea to keep the handling similar to this in your new diag ## Accessibility -The syntax for adding title and description looks like this: +Mermaid automatically adds the following accessibility information for the diagram SVG HTML element: -``` -accTitle: The title -accDescr: The description +- aria-roledescription +- accessible title +- accessible description -accDescr { - Syntax for a description text - written on multiple lines. -} -``` +### aria-roledescription + +The aria-roledescription is automatically set to [the diagram type](#step-3--detection-of-the-new-diagram-type) and inserted into the SVG element. + +See [the definition of aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) in [the Accessible Rich Internet Applications W3 standard.](https://www.w3.org/WAI/standards-guidelines/aria/) + +### accessible title and description + +The syntax for accessible titles and descriptions is described in [the Accessibility documenation section.](../config/accessibility.md) -In a similar way to the directives the jison syntax are quite similar between the diagrams. +In a similar way to the directives, the jison syntax are quite similar between the diagrams. ```jison @@ -213,18 +222,7 @@ import { } from '../../commonDb'; ``` -For rendering the accessibility tags you have again an existing function you can use. - -**In the renderer:** - -```js -import addSVGAccessibilityFields from '../../accessibility'; - -/* ... */ - -// Adds title and description to the flow chart -addSVGAccessibilityFields(parser.yy, svg, id); -``` +The accessibility title and description are inserted into the SVG element in the `render` function in mermaidAPI. ## Theming diff --git a/packages/mermaid/src/docs/config/accessibility.md b/packages/mermaid/src/docs/config/accessibility.md index ade20a8395..e7947adec9 100644 --- a/packages/mermaid/src/docs/config/accessibility.md +++ b/packages/mermaid/src/docs/config/accessibility.md @@ -4,104 +4,161 @@ Now with Mermaid library in much wider use, we have started to work towards more accessible features, based on the feedback from the community. -To begin with, we have added a new feature to Mermaid library, which is to support accessibility options, **Accessibility Title** and **Accessibility Description**. +Adding accessibility means that the rich information communicated by visual diagrams can be made available to those using assistive technologies (and of course to search engines). +[Read more about Accessible Rich Internet Applications and the W3 standards.](https://www.w3.org/WAI/standards-guidelines/aria/) -This support for accessibility options is available for all the diagrams/chart types. Also, we have tired to keep the same format for the accessibility options, so that it is easy to understand and maintain. +Mermaid will automatically insert the [aria-roledescription](#aria-roledescription) and, if provided in the diagram text by the diagram author, the [accessible title and description.](#accessible-title-and-description) -## Defining Accessibility Options +### aria-roledescription -### Single line accessibility values +The [aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) for the SVG HTML element is set to the diagram type key. (Note this may be slightly different than the keyword used for the diagram in the diagram text.) -The diagram authors can now add the accessibility options in the diagram definition, using the `accTitle` and `accDescr` keywords, where each keyword is followed by `:` and the string value for title and description like: +For example: The diagram type key for a state diagram is "stateDiagram". Here (a part of) the HTML of the SVG tag that shows the automatically inserted aria-roledscription set to "stateDiagram". _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ -- `accTitle: "Your Accessibility Title"` or -- `accDescr: "Your Accessibility Description"` +```html + +``` -**When these two options are defined, they will add a corresponding `` and `<desc>` tag in the SVG.** +### Accessible Title and Description -Let us take a look at the following example with a flowchart diagram: +Support for accessible titles and descriptions is available for all diagrams/chart types. We have tried to keep the same keywords and format for all diagrams so that it is easy to understand and maintain. -```mermaid-example - graph LR - accTitle: Big decisions - accDescr: Flow chart of the decision making process - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] +The accessible title and description will add `<title>` and `<desc>` elements within the SVG element and the [aria-labelledby](https://www.w3.org/TR/wai-aria/#aria-labelledby) and [aria-describedby](https://www.w3.org/TR/wai-aria/#aria-describedby) attributes in the SVG tag. + +Here is HTML that is generated, showing that the SVG element is labelled by the accessible title (id = `chart-title-mermaid-1668725057758`) +and described by the accessible description (id = `chart-desc-mermaid-1668725057758` ); +and the accessible title element (text = "This is the accessible title") +and the accessible description element (text = "This is an accessible description"). + +_(Note that some of the SVG attributes and the SVG contents are omitted for clarity.)_ +```html +<svg + aria-labelledby="chart-title-mermaid-1668725057758" + aria-describedby="chart-desc-mermaid-1668725057758" + xmlns="http://www.w3.org/2000/svg" + width="100%" + id="mermaid-1668725057758" +> + <title id="chart-title-mermaid-1668725057758">This is the accessible title + This is an accessible description + ``` -See in the code snippet above, the `accTitle` and `accDescr` are defined in the diagram definition. They result in the following tags in SVG code: +Details for the syntax follow. -![Accessibility options rendered inside SVG](img/accessibility-div-example.png) +#### accessible title -### Multi-line Accessibility title/description +The **accessible title** is specified with the **accTitle** _keyword_, followed by a colon (`:`), and the string value for the title. +The string value ends at the end of the line. (It can only be a single line.) -You can also define the accessibility options in a multi-line format, where the keyword is followed by opening curly bracket `{` and then multiple lines, followed by a closing `}`. +Ex: `accTitle: This is a single line title` -`accTitle: My single line title value` (**_single line format_**) +See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. -vs +#### accessible description -`accDescr: { My multi-line description of the diagram }` (**_multi-line format_**) +An accessible description can be 1 line long (a single line) or many lines long. -Let us look at it in the following example, with same flowchart: +The **single line accessible description** is specified with the **accDescr** _keyword_, followed by a colon (`:`), followed by the string value for the description. -```mermaid-example - graph LR - accTitle: Big decisions +Ex: `accDescr: This is a single line description.` - accDescr { - My multi-line description - of the diagram - } +A **multiple line accessible description** _does not have a colon (`:`) after the accDescr keyword_ and is surrounded by curly brackets (`{}`). - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] +Ex: ``` +accDescr { The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: see the big decision and then make the big decision.} +``` -See in the code snippet above, the `accTitle` and `accDescr` are defined in the diagram definition. They result in the following tags in SVG code: - -![Accessibility options rendered inside SVG](img/accessibility-div-example-2.png) +See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. -### Sample Code Snippet for other diagram types +#### accTitle and accDescr Usage Examples -#### Sequence Diagram +- Flowchart with the accessible title "Big Decisions" and the single-line accessible description "Bob's Burgers process for making big decisions" ```mermaid-example - sequenceDiagram - accTitle: My Sequence Diagram - accDescr: My Sequence Diagram Description + graph LR + accTitle: Big Decisions + accDescr: Bob's Burgers process for making big decisions + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] +``` - Alice->>John: Hello John, how are you? - John-->>Alice: Great! - Alice-)John: See you later! +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ + +```html + + Big decisions + Bob's Burgers process for making big decisions + ``` -#### Class Diagram +- Flowchart with the accessible title "Bob's Burger's Making Big Decisions" and the multiple line accessible description "The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision." ```mermaid-example - classDiagram - accTitle: My Class Diagram - accDescr: My Class Diagram Description + graph LR + accTitle: Bob's Burger's Making Big Decisions + accDescr { + The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision. + } + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] +``` - Vehicle <|-- Car +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ + +```html + + Big decisions + + The official Bob's Burgers corporate processes that are used for making very, very big + decisions. This is actually a very simple flow: identify the big decision and then make the big + decision. + + ``` -#### State Diagram +#### Sample Code Snippets for other diagram types -```mermaid-example - stateDiagram - accTitle: My State Diagram - accDescr: My State Diagram Description +##### Class Diagram - s1 --> s2 +```mermaid-example + classDiagram + accTitle: My Class Diagram + accDescr: My Class Diagram Description + Vehicle <|-- Car ``` -#### Entity Relationship Diagram +##### Entity Relationship Diagram ```mermaid-example erDiagram @@ -114,25 +171,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### User Journey Diagram - -```mermaid-example - journey - accTitle: My User Journey Diagram - accDescr: My User Journey Diagram Description - - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me - -``` - -#### Gantt Chart +##### Gantt Chart ```mermaid-example gantt @@ -150,7 +189,27 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Pie Chart +##### Gitgraph + +```mermaid-example + gitGraph + accTitle: My Gitgraph Accessibility Title + accDescr: My Gitgraph Accessibility Description + + commit + commit + branch develop + checkout develop + commit + commit + checkout main + merge develop + commit + commit + +``` + +##### Pie Chart ```mermaid-example pie @@ -165,7 +224,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Requirement Diagram +##### Requirement Diagram ```mermaid-example requirementDiagram @@ -187,22 +246,43 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Gitgraph +##### Sequence Diagram ```mermaid-example - gitGraph - accTitle: My Gitgraph Accessibility Title - accDescr: My Gitgraph Accessibility Description + sequenceDiagram + accTitle: My Sequence Diagram + accDescr: My Sequence Diagram Description - commit - commit - branch develop - checkout develop - commit - commit - checkout main - merge develop - commit - commit + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later! +``` + +##### State Diagram + +```mermaid-example + stateDiagram + accTitle: My State Diagram + accDescr: My State Diagram Description + + s1 --> s2 + +``` + +##### User Journey Diagram + +```mermaid-example + journey + accTitle: My User Journey Diagram + accDescr: My User Journey Diagram Description + + title My working day + section Go to work + Make tea: 5: Me + Go upstairs: 3: Me + Do work: 1: Me, Cat + section Go home + Go downstairs: 5: Me + Sit down: 5: Me ``` diff --git a/packages/mermaid/src/docs/config/theming.md b/packages/mermaid/src/docs/config/theming.md index 78f3546cc0..fb3026fec7 100644 --- a/packages/mermaid/src/docs/config/theming.md +++ b/packages/mermaid/src/docs/config/theming.md @@ -1,30 +1,26 @@ # Theme Configuration -With Version 8.7.0 Mermaid comes out with a system for dynamic and integrated configuration of themes. The intent is to increase the customizability and ease of styling for mermaid diagrams. +Dynamic and integrated theme configuration was introduced in Mermaid version 8.7.0. -The theme can be altered by changing the root level variable `theme` variable in the configuration. To change it for the whole site you must use the `initialize` call. To do it for just for a single diagram you can use the `%%init%%` directive +Themes can now be customized at the site-wide level, or on individual Mermaid diagrams. For site-wide theme customization, the `initialize` call is used. For diagram specific customization, the `init` directive is used. -Themes follow and build upon the Levels of Configuration, and employ `directives` to modify and create custom configurations, as they were introduced in Version [8.6.0](./8.6.0_docs.md). +## Available Themes -## Deployable Themes +1. [**default**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-default.js) - This is the default theme for all diagrams. -The following are a list of **Deployable themes**, sample `%%init%%` directives and `initialize` calls. +2. [**neutral**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-neutral.js) - This theme is great for black and white documents that will be printed. -1. **base**- Designed to be modified, as the name implies it is supposed to be used as the base for making custom themes. +3. [**dark**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-dark.js) - This theme goes well with dark-colored elements or dark-mode. -2. **forest**- A theme full of light greens that is easy on the eyes. +4. [**forest**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-forest.js) - This theme contains shades of green. -3. **dark**- A theme that would go well with other dark-colored elements. +5. [**base**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-base.js) - This is the only theme that can be modified. Use this theme as the base for customizations. -4. **default**- The default theme for all diagrams. +## Site-wide Theme -5. **neutral**- The theme to be used for black and white printing. +To customize themes site-wide, call the `initialize` method on the `mermaidAPI`. -## Site-wide Themes - -Site-wide themes are declared via `initialize` by site owners. - -Example of `Initialize` call setting `theme` to `base`: +Example of `initialize` call setting `theme` to `base`: ```javascript mermaidAPI.initialize({ @@ -33,175 +29,52 @@ mermaidAPI.initialize({ }); ``` -**Notes**: Only site owners can use the `mermaidAPI.initialize` call, to set values. Site-Users will have to use `%%init%%` to modify or create the theme for their diagrams. +## Diagram-specific Themes -## Themes at the Local or Current Level +To customize the theme of an individual diagram, use the `init` directive. -When Generating a diagram using on a webpage that supports mermaid. It is also possible to override site-wide theme settings locally, for a specific diagram, using directives, as long as it is not prohibited by the `secure` array. +Example of `init` directive setting the `theme` to `forest`: -```mmd -%%{init: {'theme':'base'}}%% +```mermaid-example +%%{init: {'theme':'forest'}}%% graph TD a --> b ``` -Here is an example of how `%%init%%` can set the theme to 'base', this assumes that `themeVariables` are set to default: - -```mermaid-example -%%{init: {'theme':'base'}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -# List of Themes - -# Customizing Themes with `themeVariables` - -The easiest way to make a custom theme is to start with the base theme, and just modify theme variables through `themeVariables`, via `%%init%%`. - -| Parameter | Description | Type | Required | Objects contained | -| -------------- | ------------------------------------------------------------------ | ----- | -------- | ---------------------------------- | -| themeVariables | Array containing objects, modifiable with the `%%init%%` directive | Array | Required | primaryColor, lineColor, textColor | - -**Here is an example of overriding `primaryColor` through `themeVariables` and giving everything a different look, using `%%init%%`.** - -```mermaid-example -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end +```mermaid +%%{init: {'theme':'forest'}}%% + graph TD + a --> b ``` -**Notes:** -Leaving it empty will set all variable values to default. +> **Reminder**: the only theme that can be customed is the `base` theme. The following section covers how to use `themeVariables` for customizations. -## Color and Color Calculation: +## Customizing Themes with `themeVariables` -Color definitions have certain interactions in mermaid, this is in order to ensure visibility for diagrams. Mermaid will adjust some variables automatically, when colors are changed in order to compensate and maintain readability. +To make a custom theme, modify `themeVariables` via `init`. -**The Default Value Column** to the right of the Variable column will denote the Variable paired/associated with the Variable on the left and the nature of this pairing or association. If it for instance says primaryColor it means that it gets primaryColor as default value. If it says "based on primaryColor" it means that it is calculated/ derived from primaryColor. This calculation can be primary color inversion, a change of hue, darkening or lightening by 10%, etc. +You will need to use the [base](#available-themes) theme as it is the only modifiable theme. -You can create your own themes, by changing any of the given variables below. If you are using a dark background, set dark mode to true to adjust the colors. It is possible to override the calculations using the variable names below, with `%%init%%` if you wish to style it differently. +| Parameter | Description | Type | Properties | +| -------------- | ------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- | +| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables-reference-table)) | -## Theme Variables Reference Table - -```note -Variables that are unique to some diagrams can be affected by changes in Theme Variables -``` - -| Variable | Default/Base/Factor value | Calc | Description | -| -------------------- | ------------------------------ | ---- | -------------------------------------------------------------------------------------------------------------------------------- | -| darkMode | false | | Boolean Value that dictates how to calculate colors. "true" will activate darkmode. | -| background | #f4f4f4 | | Used to calculate color for items that should either be background colored or contrasting to the background. | -| fontFamily | "trebuchet ms", verdana, arial | | | -| fontSize | 16px | | Font Size, in pixels | -| primaryColor | #fff4dd | | Color to be used as background in nodes, other colors will be derived from this | -| primaryBorderColor | based on primaryColor | \* | Color to be used as border in nodes using primaryColor | -| primaryTextColor | based on darkMode #ddd/#333 | \* | Color to be used as text color in nodes using primaryColor | -| secondaryColor | based on primaryColor | \* | | -| secondaryBorderColor | based on secondaryColor | \* | Color to be used as border in nodes using secondaryColor | -| secondaryTextColor | based on secondaryColor | \* | Color to be used as text color in nodes using secondaryColor | -| tertiaryColor | based on primaryColor | \* | | -| tertiaryBorderColor | based on tertiaryColor | \* | Color to be used as border in nodes using tertiaryColor | -| tertiaryTextColor | based on tertiaryColor | \* | Color to be used as text color in nodes using tertiaryColor | -| noteBkgColor | #fff5ad | | Color used as background in notes | -| noteTextColor | #333 | | Text color in note rectangles. | -| noteBorderColor | based on noteBkgColor | \* | Border color in note rectangles. | -| lineColor | based on background | \* | | -| textColor | based on primaryTextColor | \* | Text in diagram over the background for instance text on labels and on signals in sequence diagram or the title in gantt diagram | -| mainBkg | based on primaryColor | \* | Background in flowchart objects like rects/circles, class diagram classes, sequence diagram etc | -| errorBkgColor | tertiaryColor | \* | Color for syntax error message | -| errorTextColor | tertiaryTextColor | \* | Color for syntax error message | - -# What follows are Variables, specific to different diagrams and charts. - -## Some Theme Variables serve as, or affect the Default Values for Specific Diagram Variables, unless changed using `%%init%%` . - -## Flowchart - -| Variable | Default/ Associated Value | Calc | Description | -| ------------------- | ------------------------- | ---- | ---------------------------- | -| nodeBorder | primaryBorderColor | \* | Node Border Color | -| clusterBkg | tertiaryColor | \* | Background in subgraphs | -| clusterBorder | tertiaryBorderColor | \* | Cluster Border Color | -| defaultLinkColor | lineColor | \* | Link Color | -| titleColor | tertiaryTextColor | \* | Title Color | -| edgeLabelBackground | based on secondaryColor | \* | | -| nodeTextColor | primaryTextColor | \* | Color for text inside Nodes. | - -# sequence diagram - -| name | Default value | Calc | Description | -| --------------------- | ----------------------- | ---- | --------------------------- | -| actorBorder | primaryBorderColor | \* | Actor Border Color | -| actorBkg | mainBkg | \* | Actor Background Color | -| actorTextColor | primaryTextColor | \* | Actor Text Color | -| actorLineColor | grey | \* | Actor Line Color | -| signalColor | textColor | \* | Signal Color | -| signalTextColor | textColor | \* | Signal Text Color | -| labelBoxBkgColor | actorBkg | \* | Label Box Background Color | -| labelBoxBorderColor | actorBorder | \* | Label Box Border Color | -| labelTextColor | actorTextColor | \* | Label Text Color | -| loopTextColor | actorTextColor | \* | Loop ext Color | -| activationBorderColor | based on secondaryColor | \* | Activation Border Color | -| activationBkgColor | secondaryColor | \* | Activation Background Color | -| sequenceNumberColor | based on lineColor | \* | Sequence Number Color | - -# state colors - -| name | Default value | Calc | Description | -| ------------- | ---------------- | ---- | -------------------------------------------- | -| labelColor | primaryTextColor | \* | | -| altBackground | tertiaryColor | \* | Used for background in deep composite states | - -# class colors - -| name | Default value | Calc | Description | -| --------- | ------------- | ---- | ------------------------------- | -| classText | textColor | \* | Color of Text in class diagrams | - -# User journey colors - -| name | Default value | Calc | Description | -| --------- | ----------------------- | ---- | --------------------------------------- | -| fillType0 | primaryColor | \* | Fill for 1st section in journey diagram | -| fillType1 | secondaryColor | \* | Fill for 2nd section in journey diagram | -| fillType2 | based on primaryColor | \* | Fill for 3rd section in journey diagram | -| fillType3 | based on secondaryColor | \* | Fill for 4th section in journey diagram | -| fillType4 | based on primaryColor | \* | Fill for 5th section in journey diagram | -| fillType5 | based on secondaryColor | \* | Fill for 6th section in journey diagram | -| fillType6 | based on primaryColor | \* | Fill for 7th section in journey diagram | -| fillType7 | based on secondaryColor | \* | Fill for 8th section in journey diagram | - -\*\*Notes: Values are meant to create an alternating look. - -# Here is an example of overriding `primaryColor` and giving everything a different look, using `%%init%%`. +Example of modifying `themeVariables` using the `init` directive: ```mermaid-example -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%% +%%{ + init: { + 'theme': 'base', + 'themeVariables': { + 'primaryColor': '#BB2528', + 'primaryTextColor': '#fff', + 'primaryBorderColor': '#7C0000', + 'lineColor': '#F8B229', + 'secondaryColor': '#006100', + 'tertiaryColor': '#fff' + } + } +}%% graph TD A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me think} @@ -218,14 +91,20 @@ Variables that are unique to some diagrams can be affected by changes in Theme V end ``` -\*\*This got a bit too dark and bit too colorful. With some easy steps this can be fixed: - -- Make the primary color a little lighter -- set the tertiary color to a reddish shade as well -- make the edge label background differ from the subgraph by setting the edgeLabelBackground - -```mermaid-example -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ffcccc', 'edgeLabelBackground':'#ffffee', 'tertiaryColor': '#fff0f0'}}}%% +```mermaid +%%{ + init: { + 'theme': 'base', + 'themeVariables': { + 'primaryColor': '#BB2528', + 'primaryTextColor': '#fff', + 'primaryBorderColor': '#7C0000', + 'lineColor': '#F8B229', + 'secondaryColor': '#006100', + 'tertiaryColor': '#fff' + } + } +}%% graph TD A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me think} @@ -242,225 +121,93 @@ Variables that are unique to some diagrams can be affected by changes in Theme V end ``` -The Theming Engine does not admit color codes and will only accept proper color values. Color Names is not supported so for instance, the color value 'red' will not work, but '#ff0000' will work. - -# Common theming activities - -## How to change the color of the arrows - -# Examples: - -When adjusting a theme it might be helpful to look at how your preferred theme goes with the diagrams, to evaluate whether everything is visible and looks good. -In the following examples, the directive `init` is used, with the `theme` being declared as `base`. For more information on using directives, read the documentation for [Version 8.6.0](/8.6.0_docs.md) - -### Flowchart - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -### Flowchart (beta) - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - flowchart TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[Another] - C ==>|One| D[Laptop] - C x--x|Two| E[iPhone] - C o--o|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -### Sequence diagram - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - sequenceDiagram - autonumber - par Action 1 - Alice->>John: Hello John, how are you? - and Action 2 - Alice->>Bob: Hello Bob, how are you? - end - Alice->>+John: Hello John, how are you? - Alice->>+John: John, can you hear me? - John-->>-Alice: Hi Alice, I can hear you! - Note right of John: John is perceptive - John-->>-Alice: I feel great! - loop Every minute - John-->Alice: Great! - end -``` - -### Class diagram - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - -classDiagram - Animal "1" <|-- Duck - Animal <|-- Fish - Animal <--o Zebra - Animal : +int age - Animal : +String gender - Animal: +isMammal() - Animal: +mate() - class Duck{ - +String beakColor - +swim() - +quack() - } - class Fish{ - -int sizeInFeet - -canEat() - } - class Zebra{ - +bool is_wild - +run() - } -``` - -### Gantt - -```mermaid-example -gantt - dateFormat YYYY-MM-DD - title Adding GANTT diagram functionality to mermaid - excludes :excludes the named dates/days from being included in a charted task.. - section A section - Completed task :done, des1, 2014-01-06,2014-01-08 - Active task :active, des2, 2014-01-09, 3d - Future task : des3, after des2, 5d - Future task2 : des4, after des3, 5d - - section Critical tasks - Completed task in the critical line :crit, done, 2014-01-06,24h - Implement parser and jison :crit, done, after des1, 2d - Create tests for parser :crit, active, 3d - Future task in critical line :crit, 5d - Create tests for renderer :2d - Add to mermaid :1d - - section Documentation - Describe gantt syntax :active, a1, after des1, 3d - Add gantt diagram to demo page :after a1 , 20h - Add another diagram to demo page :doc1, after a1 , 48h - - section Last section - Describe gantt syntax :after doc1, 3d - Add gantt diagram to demo page :20h - Add another diagram to demo page :48h -``` - -### State diagram - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - stateDiagram - [*] --> Active - - state Active { - [*] --> NumLockOff - NumLockOff --> NumLockOn : EvNumLockPressed - NumLockOn --> NumLockOff : EvNumLockPressed - -- - [*] --> CapsLockOff - CapsLockOff --> CapsLockOn : EvCapsLockPressed - CapsLockOn --> CapsLockOff : EvCapsLockPressed - -- - [*] --> ScrollLockOff - ScrollLockOff --> ScrollLockOn : EvCapsLockPressed - ScrollLockOn --> ScrollLockOff : EvCapsLockPressed - } - state SomethingElse { - A --> B - B --> A - } - - Active --> SomethingElse - note right of SomethingElse : This is the note to the right. - - SomethingElse --> [*] - -``` - -### State diagram (beta) - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% -stateDiagram-v2 - [*] --> Active - - state Active { - [*] --> NumLockOff - NumLockOff --> NumLockOn : EvNumLockPressed - NumLockOn --> NumLockOff : EvNumLockPressed - -- - [*] --> CapsLockOff - CapsLockOff --> CapsLockOn : EvCapsLockPressed - CapsLockOn --> CapsLockOff : EvCapsLockPressed - -- - [*] --> ScrollLockOff - ScrollLockOff --> ScrollLockOn : EvCapsLockPressed - ScrollLockOn --> ScrollLockOff : EvCapsLockPressed - } - state SomethingElse { - A --> B - B --> A - } - - Active --> SomethingElse2 - note right of SomethingElse2 : This is the note to the right. - - SomethingElse2 --> [*] -``` - -### Entity Relations diagram - -```mermaid-example - erDiagram - CUSTOMER }|..|{ DELIVERY-ADDRESS : has - CUSTOMER ||--o{ ORDER : places - CUSTOMER ||--o{ INVOICE : "liable for" - DELIVERY-ADDRESS ||--o{ ORDER : receives - INVOICE ||--|{ ORDER : covers - ORDER ||--|{ ORDER-ITEM : includes - PRODUCT-CATEGORY ||--|{ PRODUCT : contains - PRODUCT ||--o{ ORDER-ITEM : "ordered in" -``` - -### User journey diagram - -```mermaid-example -journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me -``` +## Color and Color Calculation + +To ensure diagram readability, the default value of certain variables is calculated or derived from other variables. For example, `primaryBorderColor` is derived from the `primaryColor` variable. So if the `primaryColor` variable is customized, Mermaid will adjust `primaryBorderColor` automatically. Adjustments can mean a color inversion, a hue change, a darkening/lightening by 10%, etc. + +The theming engine will only recognize hex colors and not color names. So, the value `#ff0000` will work, but `red` will not. + +## Theme Variables + +| Variable | Default value | Description | +| -------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| darkMode | false | Affects how derived colors are calculated. Set value to `true` for dark mode. | +| background | #f4f4f4 | Used to calculate color for items that should either be background colored or contrasting to the background | +| fontFamily | trebuchet ms, verdana, arial | | +| fontSize | 16px | Font size in pixels | +| primaryColor | #fff4dd | Color to be used as background in nodes, other colors will be derived from this | +| primaryBorderColor | calculated from primaryColor | Color to be used as border in nodes using `primaryColor` | +| primaryBorderColor | calculated from primaryColor | Color to be used as border in nodes using `primaryColor` | +| primaryTextColor | calculated from darkMode #ddd/#333 | Color to be used as text color in nodes using `primaryColor` | +| secondaryColor | calculated from primaryColor | | +| primaryBorderColor | calculated from primaryColor | Color to be used as border in nodes using `primaryColor` | +| secondaryBorderColor | calculated from secondaryColor | Color to be used as border in nodes using `secondaryColor` | +| primaryBorderColor | calculated from primaryColor | Color to be used as border in nodes using `primaryColor` | +| secondaryTextColor | calculated from secondaryColor | Color to be used as text color in nodes using `secondaryColor` | +| tertiaryColor | calculated from primaryColor | | +| tertiaryBorderColor | calculated from tertiaryColor | Color to be used as border in nodes using `tertiaryColor` | +| tertiaryTextColor | calculated from tertiaryColor | Color to be used as text color in nodes using `tertiaryColor` | +| noteBkgColor | #fff5ad | Color used as background in notes | +| noteTextColor | #333 | Text color in note rectangles | +| noteBorderColor | calculated from noteBkgColor | Border color in note rectangles | +| lineColor | calculated from background | | +| textColor | calculated from primaryTextColor | Text in diagram over the background for instance text on labels and on signals in sequence diagram or the title in Gantt diagram | +| mainBkg | calculated from primaryColor | Background in flowchart objects like rects/circles, class diagram classes, sequence diagram etc | +| errorBkgColor | tertiaryColor | Color for syntax error message | +| errorTextColor | tertiaryTextColor | Color for syntax error message | + +## Flowchart Variables + +| Variable | Default value | Description | +| ------------------- | ------------------------------ | --------------------------- | +| nodeBorder | primaryBorderColor | Node Border Color | +| clusterBkg | tertiaryColor | Background in subgraphs | +| clusterBorder | tertiaryBorderColor | Cluster Border Color | +| defaultLinkColor | lineColor | Link Color | +| titleColor | tertiaryTextColor | Title Color | +| edgeLabelBackground | calculated from secondaryColor | | +| nodeTextColor | primaryTextColor | Color for text inside Nodes | + +## Sequence Diagram Variables + +| Variable | Default value | Description | +| --------------------- | ------------------------------ | --------------------------- | +| actorBkg | mainBkg | Actor Background Color | +| actorBorder | primaryBorderColor | Actor Border Color | +| actorTextColor | primaryTextColor | Actor Text Color | +| actorLineColor | grey | Actor Line Color | +| signalColor | textColor | Signal Color | +| signalTextColor | textColor | Signal Text Color | +| labelBoxBkgColor | actorBkg | Label Box Background Color | +| labelBoxBorderColor | actorBorder | Label Box Border Color | +| labelTextColor | actorTextColor | Label Text Color | +| loopTextColor | actorTextColor | Loop Text Color | +| activationBorderColor | calculated from secondaryColor | Activation Border Color | +| activationBkgColor | secondaryColor | Activation Background Color | +| sequenceNumberColor | calculated from lineColor | Sequence Number Color | + +## State Colors + +| Variable | Default value | Description | +| ------------- | ---------------- | -------------------------------------------- | +| labelColor | primaryTextColor | | +| altBackground | tertiaryColor | Used for background in deep composite states | + +## Class Colors + +| Variable | Default value | Description | +| --------- | ------------- | ------------------------------- | +| classText | textColor | Color of Text in class diagrams | + +## User Journey Colors + +| Variable | Default value | Description | +| --------- | ------------------------------ | --------------------------------------- | +| fillType0 | primaryColor | Fill for 1st section in journey diagram | +| fillType1 | secondaryColor | Fill for 2nd section in journey diagram | +| fillType2 | calculated from primaryColor | Fill for 3rd section in journey diagram | +| fillType3 | calculated from secondaryColor | Fill for 4th section in journey diagram | +| fillType4 | calculated from primaryColor | Fill for 5th section in journey diagram | +| fillType5 | calculated from secondaryColor | Fill for 6th section in journey diagram | +| fillType6 | calculated from primaryColor | Fill for 7th section in journey diagram | +| fillType7 | calculated from secondaryColor | Fill for 8th section in journey diagram | diff --git a/packages/mermaid/src/docs/intro/n00b-gettingStarted.md b/packages/mermaid/src/docs/intro/n00b-gettingStarted.md index 8360d24109..a4dd7662f2 100644 --- a/packages/mermaid/src/docs/intro/n00b-gettingStarted.md +++ b/packages/mermaid/src/docs/intro/n00b-gettingStarted.md @@ -126,6 +126,10 @@ Rendering in Mermaid is initialized by `mermaid.initialize()` call. You can plac | ----------- | --------------------------------- | ------- | ----------- | | startOnLoad | Toggle for Rendering upon loading | Boolean | true, false | +### Adding external diagrams to mermaid + +Please refer to the [Mindmap](../syntax/mindmap.md?id=integrating-with-your-librarywebsite) section for more information. + ### Working Examples **Here is a full working example of the mermaidAPI being called through the CDN:** diff --git a/packages/mermaid/src/docs/landing/class.png b/packages/mermaid/src/docs/landing/class.png new file mode 100644 index 0000000000..5b2f663d86 Binary files /dev/null and b/packages/mermaid/src/docs/landing/class.png differ diff --git a/packages/mermaid/src/docs/landing/cover.jpg b/packages/mermaid/src/docs/landing/cover.jpg new file mode 100644 index 0000000000..24f8b13bfb Binary files /dev/null and b/packages/mermaid/src/docs/landing/cover.jpg differ diff --git a/packages/mermaid/src/docs/landing/er.png b/packages/mermaid/src/docs/landing/er.png new file mode 100644 index 0000000000..e3d8dd7cab Binary files /dev/null and b/packages/mermaid/src/docs/landing/er.png differ diff --git a/packages/mermaid/src/docs/landing/flowchart.png b/packages/mermaid/src/docs/landing/flowchart.png new file mode 100644 index 0000000000..714626b70e Binary files /dev/null and b/packages/mermaid/src/docs/landing/flowchart.png differ diff --git a/packages/mermaid/src/docs/landing/gantt.png b/packages/mermaid/src/docs/landing/gantt.png new file mode 100644 index 0000000000..95c8d08a14 Binary files /dev/null and b/packages/mermaid/src/docs/landing/gantt.png differ diff --git a/packages/mermaid/src/docs/landing/index.html b/packages/mermaid/src/docs/landing/index.html new file mode 100644 index 0000000000..7b256f47f8 --- /dev/null +++ b/packages/mermaid/src/docs/landing/index.html @@ -0,0 +1,337 @@ + + + + + + + The Official Guide to Mermaid.js + + + + + + + + + + + + + + + + +
+
+ +
+
+
+

MermaidPress

+

+ The Official Guide to Mermaid.js +

+

+ Learn to create complex diagrams and beautiful flowcharts easily using text and code + using Mermaid.js. +

+ + + +
+
+
+ +
+ +
+
+
+
+ + + + + + + + + + + + +
+
+
+

+ Get up to speed with using Mermaid diagrams along with real-world examples and expert tips + from the authors to facilitate a seamless development workflow +

+
+
+
+
+
+

+ Flowcharts is a diagram type that visualizes a process or an algorithm by showing the + steps in order, as well as the different paths the execution can take. +

+
+
+ +
+
+
+
+ +
+
+
+

+ Sequence diagrams lets you model and visualize interactions between different actors + or objects in a system, as well as the order of those interactions +

+
+
+
+
+
+

+ A class diagram is a graphical representation that is used to visualize and describe + an object-oriented system. +

+
+
+ +
+
+
+
+ +
+
+
+

+ An entity-relationship diagram is a graphical representation that is used to + visualize the different types of entities that exist within a system. +

+
+
+
+
+
+

+ Use State diagrams to model and document state machines, an abstract way of + representing a system or an algorithm. +

+
+
+ +
+
+
+
+ +
+
+
+

+ A Gantt chart is a graphical representation that is used to visualize and describe + tasks (events or activities) over time. +

+
+
+
+
+

+ These were a few of the diagrams supported by Mermaid. +

+
+ +
+
+

+ Book description +

+
+

+ Mermaid lets you represent diagrams using text and code which simplifies the maintenance + of complex diagrams. This is a great option for developers as they’re more familiar with + code, rather than special tools for generating diagrams. Besides, diagrams in code + simplify maintenance and ensure that the code is supported by version control systems. + In some cases, Mermaid makes refactoring support for name changes possible while also + enabling team collaboration for review distribution and updates. +

+

+ Developers working with any system will be able to put their knowledge to work with this + practical guide to using Mermaid for documentation. The book is also a great reference + for looking up the syntax for specific diagrams when authoring diagrams. +

+

+ You’ll start by getting up to speed with the importance of accurate and visual + documentation. Next, the book introduces Mermaid and establishes how to use it to create + effective documentation. By using different tools, editors, or a custom documentation + platform, you’ll also learn how to use Mermaid syntax for various diagrams. Later + chapters cover advanced configuration settings and theme options to manipulate your + diagram as per your needs. +

+

+ By the end of this Mermaid book, you’ll have become well-versed with the different types + of Mermaid diagrams and how they can be used in your workflows. +

+
+
+
+
+
+

+ What you will learn +

+
+
+
+
+
+
+
    +
  • + Understand good and bad documentation, and the art of effective documentation +
  • +
  • + Become well-versed with maintaining complex diagrams with ease +
  • +
  • + Learn how to set up a custom documentation system +
  • +
  • + Learn how to implement Mermaid diagrams in your workflows +
  • +
  • + Understand how to set up themes for a Mermaid diagram for an entire site +
  • +
  • + Discover how to draw different types of diagrams such as flowcharts, class + diagrams, Gantt charts, and more +
  • +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + +
+

+ Purchase The Official Guide to Mermaid.js +

+
+
+
+

+

Written by Knut Sveidqvist and Ashish Jain.

+

+ Knut is the creator of Mermaid and both authors are active core team members of the + Mermaid open-source project. +

+

+ + + +
+ + + diff --git a/packages/mermaid/src/docs/landing/sequence-diagram.png b/packages/mermaid/src/docs/landing/sequence-diagram.png new file mode 100644 index 0000000000..8c51ac1c5d Binary files /dev/null and b/packages/mermaid/src/docs/landing/sequence-diagram.png differ diff --git a/packages/mermaid/src/docs/landing/state.png b/packages/mermaid/src/docs/landing/state.png new file mode 100644 index 0000000000..2ef66ea2f1 Binary files /dev/null and b/packages/mermaid/src/docs/landing/state.png differ diff --git a/packages/mermaid/src/docs/misc/integrations.md b/packages/mermaid/src/docs/misc/integrations.md index 06d09634ff..e163758723 100644 --- a/packages/mermaid/src/docs/misc/integrations.md +++ b/packages/mermaid/src/docs/misc/integrations.md @@ -15,6 +15,7 @@ They also serve as proof of concept, for the variety of things that can be built - [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) (**Native support**) - [Tuleap](https://docs.tuleap.org/user-guide/writing-in-tuleap.html#graphs) (**Native support**) - [Joplin](https://joplinapp.org) (**Native support**) +- [Swimm](https://swimm.io) (**Native support**) - [Notion](https://notion.so) (**Native support**) - [Observable](https://observablehq.com/@observablehq/mermaid) (**Native support**) - [Obsidian](https://help.obsidian.md/How+to/Format+your+notes#Diagram) (**Native support**) @@ -103,10 +104,10 @@ They also serve as proof of concept, for the variety of things that can be built - [md-it-mermaid](https://github.com/iamcco/md-it-mermaid) - [markdown-it-mermaid-fence-new](https://github.com/Revomatico/markdown-it-mermaid-fence-new) - [markdown-it-mermaid-less](https://github.com/searKing/markdown-it-mermaid-less) -- [Atom](https://atom.io) - - [Markdown Preview Enhanced](https://atom.io/packages/markdown-preview-enhanced) - - [Atom Mermaid](https://atom.io/packages/atom-mermaid) - - [Language Mermaid Syntax Highlighter](https://atom.io/packages/language-mermaid) +- Atom _(Atom has been [archived.](https://github.blog/2022-06-08-sunsetting-atom/))_ + - [Markdown Preview Enhanced](https://github.com/shd101wyy/markdown-preview-enhanced) + - [Atom Mermaid](https://github.com/y-takey/atom-mermaid) + - [Language Mermaid Syntax Highlighter](https://github.com/ytisf/language-mermaid) - [Sublime Text 3](https://sublimetext.com) - [Mermaid Package](https://packagecontrol.io/packages/Mermaid) - [Astah](https://astah.net) diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index e9b9185290..50593f7293 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -1,7 +1,8 @@ # Class diagrams > "In software engineering, a class diagram in the Unified Modeling Language (UML) is a type of static structure diagram that describes the structure of a system by showing the system's classes, their attributes, operations (or methods), and the relationships among objects." -> Wikipedia +> +> -Wikipedia The class diagram is the main building block of object-oriented modeling. It is used for general conceptual modeling of the structure of the application, and for detailed modeling to translate the models into programming code. Class diagrams can also be used for data modeling. The classes in a class diagram represent both the main elements, interactions in the application, and the classes to be programmed. @@ -140,10 +141,6 @@ Square : +setMessages(List~string~ messages) Square : +getMessages() List~string~ ``` -#### Return Type - -Optionally you can end the method/function definition with the data type that will be returned. - #### Visibility To describe the visibility (or encapsulation) of an attribute or method/function that is a part of a class (i.e. a class member), optional notation may be placed before that members' name: @@ -175,7 +172,7 @@ There are eight different types of relations defined for classes under UML which | Type | Description | | ------- | ------------- | | `<\|--` | Inheritance | -| `\*--` | Composition | +| `*--` | Composition | | `o--` | Aggregation | | `-->` | Association | | `--` | Link (Solid) | diff --git a/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md b/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md index c666877c5f..e136cb31ab 100644 --- a/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md +++ b/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md @@ -160,25 +160,26 @@ erDiagram } ``` -The `type` and `name` values must begin with an alphabetic character and may contain digits, hyphens or underscores. Other than that, there are no restrictions, and there is no implicit set of valid data types. +The `type` and `name` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. Other than that, there are no restrictions, and there is no implicit set of valid data types. #### Attribute Keys and Comments -Attributes may also have a `key` or comment defined. Keys can be "PK" or "FK", for Primary Key or Foreign Key. And a `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them. +Attributes may also have a `key` or comment defined. Keys can be "PK", "FK" or "UK", for Primary Key, Foreign Key or Unique Key. And a `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them. ```mermaid-example erDiagram CAR ||--o{ NAMED-DRIVER : allows CAR { string allowedDriver FK "The license of the allowed driver" - string registrationNumber + string registrationNumber UK string make string model + string[] parts } PERSON ||--o{ NAMED-DRIVER : is PERSON { string driversLicense PK "The license #" - string firstName + string(99) firstName "Only 99 characters are allowed" string lastName int age } diff --git a/packages/mermaid/src/docs/syntax/mindmap.md b/packages/mermaid/src/docs/syntax/mindmap.md index edad887c29..3b737a5721 100644 --- a/packages/mermaid/src/docs/syntax/mindmap.md +++ b/packages/mermaid/src/docs/syntax/mindmap.md @@ -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. @@ -161,3 +161,17 @@ Root B C ``` + +## Integrating with your library/website. + +Mindmap uses the experimental lazy loading & async rendering features which could change in the future. + +```html + +``` + +You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/fcf53c98c25604c90a218104268c339be53035a6/src/lib/util/mermaid.ts) to see how the async loading is done. diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index 092661dc6c..67138435ef 100644 --- a/packages/mermaid/src/mermaidAPI.spec.ts +++ b/packages/mermaid/src/mermaidAPI.spec.ts @@ -1,6 +1,38 @@ 'use strict'; import { vi } from 'vitest'; +// ------------------------------------- +// Mocks and mocking + +import { MockedD3 } from './tests/MockedD3'; + +// Note: If running this directly from within an IDE, the mocks directory must be at packages/mermaid/mocks +vi.mock('d3'); +vi.mock('dagre-d3'); + +// mermaidAPI.spec.ts: +import * as accessibility from './accessibility'; // Import it this way so we can use spyOn(accessibility,...) +vi.mock('./accessibility', () => ({ + setA11yDiagramInfo: vi.fn(), + addSVGa11yTitleDescription: vi.fn(), +})); + +// Mock the renderers specifically so we can test render(). Need to mock draw() for each renderer +vi.mock('./diagrams/c4/c4Renderer'); +vi.mock('./diagrams/class/classRenderer'); +vi.mock('./diagrams/class/classRenderer-v2'); +vi.mock('./diagrams/er/erRenderer'); +vi.mock('./diagrams/flowchart/flowRenderer-v2'); +vi.mock('./diagrams/git/gitGraphRenderer'); +vi.mock('./diagrams/gantt/ganttRenderer'); +vi.mock('./diagrams/user-journey/journeyRenderer'); +vi.mock('./diagrams/pie/pieRenderer'); +vi.mock('./diagrams/requirement/requirementRenderer'); +vi.mock('./diagrams/sequence/sequenceRenderer'); +vi.mock('./diagrams/state/stateRenderer-v2'); + +// ------------------------------------- + import mermaid from './mermaid'; import { MermaidConfig } from './config.type'; @@ -37,7 +69,10 @@ vi.mock('stylis', () => { }); import { compile, serialize } from 'stylis'; -import { MockedD3 } from './tests/MockedD3'; +/** + * @see https://vitest.dev/guide/mocking.html Mock part of a module + * To investigate how to mock just some methods from a module - call the actual implementation and then mock others, e.g. so they can be spied on + */ // ------------------------------------------------------------------------------------- @@ -335,7 +370,8 @@ describe('mermaidAPI', function () { const htmlElements = ['> *', 'span']; it('creates CSS styles for every style and textStyle in every classDef', () => { - // @todo TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result + // @todo TODO Can't figure out how to spy on the cssImportantStyles method. + // That would be a much better approach than manually checking the result const styles = createCssStyles(mocked_config, graphType, classDefs); htmlElements.forEach((htmlElement) => { @@ -373,7 +409,7 @@ describe('mermaidAPI', function () { const htmlElements = ['rect', 'polygon', 'ellipse', 'circle']; it('creates CSS styles for every style and textStyle in every classDef', () => { - // @todo TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result + // TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result. const styles = createCssStyles(mocked_config_no_htmlLabels, graphType, classDefs); htmlElements.forEach((htmlElement) => { @@ -434,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 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(); - }); + it('removes an existing iframe element even if div element is absent', () => { + tempiFrameElement.append(svgElement); + rootHtml.append(tempiFrameElement); + + 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); - - 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(); - }); + + 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, svgId, tempDivId, tempIframeId); + expect(givenDocument.getElementById(tempIframeId)).toBeNull(); + expect(givenDocument.getElementById(tempDivId)).toBeNull(); + expect(givenDocument.getElementById(svgId)).toBeNull(); }); }); @@ -510,7 +533,7 @@ describe('mermaidAPI', function () { expect(config.testLiteral).toBe(true); }); - it('copies a an object into the configuration', function () { + it('copies an object into the configuration', function () { const orgConfig: any = mermaidAPI.getConfig(); expect(orgConfig.testObject).toBe(undefined); @@ -616,6 +639,7 @@ describe('mermaidAPI', function () { expect(mermaidAPI.defaultConfig['logLevel']).toBe(5); }); }); + describe('dompurify config', function () { it('allows dompurify config to be set', function () { mermaidAPI.initialize({ dompurifyConfig: { ADD_ATTR: ['onclick'] } }); @@ -623,6 +647,7 @@ describe('mermaidAPI', function () { expect(mermaidAPI!.getConfig()!.dompurifyConfig!.ADD_ATTR).toEqual(['onclick']); }); }); + describe('parse', function () { mermaid.parseError = undefined; // ensure it parseError undefined it('throws for an invalid definition (with no mermaid.parseError() defined)', function () { @@ -659,4 +684,106 @@ describe('mermaidAPI', function () { expect(mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).toEqual(true); }); }); + + describe('render', () => { + // These are more like integration tests right now because nothing is mocked. + // But it is faster that a cypress test and there's no real reason to actually evaluate an image pixel by pixel. + + // render(id, text, cb?, svgContainingElement?) + + // Test all diagram types. Note that old flowchart 'graph' type will invoke the flowRenderer-v2. (See the flowchart v2 detector.) + // We have to have both the specific textDiagramType and the expected type name because the expected type may be slightly different than was is put in the diagram text (ex: in -v2 diagrams) + const diagramTypesAndExpectations = [ + { textDiagramType: 'C4Context', expectedType: 'c4' }, + { textDiagramType: 'classDiagram', expectedType: 'classDiagram' }, + { textDiagramType: 'classDiagram-v2', expectedType: 'classDiagram' }, + { textDiagramType: 'erDiagram', expectedType: 'er' }, + { textDiagramType: 'graph', expectedType: 'flowchart-v2' }, + { textDiagramType: 'flowchart', expectedType: 'flowchart-v2' }, + { textDiagramType: 'gitGraph', expectedType: 'gitGraph' }, + { textDiagramType: 'gantt', expectedType: 'gantt' }, + { textDiagramType: 'journey', expectedType: 'journey' }, + { textDiagramType: 'pie', expectedType: 'pie' }, + { textDiagramType: 'requirementDiagram', expectedType: 'requirement' }, + { textDiagramType: 'sequenceDiagram', expectedType: 'sequence' }, + { textDiagramType: 'stateDiagram-v2', expectedType: 'stateDiagram' }, + ]; + + describe('accessibility', () => { + const id = 'mermaid-fauxId'; + const a11yTitle = 'a11y title'; + const a11yDescr = 'a11y description'; + + diagramTypesAndExpectations.forEach((testedDiagram) => { + describe(`${testedDiagram.textDiagramType}`, () => { + const diagramType = testedDiagram.textDiagramType; + const diagramText = `${diagramType}\n accTitle: ${a11yTitle}\n accDescr: ${a11yDescr}\n`; + const expectedDiagramType = testedDiagram.expectedType; + + it('aria-roledscription is set to the diagram type, addSVGa11yTitleDescription is called', () => { + const a11yDiagramInfo_spy = vi.spyOn(accessibility, 'setA11yDiagramInfo'); + const a11yTitleDesc_spy = vi.spyOn(accessibility, 'addSVGa11yTitleDescription'); + mermaidAPI.render(id, diagramText); + expect(a11yDiagramInfo_spy).toHaveBeenCalledWith( + expect.anything(), + expectedDiagramType + ); + expect(a11yTitleDesc_spy).toHaveBeenCalled(); + }); + }); + }); + }); + }); + + describe('renderAsync', () => { + // Be sure to add async before each test (anonymous) method + + // These are more like integration tests right now because nothing is mocked. + // But it is faster that a cypress test and there's no real reason to actually evaluate an image pixel by pixel. + + // render(id, text, cb?, svgContainingElement?) + + // Test all diagram types. Note that old flowchart 'graph' type will invoke the flowRenderer-v2. (See the flowchart v2 detector.) + // We have to have both the specific textDiagramType and the expected type name because the expected type may be slightly different than was is put in the diagram text (ex: in -v2 diagrams) + const diagramTypesAndExpectations = [ + { textDiagramType: 'C4Context', expectedType: 'c4' }, + { textDiagramType: 'classDiagram', expectedType: 'classDiagram' }, + { textDiagramType: 'classDiagram-v2', expectedType: 'classDiagram' }, + { textDiagramType: 'erDiagram', expectedType: 'er' }, + { textDiagramType: 'graph', expectedType: 'flowchart-v2' }, + { textDiagramType: 'flowchart', expectedType: 'flowchart-v2' }, + { textDiagramType: 'gitGraph', expectedType: 'gitGraph' }, + { textDiagramType: 'gantt', expectedType: 'gantt' }, + { textDiagramType: 'journey', expectedType: 'journey' }, + { textDiagramType: 'pie', expectedType: 'pie' }, + { textDiagramType: 'requirementDiagram', expectedType: 'requirement' }, + { textDiagramType: 'sequenceDiagram', expectedType: 'sequence' }, + { textDiagramType: 'stateDiagram-v2', expectedType: 'stateDiagram' }, + ]; + + describe('accessibility', () => { + const id = 'mermaid-fauxId'; + const a11yTitle = 'a11y title'; + const a11yDescr = 'a11y description'; + + diagramTypesAndExpectations.forEach((testedDiagram) => { + describe(`${testedDiagram.textDiagramType}`, () => { + const diagramType = testedDiagram.textDiagramType; + const diagramText = `${diagramType}\n accTitle: ${a11yTitle}\n accDescr: ${a11yDescr}\n`; + const expectedDiagramType = testedDiagram.expectedType; + + it('aria-roledscription is set to the diagram type, addSVGa11yTitleDescription is called', async () => { + const a11yDiagramInfo_spy = vi.spyOn(accessibility, 'setA11yDiagramInfo'); + const a11yTitleDesc_spy = vi.spyOn(accessibility, 'addSVGa11yTitleDescription'); + await mermaidAPI.renderAsync(id, diagramText); + expect(a11yDiagramInfo_spy).toHaveBeenCalledWith( + expect.anything(), + expectedDiagramType + ); + expect(a11yTitleDesc_spy).toHaveBeenCalled(); + }); + }); + }); + }); + }); }); diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 5687a18074..5bf11fad18 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -29,7 +29,8 @@ 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 const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram', 'stateDiagram-v2']; @@ -54,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. @@ -68,7 +69,7 @@ interface DiagramStyleClassDef { // This makes it clear that we're working with a d3 selected element of some kind, even though it's hard to specify the exact type. // @ts-ignore Could replicate the type definition in d3. This also makes it possible to use the untyped info from the js diagram files. -type D3Element = any; +export type D3Element = any; // ---------------------------------------------------------------------------- @@ -327,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(); }; /** @@ -371,7 +365,7 @@ export const removeExistingElements = ( * @param id - The id for the SVG element (the element to be rendered) * @param text - The text for the graph definition * @param cb - Callback which is called after rendering is finished with the svg code as in param. - * @param container - HTML element where the svg will be inserted. (Is usually element with the .mermaid class) + * @param svgContainingElement - HTML element where the svg will be inserted. (Is usually element with the .mermaid class) * If no svgContainingElement is provided then the SVG element will be appended to the body. * Selector to element in which a div with the graph temporarily will be * inserted. If one is provided a hidden div will be inserted in the body of the page instead. The @@ -442,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 @@ -479,12 +473,13 @@ const render = function ( parseEncounteredException = error; } - // Get the temporary div element containing the svg + // Get the temporary div element containing the svg (the parent HTML Element) const element = root.select(enclosingDivID_selector).node(); const graphType = diag.type; // ------------------------------------------------------------------------------- // Create and insert the styles (user styles, theme styles, config styles) + // These are dealing with HTML Elements, not d3 nodes. // Insert an element into svg. This is where we put the styles const svg = element.firstChild; @@ -501,6 +496,7 @@ const render = function ( idSelector ); + // svg is a HTML element (not a d3 node) const style1 = document.createElement('style'); style1.innerHTML = rules; svg.insertBefore(style1, firstChild); @@ -514,6 +510,12 @@ const render = function ( throw e; } + // This is the d3 node for the svg element + const svgNode = root.select(`${enclosingDivID_selector} svg`); + const a11yTitle = diag.db.getAccTitle?.(); + const a11yDescr = diag.db.getAccDescription?.(); + addA11yInfo(graphType, svgNode, a11yTitle, a11yDescr); + // ------------------------------------------------------------------------------- // Clean up SVG code root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD); @@ -527,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, }); } @@ -641,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 @@ -710,6 +712,12 @@ const renderAsync = async function ( throw e; } + // This is the d3 node for the svg element + const svgNode = root.select(`${enclosingDivID_selector} svg`); + const a11yTitle = diag.db.getAccTitle?.(); + const a11yDescr = diag.db.getAccDescription?.(); + addA11yInfo(graphType, svgNode, a11yTitle, a11yDescr); + // ------------------------------------------------------------------------------- // Clean up SVG code root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD); @@ -723,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, }); } @@ -755,7 +763,7 @@ const renderAsync = async function ( attachFunctions(); // ------------------------------------------------------------------------------- - // Remove the temporary element if appropriate + // Remove the temporary HTML element if appropriate const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector; const node = select(tmpElementSelector).node(); if (node && 'remove' in node) { @@ -874,6 +882,20 @@ function initialize(options: MermaidConfig = {}) { addDiagrams(); } +/** + * Add accessibility (a11y) information to the diagram. + * + */ +function addA11yInfo( + graphType: string, + svgNode: D3Element, + a11yTitle: string | undefined, + a11yDescr: string | undefined +) { + setA11yDiagramInfo(svgNode, graphType); + addSVGa11yTitleDescription(svgNode, a11yTitle, a11yDescr, svgNode.attr('id')); +} + /** * ## mermaidAPI configuration defaults * diff --git a/packages/mermaid/src/tests/MockedD3.ts b/packages/mermaid/src/tests/MockedD3.ts index 9cf01ddadd..284b21b08c 100644 --- a/packages/mermaid/src/tests/MockedD3.ts +++ b/packages/mermaid/src/tests/MockedD3.ts @@ -1,12 +1,18 @@ /** * This is a mocked/stubbed version of the d3 Selection type. Each of the main functions are all * mocked (via vi.fn()) so you can track if they have been called, etc. + * + * Note that node() returns a HTML Element with tag 'svg'. It is an empty element (no innerHTML, no children, etc). + * This potentially allows testing of mermaidAPI render(). */ + export class MockedD3 { public attribs = new Map(); public id: string | undefined = ''; _children: MockedD3[] = []; + _containingHTMLdoc = new Document(); + constructor(givenId = 'mock-id') { this.id = givenId; } @@ -29,6 +35,11 @@ export class MockedD3 { return new MockedD3(cleanId); }); + // This has the same implementation as select(). (It calls it.) + selectAll = vi.fn().mockImplementation(({ select_str = '' }): MockedD3 => { + return this.select(select_str); + }); + append = vi .fn() .mockImplementation(function (this: MockedD3, type: string, id = '' + '-appended'): MockedD3 { @@ -87,9 +98,18 @@ export class MockedD3 { this.attribs.set('text', attrValue); return this; } - // NOTE: Arbitrarily returns an empty object. The return value could be something different with a mockReturnValue() or mockImplementation() - public node = vi.fn().mockReturnValue({}); + // NOTE: Returns a HTML Element with tag 'svg' that has _another_ 'svg' element child. + // This allows different tests to succeed -- some need a top level 'svg' and some need a 'svg' element to be the firstChild + // Real implementation returns an HTML Element + public node = vi.fn().mockImplementation(() => { + const topElem = this._containingHTMLdoc.createElement('svg'); + const elem_svgChild = this._containingHTMLdoc.createElement('svg'); // another svg element + topElem.appendChild(elem_svgChild); + return topElem; + }); + + // TODO Is this correct? shouldn't it return a list of HTML Elements? nodes = vi.fn().mockImplementation(function (this: MockedD3): MockedD3[] { return this._children; }); diff --git a/packages/mermaid/src/utils.spec.js b/packages/mermaid/src/utils.spec.js index bdf94d9926..7ee6aa000c 100644 --- a/packages/mermaid/src/utils.spec.js +++ b/packages/mermaid/src/utils.spec.js @@ -3,8 +3,9 @@ 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 { MockD3 } from 'd3'; +import memoize from 'lodash-es/memoize.js'; +import { MockedD3 } from './tests/MockedD3'; + addDiagrams(); describe('when assignWithDepth: should merge objects within objects', function () { @@ -352,21 +353,52 @@ describe('when initializing the id generator', function () { }); describe('when inserting titles', function () { - it('should do nothing when title is empty', function () { - const svg = MockD3('svg'); + const svg = new MockedD3('svg'); + const mockedElement = { + getBBox: vi.fn().mockReturnValue({ x: 10, y: 11, width: 100, height: 200 }), + }; + const fauxTitle = new MockedD3('title'); + + beforeEach(() => { + svg.node = vi.fn().mockReturnValue(mockedElement); + }); + + it('does nothing if the title is empty', function () { + const svgAppendSpy = vi.spyOn(svg, 'append'); utils.insertTitle(svg, 'testClass', 0, ''); - expect(svg.__children.length).toBe(0); + expect(svgAppendSpy).not.toHaveBeenCalled(); + }); + + it('appends the title as a text item with the given title text', function () { + const svgAppendSpy = vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const titleTextSpy = vi.spyOn(fauxTitle, 'text'); + + utils.insertTitle(svg, 'testClass', 5, 'test title'); + expect(svgAppendSpy).toHaveBeenCalled(); + expect(titleTextSpy).toHaveBeenCalledWith('test title'); }); - it('should insert title centered', function () { - const svg = MockD3('svg'); + it('x value is the bounds x position + half of the bounds width', () => { + vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const titleAttrSpy = vi.spyOn(fauxTitle, 'attr'); + + utils.insertTitle(svg, 'testClass', 5, 'test title'); + expect(titleAttrSpy).toHaveBeenCalledWith('x', 10 + 100 / 2); + }); + + it('y value is the negative of given title top margin', () => { + vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const titleAttrSpy = vi.spyOn(fauxTitle, 'attr'); + + utils.insertTitle(svg, 'testClass', 5, 'test title'); + expect(titleAttrSpy).toHaveBeenCalledWith('y', -5); + }); + + it('class is the given css class', () => { + vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const titleAttrSpy = vi.spyOn(fauxTitle, 'attr'); + utils.insertTitle(svg, 'testClass', 5, 'test title'); - expect(svg.__children.length).toBe(1); - const text = svg.__children[0]; - expect(text.__name).toBe('text'); - expect(text.text).toHaveBeenCalledWith('test title'); - expect(text.attr).toHaveBeenCalledWith('x', 15); - expect(text.attr).toHaveBeenCalledWith('y', -5); - expect(text.attr).toHaveBeenCalledWith('class', 'testClass'); + expect(titleAttrSpy).toHaveBeenCalledWith('class', 'testClass'); }); }); diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 16566c3b12..4c2f844e2c 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -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 = { @@ -194,7 +194,10 @@ export const isSubstringInArray = function (str: string, arr: string[]): number * @param defaultCurve - The default curve to return * @returns The curve factory to use */ -export function interpolateToCurve(interpolate?: string, defaultCurve: CurveFactory): CurveFactory { +export function interpolateToCurve( + interpolate: string | undefined, + defaultCurve: CurveFactory +): CurveFactory { if (!interpolate) { return defaultCurve; } @@ -913,7 +916,7 @@ export function getErrorMessage(error: unknown): string { } /** - * Appends element with the given title, centered. + * Appends element with the given title and css class. * * @param parent - d3 svg object to append title to * @param cssClass - CSS class for the element containing the title diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index bb8f1a32f4..ed90f56cd6 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -68,8 +68,8 @@ importers: specifier: ^4.0.1 version: 4.0.1_bg25yee4qeg7mpleuvd346a3tq esbuild: - specifier: ^0.15.13 - version: 0.15.13 + specifier: ^0.16.0 + version: 0.16.7 eslint: specifier: ^8.27.0 version: 8.27.0 @@ -173,8 +173,8 @@ importers: specifier: ^7.0.0 version: 7.6.1 dagre-d3-es: - specifier: 7.0.4 - version: 7.0.4 + specifier: 7.0.6 + version: 7.0.6 dompurify: specifier: 2.4.1 version: 2.4.1 @@ -239,6 +239,9 @@ importers: coveralls: specifier: ^3.1.1 version: 3.1.1 + cpy-cli: + specifier: ^4.2.0 + version: 4.2.0 cspell: specifier: ^6.14.3 version: 6.14.3 @@ -288,11 +291,11 @@ importers: specifier: ^1.0.0 version: 1.0.0 vitepress: - specifier: ^1.0.0-alpha.28 - version: 1.0.0-alpha.28_tbpndr44ulefs3hehwpi2mkf2y + specifier: ^1.0.0-alpha.31 + version: 1.0.0-alpha.31_tbpndr44ulefs3hehwpi2mkf2y vitepress-plugin-search: - specifier: ^1.0.4-alpha.15 - version: 1.0.4-alpha.15_s3edpouswd4dgoi2en7bdlrp54 + specifier: ^1.0.4-alpha.16 + version: 1.0.4-alpha.16_ifjhkyx3os4sbm7zdnvthc52am packages/mermaid-example-diagram: devDependencies: @@ -1730,6 +1733,96 @@ packages: dev: true optional: true + /@esbuild/android-arm/0.16.7: + resolution: {integrity: sha512-yhzDbiVcmq6T1/XEvdcJIVcXHdLjDJ5cQ0Dp9R9p9ERMBTeO1dR5tc8YYv8zwDeBw1xZm+Eo3MRo8cwclhBS0g==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64/0.16.7: + resolution: {integrity: sha512-tYFw0lBJSEvLoGzzYh1kXuzoX1iPkbOk3O29VqzQb0HbOy7t/yw1hGkvwoJhXHwzQUPsShyYcTgRf6bDBcfnTw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64/0.16.7: + resolution: {integrity: sha512-3P2OuTxwAtM3k/yEWTNUJRjMPG1ce8rXs51GTtvEC5z1j8fC1plHeVVczdeHECU7aM2/Buc0MwZ6ciM/zysnWg==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64/0.16.7: + resolution: {integrity: sha512-VUb9GK23z8jkosHU9yJNUgQpsfJn+7ZyBm6adi2Ec5/U241eR1tAn82QicnUzaFDaffeixiHwikjmnec/YXEZg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64/0.16.7: + resolution: {integrity: sha512-duterlv3tit3HI9vhzMWnSVaB1B6YsXpFq1Ntd6Fou82BB1l4tucYy3FI9dHv3tvtDuS0NiGf/k6XsdBqPZ01w==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64/0.16.7: + resolution: {integrity: sha512-9kkycpBFes/vhi7B7o0cf+q2WdJi+EpVzpVTqtWFNiutARWDFFLcB93J8PR1cG228sucsl3B+7Ts27izE6qiaQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64/0.16.7: + resolution: {integrity: sha512-5Ahf6jzWXJ4J2uh9dpy5DKOO+PeRUE/9DMys6VuYfwgQzd6n5+pVFm58L2Z2gRe611RX6SdydnNaiIKM3svY7g==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm/0.16.7: + resolution: {integrity: sha512-QqJnyCfu5OF78Olt7JJSZ7OSv/B4Hf+ZJWp4kkq9xwMsgu7yWq3crIic8gGOpDYTqVKKMDAVDgRXy5Wd/nWZyQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64/0.16.7: + resolution: {integrity: sha512-2wv0xYDskk2+MzIm/AEprDip39a23Chptc4mL7hsHg26P0gD8RUhzmDu0KCH2vMThUI1sChXXoK9uH0KYQKaDg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32/0.16.7: + resolution: {integrity: sha512-APVYbEilKbD5ptmKdnIcXej2/+GdV65TfTjxR2Uk8t1EsOk49t6HapZW6DS/Bwlvh5hDwtLapdSumIVNGxgqLg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + /@esbuild/linux-loong64/0.15.13: resolution: {integrity: sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==} engines: {node: '>=12'} @@ -1739,6 +1832,114 @@ packages: dev: true optional: true + /@esbuild/linux-loong64/0.16.7: + resolution: {integrity: sha512-5wPUAGclplQrAW7EFr3F84Y/d++7G0KykohaF4p54+iNWhUnMVU8Bh2sxiEOXUy4zKIdpHByMgJ5/Ko6QhtTUw==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el/0.16.7: + resolution: {integrity: sha512-hxzlXtWF6yWfkE/SMTscNiVqLOAn7fOuIF3q/kiZaXxftz1DhZW/HpnTmTTWrzrS7zJWQxHHT4QSxyAj33COmA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64/0.16.7: + resolution: {integrity: sha512-WM83Dac0LdXty5xPhlOuCD5Egfk1xLND/oRLYeB7Jb/tY4kzFSDgLlq91wYbHua/s03tQGA9iXvyjgymMw62Vw==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64/0.16.7: + resolution: {integrity: sha512-3nkNnNg4Ax6MS/l8O8Ynq2lGEVJYyJ2EoY3PHjNJ4PuZ80EYLMrFTFZ4L/Hc16AxgtXKwmNP9TM0YKNiBzBiJQ==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x/0.16.7: + resolution: {integrity: sha512-3SA/2VJuv0o1uD7zuqxEP+RrAyRxnkGddq0bwHQ98v1KNlzXD/JvxwTO3T6GM5RH6JUd29RTVQTOJfyzMkkppA==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64/0.16.7: + resolution: {integrity: sha512-xi/tbqCqvPIzU+zJVyrpz12xqciTAPMi2fXEWGnapZymoGhuL2GIWIRXg4O2v5BXaYA5TSaiKYE14L0QhUTuQg==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64/0.16.7: + resolution: {integrity: sha512-NUsYbq3B+JdNKn8SXkItFvdes9qTwEoS3aLALtiWciW/ystiCKM20Fgv9XQBOXfhUHyh5CLEeZDXzLOrwBXuCQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64/0.16.7: + resolution: {integrity: sha512-qjwzsgeve9I8Tbsko2FEkdSk2iiezuNGFgipQxY/736NePXDaDZRodIejYGWOlbYXugdxb0nif5yvypH6lKBmA==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64/0.16.7: + resolution: {integrity: sha512-mFWDz4RoBTzPphTCkM7Kc7Qpa0o/Z01acajR+Ai7LdfKgcP/C6jYOaKwv7nKzD0+MjOT20j7You9g4ozYy1dKQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64/0.16.7: + resolution: {integrity: sha512-m39UmX19RvEIuC8sYZ0M+eQtdXw4IePDSZ78ZQmYyFaXY9krq4YzQCK2XWIJomNLtg4q+W5aXr8bW3AbqWNoVg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32/0.16.7: + resolution: {integrity: sha512-1cbzSEZA1fANwmT6rjJ4G1qQXHxCxGIcNYFYR9ctI82/prT38lnwSRZ0i5p/MVXksw9eMlHlet6pGu2/qkXFCg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64/0.16.7: + resolution: {integrity: sha512-QaQ8IH0JLacfGf5cf0HCCPnQuCTd/dAI257vXBgb/cccKGbH/6pVtI1gwhdAQ0Y48QSpTIFrh9etVyNdZY+zzw==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + /@eslint/eslintrc/1.3.3: resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2856,15 +3057,15 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /@vitejs/plugin-vue/3.2.0_vite@3.2.3+vue@3.2.41: - resolution: {integrity: sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==} + /@vitejs/plugin-vue/4.0.0_vite@4.0.1+vue@3.2.45: + resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==} engines: {node: ^14.18.0 || >=16.0.0} peerDependencies: - vite: ^3.0.0 + vite: ^4.0.0 vue: ^3.2.25 dependencies: - vite: 3.2.3 - vue: 3.2.41 + vite: 4.0.1 + vue: 3.2.45 dev: true /@vitest/coverage-c8/0.25.1_oullksb5ic6y72oh2wekoaiuii: @@ -2892,114 +3093,113 @@ packages: sirv: 2.0.2 dev: true - /@vue/compiler-core/3.2.41: - resolution: {integrity: sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==} + /@vue/compiler-core/3.2.45: + resolution: {integrity: sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==} dependencies: '@babel/parser': 7.19.1 - '@vue/shared': 3.2.41 + '@vue/shared': 3.2.45 estree-walker: 2.0.2 source-map: 0.6.1 dev: true - /@vue/compiler-dom/3.2.41: - resolution: {integrity: sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==} + /@vue/compiler-dom/3.2.45: + resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==} dependencies: - '@vue/compiler-core': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/compiler-core': 3.2.45 + '@vue/shared': 3.2.45 dev: true - /@vue/compiler-sfc/3.2.41: - resolution: {integrity: sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==} - requiresBuild: true + /@vue/compiler-sfc/3.2.45: + resolution: {integrity: sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==} dependencies: '@babel/parser': 7.19.1 - '@vue/compiler-core': 3.2.41 - '@vue/compiler-dom': 3.2.41 - '@vue/compiler-ssr': 3.2.41 - '@vue/reactivity-transform': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/compiler-core': 3.2.45 + '@vue/compiler-dom': 3.2.45 + '@vue/compiler-ssr': 3.2.45 + '@vue/reactivity-transform': 3.2.45 + '@vue/shared': 3.2.45 estree-walker: 2.0.2 magic-string: 0.25.9 postcss: 8.4.18 source-map: 0.6.1 dev: true - /@vue/compiler-ssr/3.2.41: - resolution: {integrity: sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==} + /@vue/compiler-ssr/3.2.45: + resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==} dependencies: - '@vue/compiler-dom': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/compiler-dom': 3.2.45 + '@vue/shared': 3.2.45 dev: true /@vue/devtools-api/6.4.5: resolution: {integrity: sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==} dev: true - /@vue/reactivity-transform/3.2.41: - resolution: {integrity: sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==} + /@vue/reactivity-transform/3.2.45: + resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==} dependencies: '@babel/parser': 7.19.1 - '@vue/compiler-core': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/compiler-core': 3.2.45 + '@vue/shared': 3.2.45 estree-walker: 2.0.2 magic-string: 0.25.9 dev: true - /@vue/reactivity/3.2.41: - resolution: {integrity: sha512-9JvCnlj8uc5xRiQGZ28MKGjuCoPhhTwcoAdv3o31+cfGgonwdPNuvqAXLhlzu4zwqavFEG5tvaoINQEfxz+l6g==} + /@vue/reactivity/3.2.45: + resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==} dependencies: - '@vue/shared': 3.2.41 + '@vue/shared': 3.2.45 dev: true - /@vue/runtime-core/3.2.41: - resolution: {integrity: sha512-0LBBRwqnI0p4FgIkO9q2aJBBTKDSjzhnxrxHYengkAF6dMOjeAIZFDADAlcf2h3GDALWnblbeprYYpItiulSVQ==} + /@vue/runtime-core/3.2.45: + resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==} dependencies: - '@vue/reactivity': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/reactivity': 3.2.45 + '@vue/shared': 3.2.45 dev: true - /@vue/runtime-dom/3.2.41: - resolution: {integrity: sha512-U7zYuR1NVIP8BL6jmOqmapRAHovEFp7CSw4pR2FacqewXNGqZaRfHoNLQsqQvVQ8yuZNZtxSZy0FFyC70YXPpA==} + /@vue/runtime-dom/3.2.45: + resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==} dependencies: - '@vue/runtime-core': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/runtime-core': 3.2.45 + '@vue/shared': 3.2.45 csstype: 2.6.21 dev: true - /@vue/server-renderer/3.2.41_vue@3.2.41: - resolution: {integrity: sha512-7YHLkfJdTlsZTV0ae5sPwl9Gn/EGr2hrlbcS/8naXm2CDpnKUwC68i1wGlrYAfIgYWL7vUZwk2GkYLQH5CvFig==} + /@vue/server-renderer/3.2.45_vue@3.2.45: + resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==} peerDependencies: - vue: 3.2.41 + vue: 3.2.45 dependencies: - '@vue/compiler-ssr': 3.2.41 - '@vue/shared': 3.2.41 - vue: 3.2.41 + '@vue/compiler-ssr': 3.2.45 + '@vue/shared': 3.2.45 + vue: 3.2.45 dev: true - /@vue/shared/3.2.41: - resolution: {integrity: sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==} + /@vue/shared/3.2.45: + resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==} dev: true - /@vueuse/core/9.4.0_vue@3.2.41: - resolution: {integrity: sha512-JzgenGj1ZF2BHOen5rsFiAyyI9sXAv7aKhNLlm9b7SwYQeKTcxTWdhudonURCSP3Egl9NQaRBzes2lv/1JUt/Q==} + /@vueuse/core/9.6.0_vue@3.2.45: + resolution: {integrity: sha512-qGUcjKQXHgN+jqXEgpeZGoxdCbIDCdVPz3QiF1uyecVGbMuM63o96I1GjYx5zskKgRI0FKSNsVWM7rwrRMTf6A==} dependencies: '@types/web-bluetooth': 0.0.16 - '@vueuse/metadata': 9.4.0 - '@vueuse/shared': 9.4.0_vue@3.2.41 - vue-demi: 0.13.11_vue@3.2.41 + '@vueuse/metadata': 9.6.0 + '@vueuse/shared': 9.6.0_vue@3.2.45 + vue-demi: 0.13.11_vue@3.2.45 transitivePeerDependencies: - '@vue/composition-api' - vue dev: true - /@vueuse/metadata/9.4.0: - resolution: {integrity: sha512-7GKMdGAsJyQJl35MYOz/RDpP0FxuiZBRDSN79QIPbdqYx4Sd0sVTnIC68KJ6Oln0t0SouvSUMvRHuno216Ud2Q==} + /@vueuse/metadata/9.6.0: + resolution: {integrity: sha512-sIC8R+kWkIdpi5X2z2Gk8TRYzmczDwHRhEFfCu2P+XW2JdPoXrziqsGpDDsN7ykBx4ilwieS7JUIweVGhvZ93w==} dev: true - /@vueuse/shared/9.4.0_vue@3.2.41: - resolution: {integrity: sha512-fTuem51KwMCnqUKkI8B57qAIMcFovtGgsCtAeqxIzH3i6nE9VYge+gVfneNHAAy7lj8twbkNfqQSygOPJTm4tQ==} + /@vueuse/shared/9.6.0_vue@3.2.45: + resolution: {integrity: sha512-/eDchxYYhkHnFyrb00t90UfjCx94kRHxc7J1GtBCqCG4HyPMX+krV9XJgVtWIsAMaxKVU4fC8NSUviG1JkwhUQ==} dependencies: - vue-demi: 0.13.11_vue@3.2.41 + vue-demi: 0.13.11_vue@3.2.45 transitivePeerDependencies: - '@vue/composition-api' - vue @@ -3298,6 +3498,14 @@ packages: indent-string: 4.0.0 dev: true + /aggregate-error/4.0.1: + resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} + engines: {node: '>=12'} + dependencies: + clean-stack: 4.2.0 + indent-string: 5.0.0 + dev: true + /ajv-formats/2.1.1_ajv@8.11.0: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -3490,6 +3698,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /arrify/3.0.0: + resolution: {integrity: sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==} + engines: {node: '>=12'} + dev: true + /asn1/0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} dependencies: @@ -3824,6 +4037,16 @@ packages: quick-lru: 4.0.1 dev: true + /camelcase-keys/7.0.2: + resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} + engines: {node: '>=12'} + dependencies: + camelcase: 6.3.0 + map-obj: 4.3.0 + quick-lru: 5.1.1 + type-fest: 1.4.0 + dev: true + /camelcase/5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -3969,6 +4192,13 @@ packages: engines: {node: '>=6'} dev: true + /clean-stack/4.2.0: + resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} + engines: {node: '>=12'} + dependencies: + escape-string-regexp: 5.0.0 + dev: true + /clear-module/4.1.2: resolution: {integrity: sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==} engines: {node: '>=8'} @@ -4318,6 +4548,39 @@ packages: request: 2.88.2 dev: true + /cp-file/9.1.0: + resolution: {integrity: sha512-3scnzFj/94eb7y4wyXRWwvzLFaQp87yyfTnChIjlfYrVqp5lVO3E2hIJMeQIltUT0K2ZAB3An1qXcBmwGyvuwA==} + engines: {node: '>=10'} + dependencies: + graceful-fs: 4.2.10 + make-dir: 3.1.0 + nested-error-stacks: 2.1.1 + p-event: 4.2.0 + dev: true + + /cpy-cli/4.2.0: + resolution: {integrity: sha512-b04b+cbdr29CdpREPKw/itrfjO43Ty0Aj7wRM6M6LoE4GJxZJCk9Xp+Eu1IqztkKh3LxIBt1tDplENsa6KYprg==} + engines: {node: '>=12.20'} + hasBin: true + dependencies: + cpy: 9.0.1 + meow: 10.1.5 + dev: true + + /cpy/9.0.1: + resolution: {integrity: sha512-D9U0DR5FjTCN3oMTcFGktanHnAG5l020yvOCR1zKILmAyPP7I/9pl6NFgRbDcmSENtbK1sQLBz1p9HIOlroiNg==} + engines: {node: ^12.20.0 || ^14.17.0 || >=16.0.0} + dependencies: + arrify: 3.0.0 + cp-file: 9.1.0 + globby: 13.1.2 + junk: 4.0.0 + micromatch: 4.0.5 + nested-error-stacks: 2.1.1 + p-filter: 3.0.0 + p-map: 5.5.0 + dev: true + /create-require/1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true @@ -4902,10 +5165,46 @@ packages: d3-zoom: 3.0.0 dev: false - /dagre-d3-es/7.0.4: - resolution: {integrity: sha512-fQL8ldFR9UYpecz48d1smrXNJ9zGUK38Vl5OzX6Fhn9LR+oQh0GzHRPQylP5kWawmMTKm1QtqcHMVySMJ5CYaQ==} + /d3/7.7.0: + resolution: {integrity: sha512-VEwHCMgMjD2WBsxeRGUE18RmzxT9Bn7ghDpzvTEvkLSBAKgTMydJjouZTjspgQfRHpPt/PB3EHWBa6SSyFQq4g==} + engines: {node: '>=12'} + dependencies: + d3-array: 3.2.0 + d3-axis: 3.0.0 + d3-brush: 3.0.0 + d3-chord: 3.0.1 + d3-color: 3.1.0 + d3-contour: 4.0.0 + d3-delaunay: 6.0.2 + d3-dispatch: 3.0.1 + d3-drag: 3.0.0 + d3-dsv: 3.0.1 + d3-ease: 3.0.1 + d3-fetch: 3.0.1 + d3-force: 3.0.0 + d3-format: 3.1.0 + d3-geo: 3.0.1 + d3-hierarchy: 3.1.2 + d3-interpolate: 3.0.1 + d3-path: 3.0.1 + d3-polygon: 3.0.1 + d3-quadtree: 3.0.1 + d3-random: 3.0.1 + d3-scale: 4.0.2 + d3-scale-chromatic: 3.0.0 + d3-selection: 3.0.0 + d3-shape: 3.1.0 + d3-time: 3.0.0 + d3-time-format: 4.1.0 + d3-timer: 3.0.1 + d3-transition: 3.0.1_d3-selection@3.0.0 + d3-zoom: 3.0.0 + dev: false + + /dagre-d3-es/7.0.6: + resolution: {integrity: sha512-CaaE/nZh205ix+Up4xsnlGmpog5GGm81Upi2+/SBHxwNwrccBb3K51LzjZ1U6hgvOlAEUsVWf1xSTzCyKpJ6+Q==} dependencies: - d3: 7.6.1 + d3: 7.7.0 lodash-es: 4.17.21 dev: false @@ -5017,6 +5316,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /decamelize/5.0.1: + resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} + engines: {node: '>=10'} + dev: true + /decimal.js/10.4.1: resolution: {integrity: sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw==} dev: true @@ -5508,6 +5812,36 @@ packages: esbuild-windows-arm64: 0.15.13 dev: true + /esbuild/0.16.7: + resolution: {integrity: sha512-P6OBFYFSQOGzfApqCeYKqfKRRbCIRsdppTXFo4aAvtiW3o8TTyiIplBvHJI171saPAiy3WlawJHCveJVIOIx1A==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.16.7 + '@esbuild/android-arm64': 0.16.7 + '@esbuild/android-x64': 0.16.7 + '@esbuild/darwin-arm64': 0.16.7 + '@esbuild/darwin-x64': 0.16.7 + '@esbuild/freebsd-arm64': 0.16.7 + '@esbuild/freebsd-x64': 0.16.7 + '@esbuild/linux-arm': 0.16.7 + '@esbuild/linux-arm64': 0.16.7 + '@esbuild/linux-ia32': 0.16.7 + '@esbuild/linux-loong64': 0.16.7 + '@esbuild/linux-mips64el': 0.16.7 + '@esbuild/linux-ppc64': 0.16.7 + '@esbuild/linux-riscv64': 0.16.7 + '@esbuild/linux-s390x': 0.16.7 + '@esbuild/linux-x64': 0.16.7 + '@esbuild/netbsd-x64': 0.16.7 + '@esbuild/openbsd-x64': 0.16.7 + '@esbuild/sunos-x64': 0.16.7 + '@esbuild/win32-arm64': 0.16.7 + '@esbuild/win32-ia32': 0.16.7 + '@esbuild/win32-x64': 0.16.7 + dev: true + /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -5532,6 +5866,11 @@ packages: engines: {node: '>=10'} dev: true + /escape-string-regexp/5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: true + /escodegen/1.14.3: resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==} engines: {node: '>=4.0'} @@ -6818,6 +7157,11 @@ packages: engines: {node: '>=8'} dev: true + /indent-string/5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + dev: true + /inflight/1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -7779,6 +8123,11 @@ packages: verror: 1.10.0 dev: true + /junk/4.0.0: + resolution: {integrity: sha512-ojtSU++zLJ3jQG9bAYjg94w+/DOJtRyD7nPaerMFrBhmdVmiV5/exYH5t4uHga4G/95nT6hr1OJoKIFbYbrW5w==} + engines: {node: '>=12.20'} + dev: true + /keyv/4.5.0: resolution: {integrity: sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==} dependencies: @@ -8165,6 +8514,24 @@ packages: fs-monkey: 1.0.3 dev: true + /meow/10.1.5: + resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 7.0.2 + decamelize: 5.0.1 + decamelize-keys: 1.1.0 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 8.0.0 + redent: 4.0.0 + trim-newlines: 4.0.2 + type-fest: 1.4.0 + yargs-parser: 20.2.9 + dev: true + /meow/8.1.2: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} @@ -8534,6 +8901,10 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true + /nested-error-stacks/2.1.1: + resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==} + dev: true + /netmask/2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} @@ -8720,6 +9091,20 @@ packages: engines: {node: '>=8'} dev: true + /p-event/4.2.0: + resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} + engines: {node: '>=8'} + dependencies: + p-timeout: 3.2.0 + dev: true + + /p-filter/3.0.0: + resolution: {integrity: sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + p-map: 5.5.0 + dev: true + /p-finally/1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -8772,6 +9157,13 @@ packages: aggregate-error: 3.1.0 dev: true + /p-map/5.5.0: + resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} + engines: {node: '>=12'} + dependencies: + aggregate-error: 4.0.1 + dev: true + /p-retry/4.6.2: resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} engines: {node: '>=8'} @@ -8780,6 +9172,13 @@ packages: retry: 0.13.1 dev: true + /p-timeout/3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + dependencies: + p-finally: 1.0.0 + dev: true + /p-try/2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -9022,6 +9421,15 @@ packages: source-map-js: 1.0.2 dev: true + /postcss/8.4.20: + resolution: {integrity: sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + /preact/10.11.0: resolution: {integrity: sha512-Fk6+vB2kb6mSJfDgODq0YDhMfl0HNtK5+Uc9QqECO4nlyPAQwCI+BKyWO//idA7ikV7o+0Fm6LQmNuQi1wXI1w==} dev: true @@ -9207,6 +9615,15 @@ packages: type-fest: 0.8.1 dev: true + /read-pkg-up/8.0.0: + resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} + engines: {node: '>=12'} + dependencies: + find-up: 5.0.0 + read-pkg: 6.0.0 + type-fest: 1.4.0 + dev: true + /read-pkg/5.2.0: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} @@ -9217,6 +9634,16 @@ packages: type-fest: 0.6.0 dev: true + /read-pkg/6.0.0: + resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} + engines: {node: '>=12'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 3.0.3 + parse-json: 5.2.0 + type-fest: 1.4.0 + dev: true + /readable-stream/1.1.14: resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} dependencies: @@ -9269,6 +9696,14 @@ packages: strip-indent: 3.0.0 dev: true + /redent/4.0.0: + resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} + engines: {node: '>=12'} + dependencies: + indent-string: 5.0.0 + strip-indent: 4.0.0 + dev: true + /regexp-tree/0.1.24: resolution: {integrity: sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==} hasBin: true @@ -9485,6 +9920,14 @@ packages: fsevents: 2.3.2 dev: true + /rollup/3.7.4: + resolution: {integrity: sha512-jN9rx3k5pfg9H9al0r0y1EYKSeiRANZRYX32SuNXAnKzh6cVyf4LZVto1KAuDnbHT03E1CpsgqDKaqQ8FZtgxw==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + /run-parallel/1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -9838,6 +10281,7 @@ packages: /sourcemap-codec/1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead dev: true /spawn-command/0.0.2-1: @@ -10062,6 +10506,13 @@ packages: min-indent: 1.0.1 dev: true + /strip-indent/4.0.0: + resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} + engines: {node: '>=12'} + dependencies: + min-indent: 1.0.1 + dev: true + /strip-json-comments/3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} @@ -10286,6 +10737,11 @@ packages: engines: {node: '>=8'} dev: true + /trim-newlines/4.0.2: + resolution: {integrity: sha512-GJtWyq9InR/2HRiLZgpIKv+ufIKrVrvjQWEj7PxAXNc5dwbNJkqhAUoAGgzRmULAnoOM5EIpveYd3J2VeSAIew==} + engines: {node: '>=12'} + dev: true + /trough/2.1.0: resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} dev: true @@ -10429,6 +10885,11 @@ packages: engines: {node: '>=8'} dev: true + /type-fest/1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + dev: true + /type-is/1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -10744,8 +11205,41 @@ packages: fsevents: 2.3.2 dev: true - /vitepress-plugin-search/1.0.4-alpha.15_s3edpouswd4dgoi2en7bdlrp54: - resolution: {integrity: sha512-Ef/VkhTVYlECVI0H9Ck6745UNPfYFppAqnlxVSMJXdxP2vjOZ5TYNczlTTQ2p9dh16MFw/IurbL1/GrG4nXdNw==} + /vite/4.0.1: + resolution: {integrity: sha512-kZQPzbDau35iWOhy3CpkrRC7It+HIHtulAzBhMqzGHKRf/4+vmh8rPDDdv98SWQrFWo6//3ozwsRmwQIPZsK9g==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true + peerDependencies: + '@types/node': '>= 14' + less: '*' + sass: '*' + stylus: '*' + sugarss: '*' + terser: ^5.4.0 + peerDependenciesMeta: + '@types/node': + optional: true + less: + optional: true + sass: + optional: true + stylus: + optional: true + sugarss: + optional: true + terser: + optional: true + dependencies: + esbuild: 0.16.7 + postcss: 8.4.20 + resolve: 1.22.1 + rollup: 3.7.4 + optionalDependencies: + fsevents: 2.3.2 + dev: true + + /vitepress-plugin-search/1.0.4-alpha.16_ifjhkyx3os4sbm7zdnvthc52am: + resolution: {integrity: sha512-D+rs7bwzH+IO+7T9NlxvqSOqmSKbN1yHxUoqClTy5JH+DomL3CcrH2TgSvXc2s58ztlc1dC07c7THo4cNjlUAg==} engines: {node: ^14.13.1 || ^16.7.0 || >=18} peerDependencies: flexsearch: ^0.7.31 @@ -10758,23 +11252,23 @@ packages: flexsearch: 0.7.31 markdown-it: 13.0.1 vite: 3.2.3 - vitepress: 1.0.0-alpha.28_tbpndr44ulefs3hehwpi2mkf2y - vue: 3.2.41 + vitepress: 1.0.0-alpha.31_tbpndr44ulefs3hehwpi2mkf2y + vue: 3.2.45 dev: true - /vitepress/1.0.0-alpha.28_tbpndr44ulefs3hehwpi2mkf2y: - resolution: {integrity: sha512-pvbLssDMgLUN1terajmPlFBxHSDGO4DqwexKbjFyr7LeELerVuwGrG6F2J1hxmwOlbpLd1kHXEDqGm9JX/kTDQ==} + /vitepress/1.0.0-alpha.31_tbpndr44ulefs3hehwpi2mkf2y: + resolution: {integrity: sha512-FWFXLs7WLbFbemxjBWo2S2+qUZCIoeLLyAKfVUpIu3LUB8oQ8cyIANRGO6f6zsM51u2bvJU9Sm+V6Z0WjOWS2Q==} hasBin: true dependencies: '@docsearch/css': 3.3.0 '@docsearch/js': 3.3.0_tbpndr44ulefs3hehwpi2mkf2y - '@vitejs/plugin-vue': 3.2.0_vite@3.2.3+vue@3.2.41 + '@vitejs/plugin-vue': 4.0.0_vite@4.0.1+vue@3.2.45 '@vue/devtools-api': 6.4.5 - '@vueuse/core': 9.4.0_vue@3.2.41 + '@vueuse/core': 9.6.0_vue@3.2.45 body-scroll-lock: 4.0.0-beta.0 shiki: 0.11.1 - vite: 3.2.3 - vue: 3.2.41 + vite: 4.0.1 + vue: 3.2.45 transitivePeerDependencies: - '@algolia/client-search' - '@types/node' @@ -10926,7 +11420,7 @@ packages: resolution: {integrity: sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==} dev: true - /vue-demi/0.13.11_vue@3.2.41: + /vue-demi/0.13.11_vue@3.2.45: resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} engines: {node: '>=12'} hasBin: true @@ -10938,17 +11432,17 @@ packages: '@vue/composition-api': optional: true dependencies: - vue: 3.2.41 + vue: 3.2.45 dev: true - /vue/3.2.41: - resolution: {integrity: sha512-uuuvnrDXEeZ9VUPljgHkqB5IaVO8SxhPpqF2eWOukVrBnRBx2THPSGQBnVRt0GrIG1gvCmFXMGbd7FqcT1ixNQ==} + /vue/3.2.45: + resolution: {integrity: sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==} dependencies: - '@vue/compiler-dom': 3.2.41 - '@vue/compiler-sfc': 3.2.41 - '@vue/runtime-dom': 3.2.41 - '@vue/server-renderer': 3.2.41_vue@3.2.41 - '@vue/shared': 3.2.41 + '@vue/compiler-dom': 3.2.45 + '@vue/compiler-sfc': 3.2.45 + '@vue/runtime-dom': 3.2.45 + '@vue/server-renderer': 3.2.45_vue@3.2.45 + '@vue/shared': 3.2.45 dev: true /w3c-hr-time/1.0.2: