diff --git a/.eslintrc.json b/.eslintrc.json index 02753280c..b8053795e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -52,24 +52,25 @@ }, "overrides": [ { - "files": "./**/*.html", - "rules": { - "no-undef": "off", - "jsdoc/require-jsdoc": "off" - } - }, - { - "files": ["./cypress/**", "./demos/**"], + "files": ["cypress/**", "demos/**"], "rules": { "no-console": "off" } }, { - "files": ["./**/*.spec.{ts,js}", "./cypress/**", "./demos/**", "./**/docs/**"], + "files": ["*.spec.{ts,js}", "cypress/**", "demos/**", "**/docs/**"], "rules": { "jsdoc/require-jsdoc": "off", "@typescript-eslint/no-unused-vars": "off" } + }, + { + "files": ["*.html", "*.md", "**/*.md/*"], + "rules": { + "no-var": "error", + "no-undef": "off", + "@typescript-eslint/no-unused-vars": "off" + } } ] } diff --git a/.github/workflows/docs.yml b/.github/workflows/docs.yml new file mode 100644 index 000000000..1e08a5c16 --- /dev/null +++ b/.github/workflows/docs.yml @@ -0,0 +1,28 @@ +name: Documentation Checks + +on: + push: + branches: + - develop + paths: + - 'packages/mermaid/src/docs/**/*' + pull_request: + branches: + - develop + paths: + - 'packages/mermaid/src/docs/**/*' +jobs: + spellcheck: + name: 'Docs: Spellcheck' + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v2 + name: Check out the code + - uses: actions/setup-node@v1 + name: Setup node + with: + node-version: '16' + - run: npm install -g cspell + name: Install cSpell + - run: cspell --config ./cSpell.json "packages/mermaid/src/docs/**/*.md" --no-progress + name: Run cSpell diff --git a/.vite/build.ts b/.vite/build.ts index 6855d3e48..7398d30d5 100644 --- a/.vite/build.ts +++ b/.vite/build.ts @@ -22,22 +22,22 @@ const packageOptions = { 'mermaid-mindmap': { name: 'mermaid-mindmap', packageName: 'mermaid-mindmap', - file: 'add-diagram.ts', + file: 'diagram-definition.ts', }, 'mermaid-mindmap-detector': { name: 'mermaid-mindmap-detector', packageName: 'mermaid-mindmap', - file: 'registry.ts', + file: 'detector.ts', }, 'mermaid-example-diagram': { name: 'mermaid-example-diagram', packageName: 'mermaid-example-diagram', - file: 'add-diagram.ts', + file: 'diagram-definition.ts', }, 'mermaid-example-diagram-detector': { name: 'mermaid-example-diagram-detector', packageName: 'mermaid-example-diagram', - file: 'registry.ts', + file: 'detector.ts', }, }; diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 000000000..0917a17fc --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,128 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone, regardless of age, body +size, visible or invisible disability, ethnicity, sex characteristics, gender +identity and expression, level of experience, education, socio-economic status, +nationality, personal appearance, race, religion, or sexual identity +and orientation. + +We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +- Demonstrating empathy and kindness toward other people +- Being respectful of differing opinions, viewpoints, and experiences +- Giving and gracefully accepting constructive feedback +- Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +- Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +- The use of sexualized language or imagery, and sexual attention or + advances of any kind +- Trolling, insulting or derogatory comments, and personal or political attacks +- Public or private harassment +- Publishing others' private information, such as a physical or email + address, without their explicit permission +- Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at security@mermaid.live +. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +### 1. Correction + +**Community Impact**: Use of inappropriate language or other behavior deemed +unprofessional or unwelcome in the community. + +**Consequence**: A private, written warning from community leaders, providing +clarity around the nature of the violation and an explanation of why the +behavior was inappropriate. A public apology may be requested. + +### 2. Warning + +**Community Impact**: A violation through a single incident or series +of actions. + +**Consequence**: A warning with consequences for continued behavior. No +interaction with the people involved, including unsolicited interaction with +those enforcing the Code of Conduct, for a specified period of time. This +includes avoiding interactions in community spaces as well as external channels +like social media. Violating these terms may lead to a temporary or +permanent ban. + +### 3. Temporary Ban + +**Community Impact**: A serious violation of community standards, including +sustained inappropriate behavior. + +**Consequence**: A temporary ban from any sort of interaction or public +communication with the community for a specified period of time. No public or +private interaction with the people involved, including unsolicited interaction +with those enforcing the Code of Conduct, is allowed during this period. +Violating these terms may lead to a permanent ban. + +### 4. Permanent Ban + +**Community Impact**: Demonstrating a pattern of violation of community +standards, including sustained inappropriate behavior, harassment of an +individual, or aggression toward or disparagement of classes of individuals. + +**Consequence**: A permanent ban from any sort of public interaction within +the community. + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index d7efa8e41..2234d5148 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -39,16 +39,16 @@ Less strict here, it is OK to commit directly in the `develop` branch if you are The documentation is written in **Markdown**. For more information about Markdown [see the GitHub Markdown help page](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax). -### Documentation source files are in /src/docs +### Documentation source files are in [`/packages/mermaid/src/docs`](packages/mermaid/src/docs) -The source files for the project documentation are located in the `/src/docs` directory. This is where you should make changes. -The files under `/src/docs` are processed to generate the published documentation, and the resulting files are put into the `/docs` directory. +The source files for the project documentation are located in the [`/packages/mermaid/src/docs`](packages/mermaid/src/docs) directory. This is where you should make changes. +The files under `/packages/mermaid/src/docs` are processed to generate the published documentation, and the resulting files are put into the `/docs` directory. ```mermaid flowchart LR classDef default fill:#fff,color:black,stroke:black - source["files in /src/docs\n(changes should be done here)"] -- automatic processing\nto generate the final documentation--> published["files in /docs\ndisplayed on the official documentation site"] + source["files in /packages/mermaid/src/docs\n(changes should be done here)"] -- automatic processing\nto generate the final documentation--> published["files in /docs\ndisplayed on the official documentation site"] ``` @@ -137,7 +137,7 @@ it('should render forks and joins', () => { Finally, if it is not in the documentation, no one will know about it and then **no one will use it**. Wouldn't that be sad? With all the effort that was put into the feature? -The source files for documentation are in `/src/docs` and are written in markdown. Just pick the right section and start typing. See the [Committing Documentation](#committing-documentation) section for more about how the documentation is generated. +The source files for documentation are in `/packages/mermaid/src/docs` and are written in markdown. Just pick the right section and start typing. See the [Committing Documentation](#committing-documentation) section for more about how the documentation is generated. #### Adding to or changing the documentation organization diff --git a/V10-BreakingChanges.md b/V10-BreakingChanges.md new file mode 100644 index 000000000..bd9110d1a --- /dev/null +++ b/V10-BreakingChanges.md @@ -0,0 +1,5 @@ +# A collection of updates that change the behaviour + +## Lazy loading and asynchronisity + +- Invalid dates are rendered as syntax error instead of returning best guess or the current date diff --git a/__mocks__/dagre-d3.ts b/__mocks__/dagre-d3.ts index a1a677591..bf6d341dc 100644 --- a/__mocks__/dagre-d3.ts +++ b/__mocks__/dagre-d3.ts @@ -1,3 +1 @@ -import { vi } from 'vitest'; - -// export const render = vi.fn(); +// DO NOT delete this file. It is used by vitest to mock the dagre-d3 module. diff --git a/cSpell.json b/cSpell.json new file mode 100644 index 000000000..5abf6e283 --- /dev/null +++ b/cSpell.json @@ -0,0 +1,95 @@ +{ + "version": "0.2", + "language": "en", + "words": [ + "customizability", + "Gantt", + "jison", + "knsv", + "Knut", + "mindmap", + "Mindmaps", + "mitigations", + "sandboxed", + "Sveidqvist", + "verdana", + "Visio" + ], + "ignoreWords": [ + "Adamiecki", + "applitools", + "Asciidoctor", + "Astah", + "Bisheng", + "codedoc", + "Docsy", + "Doku", + "Gitea", + "Gitgraph", + "Grav", + "Inkdrop", + "Jaoude", + "mdbook", + "mermerd", + "mkdocs", + "phpbb", + "Plantuml", + "Playfair's", + "Podlite", + "redmine", + "sphinxcontrib", + "Tuleap" + ], + "patterns": [ + { + "name": "Markdown links", + "pattern": "\\((.*)\\)", + "description": "" + }, + { + "name": "Markdown code blocks", + "pattern": "/^(\\s*`{3,}).*[\\s\\S]*?^\\1/gmx", + "description": "Taken from the cSpell example at https://cspell.org/configuration/patterns/#verbose-regular-expressions" + }, + { + "name": "Inline code blocks", + "pattern": "\\`([^\\`\\r\\n]+?)\\`", + "description": "https://stackoverflow.com/questions/41274241/how-to-capture-inline-markdown-code-but-not-a-markdown-code-fence-with-regex" + }, + { + "name": "Link contents", + "pattern": "\\", + "description": "" + }, + { + "name": "Snippet references", + "pattern": "-- snippet:(.*)", + "description": "" + }, + { + "name": "Snippet references 2", + "pattern": "\\<\\[sample:(.*)", + "description": "another kind of snippet reference" + }, + { + "name": "Multi-line code blocks", + "pattern": "/^\\s*```[\\s\\S]*?^\\s*```/gm" + }, + { + "name": "HTML Tags", + "pattern": "<[^>]*>", + "description": "Reference: https://stackoverflow.com/questions/11229831/regular-expression-to-remove-html-tags-from-a-string" + } + ], + "ignoreRegExpList": [ + "Markdown links", + "Markdown code blocks", + "Inline code blocks", + "Link contents", + "Snippet references", + "Snippet references 2", + "Multi-line code blocks", + "HTML Tags" + ], + "ignorePaths": ["packages/mermaid/src/docs/CHANGELOG.md"] +} diff --git a/cypress/integration/other/interaction.spec.js b/cypress/integration/other/interaction.spec.js index b253d0389..857141b5b 100644 --- a/cypress/integration/other/interaction.spec.js +++ b/cypress/integration/other/interaction.spec.js @@ -1,266 +1,180 @@ describe('Interaction', () => { - describe('Interaction - security level loose', () => { - it('Graph: should handle a click on a node with a bound function', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g#flowchart-Function-4').click(); + describe('Security level loose', () => { + beforeEach(() => { + cy.visit('http://localhost:9000/click_security_loose.html'); + }); + it('Graph: should handle a click on a node with a bound function', () => { + cy.contains('FunctionTest1').parents('.node').click(); cy.get('.created-by-click').should('have.text', 'Clicked By Flow'); }); + it('Graph: should handle a click on a node with a bound function with args', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g#flowchart-FunctionArg-28').click(); - + cy.contains('FunctionArgTest2').parents('.node').click(); cy.get('.created-by-click-2').should('have.text', 'Clicked By Flow: ARGUMENT'); }); + it('Flowchart: should handle a click on a node with a bound function where the node starts with a number', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g[id="flowchart-FunctionArg-34"]').click(); - + cy.contains('2FunctionArg').parents('.node').click(); cy.get('.created-by-click-2').should('have.text', 'Clicked By Flow: ARGUMENT'); }); - it('Graph: should handle a click on a node with a bound url', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('#flowchart-URL-5').click(); - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + it('Graph: should handle a click on a node with a bound url', () => { + // When there is a URL, cy.contains selects the a tag instead of the span. The .node is a child of a, so we have to use find instead of parent. + cy.contains('URLTest1').find('.node').click(); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); - it('Graph: should handle a click on a node with a bound url where the node starts with a number', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g[id="flowchart-2URL-11"]').click(); - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + it('Graph: should handle a click on a node with a bound url where the node starts with a number', () => { + cy.contains('2URL').find('.node').click(); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); it('Flowchart-v2: should handle a click on a node with a bound function', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g#flowchart-Function-16').click(); - + cy.contains('FunctionTest2').parents('.node').click(); cy.get('.created-by-click').should('have.text', 'Clicked By Flow'); }); + it('Flowchart-v2: should handle a click on a node with a bound function where the node starts with a number', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g[id="flowchart-1Function-22"]').click(); - + cy.contains('10Function').parents('.node').click(); cy.get('.created-by-click').should('have.text', 'Clicked By Flow'); }); - it('Flowchart-v2: should handle a click on a node with a bound url', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('#flowchart-URL-17').click(); - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + it('Flowchart-v2: should handle a click on a node with a bound url', () => { + cy.contains('URLTest2').find('.node').click(); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); - it('Flowchart-v2: should handle a click on a node with a bound url where the node starts with a number', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g[id="flowchart-2URL-23"]').click(); - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + it('Flowchart-v2: should handle a click on a node with a bound url where the node starts with a number', () => { + cy.contains('20URL').find('.node').click(); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); it('should handle a click on a task with a bound URL clicking on the rect', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('rect#cl1').click({ force: true }); - - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + cy.get('rect#cl1').click({ force: true }); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); + it('should handle a click on a task with a bound URL clicking on the text', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('text#cl1-text').click({ force: true }); - - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + cy.get('text#cl1-text').click({ force: true }); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); - it('should handle a click on a task with a bound function without args', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('rect#cl2').click({ force: true }); + it('should handle a click on a task with a bound function without args', () => { + cy.get('rect#cl2').click({ force: true }); cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant cl2'); }); - it('should handle a click on a task with a bound function with args', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('rect#cl3').click({ force: true }); + it('should handle a click on a task with a bound function with args', () => { + cy.get('rect#cl3').click({ force: true }); cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant test1 test2 test3'); }); it('should handle a click on a task with a bound function without args', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('text#cl2-text').click({ force: true }); - + cy.get('text#cl2-text').click({ force: true }); cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant cl2'); }); - it('should handle a click on a task with a bound function with args ', () => { - const url = 'http://localhost:9000/click_security_loose.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('text#cl3-text').click({ force: true }); + it('should handle a click on a task with a bound function with args ', () => { + cy.get('text#cl3-text').click({ force: true }); cy.get('.created-by-gant-click').should('have.text', 'Clicked By Gant test1 test2 test3'); }); }); describe('Interaction - security level tight', () => { + beforeEach(() => { + cy.visit('http://localhost:9000/click_security_strict.html'); + }); it('should handle a click on a node without a bound function', () => { - const url = 'http://localhost:9000/click_security_strict.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g#flowchart-Function-4').click(); - + cy.contains('Function1').parents('.node').click(); cy.get('.created-by-click').should('not.exist'); - // cy.get('.created-by-click').should('not.have.text', 'Clicked By Flow'); }); + it('should handle a click on a node with a bound function where the node starts with a number', () => { - const url = 'http://localhost:9000/click_security_strict.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g[id="flowchart-1Function-10"]').click(); - - // cy.get('.created-by-click').should('not.have.text', 'Clicked By Flow'); + cy.contains('1Function').parents('.node').click(); cy.get('.created-by-click').should('not.exist'); }); - it('should handle a click on a node with a bound url', () => { - const url = 'http://localhost:9000/click_security_strict.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g#flowchart-URL-5').click(); - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + it('should handle a click on a node with a bound url', () => { + cy.contains('URL1').find('.node').click(); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); - it('should handle a click on a node with a bound url where the node starts with a number', () => { - const url = 'http://localhost:9000/click_security_strict.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g[id="flowchart-2URL-11"]').click(); - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + it('should handle a click on a node with a bound url where the node starts with a number', () => { + cy.contains('2URL').find('.node').click(); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); it('should handle a click on a task with a bound URL clicking on the rect', () => { - const url = 'http://localhost:9000/click_security_strict.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('rect#cl1').click({ force: true }); - - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + cy.get('rect#cl1').click({ force: true }); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); + it('should handle a click on a task with a bound URL clicking on the text', () => { - const url = 'http://localhost:9000/click_security_strict.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('text#cl1-text').click({ force: true }); - - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + cy.get('text#cl1-text').click({ force: true }); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); - it('should handle a click on a task with a bound function', () => { - const url = 'http://localhost:9000/click_security_strict.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('rect#cl2').click({ force: true }); - // cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant cl2'); + it('should handle a click on a task with a bound function', () => { + cy.get('rect#cl2').click({ force: true }); cy.get('.created-by-gant-click').should('not.exist'); }); - it('should handle a click on a task with a bound function', () => { - const url = 'http://localhost:9000/click_security_strict.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('text#cl2-text').click({ force: true }); - // cy.get('.created-by-gant-click').should('not.have.text', 'Clicked By Gant cl2'); + it('should handle a click on a task with a bound function', () => { + cy.get('text#cl2-text').click({ force: true }); cy.get('.created-by-gant-click').should('not.exist'); }); }); describe('Interaction - security level other, missspelling', () => { + beforeEach(() => { + cy.visit('http://localhost:9000/click_security_other.html'); + }); + it('should handle a click on a node with a bound function', () => { - const url = 'http://localhost:9000/click_security_other.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g#flowchart-Function-4').click(); - - // cy.get('.created-by-click').should('not.have.text', 'Clicked By Flow'); + cy.contains('Function1').parents('.node').click(); cy.get('.created-by-click').should('not.exist'); }); + it('should handle a click on a node with a bound function where the node starts with a number', () => { - const url = 'http://localhost:9000/click_security_other.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g[id="flowchart-1Function-10"]').click(); - - cy.get('.created-by-click').should('not.exist'); + cy.contains('1Function').parents('.node').click(); cy.get('.created-by-click').should('not.exist'); }); - it('should handle a click on a node with a bound url', () => { - const url = 'http://localhost:9000/click_security_other.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('g#flowchart-URL-5').click(); - cy.location().should((location) => { - expect(location.href).to.eq('http://localhost:9000/webpackUsage.html'); + it('should handle a click on a node with a bound url', () => { + cy.contains('URL1').find('.node').click(); + cy.location().should(({ href }) => { + expect(href).to.eq('http://localhost:9000/empty.html'); }); }); it('should handle a click on a task with a bound function', () => { - const url = 'http://localhost:9000/click_security_other.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('rect#cl2').click({ force: true }); - + cy.get('rect#cl2').click({ force: true }); cy.get('.created-by-gant-click').should('not.exist'); }); - it('should handle a click on a task with a bound function', () => { - const url = 'http://localhost:9000/click_security_other.html'; - cy.viewport(1440, 1024); - cy.visit(url); - cy.get('body').find('text#cl2-text').click({ force: true }); + it('should handle a click on a task with a bound function', () => { + cy.get('text#cl2-text').click({ force: true }); cy.get('.created-by-gant-click').should('not.exist'); }); }); diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index 438b80267..16a70ece0 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -92,7 +92,7 @@ describe('Gantt diagram', () => { {} ); }); - it('should render a gantt chart for issue #1060', () => { + it('should FAIL redering a gantt chart for issue #1060 with invalid date', () => { imgSnapshotTest( ` gantt diff --git a/cypress/platform/click_security_loose.html b/cypress/platform/click_security_loose.html index 6c77e90af..459c14e85 100644 --- a/cypress/platform/click_security_loose.html +++ b/cypress/platform/click_security_loose.html @@ -13,42 +13,42 @@
-
+      
     graph TB
-      Function-->URL
-      click Function clickByFlow "Add a div"
-      click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+      FunctionTest1-->URLTest1
+      click FunctionTest1 clickByFlow "Add a div"
+      click URLTest1 "http://localhost:9000/empty.html" "Visit mermaid docs"
       
-
+      
   graph TB
     1Function--->2URL
     click 1Function clickByFlow "Add a div"
-    click 2URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+    click 2URL "http://localhost:9000/empty.html" "Visit mermaid docs"
       
-
+      
     flowchart TB
-      Function-->URL
-      click Function clickByFlow "Add a div"
-      click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" _self
+      FunctionTest2-->URLTest2
+      click FunctionTest2 clickByFlow "Add a div"
+      click URLTest2 "http://localhost:9000/empty.html" "Visit mermaid docs" _self
       
-
+      
   flowchart TB
-    1Function--->2URL
-    click 1Function clickByFlow "Add a div"
-    click 2URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" _self
+    10Function--->20URL
+    click 10Function clickByFlow "Add a div"
+    click 20URL "http://localhost:9000/empty.html" "Visit mermaid docs" _self
       
-
+      
   classDiagram
     class ShapeLink
-    link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
+    link ShapeLink "http://localhost:9000/empty.html" "This is a tooltip for a link"
     class ShapeCallback
     callback ShapeCallback "clickByClass" "This is a tooltip for a callback"
       
-
+      
   classDiagram-v2
     class ShapeLink2
-    link ShapeLink2 "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
+    link ShapeLink2 "http://localhost:9000/empty.html" "This is a tooltip for a link"
     class ShapeCallback2
     callback ShapeCallback2 "clickByClass" "This is a tooltip for a callback"
       
@@ -85,7 +85,7 @@ Calling a Callback (look at the console log) :cl2, after cl1, 3d Calling a Callback with args :cl3, after cl1, 3d - click cl1 href "http://localhost:9000/webpackUsage.html" + click cl1 href "http://localhost:9000/empty.html" click cl2 call clickByGantt() click cl3 call clickByGantt("test1", test2, test3) @@ -95,31 +95,31 @@ Add another diagram to demo page : 48h
-
+      
       graph TB
-        FunctionArg-->URL
-        click FunctionArg call clickByFlowArg(ARGUMENT) "Add a div"
-        click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+        FunctionArgTest2-->URL
+        click FunctionArgTest2 call clickByFlowArg(ARGUMENT) "Add a div"
+        click URL "http://localhost:9000/empty.html" "Visit mermaid docs"
       
-
+      
       flowchart TB
-        FunctionArg-->URL
-        click FunctionArg call clickByFlowArg(ARGUMENT) "Add a div"
-        click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+        2FunctionArg-->URL
+        click 2FunctionArg call clickByFlowArg(ARGUMENT) "Add a div"
+        click URL "http://localhost:9000/empty.html" "Visit mermaid docs"
       
-
+      
       classDiagram
       class ShapeLink
-      link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
+      link ShapeLink "http://localhost:9000/empty.html" "This is a tooltip for a link"
       class ShapeCallback
       click ShapeCallback call clickByClass(123) "This is a tooltip for a callback"
       
-
+      
       classDiagram-v2
         class ShapeLink2
-        link ShapeLink2 "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
+        link ShapeLink2 "http://localhost:9000/empty.html" "This is a tooltip for a link"
         class ShapeCallback2
         click ShapeCallback2 call clickByClass(123) "This is a tooltip for a callback"
       
diff --git a/cypress/platform/click_security_other.html b/cypress/platform/click_security_other.html index 20bfd5293..5338cac06 100644 --- a/cypress/platform/click_security_other.html +++ b/cypress/platform/click_security_other.html @@ -9,15 +9,15 @@
     graph TB
-      Function-->URL
-      click Function clickByFlow "Add a div"
-      click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+      Function1-->URL1
+      click Function1 clickByFlow "Add a div"
+      click URL1 "http://localhost:9000/empty.html" "Visit mermaid docs"
     
   graph TB
     1Function-->2URL
     click 1Function clickByFlow "Add a div"
-    click 2URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+    click 2URL "http://localhost:9000/empty.html" "Visit mermaid docs"
     
@@ -50,7 +50,7 @@
     Visit mermaidjs               :active, cl1, 2014-01-07,2014-01-10
     Calling a Callback (look at the console log) :cl2, after cl1, 3d
 
-    click cl1 href "http://localhost:9000/webpackUsage.html"
+    click cl1 href "http://localhost:9000/empty.html"
     click cl2 call clickByGantt("test", test, test)
 
     section Last section
diff --git a/cypress/platform/click_security_sandbox.html b/cypress/platform/click_security_sandbox.html
index 94229500c..49c5d71c0 100644
--- a/cypress/platform/click_security_sandbox.html
+++ b/cypress/platform/click_security_sandbox.html
@@ -17,38 +17,38 @@
     graph TB
       Function-->URL
       click Function clickByFlow "Add a div"
-      click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+      click URL "http://localhost:9000/empty.html" "Visit mermaid docs"
       
   graph TB
     1Function--->2URL
     click 1Function clickByFlow "Add a div"
-    click 2URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+    click 2URL "http://localhost:9000/empty.html" "Visit mermaid docs"
       
     flowchart TB
       Function-->URL
       click Function clickByFlow "Add a div"
-      click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" _self
+      click URL "http://localhost:9000/empty.html" "Visit mermaid docs" _self
       
   flowchart TB
     1Function--->2URL
     click 1Function clickByFlow "Add a div"
-    click 2URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" _self
+    click 2URL "http://localhost:9000/empty.html" "Visit mermaid docs" _self
       
   classDiagram
     class ShapeLink
-    link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
+    link ShapeLink "http://localhost:9000/empty.html" "This is a tooltip for a link"
     class ShapeCallback
     callback ShapeCallback "clickByClass" "This is a tooltip for a callback"
       
   classDiagram-v2
     class ShapeLink2
-    link ShapeLink2 "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
+    link ShapeLink2 "http://localhost:9000/empty.html" "This is a tooltip for a link"
     class ShapeCallback2
     callback ShapeCallback2 "clickByClass" "This is a tooltip for a callback"
       
@@ -85,7 +85,7 @@ Calling a Callback (look at the console log) :cl2, after cl1, 3d Calling a Callback with args :cl3, after cl1, 3d - click cl1 href "http://localhost:9000/webpackUsage.html" + click cl1 href "http://localhost:9000/empty.html" click cl2 call clickByGantt() click cl3 call clickByGantt("test1", test2, test3) @@ -99,19 +99,19 @@ graph TB FunctionArg-->URL click FunctionArg call clickByFlowArg(ARGUMENT) "Add a div" - click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs" + click URL "http://localhost:9000/empty.html" "Visit mermaid docs"
       flowchart TB
         FunctionArg-->URL
         click FunctionArg call clickByFlowArg(ARGUMENT) "Add a div"
-        click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+        click URL "http://localhost:9000/empty.html" "Visit mermaid docs"
       
       classDiagram
       class ShapeLink
-      link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
+      link ShapeLink "http://localhost:9000/empty.html" "This is a tooltip for a link"
       class ShapeCallback
       click ShapeCallback call clickByClass(123) "This is a tooltip for a callback"
       
@@ -119,7 +119,7 @@
       classDiagram-v2
         class ShapeLink2
-        link ShapeLink2 "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link"
+        link ShapeLink2 "http://localhost:9000/empty.html" "This is a tooltip for a link"
         class ShapeCallback2
         click ShapeCallback2 call clickByClass(123) "This is a tooltip for a callback"
       
diff --git a/cypress/platform/click_security_strict.html b/cypress/platform/click_security_strict.html index 00c6d9c6a..26605ddf9 100644 --- a/cypress/platform/click_security_strict.html +++ b/cypress/platform/click_security_strict.html @@ -9,15 +9,15 @@
     graph TB
-      Function-->URL
-      click Function clickByFlow "Add a div"
-      click URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+      Function1-->URL1
+      click Function1 clickByFlow "Add a div"
+      click URL1 "http://localhost:9000/empty.html" "Visit mermaid docs"
     
   graph TB
     1Function-->2URL
     click 1Function clickByFlow "Add a div"
-    click 2URL "http://localhost:9000/webpackUsage.html" "Visit mermaid docs"
+    click 2URL "http://localhost:9000/empty.html" "Visit mermaid docs"
     
@@ -51,7 +51,7 @@
     Calling a Callback (look at the console log) :cl2, after cl1, 3d
     Calling a Callback with args :cl3, after cl1, 3d
 
-    click cl1 href "http://localhost:9000/webpackUsage.html"
+    click cl1 href "http://localhost:9000/empty.html"
     click cl2 call clickByGantt()
     click cl3 call clickByGantt("test1", test2, test3)
 
diff --git a/cypress/platform/empty.html b/cypress/platform/empty.html
new file mode 100644
index 000000000..2961644d6
--- /dev/null
+++ b/cypress/platform/empty.html
@@ -0,0 +1,10 @@
+
+
+  
+    
+    
+    
+    Empty
+  
+  
+
diff --git a/cypress/platform/knsv.html b/cypress/platform/knsv.html
index d4ffa0c0c..f6ee1ef03 100644
--- a/cypress/platform/knsv.html
+++ b/cypress/platform/knsv.html
@@ -368,7 +368,6 @@ flowchart TD
     
- - + + + + diff --git a/cypress/platform/rerender.html b/cypress/platform/rerender.html index 9b6cb130e..61c6891f7 100644 --- a/cypress/platform/rerender.html +++ b/cypress/platform/rerender.html @@ -17,11 +17,12 @@ rerender('XMas'); function rerender(text) { - var graphText = `graph TD + const graphText = `graph TD A[${text}] -->|Get money| B(Go shopping)`; - var graph = mermaid.mermaidAPI.render('id', graphText); - console.log('\x1b[35m%s\x1b[0m', '>> graph', graph); - document.getElementById('graph').innerHTML = graph; + mermaid.mermaidAPI.render('id', graphText).then((svg) => { + console.log('\x1b[35m%s\x1b[0m', '>> graph', svg); + document.getElementById('graph').innerHTML = svg; + }); } diff --git a/cypress/platform/viewer.js b/cypress/platform/viewer.js index 8f1ccfdab..f0426dc09 100644 --- a/cypress/platform/viewer.js +++ b/cypress/platform/viewer.js @@ -36,6 +36,8 @@ const contentLoaded = function () { document.getElementsByTagName('body')[0].appendChild(div); } + graphObj.mermaid.lazyLoadedDiagrams = ['/mermaid-mindmap-detector.esm.mjs']; + mermaid2.initialize(graphObj.mermaid); mermaid2.init(); } diff --git a/cypress/platform/xss10.html b/cypress/platform/xss10.html index 6a027f514..b39728c84 100644 --- a/cypress/platform/xss10.html +++ b/cypress/platform/xss10.html @@ -93,7 +93,7 @@ throw new Error('XSS Succeeded'); } - var diagram = 'classDiagram\n'; + let diagram = 'classDiagram\n'; diagram += 'class Square~" <--> "" C2: Cool label`; // // var diagram = "stateDiagram-v2\n"; diff --git a/cypress/platform/xss19.html b/cypress/platform/xss19.html index 590a195aa..ca747b39e 100644 --- a/cypress/platform/xss19.html +++ b/cypress/platform/xss19.html @@ -93,7 +93,7 @@ throw new Error('XSS Succeeded'); } - var diagram = `classDiagram + let diagram = `classDiagram class Shape{ << { + let cnt = 0; + let a; + const handler = setInterval(() => { cnt++; a = {}; if (typeof a.polluted !== 'undefined') { diff --git a/cypress/platform/xss20.html b/cypress/platform/xss20.html index 7b8aa9f85..9efd17215 100644 --- a/cypress/platform/xss20.html +++ b/cypress/platform/xss20.html @@ -96,7 +96,7 @@ // var diagram = ` graph TD // A --> B["<a href='javasc`; // diagram += `ript#colon;xssAttack()'>AAA</a>"]`; - var diagram = ` graph TD + let diagram = ` graph TD A --> B["AAA"]`; // diagram += '//via.placeholder.com/64\' width=64 />"]'; diff --git a/cypress/platform/xss21.html b/cypress/platform/xss21.html index e65a357ee..fed0e4289 100644 --- a/cypress/platform/xss21.html +++ b/cypress/platform/xss21.html @@ -96,7 +96,7 @@ // var diagram = ` graph TD // A --> B["<a href='javasc`; // diagram += `ript#colon;xssAttack()'>AAA</a>"]`; - var diagram = ` graph TD + let diagram = ` graph TD A --> B["AAA"]`; // diagram += '//via.placeholder.com/64\' width=64 />"]'; diff --git a/cypress/platform/xss3.html b/cypress/platform/xss3.html index b72e8743c..78fabc4aa 100644 --- a/cypress/platform/xss3.html +++ b/cypress/platform/xss3.html @@ -42,9 +42,9 @@ startOnLoad: true, useMaxWidth: true, }); - var cnt = 0; - var a; - var handler = setInterval(() => { + let cnt = 0; + let a; + const handler = setInterval(() => { cnt++; a = {}; if (typeof a.polluted !== 'undefined') { diff --git a/cypress/platform/xss4.html b/cypress/platform/xss4.html index b6e36edcd..924a65e08 100644 --- a/cypress/platform/xss4.html +++ b/cypress/platform/xss4.html @@ -85,7 +85,7 @@ alert('It worked'); } - var diagram = '%%{init: {"flowchart": {"htmlLabels": "true"}} }%%\n'; + let diagram = '%%{init: {"flowchart": {"htmlLabels": "true"}} }%%\n'; diagram += 'flowchart\n'; diagram += 'A[" - + @@ -14,6 +14,7 @@ +

C4 context diagram demos

     C4Context
       accTitle: C4 context demo
@@ -62,6 +63,7 @@
       
       UpdateLayoutConfig($c4ShapeInRow="3", $c4BoundaryInRow="1")
     
+
     C4Container
@@ -101,6 +103,7 @@
     Rel(backend_api, banking_system, "Uses", "sync/async, XML/HTTPS")
     UpdateRelStyle(backend_api, banking_system, $offsetY="-50", $offsetX="-140")
     
+
     C4Component
@@ -140,6 +143,7 @@
         UpdateRelStyle(security, db, $offsetY="-40")
         UpdateRelStyle(mbsfacade, mbs, $offsetY="-40")
     
+
     C4Dynamic
@@ -159,6 +163,7 @@
     UpdateRelStyle(c2, c3, $textColor="red", $offsetX="-40", $offsetY="60")
     UpdateRelStyle(c3, c4, $textColor="red", $offsetY="-40", $offsetX="10")
     
+
     C4Deployment
@@ -210,7 +215,6 @@
     UpdateRelStyle(api, db2, $offsetX="-40", $offsetY="-20")
     UpdateRelStyle(db, db2, $offsetY="-10")
      
-
@@ -273,7 +277,7 @@ + - - - diff --git a/demos/journey.html b/demos/journey.html index dc1c379d3..c5c6c25e8 100644 --- a/demos/journey.html +++ b/demos/journey.html @@ -1,5 +1,5 @@ - + @@ -14,6 +14,7 @@ +

Journey diagram demo

          journey
     title My working day
diff --git a/demos/pie.html b/demos/pie.html
index 3232d2534..333ef9491 100644
--- a/demos/pie.html
+++ b/demos/pie.html
@@ -1,5 +1,5 @@
 
-
+
   
     
     
@@ -14,6 +14,7 @@
   
 
   
+    

Pie chart demos

       pie title Pets adopted by volunteers
       accTitle: simple pie char demo
@@ -23,6 +24,7 @@
     "Rats" : 15
     
+
     pie
       title Key elements in Product X
@@ -35,7 +37,7 @@
     
@@ -653,10 +653,10 @@ Beginner's tip—a full example using interactive links in an HTML page:
@@ -727,10 +727,10 @@ Beginner's tip—here's a full example of using interactive links in HTML:
@@ -771,10 +771,10 @@ Beginner's tip—a full example using interactive links in a html context:
@@ -60,7 +60,7 @@ initEditor(monaco); return new Promise((resolve, reject) => { monaco.editor.setTheme('mermaid'); - let parsed = parser.parseFromString(html, 'text/html').body; + const parsed = parser.parseFromString(html, 'text/html').body; Promise.all( [...parsed.querySelectorAll('pre[id*="code"]')].map((codeBlock) => monaco.editor.colorize(codeBlock.innerText, 'mermaid') @@ -136,7 +136,6 @@ ], }; - let num = 0; const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches; const conf = { diff --git a/docs/integrations.md b/docs/integrations.md index 49c103492..09df7cf9f 100644 --- a/docs/integrations.md +++ b/docs/integrations.md @@ -33,7 +33,7 @@ They also serve as proof of concept, for the variety of things that can be built - [Mermaid Macro](https://www.redmine.org/plugins/redmine_mermaid_macro) - [redmine-mermaid](https://github.com/styz/redmine_mermaid) - [markdown-for-mermaid-plugin](https://github.com/jamieh-mongolian/markdown-for-mermaid-plugin) -- [Jetsbrain IDE eg Pycharm](https://www.jetbrains.com/go/guide/tips/mermaid-js-support-in-markdown/) +- [JetBrains IDE eg Pycharm](https://www.jetbrains.com/go/guide/tips/mermaid-js-support-in-markdown/) - [mermerd](https://github.com/KarnerTh/mermerd) ## CRM/ERP/Similar diff --git a/docs/mindmap.md b/docs/mindmap.md index f56a2186d..94baf43e0 100644 --- a/docs/mindmap.md +++ b/docs/mindmap.md @@ -2,7 +2,7 @@ # Mindmap -> Mindmap: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stabel except for the icon integration which is the experimental part. +> Mindmap: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part. "A mind map is a diagram used to visually organize information into a hierarchy, showing relationships among pieces of the whole. It is often created around a single concept, drawn as an image in the center of a blank page, to which associated representations of ideas such as images, words and parts of words are added. Major ideas are connected directly to the central concept, and other ideas branch out from those major ideas." Wikipedia @@ -54,7 +54,7 @@ mindmap The syntax for creating Mindmaps is simple and relies on indentation for setting the levels in the hierarchy. -In the following example you can see how there are 3 dufferent levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further then the prevoius lines defining the nodes B and C. +In the following example you can see how there are 3 different levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further then the previous lines defining the nodes B and C. mindmap Root diff --git a/docs/n00b-syntaxReference.md b/docs/n00b-syntaxReference.md index 7edb4c319..2c9ef7207 100644 --- a/docs/n00b-syntaxReference.md +++ b/docs/n00b-syntaxReference.md @@ -26,6 +26,18 @@ erDiagram PRODUCT ||--o{ ORDER-ITEM : "ordered in" ``` +```mermaid +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" +``` + The [Getting Started](./n00b-gettingStarted.md) section can also provide some practical examples of mermaid syntax. ## Diagram Breaking diff --git a/docs/usage.md b/docs/usage.md index be986a56b..c804d0f05 100644 --- a/docs/usage.md +++ b/docs/usage.md @@ -208,14 +208,14 @@ The example below show an outline of how this could be used. The example just lo ``` @@ -339,7 +339,7 @@ on what kind of integration you use. ```html ``` diff --git a/package.json b/package.json index 67f546d97..4ad882420 100644 --- a/package.json +++ b/package.json @@ -7,7 +7,7 @@ "module": "dist/mermaid.core.mjs", "types": "dist/mermaid.d.ts", "type": "module", - "packageManager": "pnpm@7.12.2", + "packageManager": "pnpm@7.13.2", "exports": { ".": { "require": "./dist/mermaid.min.js", @@ -26,12 +26,11 @@ "git graph" ], "scripts": { - "clean": "rimraf dist", "build:vite": "ts-node-esm --transpileOnly --project=.vite/tsconfig.json .vite/build.ts", "build:types": "concurrently \"tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly\" \"tsc -p ./packages/mermaid-mindmap/tsconfig.json --emitDeclarationOnly\"", "build:watch": "pnpm build:vite --watch", - "build": "pnpm clean; concurrently \"pnpm build:vite\" \"pnpm build:types\"", - "dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server\"", + "build": "pnpm run -r clean && concurrently \"pnpm build:vite\" \"pnpm build:types\"", + "dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"", "docs:build": "ts-node-esm --transpileOnly packages/mermaid/src/docs.mts", "docs:verify": "pnpm docs:build --verify", "todo-postbuild": "documentation build src/mermaidAPI.ts src/config.ts src/defaultConfig.ts --shallow -f md --markdown-toc false > src/docs/Setup.md && prettier --write src/docs/Setup.md", @@ -71,8 +70,8 @@ }, "dependencies": { "@braintree/sanitize-url": "^6.0.0", - "@types/uuid": "^8.3.4", "@types/node": "^18.8.1", + "@types/uuid": "^8.3.4", "d3": "^7.0.0", "dagre": "^0.8.5", "dagre-d3": "^0.6.4", @@ -107,8 +106,8 @@ "coveralls": "^3.1.1", "cypress": "^10.0.0", "cypress-image-snapshot": "^4.0.1", - "esbuild": "^0.15.10", "documentation": "13.2.0", + "esbuild": "^0.15.10", "eslint": "^8.24.0", "eslint-config-prettier": "^8.5.0", "eslint-plugin-cypress": "^2.12.1", @@ -126,8 +125,8 @@ "jsdom": "^20.0.1", "lint-staged": "^13.0.3", "markdown-it": "^13.0.1", - "moment": "^2.23.0", "path-browserify": "^1.0.1", + "pnpm": "^7.13.2", "prettier": "^2.7.1", "prettier-plugin-jsdoc": "^0.4.2", "remark": "^14.0.2", @@ -136,10 +135,10 @@ "ts-node": "^10.9.1", "typescript": "^4.8.4", "unist-util-flatmap": "^1.0.0", + "vite": "^3.1.4", "vitepress": "^1.0.0-alpha.19", "vitepress-plugin-mermaid": "^2.0.8", "vitepress-plugin-search": "^1.0.4-alpha.11", - "vite": "^3.1.4", "vitest": "^0.23.4" }, "resolutions": { diff --git a/packages/mermaid-example-diagram/package.json b/packages/mermaid-example-diagram/package.json index fa3c570b6..8898a66f6 100644 --- a/packages/mermaid-example-diagram/package.json +++ b/packages/mermaid-example-diagram/package.json @@ -29,10 +29,6 @@ "lint": "eslint --cache --ignore-path .gitignore . && yarn lint:jison && prettier --check .", "lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .", "lint:jison": "ts-node-esm src/jison/lint.mts", - "ci": "vitest run", - "test": "yarn lint && vitest run", - "test:watch": "vitest --coverage --watch", - "todo-prepublishOnly": "yarn build && yarn test", "todo-prepare": "concurrently \"husky install ../../.husky\" \"yarn build\"", "todo-pre-commit": "lint-staged" }, diff --git a/packages/mermaid-example-diagram/src/exampleDetector.ts b/packages/mermaid-example-diagram/src/detector.ts similarity index 62% rename from packages/mermaid-example-diagram/src/exampleDetector.ts rename to packages/mermaid-example-diagram/src/detector.ts index 55fbfe2e3..4d857ac26 100644 --- a/packages/mermaid-example-diagram/src/exampleDetector.ts +++ b/packages/mermaid-example-diagram/src/detector.ts @@ -1,3 +1,6 @@ +// @ts-ignore: TODO Fix ts errors +export const id = 'example-diagram'; + /** * Detector function that will be called by mermaid to determine if the diagram is this type of digram. * @@ -8,3 +11,8 @@ export const detector = (txt: string) => { return txt.match(/^\s*example-diagram/) !== null; }; + +export const loadDiagram = async () => { + const { diagram } = await import('./diagram-definition'); + return { id, diagram }; +}; diff --git a/packages/mermaid-example-diagram/src/add-diagram.ts b/packages/mermaid-example-diagram/src/diagram-definition.ts similarity index 66% rename from packages/mermaid-example-diagram/src/add-diagram.ts rename to packages/mermaid-example-diagram/src/diagram-definition.ts index ec32ed838..c31b3d6e7 100644 --- a/packages/mermaid-example-diagram/src/add-diagram.ts +++ b/packages/mermaid-example-diagram/src/diagram-definition.ts @@ -5,13 +5,10 @@ import renderer from './exampleDiagramRenderer'; import styles from './styles'; import { injectUtils } from './mermaidUtils'; -window.mermaid.connectDiagram( - 'example-diagram', - { - db, - renderer, - parser, - styles, - }, - injectUtils -); +export const diagram = { + db, + renderer, + parser, + styles, + injectUtils, +}; diff --git a/packages/mermaid-example-diagram/src/registry.ts b/packages/mermaid-example-diagram/src/registry.ts deleted file mode 100644 index 92ff3bd00..000000000 --- a/packages/mermaid-example-diagram/src/registry.ts +++ /dev/null @@ -1,33 +0,0 @@ -// @ts-ignore: TODO Fix ts errors -import { detector } from './exampleDetector'; - -const scriptElement = document.currentScript as HTMLScriptElement; -const path = scriptElement.src; -const lastSlash = path.lastIndexOf('/'); -const baseFolder = lastSlash < 0 ? path : path.substring(0, lastSlash + 1); - -if (typeof document !== 'undefined') { - if (window.mermaid && typeof window.mermaid.detectors === 'object') { - window.mermaid.detectors.push({ id: 'example-diagram', detector }); - } else { - window.mermaid = {}; - window.mermaid.detectors = [{ id: 'example-diagram', detector }]; - } - - /* - * Wait for document loaded before starting the execution. - */ - window.addEventListener( - 'load', - () => { - if (window.mermaid && typeof window.mermaid.detectors === 'object') { - window.mermaid.detectors.push({ - id: 'example-diagram', - detector, - path: baseFolder, - }); - } - }, - false - ); -} diff --git a/packages/mermaid-mindmap/package.json b/packages/mermaid-mindmap/package.json index 8599f2b54..befe56016 100644 --- a/packages/mermaid-mindmap/package.json +++ b/packages/mermaid-mindmap/package.json @@ -29,10 +29,6 @@ "lint": "eslint --cache --ignore-path .gitignore . && yarn lint:jison && prettier --check .", "lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .", "lint:jison": "ts-node-esm src/jison/lint.mts", - "ci": "vitest run", - "test": "yarn lint && vitest run", - "test:watch": "vitest --coverage --watch", - "todo-prepublishOnly": "yarn build && yarn test", "todo-prepare": "concurrently \"husky install ../../.husky\" \"yarn build\"", "todo-pre-commit": "lint-staged" }, diff --git a/packages/mermaid-mindmap/src/detector.ts b/packages/mermaid-mindmap/src/detector.ts new file mode 100644 index 000000000..af7002b3c --- /dev/null +++ b/packages/mermaid-mindmap/src/detector.ts @@ -0,0 +1,10 @@ +export const id = 'mindmap'; + +export const detector = (txt: string) => { + return txt.match(/^\s*mindmap/) !== null; +}; + +export const loadDiagram = async () => { + const { diagram } = await import('./diagram-definition'); + return { id, diagram }; +}; diff --git a/packages/mermaid-mindmap/src/add-diagram.ts b/packages/mermaid-mindmap/src/diagram-definition.ts similarity index 59% rename from packages/mermaid-mindmap/src/add-diagram.ts rename to packages/mermaid-mindmap/src/diagram-definition.ts index 873d55232..e7856289d 100644 --- a/packages/mermaid-mindmap/src/add-diagram.ts +++ b/packages/mermaid-mindmap/src/diagram-definition.ts @@ -5,13 +5,10 @@ import mindmapRenderer from './mindmapRenderer'; import mindmapStyles from './styles'; import { injectUtils } from './mermaidUtils'; -window.mermaid.connectDiagram( - 'mindmap', - { - db: mindmapDb, - renderer: mindmapRenderer, - parser: mindmapParser, - styles: mindmapStyles, - }, - injectUtils -); +export const diagram = { + db: mindmapDb, + renderer: mindmapRenderer, + parser: mindmapParser, + styles: mindmapStyles, + injectUtils, +}; diff --git a/packages/mermaid-mindmap/src/mermaidUtils.ts b/packages/mermaid-mindmap/src/mermaidUtils.ts index a65523f0c..7d8ac38bf 100644 --- a/packages/mermaid-mindmap/src/mermaidUtils.ts +++ b/packages/mermaid-mindmap/src/mermaidUtils.ts @@ -53,29 +53,3 @@ export const injectUtils = ( sanitizeText = _sanitizeText; setupGraphViewbox = _setupGraphViewbox; }; - -/* -const warning = (..._args: any[]) => { - console.error('Log function was called before initialization'); -}; - -export let log = { - trace: warning, - debug: warning, - info: warning, - warn: warning, - error: warning, - fatal: warning, -}; -export let setLogLevel; -export let getConfig; -export let sanitizeText; -export let setupGraphViewbox; -export const injectUtils = (_log, _setLogLevel, _getConfig, _sanitizeText, _setupGraphViewbox) => { - log = _log; - setLogLevel = _setLogLevel; - getConfig = _getConfig; - sanitizeText = _sanitizeText; - setupGraphViewbox = _setupGraphViewbox; -}; -*/ diff --git a/packages/mermaid-mindmap/src/mindmapDetector.ts b/packages/mermaid-mindmap/src/mindmapDetector.ts deleted file mode 100644 index 9a3f6f426..000000000 --- a/packages/mermaid-mindmap/src/mindmapDetector.ts +++ /dev/null @@ -1,3 +0,0 @@ -export const mindmapDetector = (txt: string) => { - return txt.match(/^\s*mindmap/) !== null; -}; diff --git a/packages/mermaid-mindmap/src/registry.ts b/packages/mermaid-mindmap/src/registry.ts deleted file mode 100644 index e70967ba6..000000000 --- a/packages/mermaid-mindmap/src/registry.ts +++ /dev/null @@ -1,34 +0,0 @@ -// @ts-ignore: TODO Fix ts errors -import { mindmapDetector } from './mindmapDetector'; - -const scriptElement = document.currentScript as HTMLScriptElement; -const path = scriptElement.src; -const lastSlash = path.lastIndexOf('/'); -const baseFolder = lastSlash < 0 ? path : path.substring(0, lastSlash + 1); - -if (typeof document !== 'undefined') { - if (window.mermaid && typeof window.mermaid.detectors === 'object') { - window.mermaid.detectors.push({ id: 'mindmap', detector: mindmapDetector }); - } else { - window.mermaid = {}; - window.mermaid.detectors = [{ id: 'mindmap', detector: mindmapDetector }]; - } - - /*! - * Wait for document loaded before starting the execution. - */ - window.addEventListener( - 'load', - () => { - if (window.mermaid && typeof window.mermaid.detectors === 'object') { - window.mermaid.detectors.push({ - id: 'mindmap', - detector: mindmapDetector, - path: baseFolder, - }); - console.error(window.mermaid.detectors); // eslint-disable-line no-console - } - }, - false - ); -} diff --git a/packages/mermaid-mindmap/tsconfig.json b/packages/mermaid-mindmap/tsconfig.json index 45076b7b5..310137cc0 100644 --- a/packages/mermaid-mindmap/tsconfig.json +++ b/packages/mermaid-mindmap/tsconfig.json @@ -1,5 +1,6 @@ { "extends": "../../tsconfig.json", + "module": "esnext", "compilerOptions": { "rootDir": "./src", "outDir": "./dist" diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 0c42dc3b9..6ce4f8109 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -41,10 +41,6 @@ "cypress": "cypress run", "cypress:open": "cypress open", "e2e": "start-server-and-test dev http://localhost:9000/ cypress", - "ci": "vitest run", - "test": "yarn lint && vitest run", - "test:watch": "vitest --coverage --watch", - "prepublishOnly": "yarn build && yarn test", "todo-prepare": "concurrently \"husky install\" \"yarn build\"", "pre-commit": "lint-staged" }, @@ -92,8 +88,6 @@ "@types/stylis": "^4.0.2", "@typescript-eslint/eslint-plugin": "^5.37.0", "@typescript-eslint/parser": "^5.37.0", - "@vitest/coverage-c8": "^0.23.2", - "@vitest/ui": "^0.23.2", "concurrently": "^7.4.0", "coveralls": "^3.1.1", "cypress": "^10.0.0", @@ -125,8 +119,7 @@ "start-server-and-test": "^1.12.6", "ts-node": "^10.9.1", "typescript": "^4.8.3", - "unist-util-flatmap": "^1.0.0", - "vitest": "^0.23.1" + "unist-util-flatmap": "^1.0.0" }, "resolutions": { "d3": "^7.0.0" diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index 231a78af6..9bdc92079 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -1,7 +1,7 @@ import * as configApi from './config'; import { log } from './logger'; -import { getDiagram, loadDiagram } from './diagram-api/diagramAPI'; -import { detectType, getPathForDiagram } from './diagram-api/detectType'; +import { getDiagram, registerDiagram } from './diagram-api/diagramAPI'; +import { detectType, getDiagramLoader } from './diagram-api/detectType'; import { isDetailedError } from './utils'; export class Diagram { type = 'graph'; @@ -75,10 +75,25 @@ export const getDiagramFromText = async (txt: string, parseError?: Function) => // Trying to find the diagram getDiagram(type); } catch (error) { + const loader = getDiagramLoader(type); + if (!loader) { + throw new Error(`Diagram ${type} not found.`); + } // Diagram not avaiable, loading it - const path = getPathForDiagram(type); + // const path = getPathForDiagram(type); + const { diagram } = await loader(); // eslint-disable-line @typescript-eslint/no-explicit-any + registerDiagram( + type, + { + db: diagram.db, + renderer: diagram.renderer, + parser: diagram.parser, + styles: diagram.styles, + }, + diagram.injectUtils + ); // await loadDiagram('./packages/mermaid-mindmap/dist/mermaid-mindmap.js'); - await loadDiagram(path + 'mermaid-' + type + '.js'); + // await loadDiagram(path + 'mermaid-' + type + '.js'); // new diagram will try getDiagram again and if fails then it is a valid throw } // If either of the above worked, we have the diagram diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index d9d94226f..1059d5709 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -3,6 +3,7 @@ import DOMPurify from 'dompurify'; export interface MermaidConfig { + lazyLoadedDiagrams?: any; theme?: string; themeVariables?: any; themeCSS?: string; diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index 681fda60c..710557bd9 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -115,7 +115,7 @@ const config: Partial = { * Default value: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize'] */ secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize'], - + lazyLoadedDiagrams: [], /** * This option controls if the generated ids of nodes in the SVG are generated randomly or based * on a seed. If set to false, the IDs are generated based on the current date and thus are not diff --git a/packages/mermaid/src/diagram-api/detectType.ts b/packages/mermaid/src/diagram-api/detectType.ts index afb9a9078..340cbfbae 100644 --- a/packages/mermaid/src/diagram-api/detectType.ts +++ b/packages/mermaid/src/diagram-api/detectType.ts @@ -1,8 +1,8 @@ import { MermaidConfig } from '../config.type'; export type DiagramDetector = (text: string, config?: MermaidConfig) => boolean; -export type DetectorRecord = { detector: DiagramDetector; path: string }; - +export type DiagramLoader = (() => any) | null; +export type DetectorRecord = { detector: DiagramDetector; loader: DiagramLoader }; const directive = /[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi; const anyComment = /\s*%%.*\n/gm; @@ -37,8 +37,9 @@ export const detectType = function (text: string, config?: MermaidConfig): strin // console.log(detectors); - for (const [key, detectorRecord] of Object.entries(detectors)) { - if (detectorRecord.detector(text, config)) { + for (const [key, { detector }] of Object.entries(detectors)) { + const diagram = detector(text, config); + if (diagram) { return key; } } @@ -47,13 +48,12 @@ export const detectType = function (text: string, config?: MermaidConfig): strin return 'flowchart'; }; -export const addDetector = (key: string, detector: DiagramDetector, path: string) => { - detectors[key] = { detector, path }; +export const addDetector = ( + key: string, + detector: DiagramDetector, + loader: DiagramLoader | null +) => { + detectors[key] = { detector, loader }; }; -export const getPathForDiagram = (id: string) => { - const detectorRecord = detectors[id]; - if (detectorRecord) { - return detectorRecord.path; - } -}; +export const getDiagramLoader = (key: string) => detectors[key].loader; diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 912559c90..1693f4f51 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -112,7 +112,7 @@ const registerDiagramAndDetector = ( detector: DiagramDetector ) => { registerDiagram(id, diagram); - registerDetector(id, detector, ''); + registerDetector(id, detector); }; export const addDiagrams = () => { diff --git a/packages/mermaid/src/diagram-api/diagramAPI.spec.ts b/packages/mermaid/src/diagram-api/diagramAPI.spec.ts index 018e72bd4..584a36fa3 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.spec.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.spec.ts @@ -19,17 +19,13 @@ describe('DiagramAPI', () => { const detector: DiagramDetector = (str: string) => { return str.match('loki') !== null; }; - registerDetector('loki', detector, ''); - registerDiagram( - 'loki', - { - db: {}, - parser: {}, - renderer: {}, - styles: {}, - }, - (text: string) => text.includes('loki') - ); + registerDetector('loki', detector); + registerDiagram('loki', { + db: {}, + parser: {}, + renderer: {}, + styles: {}, + }); expect(getDiagram('loki')).not.toBeNull(); expect(detectType('loki diagram')).toBe('loki'); }); diff --git a/packages/mermaid/src/diagram-api/diagramAPI.ts b/packages/mermaid/src/diagram-api/diagramAPI.ts index beb770f31..002619bbb 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.ts @@ -18,12 +18,21 @@ export const getConfig = _getConfig; export const sanitizeText = (text: string) => _sanitizeText(text, getConfig()); export const setupGraphViewbox = _setupGraphViewbox; +export interface InjectUtils { + _log: any; + _setLogLevel: any; + _getConfig: any; + _sanitizeText: any; + _setupGraphViewbox: any; +} + export interface DiagramDefinition { db: any; renderer: any; parser: any; styles: any; init?: (config: MermaidConfig) => void; + injectUtils?: (utils: InjectUtils) => void; } const diagrams: Record = {}; @@ -32,8 +41,8 @@ export interface Detectors { [key: string]: DiagramDetector; } -export const registerDetector = (id: string, detector: DiagramDetector, path: string) => { - addDetector(id, detector, path); +export const registerDetector = (id: string, detector: DiagramDetector) => { + addDetector(id, detector, null); }; export const registerDiagram = ( @@ -52,7 +61,9 @@ export const registerDiagram = ( } diagrams[id] = diagram; addStylesForDiagram(id, diagram.styles); - connectCallbacks[id] = callback; + if (typeof callback !== 'undefined') { + callback(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox); + } }; export const getDiagram = (name: string): DiagramDefinition => { diff --git a/packages/mermaid/src/diagrams/class/svgDraw.js b/packages/mermaid/src/diagrams/class/svgDraw.js index 3d44e94b4..9a4dc761e 100644 --- a/packages/mermaid/src/diagrams/class/svgDraw.js +++ b/packages/mermaid/src/diagrams/class/svgDraw.js @@ -347,9 +347,10 @@ const buildMethodDisplay = function (parsedText) { }; const buildLegacyDisplay = function (text) { - // if for some reason we don't have any match, use old format to parse text + // if for some reason we dont 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(')'); @@ -370,21 +371,21 @@ const buildLegacyDisplay = function (text) { } const parameters = text.substring(methodStart + 1, methodEnd); - const classifier = text.substring(methodEnd + 1, methodEnd + 2); - cssStyle = parseClassifier(classifier); + const classifier = text.substring(methodEnd + 1, 1); + cssStyle = parseClassifier(text.substring(methodEnd + 1, methodEnd + 2)); displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')'; - if (methodEnd <= text.length) { + if (methodEnd < text.length) { returnType = text.substring(methodEnd + 2).trim(); if (returnType !== '') { returnType = ' : ' + parseGenericTypes(returnType); displayText += returnType; } - } else { - // finally - if all else fails, just send the text back as written (other than parsing for generic types) - displayText = parseGenericTypes(text); } + } else { + // finally - if all else fails, just send the text back as written (other than parsing for generic types) + displayText = parseGenericTypes(text); } return { @@ -392,7 +393,6 @@ const buildLegacyDisplay = function (text) { cssStyle, }; }; - /** * Adds a for a member in a diagram * diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.js b/packages/mermaid/src/diagrams/gantt/ganttDb.js index 5d072b903..9183cc766 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttDb.js +++ b/packages/mermaid/src/diagrams/gantt/ganttDb.js @@ -217,10 +217,12 @@ const getStartDate = function (prevTime, dateFormat, str) { } else { log.debug('Invalid date:' + str); log.debug('With date format:' + dateFormat.trim()); + const d = new Date(str); + if (typeof d === 'undefined' || isNaN(d.getTime())) { + throw new Error('Invalid date:' + str); + } + return d; } - - // Default date - now - return new Date(); }; /** diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts b/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts index 28b64d4f5..09df96f12 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts +++ b/packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts @@ -1,7 +1,6 @@ // @ts-nocheck TODO: Fix TS import moment from 'moment-mini'; import ganttDb from './ganttDb'; -import { it, describe } from 'vitest'; import { convert } from '../../tests/util'; describe('when using the ganttDb', function () { diff --git a/packages/mermaid/src/docs/README.md b/packages/mermaid/src/docs/README.md index e8bba38da..9040eea39 100644 --- a/packages/mermaid/src/docs/README.md +++ b/packages/mermaid/src/docs/README.md @@ -263,7 +263,7 @@ Update version number in `package.json`. npm publish ``` -The above command generates files into the `dist` folder and publishes them to npmjs.org. +The above command generates files into the `dist` folder and publishes them to . ## Related projects @@ -279,7 +279,7 @@ Detailed information about how to contribute can be found in the [contribution g ## Security and safe diagrams -For public sites, it can be precarious to retrieve text from users on the internet, storing that content for presentation in a browser at a later stage. The reason is that the user content can contain embedded malicious scripts that will run when the data is presented. For Mermaid this is a risk, specially as mermaid diagrams contain many characters that are used in html which makes the standard sanitation unusable as it also breaks the diagrams. We still make an effort to sanitise the incoming code and keep refining the process but it is hard to guarantee that there are no loop holes. +For public sites, it can be precarious to retrieve text from users on the internet, storing that content for presentation in a browser at a later stage. The reason is that the user content can contain embedded malicious scripts that will run when the data is presented. For Mermaid this is a risk, specially as mermaid diagrams contain many characters that are used in html which makes the standard sanitation unusable as it also breaks the diagrams. We still make an effort to sanitize the incoming code and keep refining the process but it is hard to guarantee that there are no loop holes. As an extra level of security for sites with external users we are happy to introduce a new security level in which the diagram is rendered in a sandboxed iframe preventing JavaScript in the code from being executed. This is a great step forward for better security. diff --git a/packages/mermaid/src/docs/Setup.md b/packages/mermaid/src/docs/Setup.md index aae2ae9c0..95ae2dde4 100644 --- a/packages/mermaid/src/docs/Setup.md +++ b/packages/mermaid/src/docs/Setup.md @@ -72,15 +72,15 @@ Theme , the CSS style sheet | Parameter | Description | Type | Required | Values | | ------------- | --------------------------------- | ------ | -------- | ------------------------------------------ | -| securityLevel | Level of trust for parsed diagram | string | Required | 'sandbox', 'strict', 'loose', 'antiscript' | +| securityLevel | Level of trust for parsed diagram | string | Required | `sandbox`, `strict`, `loose`, `antiscript` | **Notes**: -- **strict**: (**default**) tags in text are encoded, click functionality is disabled -- **loose**: tags in text are allowed, click functionality is enabled -- **antiscript**: html tags in text are allowed, (only script element is removed), click +- **`strict`**: (**default**) tags in text are encoded, click functionality is disabled +- **`loose`**: tags in text are allowed, click functionality is enabled +- **`antiscript`**: html tags in text are allowed, (only script element is removed), click functionality is enabled -- **sandbox**: With this security level all rendering takes place in a sandboxed iframe. This +- **`sandbox`**: With this security level all rendering takes place in a sandboxed iframe. This prevent any JavaScript from running in the context. This may hinder interactive functionality of the diagram like scripts, popups in sequence diagram or links to other tabs/targets etc. @@ -119,11 +119,11 @@ Default value: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize'] This option controls if the generated ids of nodes in the SVG are generated randomly or based on a seed. If set to false, the IDs are generated based on the current date and thus are not -deterministic. This is the default behaviour. +deterministic. This is the default behavior. **Notes**: -This matters if your files are checked into sourcecontrol e.g. git and should not change unless +This matters if your files are checked into source control e.g. git and should not change unless content is changed. Default value: false @@ -210,16 +210,16 @@ Default value: true ### defaultRenderer -| Parameter | Description | Type | Required | Values | -| --------------- | ----------- | ------- | -------- | ----------------------- | -| defaultRenderer | See notes | boolean | 4 | dagre-d3, dagre-wrapper | +| Parameter | Description | Type | Required | Values | +| --------------- | ----------- | ------- | -------- | --------------------------- | +| defaultRenderer | See notes | boolean | 4 | `dagre-d3`, `dagre-wrapper` | **Notes:** Decides which rendering engine that is to be used for the rendering. Legal values are: -dagre-d3 dagre-wrapper - wrapper for dagre implemented in mermaid +`dagre-d3` `dagre-wrapper` - wrapper for `dagre` implemented in mermaid -Default value: 'dagre-wrapper' +Default value: `dagre-wrapper` ## sequence @@ -735,16 +735,16 @@ Default value: true ## defaultRenderer -| Parameter | Description | Type | Required | Values | -| --------------- | ----------- | ------- | -------- | ----------------------- | -| defaultRenderer | See notes | boolean | 4 | dagre-d3, dagre-wrapper | +| Parameter | Description | Type | Required | Values | +| --------------- | ----------- | ------- | -------- | --------------------------- | +| defaultRenderer | See notes | boolean | 4 | `dagre-d3`, `dagre-wrapper` | **Notes**: Decides which rendering engine that is to be used for the rendering. Legal values are: -dagre-d3 dagre-wrapper - wrapper for dagre implemented in mermaid +`dagre-d3` `dagre-wrapper` - wrapper for `dagre` implemented in mermaid -Default value: 'dagre-d3' +Default value: `dagre-d3` ## useMaxWidth @@ -761,16 +761,16 @@ Default value: true ## defaultRenderer -| Parameter | Description | Type | Required | Values | -| --------------- | ----------- | ------- | -------- | ----------------------- | -| defaultRenderer | See notes | boolean | 4 | dagre-d3, dagre-wrapper | +| Parameter | Description | Type | Required | Values | +| --------------- | ----------- | ------- | -------- | --------------------------- | +| defaultRenderer | See notes | boolean | 4 | `dagre-d3`, `dagre-wrapper` | **Notes:** Decides which rendering engine that is to be used for the rendering. Legal values are: -dagre-d3 dagre-wrapper - wrapper for dagre implemented in mermaid +`dagre-d3` `dagre-wrapper` - wrapper for `dagre` implemented in mermaid -Default value: 'dagre-d3' +Default value: `dagre-d3` ## er @@ -992,7 +992,7 @@ Default value: 4 | --------------- | ----------- | ------- | -------- | ------------------ | | c4BoundaryInRow | See Notes | Integer | Required | Any Positive Value | -**Notes:** How many boundarys to place in each row. +**Notes:** How many boundaries to place in each row. Default value: 2 @@ -1559,7 +1559,7 @@ Returns **void** ```html @@ -451,10 +451,10 @@ Beginner's tip—a full example using interactive links in an HTML page:
@@ -471,10 +471,10 @@ Beginner's tip—here's a full example of using interactive links in HTML:
@@ -495,10 +495,10 @@ Beginner's tip—a full example using interactive links in a html context:
@@ -51,16 +51,16 @@
``` @@ -339,7 +339,7 @@ on what kind of integration you use. ```html ``` diff --git a/packages/mermaid/src/mermaid.spec.ts b/packages/mermaid/src/mermaid.spec.ts index df6439c82..8cf180ae7 100644 --- a/packages/mermaid/src/mermaid.spec.ts +++ b/packages/mermaid/src/mermaid.spec.ts @@ -1,7 +1,6 @@ import mermaid from './mermaid'; import { mermaidAPI } from './mermaidAPI'; import './diagram-api/diagram-orchestration'; -import { vi, describe, it, beforeEach, afterEach, expect } from 'vitest'; const spyOn = vi.spyOn; vi.mock('./mermaidAPI'); diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts index 6a709dd32..7ddbc9f06 100644 --- a/packages/mermaid/src/mermaid.ts +++ b/packages/mermaid/src/mermaid.ts @@ -45,7 +45,7 @@ import { isDetailedError } from './utils'; * @param nodes * @param callback */ -const init = function ( +const init = async function ( config?: MermaidConfig, // eslint-disable-next-line no-undef nodes?: string | HTMLElement | NodeListOf, @@ -54,10 +54,17 @@ const init = function ( ) { try { log.info('Detectors in init', mermaid.detectors); // eslint-disable-line + const conf = mermaidAPI.getConfig(); + if (typeof conf.lazyLoadedDiagrams !== 'undefined' && conf.lazyLoadedDiagrams.length > 0) { + for (let i = 0; i < conf.lazyLoadedDiagrams.length; i++) { + const { id, detector, loadDiagram } = await import(conf.lazyLoadedDiagrams[i]); + addDetector(id, detector, loadDiagram); + } + } mermaid.detectors.forEach(({ id, detector, path }) => { addDetector(id, detector, path); }); - initThrowsErrors(config, nodes, callback); + await initThrowsErrors(config, nodes, callback); } catch (e) { log.warn('Syntax Error rendering'); if (isDetailedError(e)) { @@ -69,7 +76,7 @@ const init = function ( } }; -const initThrowsErrors = function ( +const initThrowsErrors = async function ( config?: MermaidConfig, // eslint-disable-next-line no-undef nodes?: string | HTMLElement | NodeListOf, @@ -108,7 +115,7 @@ const initThrowsErrors = function ( // generate the id of the diagram const idGenerator = new utils.initIdGenerator(conf.deterministicIds, conf.deterministicIDSeed); - let txt; + let txt: string; const errors = []; // element is the current div with mermaid class @@ -136,7 +143,7 @@ const initThrowsErrors = function ( log.debug('Detected early reinit: ', init); } try { - mermaidAPI.render( + await mermaidAPI.render( id, txt, (svgCode: string, bindFunctions?: (el: Element) => void) => { @@ -164,8 +171,8 @@ const initThrowsErrors = function ( } }; -const initialize = function (config: MermaidConfig) { - mermaidAPI.initialize(config); +const initialize = async function (config: MermaidConfig) { + await mermaidAPI.initialize(config); }; /** diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 50eb51ec5..f9544ee44 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -18,6 +18,7 @@ import { compile, serialize, stringify } from 'stylis'; import pkg from '../package.json'; import * as configApi from './config'; import { addDiagrams } from './diagram-api/diagram-orchestration'; +import { addDetector } from './diagram-api/detectType'; import classDb from './diagrams/class/classDb'; import flowDb from './diagrams/flowchart/flowDb'; import flowRenderer from './diagrams/flowchart/flowRenderer'; @@ -461,7 +462,7 @@ const handleDirective = function (p: any, directive: any, type: string): void { }; /** @param {MermaidConfig} options */ -function initialize(options: MermaidConfig) { +async function initialize(options: MermaidConfig) { // Handle legacy location of font-family configuration if (options?.fontFamily) { if (!options.themeVariables?.fontFamily) { @@ -485,6 +486,7 @@ function initialize(options: MermaidConfig) { typeof options === 'object' ? configApi.setSiteConfig(options) : configApi.getSiteConfig(); setLogLevel(config.logLevel); + if (!hasLoadedDiagrams) { addDiagrams(); hasLoadedDiagrams = true; diff --git a/packages/mermaid/src/utils.spec.js b/packages/mermaid/src/utils.spec.js index f1523aca5..4a511b3c0 100644 --- a/packages/mermaid/src/utils.spec.js +++ b/packages/mermaid/src/utils.spec.js @@ -1,4 +1,4 @@ -import { vi, describe, it, expect, beforeEach } from 'vitest'; +import { vi } from 'vitest'; import utils from './utils'; import assignWithDepth from './assignWithDepth'; import { detectType } from './diagram-api/detectType'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 477685a45..89b929342 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -56,10 +56,10 @@ importers: lint-staged: ^13.0.3 lodash: ^4.17.21 markdown-it: ^13.0.1 - moment: ^2.23.0 moment-mini: ^2.24.0 non-layered-tidy-tree-layout: ^2.0.2 path-browserify: ^1.0.1 + pnpm: ^7.13.2 prettier: ^2.7.1 prettier-plugin-jsdoc: ^0.4.2 remark: ^14.0.2 @@ -132,8 +132,8 @@ importers: jsdom: 20.0.1 lint-staged: 13.0.3 markdown-it: 13.0.1 - moment: 2.29.4 path-browserify: 1.0.1 + pnpm: 7.13.2 prettier: 2.7.1 prettier-plugin-jsdoc: 0.4.2_prettier@2.7.1 remark: 14.0.2 @@ -164,8 +164,6 @@ importers: '@types/stylis': ^4.0.2 '@typescript-eslint/eslint-plugin': ^5.37.0 '@typescript-eslint/parser': ^5.37.0 - '@vitest/coverage-c8': ^0.23.2 - '@vitest/ui': ^0.23.2 concurrently: ^7.4.0 coveralls: ^3.1.1 cypress: ^10.0.0 @@ -209,7 +207,6 @@ importers: ts-node: ^10.9.1 typescript: ^4.8.3 unist-util-flatmap: ^1.0.0 - vitest: ^0.23.1 dependencies: '@braintree/sanitize-url': 6.0.0 d3: 7.6.1 @@ -237,8 +234,6 @@ importers: '@types/stylis': 4.0.2 '@typescript-eslint/eslint-plugin': 5.38.0_wsb62dxj2oqwgas4kadjymcmry '@typescript-eslint/parser': 5.38.0_irgkl5vooow2ydyo6aokmferha - '@vitest/coverage-c8': 0.23.4_y2hohvmcqnhseytaw4yjcnsnkm - '@vitest/ui': 0.23.4 concurrently: 7.4.0 coveralls: 3.1.1 cypress: 10.8.0 @@ -271,7 +266,6 @@ importers: ts-node: 10.9.1_typescript@4.8.3 typescript: 4.8.3 unist-util-flatmap: 1.0.0 - vitest: 0.23.4_y2hohvmcqnhseytaw4yjcnsnkm packages/mermaid-example-diagram: specifiers: @@ -3582,24 +3576,6 @@ packages: - terser dev: true - /@vitest/coverage-c8/0.23.4_y2hohvmcqnhseytaw4yjcnsnkm: - resolution: {integrity: sha512-jmD00a5DQH9gu9K+YdvVhcMuv2CzHvU4gCnySS40Ec5hKlXtlCzRfNHl00VnhfuBeaQUmaQYe60BLT413HyDdg==} - dependencies: - c8: 7.12.0 - vitest: 0.23.4_y2hohvmcqnhseytaw4yjcnsnkm - transitivePeerDependencies: - - '@edge-runtime/vm' - - '@vitest/browser' - - '@vitest/ui' - - happy-dom - - jsdom - - less - - sass - - stylus - - supports-color - - terser - dev: true - /@vitest/ui/0.23.4: resolution: {integrity: sha512-lNZVTTrkHThGAwNQ1ah1qCNnm70r7OLB5LCUdSqboStve/1eKTrtt27QfDSSUTG8AVJQzU0eaN/j8UocH+CqfA==} dependencies: @@ -10753,6 +10729,12 @@ packages: engines: {node: '>=12.13.0'} dev: true + /pnpm/7.13.2: + resolution: {integrity: sha512-lOQRBcCWycLK1PB9KptqTd6iyiH7m4GRuS4G2j4b74yDx/XvRXtP/weYz8e0/ia7HX1pMF1vJCF48ssklq0TJQ==} + engines: {node: '>=14.6'} + hasBin: true + dev: true + /posix-character-classes/0.1.1: resolution: {integrity: sha512-xTgYBc3fuo7Yt7JbiuFxSYGToMoz8fLoE6TC9Wx1P/u+LfeThMOAqmuyECnlBaaJb+u1m9hHiXUEtwW4OzfUJg==} engines: {node: '>=0.10.0'} @@ -13187,49 +13169,6 @@ packages: - terser dev: true - /vitest/0.23.4_y2hohvmcqnhseytaw4yjcnsnkm: - resolution: {integrity: sha512-iukBNWqQAv8EKDBUNntspLp9SfpaVFbmzmM0sNcnTxASQZMzRw3PsM6DMlsHiI+I6GeO5/sYDg3ecpC+SNFLrQ==} - 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.7.21 - '@vitest/ui': 0.23.4 - chai: 4.3.6 - debug: 4.3.4 - jsdom: 20.0.0 - local-pkg: 0.4.2 - strip-literal: 0.4.2 - tinybench: 2.1.5 - tinypool: 0.3.0 - tinyspy: 1.0.2 - vite: 3.1.3 - transitivePeerDependencies: - - less - - sass - - stylus - - supports-color - - terser - dev: true - /vm2/3.9.11: resolution: {integrity: sha512-PFG8iJRSjvvBdisowQ7iVF580DXb1uCIiGaXgm7tynMR1uTBlv7UJlB1zdv5KJ+Tmq1f0Upnj3fayoEOPpCBKg==} engines: {node: '>=6.0'} diff --git a/tsconfig.json b/tsconfig.json index 99feb5bda..fe107f205 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -27,7 +27,7 @@ // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ /* Modules */ - "module": "ES6" /* Specify what module code is generated. */, + "module": "es2022" /* Specify what module code is generated. */, // "rootDir": "./packages" /* Specify the root folder within your source files. */, "moduleResolution": "node" /* Specify how TypeScript looks up a file from a given module specifier. */, // "baseUrl": "./src" /* Specify the base directory to resolve non-relative module names. */, diff --git a/vdocs/config/Setup.md b/vdocs/config/Setup.md index bcf8ef583..fb58b78ea 100644 --- a/vdocs/config/Setup.md +++ b/vdocs/config/Setup.md @@ -29,7 +29,7 @@ mermaid.initialize({ flowchart: { htmlLabels: false } }); **Example 2:** ```js -var config = { +const config = { startOnLoad: true, flowchart: { useMaxWidth: true, htmlLabels: true, curve: 'cardinal' }, @@ -1560,7 +1560,7 @@ Returns **void** ```html ``` @@ -339,7 +339,7 @@ on what kind of integration you use. ```html ``` diff --git a/vdocs/syntax/classDiagram.md b/vdocs/syntax/classDiagram.md index 87d76cd37..be77f1a1a 100644 --- a/vdocs/syntax/classDiagram.md +++ b/vdocs/syntax/classDiagram.md @@ -399,7 +399,7 @@ click Shape2 call callbackFunction() "This is a tooltip for a callback" ```html @@ -451,10 +451,10 @@ Beginner's tip—a full example using interactive links in an HTML page:
@@ -502,10 +502,10 @@ Beginner's tip—a full example using interactive links in a html context: