Merge branch 'develop' into patch-1

This commit is contained in:
Sidharth Vinod 2025-01-25 12:14:31 +05:30 committed by GitHub
commit 3536ceb5d3
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
67 changed files with 4981 additions and 2552 deletions

View File

@ -0,0 +1,5 @@
---
'mermaid': patch
---
Fixes for consistent edge id creation & handling edge cases for animate edge feature

View File

@ -0,0 +1,5 @@
---
'mermaid': patch
---
Fix for issue #6195 - allowing @ signs inside node labels

View File

@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: `mermaidAPI.getDiagramFromText()` now returns a new different db for each class diagram

View File

@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: revert state db to resolve getData returning empty nodes and edges

View File

@ -0,0 +1,8 @@
---
'mermaid': minor
---
Flowchart new syntax for node metadata bugs
- Incorrect label mapping for nodes when using `&`
- Syntax error when `}` with trailing spaces before new line

View File

@ -0,0 +1,5 @@
---
'mermaid': minor
---
Adding support for animation of flowchart edges

View File

@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: `mermaidAPI.getDiagramFromText()` now returns a new different db for each flowchart

View File

@ -0,0 +1,5 @@
---
'mermaid': patch
---
fix: Gantt, Sankey and User Journey diagram are now able to pick font-family from mermaid config.

View File

@ -0,0 +1,5 @@
---
'mermaid': patch
---
`mermaidAPI.getDiagramFromText()` now returns a new different db for each state diagram

View File

@ -42,4 +42,4 @@ jobs:
working-directory: ./packages/mermaid
run: pnpm run docs:build
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c # main
- uses: autofix-ci/action@551dded8c6cc8a1054039c8bc0b8b48c51dfc6ef # main

View File

@ -7,6 +7,9 @@ on:
- master
- release/**
pull_request:
issue_comment:
types:
- created
merge_group:
concurrency: ${{ github.workflow }}-${{ github.ref }}
@ -28,8 +31,12 @@ env:
) ||
github.event.before
}}
# Check if this is a new comment with '/visual-test'
RUN_VISUAL_TEST: >-
${{ github.event_name == 'issue_comment' && github.event.action == 'created' && contains(github.event.comment.body, '/visual-test') && github.event.issue.pull_request != null }}
jobs:
cache:
if: ${{ github.event_name != 'issue_comment' || contains(github.event.comment.body, '/visual-test') }}
runs-on: ubuntu-latest
container:
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
@ -127,16 +134,17 @@ jobs:
# e.g. if this action was run from a fork
record: ${{ secrets.CYPRESS_RECORD_KEY != '' }}
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
VITEST_COVERAGE: true
# Only set Argos environment variables if the visual test comment trigger is present
ARGOS_TOKEN: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.ARGOS_TOKEN || '' }}
ARGOS_PARALLEL: ${{ env.RUN_VISUAL_TEST == 'true' }}
ARGOS_PARALLEL_TOTAL: ${{ env.RUN_VISUAL_TEST == 'true' && strategy.job-total || 1 }}
ARGOS_PARALLEL_INDEX: ${{ env.RUN_VISUAL_TEST == 'true' && matrix.containers || 1 }}
CYPRESS_COMMIT: ${{ github.sha }}
ARGOS_TOKEN: ${{ secrets.ARGOS_TOKEN }}
ARGOS_PARALLEL: true
ARGOS_PARALLEL_TOTAL: ${{ strategy.job-total }}
ARGOS_PARALLEL_INDEX: ${{ matrix.containers }}
CYPRESS_RECORD_KEY: ${{ env.RUN_VISUAL_TEST == 'true' && secrets.CYPRESS_RECORD_KEY || ''}}
SPLIT: ${{ strategy.job-total }}
SPLIT_INDEX: ${{ strategy.job-index }}
SPLIT_FILE: 'cypress/timings.json'
VITEST_COVERAGE: true
- name: Upload Coverage to Codecov
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0

View File

@ -26,7 +26,7 @@ jobs:
results_format: sarif
publish_results: true
- name: Upload artifact
uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20
uses: actions/upload-artifact@ff15f0306b3f739f7b6fd43fb5d26cd321bd4de5 # v3.pre.node20
with:
name: SARIF file
path: results.sarif

View File

@ -23,7 +23,7 @@ export default eyesPlugin(
});
// copy any needed variables from process.env to config.env
config.env.useAppli = process.env.USE_APPLI ? true : false;
config.env.useArgos = !!process.env.CI;
config.env.useArgos = !!process.env.CI && !!process.env.ARGOS_TOKEN;
if (config.env.useArgos) {
registerArgosTask(on, config, {

View File

@ -1076,4 +1076,41 @@ end
);
});
});
describe('New @ sytax for node metadata edge cases', () => {
it('should be possible to use @ syntax to add labels on multi nodes', () => {
imgSnapshotTest(
`flowchart TB
n2["label for n2"] & n4@{ label: "labe for n4"} & n5@{ label: "labe for n5"}
`,
{}
);
});
it('should be possible to use @ syntax to add labels with trail spaces and &', () => {
imgSnapshotTest(
`flowchart TB
n2["label for n2"] & n4@{ label: "labe for n4"} & n5@{ label: "labe for n5"}
`,
{}
);
});
it('should be possible to use @ syntax to add labels with trail spaces', () => {
imgSnapshotTest(
`flowchart TB
n2["label for n2"]
n4@{ label: "labe for n4"}
n5@{ label: "labe for n5"}
`,
{}
);
});
it('should be possible to use @ syntax to add labels with trail spaces and edge/link', () => {
imgSnapshotTest(
`flowchart TD
A["A"] --> B["for B"] & C@{ label: "for c"} & E@{label : "for E"}
D@{label: "for D"}
`,
{}
);
});
});
});

View File

@ -78,35 +78,78 @@
font-family: monospace;
font-size: 72px;
}
pre {
width: 100%;
}
/* tspan {
font-size: 6px !important;
} */
/* .flowchart-link {
stroke-dasharray: 4, 4 !important;
animation: flow 1s linear infinite;
animation: dashdraw 4.93282s linear infinite;
stroke-width: 2px !important;
} */
@keyframes dashdraw {
from {
stroke-dashoffset: 0;
}
}
/*stroke-width:2;stroke-dasharray:10.000000,9.865639;stroke-dashoffset:-198.656393;animation: 4.932820s linear infinite;*/
/* stroke-width:2;stroke-dasharray:10.000000,9.865639;stroke-dashoffset:-198.656393;animation: dashdraw 4.932820s linear infinite;*/
</style>
</head>
<body>
<pre id="diagram4" class="mermaid">
---
config:
layout: elk
---
flowchart LR
subgraph S2
subgraph s1["APA"]
D{"Use the editor"}
end
D -- Mermaid js --> I{"fa:fa-code Text"}
D --> I
D --> I
end
AB["apa@apa@"] --> B(("`apa@apa`"))
</pre>
<pre id="diagram4" class="mermaid">
flowchart
D(("for D"))
</pre>
<pre id="diagram4" class="mermaid">
flowchart LR
A e1@==> B
e1@{ animate: true}
</pre>
<pre id="diagram4" class="mermaid">
flowchart LR
A e1@--> B
classDef animate stroke-width:2,stroke-dasharray:10\,8,stroke-dashoffset:-180,animation: edge-animation-frame 6s linear infinite, stroke-linecap: round
class e1 animate
</pre>
<h2>infinite</h2>
<pre id="diagram4" class="mermaid2">
flowchart LR
A e1@--> B
classDef animate stroke-dasharray: 9\,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class e1 animate
</pre>
<h2>Mermaid - edge-animation-slow</h2>
<pre id="diagram4" class="mermaid2">
flowchart LR
A e1@--> B
e1@{ animation: fast}
</pre>
<h2>Mermaid - edge-animation-fast</h2>
<pre id="diagram4" class="mermaid2">
flowchart LR
A e1@--> B
classDef animate stroke-dasharray: 1000,stroke-dashoffset: 1000,animation: dash 10s linear;
class e1 edge-animation-fast
</pre>
<pre id="diagram4" class="mermaid2">
info </pre
>
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@ -131,7 +174,7 @@ config:
end
end
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@ -144,7 +187,7 @@ config:
D-->I
D-->I
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@ -183,7 +226,7 @@ flowchart LR
n8@{ shape: rect}
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@ -199,7 +242,7 @@ flowchart LR
</pre>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@ -208,7 +251,7 @@ flowchart LR
A{A} --> B & C
</pre
>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@ -220,7 +263,7 @@ flowchart LR
end
</pre
>
<pre id="diagram4" class="mermaid">
<pre id="diagram4" class="mermaid2">
---
config:
layout: elk
@ -391,7 +434,10 @@ kanban
window.callback = function () {
alert('A callback was triggered');
};
mermaid.initialize({
function callback() {
alert('It worked');
}
await mermaid.initialize({
// theme: 'base',
// theme: 'default',
// theme: 'forest',
@ -403,9 +449,11 @@ kanban
// layout: 'fixed',
// htmlLabels: false,
flowchart: { titleTopMargin: 10 },
// fontFamily: 'Caveat',
// fontFamily: 'Kalam',
// fontFamily: 'courier',
fontFamily: 'arial',
sequence: {
actorFontFamily: 'courier',
noteFontFamily: 'courier',
@ -417,10 +465,9 @@ kanban
fontSize: 12,
logLevel: 0,
securityLevel: 'loose',
callback,
});
function callback() {
alert('It worked');
}
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);

View File

@ -62,56 +62,23 @@
<body style="display: flex; gap: 2rem; flex-direction: row">
<pre id="diagram4" class="mermaid">
flowchart LR
A@{ icon: "fa:window-minimize", form: circle }
E@{ icon: "fa:window-minimize", form: circle }
B@{ icon: "fa:bell", form: circle }
B2@{ icon: "fa:bell", form: circle }
C@{ icon: "fa:address-book", form: square }
D@{ icon: "fa:star-half", form: square }
A --> E
B --> B2
flowchart
A --> A
subgraph B
B1 --> B1
end
subgraph C
subgraph C1
C2 --> C2
subgraph D
D1 --> D1
end
D --> D
end
C1 --> C1
end
</pre>
<pre id="diagram4" class="mermaid2">
flowchart TB
A --test2--> B2@{ icon: "fa:bell", form: "rounded", label: "B2 aiduaid uyawduad uaduabd uyduadb", pos: "b" }
B2 --test--> C
D --> B2 --> E
style B2 fill:#f9f,stroke:#333,stroke-width:4px
</pre
>
<pre id="diagram43" class="mermaid2">
flowchart BT
A --test2--> B2@{ icon: "fa:bell", form: "square", label: "B2", pos: "t", h: 40, w: 30 }
B2 --test--> C
D --> B2 --> E
</pre
>
<pre id="diagram4" class="mermaid2">
flowchart BT
A --test2--> B2@{ icon: "fa:bell", label: "B2 awiugdawu uydgayuiwd wuydguy", pos: "b", h: 40, w: 30 }
B2 --test--> C
</pre
>
<pre id="diagram43" class="mermaid2">
flowchart BT
A --test2--> B2@{ icon: "fa:bell", label: "B2 dawuygd ayuwgd uy", pos: "t", h: 40, w: 30 }
B2 --test--> C
</pre
>
<pre id="diagram6" class="mermaid2">
flowchart TB
A --> B2@{ icon: "fa:bell", form: "circle", label: "test augfuyfavf ydvaubfuac", pos: "t", w: 200, h: 100 } --> C
</pre
>
<pre id="diagram6" class="mermaid2">
flowchart TB
A --> B2@{ icon: "fa:bell", form: "circle", label: "test augfuyfavf ydvaubfuac", pos: "b", w: 200, h: 100 } --> C
D --> B2 --> E
</pre
>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import layouts from './mermaid-layout-elk.esm.mjs';

View File

@ -20,7 +20,7 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.ts:144](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L144)
[packages/mermaid/src/rendering-util/types.ts:148](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L148)
---
@ -30,7 +30,7 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.ts:143](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L143)
[packages/mermaid/src/rendering-util/types.ts:147](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L147)
---
@ -40,4 +40,4 @@
#### Defined in
[packages/mermaid/src/rendering-util/types.ts:142](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L142)
[packages/mermaid/src/rendering-util/types.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/types.ts#L146)

View File

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

View File

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

View File

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

View File

@ -99,8 +99,7 @@ Blogging frameworks and platforms
- [Nextra](https://nextra.site/)
- [Mermaid](https://nextra.site/docs/guide/mermaid)
- [WordPress](https://wordpress.org)
- [WordPress Markdown Editor](https://wordpress.org/plugins/wp-githuber-md)
- [WP-ReliableMD](https://wordpress.org/plugins/wp-reliablemd/)
- [MerPRess](https://wordpress.org/plugins/merpress/)
### CMS/ECM

View File

@ -1183,6 +1183,91 @@ flowchart TB
B --> D
```
### Attaching an ID to Edges
Mermaid now supports assigning IDs to edges, similar to how IDs and metadata can be attached to nodes. This feature lays the groundwork for more advanced styling, classes, and animation capabilities on edges.
**Syntax:**
To give an edge an ID, prepend the edge syntax with the ID followed by an `@` character. For example:
```mermaid-example
flowchart LR
A e1@> B
```
```mermaid
flowchart LR
A e1@> B
```
In this example, `e1` is the ID of the edge connecting `A` to `B`. You can then use this ID in later definitions or style statements, just like with nodes.
### Turning an Animation On
Once you have assigned an ID to an edge, you can turn on animations for that edge by defining the edges properties:
```mermaid-example
flowchart LR
A e1@==> B
e1@{ animate: true }
```
```mermaid
flowchart LR
A e1@==> B
e1@{ animate: true }
```
This tells Mermaid that the edge `e1` should be animated.
### Selecting Type of Animation
In the initial version, two animation speeds are supported: `fast` and `slow`. Selecting a specific animation type is a shorthand for enabling animation and setting the animation speed in one go.
**Examples:**
```mermaid-example
flowchart LR
A e1@> B
e1@{ animation: fast }
```
```mermaid
flowchart LR
A e1@> B
e1@{ animation: fast }
```
This is equivalent to `{ animate: true, animation: fast }`.
### Using classDef Statements for Animations
You can also animate edges by assigning a class to them and then defining animation properties in a `classDef` statement. For example:
```mermaid-example
flowchart LR
A e1@> B
classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class e1 animate
```
```mermaid
flowchart LR
A e1@> B
classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class e1 animate
```
In this snippet:
- `e1@-->` creates an edge with ID `e1`.
- `classDef animate` defines a class named `animate` with styling and animation properties.
- `class e1 animate` applies the `animate` class to the edge `e1`.
**Note on Escaping Commas:**
When setting the `stroke-dasharray` property, remember to escape commas as `\,` since commas are used as delimiters in Mermaids style definitions.
## New arrow types
There are new types of arrows supported:

File diff suppressed because it is too large Load Diff

View File

@ -1,9 +1,11 @@
import { parser } from './parser/classDiagram.jison';
import classDb from './classDb.js';
import { ClassDB } from './classDb.js';
describe('class diagram, ', function () {
describe('when parsing data from a classDiagram it', function () {
let classDb;
beforeEach(function () {
classDb = new ClassDB();
parser.yy = classDb;
parser.yy.clear();
});

View File

@ -1,13 +1,15 @@
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';
import { ClassDB } from './classDb.js';
import styles from './styles.js';
import renderer from './classRenderer-v3-unified.js';
export const diagram: DiagramDefinition = {
parser,
db,
get db() {
return new ClassDB();
},
renderer,
styles,
init: (cnf) => {
@ -15,6 +17,5 @@ export const diagram: DiagramDefinition = {
cnf.class = {};
}
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
db.clear();
},
};

View File

@ -1,6 +1,7 @@
/* eslint-disable @typescript-eslint/unbound-method -- Broken for Vitest mocks, see https://github.com/vitest-dev/eslint-plugin-vitest/pull/286 */
// @ts-expect-error Jison doesn't export types
import { parser } from './parser/classDiagram.jison';
import classDb from './classDb.js';
import { ClassDB } from './classDb.js';
import { vi, describe, it, expect } from 'vitest';
import type { ClassMap, NamespaceNode } from './classTypes.js';
const spyOn = vi.spyOn;
@ -10,8 +11,9 @@ const abstractCssStyle = 'font-style:italic;';
describe('given a basic class diagram, ', function () {
describe('when parsing class definition', function () {
let classDb: ClassDB;
beforeEach(function () {
classDb.clear();
classDb = new ClassDB();
parser.yy = classDb;
});
it('should handle classes within namespaces', () => {
@ -564,8 +566,9 @@ class C13["With Città foreign language"]
});
describe('when parsing class defined in brackets', function () {
let classDb: ClassDB;
beforeEach(function () {
classDb.clear();
classDb = new ClassDB();
parser.yy = classDb;
});
@ -656,8 +659,9 @@ class C13["With Città foreign language"]
});
describe('when parsing comments', function () {
let classDb: ClassDB;
beforeEach(function () {
classDb.clear();
classDb = new ClassDB();
parser.yy = classDb;
});
@ -746,8 +750,9 @@ foo()
});
describe('when parsing click statements', function () {
let classDb: ClassDB;
beforeEach(function () {
classDb.clear();
classDb = new ClassDB();
parser.yy = classDb;
});
it('should handle href link', function () {
@ -857,8 +862,9 @@ foo()
});
describe('when parsing annotations', function () {
let classDb: ClassDB;
beforeEach(function () {
classDb.clear();
classDb = new ClassDB();
parser.yy = classDb;
});
@ -921,8 +927,9 @@ foo()
describe('given a class diagram with members and methods ', function () {
describe('when parsing members', function () {
let classDb: ClassDB;
beforeEach(function () {
classDb.clear();
classDb = new ClassDB();
parser.yy = classDb;
});
@ -980,8 +987,9 @@ describe('given a class diagram with members and methods ', function () {
});
describe('when parsing method definition', function () {
let classDb: ClassDB;
beforeEach(function () {
classDb.clear();
classDb = new ClassDB();
parser.yy = classDb;
});
@ -1067,8 +1075,9 @@ describe('given a class diagram with members and methods ', function () {
describe('given a class diagram with generics, ', function () {
describe('when parsing valid generic classes', function () {
let classDb: ClassDB;
beforeEach(function () {
classDb.clear();
classDb = new ClassDB();
parser.yy = classDb;
});
@ -1180,8 +1189,9 @@ namespace space {
describe('given a class diagram with relationships, ', function () {
describe('when parsing basic relationships', function () {
let classDb: ClassDB;
beforeEach(function () {
classDb.clear();
classDb = new ClassDB();
parser.yy = classDb;
});
@ -1714,7 +1724,9 @@ class Class2
});
describe('when parsing classDiagram with text labels', () => {
let classDb: ClassDB;
beforeEach(function () {
classDb = new ClassDB();
parser.yy = classDb;
parser.yy.clear();
});
@ -1897,3 +1909,40 @@ class C13["With Città foreign language"]
});
});
});
describe('class db class', () => {
let classDb: ClassDB;
beforeEach(() => {
classDb = new ClassDB();
});
// This is to ensure that functions used in class JISON are exposed as function from ClassDB
it('should have functions used in class JISON as own property', () => {
const functionsUsedInParser = [
'addRelation',
'cleanupLabel',
'setAccTitle',
'setAccDescription',
'addClassesToNamespace',
'addNamespace',
'setCssClass',
'addMembers',
'addClass',
'setClassLabel',
'addAnnotation',
'addMember',
'addNote',
'defineClass',
'setDirection',
'relationType',
'lineType',
'setClickEvent',
'setTooltip',
'setLink',
'setCssStyle',
] as const satisfies (keyof ClassDB)[];
for (const fun of functionsUsedInParser) {
expect(Object.hasOwn(classDb, fun)).toBe(true);
}
});
});

View File

@ -1,13 +1,15 @@
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';
import { ClassDB } from './classDb.js';
import styles from './styles.js';
import renderer from './classRenderer-v3-unified.js';
export const diagram: DiagramDefinition = {
parser,
db,
get db() {
return new ClassDB();
},
renderer,
styles,
init: (cnf) => {
@ -15,6 +17,5 @@ export const diagram: DiagramDefinition = {
cnf.class = {};
}
cnf.class.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
db.clear();
},
};

View File

@ -1,8 +1,10 @@
import { parser } from './classDiagram.jison';
import classDb from '../classDb.js';
import { ClassDB } from '../classDb.js';
describe('class diagram', function () {
let classDb;
beforeEach(function () {
classDb = new ClassDB();
parser.yy = classDb;
parser.yy.clear();
});

View File

@ -1,9 +1,11 @@
import flowDb from './flowDb.js';
import { FlowDB } from './flowDb.js';
import type { FlowSubGraph } from './types.js';
describe('flow db subgraphs', () => {
let flowDb: FlowDB;
let subgraphs: FlowSubGraph[];
beforeEach(() => {
flowDb = new FlowDB();
subgraphs = [
{ nodes: ['a', 'b', 'c', 'e'] },
{ nodes: ['f', 'g', 'h'] },
@ -44,8 +46,9 @@ describe('flow db subgraphs', () => {
});
describe('flow db addClass', () => {
let flowDb: FlowDB;
beforeEach(() => {
flowDb.clear();
flowDb = new FlowDB();
});
it('should detect many classes', () => {
flowDb.addClass('a,b', ['stroke-width: 8px']);
@ -65,3 +68,33 @@ describe('flow db addClass', () => {
expect(classes.get('a')?.styles).toEqual(['stroke-width: 8px']);
});
});
describe('flow db class', () => {
let flowDb: FlowDB;
beforeEach(() => {
flowDb = new FlowDB();
});
// This is to ensure that functions used in flow JISON are exposed as function from FlowDB
it('should have functions used in flow JISON as own property', () => {
const functionsUsedInParser = [
'setDirection',
'addSubGraph',
'setAccTitle',
'setAccDescription',
'addVertex',
'addLink',
'setClass',
'destructLink',
'addClass',
'setClickEvent',
'setTooltip',
'setLink',
'updateLink',
'updateLinkInterpolate',
] as const satisfies (keyof FlowDB)[];
for (const fun of functionsUsedInParser) {
expect(Object.hasOwn(flowDb, fun)).toBe(true);
}
});
});

File diff suppressed because it is too large Load Diff

View File

@ -1,14 +1,17 @@
import type { MermaidConfig } from '../../config.type.js';
import { setConfig } from '../../diagram-api/diagramAPI.js';
import flowDb from './flowDb.js';
import { FlowDB } from './flowDb.js';
import renderer from './flowRenderer-v3-unified.js';
// @ts-ignore: JISON doesn't support types
import flowParser from './parser/flow.jison';
//import flowParser from './parser/flow.jison';
import flowParser from './parser/flowParser.ts';
import flowStyles from './styles.js';
export const diagram = {
parser: flowParser,
db: flowDb,
get db() {
return new FlowDB();
},
renderer,
styles: flowStyles,
init: (cnf: MermaidConfig) => {
@ -20,7 +23,5 @@ export const diagram = {
}
cnf.flowchart.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
setConfig({ flowchart: { arrowMarkerAbsolute: cnf.arrowMarkerAbsolute } });
flowDb.clear();
flowDb.setGen('gen-2');
},
};

View File

@ -7,7 +7,6 @@ import { getRegisteredLayoutAlgorithm, render } from '../../rendering-util/rende
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
import type { LayoutData } from '../../rendering-util/types.js';
import utils from '../../utils.js';
import { getDirection } from './flowDb.js';
export const getClasses = function (
text: string,
@ -37,7 +36,7 @@ export const draw = async function (text: string, id: string, _version: string,
log.debug('Data: ', data4Layout);
// Create the root SVG
const svg = getDiagramElement(id, securityLevel);
const direction = getDirection();
const direction = diag.db.getDirection();
data4Layout.type = diag.type;
data4Layout.layoutAlgorithm = getRegisteredLayoutAlgorithm(layout);

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -8,7 +8,7 @@ setConfig({
describe('[Arrows] when parsing', () => {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
import { cleanupComments } from '../../../diagram-api/comments.js';
@ -9,7 +9,7 @@ setConfig({
describe('[Comments] when parsing', () => {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -8,7 +8,7 @@ setConfig({
describe('when parsing directions', function () {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2');
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -39,10 +39,31 @@ const doubleEndedEdges = [
{ edgeStart: '<==', edgeEnd: '==>', stroke: 'thick', type: 'double_arrow_point' },
{ edgeStart: '<-.', edgeEnd: '.->', stroke: 'dotted', type: 'double_arrow_point' },
];
const regularEdges = [
{ edgeStart: '--', edgeEnd: '--x', stroke: 'normal', type: 'arrow_cross' },
{ edgeStart: '==', edgeEnd: '==x', stroke: 'thick', type: 'arrow_cross' },
{ edgeStart: '-.', edgeEnd: '.-x', stroke: 'dotted', type: 'arrow_cross' },
{ edgeStart: '--', edgeEnd: '--o', stroke: 'normal', type: 'arrow_circle' },
{ edgeStart: '==', edgeEnd: '==o', stroke: 'thick', type: 'arrow_circle' },
{ edgeStart: '-.', edgeEnd: '.-o', stroke: 'dotted', type: 'arrow_circle' },
{ edgeStart: '--', edgeEnd: '-->', stroke: 'normal', type: 'arrow_point' },
{ edgeStart: '==', edgeEnd: '==>', stroke: 'thick', type: 'arrow_point' },
{ edgeStart: '-.', edgeEnd: '.->', stroke: 'dotted', type: 'arrow_point' },
{ edgeStart: '--', edgeEnd: '----x', stroke: 'normal', type: 'arrow_cross' },
{ edgeStart: '==', edgeEnd: '====x', stroke: 'thick', type: 'arrow_cross' },
{ edgeStart: '-.', edgeEnd: '...-x', stroke: 'dotted', type: 'arrow_cross' },
{ edgeStart: '--', edgeEnd: '----o', stroke: 'normal', type: 'arrow_circle' },
{ edgeStart: '==', edgeEnd: '====o', stroke: 'thick', type: 'arrow_circle' },
{ edgeStart: '-.', edgeEnd: '...-o', stroke: 'dotted', type: 'arrow_circle' },
{ edgeStart: '--', edgeEnd: '---->', stroke: 'normal', type: 'arrow_point' },
{ edgeStart: '==', edgeEnd: '====>', stroke: 'thick', type: 'arrow_point' },
{ edgeStart: '-.', edgeEnd: '...->', stroke: 'dotted', type: 'arrow_point' },
];
describe('[Edges] when parsing', () => {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
});
@ -67,6 +88,74 @@ describe('[Edges] when parsing', () => {
expect(edges[0].type).toBe('arrow_circle');
});
describe('edges with ids', function () {
describe('open ended edges with ids and labels', function () {
regularEdges.forEach((edgeType) => {
it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {
const res = flow.parser.parse(
`flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].id).toBe('e1');
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe(`${edgeType.type}`);
expect(edges[0].text).toBe('');
expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
});
it(`should handle ${edgeType.stroke} ${edgeType.type} with text`, function () {
const res = flow.parser.parse(
`flowchart TD;\nA e1@${edgeType.edgeStart}${edgeType.edgeEnd} B;`
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].id).toBe('e1');
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe(`${edgeType.type}`);
expect(edges[0].text).toBe('');
expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
});
});
it('should handle normal edges where you also have a node with metadata', function () {
const res = flow.parser.parse(`flowchart LR
A id1@-->B
A@{ shape: 'rect' }
`);
const edges = flow.parser.yy.getEdges();
expect(edges[0].id).toBe('id1');
});
});
describe('double ended edges with ids and labels', function () {
doubleEndedEdges.forEach((edgeType) => {
it(`should handle ${edgeType.stroke} ${edgeType.type} with text`, function () {
const res = flow.parser.parse(
`flowchart TD;\nA e1@${edgeType.edgeStart} label ${edgeType.edgeEnd} B;`
);
const vert = flow.parser.yy.getVertices();
const edges = flow.parser.yy.getEdges();
expect(vert.get('A').id).toBe('A');
expect(vert.get('B').id).toBe('B');
expect(edges.length).toBe(1);
expect(edges[0].id).toBe('e1');
expect(edges[0].start).toBe('A');
expect(edges[0].end).toBe('B');
expect(edges[0].type).toBe(`${edgeType.type}`);
expect(edges[0].text).toBe('label');
expect(edges[0].stroke).toBe(`${edgeType.stroke}`);
});
});
});
});
describe('edges', function () {
doubleEndedEdges.forEach((edgeType) => {
it(`should handle ${edgeType.stroke} ${edgeType.type} with no text`, function () {

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -8,7 +8,7 @@ setConfig({
describe('[Text] when parsing', () => {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
import { vi } from 'vitest';
const spyOn = vi.spyOn;
@ -9,7 +9,9 @@ setConfig({
});
describe('[Interactions] when parsing', () => {
let flowDb;
beforeEach(function () {
flowDb = new FlowDB();
flow.parser.yy = flowDb;
flow.parser.yy.clear();
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -8,7 +8,7 @@ setConfig({
describe('[Lines] when parsing', () => {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -8,7 +8,7 @@ setConfig({
describe('parsing a flow chart with markdown strings', function () {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -8,7 +8,7 @@ setConfig({
describe('when parsing directions', function () {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2');
});
@ -251,7 +251,7 @@ describe('when parsing directions', function () {
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a<br/>multiline string');
});
it(' should be possible to use } in strings', function () {
it('should be possible to use } in strings', function () {
const res = flow.parser.parse(`flowchart TB
A@{
label: "This is a string with }"
@ -264,7 +264,7 @@ describe('when parsing directions', function () {
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a string with }');
});
it(' should be possible to use @ in strings', function () {
it('should be possible to use @ in strings', function () {
const res = flow.parser.parse(`flowchart TB
A@{
label: "This is a string with @"
@ -277,7 +277,7 @@ describe('when parsing directions', function () {
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a string with @');
});
it(' should be possible to use @ in strings', function () {
it('should be possible to use @ in strings', function () {
const res = flow.parser.parse(`flowchart TB
A@{
label: "This is a string with}"
@ -290,4 +290,126 @@ describe('when parsing directions', function () {
expect(data4Layout.nodes[0].shape).toEqual('squareRect');
expect(data4Layout.nodes[0].label).toEqual('This is a string with}');
});
it('should be possible to use @ syntax to add labels on multi nodes', function () {
const res = flow.parser.parse(`flowchart TB
n2["label for n2"] & n4@{ label: "labe for n4"} & n5@{ label: "labe for n5"}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(3);
expect(data4Layout.nodes[0].label).toEqual('label for n2');
expect(data4Layout.nodes[1].label).toEqual('labe for n4');
expect(data4Layout.nodes[2].label).toEqual('labe for n5');
});
it('should be possible to use @ syntax to add labels on multi nodes with edge/link', function () {
const res = flow.parser.parse(`flowchart TD
A["A"] --> B["for B"] & C@{ label: "for c"} & E@{label : "for E"}
D@{label: "for D"}
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(5);
expect(data4Layout.nodes[0].label).toEqual('A');
expect(data4Layout.nodes[1].label).toEqual('for B');
expect(data4Layout.nodes[2].label).toEqual('for c');
expect(data4Layout.nodes[3].label).toEqual('for E');
expect(data4Layout.nodes[4].label).toEqual('for D');
});
it('should be possible to use @ syntax in labels', function () {
const res = flow.parser.parse(`flowchart TD
A["@A@"] --> B["@for@ B@"] & C@{ label: "@for@ c@"} & E{"\`@for@ E@\`"} & D(("@for@ D@"))
H1{{"@for@ H@"}}
H2{{"\`@for@ H@\`"}}
Q1{"@for@ Q@"}
Q2{"\`@for@ Q@\`"}
AS1>"@for@ AS@"]
AS2>"\`@for@ AS@\`"]
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(11);
expect(data4Layout.nodes[0].label).toEqual('@A@');
expect(data4Layout.nodes[1].label).toEqual('@for@ B@');
expect(data4Layout.nodes[2].label).toEqual('@for@ c@');
expect(data4Layout.nodes[3].label).toEqual('@for@ E@');
expect(data4Layout.nodes[4].label).toEqual('@for@ D@');
expect(data4Layout.nodes[5].label).toEqual('@for@ H@');
expect(data4Layout.nodes[6].label).toEqual('@for@ H@');
expect(data4Layout.nodes[7].label).toEqual('@for@ Q@');
expect(data4Layout.nodes[8].label).toEqual('@for@ Q@');
expect(data4Layout.nodes[9].label).toEqual('@for@ AS@');
expect(data4Layout.nodes[10].label).toEqual('@for@ AS@');
});
it('should handle unique edge creation with using @ and &', function () {
const res = flow.parser.parse(`flowchart TD
A & B e1@--> C & D
A1 e2@--> C1 & D1
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(7);
expect(data4Layout.edges.length).toBe(6);
expect(data4Layout.edges[0].id).toEqual('L_A_C_0');
expect(data4Layout.edges[1].id).toEqual('L_A_D_0');
expect(data4Layout.edges[2].id).toEqual('e1');
expect(data4Layout.edges[3].id).toEqual('L_B_D_0');
expect(data4Layout.edges[4].id).toEqual('e2');
expect(data4Layout.edges[5].id).toEqual('L_A1_D1_0');
});
it('should handle redefine same edge ids again', function () {
const res = flow.parser.parse(`flowchart TD
A & B e1@--> C & D
A1 e1@--> C1 & D1
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(7);
expect(data4Layout.edges.length).toBe(6);
expect(data4Layout.edges[0].id).toEqual('L_A_C_0');
expect(data4Layout.edges[1].id).toEqual('L_A_D_0');
expect(data4Layout.edges[2].id).toEqual('e1');
expect(data4Layout.edges[3].id).toEqual('L_B_D_0');
expect(data4Layout.edges[4].id).toEqual('L_A1_C1_0');
expect(data4Layout.edges[5].id).toEqual('L_A1_D1_0');
});
it('should handle overriding edge animate again', function () {
const res = flow.parser.parse(`flowchart TD
A e1@--> B
C e2@--> D
E e3@--> F
e1@{ animate: true }
e2@{ animate: false }
e3@{ animate: true }
e3@{ animate: false }
`);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(6);
expect(data4Layout.edges.length).toBe(3);
expect(data4Layout.edges[0].id).toEqual('e1');
expect(data4Layout.edges[0].animate).toEqual(true);
expect(data4Layout.edges[1].id).toEqual('e2');
expect(data4Layout.edges[1].animate).toEqual(false);
expect(data4Layout.edges[2].id).toEqual('e3');
expect(data4Layout.edges[2].animate).toEqual(false);
});
it.skip('should be possible to use @ syntax to add labels with trail spaces', function () {
const res = flow.parser.parse(
`flowchart TB
n2["label for n2"] & n4@{ label: "labe for n4"} & n5@{ label: "labe for n5"} `
);
const data4Layout = flow.parser.yy.getData();
expect(data4Layout.nodes.length).toBe(3);
expect(data4Layout.nodes[0].label).toEqual('label for n2');
expect(data4Layout.nodes[1].label).toEqual('labe for n4');
expect(data4Layout.nodes[2].label).toEqual('labe for n5');
});
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -31,7 +31,7 @@ const specialChars = ['#', ':', '0', '&', ',', '*', '.', '\\', 'v', '-', '/', '_
describe('[Singlenodes] when parsing', () => {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -8,7 +8,7 @@ setConfig({
describe('[Style] when parsing', () => {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2');
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -8,7 +8,7 @@ setConfig({
describe('[Text] when parsing', () => {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
});

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -8,7 +8,7 @@ setConfig({
describe('when parsing flowcharts', function () {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2');
});

View File

@ -141,6 +141,7 @@ that id.
.*direction\s+RL[^\n]* return 'direction_rl';
.*direction\s+LR[^\n]* return 'direction_lr';
[^\s\"]+\@(?=[^\{\"]) { return 'LINK_ID'; }
[0-9]+ return 'NUM';
\# return 'BRKT';
":::" return 'STYLE_SEPARATOR';
@ -201,7 +202,9 @@ that id.
"*" return 'MULT';
"#" return 'BRKT';
"&" return 'AMP';
([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|\-(?=[^\>\-\.])|=(?!=))+ return 'NODE_STRING';
([A-Za-z0-9!"\#$%&'*+\.`?\\_\/]|\-(?=[^\>\-\.])|=(?!=))+ {
return 'NODE_STRING';
}
"-" return 'MINUS'
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
@ -361,7 +364,7 @@ spaceList
statement
: vertexStatement separator
{ /* console.warn('finat vs', $vertexStatement.nodes); */ $$=$vertexStatement.nodes}
{ $$=$vertexStatement.nodes}
| styleStatement separator
{$$=[];}
| linkStyleStatement separator
@ -396,7 +399,7 @@ shapeData:
;
vertexStatement: vertexStatement link node shapeData
{ /* console.warn('vs shapeData',$vertexStatement.stmt,$node, $shapeData);*/ yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
{ /* console.warn('vs shapeData',$vertexStatement.stmt,$node, $shapeData);*/ yy.addVertex($node[$node.length-1],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
| vertexStatement link node
{ /*console.warn('vs',$vertexStatement.stmt,$node);*/ yy.addLink($vertexStatement.stmt,$node,$link); $$ = { stmt: $node, nodes: $node.concat($vertexStatement.nodes) } }
| vertexStatement link node spaceList
@ -404,7 +407,7 @@ vertexStatement: vertexStatement link node shapeData
|node spaceList { /*console.warn('vertexStatement: node spaceList', $node);*/ $$ = {stmt: $node, nodes:$node }}
|node shapeData {
/*console.warn('vertexStatement: node shapeData', $node[0], $shapeData);*/
yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData);
yy.addVertex($node[$node.length-1],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData);
$$ = {stmt: $node, nodes:$node, shapeData: $shapeData}
}
|node { /* console.warn('vertexStatement: single node', $node); */ $$ = {stmt: $node, nodes:$node }}
@ -413,7 +416,7 @@ vertexStatement: vertexStatement link node shapeData
node: styledVertex
{ /*console.warn('nod', $styledVertex);*/ $$ = [$styledVertex];}
| node shapeData spaceList AMP spaceList styledVertex
{ yy.addVertex($node[0],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); $$ = $node.concat($styledVertex); /*console.warn('pip2', $node[0], $styledVertex, $$);*/ }
{ yy.addVertex($node[$node.length-1],undefined,undefined,undefined, undefined,undefined, undefined,$shapeData); $$ = $node.concat($styledVertex); /*console.warn('pip2', $node[0], $styledVertex, $$);*/ }
| node spaceList AMP spaceList styledVertex
{ $$ = $node.concat($styledVertex); /*console.warn('pip', $node[0], $styledVertex, $$);*/ }
;
@ -472,6 +475,8 @@ link: linkStatement arrowText
{$$ = $linkStatement;}
| START_LINK edgeText LINK
{var inf = yy.destructLink($LINK, $START_LINK); $$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length,"text":$edgeText};}
| LINK_ID START_LINK edgeText LINK
{var inf = yy.destructLink($LINK, $START_LINK); $$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length,"text":$edgeText, "id": $LINK_ID};}
;
edgeText: edgeTextToken
@ -487,6 +492,8 @@ edgeText: edgeTextToken
linkStatement: LINK
{var inf = yy.destructLink($LINK);$$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length};}
| LINK_ID LINK
{var inf = yy.destructLink($LINK);$$ = {"type":inf.type,"stroke":inf.stroke,"length":inf.length, "id": $LINK_ID};}
;
arrowText:

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { cleanupComments } from '../../../diagram-api/comments.js';
import { setConfig } from '../../../config.js';
@ -9,7 +9,7 @@ setConfig({
describe('parsing a flow chart', function () {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
});

View File

@ -0,0 +1,12 @@
// @ts-ignore: JISON doesn't support types
import flowJisonParser from './flow.jison';
const newParser = Object.assign({}, flowJisonParser);
newParser.parse = (src: string): unknown => {
// remove the trailing whitespace after closing curly braces when ending a line break
const newSrc = src.replace(/}\s*\n/g, '}\n');
return flowJisonParser.parse(newSrc);
};
export default newParser;

View File

@ -1,5 +1,5 @@
import flowDb from '../flowDb.js';
import flow from './flow.jison';
import { FlowDB } from '../flowDb.js';
import flow from './flowParser.ts';
import { setConfig } from '../../../config.js';
setConfig({
@ -8,7 +8,7 @@ setConfig({
describe('when parsing subgraphs', function () {
beforeEach(function () {
flow.parser.yy = flowDb;
flow.parser.yy = new FlowDB();
flow.parser.yy.clear();
flow.parser.yy.setGen('gen-2');
});

View File

@ -53,6 +53,7 @@ export interface FlowText {
}
export interface FlowEdge {
isUserDefinedId: boolean;
start: string;
end: string;
interpolate?: string;
@ -62,6 +63,10 @@ export interface FlowEdge {
length?: number;
text: string;
labelType: 'text';
classes: string[];
id?: string;
animation?: 'fast' | 'slow';
animate?: boolean;
}
export interface FlowClass {

View File

@ -1,7 +1,7 @@
const getStyles = (options) =>
`
.mermaid-main-font {
font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif);
font-family: ${options.fontFamily};
}
.exclude-range {
@ -45,7 +45,7 @@ const getStyles = (options) =>
.sectionTitle {
text-anchor: start;
font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif);
font-family: ${options.fontFamily};
}
@ -86,13 +86,13 @@ const getStyles = (options) =>
.taskText {
text-anchor: middle;
font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif);
font-family: ${options.fontFamily};
}
.taskTextOutsideRight {
fill: ${options.taskTextDarkColor};
text-anchor: start;
font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif);
font-family: ${options.fontFamily};
}
.taskTextOutsideLeft {
@ -248,7 +248,7 @@ const getStyles = (options) =>
text-anchor: middle;
font-size: 18px;
fill: ${options.titleColor || options.textColor};
font-family: var(--mermaid-font-family, "trebuchet ms", verdana, arial, sans-serif);
font-family: ${options.fontFamily};
}
`;

View File

@ -4,11 +4,13 @@ import parser from './parser/sankey.jison';
import db from './sankeyDB.js';
import renderer from './sankeyRenderer.js';
import { prepareTextForParsing } from './sankeyUtils.js';
import sankeyStyles from './styles.js';
const originalParse = parser.parse.bind(parser);
parser.parse = (text: string) => originalParse(prepareTextForParsing(text));
export const diagram: DiagramDefinition = {
styles: sankeyStyles,
parser,
db,
renderer,

View File

@ -136,7 +136,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb
svg
.append('g')
.attr('class', 'node-labels')
.attr('font-family', 'sans-serif')
.attr('font-size', 14)
.selectAll('text')
.data(graph.nodes)

View File

@ -0,0 +1,6 @@
const getStyles = (options) =>
`.label {
font-family: ${options.fontFamily};
}`;
export default getStyles;

View File

@ -15,6 +15,5 @@ export const diagram: DiagramDefinition = {
cnf.state = {};
}
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
db.clear();
},
};

View File

@ -15,6 +15,5 @@ export const diagram: DiagramDefinition = {
cnf.state = {};
}
cnf.state.arrowMarkerAbsolute = cnf.arrowMarkerAbsolute;
db.clear();
},
};

View File

@ -1,7 +1,6 @@
const getStyles = (options) =>
`.label {
font-family: 'trebuchet ms', verdana, arial, sans-serif;
font-family: var(--mermaid-font-family);
font-family: ${options.fontFamily};
color: ${options.textColor};
}
.mouth {
@ -79,8 +78,7 @@ const getStyles = (options) =>
text-align: center;
max-width: 200px;
padding: 2px;
font-family: 'trebuchet ms', verdana, arial, sans-serif;
font-family: var(--mermaid-font-family);
font-family: ${options.fontFamily};
font-size: 12px;
background: ${options.tertiaryColor};
border: 1px solid ${options.border2};

View File

@ -94,8 +94,7 @@ Blogging frameworks and platforms
- [Nextra](https://nextra.site/)
- [Mermaid](https://nextra.site/docs/guide/mermaid)
- [WordPress](https://wordpress.org)
- [WordPress Markdown Editor](https://wordpress.org/plugins/wp-githuber-md)
- [WP-ReliableMD](https://wordpress.org/plugins/wp-reliablemd/)
- [MerPRess](https://wordpress.org/plugins/merpress/)
### CMS/ECM

View File

@ -711,6 +711,67 @@ flowchart TB
B --> D
```
### Attaching an ID to Edges
Mermaid now supports assigning IDs to edges, similar to how IDs and metadata can be attached to nodes. This feature lays the groundwork for more advanced styling, classes, and animation capabilities on edges.
**Syntax:**
To give an edge an ID, prepend the edge syntax with the ID followed by an `@` character. For example:
```mermaid
flowchart LR
A e1@> B
```
In this example, `e1` is the ID of the edge connecting `A` to `B`. You can then use this ID in later definitions or style statements, just like with nodes.
### Turning an Animation On
Once you have assigned an ID to an edge, you can turn on animations for that edge by defining the edges properties:
```mermaid
flowchart LR
A e1@==> B
e1@{ animate: true }
```
This tells Mermaid that the edge `e1` should be animated.
### Selecting Type of Animation
In the initial version, two animation speeds are supported: `fast` and `slow`. Selecting a specific animation type is a shorthand for enabling animation and setting the animation speed in one go.
**Examples:**
```mermaid
flowchart LR
A e1@> B
e1@{ animation: fast }
```
This is equivalent to `{ animate: true, animation: fast }`.
### Using classDef Statements for Animations
You can also animate edges by assigning a class to them and then defining animation properties in a `classDef` statement. For example:
```mermaid
flowchart LR
A e1@> B
classDef animate stroke-dasharray: 9,5,stroke-dashoffset: 900,animation: dash 25s linear infinite;
class e1 animate
```
In this snippet:
- `e1@-->` creates an edge with ID `e1`.
- `classDef animate` defines a class named `animate` with styling and animation properties.
- `class e1 animate` applies the `animate` class to the edge `e1`.
**Note on Escaping Commas:**
When setting the `stroke-dasharray` property, remember to escape commas as `\,` since commas are used as delimiters in Mermaids style definitions.
## New arrow types
There are new types of arrows supported:

View File

@ -1,4 +1,4 @@
import { beforeEach, describe, expect, it, vi } from 'vitest';
import { assert, beforeEach, describe, expect, it, vi } from 'vitest';
// -------------------------------------
// Mocks and mocking
@ -69,6 +69,8 @@ import { compile, serialize } from 'stylis';
import { Diagram } from './Diagram.js';
import { decodeEntities, encodeEntities } from './utils.js';
import { toBase64 } from './utils/base64.js';
import { ClassDB } from './diagrams/class/classDb.js';
import { FlowDB } from './diagrams/flowchart/flowDb.js';
/**
* @see https://vitest.dev/guide/mocking.html Mock part of a module
@ -832,5 +834,93 @@ graph TD;A--x|text including URL space|B;`)
expect(diagram).toBeInstanceOf(Diagram);
expect(diagram.type).toBe('flowchart-v2');
});
it('should not modify db when rendering different diagrams', async () => {
const flowDiagram1 = await mermaidAPI.getDiagramFromText(
`flowchart LR
A -- text --> B -- text2 --> C`
);
const flowDiagram2 = await mermaidAPI.getDiagramFromText(
`flowchart TD
A -- text --> B -- text2 --> C`
);
// Since flowDiagram will return new Db object each time, we can compare the db to be different.
expect(flowDiagram1.db).not.toBe(flowDiagram2.db);
assert(flowDiagram1.db instanceof FlowDB);
assert(flowDiagram2.db instanceof FlowDB);
expect(flowDiagram1.db.getDirection()).not.toEqual(flowDiagram2.db.getDirection());
const classDiagram1 = await mermaidAPI.getDiagramFromText(
`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`
);
const classDiagram2 = await mermaidAPI.getDiagramFromText(
`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`
);
// Since classDiagram will return new Db object each time, we can compare the db to be different.
expect(classDiagram1.db).not.toBe(classDiagram2.db);
assert(classDiagram1.db instanceof ClassDB);
assert(classDiagram2.db instanceof ClassDB);
expect(classDiagram1.db.getDirection()).not.toEqual(classDiagram2.db.getDirection());
const sequenceDiagram1 = await mermaidAPI.getDiagramFromText(
`sequenceDiagram
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!`
);
const sequenceDiagram2 = await mermaidAPI.getDiagramFromText(
`sequenceDiagram
Alice->>+John: Hello John, how are you?
Alice->>+John: John, can you hear me?
John-->>-Alice: Hi Alice, I can hear you!
John-->>-Alice: I feel great!`
);
// Since sequenceDiagram will return same Db object each time, we can compare the db to be same.
expect(sequenceDiagram1.db).toBe(sequenceDiagram2.db);
});
});
// Sequence Diagram currently uses a singleton DB, so this test will fail
it.fails('should not modify db when rendering different sequence diagrams', async () => {
const sequenceDiagram1 = await mermaidAPI.getDiagramFromText(
`sequenceDiagram
Alice->>Bob: Hello Bob, how are you?
Bob-->>John: How about you John?`
);
const sequenceDiagram2 = await mermaidAPI.getDiagramFromText(
`sequenceDiagram
Alice->>Bob: Hello Bob, how are you?
Bob-->>John: How about you John?`
);
expect(sequenceDiagram1.db).not.toBe(sequenceDiagram2.db);
});
});

View File

@ -9,6 +9,7 @@ import { curveBasis, line, select } from 'd3';
import rough from 'roughjs';
import createLabel from './createLabel.js';
import { addEdgeMarkers } from './edgeMarker.ts';
import { isLabelStyle } from './shapes/handDrawnShapeStyles.js';
const edgeLabels = new Map();
const terminalLabels = new Map();
@ -428,6 +429,13 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
let pointsHasChanged = false;
const tail = startNode;
var head = endNode;
const edgeClassStyles = [];
for (const key in edge.cssCompiledStyles) {
if (isLabelStyle(key)) {
continue;
}
edgeClassStyles.push(edge.cssCompiledStyles[key]);
}
if (head.intersect && tail.intersect) {
points = points.slice(1, edge.points.length - 1);
@ -501,6 +509,7 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
let svgPath;
let linePath = lineFunction(lineData);
const edgeStyles = Array.isArray(edge.style) ? edge.style : [edge.style];
if (edge.look === 'handDrawn') {
const rc = rough.svg(elem);
Object.assign([], lineData);
@ -521,12 +530,27 @@ export const insertEdge = function (elem, edge, clusterDb, diagramType, startNod
svgPath.attr('d', d);
elem.node().appendChild(svgPath.node());
} else {
const stylesFromClasses = edgeClassStyles.join(';');
const styles = edgeStyles ? edgeStyles.reduce((acc, style) => acc + style + ';', '') : '';
let animationClass = '';
if (edge.animate) {
animationClass = ' edge-animation-fast';
}
if (edge.animation) {
animationClass = ' edge-animation-' + edge.animation;
}
svgPath = elem
.append('path')
.attr('d', linePath)
.attr('id', edge.id)
.attr('class', ' ' + strokeClasses + (edge.classes ? ' ' + edge.classes : ''))
.attr('style', edgeStyles ? edgeStyles.reduce((acc, style) => acc + ';' + style, '') : '');
.attr(
'class',
' ' +
strokeClasses +
(edge.classes ? ' ' + edge.classes : '') +
(animationClass ? animationClass : '')
)
.attr('style', stylesFromClasses ? stylesFromClasses + ';' + styles + ';' : styles);
}
// DEBUG code, DO NOT REMOVE

View File

@ -32,7 +32,28 @@ export const styles2Map = (styles: string[]) => {
});
return styleMap;
};
export const isLabelStyle = (key: string) => {
return (
key === 'color' ||
key === 'font-size' ||
key === 'font-family' ||
key === 'font-weight' ||
key === 'font-style' ||
key === 'text-decoration' ||
key === 'text-align' ||
key === 'text-transform' ||
key === 'line-height' ||
key === 'letter-spacing' ||
key === 'word-spacing' ||
key === 'text-shadow' ||
key === 'text-overflow' ||
key === 'white-space' ||
key === 'word-wrap' ||
key === 'word-break' ||
key === 'overflow-wrap' ||
key === 'hyphens'
);
};
export const styles2String = (node: Node) => {
const { stylesArray } = compileStyles(node);
const labelStyles: string[] = [];
@ -42,26 +63,7 @@ export const styles2String = (node: Node) => {
stylesArray.forEach((style) => {
const key = style[0];
if (
key === 'color' ||
key === 'font-size' ||
key === 'font-family' ||
key === 'font-weight' ||
key === 'font-style' ||
key === 'text-decoration' ||
key === 'text-align' ||
key === 'text-transform' ||
key === 'line-height' ||
key === 'letter-spacing' ||
key === 'word-spacing' ||
key === 'text-shadow' ||
key === 'text-overflow' ||
key === 'white-space' ||
key === 'word-wrap' ||
key === 'word-break' ||
key === 'overflow-wrap' ||
key === 'hyphens'
) {
if (isLabelStyle(key)) {
labelStyles.push(style.join(':') + ' !important');
} else {
nodeStyles.push(style.join(':') + ' !important');

View File

@ -96,11 +96,14 @@ export interface Edge {
label?: string;
classes?: string;
style?: string[];
animate?: boolean;
animation?: 'fast' | 'slow';
// Properties common to both Flowchart and State Diagram edges
arrowhead?: string;
arrowheadStyle?: string;
arrowTypeEnd?: string;
arrowTypeStart?: string;
cssCompiledStyles?: string[];
// Flowchart specific properties
defaultInterpolate?: string;
end?: string;
@ -122,6 +125,7 @@ export interface Edge {
pattern?: string;
thickness?: 'normal' | 'thick' | 'invisible' | 'dotted';
look?: string;
isUserDefinedId?: boolean;
}
export interface RectOptions {

View File

@ -27,7 +27,28 @@ const getStyles = (
font-size: ${options.fontSize};
fill: ${options.textColor}
}
@keyframes edge-animation-frame {
from {
stroke-dashoffset: 0;
}
}
@keyframes dash {
to {
stroke-dashoffset: 0;
}
}
& .edge-animation-slow {
stroke-dasharray: 9,5 !important;
stroke-dashoffset: 900;
animation: dash 50s linear infinite;
stroke-linecap: round;
}
& .edge-animation-fast {
stroke-dasharray: 9,5 !important;
stroke-dashoffset: 900;
animation: dash 20s linear infinite;
stroke-linecap: round;
}
/* Classes common for multiple diagrams */
& .error-icon {

View File

@ -12,6 +12,11 @@ export interface NodeMetaData {
assigned?: string;
ticket?: string;
}
export interface EdgeMetaData {
animation?: 'fast' | 'slow';
animate?: boolean;
}
import type { MermaidConfig } from './config.type.js';
export interface Point {

View File

@ -937,8 +937,12 @@ export const getEdgeId = (
counter?: number;
prefix?: string;
suffix?: string;
}
},
id?: string
) => {
if (id) {
return id;
}
return `${prefix ? `${prefix}_` : ''}${from}_${to}_${counter}${suffix ? `_${suffix}` : ''}`;
};

3062
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff