Compare commits

...

236 Commits

Author SHA1 Message Date
Knut Sveidqvist
9868f3a4c3 Merge pull request #6084 from mermaid-js/changeset-release/master
Version Packages
2024-11-27 12:01:43 +01:00
github-actions[bot]
d8bf155f0e Version Packages 2024-11-27 09:21:59 +00:00
Sidharth Vinod
0b4f85230a Merge pull request #6083 from mermaid-js/release/11.4.1
Release/11.4.1
2024-11-27 14:49:50 +05:30
Ashish Jain
0dff4ca438 chore: broken link clean up. Enable flowchart elk tests 2024-11-27 09:49:26 +01:00
Ashish Jain
cc29437ede Merge pull request #6081 from mermaid-js/6080-fix
fix: Elk rendering of Diamond shape intersections
2024-11-26 10:52:42 +01:00
Ashish Jain
dfaaf361f3 Merge pull request #6079 from aloisklink/chore/update-to-dompurify-3.2.1
fix: update dompurify to `^3.2.1` and remove `@types/dompurify`
2024-11-26 10:48:23 +01:00
Knut Sveidqvist
37538310d3 Added cypress test 2024-11-25 17:15:59 +01:00
Knut Sveidqvist
c7ae08abc3 #6080: Fix for issue with diamond intersections when using elk-layout 2024-11-25 17:15:35 +01:00
Knut Sveidqvist
69973eaa02 Merge branch 'develop' into 6080-fix 2024-11-25 17:12:29 +01:00
Knut Sveidqvist
d3b2c7ea18 Revert "#6080: Fix for issue with diamond intersections when using elk-layout"
This reverts commit 255279eb22.
2024-11-25 17:10:25 +01:00
Knut Sveidqvist
68f41f685d Added changeset 2024-11-25 16:59:04 +01:00
Knut Sveidqvist
255279eb22 #6080: Fix for issue with diamond intersections when using elk-layout 2024-11-25 16:50:16 +01:00
Alois Klink
fe3cffbb67 chore: update dompurify to ^3.2.1
As [DOMPurify 3.2.0 added TypeScript types][1], this means that we can
remove our dependency on the `@types/dompurify` package.

[DOMPurify 3.2.0 also adds the `HTML_INTEGRATION_POINTS` option][2],
which adds back support for `<foreignObject>`,
[which broke in DOMPurify 3.1.7.][3]

[1]: https://github.com/cure53/DOMPurify/releases/tag/3.2.0
[2]: e4caa67971
[3]: de2c05cd54
2024-11-25 21:00:49 +09:00
Ashish Jain
8328f74751 Merge pull request #6059 from mermaid-js/6058-task-id-as-a-number
fix:Kanban diagrams will not render when adding a number as ticket id or assigned for a task
2024-11-14 16:32:32 +00:00
Knut Sveidqvist
01b5079562 Adding changeset 2024-11-14 15:13:07 +01:00
Knut Sveidqvist
6b23647bec #6058 Casting task-id and assigned fields to string after yaml parsing 2024-11-14 15:07:46 +01:00
Alois Klink
c8ce416aba Merge pull request #6038 from mermaid-js/fix-for-tilted-cylinder-intersect
Fix for intersection issue
2024-11-08 13:27:57 +00:00
Knut Sveidqvist
1388662132 Added changeset 2024-11-08 13:54:28 +01:00
Knut Sveidqvist
757627427b Fix for intersection issue 2024-11-08 13:47:39 +01:00
Ashish Jain
c218e365bd Merge pull request #6032 from mermaid-js/master
Merge back v4.11.0 to develop
2024-11-07 12:14:09 +00:00
Sidharth Vinod
f507dbbe00 Merge pull request #6010 from mermaid-js/update-whiteboard-tagline
TopBar: update Whiteboard tagline
2024-11-06 11:42:22 +05:30
Sidharth Vinod
9966beb99b Merge pull request #6016 from mermaid-js/update-blog
DOCS: add latest blog posts
2024-11-06 11:42:08 +05:30
Steph
f4713332c0 minor fix 2024-11-05 07:58:56 -08:00
Steph
63ff5b1d98 update utm 2024-11-05 07:57:55 -08:00
Steph
7e71b85668 update blog page 2024-11-01 06:43:26 -07:00
Steph
1dd7bcb0a6 minor update 2024-10-30 09:20:35 -07:00
Steph
bbf6ab1206 update whiteboard tagline 2024-10-30 08:58:52 -07:00
Knut Sveidqvist
56f522ec50 Merge pull request #6008 from mermaid-js/changeset-release/master
Version Packages
2024-10-30 16:42:15 +01:00
github-actions[bot]
d4e5acc530 Version Packages 2024-10-30 15:19:33 +00:00
Knut Sveidqvist
ef9cbb61e5 Merge pull request #6009 from mermaid-js/chore/fix-changeset-for-#5880
chore: update changeset for #5880
2024-10-30 16:17:20 +01:00
Alois Klink
8a0ecc1fe9 chore: update changeset for #5880
This isn't documented, but apparently changesets supports this in 91d1ef2ef7

This is needed to give @yari-dewalt credit for PR #5880
2024-10-31 00:09:10 +09:00
Ashish Jain
c6e1271ef7 Merge pull request #6007 from mermaid-js/release/11.4.0
Release v11.4.0
2024-10-30 15:19:37 +01:00
autofix-ci[bot]
b02294376a [autofix.ci] apply automated fixes 2024-10-30 14:05:05 +00:00
Ashish Jain
97c61d818c added changeset for class diagram 2024-10-30 14:11:41 +01:00
Alois Klink
1c66237fa8 test(unit): update docs unit test snapshot
This snapshot should have been modified in
c1ca3511c (Fix (tmp) for handling of config of htmlLabels ion note. MOvint class box out of flowchart docs, 2024-10-30

Fixes: c1ca3511c4
2024-10-30 21:41:38 +09:00
Knut Sveidqvist
8ef5d324fd Typescript fixes 2024-10-30 13:32:03 +01:00
Knut Sveidqvist
c1ca3511c4 Fix (tmp) for handling of config of htmlLabels ion note. MOvint class box out of flowchart docs 2024-10-30 11:54:31 +01:00
Alois Klink
953d288957 refactor(types): fix kanbanRenderer types
Add the appropriate type checks to ensure that we're only ever calling:

- `insertCluster()` with `ClusterNode`
- `insertNode()` with a `NonClusterNode`

Fixes: 7401cb8f6a
2024-10-30 19:00:35 +09:00
Alois Klink
143806b360 refactor(types): assert insertNode returns non-empty
Fixes: 7401cb8f6a
2024-10-30 19:00:35 +09:00
Alois Klink
a381ab6c41 refactor(types): separate ClusterNodes
Fixes: 7401cb8f6a
2024-10-30 19:00:34 +09:00
Ashish Jain
93e20fdaa3 Merge from develop 2024-10-30 10:38:51 +01:00
Knut Sveidqvist
ece3b254db Merge pull request #6006 from yari-dewalt/fix-note-non-html-text
Fix note non html text
2024-10-30 10:37:35 +01:00
Yari DeWalt
dd42305e8c Merge branch 'develop' into fix-note-non-html-text 2024-10-29 12:21:52 -07:00
yari-dewalt
c34fd152d5 Add handling for ' characters in non-html text so they don't get sanitized into html code values 2024-10-29 11:57:22 -07:00
yari-dewalt
8f0e0b9c39 Change useHtmlLabels to use general config's htmlLabels attribute not flowchart's 2024-10-29 11:55:34 -07:00
yari-dewalt
fe8932e906 Set node.centerLabel before using labelHelper() 2024-10-29 11:54:25 -07:00
Knut Sveidqvist
73bbde8476 Update of the background-fix 2024-10-29 16:21:56 +01:00
Knut Sveidqvist
01a41ef530 Merge branch 'release/11.4.0' of github.com:mermaid-js/mermaid into release/11.4.0 2024-10-29 14:52:25 +01:00
Knut Sveidqvist
124d2f72dd Fix for icon borders 2024-10-29 14:51:55 +01:00
Alois Klink
34e8946c58 refactor(types): fix kanbanItem circular types
Exclude the `node.shape` type from `kanbanItem()`, as otherwise it
causes a circular dependency in the types.
2024-10-29 22:18:38 +09:00
Ashish Jain
0197c08916 revert adding kanban section to shape 2024-10-29 14:04:57 +01:00
Alois Klink
ef30283fe9 refactor: fix more type errors in kanbanItem
Fixes: 7401cb8f6a
2024-10-29 21:44:34 +09:00
Ashish Jain
dffaf9fa8f fix for kanban section shape 2024-10-29 13:41:20 +01:00
Alois Klink
9168d5d1f4 refactor(types): fix some type errors in kanbanItem
Fixes: 7401cb8f6a
2024-10-29 21:14:51 +09:00
Alois Klink
f33ebfaf7a refactor: fix broken generic on insertLabel()
Fixes: 7401cb8f6a
2024-10-29 21:14:48 +09:00
Alois Klink
054f929150 refactor: remove unused param from labelHelper()
Fixes: 7401cb8f6a
2024-10-29 21:14:40 +09:00
Knut Sveidqvist
8d0902de4c Merge pull request #6002 from aloisklink/fix/make-parse-error-on-invalid-shape
fix: make `mermaid.parse` throw an error on invalid shapes
2024-10-29 10:46:02 +01:00
Knut Sveidqvist
f6c32b6da4 Merge branch 'develop' into fix/make-parse-error-on-invalid-shape 2024-10-29 10:45:50 +01:00
Knut Sveidqvist
bdf145ffe3 Merge pull request #5880 from yari-dewalt/update-class-diagram
Update class diagram to v3 using new renderer
2024-10-29 10:44:07 +01:00
Knut Sveidqvist
75ca802b62 Merge branch 'develop' into update-class-diagram 2024-10-29 10:40:39 +01:00
Ashish Jain
3f85b6179d Merge pull request #5999 from mermaid-js/knsv/5342-kanban
Knsv/5342 kanban
2024-10-29 10:38:18 +01:00
yari-dewalt
b315d70af5 Merge branch 'develop' into update-class-diagram and fix conflicts 2024-10-28 10:32:19 -07:00
autofix-ci[bot]
848be3d129 [autofix.ci] apply automated fixes 2024-10-28 14:33:47 +00:00
Alois Klink
5fabd414fb fix: error mermaid.parse on invalid shapes
Currently, invalid shapes cause an error when rendering, but not when
parsing. This confuses the Mermaid Live Editor, e.g. by not showing the
error message.

Instead, I've added an `isValidShape()` that validates if the shape is
valid at parse time. This only checks shapes using the new extended
shapes syntax. Currenlty, using `A(-this is an ellipse node-)` is broken
(see #5976) and also causes an invalid shape error at render time, but
I've ignored it in this PR, so it will continue pass at parse-time
(we have unit tests checking ellipse parsing).

See: https://github.com/mermaid-js/mermaid/issues/5976
2024-10-28 23:17:13 +09:00
Alois Klink
17e2f9e0d4 refactor: fix addVertex type parameter
I went through
`packages/mermaid/src/diagrams/flowchart/parser/flow.jison` and found
every possible value this `type` parameter could be.
2024-10-28 23:17:13 +09:00
Alois Klink
7fa8b35bdc refactor: convert if-statements to switch..case 2024-10-28 23:17:13 +09:00
Knut Sveidqvist
e6ea4eae50 Fix for missing function 2024-10-28 14:29:06 +01:00
Knut Sveidqvist
7401cb8f6a Merge branch 'develop' into knsv/5342-kanban 2024-10-28 14:22:10 +01:00
Sidharth Vinod
e765007b21 Merge pull request #5974 from aloisklink/refactor/improving-rendering-shape-types
refactor: TypeScript improvements to `packages/mermaid/src/rendering-util/rendering-elements`
2024-10-28 18:22:23 +05:30
Sidharth Vinod
b5cd101d86 Merge branch 'develop' into refactor/improving-rendering-shape-types 2024-10-28 18:11:41 +05:30
autofix-ci[bot]
da12ace593 [autofix.ci] apply automated fixes 2024-10-28 12:08:48 +00:00
Knut Sveidqvist
2c676b6222 Moving kanbanItem out of the shapes doc 2024-10-28 13:03:18 +01:00
Knut Sveidqvist
0af08276d2 #5232 Enabling handdrawn mode 2024-10-28 12:53:03 +01:00
Knut Sveidqvist
d790e353b0 #5432 Theme based text color for labels 2024-10-28 11:51:44 +01:00
Knut Sveidqvist
e07e9b9daf Merge branch 'knsv/5342-kanban' of github.com:mermaid-js/mermaid into knsv/5342-kanban 2024-10-28 11:47:17 +01:00
Knut Sveidqvist
948ec4d1ce #5342 Handing of wide section texts 2024-10-28 11:43:56 +01:00
autofix-ci[bot]
e62d268c89 [autofix.ci] apply automated fixes 2024-10-28 09:19:37 +00:00
Knut Sveidqvist
742ad7c130 Adding changeset and kanban in the docs sidenav 2024-10-28 10:03:42 +01:00
Knut Sveidqvist
56cc3728c5 End to end tests and docs 2024-10-25 15:40:58 +02:00
Knut Sveidqvist
fb86e5c689 Merge branch 'develop' into knsv/5342-kanban 2024-10-25 14:56:46 +02:00
Alois Klink
f8746bee04 refactor: move MaybePromise type to src/types.ts
Suggested-by: https://github.com/mermaid-js/mermaid/pull/5974#discussion_r1810562136
Co-authored-by: Sidharth Vinod <sidharthv96@gmail.com>
2024-10-22 21:49:23 +09:00
Alois Klink
bb97fdbd1e refactor: use parseFontSize()
Handle `NaN` values by using `parseFontSize()`.

Suggested-by: https://github.com/mermaid-js/mermaid/pull/5974#discussion_r1810570967
2024-10-22 21:49:02 +09:00
Sidharth Vinod
e3ec6ed993 Merge pull request #5987 from mermaid-js/update-promos
Remove Product Hunt promos & Update Promo bar
2024-10-22 12:03:05 +05:30
Steph
6df1fa1a03 update taglines 2024-10-21 23:26:17 -07:00
yari-dewalt
fd6b875f87 Fix subgraph / cluster node width based off of label 2024-10-21 16:09:55 -07:00
yari-dewalt
be3da0b39d Update testing file 2024-10-21 15:37:41 -07:00
yari-dewalt
64bc7f4bd9 Add fix for flipped edge markers 2024-10-21 15:37:24 -07:00
Yari DeWalt
75e796f183 Merge branch 'develop' into update-class-diagram 2024-10-21 15:36:29 -07:00
Alois Klink
6cc0132e0a refactor(elk): add LabelData type for layout-elk
I don't know what exactly the type does or is for, but I've tried to
type it to what it seems to be.
2024-10-21 22:23:36 +09:00
Alois Klink
4bc70b7325 refactor: use more standard types
See: https://github.com/mermaid-js/mermaid/pull/5974#discussion_r1806473749
See: https://github.com/mermaid-js/mermaid/pull/5974#discussion_r1808511738

Co-authored-by: saurabhg772244 <saurabh@mermaidchart.com>
2024-10-21 22:23:36 +09:00
yari-dewalt
bfdd0f244d Fix tests to work with new cssClasses attribute 2024-10-18 16:36:06 -07:00
yari-dewalt
0e819ae050 Change cssClasses to use concatenated string instead of array to pick up css styles 2024-10-18 10:33:56 -07:00
yari-dewalt
10f11e016f Merge branch 'update-class-diagram' of github.com:yari-dewalt/mermaid into update-class-diagram 2024-10-18 10:12:48 -07:00
yari-dewalt
b7d66bb975 Add elk and handDrawn specific test files 2024-10-18 10:12:09 -07:00
yari-dewalt
74695d2e88 Fix undesired wrapping on notes 2024-10-18 10:11:36 -07:00
Steph
38eed7c988 product hunt promo material updates 2024-10-18 09:54:46 -07:00
Alois Klink
d16e46a386 Merge pull request #5972 from gabeidx/zed-mermaid
docs(ecosystem): Add Zed extension zed-mermaid
2024-10-18 08:45:30 +00:00
Alois Klink
97820a50c2 Merge pull request #5918 from vorburger/patch-2
docs: Fix inconsistency in Block Diagram doc example
2024-10-18 08:42:34 +00:00
Alois Klink
df948bcf2c Merge pull request #5968 from eitsupi/fix-ssg
docs(integrations): fix listing order
2024-10-18 08:39:31 +00:00
Alois Klink
cfd097b029 Merge pull request #5967 from yihui/patch-1
docs: bullet lists should not be code blocks
2024-10-18 08:37:12 +00:00
Alois Klink
ffc1fef599 Merge pull request #5957 from TomasHubelbauer/patch-1
Remove extraneous period
2024-10-18 08:36:31 +00:00
Alois Klink
1e8e4e4b73 Merge branch 'develop' into patch-2 2024-10-18 08:34:07 +00:00
autofix-ci[bot]
606ecf8990 [autofix.ci] apply automated fixes 2024-10-17 17:42:10 +00:00
Alois Klink
16a5fc05d6 refactor: remove some any types from render()
This let's us confirm that the types we're passing to `insertNode()` are
valid.
2024-10-18 01:51:53 +09:00
Alois Klink
62fd359c0e refactor: convert shapes/util.js to TypeScript
Convert the
`packages/mermaid/src/rendering-util/rendering-elements/shapes/util.js`
file to TypeScript at:
`packages/mermaid/src/rendering-util/rendering-elements/shapes/util.ts`.
2024-10-18 01:51:53 +09:00
Alois Klink
b0f4ace3a1 refactor: convert rendering-elements/nodes to TS
Convert the
`packages/mermaid/src/rendering-util/rendering-elements/nodes.js`
file to TypeScript at
`packages/mermaid/src/rendering-util/rendering-elements/nodes.ts`
2024-10-18 01:51:53 +09:00
autofix-ci[bot]
6f506c30eb [autofix.ci] apply automated fixes 2024-10-17 16:04:56 +00:00
yari-dewalt
cf2c7f7f8c Update tests 2024-10-17 08:58:49 -07:00
yari-dewalt
6041729ae2 Add suport for start and end labels (cardinality) in elk renderer 2024-10-17 08:58:06 -07:00
yari-dewalt
b7ef8b2b5a Refactor to make code more clear 2024-10-17 08:57:03 -07:00
yari-dewalt
bfea287b44 Register and update classBox shape 2024-10-17 08:55:42 -07:00
Alois Klink
9afb181d06 refactor: correct shapes types
All of the shapes in
`packages/mermaid/src/rendering-util/rendering-elements/shapes/` use
`D3.Selection`s as input parameters and as return parameters.

Although, for some reason, passing them to roughjs seems to work?
2024-10-17 22:29:38 +09:00
Alois Klink
cfe7cce41d refactor: improve types of shapes
Instead of being a `Record<string, ShapeHandler>`, the type now knows
every key in the variable.
2024-10-17 18:59:43 +09:00
autofix-ci[bot]
33029c8bd2 [autofix.ci] apply automated fixes 2024-10-17 08:31:48 +00:00
Gabriel Silva
e54d10c706 docs(ecosystem): Add Zed extension zed-mermaid to community integrations 2024-10-17 10:21:15 +02:00
Yari DeWalt
86c7532f46 Merge branch 'develop' into update-class-diagram 2024-10-16 09:43:58 -07:00
eitsupi
8aa87c7091 docs(integrations): fix listing order 2024-10-16 21:36:26 +09:00
autofix-ci[bot]
e1024838e8 [autofix.ci] apply automated fixes 2024-10-16 03:52:48 +00:00
Yihui Xie
4cce43f0a1 Bullet lists should not be code blocks 2024-10-15 22:45:38 -05:00
autofix-ci[bot]
c69ab34eac [autofix.ci] apply automated fixes 2024-10-11 20:27:05 +00:00
Tomáš Hübelbauer
7353be3b16 Remove extraneous period
Other cells in the table do not end with one.
2024-10-11 22:21:23 +02:00
Knut Sveidqvist
02be63ed72 Refactoring and cleanup including updated unit tests 2024-10-11 08:48:04 -07:00
Sidharth Vinod
742531a2c2 Merge pull request #5951 from gbremond/docs/5949_update-icon-name-for-database
docs(architecture): update icon name for database in exemple
2024-10-11 06:12:42 +00:00
Knut Sveidqvist
991d403d7f Typescript fixes 2024-10-10 09:47:52 -07:00
Knut Sveidqvist
290c678dc7 Adding ticket handling 2024-10-10 08:23:58 -07:00
g.bremont-ext
2c31a599af docs(architecture): use right icon for database in exemple 2024-10-10 17:00:13 +02:00
autofix-ci[bot]
2655be0bad [autofix.ci] apply automated fixes 2024-10-10 14:21:46 +00:00
g.bremont-ext
4f97c8c70e docs(architecture): update icon name for database in exemple 2024-10-10 16:15:23 +02:00
Sidharth Vinod
f85977246c Merge pull request #5945 from mermaid-js/saurabh/doc-update
Updated doc for image and icon shapes
2024-10-10 16:46:02 +05:30
saurabhg772244
a4fd42214b Use relative URL in linking docs 2024-10-10 15:17:26 +05:30
saurabhg772244
ca885027d4 Moved position for icon config and added major version in icon docs 2024-10-10 14:15:16 +05:30
saurabhg772244
9f6f1e96e5 Added new doc for registering icon pack in mermaid 2024-10-10 13:11:54 +05:30
saurabhg772244
351ce30d8a Merge branch 'develop' of https://github.com/mermaid-js/mermaid into saurabh/doc-update 2024-10-10 12:26:14 +05:30
Sidharth Vinod
2933eb5c28 fix: Build options 2024-10-09 16:44:51 +05:30
saurabhg772244
09ecf76034 Updated doc for image and icon shapes 2024-10-09 15:34:12 +05:30
Sidharth Vinod
2b86750cb3 Merge pull request #5924 from mermaid-js/sidv/autoGenerateShapeDocs
Auto generate shape docs
2024-10-09 14:11:56 +05:30
Sidharth Vinod
b45224755e Merge branch 'develop' into sidv/autoGenerateShapeDocs 2024-10-09 13:41:43 +05:30
Sidharth Vinod
14524e1b0e docs: Optimise document generation
Co-authored-by: Alois Klink <alois@aloisklink.com>
2024-10-09 13:39:06 +05:30
Sidharth Vinod
efa47e3176 fix: Docs vitepress build 2024-10-09 13:18:01 +05:30
Sidharth Vinod
1bc45405e0 chore: Generate shape doc before verification 2024-10-09 13:18:01 +05:30
Sidharth Vinod
3720e1a894 docs: Auto generate shape docs
Not keen on how it's currently run, open to suggestions on better ways.
2024-10-09 13:18:00 +05:30
Sidharth Vinod
76138cf3b9 chore: Decouple build logic 2024-10-09 13:17:59 +05:30
Alois Klink
2506c29f6b Merge pull request #5937 from mermaid-js/saurabh/icon-shape-style-fix
Fix icon shape color for colored icons.
2024-10-09 07:47:34 +00:00
Sidharth Vinod
463ba6ed6d Merge pull request #5923 from mermaid-js/sidv/refactorShapes
Refactor shapes definition
2024-10-09 13:17:06 +05:30
saurabhg772244
17b783135f Added pnpm changeset 2024-10-09 12:55:37 +05:30
saurabhg772244
8df2729bd4 Merge branch 'saurabh/icon-shape-style-fix' of https://github.com/mermaid-js/mermaid into saurabh/icon-shape-style-fix 2024-10-09 12:16:05 +05:30
saurabhg772244
a001619464 Move MIT license just before icon definition 2024-10-09 12:15:23 +05:30
Saurabh Gore
3c5a19d297 Merge branch 'develop' into saurabh/icon-shape-style-fix 2024-10-09 11:12:56 +05:30
saurabhg772244
a98290a6e2 remove doc for icon shape 2024-10-09 11:08:27 +05:30
saurabhg772244
9bac464442 Use tropical-fish icon with MIT license in e2e tests 2024-10-09 10:41:37 +05:30
autofix-ci[bot]
11fb806992 [autofix.ci] apply automated fixes 2024-10-08 15:03:03 +00:00
Yari DeWalt
d424a4473a Merge branch 'develop' into update-class-diagram 2024-10-08 07:58:15 -07:00
saurabhg772244
4062813d44 Added docs for icon-shape 2024-10-08 17:45:53 +05:30
Sidharth Vinod
29b9d3069b Merge branch 'develop' into sidv/refactorShapes 2024-10-08 16:15:34 +05:30
Sidharth Vinod
c38d9a6620 Merge pull request #5808 from mermaid-js/renovate/patch-all-patch
chore(deps): update all patch dependencies (patch)
2024-10-08 15:11:26 +05:30
Sidharth Vinod
2f65429aa4 chore: Rollback dompurify update range 2024-10-08 15:01:11 +05:30
Sidharth Vinod
c24028f2a4 chore: Separate dompurify updates 2024-10-08 15:00:57 +05:30
autofix-ci[bot]
a20c292653 [autofix.ci] apply automated fixes 2024-10-08 09:17:15 +00:00
renovate[bot]
1c6304b3fb chore(deps): update all patch dependencies 2024-10-08 09:11:33 +00:00
Sidharth Vinod
3ac93af80c Merge pull request #5936 from mermaid-js/renovate/major-eslint
chore(deps): update dependency eslint-plugin-unicorn to v56
2024-10-08 13:58:24 +05:30
renovate[bot]
ce586e1b31 chore(deps): update dependency eslint-plugin-unicorn to v56 2024-10-08 08:18:32 +00:00
Sidharth Vinod
926f97124b Merge pull request #5939 from mermaid-js/renovate/npm-express-vulnerability
chore(deps): update dependency express to v4.20.0 [security]
2024-10-08 13:39:14 +05:30
Sidharth Vinod
0f2301f2d6 Merge pull request #5938 from mermaid-js/renovate/npm-vite-vulnerability
chore(deps): update dependency vite to v5.4.6 [security]
2024-10-08 13:38:29 +05:30
Sidharth Vinod
58e8f65135 Merge pull request #5935 from mermaid-js/renovate/eslint
chore(deps): update eslint to v9.12.0 (minor)
2024-10-08 13:37:54 +05:30
Sidharth Vinod
1b51fbfcca Revert "chore: Split newshape test"
This reverts commit 1b12ac133e.
2024-10-08 13:36:16 +05:30
renovate[bot]
1296840696 chore(deps): update eslint to v9.12.0 2024-10-08 07:57:45 +00:00
Sidharth Vinod
f99e2c9366 fix: Add internalAliases
Co-authored-by: Alois Klink <alois@aloisklink.com>
2024-10-08 13:25:00 +05:30
renovate[bot]
c71b56ebe3 chore(deps): update dependency express to v4.20.0 [security] 2024-10-08 07:52:44 +00:00
renovate[bot]
9904afce64 chore(deps): update dependency vite to v5.4.6 [security] 2024-10-08 07:52:17 +00:00
Sidharth Vinod
a827fb1202 fix: Argos index numbering 2024-10-08 13:20:55 +05:30
Sidharth Vinod
51be99c9de fix: Remove duplicates, add documentation, rename legacyAlias
Co-authored-by: Alois Klink <alois@aloisklink.com>
2024-10-08 13:01:31 +05:30
Sidharth Vinod
b83b778f2a Merge pull request #5933 from remcohaszing/fix-types-dependencies
Fix TypeScript dependencies
2024-10-08 12:55:49 +05:30
Sidharth Vinod
86a91597f9 Upgrade @argos-ci 2024-10-07 19:12:23 +05:30
Sidharth Vinod
1b12ac133e chore: Split newshape test 2024-10-07 18:30:05 +05:30
Sidharth Vinod
e55880a9c4 Merge pull request #5925 from mermaid-js/sidv/parallelCypress
chore: Split cypress tests locally
2024-10-07 17:20:55 +05:30
Sidharth Vinod
9cc309b69c Apply suggestions from code review
Co-authored-by: Alois Klink <alois@aloisklink.com>
2024-10-07 17:20:38 +05:30
saurabhg772244
d3308232e8 updated test case for icon shape 2024-10-07 16:11:31 +05:30
saurabhg772244
0b8f27e1d2 Updated icon shape to use css color style instead of stroke 2024-10-07 15:52:12 +05:30
Knut Sveidqvist
93f2c241b8 5432 WIP, rendering items 2024-10-06 19:22:07 +02:00
Knut Sveidqvist
fb44e769f2 5432 WIP, rendering sections 2024-10-06 18:34:24 +02:00
Knut Sveidqvist
fabdfd9ae8 5432 WIP, parsing works 2024-10-06 16:55:57 +02:00
Remco Haszing
72d60d2633 Fix TypeScript dependencies
The Mermaid type definitions depend on `@types/d3` and
`@types/dompurify`. Without these dependencies, the Mermaid types cause
a type error for users.
2024-10-04 17:19:04 +02:00
Knut Sveidqvist
2d7686eb65 #5342 Initial commit 2024-10-04 16:03:55 +02:00
Sidharth Vinod
d9a26ff193 Merge branch 'develop' into sidv/refactorShapes 2024-10-04 14:44:31 +05:30
Sidharth Vinod
2401eea725 Merge branch 'develop' into sidv/parallelCypress 2024-10-04 14:43:49 +05:30
Sidharth Vinod
ddf18dd233 chore: Disable architecture diagram test 2024-10-04 14:25:45 +05:30
Sidharth Vinod
4b98dabc3f chore: Update test name 2024-10-04 02:56:09 +05:30
Sidharth Vinod
5a3e0ccc3d chore: rebuild 2024-10-04 02:33:45 +05:30
Sidharth Vinod
38a3ad3370 chore: Add timing update action 2024-10-04 02:29:55 +05:30
Sidharth Vinod
b777c12094 chore: Add timings file 2024-10-04 02:29:43 +05:30
Sidharth Vinod
8fc36d2684 chore: Split cypress tests locally 2024-10-04 01:54:36 +05:30
Sidharth Vinod
e2474049d8 Merge branch 'develop' into sidv/refactorShapes
* develop:
  chore: move handDrawnSeed to mermaidUrl function
2024-10-04 01:31:54 +05:30
Sidharth Vinod
6e0fe03552 chore: move handDrawnSeed to mermaidUrl function 2024-10-04 01:31:20 +05:30
Sidharth Vinod
cda48a7143 Merge branch 'develop' into sidv/refactorShapes
* develop:
  fix: Hard code handDrawnSeed
2024-10-04 00:46:01 +05:30
Sidharth Vinod
e5a28de26e fix: Hard code handDrawnSeed 2024-10-04 00:44:08 +05:30
Sidharth Vinod
0ae4f97036 test: Update nodes test 2024-10-04 00:05:38 +05:30
Sidharth Vinod
218bfe1603 chore: Update import path 2024-10-03 22:13:00 +05:30
Sidharth Vinod
339c670839 docs: Update new shape registration doc 2024-10-03 22:12:41 +05:30
Sidharth Vinod
c5a19f6e85 chore: Organize shape handling 2024-10-03 22:08:30 +05:30
autofix-ci[bot]
719685b930 [autofix.ci] apply automated fixes 2024-10-03 08:54:12 +00:00
Michael Vorburger
0cbc000bfa docs: Fix inconsistency in Block Diagram doc example 2024-10-03 10:47:31 +02:00
yari-dewalt
91e3da1293 Update tests to take into account 'default' class 2024-09-24 10:47:26 -07:00
yari-dewalt
fa46267c14 Update documentation 2024-09-24 10:22:46 -07:00
yari-dewalt
10679297f9 Fix conditional logic 2024-09-24 09:35:34 -07:00
yari-dewalt
3e697e2ea4 Update and fix logic for lollipop interfaces 2024-09-24 09:28:17 -07:00
yari-dewalt
f031abcec2 Support lollipop interface feature 2024-09-23 12:19:22 -07:00
yari-dewalt
dd62322ae5 Add default class support 2024-09-23 10:09:05 -07:00
yari-dewalt
48e5dee963 Fix edge markers being flipped 2024-09-23 09:51:36 -07:00
yari-dewalt
bd0a00d373 Update config yaml so ClassDiagramConfig can be properly updated 2024-09-20 09:56:17 -07:00
yari-dewalt
3b8bd6b979 Include classRenderer.js to fix lint error 2024-09-20 09:13:21 -07:00
yari-dewalt
41f22864b4 Add adjustments for text and useHtmlLabels 2024-09-19 12:33:34 -07:00
yari-dewalt
6a99a1a40a Update HTML file and add rendering tests 2024-09-19 12:30:38 -07:00
yari-dewalt
1fcebab9ed Change cssDiagram parameter to 'classDiagram' 2024-09-18 13:22:21 -07:00
yari-dewalt
bed301a485 Update tests 2024-09-18 13:21:27 -07:00
yari-dewalt
31546b08ff Fix note text 2024-09-18 13:20:47 -07:00
yari-dewalt
25337dc453 Update text logic and change workaround for markdown 2024-09-18 12:54:32 -07:00
yari-dewalt
745495c2d5 Refactor and cleanup logic for classBox shape 2024-09-18 09:12:15 -07:00
yari-dewalt
94d2a0ee89 Cleanup 2024-09-18 09:11:25 -07:00
yari-dewalt
b105c7b35e Add node look and fix text attribute on class 2024-09-18 09:10:35 -07:00
yari-dewalt
149f991c1d Fix typo 2024-09-18 08:53:54 -07:00
yari-dewalt
a43ee60942 Add fixes to utils 2024-09-18 08:53:54 -07:00
yari-dewalt
5f11cd417f Add classBox shape 2024-09-18 08:53:54 -07:00
yari-dewalt
3782b69744 Add support for classDef statement 2024-09-18 08:53:54 -07:00
yari-dewalt
ca93280b17 Change types 2024-09-18 08:53:54 -07:00
yari-dewalt
2ec9f0af2e Add support for new renderer 2024-09-18 08:53:54 -07:00
yari-dewalt
3eb338df04 Add default option for hideEmptyBox in config 2024-09-18 08:53:54 -07:00
yari-dewalt
b7790ba6b5 Add diagrams for testing 2024-09-18 08:53:54 -07:00
yari-dewalt
b67c931f18 Update snapshot to include new data 2024-09-18 08:53:54 -07:00
yari-dewalt
97186504b5 Add classifier test diagram 2024-09-18 08:53:54 -07:00
yari-dewalt
bd4ca9af1b Add support for classifiers in text attribute 2024-09-18 08:53:54 -07:00
yari-dewalt
9ac20a5cb8 Add styling compatibility to shape 2024-09-18 08:53:54 -07:00
yari-dewalt
20a58d6dbb Add special handling to avoid markdown to HTML issues 2024-09-18 08:53:54 -07:00
yari-dewalt
191e4217fc Adjust classNode text when label is set 2024-09-18 08:53:54 -07:00
yari-dewalt
8745daa701 Add handling for escape type 2024-09-18 08:53:54 -07:00
yari-dewalt
27f7b615cc Add more tests / samples 2024-09-18 08:53:54 -07:00
yari-dewalt
e3c426de86 Initial work on updating to new renderer 2024-09-18 08:53:54 -07:00
184 changed files with 12884 additions and 4719 deletions

View File

@@ -1,3 +1,9 @@
export interface PackageOptions {
name: string;
packageName: string;
file: string;
}
/**
* Shared common options for both ESBuild and Vite
*/
@@ -27,4 +33,4 @@ export const packageOptions = {
packageName: 'mermaid-layout-elk',
file: 'layouts.ts',
},
} as const;
} as const satisfies Record<string, PackageOptions>;

View File

@@ -19,6 +19,7 @@ const MERMAID_CONFIG_DIAGRAM_KEYS = [
'xyChart',
'requirement',
'mindmap',
'kanban',
'timeline',
'gitGraph',
'c4',

View File

@@ -12,6 +12,7 @@ gantt
gitgraph
gzipped
handDrawn
kanban
knsv
Knut
marginx

View File

@@ -8,7 +8,10 @@ import { defaultOptions, getBuildConfig } from './util.js';
const shouldVisualize = process.argv.includes('--visualize');
const buildPackage = async (entryName: keyof typeof packageOptions) => {
const commonOptions: MermaidBuildOptions = { ...defaultOptions, entryName } as const;
const commonOptions: MermaidBuildOptions = {
...defaultOptions,
options: packageOptions[entryName],
} as const;
const buildConfigs: MermaidBuildOptions[] = [
// package.mjs
{ ...commonOptions },
@@ -40,7 +43,7 @@ const buildPackage = async (entryName: keyof typeof packageOptions) => {
continue;
}
const fileName = Object.keys(metafile.outputs)
.find((file) => !file.includes('chunks') && file.endsWith('js'))
.find((file) => !file.includes('chunks') && file.endsWith('js'))!
.replace('dist/', '');
// Upload metafile into https://esbuild.github.io/analyze/
await writeFile(`stats/${fileName}.meta.json`, JSON.stringify(metafile));

View File

@@ -9,13 +9,18 @@ import { generateLangium } from '../.build/generateLangium.js';
import { defaultOptions, getBuildConfig } from './util.js';
const configs = Object.values(packageOptions).map(({ packageName }) =>
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: packageName })
getBuildConfig({
...defaultOptions,
minify: false,
core: false,
options: packageOptions[packageName],
})
);
const mermaidIIFEConfig = getBuildConfig({
...defaultOptions,
minify: false,
core: false,
entryName: 'mermaid',
options: packageOptions.mermaid,
format: 'iife',
});
configs.push(mermaidIIFEConfig);

View File

@@ -3,7 +3,7 @@ import { fileURLToPath } from 'url';
import type { BuildOptions } from 'esbuild';
import { readFileSync } from 'fs';
import jsonSchemaPlugin from './jsonSchemaPlugin.js';
import { packageOptions } from '../.build/common.js';
import type { PackageOptions } from '../.build/common.js';
import { jisonPlugin } from './jisonPlugin.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
@@ -13,10 +13,10 @@ export interface MermaidBuildOptions extends BuildOptions {
core: boolean;
metafile: boolean;
format: 'esm' | 'iife';
entryName: keyof typeof packageOptions;
options: PackageOptions;
}
export const defaultOptions: Omit<MermaidBuildOptions, 'entryName'> = {
export const defaultOptions: Omit<MermaidBuildOptions, 'entryName' | 'options'> = {
minify: false,
metafile: false,
core: false,
@@ -52,9 +52,14 @@ const getFileName = (fileName: string, { core, format, minify }: MermaidBuildOpt
};
export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
const { core, entryName, metafile, format, minify } = options;
const {
core,
metafile,
format,
minify,
options: { name, file, packageName },
} = options;
const external: string[] = ['require', 'fs', 'path'];
const { name, file, packageName } = packageOptions[entryName];
const outFileName = getFileName(name, options);
const output: BuildOptions = buildOptions({
absWorkingDir: resolve(__dirname, `../packages/${packageName}`),

View File

@@ -19,7 +19,7 @@ jobs:
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -23,7 +23,7 @@ jobs:
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -36,7 +36,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
uses: github/codeql-action/init@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
with:
config-file: ./.github/codeql/codeql-config.yml
languages: ${{ matrix.language }}
@@ -48,7 +48,7 @@ jobs:
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
uses: github/codeql-action/autobuild@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
# Command-line programs to run using the OS shell.
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
@@ -62,4 +62,4 @@ jobs:
# make release
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
uses: github/codeql-action/analyze@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12

View File

@@ -38,7 +38,7 @@ jobs:
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version-file: '.node-version'

53
.github/workflows/e2e-timings.yml vendored Normal file
View File

@@ -0,0 +1,53 @@
name: E2E - Generate Timings
on:
# run this workflow every night at 3am
schedule:
- cron: '28 3 * * *'
# or when the user triggers it from GitHub Actions page
workflow_dispatch:
concurrency: ${{ github.workflow }}-${{ github.ref }}
permissions:
contents: write
jobs:
timings:
runs-on: ubuntu-latest
container:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
options: --user 1001
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version-file: '.node-version'
- name: Install dependencies
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
with:
runTests: false
- name: Cypress run
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
id: cypress
with:
install: false
start: pnpm run dev:coverage
wait-on: 'http://localhost:9000'
browser: chrome
publish-summary: false
env:
VITEST_COVERAGE: true
CYPRESS_COMMIT: ${{ github.sha }}
SPLIT: 1
SPLIT_INDEX: 0
SPLIT_FILE: 'cypress/timings.json'
- name: Commit changes
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
with:
add: 'cypress/timings.json'
author_name: 'github-actions[bot]'
author_email: '41898282+github-actions[bot]@users.noreply.github.com'
message: 'chore: update E2E timings'

View File

@@ -28,7 +28,6 @@ env:
) ||
github.event.before
}}
shouldRunParallel: ${{ secrets.CYPRESS_RECORD_KEY != '' && !(github.event_name == 'push' && github.ref == 'refs/heads/develop') }}
jobs:
cache:
runs-on: ubuntu-latest
@@ -39,7 +38,7 @@ jobs:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version-file: '.node-version'
- name: Cache snapshots
@@ -59,7 +58,7 @@ jobs:
- name: Install dependencies
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
with:
# just perform install
runTests: false
@@ -80,7 +79,7 @@ jobs:
strategy:
fail-fast: false
matrix:
containers: [1, 2, 3, 4]
containers: [1, 2, 3, 4, 5]
steps:
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
@@ -88,7 +87,7 @@ jobs:
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
node-version-file: '.node-version'
@@ -101,7 +100,7 @@ jobs:
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
- name: Install dependencies
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
with:
runTests: false
@@ -117,11 +116,8 @@ jobs:
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
uses: cypress-io/github-action@0da3c06ed8217b912deea9d8ee69630baed1737e # v6.7.6
id: cypress
# If CYPRESS_RECORD_KEY is set, run in parallel on all containers
# Otherwise (e.g. if running from fork), we run on a single container only
if: ${{ env.shouldRunParallel == 'true' || ( matrix.containers == 1 ) }}
with:
install: false
start: pnpm run dev:coverage
@@ -129,16 +125,18 @@ jobs:
browser: chrome
# Disable recording if we don't have an API key
# e.g. if this action was run from a fork
record: ${{ env.shouldRunParallel == 'true' }}
parallel: ${{ env.shouldRunParallel == 'true' }}
record: ${{ secrets.CYPRESS_RECORD_KEY != '' }}
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
VITEST_COVERAGE: true
CYPRESS_COMMIT: ${{ github.sha }}
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_PARALLEL: ${{ env.shouldRunParallel == 'true' }}
ARGOS_PARALLEL_TOTAL: 4
ARGOS_PARALLEL: true
ARGOS_PARALLEL_TOTAL: ${{ strategy.job-total }}
ARGOS_PARALLEL_INDEX: ${{ matrix.containers }}
SPLIT: ${{ strategy.job-total }}
SPLIT_INDEX: ${{ strategy.job-index }}
SPLIT_FILE: 'cypress/timings.json'
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0

View File

@@ -29,7 +29,7 @@ jobs:
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -28,7 +28,7 @@ jobs:
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -16,7 +16,7 @@ jobs:
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -31,7 +31,7 @@ jobs:
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -26,7 +26,7 @@ jobs:
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'
@@ -36,7 +36,7 @@ jobs:
- name: Create Release Pull Request or Publish to npm
id: changesets
uses: changesets/action@aba318e9165b45b7948c60273e0b72fce0a64eb9 # v1.4.7
uses: changesets/action@3de3850952bec538fde60aac71731376e57b9b57 # v1.4.8
with:
version: pnpm changeset:version
publish: pnpm changeset:publish

View File

@@ -16,11 +16,11 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout code
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
with:
persist-credentials: false
- name: Run analysis
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
uses: ossf/scorecard-action@dc50aa9510b46c811795eb24b2f1ba02a914e534 # v2.3.3
with:
results_file: results.sarif
results_format: sarif
@@ -32,6 +32,6 @@ jobs:
path: results.sarif
retention-days: 5
- name: Upload to code-scanning
uses: github/codeql-action/upload-sarif@f0f3afee809481da311ca3a6ff1ff51d81dbeb24 # v3.26.4
uses: github/codeql-action/upload-sarif@c36620d31ac7c881962c3d9dd939c40ec9434f2b # v3.26.12
with:
sarif_file: results.sarif

View File

@@ -15,7 +15,7 @@ jobs:
# uses version from "packageManager" field in package.json
- name: Setup Node.js
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
uses: actions/setup-node@0a44ba7841725637a19e28fa30b79a866c81b0a6 # v4.0.4
with:
cache: pnpm
node-version-file: '.node-version'

View File

@@ -1,8 +1,9 @@
import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin';
import coverage from '@cypress/code-coverage/task';
import eyesPlugin from '@applitools/eyes-cypress';
import { registerArgosTask } from '@argos-ci/cypress/task';
import coverage from '@cypress/code-coverage/task';
import { defineConfig } from 'cypress';
import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin';
import cypressSplit from 'cypress-split';
export default eyesPlugin(
defineConfig({
@@ -13,6 +14,7 @@ export default eyesPlugin(
specPattern: 'cypress/integration/**/*.{js,ts}',
setupNodeEvents(on, config) {
coverage(on, config);
cypressSplit(on, config);
on('before:browser:launch', (browser, launchOptions) => {
if (browser.name === 'chrome' && browser.isHeadless) {
launchOptions.args.push('--window-size=1440,1024', '--force-device-scale-factor=1');

View File

@@ -29,6 +29,7 @@ export const mermaidUrl = (
options: CypressMermaidConfig,
api: boolean
): string => {
options.handDrawnSeed = 1;
const codeObject: CodeObject = {
code: graphStr,
mermaid: options,

View File

@@ -173,7 +173,8 @@ describe.skip('architecture diagram', () => {
});
});
describe('architecture - external', () => {
// Skipped as the layout is not deterministic, and causes issues in E2E tests.
describe.skip('architecture - external', () => {
it('should allow adding external icons', () => {
urlSnapshotTest('http://localhost:9000/architecture-external.html');
});

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

File diff suppressed because it is too large Load Diff

View File

@@ -1,6 +1,6 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
describe.skip('Flowchart ELK', () => {
describe('Flowchart ELK', () => {
it('1-elk: should render a simple flowchart', () => {
imgSnapshotTest(
`flowchart-elk TD
@@ -857,6 +857,49 @@ flowchart LR
D --> E
A["A"]
`,
{ flowchart: { titleTopMargin: 0 } }
);
});
it('6080: should handle diamond shape intersections', () => {
imgSnapshotTest(
`---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
n3["Option 2"]
n4["fa:fa-car Option 3"]
end
subgraph s2["Untitled subgraph"]
n5["Evaluate"]
n6["Option 1"]
n7["Option 2"]
n8["fa:fa-car Option 3"]
end
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
n1 -- One --> n2
n1 -- Two --> n3
n1 -- Three --> n4
n5 -- One --> n6
n5 -- Two --> n7
n5 -- Three --> n8
n1@{ shape: diam}
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
n5@{ shape: diam}
n6@{ shape: rect}
n7@{ shape: rect}
n8@{ shape: rect}
`,
{ flowchart: { titleTopMargin: 0 } }
);

View File

@@ -12,7 +12,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -30,7 +29,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: true },
fontFamily: 'courier',
}
@@ -47,7 +45,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[iPhone]
C -->|Three| F[Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -62,7 +60,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[\\iPhone\\]
C -->|Three| F[Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -78,7 +76,7 @@ describe('Flowchart HandDrawn', () => {
classDef processHead fill:#888888,color:white,font-weight:bold,stroke-width:3px,stroke:#001f3f
class 1A,1B,D,E processHead
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -107,7 +105,7 @@ describe('Flowchart HandDrawn', () => {
35(SAM.CommonFA.PopulationFME)-->39(SAM.CommonFA.ChargeDetails)
36(SAM.CommonFA.PremetricCost)-->39(SAM.CommonFA.ChargeDetails)
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -178,7 +176,7 @@ describe('Flowchart HandDrawn', () => {
9a072290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
9a072290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -187,7 +185,7 @@ describe('Flowchart HandDrawn', () => {
`
graph TB;subgraph "number as labels";1;end;
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -199,7 +197,7 @@ describe('Flowchart HandDrawn', () => {
a1-->a2
end
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -211,7 +209,7 @@ describe('Flowchart HandDrawn', () => {
a1-->a2
end
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -246,7 +244,7 @@ describe('Flowchart HandDrawn', () => {
style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred
style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -348,7 +346,7 @@ describe('Flowchart HandDrawn', () => {
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4FC27B48-A6F9-460A-A675-021F5854FE22;
`,
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
{ look: 'handDrawn', fontFamily: 'courier' }
);
});
@@ -364,7 +362,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
listUrl: false,
listId: 'color styling',
fontFamily: 'courier',
@@ -390,7 +387,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
listUrl: false,
listId: 'color styling',
fontFamily: 'courier',
@@ -411,7 +407,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -435,7 +430,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -457,7 +451,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -471,7 +464,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -485,7 +477,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -500,7 +491,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -527,7 +517,6 @@ describe('Flowchart HandDrawn', () => {
class A someclass;`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -544,7 +533,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { nodeSpacing: 50 }, fontFamily: 'courier' }
{ look: 'handDrawn', flowchart: { nodeSpacing: 50 }, fontFamily: 'courier' }
);
});
@@ -560,7 +549,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { rankSpacing: '100' },
fontFamily: 'courier',
}
@@ -578,7 +566,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -603,7 +590,7 @@ describe('Flowchart HandDrawn', () => {
click E "notes://do-your-thing/id" "other protocol test"
click F "javascript:alert('test')" "script test"
`,
{ look: 'handDrawn', handDrawnSeed: 1, securityLevel: 'loose', fontFamily: 'courier' }
{ look: 'handDrawn', securityLevel: 'loose', fontFamily: 'courier' }
);
});
@@ -623,7 +610,7 @@ describe('Flowchart HandDrawn', () => {
click B "index.html#link-clicked" "link test"
click D testClick "click test"
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { htmlLabels: true } }
{ look: 'handDrawn', flowchart: { htmlLabels: true } }
);
});
@@ -645,7 +632,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -664,7 +650,7 @@ describe('Flowchart HandDrawn', () => {
class A myClass1
class D myClass2
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { htmlLabels: true } }
{ look: 'handDrawn', flowchart: { htmlLabels: true } }
);
});
@@ -682,7 +668,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -711,7 +696,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -728,7 +712,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -752,7 +735,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: false },
fontFamily: 'courier',
}
@@ -769,7 +751,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { diagramPadding: 0 } }
{ look: 'handDrawn', flowchart: { diagramPadding: 0 } }
);
});
@@ -804,7 +786,7 @@ describe('Flowchart HandDrawn', () => {
`graph TD
a["<strong>Haiya</strong>"]-->b
`,
{ look: 'handDrawn', handDrawnSeed: 1, htmlLabels: false, flowchart: { htmlLabels: false } }
{ look: 'handDrawn', htmlLabels: false, flowchart: { htmlLabels: false } }
);
});
it('FDH37: should render non-escaped with html labels', () => {
@@ -814,7 +796,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -830,7 +811,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { useMaxWidth: true } }
{ look: 'handDrawn', flowchart: { useMaxWidth: true } }
);
cy.get('svg').should((svg) => {
expect(svg).to.have.attr('width', '100%');
@@ -853,7 +834,7 @@ describe('Flowchart HandDrawn', () => {
C -->|Two| E[iPhone]
C -->|Three| F[fa:fa-car Car]
`,
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { useMaxWidth: false } }
{ look: 'handDrawn', flowchart: { useMaxWidth: false } }
);
cy.get('svg').should((svg) => {
// const height = parseFloat(svg.attr('height'));
@@ -874,7 +855,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -904,7 +884,6 @@ describe('Flowchart HandDrawn', () => {
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -919,7 +898,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -937,7 +915,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -977,7 +954,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -999,7 +975,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -1016,7 +991,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -1032,7 +1006,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
htmlLabels: true,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
@@ -1051,7 +1024,6 @@ graph TD
`,
{
look: 'handDrawn',
handDrawnSeed: 1,
flowchart: { htmlLabels: true },
securityLevel: 'loose',
}

View File

@@ -124,3 +124,20 @@ describe('Test iconShape with different h', () => {
imgSnapshotTest(flowchartCode);
});
});
describe('Test colored iconShape', () => {
it('with no styles', () => {
let flowchartCode = `flowchart TB\n`;
const icon = 'fluent-emoji:tropical-fish';
flowchartCode += ` nA --> nAA@{ icon: '${icon}', form: 'square', label: 'icon with color' }\n`;
imgSnapshotTest(flowchartCode);
});
it('with styles', () => {
let flowchartCode = `flowchart TB\n`;
const icon = 'fluent-emoji:tropical-fish';
flowchartCode += ` nA --> nAA@{ icon: '${icon}', form: 'square', label: 'icon with color' }\n`;
flowchartCode += ` style nAA fill:#f9f,stroke:#333,stroke-width:4px \n`;
imgSnapshotTest(flowchartCode);
});
});

View File

@@ -0,0 +1,136 @@
import { imgSnapshotTest } from '../../helpers/util.ts';
describe('Kanban diagram', () => {
it('1: should render a kanban with a single section', () => {
imgSnapshotTest(
`kanban
id1[Todo]
docs[Create Documentation]
docs[Create Blog about the new diagram]
`,
{}
);
});
it('2: should render a kanban with multiple sections', () => {
imgSnapshotTest(
`kanban
id1[Todo]
docs[Create Documentation]
id2
docs[Create Blog about the new diagram]
`,
{}
);
});
it('3: should render a kanban with a single wrapping node', () => {
imgSnapshotTest(
`kanban
id1[Todo]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char, wrapping]
`,
{}
);
});
it('4: should handle the height of a section with a wrapping node at the end', () => {
imgSnapshotTest(
`kanban
id1[Todo]
id2[One line]
id3[Title of diagram is more than 100 chars when user duplicates diagram with 100 char, wrapping]
`,
{}
);
});
it('5: should handle the height of a section with a wrapping node at the top', () => {
imgSnapshotTest(
`kanban
id1[Todo]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char, wrapping]
id3[One line]
`,
{}
);
});
it('6: should handle the height of a section with a wrapping node in the middle', () => {
imgSnapshotTest(
`kanban
id1[Todo]
id2[One line]
id3[Title of diagram is more than 100 chars when user duplicates diagram with 100 char, wrapping]
id4[One line]
`,
{}
);
});
it('6: should handle assigments', () => {
imgSnapshotTest(
`kanban
id1[Todo]
docs[Create Documentation]
id2[In progress]
docs[Create Blog about the new diagram]@{ assigned: 'knsv' }
`,
{}
);
});
it('7: should handle prioritization', () => {
imgSnapshotTest(
`kanban
id2[In progress]
vh[Very High]@{ priority: 'Very High' }
h[High]@{ priority: 'High' }
m[Default priority]
l[Low]@{ priority: 'Low' }
vl[Very Low]@{ priority: 'Very Low' }
`,
{}
);
});
it('7: should handle external tickets', () => {
imgSnapshotTest(
`kanban
id1[Todo]
docs[Create Documentation]
id2[In progress]
docs[Create Blog about the new diagram]@{ ticket: MC-2037 }
`,
{}
);
});
it('8: should handle assignments, prioritization and tickets ids in the same item', () => {
imgSnapshotTest(
`kanban
id2[In progress]
docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }
`,
{}
);
});
it('10: Full example', () => {
imgSnapshotTest(
`---
config:
kanban:
ticketBaseUrl: 'https://abc123.atlassian.net/browse/#TICKET#'
---
kanban
id1[Todo]
docs[Create Documentation]
docs[Create Blog about the new diagram]
id7[In progress]
id6[Create renderer so that it works in all cases. We also add som extra text here for testing purposes. And some more just for the extra flare.]
id8[Design grammar]@{ assigned: 'knsv' }
id9[Ready for deploy]
id10[Ready for test]
id11[Done]
id5[define getData]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
id12[Can't reproduce]
`,
{}
);
});
});

View File

@@ -10,6 +10,10 @@
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
rel="stylesheet"
/>
<link
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
rel="stylesheet"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
@@ -34,6 +38,7 @@
/* background: rgb(221, 208, 208); */
/* background: #333; */
font-family: 'Arial';
/* color: white; */
/* font-size: 18px !important; */
}
@@ -83,353 +88,262 @@
</head>
<body>
<div class="flex">
<pre id="diagram" class="mermaid2">
---
title: hello2
config:
look: handDrawn
layout: elk
elk:
<!-- nodePlacementStrategy: INTERACTIVE -->
<!-- mergeEdges: true -->
---
stateDiagram-v2
direction LR
accTitle: An idealized Open Source supply-chain graph
%%
state "🟦 Importer" as author_importer
state "🟥 Supplier, Owner" as author_owner
state "🟨🟥 Maintainer, Author\n🟨 Custodian" as author
state "🟩 Distributor" as repository_distributor
state "🟦 Importer" as language_importer
state "🟦🟨 Packager" as language_packager
state "🟦🟨 OSS Steward" as language_steward
state "🟨 Curator" as language_curator
state "🟩 Distributor" as language_distributor
state "🟦 Contributor" as contributor
state "🟦 Importer" as package_importer
state "🟨 Patcher" as package_patcher
state "🟨🟦 Builder\n🟨🟦 Packager\n🟨🟦 Containerizer" as package_packager
state "🟨 Curator" as package_curator
state "🟩 Distributor" as package_distributor
state "🟦 Importer" as integrator_importer
state "🟥 Supplier, Manufacturer, Owner" as integrator_owner
state "🟦🟨🟥 Integrator, Developer" as integrator_developer
state "🟩🟨 SBOM Redactor\n🟩 Publisher" as integrator_publisher
state "🟦🟨 Builder" as integrator_builder
state "🟨 Deployer" as deployer
state "🟦 Vuln. Checker" as integrator_checker
state "🟩🟨 SBOM Redactor" as redactor
state "🟦 Consumer\n🟦 User" as consumer
state "🟦 Auditor" as auditor_internal
state "🟦 Auditor" as auditor_external
%%
classDef createsSBOM stroke:red,stroke-width:3px;
classDef updatesSBOM stroke:yellow,stroke-width:3px;
classDef assemblesSBOM stroke:yellow,stroke-width:3px;
classDef distributesSBOM stroke:green,stroke-width:3px;
classDef verifiesSBOM stroke:#07f,stroke-width:3px;
%%
class author_importer verifiesSBOM
class author_owner createsSBOM
class manufacturer_owner createsSBOM
class author assemblesSBOM
class package_importer verifiesSBOM
class package_patcher updatesSBOM
class package_packager assemblesSBOM
class package_curator distributesSBOM
class package_distributor distributesSBOM
class language_importer verifiesSBOM
class language_packager assemblesSBOM
class language_steward updatesSBOM
class language_curator distributesSBOM
class language_distributor distributesSBOM
class repository_distributor distributesSBOM
class integrator_importer verifiesSBOM
class integrator_owner createsSBOM
class integrator_developer assemblesSBOM
class integrator_publisher distributesSBOM
class integrator_builder assemblesSBOM
class integrator_checker verifiesSBOM
class deployer assemblesSBOM
class redactor distributesSBOM
class auditor_internal verifiesSBOM
class auditor_external verifiesSBOM
state "Maintainer Environment" as environment_maintainer {
[*] --> author_importer
[*] --> author
author_importer --> author
author_owner --> author
author --> language_packager
}
[*] --> environment_maintainer
state "Language Ecosystem" as ecosystem_lang {
[*] --> language_importer
[*] --> language_steward
[*] --> language_curator
[*] --> language_distributor
language_importer --> language_distributor
language_importer --> language_curator
language_steward --> language_curator
language_curator --> language_distributor
}
language_packager --> ecosystem_lang
ecosystem_lang --> ecosystem_lang
state "Public Collaboration Ecosystem" as ecosystem_repo {
[*] --> repository_distributor
}
author --> ecosystem_repo
ecosystem_repo --> author
repository_distributor --> contributor
contributor --> repository_distributor
state "Package Ecosystem" as ecosystem_package {
[*] --> package_importer
[*] --> package_packager
[*] --> package_patcher
package_importer --> package_patcher
package_importer --> package_packager
package_patcher --> package_packager
package_packager --> package_curator
package_packager --> package_distributor
package_curator --> package_distributor
}
repository_distributor --> ecosystem_package
language_distributor --> ecosystem_package
ecosystem_package --> ecosystem_package
state "Integrator Environment" as environment_integrator {
[*] --> integrator_developer
[*] --> integrator_importer
integrator_importer --> integrator_developer
integrator_owner --> integrator_developer
integrator_builder --> integrator_publisher
integrator_developer --> integrator_checker
integrator_checker --> integrator_developer
auditor_internal --> integrator_developer
integrator_developer --> integrator_builder
integrator_developer --> auditor_internal
}
repository_distributor --> environment_integrator
language_distributor --> environment_integrator
package_distributor --> environment_integrator
state "Production Environment" as environment_prod {
[*] --> deployer
deployer --> redactor
}
integrator_publisher --> [*]
integrator_developer --> environment_prod
integrator_builder --> environment_prod
integrator_publisher --> environment_prod
deployer --> auditor_external
deployer --> consumer
redactor --> consumer
</pre>
<pre id="diagram" class="mermaid2">
---
config:
look: neo
---
flowchart RL
subgraph " "
A5@{ shape: manual-file, label: "a label"}
B5@{ shape: manual-input, label: "a label" }
C5@{ shape: mul-doc, label: "a label" }
D5@{ shape: mul-proc, label: "a label" }
E5@{ shape: paper-tape, label: "a label" }
B3@{ shape: das, label: "a label" }
C3@{ shape: disk, label: "a label" }
D4@{ shape: lin-doc, label: "a label" }
E4@{ shape: loop-limit, label: "a label" }
end
subgraph " "
B6@{ shape: summary, label: "a label" }
C6@{ shape: tag-we-rect, label: "a label" }
D6@{ shape: tag-rect, label: "a label" }
A2@{ shape: fork}
B2@{ shape: hourglass }
C2@{ shape: comment, label: "I am a comment" }
D2@{ shape: bolt }
D3@{ shape: disp, label: "a label" }
C4@{ shape: junction, label: "a label" }
A4@{ shape: extract, label: "a label"}
B52[a fr]@{ shape: fr }
end
subgraph " "
A1@{ shape: text, label: This is a textblock}
B1@{ shape: card, label: "a label" }
C1@{ shape: lined-proc, label: "a label" }
D1@{ shape: start, label: "a label" }
E1@{ shape: stop, label: "a label" }
E2@{ shape: doc, label: "a label" }
A6@{ shape: stored-data, label: "a label"}
A3@{ shape: delay, label: "a label" }
E3@{ shape: div-proc, label: "a label" }
B4[a label]@{ shape: win-pane }
end
</pre>
<pre id="diagram" class="mermaid2">
---
title: hello2
config:
look: handDrawn
elk:
<!-- nodePlacementStrategy: SIMPLE -->
---
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
flowchart TD
A([Start]) -->|go to booking page| B("select
ISBS booking no")
A --> QQ{cancel booking}
A --> RR{no show}
A --> SS{change booking}
B -->C(wmpay_request_payment.request_type= 'partial',
wmpay_request_payment.status= 'paid',
pos_booking.booking_status= partial and 'full_deposit')
style C text-align:left
C -->D{manage booking}
D -->|cancel|E[ระบบแสดงช่องให้กรอกเหตุผล]
E -->F{กดปุ่ม 'cancel' หรือไม่}
F -->|Yes|G[ระบบบันทึกค่าใหม่
และไม่สามารถแก้ไขข้อมูลได้]
F -->|No|H[กดปุ่ม 'close']
H -->|ระบบไม่เปลี่ยนแปลงข้อมูล|Z
G -->|ระบบส่งข้อมูล|I[(POS_database)]
I -->|pos_booking.booking_status='cancel'|Z([End])
D -->|no show|J[ระบบแสดงช่องให้กรอกเหตุผล]
J -->K{กดปุ่ม 'noshow' หรือไม่}
K -->|Yes|L[ระบบสร้างใบเสร็จอัตโนมัติ
Product_id: 439,
ItemName: no show]
style L text-align:left
K -->|No|O[กดปุ่ม 'close']
O -->|ระบบไม่เปลี่ยนแปลงข้อมูล|Z
L -->M[ระบบบันทึกค่าใหม่]
M -->|ระบบส่งข้อมูล|N[(POS_database)]
N -->|pos_booking.booking_status=noshow|Z
</pre>
<pre id="diagram" class="mermaid2">
---
title: hello2
config:
look: handDrawn
layout: dagre
elk:
nodePlacementStrategy: BRANDES_KOEPF
---
flowchart
A --> A
subgraph A
B --> B
subgraph B
C
end
end
</pre
>
<pre id="diagram" class="mermaid2">
---
config:
look: handdrawn
flowchart:
htmlLabels: true
---
flowchart
A[I am a long text, where do I go??? handdrawn - true]
</pre
>
</div>
<div class="flex">
<pre id="diagram" class="mermaid2">
---
config:
flowchart:
htmlLabels: false
---
flowchart
A[I am a long text, where do I go??? classic - false]
</pre
>
<pre id="diagram" class="mermaid2">
---
config:
flowchart:
htmlLabels: true
---
flowchart
A[I am a long text, where do I go??? classic - true]
</pre
>
</div>
<pre id="diagram2" class="mermaid2">
flowchart LR
id1(Start)-->id2(Stop)
style id1 fill:#f9f,stroke:#333,stroke-width:4px
style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
</pre>
<pre id="diagram3" class="mermaid2">
flowchart LR
A:::foo & B:::bar --> C:::foobar
classDef foo stroke:#f00
classDef bar stroke:#0f0
classDef ash color:red
class C ash
style C stroke:#00f, fill:black
</pre>
<pre id="diagram4" class="mermaid2">
stateDiagram
A:::foo
B:::bar --> C:::foobar
classDef foo stroke:#f00
classDef bar stroke:#0f0
style C stroke:#00f, fill:black, color:white
---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
n3["Option 2"]
n4["fa:fa-car Option 3"]
end
n1 -- One --> n2
n1 -- Two --> n3
n1 -- Three --> n4
n5
n1@{ shape: diam}
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
</pre>
<pre id="diagram4" class="mermaid">
flowchart TB
A@{
label: "aksljhf kasjdh"
}
---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
n3["Option 2"]
n4["fa:fa-car Option 3"]
end
subgraph s2["Untitled subgraph"]
n5["Evaluate"]
n6["Option 1"]
n7["Option 2"]
n8["fa:fa-car Option 3"]
end
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
n1 -- One --> n2
n1 -- Two --> n3
n1 -- Three --> n4
n5 -- One --> n6
n5 -- Two --> n7
n5 -- Three --> n8
n1@{ shape: diam}
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
n5@{ shape: diam}
n6@{ shape: rect}
n7@{ shape: rect}
n8@{ shape: rect}
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
subgraph s1["Untitled subgraph"]
n1["Evaluate"]
n2["Option 1"]
end
n1 -- One --> n2
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
A{A} --> B & C
</pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
---
flowchart LR
n2@{ shape: rect}
n3@{ shape: rect}
n4@{ shape: rect}
A["Start"] -- Some text --> B("Continue")
B --> C{"Evaluate"}
C -- One --> D["Option 1"]
C -- Two --> E["Option 2"]
C -- Three --> F["fa:fa-car Option 3"]
%% C@{ shape: hexagon}
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
kanban:
ticketBaseUrl: 'https://github.com/your-repo/issues/#TICKET#'
---
kanban
Backlog
task1[📝 Define project requirements]@{ ticket: a101 }
To Do
task2[🔍 Research technologies]@{ ticket: a102 }
Review
task4[🔍 Code review for login feature]@{ ticket: a104 }
Done
task5[✅ Deploy initial version]@{ ticket: a105 }
In Progress
task3[💻 Develop login feature]@{ ticket: 103 }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'rounded' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'rounded' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'rounded' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'square' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'square' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'square' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Default] --> A@{ icon: 'fa:bell', form: 'circle' }
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'fa:bell', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'fa:bell', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Class] --> A@{ icon: 'logos:aws', form: 'circle' }
A:::AClass
classDef AClass fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
flowchart LR
nA[Style] --> A@{ icon: 'logos:aws', form: 'circle' }
style A fill:#f9f,stroke:#333,stroke-width:4px
</pre>
<pre id="diagram4" class="mermaid2">
kanban
id2[In progress]
docs[Create Blog about the new diagram]@{ priority: 'Very Low', ticket: MC-2037, assigned: 'knsv' }
</pre>
<pre id="diagram4" class="mermaid2">
---
config:
kanban:
ticketBaseUrl: 'https://mermaidchart.atlassian.net/browse/#TICKET#'
# sectionWidth: 300
---
kanban
Todo
[Create Documentation]
docs[Create Blog about the new diagram]
id7[In progress]
id6[Create renderer so that it works in all cases. We also add som extra text here for testing purposes. And some more just for the extra flare.]
id9[Ready for deploy]
id8[Design grammar]@{ assigned: 'knsv' }
id10[Ready for test]
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
id11[Done]
id5[define getData]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
id12[Can't reproduce]
id3[Weird flickering in Firefox]
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import layouts from './mermaid-layout-elk.esm.mjs';
const staticBellIconPack = {
prefix: 'fa6-regular',
icons: {
bell: {
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',
width: 448,
},
},
width: 512,
height: 512,
};
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
},
{
name: 'fa',
loader: () => staticBellIconPack,
},
]);
mermaid.registerLayoutLoaders(layouts);
mermaid.parseError = function (err, hash) {
console.error('Mermaid error: ', err);
@@ -439,6 +353,8 @@ flowchart TB
};
mermaid.initialize({
// theme: 'base',
// theme: 'default',
// theme: 'forest',
// handDrawnSeed: 12,
// look: 'handDrawn',
// 'elk.nodePlacement.strategy': 'NETWORK_SIMPLEX',
@@ -455,8 +371,11 @@ flowchart TB
noteFontFamily: 'courier',
messageFontFamily: 'courier',
},
kanban: {
htmlLabels: false,
},
fontSize: 12,
logLevel: 3,
logLevel: 0,
securityLevel: 'loose',
});
function callback() {

File diff suppressed because one or more lines are too long

663
cypress/platform/yari.html Normal file
View File

@@ -0,0 +1,663 @@
<html>
<body>
<h1 class="header">Class Nodes</h1>
<div class="node-showcase">
<div class="test">
<h2>Basic Class</h2>
<pre class="mermaid">
---
config:
htmlLabels: false
---
classDiagram
class _Duck_ {
+String beakColor
_+_swim_()a_
__+quack() test__
}
</pre>
</div>
<div class="test">
<h2>Basic Class</h2>
<pre class="mermaid">
---
config:
htmlLabels: false
---
classDiagram
class Class10:::exClass2 {
int[] id
List~int~ ids
test(List~int~ ids) List~bool~
testArray() bool[]
}
</pre>
</div>
<div class="test">
<h2>Basic Class</h2>
<pre class="mermaid">
flowchart TD
Start --> Stop
</pre>
</div>
<div class="test">
<h2>Complex Class</h2>
<pre class="mermaid">
classDiagram
class Square~Shape~{
int id
List~int~ position
setPoints(List~int~ points)
getPoints() List~int~
}
Square : -List~string~ messages
Square : +setMessages(List~string~ messages)
Square : +getMessages() List~string~
Square : +getDistanceMatrix() List~List~int~~
</pre
>
</div>
<div class="test">
<h2>No Attributes</h2>
<pre class="mermaid">
classDiagram
class Duck {
+swim()
+quack()
}
</pre>
</div>
<div class="test">
<h2>No Methods</h2>
<pre class="mermaid">
classDiagram
class Duck {
+String beakColor
}
</pre>
</div>
<div class="test">
<h2>Only Class Name</h2>
<p>Empty line as attribute</p>
<pre class="mermaid">
---
config:
class:
hideEmptyMembersBox: false
---
classDiagram
class Duck {
}
</pre>
</div>
<div class="test">
<h2>Visibility and Types</h2>
<p>(Further tilde testing)</p>
<div class="mermaid">
classDiagram class Duck { ~interface~~~ +String beakColor #swim() ~quack()~~~
-test()~~~~~~~ +deposit(amount) bool }
</div>
</div>
<div class="test">
<h2>Additional Classifiers</h2>
<p>(* Abstract | $ Static)</p>
<div class="mermaid">
classDiagram class Square~Shape~ { int id* List~int~ position* setPoints(List~int~points)*
getPoints()* List~int~ } Square : -List~string~ messages$ Square :
+setMessages(List~string~ messages)* Square : +getMessages()$ List~string~ Square :
+getDistanceMatrix() List~List~int~~$
</div>
</div>
<div class="test">
<h2>Label</h2>
<pre class="mermaid">
classDiagram
class Animal~test~["Animal with a label"]
</pre>
</div>
<div class="test">
<h2>Spacing</h2>
<p>(Fix ensures consistent spacing rules)</p>
<p>(No space or single space?)</p>
<pre class="mermaid">
classDiagram
class ClassName {
-attribute:type
- attribute : type
test
+ GetAttribute() type
+ GetAttribute() type
}
</pre>
</div>
<div class="test">
<h2>Annotation</h2>
<pre class="mermaid">
classDiagram
class Shape
&lt;&lt;interface&gt;&gt; Shape
Shape : noOfVertices
Shape : draw()
</pre>
</div>
<div class="test">
<h2>Long Class Name Text</h2>
<pre class="mermaid">
classDiagram
class ThisIsATestForALongClassName {
&lt;&lt;interface&gt;&gt;
noOfLetters
delete()
}
</pre>
</div>
<div class="test">
<h2>Long Annotation Text</h2>
<pre class="mermaid">
classDiagram
class Shape
&lt;&lt;superlongannotationtext&gt;&gt; Shape
Shape : noOfVertices
Shape : draw()
</pre>
</div>
<div class="test">
<h2>Long Member Text</h2>
<pre class="mermaid">
classDiagram
class Shape
&lt;&lt;interface&gt;&gt; Shape
Shape : noOfVertices
Shape : longtexttestkeepgoingandgoing
Shape : draw()
</pre>
</div>
<div class="test">
<h2>Link</h2>
<pre class="mermaid">
classDiagram
class Shape
link Shape "https://www.github.com" "This is a tooltip for a link"
</pre>
</div>
<div class="test">
<h2>Click</h2>
<pre class="mermaid">
classDiagram
class Shape
click Shape href "https://www.github.com" "This is a tooltip for a link"
</pre>
</div>
<div class="test">
<h2>Hand Drawn</h2>
<pre class="mermaid">
---
config:
look: handDrawn
htmlLabels: true
---
classDiagram
class Hand {
+String beakColor
+swim()
+quack()
}
style Hand fill:#f9f,stroke:#29f,stroke-width:2px
</pre>
</div>
<div class="test">
<h2>Neutral Theme</h2>
<pre class="mermaid">
---
config:
theme: neutral
---
classDiagram
class Duck {
+String beakColor
+swim()
+quack()
}
</pre>
</div>
<div class="test">
<h2>Dark Theme</h2>
<pre class="mermaid">
---
config:
theme: dark
---
classDiagram
class Duck {
+String beakColor
+swim()
+quack()
}
</pre>
</div>
<div class="test">
<h2>Forest Theme</h2>
<pre class="mermaid">
---
config:
theme: forest
---
classDiagram
class Duck {
+String beakColor
+swim()
+quack()
}
</pre>
</div>
<div class="test">
<h2>Base Theme</h2>
<pre class="mermaid">
---
config:
theme: base
---
classDiagram
class Duck {
+String beakColor
+swim()
+quack()
}
</pre>
</div>
<div class="test">
<h2>Custom Theme</h2>
<pre class="mermaid">
%%{
init: {
'theme': 'base',
'themeVariables': {
'primaryColor': '#BB2528',
'primaryTextColor': '#fff',
'primaryBorderColor': '#7C0000',
'lineColor': '#F83d29',
'secondaryColor': '#006100',
'tertiaryColor': '#fff'
}
}
}%%
classDiagram
class Duck {
+String beakColor
+swim()
+quack()
}
Duck--Dog
</pre>
</div>
<div class="test">
<h2>Styling within Diagram</h2>
<pre class="mermaid">
classDiagram
class Duck {
+String beakColor
+swim()
+quack()
}
style Duck fill:#f9f,stroke:#333,stroke-width:8px
</pre>
</div>
<div class="test">
<h2>Styling with classDef Statement</h2>
<pre class="mermaid">
classDiagram
class Duck:::bold {
+String beakColor
+swim()
+quack()
}
class Dog {
+int numTeeth
+bark()
}
cssClass "Duck,Dog" pink
classDef pink fill:#f9f
classDef default color:#f1e
classDef bold stroke:#333,stroke-width:6px,color:#fff
</pre>
</div>
<div class="test">
<h2>Styling with Class in Stylesheet</h2>
<pre class="mermaid">
classDiagram
class Duck {
+String beakColor
+swim()
+quack()
}
class Duck:::styleClass
</pre>
</div>
</div>
<h1 class="header">Diagram Testing</h1>
<div class="diagram-showcase">
<div class="test">
<h2>Class Nodes Only</h2>
<pre class="mermaid">
---
title: Animal example
---
classDiagram
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}
</pre>
</div>
<div class="test">
<h2>Class Nodes LR</h2>
<pre class="mermaid">
---
title: Animal example
---
classDiagram
direction LR
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}
</pre>
</div>
<div class="test">
<h2>Relations</h2>
<pre class="mermaid">
classDiagram
classA <|-- classB
classC *-- classD
classE o-- classF
classG <-- classH
classI -- classJ
classK <.. classL
classM <|.. classN
classO .. classP
</pre>
</div>
<div class="test">
<h2>Two Way Relation</h2>
<pre class="mermaid">
classDiagram
class Animal {
int size
walk()
}
class Zebra {
int size
walk()
}
Animal o--|> Zebra
</pre>
</div>
<div class="test">
<h2>Relations with Labels</h2>
<pre class="mermaid">
classDiagram
classA <|-- classB : implements
classC *-- classD : composition
classE o-- classF : aggregation
</pre>
</div>
<div class="test">
<h2>Cardinality / Multiplicity</h2>
<pre class="mermaid">
classDiagram
Customer "1" --> "*" Ticket
Student "1" --> "1..*" Course
Galaxy --> "many" Star : Contains
</pre>
</div>
<div class="test">
<h2>Complex Relations with Theme</h2>
<pre class="mermaid">
---
config:
theme: forest
look: handDrawns
layout: elk
---
classDiagram
direction RL
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" o--o "1" IdCard : carries
Student "1" o--o "1" Bike : rides
</pre>
</div>
<div class="test">
<h2>Notes</h2>
<pre class="mermaid">
classDiagram
note "This is a general note"
note for MyClass "This is a note for a class"
class MyClass
</pre>
</div>
<div class="test">
<h2>Namespaces</h2>
<pre class="mermaid">
classDiagram
namespace BaseShapes {
class Triangle
class Rectangle {
double width
double height
}
}
</pre>
</div>
<div class="test">
<h2>Namespaces</h2>
<pre class="mermaid">
---
config:
layout: elk
---
classDiagram
namespace Namespace1 {
class C1
class C2
}
C1 --> C2
class C3
class C4
</pre>
</div>
<div class="test">
<h2>Full Example</h2>
<pre class="mermaid">
---
title: Animal example
config:
layout: dagre
---
classDiagram
note "From Duck till Zebra"
Animal <|--|> Duck
note for Duck "can fly<br>can swim<br>can dive<br>can help in debugging"
Animal <|-- Fish
Animal <|--|> Zebra
Animal : +int age
Animal : +String gender
Animal: +isMammal()
Animal: +mate()
class Duck{
+String beakColor
+swim()
+quack()
}
class Fish{
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run()
}
cssClass "Duck" test
classDef test fill:#f71
%%classDef default fill:#f93
</pre>
</div>
<div class="test">
<h2>Full Example</h2>
<pre class="mermaid">
---
config:
theme: forest
look: handDrawn
---
classDiagram
note for Outside "Note testing"
namespace Test {
class Outside
}
namespace BaseShapes {
class Triangle
class Rectangle {
double width
double height
}
}
Outside <|--|> Rectangle
style Triangle fill:#f9f,stroke:#333,stroke-width:4px
</pre>
</div>
<div class="test">
<pre class="mermaid">
---
config:
look: handDrawn
layout: elk
---
classDiagram
Class01 "1" <|--|> "*" AveryLongClass : Cool
&lt;&lt;interface&gt;&gt; Class01
Class03 "1" *-- "*" Class04
Class05 "1" o-- "many" Class06
Class07 "1" .. "*" Class08
Class09 "1" --> "*" C2 : Where am i?
Class09 "*" --* "*" C3
Class09 "1" --|> "1" Class07
NewClass ()--() Class04
Class09 <|--|> AveryLongClass
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
Class01 : int chimp
Class01 : int gorilla
Class08 "1" <--> "*" C2: Cool label
class Class10 {
&lt;&lt;service&gt;&gt;
int id
test()
}
Class10 o--o AveryLongClass
Class10 <--> Class07
</pre>
</div>
<div class="test">
<pre class="mermaid">
classDiagram
test ()--() test2
</pre>
</div>
</div>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import layouts from './mermaid-layout-elk.esm.mjs';
mermaid.registerLayoutLoaders(layouts);
mermaid.parseError = function (err, hash) {
console.error('Mermaid error: ', err);
};
mermaid.initialize();
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
</script>
</body>
<style>
.header {
text-decoration: underline;
text-align: center;
}
.node-showcase {
display: grid;
grid-template-columns: 1fr 1fr;
}
.test {
flex-grow: 1;
display: flex;
flex-direction: column;
align-items: center;
gap: 0.4rem;
}
.test > h2 {
margin: 0;
text-align: center;
}
.test > p {
margin-top: -6px;
color: gray;
}
.diagram-showcase {
display: grid;
grid-template-columns: 1fr;
}
.styleClass > * > path {
fill: #ff0000 !important;
stroke: #ffff00 !important;
stroke-width: 4px !important;
stroke-dasharray: 2 !important;
}
</style>
</html>

152
cypress/timings.json Normal file
View File

@@ -0,0 +1,152 @@
{
"durations": [
{
"spec": "cypress/integration/other/configuration.spec.js",
"duration": 4989
},
{
"spec": "cypress/integration/other/external-diagrams.spec.js",
"duration": 1382
},
{
"spec": "cypress/integration/other/ghsa.spec.js",
"duration": 3178
},
{
"spec": "cypress/integration/other/iife.spec.js",
"duration": 1372
},
{
"spec": "cypress/integration/other/interaction.spec.js",
"duration": 8998
},
{
"spec": "cypress/integration/other/rerender.spec.js",
"duration": 1249
},
{
"spec": "cypress/integration/other/xss.spec.js",
"duration": 25664
},
{
"spec": "cypress/integration/rendering/appli.spec.js",
"duration": 1928
},
{
"spec": "cypress/integration/rendering/architecture.spec.ts",
"duration": 2330
},
{
"spec": "cypress/integration/rendering/block.spec.js",
"duration": 11156
},
{
"spec": "cypress/integration/rendering/c4.spec.js",
"duration": 3418
},
{
"spec": "cypress/integration/rendering/classDiagram-v2.spec.js",
"duration": 14866
},
{
"spec": "cypress/integration/rendering/classDiagram.spec.js",
"duration": 9894
},
{
"spec": "cypress/integration/rendering/conf-and-directives.spec.js",
"duration": 5778
},
{
"spec": "cypress/integration/rendering/current.spec.js",
"duration": 1690
},
{
"spec": "cypress/integration/rendering/erDiagram.spec.js",
"duration": 9144
},
{
"spec": "cypress/integration/rendering/errorDiagram.spec.js",
"duration": 1951
},
{
"spec": "cypress/integration/rendering/flowchart-elk.spec.js",
"duration": 2196
},
{
"spec": "cypress/integration/rendering/flowchart-handDrawn.spec.js",
"duration": 21029
},
{
"spec": "cypress/integration/rendering/flowchart-shape-alias.spec.ts",
"duration": 16087
},
{
"spec": "cypress/integration/rendering/flowchart-v2.spec.js",
"duration": 27465
},
{
"spec": "cypress/integration/rendering/flowchart.spec.js",
"duration": 20035
},
{
"spec": "cypress/integration/rendering/gantt.spec.js",
"duration": 11366
},
{
"spec": "cypress/integration/rendering/gitGraph.spec.js",
"duration": 34025
},
{
"spec": "cypress/integration/rendering/iconShape.spec.ts",
"duration": 185902
},
{
"spec": "cypress/integration/rendering/imageShape.spec.ts",
"duration": 41631
},
{
"spec": "cypress/integration/rendering/info.spec.ts",
"duration": 1736
},
{
"spec": "cypress/integration/rendering/journey.spec.js",
"duration": 2247
},
{
"spec": "cypress/integration/rendering/katex.spec.js",
"duration": 2144
},
{
"spec": "cypress/integration/rendering/marker_unique_id.spec.js",
"duration": 1646
},
{
"spec": "cypress/integration/rendering/mindmap.spec.ts",
"duration": 6406
},
{
"spec": "cypress/integration/rendering/newShapes.spec.ts",
"duration": 107219
},
{
"spec": "cypress/integration/rendering/stateDiagram.spec.js",
"duration": 15834
},
{
"spec": "cypress/integration/rendering/theme.spec.js",
"duration": 33240
},
{
"spec": "cypress/integration/rendering/timeline.spec.ts",
"duration": 7122
},
{
"spec": "cypress/integration/rendering/xyChart.spec.js",
"duration": 11127
},
{
"spec": "cypress/integration/rendering/zenuml.spec.js",
"duration": 2391
}
]
}

View File

@@ -101,7 +101,7 @@ To add a new shape:
- **Example**:
```typescript
import { Node, RenderOptions } from '../../types.d.ts';
import { Node, RenderOptions } from '../../types.ts';
export const myNewShape = async (
parent: SVGAElement,
@@ -117,7 +117,7 @@ To add a new shape:
### 2. Register the Shape
- **Register the shape**: Add your shape to the `shapes` object in the main shapes module. This allows your shape to be recognized and used within the system.
- **Register the shape**: Add your shape to the `shapes` object in the [main shapes module](../rendering-util/rendering-elements/shapes.ts). This allows your shape to be recognized and used within the system.
- **Example**:
@@ -126,9 +126,14 @@ To add a new shape:
const shapes = {
...,
'my-new-shape': myNewShape,
// Shortened alias (if any).
'm-nsh': myNewShape
{
semanticName: 'My Shape',
name: 'Shape Name',
shortName: '<short-name>',
description: '<Description for the shape>',
aliases: ['<alias-one>', '<al-on>', '<alias-two>', '<al-two>'],
handler: myNewShape,
},
};
```

55
docs/config/icons.md Normal file
View File

@@ -0,0 +1,55 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/icons.md](../../packages/mermaid/src/docs/config/icons.md).
# Registering icon pack in mermaid
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
Using JSON file directly from CDN:
```js
import mermaid from 'CDN/mermaid.esm.mjs';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
},
]);
```
Using packages and a bundler:
```bash
npm install @iconify-json/logos@1
```
With lazy loading
```js
import mermaid from 'mermaid';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () => import('@iconify-json/logos').then((module) => module.icons),
},
]);
```
Without lazy loading
```js
import mermaid from 'mermaid';
import { icons } from '@iconify-json/logos';
mermaid.registerIconPacks([
{
name: icons.prefix, // To use the prefix defined in the icon pack
icons,
},
]);
```

View File

@@ -127,7 +127,7 @@ Error.prepareStackTrace
#### Defined in
node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:28
node_modules/.pnpm/@types+node\@20.16.11/node_modules/@types/node/globals.d.ts:98
---
@@ -141,7 +141,7 @@ Error.stackTraceLimit
#### Defined in
node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:30
node_modules/.pnpm/@types+node\@20.16.11/node_modules/@types/node/globals.d.ts:100
## Methods
@@ -168,4 +168,4 @@ Error.captureStackTrace
#### Defined in
node_modules/.pnpm/@types+node\@20.16.2/node_modules/@types/node/globals.d.ts:21
node_modules/.pnpm/@types+node\@20.16.11/node_modules/@types/node/globals.d.ts:91

View File

@@ -20,7 +20,7 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L125)
[packages/mermaid/src/rendering-util/types.ts:144](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L144)
---
@@ -30,7 +30,7 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.ts:124](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L124)
[packages/mermaid/src/rendering-util/types.ts:143](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L143)
---
@@ -40,4 +40,4 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.ts:123](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L123)
[packages/mermaid/src/rendering-util/types.ts:142](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L142)

View File

@@ -49,7 +49,7 @@ This matters if you are using base tag settings.
#### Defined in
[packages/mermaid/src/config.type.ts:200](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L200)
[packages/mermaid/src/config.type.ts:201](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L201)
---
@@ -59,7 +59,7 @@ This matters if you are using base tag settings.
#### Defined in
[packages/mermaid/src/config.type.ts:197](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L197)
[packages/mermaid/src/config.type.ts:198](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L198)
---
@@ -121,7 +121,7 @@ should not change unless content is changed.
#### Defined in
[packages/mermaid/src/config.type.ts:201](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L201)
[packages/mermaid/src/config.type.ts:202](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L202)
---
@@ -183,7 +183,7 @@ See <https://developer.mozilla.org/en-US/docs/Web/CSS/font-family>
#### Defined in
[packages/mermaid/src/config.type.ts:203](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L203)
[packages/mermaid/src/config.type.ts:204](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L204)
---
@@ -217,7 +217,7 @@ If set to true, ignores legacyMathML.
#### Defined in
[packages/mermaid/src/config.type.ts:196](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L196)
[packages/mermaid/src/config.type.ts:197](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L197)
---
@@ -253,6 +253,16 @@ Defines the seed to be used when using handDrawn look. This is important for the
---
### kanban
`Optional` **kanban**: `KanbanDiagramConfig`
#### Defined in
[packages/mermaid/src/config.type.ts:196](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L196)
---
### layout
`Optional` **layout**: `string`
@@ -310,7 +320,7 @@ Defines which main look to use for the diagram.
#### Defined in
[packages/mermaid/src/config.type.ts:204](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L204)
[packages/mermaid/src/config.type.ts:205](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L205)
---
@@ -354,7 +364,7 @@ The maximum allowed size of the users text diagram
#### Defined in
[packages/mermaid/src/config.type.ts:199](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L199)
[packages/mermaid/src/config.type.ts:200](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L200)
---
@@ -394,7 +404,7 @@ The maximum allowed size of the users text diagram
#### Defined in
[packages/mermaid/src/config.type.ts:198](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L198)
[packages/mermaid/src/config.type.ts:199](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L199)
---
@@ -465,7 +475,7 @@ This is useful when you want to control how to handle syntax errors in your appl
#### Defined in
[packages/mermaid/src/config.type.ts:210](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L210)
[packages/mermaid/src/config.type.ts:211](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L211)
---
@@ -518,7 +528,7 @@ You may also use `themeCSS` to override this value.
#### Defined in
[packages/mermaid/src/config.type.ts:202](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L202)
[packages/mermaid/src/config.type.ts:203](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L203)
---

View File

@@ -19,4 +19,4 @@ The `parseError` function will not be called.
#### Defined in
[packages/mermaid/src/types.ts:56](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L56)
[packages/mermaid/src/types.ts:59](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L59)

View File

@@ -18,7 +18,7 @@ The config passed as YAML frontmatter or directives
#### Defined in
[packages/mermaid/src/types.ts:67](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L67)
[packages/mermaid/src/types.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L70)
---
@@ -30,4 +30,4 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
#### Defined in
[packages/mermaid/src/types.ts:63](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L63)
[packages/mermaid/src/types.ts:66](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L66)

View File

@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
#### Defined in
[packages/mermaid/src/types.ts:90](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L90)
[packages/mermaid/src/types.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L98)
---
@@ -51,7 +51,7 @@ The diagram type, e.g. 'flowchart', 'sequence', etc.
#### Defined in
[packages/mermaid/src/types.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L80)
[packages/mermaid/src/types.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L88)
---
@@ -63,4 +63,4 @@ The svg code for the rendered graph.
#### Defined in
[packages/mermaid/src/types.ts:76](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L76)
[packages/mermaid/src/types.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/types.ts#L84)

View File

@@ -14,7 +14,7 @@
#### Defined in
[packages/mermaid/src/defaultConfig.ts:267](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L267)
[packages/mermaid/src/defaultConfig.ts:270](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L270)
---

View File

@@ -185,8 +185,6 @@ Communication tools and platforms
- [=Diagram block](https://github.com/zag/podlite/tree/main/packages/podlite-diagrams)
- [Standard Notes](https://standardnotes.com/)
- [Mermaid Extension](https://github.com/nienow/sn-mermaid)
- [Sublime Text 3](https://sublimetext.com)
- [Mermaid Package](https://packagecontrol.io/packages/Mermaid)
- [VS Code](https://code.visualstudio.com/)
- [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor)
- [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export)
@@ -200,15 +198,22 @@ Communication tools and platforms
- [Vim](https://www.vim.org)
- [Vim Diagram Syntax](https://github.com/zhaozg/vim-diagram)
- [Official Vim Syntax and ft plugin](https://github.com/craigmac/vim-mermaid)
- [Zed](https://zed.dev)
- [zed-mermaid](https://github.com/gabeidx/zed-mermaid)
### Document Generation
- [Astro](https://astro.build/)
- [Adding diagrams to your Astro site with MermaidJS and Playwright](https://agramont.net/blog/diagraming-with-mermaidjs-astro/)
- [Codedoc](https://codedoc.cc/)
- [codedoc-mermaid-plugin](https://www.npmjs.com/package/codedoc-mermaid-plugin)
- [Docsy Hugo Theme](https://www.docsy.dev/docs/adding-content/lookandfeel/#diagrams-with-mermaid) ✅
- [Docusaurus](https://docusaurus.io/docs/markdown-features/diagrams) ✅
- [Gatsby](https://www.gatsbyjs.com/)
- [gatsby-remark-mermaid](https://github.com/remcohaszing/gatsby-remark-mermaid)
- [Jekyll](https://jekyllrb.com/)
- [jekyll-mermaid](https://rubygems.org/gems/jekyll-mermaid)
- [jekyll-mermaid-diagrams](https://github.com/fuzhibo/jekyll-mermaid-diagrams)
- [JSDoc](https://jsdoc.app/)
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
- [Madness](https://madness.dannyb.co/)
@@ -217,7 +222,7 @@ Communication tools and platforms
- [MkDocs](https://www.mkdocs.org)
- [mkdocs-mermaid2-plugin](https://github.com/fralau/mkdocs-mermaid2-plugin)
- [mkdocs-material](https://github.com/squidfunk/mkdocs-material), check the [docs](https://squidfunk.github.io/mkdocs-material/reference/diagrams/)
- [Quarto](https://quarto.org/)
- [Quarto](https://quarto.org/)
- [rehype](https://github.com/rehypejs/rehype)
- [rehype-mermaid](https://github.com/remcohaszing/rehype-mermaid)
- [remark](https://remark.js.org/)
@@ -246,17 +251,12 @@ Communication tools and platforms
### Other
- [Astro](https://astro.build/)
- [Adding diagrams to your Astro site with MermaidJS and Playwright](https://agramont.net/blog/diagraming-with-mermaidjs-astro/)
- [Bisheng](https://www.npmjs.com/package/bisheng)
- [bisheng-plugin-mermaid](https://github.com/yct21/bisheng-plugin-mermaid)
- [Blazorade Mermaid: Render Mermaid diagrams in Blazor applications](https://github.com/Blazorade/Blazorade-Mermaid/wiki)
- [Codemia: A tool to practice system design problems](https://codemia.io) ✅
- [ExDoc](https://github.com/elixir-lang/ex_doc)
- [Rendering Mermaid graphs](https://github.com/elixir-lang/ex_doc#rendering-mermaid-graphs)
- [Jekyll](https://jekyllrb.com/)
- [jekyll-mermaid](https://rubygems.org/gems/jekyll-mermaid)
- [jekyll-mermaid-diagrams](https://github.com/fuzhibo/jekyll-mermaid-diagrams)
- [MarkChart: Preview Mermaid diagrams on macOS](https://markchart.app/)
- [mermaid-isomorphic](https://github.com/remcohaszing/mermaid-isomorphic)
- [mermaid-server: Generate diagrams using a HTTP request](https://github.com/TomWright/mermaid-server)

View File

@@ -12,7 +12,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
<br />
<a href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;whiteboard" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light" alt="Mermaid&#0032;Whiteboard - Drag&#0032;&#0038;&#0032;Drop&#0032;your&#0032;Nodes&#0032;with&#0032;Mermaid&#0039;s&#0032;new&#0032;Whiteboard&#0033; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light" alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## About

View File

@@ -94,10 +94,8 @@ Mermaid offers a variety of styles or “looks” for your diagrams, allowing yo
**Available Looks:**
```
• Hand-Drawn Look: For a more personal, creative touch, the hand-drawn look brings a sketch-like quality to your diagrams. This style is perfect for informal settings or when you want to add a bit of personality to your diagrams.
• Classic Look: If you prefer the traditional Mermaid style, the classic look maintains the original appearance that many users are familiar with. Its great for consistency across projects or when you want to keep the familiar aesthetic.
```
- Hand-Drawn Look: For a more personal, creative touch, the hand-drawn look brings a sketch-like quality to your diagrams. This style is perfect for informal settings or when you want to add a bit of personality to your diagrams.
- Classic Look: If you prefer the traditional Mermaid style, the classic look maintains the original appearance that many users are familiar with. Its great for consistency across projects or when you want to keep the familiar aesthetic.
**How to Select a Look:**
@@ -133,10 +131,8 @@ In addition to customizing the look of your diagrams, Mermaid Chart now allows y
#### Supported Layout Algorithms:
```
• Dagre (default): This is the classic layout algorithm that has been used in Mermaid for a long time. It provides a good balance of simplicity and visual clarity, making it ideal for most diagrams.
• ELK: For those who need more sophisticated layout capabilities, especially when working with large or intricate diagrams, the ELK (Eclipse Layout Kernel) layout offers advanced options. It provides a more optimized arrangement, potentially reducing overlapping and improving readability. This is not included out the box but needs to be added when integrating mermaid for sites/applications that want to have elk support.
```
- Dagre (default): This is the classic layout algorithm that has been used in Mermaid for a long time. It provides a good balance of simplicity and visual clarity, making it ideal for most diagrams.
- ELK: For those who need more sophisticated layout capabilities, especially when working with large or intricate diagrams, the ELK (Eclipse Layout Kernel) layout offers advanced options. It provides a more optimized arrangement, potentially reducing overlapping and improving readability. This is not included out the box but needs to be added when integrating mermaid for sites/applications that want to have elk support.
#### How to Select a Layout Algorithm:

View File

@@ -6,6 +6,18 @@
# Blog
## [Mermaid 11.4 is out: New Features and Kanban Diagramming](https://www.mermaidchart.com/blog/posts/mermaid-11-4-is-out-new-features-and-kanban-diagramming)
Mermaid 11.4 brings enhanced functionality with the introduction of Kanban diagrams, allowing users to create visual workflows with status columns and task details.
October 31, 2024 · 2 mins
## [How To Build an ER Diagram with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-build-an-er-diagram-with-mermaid-chart)
An entity relationship (ER) diagram acts like a blueprint for your database. This makes ER diagrams effective tools for anyone dealing with complex databases, data modeling, and AI model training.
October 24, 2024 · 4 mins
## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/)
24 September 2024 · 5 mins

View File

@@ -79,15 +79,15 @@ service {service id}({icon name})[{title}] (in {parent id})?
Put together:
```
service database(db)[Database]
service database1(database)[My Database]
```
creates the service identified as `database`, using the icon `db`, with the label `Database`.
creates the service identified as `database1`, using the icon `database`, with the label `My Database`.
If the service belongs to a group, it can be placed inside it through the optional `in` keyword
```
service database(db)[Database] in private_api
service database1(database)[My Database] in private_api
```
### Edges
@@ -194,55 +194,7 @@ architecture-beta
## Icons
By default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`.
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps below.
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
Using JSON file directly from CDN:
```js
import mermaid from 'CDN/mermaid.esm.mjs';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
},
]);
```
Using packages and a bundler:
```bash
npm install @iconify-json/logos
```
With lazy loading
```js
import mermaid from 'mermaid';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () => import('@iconify-json/logos').then((module) => module.icons),
},
]);
```
Without lazy loading
```js
import mermaid from 'mermaid';
import { icons } from '@iconify-json/logos';
mermaid.registerIconPacks([
{
name: icons.prefix, // To use the prefix defined in the icon pack
icons,
},
]);
```
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps [here](../config/icons.md).
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.

View File

@@ -141,7 +141,7 @@ block-beta
a["A label"] b:2 c:2 d
```
In this example, the block labeled "A wide one" spans two columns, while blocks 'b', 'c', and 'd' are allocated their own columns. This flexibility in block sizing is crucial for accurately representing systems with components of varying significance or size.
In this example, the block labeled "A labels" spans one column, while blocks 'b', 'c' span 2 columns, and 'd' is again allocated its own column. This flexibility in block sizing is crucial for accurately representing systems with components of varying significance or size.
### Creating Composite Blocks

View File

@@ -427,6 +427,51 @@ And `Link` can be one of:
| -- | Solid |
| .. | Dashed |
### Lollipop Interfaces
Classes can also be given a special relation type that defines a lollipop interface on the class. A lollipop interface is defined using the following syntax:
- `bar ()-- foo`
- `foo --() bar`
The interface (bar) with the lollipop connects to the class (foo).
Note: Each interface that is defined is unique and is meant to not be shared between classes / have multiple edges connecting to it.
```mermaid-example
classDiagram
bar ()-- foo
```
```mermaid
classDiagram
bar ()-- foo
```
```mermaid-example
classDiagram
class Class01 {
int amount
draw()
}
Class01 --() bar
Class02 --() bar
foo ()-- Class01
```
```mermaid
classDiagram
class Class01 {
int amount
draw()
}
Class01 --() bar
Class02 --() bar
foo ()-- Class01
```
## Define Namespace
A namespace groups classes.
@@ -776,10 +821,12 @@ Beginner's tip—a full example using interactive links in an HTML page:
## Styling
### Styling a node (v10.7.0+)
### Styling a node
It is possible to apply specific styles such as a thicker border or a different background color to an individual node using the `style` keyword.
Note that notes and namespaces cannot be styled individually but do support themes.
```mermaid-example
classDiagram
class Animal
@@ -799,11 +846,102 @@ classDiagram
#### Classes
More convenient than defining the style every time is to define a class of styles and attach this class to the nodes that
should have a different look. This is done by predefining classes in css styles that can be applied from the graph definition using the `cssClass` statement or the `:::` short hand.
should have a different look.
A class definition looks like the example below:
```
classDef className fill:#f9f,stroke:#333,stroke-width:4px;
```
Also, it is possible to define style to multiple classes in one statement:
```
classDef firstClassName,secondClassName font-size:12pt;
```
Attachment of a class to a node is done as per below:
```
cssClass "nodeId1" className;
```
It is also possible to attach a class to a list of nodes in one statement:
```
cssClass "nodeId1,nodeId2" className;
```
A shorter form of adding a class is to attach the classname to the node using the `:::` operator:
```mermaid-example
classDiagram
class Animal:::someclass
classDef someclass fill:#f96
```
```mermaid
classDiagram
class Animal:::someclass
classDef someclass fill:#f96
```
Or:
```mermaid-example
classDiagram
class Animal:::someclass {
-int sizeInFeet
-canEat()
}
classDef someclass fill:#f96
```
```mermaid
classDiagram
class Animal:::someclass {
-int sizeInFeet
-canEat()
}
classDef someclass fill:#f96
```
### Default class
If a class is named default it will be applied to all nodes. Specific styles and classes should be defined afterwards to override the applied default styling.
```
classDef default fill:#f9f,stroke:#333,stroke-width:4px;
```
```mermaid-example
classDiagram
class Animal:::pink
class Mineral
classDef default fill:#f96,color:red
classDef pink color:#f9f
```
```mermaid
classDiagram
class Animal:::pink
class Mineral
classDef default fill:#f96,color:red
classDef pink color:#f9f
```
### CSS Classes
It is also possible to predefine classes in CSS styles that can be applied from the graph definition as in the example
below:
**Example style**
```html
<style>
.styleClass > rect {
.styleClass > * > g {
fill: #ff0000;
stroke: #ffff00;
stroke-width: 4px;
@@ -811,19 +949,7 @@ should have a different look. This is done by predefining classes in css styles
</style>
```
Then attaching that class to a specific node:
```
cssClass "nodeId1" styleClass;
```
It is also possible to attach a class to a list of nodes in one statement:
```
cssClass "nodeId1,nodeId2" styleClass;
```
A shorter form of adding a class is to attach the classname to the node using the `:::` operator:
**Example definition**
```mermaid-example
classDiagram
@@ -835,136 +961,32 @@ classDiagram
class Animal:::styleClass
```
Or:
```mermaid-example
classDiagram
class Animal:::styleClass {
-int sizeInFeet
-canEat()
}
```
```mermaid
classDiagram
class Animal:::styleClass {
-int sizeInFeet
-canEat()
}
```
?> cssClasses cannot be added using this shorthand method at the same time as a relation statement.
?> Due to limitations with existing markup for class diagrams, it is not currently possible to define css classes within the diagram itself. **_Coming soon!_**
### Default Styles
The main styling of the class diagram is done with a preset number of css classes. During rendering these classes are extracted from the file located at src/themes/class.scss. The classes used here are described below:
| Class | Description |
| ------------------ | ----------------------------------------------------------------- |
| g.classGroup text | Styles for general class text |
| classGroup .title | Styles for general class title |
| g.classGroup rect | Styles for class diagram rectangle |
| g.classGroup line | Styles for class diagram line |
| .classLabel .box | Styles for class label box |
| .classLabel .label | Styles for class label text |
| composition | Styles for composition arrow head and arrow line |
| aggregation | Styles for aggregation arrow head and arrow line(dashed or solid) |
| dependency | Styles for dependency arrow head and arrow line |
#### Sample stylesheet
```scss
body {
background: white;
}
g.classGroup text {
fill: $nodeBorder;
stroke: none;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
font-size: 10px;
.title {
font-weight: bolder;
}
}
g.classGroup rect {
fill: $nodeBkg;
stroke: $nodeBorder;
}
g.classGroup line {
stroke: $nodeBorder;
stroke-width: 1;
}
.classLabel .box {
stroke: none;
stroke-width: 0;
fill: $nodeBkg;
opacity: 0.5;
}
.classLabel .label {
fill: $nodeBorder;
font-size: 10px;
}
.relation {
stroke: $nodeBorder;
stroke-width: 1;
fill: none;
}
@mixin composition {
fill: $nodeBorder;
stroke: $nodeBorder;
stroke-width: 1;
}
#compositionStart {
@include composition;
}
#compositionEnd {
@include composition;
}
@mixin aggregation {
fill: $nodeBkg;
stroke: $nodeBorder;
stroke-width: 1;
}
#aggregationStart {
@include aggregation;
}
#aggregationEnd {
@include aggregation;
}
#dependencyStart {
@include composition;
}
#dependencyEnd {
@include composition;
}
#extensionStart {
@include composition;
}
#extensionEnd {
@include composition;
}
```
> cssClasses cannot be added using this shorthand method at the same time as a relation statement.
## Configuration
`Coming soon!`
### Members Box
It is possible to hide the empty members box of a class node.
This is done by changing the **hideEmptyMembersBox** value of the class diagram configuration. For more information on how to edit the Mermaid configuration see the [configuration page.](https://mermaid.js.org/config/configuration.html)
```mermaid-example
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
class Duck
```
```mermaid
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
class Duck
```

View File

@@ -316,53 +316,53 @@ This syntax creates a node A as a rectangle. It renders in the same way as `A["A
Below is a comprehensive list of the newly introduced shapes and their corresponding semantic meanings, short names, and aliases:
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
| ------------------------------------- | ---------------------- | -------------- | ------------------------------ | -------------------------------------------------------------- |
| **Process** | Rectangle | `rect` | Standard process shape | `proc`, `process`, `rectangle` |
| **Event** | Rounded Rectangle | `rounded` | Represents an event | `event` |
| **Terminal Point** | Stadium | `stadium` | Terminal point | `terminal`, `pill` |
| **Subprocess** | Framed Rectangle | `fr-rect` | Subprocess | `subprocess`,`subproc`, `framed-rectangle`, `subroutine` |
| **Database** | Cylinder | `cyl` | Database storage | `db`, `database`, `cylinder` |
| **Start** | Circle | `circle` | Starting point | `circ` |
| **Odd** | Odd | `odd` | Odd shape | |
| **Decision** | Diamond | `diam` | Decision-making step | `decision`, `diamond` |
| **Prepare Conditional** | Hexagon | `hex` | Preparation or condition step | `hexagon`, `prepare` |
| **Data Input/Output** | Lean Right | `lean-r` | Represents input or output | `lean-right`, `in-out` |
| **Data Input/Output** | Lean Left | `lean-l` | Represents output or input | `lean-left`, `out-in` |
| **Priority Action** | Trapezoid Base Bottom | `trap-b` | Priority action | `priority`, `trapezoid-bottom` |
| **Manual Operation** | Trapezoid Base Top | `trap-t` | Represents a manual task | `manual`, `trapezoid-top` |
| **Stop** | Double Circle | `dbl-circ` | Represents a stop point | `double-circle` |
| **Text Block** | Text Block | `text` | Text block | - |
| **Card** | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
| **Lined/Shaded Process** | Lined Rectangle | `lin-rect` | Lined process shape | `lined-rectangle`,`lined-process`, `lin-proc`,`shaded-process` |
| **Start** | Small Circle | `sm-circ` | Small starting point | `start`, `small-circle` |
| **Stop** | Framed Circle | `fr-circ` | Stop point | `stop`, `framed-circle` |
| **Fork/Join** | Filled Rectangle | `fork` | Fork or join in process flow | `join` |
| **Collate** | Hourglass | `hourglass` | Represents a collate operation | `hourglass` |
| **Comment** | Curly Brace | `brace` | Adds a comment | `comment`, `brace-l` |
| **Comment Right** | Curly Brace | `brace-r` | Adds a comment | - |
| **Comment with braces on both sides** | Curly Braces | `braces` | Adds a comment | - |
| **Com Link** | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
| **Document** | Document | `doc` | Represents a document | `doc`, `document` |
| **Delay** | Half-Rounded Rectangle | `delay` | Represents a delay | `half-rounded-rectangle` |
| **Direct Access Storage** | Horizontal Cylinder | `h-cyl` | Direct access storage | `das`, `horizontal-cylinder` |
| **Disk Storage** | Lined Cylinder | `lin-cyl` | Disk storage | `disk`, `lined-cylinder` |
| **Display** | Curved Trapezoid | `curv-trap` | Represents a display | `curved-trapezoid`, `display` |
| **Divided Process** | Divided Rectangle | `div-rect` | Divided process shape | `div-proc`, `divided-rectangle`, `divided-process` |
| **Extract** | Triangle | `tri` | Extraction process | `extract`, `triangle` |
| **Internal Storage** | Window Pane | `win-pane` | Internal storage | `internal-storage`, `window-pane` |
| **Junction** | Filled Circle | `f-circ` | Junction point | `junction`, `filled-circle` |
| **Lined Document** | Lined Document | `lin-doc` | Lined document | `lined-document` |
| **Loop Limit** | Trapezoidal Pentagon | `notch-pent` | Loop limit step | `loop-limit`, `notched-pentagon` |
| **Manual File** | Flipped Triangle | `flip-tri` | Manual file operation | `manual-file`, `flipped-triangle` |
| **Manual Input** | Sloped Rectangle | `sl-rect` | Manual input step | `manual-input`, `sloped-rectangle` |
| **Multi-Document** | Stacked Document | `docs` | Multiple documents | `documents`, `st-doc`, `stacked-document` |
| **Multi-Process** | Stacked Rectangle | `st-rect` | Multiple processes | `procs`, `processes`, `stacked-rectangle` |
| **Paper Tape** | Flag | `flag` | Paper tape | `paper-tape` |
| **Stored Data** | Bow Tie Rectangle | `bow-rect` | Stored data | `stored-data`, `bow-tie-rectangle` |
| **Summary** | Crossed Circle | `cross-circ` | Summary | `summary`, `crossed-circle` |
| **Tagged Document** | Tagged Document | `tag-doc` | Tagged document | `tag-doc`, `tagged-document` |
| **Tagged Process** | Tagged Rectangle | `tag-rect` | Tagged process | `tagged-rectangle`,`tag-proc`, `tagged-process` |
| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
| Card | Notched Rectangle | `notch-rect` | Represents a card | `card`, `notched-rectangle` |
| Collate | Hourglass | `hourglass` | Represents a collate operation | `collate`, `hourglass` |
| Com Link | Lightning Bolt | `bolt` | Communication link | `com-link`, `lightning-bolt` |
| Comment | Curly Brace | `brace` | Adds a comment | `brace-l`, `comment` |
| Comment Right | Curly Brace | `brace-r` | Adds a comment | |
| Comment with braces on both sides | Curly Braces | `braces` | Adds a comment | |
| Data Input/Output | Lean Right | `lean-r` | Represents input or output | `in-out`, `lean-right` |
| Data Input/Output | Lean Left | `lean-l` | Represents output or input | `lean-left`, `out-in` |
| Database | Cylinder | `cyl` | Database storage | `cylinder`, `database`, `db` |
| Decision | Diamond | `diam` | Decision-making step | `decision`, `diamond`, `question` |
| Delay | Half-Rounded Rectangle | `delay` | Represents a delay | `half-rounded-rectangle` |
| Direct Access Storage | Horizontal Cylinder | `h-cyl` | Direct access storage | `das`, `horizontal-cylinder` |
| Disk Storage | Lined Cylinder | `lin-cyl` | Disk storage | `disk`, `lined-cylinder` |
| Display | Curved Trapezoid | `curv-trap` | Represents a display | `curved-trapezoid`, `display` |
| Divided Process | Divided Rectangle | `div-rect` | Divided process shape | `div-proc`, `divided-process`, `divided-rectangle` |
| Document | Document | `doc` | Represents a document | `doc`, `document` |
| Event | Rounded Rectangle | `rounded` | Represents an event | `event` |
| Extract | Triangle | `tri` | Extraction process | `extract`, `triangle` |
| Fork/Join | Filled Rectangle | `fork` | Fork or join in process flow | `join` |
| Internal Storage | Window Pane | `win-pane` | Internal storage | `internal-storage`, `window-pane` |
| Junction | Filled Circle | `f-circ` | Junction point | `filled-circle`, `junction` |
| Lined Document | Lined Document | `lin-doc` | Lined document | `lined-document` |
| Lined/Shaded Process | Lined Rectangle | `lin-rect` | Lined process shape | `lin-proc`, `lined-process`, `lined-rectangle`, `shaded-process` |
| Loop Limit | Trapezoidal Pentagon | `notch-pent` | Loop limit step | `loop-limit`, `notched-pentagon` |
| Manual File | Flipped Triangle | `flip-tri` | Manual file operation | `flipped-triangle`, `manual-file` |
| Manual Input | Sloped Rectangle | `sl-rect` | Manual input step | `manual-input`, `sloped-rectangle` |
| Manual Operation | Trapezoid Base Top | `trap-t` | Represents a manual task | `inv-trapezoid`, `manual`, `trapezoid-top` |
| Multi-Document | Stacked Document | `docs` | Multiple documents | `documents`, `st-doc`, `stacked-document` |
| Multi-Process | Stacked Rectangle | `st-rect` | Multiple processes | `processes`, `procs`, `stacked-rectangle` |
| Odd | Odd | `odd` | Odd shape | |
| Paper Tape | Flag | `flag` | Paper tape | `paper-tape` |
| Prepare Conditional | Hexagon | `hex` | Preparation or condition step | `hexagon`, `prepare` |
| Priority Action | Trapezoid Base Bottom | `trap-b` | Priority action | `priority`, `trapezoid`, `trapezoid-bottom` |
| Process | Rectangle | `rect` | Standard process shape | `proc`, `process`, `rectangle` |
| Start | Circle | `circle` | Starting point | `circ` |
| Start | Small Circle | `sm-circ` | Small starting point | `small-circle`, `start` |
| Stop | Double Circle | `dbl-circ` | Represents a stop point | `double-circle` |
| Stop | Framed Circle | `fr-circ` | Stop point | `framed-circle`, `stop` |
| Stored Data | Bow Tie Rectangle | `bow-rect` | Stored data | `bow-tie-rectangle`, `stored-data` |
| Subprocess | Framed Rectangle | `fr-rect` | Subprocess | `framed-rectangle`, `subproc`, `subprocess`, `subroutine` |
| Summary | Crossed Circle | `cross-circ` | Summary | `crossed-circle`, `summary` |
| Tagged Document | Tagged Document | `tag-doc` | Tagged document | `tag-doc`, `tagged-document` |
| Tagged Process | Tagged Rectangle | `tag-rect` | Tagged process | `tag-proc`, `tagged-process`, `tagged-rectangle` |
| Terminal Point | Stadium | `stadium` | Terminal point | `pill`, `terminal` |
| Text Block | Text Block | `text` | Text block | |
### Example Flowchart with New Shapes
@@ -926,6 +926,66 @@ flowchart TD
A@{ shape: tag-rect, label: "Tagged process" }
```
## Special shapes in Mermaid Flowcharts (v11.3.0+)
Mermaid also introduces 2 special shapes to enhance your flowcharts: **icon** and **image**. These shapes allow you to include icons and images directly within your flowcharts, providing more visual context and clarity.
### Icon Shape
You can use the `icon` shape to include an icon in your flowchart. To use icons, you need to register the icon pack first. Follow the instructions provided [here](../config/icons.md). The syntax for defining an icon shape is as follows:
```mermaid-example
flowchart TD
A@{ icon: "fa:user", form: "square", label: "User Icon", pos: "t", h: 60 }
```
```mermaid
flowchart TD
A@{ icon: "fa:user", form: "square", label: "User Icon", pos: "t", h: 60 }
```
### Parameters
- **icon**: The name of the icon from the registered icon pack.
- **form**: Specifies the background shape of the icon. If not defined there will be no background to icon. Options include:
- `square`
- `circle`
- `rounded`
- **label**: The text label associated with the icon. This can be any string. If not defined, no label will be displayed.
- **pos**: The position of the label. If not defined label will default to bottom of icon. Possible values are:
- `t`
- `b`
- **h**: The height of the icon. If not defined this will default to 48 which is minimum.
### Image Shape
You can use the `image` shape to include an image in your flowchart. The syntax for defining an image shape is as follows:
```mermaid-example
flowchart TD
A@{ img: "https://example.com/image.png", label: "Image Label", pos: "t", w: 60, h: 60, constraint: "off" }
```
```mermaid
flowchart TD
A@{ img: "https://example.com/image.png", label: "Image Label", pos: "t", w: 60, h: 60, constraint: "off" }
```
### Parameters
- **img**: The URL of the image to be displayed.
- **label**: The text label associated with the image. This can be any string. If not defined, no label will be displayed.
- **pos**: The position of the label. If not defined, the label will default to the bottom of the image. Possible values are:
- `t`
- `b`
- **w**: The width of the image. If not defined, this will default to the natural width of the image.
- **h**: The height of the image. If not defined, this will default to the natural height of the image.
- **constraint**: Determines if the image should constrain the node size. This setting also ensures the image maintains its original aspect ratio, adjusting the height (`h`) accordingly to the width (`w`). If not defined, this will default to `off` Possible values are:
- `on`
- `off`
These new shapes provide additional flexibility and visual appeal to your flowcharts, making them more informative and engaging.
## Links between nodes
Nodes can be connected with links/edges. It is possible to have different types of links or attach a text string to a link.

161
docs/syntax/kanban.md Normal file
View File

@@ -0,0 +1,161 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/kanban.md](../../packages/mermaid/src/docs/syntax/kanban.md).
# Mermaid Kanban Diagram Documentation
Mermaids Kanban diagram allows you to create visual representations of tasks moving through different stages of a workflow. This guide explains how to use the Kanban diagram syntax, based on the provided example.
## Overview
A Kanban diagram in Mermaid starts with the kanban keyword, followed by the definition of columns (stages) and tasks within those columns.
```mermaid-example
kanban
column1[Column Title]
task1[Task Description]
```
```mermaid
kanban
column1[Column Title]
task1[Task Description]
```
## Defining Columns
Columns represent the different stages in your workflow, such as “Todo,” “In Progress,” “Done,” etc. Each column is defined using a unique identifier and a title enclosed in square brackets.
**Syntax:**
```
columnId[Column Title]
```
- columnId: A unique identifier for the column.
- \[Column Title]: The title displayed on the column header.
Like this `id1[Todo]`
## Adding Tasks to Columns
Tasks are listed under their respective columns with an indentation. Each task also has a unique identifier and a description enclosed in square brackets.
**Syntax:**
```
taskId[Task Description]
```
```
• taskId: A unique identifier for the task.
• [Task Description]: The description of the task.
```
**Example:**
```
docs[Create Documentation]
```
## Adding Metadata to Tasks
You can include additional metadata for each task using the @{ ... } syntax. Metadata can contain key-value pairs like assigned, ticket, priority, etc. This will be rendered added to the rendering of the node.
## Supported Metadata Keys
```
• assigned: Specifies who is responsible for the task.
• ticket: Links the task to a ticket or issue number.
• priority: Indicates the urgency of the task. Allowed values: 'Very High', 'High', 'Low' and 'Very Low'
```
```mermaid-example
kanban
todo[Todo]
id3[Update Database Function]@{ ticket: MC-2037, assigned: 'knsv', priority: 'High' }
```
```mermaid
kanban
todo[Todo]
id3[Update Database Function]@{ ticket: MC-2037, assigned: 'knsv', priority: 'High' }
```
## Configuration Options
You can customize the Kanban diagram using a configuration block at the beginning of your markdown file. This is useful for setting global settings like a base URL for tickets. Currently there is one configuration option for kanban diagrams tacketBaseUrl. This can be set as in the the following example:
```yaml
---
config:
kanban:
ticketBaseUrl: 'https://yourproject.atlassian.net/browse/#TICKET#'
---
```
When the kanban item has an assigned ticket number the ticket number in the diagram will have a link to an external system where the ticket is defined. The `ticketBaseUrl` sets the base URL to the external system and #TICKET# is replaced with the ticket value from task metadata to create a valid link.
## Full Example
Below is the full Kanban diagram based on the provided example:
```mermaid-example
---
config:
kanban:
ticketBaseUrl: 'https://mermaidchart.atlassian.net/browse/#TICKET#'
---
kanban
Todo
[Create Documentation]
docs[Create Blog about the new diagram]
[In progress]
id6[Create renderer so that it works in all cases. We also add som extra text here for testing purposes. And some more just for the extra flare.]
id9[Ready for deploy]
id8[Design grammar]@{ assigned: 'knsv' }
id10[Ready for test]
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
id11[Done]
id5[define getData]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
id12[Can't reproduce]
id3[Weird flickering in Firefox]
```
```mermaid
---
config:
kanban:
ticketBaseUrl: 'https://mermaidchart.atlassian.net/browse/#TICKET#'
---
kanban
Todo
[Create Documentation]
docs[Create Blog about the new diagram]
[In progress]
id6[Create renderer so that it works in all cases. We also add som extra text here for testing purposes. And some more just for the extra flare.]
id9[Ready for deploy]
id8[Design grammar]@{ assigned: 'knsv' }
id10[Ready for test]
id4[Create parsing tests]@{ ticket: MC-2038, assigned: 'K.Sveidqvist', priority: 'High' }
id66[last item]@{ priority: 'Very Low', assigned: 'knsv' }
id11[Done]
id5[define getData]
id2[Title of diagram is more than 100 chars when user duplicates diagram with 100 char]@{ ticket: MC-2036, priority: 'Very High'}
id3[Update DB function]@{ ticket: MC-2037, assigned: knsv, priority: 'High' }
id12[Can't reproduce]
id3[Weird flickering in Firefox]
```
In conclusion, creating a Kanban diagram in Mermaid is a straightforward process that effectively visualizes your workflow. Start by using the kanban keyword to initiate the diagram. Define your columns with unique identifiers and titles to represent different stages of your project. Under each column, list your tasks—also with unique identifiers—and provide detailed descriptions as needed. Remember that proper indentation is crucial; tasks must be indented under their parent columns to maintain the correct structure.
You can enhance your diagram by adding optional metadata to tasks using the @{ ... } syntax, which allows you to include additional context such as assignee, ticket numbers, and priority levels. For further customization, utilize the configuration block at the top of your file to set global options like ticketBaseUrl for linking tickets directly from your diagram.
By adhering to these guidelines—ensuring unique identifiers, proper indentation, and utilizing metadata and configuration options—you can create a comprehensive and customized Kanban board that effectively maps out your projects workflow using Mermaid.

View File

@@ -220,7 +220,7 @@ There are ten types of arrows currently supported:
| `<<->>` | Solid line with bidirectional arrowheads (v11.0.0+) |
| `<<-->>` | Dotted line with bidirectional arrowheads (v11.0.0+) |
| `-x` | Solid line with a cross at the end |
| `--x` | Dotted line with a cross at the end. |
| `--x` | Dotted line with a cross at the end |
| `-)` | Solid line with an open arrow at the end (async) |
| `--)` | Dotted line with a open arrow at the end (async) |

View File

@@ -64,7 +64,7 @@
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.44.4",
"@argos-ci/cypress": "^2.1.0",
"@argos-ci/cypress": "^2.2.2",
"@changesets/changelog-github": "^0.5.0",
"@changesets/cli": "^2.27.7",
"@cspell/eslint-plugin": "^8.8.4",
@@ -91,6 +91,7 @@
"cspell": "^8.6.0",
"cypress": "^13.14.1",
"cypress-image-snapshot": "^4.0.1",
"cypress-split": "^1.24.0",
"esbuild": "^0.21.5",
"eslint": "^9.4.0",
"eslint-config-prettier": "^9.1.0",
@@ -103,7 +104,7 @@
"eslint-plugin-markdown": "^5.0.0",
"eslint-plugin-no-only-tests": "^3.1.0",
"eslint-plugin-tsdoc": "^0.3.0",
"eslint-plugin-unicorn": "^55.0.0",
"eslint-plugin-unicorn": "^56.0.0",
"express": "^4.19.1",
"globals": "^15.4.0",
"globby": "^14.0.1",

View File

@@ -1,5 +1,14 @@
# @mermaid-js/layout-elk
## 0.1.6
### Patch Changes
- [#6081](https://github.com/mermaid-js/mermaid/pull/6081) [`68f41f6`](https://github.com/mermaid-js/mermaid/commit/68f41f685d2afe7d12f63aabf3de0c3461898471) Thanks [@knsv](https://github.com/knsv)! - fix: Elk rendering of Diamond shape intersections
- Updated dependencies [[`01b5079`](https://github.com/mermaid-js/mermaid/commit/01b5079562ec8d34ce9964910f168873843c68f8), [`1388662`](https://github.com/mermaid-js/mermaid/commit/1388662132cc829f9820c2e9970ae04e2dd90588), [`fe3cffb`](https://github.com/mermaid-js/mermaid/commit/fe3cffbb673a25b81989aacb06e5d0eda35326db)]:
- mermaid@11.4.1
## 0.1.5
### Patch Changes

View File

@@ -1,6 +1,6 @@
{
"name": "@mermaid-js/layout-elk",
"version": "0.1.5",
"version": "0.1.6",
"description": "ELK layout engine for mermaid",
"module": "dist/mermaid-layout-elk.core.mjs",
"types": "dist/layouts.d.ts",

View File

@@ -3,6 +3,21 @@ import ELK from 'elkjs/lib/elk.bundled.js';
import type { InternalHelpers, LayoutData, RenderOptions, SVG, SVGGroup } from 'mermaid';
import { type TreeData, findCommonAncestor } from './find-common-ancestor.js';
type Node = LayoutData['nodes'][number];
interface LabelData {
width: number;
height: number;
wrappingWidth?: number;
labelNode?: SVGGElement | null;
}
interface NodeWithVertex extends Omit<Node, 'domId'> {
children?: unknown[];
labelData?: LabelData;
domId?: Node['domId'] | SVGGroup | d3.Selection<SVGAElement, unknown, Element | null, unknown>;
}
export const render = async (
data4Layout: LayoutData,
svg: SVG,
@@ -24,27 +39,37 @@ export const render = async (
const nodeDb: Record<string, any> = {};
const clusterDb: Record<string, any> = {};
const addVertex = async (nodeEl: any, graph: { children: any[] }, nodeArr: any, node: any) => {
const labelData: any = { width: 0, height: 0 };
const addVertex = async (
nodeEl: SVGGroup,
graph: { children: NodeWithVertex[] },
nodeArr: Node[],
node: Node
) => {
const labelData: LabelData = { width: 0, height: 0 };
let boundingBox;
const child = {
...node,
};
graph.children.push(child);
nodeDb[node.id] = child;
const config = getConfig();
// Add the element to the DOM
if (!node.isGroup) {
const child: NodeWithVertex = {
...node,
};
graph.children.push(child);
nodeDb[node.id] = child;
const childNodeEl = await insertNode(nodeEl, node, { config, dir: node.dir });
boundingBox = childNodeEl.node().getBBox();
const boundingBox = childNodeEl.node()!.getBBox();
child.domId = childNodeEl;
child.width = boundingBox.width;
child.height = boundingBox.height;
} else {
// A subgraph
child.children = [];
const child: NodeWithVertex & { children: NodeWithVertex[] } = {
...node,
children: [],
};
graph.children.push(child);
nodeDb[node.id] = child;
await addVertices(nodeEl, nodeArr, child, node.id);
if (node.label) {
@@ -68,28 +93,16 @@ export const render = async (
};
const addVertices = async function (
nodeEl: any,
nodeArr: any[],
graph: {
id: string;
layoutOptions: {
'elk.hierarchyHandling': string;
'elk.algorithm': any;
'nodePlacement.strategy': any;
'elk.layered.mergeEdges': any;
'elk.direction': string;
'spacing.baseValue': number;
};
children: never[];
edges: never[];
},
parentId?: undefined
nodeEl: SVGGroup,
nodeArr: Node[],
graph: { children: NodeWithVertex[] },
parentId?: string
) {
const siblings = nodeArr.filter((node: { parentId: any }) => node.parentId === parentId);
const siblings = nodeArr.filter((node) => node?.parentId === parentId);
log.info('addVertices APA12', siblings, parentId);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
await Promise.all(
siblings.map(async (node: any) => {
siblings.map(async (node) => {
await addVertex(nodeEl, graph, nodeArr, node);
})
);
@@ -136,6 +149,7 @@ export const render = async (
const clusterNode = JSON.parse(JSON.stringify(node));
clusterNode.x = node.offset.posX + node.width / 2;
clusterNode.y = node.offset.posY + node.height / 2;
clusterNode.width = Math.max(clusterNode.width, node.labelData.width);
await insertCluster(subgraphEl, clusterNode);
log.debug('Id (UIO)= ', node.id, node.width, node.shape, node.labels);
@@ -262,6 +276,8 @@ export const render = async (
interpolate: undefined;
style: undefined;
labelType: any;
startLabelRight?: string;
endLabelLeft?: string;
}) {
// Identify Link
const linkIdBase = edge.id; // 'L-' + edge.start + '-' + edge.end;
@@ -315,6 +331,9 @@ export const render = async (
let style = '';
let labelStyle = '';
edgeData.startLabelRight = edge.startLabelRight;
edgeData.endLabelLeft = edge.endLabelLeft;
switch (edge.stroke) {
case 'normal':
style = 'fill:none;';
@@ -465,6 +484,8 @@ export const render = async (
const r3 = a1 * q1.x + b1 * q1.y + c1;
const r4 = a1 * q2.x + b1 * q2.y + c1;
const epsilon = 1e-6;
// Check signs of r3 and r4. If both point 3 and point 4 lie on
// same side of line 1, the line segments do not intersect.
if (r3 !== 0 && r4 !== 0 && sameSign(r3, r4)) {
@@ -483,7 +504,7 @@ export const render = async (
// Check signs of r1 and r2. If both point 1 and point 2 lie
// on same side of second line segment, the line segments do
// not intersect.
if (r1 !== 0 && r2 !== 0 && sameSign(r1, r2)) {
if (Math.abs(r1) < epsilon && Math.abs(r2) < epsilon && sameSign(r1, r2)) {
return /*DON'T_INTERSECT*/;
}
@@ -528,11 +549,11 @@ export const render = async (
{ x: x1 - w / 2, y: y1 },
];
log.debug(
`UIO diamondIntersection calc abc89:
`APA16 diamondIntersection calc abc89:
outsidePoint: ${JSON.stringify(outsidePoint)}
insidePoint : ${JSON.stringify(insidePoint)}
node : x:${bounds.x} y:${bounds.y} w:${bounds.width} h:${bounds.height}`,
polyPoints
node-bounds : x:${bounds.x} y:${bounds.y} w:${bounds.width} h:${bounds.height}`,
JSON.stringify(polyPoints)
);
const intersections = [];
@@ -545,8 +566,8 @@ export const render = async (
minY = Math.min(minY, entry.y);
});
// const left = x1 - w / 2;
// const top = y1 + h / 2;
const left = x1 - w / 2 - minX;
const top = y1 - h / 2 - minY;
for (let i = 0; i < polyPoints.length; i++) {
const p1 = polyPoints[i];
@@ -554,8 +575,8 @@ export const render = async (
const intersect = intersectLine(
bounds,
outsidePoint,
{ x: p1.x, y: p1.y },
{ x: p2.x, y: p2.y }
{ x: left + p1.x, y: top + p1.y },
{ x: left + p2.x, y: top + p2.y }
);
if (intersect) {
@@ -734,7 +755,6 @@ export const render = async (
}
}
});
log.debug('returning points', points);
return points;
};
@@ -949,17 +969,17 @@ export const render = async (
startNode.innerHTML
);
}
if (startNode.shape === 'diamond') {
if (startNode.shape === 'diamond' || startNode.shape === 'diam') {
edge.points.unshift({
x: startNode.x + startNode.width / 2 + offset.x,
y: startNode.y + startNode.height / 2 + offset.y,
});
}
if (endNode.shape === 'diamond') {
if (endNode.shape === 'diamond' || endNode.shape === 'diam') {
const x = endNode.x + endNode.width / 2 + offset.x;
// Add a point at the center of the diamond
if (
Math.abs(edge.points[edge.points.length - 1].y - endNode.y - offset.y) > 0.001 ||
Math.abs(edge.points[edge.points.length - 1].y - endNode.y - offset.y) > 0.01 ||
Math.abs(edge.points[edge.points.length - 1].x - x) > 0.001
) {
edge.points.push({
@@ -978,7 +998,7 @@ export const render = async (
height: startNode.height,
padding: startNode.padding,
},
startNode.shape === 'diamond'
startNode.shape === 'diamond' || startNode.shape === 'diam'
).reverse();
edge.points = cutPathAtIntersect(
@@ -990,7 +1010,7 @@ export const render = async (
height: endNode.height,
padding: endNode.padding,
},
endNode.shape === 'diamond'
endNode.shape === 'diamond' || endNode.shape === 'diam'
);
const paths = insertEdge(

View File

@@ -1,5 +1,43 @@
# mermaid
## 11.4.1
### Patch Changes
- [#6059](https://github.com/mermaid-js/mermaid/pull/6059) [`01b5079`](https://github.com/mermaid-js/mermaid/commit/01b5079562ec8d34ce9964910f168873843c68f8) Thanks [@knsv](https://github.com/knsv)! - fix: Kanban diagrams will not render when adding a number as ticket id or assigned for a task
- [#6038](https://github.com/mermaid-js/mermaid/pull/6038) [`1388662`](https://github.com/mermaid-js/mermaid/commit/1388662132cc829f9820c2e9970ae04e2dd90588) Thanks [@knsv](https://github.com/knsv)! - fix: Intersection calculations for tilted cylinder/DAS when using handdrawn look. Some random seeds could cause the calculations to break.
- [#6079](https://github.com/mermaid-js/mermaid/pull/6079) [`fe3cffb`](https://github.com/mermaid-js/mermaid/commit/fe3cffbb673a25b81989aacb06e5d0eda35326db) Thanks [@aloisklink](https://github.com/aloisklink)! - Bump dompurify to `^3.2.1`. This removes the need for `@types/dompurify`.
## 11.4.0
### Minor Changes
- [#5999](https://github.com/mermaid-js/mermaid/pull/5999) [`742ad7c`](https://github.com/mermaid-js/mermaid/commit/742ad7c130964df1fb5544e909d9556081285f68) Thanks [@knsv](https://github.com/knsv)! - Adding Kanban board, a new diagram type
- [#5880](https://github.com/mermaid-js/mermaid/pull/5880) [`bdf145f`](https://github.com/mermaid-js/mermaid/commit/bdf145ffe362462176d9c1e68d5f3ff5c9d962b0) Thanks [@yari-dewalt](https://github.com/yari-dewalt)! - Class diagram changes:
- Updates the class diagram to the new unified way of rendering.
- Includes a new "classBox" shape to be used in diagrams
- Other updates such as:
- the option to hide the empty members box in class diagrams,
- support for handDrawn look,
- the introduction of the classDef statement into class diagrams,
- support for styling the default class,
- support lollipop interfaces.
- Includes fixes / additions for #5562 #3139 and #4037
### Patch Changes
- [#5937](https://github.com/mermaid-js/mermaid/pull/5937) [`17b7831`](https://github.com/mermaid-js/mermaid/commit/17b783135f9b2b7748b620dbf81d0f56ab4755f1) Thanks [@saurabhg772244](https://github.com/saurabhg772244)! - fix: Jagged edge fix for icon shape
- [#5933](https://github.com/mermaid-js/mermaid/pull/5933) [`72d60d2`](https://github.com/mermaid-js/mermaid/commit/72d60d2633584eb59bccdb6cf30b9522db645db2) Thanks [@remcohaszing](https://github.com/remcohaszing)! - Add missing TypeScript dependencies
- [#5937](https://github.com/mermaid-js/mermaid/pull/5937) [`17b7831`](https://github.com/mermaid-js/mermaid/commit/17b783135f9b2b7748b620dbf81d0f56ab4755f1) Thanks [@saurabhg772244](https://github.com/saurabhg772244)! - fix: Icon color fix for colored icons.
- [#6002](https://github.com/mermaid-js/mermaid/pull/6002) [`5fabd41`](https://github.com/mermaid-js/mermaid/commit/5fabd414fbee01e43bf6c900907ffc1511ca7440) Thanks [@aloisklink](https://github.com/aloisklink)! - fix: error `mermaid.parse` on an invalid shape, so that it matches the errors thrown by `mermaid.render`
## 11.3.0
### Minor Changes

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "11.3.0",
"version": "11.4.1",
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
@@ -35,8 +35,8 @@
"clean": "rimraf dist",
"dev": "pnpm -w dev",
"docs:code": "typedoc src/defaultConfig.ts src/config.ts src/mermaid.ts && prettier --write ./src/docs/config/setup",
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && tsx scripts/docs.cli.mts",
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && tsx scripts/docs.cli.mts --verify",
"docs:build": "rimraf ../../docs && pnpm docs:code && pnpm docs:spellcheck && tsx scripts/docs.cli.mts",
"docs:verify": "pnpm docs:code && pnpm docs:spellcheck && tsx scripts/docs.cli.mts --verify",
"docs:pre:vitepress": "pnpm --filter ./src/docs prefetch && rimraf src/vitepress && pnpm docs:code && tsx scripts/docs.cli.mts --vitepress && pnpm --filter ./src/vitepress install --no-frozen-lockfile --ignore-scripts",
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev\" \"tsx scripts/docs.cli.mts --watch --vitepress\"",
@@ -70,14 +70,15 @@
"@braintree/sanitize-url": "^7.0.1",
"@iconify/utils": "^2.1.32",
"@mermaid-js/parser": "workspace:^",
"@types/d3": "^7.4.3",
"cytoscape": "^3.29.2",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.2.0",
"d3": "^7.9.0",
"d3-sankey": "^0.12.3",
"dagre-d3-es": "7.0.10",
"dagre-d3-es": "7.0.11",
"dayjs": "^1.11.10",
"dompurify": "^3.0.11 <3.1.7",
"dompurify": "^3.2.1",
"katex": "^0.16.9",
"khroma": "^2.1.0",
"lodash-es": "^4.17.21",
@@ -92,13 +93,11 @@
"@iconify/types": "^2.0.0",
"@types/cytoscape": "^3.21.4",
"@types/cytoscape-fcose": "^2.2.4",
"@types/d3": "^7.4.3",
"@types/d3-sankey": "^0.12.4",
"@types/d3-scale": "^4.0.8",
"@types/d3-scale-chromatic": "^3.0.3",
"@types/d3-selection": "^3.0.10",
"@types/d3-shape": "^3.1.6",
"@types/dompurify": "^3.0.5",
"@types/jsdom": "^21.1.6",
"@types/katex": "^0.16.7",
"@types/lodash-es": "^4.17.12",

View File

@@ -41,7 +41,8 @@ import { exec } from 'child_process';
import { globby } from 'globby';
import { JSDOM } from 'jsdom';
import { dump, load, JSON_SCHEMA } from 'js-yaml';
import type { Code, ListItem, Root, Text, YAML } from 'mdast';
import type { Code, ListItem, PhrasingContent, Root, Text, YAML } from 'mdast';
import { register } from 'node:module';
import { posix, dirname, relative, join } from 'path';
import prettier from 'prettier';
import { remark } from 'remark';
@@ -53,6 +54,10 @@ import mm from 'micromatch';
import flatmap from 'unist-util-flatmap';
import { visit } from 'unist-util-visit';
// short-circuit `.schema.yaml` imports, so that we can safely import `shapes.js`
register('./loadHook.mjs', import.meta.url);
const { shapesDefs } = await import('../src/rendering-util/rendering-elements/shapes.js');
export const MERMAID_RELEASE_VERSION = JSON.parse(readFileSync('../mermaid/package.json', 'utf8'))
.version as string;
const MERMAID_MAJOR_VERSION = MERMAID_RELEASE_VERSION.split('.')[0];
@@ -103,6 +108,60 @@ const generateHeader = (file: string): string => {
> ## Please edit the corresponding file in [${filePathFromRoot}](${sourcePathRelativeToGenerated}).`;
};
/**
* Builds a markdown list of shapes supported in flowcharts.
*/
export function buildShapeDoc() {
const data = shapesDefs
.sort((a, b) => a.semanticName.localeCompare(b.semanticName))
.map((shape): PhrasingContent[][] => {
const { name, semanticName, description, shortName, aliases = [] } = shape;
return [
[{ type: 'text', value: semanticName }],
[{ type: 'text', value: name }],
[{ type: 'inlineCode', value: shortName }],
[{ type: 'text', value: description }],
aliases.sort().flatMap((alias, index) => [
...(index !== 0 ? ([{ type: 'text', value: ', ' }] as const) : []),
{
type: 'inlineCode',
value: alias,
},
]),
];
});
// don't prettify this table, since we'd do it later
return remark()
.use(remarkGfm)
.stringify({
type: 'root',
children: [
{
type: 'table',
children: [
['Semantic Name', 'Shape Name', 'Short Name', 'Description', 'Alias Supported'].map(
(s): PhrasingContent[] => [
{
type: 'strong',
children: [{ type: 'text', value: s }],
},
]
),
...data,
].map((row) => ({
type: 'tableRow',
children: row.map((cell) => ({
type: 'tableCell',
children: cell,
})),
})),
},
],
})
.toString();
}
/**
* Given a source file name and path, return the documentation destination full path and file name
* Create the destination path if it does not already exist.
@@ -192,10 +251,22 @@ export const transformToBlockQuote = (
const injectPlaceholders = (text: string): string =>
text.replace(/<MERMAID_VERSION>/g, MERMAID_MAJOR_VERSION).replace(/<CDN_URL>/g, CDN_URL);
const virtualGenerators: Record<string, () => string> = {
shapesTable: buildShapeDoc,
};
const transformIncludeStatements = (file: string, text: string): string => {
// resolve includes - src https://github.com/vuejs/vitepress/blob/428eec3750d6b5648a77ac52d88128df0554d4d1/src/node/markdownToVue.ts#L65-L76
return text.replace(includesRE, (m, m1) => {
return text.replace(includesRE, (m, m1: string) => {
try {
if (m1.startsWith('virtual:')) {
const key = m1.replace('virtual:', '');
const generator = virtualGenerators[key];
if (!generator) {
throw new Error(`Unknown virtual generator: ${key} in "${file}"`);
}
return generator();
}
const includePath = join(dirname(file), m1).replaceAll('\\', '/');
const content = readSyncedUTF8file(includePath);
includedFiles.add(changeToFinalDocDir(includePath));

View File

@@ -1,4 +1,4 @@
import { transformMarkdownAst, transformToBlockQuote } from './docs.mjs';
import { buildShapeDoc, transformMarkdownAst, transformToBlockQuote } from './docs.mjs';
import { remark } from 'remark'; // import it this way so we can mock it
import remarkFrontmatter from 'remark-frontmatter';
@@ -165,4 +165,59 @@ This Markdown should be kept.
});
});
});
describe('buildShapeDoc', () => {
it('should build shapesTable based on the shapeDefs', () => {
expect(buildShapeDoc()).toMatchInlineSnapshot(`
"| **Semantic Name** | **Shape Name** | **Short Name** | **Description** | **Alias Supported** |
| --------------------------------- | ---------------------- | -------------- | ------------------------------ | ---------------------------------------------------------------- |
| Card | Notched Rectangle | \`notch-rect\` | Represents a card | \`card\`, \`notched-rectangle\` |
| Collate | Hourglass | \`hourglass\` | Represents a collate operation | \`collate\`, \`hourglass\` |
| Com Link | Lightning Bolt | \`bolt\` | Communication link | \`com-link\`, \`lightning-bolt\` |
| Comment | Curly Brace | \`brace\` | Adds a comment | \`brace-l\`, \`comment\` |
| Comment Right | Curly Brace | \`brace-r\` | Adds a comment | |
| Comment with braces on both sides | Curly Braces | \`braces\` | Adds a comment | |
| Data Input/Output | Lean Right | \`lean-r\` | Represents input or output | \`in-out\`, \`lean-right\` |
| Data Input/Output | Lean Left | \`lean-l\` | Represents output or input | \`lean-left\`, \`out-in\` |
| Database | Cylinder | \`cyl\` | Database storage | \`cylinder\`, \`database\`, \`db\` |
| Decision | Diamond | \`diam\` | Decision-making step | \`decision\`, \`diamond\`, \`question\` |
| Delay | Half-Rounded Rectangle | \`delay\` | Represents a delay | \`half-rounded-rectangle\` |
| Direct Access Storage | Horizontal Cylinder | \`h-cyl\` | Direct access storage | \`das\`, \`horizontal-cylinder\` |
| Disk Storage | Lined Cylinder | \`lin-cyl\` | Disk storage | \`disk\`, \`lined-cylinder\` |
| Display | Curved Trapezoid | \`curv-trap\` | Represents a display | \`curved-trapezoid\`, \`display\` |
| Divided Process | Divided Rectangle | \`div-rect\` | Divided process shape | \`div-proc\`, \`divided-process\`, \`divided-rectangle\` |
| Document | Document | \`doc\` | Represents a document | \`doc\`, \`document\` |
| Event | Rounded Rectangle | \`rounded\` | Represents an event | \`event\` |
| Extract | Triangle | \`tri\` | Extraction process | \`extract\`, \`triangle\` |
| Fork/Join | Filled Rectangle | \`fork\` | Fork or join in process flow | \`join\` |
| Internal Storage | Window Pane | \`win-pane\` | Internal storage | \`internal-storage\`, \`window-pane\` |
| Junction | Filled Circle | \`f-circ\` | Junction point | \`filled-circle\`, \`junction\` |
| Lined Document | Lined Document | \`lin-doc\` | Lined document | \`lined-document\` |
| Lined/Shaded Process | Lined Rectangle | \`lin-rect\` | Lined process shape | \`lin-proc\`, \`lined-process\`, \`lined-rectangle\`, \`shaded-process\` |
| Loop Limit | Trapezoidal Pentagon | \`notch-pent\` | Loop limit step | \`loop-limit\`, \`notched-pentagon\` |
| Manual File | Flipped Triangle | \`flip-tri\` | Manual file operation | \`flipped-triangle\`, \`manual-file\` |
| Manual Input | Sloped Rectangle | \`sl-rect\` | Manual input step | \`manual-input\`, \`sloped-rectangle\` |
| Manual Operation | Trapezoid Base Top | \`trap-t\` | Represents a manual task | \`inv-trapezoid\`, \`manual\`, \`trapezoid-top\` |
| Multi-Document | Stacked Document | \`docs\` | Multiple documents | \`documents\`, \`st-doc\`, \`stacked-document\` |
| Multi-Process | Stacked Rectangle | \`st-rect\` | Multiple processes | \`processes\`, \`procs\`, \`stacked-rectangle\` |
| Odd | Odd | \`odd\` | Odd shape | |
| Paper Tape | Flag | \`flag\` | Paper tape | \`paper-tape\` |
| Prepare Conditional | Hexagon | \`hex\` | Preparation or condition step | \`hexagon\`, \`prepare\` |
| Priority Action | Trapezoid Base Bottom | \`trap-b\` | Priority action | \`priority\`, \`trapezoid\`, \`trapezoid-bottom\` |
| Process | Rectangle | \`rect\` | Standard process shape | \`proc\`, \`process\`, \`rectangle\` |
| Start | Circle | \`circle\` | Starting point | \`circ\` |
| Start | Small Circle | \`sm-circ\` | Small starting point | \`small-circle\`, \`start\` |
| Stop | Double Circle | \`dbl-circ\` | Represents a stop point | \`double-circle\` |
| Stop | Framed Circle | \`fr-circ\` | Stop point | \`framed-circle\`, \`stop\` |
| Stored Data | Bow Tie Rectangle | \`bow-rect\` | Stored data | \`bow-tie-rectangle\`, \`stored-data\` |
| Subprocess | Framed Rectangle | \`fr-rect\` | Subprocess | \`framed-rectangle\`, \`subproc\`, \`subprocess\`, \`subroutine\` |
| Summary | Crossed Circle | \`cross-circ\` | Summary | \`crossed-circle\`, \`summary\` |
| Tagged Document | Tagged Document | \`tag-doc\` | Tagged document | \`tag-doc\`, \`tagged-document\` |
| Tagged Process | Tagged Rectangle | \`tag-rect\` | Tagged process | \`tag-proc\`, \`tagged-process\`, \`tagged-rectangle\` |
| Terminal Point | Stadium | \`stadium\` | Terminal point | \`pill\`, \`terminal\` |
| Text Block | Text Block | \`text\` | Text block | |
"
`);
});
});
});

View File

@@ -0,0 +1,22 @@
import { fileURLToPath } from 'node:url';
/** @import import { LoadHook } from "node:module"; */
/**
* @type {LoadHook}
*
* Load hook that short circuits the loading of `.schema.yaml` files with `export default {}`.
* These would normally be loaded using ESBuild, but that doesn't work for these local scripts.
*
* @see https://nodejs.org/api/module.html#loadurl-context-nextload
*/
export const load = async (url, context, nextLoad) => {
const filePath = url.startsWith('file://') ? fileURLToPath(url) : url;
if (filePath.endsWith('.schema.yaml')) {
return {
format: 'module',
shortCircuit: true,
source: `export default {}`,
};
} else {
return await nextLoad(url, context);
}
};

View File

@@ -193,6 +193,7 @@ export interface MermaidConfig {
requirement?: RequirementDiagramConfig;
architecture?: ArchitectureDiagramConfig;
mindmap?: MindmapDiagramConfig;
kanban?: KanbanDiagramConfig;
gitGraph?: GitGraphDiagramConfig;
c4?: C4DiagramConfig;
sankey?: SankeyDiagramConfig;
@@ -716,6 +717,7 @@ export interface ClassDiagramConfig extends BaseDiagramConfig {
*/
diagramPadding?: number;
htmlLabels?: boolean;
hideEmptyMembersBox?: boolean;
}
/**
* The object containing configurations specific for entity relationship diagrams
@@ -1023,6 +1025,17 @@ export interface MindmapDiagramConfig extends BaseDiagramConfig {
padding?: number;
maxNodeWidth?: number;
}
/**
* The object containing configurations specific for kanban diagrams
*
* This interface was referenced by `MermaidConfig`'s JSON-Schema
* via the `definition` "KanbanDiagramConfig".
*/
export interface KanbanDiagramConfig extends BaseDiagramConfig {
padding?: number;
sectionWidth?: number;
ticketBaseUrl?: string;
}
/**
* This interface was referenced by `MermaidConfig`'s JSON-Schema
* via the `definition` "GitGraphDiagramConfig".

View File

@@ -53,6 +53,9 @@ const config: RequiredDeep<MermaidConfig> = {
};
},
},
class: {
hideEmptyMembersBox: false,
},
gantt: {
...defaultConfigJson.gantt,
tickInterval: undefined,

View File

@@ -19,6 +19,7 @@ import errorDiagram from '../diagrams/error/errorDiagram.js';
import flowchartElk from '../diagrams/flowchart/elk/detector.js';
import timeline from '../diagrams/timeline/detector.js';
import mindmap from '../diagrams/mindmap/detector.js';
import kanban from '../diagrams/kanban/detector.js';
import sankey from '../diagrams/sankey/sankeyDetector.js';
import { packet } from '../diagrams/packet/detector.js';
import block from '../diagrams/block/blockDetector.js';
@@ -70,6 +71,7 @@ export const addDiagrams = () => {
// Ordering of detectors is important. The first one to return true will be used.
registerLazyLoadedDiagrams(
c4,
kanban,
classDiagramV2,
classDiagram,
er,

View File

@@ -1,9 +1,8 @@
import type { Selection } from 'd3';
import { select } from 'd3';
import { select, type Selection } from 'd3';
import { log } from '../../logger.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
import common from '../common/common.js';
import utils from '../../utils.js';
import utils, { getEdgeId } from '../../utils.js';
import {
setAccTitle,
getAccTitle,
@@ -21,13 +20,18 @@ import type {
ClassMap,
NamespaceMap,
NamespaceNode,
StyleClass,
Interface,
} from './classTypes.js';
import type { Node, Edge } from '../../rendering-util/types.js';
const MERMAID_DOM_ID_PREFIX = 'classId-';
let relations: ClassRelation[] = [];
let classes = new Map<string, ClassNode>();
const styleClasses = new Map<string, StyleClass>();
let notes: ClassNote[] = [];
let interfaces: Interface[] = [];
let classCounter = 0;
let namespaces = new Map<string, NamespaceNode>();
let namespaceCounter = 0;
@@ -58,6 +62,8 @@ export const setClassLabel = function (_id: string, label: string) {
const { className } = splitClassNameAndType(id);
classes.get(className)!.label = label;
classes.get(className)!.text =
`${label}${classes.get(className)!.type ? `<${classes.get(className)!.type}>` : ''}`;
};
/**
@@ -80,7 +86,9 @@ export const addClass = function (_id: string) {
id: name,
type: type,
label: name,
cssClasses: [],
text: `${name}${type ? `&lt;${type}&gt;` : ''}`,
shape: 'classBox',
cssClasses: 'default',
methods: [],
members: [],
annotations: [],
@@ -91,6 +99,16 @@ export const addClass = function (_id: string) {
classCounter++;
};
const addInterface = function (label: string, classId: string) {
const classInterface: Interface = {
id: `interface${interfaces.length}`,
label,
classId,
};
interfaces.push(classInterface);
};
/**
* Function to lookup domId from id in the graph definition.
*
@@ -109,6 +127,7 @@ export const clear = function () {
relations = [];
classes = new Map();
notes = [];
interfaces = [];
functions = [];
functions.push(setupToolTips);
namespaces = new Map();
@@ -133,19 +152,50 @@ export const getNotes = function () {
return notes;
};
export const addRelation = function (relation: ClassRelation) {
log.debug('Adding relation: ' + JSON.stringify(relation));
addClass(relation.id1);
addClass(relation.id2);
export const addRelation = function (classRelation: ClassRelation) {
log.debug('Adding relation: ' + JSON.stringify(classRelation));
// Due to relationType cannot just check if it is equal to 'none' or it complains, can fix this later
const invalidTypes = [
relationType.LOLLIPOP,
relationType.AGGREGATION,
relationType.COMPOSITION,
relationType.DEPENDENCY,
relationType.EXTENSION,
];
relation.id1 = splitClassNameAndType(relation.id1).className;
relation.id2 = splitClassNameAndType(relation.id2).className;
if (
classRelation.relation.type1 === relationType.LOLLIPOP &&
!invalidTypes.includes(classRelation.relation.type2)
) {
addClass(classRelation.id2);
addInterface(classRelation.id1, classRelation.id2);
classRelation.id1 = `interface${interfaces.length - 1}`;
} else if (
classRelation.relation.type2 === relationType.LOLLIPOP &&
!invalidTypes.includes(classRelation.relation.type1)
) {
addClass(classRelation.id1);
addInterface(classRelation.id2, classRelation.id1);
classRelation.id2 = `interface${interfaces.length - 1}`;
} else {
addClass(classRelation.id1);
addClass(classRelation.id2);
}
relation.relationTitle1 = common.sanitizeText(relation.relationTitle1.trim(), getConfig());
classRelation.id1 = splitClassNameAndType(classRelation.id1).className;
classRelation.id2 = splitClassNameAndType(classRelation.id2).className;
relation.relationTitle2 = common.sanitizeText(relation.relationTitle2.trim(), getConfig());
classRelation.relationTitle1 = common.sanitizeText(
classRelation.relationTitle1.trim(),
getConfig()
);
relations.push(relation);
classRelation.relationTitle2 = common.sanitizeText(
classRelation.relationTitle2.trim(),
getConfig()
);
relations.push(classRelation);
};
/**
@@ -229,11 +279,37 @@ export const setCssClass = function (ids: string, className: string) {
}
const classNode = classes.get(id);
if (classNode) {
classNode.cssClasses.push(className);
classNode.cssClasses += ' ' + className;
}
});
};
export const defineClass = function (ids: string[], style: string[]) {
for (const id of ids) {
let styleClass = styleClasses.get(id);
if (styleClass === undefined) {
styleClass = { id, styles: [], textStyles: [] };
styleClasses.set(id, styleClass);
}
if (style) {
style.forEach(function (s) {
if (/color/.exec(s)) {
const newStyle = s.replace('fill', 'bgFill'); // .replace('color', 'fill');
styleClass.textStyles.push(newStyle);
}
styleClass.styles.push(s);
});
}
classes.forEach((value) => {
if (value.cssClasses.includes(id)) {
value.styles.push(...style.flatMap((s) => s.split(',')));
}
});
}
};
/**
* Called by parser when a tooltip is found, e.g. a clickable element.
*
@@ -472,6 +548,152 @@ export const setCssStyle = function (id: string, styles: string[]) {
}
};
/**
* Gets the arrow marker for a type index
*
* @param type - The type to look for
* @returns The arrow marker
*/
function getArrowMarker(type: number) {
let marker;
switch (type) {
case 0:
marker = 'aggregation';
break;
case 1:
marker = 'extension';
break;
case 2:
marker = 'composition';
break;
case 3:
marker = 'dependency';
break;
case 4:
marker = 'lollipop';
break;
default:
marker = 'none';
}
return marker;
}
export const getData = () => {
const nodes: Node[] = [];
const edges: Edge[] = [];
const config = getConfig();
for (const namespaceKey of namespaces.keys()) {
const namespace = namespaces.get(namespaceKey);
if (namespace) {
const node: Node = {
id: namespace.id,
label: namespace.id,
isGroup: true,
padding: config.class!.padding ?? 16,
// parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
shape: 'rect',
cssStyles: ['fill: none', 'stroke: black'],
look: config.look,
};
nodes.push(node);
}
}
for (const classKey of classes.keys()) {
const classNode = classes.get(classKey);
if (classNode) {
const node = classNode as unknown as Node;
node.parentId = classNode.parent;
node.look = config.look;
nodes.push(node);
}
}
let cnt = 0;
for (const note of notes) {
cnt++;
const noteNode: Node = {
id: note.id,
label: note.text,
isGroup: false,
shape: 'note',
padding: config.class!.padding ?? 6,
cssStyles: [
'text-align: left',
'white-space: nowrap',
`fill: ${config.themeVariables.noteBkgColor}`,
`stroke: ${config.themeVariables.noteBorderColor}`,
],
look: config.look,
};
nodes.push(noteNode);
const noteClassId = classes.get(note.class)?.id ?? '';
if (noteClassId) {
const edge: Edge = {
id: `edgeNote${cnt}`,
start: note.id,
end: noteClassId,
type: 'normal',
thickness: 'normal',
classes: 'relation',
arrowTypeStart: 'none',
arrowTypeEnd: 'none',
arrowheadStyle: '',
labelStyle: [''],
style: ['fill: none'],
pattern: 'dotted',
look: config.look,
};
edges.push(edge);
}
}
for (const _interface of interfaces) {
const interfaceNode: Node = {
id: _interface.id,
label: _interface.label,
isGroup: false,
shape: 'rect',
cssStyles: ['opacity: 0;'],
look: config.look,
};
nodes.push(interfaceNode);
}
cnt = 0;
for (const classRelation of relations) {
cnt++;
const edge: Edge = {
id: getEdgeId(classRelation.id1, classRelation.id2, {
prefix: 'id',
counter: cnt,
}),
start: classRelation.id1,
end: classRelation.id2,
type: 'normal',
label: classRelation.title,
labelpos: 'c',
thickness: 'normal',
classes: 'relation',
arrowTypeStart: getArrowMarker(classRelation.relation.type1),
arrowTypeEnd: getArrowMarker(classRelation.relation.type2),
startLabelRight: classRelation.relationTitle1 === 'none' ? '' : classRelation.relationTitle1,
endLabelLeft: classRelation.relationTitle2 === 'none' ? '' : classRelation.relationTitle2,
arrowheadStyle: '',
labelStyle: ['display: inline-block'],
style: classRelation.style || '',
pattern: classRelation.relation.lineType == 1 ? 'dashed' : 'solid',
look: config.look,
};
edges.push(edge);
}
return { nodes, edges, other: {}, config, direction: getDirection() };
};
export default {
setAccTitle,
getAccTitle,
@@ -497,6 +719,7 @@ export default {
relationType,
setClickEvent,
setCssClass,
defineClass,
setLink,
getTooltip,
setTooltip,
@@ -509,4 +732,5 @@ export default {
getNamespace,
getNamespaces,
setCssStyle,
getData,
};

View File

@@ -13,7 +13,7 @@ describe('class diagram, ', function () {
parser.parse(str);
expect(parser.yy.getClass('Class01').cssClasses[0]).toBe('exClass');
expect(parser.yy.getClass('Class01').cssClasses).toBe('default exClass');
});
it('should be possible to apply a css class to a class directly with struct', function () {
@@ -28,7 +28,7 @@ describe('class diagram, ', function () {
parser.parse(str);
const testClass = parser.yy.getClass('Class1');
expect(testClass.cssClasses[0]).toBe('exClass');
expect(testClass.cssClasses).toBe('default exClass');
});
it('should be possible to apply a css class to a class with relations', function () {
@@ -36,7 +36,7 @@ describe('class diagram, ', function () {
parser.parse(str);
expect(parser.yy.getClass('Class01').cssClasses[0]).toBe('exClass');
expect(parser.yy.getClass('Class01').cssClasses).toBe('default exClass');
});
it('should be possible to apply a cssClass to a class', function () {
@@ -44,7 +44,7 @@ describe('class diagram, ', function () {
parser.parse(str);
expect(parser.yy.getClass('Class01').cssClasses[0]).toBe('exClass');
expect(parser.yy.getClass('Class01').cssClasses).toBe('default exClass');
});
it('should be possible to apply a cssClass to a comma separated list of classes', function () {
@@ -53,8 +53,8 @@ describe('class diagram, ', function () {
parser.parse(str);
expect(parser.yy.getClass('Class01').cssClasses[0]).toBe('exClass');
expect(parser.yy.getClass('Class02').cssClasses[0]).toBe('exClass');
expect(parser.yy.getClass('Class01').cssClasses).toBe('default exClass');
expect(parser.yy.getClass('Class02').cssClasses).toBe('default exClass');
});
it('should be possible to apply a style to an individual node', function () {
const str =
@@ -69,5 +69,47 @@ describe('class diagram, ', function () {
expect(styleElements[1]).toBe('stroke:#333');
expect(styleElements[2]).toBe('stroke-width:4px');
});
it('should be possible to define and assign a class inside the diagram', function () {
const str =
'classDiagram\n' + 'class Class01\n cssClass "Class01" pink\n classDef pink fill:#f9f';
parser.parse(str);
expect(parser.yy.getClass('Class01').cssClasses).toBe('default pink');
});
it('should be possible to define and assign a class using shorthand inside the diagram', function () {
const str = 'classDiagram\n' + 'class Class01:::pink\n classDef pink fill:#f9f';
parser.parse(str);
expect(parser.yy.getClass('Class01').cssClasses).toBe('default pink');
});
it('should properly assign styles from a class defined inside the diagram', function () {
const str =
'classDiagram\n' +
'class Class01:::pink\n classDef pink fill:#f9f,stroke:#333,stroke-width:6px';
parser.parse(str);
expect(parser.yy.getClass('Class01').styles).toStrictEqual([
'fill:#f9f',
'stroke:#333',
'stroke-width:6px',
]);
});
it('should properly assign multiple classes and styles from classes defined inside the diagram', function () {
const str =
'classDiagram\n' +
'class Class01:::pink\n cssClass "Class01" bold\n classDef pink fill:#f9f\n classDef bold stroke:#333,stroke-width:6px';
parser.parse(str);
expect(parser.yy.getClass('Class01').styles).toStrictEqual([
'fill:#f9f',
'stroke:#333',
'stroke-width:6px',
]);
expect(parser.yy.getClass('Class01').cssClasses).toBe('default pink bold');
});
});
});

View File

@@ -3,7 +3,7 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
import parser from './parser/classDiagram.jison';
import db from './classDb.js';
import styles from './styles.js';
import renderer from './classRenderer-v2.js';
import renderer from './classRenderer-v3-unified.js';
export const diagram: DiagramDefinition = {
parser,

View File

@@ -246,7 +246,7 @@ describe('given a basic class diagram, ', function () {
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses[0]).toBe('styleClass');
expect(c1.cssClasses).toBe('default styleClass');
});
it('should parse a class with text label and css class', () => {
@@ -261,7 +261,7 @@ describe('given a basic class diagram, ', function () {
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
expect(c1.cssClasses[0]).toBe('styleClass');
expect(c1.cssClasses).toBe('default styleClass');
});
it('should parse two classes with text labels and css classes', () => {
@@ -276,11 +276,11 @@ describe('given a basic class diagram, ', function () {
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses[0]).toBe('styleClass');
expect(c1.cssClasses).toBe('default styleClass');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('Long long long long long long long long long long label');
expect(c2.cssClasses[0]).toBe('styleClass');
expect(c2.cssClasses).toBe('default styleClass');
});
it('should parse two classes with text labels and css class shorthands', () => {
@@ -293,11 +293,11 @@ describe('given a basic class diagram, ', function () {
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses[0]).toBe('styleClass1');
expect(c1.cssClasses).toBe('default styleClass1');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('Class 2 !@#$%^&*() label');
expect(c2.cssClasses[0]).toBe('styleClass2');
expect(c2.cssClasses).toBe('default styleClass2');
});
it('should parse multiple classes with same text labels', () => {
@@ -494,10 +494,32 @@ class C13["With Città foreign language"]
],
methods: [],
annotations: [],
cssClasses: [],
cssClasses: 'default',
});
expect(classDb.getClasses().size).toBe(3);
expect(classDb.getClasses().get('Student')).toMatchInlineSnapshot(`
{
"annotations": [],
"cssClasses": "default",
"domId": "classId-Student-141",
"id": "Student",
"label": "Student",
"members": [
ClassMember {
"classifier": "",
"id": "idCard : IdCard",
"memberType": "attribute",
"text": "\\-idCard : IdCard",
"visibility": "-",
},
],
"methods": [],
"shape": "classBox",
"styles": [],
"text": "Student",
"type": "",
}
`);
expect(classDb.getRelations().length).toBe(2);
expect(classDb.getRelations()).toMatchInlineSnapshot(`
[
@@ -738,7 +760,7 @@ foo()
const actual = parser.yy.getClass('Class1');
expect(actual.link).toBe('google.com');
expect(actual.cssClasses[0]).toBe('clickable');
expect(actual.cssClasses).toBe('default clickable');
});
it('should handle href link with tooltip', function () {
@@ -754,7 +776,7 @@ foo()
const actual = parser.yy.getClass('Class1');
expect(actual.link).toBe('google.com');
expect(actual.tooltip).toBe('A Tooltip');
expect(actual.cssClasses[0]).toBe('clickable');
expect(actual.cssClasses).toBe('default clickable');
});
it('should handle href link with tooltip and target', function () {
@@ -773,7 +795,7 @@ foo()
const actual = parser.yy.getClass('Class1');
expect(actual.link).toBe('google.com');
expect(actual.tooltip).toBe('A tooltip');
expect(actual.cssClasses[0]).toBe('clickable');
expect(actual.cssClasses).toBe('default clickable');
});
it('should handle function call', function () {
@@ -1468,8 +1490,7 @@ describe('given a class diagram with relationships, ', function () {
const testClass = parser.yy.getClass('Class1');
expect(testClass.link).toBe('google.com');
expect(testClass.cssClasses.length).toBe(1);
expect(testClass.cssClasses[0]).toBe('clickable');
expect(testClass.cssClasses).toBe('default clickable');
});
it('should associate click and href link and css appropriately', function () {
@@ -1482,8 +1503,7 @@ describe('given a class diagram with relationships, ', function () {
const testClass = parser.yy.getClass('Class1');
expect(testClass.link).toBe('google.com');
expect(testClass.cssClasses.length).toBe(1);
expect(testClass.cssClasses[0]).toBe('clickable');
expect(testClass.cssClasses).toBe('default clickable');
});
it('should associate link with tooltip', function () {
@@ -1497,8 +1517,7 @@ describe('given a class diagram with relationships, ', function () {
const testClass = parser.yy.getClass('Class1');
expect(testClass.link).toBe('google.com');
expect(testClass.tooltip).toBe('A tooltip');
expect(testClass.cssClasses.length).toBe(1);
expect(testClass.cssClasses[0]).toBe('clickable');
expect(testClass.cssClasses).toBe('default clickable');
});
it('should associate click and href link with tooltip', function () {
@@ -1512,8 +1531,7 @@ describe('given a class diagram with relationships, ', function () {
const testClass = parser.yy.getClass('Class1');
expect(testClass.link).toBe('google.com');
expect(testClass.tooltip).toBe('A tooltip');
expect(testClass.cssClasses.length).toBe(1);
expect(testClass.cssClasses[0]).toBe('clickable');
expect(testClass.cssClasses).toBe('default clickable');
});
it('should associate click and href link with tooltip and target appropriately', function () {
@@ -1770,8 +1788,7 @@ C1 --> C2
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1);
expect(c1.cssClasses[0]).toBe('styleClass');
expect(c1.cssClasses).toBe('default styleClass');
const member = c1.members[0];
expect(member.getDisplayDetails().displayText).toBe('+member1');
});
@@ -1787,8 +1804,7 @@ cssClass "C1" styleClass
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1);
expect(c1.cssClasses[0]).toBe('styleClass');
expect(c1.cssClasses).toBe('default styleClass');
const member = c1.members[0];
expect(member.getDisplayDetails().displayText).toBe('+member1');
});
@@ -1805,13 +1821,11 @@ cssClass "C1,C2" styleClass
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1);
expect(c1.cssClasses[0]).toBe('styleClass');
expect(c1.cssClasses).toBe('default styleClass');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('Long long long long long long long long long long label');
expect(c2.cssClasses.length).toBe(1);
expect(c2.cssClasses[0]).toBe('styleClass');
expect(c2.cssClasses).toBe('default styleClass');
});
it('should parse two classes with text labels and css class shorthands', () => {
@@ -1825,13 +1839,11 @@ C1 --> C2
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1);
expect(c1.cssClasses[0]).toBe('styleClass1');
expect(c1.cssClasses).toBe('default styleClass1');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('Class 2 !@#$%^&*() label');
expect(c2.cssClasses.length).toBe(1);
expect(c2.cssClasses[0]).toBe('styleClass2');
expect(c2.cssClasses).toBe('default styleClass2');
});
it('should parse multiple classes with same text labels', () => {

View File

@@ -3,7 +3,7 @@ import type { DiagramDefinition } from '../../diagram-api/types.js';
import parser from './parser/classDiagram.jison';
import db from './classDb.js';
import styles from './styles.js';
import renderer from './classRenderer.js';
import renderer from './classRenderer-v3-unified.js';
export const diagram: DiagramDefinition = {
parser,

View File

@@ -0,0 +1,79 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import { getDiagramElement } from '../../rendering-util/insertElementsForSize.js';
import { getRegisteredLayoutAlgorithm, render } from '../../rendering-util/render.js';
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
import type { LayoutData } from '../../rendering-util/types.js';
import utils from '../../utils.js';
/**
* Get the direction from the statement items.
* Look through all of the documents (docs) in the parsedItems
* Because is a _document_ direction, the default direction is not necessarily the same as the overall default _diagram_ direction.
* @param parsedItem - the parsed statement item to look through
* @param defaultDir - the direction to use if none is found
* @returns The direction to use
*/
export const getDir = (parsedItem: any, defaultDir = 'TB') => {
if (!parsedItem.doc) {
return defaultDir;
}
let dir = defaultDir;
for (const parsedItemDoc of parsedItem.doc) {
if (parsedItemDoc.stmt === 'dir') {
dir = parsedItemDoc.value;
}
}
return dir;
};
export const getClasses = function (
text: string,
diagramObj: any
): Map<string, DiagramStyleClassDef> {
return diagramObj.db.getClasses();
};
export const draw = async function (text: string, id: string, _version: string, diag: any) {
log.info('REF0:');
log.info('Drawing class diagram (v3)', id);
const { securityLevel, state: conf, layout } = getConfig();
// Extracting the data from the parsed structure into a more usable form
// Not related to the refactoring, but this is the first step in the rendering process
// diag.db.extract(diag.db.getRootDocV2());
// The getData method provided in all supported diagrams is used to extract the data from the parsed structure
// into the Layout data format
const data4Layout = diag.db.getData() as LayoutData;
// Create the root SVG - the element is the div containing the SVG element
const svg = getDiagramElement(id, securityLevel);
data4Layout.type = diag.type;
data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(layout);
data4Layout.nodeSpacing = conf?.nodeSpacing || 50;
data4Layout.rankSpacing = conf?.rankSpacing || 50;
data4Layout.markers = ['aggregation', 'extension', 'composition', 'dependency', 'lollipop'];
data4Layout.diagramId = id;
await render(data4Layout, svg);
const padding = 8;
utils.insertTitle(
svg,
'classDiagramTitleText',
conf?.titleTopMargin ?? 25,
diag.db.getDiagramTitle()
);
setupViewPortForSVG(svg, padding, 'classDiagram', conf?.useMaxWidth ?? true);
};
export default {
getClasses,
draw,
getDir,
};

View File

@@ -5,7 +5,9 @@ export interface ClassNode {
id: string;
type: string;
label: string;
cssClasses: string[];
shape: string;
text: string;
cssClasses: string;
methods: ClassMember[];
members: ClassMember[];
annotations: string[];
@@ -16,6 +18,7 @@ export interface ClassNode {
linkTarget?: string;
haveCallback?: boolean;
tooltip?: string;
look?: string;
}
export type Visibility = '#' | '+' | '~' | '-' | '';
@@ -30,6 +33,7 @@ export class ClassMember {
cssStyle!: string;
memberType!: 'method' | 'attribute';
visibility!: Visibility;
text: string;
/**
* denote if static or to determine which css class to apply to the node
* @defaultValue ''
@@ -50,6 +54,7 @@ export class ClassMember {
this.memberType = memberType;
this.visibility = '';
this.classifier = '';
this.text = '';
const sanitizedInput = sanitizeText(input, getConfig());
this.parseMember(sanitizedInput);
}
@@ -85,7 +90,7 @@ export class ClassMember {
this.visibility = detectedVisibility as Visibility;
}
this.id = match[2].trim();
this.id = match[2];
this.parameters = match[3] ? match[3].trim() : '';
potentialClassifier = match[4] ? match[4].trim() : '';
this.returnType = match[5] ? match[5].trim() : '';
@@ -118,6 +123,14 @@ export class ClassMember {
}
this.classifier = potentialClassifier;
// Preserve one space only
this.id = this.id.startsWith(' ') ? ' ' + this.id.trim() : this.id.trim();
const combinedText = `${this.visibility ? '\\' + this.visibility : ''}${parseGenericTypes(this.id)}${this.memberType === 'method' ? `(${parseGenericTypes(this.parameters)})${this.returnType ? ' : ' + parseGenericTypes(this.returnType) : ''}` : ''}`;
this.text = combinedText.replaceAll('<', '&lt;').replaceAll('>', '&gt;');
if (this.text.startsWith('\\&lt;')) {
this.text = this.text.replace('\\&lt;', '~');
}
}
parseClassifier() {
@@ -154,6 +167,12 @@ export interface ClassRelation {
};
}
export interface Interface {
id: string;
label: string;
classId: string;
}
export interface NamespaceNode {
id: string;
domId: string;
@@ -161,5 +180,11 @@ export interface NamespaceNode {
children: NamespaceMap;
}
export interface StyleClass {
id: string;
styles: string[];
textStyles: string[];
}
export type ClassMap = Map<string, ClassNode>;
export type NamespaceMap = Map<string, NamespaceNode>;

View File

@@ -61,6 +61,7 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
<string>[^"]* return "STR";
<*>["] this.begin("string");
"style" return 'STYLE';
"classDef" return 'CLASSDEF';
<INITIAL,namespace>"namespace" { this.begin('namespace'); return 'NAMESPACE'; }
<namespace>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
@@ -265,6 +266,7 @@ statement
| styleStatement
| cssClassStatement
| noteStatement
| classDefStatement
| direction
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
@@ -326,6 +328,15 @@ noteStatement
| NOTE noteText { yy.addNote($2); }
;
classDefStatement
: CLASSDEF classList stylesOpt {$$ = $CLASSDEF;yy.defineClass($classList,$stylesOpt);}
;
classList
: ALPHA { $$ = [$ALPHA]; }
| classList COMMA ALPHA = { $$ = $classList.concat([$ALPHA]); }
;
direction
: direction_tb
{ yy.setDirection('TB');}

View File

@@ -0,0 +1,223 @@
import { select } from 'd3';
import { getConfig } from '../../config.js';
import { getNodeClasses } from '../../rendering-util/rendering-elements/shapes/util.js';
import { calculateTextWidth, decodeEntities } from '../../utils.js';
import type { ClassMember, ClassNode } from './classTypes.js';
import { sanitizeText } from '../../diagram-api/diagramAPI.js';
import { createText } from '../../rendering-util/createText.js';
import { evaluate, hasKatex } from '../common/common.js';
import type { Node } from '../../rendering-util/types.js';
import type { MermaidConfig } from '../../config.type.js';
import type { D3Selection } from '../../types.js';
// Creates the shapeSvg and inserts text
export async function textHelper<T extends SVGGraphicsElement>(
parent: D3Selection<T>,
node: any,
config: MermaidConfig,
useHtmlLabels: boolean,
GAP = config.class!.padding ?? 12
) {
const TEXT_PADDING = !useHtmlLabels ? 3 : 0;
const shapeSvg = parent
// @ts-ignore: Ignore error for using .insert on SVGAElement
.insert('g')
.attr('class', getNodeClasses(node))
.attr('id', node.domId || node.id);
let annotationGroup = null;
let labelGroup = null;
let membersGroup = null;
let methodsGroup = null;
let annotationGroupHeight = 0;
let labelGroupHeight = 0;
let membersGroupHeight = 0;
annotationGroup = shapeSvg.insert('g').attr('class', 'annotation-group text');
if (node.annotations.length > 0) {
const annotation = node.annotations[0];
await addText(annotationGroup, { text: `«${annotation}»` } as unknown as ClassMember, 0);
const annotationGroupBBox = annotationGroup.node()!.getBBox();
annotationGroupHeight = annotationGroupBBox.height;
}
labelGroup = shapeSvg.insert('g').attr('class', 'label-group text');
await addText(labelGroup, node, 0, ['font-weight: bolder']);
const labelGroupBBox = labelGroup.node()!.getBBox();
labelGroupHeight = labelGroupBBox.height;
membersGroup = shapeSvg.insert('g').attr('class', 'members-group text');
let yOffset = 0;
for (const member of node.members) {
const height = await addText(membersGroup, member, yOffset, [member.parseClassifier()]);
yOffset += height + TEXT_PADDING;
}
membersGroupHeight = membersGroup.node()!.getBBox().height;
if (membersGroupHeight <= 0) {
membersGroupHeight = GAP / 2;
}
methodsGroup = shapeSvg.insert('g').attr('class', 'methods-group text');
let methodsYOffset = 0;
for (const method of node.methods) {
const height = await addText(methodsGroup, method, methodsYOffset, [method.parseClassifier()]);
methodsYOffset += height + TEXT_PADDING;
}
let bbox = shapeSvg.node()!.getBBox();
// Center annotation
if (annotationGroup !== null) {
const annotationGroupBBox = annotationGroup.node()!.getBBox();
annotationGroup.attr('transform', `translate(${-annotationGroupBBox.width / 2})`);
}
// Adjust label
labelGroup.attr('transform', `translate(${-labelGroupBBox.width / 2}, ${annotationGroupHeight})`);
bbox = shapeSvg.node()!.getBBox();
membersGroup.attr(
'transform',
`translate(${0}, ${annotationGroupHeight + labelGroupHeight + GAP * 2})`
);
bbox = shapeSvg.node()!.getBBox();
methodsGroup.attr(
'transform',
`translate(${0}, ${annotationGroupHeight + labelGroupHeight + (membersGroupHeight ? membersGroupHeight + GAP * 4 : GAP * 2)})`
);
bbox = shapeSvg.node()!.getBBox();
return { shapeSvg, bbox };
}
// Modified version of labelHelper() to help create and place text for classes
async function addText<T extends SVGGraphicsElement>(
parentGroup: D3Selection<T>,
node: Node | ClassNode | ClassMember,
yOffset: number,
styles: string[] = []
) {
const textEl = parentGroup.insert('g').attr('class', 'label').attr('style', styles.join('; '));
const config = getConfig();
let useHtmlLabels =
'useHtmlLabels' in node ? node.useHtmlLabels : (evaluate(config.htmlLabels) ?? true);
let textContent = '';
// Support regular node type (.label) and classNodes (.text)
if ('text' in node) {
textContent = node.text;
} else {
textContent = node.label!;
}
// createText() will cause unwanted behavior because of classDiagram syntax so workarounds are needed
if (!useHtmlLabels && textContent.startsWith('\\')) {
textContent = textContent.substring(1);
}
if (hasKatex(textContent)) {
useHtmlLabels = true;
}
const text = await createText(
textEl,
sanitizeText(decodeEntities(textContent)),
{
width: calculateTextWidth(textContent, config) + 50, // Add room for error when splitting text into multiple lines
classes: 'markdown-node-label',
useHtmlLabels,
},
config
);
let bbox;
let numberOfLines = 1;
if (!useHtmlLabels) {
// Undo font-weight normal
if (styles.includes('font-weight: bolder')) {
select(text).selectAll('tspan').attr('font-weight', '');
}
numberOfLines = text.children.length;
const textChild = text.children[0];
if (text.textContent === '' || text.textContent.includes('&gt')) {
textChild.textContent =
textContent[0] +
textContent.substring(1).replaceAll('&gt;', '>').replaceAll('&lt;', '<').trim();
// Text was improperly removed due to spaces (preserve one space if present)
const preserveSpace = textContent[1] === ' ';
if (preserveSpace) {
textChild.textContent = textChild.textContent[0] + ' ' + textChild.textContent.substring(1);
}
}
// To support empty boxes
if (textChild.textContent === 'undefined') {
textChild.textContent = '';
}
// Get the bounding box after the text update
bbox = text.getBBox();
} else {
const div = text.children[0];
const dv = select(text);
numberOfLines = div.innerHTML.split('<br>').length;
// Katex math support
if (div.innerHTML.includes('</math>')) {
numberOfLines += div.innerHTML.split('<mrow>').length - 1;
}
// Support images
const images = div.getElementsByTagName('img');
if (images) {
const noImgText = textContent.replace(/<img[^>]*>/g, '').trim() === '';
await Promise.all(
[...images].map(
(img) =>
new Promise((res) => {
function setupImage() {
img.style.display = 'flex';
img.style.flexDirection = 'column';
if (noImgText) {
// default size if no text
const bodyFontSize =
config.fontSize?.toString() ?? window.getComputedStyle(document.body).fontSize;
const enlargingFactor = 5;
const width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px';
img.style.minWidth = width;
img.style.maxWidth = width;
} else {
img.style.width = '100%';
}
res(img);
}
setTimeout(() => {
if (img.complete) {
setupImage();
}
});
img.addEventListener('error', setupImage);
img.addEventListener('load', setupImage);
})
)
);
}
bbox = div.getBoundingClientRect();
dv.attr('width', bbox.width);
dv.attr('height', bbox.height);
}
// Center text and offset by yOffset
textEl.attr('transform', 'translate(0,' + (-bbox.height / (2 * numberOfLines) + yOffset) + ')');
return bbox.height;
}

View File

@@ -20,6 +20,10 @@ const getStyles = (options) =>
.label text {
fill: ${options.classText};
}
.labelBkg {
background: ${options.mainBkg};
}
.edgeLabel .label span {
background: ${options.mainBkg};
}

View File

@@ -32,14 +32,14 @@ const setupDompurifyHooksIfNotSetup = (() => {
function setupDompurifyHooks() {
const TEMPORARY_ATTRIBUTE = 'data-temp-href-target';
DOMPurify.addHook('beforeSanitizeAttributes', (node: Element) => {
if (node.tagName === 'A' && node.hasAttribute('target')) {
DOMPurify.addHook('beforeSanitizeAttributes', (node) => {
if (node instanceof Element && node.tagName === 'A' && node.hasAttribute('target')) {
node.setAttribute(TEMPORARY_ATTRIBUTE, node.getAttribute('target') ?? '');
}
});
DOMPurify.addHook('afterSanitizeAttributes', (node: Element) => {
if (node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) {
DOMPurify.addHook('afterSanitizeAttributes', (node) => {
if (node instanceof Element && node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) {
node.setAttribute('target', node.getAttribute(TEMPORARY_ATTRIBUTE) ?? '');
node.removeAttribute(TEMPORARY_ATTRIBUTE);
if (node.getAttribute('target') === '_blank') {
@@ -83,7 +83,6 @@ export const sanitizeText = (text: string, config: MermaidConfig): string => {
return text;
}
if (config.dompurifyConfig) {
// eslint-disable-next-line @typescript-eslint/no-base-to-string
text = DOMPurify.sanitize(sanitizeMore(text, config), config.dompurifyConfig).toString();
} else {
text = DOMPurify.sanitize(sanitizeMore(text, config), {

View File

@@ -2,6 +2,7 @@ import { select } from 'd3';
import utils, { getEdgeId } from '../../utils.js';
import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js';
import common from '../common/common.js';
import { isValidShape, type ShapeID } from '../../rendering-util/rendering-elements/shapes.js';
import type { Node, Edge } from '../../rendering-util/types.js';
import { log } from '../../logger.js';
import * as yaml from 'js-yaml';
@@ -14,7 +15,15 @@ import {
setDiagramTitle,
getDiagramTitle,
} from '../common/commonDb.js';
import type { FlowVertex, FlowClass, FlowSubGraph, FlowText, FlowEdge, FlowLink } from './types.js';
import type {
FlowVertex,
FlowClass,
FlowSubGraph,
FlowText,
FlowEdge,
FlowLink,
FlowVertexTypeParam,
} from './types.js';
import type { NodeMetaData } from '../../types.js';
const MERMAID_DOM_ID_PREFIX = 'flowchart-';
@@ -53,12 +62,11 @@ export const lookUpDomId = function (id: string) {
/**
* Function called by parser when a node definition has been found
*
*/
export const addVertex = function (
id: string,
textObj: FlowText,
type: 'group',
type: FlowVertexTypeParam,
style: string[],
classes: string[],
dir: string,
@@ -133,14 +141,15 @@ export const addVertex = function (
}
// console.log('yamlData', yamlData);
const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
if (doc.shape && (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_'))) {
throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
}
// console.log('yamlData doc', doc);
if (doc?.shape) {
if (doc.shape) {
if (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_')) {
throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
} else if (!isValidShape(doc.shape)) {
throw new Error(`No such shape: ${doc.shape}.`);
}
vertex.type = doc?.shape;
}
if (doc?.label) {
vertex.text = doc?.label;
}
@@ -816,7 +825,7 @@ export const lex = {
firstGraph,
};
const getTypeFromVertex = (vertex: FlowVertex) => {
const getTypeFromVertex = (vertex: FlowVertex): ShapeID => {
if (vertex.img) {
return 'imageSquare';
}
@@ -832,14 +841,18 @@ const getTypeFromVertex = (vertex: FlowVertex) => {
}
return 'icon';
}
if (vertex.type === 'square') {
return 'squareRect';
switch (vertex.type) {
case 'square':
case undefined:
return 'squareRect';
case 'round':
return 'roundedRect';
case 'ellipse':
// @ts-expect-error -- Ellipses are broken, see https://github.com/mermaid-js/mermaid/issues/5976
return 'ellipse';
default:
return vertex.type;
}
if (vertex.type === 'round') {
return 'roundedRect';
}
return vertex.type ?? 'squareRect';
};
const findNode = (nodes: Node[], id: string) => nodes.find((node) => node.id === id);
@@ -880,7 +893,7 @@ const addNodeFromVertex = (
node.cssCompiledStyles = getCompiledStyles(vertex.classes);
node.cssClasses = vertex.classes.join(' ');
} else {
nodes.push({
const baseNode = {
id: vertex.id,
label: vertex.text,
labelStyle: '',
@@ -889,10 +902,8 @@ const addNodeFromVertex = (
cssStyles: vertex.styles,
cssCompiledStyles: getCompiledStyles(['default', 'node', ...vertex.classes]),
cssClasses: 'default ' + vertex.classes.join(' '),
shape: getTypeFromVertex(vertex),
dir: vertex.dir,
domId: vertex.domId,
isGroup,
look,
link: vertex.link,
linkTarget: vertex.linkTarget,
@@ -903,7 +914,20 @@ const addNodeFromVertex = (
assetWidth: vertex.assetWidth,
assetHeight: vertex.assetHeight,
constraint: vertex.constraint,
});
};
if (isGroup) {
nodes.push({
...baseNode,
isGroup: true,
shape: 'rect',
});
} else {
nodes.push({
...baseNode,
isGroup: false,
shape: getTypeFromVertex(vertex),
});
}
}
};

View File

@@ -197,6 +197,21 @@ describe('when parsing directions', function () {
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is }');
});
it('should error on non-existent shape', function () {
expect(() => {
flow.parser.parse(`flowchart TB
A@{ shape: this-shape-does-not-exist }
`);
}).toThrow('No such shape: this-shape-does-not-exist.');
});
it('should error on internal-only shape', function () {
expect(() => {
// this shape does exist, but it's only supposed to be for internal/backwards compatibility use
flow.parser.parse(`flowchart TB
A@{ shape: rect_left_inv_arrow }
`);
}).toThrow('No such shape: rect_left_inv_arrow. Shape names should be lowercase.');
});
it('Diamond shapes should work as usual', function () {
const res = flow.parser.parse(`flowchart TB
A{This is a label}

View File

@@ -1,3 +1,28 @@
import type { ShapeID } from '../../rendering-util/rendering-elements/shapes.js';
/**
* Valid `type` args to `yy.addVertex` taken from
* `packages/mermaid/src/diagrams/flowchart/parser/flow.jison`
*/
export type FlowVertexTypeParam =
| undefined
| 'square'
| 'doublecircle'
| 'circle'
| 'ellipse'
| 'stadium'
| 'subroutine'
| 'rect'
| 'cylinder'
| 'round'
| 'diamond'
| 'hexagon'
| 'odd'
| 'trapezoid'
| 'inv_trapezoid'
| 'lean_right'
| 'lean_left';
export interface FlowVertex {
classes: string[];
dir?: string;
@@ -10,7 +35,7 @@ export interface FlowVertex {
props?: any;
styles: string[];
text?: string;
type?: string;
type?: ShapeID | FlowVertexTypeParam;
icon?: string;
form?: string;
pos?: 't' | 'b';

View File

@@ -0,0 +1,23 @@
import type {
DiagramDetector,
DiagramLoader,
ExternalDiagramDefinition,
} from '../../diagram-api/types.js';
const id = 'kanban';
const detector: DiagramDetector = (txt) => {
return /^\s*kanban/.test(txt);
};
const loader: DiagramLoader = async () => {
const { diagram } = await import('./kanban-definition.js');
return { id, diagram };
};
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

View File

@@ -0,0 +1,13 @@
// @ts-ignore: JISON doesn't support types
import parser from './parser/kanban.jison';
import db from './kanbanDb.js';
import renderer from './kanbanRenderer.js';
import styles from './styles.js';
import type { DiagramDefinition } from '../../diagram-api/types.js';
export const diagram: DiagramDefinition = {
db,
renderer,
parser,
styles,
};

View File

@@ -0,0 +1,494 @@
// @ts-expect-error No types available for JISON
import { parser as kanban } from './parser/kanban.jison';
import kanbanDB from './kanbanDb.js';
import type { KanbanNode } from '../../rendering-util/types.js';
// Todo fix utils functions for tests
import { setLogLevel } from '../../diagram-api/diagramAPI.js';
describe('when parsing a kanban ', function () {
beforeEach(function () {
kanban.yy = kanbanDB;
kanban.yy.clear();
setLogLevel('trace');
});
describe('hiearchy', function () {
it('KNBN-1 should handle a simple root definition abc122', function () {
const str = `kanban
root`;
kanban.parse(str);
const sections = kanban.yy.getSections();
expect(sections.length).toEqual(1);
expect(sections[0].label).toEqual('root');
});
it('KNBN-2 should handle a hierachial kanban definition', function () {
const str = `kanban
root
child1
child2
`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections.length).toEqual(1);
expect(sections[0].label).toEqual('root');
expect(children.length).toEqual(2);
expect(children[0].label).toEqual('child1');
expect(children[1].label).toEqual('child2');
});
/** CATCH case when a lower level comes later, should throw
* a
* b
* c
*/
it('3 should handle a simple root definition with a shape and without an id abc123', function () {
const str = `kanban
(root)`;
kanban.parse(str);
const sections = kanban.yy.getSections();
expect(sections[0].label).toEqual('root');
});
it('KNBN-4 should not dsitinguis between deeper hierachial levels in thr kanban definition', function () {
const str = `kanban
root
child1
leaf1
child2`;
// less picky is better
// expect(() => kanban.parse(str)).toThrow(
// 'There can be only one root. No parent could be found for ("fakeRoot")'
// );
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections.length).toBe(1);
expect(children.length).toBe(3);
});
it('5 Multiple sections are ok', function () {
const str = `kanban
section1
section2`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections.length).toBe(2);
expect(sections[0].label).toBe('section1');
expect(sections[1].label).toBe('section2');
// expect(() => kanban.parse(str)).toThrow(
// 'There can be only one root. No parent could be found for ("fakeRoot")'
// );
});
it('KNBN-6 real root in wrong place', function () {
const str = `kanban
root
fakeRoot
realRootWrongPlace`;
expect(() => kanban.parse(str)).toThrow(
'Items without section detected, found section ("fakeRoot")'
);
});
});
describe('nodes', function () {
it('KNBN-7 should handle an id and type for a node definition', function () {
const str = `kanban
root[The root]
`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('The root');
});
it('KNBN-8 should handle an id and type for a node definition', function () {
const str = `kanban
root
theId(child1)`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].label).toEqual('root');
expect(children.length).toEqual(1);
const child = children[0];
expect(child.label).toEqual('child1');
expect(child.id).toEqual('theId');
});
it('KNBN-9 should handle an id and type for a node definition', function () {
const str = `kanban
root
theId(child1)`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].label).toEqual('root');
expect(children.length).toEqual(1);
const child = children[0];
expect(child.label).toEqual('child1');
expect(child.id).toEqual('theId');
});
});
describe('decorations', function () {
it('KNBN-13 should be possible to set an icon for the node', function () {
const str = `kanban
root[The root]
::icon(bomb)
`;
// ::class1 class2
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('The root');
expect(sections[0].icon).toEqual('bomb');
});
it('KNBN-14 should be possible to set classes for the node', function () {
const str = `kanban
root[The root]
:::m-4 p-8
`;
// ::class1 class2
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('The root');
expect(sections[0].cssClasses).toEqual('m-4 p-8');
});
it('KNBN-15 should be possible to set both classes and icon for the node', function () {
const str = `kanban
root[The root]
:::m-4 p-8
::icon(bomb)
`;
// ::class1 class2
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('The root');
expect(sections[0].cssClasses).toEqual('m-4 p-8');
expect(sections[0].icon).toEqual('bomb');
});
it('KNBN-16 should be possible to set both classes and icon for the node', function () {
const str = `kanban
root[The root]
::icon(bomb)
:::m-4 p-8
`;
// ::class1 class2
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('The root');
// expect(sections[0].type).toEqual('rect');
expect(sections[0].cssClasses).toEqual('m-4 p-8');
expect(sections[0].icon).toEqual('bomb');
});
});
describe('descriptions', function () {
it('KNBN-17 should be possible to use node syntax in the descriptions', function () {
const str = `kanban
root["String containing []"]
`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('String containing []');
});
it('KNBN-18 should be possible to use node syntax in the descriptions in children', function () {
const str = `kanban
root["String containing []"]
child1["String containing ()"]
`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('String containing []');
expect(children.length).toEqual(1);
expect(children[0].label).toEqual('String containing ()');
});
it('KNBN-19 should be possible to have a child after a class assignment', function () {
const str = `kanban
root(Root)
Child(Child)
:::hot
a(a)
b[New Stuff]`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('Root');
expect(children.length).toEqual(3);
const item1 = children[0];
const item2 = children[1];
const item3 = children[2];
expect(item1.id).toEqual('Child');
expect(item2.id).toEqual('a');
expect(item3.id).toEqual('b');
});
});
it('KNBN-20 should be possible to have meaningless empty rows in a kanban abc124', function () {
const str = `kanban
root(Root)
Child(Child)
a(a)
b[New Stuff]`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('Root');
expect(children.length).toEqual(3);
const item1 = children[0];
const item2 = children[1];
const item3 = children[2];
expect(item1.id).toEqual('Child');
expect(item2.id).toEqual('a');
expect(item3.id).toEqual('b');
});
it('KNBN-21 should be possible to have comments in a kanban', function () {
const str = `kanban
root(Root)
Child(Child)
a(a)
%% This is a comment
b[New Stuff]`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('Root');
const child = children[0];
expect(child.id).toEqual('Child');
expect(children[1].id).toEqual('a');
expect(children[2].id).toEqual('b');
expect(children.length).toEqual(3);
});
it('KNBN-22 should be possible to have comments at the end of a line', function () {
const str = `kanban
root(Root)
Child(Child)
a(a) %% This is a comment
b[New Stuff]`;
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(sections[0].label).toEqual('Root');
expect(children.length).toEqual(3);
const child1 = children[0];
expect(child1.id).toEqual('Child');
const child2 = children[1];
expect(child2.id).toEqual('a');
const child3 = children[2];
expect(child3.id).toEqual('b');
});
it('KNBN-23 Rows with only spaces should not interfere', function () {
const str = 'kanban\nroot\n A\n \n\n B';
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(children.length).toEqual(2);
const child = children[0];
expect(child.id).toEqual('A');
const child2 = children[1];
expect(child2.id).toEqual('B');
});
it('KNBN-24 Handle rows above the kanban declarations', function () {
const str = '\n \nkanban\nroot\n A\n \n\n B';
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(children.length).toEqual(2);
const child = children[0];
expect(child.id).toEqual('A');
const child2 = children[1];
expect(child2.id).toEqual('B');
});
it('KNBN-25 Handle rows above the kanban declarations, no space', function () {
const str = '\n\n\nkanban\nroot\n A\n \n\n B';
kanban.parse(str);
const data = kanban.yy.getData();
const sections = kanban.yy.getSections();
const children = data.nodes.filter((n: KanbanNode) => n.parentId === sections[0].id);
expect(sections[0].id).toEqual('root');
expect(children.length).toEqual(2);
const child = children[0];
expect(child.id).toEqual('A');
const child2 = children[1];
expect(child2.id).toEqual('B');
});
});
describe('item data data', function () {
beforeEach(function () {
kanban.yy = kanbanDB;
kanban.yy.clear();
setLogLevel('trace');
});
it('KNBN-30 should be possible to set the priority', function () {
let str = `kanban
root
`;
str = `kanban
root@{ priority: high }
`;
kanban.parse(str);
const sections = kanban.yy.getSections();
expect(sections[0].id).toEqual('root');
expect(sections[0].priority).toEqual('high');
});
it('KNBN-31 should be possible to set the assignment', function () {
const str = `kanban
root@{ assigned: knsv }
`;
kanban.parse(str);
const sections = kanban.yy.getSections();
expect(sections[0].id).toEqual('root');
expect(sections[0].assigned).toEqual('knsv');
});
it('KNBN-32 should be possible to set the icon', function () {
const str = `kanban
root@{ icon: star }
`;
kanban.parse(str);
const sections = kanban.yy.getSections();
expect(sections[0].id).toEqual('root');
expect(sections[0].icon).toEqual('star');
});
it('KNBN-33 should be possible to set the icon', function () {
const str = `kanban
root@{ icon: star }
`;
kanban.parse(str);
const sections = kanban.yy.getSections();
expect(sections[0].id).toEqual('root');
expect(sections[0].icon).toEqual('star');
});
it('KNBN-34 should be possible to set the metadata using multiple lines', function () {
const str = `kanban
root@{
icon: star
assigned: knsv
}
`;
kanban.parse(str);
const sections = kanban.yy.getSections();
expect(sections[0].id).toEqual('root');
expect(sections[0].icon).toEqual('star');
expect(sections[0].assigned).toEqual('knsv');
});
it('KNBN-35 should be possible to set the metadata using one line', function () {
const str = `kanban
root@{ icon: star, assigned: knsv }
`;
kanban.parse(str);
const sections = kanban.yy.getSections();
expect(sections[0].id).toEqual('root');
expect(sections[0].icon).toEqual('star');
expect(sections[0].assigned).toEqual('knsv');
});
it('KNBN-36 should be possible to set the label using the new syntax', function () {
const str = `kanban
root@{ icon: star, label: 'fix things' }
`;
kanban.parse(str);
const sections = kanban.yy.getSections();
expect(sections[0].label).toEqual('fix things');
});
it('KNBN-37 should be possible to set the external id', function () {
const str = `kanban
root@{ ticket: MC-1234 }
`;
kanban.parse(str);
const sections = kanban.yy.getSections();
const data = kanban.yy.getData();
expect(sections[0].id).toEqual('root');
expect(sections[0].ticket).toEqual('MC-1234');
});
});

View File

@@ -0,0 +1,253 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { D3Element } from '../../types.js';
import { sanitizeText } from '../../diagrams/common/common.js';
import { log } from '../../logger.js';
import type { Edge, KanbanNode } from '../../rendering-util/types.js';
import defaultConfig from '../../defaultConfig.js';
import type { NodeMetaData } from '../../types.js';
import * as yaml from 'js-yaml';
let nodes: KanbanNode[] = [];
let sections: KanbanNode[] = [];
let cnt = 0;
let elements: Record<number, D3Element> = {};
const clear = () => {
nodes = [];
sections = [];
cnt = 0;
elements = {};
};
/*
* if your level is the section level return null - then you do not belong to a level
* otherwise return the current section
*/
const getSection = (level: number) => {
if (nodes.length === 0) {
// console.log('No nodes');
return null;
}
const sectionLevel = nodes[0].level;
let lastSection = null;
for (let i = nodes.length - 1; i >= 0; i--) {
if (nodes[i].level === sectionLevel && !lastSection) {
lastSection = nodes[i];
// console.log('lastSection found', lastSection);
}
// console.log('HERE', nodes[i].id, level, nodes[i].level, sectionLevel);
if (nodes[i].level < sectionLevel) {
throw new Error('Items without section detected, found section ("' + nodes[i].label + '")');
}
}
if (level === lastSection?.level) {
return null;
}
// No found
return lastSection;
};
const getSections = function () {
return sections;
};
const getData = function () {
const edges = [] as Edge[];
const _nodes: KanbanNode[] = [];
const sections = getSections();
const conf = getConfig();
for (const section of sections) {
const node = {
id: section.id,
label: sanitizeText(section.label ?? '', conf),
isGroup: true,
ticket: section.ticket,
shape: 'kanbanSection',
level: section.level,
look: conf.look,
} satisfies KanbanNode;
_nodes.push(node);
const children = nodes.filter((n) => n.parentId === section.id);
for (const item of children) {
const childNode = {
id: item.id,
parentId: section.id,
label: sanitizeText(item.label ?? '', conf),
isGroup: false,
ticket: item?.ticket,
priority: item?.priority,
assigned: item?.assigned,
icon: item?.icon,
shape: 'kanbanItem',
level: item.level,
rx: 5,
ry: 5,
cssStyles: ['text-align: left'],
} satisfies KanbanNode;
_nodes.push(childNode);
}
}
return { nodes: _nodes, edges, other: {}, config: getConfig() };
};
const addNode = (level: number, id: string, descr: string, type: number, shapeData: string) => {
const conf = getConfig();
let padding: number = conf.mindmap?.padding ?? defaultConfig.mindmap.padding;
switch (type) {
case nodeType.ROUNDED_RECT:
case nodeType.RECT:
case nodeType.HEXAGON:
padding *= 2;
}
const node: KanbanNode = {
id: sanitizeText(id, conf) || 'kbn' + cnt++,
level,
label: sanitizeText(descr, conf),
width: conf.mindmap?.maxNodeWidth ?? defaultConfig.mindmap.maxNodeWidth,
padding,
isGroup: false,
} satisfies KanbanNode;
if (shapeData !== undefined) {
let yamlData;
// detect if shapeData contains a newline character
// console.log('shapeData', shapeData);
if (!shapeData.includes('\n')) {
// console.log('yamlData shapeData has no new lines', shapeData);
yamlData = '{\n' + shapeData + '\n}';
} else {
// console.log('yamlData shapeData has new lines', shapeData);
yamlData = shapeData + '\n';
}
const doc = yaml.load(yamlData, { schema: yaml.JSON_SCHEMA }) as NodeMetaData;
// console.log('yamlData', doc);
if (doc.shape && (doc.shape !== doc.shape.toLowerCase() || doc.shape.includes('_'))) {
throw new Error(`No such shape: ${doc.shape}. Shape names should be lowercase.`);
}
// if shape is defined in the yaml data, use it if it is a valid shape kanbanItem
if (doc?.shape && doc.shape === 'kanbanItem') {
node.shape = doc?.shape;
}
if (doc?.label) {
node.label = doc?.label;
}
if (doc?.icon) {
node.icon = doc?.icon.toString();
}
if (doc?.assigned) {
node.assigned = doc?.assigned.toString();
}
if (doc?.ticket) {
node.ticket = doc?.ticket.toString();
}
if (doc?.priority) {
node.priority = doc?.priority;
}
}
const section = getSection(level);
if (section) {
// @ts-ignore false positive for section.id
node.parentId = section.id || 'kbn' + cnt++;
} else {
sections.push(node);
}
nodes.push(node);
};
const nodeType = {
DEFAULT: 0,
NO_BORDER: 0,
ROUNDED_RECT: 1,
RECT: 2,
CIRCLE: 3,
CLOUD: 4,
BANG: 5,
HEXAGON: 6,
};
const getType = (startStr: string, endStr: string): number => {
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;
case '{{':
return nodeType.HEXAGON;
default:
return nodeType.DEFAULT;
}
};
const setElementForId = (id: number, element: D3Element) => {
elements[id] = element;
};
const decorateNode = (decoration?: { class?: string; icon?: string }) => {
if (!decoration) {
return;
}
const config = getConfig();
const node = nodes[nodes.length - 1];
if (decoration.icon) {
node.icon = sanitizeText(decoration.icon, config);
}
if (decoration.class) {
node.cssClasses = sanitizeText(decoration.class, config);
}
};
const type2Str = (type: number) => {
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';
case nodeType.HEXAGON:
return 'hexgon'; // cspell: disable-line
default:
return 'no-border';
}
};
// Expose logger to grammar
const getLogger = () => log;
const getElementById = (id: number) => elements[id];
const db = {
clear,
addNode,
getSections,
getData,
nodeType,
getType,
setElementForId,
decorateNode,
type2Str,
getLogger,
getElementById,
} as const;
export default db;

View File

@@ -0,0 +1,96 @@
import { getConfig } from '../../diagram-api/diagramAPI.js';
import type { DrawDefinition } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import type { KanbanDB } from './kanbanTypes.js';
import defaultConfig from '../../defaultConfig.js';
import { insertCluster } from '../../rendering-util/rendering-elements/clusters.js';
import { insertNode, positionNode } from '../../rendering-util/rendering-elements/nodes.js';
import type { ClusterNode } from '../../rendering-util/types.js';
export const draw: DrawDefinition = async (text, id, _version, diagObj) => {
log.debug('Rendering kanban diagram\n' + text);
const db = diagObj.db as KanbanDB;
const data4Layout = db.getData();
const conf = getConfig();
conf.htmlLabels = false;
const svg = selectSvgElement(id);
// 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 sectionsElem = svg.append('g');
sectionsElem.attr('class', 'sections');
const nodesElem = svg.append('g');
nodesElem.attr('class', 'items');
const sections = data4Layout.nodes.filter(
// TODO: TypeScript 5.5 will infer this predicate automatically
(node): node is typeof node & ClusterNode => node.isGroup
);
let cnt = 0;
// TODO set padding
const padding = 10;
const sectionObjects = [];
let maxLabelHeight = 25;
for (const section of sections) {
const WIDTH = conf?.kanban?.sectionWidth || 200;
// const top = (-WIDTH * 3) / 2 + 25;
// let y = top;
cnt = cnt + 1;
section.x = WIDTH * cnt + ((cnt - 1) * padding) / 2;
section.width = WIDTH;
section.y = 0;
section.height = WIDTH * 3;
section.rx = 5;
section.ry = 5;
// Todo, use theme variable THEME_COLOR_LIMIT instead of 10
section.cssClasses = section.cssClasses + ' section-' + cnt;
const sectionObj = await insertCluster(sectionsElem, section);
maxLabelHeight = Math.max(maxLabelHeight, sectionObj?.labelBBox?.height);
sectionObjects.push(sectionObj);
}
let i = 0;
for (const section of sections) {
const sectionObj = sectionObjects[i];
i = i + 1;
const WIDTH = conf?.kanban?.sectionWidth || 200;
const top = (-WIDTH * 3) / 2 + maxLabelHeight;
let y = top;
const sectionItems = data4Layout.nodes.filter((node) => node.parentId === section.id);
for (const item of sectionItems) {
if (item.isGroup) {
// Kanban diagrams should not have groups within groups
// this should never happen
throw new Error('Groups within groups are not allowed in Kanban diagrams');
}
item.x = section.x;
item.width = WIDTH - 1.5 * padding;
const nodeEl = await insertNode(nodesElem, item, { config: conf });
const bbox = nodeEl.node()!.getBBox();
item.y = y + bbox.height / 2;
await positionNode(item);
y = item.y + bbox.height / 2 + padding / 2;
}
const rect = sectionObj.cluster.select('rect');
const height = Math.max(y - top + 3 * padding, 50) + (maxLabelHeight - 25);
rect.attr('height', height);
}
// Setup the view box and size of the svg element
setupGraphViewbox(
undefined,
svg,
conf.mindmap?.padding ?? defaultConfig.kanban.padding,
conf.mindmap?.useMaxWidth ?? defaultConfig.kanban.useMaxWidth
);
};
export default {
draw,
};

View File

@@ -0,0 +1,3 @@
import type kanbanDb from './kanbanDb.js';
export type KanbanDB = typeof kanbanDb;

View File

@@ -0,0 +1,166 @@
/** 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 NSTR2
%x ICON
%x CLASS
%x shapeData
%x shapeDataStr
%x shapeDataEndBracket
%%
\@\{ {
// console.log('=> shapeData', yytext);
this.pushState("shapeData"); yytext=""; return 'SHAPE_DATA' }
<shapeData>["] {
// console.log('=> shapeDataStr', yytext);
this.pushState("shapeDataStr");
return 'SHAPE_DATA';
}
<shapeDataStr>["] {
// console.log('shapeData <==', yytext);
this.popState(); return 'SHAPE_DATA'}
<shapeDataStr>[^\"]+ {
// console.log('shapeData', yytext);
const re = /\n\s*/g;
yytext = yytext.replace(re,"<br/>");
return 'SHAPE_DATA'}
<shapeData>[^}^"]+ {
// console.log('shapeData', yytext);
return 'SHAPE_DATA';
}
<shapeData>"}" {
// console.log('<== root', yytext)
this.popState();
}
\s*\%\%.* {yy.getLogger().trace('Found comment',yytext); return 'SPACELINE';}
// \%\%[^\n]*\n /* skip comments */
"kanban" {return 'KANBAN';}
":::" { this.begin('CLASS'); }
<CLASS>.+ { this.popState();return 'CLASS'; }
<CLASS>\n { this.popState();}
// [\s]*"::icon(" { this.begin('ICON'); }
"::icon(" { yy.getLogger().trace('Begin icon');this.begin('ICON'); }
[\s]+[\n] {yy.getLogger().trace('SPACELINE');return 'SPACELINE' /* skip all whitespace */ ;}
[\n]+ return 'NL';
<ICON>[^\)]+ { return 'ICON'; }
<ICON>\) {yy.getLogger().trace('end icon');this.popState();}
"-)" { yy.getLogger().trace('Exploding node'); this.begin('NODE');return 'NODE_DSTART'; }
"(-" { yy.getLogger().trace('Cloud'); this.begin('NODE');return 'NODE_DSTART'; }
"))" { yy.getLogger().trace('Explosion Bang'); this.begin('NODE');return 'NODE_DSTART'; }
")" { yy.getLogger().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'; }
"[" { this.begin('NODE');return 'NODE_DSTART'; }
[\s]+ return 'SPACELIST' /* skip all whitespace */ ;
// !(-\() return 'NODE_ID';
[^\(\[\n\)\{\}@]+ {return 'NODE_ID';}
<<EOF>> return 'EOF';
<NODE>["][`] { this.begin("NSTR2");}
<NSTR2>[^`"]+ { return "NODE_DESCR";}
<NSTR2>[`]["] { this.popState();}
<NODE>["] { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");}
<NSTR>[^"]+ { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";}
<NSTR>["] {this.popState();}
<NODE>[\)]\) {this.popState();yy.getLogger().trace('node end ))');return "NODE_DEND";}
<NODE>[\)] {this.popState();yy.getLogger().trace('node end )');return "NODE_DEND";}
<NODE>[\]] {this.popState();yy.getLogger().trace('node end ...',yytext);return "NODE_DEND";}
<NODE>"}}" {this.popState();yy.getLogger().trace('node end ((');return "NODE_DEND";}
<NODE>"(-" {this.popState();yy.getLogger().trace('node end (-');return "NODE_DEND";}
<NODE>"-)" {this.popState();yy.getLogger().trace('node end (-');return "NODE_DEND";}
<NODE>"((" {this.popState();yy.getLogger().trace('node end ((');return "NODE_DEND";}
<NODE>"(" {this.popState();yy.getLogger().trace('node end ((');return "NODE_DEND";}
<NODE>[^\)\]\(\}]+ { yy.getLogger().trace('Long description:', yytext); return 'NODE_DESCR';}
<NODE>.+(?!\(\() { yy.getLogger().trace('Long description:', yytext); return 'NODE_DESCR';}
// [\[] return 'NODE_START';
// .+ return 'TXT' ;
/lex
%start start
%% /* language grammar */
start
// %{ : info document 'EOF' { return yy; } }
: mindMap
| spaceLines mindMap
;
spaceLines
: SPACELINE
| spaceLines SPACELINE
| spaceLines NL
;
mindMap
: KANBAN document { return yy; }
| KANBAN NL document { return yy; }
;
stop
: NL {yy.getLogger().trace('Stop NL ');}
| EOF {yy.getLogger().trace('Stop EOF ');}
| SPACELINE
| stop NL {yy.getLogger().trace('Stop NL2 ');}
| stop EOF {yy.getLogger().trace('Stop EOF2 ');}
;
document
: document statement stop
| statement stop
;
statement
: SPACELIST node shapeData { yy.getLogger().info('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type, $3); }
| SPACELIST node { yy.getLogger().info('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); }
| SPACELIST ICON { yy.getLogger().trace('Icon: ',$2);yy.decorateNode({icon: $2}); }
| SPACELIST CLASS { yy.decorateNode({class: $2}); }
| SPACELINE { yy.getLogger().trace('SPACELIST');}
| node shapeData { yy.getLogger().trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type, $2); }
| node { yy.getLogger().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.getLogger().trace("node found ..", $1); $$ = { id: $2, descr: $2, type: yy.getType($1, $3) }; }
;
nodeWithId
: NODE_ID { $$ = { id: $1, descr: $1, type: 0 }; }
| NODE_ID NODE_DSTART NODE_DESCR NODE_DEND
{ yy.getLogger().trace("node found ..", $1); $$ = { id: $1, descr: $3, type: yy.getType($2, $4) }; }
;
shapeData:
shapeData SHAPE_DATA
{ $$ = $1 + $2; }
| SHAPE_DATA
{ $$ = $1; }
;
%%

View File

@@ -0,0 +1,105 @@
```mermaid
kanban
New
Sometimes wrong Shape type is highlighted
In progress
```
```mermaid
kanban
Todo
Create JISON
Update DB function
Create parsing tests
define getData
Create renderer
In progress
Design grammar
```
Adding ID
```mermaid
kanban
id1[Todo]
id2[Create JISON]
id3[Update DB function]
id4[Create parsing tests]
id5[define getData]
id6[Create renderer]
id7[In progress]
id8[Design grammar]
```
Background color for section
```mermaid
kanban
id1[Todo]
id2[Create JISON]
id3[Update DB function]
id4[Create parsing tests]
id5[define getData]
id6[Create renderer]
id7[In progress]
id8[Design grammar]
style n2 stroke:#AA00FF,fill:#E1BEE7
```
Background color for section
```mermaid
kanban
id1[Todo]
id2[Create JISON]
id3[Update DB function]
id4[Create parsing tests]
id5[define getData]
id6[Create renderer]
id7[In progress]
id8[Design grammar]
id2@{
assigned: knsv
icon: heart
priority: high
descr: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}
style n1 stroke:#AA00FF,fill:#E1BEE7
```
Background color for section
```mermaid
---
config:
kanban:
showIds: true
fields: [[title],[description][id, assigned]]
---
kanban
id1[Todo]
id2[Create JISON]
id3[Update DB function]
id4[Create parsing tests]
id5[define getData]
id6[Create renderer]
id7[In progress]
id8[Design grammar]
id2@{
assigned: knsv
icon: heart
priority: high
descr: "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum."
}
style n1 stroke:#AA00FF,fill:#E1BEE7
```
priority - dedicated
link - dedicated

View File

@@ -0,0 +1,109 @@
// @ts-expect-error Incorrect khroma types
import { darken, lighten, isDark } from 'khroma';
import type { DiagramStylesProvider } from '../../diagram-api/types.js';
const genSections: DiagramStylesProvider = (options) => {
let sections = '';
for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) {
options['lineColor' + i] = options['lineColor' + i] || options['cScaleInv' + i];
if (isDark(options['lineColor' + i])) {
options['lineColor' + i] = lighten(options['lineColor' + i], 20);
} else {
options['lineColor' + i] = darken(options['lineColor' + i], 20);
}
}
const adjuster = (color: string, level: number) =>
options.darkMode ? darken(color, level) : lighten(color, level);
for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) {
const sw = '' + (17 - 3 * i);
sections += `
.section-${i - 1} rect, .section-${i - 1} path, .section-${i - 1} circle, .section-${
i - 1
} polygon, .section-${i - 1} path {
fill: ${adjuster(options['cScale' + i], 10)};
stroke: ${adjuster(options['cScale' + i], 10)};
}
.section-${i - 1} text {
fill: ${options['cScaleLabel' + i]};
}
.node-icon-${i - 1} {
font-size: 40px;
color: ${options['cScaleLabel' + i]};
}
.section-edge-${i - 1}{
stroke: ${options['cScale' + i]};
}
.edge-depth-${i - 1}{
stroke-width: ${sw};
}
.section-${i - 1} line {
stroke: ${options['cScaleInv' + i]} ;
stroke-width: 3;
}
.disabled, .disabled circle, .disabled text {
fill: lightgray;
}
.disabled text {
fill: #efefef;
}
.node rect,
.node circle,
.node ellipse,
.node polygon,
.node path {
fill: ${options.background};
stroke: ${options.nodeBorder};
stroke-width: 1px;
}
.kanban-ticket-link {
fill: ${options.background};
stroke: ${options.nodeBorder};
text-decoration: underline;
}
`;
}
return sections;
};
// TODO: These options seem incorrect.
const getStyles: DiagramStylesProvider = (options) =>
`
.edge {
stroke-width: 3;
}
${genSections(options)}
.section-root rect, .section-root path, .section-root circle, .section-root polygon {
fill: ${options.git0};
}
.section-root text {
fill: ${options.gitBranchLabel0};
}
.icon-container {
height:100%;
display: flex;
justify-content: center;
align-items: center;
}
.edge {
fill: none;
}
.cluster-label, .label {
color: ${options.textColor};
fill: ${options.textColor};
}
.kanban-label {
dy: 1em;
alignment-baseline: middle;
text-anchor: middle;
dominant-baseline: middle;
text-align: center;
}
`;
export default getStyles;

View File

@@ -1,14 +0,0 @@
<template>
<div class="-mt-6 mb-8">
<a
href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;whiteboard"
target="_blank"
><img
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light"
alt="Mermaid&#0032;Whiteboard - Drag&#0032;&#0038;&#0032;Drop&#0032;your&#0032;Nodes&#0032;with&#0032;Mermaid&#0039;s&#0032;new&#0032;Whiteboard&#0033; | Product Hunt"
style="width: 250px; height: 54px"
width="250"
height="54"
/></a>
</div>
</template>

View File

@@ -7,17 +7,21 @@ interface Taglines {
}
const taglines: Taglines[] = [
{
label: 'Explore the Mermaid Whiteboard from the creators of Mermaid',
url: 'https://www.mermaidchart.com/whiteboard?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=whiteboard',
},
{
label: 'Use the Visual Editor in Mermaid Chart to design and build diagrams',
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=visual_editor',
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=visual_editor',
},
{
label: 'Diagram live with teammates in Mermaid Chart',
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=teams',
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=teams',
},
{
label: 'Skip the rough draft with Mermaid AI in Mermaid Chart',
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=mermaid_ai',
label: 'Replace ChatGPT Pro, Mermaid.live, and LucidChart with Mermaid Pro',
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_js&utm_medium=banner_ad&utm_campaign=AIbundle',
},
];
@@ -25,7 +29,7 @@ let index = ref(Math.floor(Math.random() * taglines.length));
onMounted(() => {
setInterval(() => {
index.value = (index.value + 1) % taglines.length;
}, 60_000);
}, 40_000);
});
</script>
@@ -41,7 +45,7 @@ onMounted(() => {
>
<span class="font-semibold">{{ taglines[index].label }}</span>
<button class="rounded bg-[#111113] p-1 px-2 text-sm font-semibold tracking-wide">
Try it now
Try now
</button>
</a>
</transition>

View File

@@ -157,6 +157,7 @@ function sidebarSyntax() {
{ text: 'XY Chart 🔥', link: '/syntax/xyChart' },
{ text: 'Block Diagram 🔥', link: '/syntax/block' },
{ text: 'Packet 🔥', link: '/syntax/packet' },
{ text: 'Kanban 🔥', link: '/syntax/kanban' },
{ text: 'Architecture 🔥', link: '/syntax/architecture' },
{ text: 'Other Examples', link: '/syntax/examples' },
],
@@ -174,6 +175,7 @@ function sidebarConfig() {
{ text: 'API-Usage', link: '/config/usage' },
{ text: 'Mermaid API Configuration', link: '/config/setup/README' },
{ text: 'Mermaid Configuration Options', link: '/config/schema-docs/config' },
{ text: 'Registering icons', link: '/config/icons' },
{ text: 'Directives', link: '/config/directives' },
{ text: 'Theming', link: '/config/theming' },
{ text: 'Math', link: '/config/math' },

View File

@@ -9,8 +9,6 @@ import Contributors from '../components/Contributors.vue';
import HomePage from '../components/HomePage.vue';
// @ts-ignore Type not available
import TopBar from '../components/TopBar.vue';
// @ts-ignore Type not available
import ProductHuntBadge from '../components/ProductHuntBadge.vue';
import { getRedirect } from './redirect.js';
// @ts-ignore Type not available
import 'uno.css';
@@ -25,7 +23,6 @@ export default {
return h(Theme.Layout, null, {
// Keeping this as comment as it took a lot of time to figure out how to add a component to the top bar.
'home-hero-before': () => h(TopBar),
'home-hero-info-before': () => h(ProductHuntBadge),
'home-features-after': () => h(HomePage),
'doc-before': () => h(TopBar),
});

View File

@@ -95,7 +95,7 @@ To add a new shape:
- **Example**:
```typescript
import { Node, RenderOptions } from '../../types.d.ts';
import { Node, RenderOptions } from '../../types.ts';
export const myNewShape = async (
parent: SVGAElement,
@@ -111,7 +111,7 @@ To add a new shape:
### 2. Register the Shape
- **Register the shape**: Add your shape to the `shapes` object in the main shapes module. This allows your shape to be recognized and used within the system.
- **Register the shape**: Add your shape to the `shapes` object in the [main shapes module](../rendering-util/rendering-elements/shapes.ts). This allows your shape to be recognized and used within the system.
- **Example**:
@@ -120,9 +120,14 @@ To add a new shape:
const shapes = {
...,
'my-new-shape': myNewShape,
// Shortened alias (if any).
'm-nsh': myNewShape
{
semanticName: 'My Shape',
name: 'Shape Name',
shortName: '<short-name>',
description: '<Description for the shape>',
aliases: ['<alias-one>', '<al-on>', '<alias-two>', '<al-two>'],
handler: myNewShape,
},
};
```

View File

@@ -0,0 +1,49 @@
# Registering icon pack in mermaid
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
Using JSON file directly from CDN:
```js
import mermaid from 'CDN/mermaid.esm.mjs';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
},
]);
```
Using packages and a bundler:
```bash
npm install @iconify-json/logos@1
```
With lazy loading
```js
import mermaid from 'mermaid';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () => import('@iconify-json/logos').then((module) => module.icons),
},
]);
```
Without lazy loading
```js
import mermaid from 'mermaid';
import { icons } from '@iconify-json/logos';
mermaid.registerIconPacks([
{
name: icons.prefix, // To use the prefix defined in the icon pack
icons,
},
]);
```

View File

@@ -180,8 +180,6 @@ Communication tools and platforms
- [=Diagram block](https://github.com/zag/podlite/tree/main/packages/podlite-diagrams)
- [Standard Notes](https://standardnotes.com/)
- [Mermaid Extension](https://github.com/nienow/sn-mermaid)
- [Sublime Text 3](https://sublimetext.com)
- [Mermaid Package](https://packagecontrol.io/packages/Mermaid)
- [VS Code](https://code.visualstudio.com/)
- [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor)
- [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export)
@@ -195,15 +193,22 @@ Communication tools and platforms
- [Vim](https://www.vim.org)
- [Vim Diagram Syntax](https://github.com/zhaozg/vim-diagram)
- [Official Vim Syntax and ft plugin](https://github.com/craigmac/vim-mermaid)
- [Zed](https://zed.dev)
- [zed-mermaid](https://github.com/gabeidx/zed-mermaid)
### Document Generation
- [Astro](https://astro.build/)
- [Adding diagrams to your Astro site with MermaidJS and Playwright](https://agramont.net/blog/diagraming-with-mermaidjs-astro/)
- [Codedoc](https://codedoc.cc/)
- [codedoc-mermaid-plugin](https://www.npmjs.com/package/codedoc-mermaid-plugin)
- [Docsy Hugo Theme](https://www.docsy.dev/docs/adding-content/lookandfeel/#diagrams-with-mermaid) ✅
- [Docusaurus](https://docusaurus.io/docs/markdown-features/diagrams) ✅
- [Gatsby](https://www.gatsbyjs.com/)
- [gatsby-remark-mermaid](https://github.com/remcohaszing/gatsby-remark-mermaid)
- [Jekyll](https://jekyllrb.com/)
- [jekyll-mermaid](https://rubygems.org/gems/jekyll-mermaid)
- [jekyll-mermaid-diagrams](https://github.com/fuzhibo/jekyll-mermaid-diagrams)
- [JSDoc](https://jsdoc.app/)
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
- [Madness](https://madness.dannyb.co/)
@@ -212,7 +217,7 @@ Communication tools and platforms
- [MkDocs](https://www.mkdocs.org)
- [mkdocs-mermaid2-plugin](https://github.com/fralau/mkdocs-mermaid2-plugin)
- [mkdocs-material](https://github.com/squidfunk/mkdocs-material), check the [docs](https://squidfunk.github.io/mkdocs-material/reference/diagrams/)
- [Quarto](https://quarto.org/)
- [Quarto](https://quarto.org/)
- [rehype](https://github.com/rehypejs/rehype)
- [rehype-mermaid](https://github.com/remcohaszing/rehype-mermaid)
- [remark](https://remark.js.org/)
@@ -241,17 +246,12 @@ Communication tools and platforms
### Other
- [Astro](https://astro.build/)
- [Adding diagrams to your Astro site with MermaidJS and Playwright](https://agramont.net/blog/diagraming-with-mermaidjs-astro/)
- [Bisheng](https://www.npmjs.com/package/bisheng)
- [bisheng-plugin-mermaid](https://github.com/yct21/bisheng-plugin-mermaid)
- [Blazorade Mermaid: Render Mermaid diagrams in Blazor applications](https://github.com/Blazorade/Blazorade-Mermaid/wiki)
- [Codemia: A tool to practice system design problems](https://codemia.io) ✅
- [ExDoc](https://github.com/elixir-lang/ex_doc)
- [Rendering Mermaid graphs](https://github.com/elixir-lang/ex_doc#rendering-mermaid-graphs)
- [Jekyll](https://jekyllrb.com/)
- [jekyll-mermaid](https://rubygems.org/gems/jekyll-mermaid)
- [jekyll-mermaid-diagrams](https://github.com/fuzhibo/jekyll-mermaid-diagrams)
- [MarkChart: Preview Mermaid diagrams on macOS](https://markchart.app/)
- [mermaid-isomorphic](https://github.com/remcohaszing/mermaid-isomorphic)
- [mermaid-server: Generate diagrams using a HTTP request](https://github.com/TomWright/mermaid-server)

View File

@@ -6,7 +6,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
<br />
<a href="https://www.producthunt.com/posts/mermaid-whiteboard?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;whiteboard" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=486720&theme=light" alt="Mermaid&#0032;Whiteboard - Drag&#0032;&#0038;&#0032;Drop&#0032;your&#0032;Nodes&#0032;with&#0032;Mermaid&#0039;s&#0032;new&#0032;Whiteboard&#0033; | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
<a href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light" alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## About

View File

@@ -76,8 +76,8 @@ Mermaid offers a variety of styles or “looks” for your diagrams, allowing yo
**Available Looks:**
Hand-Drawn Look: For a more personal, creative touch, the hand-drawn look brings a sketch-like quality to your diagrams. This style is perfect for informal settings or when you want to add a bit of personality to your diagrams.
Classic Look: If you prefer the traditional Mermaid style, the classic look maintains the original appearance that many users are familiar with. Its great for consistency across projects or when you want to keep the familiar aesthetic.
- Hand-Drawn Look: For a more personal, creative touch, the hand-drawn look brings a sketch-like quality to your diagrams. This style is perfect for informal settings or when you want to add a bit of personality to your diagrams.
- Classic Look: If you prefer the traditional Mermaid style, the classic look maintains the original appearance that many users are familiar with. Its great for consistency across projects or when you want to keep the familiar aesthetic.
**How to Select a Look:**
@@ -101,8 +101,8 @@ In addition to customizing the look of your diagrams, Mermaid Chart now allows y
#### Supported Layout Algorithms:
Dagre (default): This is the classic layout algorithm that has been used in Mermaid for a long time. It provides a good balance of simplicity and visual clarity, making it ideal for most diagrams.
ELK: For those who need more sophisticated layout capabilities, especially when working with large or intricate diagrams, the ELK (Eclipse Layout Kernel) layout offers advanced options. It provides a more optimized arrangement, potentially reducing overlapping and improving readability. This is not included out the box but needs to be added when integrating mermaid for sites/applications that want to have elk support.
- Dagre (default): This is the classic layout algorithm that has been used in Mermaid for a long time. It provides a good balance of simplicity and visual clarity, making it ideal for most diagrams.
- ELK: For those who need more sophisticated layout capabilities, especially when working with large or intricate diagrams, the ELK (Eclipse Layout Kernel) layout offers advanced options. It provides a more optimized arrangement, potentially reducing overlapping and improving readability. This is not included out the box but needs to be added when integrating mermaid for sites/applications that want to have elk support.
#### How to Select a Layout Algorithm:

View File

@@ -1,5 +1,17 @@
# Blog
## [Mermaid 11.4 is out: New Features and Kanban Diagramming](https://www.mermaidchart.com/blog/posts/mermaid-11-4-is-out-new-features-and-kanban-diagramming)
Mermaid 11.4 brings enhanced functionality with the introduction of Kanban diagrams, allowing users to create visual workflows with status columns and task details.
October 31, 2024 · 2 mins
## [How To Build an ER Diagram with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-build-an-er-diagram-with-mermaid-chart)
An entity relationship (ER) diagram acts like a blueprint for your database. This makes ER diagrams effective tools for anyone dealing with complex databases, data modeling, and AI model training.
October 24, 2024 · 4 mins
## [Expanding the Horizons of Mermaid Flowcharts: Introducing 30 New Shapes!](https://www.mermaidchart.com/blog/posts/new-mermaid-flowchart-shapes/)
24 September 2024 · 5 mins

View File

@@ -59,15 +59,15 @@ service {service id}({icon name})[{title}] (in {parent id})?
Put together:
```
service database(db)[Database]
service database1(database)[My Database]
```
creates the service identified as `database`, using the icon `db`, with the label `Database`.
creates the service identified as `database1`, using the icon `database`, with the label `My Database`.
If the service belongs to a group, it can be placed inside it through the optional `in` keyword
```
service database(db)[Database] in private_api
service database1(database)[My Database] in private_api
```
### Edges
@@ -156,55 +156,7 @@ architecture-beta
## Icons
By default, architecture diagram supports the following icons: `cloud`, `database`, `disk`, `internet`, `server`.
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps below.
The icon packs available can be found at [icones.js.org](https://icones.js.org/).
We use the name defined when registering the icon pack, to override the prefix field of the iconify pack. This allows the user to use shorter names for the icons. It also allows us to load a particular pack only when it is used in a diagram.
Using JSON file directly from CDN:
```js
import mermaid from 'CDN/mermaid.esm.mjs';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () =>
fetch('https://unpkg.com/@iconify-json/logos/icons.json').then((res) => res.json()),
},
]);
```
Using packages and a bundler:
```bash
npm install @iconify-json/logos
```
With lazy loading
```js
import mermaid from 'mermaid';
mermaid.registerIconPacks([
{
name: 'logos',
loader: () => import('@iconify-json/logos').then((module) => module.icons),
},
]);
```
Without lazy loading
```js
import mermaid from 'mermaid';
import { icons } from '@iconify-json/logos';
mermaid.registerIconPacks([
{
name: icons.prefix, // To use the prefix defined in the icon pack
icons,
},
]);
```
Users can use any of the 200,000+ icons available in iconify.design, or add their own custom icons, by following the steps [here](../config/icons.md).
After the icons are installed, they can be used in the architecture diagram by using the format "name:icon-name", where name is the value used when registering the icon pack.

View File

@@ -106,7 +106,7 @@ block-beta
a["A label"] b:2 c:2 d
```
In this example, the block labeled "A wide one" spans two columns, while blocks 'b', 'c', and 'd' are allocated their own columns. This flexibility in block sizing is crucial for accurately representing systems with components of varying significance or size.
In this example, the block labeled "A labels" spans one column, while blocks 'b', 'c' span 2 columns, and 'd' is again allocated its own column. This flexibility in block sizing is crucial for accurately representing systems with components of varying significance or size.
### Creating Composite Blocks

View File

@@ -277,6 +277,34 @@ And `Link` can be one of:
| -- | Solid |
| .. | Dashed |
### Lollipop Interfaces
Classes can also be given a special relation type that defines a lollipop interface on the class. A lollipop interface is defined using the following syntax:
- `bar ()-- foo`
- `foo --() bar`
The interface (bar) with the lollipop connects to the class (foo).
Note: Each interface that is defined is unique and is meant to not be shared between classes / have multiple edges connecting to it.
```mermaid-example
classDiagram
bar ()-- foo
```
```mermaid-example
classDiagram
class Class01 {
int amount
draw()
}
Class01 --() bar
Class02 --() bar
foo ()-- Class01
```
## Define Namespace
A namespace groups classes.
@@ -518,10 +546,12 @@ Beginner's tip—a full example using interactive links in an HTML page:
## Styling
### Styling a node (v10.7.0+)
### Styling a node
It is possible to apply specific styles such as a thicker border or a different background color to an individual node using the `style` keyword.
Note that notes and namespaces cannot be styled individually but do support themes.
```mermaid-example
classDiagram
class Animal
@@ -533,11 +563,78 @@ classDiagram
#### Classes
More convenient than defining the style every time is to define a class of styles and attach this class to the nodes that
should have a different look. This is done by predefining classes in css styles that can be applied from the graph definition using the `cssClass` statement or the `:::` short hand.
should have a different look.
A class definition looks like the example below:
```
classDef className fill:#f9f,stroke:#333,stroke-width:4px;
```
Also, it is possible to define style to multiple classes in one statement:
```
classDef firstClassName,secondClassName font-size:12pt;
```
Attachment of a class to a node is done as per below:
```
cssClass "nodeId1" className;
```
It is also possible to attach a class to a list of nodes in one statement:
```
cssClass "nodeId1,nodeId2" className;
```
A shorter form of adding a class is to attach the classname to the node using the `:::` operator:
```mermaid-example
classDiagram
class Animal:::someclass
classDef someclass fill:#f96
```
Or:
```mermaid-example
classDiagram
class Animal:::someclass {
-int sizeInFeet
-canEat()
}
classDef someclass fill:#f96
```
### Default class
If a class is named default it will be applied to all nodes. Specific styles and classes should be defined afterwards to override the applied default styling.
```
classDef default fill:#f9f,stroke:#333,stroke-width:4px;
```
```mermaid-example
classDiagram
class Animal:::pink
class Mineral
classDef default fill:#f96,color:red
classDef pink color:#f9f
```
### CSS Classes
It is also possible to predefine classes in CSS styles that can be applied from the graph definition as in the example
below:
**Example style**
```html
<style>
.styleClass > rect {
.styleClass > * > g {
fill: #ff0000;
stroke: #ffff00;
stroke-width: 4px;
@@ -545,147 +642,29 @@ should have a different look. This is done by predefining classes in css styles
</style>
```
Then attaching that class to a specific node:
```
cssClass "nodeId1" styleClass;
```
It is also possible to attach a class to a list of nodes in one statement:
```
cssClass "nodeId1,nodeId2" styleClass;
```
A shorter form of adding a class is to attach the classname to the node using the `:::` operator:
**Example definition**
```mermaid-example
classDiagram
class Animal:::styleClass
```
Or:
```mermaid-example
classDiagram
class Animal:::styleClass {
-int sizeInFeet
-canEat()
}
```
?> cssClasses cannot be added using this shorthand method at the same time as a relation statement.
?> Due to limitations with existing markup for class diagrams, it is not currently possible to define css classes within the diagram itself. **_Coming soon!_**
### Default Styles
The main styling of the class diagram is done with a preset number of css classes. During rendering these classes are extracted from the file located at src/themes/class.scss. The classes used here are described below:
| Class | Description |
| ------------------ | ----------------------------------------------------------------- |
| g.classGroup text | Styles for general class text |
| classGroup .title | Styles for general class title |
| g.classGroup rect | Styles for class diagram rectangle |
| g.classGroup line | Styles for class diagram line |
| .classLabel .box | Styles for class label box |
| .classLabel .label | Styles for class label text |
| composition | Styles for composition arrow head and arrow line |
| aggregation | Styles for aggregation arrow head and arrow line(dashed or solid) |
| dependency | Styles for dependency arrow head and arrow line |
#### Sample stylesheet
```scss
body {
background: white;
}
g.classGroup text {
fill: $nodeBorder;
stroke: none;
font-family: 'trebuchet ms', verdana, arial;
font-family: var(--mermaid-font-family);
font-size: 10px;
.title {
font-weight: bolder;
}
}
g.classGroup rect {
fill: $nodeBkg;
stroke: $nodeBorder;
}
g.classGroup line {
stroke: $nodeBorder;
stroke-width: 1;
}
.classLabel .box {
stroke: none;
stroke-width: 0;
fill: $nodeBkg;
opacity: 0.5;
}
.classLabel .label {
fill: $nodeBorder;
font-size: 10px;
}
.relation {
stroke: $nodeBorder;
stroke-width: 1;
fill: none;
}
@mixin composition {
fill: $nodeBorder;
stroke: $nodeBorder;
stroke-width: 1;
}
#compositionStart {
@include composition;
}
#compositionEnd {
@include composition;
}
@mixin aggregation {
fill: $nodeBkg;
stroke: $nodeBorder;
stroke-width: 1;
}
#aggregationStart {
@include aggregation;
}
#aggregationEnd {
@include aggregation;
}
#dependencyStart {
@include composition;
}
#dependencyEnd {
@include composition;
}
#extensionStart {
@include composition;
}
#extensionEnd {
@include composition;
}
```
> cssClasses cannot be added using this shorthand method at the same time as a relation statement.
## Configuration
`Coming soon!`
### Members Box
It is possible to hide the empty members box of a class node.
This is done by changing the **hideEmptyMembersBox** value of the class diagram configuration. For more information on how to edit the Mermaid configuration see the [configuration page.](https://mermaid.js.org/config/configuration.html)
```mermaid-example
---
config:
class:
hideEmptyMembersBox: true
---
classDiagram
class Duck
```

Some files were not shown because too many files have changed in this diff Show More