mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-31 06:06:44 +02:00
Compare commits
292 Commits
v10.3.1
...
fixElkTest
Author | SHA1 | Date | |
---|---|---|---|
![]() |
212b368ffd | ||
![]() |
773abbe7e2 | ||
![]() |
bcd03151e8 | ||
![]() |
c212490248 | ||
![]() |
abcf2a2eab | ||
![]() |
ebaabbf19a | ||
![]() |
fd7406b94a | ||
![]() |
e5e44dbd39 | ||
![]() |
7cc76505a8 | ||
![]() |
c38478f6c3 | ||
![]() |
3cf7649682 | ||
![]() |
12d4386fd3 | ||
![]() |
4f0abab91f | ||
![]() |
ce7783414b | ||
![]() |
1503306651 | ||
![]() |
88d1885cdc | ||
![]() |
4ab47a2ce7 | ||
![]() |
8f3bcf401d | ||
![]() |
3959e076cf | ||
![]() |
bc770c48c7 | ||
![]() |
82404dd0f6 | ||
![]() |
f707dafcb4 | ||
![]() |
a91679db30 | ||
![]() |
916c0b95e7 | ||
![]() |
bdaf58a322 | ||
![]() |
900748250c | ||
![]() |
54b78acb47 | ||
![]() |
a7d5c8492d | ||
![]() |
caa61df363 | ||
![]() |
06e44f5da8 | ||
![]() |
49446940f5 | ||
![]() |
69b4b48f62 | ||
![]() |
05c2a11e3d | ||
![]() |
650d712bd8 | ||
![]() |
d61f9fbe62 | ||
![]() |
be3829232c | ||
![]() |
20fd6d35f0 | ||
![]() |
5b724b180f | ||
![]() |
784e235ff9 | ||
![]() |
02a0596e3c | ||
![]() |
498f75eece | ||
![]() |
a61887f5a2 | ||
![]() |
44a6a6758e | ||
![]() |
a12864d9e4 | ||
![]() |
bbbae7d59f | ||
![]() |
e84ee32408 | ||
![]() |
cca9662365 | ||
![]() |
f30a23f41e | ||
![]() |
58cb827839 | ||
![]() |
7de1abbcc2 | ||
![]() |
26e32665a4 | ||
![]() |
c3df18fc5b | ||
![]() |
eca0cea339 | ||
![]() |
efa6cb3434 | ||
![]() |
5839e0de87 | ||
![]() |
4efac6721d | ||
![]() |
6e51f8fd98 | ||
![]() |
9823834a56 | ||
![]() |
6b0e0e21ac | ||
![]() |
19aaf459a5 | ||
![]() |
6aa20ed64b | ||
![]() |
12f11c6721 | ||
![]() |
75737544c8 | ||
![]() |
8f340094d9 | ||
![]() |
264f7920f0 | ||
![]() |
b513169f12 | ||
![]() |
87880fdf40 | ||
![]() |
ed819e96d5 | ||
![]() |
5a2b3db14c | ||
![]() |
fa6198b4ce | ||
![]() |
3678ad4e9d | ||
![]() |
979dcb073d | ||
![]() |
ecf14cd4e3 | ||
![]() |
92d9f35f89 | ||
![]() |
8e64995047 | ||
![]() |
8b96282c48 | ||
![]() |
15c7c37227 | ||
![]() |
267935713c | ||
![]() |
8ef5f38861 | ||
![]() |
47af4760bc | ||
![]() |
8e5275d844 | ||
![]() |
c51afa4dd2 | ||
![]() |
671ad82ad3 | ||
![]() |
1bfa1a8c88 | ||
![]() |
ab140a0fc7 | ||
![]() |
7d395e176e | ||
![]() |
3d35015ee9 | ||
![]() |
2676130146 | ||
![]() |
fb3814bf85 | ||
![]() |
2dd1415849 | ||
![]() |
9fa6dc23ca | ||
![]() |
9dccf4d9c9 | ||
![]() |
f248a22c71 | ||
![]() |
6563a6ea26 | ||
![]() |
87d2899e4e | ||
![]() |
a3a6eb9bf5 | ||
![]() |
c28c285711 | ||
![]() |
5f8f79fc3e | ||
![]() |
a4ba192aad | ||
![]() |
f553fa5eb8 | ||
![]() |
c3939d3fb3 | ||
![]() |
32d1783390 | ||
![]() |
58afdd1baa | ||
![]() |
0881619fcb | ||
![]() |
eba5fcca6b | ||
![]() |
52cbf9fd28 | ||
![]() |
fc19bf93aa | ||
![]() |
54de763a66 | ||
![]() |
56419f7459 | ||
![]() |
2537152c7a | ||
![]() |
14c1c1cc59 | ||
![]() |
108c25f19f | ||
![]() |
3ec32521f8 | ||
![]() |
72b4a74bee | ||
![]() |
c3160b2693 | ||
![]() |
2ab393cd16 | ||
![]() |
bd98da16a0 | ||
![]() |
cee2a458f1 | ||
![]() |
4a06bc1a68 | ||
![]() |
a7ae1b6890 | ||
![]() |
ce6992ea9b | ||
![]() |
0114a87f15 | ||
![]() |
adfed1e432 | ||
![]() |
11b599d77c | ||
![]() |
3944e9ac00 | ||
![]() |
37aa6632aa | ||
![]() |
7e251ee8bc | ||
![]() |
6e0f41180f | ||
![]() |
52f05552a1 | ||
![]() |
bb01b3972d | ||
![]() |
1a50a326cb | ||
![]() |
cc8b4572d5 | ||
![]() |
e5315f2933 | ||
![]() |
d21eb29672 | ||
![]() |
fd45dbfc14 | ||
![]() |
5c9eafabae | ||
![]() |
2b9dc0ea80 | ||
![]() |
8ac7dc81e0 | ||
![]() |
2967b3c1bb | ||
![]() |
534fd85339 | ||
![]() |
7298008374 | ||
![]() |
844a175039 | ||
![]() |
fd35a54735 | ||
![]() |
a6e6c3fb18 | ||
![]() |
31c0a0cbab | ||
![]() |
767baa4ec6 | ||
![]() |
f422a66dde | ||
![]() |
fae976e994 | ||
![]() |
f4c62436ea | ||
![]() |
21d0998db8 | ||
![]() |
0899f7918a | ||
![]() |
7a547abd89 | ||
![]() |
4d84f1cd71 | ||
![]() |
72fa3488b5 | ||
![]() |
cfffba2817 | ||
![]() |
e7ee3eb9ea | ||
![]() |
7b45dbea1a | ||
![]() |
341130856f | ||
![]() |
b85c011cd1 | ||
![]() |
aec97d68cc | ||
![]() |
7b7e281ec7 | ||
![]() |
35cc34d422 | ||
![]() |
edb6ceae43 | ||
![]() |
ef8b75a6da | ||
![]() |
02daf5417b | ||
![]() |
77d8e15dc4 | ||
![]() |
62142089f1 | ||
![]() |
defe40692a | ||
![]() |
337ff3c32b | ||
![]() |
744cc792f4 | ||
![]() |
3f327196fd | ||
![]() |
bbaab54ec9 | ||
![]() |
9a9abac5b1 | ||
![]() |
6ed0c9bc36 | ||
![]() |
99978da55b | ||
![]() |
50eb3cf1c9 | ||
![]() |
c1b9c54fc9 | ||
![]() |
0d179c501e | ||
![]() |
085e8f78b3 | ||
![]() |
f9d4a62609 | ||
![]() |
2a8374312f | ||
![]() |
b60410161d | ||
![]() |
d219f92a19 | ||
![]() |
3f3a7340e3 | ||
![]() |
cb5f70c139 | ||
![]() |
99c1758490 | ||
![]() |
43217e1395 | ||
![]() |
aa34b99203 | ||
![]() |
541ee1eade | ||
![]() |
f01ad644e3 | ||
![]() |
9538233573 | ||
![]() |
5a2ea7c297 | ||
![]() |
22b172d873 | ||
![]() |
92098e23eb | ||
![]() |
591cb794eb | ||
![]() |
f9d978859e | ||
![]() |
6170538c47 | ||
![]() |
c37c494a1e | ||
![]() |
65c93807d8 | ||
![]() |
8ed9ddc7aa | ||
![]() |
8a8e062342 | ||
![]() |
1721282182 | ||
![]() |
d8dd68cad2 | ||
![]() |
120bdabee1 | ||
![]() |
a5a3ffc768 | ||
![]() |
dfeb25127b | ||
![]() |
396bda8d95 | ||
![]() |
cc70d37166 | ||
![]() |
3f5da06bb0 | ||
![]() |
be106befff | ||
![]() |
d5f11fc80a | ||
![]() |
c4113541e1 | ||
![]() |
fb49f25eef | ||
![]() |
a9681d1b1c | ||
![]() |
545d361d3f | ||
![]() |
3e598f4e8e | ||
![]() |
eb397fdb04 | ||
![]() |
6c1afb7a8d | ||
![]() |
fee6f459a8 | ||
![]() |
e128a11f3b | ||
![]() |
38dc17f426 | ||
![]() |
453c67e5ea | ||
![]() |
a69a97fdd9 | ||
![]() |
820cc48c11 | ||
![]() |
06e4a6398c | ||
![]() |
ca1cdb1d94 | ||
![]() |
5485517b27 | ||
![]() |
96380600d9 | ||
![]() |
9563b22132 | ||
![]() |
e6a18eea91 | ||
![]() |
d2ed52461e | ||
![]() |
71205f5bd6 | ||
![]() |
23b6d53f80 | ||
![]() |
f4671e4e3a | ||
![]() |
c954e0eb1d | ||
![]() |
de37efefd7 | ||
![]() |
bdfd8974d4 | ||
![]() |
aecf451ed1 | ||
![]() |
7d69ad2d5b | ||
![]() |
74fa9956a3 | ||
![]() |
e33340331a | ||
![]() |
02acf9d7d9 | ||
![]() |
e29d2b29a9 | ||
![]() |
c001520e54 | ||
![]() |
8435330534 | ||
![]() |
1d391d9408 | ||
![]() |
6abf0f1d19 | ||
![]() |
7b2ef1110a | ||
![]() |
3718274a1c | ||
![]() |
760548335c | ||
![]() |
b32da2b1b1 | ||
![]() |
41c5152015 | ||
![]() |
796a761a7d | ||
![]() |
09c4a26509 | ||
![]() |
ce9d0e2e6a | ||
![]() |
ae8860eec3 | ||
![]() |
67d287f85e | ||
![]() |
9c2b95fc3c | ||
![]() |
3a22d4a501 | ||
![]() |
cecf759b0b | ||
![]() |
35c6b671de | ||
![]() |
c894c1f5b5 | ||
![]() |
a92571d588 | ||
![]() |
34a47706fd | ||
![]() |
906d909d87 | ||
![]() |
1d0aa763de | ||
![]() |
f6dc089ddf | ||
![]() |
95e01b4935 | ||
![]() |
9145a9e69e | ||
![]() |
6941814729 | ||
![]() |
32d3001e2a | ||
![]() |
452e543e77 | ||
![]() |
23a5832fc9 | ||
![]() |
8794fa0b38 | ||
![]() |
f2338f5b66 | ||
![]() |
5aba2fed8b | ||
![]() |
bd6795032f | ||
![]() |
c17b723295 | ||
![]() |
231a9630df | ||
![]() |
bdb967e0a8 | ||
![]() |
ea3fbbd58d | ||
![]() |
afea3e8f37 | ||
![]() |
4e7dbf76cc | ||
![]() |
a3901f691a | ||
![]() |
a9ee184551 | ||
![]() |
164605b442 | ||
![]() |
f4ffd5c965 | ||
![]() |
b0b3c7f410 | ||
![]() |
0aa09bfca5 | ||
![]() |
9a5cae9c63 | ||
![]() |
0b8dce4b82 | ||
![]() |
37c18eb4c0 |
@@ -48,6 +48,7 @@ module.exports = {
|
||||
'no-prototype-builtins': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'cypress/no-async-tests': 'off',
|
||||
'@typescript-eslint/consistent-type-imports': 'error',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': [
|
||||
|
15
.github/release-drafter.yml
vendored
15
.github/release-drafter.yml
vendored
@@ -1,14 +1,27 @@
|
||||
name-template: '$NEXT_PATCH_VERSION'
|
||||
tag-template: '$NEXT_PATCH_VERSION'
|
||||
categories:
|
||||
- title: '🚨 **Breaking Changes**'
|
||||
labels:
|
||||
- 'Breaking Change'
|
||||
- title: '🚀 Features'
|
||||
labels:
|
||||
- 'Type: Enhancement'
|
||||
- 'feature' # deprecated, new PRs shouldn't have this
|
||||
- title: '🐛 Bug Fixes'
|
||||
labels:
|
||||
- 'Type: Bug / Error'
|
||||
- 'fix' # deprecated, new PRs shouldn't have this
|
||||
- title: '🧰 Maintenance'
|
||||
label: 'Type: Other'
|
||||
labels:
|
||||
- 'Type: Other'
|
||||
- 'chore' # deprecated, new PRs shouldn't have this
|
||||
- title: '⚡️ Performance'
|
||||
labels:
|
||||
- 'Type: Performance'
|
||||
- title: '📚 Documentation'
|
||||
labels:
|
||||
- 'Area: Documentation'
|
||||
change-template: '- $TITLE (#$NUMBER) @$AUTHOR'
|
||||
sort-by: title
|
||||
sort-direction: ascending
|
||||
|
18
.github/workflows/lint.yml
vendored
18
.github/workflows/lint.yml
vendored
@@ -62,8 +62,22 @@ jobs:
|
||||
ERROR_MESSAGE+=' `pnpm run --filter mermaid types:build-config`'
|
||||
ERROR_MESSAGE+=' on your local machine.'
|
||||
echo "::error title=Lint failure::${ERROR_MESSAGE}"
|
||||
# make sure to return an error exitcode so that GitHub actions shows a red-cross
|
||||
exit 1
|
||||
# make sure to return an error exitcode so that GitHub actions shows a red-cross
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify no circular dependencies
|
||||
working-directory: ./packages/mermaid
|
||||
shell: bash
|
||||
run: |
|
||||
if ! pnpm run --filter mermaid checkCircle; then
|
||||
ERROR_MESSAGE='Circular dependency detected.'
|
||||
ERROR_MESSAGE+=' This should be fixed by removing the circular dependency.'
|
||||
ERROR_MESSAGE+=' Run `pnpm run --filter mermaid checkCircle` on your local machine'
|
||||
ERROR_MESSAGE+=' to see the circular dependency.'
|
||||
echo "::error title=Lint failure::${ERROR_MESSAGE}"
|
||||
# make sure to return an error exitcode so that GitHub actions shows a red-cross
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify Docs
|
||||
|
3
.gitignore
vendored
3
.gitignore
vendored
@@ -43,3 +43,6 @@ stats/
|
||||
**/contributor-names.json
|
||||
.pnpm-store
|
||||
.nyc_output
|
||||
|
||||
demos/dev/**
|
||||
!/demos/dev/example.html
|
||||
|
@@ -26,9 +26,14 @@ Install required packages:
|
||||
```bash
|
||||
# npx is required for first install as volta support for pnpm is not added yet.
|
||||
npx pnpm install
|
||||
pnpm test
|
||||
pnpm test # run unit tests
|
||||
pnpm dev # starts a dev server
|
||||
```
|
||||
|
||||
Open <http://localhost:9000> in your browser after starting the dev server.
|
||||
You can also duplicate the `example.html` file in `demos/dev`, rename it and add your own mermaid code to it.
|
||||
That will be served at <http://localhost:9000/dev/your-file-name.html>.
|
||||
|
||||
### Docker
|
||||
|
||||
If you are using docker and docker-compose, you have self-documented `run` bash script, which is a convenient alias for docker-compose commands:
|
||||
@@ -66,6 +71,8 @@ Documentation is necessary for all non bugfix/refactoring changes.
|
||||
|
||||
Only make changes to files that are in [`/packages/mermaid/src/docs`](packages/mermaid/src/docs)
|
||||
|
||||
**_DO NOT CHANGE FILES IN `/docs`_**
|
||||
**_DO NOT CHANGE FILES IN `/docs` MANUALLY_**
|
||||
|
||||
The `/docs` folder will be rebuilt and committed as part of a pre-commit hook.
|
||||
|
||||
[Join our slack community if you want closer contact!](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)
|
||||
|
@@ -60,7 +60,7 @@ Use Mermaid with your favorite applications, check out the list of [Integrations
|
||||
|
||||
You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations.md).
|
||||
|
||||
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/community/n00b-overview.md), [Usage](./docs/config/usage.md) and [Tutorials](./docs/config/Tutorials.md).
|
||||
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/intro/getting-started.md), [Usage](./docs/config/usage.md) and [Tutorials](./docs/config/Tutorials.md).
|
||||
|
||||
In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests.
|
||||
|
||||
|
@@ -55,7 +55,7 @@ Mermaid 通过允许用户创建便于修改的图表来解决这一难题,它
|
||||
Mermaid 甚至能让非程序员也能通过 [Mermaid Live Editor](https://mermaid.live/) 轻松创建详细的图表。<br/>
|
||||
你可以访问 [教程](./docs/config/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/ecosystem/integrations.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。
|
||||
|
||||
如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/community/n00b-overview.md), [用法](./docs/config/usage.md) 和 [教程](./docs/config/Tutorials.md).
|
||||
如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/intro/getting-started.md), [用法](./docs/config/usage.md) 和 [教程](./docs/config/Tutorials.md).
|
||||
|
||||
<!-- </Main description> -->
|
||||
|
||||
|
@@ -1,13 +0,0 @@
|
||||
/**
|
||||
* Mocked pie (picChart) diagram renderer
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest';
|
||||
|
||||
export const draw = vi.fn().mockImplementation(() => {
|
||||
return '';
|
||||
});
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
8
__mocks__/pieRenderer.ts
Normal file
8
__mocks__/pieRenderer.ts
Normal file
@@ -0,0 +1,8 @@
|
||||
/**
|
||||
* Mocked pie (picChart) diagram renderer
|
||||
*/
|
||||
import { vi } from 'vitest';
|
||||
|
||||
const draw = vi.fn().mockImplementation(() => '');
|
||||
|
||||
export const renderer = { draw };
|
@@ -38,12 +38,16 @@
|
||||
"docsy",
|
||||
"doku",
|
||||
"dompurify",
|
||||
"dont",
|
||||
"doublecircle",
|
||||
"edgechromium",
|
||||
"elems",
|
||||
"elkjs",
|
||||
"elle",
|
||||
"faber",
|
||||
"flatmap",
|
||||
"foswiki",
|
||||
"frontmatter",
|
||||
"ftplugin",
|
||||
"gantt",
|
||||
"gitea",
|
||||
@@ -91,6 +95,7 @@
|
||||
"nextra",
|
||||
"nikolay",
|
||||
"nirname",
|
||||
"npmjs",
|
||||
"orlandoni",
|
||||
"pathe",
|
||||
"pbrolin",
|
||||
@@ -106,6 +111,7 @@
|
||||
"rects",
|
||||
"reda",
|
||||
"redmine",
|
||||
"regexes",
|
||||
"rehype",
|
||||
"roledescription",
|
||||
"rozhkov",
|
||||
@@ -151,7 +157,8 @@
|
||||
"xlink",
|
||||
"yash",
|
||||
"yokozuna",
|
||||
"zenuml"
|
||||
"zenuml",
|
||||
"zune"
|
||||
],
|
||||
"patterns": [
|
||||
{ "name": "Markdown links", "pattern": "\\((.*)\\)", "description": "" },
|
||||
|
@@ -18,7 +18,11 @@ const utf8ToB64 = (str: string): string => {
|
||||
return Buffer.from(decodeURIComponent(encodeURIComponent(str))).toString('base64');
|
||||
};
|
||||
|
||||
const batchId: string = 'mermaid-batch-' + Cypress.env('CYPRESS_COMMIT') || Date.now().toString();
|
||||
const batchId: string =
|
||||
'mermaid-batch-' +
|
||||
(Cypress.env('useAppli')
|
||||
? Date.now().toString()
|
||||
: Cypress.env('CYPRESS_COMMIT') || Date.now().toString());
|
||||
|
||||
export const mermaidUrl = (
|
||||
graphStr: string,
|
||||
|
@@ -386,30 +386,6 @@ describe('Class diagram V2', () => {
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('18: should handle the direction statement with LR', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
direction LR
|
||||
class Student {
|
||||
-idCard : IdCard
|
||||
}
|
||||
class IdCard{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
class Bike{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
Student "1" --o "1" IdCard : carries
|
||||
Student "1" --o "1" Bike : rides
|
||||
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
it('17a: should handle the direction statement with BT', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -457,7 +433,31 @@ describe('Class diagram V2', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('18: should render a simple class diagram with notes', () => {
|
||||
it('18a: should handle the direction statement with LR', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
direction LR
|
||||
class Student {
|
||||
-idCard : IdCard
|
||||
}
|
||||
class IdCard{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
class Bike{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
Student "1" --o "1" IdCard : carries
|
||||
Student "1" --o "1" Bike : rides
|
||||
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
|
||||
it('18b: should render a simple class diagram with notes', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram-v2
|
||||
@@ -562,4 +562,13 @@ class C13["With Città foreign language"]
|
||||
`
|
||||
);
|
||||
});
|
||||
it('should render a simple class diagram with no members', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram-v2
|
||||
class Class10
|
||||
`,
|
||||
{ logLevel: 1, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -14,7 +14,6 @@ describe('Configuration and directives - nodes should be light blue', () => {
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Settings from initialize - nodes should be green', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -28,7 +27,6 @@ graph TD
|
||||
end `,
|
||||
{ theme: 'forest' }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Settings from initialize overriding themeVariable - nodes should be red', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -46,7 +44,6 @@ graph TD
|
||||
`,
|
||||
{ theme: 'base', themeVariables: { primaryColor: '#ff0000' }, logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Settings from directive - nodes should be grey', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -62,7 +59,24 @@ graph TD
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Settings from frontmatter - nodes should be grey', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
---
|
||||
config:
|
||||
theme: neutral
|
||||
---
|
||||
graph TD
|
||||
A(Start) --> B[/Another/]
|
||||
A[/Another/] --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('Settings from directive overriding theme variable - nodes should be red', () => {
|
||||
@@ -79,7 +93,6 @@ graph TD
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Settings from initialize and directive - nodes should be grey', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -95,7 +108,6 @@ graph TD
|
||||
`,
|
||||
{ theme: 'forest' }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Theme from initialize, directive overriding theme variable - nodes should be red', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -111,8 +123,71 @@ graph TD
|
||||
`,
|
||||
{ theme: 'base' }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Theme from initialize, frontmatter overriding theme variable - nodes should be red', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
---
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: '#ff0000'
|
||||
---
|
||||
graph TD
|
||||
A(Start) --> B[/Another/]
|
||||
A[/Another/] --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
`,
|
||||
{ theme: 'forest' }
|
||||
);
|
||||
});
|
||||
it('Theme from initialize, frontmatter overriding theme variable, directive overriding primaryColor - nodes should be red', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
---
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: '#00ff00'
|
||||
---
|
||||
%%{init: {'theme': 'base', 'themeVariables':{ 'primaryColor': '#ff0000'}}}%%
|
||||
graph TD
|
||||
A(Start) --> B[/Another/]
|
||||
A[/Another/] --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
`,
|
||||
{ theme: 'forest' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should render if values are not quoted properly', () => {
|
||||
// #ff0000 is not quoted properly, and will evaluate to null.
|
||||
// This test ensures that the rendering still works.
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: #ff0000
|
||||
---
|
||||
graph TD
|
||||
A(Start) --> B[/Another/]
|
||||
A[/Another/] --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
`,
|
||||
{ theme: 'forest' }
|
||||
);
|
||||
});
|
||||
|
||||
it('Theme variable from initialize, theme from directive - nodes should be red', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -127,13 +202,11 @@ graph TD
|
||||
`,
|
||||
{ themeVariables: { primaryColor: '#ff0000' } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
describe('when rendering several diagrams', () => {
|
||||
it('diagrams should not taint later diagrams', () => {
|
||||
const url = 'http://localhost:9000/theme-directives.html';
|
||||
cy.visit(url);
|
||||
cy.get('svg');
|
||||
cy.matchImageSnapshot('conf-and-directives.spec-when-rendering-several-diagrams-diagram-1');
|
||||
});
|
||||
});
|
||||
|
@@ -305,4 +305,21 @@ ORDER ||--|{ LINE-ITEM : contains
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should render entities with entity name aliases', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
erDiagram
|
||||
p[Person] {
|
||||
varchar(64) firstName
|
||||
varchar(64) lastName
|
||||
}
|
||||
c["Customer Account"] {
|
||||
varchar(128) email
|
||||
}
|
||||
p ||--o| c : has
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@@ -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
|
||||
|
@@ -330,6 +330,48 @@ describe('Gantt diagram', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a gantt diagram with tick is 2 milliseconds', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat SSS
|
||||
axisFormat %Lms
|
||||
tickInterval 2millisecond
|
||||
excludes weekends
|
||||
|
||||
section Section
|
||||
A task : a1, 000, 6ms
|
||||
Another task : after a1, 6ms
|
||||
section Another
|
||||
Task in sec : a2, 006, 3ms
|
||||
another task : 3ms
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a gantt diagram with tick is 2 seconds', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
dateFormat ss
|
||||
axisFormat %Ss
|
||||
tickInterval 2second
|
||||
excludes weekends
|
||||
|
||||
section Section
|
||||
A task : a1, 00, 6s
|
||||
Another task : after a1, 6s
|
||||
section Another
|
||||
Task in sec : 06, 3s
|
||||
another task : 3s
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a gantt diagram with tick is 15 minutes', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
|
@@ -1,89 +1,85 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('Pie Chart', () => {
|
||||
describe('pie chart', () => {
|
||||
it('should render a simple pie diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`pie title Sports in Sweden
|
||||
"Bandy": 40
|
||||
"Ice-Hockey": 80
|
||||
"Football": 90
|
||||
`
|
||||
pie title Sports in Sweden
|
||||
"Bandy" : 40
|
||||
"Ice-Hockey" : 80
|
||||
"Football" : 90
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a simple pie diagram with long labels', () => {
|
||||
imgSnapshotTest(
|
||||
`pie title NETFLIX
|
||||
"Time spent looking for movie": 90
|
||||
"Time spent watching it": 10
|
||||
`
|
||||
pie title NETFLIX
|
||||
"Time spent looking for movie" : 90
|
||||
"Time spent watching it" : 10
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a simple pie diagram with capital letters for labels', () => {
|
||||
imgSnapshotTest(
|
||||
`pie title What Voldemort doesn't have?
|
||||
"FRIENDS": 2
|
||||
"FAMILY": 3
|
||||
"NOSE": 45
|
||||
`
|
||||
pie title What Voldemort doesn't have?
|
||||
"FRIENDS" : 2
|
||||
"FAMILY" : 3
|
||||
"NOSE" : 45
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a pie diagram when useMaxWidth is true (default)', () => {
|
||||
renderGraph(
|
||||
`
|
||||
pie title Sports in Sweden
|
||||
"Bandy" : 40
|
||||
"Ice-Hockey" : 80
|
||||
"Football" : 90
|
||||
`pie title Sports in Sweden
|
||||
"Bandy": 40
|
||||
"Ice-Hockey": 80
|
||||
"Football": 90
|
||||
`,
|
||||
{ pie: { useMaxWidth: true } }
|
||||
);
|
||||
cy.get('svg').should((svg) => {
|
||||
expect(svg).to.have.attr('width', '100%');
|
||||
// expect(svg).to.have.attr('height');
|
||||
// const height = parseFloat(svg.attr('height'));
|
||||
// expect(height).to.eq(450);
|
||||
const style = svg.attr('style');
|
||||
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
||||
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
|
||||
expect(maxWidthValue).to.eq(984);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a pie diagram when useMaxWidth is false', () => {
|
||||
renderGraph(
|
||||
`
|
||||
pie title Sports in Sweden
|
||||
"Bandy" : 40
|
||||
"Ice-Hockey" : 80
|
||||
"Football" : 90
|
||||
`pie title Sports in Sweden
|
||||
"Bandy": 40
|
||||
"Ice-Hockey": 80
|
||||
"Football": 90
|
||||
`,
|
||||
{ pie: { useMaxWidth: false } }
|
||||
);
|
||||
cy.get('svg').should((svg) => {
|
||||
// const height = parseFloat(svg.attr('height'));
|
||||
const width = parseFloat(svg.attr('width'));
|
||||
// expect(height).to.eq(450);
|
||||
expect(width).to.eq(984);
|
||||
expect(svg).to.not.have.attr('style');
|
||||
});
|
||||
});
|
||||
it('should render a pie diagram when textPosition is set', () => {
|
||||
|
||||
it('should render a pie diagram when textPosition is setted', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
pie
|
||||
"Dogs": 50
|
||||
"Cats": 25
|
||||
`,
|
||||
`pie
|
||||
"Dogs": 50
|
||||
"Cats": 25
|
||||
`,
|
||||
{ logLevel: 1, pie: { textPosition: 0.9 } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a pie diagram with showData', () => {
|
||||
imgSnapshotTest(
|
||||
`pie showData
|
||||
"Dogs": 50
|
||||
"Cats": 25
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
@@ -38,12 +38,14 @@
|
||||
+quack()
|
||||
}
|
||||
class Fish{
|
||||
-int sizeInFeet
|
||||
-Listint sizeInFeet
|
||||
-canEat()
|
||||
}
|
||||
class Zebra{
|
||||
+bool is_wild
|
||||
+run()
|
||||
+run(List~T~, List~OT~)
|
||||
%% +run-composite(List~T, K~)
|
||||
+run-nested(List~List~OT~~)
|
||||
}
|
||||
|
||||
</pre>
|
||||
@@ -80,6 +82,7 @@
|
||||
Class01 : #size()
|
||||
Class01 : -int chimp
|
||||
Class01 : +int gorilla
|
||||
Class01 : +abstractAttribute string*
|
||||
class Class10~T~ {
|
||||
<<service>>
|
||||
int id
|
||||
@@ -122,6 +125,8 @@
|
||||
classDiagram
|
||||
direction LR
|
||||
Animal ()-- Dog
|
||||
Animal ()-- Cat
|
||||
note for Cat "should have no members area"
|
||||
Dog : bark()
|
||||
Dog : species()
|
||||
</pre>
|
||||
@@ -151,6 +156,7 @@
|
||||
~InternalProperty : string
|
||||
~AnotherInternalProperty : List~List~string~~
|
||||
}
|
||||
class People List~List~Person~~
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
|
34
demos/dev/example.html
Normal file
34
demos/dev/example.html
Normal file
@@ -0,0 +1,34 @@
|
||||
<!--Do not edit this file-->
|
||||
<!--Duplicate this file to any name you like, run `pnpm dev`, open http://localhost:9000/dev/name.html to view-->
|
||||
<html>
|
||||
<head>
|
||||
<title>Mermaid development page</title>
|
||||
</head>
|
||||
<body>
|
||||
<pre id="diagram" class="mermaid">
|
||||
graph TB
|
||||
a --> b
|
||||
a --> c
|
||||
b --> d
|
||||
c --> d
|
||||
</pre>
|
||||
|
||||
<div id="dynamicDiagram"></div>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from '/mermaid.esm.mjs';
|
||||
mermaid.parseError = function (err, hash) {
|
||||
console.error('Mermaid error: ', err);
|
||||
};
|
||||
mermaid.initialize({
|
||||
startOnLoad: true,
|
||||
logLevel: 0,
|
||||
});
|
||||
const value = `graph TD\nHello --> World`;
|
||||
const el = document.getElementById('dynamicDiagram');
|
||||
const { svg } = await mermaid.render('dd', value);
|
||||
console.log(svg);
|
||||
el.innerHTML = svg;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@@ -21,6 +21,8 @@
|
||||
<pre class="mermaid">
|
||||
---
|
||||
title: This is a title
|
||||
config:
|
||||
theme: forest
|
||||
---
|
||||
erDiagram
|
||||
%% title This is a title
|
||||
@@ -108,6 +110,35 @@
|
||||
}
|
||||
MANUFACTURER only one to zero or more CAR : makes
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<pre class="mermaid">
|
||||
erDiagram
|
||||
p[Person] {
|
||||
string firstName
|
||||
string lastName
|
||||
}
|
||||
a["Customer Account"] {
|
||||
string email
|
||||
}
|
||||
p ||--o| a : has
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<pre class="mermaid">
|
||||
erDiagram
|
||||
_customer_order {
|
||||
bigint id PK
|
||||
bigint customer_id FK
|
||||
text shipping_address
|
||||
text delivery_method
|
||||
timestamp_with_time_zone ordered_at
|
||||
numeric total_tax_amount
|
||||
numeric total_price
|
||||
text payment_method
|
||||
}
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
|
@@ -123,6 +123,13 @@
|
||||
|
||||
<h3>flowchart</h3>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
title: This is another complicated flow
|
||||
config:
|
||||
theme: base
|
||||
flowchart:
|
||||
curve: cardinal
|
||||
---
|
||||
flowchart LR
|
||||
sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
|
||||
|
||||
|
@@ -7,7 +7,6 @@
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||
<style>
|
||||
div.mermaid {
|
||||
/* font-family: 'trebuchet ms', verdana, arial; */
|
||||
font-family: 'Courier New', Courier, monospace !important;
|
||||
}
|
||||
</style>
|
||||
@@ -17,37 +16,32 @@
|
||||
<h1>Pie chart demos</h1>
|
||||
<pre class="mermaid">
|
||||
pie title Pets adopted by volunteers
|
||||
accTitle: simple pie char demo
|
||||
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
||||
"Dogs" : 386
|
||||
"Cats" : 85
|
||||
"Rats" : 15
|
||||
accTitle: simple pie char demo
|
||||
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
||||
"Dogs": 386
|
||||
"Cats": 85
|
||||
"Rats": 15
|
||||
</pre>
|
||||
|
||||
<hr />
|
||||
<pre class="mermaid">
|
||||
%%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
|
||||
pie
|
||||
title Key elements in Product X
|
||||
%%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}}}%%
|
||||
pie
|
||||
title Key elements in Product X
|
||||
accTitle: Key elements in Product X
|
||||
accDescr: This is a pie chart showing the key elements in Product X.
|
||||
"Calcium" : 42.96
|
||||
"Potassium" : 50.05
|
||||
"Magnesium" : 10.01
|
||||
"Iron" : 5
|
||||
accDescr: This is a pie chart showing the key elements in Product X.
|
||||
"Calcium": 42.96
|
||||
"Potassium": 50.05
|
||||
"Magnesium": 10.01
|
||||
"Iron": 5
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
// themeCSS: '.node rect { fill: red; }',
|
||||
logLevel: 3,
|
||||
securityLevel: 'loose',
|
||||
// flowchart: { curve: 'basis' },
|
||||
// gantt: { axisFormat: '%m/%d/%Y' },
|
||||
sequence: { actorMargin: 50 },
|
||||
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
@@ -3,20 +3,42 @@
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>States Mermaid Quick Test Page</title>
|
||||
<title>Sankey Mermaid Quick Test Page</title>
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||
<style>
|
||||
div.mermaid {
|
||||
/* font-family: 'trebuchet ms', verdana, arial; */
|
||||
font-family: 'Courier New', Courier, monospace !important;
|
||||
}
|
||||
</style>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Sankey diagram demos</h1>
|
||||
<h2>FY20-21 Performance</h2>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
sankey:
|
||||
showValues: true
|
||||
prefix: $
|
||||
suffix: B
|
||||
width: 800
|
||||
nodeAlignment: left
|
||||
---
|
||||
sankey-beta
|
||||
Revenue,Expenses,10
|
||||
Revenue,Profit,10
|
||||
Expenses,Manufacturing,5
|
||||
Expenses,Tax,3
|
||||
Expenses,Research,2
|
||||
</pre>
|
||||
|
||||
<h2>Energy flow</h2>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
sankey:
|
||||
showValues: false
|
||||
width: 1200
|
||||
height: 600
|
||||
linkColor: gradient
|
||||
nodeAlignment: justify
|
||||
---
|
||||
sankey-beta
|
||||
|
||||
Agricultural 'waste',Bio-conversion,124.729
|
||||
@@ -95,13 +117,7 @@
|
||||
theme: 'default',
|
||||
logLevel: 3,
|
||||
securityLevel: 'loose',
|
||||
sankey: {
|
||||
title: 'Hey, this is Sankey-Beta',
|
||||
width: 1200,
|
||||
height: 600,
|
||||
linkColor: 'gradient',
|
||||
nodeAlignment: 'justify',
|
||||
},
|
||||
startOnLoad: true,
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
@@ -1,13 +1,13 @@
|
||||
version: '3.9'
|
||||
services:
|
||||
mermaid:
|
||||
image: node:18.17.0-alpine3.18
|
||||
image: node:18.17.1-alpine3.18
|
||||
stdin_open: true
|
||||
tty: true
|
||||
working_dir: /mermaid
|
||||
mem_limit: '2G'
|
||||
mem_limit: '4G'
|
||||
environment:
|
||||
- NODE_OPTIONS=--max_old_space_size=2048
|
||||
- NODE_OPTIONS=--max_old_space_size=4096
|
||||
volumes:
|
||||
- ./:/mermaid
|
||||
- root_cache:/root/.cache
|
||||
@@ -17,7 +17,7 @@ services:
|
||||
- 9000:9000
|
||||
- 3333:3333
|
||||
cypress:
|
||||
image: cypress/included:12.17.2
|
||||
image: cypress/included:12.17.4
|
||||
stdin_open: true
|
||||
tty: true
|
||||
working_dir: /mermaid
|
||||
|
176
docs/community/code.md
Normal file
176
docs/community/code.md
Normal file
@@ -0,0 +1,176 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/community/code.md](../../packages/mermaid/src/docs/community/code.md).
|
||||
|
||||
# Contributing Code
|
||||
|
||||
The basic steps for contributing code are:
|
||||
|
||||
```mermaid-example
|
||||
graph LR
|
||||
git[1. Checkout a git branch] --> codeTest[2. Write tests and code] --> doc[3. Update documentation] --> submit[4. Submit a PR] --> review[5. Review and merge]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
git[1. Checkout a git branch] --> codeTest[2. Write tests and code] --> doc[3. Update documentation] --> submit[4. Submit a PR] --> review[5. Review and merge]
|
||||
```
|
||||
|
||||
1. **Create** and checkout a git branch and work on your code in the branch
|
||||
2. Write and update **tests** (unit and perhaps even integration (e2e) tests) (If you do TDD/BDD, the order might be different.)
|
||||
3. **Let users know** that things have changed or been added in the documents! This is often overlooked, but _critical_
|
||||
4. **Submit** your code as a _pull request_.
|
||||
5. Maintainers will **review** your code. If there are no changes necessary, the PR will be merged. Otherwise, make the requested changes and repeat.
|
||||
|
||||
## 1. Checkout a git branch
|
||||
|
||||
Mermaid uses a [Git Flow](https://guides.github.com/introduction/flow/)–inspired approach to branching.
|
||||
|
||||
Development is done in the `develop` branch.
|
||||
|
||||
Once development is done we create a `release/vX.X.X` branch from `develop` for testing.
|
||||
|
||||
Once the release happens we add a tag to the `release` branch and merge it with `master`. The live product and on-line documentation are what is in the `master` branch.
|
||||
|
||||
**All new work should be based on the `develop` branch.**
|
||||
|
||||
**When you are ready to do work, always, ALWAYS:**
|
||||
|
||||
1. Make sure you have the most up-to-date version of the `develop` branch. (fetch or pull to update it)
|
||||
2. Check out the `develop` branch
|
||||
3. Create a new branch for your work. Please name the branch following our naming convention below.
|
||||
|
||||
We use the following naming convention for branches:
|
||||
|
||||
```txt
|
||||
[feature | bug | chore | docs]/[issue number]_[short description using dashes ('-') or underscores ('_') instead of spaces]
|
||||
```
|
||||
|
||||
You can always check current [configuration of labelling and branch prefixes](https://github.com/mermaid-js/mermaid/blob/develop/.github/pr-labeler.yml)
|
||||
|
||||
- The first part is the **type** of change: a feature, bug, chore, or documentation change ('docs')
|
||||
- followed by a _slash_ (which helps to group like types together in many git tools)
|
||||
- followed by the **issue number**
|
||||
- followed by an _underscore_ ('\_')
|
||||
- followed by a short text description (but use dashes ('-') or underscores ('\_') instead of spaces)
|
||||
|
||||
If your work is specific to a single diagram type, it is a good idea to put the diagram type at the start of the description. This will help us keep release notes organized: it will help us keep changes for a diagram type together.
|
||||
|
||||
**Ex: A new feature described in issue 2945 that adds a new arrow type called 'florbs' to state diagrams**
|
||||
|
||||
`feature/2945_state-diagram-new-arrow-florbs`
|
||||
|
||||
**Ex: A bug described in issue 1123 that causes random ugly red text in multiple diagram types**
|
||||
`bug/1123_fix_random_ugly_red_text`
|
||||
|
||||
## 2. Write Tests
|
||||
|
||||
Tests ensure that each function, module, or part of code does what it says it will do. This is critically
|
||||
important when other changes are made to ensure that existing code is not broken (no regression).
|
||||
|
||||
Just as important, the tests act as _specifications:_ they specify what the code does (or should do).
|
||||
Whenever someone is new to a section of code, they should be able to read the tests to get a thorough understanding of what it does and why.
|
||||
|
||||
If you are fixing a bug, you should add tests to ensure that your code has actually fixed the bug, to specify/describe what the code is doing, and to ensure the bug doesn't happen again.
|
||||
(If there had been a test for the situation, the bug never would have happened in the first place.)
|
||||
You may need to change existing tests if they were inaccurate.
|
||||
|
||||
If you are adding a feature, you will definitely need to add tests. Depending on the size of your feature, you may need to add integration tests.
|
||||
|
||||
### Unit Tests
|
||||
|
||||
Unit tests are tests that test a single function or module. They are the easiest to write and the fastest to run.
|
||||
|
||||
Unit tests are mandatory all code except the renderers. (The renderers are tested with integration tests.)
|
||||
|
||||
We use [Vitest](https://vitest.dev) to run unit tests.
|
||||
|
||||
You can use the following command to run the unit tests:
|
||||
|
||||
```sh
|
||||
pnpm test
|
||||
```
|
||||
|
||||
When writing new tests, it's easier to have the tests automatically run as you make changes. You can do this by running the following command:
|
||||
|
||||
```sh
|
||||
pnpm test:watch
|
||||
```
|
||||
|
||||
### Integration/End-to-End (e2e) tests
|
||||
|
||||
These test the rendering and visual appearance of the diagrams.
|
||||
This ensures that the rendering of that feature in the e2e will be reviewed in the release process going forward. Less chance that it breaks!
|
||||
|
||||
To start working with the e2e tests:
|
||||
|
||||
1. Run `pnpm dev` to start the dev server
|
||||
2. Start **Cypress** by running `pnpm cypress:open`.
|
||||
|
||||
The rendering tests are very straightforward to create. There is a function `imgSnapshotTest`, which takes a diagram in text form and the mermaid options, and it renders that diagram in Cypress.
|
||||
|
||||
When running in CI it will take a snapshot of the rendered diagram and compare it with the snapshot from last build and flag it for review if it differs.
|
||||
|
||||
This is what a rendering test looks like:
|
||||
|
||||
```js
|
||||
it('should render forks and joins', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram
|
||||
state fork_state <<fork>>
|
||||
[*] --> fork_state
|
||||
fork_state --> State2
|
||||
fork_state --> State3
|
||||
|
||||
state join_state <<join>>
|
||||
State2 --> join_state
|
||||
State3 --> join_state
|
||||
join_state --> State4
|
||||
State4 --> [*]
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
```
|
||||
|
||||
**_\[TODO - running the tests against what is expected in development. ]_**
|
||||
|
||||
**_\[TODO - how to generate new screenshots]_**
|
||||
....
|
||||
|
||||
## 3. Update Documentation
|
||||
|
||||
If the users have no way to know that things have changed, then you haven't really _fixed_ anything for the users; you've just added to making Mermaid feel broken.
|
||||
Likewise, if users don't know that there is a new feature that you've implemented, it will forever remain unknown and unused.
|
||||
|
||||
The documentation has to be updated to users know that things have changed and added!
|
||||
If you are adding a new feature, add `(v<MERMAID_RELEASE_VERSION>+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
|
||||
|
||||
eg: `# Feature Name (v<MERMAID_RELEASE_VERSION>+)`
|
||||
|
||||
We know it can sometimes be hard to code _and_ write user documentation.
|
||||
|
||||
Our documentation is managed in `packages/mermaid/src/docs`. Details on how to edit is in the [Contributing Documentation](#contributing-documentation) section.
|
||||
|
||||
Create another issue specifically for the documentation.\
|
||||
You will need to help with the PR, but definitely ask for help if you feel stuck.
|
||||
When it feels hard to write stuff out, explaining it to someone and having that person ask you clarifying questions can often be 80% of the work!
|
||||
|
||||
When in doubt, write up and submit what you can. It can be clarified and refined later. (With documentation, something is better than nothing!)
|
||||
|
||||
## 4. Submit your pull request
|
||||
|
||||
**\[TODO - PR titles should start with (fix | feat | ....)]**
|
||||
|
||||
We make all changes via Pull Requests (PRs). As we have many Pull Requests from developers new to Mermaid, we have put in place a process wherein _knsv, Knut Sveidqvist_ is in charge of the final release process and the active maintainers are in charge of reviewing and merging most PRs.
|
||||
|
||||
- PRs will be reviewed by active maintainers, who will provide feedback and request changes as needed.
|
||||
- The maintainers will request a review from knsv, if necessary.
|
||||
- Once the PR is approved, the maintainers will merge the PR into the `develop` branch.
|
||||
- When a release is ready, the `release/x.x.x` branch will be created, extensively tested and knsv will be in charge of the release process.
|
||||
|
||||
**Reminder: Pull Requests should be submitted to the develop branch.**
|
@@ -6,15 +6,9 @@
|
||||
|
||||
# Contributing to Mermaid
|
||||
|
||||
## Contents
|
||||
|
||||
- [Technical Requirements and Setup](#technical-requirements-and-setup)
|
||||
- [Contributing Code](#contributing-code)
|
||||
- [Contributing Documentation](#contributing-documentation)
|
||||
- [Questions or Suggestions?](#questions-or-suggestions)
|
||||
- [Last Words](#last-words)
|
||||
|
||||
---
|
||||
> The following documentation describes how to work with Mermaid in your host environment.
|
||||
> There's also a [Docker installation guide](../community/docker-development.md)
|
||||
> if you prefer to work in a Docker environment.
|
||||
|
||||
So you want to help? That's great!
|
||||
|
||||
@@ -22,47 +16,68 @@ So you want to help? That's great!
|
||||
|
||||
Here are a few things to get you started on the right path.
|
||||
|
||||
## Technical Requirements and Setup
|
||||
## Get the Source Code
|
||||
|
||||
### Technical Requirements
|
||||
In GitHub, you first **fork** a repository when you are going to make changes and submit pull requests.
|
||||
|
||||
These are the tools we use for working with the code and documentation.
|
||||
Then you **clone** a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with.
|
||||
|
||||
[Fork mermaid](https://github.com/mermaid-js/mermaid/fork) to start contributing to the main project and its documentation, or [search for other repositories](https://github.com/orgs/mermaid-js/repositories).
|
||||
|
||||
[Here is a GitHub document that gives an overview of the process.](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
|
||||
|
||||
## Technical Requirements
|
||||
|
||||
> The following documentation describes how to work with Mermaid in your host environment.
|
||||
> There's also a [Docker installation guide](../community/docker-development.md)
|
||||
> if you prefer to work in a Docker environment.
|
||||
|
||||
These are the tools we use for working with the code and documentation:
|
||||
|
||||
- [volta](https://volta.sh/) to manage node versions.
|
||||
- [Node.js](https://nodejs.org/en/). `volta install node`
|
||||
- [pnpm](https://pnpm.io/) package manager. `volta install pnpm`
|
||||
- [npx](https://docs.npmjs.com/cli/v8/commands/npx) the packaged executor in npm. This is needed [to install pnpm.](#2-install-pnpm)
|
||||
- [npx](https://docs.npmjs.com/cli/v8/commands/npx) the packaged executor in npm. This is needed [to install pnpm.](#install-packages)
|
||||
|
||||
Follow [the setup steps below](#setup) to install them and verify they are working
|
||||
Follow the setup steps below to install them and start the development.
|
||||
|
||||
### Setup
|
||||
## Setup and Launch
|
||||
|
||||
Follow these steps to set up the environment you need to work on code and/or documentation.
|
||||
### Switch to project
|
||||
|
||||
#### 1. Fork and clone the repository
|
||||
|
||||
In GitHub, you first _fork_ a repository when you are going to make changes and submit pull requests.
|
||||
|
||||
Then you _clone_ a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with.
|
||||
|
||||
[Here is a GitHub document that gives an overview of the process.](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
|
||||
|
||||
#### 2. Install pnpm
|
||||
|
||||
Once you have cloned the repository onto your development machine, change into the `mermaid` project folder so that you can install `pnpm`. You will need `npx` to install pnpm because volta doesn't support it yet.
|
||||
|
||||
Ex:
|
||||
Once you have cloned the repository onto your development machine, change into the `mermaid` project folder (the top level directory of the mermaid project repository)
|
||||
|
||||
```bash
|
||||
# Change into the mermaid directory (the top level director of the mermaid project repository)
|
||||
cd mermaid
|
||||
# npx is required for first install because volta does not support pnpm yet
|
||||
npx pnpm install
|
||||
```
|
||||
|
||||
#### 3. Verify Everything Is Working
|
||||
### Install packages
|
||||
|
||||
Once you have installed pnpm, you can run the `test` script to verify that pnpm is working _and_ that the repository has been cloned correctly:
|
||||
Run `npx pnpm install`. You will need `npx` for this because volta doesn't support it yet.
|
||||
|
||||
```bash
|
||||
npx pnpm install # npx is required for first install
|
||||
```
|
||||
|
||||
### Launch
|
||||
|
||||
```bash
|
||||
npx pnpm run dev
|
||||
```
|
||||
|
||||
Now you are ready to make your changes! Edit whichever files in `src` as required.
|
||||
|
||||
Open <http://localhost:9000> in your browser, after starting the dev server.
|
||||
There is a list of demos that can be used to see and test your changes.
|
||||
|
||||
If you need a specific diagram, you can duplicate the `example.html` file in `/demos/dev` and add your own mermaid code to your copy.
|
||||
|
||||
That will be served at <http://localhost:9000/dev/your-file-name.html>.
|
||||
After making code changes, the dev server will rebuild the mermaid library. You will need to reload the browser page yourself to see the changes. (PRs for auto reload are welcome!)
|
||||
|
||||
## Verify Everything is Working
|
||||
|
||||
You can run the `test` script to verify that pnpm is working _and_ that the repository has been cloned correctly:
|
||||
|
||||
```bash
|
||||
pnpm test
|
||||
@@ -72,306 +87,6 @@ The `test` script and others are in the top-level `package.json` file.
|
||||
|
||||
All tests should run successfully without any errors or failures. (You might see _lint_ or _formatting_ warnings; those are ok during this step.)
|
||||
|
||||
### Docker
|
||||
|
||||
If you are using docker and docker-compose, you have self-documented `run` bash script, which is a convenient alias for docker-compose commands:
|
||||
|
||||
```bash
|
||||
./run install # npx pnpm install
|
||||
./run test # pnpm test
|
||||
```
|
||||
|
||||
## Contributing Code
|
||||
|
||||
The basic steps for contributing code are:
|
||||
|
||||
```mermaid-example
|
||||
graph LR
|
||||
git[1. Checkout a git branch] --> codeTest[2. Write tests and code] --> doc[3. Update documentation] --> submit[4. Submit a PR] --> review[5. Review and merge]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
graph LR
|
||||
git[1. Checkout a git branch] --> codeTest[2. Write tests and code] --> doc[3. Update documentation] --> submit[4. Submit a PR] --> review[5. Review and merge]
|
||||
```
|
||||
|
||||
1. **Create** and checkout a git branch and work on your code in the branch
|
||||
2. Write and update **tests** (unit and perhaps even integration (e2e) tests) (If you do TDD/BDD, the order might be different.)
|
||||
3. **Let users know** that things have changed or been added in the documents! This is often overlooked, but _critical_
|
||||
4. **Submit** your code as a _pull request_.
|
||||
5. Maintainers will **review** your code. If there are no changes necessary, the PR will be merged. Otherwise, make the requested changes and repeat.
|
||||
|
||||
### 1. Checkout a git branch
|
||||
|
||||
Mermaid uses a [Git Flow](https://guides.github.com/introduction/flow/)–inspired approach to branching.
|
||||
|
||||
Development is done in the `develop` branch.
|
||||
|
||||
Once development is done we create a `release/vX.X.X` branch from `develop` for testing.
|
||||
|
||||
Once the release happens we add a tag to the `release` branch and merge it with `master`. The live product and on-line documentation are what is in the `master` branch.
|
||||
|
||||
**All new work should be based on the `develop` branch.**
|
||||
|
||||
**When you are ready to do work, always, ALWAYS:**
|
||||
|
||||
1. Make sure you have the most up-to-date version of the `develop` branch. (fetch or pull to update it)
|
||||
2. Check out the `develop` branch
|
||||
3. Create a new branch for your work. Please name the branch following our naming convention below.
|
||||
|
||||
We use the follow naming convention for branches:
|
||||
|
||||
```text
|
||||
[feature | bug | chore | docs]/[issue number]_[short description using dashes ('-') or underscores ('_') instead of spaces]
|
||||
```
|
||||
|
||||
- The first part is the **type** of change: a feature, bug, chore, or documentation change ('docs')
|
||||
- followed by a _slash_ (which helps to group like types together in many git tools)
|
||||
- followed by the **issue number**
|
||||
- followed by an _underscore_ ('\_')
|
||||
- followed by a short text description (but use dashes ('-') or underscores ('\_') instead of spaces)
|
||||
|
||||
If your work is specific to a single diagram type, it is a good idea to put the diagram type at the start of the description. This will help us keep release notes organized: it will help us keep changes for a diagram type together.
|
||||
|
||||
**Ex: A new feature described in issue 2945 that adds a new arrow type called 'florbs' to state diagrams**
|
||||
|
||||
`feature/2945_state-diagram-new-arrow-florbs`
|
||||
|
||||
**Ex: A bug described in issue 1123 that causes random ugly red text in multiple diagram types**
|
||||
`bug/1123_fix_random_ugly_red_text`
|
||||
|
||||
### 2. Write Tests
|
||||
|
||||
Tests ensure that each function, module, or part of code does what it says it will do. This is critically
|
||||
important when other changes are made to ensure that existing code is not broken (no regression).
|
||||
|
||||
Just as important, the tests act as _specifications:_ they specify what the code does (or should do).
|
||||
Whenever someone is new to a section of code, they should be able to read the tests to get a thorough understanding of what it does and why.
|
||||
|
||||
If you are fixing a bug, you should add tests to ensure that your code has actually fixed the bug, to specify/describe what the code is doing, and to ensure the bug doesn't happen again.
|
||||
(If there had been a test for the situation, the bug never would have happened in the first place.)
|
||||
You may need to change existing tests if they were inaccurate.
|
||||
|
||||
If you are adding a feature, you will definitely need to add tests. Depending on the size of your feature, you may need to add integration tests.
|
||||
|
||||
#### Unit Tests
|
||||
|
||||
Unit tests are tests that test a single function or module. They are the easiest to write and the fastest to run.
|
||||
|
||||
Unit tests are mandatory all code except the renderers. (The renderers are tested with integration tests.)
|
||||
|
||||
We use [Vitest](https://vitest.dev) to run unit tests.
|
||||
|
||||
You can use the following command to run the unit tests:
|
||||
|
||||
```sh
|
||||
pnpm test
|
||||
```
|
||||
|
||||
When writing new tests, it's easier to have the tests automatically run as you make changes. You can do this by running the following command:
|
||||
|
||||
```sh
|
||||
pnpm test:watch
|
||||
```
|
||||
|
||||
#### Integration/End-to-End (e2e) tests
|
||||
|
||||
These test the rendering and visual appearance of the diagrams.
|
||||
This ensures that the rendering of that feature in the e2e will be reviewed in the release process going forward. Less chance that it breaks!
|
||||
|
||||
To start working with the e2e tests:
|
||||
|
||||
1. Run `pnpm dev` to start the dev server
|
||||
2. Start **Cypress** by running `pnpm cypress:open`.
|
||||
|
||||
The rendering tests are very straightforward to create. There is a function `imgSnapshotTest`, which takes a diagram in text form and the mermaid options, and it renders that diagram in Cypress.
|
||||
|
||||
When running in CI it will take a snapshot of the rendered diagram and compare it with the snapshot from last build and flag it for review if it differs.
|
||||
|
||||
This is what a rendering test looks like:
|
||||
|
||||
```js
|
||||
it('should render forks and joins', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
stateDiagram
|
||||
state fork_state <<fork>>
|
||||
[*] --> fork_state
|
||||
fork_state --> State2
|
||||
fork_state --> State3
|
||||
|
||||
state join_state <<join>>
|
||||
State2 --> join_state
|
||||
State3 --> join_state
|
||||
join_state --> State4
|
||||
State4 --> [*]
|
||||
`,
|
||||
{ logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
```
|
||||
|
||||
**_\[TODO - running the tests against what is expected in development. ]_**
|
||||
|
||||
**_\[TODO - how to generate new screenshots]_**
|
||||
....
|
||||
|
||||
### 3. Update Documentation
|
||||
|
||||
If the users have no way to know that things have changed, then you haven't really _fixed_ anything for the users; you've just added to making Mermaid feel broken.
|
||||
Likewise, if users don't know that there is a new feature that you've implemented, it will forever remain unknown and unused.
|
||||
|
||||
The documentation has to be updated to users know that things have changed and added!
|
||||
If you are adding a new feature, add `(v<MERMAID_RELEASE_VERSION>+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
|
||||
|
||||
eg: `# Feature Name (v<MERMAID_RELEASE_VERSION>+)`
|
||||
|
||||
We know it can sometimes be hard to code _and_ write user documentation.
|
||||
|
||||
Our documentation is managed in `packages/mermaid/src/docs`. Details on how to edit is in the [Contributing Documentation](#contributing-documentation) section.
|
||||
|
||||
Create another issue specifically for the documentation.\
|
||||
You will need to help with the PR, but definitely ask for help if you feel stuck.
|
||||
When it feels hard to write stuff out, explaining it to someone and having that person ask you clarifying questions can often be 80% of the work!
|
||||
|
||||
When in doubt, write up and submit what you can. It can be clarified and refined later. (With documentation, something is better than nothing!)
|
||||
|
||||
### 4. Submit your pull request
|
||||
|
||||
**\[TODO - PR titles should start with (fix | feat | ....)]**
|
||||
|
||||
We make all changes via Pull Requests (PRs). As we have many Pull Requests from developers new to Mermaid, we have put in place a process wherein _knsv, Knut Sveidqvist_ is in charge of the final release process and the active maintainers are in charge of reviewing and merging most PRs.
|
||||
|
||||
- PRs will be reviewed by active maintainers, who will provide feedback and request changes as needed.
|
||||
- The maintainers will request a review from knsv, if necessary.
|
||||
- Once the PR is approved, the maintainers will merge the PR into the `develop` branch.
|
||||
- When a release is ready, the `release/x.x.x` branch will be created, extensively tested and knsv will be in charge of the release process.
|
||||
|
||||
**Reminder: Pull Requests should be submitted to the develop branch.**
|
||||
|
||||
## Contributing Documentation
|
||||
|
||||
**_\[TODO: This section is still a WIP. It still needs MAJOR revision.]_**
|
||||
|
||||
If it is not in the documentation, it's like it never happened. Wouldn't that be sad? With all the effort that was put into the feature?
|
||||
|
||||
The docs are located in the `packages/mermaid/src/docs` folder and are written in Markdown. Just pick the right section and start typing.
|
||||
|
||||
The contents of [mermaid.js.org](https://mermaid.js.org/) are based on the docs from the `master` branch.
|
||||
Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid.js.org/) once published.
|
||||
|
||||
### How to Contribute to Documentation
|
||||
|
||||
We are a little less strict here, it is OK to commit directly in the `develop` branch if you are a collaborator.
|
||||
|
||||
The documentation is located in the `packages/mermaid/src/docs` directory and organized according to relevant subfolder.
|
||||
|
||||
The `docs` folder will be automatically generated when committing to `packages/mermaid/src/docs` and **should not** be edited manually.
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
classDef default fill:#fff,color:black,stroke:black
|
||||
|
||||
source["files in /packages/mermaid/src/docs\n(changes should be done here)"] -- automatic processing\nto generate the final documentation--> published["files in /docs\ndisplayed on the official documentation site"]
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
classDef default fill:#fff,color:black,stroke:black
|
||||
|
||||
source["files in /packages/mermaid/src/docs\n(changes should be done here)"] -- automatic processing\nto generate the final documentation--> published["files in /docs\ndisplayed on the official documentation site"]
|
||||
|
||||
```
|
||||
|
||||
You can use `note`, `tip`, `warning` and `danger` in triple backticks to add a note, tip, warning or danger box.
|
||||
Do not use vitepress specific markdown syntax `::: warning` as it will not be processed correctly.
|
||||
|
||||
````
|
||||
```note
|
||||
Note content
|
||||
```
|
||||
|
||||
```tip
|
||||
Tip content
|
||||
```
|
||||
|
||||
```warning
|
||||
Warning content
|
||||
```
|
||||
|
||||
```danger
|
||||
Danger content
|
||||
```
|
||||
|
||||
````
|
||||
|
||||
> **Note**
|
||||
> If the change is _only_ to the documentation, you can get your changes published to the site quicker by making a PR to the `master` branch.
|
||||
|
||||
We encourage contributions to the documentation at [packages/mermaid/src/docs in the _develop_ branch](https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/src/docs).
|
||||
|
||||
**_DO NOT CHANGE FILES IN `/docs`_**
|
||||
|
||||
### The official documentation site
|
||||
|
||||
**[The mermaid documentation site](https://mermaid.js.org/) is powered by [Vitepress](https://vitepress.vuejs.org/).**
|
||||
|
||||
To run the documentation site locally:
|
||||
|
||||
1. Run `pnpm --filter mermaid run docs:dev` to start the dev server. (Or `pnpm docs:dev` inside the `packages/mermaid` directory.)
|
||||
2. Open <http://localhost:3333/> in your browser.
|
||||
|
||||
Markdown is used to format the text, for more information about Markdown [see the GitHub Markdown help page](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax).
|
||||
|
||||
To edit Docs on your computer:
|
||||
|
||||
_\[TODO: need to keep this in sync with [check out a git branch in Contributing Code above](#1-checkout-a-git-branch) ]_
|
||||
|
||||
1. Create a fork of the develop branch to work on.
|
||||
2. Find the Markdown file (.md) to edit in the `packages/mermaid/src/docs` directory.
|
||||
3. Make changes or add new documentation.
|
||||
4. Commit changes to your branch and push it to GitHub (which should create a new branch).
|
||||
5. Create a Pull Request of your fork.
|
||||
|
||||
To edit Docs on GitHub:
|
||||
|
||||
1. Login to [GitHub.com](https://www.github.com).
|
||||
2. Navigate to [packages/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/src/docs) in the mermaid-js repository.
|
||||
3. To edit a file, click the pencil icon at the top-right of the file contents panel.
|
||||
4. Describe what you changed in the **Propose file change** section, located at the bottom of the page.
|
||||
5. Submit your changes by clicking the button **Propose file change** at the bottom (by automatic creation of a fork and a new branch).
|
||||
6. Visit the Actions tab in Github, `https://github.com/<Your Username>/mermaid/actions` and enable the actions for your fork. This will ensure that the documentation is built and updated in your fork.
|
||||
7. Create a Pull Request of your newly forked branch by clicking the green **Create Pull Request** button.
|
||||
|
||||
### Documentation organization: Sidebar navigation
|
||||
|
||||
If you want to propose changes to how the documentation is _organized_, such as adding a new section or re-arranging or renaming a section, you must update the **sidebar navigation.**
|
||||
|
||||
The sidebar navigation is defined in [the vitepress configuration file config.ts](../.vitepress/config.ts).
|
||||
|
||||
## Questions or Suggestions?
|
||||
|
||||
#### First search to see if someone has already asked (and hopefully been answered) or suggested the same thing.
|
||||
|
||||
- Search in Discussions
|
||||
- Search in open Issues
|
||||
- Search in closed Issues
|
||||
|
||||
If you find an open issue or discussion thread that is similar to your question but isn't answered, you can let us know that you are also interested in it.
|
||||
Use the GitHub reactions to add a thumbs-up to the issue or discussion thread.
|
||||
|
||||
This helps the team know the relative interest in something and helps them set priorities and assignments.
|
||||
|
||||
Feel free to add to the discussion on the issue or topic.
|
||||
|
||||
If you can't find anything that already addresses your question or suggestion, _open a new issue:_
|
||||
|
||||
Log in to [GitHub.com](https://www.github.com), open or append to an issue [using the GitHub issue tracker of the mermaid-js repository](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Area%3A+Documentation%22).
|
||||
|
||||
### How to Contribute a Suggestion
|
||||
|
||||
## Last Words
|
||||
|
||||
Don't get daunted if it is hard in the beginning. We have a great community with only encouraging words. So, if you get stuck, ask for help and hints in the Slack forum. If you want to show off something good, show it off there.
|
||||
|
109
docs/community/docker-development.md
Normal file
109
docs/community/docker-development.md
Normal file
@@ -0,0 +1,109 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/community/docker-development.md](../../packages/mermaid/src/docs/community/docker-development.md).
|
||||
|
||||
# Contributing to Mermaid via Docker
|
||||
|
||||
> The following documentation describes how to work with Mermaid in a Docker environment.
|
||||
> There's also a [host installation guide](../community/development.md)
|
||||
> if you prefer to work without a Docker environment.
|
||||
|
||||
So you want to help? That's great!
|
||||
|
||||

|
||||
|
||||
Here are a few things to get you started on the right path.
|
||||
|
||||
## Get the Source Code
|
||||
|
||||
In GitHub, you first **fork** a repository when you are going to make changes and submit pull requests.
|
||||
|
||||
Then you **clone** a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with.
|
||||
|
||||
[Fork mermaid](https://github.com/mermaid-js/mermaid/fork) to start contributing to the main project and its documentation, or [search for other repositories](https://github.com/orgs/mermaid-js/repositories).
|
||||
|
||||
[Here is a GitHub document that gives an overview of the process.](https://docs.github.com/en/get-started/quickstart/fork-a-repo)
|
||||
|
||||
## Technical Requirements
|
||||
|
||||
> The following documentation describes how to work with Mermaid in a Docker environment.
|
||||
> There's also a [host installation guide](../community/development.md)
|
||||
> if you prefer to work without a Docker environment.
|
||||
|
||||
[Install Docker](https://docs.docker.com/engine/install/). And that is pretty much all you need.
|
||||
|
||||
Optionally, to run GUI (Cypress) within Docker you will also need an X11 server installed.
|
||||
You might already have it installed, so check this by running:
|
||||
|
||||
```bash
|
||||
echo $DISPLAY
|
||||
```
|
||||
|
||||
If the `$DISPLAY` variable is not empty, then an X11 server is running. Otherwise you may need to install one.
|
||||
|
||||
## Setup and Launch
|
||||
|
||||
### Switch to project
|
||||
|
||||
Once you have cloned the repository onto your development machine, change into the `mermaid` project folder (the top level directory of the mermaid project repository)
|
||||
|
||||
```bash
|
||||
cd mermaid
|
||||
```
|
||||
|
||||
### Make `./run` executable
|
||||
|
||||
For development using Docker there is a self-documented `run` bash script, which provides convenient aliases for `docker compose` commands.
|
||||
|
||||
Ensure `./run` script is executable:
|
||||
|
||||
```bash
|
||||
chmod +x run
|
||||
```
|
||||
|
||||
> **💡 Tip**
|
||||
> To get detailed help simply type `./run` or `./run help`.
|
||||
>
|
||||
> It also has short _Development quick start guide_ embedded.
|
||||
|
||||
### Install packages
|
||||
|
||||
```bash
|
||||
./run pnpm install # Install packages
|
||||
```
|
||||
|
||||
### Launch
|
||||
|
||||
```bash
|
||||
./run dev
|
||||
```
|
||||
|
||||
Now you are ready to make your changes! Edit whichever files in `src` as required.
|
||||
|
||||
Open <http://localhost:9000> in your browser, after starting the dev server.
|
||||
There is a list of demos that can be used to see and test your changes.
|
||||
|
||||
If you need a specific diagram, you can duplicate the `example.html` file in `/demos/dev` and add your own mermaid code to your copy.
|
||||
|
||||
That will be served at <http://localhost:9000/dev/your-file-name.html>.
|
||||
After making code changes, the dev server will rebuild the mermaid library. You will need to reload the browser page yourself to see the changes. (PRs for auto reload are welcome!)
|
||||
|
||||
## Verify Everything is Working
|
||||
|
||||
```bash
|
||||
./run pnpm test
|
||||
```
|
||||
|
||||
The `test` script and others are in the top-level `package.json` file.
|
||||
|
||||
All tests should run successfully without any errors or failures. (You might see _lint_ or _formatting_ warnings; those are ok during this step.)
|
||||
|
||||
## Last Words
|
||||
|
||||
Don't get daunted if it is hard in the beginning. We have a great community with only encouraging words. So, if you get stuck, ask for help and hints in the Slack forum. If you want to show off something good, show it off there.
|
||||
|
||||
[Join our Slack community if you want closer contact!](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)
|
||||
|
||||

|
105
docs/community/documentation.md
Normal file
105
docs/community/documentation.md
Normal file
@@ -0,0 +1,105 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/community/documentation.md](../../packages/mermaid/src/docs/community/documentation.md).
|
||||
|
||||
# Contributing Documentation
|
||||
|
||||
**_\[TODO: This section is still a WIP. It still needs MAJOR revision.]_**
|
||||
|
||||
If it is not in the documentation, it's like it never happened. Wouldn't that be sad? With all the effort that was put into the feature?
|
||||
|
||||
The docs are located in the `packages/mermaid/src/docs` folder and are written in Markdown. Just pick the right section and start typing.
|
||||
|
||||
The contents of [mermaid.js.org](https://mermaid.js.org/) are based on the docs from the `master` branch.
|
||||
Updates committed to the `master` branch are reflected in the [Mermaid Docs](https://mermaid.js.org/) once published.
|
||||
|
||||
## How to Contribute to Documentation
|
||||
|
||||
We are a little less strict here, it is OK to commit directly in the `develop` branch if you are a collaborator.
|
||||
|
||||
The documentation is located in the `packages/mermaid/src/docs` directory and organized according to relevant subfolder.
|
||||
|
||||
The `docs` folder will be automatically generated when committing to `packages/mermaid/src/docs` and **should not** be edited manually.
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
classDef default fill:#fff,color:black,stroke:black
|
||||
|
||||
source["files in /packages/mermaid/src/docs\n(changes should be done here)"] -- automatic processing\nto generate the final documentation--> published["files in /docs\ndisplayed on the official documentation site"]
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
classDef default fill:#fff,color:black,stroke:black
|
||||
|
||||
source["files in /packages/mermaid/src/docs\n(changes should be done here)"] -- automatic processing\nto generate the final documentation--> published["files in /docs\ndisplayed on the official documentation site"]
|
||||
|
||||
```
|
||||
|
||||
You can use `note`, `tip`, `warning` and `danger` in triple backticks to add a note, tip, warning or danger box.
|
||||
Do not use vitepress specific markdown syntax `::: warning` as it will not be processed correctly.
|
||||
|
||||
````markdown
|
||||
```note
|
||||
Note content
|
||||
```
|
||||
|
||||
```tip
|
||||
Tip content
|
||||
```
|
||||
|
||||
```warning
|
||||
Warning content
|
||||
```
|
||||
|
||||
```danger
|
||||
Danger content
|
||||
```
|
||||
````
|
||||
|
||||
> **Note**
|
||||
> If the change is _only_ to the documentation, you can get your changes published to the site quicker by making a PR to the `master` branch. In that case, your branch should be based on master, not develop.
|
||||
|
||||
We encourage contributions to the documentation at [packages/mermaid/src/docs in the _develop_ branch](https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/src/docs).
|
||||
|
||||
**_DO NOT CHANGE FILES IN `/docs`_**
|
||||
|
||||
## The official documentation site
|
||||
|
||||
**[The mermaid documentation site](https://mermaid.js.org/) is powered by [Vitepress](https://vitepress.vuejs.org/).**
|
||||
|
||||
To run the documentation site locally:
|
||||
|
||||
1. Run `pnpm --filter mermaid run docs:dev` to start the dev server. (Or `pnpm docs:dev` inside the `packages/mermaid` directory.)
|
||||
2. Open <http://localhost:3333/> in your browser.
|
||||
|
||||
Markdown is used to format the text, for more information about Markdown [see the GitHub Markdown help page](https://help.github.com/en/github/writing-on-github/basic-writing-and-formatting-syntax).
|
||||
|
||||
To edit Docs on your computer:
|
||||
|
||||
_\[TODO: need to keep this in sync with [check out a git branch in Contributing Code above](#1-checkout-a-git-branch) ]_
|
||||
|
||||
1. Create a fork of the develop branch to work on.
|
||||
2. Find the Markdown file (.md) to edit in the `packages/mermaid/src/docs` directory.
|
||||
3. Make changes or add new documentation.
|
||||
4. Commit changes to your branch and push it to GitHub (which should create a new branch).
|
||||
5. Create a Pull Request from the branch of your fork.
|
||||
|
||||
To edit Docs on GitHub:
|
||||
|
||||
1. Login to [GitHub.com](https://www.github.com).
|
||||
2. Navigate to [packages/mermaid/src/docs](https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/src/docs) in the mermaid-js repository.
|
||||
3. To edit a file, click the pencil icon at the top-right of the file contents panel.
|
||||
4. Describe what you changed in the **Propose file change** section, located at the bottom of the page.
|
||||
5. Submit your changes by clicking the button **Propose file change** at the bottom (by automatic creation of a fork and a new branch).
|
||||
6. Visit the Actions tab in Github, `https://github.com/<Your Username>/mermaid/actions` and enable the actions for your fork. This will ensure that the documentation is built and updated in your fork.
|
||||
7. Create a Pull Request of your newly forked branch by clicking the green **Create Pull Request** button.
|
||||
|
||||
## Documentation organization: Sidebar navigation
|
||||
|
||||
If you want to propose changes to how the documentation is _organized_, such as adding a new section or re-arranging or renaming a section, you must update the **sidebar navigation.**
|
||||
|
||||
The sidebar navigation is defined in [the vitepress configuration file config.ts](../.vitepress/config.ts).
|
@@ -1,74 +0,0 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/community/n00b-overview.md](../../packages/mermaid/src/docs/community/n00b-overview.md).
|
||||
|
||||
# Overview for Beginners
|
||||
|
||||
**Explaining with a Diagram**
|
||||
|
||||
A picture is worth a thousand words, a good diagram is undoubtedly worth more. They make understanding easier.
|
||||
|
||||
## Creating and Maintaining Diagrams
|
||||
|
||||
Anyone who has used Visio, or (God Forbid) Excel to make a Gantt Chart, knows how hard it is to create, edit and maintain good visualizations.
|
||||
|
||||
Diagrams/Charts are significant but also become obsolete/inaccurate very fast. This catch-22 hobbles the productivity of teams.
|
||||
|
||||
# Doc Rot in Diagrams
|
||||
|
||||
Doc-Rot kills diagrams as quickly as it does text, but it takes hours in a desktop application to produce a diagram.
|
||||
|
||||
Mermaid seeks to change using markdown-inspired syntax. The process is a quicker, less complicated, and more convenient way of going from concept to visualization.
|
||||
|
||||
It is a relatively straightforward solution to a significant hurdle with the software teams.
|
||||
|
||||
# Definition of Terms/ Dictionary
|
||||
|
||||
**Mermaid text definitions can be saved for later reuse and editing.**
|
||||
|
||||
> These are the Mermaid diagram definitions inside `<div>` tags, with the `class=mermaid`.
|
||||
|
||||
```html
|
||||
<pre class="mermaid">
|
||||
graph TD
|
||||
A[Client] --> B[Load Balancer]
|
||||
B --> C[Server01]
|
||||
B --> D[Server02]
|
||||
</pre>
|
||||
```
|
||||
|
||||
**render**
|
||||
|
||||
> This is the core function of the Mermaid API. It reads all the `Mermaid Definitions` inside `div` tags and returns an SVG file, based on the definition.
|
||||
|
||||
**Nodes**
|
||||
|
||||
> These are the boxes that contain text or otherwise discrete pieces of each diagram, separated generally by arrows, except for Gantt Charts and User Journey Diagrams. They will be referred often in the instructions. Read for Diagram Specific [Syntax](../intro/n00b-syntaxReference.md)
|
||||
|
||||
## Advantages of using Mermaid
|
||||
|
||||
- Ease to generate, modify and render diagrams when you make them.
|
||||
- The number of integrations and plugins it has.
|
||||
- You can add it to your or companies website.
|
||||
- Diagrams can be created through comments like this in a script:
|
||||
|
||||
## The catch-22 of Diagrams and Charts:
|
||||
|
||||
**Diagramming and charting is a large waste of developer's time, but not having diagrams ruins productivity.**
|
||||
|
||||
Mermaid solves this by reducing the time and effort required to create diagrams and charts.
|
||||
|
||||
Because, the text base for the diagrams allows it to be updated easily. Also, it can be made part of production scripts (and other pieces of code). So less time is spent on documenting, as a separate task.
|
||||
|
||||
## Catching up with Development
|
||||
|
||||
Being based on markdown, Mermaid can be used, not only by accomplished front-end developers, but by most computer savvy people to render diagrams, at much faster speeds.
|
||||
In fact one can pick up the syntax for it quite easily from the examples given and there are many tutorials available in the internet.
|
||||
|
||||
## Mermaid is for everyone.
|
||||
|
||||
Video [Tutorials](https://mermaid.js.org/config/Tutorials.html) are also available for the mermaid [live editor](https://mermaid.live/).
|
||||
|
||||
Alternatively you can use Mermaid [Plug-Ins](https://mermaid-js.github.io/mermaid/#/./integrations), with tools you already use, like Google Docs.
|
@@ -19,7 +19,7 @@ For instance:
|
||||
|
||||
#### Store data found during parsing
|
||||
|
||||
There are some jison specific sub steps here where the parser stores the data encountered when parsing the diagram, this data is later used by the renderer. You can during the parsing call a object provided to the parser by the user of the parser. This object can be called during parsing for storing data.
|
||||
There are some jison specific sub steps here where the parser stores the data encountered when parsing the diagram, this data is later used by the renderer. You can during the parsing call an object provided to the parser by the user of the parser. This object can be called during parsing for storing data.
|
||||
|
||||
```jison
|
||||
statement
|
||||
@@ -35,7 +35,7 @@ In the extract of the grammar above, it is defined that a call to the setTitle m
|
||||
> **Note**
|
||||
> Make sure that the `parseError` function for the parser is defined and calling `mermaid.parseError`. This way a common way of detecting parse errors is provided for the end-user.
|
||||
|
||||
For more info look in the example diagram type:
|
||||
For more info look at the example diagram type:
|
||||
|
||||
The `yy` object has the following function:
|
||||
|
||||
@@ -54,7 +54,7 @@ parser.yy = db;
|
||||
|
||||
### Step 2: Rendering
|
||||
|
||||
Write a renderer that given the data found during parsing renders the diagram. To look at an example look at sequenceRenderer.js rather then the flowchart renderer as this is a more generic example.
|
||||
Write a renderer that given the data found during parsing renders the diagram. To look at an example look at sequenceRenderer.js rather than the flowchart renderer as this is a more generic example.
|
||||
|
||||
Place the renderer in the diagram folder.
|
||||
|
||||
@@ -62,7 +62,7 @@ Place the renderer in the diagram folder.
|
||||
|
||||
The second thing to do is to add the capability to detect the new diagram to type to the detectType in `diagram-api/detectType.ts`. The detection should return a key for the new diagram type.
|
||||
[This key will be used to as the aria roledescription](#aria-roledescription), so it should be a word that clearly describes the diagram type.
|
||||
For example, if your new diagram use a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader
|
||||
For example, if your new diagram uses a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader
|
||||
would voice that as "U-M-L Deployment diagram." Another good key would be "deploymentDiagram" because that would be voiced as "Deployment Diagram." A bad key would be "deployment" because that would not sufficiently describe the diagram.
|
||||
|
||||
Note that the diagram type key does not have to be the same as the diagram keyword chosen for the [grammar](#grammar), but it is helpful if they are the same.
|
||||
@@ -122,7 +122,7 @@ There are a few features that are common between the different types of diagrams
|
||||
- Themes, there is a common way to modify the styling of diagrams in Mermaid.
|
||||
- Comments should follow mermaid standards
|
||||
|
||||
Here some pointers on how to handle these different areas.
|
||||
Here are some pointers on how to handle these different areas.
|
||||
|
||||
## Accessibility
|
||||
|
||||
@@ -140,7 +140,7 @@ See [the definition of aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/
|
||||
|
||||
### accessible title and description
|
||||
|
||||
The syntax for accessible titles and descriptions is described in [the Accessibility documenation section.](../config/accessibility.md)
|
||||
The syntax for accessible titles and descriptions is described in [the Accessibility documentation section.](../config/accessibility.md)
|
||||
|
||||
As a design goal, the jison syntax should be similar between the diagrams.
|
||||
|
||||
|
26
docs/community/questions-and-suggestions.md
Normal file
26
docs/community/questions-and-suggestions.md
Normal file
@@ -0,0 +1,26 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/community/questions-and-suggestions.md](../../packages/mermaid/src/docs/community/questions-and-suggestions.md).
|
||||
|
||||
# Questions or Suggestions?
|
||||
|
||||
**_\[TODO: This section is still a WIP. It still needs MAJOR revision.]_**
|
||||
|
||||
## First search to see if someone has already asked (and hopefully been answered) or suggested the same thing.
|
||||
|
||||
- Search in Discussions
|
||||
- Search in open Issues
|
||||
- Search in closed Issues
|
||||
|
||||
If you find an open issue or discussion thread that is similar to your question but isn't answered, you can let us know that you are also interested in it.
|
||||
Use the GitHub reactions to add a thumbs-up to the issue or discussion thread.
|
||||
|
||||
This helps the team know the relative interest in something and helps them set priorities and assignments.
|
||||
|
||||
Feel free to add to the discussion on the issue or topic.
|
||||
|
||||
If you can't find anything that already addresses your question or suggestion, _open a new issue:_
|
||||
|
||||
Log in to [GitHub.com](https://www.github.com), open or append to an issue [using the GitHub issue tracker of the mermaid-js repository](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Area%3A+Documentation%22).
|
@@ -32,7 +32,7 @@ The definitions that can be generated the Live-Editor are also backwards-compati
|
||||
|
||||
## Mermaid with HTML
|
||||
|
||||
Examples are provided in [Getting Started](../intro/n00b-gettingStarted.md)
|
||||
Examples are provided in [Getting Started](../intro/getting-started.md)
|
||||
|
||||
**CodePen Examples:**
|
||||
|
||||
@@ -62,10 +62,10 @@ from IPython.display import Image, display
|
||||
import matplotlib.pyplot as plt
|
||||
|
||||
def mm(graph):
|
||||
graphbytes = graph.encode("ascii")
|
||||
base64_bytes = base64.b64encode(graphbytes)
|
||||
base64_string = base64_bytes.decode("ascii")
|
||||
display(Image(url="https://mermaid.ink/img/" + base64_string))
|
||||
graphbytes = graph.encode("utf8")
|
||||
base64_bytes = base64.b64encode(graphbytes)
|
||||
base64_string = base64_bytes.decode("ascii")
|
||||
display(Image(url="https://mermaid.ink/img/" + base64_string))
|
||||
|
||||
mm("""
|
||||
graph LR;
|
||||
|
@@ -2,9 +2,9 @@
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/n00b-advanced.md](../../packages/mermaid/src/docs/config/n00b-advanced.md).
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/advanced.md](../../packages/mermaid/src/docs/config/advanced.md).
|
||||
|
||||
# Advanced n00b mermaid (Coming soon..)
|
||||
# Advanced mermaid (Coming soon..)
|
||||
|
||||
## splitting mermaid code from html
|
||||
|
@@ -10,10 +10,41 @@ When mermaid starts, configuration is extracted to determine a configuration to
|
||||
|
||||
- The default configuration
|
||||
- Overrides at the site level are set by the initialize call, and will be applied to all diagrams in the site/app. The term for this is the **siteConfig**.
|
||||
- Directives - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
|
||||
- Frontmatter (v\<MERMAID_RELEASE_VERSION>+) - diagram authors can update select configuration parameters in the frontmatter of the diagram. These are applied to the render config.
|
||||
- Directives (Deprecated by Frontmatter) - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
|
||||
|
||||
**The render config** is configuration that is used when rendering by applying these configurations.
|
||||
|
||||
## Frontmatter config
|
||||
|
||||
The entire mermaid configuration (except the secure configs) can be overridden by the diagram author in the frontmatter of the diagram. The frontmatter is a YAML block at the top of the diagram.
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
title: Hello Title
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: "#00ff00"
|
||||
---
|
||||
flowchart
|
||||
Hello --> World
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Hello Title
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: "#00ff00"
|
||||
---
|
||||
flowchart
|
||||
Hello --> World
|
||||
|
||||
```
|
||||
|
||||
## Theme configuration
|
||||
|
||||
## Starting mermaid
|
||||
|
@@ -6,6 +6,9 @@
|
||||
|
||||
# Directives
|
||||
|
||||
> **Warning**
|
||||
> Directives are deprecated from v\<MERMAID_RELEASE_VERSION>. Please use the `config` key in frontmatter to pass configuration. See [Configuration](./configuration.md) for more details.
|
||||
|
||||
## Directives
|
||||
|
||||
Directives give a diagram author the capability to alter the appearance of a diagram before rendering by changing the applied configuration.
|
||||
@@ -123,7 +126,7 @@ The following code snippet changes `theme` to `forest`:
|
||||
|
||||
`%%{init: { "theme": "forest" } }%%`
|
||||
|
||||
Possible theme values are: `default`,`base`, `dark`, `forest` and `neutral`.
|
||||
Possible theme values are: `default`, `base`, `dark`, `forest` and `neutral`.
|
||||
Default Value is `default`.
|
||||
|
||||
Example:
|
||||
@@ -288,7 +291,7 @@ Let us see an example:
|
||||
sequenceDiagram
|
||||
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Alice->Bob: Good.
|
||||
Bob->Alice: Cool
|
||||
```
|
||||
@@ -297,7 +300,7 @@ Bob->Alice: Cool
|
||||
sequenceDiagram
|
||||
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Alice->Bob: Good.
|
||||
Bob->Alice: Cool
|
||||
```
|
||||
@@ -314,7 +317,7 @@ By applying that snippet to the diagram above, `wrap` will be enabled:
|
||||
%%{init: { "sequence": { "wrap": true, "width":300 } } }%%
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Alice->Bob: Good.
|
||||
Bob->Alice: Cool
|
||||
```
|
||||
@@ -323,7 +326,7 @@ Bob->Alice: Cool
|
||||
%%{init: { "sequence": { "wrap": true, "width":300 } } }%%
|
||||
sequenceDiagram
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
|
||||
Alice->Bob: Good.
|
||||
Bob->Alice: Cool
|
||||
```
|
||||
|
@@ -16,4 +16,4 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L77)
|
||||
[mermaidAPI.ts:78](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L78)
|
||||
|
@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L97)
|
||||
[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98)
|
||||
|
||||
---
|
||||
|
||||
@@ -51,4 +51,4 @@ The svg code for the rendered graph.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:87](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L87)
|
||||
[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88)
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:7](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L7)
|
||||
[config.ts:8](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L8)
|
||||
|
||||
## Functions
|
||||
|
||||
@@ -26,9 +26,9 @@ Pushes in a directive to the configuration
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :---------- | :---- | :----------------------- |
|
||||
| `directive` | `any` | The directive to push in |
|
||||
| Name | Type | Description |
|
||||
| :---------- | :-------------- | :----------------------- |
|
||||
| `directive` | `MermaidConfig` | The directive to push in |
|
||||
|
||||
#### Returns
|
||||
|
||||
@@ -36,7 +36,7 @@ Pushes in a directive to the configuration
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L191)
|
||||
[config.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L188)
|
||||
|
||||
---
|
||||
|
||||
@@ -60,7 +60,7 @@ The currentConfig
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:137](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L137)
|
||||
[config.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L131)
|
||||
|
||||
---
|
||||
|
||||
@@ -118,7 +118,7 @@ The siteConfig
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:223](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L223)
|
||||
[config.ts:218](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L218)
|
||||
|
||||
---
|
||||
|
||||
@@ -147,7 +147,7 @@ options in-place
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:152](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L152)
|
||||
[config.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L146)
|
||||
|
||||
---
|
||||
|
||||
@@ -242,10 +242,10 @@ The new siteConfig
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------------ | :-------------- |
|
||||
| `siteCfg` | `MermaidConfig` |
|
||||
| `_directives` | `any`\[] |
|
||||
| Name | Type |
|
||||
| :------------ | :----------------- |
|
||||
| `siteCfg` | `MermaidConfig` |
|
||||
| `_directives` | `MermaidConfig`\[] |
|
||||
|
||||
#### Returns
|
||||
|
||||
@@ -253,7 +253,7 @@ The new siteConfig
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:14](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L14)
|
||||
[config.ts:15](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L15)
|
||||
|
||||
---
|
||||
|
||||
|
@@ -10,17 +10,17 @@
|
||||
|
||||
### configKeys
|
||||
|
||||
• `Const` **configKeys**: `string`\[]
|
||||
• `Const` **configKeys**: `Set`<`string`>
|
||||
|
||||
#### Defined in
|
||||
|
||||
[defaultConfig.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L266)
|
||||
[defaultConfig.ts:268](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L268)
|
||||
|
||||
---
|
||||
|
||||
### default
|
||||
|
||||
• `Const` **default**: `Partial`<`MermaidConfig`>
|
||||
• `Const` **default**: `RequiredDeep`<`MermaidConfig`>
|
||||
|
||||
Default mermaid configuration options.
|
||||
|
||||
@@ -30,4 +30,4 @@ Non-JSON JS default values are listed in this file, e.g. functions, or
|
||||
|
||||
#### Defined in
|
||||
|
||||
[defaultConfig.ts:16](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L16)
|
||||
[defaultConfig.ts:18](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L18)
|
||||
|
@@ -25,7 +25,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L81)
|
||||
[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82)
|
||||
|
||||
## Variables
|
||||
|
||||
@@ -96,7 +96,7 @@ mermaid.initialize(config);
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:668](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L668)
|
||||
[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673)
|
||||
|
||||
## Functions
|
||||
|
||||
@@ -127,7 +127,7 @@ Return the last node appended
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:309](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L309)
|
||||
[mermaidAPI.ts:310](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L310)
|
||||
|
||||
---
|
||||
|
||||
@@ -153,7 +153,7 @@ the cleaned up svgCode
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:255](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L255)
|
||||
[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256)
|
||||
|
||||
---
|
||||
|
||||
@@ -179,7 +179,7 @@ the string with all the user styles
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:184](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L184)
|
||||
[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185)
|
||||
|
||||
---
|
||||
|
||||
@@ -202,7 +202,7 @@ the string with all the user styles
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:232](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L232)
|
||||
[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233)
|
||||
|
||||
---
|
||||
|
||||
@@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:168](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L168)
|
||||
[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169)
|
||||
|
||||
---
|
||||
|
||||
@@ -249,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:154](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L154)
|
||||
[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155)
|
||||
|
||||
---
|
||||
|
||||
@@ -269,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L125)
|
||||
[mermaidAPI.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L126)
|
||||
|
||||
---
|
||||
|
||||
@@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:286](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L286)
|
||||
[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287)
|
||||
|
||||
---
|
||||
|
||||
@@ -320,4 +320,4 @@ Remove any existing elements from the given document
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:359](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L359)
|
||||
[mermaidAPI.ts:360](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L360)
|
||||
|
@@ -41,7 +41,7 @@ pnpm add mermaid
|
||||
|
||||
**Hosting mermaid on a web page:**
|
||||
|
||||
> Note:This topic explored in greater depth in the [User Guide for Beginners](../intro/n00b-gettingStarted.md)
|
||||
> Note: This topic is explored in greater depth in the [User Guide for Beginners](../intro/getting-started.md)
|
||||
|
||||
The easiest way to integrate mermaid on a web page requires two elements:
|
||||
|
||||
@@ -100,7 +100,7 @@ Mermaid can load multiple diagrams, in the same page.
|
||||
|
||||
## Enabling Click Event and Tags in Nodes
|
||||
|
||||
A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduce in version 8.2 as a security improvement, aimed at preventing malicious use.
|
||||
A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduced in version 8.2 as a security improvement, aimed at preventing malicious use.
|
||||
|
||||
**It is the site owner's responsibility to discriminate between trustworthy and untrustworthy user-bases and we encourage the use of discretion.**
|
||||
|
||||
@@ -115,13 +115,13 @@ Values:
|
||||
- **strict**: (**default**) HTML tags in the text are encoded and click functionality is disabled.
|
||||
- **antiscript**: HTML tags in text are allowed (only script elements are removed) and click functionality is enabled.
|
||||
- **loose**: HTML tags in text are allowed and click functionality is enabled.
|
||||
- **sandbox**: With this security level, all rendering takes place in a sandboxed iframe. This prevent any JavaScript from running in the context. This may hinder interactive functionality of the diagram, like scripts, popups in the sequence diagram, links to other tabs or targets, etc.
|
||||
- **sandbox**: With this security level, all rendering takes place in a sandboxed iframe. This prevents any JavaScript from running in the context. This may hinder interactive functionality of the diagram, like scripts, popups in the sequence diagram, links to other tabs or targets, etc.
|
||||
|
||||
> **Note**
|
||||
> This changes the default behaviour of mermaid so that after upgrade to 8.2, unless the `securityLevel` is not changed, tags in flowcharts are encoded as tags and clicking is disabled.
|
||||
> **sandbox** security level is still in the beta version.
|
||||
|
||||
**If you are taking responsibility for the diagram source security you can set the `securityLevel` to a value of your choosing . This allows clicks and tags are allowed.**
|
||||
**If you are taking responsibility for the diagram source security you can set the `securityLevel` to a value of your choosing. This allows clicks and tags are allowed.**
|
||||
|
||||
**To change `securityLevel`, you have to call `mermaid.initialize`:**
|
||||
|
||||
|
@@ -49,6 +49,7 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [Mermaid plugin for GitBook](https://github.com/wwformat/gitbook-plugin-mermaid-pdf)
|
||||
- [LiveBook](https://livebook.dev) (**Native support**)
|
||||
- [Atlassian Products](https://www.atlassian.com)
|
||||
- [Mermaid Live Editor for Confluence Cloud](https://marketplace.atlassian.com/apps/1231571/mermaid-live-editor-for-confluence?hosting=cloud&tab=overview)
|
||||
- [Mermaid Plugin for Confluence](https://marketplace.atlassian.com/apps/1214124/mermaid-plugin-for-confluence?hosting=server&tab=overview)
|
||||
- [CloudScript.io Addon](https://marketplace.atlassian.com/apps/1219878/cloudscript-io-mermaid-addon?hosting=cloud&tab=overview)
|
||||
- [Auto convert diagrams in Jira](https://github.com/coddingtonbear/jirafs-mermaid)
|
||||
|
@@ -2,13 +2,13 @@
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/intro/n00b-gettingStarted.md](../../packages/mermaid/src/docs/intro/n00b-gettingStarted.md).
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/intro/getting-started.md](../../packages/mermaid/src/docs/intro/getting-started.md).
|
||||
|
||||
# A Mermaid User-Guide for Beginners
|
||||
|
||||
Mermaid is composed of three parts: Deployment, Syntax and Configuration.
|
||||
|
||||
This section talks about the different ways to deploy Mermaid. Learning the [Syntax](n00b-syntaxReference.md) would be of great help to the beginner.
|
||||
This section talks about the different ways to deploy Mermaid. Learning the [Syntax](syntax-reference.md) would be of great help to the beginner.
|
||||
|
||||
> Generally the live editor is enough for most general uses of mermaid, and is a good place to start learning.
|
||||
|
||||
@@ -53,7 +53,7 @@ graph TD
|
||||
|
||||
In the `Code` section one can write or edit raw mermaid code, and instantly `Preview` the rendered result on the panel beside it.
|
||||
|
||||
The `Configuration` Section is for changing the appearance and behavior of mermaid diagrams. An easy introduction to mermaid configuration is found in the [Advanced usage](../config/n00b-advanced.md) section. A complete configuration reference cataloging the default values can be found on the [mermaidAPI](../config/setup/README.md) page.
|
||||
The `Configuration` Section is for changing the appearance and behavior of mermaid diagrams. An easy introduction to mermaid configuration is found in the [Advanced usage](../config/advanced.md) section. A complete configuration reference cataloging the default values can be found on the [mermaidAPI](../config/setup/README.md) page.
|
||||
|
||||

|
||||
|
||||
@@ -103,7 +103,7 @@ When writing the .html file, we give two instructions inside the html code to th
|
||||
|
||||
a. The mermaid code for the diagram we want to create.
|
||||
|
||||
b. The importing of mermaid library through the `mermaid.esm.mjs` or `mermaid.esm.min.mjs` and the `mermaid.initialize()` call, which dictates the appearance of diagrams and also starts the rendering process .
|
||||
b. The importing of mermaid library through the `mermaid.esm.mjs` or `mermaid.esm.min.mjs` and the `mermaid.initialize()` call, which dictates the appearance of diagrams and also starts the rendering process.
|
||||
|
||||
**a. The embedded mermaid diagram definition inside a `<pre class="mermaid">`:**
|
||||
|
||||
@@ -221,4 +221,4 @@ In this example mermaid.js is referenced in `src` as a separate JavaScript file,
|
||||
|
||||
**Comments from Knut Sveidqvist, creator of mermaid:**
|
||||
|
||||
- In early versions of mermaid, the `<script>` tag was invoked in the `<head>` part of the web page. Nowadays we can place it in the `<body>` as seen above. Older parts of the documentation frequently reflects the previous way which still works.
|
||||
- In early versions of mermaid, the `<script>` tag was invoked in the `<head>` part of the web page. Nowadays we can place it in the `<body>` as seen above. Older parts of the documentation frequently reflect the previous way which still works.
|
@@ -10,11 +10,22 @@
|
||||
|
||||
It is a JavaScript based diagramming and charting tool that renders Markdown-inspired text definitions to create and modify diagrams dynamically.
|
||||
|
||||
> If you are familiar with Markdown you should have no problem learning [Mermaid's Syntax](n00b-syntaxReference.md).
|
||||
> If you are familiar with Markdown you should have no problem learning [Mermaid's Syntax](syntax-reference.md).
|
||||
|
||||
<img src="/header.png" alt="" />
|
||||
|
||||
[](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [](https://www.npmjs.com/package/mermaid) [](https://bundlephobia.com/package/mermaid) [](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [](https://www.jsdelivr.com/package/npm/mermaid) [](https://www.npmjs.com/package/mermaid) [](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [](https://twitter.com/mermaidjs_)
|
||||
<div class='badges'>
|
||||
|
||||
[](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml)
|
||||
[](https://www.npmjs.com/package/mermaid)
|
||||
[](https://bundlephobia.com/package/mermaid)
|
||||
[](https://coveralls.io/github/mermaid-js/mermaid?branch=master)
|
||||
[](https://www.jsdelivr.com/package/npm/mermaid)
|
||||
[](https://www.npmjs.com/package/mermaid)
|
||||
[](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)
|
||||
[](https://twitter.com/mermaidjs_)
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Mermaid book banner -->
|
||||
|
||||
@@ -33,7 +44,7 @@ Mermaid allows even non-programmers to easily create detailed and diagrams throu
|
||||
[Tutorials](../config/Tutorials.md) has video tutorials.
|
||||
Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](../ecosystem/integrations.md).
|
||||
|
||||
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](../community/n00b-overview.md) and [Usage](../config/usage.md).
|
||||
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](../intro/getting-started.md) and [Usage](../config/usage.md).
|
||||
|
||||
🌐 [CDN](https://www.jsdelivr.com/package/npm/mermaid) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](../community/development.md) | 🔌 [Plug-Ins](../ecosystem/integrations.md)
|
||||
|
||||
@@ -273,9 +284,9 @@ quadrantChart
|
||||
|
||||
## Installation
|
||||
|
||||
**In depth guides and examples can be found at [Getting Started](./n00b-gettingStarted.md) and [Usage](../config/usage.md).**
|
||||
**In depth guides and examples can be found at [Getting Started](./getting-started.md) and [Usage](../config/usage.md).**
|
||||
|
||||
**It would also be helpful to learn more about mermaid's [Syntax](./n00b-syntaxReference.md).**
|
||||
**It would also be helpful to learn more about mermaid's [Syntax](./syntax-reference.md).**
|
||||
|
||||
### CDN
|
||||
|
||||
@@ -334,7 +345,19 @@ Together we could continue the work with things like:
|
||||
|
||||
Don't hesitate to contact me if you want to get involved!
|
||||
|
||||
## For contributors
|
||||
## Contributors
|
||||
|
||||
<div class='badges'>
|
||||
|
||||
[](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%21%22)
|
||||
[](https://github.com/mermaid-js/mermaid/graphs/contributors)
|
||||
[](https://github.com/mermaid-js/mermaid/graphs/contributors)
|
||||
|
||||
</div>
|
||||
|
||||
Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out.
|
||||
|
||||
Detailed information about how to contribute can be found in the [contribution guideline](/community/development).
|
||||
|
||||
### Requirements
|
||||
|
||||
@@ -342,7 +365,7 @@ Don't hesitate to contact me if you want to get involved!
|
||||
- [Node.js](https://nodejs.org/en/). `volta install node`
|
||||
- [pnpm](https://pnpm.io/) package manager. `volta install pnpm`
|
||||
|
||||
## Development Installation
|
||||
### Development Installation
|
||||
|
||||
```bash
|
||||
git clone git@github.com:mermaid-js/mermaid.git
|
||||
@@ -379,21 +402,7 @@ Update version number in `package.json`.
|
||||
npm publish
|
||||
```
|
||||
|
||||
The above command generates files into the `dist` folder and publishes them to \<npmjs.org>.
|
||||
|
||||
## Related projects
|
||||
|
||||
- [Command Line Interface](https://github.com/mermaid-js/mermaid-cli)
|
||||
- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor)
|
||||
- [HTTP Server](https://github.com/TomWright/mermaid-server)
|
||||
|
||||
## Contributors
|
||||
|
||||
[](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%21%22) [](https://github.com/mermaid-js/mermaid/graphs/contributors) [](https://github.com/mermaid-js/mermaid/graphs/contributors)
|
||||
|
||||
Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out.
|
||||
|
||||
Detailed information about how to contribute can be found in the [contribution guide](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md)
|
||||
The above command generates files into the `dist` folder and publishes them to [npmjs.com](https://www.npmjs.com/).
|
||||
|
||||
## Security and safe diagrams
|
||||
|
||||
@@ -424,20 +433,15 @@ A quick note from Knut Sveidqvist:
|
||||
_Mermaid was created by Knut Sveidqvist for easier documentation._
|
||||
|
||||
<style scoped>
|
||||
#contributors + p,
|
||||
#about-mermaid + p + p + blockquote + img + p
|
||||
{
|
||||
display: flex
|
||||
.badges > p {
|
||||
display: flex;
|
||||
}
|
||||
|
||||
#contributors + p a,
|
||||
#about-mermaid + p + p + blockquote + img + p a
|
||||
{
|
||||
margin: 0 0.5rem
|
||||
.badges > p > a {
|
||||
margin: 0 0.5rem;
|
||||
}
|
||||
|
||||
.dark #VPContent > div > div > div.content > div > main > div > div > img
|
||||
{
|
||||
.dark #VPContent > div > div > div.content > div > main > div > div > img {
|
||||
filter: invert(1) hue-rotate(217deg) contrast(0.72);
|
||||
}
|
||||
</style>
|
||||
|
@@ -2,7 +2,7 @@
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/intro/n00b-syntaxReference.md](../../packages/mermaid/src/docs/intro/n00b-syntaxReference.md).
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/intro/syntax-reference.md](../../packages/mermaid/src/docs/intro/syntax-reference.md).
|
||||
|
||||
# Diagram Syntax
|
||||
|
||||
@@ -42,7 +42,7 @@ erDiagram
|
||||
PRODUCT ||--o{ ORDER-ITEM : "ordered in"
|
||||
```
|
||||
|
||||
The [Getting Started](./n00b-gettingStarted.md) section can also provide some practical examples of mermaid syntax.
|
||||
The [Getting Started](./getting-started.md) section can also provide some practical examples of mermaid syntax.
|
||||
|
||||
## Diagram Breaking
|
||||
|
||||
@@ -56,30 +56,30 @@ One should **beware the use of some words or symbols** that can break diagrams.
|
||||
| 'end' | The word "End" can cause Flowcharts and Sequence diagrams to break | Wrap them in quotation marks to prevent breakage. |
|
||||
| [Nodes inside Nodes](../syntax/flowchart.md?id=special-characters-that-break-syntax) | Mermaid gets confused with nested shapes | wrap them in quotation marks to prevent breaking |
|
||||
|
||||
### Mermaid Live Editor
|
||||
## Mermaid Live Editor
|
||||
|
||||
Now, that you've seen what you should not add to your diagrams, you can play around with them in the [Mermaid Live Editor](https://mermaid.live).
|
||||
|
||||
# Configuration
|
||||
## Configuration
|
||||
|
||||
Configuration is the third part of Mermaid, after deployment and syntax. It deals with the different ways that Mermaid can be customized across different deployments.
|
||||
|
||||
If you are interested in altering and customizing your Mermaid Diagrams, you will find the methods and values available for [Configuration](../config/setup/README.md) here. It includes themes.
|
||||
This section will introduce the different methods of configuring the behaviors and appearances of Mermaid Diagrams.
|
||||
The following are the most commonly used methods, and they are all tied to Mermaid [Deployment](./n00b-gettingStarted.md) methods.
|
||||
The following are the most commonly used methods, and they are all tied to Mermaid [Deployment](./getting-started.md) methods.
|
||||
|
||||
### Configuration Section in the [Live Editor](https://mermaid.live).
|
||||
|
||||
Here you can edit certain values to change the behavior and appearance of the diagram.
|
||||
|
||||
### [The initialize() call](https://mermaid-js.github.io/mermaid/#/n00b-gettingStarted?id=_3-calling-the-javascript-api),
|
||||
### [The initialize() call](./getting-started.md#_3-calling-the-javascript-api)
|
||||
|
||||
Used when Mermaid is called via an API, or through a `<script>` tag.
|
||||
|
||||
### [Directives](../config/directives.md),
|
||||
### [Directives](../config/directives.md)
|
||||
|
||||
Allows for the limited reconfiguration of a diagram just before it is rendered. It can alter the font style, color and other aesthetic aspects of the diagram. You can pass a directive alongside your definition inside `%%{ }%%`. It can be done either above or below your diagram definition.
|
||||
|
||||
### [Theme Manipulation](../config/theming.md):
|
||||
### [Theme Manipulation](../config/theming.md)
|
||||
|
||||
An application of using Directives to change [Themes](../config/theming.md). `Theme` is a value within Mermaid's configuration that dictates the color scheme for diagrams.
|
@@ -6,8 +6,8 @@
|
||||
|
||||
# Announcements
|
||||
|
||||
## [From Chaos to Clarity: Exploring Mind Maps with MermaidJS](https://www.mermaidchart.com/blog/posts/from-chaos-to-clarity-exploring-mind-maps-with-mermaidjs)
|
||||
## [Special cases broke Microsoft Zune and can ruin your code base too](https://www.mermaidchart.com/blog/posts/special-cases-broke-microsoft-zune-and-can-ruin-your-code-base-too/)
|
||||
|
||||
24 July 2023 · 4 mins
|
||||
23 August 2023 · 15 mins
|
||||
|
||||
Introducing the concept of mind mapping as a tool for organizing complex information, and highlights Mermaid as a user-friendly software that simplifies the creation and editing of mind maps for applications in IT solution design, business decision-making, and knowledge organization.
|
||||
Read about the pitfalls of special cases in programming, illustrating how they can lead to complexity, diminish readability, and create maintenance challenges.
|
||||
|
@@ -6,6 +6,24 @@
|
||||
|
||||
# Blog
|
||||
|
||||
## [Special cases broke Microsoft Zune and can ruin your code base too](https://www.mermaidchart.com/blog/posts/special-cases-broke-microsoft-zune-and-can-ruin-your-code-base-too/)
|
||||
|
||||
23 August 2023 · 15 mins
|
||||
|
||||
Read about the pitfalls of special cases in programming, illustrating how they can lead to complexity, diminish readability, and create maintenance challenges.
|
||||
|
||||
## [New AI chatbot now available on Mermaid Chart to simplify text-based diagram creation](https://www.mermaidchart.com/blog/posts/ai-chatbot-now-available-on-mermaid-chart-to-simplify-text-based-diagram-creation/)
|
||||
|
||||
14 August 2023 · 4 mins
|
||||
|
||||
Introducing Mermaid Chart’s new AI chatbot, a diagramming assistant that simplifies text-based diagram creation for everyone, from developers to educators, offering features to start, edit, and fix diagrams, and embodying our vision to make diagramming accessible, user-friendly, and fun.
|
||||
|
||||
## [Believe It or Not, You Still Need an Online UML Diagram Tool](https://www.mermaidchart.com/blog/posts/uml-diagram-tool/)
|
||||
|
||||
14 August 2023 · 8 mins
|
||||
|
||||
A UML diagram tool helps developers and other professionals quickly create and share UML diagrams that communicate information about complex software systems.
|
||||
|
||||
## [From Chaos to Clarity: Exploring Mind Maps with MermaidJS](https://www.mermaidchart.com/blog/posts/from-chaos-to-clarity-exploring-mind-maps-with-mermaidjs)
|
||||
|
||||
24 July 2023 · 4 mins
|
||||
|
@@ -90,7 +90,7 @@ Mermaid syntax for ER diagrams is compatible with PlantUML, with an extension to
|
||||
|
||||
Where:
|
||||
|
||||
- `first-entity` is the name of an entity. Names must begin with an alphabetic character and may also contain digits, hyphens, and underscores.
|
||||
- `first-entity` is the name of an entity. Names must begin with an alphabetic character or an underscore (from v\<MERMAID_RELEASE_VERSION>+), and may also contain digits and hyphens.
|
||||
- `relationship` describes the way that both entities inter-relate. See below.
|
||||
- `second-entity` is the name of the other entity.
|
||||
- `relationship-label` describes the relationship from the perspective of the first entity.
|
||||
@@ -198,6 +198,34 @@ erDiagram
|
||||
|
||||
The `type` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The `name` values follow a similar format to `type`, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
|
||||
|
||||
### Entity Name Aliases (v\<MERMAID_RELEASE_VERSION>+)
|
||||
|
||||
An alias can be added to an entity using square brackets. If provided, the alias will be showed in the diagram instead of the entity name.
|
||||
|
||||
```mermaid-example
|
||||
erDiagram
|
||||
p[Person] {
|
||||
string firstName
|
||||
string lastName
|
||||
}
|
||||
a["Customer Account"] {
|
||||
string email
|
||||
}
|
||||
p ||--o| a : has
|
||||
```
|
||||
|
||||
```mermaid
|
||||
erDiagram
|
||||
p[Person] {
|
||||
string firstName
|
||||
string lastName
|
||||
}
|
||||
a["Customer Account"] {
|
||||
string email
|
||||
}
|
||||
p ||--o| a : has
|
||||
```
|
||||
|
||||
#### Attribute Keys and Comments
|
||||
|
||||
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
|
||||
|
@@ -748,6 +748,48 @@ flowchart LR
|
||||
B1 --> B2
|
||||
```
|
||||
|
||||
#### Limitation
|
||||
|
||||
If any of a subgraph's nodes are linked to the outside, subgraph direction will be ignored. Instead the subgraph will inherit the direction of the parent graph:
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
subgraph subgraph1
|
||||
direction TB
|
||||
top1[top] --> bottom1[bottom]
|
||||
end
|
||||
subgraph subgraph2
|
||||
direction TB
|
||||
top2[top] --> bottom2[bottom]
|
||||
end
|
||||
%% ^ These subgraphs are identical, except for the links to them:
|
||||
|
||||
%% Link *to* subgraph1: subgraph1 direction is mantained
|
||||
outside --> subgraph1
|
||||
%% Link *within* subgraph2:
|
||||
%% subgraph2 inherits the direction of the top-level graph (LR)
|
||||
outside ---> top2
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph subgraph1
|
||||
direction TB
|
||||
top1[top] --> bottom1[bottom]
|
||||
end
|
||||
subgraph subgraph2
|
||||
direction TB
|
||||
top2[top] --> bottom2[bottom]
|
||||
end
|
||||
%% ^ These subgraphs are identical, except for the links to them:
|
||||
|
||||
%% Link *to* subgraph1: subgraph1 direction is mantained
|
||||
outside --> subgraph1
|
||||
%% Link *within* subgraph2:
|
||||
%% subgraph2 inherits the direction of the top-level graph (LR)
|
||||
outside ---> top2
|
||||
```
|
||||
|
||||
## Markdown Strings
|
||||
|
||||
The "Markdown Strings" feature enhances flowcharts and mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels.
|
||||
@@ -1009,9 +1051,9 @@ flowchart LR
|
||||
classDef foobar stroke:#00f
|
||||
```
|
||||
|
||||
### Css classes
|
||||
### CSS classes
|
||||
|
||||
It is also possible to predefine classes in css styles that can be applied from the graph definition as in the example
|
||||
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**
|
||||
@@ -1056,7 +1098,7 @@ The icons are accessed via the syntax fa:#icon class name#.
|
||||
|
||||
```mermaid-example
|
||||
flowchart TD
|
||||
B["fab:fa-twitter for peace"]
|
||||
B["fa:fa-twitter for peace"]
|
||||
B-->C[fa:fa-ban forbidden]
|
||||
B-->D(fa:fa-spinner)
|
||||
B-->E(A fa:fa-camera-retro perhaps?)
|
||||
@@ -1064,7 +1106,7 @@ flowchart TD
|
||||
|
||||
```mermaid
|
||||
flowchart TD
|
||||
B["fab:fa-twitter for peace"]
|
||||
B["fa:fa-twitter for peace"]
|
||||
B-->C[fa:fa-ban forbidden]
|
||||
B-->D(fa:fa-spinner)
|
||||
B-->E(A fa:fa-camera-retro perhaps?)
|
||||
|
@@ -241,7 +241,7 @@ The following formatting strings are supported:
|
||||
|
||||
More info in: <https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format>
|
||||
|
||||
### Axis ticks
|
||||
### Axis ticks (v10.3.0+)
|
||||
|
||||
The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`.
|
||||
|
||||
@@ -252,7 +252,7 @@ tickInterval 1day
|
||||
The pattern is:
|
||||
|
||||
```javascript
|
||||
/^([1-9][0-9]*)(minute|hour|day|week|month)$/;
|
||||
/^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/;
|
||||
```
|
||||
|
||||
More info in: <https://github.com/d3/d3-time#interval_every>
|
||||
@@ -271,7 +271,7 @@ gantt
|
||||
weekday monday
|
||||
```
|
||||
|
||||
Support: v10.3.0+
|
||||
> **Warning** > `millisecond` and `second` support was added in vMERMAID_RELEASE_VERSION
|
||||
|
||||
## Output in compact mode
|
||||
|
||||
|
@@ -58,7 +58,7 @@ mindmap
|
||||
|
||||
The syntax for creating Mindmaps is simple and relies on indentation for setting the levels in the hierarchy.
|
||||
|
||||
In the following example you can see how there are 3 different levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further then the previous lines defining the nodes B and C.
|
||||
In the following example you can see how there are 3 different levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further than the previous lines defining the nodes B and C.
|
||||
|
||||
mindmap
|
||||
Root
|
||||
@@ -66,7 +66,7 @@ In the following example you can see how there are 3 different levels. One with
|
||||
B
|
||||
C
|
||||
|
||||
In summary is a simple text outline where there are one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
|
||||
In summary is a simple text outline where there is one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
@@ -228,7 +228,7 @@ _These classes need to be supplied by the site administrator._
|
||||
|
||||
## Unclear indentation
|
||||
|
||||
The actual indentation does not really matter only compared with the previous rows. If we take the previous example and disrupt it a little we can se how the calculations are performed. Let us start with placing C with a smaller indentation than `B`but larger then `A`.
|
||||
The actual indentation does not really matter only compared with the previous rows. If we take the previous example and disrupt it a little we can see how the calculations are performed. Let us start with placing C with a smaller indentation than `B` but larger then `A`.
|
||||
|
||||
mindmap
|
||||
Root
|
||||
|
@@ -47,8 +47,8 @@ quadrantChart
|
||||
## Syntax
|
||||
|
||||
> **Note**
|
||||
> If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
|
||||
> If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
|
||||
> If there are no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
|
||||
> If there are points **x-axis** labels will rendered from the left of the respective quadrant also they will be displayed at the bottom of the chart, and **y-axis** labels will be rendered at the bottom of the respective quadrant, the quadrant text will render at the top of the respective quadrant.
|
||||
|
||||
> **Note**
|
||||
> For points x and y value min value is 0 and max value is 1.
|
||||
@@ -64,7 +64,7 @@ The title is a short description of the chart and it will always render on top o
|
||||
|
||||
### x-axis
|
||||
|
||||
The x-axis determine what text would be displayed in the x-axis. In x-axis there is two part **left** and **right** you can pass **both** or you can pass only **left**. The statement should start with `x-axis` then the `left axis text` followed by the delimiter `-->` then `right axis text`.
|
||||
The x-axis determines what text would be displayed in the x-axis. In x-axis there is two part **left** and **right** you can pass **both** or you can pass only **left**. The statement should start with `x-axis` then the `left axis text` followed by the delimiter `-->` then `right axis text`.
|
||||
|
||||
#### Example
|
||||
|
||||
@@ -73,7 +73,7 @@ The x-axis determine what text would be displayed in the x-axis. In x-axis there
|
||||
|
||||
### y-axis
|
||||
|
||||
The y-axis determine what text would be displayed in the y-axis. In y-axis there is two part **top** and **bottom** you can pass **both** or you can pass only **bottom**. The statement should start with `y-axis` then the `bottom axis text` followed by the delimiter `-->` then `top axis text`.
|
||||
The y-axis determines what text would be displayed in the y-axis. In y-axis there is two part **top** and **bottom** you can pass **both** or you can pass only **bottom**. The statement should start with `y-axis` then the `bottom axis text` followed by the delimiter `-->` then `top axis text`.
|
||||
|
||||
#### Example
|
||||
|
||||
|
@@ -197,7 +197,7 @@ Electricity grid,H2 conversion,27.14
|
||||
|
||||
### Empty Lines
|
||||
|
||||
CSV does not support empty lines without comma delimeters by default. But you can add them if needed:
|
||||
CSV does not support empty lines without comma delimiters by default. But you can add them if needed:
|
||||
|
||||
```mermaid-example
|
||||
sankey-beta
|
||||
|
@@ -164,7 +164,7 @@ The actor(s) can be grouped in vertical boxes. You can define a color (if not, i
|
||||
end
|
||||
A->>J: Hello John, how are you?
|
||||
J->>A: Great!
|
||||
A->>B: Hello Bob, how is Charly ?
|
||||
A->>B: Hello Bob, how is Charly?
|
||||
B->>C: Hello Charly, how are you?
|
||||
```
|
||||
|
||||
@@ -180,7 +180,7 @@ The actor(s) can be grouped in vertical boxes. You can define a color (if not, i
|
||||
end
|
||||
A->>J: Hello John, how are you?
|
||||
J->>A: Great!
|
||||
A->>B: Hello Bob, how is Charly ?
|
||||
A->>B: Hello Bob, how is Charly?
|
||||
B->>C: Hello Charly, how are you?
|
||||
```
|
||||
|
||||
|
18
netlify.toml
Normal file
18
netlify.toml
Normal file
@@ -0,0 +1,18 @@
|
||||
# Settings in the [build] context are global and are applied to
|
||||
# all contexts unless otherwise overridden by more specific contexts.
|
||||
[build]
|
||||
# Directory where the build system installs dependencies
|
||||
# and runs your build. Store your package.json, .nvmrc, etc here.
|
||||
# If not set, defaults to the root directory.
|
||||
base = ""
|
||||
|
||||
# Directory that contains the deploy-ready HTML files and
|
||||
# assets generated by the build. This is an absolute path relative
|
||||
# to the base directory, which is the root by default (/).
|
||||
# This sample publishes the directory located at the absolute
|
||||
# path "root/project/build-output"
|
||||
|
||||
publish = "mermaid-live-editor/docs"
|
||||
|
||||
# Default build command.
|
||||
command = "./scripts/editor.bash"
|
14
package.json
14
package.json
@@ -4,7 +4,7 @@
|
||||
"version": "10.2.4",
|
||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@8.6.11",
|
||||
"packageManager": "pnpm@8.7.1",
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
@@ -78,15 +78,15 @@
|
||||
"@types/rollup-plugin-visualizer": "^4.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@vitest/coverage-v8": "^0.33.0",
|
||||
"@vitest/spy": "^0.33.0",
|
||||
"@vitest/ui": "^0.33.0",
|
||||
"@vitest/coverage-v8": "^0.34.0",
|
||||
"@vitest/spy": "^0.34.0",
|
||||
"@vitest/ui": "^0.34.0",
|
||||
"ajv": "^8.12.0",
|
||||
"concurrently": "^8.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"cypress": "^12.10.0",
|
||||
"cypress-image-snapshot": "^4.0.1",
|
||||
"esbuild": "^0.18.0",
|
||||
"esbuild": "^0.19.0",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-cypress": "^2.13.2",
|
||||
@@ -119,10 +119,10 @@
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-istanbul": "^4.1.0",
|
||||
"vitest": "^0.33.0"
|
||||
"vitest": "^0.34.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.17.0"
|
||||
"node": "18.17.1"
|
||||
},
|
||||
"nyc": {
|
||||
"report-dir": "coverage/cypress"
|
||||
|
@@ -1 +1 @@
|
||||
../mermaid/src/docs/syntax/zenuml.md
|
||||
../mermaid/src/docs/syntax/zenuml.md
|
||||
|
22
packages/mermaid/.madgerc
Normal file
22
packages/mermaid/.madgerc
Normal file
@@ -0,0 +1,22 @@
|
||||
{
|
||||
"detectiveOptions": {
|
||||
"ts": {
|
||||
"skipTypeImports": true
|
||||
},
|
||||
"es6": {
|
||||
"skipTypeImports": true
|
||||
}
|
||||
},
|
||||
"fileExtensions": [
|
||||
"js",
|
||||
"ts"
|
||||
],
|
||||
"excludeRegExp": [
|
||||
"node_modules",
|
||||
"docs",
|
||||
"vitepress",
|
||||
"detector",
|
||||
"Detector"
|
||||
],
|
||||
"tsConfig": "./tsconfig.json"
|
||||
}
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "10.3.1",
|
||||
"version": "10.4.0",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
@@ -24,6 +24,7 @@
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"dev": "pnpm -w dev",
|
||||
"docs:code": "typedoc src/defaultConfig.ts src/config.ts src/mermaidAPI.ts && prettier --write ./src/docs/config/setup",
|
||||
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm scripts/docs.cli.mts",
|
||||
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm scripts/docs.cli.mts --verify",
|
||||
@@ -37,6 +38,7 @@
|
||||
"docs:verify-version": "ts-node-esm scripts/update-release-version.mts --verify",
|
||||
"types:build-config": "ts-node-esm --transpileOnly scripts/create-types-from-json-schema.mts",
|
||||
"types:verify-config": "ts-node-esm scripts/create-types-from-json-schema.mts --verify",
|
||||
"checkCircle": "npx madge --circular ./src",
|
||||
"release": "pnpm build",
|
||||
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build"
|
||||
},
|
||||
@@ -83,7 +85,9 @@
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-sankey": "^0.12.1",
|
||||
"@types/d3-scale": "^4.0.3",
|
||||
"@types/d3-selection": "^3.0.5",
|
||||
"@types/d3-shape": "^3.1.1",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
@@ -112,7 +116,8 @@
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rimraf": "^5.0.0",
|
||||
"start-server-and-test": "^2.0.0",
|
||||
"typedoc": "^0.24.5",
|
||||
"type-fest": "^4.1.0",
|
||||
"typedoc": "^0.25.0",
|
||||
"typedoc-plugin-markdown": "^3.15.2",
|
||||
"typescript": "^5.0.4",
|
||||
"unist-util-flatmap": "^1.0.0",
|
||||
|
@@ -48,7 +48,7 @@ export class Diagram {
|
||||
// extractFrontMatter().
|
||||
|
||||
this.parser.parse = (text: string) =>
|
||||
originalParse(cleanupComments(extractFrontMatter(text, this.db)));
|
||||
originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective)));
|
||||
|
||||
this.parser.parser.yy = this.db;
|
||||
this.init = diagram.init;
|
||||
|
@@ -1,32 +1,36 @@
|
||||
'use strict';
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/**
|
||||
* @function assignWithDepth Extends the functionality of {@link ObjectConstructor.assign} with the
|
||||
* assignWithDepth Extends the functionality of {@link Object.assign} with the
|
||||
* ability to merge arbitrary-depth objects For each key in src with path `k` (recursively)
|
||||
* performs an Object.assign(dst[`k`], src[`k`]) with a slight change from the typical handling of
|
||||
* undefined for dst[`k`]: instead of raising an error, dst[`k`] is auto-initialized to {} and
|
||||
* undefined for dst[`k`]: instead of raising an error, dst[`k`] is auto-initialized to `{}` and
|
||||
* effectively merged with src[`k`]<p> Additionally, dissimilar types will not clobber unless the
|
||||
* config.clobber parameter === true. Example:
|
||||
*
|
||||
* ```js
|
||||
* let config_0 = { foo: { bar: 'bar' }, bar: 'foo' };
|
||||
* let config_1 = { foo: 'foo', bar: 'bar' };
|
||||
* let result = assignWithDepth(config_0, config_1);
|
||||
* console.log(result);
|
||||
* //-> result: { foo: { bar: 'bar' }, bar: 'bar' }
|
||||
* ```
|
||||
* ```
|
||||
* const config_0 = { foo: { bar: 'bar' }, bar: 'foo' };
|
||||
* const config_1 = { foo: 'foo', bar: 'bar' };
|
||||
* const result = assignWithDepth(config_0, config_1);
|
||||
* console.log(result);
|
||||
* //-> result: { foo: { bar: 'bar' }, bar: 'bar' }
|
||||
* ```
|
||||
*
|
||||
* Traditional Object.assign would have clobbered foo in config_0 with foo in config_1. If src is a
|
||||
* destructured array of objects and dst is not an array, assignWithDepth will apply each element
|
||||
* of src to dst in order.
|
||||
* @param {any} dst - The destination of the merge
|
||||
* @param {any} src - The source object(s) to merge into destination
|
||||
* @param {{ depth: number; clobber: boolean }} [config] - Depth: depth
|
||||
* to traverse within src and dst for merging - clobber: should dissimilar types clobber (default:
|
||||
* { depth: 2, clobber: false }). Default is `{ depth: 2, clobber: false }`
|
||||
* @returns {any}
|
||||
* @param dst - The destination of the merge
|
||||
* @param src - The source object(s) to merge into destination
|
||||
* @param config -
|
||||
* * depth: depth to traverse within src and dst for merging
|
||||
* * clobber: should dissimilar types clobber
|
||||
*/
|
||||
const assignWithDepth = function (dst, src, config) {
|
||||
const { depth, clobber } = Object.assign({ depth: 2, clobber: false }, config);
|
||||
const assignWithDepth = (
|
||||
dst: any,
|
||||
src: any,
|
||||
{ depth = 2, clobber = false }: { depth?: number; clobber?: boolean } = {}
|
||||
): any => {
|
||||
const config: { depth: number; clobber: boolean } = { depth, clobber };
|
||||
if (Array.isArray(src) && !Array.isArray(dst)) {
|
||||
src.forEach((s) => assignWithDepth(dst, s, config));
|
||||
return dst;
|
@@ -1,47 +0,0 @@
|
||||
import { sanitizeText as _sanitizeText } from './diagrams/common/common.js';
|
||||
import { getConfig } from './config.js';
|
||||
let title = '';
|
||||
let diagramTitle = '';
|
||||
let description = '';
|
||||
|
||||
const sanitizeText = (txt: string): string => _sanitizeText(txt, getConfig());
|
||||
|
||||
export const clear = function (): void {
|
||||
title = '';
|
||||
description = '';
|
||||
diagramTitle = '';
|
||||
};
|
||||
|
||||
export const setAccTitle = function (txt: string): void {
|
||||
title = sanitizeText(txt).replace(/^\s+/g, '');
|
||||
};
|
||||
|
||||
export const getAccTitle = function (): string {
|
||||
return title || diagramTitle;
|
||||
};
|
||||
|
||||
export const setAccDescription = function (txt: string): void {
|
||||
description = sanitizeText(txt).replace(/\n\s+/g, '\n');
|
||||
};
|
||||
|
||||
export const getAccDescription = function (): string {
|
||||
return description;
|
||||
};
|
||||
|
||||
export const setDiagramTitle = function (txt: string): void {
|
||||
diagramTitle = sanitizeText(txt);
|
||||
};
|
||||
|
||||
export const getDiagramTitle = function (): string {
|
||||
return diagramTitle;
|
||||
};
|
||||
|
||||
export default {
|
||||
getAccTitle,
|
||||
setAccTitle,
|
||||
getDiagramTitle,
|
||||
setDiagramTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
clear,
|
||||
};
|
@@ -1,11 +1,13 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import * as configApi from './config.js';
|
||||
import type { MermaidConfig } from './config.type.js';
|
||||
|
||||
describe('when working with site config', function () {
|
||||
describe('when working with site config', () => {
|
||||
beforeEach(() => {
|
||||
// Resets the site config to default config
|
||||
configApi.setSiteConfig({});
|
||||
});
|
||||
it('should set site config and config properly', function () {
|
||||
it('should set site config and config properly', () => {
|
||||
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
const config_1 = configApi.getSiteConfig();
|
||||
@@ -14,19 +16,26 @@ describe('when working with site config', function () {
|
||||
expect(config_1.fontSize).toEqual(config_0.fontSize);
|
||||
expect(config_1).toEqual(config_2);
|
||||
});
|
||||
it('should respect secure keys when applying directives', function () {
|
||||
const config_0 = {
|
||||
it('should respect secure keys when applying directives', () => {
|
||||
const config_0: MermaidConfig = {
|
||||
fontFamily: 'foo-font',
|
||||
securityLevel: 'strict', // can't be changed
|
||||
fontSize: 12345, // can't be changed
|
||||
secure: [...configApi.defaultConfig.secure!, 'fontSize'],
|
||||
};
|
||||
configApi.setSiteConfig(config_0);
|
||||
const directive = { fontFamily: 'baf', fontSize: 54321 /* fontSize shouldn't be changed */ };
|
||||
const cfg = configApi.updateCurrentConfig(config_0, [directive]);
|
||||
const directive: MermaidConfig = {
|
||||
fontFamily: 'baf',
|
||||
// fontSize and securityLevel shouldn't be changed
|
||||
fontSize: 54321,
|
||||
securityLevel: 'loose',
|
||||
};
|
||||
const cfg: MermaidConfig = configApi.updateCurrentConfig(config_0, [directive]);
|
||||
expect(cfg.fontFamily).toEqual(directive.fontFamily);
|
||||
expect(cfg.fontSize).toBe(config_0.fontSize);
|
||||
expect(cfg.securityLevel).toBe(config_0.securityLevel);
|
||||
});
|
||||
it('should allow setting partial options', function () {
|
||||
it('should allow setting partial options', () => {
|
||||
const defaultConfig = configApi.getConfig();
|
||||
|
||||
configApi.setConfig({
|
||||
@@ -42,7 +51,7 @@ describe('when working with site config', function () {
|
||||
updatedConfig.quadrantChart!.chartWidth
|
||||
);
|
||||
});
|
||||
it('should set reset config properly', function () {
|
||||
it('should set reset config properly', () => {
|
||||
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
const config_1 = { fontFamily: 'baf' };
|
||||
@@ -55,7 +64,7 @@ describe('when working with site config', function () {
|
||||
const config_4 = configApi.getSiteConfig();
|
||||
expect(config_4.fontFamily).toEqual(config_0.fontFamily);
|
||||
});
|
||||
it('should set global reset config properly', function () {
|
||||
it('should set global reset config properly', () => {
|
||||
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
const config_1 = configApi.getSiteConfig();
|
||||
|
@@ -3,15 +3,16 @@ import { log } from './logger.js';
|
||||
import theme from './themes/index.js';
|
||||
import config from './defaultConfig.js';
|
||||
import type { MermaidConfig } from './config.type.js';
|
||||
import { sanitizeDirective } from './utils.js';
|
||||
|
||||
export const defaultConfig: MermaidConfig = Object.freeze(config);
|
||||
|
||||
let siteConfig: MermaidConfig = assignWithDepth({}, defaultConfig);
|
||||
let configFromInitialize: MermaidConfig;
|
||||
let directives: any[] = [];
|
||||
let directives: MermaidConfig[] = [];
|
||||
let currentConfig: MermaidConfig = assignWithDepth({}, defaultConfig);
|
||||
|
||||
export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: any[]) => {
|
||||
export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: MermaidConfig[]) => {
|
||||
// start with config being the siteConfig
|
||||
let cfg: MermaidConfig = assignWithDepth({}, siteCfg);
|
||||
// let sCfg = assignWithDepth(defaultConfig, siteConfigDelta);
|
||||
@@ -20,7 +21,6 @@ export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: any[])
|
||||
let sumOfDirectives: MermaidConfig = {};
|
||||
for (const d of _directives) {
|
||||
sanitize(d);
|
||||
|
||||
// Apply the data from the directive where the the overrides the themeVariables
|
||||
sumOfDirectives = assignWithDepth(sumOfDirectives, d);
|
||||
}
|
||||
@@ -111,12 +111,6 @@ export const getSiteConfig = (): MermaidConfig => {
|
||||
* @returns The currentConfig merged with the sanitized conf
|
||||
*/
|
||||
export const setConfig = (conf: MermaidConfig): MermaidConfig => {
|
||||
// sanitize(conf);
|
||||
// Object.keys(conf).forEach(key => {
|
||||
// const manipulator = manipulators[key];
|
||||
// conf[key] = manipulator ? manipulator(conf[key]) : conf[key];
|
||||
// });
|
||||
|
||||
checkConfig(conf);
|
||||
assignWithDepth(currentConfig, conf);
|
||||
|
||||
@@ -150,9 +144,12 @@ export const getConfig = (): MermaidConfig => {
|
||||
* @param options - The potential setConfig parameter
|
||||
*/
|
||||
export const sanitize = (options: any) => {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
// Checking that options are not in the list of excluded options
|
||||
['secure', ...(siteConfig.secure ?? [])].forEach((key) => {
|
||||
if (options[key] !== undefined) {
|
||||
if (Object.hasOwn(options, key)) {
|
||||
// DO NOT attempt to print options[key] within `${}` as a malicious script
|
||||
// can exploit the logger's attempt to stringify the value and execute arbitrary code
|
||||
log.debug(`Denied attempt to modify a secure key ${key}`, options[key]);
|
||||
@@ -162,7 +159,7 @@ export const sanitize = (options: any) => {
|
||||
|
||||
// Check that there no attempts of prototype pollution
|
||||
Object.keys(options).forEach((key) => {
|
||||
if (key.indexOf('__') === 0) {
|
||||
if (key.startsWith('__')) {
|
||||
delete options[key];
|
||||
}
|
||||
});
|
||||
@@ -188,16 +185,14 @@ export const sanitize = (options: any) => {
|
||||
*
|
||||
* @param directive - The directive to push in
|
||||
*/
|
||||
export const addDirective = (directive: any) => {
|
||||
if (directive.fontFamily) {
|
||||
if (!directive.themeVariables) {
|
||||
directive.themeVariables = { fontFamily: directive.fontFamily };
|
||||
} else {
|
||||
if (!directive.themeVariables.fontFamily) {
|
||||
directive.themeVariables = { fontFamily: directive.fontFamily };
|
||||
}
|
||||
}
|
||||
export const addDirective = (directive: MermaidConfig) => {
|
||||
sanitizeDirective(directive);
|
||||
|
||||
// If the directive has a fontFamily, but no themeVariables, add the fontFamily to the themeVariables
|
||||
if (directive.fontFamily && (!directive.themeVariables || !directive.themeVariables.fontFamily)) {
|
||||
directive.themeVariables = { fontFamily: directive.fontFamily };
|
||||
}
|
||||
|
||||
directives.push(directive);
|
||||
updateCurrentConfig(siteConfig, directives);
|
||||
};
|
||||
|
@@ -1048,7 +1048,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
|
||||
* Pattern is:
|
||||
*
|
||||
* ```javascript
|
||||
* /^([1-9][0-9]*)(minute|hour|day|week|month)$/
|
||||
* /^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
@@ -1298,6 +1298,21 @@ export interface SankeyDiagramConfig extends BaseDiagramConfig {
|
||||
*/
|
||||
nodeAlignment?: 'left' | 'right' | 'center' | 'justify';
|
||||
useMaxWidth?: boolean;
|
||||
/**
|
||||
* Toggle to display or hide values along with title.
|
||||
*
|
||||
*/
|
||||
showValues?: boolean;
|
||||
/**
|
||||
* The prefix to use for values
|
||||
*
|
||||
*/
|
||||
prefix?: string;
|
||||
/**
|
||||
* The suffix to use for values
|
||||
*
|
||||
*/
|
||||
suffix?: string;
|
||||
}
|
||||
/**
|
||||
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
||||
|
@@ -368,7 +368,20 @@ const cutPathAtIntersect = (_points, boundryNode) => {
|
||||
return points;
|
||||
};
|
||||
|
||||
//(edgePaths, e, edge, clusterDb, diagramtype, graph)
|
||||
/**
|
||||
* Calculate the deltas and angle between two points
|
||||
* @param {{x: number, y:number}} point1
|
||||
* @param {{x: number, y:number}} point2
|
||||
* @returns {{angle: number, deltaX: number, deltaY: number}}
|
||||
*/
|
||||
function calculateDeltaAndAngle(point1, point2) {
|
||||
const [x1, y1] = [point1.x, point1.y];
|
||||
const [x2, y2] = [point2.x, point2.y];
|
||||
const deltaX = x2 - x1;
|
||||
const deltaY = y2 - y1;
|
||||
return { angle: Math.atan(deltaY / deltaX), deltaX, deltaY };
|
||||
}
|
||||
|
||||
export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph) {
|
||||
let points = edge.points;
|
||||
let pointsHasChanged = false;
|
||||
@@ -435,22 +448,62 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
|
||||
const lineData = points.filter((p) => !Number.isNaN(p.y));
|
||||
|
||||
// This is the accessor function we talked about above
|
||||
let curve;
|
||||
let curve = curveBasis;
|
||||
// Currently only flowcharts get the curve from the settings, perhaps this should
|
||||
// be expanded to a common setting? Restricting it for now in order not to cause side-effects that
|
||||
// have not been thought through
|
||||
if (diagramType === 'graph' || diagramType === 'flowchart') {
|
||||
curve = edge.curve || curveBasis;
|
||||
} else {
|
||||
curve = curveBasis;
|
||||
if (edge.curve && (diagramType === 'graph' || diagramType === 'flowchart')) {
|
||||
curve = edge.curve;
|
||||
}
|
||||
// curve = curveLinear;
|
||||
|
||||
// We need to draw the lines a bit shorter to avoid drawing
|
||||
// under any transparent markers.
|
||||
// The offsets are calculated from the markers' dimensions.
|
||||
const markerOffsets = {
|
||||
aggregation: 18,
|
||||
extension: 18,
|
||||
composition: 18,
|
||||
dependency: 6,
|
||||
lollipop: 13.5,
|
||||
arrow_point: 5.3,
|
||||
};
|
||||
|
||||
const lineFunction = line()
|
||||
.x(function (d) {
|
||||
return d.x;
|
||||
.x(function (d, i, data) {
|
||||
let offset = 0;
|
||||
if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
|
||||
// Handle first point
|
||||
// Calculate the angle and delta between the first two points
|
||||
const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]);
|
||||
// Calculate the offset based on the angle and the marker's dimensions
|
||||
offset = markerOffsets[edge.arrowTypeStart] * Math.cos(angle) * (deltaX >= 0 ? 1 : -1) || 0;
|
||||
} else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) {
|
||||
// Handle last point
|
||||
// Calculate the angle and delta between the last two points
|
||||
const { angle, deltaX } = calculateDeltaAndAngle(
|
||||
data[data.length - 1],
|
||||
data[data.length - 2]
|
||||
);
|
||||
offset = markerOffsets[edge.arrowTypeEnd] * Math.cos(angle) * (deltaX >= 0 ? 1 : -1) || 0;
|
||||
}
|
||||
return d.x + offset;
|
||||
})
|
||||
.y(function (d) {
|
||||
return d.y;
|
||||
.y(function (d, i, data) {
|
||||
// Same handling as X above
|
||||
let offset = 0;
|
||||
if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
|
||||
const { angle, deltaY } = calculateDeltaAndAngle(data[0], data[1]);
|
||||
offset =
|
||||
markerOffsets[edge.arrowTypeStart] * Math.abs(Math.sin(angle)) * (deltaY >= 0 ? 1 : -1);
|
||||
} else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) {
|
||||
const { angle, deltaY } = calculateDeltaAndAngle(
|
||||
data[data.length - 1],
|
||||
data[data.length - 2]
|
||||
);
|
||||
offset =
|
||||
markerOffsets[edge.arrowTypeEnd] * Math.abs(Math.sin(angle)) * (deltaY >= 0 ? 1 : -1);
|
||||
}
|
||||
return d.y + offset;
|
||||
})
|
||||
.curve(curve);
|
||||
|
||||
|
@@ -155,9 +155,9 @@ export const render = async (elem, graph, markers, diagramtype, id) => {
|
||||
clearClusters();
|
||||
clearGraphlib();
|
||||
|
||||
log.warn('Graph at first:', graphlibJson.write(graph));
|
||||
log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph)));
|
||||
adjustClustersAndEdges(graph);
|
||||
log.warn('Graph after:', graphlibJson.write(graph));
|
||||
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
|
||||
// log.warn('Graph ever after:', graphlibJson.write(graph.node('A').graph));
|
||||
await recursiveRender(elem, graph, diagramtype);
|
||||
};
|
||||
|
@@ -16,7 +16,7 @@ const extension = (elem, type, id) => {
|
||||
.append('marker')
|
||||
.attr('id', type + '-extensionStart')
|
||||
.attr('class', 'marker extension ' + type)
|
||||
.attr('refX', 0)
|
||||
.attr('refX', 18)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
@@ -29,7 +29,7 @@ const extension = (elem, type, id) => {
|
||||
.append('marker')
|
||||
.attr('id', type + '-extensionEnd')
|
||||
.attr('class', 'marker extension ' + type)
|
||||
.attr('refX', 19)
|
||||
.attr('refX', 1)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 28)
|
||||
@@ -44,7 +44,7 @@ const composition = (elem, type) => {
|
||||
.append('marker')
|
||||
.attr('id', type + '-compositionStart')
|
||||
.attr('class', 'marker composition ' + type)
|
||||
.attr('refX', 0)
|
||||
.attr('refX', 18)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
@@ -57,7 +57,7 @@ const composition = (elem, type) => {
|
||||
.append('marker')
|
||||
.attr('id', type + '-compositionEnd')
|
||||
.attr('class', 'marker composition ' + type)
|
||||
.attr('refX', 19)
|
||||
.attr('refX', 1)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 28)
|
||||
@@ -71,7 +71,7 @@ const aggregation = (elem, type) => {
|
||||
.append('marker')
|
||||
.attr('id', type + '-aggregationStart')
|
||||
.attr('class', 'marker aggregation ' + type)
|
||||
.attr('refX', 0)
|
||||
.attr('refX', 18)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
@@ -84,7 +84,7 @@ const aggregation = (elem, type) => {
|
||||
.append('marker')
|
||||
.attr('id', type + '-aggregationEnd')
|
||||
.attr('class', 'marker aggregation ' + type)
|
||||
.attr('refX', 19)
|
||||
.attr('refX', 1)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 28)
|
||||
@@ -98,7 +98,7 @@ const dependency = (elem, type) => {
|
||||
.append('marker')
|
||||
.attr('id', type + '-dependencyStart')
|
||||
.attr('class', 'marker dependency ' + type)
|
||||
.attr('refX', 0)
|
||||
.attr('refX', 6)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
@@ -111,7 +111,7 @@ const dependency = (elem, type) => {
|
||||
.append('marker')
|
||||
.attr('id', type + '-dependencyEnd')
|
||||
.attr('class', 'marker dependency ' + type)
|
||||
.attr('refX', 19)
|
||||
.attr('refX', 13)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 28)
|
||||
@@ -125,15 +125,32 @@ const lollipop = (elem, type) => {
|
||||
.append('marker')
|
||||
.attr('id', type + '-lollipopStart')
|
||||
.attr('class', 'marker lollipop ' + type)
|
||||
.attr('refX', 0)
|
||||
.attr('refX', 13)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('circle')
|
||||
.attr('stroke', 'black')
|
||||
.attr('fill', 'white')
|
||||
.attr('cx', 6)
|
||||
.attr('fill', 'transparent')
|
||||
.attr('cx', 7)
|
||||
.attr('cy', 7)
|
||||
.attr('r', 6);
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', type + '-lollipopEnd')
|
||||
.attr('class', 'marker lollipop ' + type)
|
||||
.attr('refX', 1)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('circle')
|
||||
.attr('stroke', 'black')
|
||||
.attr('fill', 'transparent')
|
||||
.attr('cx', 7)
|
||||
.attr('cy', 7)
|
||||
.attr('r', 6);
|
||||
};
|
||||
@@ -143,7 +160,7 @@ const point = (elem, type) => {
|
||||
.attr('id', type + '-pointEnd')
|
||||
.attr('class', 'marker ' + type)
|
||||
.attr('viewBox', '0 0 10 10')
|
||||
.attr('refX', 10)
|
||||
.attr('refX', 6)
|
||||
.attr('refY', 5)
|
||||
.attr('markerUnits', 'userSpaceOnUse')
|
||||
.attr('markerWidth', 12)
|
||||
|
@@ -291,8 +291,8 @@ export const adjustClustersAndEdges = (graph, depth) => {
|
||||
shape: 'labelRect',
|
||||
style: '',
|
||||
});
|
||||
const edge1 = JSON.parse(JSON.stringify(edge));
|
||||
const edge2 = JSON.parse(JSON.stringify(edge));
|
||||
const edge1 = structuredClone(edge);
|
||||
const edge2 = structuredClone(edge);
|
||||
edge1.label = '';
|
||||
edge1.arrowTypeEnd = 'none';
|
||||
edge2.label = '';
|
||||
|
@@ -5,7 +5,6 @@ import { getConfig } from '../config.js';
|
||||
import intersect from './intersect/index.js';
|
||||
import createLabel from './createLabel.js';
|
||||
import note from './shapes/note.js';
|
||||
import { parseMember } from '../diagrams/class/svgDraw.js';
|
||||
import { evaluate } from '../diagrams/common/common.js';
|
||||
|
||||
const formatClass = (str) => {
|
||||
@@ -880,8 +879,8 @@ const class_box = (parent, node) => {
|
||||
maxWidth = classTitleBBox.width;
|
||||
}
|
||||
const classAttributes = [];
|
||||
node.classData.members.forEach((str) => {
|
||||
const parsedInfo = parseMember(str);
|
||||
node.classData.members.forEach((member) => {
|
||||
const parsedInfo = member.getDisplayDetails();
|
||||
let parsedText = parsedInfo.displayText;
|
||||
if (getConfig().flowchart.htmlLabels) {
|
||||
parsedText = parsedText.replace(/</g, '<').replace(/>/g, '>');
|
||||
@@ -914,8 +913,8 @@ const class_box = (parent, node) => {
|
||||
maxHeight += lineHeight;
|
||||
|
||||
const classMethods = [];
|
||||
node.classData.methods.forEach((str) => {
|
||||
const parsedInfo = parseMember(str);
|
||||
node.classData.methods.forEach((member) => {
|
||||
const parsedInfo = member.getDisplayDetails();
|
||||
let displayText = parsedInfo.displayText;
|
||||
if (getConfig().flowchart.htmlLabels) {
|
||||
displayText = displayText.replace(/</g, '<').replace(/>/g, '>');
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import type { RequiredDeep } from 'type-fest';
|
||||
|
||||
import theme from './themes/index.js';
|
||||
import { type MermaidConfig } from './config.type.js';
|
||||
import type { MermaidConfig } from './config.type.js';
|
||||
|
||||
// Uses our custom Vite jsonSchemaPlugin to load only the default values from
|
||||
// our JSON Schema
|
||||
@@ -13,7 +15,7 @@ import defaultConfigJson from './schemas/config.schema.yaml?only-defaults=true';
|
||||
* Non-JSON JS default values are listed in this file, e.g. functions, or
|
||||
* `undefined` (explicitly set so that `configKeys` finds them).
|
||||
*/
|
||||
const config: Partial<MermaidConfig> = {
|
||||
const config: RequiredDeep<MermaidConfig> = {
|
||||
...defaultConfigJson,
|
||||
// Set, even though they're `undefined` so that `configKeys` finds these keys
|
||||
// TODO: Should we replace these with `null` so that they can go in the JSON Schema?
|
||||
@@ -232,7 +234,7 @@ const config: Partial<MermaidConfig> = {
|
||||
},
|
||||
pie: {
|
||||
...defaultConfigJson.pie,
|
||||
useWidth: undefined,
|
||||
useWidth: 984,
|
||||
},
|
||||
requirement: {
|
||||
...defaultConfigJson.requirement,
|
||||
@@ -263,5 +265,5 @@ const keyify = (obj: any, prefix = ''): string[] =>
|
||||
return [...res, prefix + el];
|
||||
}, []);
|
||||
|
||||
export const configKeys: string[] = keyify(config, '');
|
||||
export const configKeys: Set<string> = new Set(keyify(config, ''));
|
||||
export default config;
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { MermaidConfig } from '../config.type.js';
|
||||
import type { MermaidConfig } from '../config.type.js';
|
||||
import { log } from '../logger.js';
|
||||
import type {
|
||||
DetectorRecord,
|
||||
@@ -6,14 +6,10 @@ import type {
|
||||
DiagramLoader,
|
||||
ExternalDiagramDefinition,
|
||||
} from './types.js';
|
||||
import { frontMatterRegex } from './frontmatter.js';
|
||||
import { getDiagram, registerDiagram } from './diagramAPI.js';
|
||||
import { anyCommentRegex, directiveRegex, frontMatterRegex } from './regexes.js';
|
||||
import { UnknownDiagramError } from '../errors.js';
|
||||
|
||||
const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
|
||||
const anyComment = /\s*%%.*\n/gm;
|
||||
|
||||
const detectors: Record<string, DetectorRecord> = {};
|
||||
export const detectors: Record<string, DetectorRecord> = {};
|
||||
|
||||
/**
|
||||
* Detects the type of the graph text.
|
||||
@@ -38,7 +34,10 @@ const detectors: Record<string, DetectorRecord> = {};
|
||||
* @returns A graph definition key
|
||||
*/
|
||||
export const detectType = function (text: string, config?: MermaidConfig): string {
|
||||
text = text.replace(frontMatterRegex, '').replace(directive, '').replace(anyComment, '\n');
|
||||
text = text
|
||||
.replace(frontMatterRegex, '')
|
||||
.replace(directiveRegex, '')
|
||||
.replace(anyCommentRegex, '\n');
|
||||
for (const [key, { detector }] of Object.entries(detectors)) {
|
||||
const diagram = detector(text, config);
|
||||
if (diagram) {
|
||||
@@ -70,39 +69,6 @@ export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinitio
|
||||
}
|
||||
};
|
||||
|
||||
export const loadRegisteredDiagrams = async () => {
|
||||
log.debug(`Loading registered diagrams`);
|
||||
// Load all lazy loaded diagrams in parallel
|
||||
const results = await Promise.allSettled(
|
||||
Object.entries(detectors).map(async ([key, { detector, loader }]) => {
|
||||
if (loader) {
|
||||
try {
|
||||
getDiagram(key);
|
||||
} catch (error) {
|
||||
try {
|
||||
// Register diagram if it is not already registered
|
||||
const { diagram, id } = await loader();
|
||||
registerDiagram(id, diagram, detector);
|
||||
} catch (err) {
|
||||
// Remove failed diagram from detectors
|
||||
log.error(`Failed to load external diagram with key ${key}. Removing from detectors.`);
|
||||
delete detectors[key];
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
const failed = results.filter((result) => result.status === 'rejected');
|
||||
if (failed.length > 0) {
|
||||
log.error(`Failed to load ${failed.length} external diagrams`);
|
||||
for (const res of failed) {
|
||||
log.error(res);
|
||||
}
|
||||
throw new Error(`Failed to load ${failed.length} external diagrams`);
|
||||
}
|
||||
};
|
||||
|
||||
export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
|
||||
if (detectors[key]) {
|
||||
log.error(`Detector with key ${key} already exists`);
|
||||
|
@@ -5,7 +5,7 @@ import er from '../diagrams/er/erDetector.js';
|
||||
import git from '../diagrams/git/gitGraphDetector.js';
|
||||
import gantt from '../diagrams/gantt/ganttDetector.js';
|
||||
import { info } from '../diagrams/info/infoDetector.js';
|
||||
import pie from '../diagrams/pie/pieDetector.js';
|
||||
import { pie } from '../diagrams/pie/pieDetector.js';
|
||||
import quadrantChart from '../diagrams/quadrant-chart/quadrantDetector.js';
|
||||
import requirement from '../diagrams/requirement/requirementDetector.js';
|
||||
import sequence from '../diagrams/sequence/sequenceDetector.js';
|
||||
|
@@ -1,7 +1,7 @@
|
||||
import { detectType } from './detectType.js';
|
||||
import { getDiagram, registerDiagram } from './diagramAPI.js';
|
||||
import { addDiagrams } from './diagram-orchestration.js';
|
||||
import { DiagramDetector } from './types.js';
|
||||
import type { DiagramDetector } from './types.js';
|
||||
import { getDiagramFromText } from '../Diagram.js';
|
||||
import { it, describe, expect, beforeAll } from 'vitest';
|
||||
|
||||
|
@@ -4,8 +4,8 @@ import { getConfig as _getConfig } from '../config.js';
|
||||
import { sanitizeText as _sanitizeText } from '../diagrams/common/common.js';
|
||||
import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox.js';
|
||||
import { addStylesForDiagram } from '../styles.js';
|
||||
import { DiagramDefinition, DiagramDetector } from './types.js';
|
||||
import * as _commonDb from '../commonDb.js';
|
||||
import type { DiagramDefinition, DiagramDetector } from './types.js';
|
||||
import * as _commonDb from '../diagrams/common/commonDb.js';
|
||||
import { parseDirective as _parseDirective } from '../directiveUtils.js';
|
||||
|
||||
/*
|
||||
|
@@ -2,8 +2,13 @@ import { vi } from 'vitest';
|
||||
import { extractFrontMatter } from './frontmatter.js';
|
||||
|
||||
const dbMock = () => ({ setDiagramTitle: vi.fn() });
|
||||
const setConfigMock = vi.fn();
|
||||
|
||||
describe('extractFrontmatter', () => {
|
||||
beforeEach(() => {
|
||||
setConfigMock.mockClear();
|
||||
});
|
||||
|
||||
it('returns text unchanged if no frontmatter', () => {
|
||||
expect(extractFrontMatter('diagram', dbMock())).toEqual('diagram');
|
||||
});
|
||||
@@ -75,4 +80,21 @@ describe('extractFrontmatter', () => {
|
||||
'tag suffix cannot contain exclamation marks'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles frontmatter with config', () => {
|
||||
const text = `---
|
||||
title: hello
|
||||
config:
|
||||
graph:
|
||||
string: hello
|
||||
number: 14
|
||||
boolean: false
|
||||
array: [1, 2, 3]
|
||||
---
|
||||
diagram`;
|
||||
expect(extractFrontMatter(text, {}, setConfigMock)).toEqual('diagram');
|
||||
expect(setConfigMock).toHaveBeenCalledWith({
|
||||
graph: { string: 'hello', number: 14, boolean: false, array: [1, 2, 3] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,46 +1,53 @@
|
||||
import { DiagramDB } from './types.js';
|
||||
import type { MermaidConfig } from '../config.type.js';
|
||||
import { frontMatterRegex } from './regexes.js';
|
||||
import type { DiagramDB } from './types.js';
|
||||
// The "* as yaml" part is necessary for tree-shaking
|
||||
import * as yaml from 'js-yaml';
|
||||
|
||||
// Match Jekyll-style front matter blocks (https://jekyllrb.com/docs/front-matter/).
|
||||
// Based on regex used by Jekyll: https://github.com/jekyll/jekyll/blob/6dd3cc21c40b98054851846425af06c64f9fb466/lib/jekyll/document.rb#L10
|
||||
// Note that JS doesn't support the "\A" anchor, which means we can't use
|
||||
// multiline mode.
|
||||
// Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents
|
||||
export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s;
|
||||
|
||||
type FrontMatterMetadata = {
|
||||
interface FrontMatterMetadata {
|
||||
title?: string;
|
||||
// Allows custom display modes. Currently used for compact mode in gantt charts.
|
||||
displayMode?: string;
|
||||
};
|
||||
config?: MermaidConfig;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract and parse frontmatter from text, if present, and sets appropriate
|
||||
* properties in the provided db.
|
||||
* @param text - The text that may have a YAML frontmatter.
|
||||
* @param db - Diagram database, could be of any diagram.
|
||||
* @param setDiagramConfig - Optional function to set diagram config.
|
||||
* @returns text with frontmatter stripped out
|
||||
*/
|
||||
export function extractFrontMatter(text: string, db: DiagramDB): string {
|
||||
export function extractFrontMatter(
|
||||
text: string,
|
||||
db: DiagramDB,
|
||||
setDiagramConfig?: (config: MermaidConfig) => void
|
||||
): string {
|
||||
const matches = text.match(frontMatterRegex);
|
||||
if (matches) {
|
||||
const parsed: FrontMatterMetadata = yaml.load(matches[1], {
|
||||
// To keep things simple, only allow strings, arrays, and plain objects.
|
||||
// https://www.yaml.org/spec/1.2/spec.html#id2802346
|
||||
schema: yaml.FAILSAFE_SCHEMA,
|
||||
}) as FrontMatterMetadata;
|
||||
|
||||
if (parsed?.title) {
|
||||
db.setDiagramTitle?.(parsed.title);
|
||||
}
|
||||
|
||||
if (parsed?.displayMode) {
|
||||
db.setDisplayMode?.(parsed.displayMode);
|
||||
}
|
||||
|
||||
return text.slice(matches[0].length);
|
||||
} else {
|
||||
if (!matches) {
|
||||
return text;
|
||||
}
|
||||
|
||||
const parsed: FrontMatterMetadata = yaml.load(matches[1], {
|
||||
// To support config, we need JSON schema.
|
||||
// https://www.yaml.org/spec/1.2/spec.html#id2803231
|
||||
schema: yaml.JSON_SCHEMA,
|
||||
}) as FrontMatterMetadata;
|
||||
|
||||
if (parsed?.title) {
|
||||
// toString() is necessary because YAML could parse the title as a number/boolean
|
||||
db.setDiagramTitle?.(parsed.title.toString());
|
||||
}
|
||||
|
||||
if (parsed?.displayMode) {
|
||||
// toString() is necessary because YAML could parse the title as a number/boolean
|
||||
db.setDisplayMode?.(parsed.displayMode.toString());
|
||||
}
|
||||
|
||||
if (parsed?.config) {
|
||||
setDiagramConfig?.(parsed.config);
|
||||
}
|
||||
|
||||
return text.slice(matches[0].length);
|
||||
}
|
||||
|
36
packages/mermaid/src/diagram-api/loadDiagram.ts
Normal file
36
packages/mermaid/src/diagram-api/loadDiagram.ts
Normal file
@@ -0,0 +1,36 @@
|
||||
import { log } from '../logger.js';
|
||||
import { detectors } from './detectType.js';
|
||||
import { getDiagram, registerDiagram } from './diagramAPI.js';
|
||||
|
||||
export const loadRegisteredDiagrams = async () => {
|
||||
log.debug(`Loading registered diagrams`);
|
||||
// Load all lazy loaded diagrams in parallel
|
||||
const results = await Promise.allSettled(
|
||||
Object.entries(detectors).map(async ([key, { detector, loader }]) => {
|
||||
if (loader) {
|
||||
try {
|
||||
getDiagram(key);
|
||||
} catch (error) {
|
||||
try {
|
||||
// Register diagram if it is not already registered
|
||||
const { diagram, id } = await loader();
|
||||
registerDiagram(id, diagram, detector);
|
||||
} catch (err) {
|
||||
// Remove failed diagram from detectors
|
||||
log.error(`Failed to load external diagram with key ${key}. Removing from detectors.`);
|
||||
delete detectors[key];
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
const failed = results.filter((result) => result.status === 'rejected');
|
||||
if (failed.length > 0) {
|
||||
log.error(`Failed to load ${failed.length} external diagrams`);
|
||||
for (const res of failed) {
|
||||
log.error(res);
|
||||
}
|
||||
throw new Error(`Failed to load ${failed.length} external diagrams`);
|
||||
}
|
||||
};
|
11
packages/mermaid/src/diagram-api/regexes.ts
Normal file
11
packages/mermaid/src/diagram-api/regexes.ts
Normal file
@@ -0,0 +1,11 @@
|
||||
// Match Jekyll-style front matter blocks (https://jekyllrb.com/docs/front-matter/).
|
||||
// Based on regex used by Jekyll: https://github.com/jekyll/jekyll/blob/6dd3cc21c40b98054851846425af06c64f9fb466/lib/jekyll/document.rb#L10
|
||||
// Note that JS doesn't support the "\A" anchor, which means we can't use
|
||||
// multiline mode.
|
||||
// Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents
|
||||
export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s;
|
||||
|
||||
export const directiveRegex =
|
||||
/%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
|
||||
|
||||
export const anyCommentRegex = /\s*%%.*\n/gm;
|
@@ -1,5 +1,5 @@
|
||||
import { Diagram } from '../Diagram.js';
|
||||
import type { MermaidConfig } from '../config.type.js';
|
||||
import type { Diagram } from '../Diagram.js';
|
||||
import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js';
|
||||
import type * as d3 from 'd3';
|
||||
|
||||
export interface InjectUtils {
|
||||
@@ -16,11 +16,19 @@ export interface InjectUtils {
|
||||
* Generic Diagram DB that may apply to any diagram type.
|
||||
*/
|
||||
export interface DiagramDB {
|
||||
// config
|
||||
getConfig?: () => BaseDiagramConfig | undefined;
|
||||
|
||||
// db
|
||||
clear?: () => void;
|
||||
setDiagramTitle?: (title: string) => void;
|
||||
setDisplayMode?: (title: string) => void;
|
||||
getDiagramTitle?: () => string;
|
||||
setAccTitle?: (title: string) => void;
|
||||
getAccTitle?: () => string;
|
||||
setAccDescription?: (describetion: string) => void;
|
||||
getAccDescription?: () => string;
|
||||
|
||||
setDisplayMode?: (title: string) => void;
|
||||
bindFunctions?: (element: Element) => void;
|
||||
}
|
||||
|
||||
|
@@ -1,7 +1,12 @@
|
||||
import mermaidAPI from '../../mermaidAPI.js';
|
||||
import * as configApi from '../../config.js';
|
||||
import { sanitizeText } from '../common/common.js';
|
||||
import { setAccTitle, getAccTitle, getAccDescription, setAccDescription } from '../../commonDb.js';
|
||||
import {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
} from '../common/commonDb.js';
|
||||
|
||||
let c4ShapeArray = [];
|
||||
let boundaryParseStack = [''];
|
||||
|
@@ -3,8 +3,8 @@ import c4Parser from './parser/c4Diagram.jison';
|
||||
import c4Db from './c4Db.js';
|
||||
import c4Renderer from './c4Renderer.js';
|
||||
import c4Styles from './styles.js';
|
||||
import { MermaidConfig } from '../../config.type.js';
|
||||
import { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
import type { MermaidConfig } from '../../config.type.js';
|
||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
|
||||
export const diagram: DiagramDefinition = {
|
||||
parser: c4Parser,
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon.js';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
|
@@ -1,5 +1,5 @@
|
||||
// @ts-nocheck - don't check until handle it
|
||||
import { select, Selection } from 'd3';
|
||||
import type { Selection } from 'd3';
|
||||
import { select } from 'd3';
|
||||
import { log } from '../../logger.js';
|
||||
import * as configApi from '../../config.js';
|
||||
import common from '../common/common.js';
|
||||
@@ -13,8 +13,9 @@ import {
|
||||
clear as commonClear,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
} from '../../commonDb.js';
|
||||
import {
|
||||
} from '../common/commonDb.js';
|
||||
import { ClassMember } from './classTypes.js';
|
||||
import type {
|
||||
ClassRelation,
|
||||
ClassNode,
|
||||
ClassNote,
|
||||
@@ -70,21 +71,21 @@ export const setClassLabel = function (id: string, label: string) {
|
||||
* @public
|
||||
*/
|
||||
export const addClass = function (id: string) {
|
||||
const classId = splitClassNameAndType(id);
|
||||
const { className, type } = splitClassNameAndType(id);
|
||||
// Only add class if not exists
|
||||
if (classes[classId.className] !== undefined) {
|
||||
if (Object.hasOwn(classes, className)) {
|
||||
return;
|
||||
}
|
||||
|
||||
classes[classId.className] = {
|
||||
id: classId.className,
|
||||
type: classId.type,
|
||||
label: classId.className,
|
||||
classes[className] = {
|
||||
id: className,
|
||||
type: type,
|
||||
label: className,
|
||||
cssClasses: [],
|
||||
methods: [],
|
||||
members: [],
|
||||
annotations: [],
|
||||
domId: MERMAID_DOM_ID_PREFIX + classId.className + '-' + classCounter,
|
||||
domId: MERMAID_DOM_ID_PREFIX + className + '-' + classCounter,
|
||||
} as ClassNode;
|
||||
|
||||
classCounter++;
|
||||
@@ -114,11 +115,11 @@ export const clear = function () {
|
||||
commonClear();
|
||||
};
|
||||
|
||||
export const getClass = function (id: string) {
|
||||
export const getClass = function (id: string): ClassNode {
|
||||
return classes[id];
|
||||
};
|
||||
|
||||
export const getClasses = function () {
|
||||
export const getClasses = function (): ClassMap {
|
||||
return classes;
|
||||
};
|
||||
|
||||
@@ -174,6 +175,8 @@ export const addAnnotation = function (className: string, annotation: string) {
|
||||
* @public
|
||||
*/
|
||||
export const addMember = function (className: string, member: string) {
|
||||
addClass(className);
|
||||
|
||||
const validatedClassName = splitClassNameAndType(className).className;
|
||||
const theClass = classes[validatedClassName];
|
||||
|
||||
@@ -186,9 +189,9 @@ export const addMember = function (className: string, member: string) {
|
||||
theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2)));
|
||||
} else if (memberString.indexOf(')') > 0) {
|
||||
//its a method
|
||||
theClass.methods.push(sanitizeText(memberString));
|
||||
theClass.methods.push(new ClassMember(memberString, 'method'));
|
||||
} else if (memberString) {
|
||||
theClass.members.push(sanitizeText(memberString));
|
||||
theClass.members.push(new ClassMember(memberString, 'attribute'));
|
||||
}
|
||||
}
|
||||
};
|
||||
@@ -255,6 +258,7 @@ export const getTooltip = function (id: string, namespace?: string) {
|
||||
|
||||
return classes[id].tooltip;
|
||||
};
|
||||
|
||||
/**
|
||||
* Called by parser when a link is found. Adds the URL to the vertex data.
|
||||
*
|
||||
@@ -367,6 +371,7 @@ export const relationType = {
|
||||
const setupToolTips = function (element: Element) {
|
||||
let tooltipElem: Selection<HTMLDivElement, unknown, HTMLElement, unknown> =
|
||||
select('.mermaidTooltip');
|
||||
// @ts-expect-error - Incorrect types
|
||||
if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
|
||||
tooltipElem = select('body').append('div').attr('class', 'mermaidTooltip').style('opacity', 0);
|
||||
}
|
||||
@@ -376,7 +381,6 @@ const setupToolTips = function (element: Element) {
|
||||
const nodes = svg.selectAll('g.node');
|
||||
nodes
|
||||
.on('mouseover', function () {
|
||||
// @ts-expect-error - select is not part of the d3 type definition
|
||||
const el = select(this);
|
||||
const title = el.attr('title');
|
||||
// Don't try to draw a tooltip if no data is provided
|
||||
@@ -386,6 +390,7 @@ const setupToolTips = function (element: Element) {
|
||||
// @ts-ignore - getBoundingClientRect is not part of the d3 type definition
|
||||
const rect = this.getBoundingClientRect();
|
||||
|
||||
// @ts-expect-error - Incorrect types
|
||||
tooltipElem.transition().duration(200).style('opacity', '.9');
|
||||
tooltipElem
|
||||
.text(el.attr('title'))
|
||||
@@ -395,8 +400,8 @@ const setupToolTips = function (element: Element) {
|
||||
el.classed('hover', true);
|
||||
})
|
||||
.on('mouseout', function () {
|
||||
// @ts-expect-error - Incorrect types
|
||||
tooltipElem.transition().duration(500).style('opacity', 0);
|
||||
// @ts-expect-error - select is not part of the d3 type definition
|
||||
const el = select(this);
|
||||
el.classed('hover', false);
|
||||
});
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
// @ts-ignore: JISON doesn't support types
|
||||
import parser from './parser/classDiagram.jison';
|
||||
import db from './classDb.js';
|
||||
|
@@ -4,6 +4,9 @@ import classDb from './classDb.js';
|
||||
import { vi, describe, it, expect } from 'vitest';
|
||||
const spyOn = vi.spyOn;
|
||||
|
||||
const staticCssStyle = 'text-decoration:underline;';
|
||||
const abstractCssStyle = 'font-style:italic;';
|
||||
|
||||
describe('given a basic class diagram, ', function () {
|
||||
describe('when parsing class definition', function () {
|
||||
beforeEach(function () {
|
||||
@@ -127,7 +130,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.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('member1');
|
||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('member1');
|
||||
});
|
||||
|
||||
it('should parse a class with a text label, member and annotation', () => {
|
||||
@@ -142,7 +145,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.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('int member1');
|
||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
|
||||
expect(c1.annotations.length).toBe(1);
|
||||
expect(c1.annotations[0]).toBe('interface');
|
||||
});
|
||||
@@ -168,7 +171,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]).toBe('int member1');
|
||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
|
||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
||||
});
|
||||
|
||||
@@ -371,6 +374,74 @@ class C13["With Città foreign language"]
|
||||
note ${keyword}`;
|
||||
expect(() => parser.parse(str)).toThrowError(/(Expecting\s'STR'|Unrecognized\stext)/);
|
||||
});
|
||||
|
||||
it('should parse diagram with direction', () => {
|
||||
parser.parse(`classDiagram
|
||||
direction TB
|
||||
class Student {
|
||||
-idCard : IdCard
|
||||
}
|
||||
class IdCard{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
class Bike{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
Student "1" --o "1" IdCard : carries
|
||||
Student "1" --o "1" Bike : rides`);
|
||||
|
||||
expect(Object.keys(classDb.getClasses()).length).toBe(3);
|
||||
expect(classDb.getClasses().Student).toMatchInlineSnapshot(`
|
||||
{
|
||||
"annotations": [],
|
||||
"cssClasses": [],
|
||||
"domId": "classId-Student-134",
|
||||
"id": "Student",
|
||||
"label": "Student",
|
||||
"members": [
|
||||
ClassMember {
|
||||
"classifier": "",
|
||||
"id": "idCard : IdCard",
|
||||
"memberType": "attribute",
|
||||
"visibility": "-",
|
||||
},
|
||||
],
|
||||
"methods": [],
|
||||
"type": "",
|
||||
}
|
||||
`);
|
||||
expect(classDb.getRelations().length).toBe(2);
|
||||
expect(classDb.getRelations()).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"id1": "Student",
|
||||
"id2": "IdCard",
|
||||
"relation": {
|
||||
"lineType": 0,
|
||||
"type1": "none",
|
||||
"type2": 0,
|
||||
},
|
||||
"relationTitle1": "1",
|
||||
"relationTitle2": "1",
|
||||
"title": "carries",
|
||||
},
|
||||
{
|
||||
"id1": "Student",
|
||||
"id2": "Bike",
|
||||
"relation": {
|
||||
"lineType": 0,
|
||||
"type1": "none",
|
||||
"type2": 0,
|
||||
},
|
||||
"relationTitle1": "1",
|
||||
"relationTitle2": "1",
|
||||
"title": "rides",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing class defined in brackets', function () {
|
||||
@@ -422,8 +493,8 @@ class C13["With Città foreign language"]
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'int testMember\n' +
|
||||
'string fooMember\n' +
|
||||
'test()\n' +
|
||||
'string fooMember\n' +
|
||||
'foo()\n' +
|
||||
'}';
|
||||
parser.parse(str);
|
||||
@@ -431,10 +502,10 @@ class C13["With Città foreign language"]
|
||||
const actual = parser.yy.getClass('Class1');
|
||||
expect(actual.members.length).toBe(2);
|
||||
expect(actual.methods.length).toBe(2);
|
||||
expect(actual.members[0]).toBe('int testMember');
|
||||
expect(actual.members[1]).toBe('string fooMember');
|
||||
expect(actual.methods[0]).toBe('test()');
|
||||
expect(actual.methods[1]).toBe('foo()');
|
||||
expect(actual.members[0].getDisplayDetails().displayText).toBe('int testMember');
|
||||
expect(actual.members[1].getDisplayDetails().displayText).toBe('string fooMember');
|
||||
expect(actual.methods[0].getDisplayDetails().displayText).toBe('test()');
|
||||
expect(actual.methods[1].getDisplayDetails().displayText).toBe('foo()');
|
||||
});
|
||||
|
||||
it('should parse a class with a text label and members', () => {
|
||||
@@ -444,7 +515,7 @@ class C13["With Città foreign language"]
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.members.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1');
|
||||
});
|
||||
|
||||
it('should parse a class with a text label, members and annotation', () => {
|
||||
@@ -459,7 +530,7 @@ class C13["With Città foreign language"]
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.members.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1');
|
||||
expect(c1.annotations.length).toBe(1);
|
||||
expect(c1.annotations[0]).toBe('interface');
|
||||
});
|
||||
@@ -742,6 +813,20 @@ describe('given a class diagram with members and methods ', function () {
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle direct member declaration', function () {
|
||||
parser.parse('classDiagram\n' + 'Car : wheels');
|
||||
const car = classDb.getClass('Car');
|
||||
expect(car.members.length).toBe(1);
|
||||
expect(car.members[0].id).toBe('wheels');
|
||||
});
|
||||
|
||||
it('should handle direct member declaration with type', function () {
|
||||
parser.parse('classDiagram\n' + 'Car : int wheels');
|
||||
const car = classDb.getClass('Car');
|
||||
expect(car.members.length).toBe(1);
|
||||
expect(car.members[0].id).toBe('int wheels');
|
||||
});
|
||||
|
||||
it('should handle simple member declaration with type', function () {
|
||||
const str = 'classDiagram\n' + 'class Car\n' + 'Car : int wheels';
|
||||
|
||||
@@ -762,10 +847,10 @@ describe('given a class diagram with members and methods ', function () {
|
||||
const actual = parser.yy.getClass('actual');
|
||||
expect(actual.members.length).toBe(4);
|
||||
expect(actual.methods.length).toBe(0);
|
||||
expect(actual.members[0]).toBe('-int privateMember');
|
||||
expect(actual.members[1]).toBe('+int publicMember');
|
||||
expect(actual.members[2]).toBe('#int protectedMember');
|
||||
expect(actual.members[3]).toBe('~int privatePackage');
|
||||
expect(actual.members[0].getDisplayDetails().displayText).toBe('-int privateMember');
|
||||
expect(actual.members[1].getDisplayDetails().displayText).toBe('+int publicMember');
|
||||
expect(actual.members[2].getDisplayDetails().displayText).toBe('#int protectedMember');
|
||||
expect(actual.members[3].getDisplayDetails().displayText).toBe('~int privatePackage');
|
||||
});
|
||||
|
||||
it('should handle generic types', function () {
|
||||
@@ -818,7 +903,9 @@ describe('given a class diagram with members and methods ', function () {
|
||||
expect(actual.annotations.length).toBe(0);
|
||||
expect(actual.members.length).toBe(0);
|
||||
expect(actual.methods.length).toBe(1);
|
||||
expect(actual.methods[0]).toBe('someMethod()*');
|
||||
const method = actual.methods[0];
|
||||
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
||||
expect(method.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
|
||||
it('should handle static methods', function () {
|
||||
@@ -829,7 +916,9 @@ describe('given a class diagram with members and methods ', function () {
|
||||
expect(actual.annotations.length).toBe(0);
|
||||
expect(actual.members.length).toBe(0);
|
||||
expect(actual.methods.length).toBe(1);
|
||||
expect(actual.methods[0]).toBe('someMethod()$');
|
||||
const method = actual.methods[0];
|
||||
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
||||
expect(method.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should handle generic types in arguments', function () {
|
||||
@@ -955,53 +1044,6 @@ foo()
|
||||
parser.parse(str);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing invalid generic classes', function () {
|
||||
beforeEach(function () {
|
||||
classDb.clear();
|
||||
parser.yy = classDb;
|
||||
});
|
||||
|
||||
it('should break when another `{`is encountered before closing the first one while defining generic class with brackets', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Dummy_Class~T~ {\n' +
|
||||
'String data\n' +
|
||||
' void methods()\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'class Dummy_Class {\n' +
|
||||
'class Flight {\n' +
|
||||
' flightNumber : Integer\n' +
|
||||
' departureTime : Date\n' +
|
||||
'}';
|
||||
let testPassed = false;
|
||||
try {
|
||||
parser.parse(str);
|
||||
} catch (error) {
|
||||
testPassed = true;
|
||||
}
|
||||
expect(testPassed).toBe(true);
|
||||
});
|
||||
|
||||
it('should break when EOF is encountered before closing the first `{` while defining generic class with brackets', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Dummy_Class~T~ {\n' +
|
||||
'String data\n' +
|
||||
' void methods()\n' +
|
||||
'}\n' +
|
||||
'\n' +
|
||||
'class Dummy_Class {\n';
|
||||
let testPassed = false;
|
||||
try {
|
||||
parser.parse(str);
|
||||
} catch (error) {
|
||||
testPassed = true;
|
||||
}
|
||||
expect(testPassed).toBe(true);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a class diagram with relationships, ', function () {
|
||||
@@ -1274,10 +1316,10 @@ describe('given a class diagram with relationships, ', function () {
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.members.length).toBe(2);
|
||||
expect(testClass.methods.length).toBe(2);
|
||||
expect(testClass.members[0]).toBe('int : test');
|
||||
expect(testClass.members[1]).toBe('string : foo');
|
||||
expect(testClass.methods[0]).toBe('test()');
|
||||
expect(testClass.methods[1]).toBe('foo()');
|
||||
expect(testClass.members[0].getDisplayDetails().displayText).toBe('int : test');
|
||||
expect(testClass.members[1].getDisplayDetails().displayText).toBe('string : foo');
|
||||
expect(testClass.methods[0].getDisplayDetails().displayText).toBe('test()');
|
||||
expect(testClass.methods[1].getDisplayDetails().displayText).toBe('foo()');
|
||||
});
|
||||
|
||||
it('should handle abstract methods', function () {
|
||||
@@ -1288,7 +1330,9 @@ describe('given a class diagram with relationships, ', function () {
|
||||
expect(testClass.annotations.length).toBe(0);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.methods[0]).toBe('someMethod()*');
|
||||
const method = testClass.methods[0];
|
||||
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
||||
expect(method.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
|
||||
it('should handle static methods', function () {
|
||||
@@ -1299,7 +1343,9 @@ describe('given a class diagram with relationships, ', function () {
|
||||
expect(testClass.annotations.length).toBe(0);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.methods[0]).toBe('someMethod()$');
|
||||
const method = testClass.methods[0];
|
||||
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
|
||||
expect(method.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should associate link and css appropriately', function () {
|
||||
@@ -1514,11 +1560,19 @@ class Class2
|
||||
const testClasses = parser.yy.getClasses();
|
||||
const testRelations = parser.yy.getRelations();
|
||||
expect(Object.keys(testNamespaceA.classes).length).toBe(2);
|
||||
expect(testNamespaceA.classes['A1'].members[0]).toBe('+foo : string');
|
||||
expect(testNamespaceA.classes['A2'].members[0]).toBe('+bar : int');
|
||||
expect(testNamespaceA.classes['A1'].members[0].getDisplayDetails().displayText).toBe(
|
||||
'+foo : string'
|
||||
);
|
||||
expect(testNamespaceA.classes['A2'].members[0].getDisplayDetails().displayText).toBe(
|
||||
'+bar : int'
|
||||
);
|
||||
expect(Object.keys(testNamespaceB.classes).length).toBe(2);
|
||||
expect(testNamespaceB.classes['B1'].members[0]).toBe('+foo : bool');
|
||||
expect(testNamespaceB.classes['B2'].members[0]).toBe('+bar : float');
|
||||
expect(testNamespaceB.classes['B1'].members[0].getDisplayDetails().displayText).toBe(
|
||||
'+foo : bool'
|
||||
);
|
||||
expect(testNamespaceB.classes['B2'].members[0].getDisplayDetails().displayText).toBe(
|
||||
'+bar : float'
|
||||
);
|
||||
expect(Object.keys(testClasses).length).toBe(4);
|
||||
expect(testClasses['A1'].parent).toBe('A');
|
||||
expect(testClasses['A2'].parent).toBe('A');
|
||||
@@ -1570,8 +1624,8 @@ class Class2
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.members.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
|
||||
const member = c1.members[0];
|
||||
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
});
|
||||
@@ -1587,9 +1641,10 @@ class Class2
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.members.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
expect(c1.annotations.length).toBe(1);
|
||||
expect(c1.annotations[0]).toBe('interface');
|
||||
const member = c1.members[0];
|
||||
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
@@ -1606,8 +1661,9 @@ C1 --> C2
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.members[0]).toBe('+member1');
|
||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
||||
const member = c1.members[0];
|
||||
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
||||
});
|
||||
|
||||
it('should parse a class with text label and css class', () => {
|
||||
@@ -1622,8 +1678,9 @@ 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.members[0]).toBe('+member1');
|
||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
||||
const member = c1.members[0];
|
||||
expect(member.getDisplayDetails().displayText).toBe('+member1');
|
||||
});
|
||||
|
||||
it('should parse two classes with text labels and css classes', () => {
|
||||
|
@@ -1,4 +1,4 @@
|
||||
import { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
// @ts-ignore: JISON doesn't support types
|
||||
import parser from './parser/classDiagram.jison';
|
||||
import db from './classDb.js';
|
||||
|
@@ -1,78 +0,0 @@
|
||||
import { setConfig } from '../../config.js';
|
||||
import classDB from './classDb.js';
|
||||
// @ts-ignore - no types in jison
|
||||
import classDiagram from './parser/classDiagram.jison';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
});
|
||||
|
||||
describe('when parsing class diagram', function () {
|
||||
beforeEach(function () {
|
||||
classDiagram.parser.yy = classDB;
|
||||
classDiagram.parser.yy.clear();
|
||||
});
|
||||
|
||||
it('should parse diagram with direction', () => {
|
||||
classDiagram.parser.parse(`classDiagram
|
||||
direction TB
|
||||
class Student {
|
||||
-idCard : IdCard
|
||||
}
|
||||
class IdCard{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
class Bike{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
Student "1" --o "1" IdCard : carries
|
||||
Student "1" --o "1" Bike : rides`);
|
||||
|
||||
expect(Object.keys(classDB.getClasses()).length).toBe(3);
|
||||
expect(classDB.getClasses().Student).toMatchInlineSnapshot(`
|
||||
{
|
||||
"annotations": [],
|
||||
"cssClasses": [],
|
||||
"domId": "classId-Student-0",
|
||||
"id": "Student",
|
||||
"label": "Student",
|
||||
"members": [
|
||||
"-idCard : IdCard",
|
||||
],
|
||||
"methods": [],
|
||||
"type": "",
|
||||
}
|
||||
`);
|
||||
expect(classDB.getRelations().length).toBe(2);
|
||||
expect(classDB.getRelations()).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"id1": "Student",
|
||||
"id2": "IdCard",
|
||||
"relation": {
|
||||
"lineType": 0,
|
||||
"type1": "none",
|
||||
"type2": 0,
|
||||
},
|
||||
"relationTitle1": "1",
|
||||
"relationTitle2": "1",
|
||||
"title": "carries",
|
||||
},
|
||||
{
|
||||
"id1": "Student",
|
||||
"id2": "Bike",
|
||||
"relation": {
|
||||
"lineType": 0,
|
||||
"type1": "none",
|
||||
"type2": 0,
|
||||
},
|
||||
"relationTitle1": "1",
|
||||
"relationTitle2": "1",
|
||||
"title": "rides",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
@@ -8,7 +8,7 @@ import utils from '../../utils.js';
|
||||
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
|
||||
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
|
||||
import common from '../common/common.js';
|
||||
import { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js';
|
||||
import type { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js';
|
||||
|
||||
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
|
||||
|
||||
@@ -156,24 +156,17 @@ export const addNotes = function (
|
||||
) {
|
||||
log.info(notes);
|
||||
|
||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||
notes.forEach(function (note, i) {
|
||||
const vertex = note;
|
||||
|
||||
/**
|
||||
* Variable for storing the classes for the vertex
|
||||
*
|
||||
*/
|
||||
const cssNoteStr = '';
|
||||
|
||||
const styles = { labelStyle: '', style: '' };
|
||||
|
||||
// Use vertex id as text in the box if no text is provided by the graph definition
|
||||
const vertexText = vertex.text;
|
||||
|
||||
const radius = 0;
|
||||
const shape = 'note';
|
||||
// Add the node
|
||||
const node = {
|
||||
labelStyle: styles.labelStyle,
|
||||
shape: shape,
|
||||
@@ -301,7 +294,7 @@ export const setConf = function (cnf: any) {
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a flowchart in the tag with id: id based on the graph definition in text.
|
||||
* Draws a class diagram in the tag with id: id based on the definition in text.
|
||||
*
|
||||
* @param text -
|
||||
* @param id -
|
||||
|
683
packages/mermaid/src/diagrams/class/classTypes.spec.ts
Normal file
683
packages/mermaid/src/diagrams/class/classTypes.spec.ts
Normal file
@@ -0,0 +1,683 @@
|
||||
import { ClassMember } from './classTypes.js';
|
||||
import { vi, describe, it, expect } from 'vitest';
|
||||
const spyOn = vi.spyOn;
|
||||
|
||||
const staticCssStyle = 'text-decoration:underline;';
|
||||
const abstractCssStyle = 'font-style:italic;';
|
||||
|
||||
describe('given text representing a method, ', function () {
|
||||
describe('when method has no parameters', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTime()`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(str);
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTime()`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('+getTime()');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTime()`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('-getTime()');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTime()`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('#getTime()');
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTime()`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('~getTime()');
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTime()$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime()');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTime()*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime()');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method has single parameter value', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTime(int)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(str);
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTime(int)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('+getTime(int)');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTime(int)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('-getTime(int)');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTime(int)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('#getTime(int)');
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTime(int)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('~getTime(int)');
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTime(int)$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTime(int)*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method has single parameter type and name (type first)', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTime(int count)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(str);
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTime(int count)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('+getTime(int count)');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTime(int count)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('-getTime(int count)');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTime(int count)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('#getTime(int count)');
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTime(int count)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('~getTime(int count)');
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTime(int count)$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTime(int count)*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method has single parameter type and name (name first)', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTime(count int)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(str);
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTime(count int)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('+getTime(count int)');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTime(count int)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('-getTime(count int)');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTime(count int)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('#getTime(count int)');
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTime(count int)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('~getTime(count int)');
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTime(count int)$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTime(count int)*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method has multiple parameters', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTime(string text, int count)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(str);
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTime(string text, int count)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(str);
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTime(string text, int count)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(str);
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTime(string text, int count)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(str);
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTime(string text, int count)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(str);
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTime(string text, int count)$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTime(string text, int count)*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method has return type', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTime() DateTime`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime');
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTime() DateTime`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('+getTime() : DateTime');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTime() DateTime`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('-getTime() : DateTime');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTime() DateTime`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('#getTime() : DateTime');
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTime() DateTime`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('~getTime() : DateTime');
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTime() DateTime$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTime() DateTime*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method parameter is generic', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTimes(List~T~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>)');
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTimes(List~T~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('+getTimes(List<T>)');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTimes(List~T~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('-getTimes(List<T>)');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTimes(List~T~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('#getTimes(List<T>)');
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTimes(List~T~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('~getTimes(List<T>)');
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTimes(List~T~)$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTimes(List~T~)*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method parameter contains two generic', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTimes(List~T~, List~OT~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>, List<OT>)');
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTimes(List~T~, List~OT~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('+getTimes(List<T>, List<OT>)');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTimes(List~T~, List~OT~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('-getTimes(List<T>, List<OT>)');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTimes(List~T~, List~OT~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('#getTimes(List<T>, List<OT>)');
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTimes(List~T~, List~OT~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('~getTimes(List<T>, List<OT>)');
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTimes(List~T~, List~OT~)$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>, List<OT>)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTimes(List~T~, List~OT~)*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>, List<OT>)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method parameter is a nested generic', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTimetableList(List~List~T~~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List<List<T>>)');
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTimetableList(List~List~T~~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('+getTimetableList(List<List<T>>)');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTimetableList(List~List~T~~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('-getTimetableList(List<List<T>>)');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTimetableList(List~List~T~~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('#getTimetableList(List<List<T>>)');
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTimetableList(List~List~T~~)`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('~getTimetableList(List<List<T>>)');
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTimetableList(List~List~T~~)$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List<List<T>>)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTimetableList(List~List~T~~)*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List<List<T>>)');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method parameter is a composite generic', function () {
|
||||
const methodNameAndParameters = 'getTimes(List~K, V~)';
|
||||
const expectedMethodNameAndParameters = 'getTimes(List<K, V>)';
|
||||
it('should parse correctly', function () {
|
||||
const str = methodNameAndParameters;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters);
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = '+' + methodNameAndParameters;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'+' + expectedMethodNameAndParameters
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = '-' + methodNameAndParameters;
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'-' + expectedMethodNameAndParameters
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = '#' + methodNameAndParameters;
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'#' + expectedMethodNameAndParameters
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = '~' + methodNameAndParameters;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'~' + expectedMethodNameAndParameters
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = methodNameAndParameters + '$';
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters);
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = methodNameAndParameters + '*';
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters);
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method return type is generic', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTimes() List~T~`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List<T>');
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTimes() List~T~`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('+getTimes() : List<T>');
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTimes() List~T~`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('-getTimes() : List<T>');
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTimes() List~T~`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('#getTimes() : List<T>');
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTimes() List~T~`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('~getTimes() : List<T>');
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTimes() List~T~$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List<T>');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTimes() List~T~*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List<T>');
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when method return type is a nested generic', function () {
|
||||
it('should parse correctly', function () {
|
||||
const str = `getTimetableList() List~List~T~~`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'getTimetableList() : List<List<T>>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle public visibility', function () {
|
||||
const str = `+getTimetableList() List~List~T~~`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'+getTimetableList() : List<List<T>>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle private visibility', function () {
|
||||
const str = `-getTimetableList() List~List~T~~`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'-getTimetableList() : List<List<T>>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle protected visibility', function () {
|
||||
const str = `#getTimetableList() List~List~T~~`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'#getTimetableList() : List<List<T>>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should handle internal visibility', function () {
|
||||
const str = `~getTimetableList() List~List~T~~`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'~getTimetableList() : List<List<T>>'
|
||||
);
|
||||
});
|
||||
|
||||
it('should return correct css for static classifier', function () {
|
||||
const str = `getTimetableList() List~List~T~~$`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'getTimetableList() : List<List<T>>'
|
||||
);
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
|
||||
it('should return correct css for abstract classifier', function () {
|
||||
const str = `getTimetableList() List~List~T~~*`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'getTimetableList() : List<List<T>>'
|
||||
);
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
|
||||
});
|
||||
});
|
||||
|
||||
describe('--uncategorized tests--', function () {
|
||||
it('member name should handle double colons', function () {
|
||||
const str = `std::map ~int,string~ pMap;`;
|
||||
|
||||
const classMember = new ClassMember(str, 'attribute');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe('std::map <int,string> pMap;');
|
||||
});
|
||||
|
||||
it('member name should handle generic type', function () {
|
||||
const str = `getTime~T~(this T, int seconds)$ DateTime`;
|
||||
|
||||
const classMember = new ClassMember(str, 'method');
|
||||
expect(classMember.getDisplayDetails().displayText).toBe(
|
||||
'getTime<T>(this T, int seconds) : DateTime'
|
||||
);
|
||||
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
|
||||
});
|
||||
});
|
||||
});
|
@@ -1,10 +1,13 @@
|
||||
import { getConfig } from '../../config.js';
|
||||
import { parseGenericTypes, sanitizeText } from '../common/common.js';
|
||||
|
||||
export interface ClassNode {
|
||||
id: string;
|
||||
type: string;
|
||||
label: string;
|
||||
cssClasses: string[];
|
||||
methods: string[];
|
||||
members: string[];
|
||||
methods: ClassMember[];
|
||||
members: ClassMember[];
|
||||
annotations: string[];
|
||||
domId: string;
|
||||
parent?: string;
|
||||
@@ -14,6 +17,120 @@ export interface ClassNode {
|
||||
tooltip?: string;
|
||||
}
|
||||
|
||||
export type Visibility = '#' | '+' | '~' | '-' | '';
|
||||
export const visibilityValues = ['#', '+', '~', '-', ''];
|
||||
|
||||
/**
|
||||
* Parses and stores class diagram member variables/methods.
|
||||
*
|
||||
*/
|
||||
export class ClassMember {
|
||||
id!: string;
|
||||
cssStyle!: string;
|
||||
memberType!: 'method' | 'attribute';
|
||||
visibility!: Visibility;
|
||||
/**
|
||||
* denote if static or to determine which css class to apply to the node
|
||||
* @defaultValue ''
|
||||
*/
|
||||
classifier!: string;
|
||||
/**
|
||||
* parameters for method
|
||||
* @defaultValue ''
|
||||
*/
|
||||
parameters!: string;
|
||||
/**
|
||||
* return type for method
|
||||
* @defaultValue ''
|
||||
*/
|
||||
returnType!: string;
|
||||
|
||||
constructor(input: string, memberType: 'method' | 'attribute') {
|
||||
this.memberType = memberType;
|
||||
this.visibility = '';
|
||||
this.classifier = '';
|
||||
const sanitizedInput = sanitizeText(input, getConfig());
|
||||
this.parseMember(sanitizedInput);
|
||||
}
|
||||
|
||||
getDisplayDetails() {
|
||||
let displayText = this.visibility + parseGenericTypes(this.id);
|
||||
if (this.memberType === 'method') {
|
||||
displayText += `(${parseGenericTypes(this.parameters.trim())})`;
|
||||
if (this.returnType) {
|
||||
displayText += ' : ' + parseGenericTypes(this.returnType);
|
||||
}
|
||||
}
|
||||
|
||||
displayText = displayText.trim();
|
||||
const cssStyle = this.parseClassifier();
|
||||
|
||||
return {
|
||||
displayText,
|
||||
cssStyle,
|
||||
};
|
||||
}
|
||||
|
||||
parseMember(input: string) {
|
||||
let potentialClassifier = '';
|
||||
|
||||
if (this.memberType === 'method') {
|
||||
const methodRegEx = /([#+~-])?(.+)\((.*)\)([\s$*])?(.*)([$*])?/;
|
||||
const match = input.match(methodRegEx);
|
||||
if (match) {
|
||||
const detectedVisibility = match[1] ? match[1].trim() : '';
|
||||
|
||||
if (visibilityValues.includes(detectedVisibility)) {
|
||||
this.visibility = detectedVisibility as Visibility;
|
||||
}
|
||||
|
||||
this.id = match[2].trim();
|
||||
this.parameters = match[3] ? match[3].trim() : '';
|
||||
potentialClassifier = match[4] ? match[4].trim() : '';
|
||||
this.returnType = match[5] ? match[5].trim() : '';
|
||||
|
||||
if (potentialClassifier === '') {
|
||||
const lastChar = this.returnType.substring(this.returnType.length - 1);
|
||||
if (lastChar.match(/[$*]/)) {
|
||||
potentialClassifier = lastChar;
|
||||
this.returnType = this.returnType.substring(0, this.returnType.length - 1);
|
||||
}
|
||||
}
|
||||
}
|
||||
} else {
|
||||
const length = input.length;
|
||||
const firstChar = input.substring(0, 1);
|
||||
const lastChar = input.substring(length - 1);
|
||||
|
||||
if (visibilityValues.includes(firstChar)) {
|
||||
this.visibility = firstChar as Visibility;
|
||||
}
|
||||
|
||||
if (lastChar.match(/[*?]/)) {
|
||||
potentialClassifier = lastChar;
|
||||
}
|
||||
|
||||
this.id = input.substring(
|
||||
this.visibility === '' ? 0 : 1,
|
||||
potentialClassifier === '' ? length : length - 1
|
||||
);
|
||||
}
|
||||
|
||||
this.classifier = potentialClassifier;
|
||||
}
|
||||
|
||||
parseClassifier() {
|
||||
switch (this.classifier) {
|
||||
case '*':
|
||||
return 'font-style:italic;';
|
||||
case '$':
|
||||
return 'text-decoration:underline;';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface ClassNote {
|
||||
id: string;
|
||||
class: string;
|
||||
|
@@ -224,21 +224,14 @@ start
|
||||
| statements
|
||||
;
|
||||
|
||||
direction
|
||||
: direction_tb
|
||||
{ yy.setDirection('TB');}
|
||||
| direction_bt
|
||||
{ yy.setDirection('BT');}
|
||||
| direction_rl
|
||||
{ yy.setDirection('RL');}
|
||||
| direction_lr
|
||||
{ yy.setDirection('LR');}
|
||||
;
|
||||
|
||||
mermaidDoc
|
||||
: graphConfig
|
||||
;
|
||||
|
||||
graphConfig
|
||||
: CLASS_DIAGRAM NEWLINE statements EOF
|
||||
;
|
||||
|
||||
directive
|
||||
: openDirective typeDirective closeDirective NEWLINE
|
||||
| openDirective typeDirective ':' argDirective closeDirective NEWLINE
|
||||
@@ -260,10 +253,6 @@ closeDirective
|
||||
: close_directive { yy.parseDirective('}%%', 'close_directive', 'class'); }
|
||||
;
|
||||
|
||||
graphConfig
|
||||
: CLASS_DIAGRAM NEWLINE statements EOF
|
||||
;
|
||||
|
||||
statements
|
||||
: statement
|
||||
| statement NEWLINE
|
||||
@@ -292,7 +281,7 @@ statement
|
||||
| relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); }
|
||||
| namespaceStatement
|
||||
| classStatement
|
||||
| methodStatement
|
||||
| memberStatement
|
||||
| annotationStatement
|
||||
| clickStatement
|
||||
| cssClassStatement
|
||||
@@ -339,7 +328,7 @@ members
|
||||
| MEMBER members { $2.push($1);$$=$2;}
|
||||
;
|
||||
|
||||
methodStatement
|
||||
memberStatement
|
||||
: className {/*console.log('Rel found',$1);*/}
|
||||
| className LABEL {yy.addMember($1,yy.cleanupLabel($2));}
|
||||
| MEMBER {/*console.warn('Member',$1);*/}
|
||||
@@ -358,6 +347,17 @@ noteStatement
|
||||
| NOTE noteText { yy.addNote($2); }
|
||||
;
|
||||
|
||||
direction
|
||||
: direction_tb
|
||||
{ yy.setDirection('TB');}
|
||||
| direction_bt
|
||||
{ yy.setDirection('BT');}
|
||||
| direction_rl
|
||||
{ yy.setDirection('RL');}
|
||||
| direction_lr
|
||||
{ yy.setDirection('LR');}
|
||||
;
|
||||
|
||||
relation
|
||||
: relationType lineType relationType { $$={type1:$1,type2:$3,lineType:$2}; }
|
||||
| lineType relationType { $$={type1:'none',type2:$2,lineType:$1}; }
|
||||
|
@@ -109,25 +109,25 @@ g.classGroup line {
|
||||
}
|
||||
|
||||
#extensionStart, .extension {
|
||||
fill: ${options.mainBkg} !important;
|
||||
fill: transparent !important;
|
||||
stroke: ${options.lineColor} !important;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
#extensionEnd, .extension {
|
||||
fill: ${options.mainBkg} !important;
|
||||
fill: transparent !important;
|
||||
stroke: ${options.lineColor} !important;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
#aggregationStart, .aggregation {
|
||||
fill: ${options.mainBkg} !important;
|
||||
fill: transparent !important;
|
||||
stroke: ${options.lineColor} !important;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
#aggregationEnd, .aggregation {
|
||||
fill: ${options.mainBkg} !important;
|
||||
fill: transparent !important;
|
||||
stroke: ${options.lineColor} !important;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
@@ -172,7 +172,6 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
||||
// add class group
|
||||
const g = elem.append('g').attr('id', diagObj.db.lookUpDomId(id)).attr('class', 'classGroup');
|
||||
|
||||
// add title
|
||||
let title;
|
||||
if (classDef.link) {
|
||||
title = g
|
||||
@@ -209,47 +208,56 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
||||
}
|
||||
|
||||
const titleHeight = title.node().getBBox().height;
|
||||
let membersLine;
|
||||
let membersBox;
|
||||
let methodsLine;
|
||||
|
||||
const membersLine = g
|
||||
.append('line') // text label for the x axis
|
||||
.attr('x1', 0)
|
||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2);
|
||||
// don't draw box if no members
|
||||
if (classDef.members.length > 0) {
|
||||
membersLine = g
|
||||
.append('line') // text label for the x axis
|
||||
.attr('x1', 0)
|
||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2);
|
||||
|
||||
const members = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText');
|
||||
const members = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText');
|
||||
|
||||
isFirst = true;
|
||||
classDef.members.forEach(function (member) {
|
||||
addTspan(members, member, isFirst, conf);
|
||||
isFirst = false;
|
||||
});
|
||||
isFirst = true;
|
||||
classDef.members.forEach(function (member) {
|
||||
addTspan(members, member, isFirst, conf);
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
const membersBox = members.node().getBBox();
|
||||
membersBox = members.node().getBBox();
|
||||
}
|
||||
|
||||
const methodsLine = g
|
||||
.append('line') // text label for the x axis
|
||||
.attr('x1', 0)
|
||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height);
|
||||
// don't draw box if no methods
|
||||
if (classDef.methods.length > 0) {
|
||||
methodsLine = g
|
||||
.append('line') // text label for the x axis
|
||||
.attr('x1', 0)
|
||||
.attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
|
||||
.attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height);
|
||||
|
||||
const methods = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText');
|
||||
const methods = g
|
||||
.append('text') // text label for the x axis
|
||||
.attr('x', conf.padding)
|
||||
.attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
|
||||
.attr('fill', 'white')
|
||||
.attr('class', 'classText');
|
||||
|
||||
isFirst = true;
|
||||
isFirst = true;
|
||||
|
||||
classDef.methods.forEach(function (method) {
|
||||
addTspan(methods, method, isFirst, conf);
|
||||
isFirst = false;
|
||||
});
|
||||
classDef.methods.forEach(function (method) {
|
||||
addTspan(methods, method, isFirst, conf);
|
||||
isFirst = false;
|
||||
});
|
||||
}
|
||||
|
||||
const classBox = g.node().getBBox();
|
||||
var cssClassStr = ' ';
|
||||
@@ -278,8 +286,12 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
||||
title.insert('title').text(classDef.tooltip);
|
||||
}
|
||||
|
||||
membersLine.attr('x2', rectWidth);
|
||||
methodsLine.attr('x2', rectWidth);
|
||||
if (membersLine) {
|
||||
membersLine.attr('x2', rectWidth);
|
||||
}
|
||||
if (methodsLine) {
|
||||
methodsLine.attr('x2', rectWidth);
|
||||
}
|
||||
|
||||
classInfo.width = rectWidth;
|
||||
classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin;
|
||||
@@ -291,7 +303,7 @@ export const getClassTitleString = function (classDef) {
|
||||
let classTitleString = classDef.id;
|
||||
|
||||
if (classDef.type) {
|
||||
classTitleString += '<' + classDef.type + '>';
|
||||
classTitleString += '<' + parseGenericTypes(classDef.type) + '>';
|
||||
}
|
||||
|
||||
return classTitleString;
|
||||
@@ -360,82 +372,19 @@ export const drawNote = function (elem, note, conf, diagObj) {
|
||||
return noteInfo;
|
||||
};
|
||||
|
||||
export const parseMember = function (text) {
|
||||
let displayText = '';
|
||||
let cssStyle = '';
|
||||
let returnType = '';
|
||||
|
||||
let visibility = '';
|
||||
let firstChar = text.substring(0, 1);
|
||||
let lastChar = text.substring(text.length - 1, text.length);
|
||||
|
||||
if (firstChar.match(/[#+~-]/)) {
|
||||
visibility = firstChar;
|
||||
}
|
||||
|
||||
let noClassifierRe = /[\s\w)~]/;
|
||||
if (!lastChar.match(noClassifierRe)) {
|
||||
cssStyle = parseClassifier(lastChar);
|
||||
}
|
||||
|
||||
const startIndex = visibility === '' ? 0 : 1;
|
||||
let endIndex = cssStyle === '' ? text.length : text.length - 1;
|
||||
text = text.substring(startIndex, endIndex);
|
||||
|
||||
const methodStart = text.indexOf('(');
|
||||
const methodEnd = text.indexOf(')');
|
||||
const isMethod = methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length;
|
||||
|
||||
if (isMethod) {
|
||||
let methodName = text.substring(0, methodStart).trim();
|
||||
|
||||
const parameters = text.substring(methodStart + 1, methodEnd);
|
||||
|
||||
displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')';
|
||||
|
||||
if (methodEnd < text.length) {
|
||||
// special case: classifier after the closing parenthesis
|
||||
let potentialClassifier = text.substring(methodEnd + 1, methodEnd + 2);
|
||||
if (cssStyle === '' && !potentialClassifier.match(noClassifierRe)) {
|
||||
cssStyle = parseClassifier(potentialClassifier);
|
||||
returnType = text.substring(methodEnd + 2).trim();
|
||||
} else {
|
||||
returnType = text.substring(methodEnd + 1).trim();
|
||||
}
|
||||
|
||||
if (returnType !== '') {
|
||||
if (returnType.charAt(0) === ':') {
|
||||
returnType = returnType.substring(1).trim();
|
||||
}
|
||||
returnType = ' : ' + parseGenericTypes(returnType);
|
||||
displayText += returnType;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// finally - if all else fails, just send the text back as written (other than parsing for generic types)
|
||||
displayText = visibility + parseGenericTypes(text);
|
||||
}
|
||||
|
||||
return {
|
||||
displayText,
|
||||
cssStyle,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a <tspan> for a member in a diagram
|
||||
*
|
||||
* @param {SVGElement} textEl The element to append to
|
||||
* @param {string} txt The member
|
||||
* @param {string} member The member
|
||||
* @param {boolean} isFirst
|
||||
* @param {{ padding: string; textHeight: string }} conf The configuration for the member
|
||||
*/
|
||||
const addTspan = function (textEl, txt, isFirst, conf) {
|
||||
let member = parseMember(txt);
|
||||
const addTspan = function (textEl, member, isFirst, conf) {
|
||||
const { displayText, cssStyle } = member.getDisplayDetails();
|
||||
const tSpan = textEl.append('tspan').attr('x', conf.padding).text(displayText);
|
||||
|
||||
const tSpan = textEl.append('tspan').attr('x', conf.padding).text(member.displayText);
|
||||
|
||||
if (member.cssStyle !== '') {
|
||||
if (cssStyle !== '') {
|
||||
tSpan.attr('style', member.cssStyle);
|
||||
}
|
||||
|
||||
@@ -444,27 +393,9 @@ const addTspan = function (textEl, txt, isFirst, conf) {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Gives the styles for a classifier
|
||||
*
|
||||
* @param {'+' | '-' | '#' | '~' | '*' | '$'} classifier The classifier string
|
||||
* @returns {string} Styling for the classifier
|
||||
*/
|
||||
const parseClassifier = function (classifier) {
|
||||
switch (classifier) {
|
||||
case '*':
|
||||
return 'font-style:italic;';
|
||||
case '$':
|
||||
return 'text-decoration:underline;';
|
||||
default:
|
||||
return '';
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
getClassTitleString,
|
||||
drawClass,
|
||||
drawEdge,
|
||||
drawNote,
|
||||
parseMember,
|
||||
};
|
||||
|
@@ -1,330 +1,27 @@
|
||||
import svgDraw from './svgDraw.js';
|
||||
import { JSDOM } from 'jsdom';
|
||||
|
||||
describe('given a string representing class method, ', function () {
|
||||
it('should handle class names with generics', function () {
|
||||
const classDef = {
|
||||
id: 'Car',
|
||||
type: 'T',
|
||||
label: 'Car',
|
||||
};
|
||||
describe('given a string representing a class, ', function () {
|
||||
describe('when class name includes generic, ', function () {
|
||||
it('should return correct text for generic', function () {
|
||||
const classDef = {
|
||||
id: 'Car',
|
||||
type: 'T',
|
||||
label: 'Car',
|
||||
};
|
||||
|
||||
let actual = svgDraw.getClassTitleString(classDef);
|
||||
expect(actual).toBe('Car<T>');
|
||||
});
|
||||
|
||||
describe('when parsing base method declaration', function () {
|
||||
it('should handle simple declaration', function () {
|
||||
const str = 'foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
let actual = svgDraw.getClassTitleString(classDef);
|
||||
expect(actual).toBe('Car<T>');
|
||||
});
|
||||
it('should return correct text for nested generics', function () {
|
||||
const classDef = {
|
||||
id: 'Car',
|
||||
type: 'T~T~',
|
||||
label: 'Car',
|
||||
};
|
||||
|
||||
it('should handle declaration with parameters', function () {
|
||||
const str = 'foo(int id)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(int id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with multiple parameters', function () {
|
||||
const str = 'foo(int id, object thing)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(int id, object thing)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with single item in parameters', function () {
|
||||
const str = 'foo(id)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with single item in parameters with extra spaces', function () {
|
||||
const str = ' foo ( id) ';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with generic parameter', function () {
|
||||
const str = 'foo(List~int~)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(List<int>)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with normal and generic parameter', function () {
|
||||
const str = 'foo(int, List~int~)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(int, List<int>)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with return value', function () {
|
||||
const str = 'foo(id) int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with colon return value', function () {
|
||||
const str = 'foo(id) : int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with generic return value', function () {
|
||||
const str = 'foo(id) List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with colon generic return value', function () {
|
||||
const str = 'foo(id) : List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with all possible markup', function () {
|
||||
const str = '+foo ( List~int~ ids )* List~Item~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo(List<int> ids) : List<Item>');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle method declaration with nested generics', function () {
|
||||
const str = '+foo ( List~List~int~~ ids )* List~List~Item~~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo(List<List<int>> ids) : List<List<Item>>');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing method visibility', function () {
|
||||
it('should correctly handle public', function () {
|
||||
const str = '+foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should correctly handle private', function () {
|
||||
const str = '-foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('-foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should correctly handle protected', function () {
|
||||
const str = '#foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('#foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should correctly handle package/internal', function () {
|
||||
const str = '~foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('~foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing method classifier', function () {
|
||||
it('should handle abstract method', function () {
|
||||
const str = 'foo()*';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle abstract method with return type', function () {
|
||||
const str = 'foo(name: String) int*';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle abstract method classifier after parenthesis with return type', function () {
|
||||
const str = 'foo(name: String)* int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier', function () {
|
||||
const str = 'foo()$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier with return type', function () {
|
||||
const str = 'foo(name: String) int$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier with colon and return type', function () {
|
||||
const str = 'foo(name: String): int$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier after parenthesis with return type', function () {
|
||||
const str = 'foo(name: String)$ int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should ignore unknown character for classifier', function () {
|
||||
const str = 'foo()!';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a string representing class member, ', function () {
|
||||
describe('when parsing member declaration', function () {
|
||||
it('should handle simple field', function () {
|
||||
const str = 'id';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('id');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with type', function () {
|
||||
const str = 'int id';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('int id');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with type (name first)', function () {
|
||||
const str = 'id: int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('id: int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle array field', function () {
|
||||
const str = 'int[] ids';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('int[] ids');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle array field (name first)', function () {
|
||||
const str = 'ids: int[]';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('ids: int[]');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with generic type', function () {
|
||||
const str = 'List~int~ ids';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('List<int> ids');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with generic type (name first)', function () {
|
||||
const str = 'ids: List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('ids: List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing classifiers', function () {
|
||||
it('should handle static field', function () {
|
||||
const str = 'String foo$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('String foo');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static field (name first)', function () {
|
||||
const str = 'foo: String$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo: String');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static field with generic type', function () {
|
||||
const str = 'List~String~ foo$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('List<String> foo');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static field with generic type (name first)', function () {
|
||||
const str = 'foo: List~String~$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo: List<String>');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle field with nested generic type', function () {
|
||||
const str = 'List~List~int~~ idLists';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('List<List<int>> idLists');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with nested generic type (name first)', function () {
|
||||
const str = 'idLists: List~List~int~~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('idLists: List<List<int>>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
let actual = svgDraw.getClassTitleString(classDef);
|
||||
expect(actual).toBe('Car<T<T>>');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,74 +0,0 @@
|
||||
import { sanitizeText, removeScript, parseGenericTypes } from './common.js';
|
||||
|
||||
describe('when securityLevel is antiscript, all script must be removed', function () {
|
||||
/**
|
||||
* @param {string} original The original text
|
||||
* @param {string} result The expected sanitized text
|
||||
*/
|
||||
function compareRemoveScript(original, result) {
|
||||
expect(removeScript(original).trim()).toEqual(result);
|
||||
}
|
||||
|
||||
it('should remove all script block, script inline.', function () {
|
||||
const labelString = `1
|
||||
Act1: Hello 1<script src="http://abc.com/script1.js"></script>1
|
||||
<b>Act2</b>:
|
||||
1<script>
|
||||
alert('script run......');
|
||||
</script>1
|
||||
1`;
|
||||
const exactlyString = `1
|
||||
Act1: Hello 11
|
||||
<b>Act2</b>:
|
||||
11
|
||||
1`;
|
||||
compareRemoveScript(labelString, exactlyString);
|
||||
});
|
||||
|
||||
it('should remove all javascript urls', function () {
|
||||
compareRemoveScript(
|
||||
`This is a <a href="javascript:runHijackingScript();">clean link</a> + <a href="javascript:runHijackingScript();">clean link</a>
|
||||
and <a href="javascript:bipassedMining();">me too</a>`,
|
||||
`This is a <a>clean link</a> + <a>clean link</a>
|
||||
and <a>me too</a>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should detect malicious images', function () {
|
||||
compareRemoveScript(`<img onerror="alert('hello');">`, `<img>`);
|
||||
});
|
||||
|
||||
it('should detect iframes', function () {
|
||||
compareRemoveScript(
|
||||
`<iframe src="http://abc.com/script1.js"></iframe>
|
||||
<iframe src="http://example.com/iframeexample"></iframe>`,
|
||||
''
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sanitize text', function () {
|
||||
it('should remove script tag', function () {
|
||||
const maliciousStr = 'javajavascript:script:alert(1)';
|
||||
const result = sanitizeText(maliciousStr, {
|
||||
securityLevel: 'strict',
|
||||
flowchart: { htmlLabels: true },
|
||||
});
|
||||
expect(result).not.toContain('javascript:alert(1)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generic parser', function () {
|
||||
it('should parse generic types', function () {
|
||||
expect(parseGenericTypes('test~T~')).toEqual('test<T>');
|
||||
expect(parseGenericTypes('test~Array~Array~string~~~')).toEqual('test<Array<Array<string>>>');
|
||||
expect(parseGenericTypes('test~Array~Array~string[]~~~')).toEqual(
|
||||
'test<Array<Array<string[]>>>'
|
||||
);
|
||||
expect(parseGenericTypes('test ~Array~Array~string[]~~~')).toEqual(
|
||||
'test <Array<Array<string[]>>>'
|
||||
);
|
||||
expect(parseGenericTypes('~test')).toEqual('~test');
|
||||
expect(parseGenericTypes('~test Array~string~')).toEqual('~test Array<string>');
|
||||
});
|
||||
});
|
87
packages/mermaid/src/diagrams/common/common.spec.ts
Normal file
87
packages/mermaid/src/diagrams/common/common.spec.ts
Normal file
@@ -0,0 +1,87 @@
|
||||
import { sanitizeText, removeScript, parseGenericTypes, countOccurrence } from './common.js';
|
||||
|
||||
describe('when securityLevel is antiscript, all script must be removed', () => {
|
||||
/**
|
||||
* @param original - The original text
|
||||
* @param result - The expected sanitized text
|
||||
*/
|
||||
function compareRemoveScript(original: string, result: string) {
|
||||
expect(removeScript(original).trim()).toEqual(result);
|
||||
}
|
||||
|
||||
it('should remove all script block, script inline.', () => {
|
||||
const labelString = `1
|
||||
Act1: Hello 1<script src="http://abc.com/script1.js"></script>1
|
||||
<b>Act2</b>:
|
||||
1<script>
|
||||
alert('script run......');
|
||||
</script>1
|
||||
1`;
|
||||
const exactlyString = `1
|
||||
Act1: Hello 11
|
||||
<b>Act2</b>:
|
||||
11
|
||||
1`;
|
||||
compareRemoveScript(labelString, exactlyString);
|
||||
});
|
||||
|
||||
it('should remove all javascript urls', () => {
|
||||
compareRemoveScript(
|
||||
`This is a <a href="javascript:runHijackingScript();">clean link</a> + <a href="javascript:runHijackingScript();">clean link</a>
|
||||
and <a href="javascript:bipassedMining();">me too</a>`,
|
||||
`This is a <a>clean link</a> + <a>clean link</a>
|
||||
and <a>me too</a>`
|
||||
);
|
||||
});
|
||||
|
||||
it('should detect malicious images', () => {
|
||||
compareRemoveScript(`<img onerror="alert('hello');">`, `<img>`);
|
||||
});
|
||||
|
||||
it('should detect iframes', () => {
|
||||
compareRemoveScript(
|
||||
`<iframe src="http://abc.com/script1.js"></iframe>
|
||||
<iframe src="http://example.com/iframeexample"></iframe>`,
|
||||
''
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sanitize text', () => {
|
||||
it('should remove script tag', () => {
|
||||
const maliciousStr = 'javajavascript:script:alert(1)';
|
||||
const result = sanitizeText(maliciousStr, {
|
||||
securityLevel: 'strict',
|
||||
flowchart: { htmlLabels: true },
|
||||
});
|
||||
expect(result).not.toContain('javascript:alert(1)');
|
||||
});
|
||||
});
|
||||
|
||||
describe('generic parser', () => {
|
||||
it.each([
|
||||
['test~T~', 'test<T>'],
|
||||
['test~Array~Array~string~~~', 'test<Array<Array<string>>>'],
|
||||
['test~Array~Array~string[]~~~', 'test<Array<Array<string[]>>>'],
|
||||
['test ~Array~Array~string[]~~~', 'test <Array<Array<string[]>>>'],
|
||||
['~test', '~test'],
|
||||
['~test~T~', '~test<T>'],
|
||||
])('should parse generic types: %s to %s', (input: string, expected: string) => {
|
||||
expect(parseGenericTypes(input)).toEqual(expected);
|
||||
});
|
||||
});
|
||||
|
||||
it.each([
|
||||
['', '', 0],
|
||||
['', 'x', 0],
|
||||
['test', 'x', 0],
|
||||
['test', 't', 2],
|
||||
['test', 'te', 1],
|
||||
['test~T~', '~', 2],
|
||||
['test~Array~Array~string~~~', '~', 6],
|
||||
])(
|
||||
'should count `%s` to contain occurrences of `%s` to be `%i`',
|
||||
(str: string, substring: string, count: number) => {
|
||||
expect(countOccurrence(str, substring)).toEqual(count);
|
||||
}
|
||||
);
|
@@ -1,6 +1,7 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import { MermaidConfig } from '../../config.type.js';
|
||||
import type { MermaidConfig } from '../../config.type.js';
|
||||
|
||||
// Remove and ignore br:s
|
||||
export const lineBreakRegex = /<br\s*\/?>/gi;
|
||||
|
||||
/**
|
||||
@@ -177,23 +178,80 @@ export const getMin = function (...values: number[]): number {
|
||||
* @param text - The text to convert
|
||||
* @returns The converted string
|
||||
*/
|
||||
export const parseGenericTypes = function (text: string): string {
|
||||
let cleanedText = text;
|
||||
export const parseGenericTypes = function (input: string): string {
|
||||
const inputSets = input.split(/(,)/);
|
||||
const output = [];
|
||||
|
||||
if (text.split('~').length - 1 >= 2) {
|
||||
let newCleanedText = cleanedText;
|
||||
for (let i = 0; i < inputSets.length; i++) {
|
||||
let thisSet = inputSets[i];
|
||||
|
||||
// use a do...while loop instead of replaceAll to detect recursion
|
||||
// e.g. Array~Array~T~~
|
||||
do {
|
||||
cleanedText = newCleanedText;
|
||||
newCleanedText = cleanedText.replace(/~([^\s,:;]+)~/, '<$1>');
|
||||
} while (newCleanedText != cleanedText);
|
||||
// if the original input included a value such as "~K, V~"", these will be split into
|
||||
// an array of ["~K",","," V~"].
|
||||
// This means that on each call of processSet, there will only be 1 ~ present
|
||||
// To account for this, if we encounter a ",", we are checking the previous and next sets in the array
|
||||
// to see if they contain matching ~'s
|
||||
// in which case we are assuming that they should be rejoined and sent to be processed
|
||||
if (thisSet === ',' && i > 0 && i + 1 < inputSets.length) {
|
||||
const previousSet = inputSets[i - 1];
|
||||
const nextSet = inputSets[i + 1];
|
||||
|
||||
return parseGenericTypes(newCleanedText);
|
||||
} else {
|
||||
return cleanedText;
|
||||
if (shouldCombineSets(previousSet, nextSet)) {
|
||||
thisSet = previousSet + ',' + nextSet;
|
||||
i++; // Move the index forward to skip the next iteration since we're combining sets
|
||||
output.pop();
|
||||
}
|
||||
}
|
||||
|
||||
output.push(processSet(thisSet));
|
||||
}
|
||||
|
||||
return output.join('');
|
||||
};
|
||||
|
||||
export const countOccurrence = (string: string, substring: string): number => {
|
||||
return Math.max(0, string.split(substring).length - 1);
|
||||
};
|
||||
|
||||
const shouldCombineSets = (previousSet: string, nextSet: string): boolean => {
|
||||
const prevCount = countOccurrence(previousSet, '~');
|
||||
const nextCount = countOccurrence(nextSet, '~');
|
||||
|
||||
return prevCount === 1 && nextCount === 1;
|
||||
};
|
||||
|
||||
const processSet = (input: string): string => {
|
||||
const tildeCount = countOccurrence(input, '~');
|
||||
let hasStartingTilde = false;
|
||||
|
||||
if (tildeCount <= 1) {
|
||||
return input;
|
||||
}
|
||||
|
||||
// If there is an odd number of tildes, and the input starts with a tilde, we need to remove it and add it back in later
|
||||
if (tildeCount % 2 !== 0 && input.startsWith('~')) {
|
||||
input = input.substring(1);
|
||||
hasStartingTilde = true;
|
||||
}
|
||||
|
||||
const chars = [...input];
|
||||
|
||||
let first = chars.indexOf('~');
|
||||
let last = chars.lastIndexOf('~');
|
||||
|
||||
while (first !== -1 && last !== -1 && first !== last) {
|
||||
chars[first] = '<';
|
||||
chars[last] = '>';
|
||||
|
||||
first = chars.indexOf('~');
|
||||
last = chars.lastIndexOf('~');
|
||||
}
|
||||
|
||||
// Add the starting tilde back in if we removed it
|
||||
if (hasStartingTilde) {
|
||||
chars.unshift('~');
|
||||
}
|
||||
|
||||
return chars.join('');
|
||||
};
|
||||
|
||||
export default {
|
||||
|
32
packages/mermaid/src/diagrams/common/commonDb.ts
Normal file
32
packages/mermaid/src/diagrams/common/commonDb.ts
Normal file
@@ -0,0 +1,32 @@
|
||||
import { sanitizeText as _sanitizeText } from './common.js';
|
||||
import { getConfig } from '../../config.js';
|
||||
|
||||
let accTitle = '';
|
||||
let diagramTitle = '';
|
||||
let accDescription = '';
|
||||
|
||||
const sanitizeText = (txt: string): string => _sanitizeText(txt, getConfig());
|
||||
|
||||
export const clear = (): void => {
|
||||
accTitle = '';
|
||||
accDescription = '';
|
||||
diagramTitle = '';
|
||||
};
|
||||
|
||||
export const setAccTitle = (txt: string): void => {
|
||||
accTitle = sanitizeText(txt).replace(/^\s+/g, '');
|
||||
};
|
||||
|
||||
export const getAccTitle = (): string => accTitle;
|
||||
|
||||
export const setAccDescription = (txt: string): void => {
|
||||
accDescription = sanitizeText(txt).replace(/\n\s+/g, '\n');
|
||||
};
|
||||
|
||||
export const getAccDescription = (): string => accDescription;
|
||||
|
||||
export const setDiagramTitle = (txt: string): void => {
|
||||
diagramTitle = sanitizeText(txt);
|
||||
};
|
||||
|
||||
export const getDiagramTitle = (): string => diagramTitle;
|
58
packages/mermaid/src/diagrams/common/commonTypes.ts
Normal file
58
packages/mermaid/src/diagrams/common/commonTypes.ts
Normal file
@@ -0,0 +1,58 @@
|
||||
export interface RectData {
|
||||
x: number;
|
||||
y: number;
|
||||
fill: string;
|
||||
width: number;
|
||||
height: number;
|
||||
stroke: string;
|
||||
class?: string;
|
||||
color?: string;
|
||||
rx?: number;
|
||||
ry?: number;
|
||||
attrs?: Record<string, string | number>;
|
||||
anchor?: string;
|
||||
}
|
||||
|
||||
export interface Bound {
|
||||
startx: number;
|
||||
stopx: number;
|
||||
starty: number;
|
||||
stopy: number;
|
||||
fill: string;
|
||||
stroke: string;
|
||||
}
|
||||
|
||||
export interface TextData {
|
||||
x: number;
|
||||
y: number;
|
||||
anchor: string;
|
||||
text: string;
|
||||
textMargin: number;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export interface TextObject {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
fill?: string;
|
||||
anchor?: string;
|
||||
'text-anchor': string;
|
||||
style: string;
|
||||
textMargin: number;
|
||||
rx: number;
|
||||
ry: number;
|
||||
tspan: boolean;
|
||||
valign?: string;
|
||||
}
|
||||
|
||||
export type D3RectElement = d3.Selection<SVGRectElement, unknown, Element | null, unknown>;
|
||||
|
||||
export type D3UseElement = d3.Selection<SVGUseElement, unknown, Element | null, unknown>;
|
||||
|
||||
export type D3ImageElement = d3.Selection<SVGImageElement, unknown, Element | null, unknown>;
|
||||
|
||||
export type D3TextElement = d3.Selection<SVGTextElement, unknown, Element | null, unknown>;
|
||||
|
||||
export type D3TSpanElement = d3.Selection<SVGTSpanElement, unknown, Element | null, unknown>;
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user