diff --git a/.eslintrc.json b/.eslintrc.json index 1d052e22b..9d7eacecd 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -29,7 +29,8 @@ "jsdoc", "json", "@cspell", - "lodash" + "lodash", + "unicorn" ], "rules": { "curly": "error", @@ -63,7 +64,29 @@ } ], "no-only-tests/no-only-tests": "error", - "lodash/import-scope": ["error", "method"] + "lodash/import-scope": ["error", "method"], + "unicorn/better-regex": "error", + "unicorn/no-abusive-eslint-disable": "error", + "unicorn/no-array-push-push": "error", + "unicorn/no-for-loop": "error", + "unicorn/no-instanceof-array": "error", + "unicorn/no-typeof-undefined": "error", + "unicorn/no-unnecessary-await": "error", + "unicorn/no-unsafe-regex": "warn", + "unicorn/no-useless-promise-resolve-reject": "error", + "unicorn/prefer-array-find": "error", + "unicorn/prefer-array-flat-map": "error", + "unicorn/prefer-array-index-of": "error", + "unicorn/prefer-array-some": "error", + "unicorn/prefer-default-parameters": "error", + "unicorn/prefer-includes": "error", + "unicorn/prefer-negative-index": "error", + "unicorn/prefer-object-from-entries": "error", + "unicorn/prefer-string-starts-ends-with": "error", + "unicorn/prefer-string-trim-start-end": "error", + "unicorn/string-content": "error", + "unicorn/prefer-spread": "error", + "unicorn/no-lonely-if": "error" }, "overrides": [ { diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8ba06989d..d67144540 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -9,7 +9,7 @@ on: - ready_for_review permissions: - contents: read + contents: write jobs: lint: @@ -39,5 +39,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 2b2ff559b..5f4936ab6 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/__mocks__/c4Renderer.js b/__mocks__/c4Renderer.js new file mode 100644 index 000000000..576d5d863 --- /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 000000000..1ad95806f --- /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 000000000..1c20de4b1 --- /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 f90d93557..af35020c5 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 000000000..845d641f7 --- /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 000000000..89cc86031 --- /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 000000000..957249832 --- /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 000000000..1daa82ca4 --- /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 000000000..2bc77c0b1 --- /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 000000000..317c69901 --- /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 000000000..48d8997ac --- /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 000000000..11080c6bb --- /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 000000000..a2d103b50 --- /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 428003cef..45e7fe99a 100644 --- a/cSpell.json +++ b/cSpell.json @@ -16,6 +16,7 @@ "braintree", "brolin", "brotli", + "classdef", "codedoc", "colour", "cpettitt", @@ -31,6 +32,7 @@ "edgechromium", "faber", "flatmap", + "ftplugin", "gantt", "gitea", "gitgraph", @@ -62,9 +64,13 @@ "phpbb", "plantuml", "playfair", + "pnpm", "podlite", "ranksep", + "rect", + "rects", "redmine", + "roledescription", "sandboxed", "setupgraphviewbox", "shiki", diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index b75e682c6..c0156eee3 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 = Array.from(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/gitGraph.spec.js b/cypress/integration/rendering/gitGraph.spec.js index 0b5048b44..43f91a983 100644 --- a/cypress/integration/rendering/gitGraph.spec.js +++ b/cypress/integration/rendering/gitGraph.spec.js @@ -328,7 +328,7 @@ describe('Git Graph diagram', () => { title: simple gitGraph --- gitGraph - commit + commit id: "1-abcdefg" `, {} ); diff --git a/cypress/integration/rendering/requirement.spec.js b/cypress/integration/rendering/requirement.spec.js index be27f39fa..0bf9014bf 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 = Array.from(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 0eca01873..047e240fc 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/integration/rendering/theme.spec.js b/cypress/integration/rendering/theme.spec.js index 0eb8d111b..ef3bd9a4b 100644 --- a/cypress/integration/rendering/theme.spec.js +++ b/cypress/integration/rendering/theme.spec.js @@ -25,6 +25,7 @@ describe('themeCSS balancing, it', () => { }); }); +// TODO: Delete/Rename this describe, keeping the inner contents. describe('Pie Chart', () => { // beforeEach(()=>{ // cy.clock((new Date('2014-06-09')).getTime()); diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 084b8151e..83472039d 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -56,28 +56,30 @@
Security check
-flowchart TD
-    A --> B
-    B --> C
-    A --> C
+flowchart LR
+    %% Actors
+    A
+    subgraph Sub
+        B --> C
+    end
+
+    %% 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
@@ -91,14 +93,18 @@ mindmap
     
     
     
-    
+    
 
-    
 ```
@@ -85,7 +85,7 @@ Example:
       B-->D(fa:fa-spinner);
     
diff --git a/docs/index.html.todo b/docs/index.html.todo deleted file mode 100644 index 8728338f9..000000000 --- a/docs/index.html.todo +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - mermaid - Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, - gantt charts and git graphs. - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - diff --git a/docs/intro/index.md b/docs/intro/index.md index b8a27acff..5aa068e27 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 498aa1595..2a05a1fdd 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 a4c9e31a5..5f4d87792 100644 --- a/docs/misc/integrations.md +++ b/docs/misc/integrations.md @@ -109,10 +109,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) @@ -122,6 +122,7 @@ They also serve as proof of concept, for the variety of things that can be built - [Draw.io](https://draw.io) - [Plugin](https://github.com/nopeslide/drawio_mermaid_plugin) - [Inkdrop](https://www.inkdrop.app) - [Plugin](https://github.com/inkdropapp/inkdrop-mermaid) - [Vim](https://www.vim.org) + - [Official Vim Syntax and ftplugin](https://github.com/craigmac/vim-mermaid) - [Vim Diagram Syntax](https://github.com/zhaozg/vim-diagram) - [GNU Emacs](https://www.gnu.org/software/emacs/) - [Major mode for .mmd files](https://github.com/abrochard/mermaid-mode) diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index 5870d0743..c4fc07732 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: diff --git a/docs/syntax/mindmap.md b/docs/syntax/mindmap.md index 628461c4f..0214a5512 100644 --- a/docs/syntax/mindmap.md +++ b/docs/syntax/mindmap.md @@ -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 628fcecd2..11dcaa236 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "9.2.2", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", - "packageManager": "pnpm@7.17.0", + "packageManager": "pnpm@7.18.1", "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", @@ -91,7 +91,6 @@ "express": "^4.18.2", "globby": "^13.1.2", "husky": "^8.0.2", - "identity-obj-proxy": "^3.0.0", "jest": "^29.3.1", "jison": "^0.4.18", "js-yaml": "^4.1.0", @@ -107,15 +106,8 @@ "ts-node": "^10.9.1", "typescript": "^4.8.4", "vite": "^3.2.3", - "vitepress": "^1.0.0-alpha.28", - "vitepress-plugin-mermaid": "^2.0.8", - "vitepress-plugin-search": "^1.0.4-alpha.15", - "vitest": "^0.25.1" + "vitest": "^0.25.3" }, - "sideEffects": [ - "**/*.css", - "**/*.scss" - ], "volta": { "node": "18.12.1" } diff --git a/packages/mermaid-mindmap/src/mermaidUtils.ts b/packages/mermaid-mindmap/src/mermaidUtils.ts index 4834b936d..4f894b48d 100644 --- a/packages/mermaid-mindmap/src/mermaidUtils.ts +++ b/packages/mermaid-mindmap/src/mermaidUtils.ts @@ -1,6 +1,7 @@ const warning = (s: string) => { // Todo remove debug code - console.error('Log function was called before initialization', s); // eslint-disable-line + // eslint-disable-next-line no-console + console.error('Log function was called before initialization', s); }; export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 4b24fb661..66fa4f224 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -1,11 +1,10 @@ { "name": "mermaid", - "version": "9.2.2", + "version": "9.2.3-rc.1", "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", @@ -57,8 +56,6 @@ "d3": "^7.0.0", "dagre-d3-es": "7.0.4", "dompurify": "2.4.1", - "fast-clone": "^1.5.13", - "graphlib": "^2.1.8", "khroma": "^2.0.0", "lodash-es": "^4.17.21", "moment-mini": "^2.24.0", @@ -82,7 +79,6 @@ "coveralls": "^3.1.1", "cspell": "^6.14.3", "globby": "^13.1.2", - "identity-obj-proxy": "^3.0.0", "jison": "^0.4.18", "js-base64": "^3.7.2", "jsdom": "^20.0.2", @@ -96,7 +92,9 @@ "typedoc": "^0.23.18", "typedoc-plugin-markdown": "^3.13.6", "typescript": "^4.8.4", - "unist-util-flatmap": "^1.0.0" + "unist-util-flatmap": "^1.0.0", + "vitepress": "^1.0.0-alpha.28", + "vitepress-plugin-search": "^1.0.4-alpha.15" }, "files": [ "dist", diff --git a/packages/mermaid/src/accessibility.js b/packages/mermaid/src/accessibility.js deleted file mode 100644 index c59ba270c..000000000 --- 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 (typeof 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 000000000..c633d0e15 --- /dev/null +++ b/packages/mermaid/src/accessibility.spec.ts @@ -0,0 +1,219 @@ +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 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 nothing 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).not.toHaveBeenCalled(); + }); + }); + + 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 000000000..b9e088e0b --- /dev/null +++ b/packages/mermaid/src/accessibility.ts @@ -0,0 +1,55 @@ +/** + * Accessibility (a11y) functions, types, helpers + * + */ +import { D3Element } from './mermaidAPI'; + +import isEmpty from 'lodash-es/isEmpty'; + +/** + * Add aria-roledescription to the svg element to the diagramType + * + * @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) { + 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/assignWithDepth.js b/packages/mermaid/src/assignWithDepth.js index eff568d3e..898481c30 100644 --- a/packages/mermaid/src/assignWithDepth.js +++ b/packages/mermaid/src/assignWithDepth.js @@ -32,20 +32,20 @@ const assignWithDepth = function (dst, src, config) { return dst; } else if (Array.isArray(src) && Array.isArray(dst)) { src.forEach((s) => { - if (dst.indexOf(s) === -1) { + if (!dst.includes(s)) { dst.push(s); } }); return dst; } - if (typeof dst === 'undefined' || depth <= 0) { + if (dst === undefined || depth <= 0) { if (dst !== undefined && dst !== null && typeof dst === 'object' && typeof src === 'object') { return Object.assign(dst, src); } else { return src; } } - if (typeof src !== 'undefined' && typeof dst === 'object' && typeof src === 'object') { + if (src !== undefined && typeof dst === 'object' && typeof src === 'object') { Object.keys(src).forEach((key) => { if ( typeof src[key] === 'object' && diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts index 3ff946af2..8750c8fa5 100644 --- a/packages/mermaid/src/config.ts +++ b/packages/mermaid/src/config.ts @@ -18,8 +18,7 @@ export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: any[]) // Join directives let sumOfDirectives: MermaidConfig = {}; - for (let i = 0; i < _directives.length; i++) { - const d = _directives[i]; + for (const d of _directives) { sanitize(d); // Apply the data from the directive where the the overrides the themeVariables @@ -153,7 +152,7 @@ export const getConfig = (): MermaidConfig => { export const sanitize = (options: any) => { // Checking that options are not in the list of excluded options ['secure', ...(siteConfig.secure ?? [])].forEach((key) => { - if (typeof options[key] !== 'undefined') { + if (options[key] !== undefined) { // DO NOT attempt to print options[key] within `${}` as a malicious script // can exploit the logger's attempt to stringify the value and execute arbitrary code log.debug(`Denied attempt to modify a secure key ${key}`, options[key]); @@ -170,14 +169,13 @@ export const sanitize = (options: any) => { // Check that there no attempts of xss, there should be no tags at all in the directive // blocking data urls as base64 urls can contain svg's with inline script tags Object.keys(options).forEach((key) => { - if (typeof options[key] === 'string') { - if ( - options[key].indexOf('<') > -1 || - options[key].indexOf('>') > -1 || - options[key].indexOf('url(data:') > -1 - ) { - delete options[key]; - } + if ( + typeof options[key] === 'string' && + (options[key].includes('<') || + options[key].includes('>') || + options[key].includes('url(data:')) + ) { + delete options[key]; } if (typeof options[key] === 'object') { sanitize(options[key]); diff --git a/packages/mermaid/src/dagre-wrapper/clusters.js b/packages/mermaid/src/dagre-wrapper/clusters.js index 40729dead..57c3ff513 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/createLabel.js b/packages/mermaid/src/dagre-wrapper/createLabel.js index 9d7951798..af5032096 100644 --- a/packages/mermaid/src/dagre-wrapper/createLabel.js +++ b/packages/mermaid/src/dagre-wrapper/createLabel.js @@ -54,7 +54,7 @@ const createLabel = (_vertexText, style, isTitle, isNode) => { const node = { isNode, label: decodeEntities(vertexText).replace( - /fa[lrsb]?:fa-[\w-]+/g, + /fa[blrs]?:fa-[\w-]+/g, (s) => `` ), labelStyle: style.replace('fill:', 'color:'), @@ -74,7 +74,7 @@ const createLabel = (_vertexText, style, isTitle, isNode) => { rows = []; } - for (let j = 0; j < rows.length; j++) { + for (const row of rows) { const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan'); tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); tspan.setAttribute('dy', '1em'); @@ -84,7 +84,7 @@ const createLabel = (_vertexText, style, isTitle, isNode) => { } else { tspan.setAttribute('class', 'row'); } - tspan.textContent = rows[j].trim(); + tspan.textContent = row.trim(); svgLabel.appendChild(tspan); } return svgLabel; diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index 4d23ca3ea..bb22cee83 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 + ')'); } @@ -324,7 +336,7 @@ const cutPathAtIntersect = (_points, boundryNode) => { pointPresent = pointPresent || (p.x === inter.x && p.y === inter.y); }); // // if (!pointPresent) { - if (!points.find((e) => e.x === inter.x && e.y === inter.y)) { + if (!points.some((e) => e.x === inter.x && e.y === inter.y)) { points.push(inter); } else { log.warn('abc88 no intersect', inter, points); @@ -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 8b77c8520..e2d7d51f4 100644 --- a/packages/mermaid/src/dagre-wrapper/index.js +++ b/packages/mermaid/src/dagre-wrapper/index.js @@ -37,7 +37,7 @@ const recursiveRender = (_elem, graph, diagramtype, parentCluster) => { // to the abstract node and is later used by dagre for the layout graph.nodes().forEach(function (v) { const node = graph.node(v); - if (typeof parentCluster !== 'undefined') { + if (parentCluster !== undefined) { const data = JSON.parse(JSON.stringify(parentCluster.clusterData)); // data.clusterPositioning = true; log.info('Setting data for cluster XXX (', v, ') ', data, parentCluster); diff --git a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js index 15fadc940..5722f7cc0 100644 --- a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js +++ b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js @@ -4,27 +4,20 @@ import * as graphlibJson from 'dagre-d3-es/src/graphlib/json'; import * as graphlib from 'dagre-d3-es/src/graphlib'; export let clusterDb = {}; -let decendants = {}; +let descendants = {}; let parents = {}; export const clear = () => { - decendants = {}; + descendants = {}; parents = {}; clusterDb = {}; }; -const isDecendant = (id, ancenstorId) => { +const isDescendant = (id, ancenstorId) => { // if (id === ancenstorId) return true; - log.trace( - 'In isDecendant', - ancenstorId, - ' ', - id, - ' = ', - decendants[ancenstorId].indexOf(id) >= 0 - ); - if (decendants[ancenstorId].indexOf(id) >= 0) { + log.trace('In isDecendant', ancenstorId, ' ', id, ' = ', descendants[ancenstorId].includes(id)); + if (descendants[ancenstorId].includes(id)) { return true; } @@ -32,7 +25,7 @@ const isDecendant = (id, ancenstorId) => { }; const edgeInCluster = (edge, clusterId) => { - log.info('Decendants of ', clusterId, ' is ', decendants[clusterId]); + log.info('Decendants of ', clusterId, ' is ', descendants[clusterId]); log.info('Edge is ', edge); // Edges to/from the cluster is not in the cluster, they are in the parent if (edge.v === clusterId) { @@ -42,15 +35,15 @@ const edgeInCluster = (edge, clusterId) => { return false; } - if (!decendants[clusterId]) { + if (!descendants[clusterId]) { log.debug('Tilt, ', clusterId, ',not in decendants'); return false; } return ( - decendants[clusterId].indexOf(edge.v) >= 0 || - isDecendant(edge.v, clusterId) || - isDecendant(edge.w, clusterId) || - decendants[clusterId].indexOf(edge.w) >= 0 + descendants[clusterId].includes(edge.v) || + isDescendant(edge.v, clusterId) || + isDescendant(edge.w, clusterId) || + descendants[clusterId].includes(edge.w) ); }; @@ -132,14 +125,14 @@ const copy = (clusterId, graph, newGraph, rootId) => { graph.removeNode(node); }); }; -export const extractDecendants = (id, graph) => { +export const extractDescendants = (id, graph) => { // log.debug('Extracting ', id); const children = graph.children(id); - let res = [].concat(children); + let res = [...children]; - for (let i = 0; i < children.length; i++) { - parents[children[i]] = id; - res = res.concat(extractDecendants(children[i], graph)); + for (const child of children) { + parents[child] = id; + res = [...res, ...extractDescendants(child, graph)]; } return res; @@ -154,13 +147,13 @@ export const extractDecendants = (id, graph) => { export const validate = (graph) => { const edges = graph.edges(); log.trace('Edges: ', edges); - for (let i = 0; i < edges.length; i++) { - if (graph.children(edges[i].v).length > 0) { - log.trace('The node ', edges[i].v, ' is part of and edge even though it has children'); + for (const edge of edges) { + if (graph.children(edge.v).length > 0) { + log.trace('The node ', edge.v, ' is part of and edge even though it has children'); return false; } - if (graph.children(edges[i].w).length > 0) { - log.trace('The node ', edges[i].w, ' is part of and edge even though it has children'); + if (graph.children(edge.w).length > 0) { + log.trace('The node ', edge.w, ' is part of and edge even though it has children'); return false; } } @@ -183,8 +176,8 @@ export const findNonClusterChild = (id, graph) => { log.trace('This is a valid node', id); return id; } - for (let i = 0; i < children.length; i++) { - const _id = findNonClusterChild(children[i], graph); + for (const child of children) { + const _id = findNonClusterChild(child, graph); if (_id) { log.trace('Found replacement for', id, ' => ', _id); return _id; @@ -226,7 +219,7 @@ export const adjustClustersAndEdges = (graph, depth) => { ' Replacement id in edges: ', findNonClusterChild(id, graph) ); - decendants[id] = extractDecendants(id, graph); + descendants[id] = extractDescendants(id, graph); clusterDb[id] = { id: findNonClusterChild(id, graph), clusterData: graph.node(id) }; } }); @@ -236,7 +229,7 @@ export const adjustClustersAndEdges = (graph, depth) => { const children = graph.children(id); const edges = graph.edges(); if (children.length > 0) { - log.debug('Cluster identified', id, decendants); + log.debug('Cluster identified', id, descendants); edges.forEach((edge) => { // log.debug('Edge, decendants: ', edge, decendants[id]); @@ -245,19 +238,19 @@ export const adjustClustersAndEdges = (graph, depth) => { // Any edge where either the one of the nodes is descending to the cluster but not the other // if (decendants[id].indexOf(edge.v) < 0 && decendants[id].indexOf(edge.w) < 0) { - const d1 = isDecendant(edge.v, id); - const d2 = isDecendant(edge.w, id); + const d1 = isDescendant(edge.v, id); + const d2 = isDescendant(edge.w, id); // d1 xor d2 - if either d1 is true and d2 is false or the other way around if (d1 ^ d2) { log.warn('Edge: ', edge, ' leaves cluster ', id); - log.warn('Decendants of XXX ', id, ': ', decendants[id]); + log.warn('Decendants of XXX ', id, ': ', descendants[id]); clusterDb[id].externalConnections = true; } } }); } else { - log.debug('Not a cluster ', id, decendants); + log.debug('Not a cluster ', id, descendants); } }); @@ -277,7 +270,7 @@ export const adjustClustersAndEdges = (graph, depth) => { 'ids:', e.v, e.w, - 'Translateing: ', + 'Translating: ', clusterDb[e.v], ' --- ', clusterDb[e.w] @@ -347,8 +340,7 @@ export const extractor = (graph, depth) => { // for (let i = 0;) let nodes = graph.nodes(); let hasChildren = false; - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; + for (const node of nodes) { const children = graph.children(node); hasChildren = hasChildren || children.length > 0; } @@ -360,9 +352,7 @@ export const extractor = (graph, depth) => { // const clusters = Object.keys(clusterDb); // clusters.forEach(clusterId => { log.debug('Nodes = ', nodes, depth); - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; - + for (const node of nodes) { log.debug( 'Extracting node', node, @@ -394,11 +384,9 @@ export const extractor = (graph, depth) => { const graphSettings = graph.graph(); let dir = graphSettings.rankdir === 'TB' ? 'LR' : 'TB'; - if (clusterDb[node]) { - if (clusterDb[node].clusterData && clusterDb[node].clusterData.dir) { - dir = clusterDb[node].clusterData.dir; - log.warn('Fixing dir', clusterDb[node].clusterData.dir, dir); - } + if (clusterDb[node] && clusterDb[node].clusterData && clusterDb[node].clusterData.dir) { + dir = clusterDb[node].clusterData.dir; + log.warn('Fixing dir', clusterDb[node].clusterData.dir, dir); } const clusterGraph = new graphlib.Graph({ @@ -446,8 +434,7 @@ export const extractor = (graph, depth) => { nodes = graph.nodes(); log.warn('New list of nodes', nodes); - for (let i = 0; i < nodes.length; i++) { - const node = nodes[i]; + for (const node of nodes) { const data = graph.node(node); log.warn(' Now next level', node, data); if (data.clusterNode) { @@ -464,7 +451,7 @@ const sorter = (graph, nodes) => { nodes.forEach((node) => { const children = graph.children(node); const sorted = sorter(graph, children); - result = result.concat(sorted); + result = [...result, ...sorted]; }); return result; diff --git a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js index 49b022f4d..f594e3430 100644 --- a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js +++ b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js @@ -3,7 +3,7 @@ import * as graphlib from 'dagre-d3-es/src/graphlib'; import { validate, adjustClustersAndEdges, - extractDecendants, + extractDescendants, sortNodesByHierarchy, } from './mermaid-graphlib'; import { setLogLevel, log } from '../logger'; @@ -400,7 +400,7 @@ flowchart TB expect(aGraph.parent('B')).toBe(undefined); }); }); -describe('extractDecendants', function () { +describe('extractDescendants', function () { let g; beforeEach(function () { setLogLevel(1); @@ -443,9 +443,9 @@ describe('extractDecendants', function () { g.setEdge('A', 'C', { data: 'link2' }, '2'); // log.info(g.edges()) - const d1 = extractDecendants('A', g); - const d2 = extractDecendants('B', g); - const d3 = extractDecendants('C', g); + const d1 = extractDescendants('A', g); + const d2 = extractDescendants('B', g); + const d3 = extractDescendants('C', g); expect(d1).toEqual(['a']); expect(d2).toEqual(['b']); diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js index 316432b95..fda789323 100644 --- a/packages/mermaid/src/dagre-wrapper/nodes.js +++ b/packages/mermaid/src/dagre-wrapper/nodes.js @@ -391,12 +391,10 @@ const labelRect = (parent, node) => { function applyNodePropertyBorders(rect, borders, totalWidth, totalHeight) { const strokeDashArray = []; const addBorder = (length) => { - strokeDashArray.push(length); - strokeDashArray.push(0); + strokeDashArray.push(length, 0); }; const skipBorder = (length) => { - strokeDashArray.push(0); - strokeDashArray.push(length); + strokeDashArray.push(0, length); }; if (borders.includes('t')) { log.debug('add top border'); diff --git a/packages/mermaid/src/dagre-wrapper/shapes/util.js b/packages/mermaid/src/dagre-wrapper/shapes/util.js index dfd1a93ba..6de0da638 100644 --- a/packages/mermaid/src/dagre-wrapper/shapes/util.js +++ b/packages/mermaid/src/dagre-wrapper/shapes/util.js @@ -21,7 +21,7 @@ export const labelHelper = (parent, node, _classes, isNode) => { // Replace labelText with default value if undefined let labelText; - if (typeof node.labelText === 'undefined') { + if (node.labelText === undefined) { labelText = ''; } else { labelText = typeof node.labelText === 'string' ? node.labelText : node.labelText[0]; diff --git a/packages/mermaid/src/diagram-api/detectType.ts b/packages/mermaid/src/diagram-api/detectType.ts index 6f9857221..2ff0b5532 100644 --- a/packages/mermaid/src/diagram-api/detectType.ts +++ b/packages/mermaid/src/diagram-api/detectType.ts @@ -3,8 +3,7 @@ import { log } from '../logger'; import { DetectorRecord, DiagramDetector, DiagramLoader } from './types'; import { frontMatterRegex } from './frontmatter'; -const directive = - /[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi; +const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; const anyComment = /\s*%%.*\n/gm; const detectors: Record = {}; diff --git a/packages/mermaid/src/diagram-api/frontmatter.ts b/packages/mermaid/src/diagram-api/frontmatter.ts index 800e7399b..d6811388c 100644 --- a/packages/mermaid/src/diagram-api/frontmatter.ts +++ b/packages/mermaid/src/diagram-api/frontmatter.ts @@ -7,7 +7,7 @@ import * as yaml from 'js-yaml'; // Note that JS doesn't support the "\A" anchor, which means we can't use // multiline mode. // Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents -export const frontMatterRegex = /^(?:---\s*[\r\n])(.*?)(?:[\r\n]---\s*[\r\n]+)/s; +export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s; type FrontMatterMetadata = { title?: string; diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index 652f35402..22b4c1c8c 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -15,6 +15,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/diagrams/c4/c4Renderer.js b/packages/mermaid/src/diagrams/c4/c4Renderer.js index a9072346a..a51fe0b6a 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; @@ -48,7 +47,7 @@ class Bounds { } updateVal(obj, key, val, fun) { - if (typeof obj[key] === 'undefined') { + if (obj[key] === undefined) { obj[key] = val; } else { obj[key] = fun(val, obj[key]); @@ -177,12 +176,12 @@ function calcC4ShapeTextWH(textType, c4Shape, c4ShapeTextWrap, textConf, textLim let lineHeight = 0; c4Shape[textType].height = 0; c4Shape[textType].width = 0; - for (let i = 0; i < lines.length; i++) { + for (const line of lines) { c4Shape[textType].width = Math.max( - calculateTextWidth(lines[i], textConf), + calculateTextWidth(line, textConf), c4Shape[textType].width ); - lineHeight = calculateTextHeight(lines[i], textConf); + lineHeight = calculateTextHeight(line, textConf); c4Shape[textType].height = c4Shape[textType].height + lineHeight; } // c4Shapes[textType].height = c4Shapes[textType].textLines * textConf.fontSize; @@ -212,9 +211,9 @@ export const drawC4ShapeArray = function (currentBounds, diagram, c4ShapeArray, // Upper Y is relative point let Y = 0; // Draw the c4ShapeArray - for (let i = 0; i < c4ShapeKeys.length; i++) { + for (const c4ShapeKey of c4ShapeKeys) { Y = 0; - const c4Shape = c4ShapeArray[c4ShapeKeys[i]]; + const c4Shape = c4ShapeArray[c4ShapeKey]; // calc c4 shape type width and height @@ -461,8 +460,7 @@ function drawInsideBoundary( // conf.width * conf.c4ShapeInRow + conf.c4ShapeMargin * conf.c4ShapeInRow * 2, // parentBounds.data.widthLimit / Math.min(conf.c4BoundaryInRow, currentBoundaries.length) // ); - for (let i = 0; i < currentBoundaries.length; i++) { - let currentBoundary = currentBoundaries[i]; + for (let [i, currentBoundary] of currentBoundaries.entries()) { let Y = 0; currentBoundary.image = { width: 0, height: 0, Y: 0 }; if (currentBoundary.sprite) { @@ -676,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 437a24bcb..d3d66a80d 100644 --- a/packages/mermaid/src/diagrams/c4/svgDraw.js +++ b/packages/mermaid/src/diagrams/c4/svgDraw.js @@ -35,184 +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 ( - typeof textData.valign !== 'undefined' && - typeof 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 ( - typeof textData.anchor !== 'undefined' && - typeof textData.textMargin !== 'undefined' && - typeof 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 = 0; i < lines.length; i++) { - let line = lines[i]; - if ( - typeof textData.textMargin !== 'undefined' && - textData.textMargin === 0 && - typeof textData.fontSize !== 'undefined' - ) { - dy = i * textData.fontSize; - } - - const textElem = elem.append('text'); - textElem.attr('x', textData.x); - textElem.attr('y', yfunc()); - if (typeof textData.anchor !== 'undefined') { - textElem - .attr('text-anchor', textData.anchor) - .attr('dominant-baseline', textData.dominantBaseline) - .attr('alignment-baseline', textData.alignmentBaseline); - } - if (typeof textData.fontFamily !== 'undefined') { - textElem.style('font-family', textData.fontFamily); - } - if (typeof textData.fontSize !== 'undefined') { - textElem.style('font-size', textData.fontSize); - } - if (typeof textData.fontWeight !== 'undefined') { - textElem.style('font-weight', textData.fontWeight); - } - if (typeof textData.fill !== 'undefined') { - textElem.attr('fill', textData.fill); - } - if (typeof textData.class !== 'undefined') { - textElem.attr('class', textData.class); - } - if (typeof 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 (typeof textData.fill !== 'undefined') { - span.attr('fill', textData.fill); - } - span.text(line); - } else { - textElem.text(line); - } - if ( - typeof textData.valign !== 'undefined' && - typeof 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; @@ -745,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, @@ -896,13 +701,10 @@ const _drawTextCandidateFunc = (function () { export default { drawRect, - drawText, - drawLabel, drawBoundary, drawC4Shape, drawRels, drawImage, - drawEmbeddedImage, insertArrowHead, insertArrowEnd, insertArrowFilledHead, @@ -911,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/classDb.js b/packages/mermaid/src/diagrams/class/classDb.js index 9830c059e..2c6690e39 100644 --- a/packages/mermaid/src/diagrams/class/classDb.js +++ b/packages/mermaid/src/diagrams/class/classDb.js @@ -52,7 +52,7 @@ const splitClassNameAndType = function (id) { export const addClass = function (id) { let classId = splitClassNameAndType(id); // Only add class if not exists - if (typeof classes[classId.className] !== 'undefined') { + if (classes[classId.className] !== undefined) { return; } @@ -77,9 +77,9 @@ export const addClass = function (id) { */ export const lookUpDomId = function (id) { const classKeys = Object.keys(classes); - for (let i = 0; i < classKeys.length; i++) { - if (classes[classKeys[i]].id === id) { - return classes[classKeys[i]].domId; + for (const classKey of classKeys) { + if (classes[classKey].id === id) { + return classes[classKey].domId; } } }; @@ -207,7 +207,7 @@ export const setCssClass = function (ids, className) { if (_id[0].match(/\d/)) { id = MERMAID_DOM_ID_PREFIX + id; } - if (typeof classes[id] !== 'undefined') { + if (classes[id] !== undefined) { classes[id].cssClasses.push(className); } }); @@ -222,7 +222,7 @@ export const setCssClass = function (ids, className) { const setTooltip = function (ids, tooltip) { const config = configApi.getConfig(); ids.split(',').forEach(function (id) { - if (typeof tooltip !== 'undefined') { + if (tooltip !== undefined) { classes[id].tooltip = common.sanitizeText(tooltip, config); } }); @@ -244,7 +244,7 @@ export const setLink = function (ids, linkStr, target) { if (_id[0].match(/\d/)) { id = MERMAID_DOM_ID_PREFIX + id; } - if (typeof classes[id] !== 'undefined') { + if (classes[id] !== undefined) { classes[id].link = utils.formatUrl(linkStr, config); if (config.securityLevel === 'sandbox') { classes[id].linkTarget = '_top'; @@ -281,10 +281,10 @@ const setClickFunc = function (domId, functionName, functionArgs) { if (config.securityLevel !== 'loose') { return; } - if (typeof functionName === 'undefined') { + if (functionName === undefined) { return; } - if (typeof classes[id] !== 'undefined') { + if (classes[id] !== undefined) { let argList = []; if (typeof functionArgs === 'string') { /* Splits functionArgs by ',', ignoring all ',' in double quoted strings */ diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.js b/packages/mermaid/src/diagrams/class/classRenderer-v2.js index 905e6ee2f..3fecfcb9f 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.js +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.js @@ -8,9 +8,6 @@ import { curveLinear } from 'd3'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import common from '../common/common'; -import addSVGAccessibilityFields from '../../accessibility'; - -let idCache = {}; const sanitizeText = (txt) => common.sanitizeText(txt, getConfig()); @@ -275,16 +272,16 @@ export const addRelations = function (relations, g) { let style = ''; let labelStyle = ''; - if (typeof edge.style !== 'undefined') { + if (edge.style !== undefined) { const styles = getStylesFromArray(edge.style); style = styles.style; labelStyle = styles.labelStyle; } else { style = 'fill:none'; - if (typeof defaultStyle !== 'undefined') { + if (defaultStyle !== undefined) { style = defaultStyle; } - if (typeof defaultLabelStyle !== 'undefined') { + if (defaultLabelStyle !== undefined) { labelStyle = defaultLabelStyle; } } @@ -292,17 +289,17 @@ export const addRelations = function (relations, g) { edgeData.style = style; edgeData.labelStyle = labelStyle; - if (typeof edge.interpolate !== 'undefined') { + if (edge.interpolate !== undefined) { edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear); - } else if (typeof relations.defaultInterpolate !== 'undefined') { + } else if (relations.defaultInterpolate !== undefined) { edgeData.curve = interpolateToCurve(relations.defaultInterpolate, curveLinear); } else { edgeData.curve = interpolateToCurve(conf.curve, curveLinear); } edge.text = edge.title; - if (typeof edge.text === 'undefined') { - if (typeof edge.style !== 'undefined') { + if (edge.text === undefined) { + if (edge.style !== undefined) { edgeData.arrowheadStyle = 'fill: #333'; } } else { @@ -316,7 +313,7 @@ export const addRelations = function (relations, g) { edgeData.labelType = 'text'; edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); - if (typeof edge.style === 'undefined') { + if (edge.style === undefined) { edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none'; } @@ -438,9 +435,7 @@ export const draw = function (text, id, _version, diagObj) { if (!conf.htmlLabels) { const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document; const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); - for (let k = 0; k < labels.length; k++) { - const label = labels[k]; - + for (const label of labels) { // Get dimensions of label const dim = label.getBBox(); @@ -455,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 74e2a48c2..80a7f26e4 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; @@ -180,8 +179,8 @@ export const draw = function (text, id, _version, diagObj) { const classes = diagObj.db.getClasses(); const keys = Object.keys(classes); - for (let i = 0; i < keys.length; i++) { - const classDef = classes[keys[i]]; + for (const key of keys) { + const classDef = classes[key]; const node = svgDraw.drawClass(diagram, classDef, conf, diagObj); idCache[node.id] = node; @@ -240,7 +239,7 @@ export const draw = function (text, id, _version, diagObj) { dagreLayout(g); g.nodes().forEach(function (v) { - if (typeof v !== 'undefined' && typeof g.node(v) !== 'undefined') { + if (v !== undefined && g.node(v) !== undefined) { log.debug('Node ' + v + ': ' + JSON.stringify(g.node(v))); root .select('#' + (diagObj.db.lookUpDomId(v) || v)) @@ -256,7 +255,7 @@ export const draw = function (text, id, _version, diagObj) { }); g.edges().forEach(function (e) { - if (typeof e !== 'undefined' && typeof g.edge(e) !== 'undefined') { + if (e !== undefined && g.edge(e) !== undefined) { log.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(g.edge(e))); svgDraw.drawEdge(diagram, g.edge(e), g.edge(e).relation, conf, diagObj); } @@ -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/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index 35f793460..cc6909280 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -102,7 +102,7 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) { p2_card_y = cardinality_2_point.y; } - if (typeof relation.title !== 'undefined') { + if (relation.title !== undefined) { const g = elem.append('g').attr('class', 'classLabel'); const label = g .append('text') @@ -125,7 +125,7 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) { } log.info('Rendering relation ' + JSON.stringify(relation)); - if (typeof relation.relationTitle1 !== 'undefined' && relation.relationTitle1 !== 'none') { + if (relation.relationTitle1 !== undefined && relation.relationTitle1 !== 'none') { const g = elem.append('g').attr('class', 'cardinality'); g.append('text') .attr('class', 'type1') @@ -135,7 +135,7 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) { .attr('font-size', '6') .text(relation.relationTitle1); } - if (typeof relation.relationTitle2 !== 'undefined' && relation.relationTitle2 !== 'none') { + if (relation.relationTitle2 !== undefined && relation.relationTitle2 !== 'none') { const g = elem.append('g').attr('class', 'cardinality'); g.append('text') .attr('class', 'type2') @@ -355,8 +355,8 @@ export const drawNote = function (elem, note, conf, diagObj) { }; export const parseMember = function (text) { - const fieldRegEx = /^(\+|-|~|#)?(\w+)(~\w+~|\[\])?\s+(\w+) *(\*|\$)?$/; - const methodRegEx = /^([+|\-|~|#])?(\w+) *\( *(.*)\) *(\*|\$)? *(\w*[~|[\]]*\s*\w*~?)$/; + const fieldRegEx = /^([#+~-])?(\w+)(~\w+~|\[])?\s+(\w+) *([$*])?$/; + const methodRegEx = /^([#+|~-])?(\w+) *\( *(.*)\) *([$*])? *(\w*[[\]|~]*\s*\w*~?)$/; let fieldMatch = text.match(fieldRegEx); let methodMatch = text.match(methodRegEx); @@ -420,7 +420,6 @@ const buildLegacyDisplay = function (text) { // if for some reason we don't have any match, use old format to parse text let displayText = ''; let cssStyle = ''; - let memberText = ''; let returnType = ''; let methodStart = text.indexOf('('); let methodEnd = text.indexOf(')'); @@ -433,7 +432,7 @@ const buildLegacyDisplay = function (text) { if (firstChar.match(/\w/)) { methodName = text.substring(0, methodStart).trim(); } else { - if (firstChar.match(/\+|-|~|#/)) { + if (firstChar.match(/[#+~-]/)) { visibility = firstChar; } diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 782915cc1..194a9a4c0 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -152,7 +152,7 @@ export const evaluate = (val?: string | boolean): boolean => export const parseGenericTypes = function (text: string): string { let cleanedText = text; - if (text.indexOf('~') !== -1) { + if (text.includes('~')) { cleanedText = cleanedText.replace(/~([^~].*)/, '<$1'); cleanedText = cleanedText.replace(/~([^~]*)$/, '>$1'); diff --git a/packages/mermaid/src/diagrams/er/erDb.js b/packages/mermaid/src/diagrams/er/erDb.js index 96b60836b..026e08420 100644 --- a/packages/mermaid/src/diagrams/er/erDb.js +++ b/packages/mermaid/src/diagrams/er/erDb.js @@ -32,7 +32,7 @@ export const parseDirective = function (statement, context, type) { }; const addEntity = function (name) { - if (typeof entities[name] === 'undefined') { + if (entities[name] === undefined) { entities[name] = { attributes: [] }; log.info('Added new entity :', name); } diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js index 36267f415..ebf338ae1 100644 --- a/packages/mermaid/src/diagrams/er/erRenderer.js +++ b/packages/mermaid/src/diagrams/er/erRenderer.js @@ -6,12 +6,11 @@ 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'; /** Regex used to remove chars from the entity name so the result can be used in an id */ -const BAD_ID_CHARS_REGEXP = /[^A-Za-z0-9]([\W])*/g; +const BAD_ID_CHARS_REGEXP = /[^\dA-Za-z](\W)*/g; // Configuration let conf = {}; @@ -28,8 +27,8 @@ let entityNameIds = new Map(); */ export const setConf = function (cnf) { const keys = Object.keys(cnf); - for (let i = 0; i < keys.length; i++) { - conf[keys[i]] = cnf[keys[i]]; + for (const key of keys) { + conf[key] = cnf[key]; } }; @@ -356,7 +355,7 @@ const drawEntities = function (svgNode, entities, graph) { const adjustEntities = function (svgNode, graph) { graph.nodes().forEach(function (v) { - if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') { + if (v !== undefined && graph.node(v) !== undefined) { svgNode .select('#' + v) .attr( @@ -387,7 +386,7 @@ const getEdgeName = function (rel) { * Add each relationship to the graph * * @param relationships The relationships to be added - * @param {Graph} g The graph + * @param g The graph * @returns {Array} The array of relationships */ const addRelationships = function (relationships, g) { @@ -642,8 +641,6 @@ 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 /** @@ -652,10 +649,8 @@ export const draw = function (text, id, _version, diagObj) { * 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. * - * @param {string} [str?=''] Given string to use as the basis for the id. Default is `''` - * @param {string} [prefix?=''] String to put at the start, followed by '-'. Default is `''` - * @param str - * @param prefix + * @param {string} str Given string to use as the basis for the id. Default is `''` + * @param {string} prefix String to put at the start, followed by '-'. Default is `''` * @returns {string} * @see https://www.w3.org/TR/xml/#NT-Name */ diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js index eb738fe4b..43bc13e6d 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js @@ -323,34 +323,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'); - }); + ); + 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}"`; - - 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/flowDb.js b/packages/mermaid/src/diagrams/flowchart/flowDb.js index 38754c667..9181ab9cc 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.js +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.js @@ -46,9 +46,9 @@ export const parseDirective = function (statement, context, type) { */ export const lookUpDomId = function (id) { const veritceKeys = Object.keys(vertices); - for (let i = 0; i < veritceKeys.length; i++) { - if (vertices[veritceKeys[i]].id === id) { - return vertices[veritceKeys[i]].domId; + for (const veritceKey of veritceKeys) { + if (vertices[veritceKey].id === id) { + return vertices[veritceKey].domId; } } return id; @@ -68,7 +68,7 @@ export const lookUpDomId = function (id) { export const addVertex = function (_id, text, type, style, classes, dir, props = {}) { let txt; let id = _id; - if (typeof id === 'undefined') { + if (id === undefined) { return; } if (id.trim().length === 0) { @@ -77,7 +77,7 @@ export const addVertex = function (_id, text, type, style, classes, dir, props = // if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; - if (typeof vertices[id] === 'undefined') { + if (vertices[id] === undefined) { vertices[id] = { id: id, domId: MERMAID_DOM_ID_PREFIX + id + '-' + vertexCounter, @@ -86,7 +86,7 @@ export const addVertex = function (_id, text, type, style, classes, dir, props = }; } vertexCounter++; - if (typeof text !== 'undefined') { + if (text !== undefined) { config = configApi.getConfig(); txt = sanitizeText(text.trim()); @@ -97,33 +97,29 @@ export const addVertex = function (_id, text, type, style, classes, dir, props = vertices[id].text = txt; } else { - if (typeof vertices[id].text === 'undefined') { + if (vertices[id].text === undefined) { vertices[id].text = _id; } } - if (typeof type !== 'undefined') { + if (type !== undefined) { vertices[id].type = type; } - if (typeof style !== 'undefined') { - if (style !== null) { - style.forEach(function (s) { - vertices[id].styles.push(s); - }); - } + if (style !== undefined && style !== null) { + style.forEach(function (s) { + vertices[id].styles.push(s); + }); } - if (typeof classes !== 'undefined') { - if (classes !== null) { - classes.forEach(function (s) { - vertices[id].classes.push(s); - }); - } + if (classes !== undefined && classes !== null) { + classes.forEach(function (s) { + vertices[id].classes.push(s); + }); } - if (typeof dir !== 'undefined') { + if (dir !== undefined) { vertices[id].dir = dir; } - if (typeof vertices[id].props === 'undefined') { + if (vertices[id].props === undefined) { vertices[id].props = props; - } else if (typeof props !== 'undefined') { + } else if (props !== undefined) { Object.assign(vertices[id].props, props); } }; @@ -134,9 +130,9 @@ export const addVertex = function (_id, text, type, style, classes, dir, props = * @param _start * @param _end * @param type - * @param linktext + * @param linkText */ -export const addSingleLink = function (_start, _end, type, linktext) { +export const addSingleLink = function (_start, _end, type, linkText) { let start = _start; let end = _end; // if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start; @@ -144,18 +140,18 @@ export const addSingleLink = function (_start, _end, type, linktext) { // log.info('Got edge...', start, end); const edge = { start: start, end: end, type: undefined, text: '' }; - linktext = type.text; + linkText = type.text; - if (typeof linktext !== 'undefined') { - edge.text = sanitizeText(linktext.trim()); + if (linkText !== undefined) { + edge.text = sanitizeText(linkText.trim()); - // strip quotes if string starts and exnds with a quote + // strip quotes if string starts and ends with a quote if (edge.text[0] === '"' && edge.text[edge.text.length - 1] === '"') { edge.text = edge.text.substring(1, edge.text.length - 1); } } - if (typeof type !== 'undefined') { + if (type !== undefined) { edge.type = type.type; edge.stroke = type.stroke; edge.length = type.length; @@ -207,21 +203,19 @@ export const updateLink = function (positions, style) { }; export const addClass = function (id, style) { - if (typeof classes[id] === 'undefined') { + if (classes[id] === undefined) { classes[id] = { id: id, styles: [], textStyles: [] }; } - if (typeof style !== 'undefined') { - if (style !== null) { - style.forEach(function (s) { - if (s.match('color')) { - const newStyle1 = s.replace('fill', 'bgFill'); - const newStyle2 = newStyle1.replace('color', 'fill'); - classes[id].textStyles.push(newStyle2); - } - classes[id].styles.push(s); - }); - } + if (style !== undefined && style !== null) { + style.forEach(function (s) { + if (s.match('color')) { + const newStyle1 = s.replace('fill', 'bgFill'); + const newStyle2 = newStyle1.replace('color', 'fill'); + classes[id].textStyles.push(newStyle2); + } + classes[id].styles.push(s); + }); } }; @@ -257,11 +251,11 @@ export const setClass = function (ids, className) { // let id = version === 'gen-2' ? lookUpDomId(_id) : _id; let id = _id; // if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; - if (typeof vertices[id] !== 'undefined') { + if (vertices[id] !== undefined) { vertices[id].classes.push(className); } - if (typeof subGraphLookup[id] !== 'undefined') { + if (subGraphLookup[id] !== undefined) { subGraphLookup[id].classes.push(className); } }); @@ -269,7 +263,7 @@ export const setClass = function (ids, className) { const setTooltip = function (ids, tooltip) { ids.split(',').forEach(function (id) { - if (typeof tooltip !== 'undefined') { + if (tooltip !== undefined) { tooltips[version === 'gen-1' ? lookUpDomId(id) : id] = sanitizeText(tooltip); } }); @@ -281,7 +275,7 @@ const setClickFun = function (id, functionName, functionArgs) { if (configApi.getConfig().securityLevel !== 'loose') { return; } - if (typeof functionName === 'undefined') { + if (functionName === undefined) { return; } let argList = []; @@ -304,7 +298,7 @@ const setClickFun = function (id, functionName, functionArgs) { argList.push(id); } - if (typeof vertices[id] !== 'undefined') { + if (vertices[id] !== undefined) { vertices[id].haveCallback = true; funs.push(function () { const elem = document.querySelector(`[id="${domId}"]`); @@ -330,7 +324,7 @@ const setClickFun = function (id, functionName, functionArgs) { */ export const setLink = function (ids, linkStr, target) { ids.split(',').forEach(function (id) { - if (typeof vertices[id] !== 'undefined') { + if (vertices[id] !== undefined) { vertices[id].link = utils.formatUrl(linkStr, config); vertices[id].linkTarget = target; } @@ -484,7 +478,7 @@ export const addSubGraph = function (_id, list, _title) { if (type in prims) { return prims[type].hasOwnProperty(item) ? false : (prims[type][item] = true); } else { - return objs.indexOf(item) >= 0 ? false : objs.push(item); + return objs.includes(item) ? false : objs.push(item); } }); return { nodeList, dir }; @@ -530,8 +524,8 @@ export const addSubGraph = function (_id, list, _title) { }; const getPosForId = function (id) { - for (let i = 0; i < subGraphs.length; i++) { - if (subGraphs[i].id === id) { + for (const [i, subGraph] of subGraphs.entries()) { + if (subGraph.id === id) { return i; } } @@ -622,11 +616,11 @@ const destructStartLink = (_str) => { let stroke = 'normal'; - if (str.indexOf('=') !== -1) { + if (str.includes('=')) { stroke = 'thick'; } - if (str.indexOf('.') !== -1) { + if (str.includes('.')) { stroke = 'dotted'; } diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js index fa1b692cf..3e46aaaa5 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -11,13 +11,12 @@ 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) { const keys = Object.keys(cnf); - for (let i = 0; i < keys.length; i++) { - conf[keys[i]] = cnf[keys[i]]; + for (const key of keys) { + conf[key] = cnf[key]; } }; @@ -60,7 +59,7 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) { // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? const node = { label: vertexText.replace( - /fa[lrsb]?:fa-[\w-]+/g, + /fa[blrs]?:fa-[\w-]+/g, (s) => `` ), }; @@ -72,12 +71,12 @@ export const addVertices = function (vert, g, svgId, root, doc, diagObj) { const rows = vertexText.split(common.lineBreakRegex); - for (let j = 0; j < rows.length; j++) { + for (const row of rows) { const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); tspan.setAttribute('dy', '1em'); tspan.setAttribute('x', '1'); - tspan.textContent = rows[j]; + tspan.textContent = row; svgLabel.appendChild(tspan); } vertexNode = svgLabel; @@ -198,7 +197,7 @@ export const addEdges = function (edges, g, diagObj) { let defaultStyle; let defaultLabelStyle; - if (typeof edges.defaultStyle !== 'undefined') { + if (edges.defaultStyle !== undefined) { const defaultStyles = getStylesFromArray(edges.defaultStyle); defaultStyle = defaultStyles.style; defaultLabelStyle = defaultStyles.labelStyle; @@ -210,7 +209,7 @@ export const addEdges = function (edges, g, diagObj) { // Identify Link var linkIdBase = 'L-' + edge.start + '-' + edge.end; // count the links from+to the same node to give unique id - if (typeof linkIdCnt[linkIdBase] === 'undefined') { + if (linkIdCnt[linkIdBase] === undefined) { linkIdCnt[linkIdBase] = 0; log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]); } else { @@ -262,10 +261,10 @@ export const addEdges = function (edges, g, diagObj) { switch (edge.stroke) { case 'normal': style = 'fill:none;'; - if (typeof defaultStyle !== 'undefined') { + if (defaultStyle !== undefined) { style = defaultStyle; } - if (typeof defaultLabelStyle !== 'undefined') { + if (defaultLabelStyle !== undefined) { labelStyle = defaultLabelStyle; } edgeData.thickness = 'normal'; @@ -282,7 +281,7 @@ export const addEdges = function (edges, g, diagObj) { edgeData.style = 'stroke-width: 3.5px;fill:none;'; break; } - if (typeof edge.style !== 'undefined') { + if (edge.style !== undefined) { const styles = getStylesFromArray(edge.style); style = styles.style; labelStyle = styles.labelStyle; @@ -291,16 +290,16 @@ export const addEdges = function (edges, g, diagObj) { edgeData.style = edgeData.style += style; edgeData.labelStyle = edgeData.labelStyle += labelStyle; - if (typeof edge.interpolate !== 'undefined') { + if (edge.interpolate !== undefined) { edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear); - } else if (typeof edges.defaultInterpolate !== 'undefined') { + } else if (edges.defaultInterpolate !== undefined) { edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear); } else { edgeData.curve = interpolateToCurve(conf.curve, curveLinear); } - if (typeof edge.text === 'undefined') { - if (typeof edge.style !== 'undefined') { + if (edge.text === undefined) { + if (edge.style !== undefined) { edgeData.arrowheadStyle = 'fill: #333'; } } else { @@ -311,7 +310,7 @@ export const addEdges = function (edges, g, diagObj) { edgeData.labelType = 'text'; edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); - if (typeof edge.style === 'undefined') { + if (edge.style === undefined) { edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;'; } @@ -360,7 +359,7 @@ export const draw = function (text, id, _version, diagObj) { // Fetch the default direction, use TD if none was found let dir = diagObj.db.getDirection(); - if (typeof dir === 'undefined') { + if (dir === undefined) { dir = 'TD'; } @@ -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); @@ -448,9 +444,7 @@ export const draw = function (text, id, _version, diagObj) { // Add label rects for non html labels if (!conf.htmlLabels) { const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); - for (let k = 0; k < labels.length; k++) { - const label = labels[k]; - + for (const label of labels) { // Get dimensions of label const dim = label.getBBox(); diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index 7febc4034..6cbc65532 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -9,13 +9,12 @@ 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) { const keys = Object.keys(cnf); - for (let i = 0; i < keys.length; i++) { - conf[keys[i]] = cnf[keys[i]]; + for (const key of keys) { + conf[key] = cnf[key]; } }; @@ -59,7 +58,7 @@ export const addVertices = function (vert, g, svgId, root, _doc, diagObj) { // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? const node = { label: vertexText.replace( - /fa[lrsb]?:fa-[\w-]+/g, + /fa[blrs]?:fa-[\w-]+/g, (s) => `` ), }; @@ -71,12 +70,12 @@ export const addVertices = function (vert, g, svgId, root, _doc, diagObj) { const rows = vertexText.split(common.lineBreakRegex); - for (let j = 0; j < rows.length; j++) { + for (const row of rows) { const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); tspan.setAttribute('dy', '1em'); tspan.setAttribute('x', '1'); - tspan.textContent = rows[j]; + tspan.textContent = row; svgLabel.appendChild(tspan); } vertexNode = svgLabel; @@ -167,7 +166,7 @@ export const addEdges = function (edges, g, diagObj) { let defaultStyle; let defaultLabelStyle; - if (typeof edges.defaultStyle !== 'undefined') { + if (edges.defaultStyle !== undefined) { const defaultStyles = getStylesFromArray(edges.defaultStyle); defaultStyle = defaultStyles.style; defaultLabelStyle = defaultStyles.labelStyle; @@ -193,7 +192,7 @@ export const addEdges = function (edges, g, diagObj) { let style = ''; let labelStyle = ''; - if (typeof edge.style !== 'undefined') { + if (edge.style !== undefined) { const styles = getStylesFromArray(edge.style); style = styles.style; labelStyle = styles.labelStyle; @@ -201,10 +200,10 @@ export const addEdges = function (edges, g, diagObj) { switch (edge.stroke) { case 'normal': style = 'fill:none'; - if (typeof defaultStyle !== 'undefined') { + if (defaultStyle !== undefined) { style = defaultStyle; } - if (typeof defaultLabelStyle !== 'undefined') { + if (defaultLabelStyle !== undefined) { labelStyle = defaultLabelStyle; } break; @@ -220,16 +219,16 @@ export const addEdges = function (edges, g, diagObj) { edgeData.style = style; edgeData.labelStyle = labelStyle; - if (typeof edge.interpolate !== 'undefined') { + if (edge.interpolate !== undefined) { edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear); - } else if (typeof edges.defaultInterpolate !== 'undefined') { + } else if (edges.defaultInterpolate !== undefined) { edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear); } else { edgeData.curve = interpolateToCurve(conf.curve, curveLinear); } - if (typeof edge.text === 'undefined') { - if (typeof edge.style !== 'undefined') { + if (edge.text === undefined) { + if (edge.style !== undefined) { edgeData.arrowheadStyle = 'fill: #333'; } } else { @@ -241,14 +240,14 @@ export const addEdges = function (edges, g, diagObj) { edgeData.label = `${edge.text.replace( - /fa[lrsb]?:fa-[\w-]+/g, + /fa[blrs]?:fa-[\w-]+/g, (s) => `` )}`; } else { edgeData.labelType = 'text'; edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); - if (typeof edge.style === 'undefined') { + if (edge.style === undefined) { edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none'; } @@ -316,7 +315,7 @@ export const draw = function (text, id, _version, diagObj) { // Fetch the default direction, use TD if none was found let dir = diagObj.db.getDirection(); - if (typeof dir === 'undefined') { + if (dir === undefined) { dir = 'TD'; } const nodeSpacing = conf.nodeSpacing || 50; @@ -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); @@ -459,9 +455,7 @@ export const draw = function (text, id, _version, diagObj) { // Add label rects for non html labels if (!conf.htmlLabels) { const labels = doc.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); - for (let k = 0; k < labels.length; k++) { - const label = labels[k]; - + for (const label of labels) { // Get dimensions of label const dim = label.getBBox(); 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 6cba33f88..5c2094737 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js @@ -79,8 +79,8 @@ describe('when parsing directions', function () { const subgraphs = flow.parser.yy.getSubGraphs(); expect(subgraphs.length).toBe(2); - const subgraphA = filter(subgraphs, (o) => o.id === 'A')[0]; - const subgraphB = filter(subgraphs, (o) => o.id === 'B')[0]; + const subgraphA = subgraphs.find((o) => o.id === 'A'); + const subgraphB = subgraphs.find((o) => o.id === 'B'); expect(subgraphB.nodes[0]).toBe('c'); expect(subgraphB.dir).toBe('LR'); diff --git a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js index 6fec233e7..5ba6a5361 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js @@ -254,8 +254,8 @@ describe('when parsing subgraphs', function () { const subgraphs = flow.parser.yy.getSubGraphs(); expect(subgraphs.length).toBe(2); - const subgraphA = filter(subgraphs, (o) => o.id === 'A')[0]; - const subgraphB = filter(subgraphs, (o) => o.id === 'B')[0]; + const subgraphA = subgraphs.find((o) => o.id === 'A'); + const subgraphB = subgraphs.find((o) => o.id === 'B'); expect(subgraphB.nodes[0]).toBe('c'); expect(subgraphA.nodes).toContain('B'); @@ -279,8 +279,8 @@ describe('when parsing subgraphs', function () { const subgraphs = flow.parser.yy.getSubGraphs(); expect(subgraphs.length).toBe(2); - const subgraphA = filter(subgraphs, (o) => o.id === 'A')[0]; - const subgraphB = filter(subgraphs, (o) => o.id === 'B')[0]; + const subgraphA = subgraphs.find((o) => o.id === 'A'); + const subgraphB = subgraphs.find((o) => o.id === 'B'); expect(subgraphB.nodes[0]).toBe('c'); expect(subgraphA.nodes).toContain('B'); @@ -302,8 +302,8 @@ describe('when parsing subgraphs', function () { const subgraphs = flow.parser.yy.getSubGraphs(); expect(subgraphs.length).toBe(2); - const subgraphA = filter(subgraphs, (o) => o.id === 'A')[0]; - const subgraphB = filter(subgraphs, (o) => o.id === 'B')[0]; + const subgraphA = subgraphs.find((o) => o.id === 'A'); + const subgraphB = subgraphs.find((o) => o.id === 'B'); expect(subgraphB.nodes[0]).toBe('c'); expect(subgraphA.nodes).toContain('B'); expect(subgraphA.nodes).toContain('b'); diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.js b/packages/mermaid/src/diagrams/gantt/ganttDb.js index a0f18c3b8..a1c74dd62 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttDb.js +++ b/packages/mermaid/src/diagrams/gantt/ganttDb.js @@ -150,16 +150,16 @@ export const getTasks = function () { }; export const isInvalidDate = function (date, dateFormat, excludes, includes) { - if (includes.indexOf(date.format(dateFormat.trim())) >= 0) { + if (includes.includes(date.format(dateFormat.trim()))) { return false; } - if (date.isoWeekday() >= 6 && excludes.indexOf('weekends') >= 0) { + if (date.isoWeekday() >= 6 && excludes.includes('weekends')) { return true; } - if (excludes.indexOf(date.format('dddd').toLowerCase()) >= 0) { + if (excludes.includes(date.format('dddd').toLowerCase())) { return true; } - return excludes.indexOf(date.format(dateFormat.trim())) >= 0; + return excludes.includes(date.format(dateFormat.trim())); }; const checkTaskDates = function (task, dateFormat, excludes, includes) { @@ -202,7 +202,7 @@ const getStartDate = function (prevTime, dateFormat, str) { let latestEndingTask = null; afterStatement[1].split(' ').forEach(function (id) { let task = findTaskById(id); - if (typeof task !== 'undefined') { + if (task !== undefined) { if (!latestEndingTask) { latestEndingTask = task; } else { @@ -230,7 +230,7 @@ const getStartDate = function (prevTime, dateFormat, str) { log.debug('Invalid date:' + str); log.debug('With date format:' + dateFormat.trim()); const d = new Date(str); - if (typeof d === 'undefined' || isNaN(d.getTime())) { + if (d === undefined || isNaN(d.getTime())) { throw new Error('Invalid date:' + str); } return d; @@ -258,15 +258,14 @@ const getStartDate = function (prevTime, dateFormat, str) { * string. */ const parseDuration = function (str) { - const statement = /^(\d+(?:\.\d+)?)([yMwdhms]|ms)$/.exec(str.trim()); + const statement = /^(\d+(?:\.\d+)?)([Mdhmswy]|ms)$/.exec(str.trim()); if (statement !== null) { return moment.duration(Number.parseFloat(statement[1]), statement[2]); } return moment.duration.invalid(); }; -const getEndDate = function (prevTime, dateFormat, str, inclusive) { - inclusive = inclusive || false; +const getEndDate = function (prevTime, dateFormat, str, inclusive = false) { str = str.trim(); // Check for actual date @@ -288,7 +287,7 @@ const getEndDate = function (prevTime, dateFormat, str, inclusive) { let taskCnt = 0; const parseId = function (idStr) { - if (typeof idStr === 'undefined') { + if (idStr === undefined) { taskCnt = taskCnt + 1; return 'task' + taskCnt; } @@ -510,10 +509,10 @@ const compileTasks = function () { }; let allProcessed = true; - for (let i = 0; i < rawTasks.length; i++) { + for (const [i, rawTask] of rawTasks.entries()) { compileTask(i); - allProcessed = allProcessed && rawTasks[i].processed; + allProcessed = allProcessed && rawTask.processed; } return allProcessed; }; @@ -531,7 +530,7 @@ export const setLink = function (ids, _linkStr) { } ids.split(',').forEach(function (id) { let rawTask = findTaskById(id); - if (typeof rawTask !== 'undefined') { + if (rawTask !== undefined) { pushFun(id, () => { window.open(linkStr, '_self'); }); @@ -550,7 +549,7 @@ export const setLink = function (ids, _linkStr) { export const setClass = function (ids, className) { ids.split(',').forEach(function (id) { let rawTask = findTaskById(id); - if (typeof rawTask !== 'undefined') { + if (rawTask !== undefined) { rawTask.classes.push(className); } }); @@ -560,7 +559,7 @@ const setClickFun = function (id, functionName, functionArgs) { if (configApi.getConfig().securityLevel !== 'loose') { return; } - if (typeof functionName === 'undefined') { + if (functionName === undefined) { return; } @@ -585,7 +584,7 @@ const setClickFun = function (id, functionName, functionArgs) { } let rawTask = findTaskById(id); - if (typeof rawTask !== 'undefined') { + if (rawTask !== undefined) { pushFun(id, () => { utils.runFunc(functionName, ...argList); }); @@ -600,24 +599,26 @@ const setClickFun = function (id, functionName, functionArgs) { * @param callbackFunction A function to be executed when clicked on the task or the task's text */ const pushFun = function (id, callbackFunction) { - funs.push(function () { - // const elem = d3.select(element).select(`[id="${id}"]`) - const elem = document.querySelector(`[id="${id}"]`); - if (elem !== null) { - elem.addEventListener('click', function () { - callbackFunction(); - }); + funs.push( + function () { + // const elem = d3.select(element).select(`[id="${id}"]`) + const elem = document.querySelector(`[id="${id}"]`); + if (elem !== null) { + elem.addEventListener('click', function () { + callbackFunction(); + }); + } + }, + function () { + // const elem = d3.select(element).select(`[id="${id}-text"]`) + const elem = document.querySelector(`[id="${id}-text"]`); + if (elem !== null) { + elem.addEventListener('click', function () { + callbackFunction(); + }); + } } - }); - funs.push(function () { - // const elem = d3.select(element).select(`[id="${id}-text"]`) - const elem = document.querySelector(`[id="${id}-text"]`); - if (elem !== null) { - elem.addEventListener('click', function () { - callbackFunction(); - }); - } - }); + ); }; /** diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js index 9501dd024..faec35a86 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'); @@ -46,11 +45,11 @@ export const draw = function (text, id, version, diagObj) { const elem = doc.getElementById(id); w = elem.parentElement.offsetWidth; - if (typeof w === 'undefined') { + if (w === undefined) { w = 1200; } - if (typeof conf.useWidth !== 'undefined') { + if (conf.useWidth !== undefined) { w = conf.useWidth; } @@ -77,8 +76,8 @@ export const draw = function (text, id, version, diagObj) { let categories = []; - for (let i = 0; i < taskArray.length; i++) { - categories.push(taskArray[i].type); + for (const element of taskArray) { + categories.push(element.type); } const catsUnfiltered = categories; // for vert labels @@ -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 @@ -178,8 +175,8 @@ export const draw = function (text, id, version, diagObj) { }) .attr('height', theGap) .attr('class', function (d) { - for (let i = 0; i < categories.length; i++) { - if (d.type === categories[i]) { + for (const [i, category] of categories.entries()) { + if (d.type === category) { return 'section section' + (i % conf.numberSectionStyles); } } @@ -247,8 +244,8 @@ export const draw = function (text, id, version, diagObj) { } let secNum = 0; - for (let i = 0; i < categories.length; i++) { - if (d.type === categories[i]) { + for (const [i, category] of categories.entries()) { + if (d.type === category) { secNum = i % conf.numberSectionStyles; } } @@ -339,8 +336,8 @@ export const draw = function (text, id, version, diagObj) { } let secNum = 0; - for (let i = 0; i < categories.length; i++) { - if (d.type === categories[i]) { + for (const [i, category] of categories.entries()) { + if (d.type === category) { secNum = i % conf.numberSectionStyles; } } @@ -400,7 +397,7 @@ export const draw = function (text, id, version, diagObj) { rectangles .filter(function (d) { - return typeof links[d.id] !== 'undefined'; + return links[d.id] !== undefined; }) .each(function (o) { var taskRect = doc.querySelector('#' + o.id); @@ -500,7 +497,7 @@ export const draw = function (text, id, version, diagObj) { .tickSize(-h + theTopPad + conf.gridLineStartPadding) .tickFormat(timeFormat(diagObj.db.getAxisFormat() || conf.axisFormat || '%Y-%m-%d')); - const reTickInterval = /^([1-9][0-9]*)(minute|hour|day|week|month)$/; + const reTickInterval = /^([1-9]\d*)(minute|hour|day|week|month)$/; const resultTickInterval = reTickInterval.exec( diagObj.db.getTickInterval() || conf.tickInterval ); @@ -588,8 +585,8 @@ export const draw = function (text, id, version, diagObj) { const numOccurances = []; let prevGap = 0; - for (let i = 0; i < categories.length; i++) { - numOccurances[i] = [categories[i], getCount(categories[i], catsUnfiltered)]; + for (const [i, category] of categories.entries()) { + numOccurances[i] = [category, getCount(category, catsUnfiltered)]; } svg @@ -604,14 +601,14 @@ export const draw = function (text, id, version, diagObj) { const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text'); svgLabel.setAttribute('dy', dy + 'em'); - for (let j = 0; j < rows.length; j++) { + for (const [j, row] of rows.entries()) { const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); tspan.setAttribute('alignment-baseline', 'central'); tspan.setAttribute('x', '10'); if (j > 0) { tspan.setAttribute('dy', '1em'); } - tspan.textContent = rows[j]; + tspan.textContent = row; svgLabel.appendChild(tspan); } return svgLabel; @@ -630,8 +627,8 @@ export const draw = function (text, id, version, diagObj) { .attr('font-size', conf.sectionFontSize) .attr('font-size', conf.sectionFontSize) .attr('class', function (d) { - for (let i = 0; i < categories.length; i++) { - if (d[0] === categories[i]) { + for (const [i, category] of categories.entries()) { + if (d[0] === category) { return 'sectionTitle sectionTitle' + (i % conf.numberSectionStyles); } } diff --git a/packages/mermaid/src/diagrams/git/gitGraphAst.js b/packages/mermaid/src/diagrams/git/gitGraphAst.js index 65980933d..dded48efa 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphAst.js +++ b/packages/mermaid/src/diagrams/git/gitGraphAst.js @@ -131,7 +131,7 @@ export const commit = function (msg, id, type, tag) { export const branch = function (name, order) { name = common.sanitizeText(name, configApi.getConfig()); - if (typeof branches[name] === 'undefined') { + if (branches[name] === undefined) { branches[name] = head != null ? head.id : null; branchesConfig[name] = { name, order: order ? parseInt(order, 10) : null }; checkout(name); @@ -169,7 +169,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag expected: ['branch abc'], }; throw error; - } else if (typeof currentCommit === 'undefined' || !currentCommit) { + } else if (currentCommit === undefined || !currentCommit) { let error = new Error( 'Incorrect usage of "merge". Current branch (' + curBranch + ')has no commits' ); @@ -181,7 +181,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag expected: ['commit'], }; throw error; - } else if (typeof branches[otherBranch] === 'undefined') { + } else if (branches[otherBranch] === undefined) { let error = new Error( 'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') does not exist' ); @@ -193,7 +193,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag expected: ['branch ' + otherBranch], }; throw error; - } else if (typeof otherCommit === 'undefined' || !otherCommit) { + } else if (otherCommit === undefined || !otherCommit) { let error = new Error( 'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') has no commits' ); @@ -215,7 +215,7 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag expected: ['branch abc'], }; throw error; - } else if (custom_id && typeof commits[custom_id] !== 'undefined') { + } else if (custom_id && commits[custom_id] !== undefined) { let error = new Error( 'Incorrect usage of "merge". Commit with id:' + custom_id + @@ -267,7 +267,7 @@ export const cherryPick = function (sourceId, targetId, tag) { targetId = common.sanitizeText(targetId, configApi.getConfig()); tag = common.sanitizeText(tag, configApi.getConfig()); - if (!sourceId || typeof commits[sourceId] === 'undefined') { + if (!sourceId || commits[sourceId] === undefined) { let error = new Error( 'Incorrect usage of "cherryPick". Source commit id should exist and provided' ); @@ -296,7 +296,7 @@ export const cherryPick = function (sourceId, targetId, tag) { }; throw error; } - if (!targetId || typeof commits[targetId] === 'undefined') { + if (!targetId || commits[targetId] === undefined) { // cherry-pick source commit to current branch if (sourceCommitBranch === curBranch) { @@ -313,7 +313,7 @@ export const cherryPick = function (sourceId, targetId, tag) { throw error; } const currentCommit = commits[branches[curBranch]]; - if (typeof currentCommit === 'undefined' || !currentCommit) { + if (currentCommit === undefined || !currentCommit) { let error = new Error( 'Incorrect usage of "cherry-pick". Current branch (' + curBranch + ')has no commits' ); @@ -344,7 +344,7 @@ export const cherryPick = function (sourceId, targetId, tag) { }; export const checkout = function (branch) { branch = common.sanitizeText(branch, configApi.getConfig()); - if (typeof branches[branch] === 'undefined') { + if (branches[branch] === undefined) { let error = new Error( 'Trying to checkout branch which is not yet created. (Help try using "branch ' + branch + '")' ); diff --git a/packages/mermaid/src/diagrams/git/gitGraphRenderer-old.js b/packages/mermaid/src/diagrams/git/gitGraphRenderer-old.js index bfb0ea71c..ca288bfae 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphRenderer-old.js +++ b/packages/mermaid/src/diagrams/git/gitGraphRenderer-old.js @@ -310,8 +310,7 @@ function renderCommitHistory(svg, commitId, branches, direction) { * @param direction * @param branchColor */ -function renderLines(svg, commit, direction, branchColor) { - branchColor = branchColor || 0; +function renderLines(svg, commit, direction, branchColor = 0) { while (commit.seq > 0 && !commit.lineDrawn) { if (typeof commit.parent === 'string') { svgDrawLineForCommits(svg, commit.id, commit.parent, direction, branchColor); diff --git a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js index 75e8d445d..787eb2490 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 = {}; @@ -47,13 +46,13 @@ const drawText = (txt) => { rows = []; } - for (let j = 0; j < rows.length; j++) { + for (const row of rows) { const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan'); tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); tspan.setAttribute('dy', '1em'); tspan.setAttribute('x', '0'); tspan.setAttribute('class', 'row'); - tspan.textContent = rows[j].trim(); + tspan.textContent = row.trim(); svgLabel.appendChild(tspan); } /** @@ -91,7 +90,7 @@ const drawCommits = (svg, commits, modifyGraph) => { if (modifyGraph) { let typeClass; let commitSymbolType = - typeof commit.customType !== 'undefined' && commit.customType !== '' + commit.customType !== undefined && commit.customType !== '' ? commit.customType : commit.type; switch (commitSymbolType) { @@ -319,23 +318,16 @@ const hasOverlappingCommits = (commit1, commit2, allCommits) => { * * @param {any} y1 * @param {any} y2 - * @param {any} _depth + * @param {any} depth * @returns {number} Y value between y1 and y2 */ -const findLane = (y1, y2, _depth) => { - const depth = _depth || 0; - +const findLane = (y1, y2, depth = 0) => { const candidate = y1 + Math.abs(y1 - y2) / 2; if (depth > 5) { return candidate; } - let ok = true; - for (let i = 0; i < lanes.length; i++) { - if (Math.abs(lanes[i] - candidate) < 10) { - ok = false; - } - } + let ok = lanes.every((lane) => Math.abs(lane - candidate) >= 10); if (ok) { lanes.push(candidate); return candidate; @@ -513,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/git/layout.js b/packages/mermaid/src/diagrams/git/layout.js index dd8f23843..de866a72b 100644 --- a/packages/mermaid/src/diagrams/git/layout.js +++ b/packages/mermaid/src/diagrams/git/layout.js @@ -5,8 +5,8 @@ export default (dir, _branches) => { const branches = []; const commits = []; - for (let i = 0; i < _branches.length; i++) { - const branch = Object.assign({}, _branches[i]); + for (const [i, _branch] of _branches.entries()) { + const branch = Object.assign({}, _branch); if (dir === 'TB' || dir === 'BT') { branch.x = config.branchOffset * i; branch.y = -1; diff --git a/packages/mermaid/src/diagrams/pie/pieDb.js b/packages/mermaid/src/diagrams/pie/pieDb.js index 8ef4d9efc..5ccf6d29e 100644 --- a/packages/mermaid/src/diagrams/pie/pieDb.js +++ b/packages/mermaid/src/diagrams/pie/pieDb.js @@ -21,7 +21,7 @@ export const parseDirective = function (statement, context, type) { const addSection = function (id, value) { id = common.sanitizeText(id, configApi.getConfig()); - if (typeof sections[id] === 'undefined') { + if (sections[id] === undefined) { sections[id] = value; log.debug('Added new section :', id); } diff --git a/packages/mermaid/src/diagrams/pie/pieRenderer.js b/packages/mermaid/src/diagrams/pie/pieRenderer.js index 60e9a59c9..83f301207 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(); @@ -39,21 +38,20 @@ export const draw = (txt, id, _version, diagObj) => { const elem = doc.getElementById(id); width = elem.parentElement.offsetWidth; - if (typeof width === 'undefined') { + if (width === undefined) { width = 1200; } - if (typeof conf.useWidth !== 'undefined') { + if (conf.useWidth !== undefined) { width = conf.useWidth; } - if (typeof conf.pie.useWidth !== 'undefined') { + if (conf.pie.useWidth !== undefined) { width = conf.pie.useWidth; } 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/requirementDb.js b/packages/mermaid/src/diagrams/requirement/requirementDb.js index 9d48f0b2d..df5eb0ab9 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementDb.js +++ b/packages/mermaid/src/diagrams/requirement/requirementDb.js @@ -53,7 +53,7 @@ export const parseDirective = function (statement, context, type) { }; const addRequirement = (name, type) => { - if (typeof requirements[name] === 'undefined') { + if (requirements[name] === undefined) { requirements[name] = { name, type, @@ -72,31 +72,31 @@ const addRequirement = (name, type) => { const getRequirements = () => requirements; const setNewReqId = (id) => { - if (typeof latestRequirement != 'undefined') { + if (latestRequirement !== undefined) { latestRequirement.id = id; } }; const setNewReqText = (text) => { - if (typeof latestRequirement != 'undefined') { + if (latestRequirement !== undefined) { latestRequirement.text = text; } }; const setNewReqRisk = (risk) => { - if (typeof latestRequirement != 'undefined') { + if (latestRequirement !== undefined) { latestRequirement.risk = risk; } }; const setNewReqVerifyMethod = (verifyMethod) => { - if (typeof latestRequirement != 'undefined') { + if (latestRequirement !== undefined) { latestRequirement.verifyMethod = verifyMethod; } }; const addElement = (name) => { - if (typeof elements[name] === 'undefined') { + if (elements[name] === undefined) { elements[name] = { name, @@ -113,13 +113,13 @@ const addElement = (name) => { const getElements = () => elements; const setNewElementType = (type) => { - if (typeof latestElement != 'undefined') { + if (latestElement !== undefined) { latestElement.type = type; } }; const setNewElementDocRef = (docRef) => { - if (typeof latestElement != 'undefined') { + if (latestElement !== undefined) { latestElement.docRef = docRef; } }; diff --git a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js index 10469d50d..9fd746bd1 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; @@ -284,7 +283,7 @@ const addRelationships = (relationships, g) => { const adjustEntities = function (svgNode, graph) { graph.nodes().forEach(function (v) { - if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') { + if (v !== undefined && graph.node(v) !== undefined) { svgNode.select('#' + v); svgNode .select('#' + v) @@ -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/sequenceDb.js b/packages/mermaid/src/diagrams/sequence/sequenceDb.js index ba9d0549b..fadd9f391 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.js @@ -60,15 +60,11 @@ const activationCount = (part) => { let i; let count = 0; for (i = 0; i < messages.length; i++) { - if (messages[i].type === LINETYPE.ACTIVE_START) { - if (messages[i].from.actor === part) { - count++; - } + if (messages[i].type === LINETYPE.ACTIVE_START && messages[i].from.actor === part) { + count++; } - if (messages[i].type === LINETYPE.ACTIVE_END) { - if (messages[i].from.actor === part) { - count--; - } + if (messages[i].type === LINETYPE.ACTIVE_END && messages[i].from.actor === part) { + count--; } } return count; @@ -143,7 +139,7 @@ export const setWrap = function (wrapSetting) { export const autoWrap = () => { // if setWrap has been called, use that value, otherwise use the value from the config // TODO: refactor, always use the config value let setWrap update the config value - if (typeof wrapEnabled !== 'undefined') { + if (wrapEnabled !== undefined) { return wrapEnabled; } return configApi.getConfig().sequence.wrap; @@ -159,11 +155,11 @@ export const clear = function () { export const parseMessage = function (str) { const _str = str.trim(); const message = { - text: _str.replace(/^[:]?(?:no)?wrap:/, '').trim(), + text: _str.replace(/^:?(?:no)?wrap:/, '').trim(), wrap: - _str.match(/^[:]?wrap:/) !== null + _str.match(/^:?wrap:/) !== null ? true - : _str.match(/^[:]?nowrap:/) !== null + : _str.match(/^:?nowrap:/) !== null ? false : undefined, }; @@ -223,6 +219,7 @@ export const addNote = function (actor, placement, message) { }; // Coerce actor into a [to, from, ...] array + // eslint-disable-next-line unicorn/prefer-spread const actors = [].concat(actor, actor); notes.push(note); @@ -337,7 +334,7 @@ export const addDetails = function (actorId, text) { }; export const getActorProperty = function (actor, key) { - if (typeof actor !== 'undefined' && typeof actor.properties !== 'undefined') { + if (actor !== undefined && actor.properties !== undefined) { return actor.properties[key]; } @@ -345,7 +342,7 @@ export const getActorProperty = function (actor, key) { }; export const apply = function (param) { - if (param instanceof Array) { + if (Array.isArray(param)) { param.forEach(function (item) { apply(item); }); diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 9422a5f37..6395940b0 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 fa943d658..1f6164b92 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 = {}; @@ -91,7 +90,7 @@ export const bounds = { setConf(configApi.getConfig()); }, updateVal: function (obj, key, val, fun) { - if (typeof obj[key] === 'undefined') { + if (obj[key] === undefined) { obj[key] = val; } else { obj[key] = fun(val, obj[key]); @@ -481,8 +480,8 @@ export const drawActors = function ( let prevWidth = 0; let prevMargin = 0; let maxHeight = 0; - for (let i = 0; i < actorKeys.length; i++) { - const actor = actors[actorKeys[i]]; + for (const actorKey of actorKeys) { + const actor = actors[actorKey]; // Add some rendering data to the object actor.width = actor.width || conf.width; @@ -509,8 +508,8 @@ export const drawActors = function ( export const drawActorsPopup = function (diagram, actors, actorKeys, doc) { let maxHeight = 0; let maxWidth = 0; - for (let i = 0; i < actorKeys.length; i++) { - const actor = actors[actorKeys[i]]; + for (const actorKey of actorKeys) { + const actor = actors[actorKey]; const minMenuWidth = getRequiredPopupWidth(actor); const menuDimensions = svgDraw.drawPopup( diagram, @@ -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); }; @@ -1180,7 +1178,7 @@ const buildMessageModel = function (msg, actors, diagObj) { const toBounds = activationBounds(msg.to, actors); const fromIdx = fromBounds[0] <= toBounds[0] ? 1 : 0; const toIdx = fromBounds[0] < toBounds[0] ? 0 : 1; - const allBounds = fromBounds.concat(toBounds); + const allBounds = [...fromBounds, ...toBounds]; const boundedWidth = Math.abs(toBounds[toIdx] - fromBounds[fromIdx]); if (msg.wrap && msg.message) { msg.message = utils.wrapLabel( diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index ed4373514..ce1caea69 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -13,7 +13,7 @@ export const drawRect = function (elem, rectData) { rectElem.attr('rx', rectData.rx); rectElem.attr('ry', rectData.ry); - if (typeof rectData.class !== 'undefined') { + if (rectData.class !== undefined) { rectElem.attr('class', rectData.class); } @@ -62,7 +62,7 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe g.attr('display', displayValue); addPopupInteraction('#actor' + actorCnt + '_popup', actorCnt); var actorClass = ''; - if (typeof rectData.class !== 'undefined') { + if (rectData.class !== undefined) { actorClass = ' ' + rectData.class; } @@ -160,8 +160,8 @@ export const drawText = function (elem, textData) { let dy = 0; let yfunc = () => textData.y; if ( - typeof textData.valign !== 'undefined' && - typeof textData.textMargin !== 'undefined' && + textData.valign !== undefined && + textData.textMargin !== undefined && textData.textMargin > 0 ) { switch (textData.valign) { @@ -186,9 +186,9 @@ export const drawText = function (elem, textData) { } } if ( - typeof textData.anchor !== 'undefined' && - typeof textData.textMargin !== 'undefined' && - typeof textData.width !== 'undefined' + textData.anchor !== undefined && + textData.textMargin !== undefined && + textData.width !== undefined ) { switch (textData.anchor) { case 'left': @@ -214,12 +214,11 @@ export const drawText = function (elem, textData) { break; } } - for (let i = 0; i < lines.length; i++) { - let line = lines[i]; + for (let [i, line] of lines.entries()) { if ( - typeof textData.textMargin !== 'undefined' && + textData.textMargin !== undefined && textData.textMargin === 0 && - typeof textData.fontSize !== 'undefined' + textData.fontSize !== undefined ) { dy = i * textData.fontSize; } @@ -227,28 +226,28 @@ export const drawText = function (elem, textData) { const textElem = elem.append('text'); textElem.attr('x', textData.x); textElem.attr('y', yfunc()); - if (typeof textData.anchor !== 'undefined') { + if (textData.anchor !== undefined) { textElem .attr('text-anchor', textData.anchor) .attr('dominant-baseline', textData.dominantBaseline) .attr('alignment-baseline', textData.alignmentBaseline); } - if (typeof textData.fontFamily !== 'undefined') { + if (textData.fontFamily !== undefined) { textElem.style('font-family', textData.fontFamily); } - if (typeof textData.fontSize !== 'undefined') { + if (textData.fontSize !== undefined) { textElem.style('font-size', textData.fontSize); } - if (typeof textData.fontWeight !== 'undefined') { + if (textData.fontWeight !== undefined) { textElem.style('font-weight', textData.fontWeight); } - if (typeof textData.fill !== 'undefined') { + if (textData.fill !== undefined) { textElem.attr('fill', textData.fill); } - if (typeof textData.class !== 'undefined') { + if (textData.class !== undefined) { textElem.attr('class', textData.class); } - if (typeof textData.dy !== 'undefined') { + if (textData.dy !== undefined) { textElem.attr('dy', textData.dy); } else if (dy !== 0) { textElem.attr('dy', dy); @@ -257,7 +256,7 @@ export const drawText = function (elem, textData) { if (textData.tspan) { const span = textElem.append('tspan'); span.attr('x', textData.x); - if (typeof textData.fill !== 'undefined') { + if (textData.fill !== undefined) { span.attr('fill', textData.fill); } span.text(line); @@ -265,8 +264,8 @@ export const drawText = function (elem, textData) { textElem.text(line); } if ( - typeof textData.valign !== 'undefined' && - typeof textData.textMargin !== 'undefined' && + textData.valign !== undefined && + textData.textMargin !== undefined && textData.textMargin > 0 ) { textHeight += (textElem._groups || textElem)[0][0].getBBox().height; @@ -561,7 +560,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) { drawLoopLine(loopModel.stopx, loopModel.starty, loopModel.stopx, loopModel.stopy); drawLoopLine(loopModel.startx, loopModel.stopy, loopModel.stopx, loopModel.stopy); drawLoopLine(loopModel.startx, loopModel.starty, loopModel.startx, loopModel.stopy); - if (typeof loopModel.sections !== 'undefined') { + if (loopModel.sections !== undefined) { loopModel.sections.forEach(function (item) { drawLoopLine(loopModel.startx, item.y, loopModel.stopx, item.y).style( 'stroke-dasharray', @@ -601,7 +600,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) { let textElem = drawText(g, txt); - if (typeof loopModel.sectionTitles !== 'undefined') { + if (loopModel.sectionTitles !== undefined) { loopModel.sectionTitles.forEach(function (item, idx) { if (item.message) { txt.text = item.message; diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js index 580dafe89..8e5f5f32b 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/shapes.js b/packages/mermaid/src/diagrams/state/shapes.js index aa99ff862..0a495e56c 100644 --- a/packages/mermaid/src/diagrams/state/shapes.js +++ b/packages/mermaid/src/diagrams/state/shapes.js @@ -197,10 +197,8 @@ export const addTitleAndBox = (g, stateDef, altBkg) => { if (titleWidth > orgWidth) { startX = (orgWidth - width) / 2 + pad; } - if (Math.abs(orgX - graphBox.x) < pad) { - if (titleWidth > orgWidth) { - startX = orgX - (titleWidth - orgWidth) / 2; - } + if (Math.abs(orgX - graphBox.x) < pad && titleWidth > orgWidth) { + startX = orgX - (titleWidth - orgWidth) / 2; } const lineY = 1 - getConfig().state.textHeight; @@ -301,7 +299,7 @@ export const drawText = function (elem, textData) { textElem.attr('y', textData.y); textElem.style('text-anchor', textData.anchor); textElem.attr('fill', textData.fill); - if (typeof textData.class !== 'undefined') { + if (textData.class !== undefined) { textElem.attr('class', textData.class); } @@ -464,7 +462,7 @@ export const drawEdge = function (elem, path, relation) { 'url(' + url + '#' + getRelationType(stateDb.relationType.DEPENDENCY) + 'End' + ')' ); - if (typeof relation.title !== 'undefined') { + if (relation.title !== undefined) { const label = elem.append('g').attr('class', 'stateLabel'); const { x, y } = utils.calcLabelPosition(path.points); diff --git a/packages/mermaid/src/diagrams/state/stateDb.js b/packages/mermaid/src/diagrams/state/stateDb.js index 19ecbe65f..991aba078 100644 --- a/packages/mermaid/src/diagrams/state/stateDb.js +++ b/packages/mermaid/src/diagrams/state/stateDb.js @@ -94,11 +94,9 @@ const docTranslator = (parent, node, first) => { docTranslator(parent, node.state1, true); docTranslator(parent, node.state2, false); } else { - if (node.stmt === STMT_STATE) { - if (node.id === '[*]') { - node.id = first ? parent.id + '_start' : parent.id + '_end'; - node.start = first; - } + if (node.stmt === STMT_STATE && node.id === '[*]') { + node.id = first ? parent.id + '_start' : parent.id + '_end'; + node.start = first; } if (node.doc) { @@ -218,7 +216,7 @@ export const addState = function ( textStyles = null ) { // add the state if needed - if (typeof currentDocument.states[id] === 'undefined') { + if (currentDocument.states[id] === undefined) { log.info('Adding state ', id, descr); currentDocument.states[id] = { id: id, @@ -457,25 +455,23 @@ const getDividerId = () => { */ export const addStyleClass = function (id, styleAttributes = '') { // create a new style class object with this id - if (typeof classes[id] === 'undefined') { + if (classes[id] === undefined) { classes[id] = { id: id, styles: [], textStyles: [] }; // This is a classDef } const foundClass = classes[id]; - if (typeof styleAttributes !== 'undefined') { - if (styleAttributes !== null) { - styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => { - // remove any trailing ; - const fixedAttrib = attrib.replace(/([^;]*);/, '$1').trim(); + if (styleAttributes !== undefined && styleAttributes !== null) { + styleAttributes.split(STYLECLASS_SEP).forEach((attrib) => { + // remove any trailing ; + const fixedAttrib = attrib.replace(/([^;]*);/, '$1').trim(); - // replace some style keywords - if (attrib.match(COLOR_KEYWORD)) { - const newStyle1 = fixedAttrib.replace(FILL_KEYWORD, BG_FILL); - const newStyle2 = newStyle1.replace(COLOR_KEYWORD, FILL_KEYWORD); - foundClass.textStyles.push(newStyle2); - } - foundClass.styles.push(fixedAttrib); - }); - } + // replace some style keywords + if (attrib.match(COLOR_KEYWORD)) { + const newStyle1 = fixedAttrib.replace(FILL_KEYWORD, BG_FILL); + const newStyle2 = newStyle1.replace(COLOR_KEYWORD, FILL_KEYWORD); + foundClass.textStyles.push(newStyle2); + } + foundClass.styles.push(fixedAttrib); + }); } }; @@ -498,7 +494,7 @@ export const getClasses = function () { export const setCssClass = function (itemIds, cssClassName) { itemIds.split(',').forEach(function (id) { let foundState = getState(id); - if (typeof foundState === 'undefined') { + if (foundState === undefined) { const trimmedId = id.trim(); addState(trimmedId); foundState = getState(trimmedId); @@ -519,7 +515,7 @@ export const setCssClass = function (itemIds, cssClassName) { */ export const setStyle = function (itemId, styleText) { const item = getState(itemId); - if (typeof item !== 'undefined') { + if (item !== undefined) { item.textStyles.push(styleText); } }; @@ -532,7 +528,7 @@ export const setStyle = function (itemId, styleText) { */ export const setTextStyle = function (itemId, cssClassName) { const item = getState(itemId); - if (typeof item !== 'undefined') { + if (item !== undefined) { item.textStyles.push(cssClassName); } }; diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index 6fdeb723e..fa2470d6b 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.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, @@ -71,8 +71,8 @@ const conf = {}; export const setConf = function (cnf) { const keys = Object.keys(cnf); - for (let i = 0; i < keys.length; i++) { - conf[keys[i]] = cnf[keys[i]]; + for (const key of keys) { + conf[key] = cnf[key]; } }; @@ -106,7 +106,7 @@ export const getClasses = function (text, diagramObj) { * @returns {string} */ function getClassesFromDbInfo(dbInfoItem) { - if (typeof dbInfoItem === 'undefined' || dbInfoItem === null) { + if (dbInfoItem === undefined || dbInfoItem === null) { return ''; } else { if (dbInfoItem.classes) { @@ -291,11 +291,9 @@ const setupNode = (g, parent, parsedItem, diagramStates, diagramDb, altFlag) => } } - if (parent) { - if (parent.id !== 'root') { - log.trace('Setting node ', itemId, ' to be child of its parent ', parent.id); - g.setParent(itemId, parent.id); - } + if (parent && parent.id !== 'root') { + log.trace('Setting node ', itemId, ' to be child of its parent ', parent.id); + g.setParent(itemId, parent.id); } if (parsedItem.doc) { log.trace('Adding nodes children '); @@ -386,7 +384,7 @@ export const draw = function (text, id, _version, diag) { nodeDb = {}; // Fetch the default direction, use TD if none was found let dir = diag.db.getDirection(); - if (typeof dir === 'undefined') { + if (dir === undefined) { dir = DEFAULT_DIAGRAM_DIRECTION; } @@ -459,9 +457,7 @@ export const draw = function (text, id, _version, diag) { // Add label rects for non html labels // if (!evaluate(conf.htmlLabels) || true) { const labels = document.querySelectorAll('[id="' + id + '"] .edgeLabel .label'); - for (let k = 0; k < labels.length; k++) { - const label = labels[k]; - + for (const label of labels) { // Get dimensions of label const dim = label.getBBox(); @@ -474,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 57b1d1665..8d410fdd9 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; @@ -162,8 +160,8 @@ const renderDoc = (doc, diagram, parentId, altBkg, root, domDocument, diagObj) = let first = true; - for (let i = 0; i < keys.length; i++) { - const stateDef = states[keys[i]]; + for (const key of keys) { + const stateDef = states[key]; if (parentId) { stateDef.parentId = parentId; @@ -245,7 +243,7 @@ const renderDoc = (doc, diagram, parentId, altBkg, root, domDocument, diagObj) = const svgElem = diagram.node(); graph.nodes().forEach(function (v) { - if (typeof v !== 'undefined' && typeof graph.node(v) !== 'undefined') { + if (v !== undefined && graph.node(v) !== undefined) { log.warn('Node ' + v + ': ' + JSON.stringify(graph.node(v))); root .select('#' + svgElem.id + ' #' + v) @@ -287,7 +285,7 @@ const renderDoc = (doc, diagram, parentId, altBkg, root, domDocument, diagObj) = let stateBox = svgElem.getBBox(); graph.edges().forEach(function (e) { - if (typeof e !== 'undefined' && typeof graph.edge(e) !== 'undefined') { + if (e !== undefined && graph.edge(e) !== undefined) { log.debug('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(graph.edge(e))); drawEdge(diagram, graph.edge(e), graph.edge(e).relation); } diff --git a/packages/mermaid/src/diagrams/user-journey/journeyDb.js b/packages/mermaid/src/diagrams/user-journey/journeyDb.js index 0707636f5..ce8705094 100644 --- a/packages/mermaid/src/diagrams/user-journey/journeyDb.js +++ b/packages/mermaid/src/diagrams/user-journey/journeyDb.js @@ -105,10 +105,10 @@ const compileTasks = function () { }; let allProcessed = true; - for (let i = 0; i < rawTasks.length; i++) { + for (const [i, rawTask] of rawTasks.entries()) { compileTask(i); - allProcessed = allProcessed && rawTasks[i].processed; + allProcessed = allProcessed && rawTask.processed; } return allProcessed; }; diff --git a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts index 3880a243a..df46fc9c6 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 = { @@ -146,7 +143,7 @@ export const bounds = { this.verticalPos = 0; }, updateVal: function (obj, key, val, fun) { - if (typeof obj[key] === 'undefined') { + if (obj[key] === undefined) { obj[key] = val; } else { obj[key] = fun(val, obj[key]); @@ -221,8 +218,7 @@ export const drawTasks = function (diagram, tasks, verticalPos) { let num = 0; // Draw the tasks - for (let i = 0; i < tasks.length; i++) { - const task = tasks[i]; + for (const [i, task] of tasks.entries()) { if (lastSection !== task.section) { fill = fills[sectionNumber % fills.length]; num = sectionNumber % fills.length; diff --git a/packages/mermaid/src/diagrams/user-journey/svgDraw.js b/packages/mermaid/src/diagrams/user-journey/svgDraw.js index a8c59a939..74d5d2a02 100644 --- a/packages/mermaid/src/diagrams/user-journey/svgDraw.js +++ b/packages/mermaid/src/diagrams/user-journey/svgDraw.js @@ -11,7 +11,7 @@ export const drawRect = function (elem, rectData) { rectElem.attr('rx', rectData.rx); rectElem.attr('ry', rectData.ry); - if (typeof rectData.class !== 'undefined') { + if (rectData.class !== undefined) { rectElem.attr('class', rectData.class); } @@ -116,11 +116,11 @@ export const drawCircle = function (element, circleData) { circleElement.attr('stroke', circleData.stroke); circleElement.attr('r', circleData.r); - if (typeof circleElement.class !== 'undefined') { + if (circleElement.class !== undefined) { circleElement.attr('class', circleElement.class); } - if (typeof circleData.title !== 'undefined') { + if (circleData.title !== undefined) { circleElement.append('title').text(circleData.title); } @@ -138,7 +138,7 @@ export const drawText = function (elem, textData) { textElem.style('text-anchor', textData.anchor); - if (typeof textData.class !== 'undefined') { + if (textData.class !== undefined) { textElem.attr('class', textData.class); } diff --git a/packages/mermaid/src/docs.mts b/packages/mermaid/src/docs.mts index aa57a3c01..313d1f2de 100644 --- a/packages/mermaid/src/docs.mts +++ b/packages/mermaid/src/docs.mts @@ -35,7 +35,7 @@ import { exec } from 'child_process'; import { globby } from 'globby'; import { JSDOM } from 'jsdom'; import type { Code, Root } from 'mdast'; -import { posix, dirname, relative } from 'path'; +import { posix, dirname, relative, join } from 'path'; import prettier from 'prettier'; import { remark } from 'remark'; import chokidar from 'chokidar'; @@ -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'); @@ -66,8 +66,11 @@ const LOGMSG_COPIED = `, and copied to ${FINAL_DOCS_DIR}`; const WARN_DOCSDIR_DOESNT_MATCH = `Changed files were transformed in ${SOURCE_DOCS_DIR} but do not match the files in ${FINAL_DOCS_DIR}. Please run 'pnpm --filter mermaid run docs:build' after making changes to ${SOURCE_DOCS_DIR} to update the ${FINAL_DOCS_DIR} directory with the transformed files.`; const prettierConfig = prettier.resolveConfig.sync('.') ?? {}; +// From https://github.com/vuejs/vitepress/blob/428eec3750d6b5648a77ac52d88128df0554d4d1/src/node/markdownToVue.ts#L20-L21 +const includesRE = //g; +const includedFiles: Set = new Set(); -let filesWereTransformed = false; +const filesTransformed: Set = new Set(); const generateHeader = (file: string): string => { // path from file in docs/* to repo root, e.g ../ or ../../ */ @@ -117,10 +120,10 @@ const logWasOrShouldBeTransformed = (filename: string, wasCopied: boolean) => { * transformed contents to the final documentation directory if the doCopy flag is true. Log * messages to the console. * - * @param {string} filename Name of the file that will be verified - * @param {boolean} [doCopy=false] Whether we should copy that transformedContents to the final + * @param filename Name of the file that will be verified + * @param doCopy?=false Whether we should copy that transformedContents to the final * documentation directory. Default is `false` - * @param {string} [transformedContent] New contents for the file + * @param transformedContent? New contents for the file */ const copyTransformedContents = (filename: string, doCopy = false, transformedContent?: string) => { const fileInFinalDocDir = changeToFinalDocDir(filename); @@ -132,7 +135,7 @@ const copyTransformedContents = (filename: string, doCopy = false, transformedCo return; // Files are same, skip. } - filesWereTransformed = true; + filesTransformed.add(fileInFinalDocDir); if (doCopy) { writeFileSync(fileInFinalDocDir, newBuffer); } @@ -151,6 +154,19 @@ const transformToBlockQuote = (content: string, type: string) => { const injectPlaceholders = (text: string): string => text.replace(//g, MERMAID_MAJOR_VERSION).replace(//g, CDN_URL); +const transformIncludeStatements = (file: string, text: string): string => { + // resolve includes - src https://github.com/vuejs/vitepress/blob/428eec3750d6b5648a77ac52d88128df0554d4d1/src/node/markdownToVue.ts#L65-L76 + return text.replace(includesRE, (m, m1) => { + try { + const includePath = join(dirname(file), m1); + const content = readSyncedUTF8file(includePath); + includedFiles.add(changeToFinalDocDir(includePath)); + return content; + } catch (error) { + throw new Error(`Failed to resolve include "${m1}" in "${file}": ${error}`); + } + }); +}; /** * Transform a markdown file and write the transformed file to the directory for published * documentation @@ -164,8 +180,7 @@ const injectPlaceholders = (text: string): string => * @param file {string} name of the file that will be verified */ const transformMarkdown = (file: string) => { - const doc = injectPlaceholders(readSyncedUTF8file(file)); - + const doc = injectPlaceholders(transformIncludeStatements(file, readSyncedUTF8file(file))); const ast: Root = remark.parse(doc); const out = flatmap(ast, (c: Code) => { if (c.type !== 'code' || !c.lang) { @@ -270,6 +285,12 @@ const getFilesFromGlobs = async (globs: string[]): Promise => { console.log(`${action} ${mdFiles.length} markdown files...`); mdFiles.forEach(transformMarkdown); + for (const includedFile of includedFiles) { + rmSync(includedFile, { force: true }); + filesTransformed.delete(includedFile); + console.log(`Removed ${includedFile} as it was used inside an @include block.`); + } + const htmlFileGlobs = getGlobs([posix.join(sourceDirGlob, '*.html')]); const htmlFiles = await getFilesFromGlobs(htmlFileGlobs); console.log(`${action} ${htmlFiles.length} html files...`); @@ -282,7 +303,7 @@ const getFilesFromGlobs = async (globs: string[]): Promise => { copyTransformedContents(file, !verifyOnly); // no transformation }); - if (filesWereTransformed) { + if (filesTransformed.size > 0) { if (verifyOnly) { console.log(WARN_DOCSDIR_DOESNT_MATCH); process.exit(1); diff --git a/packages/mermaid/src/docs/.vitepress/config.ts b/packages/mermaid/src/docs/.vitepress/config.ts index 1417bca72..216541d52 100644 --- a/packages/mermaid/src/docs/.vitepress/config.ts +++ b/packages/mermaid/src/docs/.vitepress/config.ts @@ -1,6 +1,5 @@ import { version } from '../../../package.json'; import MermaidExample from './mermaid-markdown-all'; -import { MermaidMarkdown } from 'vitepress-plugin-mermaid'; import { defineConfig, MarkdownOptions } from 'vitepress'; const allMarkdownTransformers: MarkdownOptions = { @@ -8,7 +7,6 @@ const allMarkdownTransformers: MarkdownOptions = { theme: 'github-dark', config: async (md) => { await MermaidExample(md); - MermaidMarkdown(md); }, }; @@ -108,6 +106,7 @@ function sidebarSyntax() { { text: 'Requirement Diagram', link: '/syntax/requirementDiagram' }, { text: 'Gitgraph (Git) Diagram 🔥', link: '/syntax/gitgraph' }, { text: 'C4C Diagram (Context) Diagram 🦺⚠️', link: '/syntax/c4c' }, + { text: 'Mindmaps 🔥', link: '/syntax/mindmap' }, { text: 'Other Examples', link: '/syntax/examples' }, ], }, diff --git a/packages/mermaid/src/docs/.vitepress/mermaid-markdown-all.ts b/packages/mermaid/src/docs/.vitepress/mermaid-markdown-all.ts index 3a9521375..14340462c 100644 --- a/packages/mermaid/src/docs/.vitepress/mermaid-markdown-all.ts +++ b/packages/mermaid/src/docs/.vitepress/mermaid-markdown-all.ts @@ -21,17 +21,30 @@ const MermaidExample = async (md: MarkdownRenderer) => { // doing ```mermaid-example {line-numbers=5 highlight=14-17} is not supported const langAttrs = ''; - return `
Code:
-
- - mermaid - ${ - // html is pre-escaped by the highlight function - // (it also adds `v-pre` to ignore Vue template syntax) - md.options.highlight(token.content, 'mermaid', langAttrs) - } -
-
Diagram:
`; + return ` +
Code:
+
+ + mermaid + ${ + // html is pre-escaped by the highlight function + // (it also adds `v-pre` to ignore Vue template syntax) + md.options.highlight(token.content, 'mermaid', langAttrs) + } +
`; + } else if (token.info.trim() === 'mermaid') { + const key = index; + return ` + + + + + +`; } if (token.info.trim() === 'warning') { return `

WARNING

${token.content}}

`; diff --git a/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue b/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue new file mode 100644 index 000000000..85c13393c --- /dev/null +++ b/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue @@ -0,0 +1,72 @@ + + + diff --git a/packages/mermaid/src/docs/.vitepress/theme/index.ts b/packages/mermaid/src/docs/.vitepress/theme/index.ts index e57bb03e2..efb065fea 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/index.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/index.ts @@ -1,7 +1,7 @@ import DefaultTheme from 'vitepress/theme'; -// @ts-ignore -import Mermaid from 'vitepress-plugin-mermaid/Mermaid.vue'; import './custom.css'; +// @ts-ignore +import Mermaid from './Mermaid.vue'; import { getRedirect } from './redirect'; export default { diff --git a/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts b/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts new file mode 100644 index 000000000..b287346f9 --- /dev/null +++ b/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts @@ -0,0 +1,14 @@ +import mermaid, { type MermaidConfig } from 'mermaid'; +import mindmap from '@mermaid-js/mermaid-mindmap'; + +try { + await mermaid.registerExternalDiagrams([mindmap]); +} catch (e) { + console.error(e); +} + +export const render = async (id: string, code: string, config: MermaidConfig): Promise => { + mermaid.initialize(config); + const svg = await mermaid.renderAsync(id, code); + return svg; +}; diff --git a/packages/mermaid/src/docs/redirect.spec.ts b/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts similarity index 96% rename from packages/mermaid/src/docs/redirect.spec.ts rename to packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts index 6bf25c03b..c26364108 100644 --- a/packages/mermaid/src/docs/redirect.spec.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts @@ -2,7 +2,7 @@ // Update https://github.com/mermaid-js/mermaid/blob/18c27c6f1d0537a738cbd95898df301b83c38ffc/packages/mermaid/src/docs.mts#L246 once fixed import { expect, test } from 'vitest'; -import { getRedirect } from './.vitepress/theme/redirect'; +import { getRedirect } from './redirect'; test.each([ ['http://localhost:1234/mermaid/#/flowchart.md', 'syntax/flowchart.html'], diff --git a/packages/mermaid/src/docs/community/newDiagram.md b/packages/mermaid/src/docs/community/newDiagram.md index 74026b3ff..57a454671 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 @@ -56,6 +56,11 @@ 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. +[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 -In a similar way to the directives the jison syntax are quite similar between the diagrams. +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. ```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 ade20a839..e7947adec 100644 --- a/packages/mermaid/src/docs/config/accessibility.md +++ b/packages/mermaid/src/docs/config/accessibility.md @@ -4,83 +4,151 @@ 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: - -- `accTitle: "Your Accessibility Title"` or -- `accDescr: "Your Accessibility Description"` - -**When these two options are defined, they will add a corresponding `` and `<desc>` tag in the SVG.** - -Let us take a look at the following example with a flowchart diagram: - -```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] +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.):_ +```html +<svg + aria-roledescription="stateDiagram" + class="statediagram" + xmlns="http://www.w3.org/2000/svg" + width="100%" + id="mermaid-1668720491568" +></svg> ``` -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: +### Accessible Title and Description -![Accessibility options rendered inside SVG](img/accessibility-div-example.png) +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. -### Multi-line Accessibility title/description +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. -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 `}`. +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"). -`accTitle: My single line title value` (**_single line format_**) +_(Note that some of the SVG attributes and the SVG contents are omitted for clarity.)_ -vs +```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 + +``` -`accDescr: { My multi-line description of the diagram }` (**_multi-line format_**) +Details for the syntax follow. -Let us look at it in the following example, with same flowchart: +#### accessible title + +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.) + +Ex: `accTitle: This is a single line title` + +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. + +#### accessible description + +An accessible description can be 1 line long (a single line) or many lines long. + +The **single line accessible description** is specified with the **accDescr** _keyword_, followed by a colon (`:`), followed by the string value for the description. + +Ex: `accDescr: This is a single line description.` + +A **multiple line accessible description** _does not have a colon (`:`) after the accDescr keyword_ and is surrounded by curly brackets (`{}`). + +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 [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. + +#### accTitle and accDescr Usage Examples + +- Flowchart with the accessible title "Big Decisions" and the single-line accessible description "Bob's Burgers process for making big decisions" ```mermaid-example - graph LR - accTitle: Big decisions + 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] +``` +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 + +``` + +- 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 + graph LR + accTitle: Bob's Burger's Making Big Decisions accDescr { - My multi-line description - of the diagram - } - - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] - + 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] ``` -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: +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ -![Accessibility options rendered inside SVG](img/accessibility-div-example-2.png) - -### Sample Code Snippet for other diagram types - -#### Sequence Diagram - -```mermaid-example - sequenceDiagram - accTitle: My Sequence Diagram - accDescr: My Sequence Diagram Description - - Alice->>John: Hello John, how are you? - John-->>Alice: Great! - Alice-)John: See you later! +```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. + + ``` -#### Class Diagram +#### Sample Code Snippets for other diagram types + +##### Class Diagram ```mermaid-example classDiagram @@ -90,18 +158,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the Vehicle <|-- Car ``` -#### State Diagram - -```mermaid-example - stateDiagram - accTitle: My State Diagram - accDescr: My State Diagram Description - - s1 --> s2 - -``` - -#### 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 78f3546cc..2d4da9530 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** - This is the default theme for all diagrams. -The following are a list of **Deployable themes**, sample `%%init%%` directives and `initialize` calls. +1. **neutral** - 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. +1. **dark** - 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. +1. **forest** - This theme contains shades of green. -3. **dark**- A theme that would go well with other dark-colored elements. +1. **base** - 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,22 +29,46 @@ 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'}}%% +%%{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: +> **Reminder**: the only theme that can be customed is the `base` theme. The following section covers how to use `themeVariables` for customizations. + +## Customizing Themes with `themeVariables` + +To make a custom theme, modify `themeVariables` via `init`. + +You will need to use the [base](#available-themes) theme as it is the only modifiable theme. + +| Parameter | Description | Type | Properties | +| -------------- | ------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- | +| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables-reference-table)) | + +Example of modifying `themeVariables` using the `init` directive: ```mermaid-example -%%{init: {'theme':'base'}}%% +%%{ + 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} @@ -65,402 +85,93 @@ Here is an example of how `%%init%%` can set the theme to 'base', this assumes t 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 -``` - -**Notes:** -Leaving it empty will set all variable values to default. - -## Color and Color Calculation: - -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. - -**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 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. - -## 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%%`. - -```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 -``` - -\*\*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'}}}%% - 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 -``` - -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/index.html.todo b/packages/mermaid/src/docs/index.html.todo deleted file mode 100644 index 8728338f9..000000000 --- a/packages/mermaid/src/docs/index.html.todo +++ /dev/null @@ -1,179 +0,0 @@ - - - - - - mermaid - Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, - gantt charts and git graphs. - - - - - - - - - - - - - - - - - - - -
- - - - - - - - - diff --git a/packages/mermaid/src/docs/intro/examples.md b/packages/mermaid/src/docs/intro/examples.md new file mode 100644 index 000000000..f4cb3b929 --- /dev/null +++ b/packages/mermaid/src/docs/intro/examples.md @@ -0,0 +1,100 @@ +## Diagram Types + +### [Flowchart](../syntax/flowchart.md?id=flowcharts-basic-syntax) + +```mermaid-example +graph TD; + A-->B; + A-->C; + B-->D; + C-->D; +``` + +### [Sequence diagram](../syntax/sequenceDiagram.md) + +```mermaid-example +sequenceDiagram + participant Alice + participant Bob + Alice->>John: Hello John, how are you? + loop Healthcheck + John->>John: Fight against hypochondria + end + Note right of John: Rational thoughts
prevail! + John-->>Alice: Great! + John->>Bob: How about you? + Bob-->>John: Jolly good! +``` + +### [Gantt diagram](../syntax/gantt.md) + +```mermaid-example +gantt +dateFormat YYYY-MM-DD +title Adding GANTT diagram to mermaid +excludes weekdays 2014-01-10 + +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 +``` + +### [Class diagram](../syntax/classDiagram.md) + +```mermaid-example +classDiagram +Class01 <|-- AveryLongClass : Cool +Class03 *-- Class04 +Class05 o-- Class06 +Class07 .. Class08 +Class09 --> C2 : Where am i? +Class09 --* C3 +Class09 --|> Class07 +Class07 : equals() +Class07 : Object[] elementData +Class01 : size() +Class01 : int chimp +Class01 : int gorilla +Class08 <--> C2: Cool label +``` + +### [Git graph](../syntax/gitgraph.md) + +```mermaid-example + gitGraph + commit + commit + branch develop + commit + commit + commit + checkout main + commit + commit +``` + +### [Entity Relationship Diagram - :exclamation: experimental](../syntax/entityRelationshipDiagram.md) + +```mermaid-example +erDiagram + CUSTOMER ||--o{ ORDER : places + ORDER ||--|{ LINE-ITEM : contains + CUSTOMER }|..|{ DELIVERY-ADDRESS : uses + +``` + +### [User Journey Diagram](../syntax/userJourney.md) + +```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 +``` diff --git a/packages/mermaid/src/docs/intro/index.md b/packages/mermaid/src/docs/intro/index.md index b58321e75..df1aa3b62 100644 --- a/packages/mermaid/src/docs/intro/index.md +++ b/packages/mermaid/src/docs/intro/index.md @@ -44,106 +44,7 @@ In our release process we rely heavily on visual regression tests using [applito -## Diagram Types - -### [Flowchart](../syntax/flowchart.md?id=flowcharts-basic-syntax) - -```mermaid-example -graph TD; - A-->B; - A-->C; - B-->D; - C-->D; -``` - -### [Sequence diagram](../syntax/sequenceDiagram.md) - -```mermaid-example -sequenceDiagram - participant Alice - participant Bob - Alice->>John: Hello John, how are you? - loop Healthcheck - John->>John: Fight against hypochondria - end - Note right of John: Rational thoughts
prevail! - John-->>Alice: Great! - John->>Bob: How about you? - Bob-->>John: Jolly good! -``` - -### [Gantt diagram](../syntax/gantt.md) - -```mermaid-example -gantt -dateFormat YYYY-MM-DD -title Adding GANTT diagram to mermaid -excludes weekdays 2014-01-10 - -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 -``` - -### [Class diagram](../syntax/classDiagram.md) - -```mermaid-example -classDiagram -Class01 <|-- AveryLongClass : Cool -Class03 *-- Class04 -Class05 o-- Class06 -Class07 .. Class08 -Class09 --> C2 : Where am i? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -Class08 <--> C2: Cool label -``` - -### [Git graph](../syntax/gitgraph.md) - -```mermaid-example - gitGraph - commit - commit - branch develop - commit - commit - commit - checkout main - commit - commit -``` - -### [Entity Relationship Diagram - :exclamation: experimental](../syntax/entityRelationshipDiagram.md) - -```mermaid-example -erDiagram - CUSTOMER ||--o{ ORDER : places - ORDER ||--|{ LINE-ITEM : contains - CUSTOMER }|..|{ DELIVERY-ADDRESS : uses - -``` - -### [User Journey Diagram](../syntax/userJourney.md) - -```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 -``` + ## Installation diff --git a/packages/mermaid/src/docs/intro/n00b-gettingStarted.md b/packages/mermaid/src/docs/intro/n00b-gettingStarted.md index 8360d2410..a4dd7662f 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/misc/integrations.md b/packages/mermaid/src/docs/misc/integrations.md index 31f2d09d0..55d0e5b20 100644 --- a/packages/mermaid/src/docs/misc/integrations.md +++ b/packages/mermaid/src/docs/misc/integrations.md @@ -103,10 +103,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) @@ -116,6 +116,7 @@ They also serve as proof of concept, for the variety of things that can be built - [Draw.io](https://draw.io) - [Plugin](https://github.com/nopeslide/drawio_mermaid_plugin) - [Inkdrop](https://www.inkdrop.app) - [Plugin](https://github.com/inkdropapp/inkdrop-mermaid) - [Vim](https://www.vim.org) + - [Official Vim Syntax and ftplugin](https://github.com/craigmac/vim-mermaid) - [Vim Diagram Syntax](https://github.com/zhaozg/vim-diagram) - [GNU Emacs](https://www.gnu.org/software/emacs/) - [Major mode for .mmd files](https://github.com/abrochard/mermaid-mode) diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index e9b918529..8cd5796ff 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: diff --git a/packages/mermaid/src/docs/syntax/mindmap.md b/packages/mermaid/src/docs/syntax/mindmap.md index edad887c2..beaae7ad5 100644 --- a/packages/mermaid/src/docs/syntax/mindmap.md +++ b/packages/mermaid/src/docs/syntax/mindmap.md @@ -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/docs/vite.config.ts b/packages/mermaid/src/docs/vite.config.ts index bea6fe26d..15652c21c 100644 --- a/packages/mermaid/src/docs/vite.config.ts +++ b/packages/mermaid/src/docs/vite.config.ts @@ -31,6 +31,11 @@ export default defineConfig({ resolve: { alias: { mermaid: path.join(__dirname, '../../dist/mermaid.esm.min.mjs'), // Use this one to build + + '@mermaid-js/mermaid-mindmap': path.join( + __dirname, + '../../../mermaid-mindmap/dist/mermaid-mindmap.esm.min.mjs' + ), // Use this one to build }, }, server: { diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts index 993f2f944..7a4d744e4 100644 --- a/packages/mermaid/src/mermaid.ts +++ b/packages/mermaid/src/mermaid.ts @@ -108,7 +108,7 @@ const initThrowsErrors = function ( // if last argument is a function this is the callback function log.debug(`${!callback ? 'No ' : ''}Callback function found`); let nodesToProcess: ArrayLike; - if (typeof nodes === 'undefined') { + if (nodes === undefined) { nodesToProcess = document.querySelectorAll('.mermaid'); } else if (typeof nodes === 'string') { nodesToProcess = document.querySelectorAll(nodes); @@ -121,7 +121,7 @@ const initThrowsErrors = function ( } log.debug(`Found ${nodesToProcess.length} diagrams`); - if (typeof config?.startOnLoad !== 'undefined') { + if (config?.startOnLoad !== undefined) { log.debug('Start On Load: ' + config?.startOnLoad); mermaidAPI.updateSiteConfig({ startOnLoad: config?.startOnLoad }); } @@ -133,6 +133,7 @@ const initThrowsErrors = function ( const errors: DetailedError[] = []; // element is the current div with mermaid class + // eslint-disable-next-line unicorn/prefer-spread for (const element of Array.from(nodesToProcess)) { log.info('Rendering diagram: ' + element.id); /*! Check if previously processed */ @@ -162,7 +163,7 @@ const initThrowsErrors = function ( txt, (svgCode: string, bindFunctions?: (el: Element) => void) => { element.innerHTML = svgCode; - if (typeof callback !== 'undefined') { + if (callback !== undefined) { callback(id); } if (bindFunctions) { @@ -249,7 +250,7 @@ const initThrowsErrorsAsync = async function ( // if last argument is a function this is the callback function log.debug(`${!callback ? 'No ' : ''}Callback function found`); let nodesToProcess: ArrayLike; - if (typeof nodes === 'undefined') { + if (nodes === undefined) { nodesToProcess = document.querySelectorAll('.mermaid'); } else if (typeof nodes === 'string') { nodesToProcess = document.querySelectorAll(nodes); @@ -262,7 +263,7 @@ const initThrowsErrorsAsync = async function ( } log.debug(`Found ${nodesToProcess.length} diagrams`); - if (typeof config?.startOnLoad !== 'undefined') { + if (config?.startOnLoad !== undefined) { log.debug('Start On Load: ' + config?.startOnLoad); mermaidAPI.updateSiteConfig({ startOnLoad: config?.startOnLoad }); } @@ -274,6 +275,7 @@ const initThrowsErrorsAsync = async function ( const errors: DetailedError[] = []; // element is the current div with mermaid class + // eslint-disable-next-line unicorn/prefer-spread for (const element of Array.from(nodesToProcess)) { log.info('Rendering diagram: ' + element.id); /*! Check if previously processed */ @@ -303,7 +305,7 @@ const initThrowsErrorsAsync = async function ( txt, (svgCode: string, bindFunctions?: (el: Element) => void) => { element.innerHTML = svgCode; - if (typeof callback !== 'undefined') { + if (callback !== undefined) { callback(id); } if (bindFunctions) { diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index 786b163c4..f9bad66d7 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 + */ // ------------------------------------------------------------------------------------- @@ -257,11 +292,11 @@ describe('mermaidAPI', function () { }); it('gets the fontFamily from the config', () => { const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', {}); - expect(styles).toMatch(/(.*)\n:root \{ --mermaid-font-family: serif(.*)/); + expect(styles).toMatch(/(.*)\n:root { --mermaid-font-family: serif(.*)/); }); it('gets the alt fontFamily from the config', () => { const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', undefined); - expect(styles).toMatch(/(.*)\n:root \{ --mermaid-alt-font-family: sans-serif(.*)/); + expect(styles).toMatch(/(.*)\n:root { --mermaid-alt-font-family: sans-serif(.*)/); }); describe('there are some classDefs', () => { @@ -277,7 +312,7 @@ describe('mermaidAPI', function () { // prefix any special RegExp characters in the given string with a \ so we can use the literal character in a RegExp function escapeForRegexp(str: string) { - const strChars = str.split(''); // split into array of every char + const strChars = [...str]; // split into array of every char const strEscaped = strChars.map((char) => { if (REGEXP_SPECIALS.includes(char)) { return `\\${char}`; @@ -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) => { @@ -510,7 +546,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 +652,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 +660,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 () { @@ -646,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 0b2e85cd6..a77aed96d 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -30,6 +30,7 @@ import DOMPurify from 'dompurify'; import { MermaidConfig } from './config.type'; import { evaluate } from './diagrams/common/common'; import isEmpty from 'lodash-es/isEmpty'; +import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility'; // diagram names that support classDef statements const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram', 'stateDiagram-v2']; @@ -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; // ---------------------------------------------------------------------------- @@ -128,15 +129,9 @@ export const encodeEntities = function (text: string): string { export const decodeEntities = function (text: string): string { let txt = text; - txt = txt.replace(/fl°°/g, function () { - return '&#'; - }); - txt = txt.replace(/fl°/g, function () { - return '&'; - }); - txt = txt.replace(/¶ß/g, function () { - return ';'; - }); + txt = txt.replace(/fl°°/g, '&#'); + txt = txt.replace(/fl°/g, '&'); + txt = txt.replace(/¶ß/g, ';'); return txt; }; @@ -189,28 +184,26 @@ export const createCssStyles = ( } // classDefs defined in the diagram text - if (!isEmpty(classDefs)) { - if (CLASSDEF_DIAGRAMS.includes(graphType)) { - const htmlLabels = config.htmlLabels || config.flowchart?.htmlLabels; // TODO why specifically check the Flowchart diagram config? + if (!isEmpty(classDefs) && CLASSDEF_DIAGRAMS.includes(graphType)) { + const htmlLabels = config.htmlLabels || config.flowchart?.htmlLabels; // TODO why specifically check the Flowchart diagram config? - const cssHtmlElements = ['> *', 'span']; // TODO make a constant - const cssShapeElements = ['rect', 'polygon', 'ellipse', 'circle', 'path']; // TODO make a constant + const cssHtmlElements = ['> *', 'span']; // TODO make a constant + const cssShapeElements = ['rect', 'polygon', 'ellipse', 'circle', 'path']; // TODO make a constant - const cssElements = htmlLabels ? cssHtmlElements : cssShapeElements; + const cssElements = htmlLabels ? cssHtmlElements : cssShapeElements; - // create the CSS styles needed for each styleClass definition and css element - for (const classId in classDefs) { - const styleClassDef = classDefs[classId]; - // create the css styles for each cssElement and the styles (only if there are styles) - if (!isEmpty(styleClassDef.styles)) { - cssElements.forEach((cssElement) => { - cssStyles += cssImportantStyles(styleClassDef.id, cssElement, styleClassDef.styles); - }); - } - // create the css styles for the tspan element and the text styles (only if there are textStyles) - if (!isEmpty(styleClassDef.textStyles)) { - cssStyles += cssImportantStyles(styleClassDef.id, 'tspan', styleClassDef.textStyles); - } + // create the CSS styles needed for each styleClass definition and css element + for (const classId in classDefs) { + const styleClassDef = classDefs[classId]; + // create the css styles for each cssElement and the styles (only if there are styles) + if (!isEmpty(styleClassDef.styles)) { + cssElements.forEach((cssElement) => { + cssStyles += cssImportantStyles(styleClassDef.id, cssElement, styleClassDef.styles); + }); + } + // create the css styles for the tspan element and the text styles (only if there are textStyles) + if (!isEmpty(styleClassDef.textStyles)) { + cssStyles += cssImportantStyles(styleClassDef.id, 'tspan', styleClassDef.textStyles); } } } @@ -379,7 +372,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 @@ -432,7 +425,7 @@ const render = function ( // Define the root d3 node // In regular execution the svgContainingElement will be the element with a mermaid class - if (typeof svgContainingElement !== 'undefined') { + if (svgContainingElement !== undefined) { if (svgContainingElement) { svgContainingElement.innerHTML = ''; } @@ -487,12 +480,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; @@ -509,8 +503,9 @@ const render = function ( idSelector ); + // svg is a HTML element (not a d3 node) const style1 = document.createElement('style'); - style1.innerHTML = `${idSelector} ` + rules; + style1.innerHTML = rules; svg.insertBefore(style1, firstChild); // ------------------------------------------------------------------------------- @@ -522,6 +517,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); @@ -545,7 +546,7 @@ const render = function ( // ------------------------------------------------------------------------------- // Do any callbacks (cb = callback) - if (typeof cb !== 'undefined') { + if (cb !== undefined) { switch (graphType) { case 'flowchart': case 'flowchart-v2': @@ -631,7 +632,7 @@ const renderAsync = async function ( // Define the root d3 node // In regular execution the svgContainingElement will be the element with a mermaid class - if (typeof svgContainingElement !== 'undefined') { + if (svgContainingElement !== undefined) { if (svgContainingElement) { svgContainingElement.innerHTML = ''; } @@ -706,7 +707,7 @@ const renderAsync = async function ( ); const style1 = document.createElement('style'); - style1.innerHTML = `${idSelector} ` + rules; + style1.innerHTML = rules; svg.insertBefore(style1, firstChild); // ------------------------------------------------------------------------------- @@ -718,6 +719,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); @@ -741,7 +748,7 @@ const renderAsync = async function ( // ------------------------------------------------------------------------------- // Do any callbacks (cb = callback) - if (typeof cb !== 'undefined') { + if (cb !== undefined) { switch (graphType) { case 'flowchart': case 'flowchart-v2': @@ -763,7 +770,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) { @@ -820,7 +827,7 @@ const handleDirective = function (p: any, directive: any, type: string): void { case 'init': case 'initialize': { ['config'].forEach((prop) => { - if (typeof directive.args[prop] !== 'undefined') { + if (directive.args[prop] !== undefined) { if (type === 'flowchart-v2') { type = 'flowchart'; } @@ -859,10 +866,8 @@ const handleDirective = function (p: any, directive: any, type: string): void { */ function initialize(options: MermaidConfig = {}) { // Handle legacy location of font-family configuration - if (options?.fontFamily) { - if (!options.themeVariables?.fontFamily) { - options.themeVariables = { fontFamily: options.fontFamily }; - } + if (options?.fontFamily && !options.themeVariables?.fontFamily) { + options.themeVariables = { fontFamily: options.fontFamily }; } // Set default options @@ -884,6 +889,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/styles.ts b/packages/mermaid/src/styles.ts index 0c4a7e1a5..055f63be5 100644 --- a/packages/mermaid/src/styles.ts +++ b/packages/mermaid/src/styles.ts @@ -53,7 +53,7 @@ const getStyles = ( } else { log.warn(`No theme found for ${type}`); } - return ` { + return ` & { font-family: ${options.fontFamily}; font-size: ${options.fontSize}; fill: ${options.textColor} @@ -61,40 +61,40 @@ const getStyles = ( /* Classes common for multiple diagrams */ - .error-icon { + & .error-icon { fill: ${options.errorBkgColor}; } - .error-text { + & .error-text { fill: ${options.errorTextColor}; stroke: ${options.errorTextColor}; } - .edge-thickness-normal { + & .edge-thickness-normal { stroke-width: 2px; } - .edge-thickness-thick { + & .edge-thickness-thick { stroke-width: 3.5px } - .edge-pattern-solid { + & .edge-pattern-solid { stroke-dasharray: 0; } - .edge-pattern-dashed{ + & .edge-pattern-dashed{ stroke-dasharray: 3; } .edge-pattern-dotted { stroke-dasharray: 2; } - .marker { + & .marker { fill: ${options.lineColor}; stroke: ${options.lineColor}; } - .marker.cross { + & .marker.cross { stroke: ${options.lineColor}; } - svg { + & svg { font-family: ${options.fontFamily}; font-size: ${options.fontSize}; } diff --git a/packages/mermaid/src/tests/MockedD3.ts b/packages/mermaid/src/tests/MockedD3.ts index d7c16b3a8..284b21b08 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; } @@ -23,12 +29,17 @@ export class MockedD3 { select = vi.fn().mockImplementation(({ select_str = '' }): MockedD3 => { // Get the id from an argument string. if it is of the form [id='some-id'], strip off the // surrounding id[..] - const stripSurroundRegexp = /\[id='(.*)'\]/; + const stripSurroundRegexp = /\[id='(.*)']/; const matchedSurrounds = select_str.match(stripSurroundRegexp); const cleanId = matchedSurrounds ? matchedSurrounds[1] : select_str; 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 54262f10e..e983d21c8 100644 --- a/packages/mermaid/src/utils.spec.js +++ b/packages/mermaid/src/utils.spec.js @@ -4,7 +4,8 @@ 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 { 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'); - utils.insertTitle(svg, 'testClass', 0, ''); - expect(svg.__children.length).toBe(0); + 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('should insert title centered', function () { - const svg = MockD3('svg'); + it('does nothing if the title is empty', function () { + const svgAppendSpy = vi.spyOn(svg, 'append'); + utils.insertTitle(svg, 'testClass', 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(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(svgAppendSpy).toHaveBeenCalled(); + expect(titleTextSpy).toHaveBeenCalledWith('test title'); + }); + + 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(titleAttrSpy).toHaveBeenCalledWith('class', 'testClass'); }); }); diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 3689a01a1..767fdaa7d 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -37,10 +37,9 @@ const d3CurveTypes = { curveStepAfter: curveStepAfter, curveStepBefore: curveStepBefore, }; -const directive = - /[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi; +const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; const directiveWithoutOpen = - /\s*(?:(?:(\w+)(?=:):|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi; + /\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; /** * Detects the init config object from the text @@ -91,7 +90,7 @@ export const detectInit = function (text: string, config?: MermaidConfig): Merma if (results) { let type = detectType(text, config); ['config'].forEach((prop) => { - if (typeof results[prop] !== 'undefined') { + if (results[prop] !== undefined) { if (type === 'flowchart-v2') { type = 'flowchart'; } @@ -180,8 +179,8 @@ export const detectDirective = function ( * @returns The array index containing the substring or -1 if not present */ export const isSubstringInArray = function (str: string, arr: string[]): number { - for (let i = 0; i < arr.length; i++) { - if (arr[i].match(str)) { + for (const [i, element] of arr.entries()) { + if (element.match(str)) { return i; } } @@ -460,13 +459,13 @@ export function getStylesFromArray(arr: string[]): { style: string; labelStyle: let style = ''; let labelStyle = ''; - for (let i = 0; i < arr.length; i++) { - if (typeof arr[i] !== 'undefined') { + for (const element of arr) { + if (element !== undefined) { // add text properties to label style definition - if (arr[i].startsWith('color:') || arr[i].startsWith('text-align:')) { - labelStyle = labelStyle + arr[i] + ';'; + if (element.startsWith('color:') || element.startsWith('text-align:')) { + labelStyle = labelStyle + element + ';'; } else { - style = style + arr[i] + ';'; + style = style + element + ';'; } } } @@ -549,7 +548,7 @@ export const drawSimpleText = function ( textElem.style('font-size', textData.fontSize); textElem.style('font-weight', textData.fontWeight); textElem.attr('fill', textData.fill); - if (typeof textData.class !== 'undefined') { + if (textData.class !== undefined) { textElem.attr('class', textData.class); } @@ -630,7 +629,7 @@ const breakString: ( { fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0 }, config ); - const characters = word.split(''); + const characters = [...word]; const lines: string[] = []; let currentLine = ''; characters.forEach((character, index) => { @@ -822,34 +821,34 @@ export const directiveSanitizer = (args: any) => { // This is an object Object.keys(args).forEach((key) => { log.debug('Checking key', key); - if (key.indexOf('__') === 0) { + if (key.startsWith('__')) { log.debug('sanitize deleting __ option', key); delete args[key]; } - if (key.indexOf('proto') >= 0) { + if (key.includes('proto')) { log.debug('sanitize deleting proto option', key); delete args[key]; } - if (key.indexOf('constr') >= 0) { + if (key.includes('constr')) { log.debug('sanitize deleting constr option', key); delete args[key]; } - if (key.indexOf('themeCSS') >= 0) { + if (key.includes('themeCSS')) { log.debug('sanitizing themeCss option'); args[key] = sanitizeCss(args[key]); } - if (key.indexOf('fontFamily') >= 0) { + if (key.includes('fontFamily')) { log.debug('sanitizing fontFamily option'); args[key] = sanitizeCss(args[key]); } - if (key.indexOf('altFontFamily') >= 0) { + if (key.includes('altFontFamily')) { log.debug('sanitizing altFontFamily option'); args[key] = sanitizeCss(args[key]); } - if (configKeys.indexOf(key) < 0) { + if (!configKeys.includes(key)) { log.debug('sanitize deleting option', key); delete args[key]; } else { @@ -863,10 +862,9 @@ export const directiveSanitizer = (args: any) => { } if (args.themeVariables) { const kArr = Object.keys(args.themeVariables); - for (let i = 0; i < kArr.length; i++) { - const k = kArr[i]; + for (const k of kArr) { const val = args.themeVariables[k]; - if (val && val.match && !val.match(/^[a-zA-Z0-9#,";()%. ]+$/)) { + if (val && val.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) { args.themeVariables[k] = ''; } } @@ -877,13 +875,13 @@ export const sanitizeCss = (str) => { let startCnt = 0; let endCnt = 0; - for (let i = 0; i < str.length; i++) { + for (const element of str) { if (startCnt < endCnt) { return '{ /* ERROR: Unbalanced CSS */ }'; } - if (str[i] === '{') { + if (element === '{') { startCnt++; - } else if (str[i] === '}') { + } else if (element === '}') { endCnt++; } } @@ -915,7 +913,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 2ba8f0776..270da02e7 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -51,7 +51,7 @@ importers: version: 5.42.1_rmayb2veg2btbq6mbmnyivgasy '@vitest/coverage-c8': specifier: ^0.25.1 - version: 0.25.1_iyb77cyw3lw7duusvxyjdsflhu + version: 0.25.1_oullksb5ic6y72oh2wekoaiuii '@vitest/ui': specifier: ^0.25.1 version: 0.25.1 @@ -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.4 eslint: specifier: ^8.27.0 version: 8.27.0 @@ -115,9 +115,6 @@ importers: husky: specifier: ^8.0.2 version: 8.0.2 - identity-obj-proxy: - specifier: ^3.0.0 - version: 3.0.0 jest: specifier: ^29.3.1 version: 29.3.1_odkjkoia5xunhxkdrka32ib6vi @@ -150,7 +147,7 @@ importers: version: 3.0.2 rollup-plugin-visualizer: specifier: ^5.8.3 - version: 5.8.3_rollup@2.79.1 + version: 5.8.3 start-server-and-test: specifier: ^1.14.0 version: 1.14.0 @@ -163,18 +160,9 @@ importers: vite: specifier: ^3.2.3 version: 3.2.3_@types+node@18.11.9 - vitepress: - specifier: ^1.0.0-alpha.28 - version: 1.0.0-alpha.28_ysryt2e75uhznkanan6iyjk4mi - vitepress-plugin-mermaid: - specifier: ^2.0.8 - version: 2.0.8_2q5vfj2vm6nj3r62ddjdsi7aoe - vitepress-plugin-search: - specifier: ^1.0.4-alpha.15 - version: 1.0.4-alpha.15_s3edpouswd4dgoi2en7bdlrp54 vitest: - specifier: ^0.25.1 - version: 0.25.1_iyb77cyw3lw7duusvxyjdsflhu + specifier: ^0.25.3 + version: 0.25.3_oullksb5ic6y72oh2wekoaiuii packages/mermaid: dependencies: @@ -190,12 +178,6 @@ importers: dompurify: specifier: 2.4.1 version: 2.4.1 - fast-clone: - specifier: ^1.5.13 - version: 1.5.13 - graphlib: - specifier: ^2.1.8 - version: 2.1.8 khroma: specifier: ^2.0.0 version: 2.0.0 @@ -260,9 +242,6 @@ importers: globby: specifier: ^13.1.2 version: 13.1.2 - identity-obj-proxy: - specifier: ^3.0.0 - version: 3.0.0 jison: specifier: ^0.4.18 version: 0.4.18 @@ -305,6 +284,12 @@ importers: unist-util-flatmap: specifier: ^1.0.0 version: 1.0.0 + vitepress: + specifier: ^1.0.0-alpha.28 + version: 1.0.0-alpha.28_tbpndr44ulefs3hehwpi2mkf2y + vitepress-plugin-search: + specifier: ^1.0.4-alpha.15 + version: 1.0.4-alpha.15_s3edpouswd4dgoi2en7bdlrp54 packages/mermaid-example-diagram: devDependencies: @@ -1758,6 +1743,96 @@ packages: dev: true optional: true + /@esbuild/android-arm/0.16.4: + resolution: {integrity: sha512-rZzb7r22m20S1S7ufIc6DC6W659yxoOrl7sKP1nCYhuvUlnCFHVSbATG4keGUtV8rDz11sRRDbWkvQZpzPaHiw==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64/0.16.4: + resolution: {integrity: sha512-VPuTzXFm/m2fcGfN6CiwZTlLzxrKsWbPkG7ArRFpuxyaHUm/XFHQPD4xNwZT6uUmpIHhnSjcaCmcla8COzmZ5Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64/0.16.4: + resolution: {integrity: sha512-MW+B2O++BkcOfMWmuHXB15/l1i7wXhJFqbJhp82IBOais8RBEQv2vQz/jHrDEHaY2X0QY7Wfw86SBL2PbVOr0g==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64/0.16.4: + resolution: {integrity: sha512-a28X1O//aOfxwJVZVs7ZfM8Tyih2Za4nKJrBwW5Wm4yKsnwBy9aiS/xwpxiiTRttw3EaTg4Srerhcm6z0bu9Wg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64/0.16.4: + resolution: {integrity: sha512-e3doCr6Ecfwd7VzlaQqEPrnbvvPjE9uoTpxG5pyLzr2rI2NMjDHmvY1E5EO81O/e9TUOLLkXA5m6T8lfjK9yAA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64/0.16.4: + resolution: {integrity: sha512-Oup3G/QxBgvvqnXWrBed7xxkFNwAwJVHZcklWyQt7YCAL5bfUkaa6FVWnR78rNQiM8MqqLiT6ZTZSdUFuVIg1w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64/0.16.4: + resolution: {integrity: sha512-vAP+eYOxlN/Bpo/TZmzEQapNS8W1njECrqkTpNgvXskkkJC2AwOXwZWai/Kc2vEFZUXQttx6UJbj9grqjD/+9Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm/0.16.4: + resolution: {integrity: sha512-A47ZmtpIPyERxkSvIv+zLd6kNIOtJH03XA0Hy7jaceRDdQaQVGSDt4mZqpWqJYgDk9rg96aglbF6kCRvPGDSUA==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64/0.16.4: + resolution: {integrity: sha512-2zXoBhv4r5pZiyjBKrOdFP4CXOChxXiYD50LRUU+65DkdS5niPFHbboKZd/c81l0ezpw7AQnHeoCy5hFrzzs4g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32/0.16.4: + resolution: {integrity: sha512-uxdSrpe9wFhz4yBwt2kl2TxS/NWEINYBUFIxQtaEVtglm1eECvsj1vEKI0KX2k2wCe17zDdQ3v+jVxfwVfvvjw==} + 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'} @@ -1767,6 +1842,114 @@ packages: dev: true optional: true + /@esbuild/linux-loong64/0.16.4: + resolution: {integrity: sha512-peDrrUuxbZ9Jw+DwLCh/9xmZAk0p0K1iY5d2IcwmnN+B87xw7kujOkig6ZRcZqgrXgeRGurRHn0ENMAjjD5DEg==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el/0.16.4: + resolution: {integrity: sha512-sD9EEUoGtVhFjjsauWjflZklTNr57KdQ6xfloO4yH1u7vNQlOfAlhEzbyBKfgbJlW7rwXYBdl5/NcZ+Mg2XhQA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64/0.16.4: + resolution: {integrity: sha512-X1HSqHUX9D+d0l6/nIh4ZZJ94eQky8d8z6yxAptpZE3FxCWYWvTDd9X9ST84MGZEJx04VYUD/AGgciddwO0b8g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64/0.16.4: + resolution: {integrity: sha512-97ANpzyNp0GTXCt6SRdIx1ngwncpkV/z453ZuxbnBROCJ5p/55UjhbaG23UdHj88fGWLKPFtMoU4CBacz4j9FA==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x/0.16.4: + resolution: {integrity: sha512-pUvPQLPmbEeJRPjP0DYTC1vjHyhrnCklQmCGYbipkep+oyfTn7GTBJXoPodR7ZS5upmEyc8lzAkn2o29wD786A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64/0.16.4: + resolution: {integrity: sha512-N55Q0mJs3Sl8+utPRPBrL6NLYZKBCLLx0bme/+RbjvMforTGGzFvsRl4xLTZMUBFC1poDzBEPTEu5nxizQ9Nlw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64/0.16.4: + resolution: {integrity: sha512-LHSJLit8jCObEQNYkgsDYBh2JrJT53oJO2HVdkSYLa6+zuLJh0lAr06brXIkljrlI+N7NNW1IAXGn/6IZPi3YQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64/0.16.4: + resolution: {integrity: sha512-nLgdc6tWEhcCFg/WVFaUxHcPK3AP/bh+KEwKtl69Ay5IBqUwKDaq/6Xk0E+fh/FGjnLwqFSsarsbPHeKM8t8Sw==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64/0.16.4: + resolution: {integrity: sha512-08SluG24GjPO3tXKk95/85n9kpyZtXCVwURR2i4myhrOfi3jspClV0xQQ0W0PYWHioJj+LejFMt41q+PG3mlAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64/0.16.4: + resolution: {integrity: sha512-yYiRDQcqLYQSvNQcBKN7XogbrSvBE45FEQdH8fuXPl7cngzkCvpsG2H9Uey39IjQ6gqqc+Q4VXYHsQcKW0OMjQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32/0.16.4: + resolution: {integrity: sha512-5rabnGIqexekYkh9zXG5waotq8mrdlRoBqAktjx2W3kb0zsI83mdCwrcAeKYirnUaTGztR5TxXcXmQrEzny83w==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64/0.16.4: + resolution: {integrity: sha512-sN/I8FMPtmtT2Yw+Dly8Ur5vQ5a/RmC8hW7jO9PtPSQUPkowxWpcUZnqOggU7VwyT3Xkj6vcXWd3V/qTXwultQ==} + 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} @@ -2129,14 +2312,6 @@ packages: resolution: {integrity: sha512-a5Sab1C4/icpTZVzZc5Ghpz88yQtGOyNqYXcZgOssB2uuAr+wF/MvN6bgtW32q7HHrvBki+BsZ0OuNv6EV3K9g==} dev: true - /@rollup/pluginutils/4.2.1: - resolution: {integrity: sha512-iKnFXr7NkdZAIHiIWE+BX5ULi/ucVFYWD6TbAV+rZctiRTY2PL6tsIKhoIOaoskiWAkgu+VsbXgUVDNLHf+InQ==} - engines: {node: '>= 8.0.0'} - dependencies: - estree-walker: 2.0.2 - picomatch: 2.3.1 - dev: true - /@sideway/address/4.1.4: resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} dependencies: @@ -2270,12 +2445,6 @@ packages: resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==} dev: true - /@types/concat-stream/1.6.1: - resolution: {integrity: sha512-eHE4cQPoj6ngxBZMvVf6Hw7Mh4jMW4U9lpGmS5GBPB9RYxlFg+CHaVN7ErNY4W9XfLIEn20b4VDYaIrbq0q4uA==} - dependencies: - '@types/node': 18.11.9 - dev: true - /@types/connect-history-api-fallback/1.3.5: resolution: {integrity: sha512-h8QJa8xSb1WD4fpKBDcATDNGXghFj6/3GRWG6dhmRcu0RX1Ubasur2Uvx5aeEwlf0MwblEC2bMzzMQntxnw/Cw==} dependencies: @@ -2523,12 +2692,6 @@ packages: resolution: {integrity: sha512-HXwADeHEP4exXkCIwy2n1+i0f1ilP1ETQOH5KDOugjkTFZPntWo0Gr8stZOaebkxsdx+k0X/K6obU/+it07ocg==} dev: true - /@types/form-data/0.0.33: - resolution: {integrity: sha512-8BSvG1kGm83cyJITQMZSulnl6QV8jqAGreJsc5tPu1Jq0vTSOiY/k24Wx82JRpWwZSqrala6sd5rWi6aNXvqcw==} - dependencies: - '@types/node': 18.11.9 - dev: true - /@types/geojson/7946.0.10: resolution: {integrity: sha512-Nmh0K3iWQJzniTuPRcJn5hxXkfB1T1pgB89SBig5PlJQU5yocazeu4jATJlaA0GYFKWMqDdvYemoSnF2pXgLVA==} dev: true @@ -2643,10 +2806,6 @@ packages: form-data: 3.0.1 dev: true - /@types/node/10.17.60: - resolution: {integrity: sha512-F0KIgDJfy2nA3zMLmWGKxcH2ZVEtCZXHHdOQs2gSaQ27+lNeEfGxzkIw90aXswATX7AZ33tahPbzy6KAfUreVw==} - dev: true - /@types/node/14.18.29: resolution: {integrity: sha512-LhF+9fbIX4iPzhsRLpK5H7iPdvW8L4IwGciXQIOEcuF62+9nw/VQVsOViAOOGxY3OlOKGLFv0sWwJXdwQeTn6A==} dev: true @@ -2659,10 +2818,6 @@ packages: resolution: {integrity: sha512-CRpX21/kGdzjOpFsZSkcrXMGIBWMGNIHXXBVFSH+ggkftxg+XYP20TESbh+zFvFj3EQOl5byk0HTRn1IL6hbqg==} dev: true - /@types/node/8.10.66: - resolution: {integrity: sha512-tktOkFUA4kXx2hhhrB8bIFb5TbwzS4uOhKEmwiD+NoiL0qtP2OQ9mFldbgD4dV1djrlBYP6eBuQZiWjuHUpqFw==} - dev: true - /@types/normalize-package-data/2.4.1: resolution: {integrity: sha512-Gj7cI7z+98M282Tqmp2K5EIsoouUEzbBJhQQzDE3jSIRk6r9gsz0oUokqIUR4u1R3dMHo0pDHM7sNOHyhulypw==} dev: true @@ -2919,15 +3074,15 @@ packages: vite: ^3.0.0 vue: ^3.2.25 dependencies: - vite: 3.2.3_@types+node@18.11.9 + vite: 3.2.3 vue: 3.2.41 dev: true - /@vitest/coverage-c8/0.25.1_iyb77cyw3lw7duusvxyjdsflhu: + /@vitest/coverage-c8/0.25.1_oullksb5ic6y72oh2wekoaiuii: resolution: {integrity: sha512-gpl5QNaNeIN0mfRiosCqBFoZcizb5GA458TDnOQXkGDc4kklazxn70u9evGfV62wiiAUfGGebgRhxlBkAa6m6g==} dependencies: c8: 7.12.0 - vitest: 0.25.1_iyb77cyw3lw7duusvxyjdsflhu + vitest: 0.25.1_oullksb5ic6y72oh2wekoaiuii transitivePeerDependencies: - '@edge-runtime/vm' - '@vitest/browser' @@ -3249,55 +3404,6 @@ packages: resolution: {integrity: sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==} dev: true - /@yankeeinlondon/builder-api/0.4.1_dsigm6qpqe3mljd7p5w42sooza: - resolution: {integrity: sha512-O6LS9Zg4xqLVpAgea72mNhZvdy9B2BuIgNdsRvNkmnACG8XvlZtEKryGt2ECI/z+dbQICbHDQFCNtZRBrfSMlA==} - peerDependencies: - fp-ts: ^2.12.1 - inferred-types: ^0.22.0 - markdown-it: ^13.0.1 - vite-plugin-md: '*' - dependencies: - '@yankeeinlondon/happy-wrapper': 2.6.0_iyb77cyw3lw7duusvxyjdsflhu - fp-ts: 2.13.1 - inferred-types: 0.22.8_iyb77cyw3lw7duusvxyjdsflhu - markdown-it: 13.0.1 - vite-plugin-md: 0.20.4_ssclijsxphu2vue5hnv6ywl23u - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@vitest/browser' - - '@vitest/ui' - - c8 - - happy-dom - - jsdom - - less - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true - - /@yankeeinlondon/happy-wrapper/2.6.0_iyb77cyw3lw7duusvxyjdsflhu: - resolution: {integrity: sha512-az+gEjG4Jl4GbM35ID5pn4v7FwfrgeA1br/B9STXlDLvIsV8q7mCxQ1oYa8bR1iHtNQg7kgW6s9DYheaTemrHQ==} - peerDependencies: - happy-dom: ^6.0.4 - dependencies: - happy-dom: 6.0.4 - native-dash: 1.23.2_iyb77cyw3lw7duusvxyjdsflhu - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@vitest/browser' - - '@vitest/ui' - - c8 - - jsdom - - less - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true - /JSONSelect/0.4.0: resolution: {integrity: sha512-VRLR3Su35MH+XV2lrvh9O7qWoug/TUyj9tLDjn9rtpUCNnILLrHjgd/tB0KrhugCxUpj3UqoLqfYb3fLJdIQQQ==} engines: {node: '>=0.4.7'} @@ -3595,10 +3701,6 @@ packages: engines: {node: '>=0.10.0'} dev: true - /asap/2.0.6: - resolution: {integrity: sha512-BSHWgDSAiKs50o2Re8ppvp3seVHXSRM44cdSsT9FfNEUUZLOGWVCsiWaRPWM1Znn+mqZ1OfVZ3z3DWEzSp7hRA==} - dev: true - /asn1/0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} dependencies: @@ -3819,29 +3921,6 @@ packages: fill-range: 7.0.1 dev: true - /brilliant-errors/0.6.0_iyb77cyw3lw7duusvxyjdsflhu: - resolution: {integrity: sha512-4+Va/hdXk7tROAmnZ8Vp9D23oOMg6IBJAiZdhRCufMApH0NIFLsvtTb7sL8YuV6gWdLsiXxzR834bh05lC8r8Q==} - engines: {node: '>=12.0.0'} - dependencies: - callsites: 3.1.0 - common-types: 1.31.1 - inferred-types: 0.22.8_iyb77cyw3lw7duusvxyjdsflhu - vitest: 0.19.1_iyb77cyw3lw7duusvxyjdsflhu - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@vitest/browser' - - '@vitest/ui' - - c8 - - happy-dom - - jsdom - - less - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true - /browser-process-hrtime/1.0.0: resolution: {integrity: sha512-9o5UecI3GhkpM6DrXr69PblIuWxPKk9Y0jHBRhdocZ2y7YECBFCsHm79Pr3OyR2AvjhDkabFJaDJMYRazHgsow==} dev: true @@ -4254,10 +4333,6 @@ packages: engines: {node: '>=4.0.0'} dev: true - /common-types/1.31.1: - resolution: {integrity: sha512-eixAd22Gmek1dgsPgyqCSjzMAlp8rpSLkb44iEMfOzR9fwGFYEkH+AWOHmwSFxWmO8MvMND/m1jpZX0Wk4+yJA==} - dev: true - /compare-func/2.0.0: resolution: {integrity: sha512-zHig5N+tPWARooBnb0Zx1MFcdfpyJrfTJ3Y5L+IFvUm8rM74hHz66z0gw0x4tijh5CorKkKUCnW82R2vmpeCRA==} dependencies: @@ -4291,16 +4366,6 @@ packages: resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} dev: true - /concat-stream/1.6.2: - resolution: {integrity: sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==} - engines: {'0': node >= 0.8} - dependencies: - buffer-from: 1.1.2 - inherits: 2.0.4 - readable-stream: 2.3.7 - typedarray: 0.0.6 - dev: true - /concurrently/7.5.0: resolution: {integrity: sha512-5E3mwiS+i2JYBzr5BpXkFxOnleZTMsG+WnE/dCG4/P+oiVXrbmrBwJ2ozn4SxwB2EZDrKR568X+puVohxz3/Mg==} engines: {node: ^12.20.0 || ^14.13.0 || >=16.0.0} @@ -4688,10 +4753,6 @@ packages: source-map: 0.6.1 dev: true - /css.escape/1.5.1: - resolution: {integrity: sha512-YUifsXXuknHlUsmlgyY0PKzgPOr7/FjCePfHNt0jxm83wHZi44VDMQ7/fGNkjY3/jV1MC+1CmZbaHzugyeRtpg==} - dev: true - /cssom/0.3.8: resolution: {integrity: sha512-b0tGHbfegbhPJpxpiBPU2sCkigAqtM9O121le6bbOlgyV+NyGyCmVfJ6QW9eRjz8CpNfWEOYBIMIGRYkLwsIYg==} dev: true @@ -5658,6 +5719,36 @@ packages: esbuild-windows-arm64: 0.15.13 dev: true + /esbuild/0.16.4: + resolution: {integrity: sha512-qQrPMQpPTWf8jHugLWHoGqZjApyx3OEm76dlTXobHwh/EBbavbRdjXdYi/GWr43GyN0sfpap14GPkb05NH3ROA==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.16.4 + '@esbuild/android-arm64': 0.16.4 + '@esbuild/android-x64': 0.16.4 + '@esbuild/darwin-arm64': 0.16.4 + '@esbuild/darwin-x64': 0.16.4 + '@esbuild/freebsd-arm64': 0.16.4 + '@esbuild/freebsd-x64': 0.16.4 + '@esbuild/linux-arm': 0.16.4 + '@esbuild/linux-arm64': 0.16.4 + '@esbuild/linux-ia32': 0.16.4 + '@esbuild/linux-loong64': 0.16.4 + '@esbuild/linux-mips64el': 0.16.4 + '@esbuild/linux-ppc64': 0.16.4 + '@esbuild/linux-riscv64': 0.16.4 + '@esbuild/linux-s390x': 0.16.4 + '@esbuild/linux-x64': 0.16.4 + '@esbuild/netbsd-x64': 0.16.4 + '@esbuild/openbsd-x64': 0.16.4 + '@esbuild/sunos-x64': 0.16.4 + '@esbuild/win32-arm64': 0.16.4 + '@esbuild/win32-ia32': 0.16.4 + '@esbuild/win32-x64': 0.16.4 + dev: true + /escalade/3.1.1: resolution: {integrity: sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==} engines: {node: '>=6'} @@ -6154,13 +6245,6 @@ packages: - supports-color dev: true - /extend-shallow/2.0.1: - resolution: {integrity: sha512-zCnTtlxNoAiDc3gqY2aYAWFx7XWWiasuF2K8Me5WbN8otHKTUKBwjPtNpRs/rbUZm7KxWAaNj7P1a/p52GbVug==} - engines: {node: '>=0.10.0'} - dependencies: - is-extendable: 0.1.1 - dev: true - /extend/3.0.2: resolution: {integrity: sha512-fjquC59cD7CyW6urNXK0FBufkZcoiGG80wTuPujX590cB5Ttln20E2UB4S/WARVqhXffZl2LNgS+gQdPIIim/g==} dev: true @@ -6184,10 +6268,6 @@ packages: engines: {'0': node >=0.6.0} dev: true - /fast-clone/1.5.13: - resolution: {integrity: sha512-0ez7coyFBQFjZtId+RJqJ+EQs61w9xARfqjqK0AD9vIUkSxWD4HvPt80+5evebZ1tTnv1GYKrPTipx7kOW5ipA==} - dev: false - /fast-deep-equal/3.1.3: resolution: {integrity: sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==} dev: true @@ -6379,10 +6459,6 @@ packages: engines: {node: '>= 0.6'} dev: true - /fp-ts/2.13.1: - resolution: {integrity: sha512-0eu5ULPS2c/jsa1lGFneEFFEdTbembJv8e4QKXeVJ3lm/5hyve06dlKZrpxmMwJt6rYen7sxmHHK2CLaXvWuWQ==} - dev: true - /fresh/0.5.2: resolution: {integrity: sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==} engines: {node: '>= 0.6'} @@ -6489,11 +6565,6 @@ packages: engines: {node: '>=8.0.0'} dev: true - /get-port/3.2.0: - resolution: {integrity: sha512-x5UJKlgeUiNT8nyo/AcnwLnZuZNcSjSw0kogRB+Whd1fjjFq4B1hySFxSFWWSn4mIBzg3sRNUDFYc4g5gjPoLg==} - engines: {node: '>=4'} - dev: true - /get-stdin/5.0.1: resolution: {integrity: sha512-jZV7n6jGE3Gt7fgSTJoz91Ak5MuTLwMwkoYdjxuJ/AmjIsE1UC03y/IWkZCQGEvVNS9qoRNwy5BCqxImv0FVeA==} engines: {node: '>=0.12.0'} @@ -6679,22 +6750,6 @@ packages: resolution: {integrity: sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==} dev: true - /graphlib/2.1.8: - resolution: {integrity: sha512-jcLLfkpoVGmH7/InMC/1hIvOPSUh38oJtGhvrOFGzioE1DZ+0YW16RgmOJhHiuWTvGiJQ9Z1Ik43JvkRPRvE+A==} - dependencies: - lodash: 4.17.21 - dev: false - - /gray-matter/4.0.3: - resolution: {integrity: sha512-5v6yZd4JK3eMI3FqqCouswVqwugaA9r4dNZB1wwcmrD02QkV5H0y7XBQW8QwQqEaZY1pM9aqORSORhJRdNK44Q==} - engines: {node: '>=6.0'} - dependencies: - js-yaml: 3.14.1 - kind-of: 6.0.3 - section-matter: 1.0.0 - strip-bom-string: 1.0.0 - dev: true - /handle-thing/2.0.1: resolution: {integrity: sha512-9Qn4yBxelxoh2Ow62nP+Ka/kMnOXRi8BXnRaUwezLNhqelnN49xKz4F/dPP8OYLxLxq6JDtZb2i9XznUQbNPTg==} dev: true @@ -6712,20 +6767,6 @@ packages: uglify-js: 3.17.3 dev: true - /happy-dom/6.0.4: - resolution: {integrity: sha512-b+ID23Ms0BY08UNLymsOMG7EI2jSlwEt4cbJs938GZfeNAg+fqgkSO3TokQMgSOFoHznpjWmpVjBUL5boJ9PWw==} - dependencies: - css.escape: 1.5.1 - he: 1.2.0 - node-fetch: 2.6.7 - sync-request: 6.1.0 - webidl-conversions: 7.0.0 - whatwg-encoding: 2.0.0 - whatwg-mimetype: 3.0.0 - transitivePeerDependencies: - - encoding - dev: true - /har-schema/2.0.0: resolution: {integrity: sha512-Oqluz6zhGX8cyRaTQlFMPw80bSJVG2x/cFb8ZPhUILGgHka9SsokCCOQgpveePerqidZOrT14ipqfJb7ILcW5Q==} engines: {node: '>=4'} @@ -6745,10 +6786,6 @@ packages: engines: {node: '>=6'} dev: true - /harmony-reflect/1.6.2: - resolution: {integrity: sha512-HIp/n38R9kQjDEziXyDTuW3vvoxxyxjxFzXLrBr18uB47GnSt+G9D29fqrpM5ZkspMcPICud3XsBJQ4Y2URg8g==} - dev: true - /has-ansi/2.0.0: resolution: {integrity: sha512-C8vBJ8DwUCx19vhm7urhTuUsr4/IyP6l4VzNQDv+ryHQObW3TTTp9yB68WpYgRe2bbaGuZ/se74IqFeVnMnLZg==} engines: {node: '>=0.10.0'} @@ -6783,11 +6820,6 @@ packages: function-bind: 1.1.1 dev: true - /he/1.2.0: - resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} - hasBin: true - dev: true - /heap/0.2.7: resolution: {integrity: sha512-2bsegYkkHO+h/9MGbn6KWcE45cHZgPANo5LXF7EvWdT0yT2EguSVO1nDgU5c8+ZOPwp2vMNa7YFsJhVcDR9Sdg==} dev: false @@ -6836,16 +6868,6 @@ packages: entities: 4.4.0 dev: true - /http-basic/8.1.3: - resolution: {integrity: sha512-/EcDMwJZh3mABI2NhGfHOGOeOZITqfkEO4p/xK+l3NpyncIHUQBoMvCSF/b5GqvKtySC2srL/GGG3+EtlqlmCw==} - engines: {node: '>=6.0.0'} - dependencies: - caseless: 0.12.0 - concat-stream: 1.6.2 - http-response-object: 3.0.2 - parse-cache-control: 1.0.1 - dev: true - /http-cache-semantics/4.1.0: resolution: {integrity: sha512-carPklcUh7ROWRK7Cv27RPtdhYhUsela/ue5/jKzjegVvXDqM2ILE9Q2BGn9JZJh1g87cp56su/FgQSzcWS8cQ==} dev: true @@ -6931,12 +6953,6 @@ packages: - debug dev: true - /http-response-object/3.0.2: - resolution: {integrity: sha512-bqX0XTF6fnXSQcEJ2Iuyr75yVakyjIDCqroJQ/aHfSdlM743Cwqoi2nDYMzLGWUcuTWGWy8AAvOKXTfiv6q9RA==} - dependencies: - '@types/node': 10.17.60 - dev: true - /http-signature/1.2.0: resolution: {integrity: sha512-CAbnr6Rz4CYQkLYUtSNXxQPUH2gK8f3iWexVlsnMeD+GjlsQ0Xsy1cOX+mN3dtxYomRy21CiOzU8Uhw6OwncEQ==} engines: {node: '>=0.8', npm: '>=1.3.7'} @@ -7007,13 +7023,6 @@ packages: dependencies: safer-buffer: 2.1.2 - /identity-obj-proxy/3.0.0: - resolution: {integrity: sha512-00n6YnVHKrinT9t0d9+5yZC6UBNJANpYEQvL2LlX6Ab9lnmxzIRcEmTPuyGScvl1+jKuCICX1Z0Ab1pPKKdikA==} - engines: {node: '>=4'} - dependencies: - harmony-reflect: 1.6.2 - dev: true - /ieee754/1.2.1: resolution: {integrity: sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==} dev: true @@ -7050,25 +7059,6 @@ packages: engines: {node: '>=8'} dev: true - /inferred-types/0.22.8_iyb77cyw3lw7duusvxyjdsflhu: - resolution: {integrity: sha512-gs0zTE04eOBso5tFZPA2UoYiH9qMCqCJCUdH9K6P6cofqNkI1L5bx9QDE0XE0khJgLN7TmH+W0JhwBbnkdjzWQ==} - dependencies: - brilliant-errors: 0.6.0_iyb77cyw3lw7duusvxyjdsflhu - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@vitest/browser' - - '@vitest/ui' - - c8 - - happy-dom - - jsdom - - less - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true - /inflight/1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -7178,11 +7168,6 @@ packages: hasBin: true dev: true - /is-extendable/0.1.1: - resolution: {integrity: sha512-5BMULNob1vgFX6EjQw5izWDxrecWK9AM72rugNr0TFldMOi0fj6Jk+zeKIt0xGj4cEfQIJth4w3OKWOJ4f+AFw==} - engines: {node: '>=0.10.0'} - dev: true - /is-extglob/2.1.1: resolution: {integrity: sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==} engines: {node: '>=0.10.0'} @@ -8773,26 +8758,6 @@ packages: hasBin: true dev: true - /native-dash/1.23.2_iyb77cyw3lw7duusvxyjdsflhu: - resolution: {integrity: sha512-Ev5OPB5vDZ+HLj4MXfAwZRHJV/LJr2LHjsIr1UN7jZigMS2JRpF7Qy77t66GURhtzp7GSWLNSLeRwXOg1iwJkQ==} - dependencies: - brilliant-errors: 0.6.0_iyb77cyw3lw7duusvxyjdsflhu - inferred-types: 0.22.8_iyb77cyw3lw7duusvxyjdsflhu - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@vitest/browser' - - '@vitest/ui' - - c8 - - happy-dom - - jsdom - - less - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true - /natural-compare-lite/1.4.0: resolution: {integrity: sha512-Tj+HTDSJJKaZnfiuw+iaF9skdPpTo2GtEly5JHnWV/hfv2Qj/9RKsGISQtLh2ox3l5EAGw487hnBee0sIJ6v2g==} dev: true @@ -9105,10 +9070,6 @@ packages: callsites: 3.1.0 dev: true - /parse-cache-control/1.0.1: - resolution: {integrity: sha512-60zvsJReQPX5/QP0Kzfd/VrpjScIQ7SHBW6bFCYfEP+fp0Eppr1SHhIO5nd1PjZtvclzSzES9D/p5nFJurwfWg==} - dev: true - /parse-entities/2.0.0: resolution: {integrity: sha512-kkywGpCcRYhqQIchaWqZ875wzpS/bMKhz5HnN3p7wveJTkTtyAB/AlnS0f8DFSqYW1T82t6yEAkEcB+A1I3MbQ==} dependencies: @@ -9354,12 +9315,6 @@ packages: resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} dev: true - /promise/8.3.0: - resolution: {integrity: sha512-rZPNPKTOYVNEEKFaq1HqTgOwZD+4/YHS5ukLzQCypkj+OkYx7iv0mA91lJlpPPZ8vMau3IIGj5Qlwrx+8iiSmg==} - dependencies: - asap: 2.0.6 - dev: true - /prompts/2.4.2: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} @@ -9748,7 +9703,7 @@ packages: resolution: {integrity: sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==} dev: false - /rollup-plugin-visualizer/5.8.3_rollup@2.79.1: + /rollup-plugin-visualizer/5.8.3: resolution: {integrity: sha512-QGJk4Bqe4AOat5AjipOh8esZH1nck5X2KFpf4VytUdSUuuuSwvIQZjMGgjcxe/zXexltqaXp5Vx1V3LmnQH15Q==} engines: {node: '>=14'} hasBin: true @@ -9759,7 +9714,6 @@ packages: optional: true dependencies: open: 8.4.0 - rollup: 2.79.1 source-map: 0.7.4 yargs: 17.5.1 dev: true @@ -9845,14 +9799,6 @@ packages: ajv-keywords: 5.1.0_ajv@8.11.0 dev: true - /section-matter/1.0.0: - resolution: {integrity: sha512-vfD3pmTzGpufjScBh50YHKzEu2lxBWhVEHsNGoEXmCmn2hKGfeNLYMzCJpe8cD7gqX7TJluOVpBkAequ6dgMmA==} - engines: {node: '>=4'} - dependencies: - extend-shallow: 2.0.1 - kind-of: 6.0.3 - dev: true - /select-hose/2.0.0: resolution: {integrity: sha512-mEugaLK+YfkijB4fx0e6kImuJdCIt2LxCRcbEYPqRGCs4F2ogyfZU5IAZRdjCP8JPq2AtdNoC/Dux63d9Kiryg==} dev: true @@ -10330,11 +10276,6 @@ packages: ansi-regex: 6.0.1 dev: true - /strip-bom-string/1.0.0: - resolution: {integrity: sha512-uCC2VHvQRYu+lMh4My/sFNmF2klFymLX1wHJeXnbEJERpV/ZsVuonzerjfrGpIGF7LBVa1O7i9kjiWvJiFck8g==} - engines: {node: '>=0.10.0'} - dev: true - /strip-bom/4.0.0: resolution: {integrity: sha512-3xurFv5tEgii33Zi8Jtp55wEIILR9eh34FAW00PZf+JnSsTmV/ioewSgQl97JHvgjoRGwPShsWm+IdrxB35d0w==} engines: {node: '>=8'} @@ -10412,21 +10353,6 @@ packages: resolution: {integrity: sha512-9QNk5KwDF+Bvz+PyObkmSYjI5ksVUYtjW7AU22r2NKcfLJcXp96hkDWU3+XndOsUb+AQ9QhfzfCT2O+CNWT5Tw==} dev: true - /sync-request/6.1.0: - resolution: {integrity: sha512-8fjNkrNlNCrVc/av+Jn+xxqfCjYaBoHqCsDz6mt030UMxJGr+GSfCV1dQt2gRtlL63+VPidwDVLr7V2OcTSdRw==} - engines: {node: '>=8.0.0'} - dependencies: - http-response-object: 3.0.2 - sync-rpc: 1.3.6 - then-request: 6.0.2 - dev: true - - /sync-rpc/1.3.6: - resolution: {integrity: sha512-J8jTXuZzRlvU7HemDgHi3pGnh/rkoqR/OZSjhTyyZrEkkYQbk7Z33AXp37mkPfPpfdOuj7Ex3H/TJM1z48uPQw==} - dependencies: - get-port: 3.2.0 - dev: true - /tapable/2.2.1: resolution: {integrity: sha512-GNzQvQTOIP6RyTfE2Qxb8ZVlNmw0n88vp1szwWRimP02mnTsx3Wtn5qRdqY9w2XduFNUgvOwhNnQsjwCp+kqaQ==} engines: {node: '>=6'} @@ -10493,23 +10419,6 @@ packages: resolution: {integrity: sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==} dev: true - /then-request/6.0.2: - resolution: {integrity: sha512-3ZBiG7JvP3wbDzA9iNY5zJQcHL4jn/0BWtXIkagfz7QgOL/LqjCEOBQuJNZfu0XYnv5JhKh+cDxCPM4ILrqruA==} - engines: {node: '>=6.0.0'} - dependencies: - '@types/concat-stream': 1.6.1 - '@types/form-data': 0.0.33 - '@types/node': 8.10.66 - '@types/qs': 6.9.7 - caseless: 0.12.0 - concat-stream: 1.6.2 - form-data: 2.3.3 - http-basic: 8.1.3 - http-response-object: 3.0.2 - promise: 8.3.0 - qs: 6.11.0 - dev: true - /throat/6.0.1: resolution: {integrity: sha512-8hmiGIJMDlwjg7dlJ4yKGLK8EsYqKgPWbG3b4wjJddKNwc7N7Dpn08Df4szr/sZdMVeOstrdYSsqzX6BYbcB+w==} dev: true @@ -10536,11 +10445,6 @@ packages: resolution: {integrity: sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==} dev: true - /tinypool/0.2.4: - resolution: {integrity: sha512-Vs3rhkUH6Qq1t5bqtb816oT+HeJTXfwt2cbPH17sWHIYKTotQIFPk3tf2fgqRrVyMDVOc1EnPgzIxfIulXVzwQ==} - engines: {node: '>=14.0.0'} - dev: true - /tinypool/0.3.0: resolution: {integrity: sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ==} engines: {node: '>=14.0.0'} @@ -10775,10 +10679,6 @@ packages: is-typedarray: 1.0.0 dev: true - /typedarray/0.0.6: - resolution: {integrity: sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==} - dev: true - /typedoc-plugin-markdown/3.13.6_typedoc@0.23.18: resolution: {integrity: sha512-ISSc9v3BK7HkokxSBuJPttXox4tJ6hP0N9wfSIk0fmLN67+eqtAxbk97gs2nDiuha+RTO5eW9gdeAb+RPP0mgg==} peerDependencies: @@ -11013,34 +10913,37 @@ packages: vfile-message: 3.1.2 dev: true - /vite-plugin-md/0.20.4_ssclijsxphu2vue5hnv6ywl23u: - resolution: {integrity: sha512-W3Z59/ROS2X6OIwPwV2PjE+QkfW0UVGxyf3Z2JR0OLqGJ+Iy2SGA503m/vmATJv+C3DjeU8Oy8diQx1R+IyRwQ==} + /vite/3.2.3: + resolution: {integrity: sha512-h8jl1TZ76eGs3o2dIBSsvXDLb1m/Ec1iej8ZMdz+PsaFUsftZeWe2CZOI3qogEsMNaywc17gu0q6cQDzh/weCQ==} + engines: {node: ^14.18.0 || >=16.0.0} + hasBin: true peerDependencies: - '@rollup/pluginutils': ^4.2.1 - rollup: ^2.77.0 + '@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: - '@rollup/pluginutils': 4.2.1 - '@yankeeinlondon/builder-api': 0.4.1_dsigm6qpqe3mljd7p5w42sooza - '@yankeeinlondon/happy-wrapper': 2.6.0_iyb77cyw3lw7duusvxyjdsflhu - gray-matter: 4.0.3 - markdown-it: 13.0.1 + esbuild: 0.15.13 + postcss: 8.4.18 + resolve: 1.22.1 rollup: 2.79.1 - source-map-js: 1.0.2 - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@vitest/browser' - - '@vitest/ui' - - c8 - - fp-ts - - happy-dom - - inferred-types - - jsdom - - less - - sass - - stylus - - sugarss - - supports-color - - terser + optionalDependencies: + fsevents: 2.3.2 dev: true /vite/3.2.3_@types+node@18.11.9: @@ -11077,18 +10980,6 @@ packages: fsevents: 2.3.2 dev: true - /vitepress-plugin-mermaid/2.0.8_2q5vfj2vm6nj3r62ddjdsi7aoe: - resolution: {integrity: sha512-ywWxTeg9kMv7ZPf/igCBF4ZHhWZAyRtbPnA12ICQuNK2AMp7r5IHOfnuX1EJQf8gNdsh8bcvvSvm8Ll92fdOTw==} - peerDependencies: - mermaid: ^8.0.0 || ^9.0.0 - vite-plugin-md: ^0.20.4 - vitepress: ^0.21.6 || ^1.0.0 || ^1.0.0-alpha - dependencies: - mermaid: link:packages/mermaid - vite-plugin-md: 0.20.4_ssclijsxphu2vue5hnv6ywl23u - vitepress: 1.0.0-alpha.28_ysryt2e75uhznkanan6iyjk4mi - dev: true - /vitepress-plugin-search/1.0.4-alpha.15_s3edpouswd4dgoi2en7bdlrp54: resolution: {integrity: sha512-Ef/VkhTVYlECVI0H9Ck6745UNPfYFppAqnlxVSMJXdxP2vjOZ5TYNczlTTQ2p9dh16MFw/IurbL1/GrG4nXdNw==} engines: {node: ^14.13.1 || ^16.7.0 || >=18} @@ -11102,12 +10993,12 @@ packages: '@types/markdown-it': 12.2.3 flexsearch: 0.7.31 markdown-it: 13.0.1 - vite: 3.2.3_@types+node@18.11.9 - vitepress: 1.0.0-alpha.28_ysryt2e75uhznkanan6iyjk4mi + vite: 3.2.3 + vitepress: 1.0.0-alpha.28_tbpndr44ulefs3hehwpi2mkf2y vue: 3.2.41 dev: true - /vitepress/1.0.0-alpha.28_ysryt2e75uhznkanan6iyjk4mi: + /vitepress/1.0.0-alpha.28_tbpndr44ulefs3hehwpi2mkf2y: resolution: {integrity: sha512-pvbLssDMgLUN1terajmPlFBxHSDGO4DqwexKbjFyr7LeELerVuwGrG6F2J1hxmwOlbpLd1kHXEDqGm9JX/kTDQ==} hasBin: true dependencies: @@ -11118,7 +11009,7 @@ packages: '@vueuse/core': 9.4.0_vue@3.2.41 body-scroll-lock: 4.0.0-beta.0 shiki: 0.11.1 - vite: 3.2.3_@types+node@18.11.9 + vite: 3.2.3 vue: 3.2.41 transitivePeerDependencies: - '@algolia/client-search' @@ -11134,53 +11025,7 @@ packages: - terser dev: true - /vitest/0.19.1_iyb77cyw3lw7duusvxyjdsflhu: - resolution: {integrity: sha512-E/ZXpFMUahn731wzhMBNzWRp4mGgiZFT0xdHa32cbNO0CSaHpE9hTfteEU247Gi2Dula8uXo5vvrNB6dtszmQA==} - engines: {node: '>=v14.16.0'} - hasBin: true - peerDependencies: - '@edge-runtime/vm': '*' - '@vitest/browser': '*' - '@vitest/ui': '*' - c8: '*' - happy-dom: '*' - jsdom: '*' - peerDependenciesMeta: - '@edge-runtime/vm': - optional: true - '@vitest/browser': - optional: true - '@vitest/ui': - optional: true - c8: - optional: true - happy-dom: - optional: true - jsdom: - optional: true - dependencies: - '@types/chai': 4.3.3 - '@types/chai-subset': 1.3.3 - '@types/node': 18.11.9 - '@vitest/ui': 0.25.1 - chai: 4.3.6 - debug: 4.3.4 - happy-dom: 6.0.4 - jsdom: 20.0.2 - local-pkg: 0.4.2 - tinypool: 0.2.4 - tinyspy: 1.0.2 - vite: 3.2.3_@types+node@18.11.9 - transitivePeerDependencies: - - less - - sass - - stylus - - sugarss - - supports-color - - terser - dev: true - - /vitest/0.25.1_iyb77cyw3lw7duusvxyjdsflhu: + /vitest/0.25.1_oullksb5ic6y72oh2wekoaiuii: resolution: {integrity: sha512-eH74h6MkuEgsqR4mAQZeMK9O0PROiKY+i+1GMz/fBi5A3L2ml5U7JQs7LfPU7+uWUziZyLHagl+rkyfR8SLhlA==} engines: {node: '>=v14.16.0'} hasBin: true @@ -11210,7 +11055,53 @@ packages: acorn-walk: 8.2.0 chai: 4.3.6 debug: 4.3.4 - happy-dom: 6.0.4 + jsdom: 20.0.2 + local-pkg: 0.4.2 + source-map: 0.6.1 + strip-literal: 0.4.2 + tinybench: 2.3.1 + tinypool: 0.3.0 + tinyspy: 1.0.2 + vite: 3.2.3_@types+node@18.11.9 + transitivePeerDependencies: + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vitest/0.25.3_oullksb5ic6y72oh2wekoaiuii: + resolution: {integrity: sha512-/UzHfXIKsELZhL7OaM2xFlRF8HRZgAHtPctacvNK8H4vOcbJJAMEgbWNGSAK7Y9b1NBe5SeM7VTuz2RsTHFJJA==} + engines: {node: '>=v14.16.0'} + hasBin: true + peerDependencies: + '@edge-runtime/vm': '*' + '@vitest/browser': '*' + '@vitest/ui': '*' + happy-dom: '*' + jsdom: '*' + peerDependenciesMeta: + '@edge-runtime/vm': + optional: true + '@vitest/browser': + optional: true + '@vitest/ui': + optional: true + happy-dom: + optional: true + jsdom: + optional: true + dependencies: + '@types/chai': 4.3.3 + '@types/chai-subset': 1.3.3 + '@types/node': 18.11.9 + '@vitest/ui': 0.25.1 + acorn: 8.8.0 + acorn-walk: 8.2.0 + chai: 4.3.6 + debug: 4.3.4 jsdom: 20.0.2 local-pkg: 0.4.2 source-map: 0.6.1 diff --git a/scripts/jison/lint.mts b/scripts/jison/lint.mts index e834a8e4f..c410d5999 100644 --- a/scripts/jison/lint.mts +++ b/scripts/jison/lint.mts @@ -28,7 +28,7 @@ const lint = async (file: string): Promise => { dot: true, }); const lintResults = await Promise.all(jisonFiles.map(lint)); - if (lintResults.some((result) => result === false)) { + if (lintResults.includes(false)) { process.exit(1); } })();