diff --git a/.eslintrc.json b/.eslintrc.json
index bb057278f..0da42ae61 100644
--- a/.eslintrc.json
+++ b/.eslintrc.json
@@ -23,6 +23,7 @@
],
"plugins": ["@typescript-eslint", "html", "jest", "jsdoc", "json"],
"rules": {
+ "no-console": "error",
"no-prototype-builtins": "off",
"no-unused-vars": "off",
"jsdoc/check-indentation": "off",
@@ -56,6 +57,12 @@
"no-undef": "off",
"jsdoc/require-jsdoc": "off"
}
+ },
+ {
+ "files": ["./cypress/**", "./demos/**"],
+ "rules": {
+ "no-console": "off"
+ }
}
]
}
diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml
index 44e2f4cb1..e567aba89 100644
--- a/.github/workflows/lint.yml
+++ b/.github/workflows/lint.yml
@@ -40,3 +40,18 @@ jobs:
- name: Verify Docs
run: yarn docs:verify
+
+ - name: Check no `console.log()` in .jison files
+ # ESLint can't parse .jison files directly
+ # In the future, it might be worth making a `eslint-plugin-jison`, so
+ # that this will be built into the `yarn lint` command.
+ run: |
+ shopt -s globstar
+ mkdir -p tmp/
+ for jison_file in src/**/*.jison; do
+ outfile="tmp/$(basename -- "$jison_file" .jison)-jison.js"
+ echo "Converting $jison_file to $outfile"
+ # default module-type (CJS) always adds a console.log()
+ yarn jison "$jison_file" --outfile "$outfile" --module-type "amd"
+ done
+ yarn eslint --no-eslintrc --rule no-console:error --parser "@babel/eslint-parser" "./tmp/*-jison.js"
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index fb7f3bf5a..8171aeca9 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -20,8 +20,8 @@ yarn test
We make all changes via pull requests. As we have many pull requests from developers new to mermaid, the current approach is to have _knsv, Knut Sveidqvist_ as a main reviewer of changes and merging pull requests. More precisely like this:
- Large changes reviewed by knsv or other developer asked to review by knsv
-- Smaller low-risk changes like dependencies, documentation etc can be merged by active collaborators
-- documentation (updates to the docs folder is also allowed via direct commits)
+- Smaller low-risk changes like dependencies, documentation, etc. can be merged by active collaborators
+- Documentation (updates to the `src/docs` folder is also allowed via direct commits)
To commit code, create a branch, let it start with the type like feature or bug followed by the issue number for reference and some describing text.
@@ -37,12 +37,28 @@ Another:
Less strict here, it is OK to commit directly in the `develop` branch if you are a collaborator.
-The documentation is located in the `docs` directory and published using GitHub Pages.
-The documentation site is powered by [Docsify](https://docsify.js.org), a simple documentation site generator.
+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).
-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
-If you want to preview the documentation site on your machine, you need to install `docsify-cli`:
+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.
+
+```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"]
+
+```
+
+**_DO NOT CHANGE FILES IN `/docs`_**
+
+### The official documentation site
+
+**[The mermaid documentation site](https://mermaid-js.github.io/mermaid/) is powered by [Docsify](https://docsify.js.org), a simple documentation site generator.**
+
+If you want to preview the whole documentation site on your machine, you need to install `docsify-cli`:
```sh
$ npm i docsify-cli -g
@@ -121,9 +137,13 @@ 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 docs are located in the docs folder and are ofc written in markdown. Just pick the right section and start typing. If you want to add to the structure as in adding a new section and new file you do that via the \_navbar.md.
+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 changes in master is reflected in https://mermaid-js.github.io/mermaid/ once released the updates are committed to https://mermaid-js.github.io/#/
+#### Adding to or changing the documentation organization
+
+If you want to add a new section or change the organization (structure), then you need to make sure to **change the side navigation** in `src/docs/_sidebar.md`.
+
+When changes are committed and then released, they become part of the `master` branch and become part of the published documentation on https://mermaid-js.github.io/mermaid/
## Last words
diff --git a/cypress/downloads/downloads.html b/cypress/downloads/downloads.html
new file mode 100644
index 000000000..b42fc821e
--- /dev/null
+++ b/cypress/downloads/downloads.html
@@ -0,0 +1 @@
+Cr24
\ No newline at end of file
diff --git a/cypress/integration/rendering/mermaid.spec.js b/cypress/integration/rendering/mermaid.spec.js
new file mode 100644
index 000000000..4b7de3027
--- /dev/null
+++ b/cypress/integration/rendering/mermaid.spec.js
@@ -0,0 +1,75 @@
+import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
+
+describe('Mindmap', () => {
+ it('square shape', () => {
+ imgSnapshotTest(
+ `
+mindmap
+ root[
+ The root
+ ]
+ `,
+ {}
+ );
+ cy.get('svg');
+ });
+ it('rounded rect shape', () => {
+ imgSnapshotTest(
+ `
+mindmap
+ root((
+ The root
+ ))
+ `,
+ {}
+ );
+ cy.get('svg');
+ });
+ it('circle shape', () => {
+ imgSnapshotTest(
+ `
+mindmap
+ root(
+ The root
+ )
+ `,
+ {}
+ );
+ cy.get('svg');
+ });
+ it('default shape', () => {
+ imgSnapshotTest(
+ `
+mindmap
+ The root
+ `,
+ {}
+ );
+ cy.get('svg');
+ });
+ it('adding children', () => {
+ imgSnapshotTest(
+ `
+mindmap
+ The root
+ child1
+ child2
+ `,
+ {}
+ );
+ cy.get('svg');
+ });
+ it('adding grand children', () => {
+ imgSnapshotTest(
+ `
+mindmap
+ The root
+ child1
+ child2
+ child3
+ `,
+ {}
+ );
+ cy.get('svg');
+ });
+});
diff --git a/cypress/integration/rendering/mindmap.spec.js b/cypress/integration/rendering/mindmap.spec.js
new file mode 100644
index 000000000..f0cc2bc3f
--- /dev/null
+++ b/cypress/integration/rendering/mindmap.spec.js
@@ -0,0 +1,115 @@
+import { imgSnapshotTest, renderGraph } from '../../helpers/util.js';
+
+describe('Mindmaps', () => {
+ it('Only a root', () => {
+ imgSnapshotTest(
+ `mindmap
+root
+ `,
+ {}
+ );
+ });
+
+ it('a root with a shape', () => {
+ imgSnapshotTest(
+ `mindmap
+root[root]
+ `,
+ {}
+ );
+ });
+
+ it('a root with wrapping text and a shape', () => {
+ imgSnapshotTest(
+ `mindmap
+root[A root with a long text that wraps to keep the node size in check]
+ `,
+ {}
+ );
+ });
+
+ it('a root with an icon', () => {
+ imgSnapshotTest(
+ `mindmap
+root[root]
+::icon(mdi mdi-fire)
+ `,
+ {}
+ );
+ });
+
+ it('Blang and cloud shape', () => {
+ imgSnapshotTest(
+ `mindmap
+root))bang((
+ ::icon(mdi mdi-fire)
+ a))Another bang((
+ ::icon(mdi mdi-fire)
+ a)A cloud(
+ ::icon(mdi mdi-fire)
+ `,
+ {}
+ );
+ });
+
+ it('Blang and cloud shape with icons', () => {
+ imgSnapshotTest(
+ `mindmap
+root))bang((
+
+ a))Another bang((
+ a)A cloud(
+ `,
+ {}
+ );
+ });
+
+ it('braches', () => {
+ imgSnapshotTest(
+ `mindmap
+root
+ child1
+ grandchild 1
+ grandchild 2
+ child2
+ grandchild 3
+ grandchild 4
+ child3
+ grandchild 5
+ grandchild 6
+ `,
+ {}
+ );
+ });
+
+ it('braches with shapes and labels', () => {
+ imgSnapshotTest(
+ `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)
+ `,
+ {}
+ );
+ });
+ it('text shouhld wrap with icon', () => {
+ imgSnapshotTest(
+ `mindmap
+root
+ Child3(A node with an icon and with a long text that wraps to keep the node size in check)
+ `,
+ {}
+ );
+ });
+
+ /* The end */
+});
diff --git a/docs/_sidebar.md b/docs/_sidebar.md
index bb4814305..40e46d835 100644
--- a/docs/_sidebar.md
+++ b/docs/_sidebar.md
@@ -19,6 +19,7 @@
- [Requirement Diagram](requirementDiagram.md)
- [Gitgraph (Git) Diagram 🔥](gitgraph.md)
- [C4C Diagram (Context) Diagram 🦺⚠️](c4c.md)
+ - [Mindmaps 🦺⚠️](mindmap.md)
- [Other Examples](examples.md)
- ⚙️ Deployment and Configuration
diff --git a/docs/development.md b/docs/development.md
index d5cbe891e..365f639d7 100644
--- a/docs/development.md
+++ b/docs/development.md
@@ -8,7 +8,7 @@ So you want to help? That's great!
Here are a few things to get you started on the right path.
-**The Docs Structure is dictated by [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/docs/_sidebar.md)**
+**The Docs Structure is dictated by [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**
**Note: Commits and Pull Requests should be directed to the develop branch.**
@@ -46,9 +46,9 @@ Start with the type, such as **feature** or **bug**, followed by the issue numbe
If it is not in the documentation, it's like it never happened. Wouldn't that be sad? With all the effort that was put into the feature?
-The docs are located in the `src/docs` folder and are written in Markdown. Just pick the right section and start typing. If you want to propose changes to the structure of the documentation, such as adding a new section or a new file you do that via the **[sidebar](https://github.com/mermaid-js/mermaid/edit/develop/docs/_sidebar.md)**.
+The docs are located in the `src/docs` folder and are written in Markdown. Just pick the right section and start typing. If you want to propose changes to the structure of the documentation, such as adding a new section or a new file you do that via the **[sidebar](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**.
-> **All the documents displayed in the github.io page are listed in [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/docs/_sidebar.md)**.
+> **All the documents displayed in the GitHub.io page are listed in [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**.
The contents of are based on the docs from the `master` branch. Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid-js.github.io/mermaid/) once released.
@@ -60,7 +60,7 @@ The documentation is located in the `src/docs` directory and organized according
The `docs` folder will be automatically generated when committing to `src/docs` and should not be edited manually.
-We encourage contributions to the documentation at [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs). We publish documentation using GitHub Pages with [Docsify](https://www.youtube.com/watch?v=TV88lp7egMw&t=3s)
+We encourage contributions to the documentation at [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs). We publish documentation using GitHub Pages with [Docsify](https://www.youtube.com/watch?v=TV88lp7egMw&t=3s)
### Add Unit Tests for Parsing
@@ -73,7 +73,7 @@ This tests the rendering and visual appearance of the diagrams. This ensures tha
To start working with the e2e tests:
1. Run `yarn dev` to start the dev server
-2. Start **Cypress** by running `cypress open` in the **mermaid** folder.\
+2. Start **Cypress** by running `cypress open` in the **mermaid** folder.
(Make sure you have path to Cypress in order, the binary is located in `node_modules/.bin`).
The rendering tests are very straightforward to create. There is a function `imgSnapshotTest`, which takes a diagram in text form and the mermaid options, and it renders that diagram in Cypress.
@@ -114,7 +114,7 @@ Markdown is used to format the text, for more information about Markdown [see th
To edit Docs on your computer:
-1. Find the Markdown file (.md) to edit in the [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs) directory in the `develop` branch.
+1. Find the Markdown file (.md) to edit in the [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs) directory in the `develop` branch.
2. Create a fork of the develop branch.
3. Make changes or add new documentation.
4. Commit changes to your fork and push it to GitHub.
@@ -123,7 +123,7 @@ To edit Docs on your computer:
To edit Docs on GitHub:
1. Login to [GitHub.com](https://www.github.com).
-2. Navigate to [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs).
+2. Navigate to [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs).
3. To edit a file, click the pencil icon at the top-right of the file contents panel.
4. Describe what you changed in the **Propose file change** section, located at the bottom of the page.
5. Submit your changes by clicking the button **Propose file change** at the bottom (by automatic creation of a fork and a new branch).
diff --git a/docs/directives.md b/docs/directives.md
index e967a4667..943dac53f 100644
--- a/docs/directives.md
+++ b/docs/directives.md
@@ -144,7 +144,7 @@ A --> C[End]
### Changing fontFamily via directive
-The following code snippet changes theme to forest:
+The following code snippet changes fontFamily to rebuchet MS, Verdana, Arial, Sans-Serif:
`%%{init: { "fontFamily": "Trebuchet MS, Verdana, Arial, Sans-Serif" } }%%`
@@ -176,7 +176,7 @@ A --> C[End]
### Changing logLevel via directive
-The following code snippet changes theme to forest:
+The following code snippet changes logLevel to 2:
`%%{init: { "logLevel": 2 } }%%`
diff --git a/docs/mindmap.md b/docs/mindmap.md
new file mode 100644
index 000000000..6ab954f5b
--- /dev/null
+++ b/docs/mindmap.md
@@ -0,0 +1,241 @@
+# THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. Please edit corresponding file in src/docs.
+
+# Mindmap
+
+**Edit this Page** [](https://github.com/mermaid-js/mermaid/blob/develop/src/docs/mindmap.md)
+
+> 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.
+
+"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
+
+### An example of a mindmap.
+
+```mermaid-example
+mindmap
+ root((mindmap))
+ Origins
+ Long history
+ ::icon(fa fa-book)
+ Popularisation
+ British popular psychology author Tony Buzan
+ Research
+ On effectivness
and eatures
+ On Automatic creation
+ Uses
+ Creative techniques
+ Strategic planning
+ Argument mapping
+ Tools
+ Pen and paper
+ Mermaid
+
+```
+
+```mermaid
+mindmap
+ root((mindmap))
+ Origins
+ Long history
+ ::icon(fa fa-book)
+ Popularisation
+ British popular psychology author Tony Buzan
+ Research
+ On effectivness
and eatures
+ On Automatic creation
+ Uses
+ Creative techniques
+ Strategic planning
+ Argument mapping
+ Tools
+ Pen and paper
+ Mermaid
+
+```
+
+## Syntax
+
+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.
+
+ mindmap
+ Root
+ A
+ B
+ C
+
+In summary is a simple text outline where there are one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
+
+```mermaid-example
+mindmap
+Root
+ A
+ B
+ C
+```
+
+```mermaid
+mindmap
+Root
+ A
+ B
+ C
+```
+
+In this way we can use a text outline to generate a hierarchical mindmap.
+
+## Different shapes
+
+Mermaids mindmaps can show node using different shapes. When specifying a shape for a node the syntax for the is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts even though they are not all supported from the start.
+
+Mindmap can show the following shapes:
+
+### Square
+
+```mermaid-example
+mindmap
+ id[I am a square]
+```
+
+```mermaid
+mindmap
+ id[I am a square]
+```
+
+### Rounded square
+
+```mermaid-example
+mindmap
+ id(I am a rounded square)
+```
+
+```mermaid
+mindmap
+ id(I am a rounded square)
+```
+
+### Circle
+
+```mermaid-example
+mindmap
+ id((I am a circle))
+```
+
+```mermaid
+mindmap
+ id((I am a circle))
+```
+
+### Bang
+
+```mermaid-example
+mindmap
+ id))I am a bang((
+```
+
+```mermaid
+mindmap
+ id))I am a bang((
+```
+
+### Cloud
+
+```mermaid-example
+mindmap
+ id)I am a cloud(
+```
+
+```mermaid
+mindmap
+ id)I am a cloud(
+```
+
+### Default
+
+```mermaid-example
+mindmap
+ I am the default shape
+```
+
+```mermaid
+mindmap
+ I am the default shape
+```
+
+More shapes will be added, beginning with the shapes available in flowcharts.
+
+# Icons and classes
+
+## icons
+
+As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and fontawesome 4 are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
+
+```mermaid-example
+mindmap
+ Root
+ A
+ ::icon(fa fa-book)
+ B(B)
+ ::icon(mdi mdi-skull-outline)
+```
+
+```mermaid
+mindmap
+ Root
+ A
+ ::icon(fa fa-book)
+ B(B)
+ ::icon(mdi mdi-skull-outline)
+```
+
+## Classes
+
+Again the syntax for adding classes is similar to flowcharts. You can add classes using a triple colon following a number of css classes separated by space. In the following example one of the nodes has two custom classes attached urgent turning the background red and the text white and large increasing the font size:
+
+```mermaid-example
+mindmap
+ Root
+ A[A]
+ :::urgent large
+ B(B)
+ C
+```
+
+```mermaid
+mindmap
+ Root
+ A[A]
+ :::urgent large
+ B(B)
+ C
+```
+
+_These classes needs top be supplied by the site administrator._
+
+## Unclear indentation
+
+The actual indentation does not really matter only compared with the previous rows. If we take the previous example and disrupt it a little we can se how the calculations are performed. Let us start with placing C with a smaller indentation than `B`but larger then `A`.
+
+ mindmap
+ Root
+ A
+ B
+ C
+
+This outline is unclear as `B` clearly is a child of `A` but when we move on to `C` the clarity is lost. `C` is not a child of `B` with a higher indentation nor does it have the same indentation as `B`. The only thing that is clear is that the first node with smaller indentation, indicating a parent, is A. Then Mermaid relies on this known truth and compensates for the unclear indentation and selects `A` as a parent of `C` leading till the same diagram with `B` and `C` as siblings.
+
+```mermaid-example
+mindmap
+Root
+ A
+ B
+ C
+```
+
+```mermaid
+mindmap
+Root
+ A
+ B
+ C
+```
diff --git a/package.json b/package.json
index 990393eb1..a791f0574 100644
--- a/package.json
+++ b/package.json
@@ -28,11 +28,11 @@
"build": "concurrently \"yarn build:dev\" \"yarn build:prod\"",
"docs:build": "ts-node-esm src/docs.mts",
"docs:verify": "ts-node-esm src/docs.mts --verify",
- "postbuild": "documentation build src/mermaidAPI.ts src/config.ts src/defaultConfig.ts --shallow -f md --markdown-toc false > src/docs/Setup.md; yarn docs:build",
+ "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 && yarn docs:build",
"build:watch": "yarn build:dev --watch",
"release": "yarn build",
- "lint": "eslint --cache --ignore-path .gitignore .; prettier --check .",
- "lint:fix": "eslint --fix --ignore-path .gitignore .; prettier --write .",
+ "lint": "eslint --cache --ignore-path .gitignore . && prettier --check .",
+ "lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .",
"e2e:depr": "yarn lint && jest e2e --config e2e/jest.config.js",
"cypress": "cypress run",
"cypress:open": "cypress open",
@@ -43,7 +43,7 @@
"test": "yarn lint && jest src/.*",
"test:watch": "jest --watch src",
"prepublishOnly": "yarn build && yarn test",
- "prepare": "concurrently \"husky install\" \"yarn build:prod\"",
+ "prepare": "concurrently \"husky install\" \"yarn build:prod\" \"yarn postbuild\"",
"pre-commit": "lint-staged"
},
"repository": {
@@ -67,10 +67,12 @@
"d3": "^7.0.0",
"dagre": "^0.8.5",
"dagre-d3": "^0.6.4",
- "dompurify": "2.4.0",
+ "dompurify": "2.3.10",
+ "fast-clone": "^1.5.13",
"graphlib": "^2.1.8",
"khroma": "^2.0.0",
- "moment-mini": "2.24.0",
+ "moment-mini": "^2.24.0",
+ "non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.0.10"
},
"devDependencies": {
@@ -82,12 +84,12 @@
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.0.0",
"@types/d3": "^7.4.0",
- "@types/dompurify": "^2.3.3",
+ "@types/dompurify": "^2.3.4",
"@types/jest": "^28.1.7",
"@types/stylis": "^4.0.2",
"@typescript-eslint/eslint-plugin": "^5.36.1",
"@typescript-eslint/parser": "^5.36.1",
- "babel-jest": "^29.0.1",
+ "babel-jest": "^29.0.2",
"babel-loader": "^8.2.2",
"concurrently": "^7.0.0",
"coveralls": "^3.0.2",
@@ -107,21 +109,21 @@
"husky": "^8.0.0",
"identity-obj-proxy": "^3.0.0",
"jest": "^28.0.3",
- "jest-environment-jsdom": "^29.0.1",
+ "jest-environment-jsdom": "^29.0.2",
"jison": "^0.4.18",
"js-base64": "3.7.2",
"lint-staged": "^13.0.0",
"moment": "^2.23.0",
"path-browserify": "^1.0.1",
- "prettier": "^2.3.2",
- "prettier-plugin-jsdoc": "^0.3.30",
+ "prettier": "^2.7.1",
+ "prettier-plugin-jsdoc": "^0.4.2",
"remark": "^14.0.2",
"start-server-and-test": "^1.12.6",
"terser-webpack-plugin": "^5.3.6",
"ts-jest": "^28.0.8",
"ts-loader": "^9.3.1",
"ts-node": "^10.9.1",
- "typescript": "^4.7.4",
+ "typescript": "^4.8.2",
"unist-util-flatmap": "^1.0.0",
"webpack": "^5.53.0",
"webpack-cli": "^4.7.2",
diff --git a/src/assignWithDepth.js b/src/assignWithDepth.js
index a74c47945..9f44b01ee 100644
--- a/src/assignWithDepth.js
+++ b/src/assignWithDepth.js
@@ -17,15 +17,6 @@
* Traditional Object.assign would have clobbered foo in config_0 with foo in config_1. If src is a
* destructured array of objects and dst is not an array, assignWithDepth will apply each element
* of src to dst in order.
- * @param dst
- * @param src
- * @param config
- * @param dst
- * @param src
- * @param config
- * @param dst
- * @param src
- * @param config
* @param {any} dst - The destination of the merge
* @param {any} src - The source object(s) to merge into destination
* @param {{ depth: number; clobber: boolean }} [config={ depth: 2, clobber: false }] - Depth: depth
diff --git a/src/config.type.ts b/src/config.type.ts
index 549fbecad..d9d94226f 100644
--- a/src/config.type.ts
+++ b/src/config.type.ts
@@ -27,10 +27,12 @@ export interface MermaidConfig {
er?: ErDiagramConfig;
pie?: PieDiagramConfig;
requirement?: RequirementDiagramConfig;
+ mindmap?: MindmapDiagramConfig;
gitGraph?: GitGraphDiagramConfig;
c4?: C4DiagramConfig;
dompurifyConfig?: DOMPurify.Config;
wrap?: boolean;
+ fontSize?: number;
}
// TODO: More configs needs to be moved in here
@@ -212,6 +214,12 @@ export interface RequirementDiagramConfig extends BaseDiagramConfig {
line_height?: number;
}
+export interface MindmapDiagramConfig extends BaseDiagramConfig {
+ useMaxWidth: boolean;
+ padding: number;
+ maxNodeWidth: number;
+}
+
export type PieDiagramConfig = BaseDiagramConfig;
export interface ErDiagramConfig extends BaseDiagramConfig {
diff --git a/src/dagre-wrapper/mermaid-graphlib.js b/src/dagre-wrapper/mermaid-graphlib.js
index aa7414ff7..fd9e40925 100644
--- a/src/dagre-wrapper/mermaid-graphlib.js
+++ b/src/dagre-wrapper/mermaid-graphlib.js
@@ -163,9 +163,9 @@ export const validate = (graph) => {
};
/**
- * Finds a child that is not a cluster. When faking a edge between a node and a cluster.
+ * Finds a child that is not a cluster. When faking an edge between a node and a cluster.
*
- * @param {Finds a} id
+ * @param id
* @param {any} graph
*/
export const findNonClusterChild = (id, graph) => {
@@ -235,7 +235,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
edges.forEach((edge) => {
// log.debug('Edge, decendants: ', edge, decendants[id]);
- // Check if any edge leaves the cluster (not the actual cluster, thats a link from the box)
+ // Check if any edge leaves the cluster (not the actual cluster, that's a link from the box)
if (edge.v !== id && edge.w !== id) {
// Any edge where either the one of the nodes is decending to the cluster but not the other
// if (decendants[id].indexOf(edge.v) < 0 && decendants[id].indexOf(edge.w) < 0) {
diff --git a/src/defaultConfig.ts b/src/defaultConfig.ts
index 1c1566467..60d867437 100644
--- a/src/defaultConfig.ts
+++ b/src/defaultConfig.ts
@@ -1823,6 +1823,12 @@ const config: Partial = {
external_component_queue_bg_color: '#CCCCCC',
external_component_queue_border_color: '#BFBFBF',
},
+ mindmap: {
+ useMaxWidth: true,
+ padding: 10,
+ maxNodeWidth: 200,
+ },
+ fontSize: 16,
};
if (config.class) config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
diff --git a/src/diagram-api/diagram-orchestration.ts b/src/diagram-api/diagram-orchestration.ts
index 06e8ab7a4..ea0c76ffa 100644
--- a/src/diagram-api/diagram-orchestration.ts
+++ b/src/diagram-api/diagram-orchestration.ts
@@ -1,29 +1,28 @@
import { registerDiagram } from './diagramAPI';
-// import mindmapDb from '../diagrams/mindmap/mindmapDb';
-// import mindmapRenderer from '../diagrams/mindmap/mindmapRenderer';
-// import mindmapParser from '../diagrams/mindmap/parser/mindmapDiagram';
-// import { mindmapDetector } from '../diagrams/mindmap/mindmapDetector';
+import * as mindmapDb from '../diagrams/mindmap/mindmapDb';
+import mindmapRenderer from '../diagrams/mindmap/mindmapRenderer';
+// @ts-ignore: TODO Fix ts errors
+import mindmapParser from '../diagrams/mindmap/parser/mindmap';
+import { mindmapDetector } from '../diagrams/mindmap/mindmapDetector';
+import mindmapStyles from '../diagrams/mindmap/styles';
import gitGraphDb from '../diagrams/git/gitGraphAst';
import gitGraphRenderer from '../diagrams/git/gitGraphRenderer';
// @ts-ignore: TODO Fix ts errors
import gitGraphParser from '../diagrams/git/parser/gitGraph';
import { gitGraphDetector } from '../diagrams/git/gitGraphDetector';
+import gitGraphStyles from '../diagrams/git/styles';
-// Register mindmap and other built-in diagrams
-// registerDiagram(
-// 'mindmap',
-// mindmapParser,
-// mindmapDb,
-// mindmapRenderer,
-// undefined,
-// mindmapRenderer,
-// mindmapDetector
-// );
export const addDiagrams = () => {
+ // Register mindmap and other built-in diagrams
registerDiagram(
'gitGraph',
- { parser: gitGraphParser, db: gitGraphDb, renderer: gitGraphRenderer },
+ { parser: gitGraphParser, db: gitGraphDb, renderer: gitGraphRenderer, styles: gitGraphStyles },
gitGraphDetector
);
+ registerDiagram(
+ 'mindmap',
+ { parser: mindmapParser, db: mindmapDb, renderer: mindmapRenderer, styles: mindmapStyles },
+ mindmapDetector
+ );
};
diff --git a/src/diagram-api/diagramAPI.spec.ts b/src/diagram-api/diagramAPI.spec.ts
index 42c09099a..f72384c11 100644
--- a/src/diagram-api/diagramAPI.spec.ts
+++ b/src/diagram-api/diagramAPI.spec.ts
@@ -19,6 +19,7 @@ describe('DiagramAPI', () => {
db: {},
parser: {},
renderer: {},
+ styles: {},
},
(text: string) => text.includes('loki')
);
diff --git a/src/diagram-api/diagramAPI.ts b/src/diagram-api/diagramAPI.ts
index 35f0f29c4..652e66465 100644
--- a/src/diagram-api/diagramAPI.ts
+++ b/src/diagram-api/diagramAPI.ts
@@ -1,58 +1,75 @@
import c4Db from '../diagrams/c4/c4Db';
import c4Renderer from '../diagrams/c4/c4Renderer';
+import c4Styles from '../diagrams/c4/styles';
// @ts-ignore: TODO Fix ts errors
import c4Parser from '../diagrams/c4/parser/c4Diagram';
import classDb from '../diagrams/class/classDb';
import classRenderer from '../diagrams/class/classRenderer';
import classRendererV2 from '../diagrams/class/classRenderer-v2';
+import classStyles from '../diagrams/class/styles';
// @ts-ignore: TODO Fix ts errors
import classParser from '../diagrams/class/parser/classDiagram';
import erDb from '../diagrams/er/erDb';
import erRenderer from '../diagrams/er/erRenderer';
// @ts-ignore: TODO Fix ts errors
import erParser from '../diagrams/er/parser/erDiagram';
+import erStyles from '../diagrams/er/styles';
import flowDb from '../diagrams/flowchart/flowDb';
import flowRenderer from '../diagrams/flowchart/flowRenderer';
import flowRendererV2 from '../diagrams/flowchart/flowRenderer-v2';
+import flowStyles from '../diagrams/flowchart/styles';
// @ts-ignore: TODO Fix ts errors
import flowParser from '../diagrams/flowchart/parser/flow';
import ganttDb from '../diagrams/gantt/ganttDb';
import ganttRenderer from '../diagrams/gantt/ganttRenderer';
// @ts-ignore: TODO Fix ts errors
import ganttParser from '../diagrams/gantt/parser/gantt';
+import ganttStyles from '../diagrams/gantt/styles';
+
import infoDb from '../diagrams/info/infoDb';
import infoRenderer from '../diagrams/info/infoRenderer';
// @ts-ignore: TODO Fix ts errors
import infoParser from '../diagrams/info/parser/info';
+import infoStyles from '../diagrams/info/styles';
// @ts-ignore: TODO Fix ts errors
import pieParser from '../diagrams/pie/parser/pie';
import pieDb from '../diagrams/pie/pieDb';
import pieRenderer from '../diagrams/pie/pieRenderer';
+import pieStyles from '../diagrams/pie/styles';
// @ts-ignore: TODO Fix ts errors
import requirementParser from '../diagrams/requirement/parser/requirementDiagram';
import requirementDb from '../diagrams/requirement/requirementDb';
import requirementRenderer from '../diagrams/requirement/requirementRenderer';
+import requirementStyles from '../diagrams/requirement/styles';
// @ts-ignore: TODO Fix ts errors
import sequenceParser from '../diagrams/sequence/parser/sequenceDiagram';
import sequenceDb from '../diagrams/sequence/sequenceDb';
import sequenceRenderer from '../diagrams/sequence/sequenceRenderer';
+import sequenceStyles from '../diagrams/sequence/styles';
// @ts-ignore: TODO Fix ts errors
import stateParser from '../diagrams/state/parser/stateDiagram';
import stateDb from '../diagrams/state/stateDb';
import stateRenderer from '../diagrams/state/stateRenderer';
import stateRendererV2 from '../diagrams/state/stateRenderer-v2';
+import stateStyles from '../diagrams/state/styles';
import journeyDb from '../diagrams/user-journey/journeyDb';
import journeyRenderer from '../diagrams/user-journey/journeyRenderer';
+import journeyStyles from '../diagrams/user-journey/styles';
// @ts-ignore: TODO Fix ts errors
import journeyParser from '../diagrams/user-journey/parser/journey';
import { addDetector, DiagramDetector } from './detectType';
-import { log } from '../logger';
+import { log as _log } from '../logger';
+import { getConfig as _getConfig } from '../config';
+import { sanitizeText as _sanitizeText } from '../diagrams/common/common';
import { MermaidConfig } from '../config.type';
+import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox';
+import { addStylesForDiagram } from '../styles';
export interface DiagramDefinition {
db: any;
renderer: any;
parser: any;
+ styles: any;
init?: (config: MermaidConfig) => void;
}
@@ -64,6 +81,7 @@ const diagrams: Record = {
init: (cnf) => {
c4Renderer.setConf(cnf.c4);
},
+ styles: c4Styles,
},
class: {
db: classDb,
@@ -76,6 +94,7 @@ const diagrams: Record = {
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
classDb.clear();
},
+ styles: classStyles,
},
classDiagram: {
db: classDb,
@@ -88,11 +107,13 @@ const diagrams: Record = {
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
classDb.clear();
},
+ styles: classStyles,
},
er: {
db: erDb,
renderer: erRenderer,
parser: erParser,
+ styles: erStyles,
},
flowchart: {
db: flowDb,
@@ -107,6 +128,7 @@ const diagrams: Record = {
flowDb.clear();
flowDb.setGen('gen-1');
},
+ styles: flowStyles,
},
'flowchart-v2': {
db: flowDb,
@@ -121,26 +143,31 @@ const diagrams: Record = {
flowDb.clear();
flowDb.setGen('gen-2');
},
+ styles: flowStyles,
},
gantt: {
db: ganttDb,
renderer: ganttRenderer,
parser: ganttParser,
+ styles: ganttStyles,
},
info: {
db: infoDb,
renderer: infoRenderer,
parser: infoParser,
+ styles: infoStyles,
},
pie: {
db: pieDb,
renderer: pieRenderer,
parser: pieParser,
+ styles: pieStyles,
},
requirement: {
db: requirementDb,
renderer: requirementRenderer,
parser: requirementParser,
+ styles: requirementStyles,
},
sequence: {
db: sequenceDb,
@@ -159,6 +186,7 @@ const diagrams: Record = {
sequenceDb.setWrap(cnf.wrap);
sequenceRenderer.setConf(cnf.sequence);
},
+ styles: sequenceStyles,
},
state: {
db: stateDb,
@@ -171,6 +199,7 @@ const diagrams: Record = {
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateDb.clear();
},
+ styles: stateStyles,
},
stateDiagram: {
db: stateDb,
@@ -183,6 +212,7 @@ const diagrams: Record = {
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
stateDb.clear();
},
+ styles: stateStyles,
},
journey: {
db: journeyDb,
@@ -192,6 +222,7 @@ const diagrams: Record = {
journeyRenderer.setConf(cnf.journey);
journeyDb.clear();
},
+ styles: journeyStyles,
},
};
@@ -205,6 +236,7 @@ export const registerDiagram = (
}
diagrams[id] = diagram;
addDetector(id, detector);
+ addStylesForDiagram(id, diagram.styles);
};
export const getDiagram = (name: string): DiagramDefinition => {
@@ -213,3 +245,8 @@ export const getDiagram = (name: string): DiagramDefinition => {
}
throw new Error(`Diagram ${name} not found.`);
};
+
+export const log = _log;
+export const getConfig = _getConfig;
+export const sanitizeText = (text: string) => _sanitizeText(text, getConfig());
+export const setupGraphViewbox = _setupGraphViewbox;
diff --git a/src/diagram-api/text-wrap b/src/diagram-api/text-wrap
new file mode 100644
index 000000000..173baecec
--- /dev/null
+++ b/src/diagram-api/text-wrap
@@ -0,0 +1,227 @@
+export const lineBreakRegex = /
/gi;
+
+/**
+ * Caches results of functions based on input
+ *
+ * @param {Function} fn Function to run
+ * @param {Function} resolver Function that resolves to an ID given arguments the `fn` takes
+ * @returns {Function} An optimized caching function
+ */
+const memoize = (fn, resolver) => {
+ let cache = {};
+ return (...args) => {
+ let n = resolver ? resolver.apply(this, args) : args[0];
+ if (n in cache) {
+ return cache[n];
+ } else {
+ let result = fn(...args);
+ cache[n] = result;
+ return result;
+ }
+ };
+};
+/**
+ * This calculates the width of the given text, font size and family.
+ *
+ * @param {any} text - The text to calculate the width of
+ * @param {any} config - The config for fontSize, fontFamily, and fontWeight all impacting the resulting size
+ * @returns {any} - The width for the given text
+ */
+export const calculateTextWidth = function (text, config) {
+ config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
+ return calculateTextDimensions(text, config).width;
+};
+
+export const getTextObj = function () {
+ return {
+ x: 0,
+ y: 0,
+ fill: undefined,
+ anchor: 'start',
+ style: '#666',
+ width: 100,
+ height: 100,
+ textMargin: 0,
+ rx: 0,
+ ry: 0,
+ valign: undefined,
+ };
+};
+
+/**
+ * Adds text to an element
+ *
+ * @param {SVGElement} elem Element to add text to
+ * @param {{
+ * text: string;
+ * x: number;
+ * y: number;
+ * anchor: 'start' | 'middle' | 'end';
+ * fontFamily: string;
+ * fontSize: string | number;
+ * fontWeight: string | number;
+ * fill: string;
+ * class: string | undefined;
+ * textMargin: number;
+ * }} textData
+ * @returns {SVGTextElement} Text element with given styling and content
+ */
+export const drawSimpleText = function (elem, textData) {
+ // Remove and ignore br:s
+ const nText = textData.text.replace(lineBreakRegex, ' ');
+
+ const textElem = elem.append('text');
+ textElem.attr('x', textData.x);
+ textElem.attr('y', textData.y);
+ textElem.style('text-anchor', textData.anchor);
+ textElem.style('font-family', textData.fontFamily);
+ textElem.style('font-size', textData.fontSize);
+ textElem.style('font-weight', textData.fontWeight);
+ textElem.attr('fill', textData.fill);
+ if (typeof textData.class !== 'undefined') {
+ textElem.attr('class', textData.class);
+ }
+
+ const span = textElem.append('tspan');
+ span.attr('x', textData.x + textData.textMargin * 2);
+ span.attr('fill', textData.fill);
+ span.text(nText);
+
+ return textElem;
+};
+
+/**
+ * This calculates the dimensions of the given text, font size, font family, font weight, and margins.
+ *
+ * @param {any} text - The text to calculate the width of
+ * @param {any} config - The config for fontSize, fontFamily, fontWeight, and margin all impacting
+ * the resulting size
+ * @returns - The width for the given text
+ */
+export const calculateTextDimensions = memoize(
+ function (text, config) {
+ config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
+ const { fontSize, fontFamily, fontWeight } = config;
+ if (!text) {
+ return { width: 0, height: 0 };
+ }
+
+ // We can't really know if the user supplied font family will render on the user agent;
+ // thus, we'll take the max width between the user supplied font family, and a default
+ // of sans-serif.
+ const fontFamilies = ['sans-serif', fontFamily];
+ const lines = text.split(common.lineBreakRegex);
+ let dims = [];
+
+ const body = select('body');
+ // We don't want to leak DOM elements - if a removal operation isn't available
+ // for any reason, do not continue.
+ if (!body.remove) {
+ return { width: 0, height: 0, lineHeight: 0 };
+ }
+
+ const g = body.append('svg');
+
+ for (let fontFamily of fontFamilies) {
+ let cheight = 0;
+ let dim = { width: 0, height: 0, lineHeight: 0 };
+ for (let line of lines) {
+ const textObj = getTextObj();
+ textObj.text = line;
+ const textElem = drawSimpleText(g, textObj)
+ .style('font-size', fontSize)
+ .style('font-weight', fontWeight)
+ .style('font-family', fontFamily);
+
+ let bBox = (textElem._groups || textElem)[0][0].getBBox();
+ dim.width = Math.round(Math.max(dim.width, bBox.width));
+ cheight = Math.round(bBox.height);
+ dim.height += cheight;
+ dim.lineHeight = Math.round(Math.max(dim.lineHeight, cheight));
+ }
+ dims.push(dim);
+ }
+
+ g.remove();
+
+ let index =
+ isNaN(dims[1].height) ||
+ isNaN(dims[1].width) ||
+ isNaN(dims[1].lineHeight) ||
+ (dims[0].height > dims[1].height &&
+ dims[0].width > dims[1].width &&
+ dims[0].lineHeight > dims[1].lineHeight)
+ ? 0
+ : 1;
+ return dims[index];
+ },
+ (text, config) => `${text}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}`
+);
+
+const breakString = memoize(
+ (word, maxWidth, hyphenCharacter = '-', config) => {
+ config = Object.assign(
+ { fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 0 },
+ config
+ );
+ const characters = word.split('');
+ const lines = [];
+ let currentLine = '';
+ characters.forEach((character, index) => {
+ const nextLine = `${currentLine}${character}`;
+ const lineWidth = calculateTextWidth(nextLine, config);
+ if (lineWidth >= maxWidth) {
+ const currentCharacter = index + 1;
+ const isLastLine = characters.length === currentCharacter;
+ const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`;
+ lines.push(isLastLine ? nextLine : hyphenatedNextLine);
+ currentLine = '';
+ } else {
+ currentLine = nextLine;
+ }
+ });
+ return { hyphenatedStrings: lines, remainingWord: currentLine };
+ },
+ (word, maxWidth, hyphenCharacter = '-', config) =>
+ `${word}-${maxWidth}-${hyphenCharacter}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}`
+);
+
+export const wrapLabel = memoize(
+ (label, maxWidth, config) => {
+ if (!label) {
+ return label;
+ }
+ config = Object.assign(
+ { fontSize: 12, fontWeight: 400, fontFamily: 'Arial', joinWith: '
' },
+ config
+ );
+ if (lineBreakRegex.test(label)) {
+ return label;
+ }
+ const words = label.split(' ');
+ const completedLines = [];
+ let nextLine = '';
+ words.forEach((word, index) => {
+ const wordLength = calculateTextWidth(`${word} `, config);
+ const nextLineLength = calculateTextWidth(nextLine, config);
+ if (wordLength > maxWidth) {
+ const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth, '-', config);
+ completedLines.push(nextLine, ...hyphenatedStrings);
+ nextLine = remainingWord;
+ } else if (nextLineLength + wordLength >= maxWidth) {
+ completedLines.push(nextLine);
+ nextLine = word;
+ } else {
+ nextLine = [nextLine, word].filter(Boolean).join(' ');
+ }
+ const currentWord = index + 1;
+ const isLastWord = currentWord === words.length;
+ if (isLastWord) {
+ completedLines.push(nextLine);
+ }
+ });
+ return completedLines.filter((line) => line !== '').join(config.joinWith);
+ },
+ (label, maxWidth, config) =>
+ `${label}-${maxWidth}-${config.fontSize}-${config.fontWeight}-${config.fontFamily}-${config.joinWith}`
+);
diff --git a/src/diagrams/c4/c4Renderer.js b/src/diagrams/c4/c4Renderer.js
index 6af59bb92..137b89b89 100644
--- a/src/diagrams/c4/c4Renderer.js
+++ b/src/diagrams/c4/c4Renderer.js
@@ -6,7 +6,8 @@ import common from '../common/common';
import c4Db from './c4Db';
import * as configApi from '../../config';
import assignWithDepth from '../../assignWithDepth';
-import { wrapLabel, calculateTextWidth, calculateTextHeight, configureSvgSize } from '../../utils';
+import { wrapLabel, calculateTextWidth, calculateTextHeight } from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import addSVGAccessibilityFields from '../../accessibility';
let globalBoundaryMaxX = 0,
@@ -567,10 +568,9 @@ function drawInsideBoundary(diagram, parentBoundaryAlias, parentBounds, currentB
/**
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
*
- * @param {any} text
- * @param _text
+ * @param {any} _text
* @param {any} id
- * @param _version
+ * @param {any} _version
* @param diagObj
*/
export const draw = function (_text, id, _version, diagObj) {
diff --git a/src/diagrams/c4/parser/c4Boundary.spec.js b/src/diagrams/c4/parser/c4Boundary.spec.js
new file mode 100644
index 000000000..c7130f557
--- /dev/null
+++ b/src/diagrams/c4/parser/c4Boundary.spec.js
@@ -0,0 +1,110 @@
+import c4Db from '../c4Db';
+import c4 from './c4Diagram.jison';
+import { setConfig } from '../../../config';
+
+setConfig({
+ securityLevel: 'strict',
+});
+
+describe.each(['Boundary'])('parsing a C4 %s', function (macroName) {
+ beforeEach(function () {
+ c4.parser.yy = c4Db;
+ c4.parser.yy.clear();
+ });
+
+ it('should parse a C4 diagram with one Boundary correctly', function () {
+ c4.parser.parse(`C4Context
+title System Context diagram for Internet Banking System
+${macroName}(b1, "BankBoundary") {
+System(SystemAA, "Internet Banking System")
+}`);
+
+ const yy = c4.parser.yy;
+
+ const boundaries = yy.getBoundarys();
+ expect(boundaries.length).toBe(2);
+ const boundary = boundaries[1];
+
+ expect(boundary).toEqual({
+ alias: 'b1',
+ label: {
+ text: 'BankBoundary',
+ },
+ // TODO: Why are link, and tags undefined instead of not appearing at all?
+ // Compare to Person where they don't show up.
+ link: undefined,
+ tags: undefined,
+ parentBoundary: 'global',
+ type: {
+ // TODO: Why is this `system` instead of `boundary`?
+ text: 'system',
+ },
+ wrap: false,
+ });
+ });
+
+ it('should parse the alias', function () {
+ c4.parser.parse(`C4Context
+${macroName}(b1, "BankBoundary") {
+System(SystemAA, "Internet Banking System")
+}`);
+
+ expect(c4.parser.yy.getBoundarys()[1]).toMatchObject({
+ alias: 'b1',
+ });
+ });
+
+ it('should parse the label', function () {
+ c4.parser.parse(`C4Context
+${macroName}(b1, "BankBoundary") {
+System(SystemAA, "Internet Banking System")
+}`);
+
+ expect(c4.parser.yy.getBoundarys()[1]).toMatchObject({
+ label: {
+ text: 'BankBoundary',
+ },
+ });
+ });
+
+ it('should parse the type', function () {
+ c4.parser.parse(`C4Context
+${macroName}(b1, "", "company") {
+System(SystemAA, "Internet Banking System")
+}`);
+
+ expect(c4.parser.yy.getBoundarys()[1]).toMatchObject({
+ type: { text: 'company' },
+ });
+ });
+
+ it('should parse a link', function () {
+ c4.parser.parse(`C4Context
+${macroName}(b1, $link="https://github.com/mermaidjs") {
+System(SystemAA, "Internet Banking System")
+}`);
+
+ expect(c4.parser.yy.getBoundarys()[1]).toMatchObject({
+ label: {
+ text: {
+ link: 'https://github.com/mermaidjs',
+ },
+ },
+ });
+ });
+
+ it('should parse tags', function () {
+ c4.parser.parse(`C4Context
+${macroName}(b1, $tags="tag1,tag2") {
+System(SystemAA, "Internet Banking System")
+}`);
+
+ expect(c4.parser.yy.getBoundarys()[1]).toMatchObject({
+ label: {
+ text: {
+ tags: 'tag1,tag2',
+ },
+ },
+ });
+ });
+});
diff --git a/src/diagrams/c4/parser/c4Diagram.jison b/src/diagrams/c4/parser/c4Diagram.jison
index 10783db53..03b851458 100644
--- a/src/diagrams/c4/parser/c4Diagram.jison
+++ b/src/diagrams/c4/parser/c4Diagram.jison
@@ -115,80 +115,80 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
"C4Dynamic" return 'C4_DYNAMIC';
"C4Deployment" return 'C4_DEPLOYMENT';
-"Person_Ext" { this.begin("person_ext"); console.log('begin person_ext'); return 'PERSON_EXT';}
-"Person" { this.begin("person"); console.log('begin person'); return 'PERSON';}
-"SystemQueue_Ext" { this.begin("system_ext_queue"); console.log('begin system_ext_queue'); return 'SYSTEM_EXT_QUEUE';}
-"SystemDb_Ext" { this.begin("system_ext_db"); console.log('begin system_ext_db'); return 'SYSTEM_EXT_DB';}
-"System_Ext" { this.begin("system_ext"); console.log('begin system_ext'); return 'SYSTEM_EXT';}
-"SystemQueue" { this.begin("system_queue"); console.log('begin system_queue'); return 'SYSTEM_QUEUE';}
-"SystemDb" { this.begin("system_db"); console.log('begin system_db'); return 'SYSTEM_DB';}
-"System" { this.begin("system"); console.log('begin system'); return 'SYSTEM';}
+"Person_Ext" { this.begin("person_ext"); return 'PERSON_EXT';}
+"Person" { this.begin("person"); return 'PERSON';}
+"SystemQueue_Ext" { this.begin("system_ext_queue"); return 'SYSTEM_EXT_QUEUE';}
+"SystemDb_Ext" { this.begin("system_ext_db"); return 'SYSTEM_EXT_DB';}
+"System_Ext" { this.begin("system_ext"); return 'SYSTEM_EXT';}
+"SystemQueue" { this.begin("system_queue"); return 'SYSTEM_QUEUE';}
+"SystemDb" { this.begin("system_db"); return 'SYSTEM_DB';}
+"System" { this.begin("system"); return 'SYSTEM';}
-"Boundary" { this.begin("boundary"); console.log('begin boundary'); return 'BOUNDARY';}
-"Enterprise_Boundary" { this.begin("enterprise_boundary"); console.log('begin enterprise_boundary'); return 'ENTERPRISE_BOUNDARY';}
-"System_Boundary" { this.begin("system_boundary"); console.log('begin system_boundary'); return 'SYSTEM_BOUNDARY';}
+"Boundary" { this.begin("boundary"); return 'BOUNDARY';}
+"Enterprise_Boundary" { this.begin("enterprise_boundary"); return 'ENTERPRISE_BOUNDARY';}
+"System_Boundary" { this.begin("system_boundary"); return 'SYSTEM_BOUNDARY';}
-"ContainerQueue_Ext" { this.begin("container_ext_queue"); console.log('begin container_ext_queue'); return 'CONTAINER_EXT_QUEUE';}
-"ContainerDb_Ext" { this.begin("container_ext_db"); console.log('begin container_ext_db'); return 'CONTAINER_EXT_DB';}
-"Container_Ext" { this.begin("container_ext"); console.log('begin container_ext'); return 'CONTAINER_EXT';}
-"ContainerQueue" { this.begin("container_queue"); console.log('begin container_queue'); return 'CONTAINER_QUEUE';}
-"ContainerDb" { this.begin("container_db"); console.log('begin container_db'); return 'CONTAINER_DB';}
-"Container" { this.begin("container"); console.log('begin container'); return 'CONTAINER';}
+"ContainerQueue_Ext" { this.begin("container_ext_queue"); return 'CONTAINER_EXT_QUEUE';}
+"ContainerDb_Ext" { this.begin("container_ext_db"); return 'CONTAINER_EXT_DB';}
+"Container_Ext" { this.begin("container_ext"); return 'CONTAINER_EXT';}
+"ContainerQueue" { this.begin("container_queue"); return 'CONTAINER_QUEUE';}
+"ContainerDb" { this.begin("container_db"); return 'CONTAINER_DB';}
+"Container" { this.begin("container"); return 'CONTAINER';}
-"Container_Boundary" { this.begin("container_boundary"); console.log('begin container_boundary'); return 'CONTAINER_BOUNDARY';}
+"Container_Boundary" { this.begin("container_boundary"); return 'CONTAINER_BOUNDARY';}
-"ComponentQueue_Ext" { this.begin("component_ext_queue"); console.log('begin component_ext_queue'); return 'COMPONENT_EXT_QUEUE';}
-"ComponentDb_Ext" { this.begin("component_ext_db"); console.log('begin component_ext_db'); return 'COMPONENT_EXT_DB';}
-"Component_Ext" { this.begin("component_ext"); console.log('begin component_ext'); return 'COMPONENT_EXT';}
-"ComponentQueue" { this.begin("component_queue"); console.log('begin component_queue'); return 'COMPONENT_QUEUE';}
-"ComponentDb" { this.begin("component_db"); console.log('begin component_db'); return 'COMPONENT_DB';}
-"Component" { this.begin("component"); console.log('begin component'); return 'COMPONENT';}
+"ComponentQueue_Ext" { this.begin("component_ext_queue"); return 'COMPONENT_EXT_QUEUE';}
+"ComponentDb_Ext" { this.begin("component_ext_db"); return 'COMPONENT_EXT_DB';}
+"Component_Ext" { this.begin("component_ext"); return 'COMPONENT_EXT';}
+"ComponentQueue" { this.begin("component_queue"); return 'COMPONENT_QUEUE';}
+"ComponentDb" { this.begin("component_db"); return 'COMPONENT_DB';}
+"Component" { this.begin("component"); return 'COMPONENT';}
-"Deployment_Node" { this.begin("node"); console.log('begin node'); return 'NODE';}
-"Node" { this.begin("node"); console.log('begin node'); return 'NODE';}
-"Node_L" { this.begin("node_l"); console.log('begin node_l'); return 'NODE_L';}
-"Node_R" { this.begin("node_r"); console.log('begin node_r'); return 'NODE_R';}
+"Deployment_Node" { this.begin("node"); return 'NODE';}
+"Node" { this.begin("node"); return 'NODE';}
+"Node_L" { this.begin("node_l"); return 'NODE_L';}
+"Node_R" { this.begin("node_r"); return 'NODE_R';}
-"Rel" { this.begin("rel"); console.log('begin rel'); return 'REL';}
-"BiRel" { this.begin("birel"); console.log('begin birel'); return 'BIREL';}
-"Rel_Up" { this.begin("rel_u"); console.log('begin rel_u'); return 'REL_U';}
-"Rel_U" { this.begin("rel_u"); console.log('begin rel_u'); return 'REL_U';}
-"Rel_Down" { this.begin("rel_d"); console.log('begin rel_d'); return 'REL_D';}
-"Rel_D" { this.begin("rel_d"); console.log('begin rel_d'); return 'REL_D';}
-"Rel_Left" { this.begin("rel_l"); console.log('begin rel_l'); return 'REL_L';}
-"Rel_L" { this.begin("rel_l"); console.log('begin rel_l'); return 'REL_L';}
-"Rel_Right" { this.begin("rel_r"); console.log('begin rel_r'); return 'REL_R';}
-"Rel_R" { this.begin("rel_r"); console.log('begin rel_r'); return 'REL_R';}
-"Rel_Back" { this.begin("rel_b"); console.log('begin rel_b'); return 'REL_B';}
-"RelIndex" { this.begin("rel_index"); console.log('begin rel_index'); return 'REL_INDEX';}
+"Rel" { this.begin("rel"); return 'REL';}
+"BiRel" { this.begin("birel"); return 'BIREL';}
+"Rel_Up" { this.begin("rel_u"); return 'REL_U';}
+"Rel_U" { this.begin("rel_u"); return 'REL_U';}
+"Rel_Down" { this.begin("rel_d"); return 'REL_D';}
+"Rel_D" { this.begin("rel_d"); return 'REL_D';}
+"Rel_Left" { this.begin("rel_l"); return 'REL_L';}
+"Rel_L" { this.begin("rel_l"); return 'REL_L';}
+"Rel_Right" { this.begin("rel_r"); return 'REL_R';}
+"Rel_R" { this.begin("rel_r"); return 'REL_R';}
+"Rel_Back" { this.begin("rel_b"); return 'REL_B';}
+"RelIndex" { this.begin("rel_index"); return 'REL_INDEX';}
-"UpdateElementStyle" { this.begin("update_el_style"); console.log('begin update_el_style'); return 'UPDATE_EL_STYLE';}
-"UpdateRelStyle" { this.begin("update_rel_style"); console.log('begin update_rel_style'); return 'UPDATE_REL_STYLE';}
-"UpdateLayoutConfig" { this.begin("update_layout_config"); console.log('begin update_layout_config'); return 'UPDATE_LAYOUT_CONFIG';}
+"UpdateElementStyle" { this.begin("update_el_style"); return 'UPDATE_EL_STYLE';}
+"UpdateRelStyle" { this.begin("update_rel_style"); return 'UPDATE_REL_STYLE';}
+"UpdateLayoutConfig" { this.begin("update_layout_config"); return 'UPDATE_LAYOUT_CONFIG';}
<> return "EOF_IN_STRUCT";
-[(][ ]*[,] { console.log('begin attribute with ATTRIBUTE_EMPTY'); this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
-[(] { console.log('begin attribute'); this.begin("attribute"); }
-[)] { console.log('STOP attribute'); this.popState();console.log('STOP diagram'); this.popState();}
+[(][ ]*[,] { this.begin("attribute"); return "ATTRIBUTE_EMPTY";}
+[(] { this.begin("attribute"); }
+[)] { this.popState();this.popState();}
-",," { console.log(',,'); return 'ATTRIBUTE_EMPTY';}
-"," { console.log(','); }
-[ ]*["]["] { console.log('ATTRIBUTE_EMPTY'); return 'ATTRIBUTE_EMPTY';}
-[ ]*["] { console.log('begin string'); this.begin("string");}
-["] { console.log('STOP string'); this.popState(); }
-[^"]* { console.log('STR'); return "STR";}
+",," { return 'ATTRIBUTE_EMPTY';}
+"," { }
+[ ]*["]["] { return 'ATTRIBUTE_EMPTY';}
+[ ]*["] { this.begin("string");}
+["] { this.popState(); }
+[^"]* { return "STR";}
-[ ]*[\$] { console.log('begin string_kv'); this.begin("string_kv");}
-[^=]* { console.log('STR_KEY'); this.begin("string_kv_key"); return "STR_KEY";}
-[=][ ]*["] { console.log('begin string_kv_value'); this.popState(); this.begin("string_kv_value"); }
-[^"]+ { console.log('STR_VALUE'); return "STR_VALUE";}
-["] { console.log('STOP string_kv_value'); this.popState(); this.popState(); }
+[ ]*[\$] { this.begin("string_kv");}
+[^=]* { this.begin("string_kv_key"); return "STR_KEY";}
+[=][ ]*["] { this.popState(); this.begin("string_kv_value"); }
+[^"]+ { return "STR_VALUE";}
+["] { this.popState(); this.popState(); }
-[^,]+ { console.log('not STR'); return "STR";}
+[^,]+ { return "STR";}
-'{' { /* this.begin("lbrace"); */ console.log('begin boundary block'); return "LBRACE";}
-'}' { /* this.popState(); */ console.log('STOP boundary block'); return "RBRACE";}
+'{' { /* this.begin("lbrace"); */ return "LBRACE";}
+'}' { /* this.popState(); */ return "RBRACE";}
[\s]+ return 'SPACE';
[\n\r]+ return 'EOL';
@@ -231,7 +231,7 @@ directive
;
openDirective
- : open_directive { console.log("open_directive: ", $1); yy.parseDirective('%%{', 'open_directive'); }
+ : open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
@@ -239,11 +239,11 @@ typeDirective
;
argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); console.log("arg_directive: ", $1); yy.parseDirective($1, 'arg_directive'); }
+ : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
- : close_directive { console.log("close_directive: ", $1); yy.parseDirective('}%%', 'close_directive', 'c4Context'); }
+ : close_directive { yy.parseDirective('}%%', 'close_directive', 'c4Context'); }
;
graphConfig
@@ -285,13 +285,13 @@ boundaryStartStatement
;
boundaryStart
- : ENTERPRISE_BOUNDARY attributes {console.log($1,JSON.stringify($2)); $2.splice(2, 0, 'ENTERPRISE'); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
- | SYSTEM_BOUNDARY attributes {console.log($1,JSON.stringify($2)); $2.splice(2, 0, 'ENTERPRISE'); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
- | BOUNDARY attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
- | CONTAINER_BOUNDARY attributes {console.log($1,JSON.stringify($2)); $2.splice(2, 0, 'CONTAINER'); yy.addContainerBoundary(...$2); $$=$2;}
- | NODE attributes {console.log($1,JSON.stringify($2)); yy.addDeploymentNode('node', ...$2); $$=$2;}
- | NODE_L attributes {console.log($1,JSON.stringify($2)); yy.addDeploymentNode('nodeL', ...$2); $$=$2;}
- | NODE_R attributes {console.log($1,JSON.stringify($2)); yy.addDeploymentNode('nodeR', ...$2); $$=$2;}
+ : ENTERPRISE_BOUNDARY attributes {$2.splice(2, 0, 'ENTERPRISE'); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
+ | SYSTEM_BOUNDARY attributes {$2.splice(2, 0, 'ENTERPRISE'); yy.addPersonOrSystemBoundary(...$2); $$=$2;}
+ | BOUNDARY attributes {yy.addPersonOrSystemBoundary(...$2); $$=$2;}
+ | CONTAINER_BOUNDARY attributes {$2.splice(2, 0, 'CONTAINER'); yy.addContainerBoundary(...$2); $$=$2;}
+ | NODE attributes {yy.addDeploymentNode('node', ...$2); $$=$2;}
+ | NODE_L attributes {yy.addDeploymentNode('nodeL', ...$2); $$=$2;}
+ | NODE_R attributes {yy.addDeploymentNode('nodeR', ...$2); $$=$2;}
;
boundaryStopStatement
@@ -305,48 +305,48 @@ diagramStatements
;
diagramStatement
- : PERSON attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('person', ...$2); $$=$2;}
- | PERSON_EXT attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_person', ...$2); $$=$2;}
- | SYSTEM attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('system', ...$2); $$=$2;}
- | SYSTEM_DB attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('system_db', ...$2); $$=$2;}
- | SYSTEM_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('system_queue', ...$2); $$=$2;}
- | SYSTEM_EXT attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_system', ...$2); $$=$2;}
- | SYSTEM_EXT_DB attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_system_db', ...$2); $$=$2;}
- | SYSTEM_EXT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addPersonOrSystem('external_system_queue', ...$2); $$=$2;}
- | CONTAINER attributes {console.log($1,JSON.stringify($2)); yy.addContainer('container', ...$2); $$=$2;}
- | CONTAINER_DB attributes {console.log($1,JSON.stringify($2)); yy.addContainer('container_db', ...$2); $$=$2;}
- | CONTAINER_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addContainer('container_queue', ...$2); $$=$2;}
- | CONTAINER_EXT attributes {console.log($1,JSON.stringify($2)); yy.addContainer('external_container', ...$2); $$=$2;}
- | CONTAINER_EXT_DB attributes {console.log($1,JSON.stringify($2)); yy.addContainer('external_container_db', ...$2); $$=$2;}
- | CONTAINER_EXT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addContainer('external_container_queue', ...$2); $$=$2;}
- | COMPONENT attributes {console.log($1,JSON.stringify($2)); yy.addComponent('component', ...$2); $$=$2;}
- | COMPONENT_DB attributes {console.log($1,JSON.stringify($2)); yy.addComponent('component_db', ...$2); $$=$2;}
- | COMPONENT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addComponent('component_queue', ...$2); $$=$2;}
- | COMPONENT_EXT attributes {console.log($1,JSON.stringify($2)); yy.addComponent('external_component', ...$2); $$=$2;}
- | COMPONENT_EXT_DB attributes {console.log($1,JSON.stringify($2)); yy.addComponent('external_component_db', ...$2); $$=$2;}
- | COMPONENT_EXT_QUEUE attributes {console.log($1,JSON.stringify($2)); yy.addComponent('external_component_queue', ...$2); $$=$2;}
+ : PERSON attributes {yy.addPersonOrSystem('person', ...$2); $$=$2;}
+ | PERSON_EXT attributes {yy.addPersonOrSystem('external_person', ...$2); $$=$2;}
+ | SYSTEM attributes {yy.addPersonOrSystem('system', ...$2); $$=$2;}
+ | SYSTEM_DB attributes {yy.addPersonOrSystem('system_db', ...$2); $$=$2;}
+ | SYSTEM_QUEUE attributes {yy.addPersonOrSystem('system_queue', ...$2); $$=$2;}
+ | SYSTEM_EXT attributes {yy.addPersonOrSystem('external_system', ...$2); $$=$2;}
+ | SYSTEM_EXT_DB attributes {yy.addPersonOrSystem('external_system_db', ...$2); $$=$2;}
+ | SYSTEM_EXT_QUEUE attributes {yy.addPersonOrSystem('external_system_queue', ...$2); $$=$2;}
+ | CONTAINER attributes {yy.addContainer('container', ...$2); $$=$2;}
+ | CONTAINER_DB attributes {yy.addContainer('container_db', ...$2); $$=$2;}
+ | CONTAINER_QUEUE attributes {yy.addContainer('container_queue', ...$2); $$=$2;}
+ | CONTAINER_EXT attributes {yy.addContainer('external_container', ...$2); $$=$2;}
+ | CONTAINER_EXT_DB attributes {yy.addContainer('external_container_db', ...$2); $$=$2;}
+ | CONTAINER_EXT_QUEUE attributes {yy.addContainer('external_container_queue', ...$2); $$=$2;}
+ | COMPONENT attributes {yy.addComponent('component', ...$2); $$=$2;}
+ | COMPONENT_DB attributes {yy.addComponent('component_db', ...$2); $$=$2;}
+ | COMPONENT_QUEUE attributes {yy.addComponent('component_queue', ...$2); $$=$2;}
+ | COMPONENT_EXT attributes {yy.addComponent('external_component', ...$2); $$=$2;}
+ | COMPONENT_EXT_DB attributes {yy.addComponent('external_component_db', ...$2); $$=$2;}
+ | COMPONENT_EXT_QUEUE attributes {yy.addComponent('external_component_queue', ...$2); $$=$2;}
| boundaryStatement
- | REL attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel', ...$2); $$=$2;}
- | BIREL attributes {console.log($1,JSON.stringify($2)); yy.addRel('birel', ...$2); $$=$2;}
- | REL_U attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_u', ...$2); $$=$2;}
- | REL_D attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_d', ...$2); $$=$2;}
- | REL_L attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_l', ...$2); $$=$2;}
- | REL_R attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_r', ...$2); $$=$2;}
- | REL_B attributes {console.log($1,JSON.stringify($2)); yy.addRel('rel_b', ...$2); $$=$2;}
- | REL_INDEX attributes {console.log($1,JSON.stringify($2)); $2.splice(0, 1); yy.addRel('rel', ...$2); $$=$2;}
- | UPDATE_EL_STYLE attributes {console.log($1,JSON.stringify($2)); yy.updateElStyle('update_el_style', ...$2); $$=$2;}
- | UPDATE_REL_STYLE attributes {console.log($1,JSON.stringify($2)); yy.updateRelStyle('update_rel_style', ...$2); $$=$2;}
- | UPDATE_LAYOUT_CONFIG attributes {console.log($1,JSON.stringify($2)); yy.updateLayoutConfig('update_layout_config', ...$2); $$=$2;}
+ | REL attributes {yy.addRel('rel', ...$2); $$=$2;}
+ | BIREL attributes {yy.addRel('birel', ...$2); $$=$2;}
+ | REL_U attributes {yy.addRel('rel_u', ...$2); $$=$2;}
+ | REL_D attributes {yy.addRel('rel_d', ...$2); $$=$2;}
+ | REL_L attributes {yy.addRel('rel_l', ...$2); $$=$2;}
+ | REL_R attributes {yy.addRel('rel_r', ...$2); $$=$2;}
+ | REL_B attributes {yy.addRel('rel_b', ...$2); $$=$2;}
+ | REL_INDEX attributes {$2.splice(0, 1); yy.addRel('rel', ...$2); $$=$2;}
+ | UPDATE_EL_STYLE attributes {yy.updateElStyle('update_el_style', ...$2); $$=$2;}
+ | UPDATE_REL_STYLE attributes {yy.updateRelStyle('update_rel_style', ...$2); $$=$2;}
+ | UPDATE_LAYOUT_CONFIG attributes {yy.updateLayoutConfig('update_layout_config', ...$2); $$=$2;}
;
attributes
- : attribute { console.log('PUSH ATTRIBUTE: ', $1); $$ = [$1]; }
- | attribute attributes { console.log('PUSH ATTRIBUTE: ', $1); $2.unshift($1); $$=$2;}
+ : attribute { $$ = [$1]; }
+ | attribute attributes { $2.unshift($1); $$=$2;}
;
attribute
: STR { $$ = $1.trim(); }
- | STR_KEY STR_VALUE { console.log('kv: ', $1, $2); let kv={}; kv[$1.trim()]=$2.trim(); $$=kv; }
+ | STR_KEY STR_VALUE { let kv={}; kv[$1.trim()]=$2.trim(); $$=kv; }
| ATTRIBUTE { $$ = $1.trim(); }
| ATTRIBUTE_EMPTY { $$ = ""; }
;
diff --git a/src/diagrams/c4/parser/flow.spec.js b/src/diagrams/c4/parser/c4Diagram.spec.js
similarity index 50%
rename from src/diagrams/c4/parser/flow.spec.js
rename to src/diagrams/c4/parser/c4Diagram.spec.js
index c01d99e40..b79934b84 100644
--- a/src/diagrams/c4/parser/flow.spec.js
+++ b/src/diagrams/c4/parser/c4Diagram.spec.js
@@ -1,49 +1,20 @@
-import flowDb from '../c4Db';
-import flow from './c4Diagram.jison';
+import c4Db from '../c4Db';
+import c4 from './c4Diagram.jison';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict',
});
-describe('parsing a flow chart', function () {
+describe('parsing a C4 diagram', function () {
beforeEach(function () {
- flow.parser.yy = flowDb;
- flow.parser.yy.clear();
- });
-
- it('should parse a C4 diagram with one Person correctly', function () {
- flow.parser.parse(`C4Context
-title System Context diagram for Internet Banking System
-Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")`);
-
- const yy = flow.parser.yy;
- expect(yy.getC4Type()).toBe('C4Context');
- expect(yy.getTitle()).toBe('System Context diagram for Internet Banking System');
-
- const shapes = yy.getC4ShapeArray();
- expect(shapes.length).toBe(1);
- const onlyShape = shapes[0];
-
- expect(onlyShape).toEqual({
- alias: 'customerA',
- descr: {
- text: 'A customer of the bank, with personal bank accounts.',
- },
- label: {
- text: 'Banking Customer A',
- },
- parentBoundary: 'global',
- typeC4Shape: {
- text: 'person',
- },
- wrap: false,
- });
+ c4.parser.yy = c4Db;
+ c4.parser.yy.clear();
});
it('should handle a trailing whitespaces after statements', function () {
const whitespace = ' ';
- const rendered = flow.parser.parse(`C4Context${whitespace}
+ const rendered = c4.parser.parse(`C4Context${whitespace}
title System Context diagram for Internet Banking System${whitespace}
Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")${whitespace}`);
@@ -51,11 +22,11 @@ Person(customerA, "Banking Customer A", "A customer of the bank, with personal b
});
it('should handle parameter names that are keywords', function () {
- flow.parser.parse(`C4Context
+ c4.parser.parse(`C4Context
title title
Person(Person, "Person", "Person")`);
- const yy = flow.parser.yy;
+ const yy = c4.parser.yy;
expect(yy.getTitle()).toBe('title');
const shapes = yy.getC4ShapeArray();
@@ -68,10 +39,10 @@ Person(Person, "Person", "Person")`);
});
it('should allow default in the parameters', function () {
- flow.parser.parse(`C4Context
+ c4.parser.parse(`C4Context
Person(default, "default", "default")`);
- const yy = flow.parser.yy;
+ const yy = c4.parser.yy;
const shapes = yy.getC4ShapeArray();
expect(shapes.length).toBe(1);
diff --git a/src/diagrams/c4/parser/c4Person.spec.js b/src/diagrams/c4/parser/c4Person.spec.js
new file mode 100644
index 000000000..b504c0384
--- /dev/null
+++ b/src/diagrams/c4/parser/c4Person.spec.js
@@ -0,0 +1,111 @@
+import c4Db from '../c4Db';
+import c4 from './c4Diagram.jison';
+import { setConfig } from '../../../config';
+
+setConfig({
+ securityLevel: 'strict',
+});
+
+describe('parsing a C4 Person', function () {
+ beforeEach(function () {
+ c4.parser.yy = c4Db;
+ c4.parser.yy.clear();
+ });
+
+ it('should parse a C4 diagram with one Person correctly', function () {
+ c4.parser.parse(`C4Context
+title System Context diagram for Internet Banking System
+Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")`);
+
+ const yy = c4.parser.yy;
+
+ const shapes = yy.getC4ShapeArray();
+ expect(shapes.length).toBe(1);
+ const onlyShape = shapes[0];
+
+ expect(onlyShape).toEqual({
+ alias: 'customerA',
+ descr: {
+ text: 'A customer of the bank, with personal bank accounts.',
+ },
+ label: {
+ text: 'Banking Customer A',
+ },
+ parentBoundary: 'global',
+ typeC4Shape: {
+ text: 'person',
+ },
+ wrap: false,
+ });
+ });
+
+ it('should parse the alias', function () {
+ c4.parser.parse(`C4Context
+Person(customerA, "Banking Customer A")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ alias: 'customerA',
+ });
+ });
+
+ it('should parse the label', function () {
+ c4.parser.parse(`C4Context
+Person(customerA, "Banking Customer A")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: 'Banking Customer A',
+ },
+ });
+ });
+
+ it('should parse the description', function () {
+ c4.parser.parse(`C4Context
+Person(customerA, "", "A customer of the bank, with personal bank accounts.")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ descr: {
+ text: 'A customer of the bank, with personal bank accounts.',
+ },
+ });
+ });
+
+ it('should parse a sprite', function () {
+ c4.parser.parse(`C4Context
+Person(customerA, $sprite="users")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: {
+ sprite: 'users',
+ },
+ },
+ });
+ });
+
+ it('should parse a link', function () {
+ c4.parser.parse(`C4Context
+Person(customerA, $link="https://github.com/mermaidjs")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: {
+ link: 'https://github.com/mermaidjs',
+ },
+ },
+ });
+ });
+
+ it('should parse tags', function () {
+ c4.parser.parse(`C4Context
+Person(customerA, $tags="tag1,tag2")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: {
+ tags: 'tag1,tag2',
+ },
+ },
+ });
+ });
+});
diff --git a/src/diagrams/c4/parser/c4PersonExt.spec.js b/src/diagrams/c4/parser/c4PersonExt.spec.js
new file mode 100644
index 000000000..76547600a
--- /dev/null
+++ b/src/diagrams/c4/parser/c4PersonExt.spec.js
@@ -0,0 +1,116 @@
+import c4Db from '../c4Db';
+import c4 from './c4Diagram.jison';
+import { setConfig } from '../../../config';
+
+setConfig({
+ securityLevel: 'strict',
+});
+
+describe('parsing a C4 Person_Ext', function () {
+ beforeEach(function () {
+ c4.parser.yy = c4Db;
+ c4.parser.yy.clear();
+ });
+
+ it('should parse a C4 diagram with one Person_Ext correctly', function () {
+ c4.parser.parse(`C4Context
+title System Context diagram for Internet Banking System
+Person_Ext(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.")`);
+
+ const yy = c4.parser.yy;
+
+ const shapes = yy.getC4ShapeArray();
+ expect(shapes.length).toBe(1);
+ const onlyShape = shapes[0];
+
+ expect(onlyShape).toEqual({
+ alias: 'customerA',
+ descr: {
+ text: 'A customer of the bank, with personal bank accounts.',
+ },
+ label: {
+ text: 'Banking Customer A',
+ },
+ // TODO: Why are link, sprite, and tags undefined instead of not appearing at all?
+ // Compare to Person where they don't show up.
+ link: undefined,
+ sprite: undefined,
+ tags: undefined,
+ parentBoundary: 'global',
+ typeC4Shape: {
+ text: 'external_person',
+ },
+ wrap: false,
+ });
+ });
+
+ it('should parse the alias', function () {
+ c4.parser.parse(`C4Context
+Person_Ext(customerA, "Banking Customer A")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ alias: 'customerA',
+ });
+ });
+
+ it('should parse the label', function () {
+ c4.parser.parse(`C4Context
+Person_Ext(customerA, "Banking Customer A")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: 'Banking Customer A',
+ },
+ });
+ });
+
+ it('should parse the description', function () {
+ c4.parser.parse(`C4Context
+Person_Ext(customerA, "", "A customer of the bank, with personal bank accounts.")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ descr: {
+ text: 'A customer of the bank, with personal bank accounts.',
+ },
+ });
+ });
+
+ it('should parse a sprite', function () {
+ c4.parser.parse(`C4Context
+Person_Ext(customerA, $sprite="users")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: {
+ sprite: 'users',
+ },
+ },
+ });
+ });
+
+ it('should parse a link', function () {
+ c4.parser.parse(`C4Context
+Person_Ext(customerA, $link="https://github.com/mermaidjs")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: {
+ link: 'https://github.com/mermaidjs',
+ },
+ },
+ });
+ });
+
+ it('should parse tags', function () {
+ c4.parser.parse(`C4Context
+Person_Ext(customerA, $tags="tag1,tag2")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: {
+ tags: 'tag1,tag2',
+ },
+ },
+ });
+ });
+});
diff --git a/src/diagrams/c4/parser/c4System.spec.js b/src/diagrams/c4/parser/c4System.spec.js
new file mode 100644
index 000000000..6d81c7379
--- /dev/null
+++ b/src/diagrams/c4/parser/c4System.spec.js
@@ -0,0 +1,123 @@
+import c4Db from '../c4Db';
+import c4 from './c4Diagram.jison';
+import { setConfig } from '../../../config';
+
+setConfig({
+ securityLevel: 'strict',
+});
+
+describe.each([
+ ['System', 'system'],
+ ['SystemDb', 'system_db'],
+ ['SystemQueue', 'system_queue'],
+ ['System_Ext', 'external_system'],
+ ['SystemDb_Ext', 'external_system_db'],
+ ['SystemQueue_Ext', 'external_system_queue'],
+])('parsing a C4 %s', function (macroName, elementName) {
+ beforeEach(function () {
+ c4.parser.yy = c4Db;
+ c4.parser.yy.clear();
+ });
+
+ it('should parse a C4 diagram with one System correctly', function () {
+ c4.parser.parse(`C4Context
+title System Context diagram for Internet Banking System
+${macroName}(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.")`);
+
+ const yy = c4.parser.yy;
+
+ const shapes = yy.getC4ShapeArray();
+ expect(shapes.length).toBe(1);
+ const onlyShape = shapes[0];
+
+ expect(onlyShape).toEqual({
+ alias: 'SystemAA',
+ descr: {
+ text: 'Allows customers to view information about their bank accounts, and make payments.',
+ },
+ label: {
+ text: 'Internet Banking System',
+ },
+ // TODO: Why are link, sprite, and tags undefined instead of not appearing at all?
+ // Compare to Person where they don't show up.
+ link: undefined,
+ sprite: undefined,
+ tags: undefined,
+ parentBoundary: 'global',
+ typeC4Shape: {
+ text: elementName,
+ },
+ wrap: false,
+ });
+ });
+
+ it('should parse the alias', function () {
+ c4.parser.parse(`C4Context
+${macroName}(SystemAA, "Internet Banking System")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ alias: 'SystemAA',
+ });
+ });
+
+ it('should parse the label', function () {
+ c4.parser.parse(`C4Context
+${macroName}(SystemAA, "Internet Banking System")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: 'Internet Banking System',
+ },
+ });
+ });
+
+ it('should parse the description', function () {
+ c4.parser.parse(`C4Context
+${macroName}(SystemAA, "", "Allows customers to view information about their bank accounts, and make payments.")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ descr: {
+ text: 'Allows customers to view information about their bank accounts, and make payments.',
+ },
+ });
+ });
+
+ it('should parse a sprite', function () {
+ c4.parser.parse(`C4Context
+${macroName}(SystemAA, $sprite="users")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: {
+ sprite: 'users',
+ },
+ },
+ });
+ });
+
+ it('should parse a link', function () {
+ c4.parser.parse(`C4Context
+${macroName}(SystemAA, $link="https://github.com/mermaidjs")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: {
+ link: 'https://github.com/mermaidjs',
+ },
+ },
+ });
+ });
+
+ it('should parse tags', function () {
+ c4.parser.parse(`C4Context
+${macroName}(SystemAA, $tags="tag1,tag2")`);
+
+ expect(c4.parser.yy.getC4ShapeArray()[0]).toMatchObject({
+ label: {
+ text: {
+ tags: 'tag1,tag2',
+ },
+ },
+ });
+ });
+});
diff --git a/src/diagrams/class/classRenderer-v2.js b/src/diagrams/class/classRenderer-v2.js
index da3f50b08..a211ab552 100644
--- a/src/diagrams/class/classRenderer-v2.js
+++ b/src/diagrams/class/classRenderer-v2.js
@@ -5,7 +5,8 @@ import { getConfig } from '../../config';
import { render } from '../../dagre-wrapper/index.js';
// import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
import { curveLinear } from 'd3';
-import { interpolateToCurve, getStylesFromArray, setupGraphViewbox } from '../../utils';
+import { interpolateToCurve, getStylesFromArray } from '../../utils';
+import { setupGraphViewbox } from '../../setupGraphViewbox';
import common from '../common/common';
import addSVGAccessibilityFields from '../../accessibility';
diff --git a/src/diagrams/class/classRenderer.js b/src/diagrams/class/classRenderer.js
index 576b2d66a..6536cb5a1 100644
--- a/src/diagrams/class/classRenderer.js
+++ b/src/diagrams/class/classRenderer.js
@@ -3,7 +3,7 @@ import dagre from 'dagre';
import graphlib from 'graphlib';
import { log } from '../../logger';
import svgDraw from './svgDraw';
-import { configureSvgSize } from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import { getConfig } from '../../config';
import addSVGAccessibilityFields from '../../accessibility';
@@ -142,8 +142,7 @@ const insertMarkers = function (elem) {
*
* @param {string} text
* @param {string} id
- * @param version
- * @param _version
+ * @param {any} _version
* @param diagObj
*/
export const draw = function (text, id, _version, diagObj) {
diff --git a/src/diagrams/er/erRenderer.js b/src/diagrams/er/erRenderer.js
index 5b5e4de80..d1258a547 100644
--- a/src/diagrams/er/erRenderer.js
+++ b/src/diagrams/er/erRenderer.js
@@ -6,7 +6,7 @@ import dagre from 'dagre';
import { getConfig } from '../../config';
import { log } from '../../logger';
import erMarkers from './erMarkers';
-import { configureSvgSize } from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import addSVGAccessibilityFields from '../../accessibility';
import { parseGenericTypes } from '../common/common';
@@ -32,7 +32,7 @@ export const setConf = function (cnf) {
* @param groupNode The svg group node for the entity
* @param entityTextNode The svg node for the entity label text
* @param attributes An array of attributes defined for the entity (each attribute has a type and a name)
- * @returns The bounding box of the entity, after attributes have been added
+ * @returns {object} The bounding box of the entity, after attributes have been added. The bounding box has a .width and .height
*/
const drawAttributes = (groupNode, entityTextNode, attributes) => {
const heightPadding = conf.entityPadding / 3; // Padding internal to attribute boxes
@@ -307,7 +307,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
* @param svgNode The svg node that contains the diagram
* @param entities The entities to be drawn
* @param graph The graph that contains the vertex and edge definitions post-layout
- * @returns The first entity that was inserted
+ * @returns {object} The first entity that was inserted
*/
const drawEntities = function (svgNode, entities, graph) {
const keys = Object.keys(entities);
@@ -545,7 +545,6 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
* @param text The text of the diagram
* @param id The unique id of the DOM node that contains the diagram
* @param _version
- * @param diag
* @param diagObj
*/
export const draw = function (text, id, _version, diagObj) {
diff --git a/src/diagrams/flowchart/flowRenderer-v2.js b/src/diagrams/flowchart/flowRenderer-v2.js
index 7d6e6d096..6b7c4c1bf 100644
--- a/src/diagrams/flowchart/flowRenderer-v2.js
+++ b/src/diagrams/flowchart/flowRenderer-v2.js
@@ -8,7 +8,8 @@ import { render } from '../../dagre-wrapper/index.js';
import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
import { log } from '../../logger';
import common, { evaluate } from '../common/common';
-import { interpolateToCurve, getStylesFromArray, setupGraphViewbox } from '../../utils';
+import { interpolateToCurve, getStylesFromArray } from '../../utils';
+import { setupGraphViewbox } from '../../setupGraphViewbox';
import addSVGAccessibilityFields from '../../accessibility';
const conf = {};
diff --git a/src/diagrams/flowchart/flowRenderer.js b/src/diagrams/flowchart/flowRenderer.js
index c6e6ebdd6..5b14330dd 100644
--- a/src/diagrams/flowchart/flowRenderer.js
+++ b/src/diagrams/flowchart/flowRenderer.js
@@ -5,7 +5,8 @@ import dagreD3 from 'dagre-d3';
import addHtmlLabel from 'dagre-d3/lib/label/add-html-label.js';
import { log } from '../../logger';
import common, { evaluate } from '../common/common';
-import { interpolateToCurve, getStylesFromArray, setupGraphViewbox } from '../../utils';
+import { interpolateToCurve, getStylesFromArray } from '../../utils';
+import { setupGraphViewbox } from '../../setupGraphViewbox';
import flowChartShapes from './flowChartShapes';
import addSVGAccessibilityFields from '../../accessibility';
@@ -24,7 +25,6 @@ export const setConf = function (cnf) {
* @param g The graph that is to be drawn.
* @param svgId
* @param root
- * @param doc
* @param _doc
* @param diagObj
*/
diff --git a/src/diagrams/gantt/ganttRenderer.js b/src/diagrams/gantt/ganttRenderer.js
index f0866617f..5a24d6d3f 100644
--- a/src/diagrams/gantt/ganttRenderer.js
+++ b/src/diagrams/gantt/ganttRenderer.js
@@ -13,7 +13,7 @@ import {
} from 'd3';
import common from '../common/common';
import { getConfig } from '../../config';
-import { configureSvgSize } from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import addSVGAccessibilityFields from '../../accessibility';
export const setConf = function () {
diff --git a/src/diagrams/git/gitGraphParserV2.spec.js b/src/diagrams/git/gitGraphParserV2.spec.js
index 9a5cc59f1..56a11a876 100644
--- a/src/diagrams/git/gitGraphParserV2.spec.js
+++ b/src/diagrams/git/gitGraphParserV2.spec.js
@@ -348,6 +348,21 @@ describe('when parsing a gitGraph', function () {
expect(Object.keys(parser.yy.getBranches()).length).toBe(2);
});
+ it('should allow branch names starting with numbers', function () {
+ const str = `gitGraph:
+ commit
+ %% branch names starting with numbers are not recommended, but are supported by git
+ branch 1.0.1
+ `;
+
+ parser.parse(str);
+ const commits = parser.yy.getCommits();
+ expect(Object.keys(commits).length).toBe(1);
+ expect(parser.yy.getCurrentBranch()).toBe('1.0.1');
+ expect(parser.yy.getDirection()).toBe('LR');
+ expect(Object.keys(parser.yy.getBranches()).length).toBe(2);
+ });
+
it('should handle new branch checkout', function () {
const str = `gitGraph:
commit
@@ -535,8 +550,6 @@ describe('when parsing a gitGraph', function () {
testBranch3Merge,
] = Object.values(commits);
- console.log(Object.keys(commits));
-
expect(mainCommit.branch).toBe('main');
expect(mainCommit.parents).toStrictEqual([]);
diff --git a/src/diagrams/git/gitGraphRenderer.js b/src/diagrams/git/gitGraphRenderer.js
index 6eb542ce8..5a9036b88 100644
--- a/src/diagrams/git/gitGraphRenderer.js
+++ b/src/diagrams/git/gitGraphRenderer.js
@@ -1,5 +1,5 @@
import { select } from 'd3';
-import { configureSvgSize } from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import { log } from '../../logger';
import { getConfig } from '../../config';
import addSVGAccessibilityFields from '../../accessibility';
@@ -290,13 +290,13 @@ const drawCommits = (svg, commits, modifyGraph) => {
};
/**
- * Detect if there are other commits between commit1s x-position and commit2s x-position on the same
+ * Detect if there are other commits between commit1's x-position and commit2's x-position on the same
* branch as commit2.
*
* @param {any} commit1
* @param {any} commit2
* @param allCommits
- * @returns
+ * @returns {boolean} if there are commits between commit1's x-position and commit2's x-position
*/
const hasOverlappingCommits = (commit1, commit2, allCommits) => {
const commit1Pos = commitPos[commit2.id];
@@ -322,7 +322,7 @@ const hasOverlappingCommits = (commit1, commit2, allCommits) => {
* @param {any} y1
* @param {any} y2
* @param {any} _depth
- * @returns
+ * @returns {number} y value between y1 and y2
*/
const findLane = (y1, y2, _depth) => {
const depth = _depth || 0;
@@ -347,7 +347,7 @@ const findLane = (y1, y2, _depth) => {
};
/**
- * This function draw the lines between the commits. They were arrows initially.
+ * Draw the lines between the commits. They were arrows initially.
*
* @param {any} svg
* @param {any} commit1
@@ -453,12 +453,10 @@ const drawArrows = (svg, commits) => {
};
/**
- * This function adds the branches and the branches' labels to the svg.
+ * Adds the branches and the branches' labels to the svg.
*
* @param svg
- * @param commitid
* @param branches
- * @param direction
*/
const drawBranches = (svg, branches) => {
const gitGraphConfig = getConfig().gitGraph;
@@ -510,10 +508,6 @@ const drawBranches = (svg, branches) => {
};
/**
- * @param svg
- * @param commit
- * @param direction
- * @param branchColor
* @param txt
* @param id
* @param ver
diff --git a/src/diagrams/git/parser/gitGraph.jison b/src/diagrams/git/parser/gitGraph.jison
index 937a7192a..29edec808 100644
--- a/src/diagrams/git/parser/gitGraph.jison
+++ b/src/diagrams/git/parser/gitGraph.jison
@@ -33,7 +33,6 @@ accDescr\s*"{"\s* { this.begin("ac
[\}] { this.popState(); }
[^\}]* return "acc_descr_multiline_value";
(\r?\n)+ /*{console.log('New line');return 'NL';}*/ return 'NL';
-\s+ /* skip all whitespace */
\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"gitGraph" return 'GG';
@@ -61,9 +60,10 @@ accDescr\s*"{"\s* { this.begin("ac
["] this.begin("string");
["] this.popState();
[^"]* return 'STR';
-[0-9]+ return 'NUM';
-[a-zA-Z][-_\./a-zA-Z0-9]*[-_a-zA-Z0-9] return 'ID';
+[0-9]+(?=\s|$) return 'NUM';
+\w[-\./\w]*[-\w] return 'ID'; // only a subset of https://git-scm.com/docs/git-check-ref-format
<> return 'EOF';
+\s+ /* skip all whitespace */ // lowest priority so we can use lookaheads in earlier regex
/lex
diff --git a/src/diagrams/info/infoDb.js b/src/diagrams/info/infoDb.js
index 5a324fbb4..2a04f1633 100644
--- a/src/diagrams/info/infoDb.js
+++ b/src/diagrams/info/infoDb.js
@@ -1,5 +1,6 @@
/** Created by knut on 15-01-14. */
import { log } from '../../logger';
+import { clear } from '../../commonDb';
var message = '';
var info = false;
@@ -30,5 +31,6 @@ export default {
getMessage,
setInfo,
getInfo,
+ clear,
// parseError
};
diff --git a/src/diagrams/info/infoRenderer.js b/src/diagrams/info/infoRenderer.js
index 8976abb75..9e81b9ffb 100644
--- a/src/diagrams/info/infoRenderer.js
+++ b/src/diagrams/info/infoRenderer.js
@@ -49,7 +49,6 @@ export const draw = (text, id, version, diagObj) => {
svg.attr('width', 400);
// svg.attr('viewBox', '0 0 300 150');
} catch (e) {
- console.error(e);
log.error('Error while rendering info diagram');
log.error(e.message);
}
diff --git a/src/diagrams/mindmap/info.spc.js b/src/diagrams/mindmap/info.spc.js
deleted file mode 100644
index 3bdaa12e3..000000000
--- a/src/diagrams/mindmap/info.spc.js
+++ /dev/null
@@ -1,15 +0,0 @@
-describe('when parsing an info graph it', function () {
- var ex;
- beforeEach(function () {
- // eslint-disable-next-line @typescript-eslint/no-var-requires
- ex = require('./parser/info').parser;
- ex.yy = require('./infoDb');
- });
-
- it('should handle an info definition', function () {
- var str = `info
- showInfo`;
-
- ex.parse(str);
- });
-});
diff --git a/src/diagrams/mindmap/infoDb.js b/src/diagrams/mindmap/infoDb.js
deleted file mode 100644
index 5a324fbb4..000000000
--- a/src/diagrams/mindmap/infoDb.js
+++ /dev/null
@@ -1,34 +0,0 @@
-/** Created by knut on 15-01-14. */
-import { log } from '../../logger';
-
-var message = '';
-var info = false;
-
-export const setMessage = (txt) => {
- log.debug('Setting message to: ' + txt);
- message = txt;
-};
-
-export const getMessage = () => {
- return message;
-};
-
-export const setInfo = (inf) => {
- info = inf;
-};
-
-export const getInfo = () => {
- return info;
-};
-
-// export const parseError = (err, hash) => {
-// global.mermaidAPI.parseError(err, hash)
-// }
-
-export default {
- setMessage,
- getMessage,
- setInfo,
- getInfo,
- // parseError
-};
diff --git a/src/diagrams/mindmap/infoRenderer.js b/src/diagrams/mindmap/infoRenderer.js
deleted file mode 100644
index cbe3c52f8..000000000
--- a/src/diagrams/mindmap/infoRenderer.js
+++ /dev/null
@@ -1,59 +0,0 @@
-/** Created by knut on 14-12-11. */
-import { select } from 'd3';
-import { log } from '../../logger';
-import { getConfig } from '../../config';
-
-/**
- * Draws a an info picture in the tag with id: id based on the graph definition in text.
- *
- * @param {any} text
- * @param {any} id
- * @param {any} version
- * @param diagObj
- */
-export const draw = (text, id, version, diagObj) => {
- try {
- // const parser = infoParser.parser;
- // parser.yy = db;
- log.debug('Renering info diagram\n' + text);
-
- const securityLevel = getConfig().securityLevel;
- // Handle root and Document for when rendering in sanbox mode
- let sandboxElement;
- if (securityLevel === 'sandbox') {
- sandboxElement = select('#i' + id);
- }
- const root =
- securityLevel === 'sandbox'
- ? select(sandboxElement.nodes()[0].contentDocument.body)
- : select('body');
- const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
-
- // Parse the graph definition
- // parser.parse(text);
- // log.debug('Parsed info diagram');
- // Fetch the default direction, use TD if none was found
- const svg = root.select('#' + id);
-
- const g = svg.append('g');
-
- g.append('text') // text label for the x axis
- .attr('x', 100)
- .attr('y', 40)
- .attr('class', 'version')
- .attr('font-size', '32px')
- .style('text-anchor', 'middle')
- .text('v ' + version);
-
- svg.attr('height', 100);
- svg.attr('width', 400);
- // svg.attr('viewBox', '0 0 300 150');
- } catch (e) {
- log.error('Error while rendering info diagram');
- log.error(e.message);
- }
-};
-
-export default {
- draw,
-};
diff --git a/src/diagrams/mindmap/mindmap.spec.js b/src/diagrams/mindmap/mindmap.spec.js
new file mode 100644
index 000000000..155b566fb
--- /dev/null
+++ b/src/diagrams/mindmap/mindmap.spec.js
@@ -0,0 +1,328 @@
+import { parser as mindmap } from './parser/mindmap';
+import * as mindmapDB from './mindmapDb';
+import { setLogLevel } from '../../logger';
+
+describe('when parsing a mindmap ', function () {
+ beforeEach(function () {
+ mindmap.yy = mindmapDB;
+ mindmap.yy.clear();
+ setLogLevel('trace');
+ });
+ describe('hiearchy', function () {
+ it('MMP-1 should handle a simple root definition abc122', function () {
+ let str = `mindmap
+ root`;
+
+ mindmap.parse(str);
+ // console.log('Time for checks', mindmap.yy.getMindmap().descr);
+ expect(mindmap.yy.getMindmap().descr).toEqual('root');
+ });
+ it('MMP-2 should handle a hierachial mindmap definition', function () {
+ let str = `mindmap
+ root
+ child1
+ child2
+ `;
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.descr).toEqual('root');
+ expect(mm.children.length).toEqual(2);
+ expect(mm.children[0].descr).toEqual('child1');
+ expect(mm.children[1].descr).toEqual('child2');
+ });
+
+ it('3 should handle a simple root definition with a shape and without an id abc123', function () {
+ let str = `mindmap
+ (root)`;
+
+ mindmap.parse(str);
+ // console.log('Time for checks', mindmap.yy.getMindmap().descr);
+ expect(mindmap.yy.getMindmap().descr).toEqual('root');
+ });
+
+ it('MMP-4 should handle a deeper hierachial mindmap definition', function () {
+ let str = `mindmap
+ root
+ child1
+ leaf1
+ child2`;
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.descr).toEqual('root');
+ expect(mm.children.length).toEqual(2);
+ expect(mm.children[0].descr).toEqual('child1');
+ expect(mm.children[0].children[0].descr).toEqual('leaf1');
+ expect(mm.children[1].descr).toEqual('child2');
+ });
+ it('5 Multiple roots are illegal', function () {
+ let str = `mindmap
+ root
+ fakeRoot`;
+
+ try {
+ mindmap.parse(str);
+ // Fail test if above expression doesn't throw anything.
+ expect(true).toBe(false);
+ } catch (e) {
+ expect(e.message).toBe(
+ 'There can be only one root. No parent could be found for ("fakeRoot")'
+ );
+ }
+ });
+ it('MMP-6 real root in wrong place', function () {
+ let str = `mindmap
+ root
+ fakeRoot
+ realRootWrongPlace`;
+
+ try {
+ mindmap.parse(str);
+ // Fail test if above expression doesn't throw anything.
+ expect(true).toBe(false);
+ } catch (e) {
+ expect(e.message).toBe(
+ 'There can be only one root. No parent could be found for ("fakeRoot")'
+ );
+ }
+ });
+ });
+ describe('nodes', function () {
+ it('MMP-7 should handle an id and type for a node definition', function () {
+ let str = `mindmap
+ root[The root]
+ `;
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('The root');
+ expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
+ });
+ it('MMP-8 should handle an id and type for a node definition', function () {
+ let str = `mindmap
+ root
+ theId(child1)`;
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.descr).toEqual('root');
+ expect(mm.children.length).toEqual(1);
+ const child = mm.children[0];
+ expect(child.descr).toEqual('child1');
+ expect(child.nodeId).toEqual('theId');
+ expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT);
+ });
+ it('MMP-9 should handle an id and type for a node definition', function () {
+ let str = `mindmap
+root
+ theId(child1)`;
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.descr).toEqual('root');
+ expect(mm.children.length).toEqual(1);
+ const child = mm.children[0];
+ expect(child.descr).toEqual('child1');
+ expect(child.nodeId).toEqual('theId');
+ expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT);
+ });
+ it('MMP-10 mutiple types (circle)', function () {
+ let str = `mindmap
+ root((the root))
+ `;
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.descr).toEqual('the root');
+ expect(mm.children.length).toEqual(0);
+ expect(mm.type).toEqual(mindmap.yy.nodeType.CIRCLE);
+ });
+
+ it('MMP-11 mutiple types (cloud)', function () {
+ let str = `mindmap
+ root)the root(
+`;
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.descr).toEqual('the root');
+ expect(mm.children.length).toEqual(0);
+ expect(mm.type).toEqual(mindmap.yy.nodeType.CLOUD);
+ });
+ it('MMP-12 mutiple types (bang)', function () {
+ let str = `mindmap
+ root))the root((
+`;
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.descr).toEqual('the root');
+ expect(mm.children.length).toEqual(0);
+ expect(mm.type).toEqual(mindmap.yy.nodeType.BANG);
+ });
+ });
+ describe('decorations', function () {
+ it('MMP-13 should be possible to set an icon for the node', function () {
+ let str = `mindmap
+ root[The root]
+ ::icon(bomb)
+ `;
+ // ::class1 class2
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('The root');
+ expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
+ expect(mm.icon).toEqual('bomb');
+ });
+ it('MMP-14 should be possible to set classes for the node', function () {
+ let str = `mindmap
+ root[The root]
+ :::m-4 p-8
+ `;
+ // ::class1 class2
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('The root');
+ expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
+ expect(mm.class).toEqual('m-4 p-8');
+ });
+ it('MMP-15 should be possible to set both classes and icon for the node', function () {
+ let str = `mindmap
+ root[The root]
+ :::m-4 p-8
+ ::icon(bomb)
+ `;
+ // ::class1 class2
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('The root');
+ expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
+ expect(mm.class).toEqual('m-4 p-8');
+ expect(mm.icon).toEqual('bomb');
+ });
+ it('MMP-16 should be possible to set both classes and icon for the node', function () {
+ let str = `mindmap
+ root[The root]
+ ::icon(bomb)
+ :::m-4 p-8
+ `;
+ // ::class1 class2
+
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('The root');
+ expect(mm.type).toEqual(mindmap.yy.nodeType.RECT);
+ expect(mm.class).toEqual('m-4 p-8');
+ expect(mm.icon).toEqual('bomb');
+ });
+ });
+ describe('descriptions', function () {
+ it('MMP-17 should be possible to use node syntax in the descriptions', function () {
+ let str = `mindmap
+ root["String containing []"]
+`;
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('String containing []');
+ });
+ it('MMP-18 should be possible to use node syntax in the descriptions in children', function () {
+ let str = `mindmap
+ root["String containing []"]
+ child1["String containing ()"]
+`;
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('String containing []');
+ expect(mm.children.length).toEqual(1);
+ expect(mm.children[0].descr).toEqual('String containing ()');
+ });
+ it('MMP-19 should be possible to have a child after a class assignment', function () {
+ let str = `mindmap
+ root(Root)
+ Child(Child)
+ :::hot
+ a(a)
+ b[New Stuff]`;
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('Root');
+ expect(mm.children.length).toEqual(1);
+
+ const child = mm.children[0];
+ expect(child.nodeId).toEqual('Child');
+ expect(child.children[0].nodeId).toEqual('a');
+ expect(child.children.length).toEqual(2);
+ expect(child.children[1].nodeId).toEqual('b');
+ });
+ });
+ it('MMP-20 should be possible to have meaningless empty rows in a mindmap abc124', function () {
+ let str = `mindmap
+ root(Root)
+ Child(Child)
+ a(a)
+
+ b[New Stuff]`;
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('Root');
+ expect(mm.children.length).toEqual(1);
+
+ const child = mm.children[0];
+ expect(child.nodeId).toEqual('Child');
+ expect(child.children[0].nodeId).toEqual('a');
+ expect(child.children.length).toEqual(2);
+ expect(child.children[1].nodeId).toEqual('b');
+ });
+ it('MMP-21 should be possible to have comments in a mindmap', function () {
+ let str = `mindmap
+ root(Root)
+ Child(Child)
+ a(a)
+
+ %% This is a comment
+ b[New Stuff]`;
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('Root');
+ expect(mm.children.length).toEqual(1);
+
+ const child = mm.children[0];
+ expect(child.nodeId).toEqual('Child');
+ expect(child.children[0].nodeId).toEqual('a');
+ expect(child.children.length).toEqual(2);
+ expect(child.children[1].nodeId).toEqual('b');
+ });
+
+ it('MMP-22 should be possible to have comments at the end of a line', function () {
+ let str = `mindmap
+ root(Root)
+ Child(Child)
+ a(a) %% This is a comment
+ b[New Stuff]`;
+ mindmap.parse(str);
+ const mm = mindmap.yy.getMindmap();
+ expect(mm.nodeId).toEqual('root');
+ expect(mm.descr).toEqual('Root');
+ expect(mm.children.length).toEqual(1);
+
+ const child = mm.children[0];
+ expect(child.nodeId).toEqual('Child');
+ expect(child.children[0].nodeId).toEqual('a');
+ expect(child.children.length).toEqual(2);
+ expect(child.children[1].nodeId).toEqual('b');
+ });
+});
diff --git a/src/diagrams/mindmap/mindmapDb.js b/src/diagrams/mindmap/mindmapDb.js
new file mode 100644
index 000000000..48b242c9e
--- /dev/null
+++ b/src/diagrams/mindmap/mindmapDb.js
@@ -0,0 +1,152 @@
+/** Created by knut on 15-01-14. */
+import { sanitizeText, getConfig } from '../../diagram-api/diagramAPI';
+import { log as _log } from '../../logger';
+
+let nodes = [];
+let cnt = 0;
+let elements = {};
+export const clear = () => {
+ nodes = [];
+ cnt = 0;
+ elements = {};
+};
+
+const getParent = function (level) {
+ for (let i = nodes.length - 1; i >= 0; i--) {
+ if (nodes[i].level < level) {
+ return nodes[i];
+ }
+ }
+ // No parent found
+ return null;
+};
+
+export const getMindmap = () => {
+ return nodes.length > 0 ? nodes[0] : null;
+};
+export const addNode = (level, id, descr, type) => {
+ log.info('addNode', level, id, descr, type);
+ const conf = getConfig();
+ const node = {
+ id: cnt++,
+ nodeId: sanitizeText(id),
+ level,
+ descr: sanitizeText(descr),
+ type,
+ children: [],
+ width: getConfig().mindmap.maxNodeWidth,
+ };
+ switch (node.type) {
+ case nodeType.ROUNDED_RECT:
+ node.padding = 2 * conf.mindmap.padding;
+ break;
+ case nodeType.RECT:
+ node.padding = 2 * conf.mindmap.padding;
+ break;
+ default:
+ node.padding = conf.mindmap.padding;
+ }
+ const parent = getParent(level);
+ if (parent) {
+ parent.children.push(node);
+ // Keep all nodes in the list
+ nodes.push(node);
+ } else {
+ if (nodes.length === 0) {
+ // First node, the root
+ nodes.push(node);
+ } else {
+ // Syntax error ... there can only bee one root
+ let error = new Error(
+ 'There can be only one root. No parent could be found for ("' + node.descr + '")'
+ );
+ error.hash = {
+ text: 'branch ' + name,
+ token: 'branch ' + name,
+ line: '1',
+ loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
+ expected: ['"checkout ' + name + '"'],
+ };
+ throw error;
+ }
+ }
+};
+
+export const nodeType = {
+ DEFAULT: 0,
+ NO_BORDER: 0,
+ ROUNDED_RECT: 1,
+ RECT: 2,
+ CIRCLE: 3,
+ CLOUD: 4,
+ BANG: 5,
+};
+
+export const getType = (startStr, endStr) => {
+ log.debug('In get type', startStr, endStr);
+ switch (startStr) {
+ case '[':
+ return nodeType.RECT;
+ case '(':
+ return endStr === ')' ? nodeType.ROUNDED_RECT : nodeType.CLOUD;
+ case '((':
+ return nodeType.CIRCLE;
+ case ')':
+ return nodeType.CLOUD;
+ case '))':
+ return nodeType.BANG;
+ default:
+ return nodeType.DEFAULT;
+ }
+};
+
+export const setElementForId = (id, element) => {
+ elements[id] = element;
+};
+
+export const decorateNode = (decoration) => {
+ const node = nodes[nodes.length - 1];
+ if (decoration && decoration.icon) {
+ node.icon = sanitizeText(decoration.icon);
+ }
+ if (decoration && decoration.class) {
+ node.class = sanitizeText(decoration.class);
+ }
+};
+
+export const type2Str = (type) => {
+ switch (type) {
+ case nodeType.DEFAULT:
+ return 'no-border';
+ case nodeType.RECT:
+ return 'rect';
+ case nodeType.ROUNDED_RECT:
+ return 'rounded-rect';
+ case nodeType.CIRCLE:
+ return 'circle';
+ case nodeType.CLOUD:
+ return 'cloud';
+ case nodeType.BANG:
+ return 'bang';
+ default:
+ return 'no-border';
+ }
+};
+// Expose logger to grammar
+export const log = _log;
+export const getNodeById = (id) => nodes[id];
+export const getElementById = (id) => elements[id];
+// export default {
+// // getMindmap,
+// // addNode,
+// // clear,
+// // nodeType,
+// // getType,
+// // decorateNode,
+// // setElementForId,
+// getElementById: (id) => elements[id],
+// // getNodeById: (id) => nodes.find((node) => node.id === id),
+// getNodeById: (id) => nodes[id],
+// // type2Str,
+// // parseError
+// };
diff --git a/src/diagrams/mindmap/mindmapDetector.old b/src/diagrams/mindmap/mindmapDetector.old
new file mode 100644
index 000000000..4fe85af03
--- /dev/null
+++ b/src/diagrams/mindmap/mindmapDetector.old
@@ -0,0 +1,8 @@
+const detector = function detect(txt) {
+ if (txt.match(/^\s*mindmap/)) {
+ return 'mindmap';
+ }
+ return null;
+};
+
+export default detector;
diff --git a/src/diagrams/mindmap/mindmapDetector.ts b/src/diagrams/mindmap/mindmapDetector.ts
new file mode 100644
index 000000000..8b45f3681
--- /dev/null
+++ b/src/diagrams/mindmap/mindmapDetector.ts
@@ -0,0 +1,5 @@
+import type { DiagramDetector } from '../../diagram-api/detectType';
+
+export const mindmapDetector: DiagramDetector = (txt) => {
+ return txt.match(/^\s*mindmap/) !== null;
+};
diff --git a/src/diagrams/mindmap/mindmapRenderer.js b/src/diagrams/mindmap/mindmapRenderer.js
new file mode 100644
index 000000000..caa8baedb
--- /dev/null
+++ b/src/diagrams/mindmap/mindmapRenderer.js
@@ -0,0 +1,271 @@
+/** Created by knut on 14-12-11. */
+import { select } from 'd3';
+import { log, getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI';
+import svgDraw from './svgDraw';
+import { BoundingBox, Layout, Tree } from 'non-layered-tidy-tree-layout';
+import clone from 'fast-clone';
+import * as db from './mindmapDb';
+
+/**
+ * @param {any} svg The svg element to draw the diagram onto
+ * @param {object} mindmap The maindmap data and hierarchy
+ * @param section
+ * @param {object} conf The configuration object
+ */
+function drawNodes(svg, mindmap, section, conf) {
+ svgDraw.drawNode(svg, mindmap, section, conf);
+ if (mindmap.children) {
+ mindmap.children.forEach((child, index) => {
+ drawNodes(svg, child, section < 0 ? index : section, conf);
+ });
+ }
+}
+
+/**
+ * @param edgesElem
+ * @param mindmap
+ * @param parent
+ * @param depth
+ * @param section
+ * @param conf
+ */
+function drawEdges(edgesElem, mindmap, parent, depth, section, conf) {
+ if (parent) {
+ svgDraw.drawEdge(edgesElem, mindmap, parent, depth, section, conf);
+ }
+ if (mindmap.children) {
+ mindmap.children.forEach((child, index) => {
+ drawEdges(edgesElem, child, mindmap, depth + 1, section < 0 ? index : section, conf);
+ });
+ }
+}
+
+/**
+ * @param mindmap
+ * @param callback
+ */
+function eachNode(mindmap, callback) {
+ callback(mindmap);
+ if (mindmap.children) {
+ mindmap.children.forEach((child) => {
+ eachNode(child, callback);
+ });
+ }
+}
+/** @param {object} mindmap */
+function transpose(mindmap) {
+ eachNode(mindmap, (node) => {
+ const orgWidth = node.width;
+ const orgX = node.x;
+ node.width = node.height;
+ node.height = orgWidth;
+ node.x = node.y;
+ node.y = orgX;
+ });
+ return mindmap;
+}
+/** @param {object} mindmap */
+function bottomToUp(mindmap) {
+ log.debug('bottomToUp', mindmap);
+ eachNode(mindmap.result, (node) => {
+ // node.y = node.y - (node.y - bb.top) * 2 - node.height;
+ node.y = node.y - (node.y - 0) * 2 - node.height;
+ });
+ return mindmap;
+}
+/** @param {object} mindmap The mindmap hierarchy */
+function rightToLeft(mindmap) {
+ eachNode(mindmap.result, (node) => {
+ // node.y = node.y - (node.y - bb.top) * 2 - node.height;
+ node.x = node.x - (node.x - 0) * 2 - node.width;
+ });
+ return mindmap;
+}
+
+/**
+ * @param mindmap
+ * @param dir
+ * @param conf
+ */
+function layout(mindmap, dir, conf) {
+ const bb = new BoundingBox(30, 60);
+
+ const layout = new Layout(bb);
+ switch (dir) {
+ case 'TB':
+ return layout.layout(mindmap);
+ case 'BT':
+ return bottomToUp(layout.layout(mindmap));
+ case 'RL': {
+ transpose(mindmap);
+ let newRes = layout.layout(mindmap);
+ transpose(newRes.result);
+ return rightToLeft(newRes);
+ }
+ case 'LR': {
+ transpose(mindmap);
+ let newRes = layout.layout(mindmap);
+ transpose(newRes.result);
+ return newRes;
+ }
+ default:
+ }
+}
+const dirFromIndex = (index) => {
+ const dirNum = (index + 2) % 4;
+ switch (dirNum) {
+ case 0:
+ return 'LR';
+ case 1:
+ return 'RL';
+ case 2:
+ return 'TB';
+ case 3:
+ return 'BT';
+ default:
+ return 'TB';
+ }
+};
+
+const mergeTrees = (node, trees) => {
+ node.x = trees[0].result.x;
+ node.y = trees[0].result.y;
+ trees.forEach((tree) => {
+ tree.result.children.forEach((child) => {
+ const dx = node.x - tree.result.x;
+ const dy = node.y - tree.result.y;
+ eachNode(child, (childNode) => {
+ const orgNode = db.getNodeById(childNode.id);
+ if (orgNode) {
+ orgNode.x = childNode.x + dx;
+ orgNode.y = childNode.y + dy;
+ }
+ });
+ });
+ });
+ return node;
+};
+
+/**
+ * @param node
+ * @param conf
+ */
+function layoutMindmap(node, conf) {
+ // BoundingBox(gap, bottomPadding)
+ // const bb = new BoundingBox(10, 10);
+ // const layout = new Layout(bb);
+ // // const layout = new HorizontalLayout(bb);
+ if (node.children.length === 0) {
+ return node;
+ }
+ const trees = [];
+ // node.children.forEach((child, index) => {
+ // const tree = clone(node);
+ // tree.children = [tree.children[index]];
+ // trees.push(layout(tree, dirFromIndex(index), conf));
+ // });
+
+ let cnt = 0;
+ // For each direction, create a new tree with the same root, and add a ubset of the children to it.
+ for (let i = 0; i < 4; i++) {
+ // Calculate the number of the children of the root node that will be used in this direction
+ const numChildren =
+ Math.floor(node.children.length / 4) + (node.children.length % 4 > i ? 1 : 0);
+ // Copy the original root node
+ const tree = clone(node);
+ // Setup the new copy with the children to be rendered in this direction
+ tree.children = [];
+ for (let j = 0; j < numChildren; j++) {
+ tree.children.push(node.children[cnt]);
+ cnt++;
+ }
+ if (tree.children.length > 0) {
+ trees.push(layout(tree, dirFromIndex(i), conf));
+ }
+ }
+ // Let each node know the direct of its tree for when we draw the branches.
+ trees.forEach((tree, index) => {
+ tree.result.direction = dirFromIndex(index);
+ eachNode(tree.result, (node) => {
+ node.direction = dirFromIndex(index);
+ });
+ });
+
+ // Merge the trees into a single tree
+ const result = mergeTrees(node, trees);
+ eachNode;
+ return node;
+}
+/**
+ * @param node
+ * @param conf
+ */
+function positionNodes(node, conf) {
+ svgDraw.positionNode(node, conf);
+ if (node.children) {
+ node.children.forEach((child) => {
+ positionNodes(child, conf);
+ });
+ }
+}
+
+/**
+ * Draws a an info picture in the tag with id: id based on the graph definition in text.
+ *
+ * @param {any} text
+ * @param {any} id
+ * @param {any} version
+ * @param diagObj
+ */
+export const draw = (text, id, version, diagObj) => {
+ const conf = getConfig();
+ try {
+ log.debug('Renering info diagram\n' + text);
+
+ const securityLevel = getConfig().securityLevel;
+ // Handle root and Document for when rendering in sanbox mode
+ let sandboxElement;
+ if (securityLevel === 'sandbox') {
+ sandboxElement = select('#i' + id);
+ }
+ const root =
+ securityLevel === 'sandbox'
+ ? select(sandboxElement.nodes()[0].contentDocument.body)
+ : select('body');
+ const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
+
+ // Parse the graph definition
+
+ const svg = root.select('#' + id);
+
+ const g = svg.append('g');
+ const mm = diagObj.db.getMindmap();
+
+ // Draw the graph and start with drawing the nodes without proper position
+ // this gives us the size of the nodes and we can set the positions later
+
+ const edgesElem = svg.append('g');
+ edgesElem.attr('class', 'mindmap-edges');
+ const nodesElem = svg.append('g');
+ nodesElem.attr('class', 'mindmap-nodes');
+ drawNodes(nodesElem, mm, -1, conf);
+
+ // Next step is to layout the mindmap, giving each node a position
+
+ const positionedMindmap = layoutMindmap(mm, conf);
+
+ // After this we can draw, first the edges and the then nodes with the correct position
+ drawEdges(edgesElem, positionedMindmap, null, 0, -1, conf);
+ positionNodes(positionedMindmap, conf);
+
+ // Setup the view box and size of the svg element
+ setupGraphViewbox(undefined, svg, conf.mindmap.padding, conf.mindmap.useMaxWidth);
+ } catch (e) {
+ log.error('Error while rendering info diagram');
+ log.error(e.message);
+ }
+};
+
+export default {
+ draw,
+};
diff --git a/src/diagrams/mindmap/parser/mindamap.jison b/src/diagrams/mindmap/parser/mindamap.jison
deleted file mode 100644
index 473b63fcf..000000000
--- a/src/diagrams/mindmap/parser/mindamap.jison
+++ /dev/null
@@ -1,48 +0,0 @@
-/** mermaid
- * https://knsv.github.io/mermaid
- * (c) 2015 Knut Sveidqvist
- * MIT license.
- */
-%lex
-
-%options case-insensitive
-
-%{
- // Pre-lexer code can go here
-%}
-
-%%
-
-"info" return 'info' ;
-[\s\n\r]+ return 'NL' ;
-[\s]+ return 'space';
-"showInfo" return 'showInfo';
-<> return 'EOF' ;
-. return 'TXT' ;
-
-/lex
-
-%start start
-
-%% /* language grammar */
-
-start
-// %{ : info document 'EOF' { return yy; } }
- : info document 'EOF' { return yy; }
- ;
-
-document
- : /* empty */
- | document line
- ;
-
-line
- : statement { }
- | 'NL'
- ;
-
-statement
- : showInfo { yy.setInfo(true); }
- ;
-
-%%
diff --git a/src/diagrams/mindmap/parser/mindmap.jison b/src/diagrams/mindmap/parser/mindmap.jison
new file mode 100644
index 000000000..f8c72787c
--- /dev/null
+++ b/src/diagrams/mindmap/parser/mindmap.jison
@@ -0,0 +1,108 @@
+/** mermaid
+ * https://knsv.github.io/mermaid
+ * (c) 2015 Knut Sveidqvist
+ * MIT license.
+ */
+%lex
+
+%options case-insensitive
+
+%{
+ // Pre-lexer code can go here
+%}
+%x NODE
+%x NSTR
+%x ICON
+%x CLASS
+
+%%
+
+\s*\%\%.* {yy.log.trace('Found comment',yytext);}
+// \%\%[^\n]*\n /* skip comments */
+"mindmap" return 'MINDMAP';
+":::" { this.begin('CLASS'); }
+.+ { this.popState();return 'CLASS'; }
+\n { this.popState();}
+// [\s]*"::icon(" { this.begin('ICON'); }
+"::icon(" { yy.log.trace('Begin icon');this.begin('ICON'); }
+[\n]+ return 'NL';
+[^\)]+ { return 'ICON'; }
+\) {yy.log.trace('end icon');this.popState();}
+"-)" { yy.log.trace('Exploding node'); this.begin('NODE');return 'NODE_DSTART'; }
+"(-" { yy.log.trace('Cloud'); this.begin('NODE');return 'NODE_DSTART'; }
+"))" { yy.log.trace('Explosion Bang'); this.begin('NODE');return 'NODE_DSTART'; }
+")" { yy.log.trace('Cloud Bang'); this.begin('NODE');return 'NODE_DSTART'; }
+"((" { this.begin('NODE');return 'NODE_DSTART'; }
+"(" { this.begin('NODE');return 'NODE_DSTART'; }
+"[" { this.begin('NODE');return 'NODE_DSTART'; }
+[\s]+ return 'SPACELIST' /* skip all whitespace */ ;
+// !(-\() return 'NODE_ID';
+[^\(\[\n\-\)]+ return 'NODE_ID';
+<> return 'EOF';
+["] { yy.log.trace('Starting NSTR');this.begin("NSTR");}
+[^"]+ { yy.log.trace('description:', yytext); return "NODE_DESCR";}
+["] {this.popState();}
+[\)]\) {this.popState();yy.log.trace('node end ))');return "NODE_DEND";}
+[\)] {this.popState();yy.log.trace('node end )');return "NODE_DEND";}
+[\]] {this.popState();yy.log.trace('node end ...',yytext);return "NODE_DEND";}
+"(-" {this.popState();yy.log.trace('node end (-');return "NODE_DEND";}
+"-)" {this.popState();yy.log.trace('node end (-');return "NODE_DEND";}
+"((" {this.popState();yy.log.trace('node end ((');return "NODE_DEND";}
+"(" {this.popState();yy.log.trace('node end ((');return "NODE_DEND";}
+[^\)\]\(]+ { yy.log.trace('Long description:', yytext); return 'NODE_DESCR';}
+.+(?!\(\() { yy.log.trace('Long description:', yytext); return 'NODE_DESCR';}
+// [\[] return 'NODE_START';
+// .+ return 'TXT' ;
+
+/lex
+
+%start start
+
+%% /* language grammar */
+
+start
+// %{ : info document 'EOF' { return yy; } }
+ : MINDMAP document { return yy; }
+ | MINDMAP NL document { return yy; }
+ | SPACELIST MINDMAP document { return yy; }
+ ;
+
+stop
+ : NL {yy.log.trace('Stop NL ');}
+ | EOF {yy.log.trace('Stop EOF ');}
+ | stop NL {yy.log.trace('Stop NL2 ');}
+ | stop EOF {yy.log.trace('Stop EOF2 ');}
+ ;
+document
+ : document statement stop
+ | statement stop
+ ;
+
+statement
+ : SPACELIST node { yy.log.trace('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); }
+ | SPACELIST ICON { yy.log.trace('Icon: ',$2);yy.decorateNode({icon: $2}); }
+ | SPACELIST CLASS { yy.decorateNode({class: $2}); }
+ | node { yy.log.trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type); }
+ | ICON { yy.decorateNode({icon: $1}); }
+ | CLASS { yy.decorateNode({class: $1}); }
+ | SPACELIST
+ ;
+
+
+
+node
+ :nodeWithId
+ |nodeWithoutId
+ ;
+
+nodeWithoutId
+ : NODE_DSTART NODE_DESCR NODE_DEND
+ { yy.log.trace("node found ..", $1); $$ = { id: $2, descr: $2, type: yy.getType($1, $3) }; }
+ ;
+
+nodeWithId
+ : NODE_ID { $$ = { id: $1, descr: $1, type: yy.nodeType.DEFAULT }; }
+ | NODE_ID NODE_DSTART NODE_DESCR NODE_DEND
+ { yy.log.trace("node found ..", $1); $$ = { id: $1, descr: $3, type: yy.getType($2, $4) }; }
+ ;
+%%
diff --git a/src/diagrams/mindmap/styles.js b/src/diagrams/mindmap/styles.js
index 0b0729813..76d6a9c1b 100644
--- a/src/diagrams/mindmap/styles.js
+++ b/src/diagrams/mindmap/styles.js
@@ -1,3 +1,76 @@
-const getStyles = () => ``;
+import { darken, lighten, adjust, invert, isDark } from 'khroma';
+const genSections = (options) => {
+ let sections = '';
+
+ for (let i = 0; i < 8; i++) {
+ options['lineColor' + i] = options['lineColor' + i] || options['gitInv' + i];
+ if (isDark(options['lineColor' + i])) {
+ options['lineColor' + i] = lighten(options['lineColor' + i], 20);
+ } else {
+ options['lineColor' + i] = darken(options['lineColor' + i], 20);
+ }
+ }
+
+ for (let i = 0; i < 8; i++) {
+ const sw = '' + (17 - 3 * i);
+ sections += `
+ .section-${i - 1} rect, .section-${i - 1} path, .section-${i - 1} circle, .section-${
+ i - 1
+ } path {
+ fill: ${options['git' + i]};
+ }
+ .section-${i - 1} text {
+ fill: ${options['gitBranchLabel' + i]};
+ // fill: ${options['gitInv' + i]};
+ }
+ .node-icon-${i - 1} {
+ font-size: 40px;
+ color: ${options['gitBranchLabel' + i]};
+ // color: ${options['gitInv' + i]};
+ }
+ .section-edge-${i - 1}{
+ stroke: ${options['git' + i]};
+ }
+ .edge-depth-${i - 1}{
+ stroke-width: ${sw};
+ }
+ .section-${i - 1} line {
+ stroke: ${options['lineColor' + i]} ;
+ stroke-width: 3;
+ }
+
+ .disabled, .disabled circle, .disabled text {
+ fill: lightgray;
+ }
+ .disabled text {
+ fill: #efefef;
+ }
+ `;
+ }
+ return sections;
+};
+
+const getStyles = (options) =>
+ `
+ .edge {
+ stroke-width: 3;
+ }
+ ${genSections(options)}
+ .section-root rect, .section-root path, .section-root circle {
+ fill: ${options.git0};
+ }
+ .section-root text {
+ fill: ${options.gitBranchLabel0};
+ }
+ .icon-container {
+ height:100%;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ }
+ .edge {
+ fill: none;
+ }
+`;
export default getStyles;
diff --git a/src/diagrams/mindmap/svgDraw.js b/src/diagrams/mindmap/svgDraw.js
new file mode 100644
index 000000000..511c46e20
--- /dev/null
+++ b/src/diagrams/mindmap/svgDraw.js
@@ -0,0 +1,322 @@
+const lineBreakRegex = /
/gi;
+import { select } from 'd3';
+import * as db from './mindmapDb';
+
+/**
+ * @param {string} text The text to be wrapped
+ * @param {number} width The max width of the text
+ */
+function wrap(text, width) {
+ text.each(function () {
+ var text = select(this),
+ words = text
+ .text()
+ .split(/(\s+|
)/)
+ .reverse(),
+ word,
+ line = [],
+ lineNumber = 0,
+ lineHeight = 1.1, // ems
+ y = text.attr('y'),
+ dy = parseFloat(text.attr('dy')),
+ tspan = text
+ .text(null)
+ .append('tspan')
+ .attr('x', 0)
+ .attr('y', y)
+ .attr('dy', dy + 'em');
+ for (let j = 0; j < words.length; j++) {
+ word = words[words.length - 1 - j];
+ line.push(word);
+ tspan.text(line.join(' ').trim());
+ if (tspan.node().getComputedTextLength() > width || word === '
') {
+ line.pop();
+ tspan.text(line.join(' ').trim());
+ if (word === '
') {
+ line = [''];
+ } else {
+ line = [word];
+ }
+
+ tspan = text
+ .append('tspan')
+ .attr('x', 0)
+ .attr('y', y)
+ .attr('dy', lineHeight + 'em')
+ .text(word);
+ }
+ }
+ });
+}
+
+const defaultBkg = function (elem, node, section, conf) {
+ const rd = 5;
+ const r = elem
+ .append('path')
+ .attr('id', 'node-' + node.id)
+ .attr('class', 'node-bkg node-' + db.type2Str(node.type))
+ .attr(
+ 'd',
+ `M0 ${node.height - rd} v${-node.height + 2 * rd} q0,-5 5,-5 h${
+ node.width - 2 * rd
+ } q5,0 5,5 v${node.height - rd} H0 Z`
+ );
+
+ elem
+ .append('line')
+ .attr('class', 'node-line-' + section)
+ .attr('x1', 0)
+ .attr('y1', node.height)
+ .attr('x2', node.width)
+ .attr('y2', node.height);
+};
+const rectBkg = function (elem, node, section, conf) {
+ const r = elem
+ .append('rect')
+ .attr('id', 'node-' + node.id)
+ .attr('class', 'node-bkg node-' + db.type2Str(node.type))
+ .attr('height', node.height)
+ .attr('width', node.width);
+};
+const cloudBkg = function (elem, node, section, conf) {
+ const rd = 5;
+ const r = elem;
+ const w = node.width;
+ const h = node.height;
+ const r0 = 0.1 * w;
+ const r1 = 0.15 * w;
+ const r2 = 0.25 * w;
+ const r3 = 0.35 * w;
+ const r4 = 0.2 * w;
+ const p = elem
+ .append('path')
+ .attr('id', 'node-' + node.id)
+ .attr('class', 'node-bkg node-' + db.type2Str(node.type))
+ .attr(
+ 'd',
+ `M0 0 a${r1},${r1} 0 0,1 ${w * 0.25},${-1 * w * 0.1}
+ a${r3},${r3} 1 0,1 ${w * 0.4},${-1 * w * 0.1}
+ a${r2},${r2} 1 0,1 ${w * 0.35},${1 * w * 0.2}
+
+ a${r1},${r1} 1 0,1 ${w * 0.15},${1 * h * 0.35}
+ a${r4},${r4} 1 0,1 ${-1 * w * 0.15},${1 * h * 0.65}
+
+ a${r2},${r1} 1 0,1 ${-1 * w * 0.25},${w * 0.15}
+ a${r3},${r3} 1 0,1 ${-1 * w * 0.5},${0}
+ a${r1},${r1} 1 0,1 ${-1 * w * 0.25},${-1 * w * 0.15}
+
+ a${r1},${r1} 1 0,1 ${-1 * w * 0.1},${-1 * h * 0.35}
+ a${r4},${r4} 1 0,1 ${w * 0.1},${-1 * h * 0.65}
+
+ H0 V0 Z`
+ );
+};
+const bangBkg = function (elem, node, section, conf) {
+ const rd = 5;
+ const w = node.width;
+ const h = node.height;
+ const r = 0.15 * w;
+ const p = elem
+ .append('path')
+ .attr('id', 'node-' + node.id)
+ .attr('class', 'node-bkg node-' + db.type2Str(node.type))
+ .attr(
+ 'd',
+ `M0 0 a${r},${r} 1 0,0 ${w * 0.25},${-1 * h * 0.1}
+ a${r},${r} 1 0,0 ${w * 0.25},${0}
+ a${r},${r} 1 0,0 ${w * 0.25},${0}
+ a${r},${r} 1 0,0 ${w * 0.25},${1 * h * 0.1}
+
+ a${r},${r} 1 0,0 ${w * 0.15},${1 * h * 0.33}
+ a${r * 0.8},${r * 0.8} 1 0,0 ${0},${1 * h * 0.34}
+ a${r},${r} 1 0,0 ${-1 * w * 0.15},${1 * h * 0.33}
+
+ a${r},${r} 1 0,0 ${-1 * w * 0.25},${h * 0.15}
+ a${r},${r} 1 0,0 ${-1 * w * 0.25},${0}
+ a${r},${r} 1 0,0 ${-1 * w * 0.25},${0}
+ a${r},${r} 1 0,0 ${-1 * w * 0.25},${-1 * h * 0.15}
+
+ a${r},${r} 1 0,0 ${-1 * w * 0.1},${-1 * h * 0.33}
+ a${r * 0.8},${r * 0.8} 1 0,0 ${0},${-1 * h * 0.34}
+ a${r},${r} 1 0,0 ${w * 0.1},${-1 * h * 0.33}
+
+ H0 V0 Z`
+ );
+};
+const circleBkg = function (elem, node, section, conf) {
+ const r = elem
+ .append('circle')
+ .attr('id', 'node-' + node.id)
+ .attr('class', 'node-bkg node-' + db.type2Str(node.type))
+ .attr('r', node.width / 2);
+ // .attr('width', node.width);
+};
+const roundedRectBkg = function (elem, node, section, conf) {
+ const r = elem
+ .append('rect')
+ .attr('id', 'node-' + node.id)
+ .attr('class', 'node-bkg node-' + db.type2Str(node.type))
+ .attr('height', node.height)
+ .attr('rx', node.padding)
+ .attr('ry', node.padding)
+ .attr('width', node.width);
+};
+
+/**
+ * @param {object} elem The D3 dom element in which the node is to be added
+ * @param {object} node The node to be added
+ * @param section
+ * @param {object} conf The configuration object
+ * @returns {number} The height nodes dom element
+ */
+export const drawNode = function (elem, node, section, conf) {
+ const nodeElem = elem.append('g');
+ nodeElem.attr(
+ 'class',
+ (node.class ? node.class + ' ' : '') +
+ 'mindmap-node ' +
+ (section === -1 ? 'section-root' : 'section-' + section)
+ );
+ const bkgElem = nodeElem.append('g');
+
+ // Create the wrapped text element
+ const textElem = nodeElem.append('g');
+ const txt = textElem
+ .append('text')
+ .text(node.descr)
+ .attr('dy', '1em')
+ // .attr('dy', '0')
+ .attr('alignment-baseline', 'middle')
+ .attr('dominant-baseline', 'middle')
+ .attr('text-anchor', 'middle')
+ .call(wrap, node.width);
+ const bbox = txt.node().getBBox();
+ const fontSize = conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize;
+ node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding;
+ node.width = bbox.width + 2 * node.padding;
+ if (node.icon) {
+ if (node.type === db.nodeType.CIRCLE) {
+ node.height += 50;
+ const orgWidth = node.width;
+ node.width += 50;
+ // node.width = Math.max(orgWidth, 100);
+ const widthDiff = Math.abs(node.width - orgWidth);
+ const icon = nodeElem
+ .append('foreignObject')
+ .attr('height', '50px')
+ .attr('width', node.width)
+ .attr('style', 'text-align: center;');
+ // .attr('x', 0)
+ // .attr('y', 0)
+ // .attr('class', 'node-icon ' + node.icon);
+ icon
+ .append('div')
+ .attr('class', 'icon-container')
+ .append('i')
+ .attr('class', 'node-icon-' + section + ' ' + node.icon);
+ textElem.attr(
+ 'transform',
+ 'translate(' + node.width / 2 + ', ' + (node.height / 2 - 1.5 * node.padding) + ')'
+ );
+ } else {
+ node.width += 50;
+ const orgHeight = node.height;
+ node.height = Math.max(orgHeight, 60);
+ const heightDiff = Math.abs(node.height - orgHeight);
+ const icon = nodeElem
+ .append('foreignObject')
+ .attr('width', '60px')
+ .attr('height', node.height)
+ .attr('style', 'text-align: center;margin-top:' + heightDiff / 2 + 'px;');
+ // .attr('x', 0)
+ // .attr('y', 0)
+ // .attr('class', 'node-icon ' + node.icon);
+ icon
+ .append('div')
+ .attr('class', 'icon-container')
+ .append('i')
+ .attr('class', 'node-icon-' + section + ' ' + node.icon);
+ textElem.attr(
+ 'transform',
+ 'translate(' + (25 + node.width / 2) + ', ' + (heightDiff / 2 + node.padding / 2) + ')'
+ );
+ }
+ } else {
+ textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')');
+ }
+
+ switch (node.type) {
+ case db.nodeType.DEFAULT:
+ defaultBkg(bkgElem, node, section, conf);
+ break;
+ case db.nodeType.ROUNDED_RECT:
+ roundedRectBkg(bkgElem, node, section, conf);
+ break;
+ case db.nodeType.RECT:
+ rectBkg(bkgElem, node, section, conf);
+ break;
+ case db.nodeType.CIRCLE:
+ bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')');
+ circleBkg(bkgElem, node, section, conf);
+ break;
+ case db.nodeType.CLOUD:
+ // bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')');
+ cloudBkg(bkgElem, node, section, conf);
+ break;
+ case db.nodeType.BANG:
+ // bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')');
+ bangBkg(bkgElem, node, section, conf);
+ break;
+ default:
+ // defaultBkg(bkgElem, node, section, conf);
+ }
+
+ // Position the node to its coordinate
+ if (typeof node.x !== 'undefined' && typeof node.y !== 'undefined') {
+ nodeElem.attr('transform', 'translate(' + node.x + ',' + node.y + ')');
+ }
+ db.setElementForId(node.id, nodeElem);
+ return node.height;
+};
+
+export const drawEdge = function drawEdge(edgesElem, mindmap, parent, depth, section, conf) {
+ // edgesElem
+ // .append('line')
+ // .attr('x1', parent.x + parent.width / 2)
+ // .attr('y1', parent.y + parent.height / 2)
+ // .attr('x2', mindmap.x + mindmap.width / 2)
+ // .attr('y2', mindmap.y + mindmap.height / 2)
+ // .attr('class', 'edge section-edge-' + section + ' edge-depth-' + depth);
+
+ //
+
+ const sx = parent.x + parent.width / 2;
+ const sy = parent.y + parent.height / 2;
+ const ex = mindmap.x + mindmap.width / 2;
+ const ey = mindmap.y + mindmap.height / 2;
+ const mx = ex > sx ? sx + Math.abs(sx - ex) / 2 : sx - Math.abs(sx - ex) / 2;
+ const my = ey > sy ? sy + Math.abs(sy - ey) / 2 : sy - Math.abs(sy - ey) / 2;
+ const qx = ex > sx ? Math.abs(sx - mx) / 2 + sx : -Math.abs(sx - mx) / 2 + sx;
+ const qy = ey > sy ? Math.abs(sy - my) / 2 + sy : -Math.abs(sy - my) / 2 + sy;
+
+ edgesElem
+ .append('path')
+ .attr(
+ 'd',
+ parent.direction === 'TB' || parent.direction === 'BT'
+ ? `M${sx},${sy} Q${sx},${qy} ${mx},${my} T${ex},${ey}`
+ : `M${sx},${sy} Q${qx},${sy} ${mx},${my} T${ex},${ey}`
+ )
+ .attr('class', 'edge section-edge-' + section + ' edge-depth-' + depth);
+};
+
+export const positionNode = function (node, conf) {
+ const nodeElem = db.getElementById(node.id);
+
+ const x = node.x || 0;
+ const y = node.y || 0;
+ // Position the node to its coordinate
+ nodeElem.attr('transform', 'translate(' + x + ',' + y + ')');
+};
+
+export default { drawNode, positionNode, drawEdge };
diff --git a/src/diagrams/pie/pieRenderer.js b/src/diagrams/pie/pieRenderer.js
index b5ffc9de3..f8e21bc9d 100644
--- a/src/diagrams/pie/pieRenderer.js
+++ b/src/diagrams/pie/pieRenderer.js
@@ -1,7 +1,7 @@
/** Created by AshishJ on 11-09-2019. */
import { select, scaleOrdinal, pie as d3pie, arc } from 'd3';
import { log } from '../../logger';
-import { configureSvgSize } from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import * as configApi from '../../config';
import addSVGAccessibilityFields from '../../accessibility';
diff --git a/src/diagrams/requirement/requirementRenderer.js b/src/diagrams/requirement/requirementRenderer.js
index e07612e0e..7a43edaee 100644
--- a/src/diagrams/requirement/requirementRenderer.js
+++ b/src/diagrams/requirement/requirementRenderer.js
@@ -2,7 +2,7 @@ import { line, select } from 'd3';
import dagre from 'dagre';
import graphlib from 'graphlib';
import { log } from '../../logger';
-import { configureSvgSize } from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import common from '../common/common';
import markers from './requirementMarkers';
import { getConfig } from '../../config';
diff --git a/src/diagrams/sequence/sequenceRenderer.js b/src/diagrams/sequence/sequenceRenderer.js
index a02f09b74..26e956039 100644
--- a/src/diagrams/sequence/sequenceRenderer.js
+++ b/src/diagrams/sequence/sequenceRenderer.js
@@ -6,7 +6,8 @@ import common from '../common/common';
// import sequenceDb from './sequenceDb';
import * as configApi from '../../config';
import assignWithDepth from '../../assignWithDepth';
-import utils, { configureSvgSize } from '../../utils';
+import utils from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import addSVGAccessibilityFields from '../../accessibility';
let conf = {};
@@ -326,7 +327,7 @@ const boundMessage = function (diagram, msgModel) {
*
* @param {any} diagram - The parent of the message element
* @param {any} msgModel - The model containing fields describing a message
- * @param {float} lineStarty - The Y coordinate at which the message line starts
+ * @param {number} lineStarty - The Y coordinate at which the message line starts
* @param diagObj
*/
const drawMessage = function (diagram, msgModel, lineStarty, diagObj) {
@@ -582,8 +583,7 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop
/**
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
*
- * @param {any} text The text of the diagram
- * @param _text
+ * @param {any} _text The text of the diagram
* @param {any} id The id of the diagram which will be used as a DOM element id¨
* @param {any} _version Mermaid version from package.json
* @param {any} diagObj A stanard diagram containing the db and the text and type etc of the diagram
diff --git a/src/diagrams/state/shapes.js b/src/diagrams/state/shapes.js
index c3826856e..e2286bb51 100644
--- a/src/diagrams/state/shapes.js
+++ b/src/diagrams/state/shapes.js
@@ -66,9 +66,9 @@ export const drawSimpleState = (g, stateDef) => {
/**
* Draws a state with descriptions
*
- * @param {any} g
+ * @param {any} g The d3 svg object to add the state to
* @param {any} stateDef
- * @returns
+ * @returns {any} The d3 svg state
*/
export const drawDescrState = (g, stateDef) => {
const addTspan = function (textEl, txt, isFirst) {
diff --git a/src/diagrams/state/stateRenderer-v2.js b/src/diagrams/state/stateRenderer-v2.js
index 37dbf2e0a..4452f9491 100644
--- a/src/diagrams/state/stateRenderer-v2.js
+++ b/src/diagrams/state/stateRenderer-v2.js
@@ -3,7 +3,7 @@ import { select } from 'd3';
import { getConfig } from '../../config';
import { render } from '../../dagre-wrapper/index.js';
import { log } from '../../logger';
-import { configureSvgSize } from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import common from '../common/common';
import addSVGAccessibilityFields from '../../accessibility';
diff --git a/src/diagrams/state/stateRenderer.js b/src/diagrams/state/stateRenderer.js
index 3b8589b92..75368c557 100644
--- a/src/diagrams/state/stateRenderer.js
+++ b/src/diagrams/state/stateRenderer.js
@@ -5,7 +5,7 @@ import { log } from '../../logger';
import common from '../common/common';
import { drawState, addTitleAndBox, drawEdge } from './shapes';
import { getConfig } from '../../config';
-import { configureSvgSize } from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import addSVGAccessibilityFields from '../../accessibility';
// TODO Move conf object to main conf in mermaidAPI
diff --git a/src/diagrams/user-journey/journeyRenderer.js b/src/diagrams/user-journey/journeyRenderer.js
index ac15ed690..c6e1bb845 100644
--- a/src/diagrams/user-journey/journeyRenderer.js
+++ b/src/diagrams/user-journey/journeyRenderer.js
@@ -1,7 +1,7 @@
import { select } from 'd3';
import svgDraw from './svgDraw';
import { getConfig } from '../../config';
-import { configureSvgSize } from '../../utils';
+import { configureSvgSize } from '../../setupGraphViewbox';
import addSVGAccessibilityFields from '../../accessibility';
export const setConf = function (cnf) {
diff --git a/src/docs/_sidebar.md b/src/docs/_sidebar.md
index 54ef50afb..f89f30318 100644
--- a/src/docs/_sidebar.md
+++ b/src/docs/_sidebar.md
@@ -17,6 +17,7 @@
- [Requirement Diagram](requirementDiagram.md)
- [Gitgraph (Git) Diagram 🔥](gitgraph.md)
- [C4C Diagram (Context) Diagram 🦺⚠️](c4c.md)
+ - [Mindmaps 🦺⚠️](mindmap.md)
- [Other Examples](examples.md)
- ⚙️ Deployment and Configuration
diff --git a/src/docs/development.md b/src/docs/development.md
index 96da46c11..95e5fe417 100644
--- a/src/docs/development.md
+++ b/src/docs/development.md
@@ -6,7 +6,7 @@ So you want to help? That's great!
Here are a few things to get you started on the right path.
-**The Docs Structure is dictated by [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/docs/_sidebar.md)**
+**The Docs Structure is dictated by [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**
**Note: Commits and Pull Requests should be directed to the develop branch.**
@@ -44,9 +44,9 @@ Start with the type, such as **feature** or **bug**, followed by the issue numbe
If it is not in the documentation, it's like it never happened. Wouldn't that be sad? With all the effort that was put into the feature?
-The docs are located in the `src/docs` folder and are written in Markdown. Just pick the right section and start typing. If you want to propose changes to the structure of the documentation, such as adding a new section or a new file you do that via the **[sidebar](https://github.com/mermaid-js/mermaid/edit/develop/docs/_sidebar.md)**.
+The docs are located in the `src/docs` folder and are written in Markdown. Just pick the right section and start typing. If you want to propose changes to the structure of the documentation, such as adding a new section or a new file you do that via the **[sidebar](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**.
-> **All the documents displayed in the github.io page are listed in [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/docs/_sidebar.md)**.
+> **All the documents displayed in the GitHub.io page are listed in [sidebar.md](https://github.com/mermaid-js/mermaid/edit/develop/src/docs/_sidebar.md)**.
The contents of [https://mermaid-js.github.io/mermaid/](https://mermaid-js.github.io/mermaid/) are based on the docs from the `master` branch. Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid-js.github.io/mermaid/) once released.
@@ -58,7 +58,7 @@ The documentation is located in the `src/docs` directory and organized according
The `docs` folder will be automatically generated when committing to `src/docs` and should not be edited manually.
-We encourage contributions to the documentation at [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs). We publish documentation using GitHub Pages with [Docsify](https://www.youtube.com/watch?v=TV88lp7egMw&t=3s)
+We encourage contributions to the documentation at [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs). We publish documentation using GitHub Pages with [Docsify](https://www.youtube.com/watch?v=TV88lp7egMw&t=3s)
### Add Unit Tests for Parsing
@@ -71,7 +71,7 @@ This tests the rendering and visual appearance of the diagrams. This ensures tha
To start working with the e2e tests:
1. Run `yarn dev` to start the dev server
-2. Start **Cypress** by running `cypress open` in the **mermaid** folder.
+2. Start **Cypress** by running `cypress open` in the **mermaid** folder.
(Make sure you have path to Cypress in order, the binary is located in `node_modules/.bin`).
The rendering tests are very straightforward to create. There is a function `imgSnapshotTest`, which takes a diagram in text form and the mermaid options, and it renders that diagram in Cypress.
@@ -112,7 +112,7 @@ Markdown is used to format the text, for more information about Markdown [see th
To edit Docs on your computer:
-1. Find the Markdown file (.md) to edit in the [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs) directory in the `develop` branch.
+1. Find the Markdown file (.md) to edit in the [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs) directory in the `develop` branch.
2. Create a fork of the develop branch.
3. Make changes or add new documentation.
4. Commit changes to your fork and push it to GitHub.
@@ -121,7 +121,7 @@ To edit Docs on your computer:
To edit Docs on GitHub:
1. Login to [GitHub.com](https://www.github.com).
-2. Navigate to [mermaid-js/mermaid/docs](https://github.com/mermaid-js/mermaid/tree/develop/docs).
+2. Navigate to [mermaid-js/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/src/docs).
3. To edit a file, click the pencil icon at the top-right of the file contents panel.
4. Describe what you changed in the **Propose file change** section, located at the bottom of the page.
5. Submit your changes by clicking the button **Propose file change** at the bottom (by automatic creation of a fork and a new branch).
diff --git a/src/docs/directives.md b/src/docs/directives.md
index cee45f0b5..47af50c31 100644
--- a/src/docs/directives.md
+++ b/src/docs/directives.md
@@ -132,7 +132,7 @@ A --> C[End]
### Changing fontFamily via directive
-The following code snippet changes theme to forest:
+The following code snippet changes fontFamily to rebuchet MS, Verdana, Arial, Sans-Serif:
`%%{init: { "fontFamily": "Trebuchet MS, Verdana, Arial, Sans-Serif" } }%%`
@@ -152,7 +152,7 @@ A --> C[End]
### Changing logLevel via directive
-The following code snippet changes theme to forest:
+The following code snippet changes logLevel to 2:
`%%{init: { "logLevel": 2 } }%%`
diff --git a/src/docs/mindmap.md b/src/docs/mindmap.md
new file mode 100644
index 000000000..d7f1b4817
--- /dev/null
+++ b/src/docs/mindmap.md
@@ -0,0 +1,158 @@
+# Mindmap
+
+**Edit this Page** [](https://github.com/mermaid-js/mermaid/blob/develop/src/docs/mindmap.md)
+
+> 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.
+
+"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
+
+### An example of a mindmap.
+
+```mermaid
+mindmap
+ root((mindmap))
+ Origins
+ Long history
+ ::icon(fa fa-book)
+ Popularisation
+ British popular psychology author Tony Buzan
+ Research
+ On effectivness
and eatures
+ On Automatic creation
+ Uses
+ Creative techniques
+ Strategic planning
+ Argument mapping
+ Tools
+ Pen and paper
+ Mermaid
+
+```
+
+## Syntax
+
+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.
+
+```
+mindmap
+ Root
+ A
+ B
+ C
+```
+
+In summary is a simple text outline where there are one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
+
+```mermaid
+mindmap
+Root
+ A
+ B
+ C
+```
+
+In this way we can use a text outline to generate a hierarchical mindmap.
+
+## Different shapes
+
+Mermaids mindmaps can show node using different shapes. When specifying a shape for a node the syntax for the is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts even though they are not all supported from the start.
+
+Mindmap can show the following shapes:
+
+### Square
+
+```mermaid-example
+mindmap
+ id[I am a square]
+```
+
+### Rounded square
+
+```mermaid-example
+mindmap
+ id(I am a rounded square)
+```
+
+### Circle
+
+```mermaid-example
+mindmap
+ id((I am a circle))
+```
+
+### Bang
+
+```mermaid-example
+mindmap
+ id))I am a bang((
+```
+
+### Cloud
+
+```mermaid-example
+mindmap
+ id)I am a cloud(
+```
+
+### Default
+
+```mermaid-example
+mindmap
+ I am the default shape
+```
+
+More shapes will be added, beginning with the shapes available in flowcharts.
+
+# Icons and classes
+
+## icons
+
+As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and fontawesome 4 are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
+
+```mermaid-example
+mindmap
+ Root
+ A
+ ::icon(fa fa-book)
+ B(B)
+ ::icon(mdi mdi-skull-outline)
+```
+
+## Classes
+
+Again the syntax for adding classes is similar to flowcharts. You can add classes using a triple colon following a number of css classes separated by space. In the following example one of the nodes has two custom classes attached urgent turning the background red and the text white and large increasing the font size:
+
+```mermaid-example
+mindmap
+ Root
+ A[A]
+ :::urgent large
+ B(B)
+ C
+```
+
+_These classes needs top be supplied by the site administrator._
+
+## Unclear indentation
+
+The actual indentation does not really matter only compared with the previous rows. If we take the previous example and disrupt it a little we can se how the calculations are performed. Let us start with placing C with a smaller indentation than `B`but larger then `A`.
+
+```
+mindmap
+ Root
+ A
+ B
+ C
+```
+
+This outline is unclear as `B` clearly is a child of `A` but when we move on to `C` the clarity is lost. `C` is not a child of `B` with a higher indentation nor does it have the same indentation as `B`. The only thing that is clear is that the first node with smaller indentation, indicating a parent, is A. Then Mermaid relies on this known truth and compensates for the unclear indentation and selects `A` as a parent of `C` leading till the same diagram with `B` and `C` as siblings.
+
+```mermaid
+mindmap
+Root
+ A
+ B
+ C
+```
diff --git a/src/mermaid.ts b/src/mermaid.ts
index e5181a56f..e42c383fd 100644
--- a/src/mermaid.ts
+++ b/src/mermaid.ts
@@ -14,7 +14,8 @@ import { isDetailedError } from './utils';
* Function that goes through the document to find the chart definitions in there and render them.
*
* The function tags the processed attributes with the attribute data-processed and ignores found
- * elements with the attribute already set. This way the init function can be triggered several times.
+ * elements with the attribute already set. This way the init function can be triggered several
+ * times.
*
* Optionally, `init` can accept in the second argument one of the following:
*
@@ -37,6 +38,7 @@ import { isDetailedError } from './utils';
*/
const init = function (
config?: MermaidConfig,
+ // eslint-disable-next-line no-undef
nodes?: string | HTMLElement | NodeListOf,
// eslint-disable-next-line @typescript-eslint/ban-types
callback?: Function
@@ -56,6 +58,7 @@ const init = function (
const initThrowsErrors = function (
config?: MermaidConfig,
+ // eslint-disable-next-line no-undef
nodes?: string | HTMLElement | NodeListOf,
// eslint-disable-next-line @typescript-eslint/ban-types
callback?: Function
@@ -130,7 +133,7 @@ const initThrowsErrors = function (
element
);
} catch (error) {
- log.warn('Catching Error (bootstrap)');
+ log.warn('Catching Error (bootstrap)', error);
// @ts-ignore: TODO Fix ts errors
// TODO: We should be throwing an error object.
throw { error, message: error.str };
@@ -144,7 +147,8 @@ const initialize = function (config: MermaidConfig) {
/**
* ##contentLoaded Callback function that is called when page is loaded. This functions fetches
- * configuration for mermaid rendering and calls init for rendering the mermaid diagrams on the page.
+ * configuration for mermaid rendering and calls init for rendering the mermaid diagrams on the
+ * page.
*/
const contentLoaded = function () {
if (mermaid.startOnLoad) {
@@ -159,13 +163,7 @@ if (typeof document !== 'undefined') {
/*!
* Wait for document loaded before starting the execution
*/
- window.addEventListener(
- 'load',
- function () {
- contentLoaded();
- },
- false
- );
+ window.addEventListener('load', contentLoaded, false);
}
/**
diff --git a/src/setupGraphViewbox.js b/src/setupGraphViewbox.js
new file mode 100644
index 000000000..8ffdab5e7
--- /dev/null
+++ b/src/setupGraphViewbox.js
@@ -0,0 +1,83 @@
+import { log } from './logger';
+
+/**
+ * Applys d3 attributes
+ *
+ * @param {any} d3Elem D3 Element to apply the attributes onto
+ * @param {[string, string][]} attrs Object.keys equivalent format of key to value mapping of attributes
+ */
+const d3Attrs = function (d3Elem, attrs) {
+ for (let attr of attrs) {
+ d3Elem.attr(attr[0], attr[1]);
+ }
+};
+
+/**
+ * Gives attributes for an SVG's size given arguments
+ *
+ * @param {number} height The height of the SVG
+ * @param {number} width The width of the SVG
+ * @param {boolean} useMaxWidth Whether or not to use max-width and set width to 100%
+ * @returns {Map<'height' | 'width' | 'style', string>} Attributes for the SVG
+ */
+export const calculateSvgSizeAttrs = function (height, width, useMaxWidth) {
+ let attrs = new Map();
+ if (useMaxWidth) {
+ attrs.set('width', '100%');
+ attrs.set('style', `max-width: ${width}px;`);
+ } else {
+ attrs.set('width', width);
+ }
+ return attrs;
+};
+
+/**
+ * Applies attributes from `calculateSvgSizeAttrs`
+ *
+ * @param {SVGSVGElement} svgElem The SVG Element to configure
+ * @param {number} height The height of the SVG
+ * @param {number} width The width of the SVG
+ * @param {boolean} useMaxWidth Whether or not to use max-width and set width to 100%
+ */
+export const configureSvgSize = function (svgElem, height, width, useMaxWidth) {
+ const attrs = calculateSvgSizeAttrs(height, width, useMaxWidth);
+ d3Attrs(svgElem, attrs);
+};
+export const setupGraphViewbox = function (graph, svgElem, padding, useMaxWidth) {
+ const svgBounds = svgElem.node().getBBox();
+ const sWidth = svgBounds.width;
+ const sHeight = svgBounds.height;
+
+ log.info(`SVG bounds: ${sWidth}x${sHeight}`, svgBounds);
+
+ let width = 0;
+ let height = 0;
+ log.info(`Graph bounds: ${width}x${height}`, graph);
+
+ // let tx = 0;
+ // let ty = 0;
+ // if (sWidth > width) {
+ // tx = (sWidth - width) / 2 + padding;
+ width = sWidth + padding * 2;
+ // } else {
+ // if (Math.abs(sWidth - width) >= 2 * padding + 1) {
+ // width = width - padding;
+ // }
+ // }
+ // if (sHeight > height) {
+ // ty = (sHeight - height) / 2 + padding;
+ height = sHeight + padding * 2;
+ // }
+
+ // width =
+ log.info(`Calculated bounds: ${width}x${height}`);
+ configureSvgSize(svgElem, height, width, useMaxWidth);
+
+ // Ensure the viewBox includes the whole svgBounds area with extra space for padding
+ // const vBox = `0 0 ${width} ${height}`;
+ const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${
+ svgBounds.width + 2 * padding
+ } ${svgBounds.height + 2 * padding}`;
+
+ svgElem.attr('viewBox', vBox);
+};
diff --git a/src/setupGraphViewbox.spec.js b/src/setupGraphViewbox.spec.js
new file mode 100644
index 000000000..6b5c3e210
--- /dev/null
+++ b/src/setupGraphViewbox.spec.js
@@ -0,0 +1,20 @@
+import utils from './utils';
+import assignWithDepth from './assignWithDepth';
+import { detectType } from './diagram-api/detectType';
+import { addDiagrams } from './diagram-api/diagram-orchestration';
+import { calculateSvgSizeAttrs } from './setupGraphViewbox';
+addDiagrams();
+
+describe('when calculating SVG size', function () {
+ it('should return width 100% when useMaxWidth is true', function () {
+ const attrs = calculateSvgSizeAttrs(100, 200, true);
+ // expect(attrs.get('height')).toEqual(100);
+ expect(attrs.get('style')).toEqual('max-width: 200px;');
+ expect(attrs.get('width')).toEqual('100%');
+ });
+ it('should return absolute width when useMaxWidth is false', function () {
+ const attrs = calculateSvgSizeAttrs(100, 200, false);
+ // expect(attrs.get('height')).toEqual(100);
+ expect(attrs.get('width')).toEqual(200);
+ });
+});
diff --git a/src/styles.ts b/src/styles.ts
index 15af0c955..c1ccf0639 100644
--- a/src/styles.ts
+++ b/src/styles.ts
@@ -2,7 +2,7 @@ import classDiagram from './diagrams/class/styles';
import er from './diagrams/er/styles';
import flowchart from './diagrams/flowchart/styles';
import gantt from './diagrams/gantt/styles';
-import gitGraph from './diagrams/git/styles';
+// import gitGraph from './diagrams/git/styles';
import info from './diagrams/info/styles';
import pie from './diagrams/pie/styles';
import requirement from './diagrams/requirement/styles';
@@ -14,7 +14,7 @@ import { FlowChartStyleOptions } from './diagrams/flowchart/styles';
import { log } from './logger';
// TODO @knut: Inject from registerDiagram.
-const themes = {
+const themes: Record = {
flowchart,
'flowchart-v2': flowchart,
sequence,
@@ -24,7 +24,7 @@ const themes = {
class: classDiagram,
stateDiagram,
state: stateDiagram,
- gitGraph,
+ // gitGraph,
info,
pie,
er,
@@ -103,4 +103,8 @@ const getStyles = (
`;
};
+export const addStylesForDiagram = (type: string, diagramTheme: unknown): void => {
+ themes[type] = diagramTheme;
+};
+
export default getStyles;
diff --git a/src/utils.spec.js b/src/utils.spec.js
index 37f004a7e..ebaa1415c 100644
--- a/src/utils.spec.js
+++ b/src/utils.spec.js
@@ -2,7 +2,6 @@ import utils from './utils';
import assignWithDepth from './assignWithDepth';
import { detectType } from './diagram-api/detectType';
import { addDiagrams } from './diagram-api/diagram-orchestration';
-
addDiagrams();
describe('when assignWithDepth: should merge objects within objects', function () {
@@ -291,19 +290,6 @@ describe('when formatting urls', function () {
expect(result).toEqual('about:blank');
});
});
-describe('when calculating SVG size', function () {
- it('should return width 100% when useMaxWidth is true', function () {
- const attrs = utils.calculateSvgSizeAttrs(100, 200, true);
- // expect(attrs.get('height')).toEqual(100);
- expect(attrs.get('style')).toEqual('max-width: 200px;');
- expect(attrs.get('width')).toEqual('100%');
- });
- it('should return absolute width when useMaxWidth is false', function () {
- const attrs = utils.calculateSvgSizeAttrs(100, 200, false);
- // expect(attrs.get('height')).toEqual(100);
- expect(attrs.get('width')).toEqual(200);
- });
-});
describe('when initializing the id generator', function () {
it('should return a random number generator based on Date', function (done) {
diff --git a/src/utils.ts b/src/utils.ts
index a88555246..9caad7edd 100644
--- a/src/utils.ts
+++ b/src/utils.ts
@@ -44,8 +44,8 @@ const anyComment = /\s*%%.*\n/gm;
/**
* @function detectInit Detects the init config object from the text
* @param config
- *
* @param config
+ *
* ```mermaid
*
* %%{init: {"theme": "debug", "logLevel": 1 }}%%
@@ -634,7 +634,8 @@ const breakString = memoize(
* If the wrapped text text has greater height, we extend the height, so it's value won't overflow.
*
* @param {any} text The text to measure
- * @param {any} config - The config for fontSize, fontFamily, and fontWeight all impacting the resulting size
+ * @param {any} config - The config for fontSize, fontFamily, and fontWeight all impacting the
+ * resulting size
* @returns {any} - The height for the given text
*/
export const calculateTextHeight = function (text, config) {
@@ -649,7 +650,8 @@ export const calculateTextHeight = function (text, config) {
* This calculates the width of the given text, font size and family.
*
* @param {any} text - The text to calculate the width of
- * @param {any} config - The config for fontSize, fontFamily, and fontWeight all impacting the resulting size
+ * @param {any} config - The config for fontSize, fontFamily, and fontWeight all impacting the
+ * resulting size
* @returns {any} - The width for the given text
*/
export const calculateTextWidth = function (text, config) {
@@ -658,7 +660,8 @@ export const calculateTextWidth = function (text, config) {
};
/**
- * This calculates the dimensions of the given text, font size, font family, font weight, and margins.
+ * This calculates the dimensions of the given text, font size, font family, font weight, and
+ * margins.
*
* @param {any} text - The text to calculate the width of
* @param {any} config - The config for fontSize, fontFamily, fontWeight, and margin all impacting
@@ -729,7 +732,8 @@ export const calculateTextDimensions = memoize(
* Applys d3 attributes
*
* @param {any} d3Elem D3 Element to apply the attributes onto
- * @param {[string, string][]} attrs Object.keys equivalent format of key to value mapping of attributes
+ * @param {[string, string][]} attrs Object.keys equivalent format of key to value mapping of
+ * attributes
*/
const d3Attrs = function (d3Elem, attrs) {
for (const attr of attrs) {
@@ -737,92 +741,6 @@ const d3Attrs = function (d3Elem, attrs) {
}
};
-/**
- * Gives attributes for an SVG's size given arguments
- *
- * @param {number} height The height of the SVG
- * @param {number} width The width of the SVG
- * @param {boolean} useMaxWidth Whether or not to use max-width and set width to 100%
- * @returns {Map<'height' | 'width' | 'style', string>} Attributes for the SVG
- */
-export const calculateSvgSizeAttrs = function (height, width, useMaxWidth) {
- const attrs = new Map();
- // attrs.set('height', height);
- if (useMaxWidth) {
- attrs.set('width', '100%');
- attrs.set('style', `max-width: ${width}px;`);
- } else {
- attrs.set('width', width);
- }
- return attrs;
-};
-
-/**
- * Applies attributes from `calculateSvgSizeAttrs`
- *
- * @param {SVGSVGElement} svgElem The SVG Element to configure
- * @param {number} height The height of the SVG
- * @param {number} width The width of the SVG
- * @param {boolean} useMaxWidth Whether or not to use max-width and set width to 100%
- */
-export const configureSvgSize = function (svgElem, height, width, useMaxWidth) {
- const attrs = calculateSvgSizeAttrs(height, 1 * width, useMaxWidth);
- d3Attrs(svgElem, attrs);
-};
-export const setupGraphViewbox = function (graph, svgElem, padding, useMaxWidth) {
- const svgBounds = svgElem.node().getBBox();
- const sWidth = svgBounds.width;
- const sHeight = svgBounds.height;
-
- log.info(`SVG bounds: ${sWidth}x${sHeight}`, svgBounds);
-
- let width = graph._label.width;
- let height = graph._label.height;
- log.info(`Graph bounds: ${width}x${height}`, graph);
-
- // let tx = 0;
- // let ty = 0;
- // if (sWidth > width) {
- // tx = (sWidth - width) / 2 + padding;
- width = sWidth + padding * 2;
- // } else {
- // if (Math.abs(sWidth - width) >= 2 * padding + 1) {
- // width = width - padding;
- // }
- // }
- // if (sHeight > height) {
- // ty = (sHeight - height) / 2 + padding;
- height = sHeight + padding * 2;
- // }
-
- // width =
- log.info(`Calculated bounds: ${width}x${height}`);
- configureSvgSize(svgElem, height, width, useMaxWidth);
-
- // Ensure the viewBox includes the whole svgBounds area with extra space for padding
- // const vBox = `0 0 ${width} ${height}`;
- const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${
- svgBounds.width + 2 * padding
- } ${svgBounds.height + 2 * padding}`;
- log.info(
- 'Graph.label',
- graph._label,
- 'swidth',
- sWidth,
- 'sheight',
- sHeight,
- 'width',
- width,
- 'height',
- height,
-
- 'vBox',
- vBox
- );
- svgElem.attr('viewBox', vBox);
- // svgElem.select('g').attr('transform', `translate(${tx}, ${ty})`);
-};
-
export const initIdGenerator = class iterator {
constructor(deterministic, seed) {
this.deterministic = deterministic;
@@ -948,18 +866,12 @@ export interface DetailedError {
hash: any;
}
-/**
- *
- * @param error
- */
+/** @param error */
export function isDetailedError(error: unknown): error is DetailedError {
return 'str' in error;
}
-/**
- *
- * @param error
- */
+/** @param error */
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
return String(error);
@@ -971,9 +883,6 @@ export default {
calculateTextHeight,
calculateTextWidth,
calculateTextDimensions,
- calculateSvgSizeAttrs,
- configureSvgSize,
- setupGraphViewbox,
detectInit,
detectDirective,
isSubstringInArray,
diff --git a/tsconfig.json b/tsconfig.json
index f3a860e32..6a29f8328 100644
--- a/tsconfig.json
+++ b/tsconfig.json
@@ -13,7 +13,8 @@
/* Language and Environment */
"target": "es2016" /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */,
"lib": [
- "ES2021"
+ "ES2021",
+ "DOM"
] /* Specify a set of bundled library declaration files that describe the target runtime environment. */,
// "jsx": "preserve", /* Specify what JSX code is generated. */
// "experimentalDecorators": true, /* Enable experimental support for TC39 stage 2 draft decorators. */
diff --git a/yarn.lock b/yarn.lock
index f51ca2b89..58c3d2818 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -1869,15 +1869,15 @@
"@types/node" "*"
jest-mock "^28.1.3"
-"@jest/environment@^29.0.1":
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.0.1.tgz#d236ce9e906744ac58bfc59ae6f7c9882ace7927"
- integrity sha512-iLcFfoq2K6DAB+Mc+2VNLzZVmHdwQFeSqvoM/X8SMON6s/+yEi1iuRX3snx/JfwSnvmiMXjSr0lktxNxOcqXYA==
+"@jest/environment@^29.0.2":
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/@jest/environment/-/environment-29.0.2.tgz#9e4b6d4c9bce5bfced6f63945d8c8e571394f572"
+ integrity sha512-Yf+EYaLOrVCgts/aTS5nGznU4prZUPa5k9S63Yct8YSOKj2jkdS17hHSUKhk5jxDFMyCy1PXknypDw7vfgc/mA==
dependencies:
- "@jest/fake-timers" "^29.0.1"
- "@jest/types" "^29.0.1"
+ "@jest/fake-timers" "^29.0.2"
+ "@jest/types" "^29.0.2"
"@types/node" "*"
- jest-mock "^29.0.1"
+ jest-mock "^29.0.2"
"@jest/expect-utils@^28.1.3":
version "28.1.3"
@@ -1906,17 +1906,17 @@
jest-mock "^28.1.3"
jest-util "^28.1.3"
-"@jest/fake-timers@^29.0.1":
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.0.1.tgz#51ba7a82431db479d4b828576c139c4c0dc5e409"
- integrity sha512-XZ+kAhLChVQ+KJNa5034p7O1Mz3vtWrelxDcMoxhZkgqmWDaEQAW9qJeutaeCfPvwaEwKYVyKDYfWpcyT8RiMw==
+"@jest/fake-timers@^29.0.2":
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/@jest/fake-timers/-/fake-timers-29.0.2.tgz#6f15f4d8eb1089d445e3f73473ddc434faa2f798"
+ integrity sha512-2JhQeWU28fvmM5r33lxg6BxxkTKaVXs6KMaJ6eXSM8ml/MaWkt2BvbIO8G9KWAJFMdBXWbn+2h9OK1/s5urKZA==
dependencies:
- "@jest/types" "^29.0.1"
+ "@jest/types" "^29.0.2"
"@sinonjs/fake-timers" "^9.1.2"
"@types/node" "*"
- jest-message-util "^29.0.1"
- jest-mock "^29.0.1"
- jest-util "^29.0.1"
+ jest-message-util "^29.0.2"
+ jest-mock "^29.0.2"
+ jest-util "^29.0.2"
"@jest/globals@^28.1.3":
version "28.1.3"
@@ -2022,22 +2022,22 @@
slash "^3.0.0"
write-file-atomic "^4.0.1"
-"@jest/transform@^29.0.1":
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.0.1.tgz#fdaa5d9e135c9bd7addbe65bedd1f15ad028cc7e"
- integrity sha512-6UxXtqrPScFdDhoip8ys60dQAIYppQinyR87n9nlasR/ZnFfJohKToqzM29KK4gb9gHRv5oDFChdqZKE0SIhsg==
+"@jest/transform@^29.0.2":
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/@jest/transform/-/transform-29.0.2.tgz#eef90ebd939b68bf2c2508d9e914377871869146"
+ integrity sha512-lajVQx2AnsR+Pa17q2zR7eikz2PkPs1+g/qPbZkqQATeS/s6eT55H+yHcsLfuI/0YQ/4VSBepSu3bOX+44q0aA==
dependencies:
"@babel/core" "^7.11.6"
- "@jest/types" "^29.0.1"
+ "@jest/types" "^29.0.2"
"@jridgewell/trace-mapping" "^0.3.15"
babel-plugin-istanbul "^6.1.1"
chalk "^4.0.0"
convert-source-map "^1.4.0"
fast-json-stable-stringify "^2.1.0"
graceful-fs "^4.2.9"
- jest-haste-map "^29.0.1"
+ jest-haste-map "^29.0.2"
jest-regex-util "^29.0.0"
- jest-util "^29.0.1"
+ jest-util "^29.0.2"
micromatch "^4.0.4"
pirates "^4.0.4"
slash "^3.0.0"
@@ -2055,10 +2055,10 @@
"@types/yargs" "^17.0.8"
chalk "^4.0.0"
-"@jest/types@^29.0.1":
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.0.1.tgz#1985650acf137bdb81710ff39a4689ec071dd86a"
- integrity sha512-ft01rxzVsbh9qZPJ6EFgAIj3PT9FCRfBF9Xljo2/33VDOUjLZr0ZJ2oKANqh9S/K0/GERCsHDAQlBwj7RxA+9g==
+"@jest/types@^29.0.2":
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/@jest/types/-/types-29.0.2.tgz#5a5391fa7f7f41bf4b201d6d2da30e874f95b6c1"
+ integrity sha512-5WNMesBLmlkt1+fVkoCjHa0X3i3q8zc4QLTDkdHgCa2gyPZc7rdlZBWgVLqwS1860ZW5xJuCDwAzqbGaXIr/ew==
dependencies:
"@jest/schemas" "^29.0.0"
"@types/istanbul-lib-coverage" "^2.0.0"
@@ -2509,10 +2509,10 @@
dependencies:
"@types/ms" "*"
-"@types/dompurify@^2.3.3":
- version "2.3.3"
- resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.3.tgz#c24c92f698f77ed9cc9d9fa7888f90cf2bfaa23f"
- integrity sha512-nnVQSgRVuZ/843oAfhA25eRSNzUFcBPk/LOiw5gm8mD9/X7CNcbRkQu/OsjCewO8+VIYfPxUnXvPEVGenw14+w==
+"@types/dompurify@^2.3.4":
+ version "2.3.4"
+ resolved "https://registry.yarnpkg.com/@types/dompurify/-/dompurify-2.3.4.tgz#94e997e30338ea24d4c8d08beca91ce4dd17a1b4"
+ integrity sha512-EXzDatIb5EspL2eb/xPGmaC8pePcTHrkDCONjeisusLFrVfl38Pjea/R0YJGu3k9ZQadSvMqW0WXPI2hEo2Ajg==
dependencies:
"@types/trusted-types" "*"
@@ -2660,16 +2660,11 @@
resolved "https://registry.yarnpkg.com/@types/node/-/node-18.0.0.tgz#67c7b724e1bcdd7a8821ce0d5ee184d3b4dd525a"
integrity sha512-cHlGmko4gWLVI27cGJntjs/Sj8th9aYwplmZFwmmgYQQvL5NUsgVJG7OddLvNfLqYS31KFN0s3qlaD9qCaxACA==
-"@types/node@^14.0.0":
+"@types/node@^14.0.0", "@types/node@^14.14.31":
version "14.18.26"
resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.26.tgz#239e19f8b4ea1a9eb710528061c1d733dc561996"
integrity sha512-0b+utRBSYj8L7XAp0d+DX7lI4cSmowNaaTkk6/1SKzbKkG+doLuPusB9EOvzLJ8ahJSk03bTLIL6cWaEd4dBKA==
-"@types/node@^14.14.31":
- version "14.18.0"
- resolved "https://registry.yarnpkg.com/@types/node/-/node-14.18.0.tgz#98df2397f6936bfbff4f089e40e06fa5dd88d32a"
- integrity sha512-0GeIl2kmVMXEnx8tg1SlG6Gg8vkqirrW752KqolYo1PHevhhZN3bhJ67qHj+bQaINhX0Ra3TlWwRvMCd9iEfNQ==
-
"@types/node@^16.11.1":
version "16.11.33"
resolved "https://registry.yarnpkg.com/@types/node/-/node-16.11.33.tgz#566713b1b626f781c5c58fe3531307283e00720c"
@@ -3521,15 +3516,15 @@ babel-jest@^28.1.3:
graceful-fs "^4.2.9"
slash "^3.0.0"
-babel-jest@^29.0.1:
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.0.1.tgz#db50de501fc8727e768f5aa417496cb871ee1ba0"
- integrity sha512-wyI9r8tqwsZEMWiIaYjdUJ6ztZIO4DMWpGq7laW34wR71WtRS+D/iBEtXOP5W2aSYCVUQMsypRl/xiJYZznnTg==
+babel-jest@^29.0.2:
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/babel-jest/-/babel-jest-29.0.2.tgz#7efde496c07607949e9be499bf277aa1543ded95"
+ integrity sha512-yTu4/WSi/HzarjQtrJSwV+/0maoNt+iP0DmpvFJdv9yY+5BuNle8TbheHzzcSWj5gIHfuhpbLYHWRDYhWKyeKQ==
dependencies:
- "@jest/transform" "^29.0.1"
+ "@jest/transform" "^29.0.2"
"@types/babel__core" "^7.1.14"
babel-plugin-istanbul "^6.1.1"
- babel-preset-jest "^29.0.0"
+ babel-preset-jest "^29.0.2"
chalk "^4.0.0"
graceful-fs "^4.2.9"
slash "^3.0.0"
@@ -3572,10 +3567,10 @@ babel-plugin-jest-hoist@^28.1.3:
"@types/babel__core" "^7.1.14"
"@types/babel__traverse" "^7.0.6"
-babel-plugin-jest-hoist@^29.0.0:
- version "29.0.0"
- resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.0.tgz#ae4873399a199ede93697a15919d3d0f614a2eb1"
- integrity sha512-B9oaXrlxXHFWeWqhDPg03iqQd2UN/mg/VdZOsLaqAVBkztru3ctTryAI4zisxLEEgmcUnLTKewqx0gGifoXD3A==
+babel-plugin-jest-hoist@^29.0.2:
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/babel-plugin-jest-hoist/-/babel-plugin-jest-hoist-29.0.2.tgz#ae61483a829a021b146c016c6ad39b8bcc37c2c8"
+ integrity sha512-eBr2ynAEFjcebVvu8Ktx580BD1QKCrBG1XwEUTXJe285p9HA/4hOhfWCFRQhTKSyBV0VzjhG7H91Eifz9s29hg==
dependencies:
"@babel/template" "^7.3.3"
"@babel/types" "^7.3.3"
@@ -3632,12 +3627,12 @@ babel-preset-jest@^28.1.3:
babel-plugin-jest-hoist "^28.1.3"
babel-preset-current-node-syntax "^1.0.0"
-babel-preset-jest@^29.0.0:
- version "29.0.0"
- resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.0.0.tgz#52d7f1afe3a15d14a3c5ab4349cbd388d98d330b"
- integrity sha512-B5Ke47Xcs8rDF3p1korT3LoilpADCwbG93ALqtvqu6Xpf4d8alKkrCBTExbNzdHJcIuEPpfYvEaFFRGee2kUgQ==
+babel-preset-jest@^29.0.2:
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/babel-preset-jest/-/babel-preset-jest-29.0.2.tgz#e14a7124e22b161551818d89e5bdcfb3b2b0eac7"
+ integrity sha512-BeVXp7rH5TK96ofyEnHjznjLMQ2nAeDJ+QzxKnHAAMs0RgrQsCywjAN8m4mOm5Di0pxU//3AoEeJJrerMH5UeA==
dependencies:
- babel-plugin-jest-hoist "^29.0.0"
+ babel-plugin-jest-hoist "^29.0.2"
babel-preset-current-node-syntax "^1.0.0"
babelify@^10.0.0:
@@ -5415,10 +5410,10 @@ domhandler@^5.0.1, domhandler@^5.0.2:
dependencies:
domelementtype "^2.3.0"
-dompurify@2.4.0:
- version "2.4.0"
- resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.4.0.tgz#c9c88390f024c2823332615c9e20a453cf3825dd"
- integrity sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==
+dompurify@2.3.10:
+ version "2.3.10"
+ resolved "https://registry.yarnpkg.com/dompurify/-/dompurify-2.3.10.tgz#901f7390ffe16a91a5a556b94043314cd4850385"
+ integrity sha512-o7Fg/AgC7p/XpKjf/+RC3Ok6k4St5F7Q6q6+Nnm3p2zGWioAY6dh0CbbuwOhH2UcSzKsdniE/YnE2/92JcsA+g==
domutils@^3.0.1:
version "3.0.1"
@@ -6048,6 +6043,11 @@ extsprintf@^1.2.0:
resolved "https://registry.yarnpkg.com/extsprintf/-/extsprintf-1.4.1.tgz#8d172c064867f235c0c84a596806d279bf4bcc07"
integrity sha512-Wrk35e8ydCKDj/ArClo1VrPVmN8zph5V4AtHwIuHhvMXsKf73UT3BOD+azBIW+3wOJ4FhEH7zyaJCFvChjYvMA==
+fast-clone@^1.5.13:
+ version "1.5.13"
+ resolved "https://registry.yarnpkg.com/fast-clone/-/fast-clone-1.5.13.tgz#7fe17542ae1c872e71bf80d177d00c11f51c2ea7"
+ integrity sha512-0ez7coyFBQFjZtId+RJqJ+EQs61w9xARfqjqK0AD9vIUkSxWD4HvPt80+5evebZ1tTnv1GYKrPTipx7kOW5ipA==
+
fast-deep-equal@^3.1.1, fast-deep-equal@^3.1.3:
version "3.1.3"
resolved "https://registry.yarnpkg.com/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz#3a7d56b559d6cbc3eb512325244e619a65c6c525"
@@ -7648,18 +7648,18 @@ jest-each@^28.1.3:
jest-util "^28.1.3"
pretty-format "^28.1.3"
-jest-environment-jsdom@^29.0.1:
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.0.1.tgz#45bb0ef6506cafcff0809c961e841d2d4c9820cf"
- integrity sha512-rMF501kfui+bw4AmwowLA2bNaYb633A3ejFMN5pVU0AeOqLv2NbMAY5XzzlMr/+lM1itEf+3ZdCr9dGGrUfoxg==
+jest-environment-jsdom@^29.0.2:
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/jest-environment-jsdom/-/jest-environment-jsdom-29.0.2.tgz#d616a19416d0dda5155b854d301197fb6092dff0"
+ integrity sha512-hWqC9FQI5yT04lTd4VJnzT5QObxq0xrSrqpGkqsYfxPeJYjyhriI7W2oJC5HZ1UbhnvA+8GS1nzgPsstvRpdVw==
dependencies:
- "@jest/environment" "^29.0.1"
- "@jest/fake-timers" "^29.0.1"
- "@jest/types" "^29.0.1"
+ "@jest/environment" "^29.0.2"
+ "@jest/fake-timers" "^29.0.2"
+ "@jest/types" "^29.0.2"
"@types/jsdom" "^20.0.0"
"@types/node" "*"
- jest-mock "^29.0.1"
- jest-util "^29.0.1"
+ jest-mock "^29.0.2"
+ jest-util "^29.0.2"
jsdom "^20.0.0"
jest-environment-node@^28.1.3:
@@ -7698,20 +7698,20 @@ jest-haste-map@^28.1.3:
optionalDependencies:
fsevents "^2.3.2"
-jest-haste-map@^29.0.1:
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.0.1.tgz#472212f93ef44309bf97d191f93ddd2e41169615"
- integrity sha512-gcKOAydafpGoSBvcj/mGCfhOKO8fRLkAeee1KXGdcJ1Pb9O2nnOl4I8bQSIID2MaZeMHtLLgNboukh/pUGkBtg==
+jest-haste-map@^29.0.2:
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/jest-haste-map/-/jest-haste-map-29.0.2.tgz#cac403a595e6e43982c9776b5c4dae63e38b22c5"
+ integrity sha512-SOorh2ysQ0fe8gsF4gaUDhoMIWAvi2hXOkwThEO48qT3JqA8GLAUieQcIvdSEd6M0scRDe1PVmKc5tXR3Z0U0A==
dependencies:
- "@jest/types" "^29.0.1"
+ "@jest/types" "^29.0.2"
"@types/graceful-fs" "^4.1.3"
"@types/node" "*"
anymatch "^3.0.3"
fb-watchman "^2.0.0"
graceful-fs "^4.2.9"
jest-regex-util "^29.0.0"
- jest-util "^29.0.1"
- jest-worker "^29.0.1"
+ jest-util "^29.0.2"
+ jest-worker "^29.0.2"
micromatch "^4.0.4"
walker "^1.0.8"
optionalDependencies:
@@ -7765,18 +7765,18 @@ jest-message-util@^28.1.3:
slash "^3.0.0"
stack-utils "^2.0.3"
-jest-message-util@^29.0.1:
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.0.1.tgz#85c4b5b90296c228da158e168eaa5b079f2ab879"
- integrity sha512-wRMAQt3HrLpxSubdnzOo68QoTfQ+NLXFzU0Heb18ZUzO2S9GgaXNEdQ4rpd0fI9dq2NXkpCk1IUWSqzYKji64A==
+jest-message-util@^29.0.2:
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/jest-message-util/-/jest-message-util-29.0.2.tgz#b2781dfb6a2d1c63830d9684c5148ae3155c6154"
+ integrity sha512-kcJAgms3ckJV0wUoLsAM40xAhY+pb9FVSZwicjFU9PFkaTNmqh9xd99/CzKse48wPM1ANUQKmp03/DpkY+lGrA==
dependencies:
"@babel/code-frame" "^7.12.13"
- "@jest/types" "^29.0.1"
+ "@jest/types" "^29.0.2"
"@types/stack-utils" "^2.0.0"
chalk "^4.0.0"
graceful-fs "^4.2.9"
micromatch "^4.0.4"
- pretty-format "^29.0.1"
+ pretty-format "^29.0.2"
slash "^3.0.0"
stack-utils "^2.0.3"
@@ -7788,12 +7788,12 @@ jest-mock@^28.1.3:
"@jest/types" "^28.1.3"
"@types/node" "*"
-jest-mock@^29.0.1:
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.0.1.tgz#12e1b137035365b022ccdb8fd67d476cd4d4bfad"
- integrity sha512-i1yTceg2GKJwUNZFjIzrH7Y74fN1SKJWxQX/Vu3LT4TiJerFARH5l+4URNyapZ+DNpchHYrGOP2deVbn3ma8JA==
+jest-mock@^29.0.2:
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/jest-mock/-/jest-mock-29.0.2.tgz#d7810966a6338aca6a440c3cd9f19276477840ad"
+ integrity sha512-giWXOIT23UCxHCN2VUfUJ0Q7SmiqQwfSFXlCaIhW5anITpNQ+3vuLPQdKt5wkuwM37GrbFyHIClce8AAK9ft9g==
dependencies:
- "@jest/types" "^29.0.1"
+ "@jest/types" "^29.0.2"
"@types/node" "*"
jest-pnp-resolver@^1.2.2:
@@ -7930,12 +7930,12 @@ jest-util@^28.0.0, jest-util@^28.1.3:
graceful-fs "^4.2.9"
picomatch "^2.2.3"
-jest-util@^29.0.1:
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.0.1.tgz#f854a4a8877c7817316c4afbc2a851ceb2e71598"
- integrity sha512-GIWkgNfkeA9d84rORDHPGGTFBrRD13A38QVSKE0bVrGSnoR1KDn8Kqz+0yI5kezMgbT/7zrWaruWP1Kbghlb2A==
+jest-util@^29.0.2:
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/jest-util/-/jest-util-29.0.2.tgz#c75c5cab7f3b410782f9570a60c5558b5dfb6e3a"
+ integrity sha512-ozk8ruEEEACxqpz0hN9UOgtPZS0aN+NffwQduR5dVlhN+eN47vxurtvgZkYZYMpYrsmlAEx1XabkB3BnN0GfKQ==
dependencies:
- "@jest/types" "^29.0.1"
+ "@jest/types" "^29.0.2"
"@types/node" "*"
chalk "^4.0.0"
ci-info "^3.2.0"
@@ -7986,10 +7986,10 @@ jest-worker@^28.1.3:
merge-stream "^2.0.0"
supports-color "^8.0.0"
-jest-worker@^29.0.1:
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.0.1.tgz#fb42ff7e05e0573f330ec0cf781fc545dcd11a31"
- integrity sha512-+B/2/8WW7goit7qVezG9vnI1QP3dlmuzi2W0zxazAQQ8dcDIA63dDn6j4pjOGBARha/ZevcwYQtNIzCySbS7fQ==
+jest-worker@^29.0.2:
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/jest-worker/-/jest-worker-29.0.2.tgz#46c9f2cb9a19663d22babbacf998e4b5d7c46574"
+ integrity sha512-EyvBlYcvd2pg28yg5A3OODQnqK9LI1kitnGUZUG5/NYIeaRgewtYBKB5wlr7oXj8zPCkzev7EmnTCsrXK7V+Xw==
dependencies:
"@types/node" "*"
merge-stream "^2.0.0"
@@ -9126,10 +9126,10 @@ module-deps-sortable@^5.0.3:
through2 "^2.0.0"
xtend "^4.0.0"
-moment-mini@2.24.0:
- version "2.24.0"
- resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.24.0.tgz#fa68d98f7fe93ae65bf1262f6abb5fb6983d8d18"
- integrity sha512-9ARkWHBs+6YJIvrIp0Ik5tyTTtP9PoV0Ssu2Ocq5y9v8+NOOpWiRshAp8c4rZVWTOe+157on/5G+zj5pwIQFEQ==
+moment-mini@^2.24.0:
+ version "2.29.4"
+ resolved "https://registry.yarnpkg.com/moment-mini/-/moment-mini-2.29.4.tgz#cbbcdc58ce1b267506f28ea6668dbe060a32758f"
+ integrity sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg==
moment@^2.23.0:
version "2.29.4"
@@ -9244,6 +9244,11 @@ nomnom@1.5.2:
chalk "~0.4.0"
underscore "~1.6.0"
+non-layered-tidy-tree-layout@^2.0.2:
+ version "2.0.2"
+ resolved "https://registry.yarnpkg.com/non-layered-tidy-tree-layout/-/non-layered-tidy-tree-layout-2.0.2.tgz#57d35d13c356643fc296a55fb11ac15e74da7804"
+ integrity sha512-gkXMxRzUH+PB0ax9dUN0yYF0S25BqeAYqhgMaLUFmpXLEk7Fcu8f4emJuOAY0V8kjDICxROIKsTAKsV/v355xw==
+
normalize-package-data@^2.3.2, normalize-package-data@^2.5.0:
version "2.5.0"
resolved "https://registry.yarnpkg.com/normalize-package-data/-/normalize-package-data-2.5.0.tgz#e66db1838b200c1dfc233225d12cb36520e234a8"
@@ -9868,16 +9873,16 @@ prelude-ls@~1.1.2:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.1.2.tgz#21932a549f5e52ffd9a827f570e04be62a97da54"
integrity sha1-IZMqVJ9eUv/ZqCf1cOBL5iqX2lQ=
-prettier-plugin-jsdoc@^0.3.30:
- version "0.3.38"
- resolved "https://registry.yarnpkg.com/prettier-plugin-jsdoc/-/prettier-plugin-jsdoc-0.3.38.tgz#b8adbe9efc1dc11f3cc5ff0b07e0233a0fdf533d"
- integrity sha512-h81ZV/nFk5gr3fzWMWzWoz/M/8FneAZxscT7DVSy+5jMIuWYnBFZfSswVKYZyTaZ5r6+6k4hpFTDWhRp85C1tg==
+prettier-plugin-jsdoc@^0.4.2:
+ version "0.4.2"
+ resolved "https://registry.yarnpkg.com/prettier-plugin-jsdoc/-/prettier-plugin-jsdoc-0.4.2.tgz#c5668fc622ed10b87d988279476f96af96b058b7"
+ integrity sha512-w2jnAQm3z0GAG0bhzVJeehzDtrhGMSxJjit5ApCc2oxWfc7+jmLAkbtdOXaSpfwZz3IWkk+PiQPeRrLNpbM+Mw==
dependencies:
binary-searching "^2.0.5"
comment-parser "^1.3.1"
mdast-util-from-markdown "^1.2.0"
-prettier@^2.3.2:
+prettier@^2.7.1:
version "2.7.1"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-2.7.1.tgz#e235806850d057f97bb08368a4f7d899f7760c64"
integrity sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==
@@ -9897,10 +9902,10 @@ pretty-format@^28.0.0, pretty-format@^28.1.3:
ansi-styles "^5.0.0"
react-is "^18.0.0"
-pretty-format@^29.0.1:
- version "29.0.1"
- resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.0.1.tgz#2f8077114cdac92a59b464292972a106410c7ad0"
- integrity sha512-iTHy3QZMzuL484mSTYbQIM1AHhEQsH8mXWS2/vd2yFBYnG3EBqGiMONo28PlPgrW7P/8s/1ISv+y7WH306l8cw==
+pretty-format@^29.0.2:
+ version "29.0.2"
+ resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.0.2.tgz#7f7666a7bf05ba2bcacde61be81c6db64f6f3be6"
+ integrity sha512-wp3CdtUa3cSJVFn3Miu5a1+pxc1iPIQTenOAn+x5erXeN1+ryTcLesV5pbK/rlW5EKwp27x38MoYfNGaNXDDhg==
dependencies:
"@jest/schemas" "^29.0.0"
ansi-styles "^5.0.0"
@@ -11814,15 +11819,10 @@ typedarray@^0.0.6, typedarray@~0.0.5:
resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777"
integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c=
-typescript@^4.6.4:
- version "4.6.4"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.6.4.tgz#caa78bbc3a59e6a5c510d35703f6a09877ce45e9"
- integrity sha512-9ia/jWHIEbo49HfjrLGfKbZSuWo9iTMwXO+Ca3pRsSpbsMbc7/IU8NKdCZVRRBafVPGnoJeFL76ZOAA84I9fEg==
-
-typescript@^4.7.4:
- version "4.7.4"
- resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.7.4.tgz#1a88596d1cf47d59507a1bcdfb5b9dfe4d488235"
- integrity sha512-C0WQT0gezHuw6AdY1M2jxUO83Rjf0HP7Sk1DtXj6j1EwkQNZrHAg2XPWlq62oqEhYvONq5pkC2Y9oPljWToLmQ==
+typescript@^4.6.4, typescript@^4.8.2:
+ version "4.8.2"
+ resolved "https://registry.yarnpkg.com/typescript/-/typescript-4.8.2.tgz#e3b33d5ccfb5914e4eeab6699cf208adee3fd790"
+ integrity sha512-C0I1UsrrDHo2fYI5oaCGbSejwX4ch+9Y5jTQELvovfmFkK3HHSZJB8MSJcWLmCUBzQBchCrZ9rMRV6GuNrvGtw==
uglify-js@^3.1.4:
version "3.14.4"