mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08:00
Merge branch 'develop' into release/10.2.0
* develop: (49 commits) Debug fetch-contributors Fix lockfile Revert "Replace esno & ts-node with tsx" Fix lockfile Fix build Update pnpm-lock Replace esno & ts-node with tsx chore(deps): update all minor dependencies chore(deps): update all patch dependencies Fix missing `await` in usage document Add integrations test: fix classDiagramGrammer unit test restore path calculation for grammar test Allow overlapping notes remove spellcheck warnings Fix path in test fix import issue requested update Reject ridiculous years in Gantt charts. Remove unnecessary dependencies ...
This commit is contained in:
commit
17c798a1b3
3
.gitignore
vendored
3
.gitignore
vendored
@ -36,3 +36,6 @@ tsconfig.tsbuildinfo
|
||||
knsv*.html
|
||||
local*.html
|
||||
stats/
|
||||
|
||||
**/user-avatars/*
|
||||
**/contributor-names.json
|
||||
|
@ -1,5 +1,5 @@
|
||||
export default {
|
||||
'!(docs/**/*)*.{ts,js,json,html,md,mts}': [
|
||||
'!(docs/**/*)*.{ts,js,html,md,mts}': [
|
||||
'eslint --cache --cache-strategy content --fix',
|
||||
// don't cache prettier yet, since we use `prettier-plugin-jsdoc`,
|
||||
// and prettier doesn't invalidate cache on plugin updates"
|
||||
|
@ -4,4 +4,5 @@ cypress/platform/xss3.html
|
||||
coverage
|
||||
# Autogenerated by PNPM
|
||||
pnpm-lock.yaml
|
||||
stats
|
||||
stats
|
||||
packages/mermaid/src/docs/.vitepress/components.d.ts
|
||||
|
14
README.md
14
README.md
@ -165,6 +165,13 @@ class Class10 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
namespace Namespace01 {
|
||||
class Class11
|
||||
class Class12 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
@ -184,6 +191,13 @@ class Class10 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
namespace Namespace01 {
|
||||
class Class11
|
||||
class Class12 {
|
||||
int id
|
||||
size()
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### State diagram [<a href="https://mermaid-js.github.io/mermaid/#/stateDiagram">docs</a> - <a href="https://mermaid.live/edit#pako:eNpdkEFvgzAMhf8K8nEqpYSNthx22Xbcqcexg0sCiZQQlDhIFeK_L8A6TfXp6fOz9ewJGssFVOAJSbwr7ByadGR1n8T6evpO0vQ1uZDSekOrXGFsPqJPO6q-2-imH8f_0TeHXm50lfelsAMjnEHFY6xpMdRAUhhRQxUlFy0GTTXU_RytYeAx-AdXZB1ULWovdoCB7OXWN1CRC-Ju-r3uz6UtchGHJqDbsPygU57iysb2reoWHpyOWBINvsqypb3vFMlw3TfWZF5xiY7keC6zkpUnZIUojwW-FAVvrvn51LLnvOXHQ84Q5nn-AVtLcwk">live editor</a>]
|
||||
|
27
cSpell.json
27
cSpell.json
@ -5,11 +5,13 @@
|
||||
"acyclicer",
|
||||
"adamiecki",
|
||||
"alois",
|
||||
"aloisklink",
|
||||
"antiscript",
|
||||
"appli",
|
||||
"applitools",
|
||||
"asciidoctor",
|
||||
"ashish",
|
||||
"ashishjain",
|
||||
"astah",
|
||||
"bbox",
|
||||
"bilkent",
|
||||
@ -23,6 +25,7 @@
|
||||
"classdef",
|
||||
"codedoc",
|
||||
"colour",
|
||||
"commitlint",
|
||||
"cpettitt",
|
||||
"customizability",
|
||||
"cuzon",
|
||||
@ -48,30 +51,41 @@
|
||||
"grav",
|
||||
"greywolf",
|
||||
"huynh",
|
||||
"huynhicode",
|
||||
"inkdrop",
|
||||
"jaoude",
|
||||
"jgreywolf",
|
||||
"jison",
|
||||
"jiti",
|
||||
"kaufmann",
|
||||
"khroma",
|
||||
"klemm",
|
||||
"klink",
|
||||
"knsv",
|
||||
"knut",
|
||||
"knutsveidqvist",
|
||||
"laganeckas",
|
||||
"linetype",
|
||||
"lintstagedrc",
|
||||
"logmsg",
|
||||
"lucida",
|
||||
"markdownish",
|
||||
"matthieu",
|
||||
"matthieumorel",
|
||||
"mdast",
|
||||
"mdbook",
|
||||
"mermaidjs",
|
||||
"mermerd",
|
||||
"mindaugas",
|
||||
"mindmap",
|
||||
"mindmaps",
|
||||
"mitigations",
|
||||
"mkdocs",
|
||||
"mmorel",
|
||||
"mult",
|
||||
"orlandoni",
|
||||
"pathe",
|
||||
"pbrolin",
|
||||
"phpbb",
|
||||
"plantuml",
|
||||
"playfair",
|
||||
@ -83,6 +97,7 @@
|
||||
"rect",
|
||||
"rects",
|
||||
"redmine",
|
||||
"rehype",
|
||||
"roledescription",
|
||||
"sandboxed",
|
||||
"setupgraphviewbox",
|
||||
@ -90,8 +105,12 @@
|
||||
"sidharth",
|
||||
"sidharthv",
|
||||
"sphinxcontrib",
|
||||
"startx",
|
||||
"starty",
|
||||
"statediagram",
|
||||
"steph",
|
||||
"stopx",
|
||||
"stopy",
|
||||
"stylis",
|
||||
"substate",
|
||||
"sveidqvist",
|
||||
@ -101,14 +120,19 @@
|
||||
"textlength",
|
||||
"treemap",
|
||||
"ts-nocheck",
|
||||
"tsdoc",
|
||||
"tuleap",
|
||||
"tylerlong",
|
||||
"ugge",
|
||||
"unist",
|
||||
"unocss",
|
||||
"valign",
|
||||
"verdana",
|
||||
"viewports",
|
||||
"vinod",
|
||||
"visio",
|
||||
"vitepress",
|
||||
"vueuse",
|
||||
"xlink",
|
||||
"yash"
|
||||
],
|
||||
@ -150,6 +174,7 @@
|
||||
],
|
||||
"ignorePaths": [
|
||||
"packages/mermaid/src/docs/CHANGELOG.md",
|
||||
"packages/mermaid/src/docs/.vitepress/redirect.ts"
|
||||
"packages/mermaid/src/docs/.vitepress/redirect.ts",
|
||||
"packages/mermaid/src/docs/.vitepress/contributor-names.json"
|
||||
]
|
||||
}
|
||||
|
@ -548,4 +548,18 @@ class C13["With Città foreign language"]
|
||||
`
|
||||
);
|
||||
});
|
||||
it('should add classes namespaces', function () {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
namespace Namespace1 {
|
||||
class C1
|
||||
class C2
|
||||
}
|
||||
C1 --> C2
|
||||
class C3
|
||||
class C4
|
||||
`
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -123,6 +123,29 @@ context('Sequence diagram', () => {
|
||||
}
|
||||
);
|
||||
});
|
||||
it('should render a sequence diagram with par_over', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
participant John
|
||||
par_over Section title
|
||||
Alice ->> Bob: Message 1<br>Second line
|
||||
Bob ->> John: Message 2
|
||||
end
|
||||
par_over Two line<br>section title
|
||||
Note over Alice: Alice note
|
||||
Note over Bob: Bob note<br>Second line
|
||||
Note over John: John note
|
||||
end
|
||||
par_over Mixed section
|
||||
Alice ->> Bob: Message 1
|
||||
Note left of Bob: Alice/Bob Note
|
||||
end
|
||||
`
|
||||
);
|
||||
});
|
||||
context('font settings', () => {
|
||||
it('should render different note fonts when configured', () => {
|
||||
imgSnapshotTest(
|
||||
|
@ -144,6 +144,26 @@
|
||||
>
|
||||
<hr />
|
||||
|
||||
<pre class="mermaid">
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant Bob
|
||||
participant John
|
||||
par_over Section title
|
||||
Alice ->> Bob: Message 1<br>Second line
|
||||
Bob ->> John: Message 2
|
||||
end
|
||||
par_over Two line<br>section title
|
||||
Note over Alice: Alice note
|
||||
Note over Bob: Bob note<br>Second line
|
||||
Note over John: John note
|
||||
end
|
||||
par_over Mixed section
|
||||
Alice ->> Bob: Message 1
|
||||
Note left of Bob: Alice/Bob Note
|
||||
end
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
|
@ -348,10 +348,10 @@ mermaid.parseError = function (err, hash) {
|
||||
displayErrorInGui(err);
|
||||
};
|
||||
|
||||
const textFieldUpdated = function () {
|
||||
const textFieldUpdated = async function () {
|
||||
const textStr = getTextFromFormField('code');
|
||||
|
||||
if (mermaid.parse(textStr)) {
|
||||
if (await mermaid.parse(textStr)) {
|
||||
reRender(textStr);
|
||||
}
|
||||
};
|
||||
|
@ -147,8 +147,12 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
|
||||
- [Sphinx](https://www.sphinx-doc.org/en/master/)
|
||||
- [sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
|
||||
- [remark.js](https://remark.js.org/)
|
||||
- [remark-mermaid](https://github.com/temando/remark-mermaid)
|
||||
- [remark](https://remark.js.org/)
|
||||
- [remark-mermaidjs](https://github.com/remcohaszing/remark-mermaidjs)
|
||||
- [rehype](https://github.com/rehypejs/rehype)
|
||||
- [rehype-mermaidjs](https://github.com/remcohaszing/rehype-mermaidjs)
|
||||
- [Gatsby](https://www.gatsbyjs.com/)
|
||||
- [gatsby-remark-mermaid](https://github.com/remcohaszing/gatsby-remark-mermaid)
|
||||
- [jSDoc](https://jsdoc.app/)
|
||||
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
|
||||
- [MkDocs](https://www.mkdocs.org)
|
||||
@ -189,6 +193,7 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [bisheng-plugin-mermaid](https://github.com/yct21/bisheng-plugin-mermaid)
|
||||
- [Reveal CK](https://github.com/jedcn/reveal-ck)
|
||||
- [reveal-ck-mermaid-plugin](https://github.com/tmtm/reveal-ck-mermaid-plugin)
|
||||
- [mermaid-isomorphic](https://github.com/remcohaszing/mermaid-isomorphic)
|
||||
- [mermaid-server: Generate diagrams using a HTTP request](https://github.com/TomWright/mermaid-server)
|
||||
- [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
- [Rendering Mermaid graphs](https://github.com/elixir-lang/ex_doc#rendering-mermaid-graphs)
|
||||
|
@ -421,6 +421,34 @@ And `Link` can be one of:
|
||||
| -- | Solid |
|
||||
| .. | Dashed |
|
||||
|
||||
## Define Namespace
|
||||
|
||||
A namespace groups classes.
|
||||
|
||||
Code:
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
namespace BaseShapes {
|
||||
class Triangle
|
||||
class Rectangle {
|
||||
double width
|
||||
double height
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
namespace BaseShapes {
|
||||
class Triangle
|
||||
class Rectangle {
|
||||
double width
|
||||
double height
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cardinality / Multiplicity on relations
|
||||
|
||||
Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies.
|
||||
|
@ -6,11 +6,10 @@
|
||||
|
||||
# Flowcharts - Basic Syntax
|
||||
|
||||
All Flowcharts are composed of **nodes**, the geometric shapes and **edges**, the arrows or lines. The mermaid code defines the way that these **nodes** and **edges** are made and interact.
|
||||
Flowcharts are composed of **nodes** (geometric shapes) and **edges** (arrows or lines). The Mermaid code defines how nodes and edges are made and accommodates different arrow types, multi-directional arrows, and any linking to and from subgraphs.
|
||||
|
||||
It can also accommodate different arrow types, multi directional arrows, and linking to and from subgraphs.
|
||||
|
||||
> **Important note**: Do not type the word "end" as a Flowchart node. Capitalize all or any one the letters to keep the flowchart from breaking, i.e, "End" or "END". Or you can apply this [workaround](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897).
|
||||
> **Warning**
|
||||
> If you are using the word "end" in a Flowchart node, capitalize the entire word or any of the letters (e.g., "End" or "END"), or apply this [workaround](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897). Typing "end" in all lowercase letters will break the Flowchart.
|
||||
|
||||
### A node (default)
|
||||
|
||||
@ -1039,7 +1038,7 @@ flowchart TD
|
||||
B-->E(A fa:fa-camera-retro perhaps?)
|
||||
```
|
||||
|
||||
?> Mermaid is now only compatible with Font Awesome versions 4 and 5. Check that you are using the correct version of Font Awesome.
|
||||
Mermaid is compatible with Font Awesome up to verion 5, Free icons only. Check that the icons you use are from the [supported set of icons](https://fontawesome.com/v5/search?o=r&m=free).
|
||||
|
||||
## Graph declarations with spaces between vertices and link and without semicolon
|
||||
|
||||
|
@ -182,7 +182,7 @@ More shapes will be added, beginning with the shapes available in flowcharts.
|
||||
|
||||
## Icons
|
||||
|
||||
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and fontawesome 4 are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
|
||||
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and [Font Awesome 5](https://fontawesome.com/v5/search?o=r&m=free) are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
|
11
package.json
11
package.json
@ -4,7 +4,7 @@
|
||||
"version": "10.1.0",
|
||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@8.3.1",
|
||||
"packageManager": "pnpm@8.4.0",
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
@ -26,6 +26,7 @@
|
||||
"lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
|
||||
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts",
|
||||
"lint:jison": "ts-node-esm ./scripts/jison/lint.mts",
|
||||
"contributors": "ts-node-esm scripts/updateContributors.ts",
|
||||
"cypress": "cypress run",
|
||||
"cypress:open": "cypress open",
|
||||
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
|
||||
@ -71,9 +72,9 @@
|
||||
"@types/rollup-plugin-visualizer": "^4.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@vitest/coverage-c8": "^0.30.1",
|
||||
"@vitest/spy": "^0.30.1",
|
||||
"@vitest/ui": "^0.30.1",
|
||||
"@vitest/coverage-c8": "^0.31.0",
|
||||
"@vitest/spy": "^0.31.0",
|
||||
"@vitest/ui": "^0.31.0",
|
||||
"concurrently": "^8.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"coveralls": "^3.1.1",
|
||||
@ -110,7 +111,7 @@
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.3.1",
|
||||
"vitest": "^0.30.1"
|
||||
"vitest": "^0.31.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.16.0"
|
||||
|
5
packages/mermaid/.gitignore
vendored
5
packages/mermaid/.gitignore
vendored
@ -1,3 +1,6 @@
|
||||
src/vitepress
|
||||
src/docs/config/setup
|
||||
README.*
|
||||
README.*
|
||||
src/docs/public/user-avatars/
|
||||
src/docs/.vitepress/cache
|
||||
src/docs/.vitepress/components.d.ts
|
||||
|
@ -28,8 +28,8 @@
|
||||
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts",
|
||||
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify",
|
||||
"docs:pre:vitepress": "rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress",
|
||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && vitepress build src/vitepress && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"vitepress dev src/vitepress\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm --filter ./ install && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./ src/vitepress dev\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
||||
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
|
||||
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
|
||||
"release": "pnpm build",
|
||||
@ -59,7 +59,7 @@
|
||||
"d3": "^7.4.0",
|
||||
"dagre-d3-es": "7.0.10",
|
||||
"dayjs": "^1.11.7",
|
||||
"dompurify": "3.0.2",
|
||||
"dompurify": "3.0.3",
|
||||
"elkjs": "^0.8.2",
|
||||
"khroma": "^2.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
|
@ -1,28 +1,9 @@
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
|
||||
for (let attrKey in rectData.attrs) {
|
||||
rectElem.attr(attrKey, rectData.attrs[attrKey]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rectData.class !== 'undefined') {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
return svgDrawCommon.drawRect(elem, rectData);
|
||||
};
|
||||
|
||||
export const drawImage = function (elem, width, height, x, y, link) {
|
||||
@ -236,7 +217,8 @@ export const drawC4Shape = function (elem, c4Shape, conf) {
|
||||
|
||||
// <rect fill="#08427B" height="119.2188" rx="2.5" ry="2.5" stroke="#073B6F" stroke-width="0.5" width="110" x="120" y="7"/>
|
||||
// draw rect of c4Shape
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
|
||||
switch (c4Shape.typeC4Shape.text) {
|
||||
case 'person':
|
||||
case 'external_person':
|
||||
@ -479,6 +461,7 @@ export const insertArrowHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
export const insertArrowEnd = function (elem) {
|
||||
elem
|
||||
.append('defs')
|
||||
@ -493,6 +476,7 @@ export const insertArrowEnd = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 10 0 L 0 5 L 10 10 z'); // this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@ -511,6 +495,7 @@ export const insertArrowFilledHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup node number. The result is appended to the svg.
|
||||
*
|
||||
@ -532,6 +517,7 @@ export const insertDynamicNumber = function (elem) {
|
||||
.attr('r', 6);
|
||||
// .style("fill", '#f00');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@ -568,20 +554,6 @@ export const insertArrowCrossHead = function (elem) {
|
||||
// this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#EDF2AE',
|
||||
stroke: '#666',
|
||||
width: 100,
|
||||
anchor: 'start',
|
||||
height: 100,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
const getC4ShapeFont = (cnf, typeC4Shape) => {
|
||||
return {
|
||||
fontFamily: cnf[typeC4Shape + 'FontFamily'],
|
||||
@ -714,6 +686,4 @@ export default {
|
||||
insertDatabaseIcon,
|
||||
insertComputerIcon,
|
||||
insertClockIcon,
|
||||
getNoteRect,
|
||||
sanitizeUrl, // TODO why is this exported?
|
||||
};
|
||||
|
@ -14,7 +14,14 @@ import {
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
} from '../../commonDb.js';
|
||||
import { ClassRelation, ClassNode, ClassNote, ClassMap } from './classTypes.js';
|
||||
import {
|
||||
ClassRelation,
|
||||
ClassNode,
|
||||
ClassNote,
|
||||
ClassMap,
|
||||
NamespaceMap,
|
||||
NamespaceNode,
|
||||
} from './classTypes.js';
|
||||
|
||||
const MERMAID_DOM_ID_PREFIX = 'classId-';
|
||||
|
||||
@ -22,6 +29,8 @@ let relations: ClassRelation[] = [];
|
||||
let classes: ClassMap = {};
|
||||
let notes: ClassNote[] = [];
|
||||
let classCounter = 0;
|
||||
let namespaces: NamespaceMap = {};
|
||||
let namespaceCounter = 0;
|
||||
|
||||
let functions: any[] = [];
|
||||
|
||||
@ -100,6 +109,8 @@ export const clear = function () {
|
||||
notes = [];
|
||||
functions = [];
|
||||
functions.push(setupToolTips);
|
||||
namespaces = {};
|
||||
namespaceCounter = 0;
|
||||
commonClear();
|
||||
};
|
||||
|
||||
@ -237,7 +248,11 @@ const setTooltip = function (ids: string, tooltip?: string) {
|
||||
});
|
||||
};
|
||||
|
||||
export const getTooltip = function (id: string) {
|
||||
export const getTooltip = function (id: string, namespace?: string) {
|
||||
if (namespace) {
|
||||
return namespaces[namespace].classes[id].tooltip;
|
||||
}
|
||||
|
||||
return classes[id].tooltip;
|
||||
};
|
||||
/**
|
||||
@ -395,6 +410,52 @@ const setDirection = (dir: string) => {
|
||||
direction = dir;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a namespace definition has been found.
|
||||
*
|
||||
* @param id - Id of the namespace to add
|
||||
* @public
|
||||
*/
|
||||
export const addNamespace = function (id: string) {
|
||||
if (namespaces[id] !== undefined) {
|
||||
return;
|
||||
}
|
||||
|
||||
namespaces[id] = {
|
||||
id: id,
|
||||
classes: {},
|
||||
children: {},
|
||||
domId: MERMAID_DOM_ID_PREFIX + id + '-' + namespaceCounter,
|
||||
} as NamespaceNode;
|
||||
|
||||
namespaceCounter++;
|
||||
};
|
||||
|
||||
const getNamespace = function (name: string): NamespaceNode {
|
||||
return namespaces[name];
|
||||
};
|
||||
|
||||
const getNamespaces = function (): NamespaceMap {
|
||||
return namespaces;
|
||||
};
|
||||
|
||||
/**
|
||||
* Function called by parser when a namespace definition has been found.
|
||||
*
|
||||
* @param id - Id of the namespace to add
|
||||
* @param classNames - Ids of the class to add
|
||||
* @public
|
||||
*/
|
||||
export const addClassesToNamespace = function (id: string, classNames: string[]) {
|
||||
if (namespaces[id] !== undefined) {
|
||||
classNames.map((className) => {
|
||||
namespaces[id].classes[className] = classes[className];
|
||||
delete classes[className];
|
||||
classCounter--;
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
parseDirective,
|
||||
setAccTitle,
|
||||
@ -428,4 +489,8 @@ export default {
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
setClassLabel,
|
||||
addNamespace,
|
||||
addClassesToNamespace,
|
||||
getNamespace,
|
||||
getNamespaces,
|
||||
};
|
||||
|
@ -816,6 +816,37 @@ describe('given a class diagram with generics, ', function () {
|
||||
|
||||
parser.parse(str);
|
||||
});
|
||||
|
||||
it('should handle "namespace"', function () {
|
||||
const str = `classDiagram
|
||||
namespace Namespace1 { class Class1 }
|
||||
namespace Namespace2 { class Class1
|
||||
}
|
||||
namespace Namespace3 {
|
||||
class Class1 {
|
||||
int : test
|
||||
string : foo
|
||||
test()
|
||||
foo()
|
||||
}
|
||||
}
|
||||
namespace Namespace4 {
|
||||
class Class1 {
|
||||
int : test
|
||||
string : foo
|
||||
test()
|
||||
foo()
|
||||
}
|
||||
class Class2 {
|
||||
int : test
|
||||
string : foo
|
||||
test()
|
||||
foo()
|
||||
}
|
||||
}
|
||||
`;
|
||||
parser.parse(str);
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing invalid generic classes', function () {
|
||||
@ -1051,5 +1082,487 @@ describe('given a class diagram with relationships, ', function () {
|
||||
expect(relations[3].relation.type2).toBe('none');
|
||||
expect(relations[3].relation.lineType).toBe(classDb.lineType.DOTTED_LINE);
|
||||
});
|
||||
|
||||
it('should handle generic class with relation definitions', function () {
|
||||
const str = 'classDiagram\n' + 'Class01~T~ <|-- Class02';
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const relations = parser.yy.getRelations();
|
||||
|
||||
expect(parser.yy.getClass('Class01').id).toBe('Class01');
|
||||
expect(parser.yy.getClass('Class01').type).toBe('T');
|
||||
expect(parser.yy.getClass('Class02').id).toBe('Class02');
|
||||
expect(relations[0].relation.type1).toBe(classDb.relationType.EXTENSION);
|
||||
expect(relations[0].relation.type2).toBe('none');
|
||||
expect(relations[0].relation.lineType).toBe(classDb.lineType.LINE);
|
||||
});
|
||||
|
||||
it('should handle class annotations', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + '<<interface>> Class1';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(1);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(0);
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should handle class annotations with members and methods', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : int test\n' +
|
||||
'Class1 : test()\n' +
|
||||
'<<interface>> Class1';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(1);
|
||||
expect(testClass.members.length).toBe(1);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should handle class annotations in brackets', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1 {\n' + '<<interface>>\n' + '}';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(1);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(0);
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should handle class annotations in brackets with members and methods', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'<<interface>>\n' +
|
||||
'int : test\n' +
|
||||
'test()\n' +
|
||||
'}';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(1);
|
||||
expect(testClass.members.length).toBe(1);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.annotations[0]).toBe('interface');
|
||||
});
|
||||
|
||||
it('should add bracket members in right order', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1 {\n' +
|
||||
'int : test\n' +
|
||||
'string : foo\n' +
|
||||
'test()\n' +
|
||||
'foo()\n' +
|
||||
'}';
|
||||
parser.parse(str);
|
||||
|
||||
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()');
|
||||
});
|
||||
|
||||
it('should handle abstract methods', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()*';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(0);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.methods[0]).toBe('someMethod()*');
|
||||
});
|
||||
|
||||
it('should handle static methods', function () {
|
||||
const str = 'classDiagram\n' + 'class Class1\n' + 'Class1 : someMethod()$';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.annotations.length).toBe(0);
|
||||
expect(testClass.members.length).toBe(0);
|
||||
expect(testClass.methods.length).toBe(1);
|
||||
expect(testClass.methods[0]).toBe('someMethod()$');
|
||||
});
|
||||
|
||||
it('should associate link and css appropriately', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'link Class1 "google.com"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('google.com');
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate click and href link and css appropriately', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 href "google.com"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('google.com');
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate link with tooltip', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'link Class1 "google.com" "A tooltip"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('google.com');
|
||||
expect(testClass.tooltip).toBe('A tooltip');
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate click and href link with tooltip', function () {
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 href "google.com" "A tooltip"';
|
||||
parser.parse(str);
|
||||
|
||||
const testClass = parser.yy.getClass('Class1');
|
||||
expect(testClass.link).toBe('google.com');
|
||||
expect(testClass.tooltip).toBe('A tooltip');
|
||||
expect(testClass.cssClasses.length).toBe(1);
|
||||
expect(testClass.cssClasses[0]).toBe('clickable');
|
||||
});
|
||||
|
||||
it('should associate click and href link with tooltip and target appropriately', function () {
|
||||
spyOn(classDb, 'setLink');
|
||||
spyOn(classDb, 'setTooltip');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 href "google.com" "A tooltip" _self';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self');
|
||||
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
|
||||
});
|
||||
|
||||
it('should associate click and href link appropriately', function () {
|
||||
spyOn(classDb, 'setLink');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 href "google.com"';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com');
|
||||
});
|
||||
|
||||
it('should associate click and href link with target appropriately', function () {
|
||||
spyOn(classDb, 'setLink');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 href "google.com" _self';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self');
|
||||
});
|
||||
|
||||
it('should associate link appropriately', function () {
|
||||
spyOn(classDb, 'setLink');
|
||||
spyOn(classDb, 'setTooltip');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'link Class1 "google.com" "A tooltip" _self';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setLink).toHaveBeenCalledWith('Class1', 'google.com', '_self');
|
||||
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
|
||||
});
|
||||
|
||||
it('should associate callback appropriately', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'callback Class1 "functionCall"';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall');
|
||||
});
|
||||
|
||||
it('should associate click and call callback appropriately', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 call functionCall()';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall');
|
||||
});
|
||||
|
||||
it('should associate callback appropriately with an arbitrary number of args', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 call functionCall("test0", test1, test2)';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith(
|
||||
'Class1',
|
||||
'functionCall',
|
||||
'"test0", test1, test2'
|
||||
);
|
||||
});
|
||||
|
||||
it('should associate callback with tooltip', function () {
|
||||
spyOn(classDb, 'setClickEvent');
|
||||
spyOn(classDb, 'setTooltip');
|
||||
const str =
|
||||
'classDiagram\n' +
|
||||
'class Class1\n' +
|
||||
'Class1 : someMethod()\n' +
|
||||
'click Class1 call functionCall() "A tooltip"';
|
||||
parser.parse(str);
|
||||
|
||||
expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall');
|
||||
expect(classDb.setTooltip).toHaveBeenCalledWith('Class1', 'A tooltip');
|
||||
});
|
||||
|
||||
it('should add classes namespaces', function () {
|
||||
const str = `classDiagram
|
||||
namespace Namespace1 {
|
||||
class Class1 {
|
||||
int : test
|
||||
string : foo
|
||||
test()
|
||||
foo()
|
||||
}
|
||||
class Class2
|
||||
}`;
|
||||
parser.parse(str);
|
||||
|
||||
const testNamespace = parser.yy.getNamespace('Namespace1');
|
||||
expect(Object.keys(testNamespace.classes).length).toBe(2);
|
||||
expect(Object.keys(testNamespace.children).length).toBe(0);
|
||||
expect(testNamespace.classes['Class1'].id).toBe('Class1');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing classDiagram with text labels', () => {
|
||||
beforeEach(function () {
|
||||
parser.yy = classDb;
|
||||
parser.yy.clear();
|
||||
});
|
||||
|
||||
it('should parse a class with a text label', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]
|
||||
C1 --> C2
|
||||
`);
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
});
|
||||
|
||||
it('should parse two classes with text labels', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]
|
||||
class C2["Class 2 with chars @?"]
|
||||
C1 --> C2
|
||||
`);
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Class 2 with chars @?');
|
||||
});
|
||||
|
||||
it('should parse a class with a text label and members', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
`);
|
||||
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 c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
});
|
||||
|
||||
it('should parse a class with a text label, members and annotation', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
<<interface>>
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
`);
|
||||
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 c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('C2');
|
||||
});
|
||||
|
||||
it('should parse a class with text label and css class shorthand', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]:::styleClass {
|
||||
+member1
|
||||
}
|
||||
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');
|
||||
});
|
||||
|
||||
it('should parse a class with text label and css class', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
+member1
|
||||
}
|
||||
C1 --> C2
|
||||
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');
|
||||
});
|
||||
|
||||
it('should parse two classes with text labels and css classes', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"] {
|
||||
+member1
|
||||
}
|
||||
class C2["Long long long long long long long long long long label"]
|
||||
C1 --> C2
|
||||
cssClass "C1,C2" styleClass
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.cssClasses[0]).toBe('styleClass');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Long long long long long long long long long long label');
|
||||
expect(c2.cssClasses.length).toBe(1);
|
||||
expect(c2.cssClasses[0]).toBe('styleClass');
|
||||
});
|
||||
|
||||
it('should parse two classes with text labels and css class shorthands', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class 1 with text label"]:::styleClass1 {
|
||||
+member1
|
||||
}
|
||||
class C2["Class 2 !@#$%^&*() label"]:::styleClass2
|
||||
C1 --> C2
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class 1 with text label');
|
||||
expect(c1.cssClasses.length).toBe(1);
|
||||
expect(c1.cssClasses[0]).toBe('styleClass1');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Class 2 !@#$%^&*() label');
|
||||
expect(c2.cssClasses.length).toBe(1);
|
||||
expect(c2.cssClasses[0]).toBe('styleClass2');
|
||||
});
|
||||
|
||||
it('should parse multiple classes with same text labels', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["Class with text label"]
|
||||
class C2["Class with text label"]
|
||||
class C3["Class with text label"]
|
||||
C1 --> C2
|
||||
C3 ..> C2
|
||||
`);
|
||||
|
||||
const c1 = classDb.getClass('C1');
|
||||
expect(c1.label).toBe('Class with text label');
|
||||
|
||||
const c2 = classDb.getClass('C2');
|
||||
expect(c2.label).toBe('Class with text label');
|
||||
|
||||
const c3 = classDb.getClass('C3');
|
||||
expect(c3.label).toBe('Class with text label');
|
||||
});
|
||||
|
||||
it('should parse classes with different text labels', () => {
|
||||
parser.parse(`classDiagram
|
||||
class C1["OneWord"]
|
||||
class C2["With, Comma"]
|
||||
class C3["With (Brackets)"]
|
||||
class C4["With [Brackets]"]
|
||||
class C5["With {Brackets}"]
|
||||
class C6[" "]
|
||||
class C7["With 1 number"]
|
||||
class C8["With . period..."]
|
||||
class C9["With - dash"]
|
||||
class C10["With _ underscore"]
|
||||
class C11["With ' single quote"]
|
||||
class C12["With ~!@#$%^&*()_+=-/?"]
|
||||
class C13["With Città foreign language"]
|
||||
`);
|
||||
expect(classDb.getClass('C1').label).toBe('OneWord');
|
||||
expect(classDb.getClass('C2').label).toBe('With, Comma');
|
||||
expect(classDb.getClass('C3').label).toBe('With (Brackets)');
|
||||
expect(classDb.getClass('C4').label).toBe('With [Brackets]');
|
||||
expect(classDb.getClass('C5').label).toBe('With {Brackets}');
|
||||
expect(classDb.getClass('C6').label).toBe(' ');
|
||||
expect(classDb.getClass('C7').label).toBe('With 1 number');
|
||||
expect(classDb.getClass('C8').label).toBe('With . period...');
|
||||
expect(classDb.getClass('C9').label).toBe('With - dash');
|
||||
expect(classDb.getClass('C10').label).toBe('With _ underscore');
|
||||
expect(classDb.getClass('C11').label).toBe("With ' single quote");
|
||||
expect(classDb.getClass('C12').label).toBe('With ~!@#$%^&*()_+=-/?');
|
||||
expect(classDb.getClass('C13').label).toBe('With Città foreign language');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,10 +1,10 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
// @ts-ignore - no types
|
||||
import { LALRGenerator } from 'jison';
|
||||
import path from 'path';
|
||||
|
||||
const getAbsolutePath = (relativePath: string) => {
|
||||
return new URL(path.join(path.dirname(import.meta.url), relativePath)).pathname;
|
||||
return fileURLToPath(new URL(relativePath, import.meta.url));
|
||||
};
|
||||
|
||||
describe('class diagram grammar', function () {
|
||||
|
@ -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 } from './classTypes.js';
|
||||
import { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js';
|
||||
|
||||
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
|
||||
|
||||
@ -19,6 +19,59 @@ let conf = {
|
||||
curve: undefined,
|
||||
};
|
||||
|
||||
interface RectParameters {
|
||||
id: string;
|
||||
shape: 'rect';
|
||||
labelStyle: string;
|
||||
domId: string;
|
||||
labelText: string;
|
||||
padding: number | undefined;
|
||||
style?: string;
|
||||
}
|
||||
|
||||
/**
|
||||
* Function that adds the vertices found during parsing to the graph to be rendered.
|
||||
*
|
||||
* @param namespaces - Object containing the vertices.
|
||||
* @param g - The graph that is to be drawn.
|
||||
* @param _id - id of the graph
|
||||
* @param diagObj - The diagram object
|
||||
*/
|
||||
export const addNamespaces = function (
|
||||
namespaces: NamespaceMap,
|
||||
g: graphlib.Graph,
|
||||
_id: string,
|
||||
diagObj: any
|
||||
) {
|
||||
const keys = Object.keys(namespaces);
|
||||
log.info('keys:', keys);
|
||||
log.info(namespaces);
|
||||
|
||||
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
|
||||
keys.forEach(function (id) {
|
||||
const vertex = namespaces[id];
|
||||
|
||||
// parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
|
||||
const shape = 'rect';
|
||||
|
||||
const node: RectParameters = {
|
||||
shape: shape,
|
||||
id: vertex.id,
|
||||
domId: vertex.domId,
|
||||
labelText: sanitizeText(vertex.id),
|
||||
labelStyle: '',
|
||||
style: 'fill: none; stroke: black',
|
||||
// TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
|
||||
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
|
||||
};
|
||||
|
||||
g.setNode(vertex.id, node);
|
||||
addClasses(vertex.classes, g, _id, diagObj, vertex.id);
|
||||
|
||||
log.info('setNode', node);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Function that adds the vertices found during parsing to the graph to be rendered.
|
||||
*
|
||||
@ -26,12 +79,14 @@ let conf = {
|
||||
* @param g - The graph that is to be drawn.
|
||||
* @param _id - id of the graph
|
||||
* @param diagObj - The diagram object
|
||||
* @param parent - id of the parent namespace, if it exists
|
||||
*/
|
||||
export const addClasses = function (
|
||||
classes: ClassMap,
|
||||
g: graphlib.Graph,
|
||||
_id: string,
|
||||
diagObj: any
|
||||
diagObj: any,
|
||||
parent?: string
|
||||
) {
|
||||
const keys = Object.keys(classes);
|
||||
log.info('keys:', keys);
|
||||
@ -55,6 +110,7 @@ export const addClasses = function (
|
||||
const vertexText = vertex.label ?? vertex.id;
|
||||
const radius = 0;
|
||||
const shape = 'class_box';
|
||||
|
||||
// Add the node
|
||||
const node = {
|
||||
labelStyle: styles.labelStyle,
|
||||
@ -67,7 +123,7 @@ export const addClasses = function (
|
||||
style: styles.style,
|
||||
id: vertex.id,
|
||||
domId: vertex.domId,
|
||||
tooltip: diagObj.db.getTooltip(vertex.id) || '',
|
||||
tooltip: diagObj.db.getTooltip(vertex.id, parent) || '',
|
||||
haveCallback: vertex.haveCallback,
|
||||
link: vertex.link,
|
||||
width: vertex.type === 'group' ? 500 : undefined,
|
||||
@ -76,6 +132,11 @@ export const addClasses = function (
|
||||
padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
|
||||
};
|
||||
g.setNode(vertex.id, node);
|
||||
|
||||
if (parent) {
|
||||
g.setParent(vertex.id, parent);
|
||||
}
|
||||
|
||||
log.info('setNode', node);
|
||||
});
|
||||
};
|
||||
@ -275,10 +336,12 @@ export const draw = async function (text: string, id: string, _version: string,
|
||||
});
|
||||
|
||||
// Fetch the vertices/nodes and edges/links from the parsed graph definition
|
||||
const namespaces: NamespaceMap = diagObj.db.getNamespaces();
|
||||
const classes: ClassMap = diagObj.db.getClasses();
|
||||
const relations: ClassRelation[] = diagObj.db.getRelations();
|
||||
const notes: ClassNote[] = diagObj.db.getNotes();
|
||||
log.info(relations);
|
||||
addNamespaces(namespaces, g, id, diagObj);
|
||||
addClasses(classes, g, id, diagObj);
|
||||
addRelations(relations, g);
|
||||
addNotes(notes, g, relations.length + 1, classes);
|
||||
|
@ -52,4 +52,13 @@ export type ClassRelation = {
|
||||
lineType: number;
|
||||
};
|
||||
};
|
||||
|
||||
export interface NamespaceNode {
|
||||
id: string;
|
||||
domId: string;
|
||||
classes: ClassMap;
|
||||
children: NamespaceMap;
|
||||
}
|
||||
|
||||
export type ClassMap = Record<string, ClassNode>;
|
||||
export type NamespaceMap = Record<string, NamespaceNode>;
|
||||
|
@ -19,6 +19,10 @@
|
||||
%x acc_title
|
||||
%x acc_descr
|
||||
%x acc_descr_multiline
|
||||
%x class
|
||||
%x class-body
|
||||
%x namespace
|
||||
%x namespace-body
|
||||
%%
|
||||
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
|
||||
.*direction\s+TB[^\n]* return 'direction_tb';
|
||||
@ -41,35 +45,41 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
||||
|
||||
\s*(\r?\n)+ return 'NEWLINE';
|
||||
\s+ /* skip whitespace */
|
||||
|
||||
"classDiagram-v2" return 'CLASS_DIAGRAM';
|
||||
"classDiagram" return 'CLASS_DIAGRAM';
|
||||
[{] { this.begin("struct"); /*console.log('Starting struct');*/ return 'STRUCT_START';}
|
||||
<INITIAL,struct>"[*]" { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';}
|
||||
<struct><<EOF>> return "EOF_IN_STRUCT";
|
||||
<struct>[{] return "OPEN_IN_STRUCT";
|
||||
<struct>[}] { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}}
|
||||
<struct>[\n] /* nothing */
|
||||
<struct>[^{}\n]* { /*console.log('lex-member: ' + yytext);*/ return "MEMBER";}
|
||||
"[*]" return 'EDGE_STATE';
|
||||
|
||||
"class" return 'CLASS';
|
||||
"cssClass" return 'CSSCLASS';
|
||||
"callback" return 'CALLBACK';
|
||||
"link" return 'LINK';
|
||||
"click" return 'CLICK';
|
||||
"note for" return 'NOTE_FOR';
|
||||
"note" return 'NOTE';
|
||||
"<<" return 'ANNOTATION_START';
|
||||
">>" return 'ANNOTATION_END';
|
||||
[~] this.begin("generic");
|
||||
<generic>[~] this.popState();
|
||||
<generic>[^~]* return "GENERICTYPE";
|
||||
["] this.begin("string");
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
<INITIAL,namespace>"namespace" { this.begin('namespace'); return 'NAMESPACE'; }
|
||||
<namespace>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
|
||||
<namespace>\s+ /* skip whitespace */
|
||||
<namespace>[{] { this.begin("namespace-body"); return 'STRUCT_START';}
|
||||
<namespace-body>[}] { this.popState(); return 'STRUCT_STOP'; }
|
||||
<namespace-body><<EOF>> return "EOF_IN_STRUCT";
|
||||
<namespace-body>\s*(\r?\n)+ return 'NEWLINE';
|
||||
<namespace-body>\s+ /* skip whitespace */
|
||||
<namespace-body>"[*]" return 'EDGE_STATE';
|
||||
|
||||
[`] this.begin("bqstring");
|
||||
<bqstring>[`] this.popState();
|
||||
<bqstring>[^`]+ return "BQUOTE_STR";
|
||||
<INITIAL,namespace-body>"class" { this.begin('class'); return 'CLASS';}
|
||||
<class>\s*(\r?\n)+ { this.popState(); return 'NEWLINE'; }
|
||||
<class>\s+ /* skip whitespace */
|
||||
<class>[}] { this.popState(); this.popState(); return 'STRUCT_STOP';}
|
||||
<class>[{] { this.begin("class-body"); return 'STRUCT_START';}
|
||||
<class-body>[}] { this.popState(); return 'STRUCT_STOP'; }
|
||||
<class-body><<EOF>> return "EOF_IN_STRUCT";
|
||||
<class-body>"[*]" { return 'EDGE_STATE';}
|
||||
<class-body>[{] return "OPEN_IN_STRUCT";
|
||||
<class-body>[\n] /* nothing */
|
||||
<class-body>[^{}\n]* { return "MEMBER";}
|
||||
|
||||
<*>"cssClass" return 'CSSCLASS';
|
||||
<*>"callback" return 'CALLBACK';
|
||||
<*>"link" return 'LINK';
|
||||
<*>"click" return 'CLICK';
|
||||
<*>"note for" return 'NOTE_FOR';
|
||||
<*>"note" return 'NOTE';
|
||||
<*>"<<" return 'ANNOTATION_START';
|
||||
<*>">>" return 'ANNOTATION_END';
|
||||
|
||||
/*
|
||||
---interactivity command---
|
||||
@ -77,7 +87,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
||||
line was introduced with 'click'.
|
||||
'href "<link>"' attaches the specified link to the node that was specified by 'click'.
|
||||
*/
|
||||
"href"[\s]+["] this.begin("href");
|
||||
<*>"href"[\s]+["] this.begin("href");
|
||||
<href>["] this.popState();
|
||||
<href>[^"]* return 'HREF';
|
||||
|
||||
@ -89,41 +99,53 @@ the line was introduced with 'click'.
|
||||
arguments to the node that was specified by 'click'.
|
||||
Function arguments are optional: 'call <callback_name>()' simply executes 'callback_name' without any arguments.
|
||||
*/
|
||||
"call"[\s]+ this.begin("callback_name");
|
||||
<*>"call"[\s]+ this.begin("callback_name");
|
||||
<callback_name>\([\s]*\) this.popState();
|
||||
<callback_name>\( this.popState(); this.begin("callback_args");
|
||||
<callback_name>[^(]* return 'CALLBACK_NAME';
|
||||
<callback_args>\) this.popState();
|
||||
<callback_args>[^)]* return 'CALLBACK_ARGS';
|
||||
|
||||
"_self" return 'LINK_TARGET';
|
||||
"_blank" return 'LINK_TARGET';
|
||||
"_parent" return 'LINK_TARGET';
|
||||
"_top" return 'LINK_TARGET';
|
||||
<generic>[~] this.popState();
|
||||
<generic>[^~]* return "GENERICTYPE";
|
||||
<*>[~] this.begin("generic");
|
||||
|
||||
\s*\<\| return 'EXTENSION';
|
||||
\s*\|\> return 'EXTENSION';
|
||||
\s*\> return 'DEPENDENCY';
|
||||
\s*\< return 'DEPENDENCY';
|
||||
\s*\* return 'COMPOSITION';
|
||||
\s*o return 'AGGREGATION';
|
||||
\s*\(\) return 'LOLLIPOP';
|
||||
\-\- return 'LINE';
|
||||
\.\. return 'DOTTED_LINE';
|
||||
":"{1}[^:\n;]+ return 'LABEL';
|
||||
":"{3} return 'STYLE_SEPARATOR';
|
||||
\- return 'MINUS';
|
||||
"." return 'DOT';
|
||||
\+ return 'PLUS';
|
||||
\% return 'PCT';
|
||||
"=" return 'EQUALS';
|
||||
\= return 'EQUALS';
|
||||
\w+ return 'ALPHA';
|
||||
"[" return 'SQS';
|
||||
"]" return 'SQE';
|
||||
[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
|
||||
[0-9]+ return 'NUM';
|
||||
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return "STR";
|
||||
<*>["] this.begin("string");
|
||||
|
||||
<bqstring>[`] this.popState();
|
||||
<bqstring>[^`]+ return "BQUOTE_STR";
|
||||
<*>[`] this.begin("bqstring");
|
||||
|
||||
<*>"_self" return 'LINK_TARGET';
|
||||
<*>"_blank" return 'LINK_TARGET';
|
||||
<*>"_parent" return 'LINK_TARGET';
|
||||
<*>"_top" return 'LINK_TARGET';
|
||||
|
||||
<*>\s*\<\| return 'EXTENSION';
|
||||
<*>\s*\|\> return 'EXTENSION';
|
||||
<*>\s*\> return 'DEPENDENCY';
|
||||
<*>\s*\< return 'DEPENDENCY';
|
||||
<*>\s*\* return 'COMPOSITION';
|
||||
<*>\s*o return 'AGGREGATION';
|
||||
<*>\s*\(\) return 'LOLLIPOP';
|
||||
<*>\-\- return 'LINE';
|
||||
<*>\.\. return 'DOTTED_LINE';
|
||||
<*>":"{1}[^:\n;]+ return 'LABEL';
|
||||
<*>":"{3} return 'STYLE_SEPARATOR';
|
||||
<*>\- return 'MINUS';
|
||||
<*>"." return 'DOT';
|
||||
<*>\+ return 'PLUS';
|
||||
<*>\% return 'PCT';
|
||||
<*>"=" return 'EQUALS';
|
||||
<*>\= return 'EQUALS';
|
||||
<*>\w+ return 'ALPHA';
|
||||
<*>"[" return 'SQS';
|
||||
<*>"]" return 'SQE';
|
||||
<*>[!"#$%&'*+,-.`?\\/] return 'PUNCTUATION';
|
||||
<*>[0-9]+ return 'NUM';
|
||||
<*>[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
|
||||
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
|
||||
[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|
|
||||
[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|
|
||||
@ -184,9 +206,9 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
|
||||
[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|
|
||||
[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|
|
||||
[\uFFD2-\uFFD7\uFFDA-\uFFDC]
|
||||
return 'UNICODE_TEXT';
|
||||
\s return 'SPACE';
|
||||
<<EOF>> return 'EOF';
|
||||
return 'UNICODE_TEXT';
|
||||
<*>\s return 'SPACE';
|
||||
<*><<EOF>> return 'EOF';
|
||||
|
||||
/lex
|
||||
|
||||
@ -254,6 +276,11 @@ classLabel
|
||||
: SQS STR SQE { $$=$2; }
|
||||
;
|
||||
|
||||
namespaceName
|
||||
: alphaNumToken { $$=$1; }
|
||||
| alphaNumToken namespaceName { $$=$1+$2; }
|
||||
;
|
||||
|
||||
className
|
||||
: alphaNumToken { $$=$1; }
|
||||
| classLiteralName { $$=$1; }
|
||||
@ -265,6 +292,7 @@ className
|
||||
statement
|
||||
: relationStatement { yy.addRelation($1); }
|
||||
| relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); }
|
||||
| namespaceStatement
|
||||
| classStatement
|
||||
| methodStatement
|
||||
| annotationStatement
|
||||
@ -277,6 +305,21 @@ statement
|
||||
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); }
|
||||
;
|
||||
|
||||
namespaceStatement
|
||||
: namespaceIdentifier STRUCT_START classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $3);}
|
||||
| namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP {yy.addClassesToNamespace($1, $4);}
|
||||
;
|
||||
|
||||
namespaceIdentifier
|
||||
: NAMESPACE namespaceName {$$=$2; yy.addNamespace($2);}
|
||||
;
|
||||
|
||||
classStatements
|
||||
: classStatement {$$=[$1]}
|
||||
| classStatement NEWLINE {$$=[$1]}
|
||||
| classStatement NEWLINE classStatements {$3.unshift($1); $$=$3}
|
||||
;
|
||||
|
||||
classStatement
|
||||
: classIdentifier
|
||||
| classIdentifier STYLE_SEPARATOR alphaNumToken {yy.setCssClass($1, $3);}
|
||||
|
@ -486,6 +486,7 @@ const buildLegacyDisplay = function (text) {
|
||||
cssStyle,
|
||||
};
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds a <tspan> for a member in a diagram
|
||||
*
|
||||
|
@ -138,6 +138,32 @@ const getUrl = (useAbsolute: boolean): string => {
|
||||
export const evaluate = (val?: string | boolean): boolean =>
|
||||
val === false || ['false', 'null', '0'].includes(String(val).trim().toLowerCase()) ? false : true;
|
||||
|
||||
/**
|
||||
* Wrapper around Math.max which removes non-numeric values
|
||||
* Returns the larger of a set of supplied numeric expressions.
|
||||
* @param values - Numeric expressions to be evaluated
|
||||
* @returns The smaller value
|
||||
*/
|
||||
export const getMax = function (...values: number[]): number {
|
||||
const newValues: number[] = values.filter((value) => {
|
||||
return !isNaN(value);
|
||||
});
|
||||
return Math.max(...newValues);
|
||||
};
|
||||
|
||||
/**
|
||||
* Wrapper around Math.min which removes non-numeric values
|
||||
* Returns the smaller of a set of supplied numeric expressions.
|
||||
* @param values - Numeric expressions to be evaluated
|
||||
* @returns The smaller value
|
||||
*/
|
||||
export const getMin = function (...values: number[]): number {
|
||||
const newValues: number[] = values.filter((value) => {
|
||||
return !isNaN(value);
|
||||
});
|
||||
return Math.min(...newValues);
|
||||
};
|
||||
|
||||
/**
|
||||
* Makes generics in typescript syntax
|
||||
*
|
||||
@ -180,4 +206,6 @@ export default {
|
||||
removeScript,
|
||||
getUrl,
|
||||
evaluate,
|
||||
getMax,
|
||||
getMin,
|
||||
};
|
||||
|
114
packages/mermaid/src/diagrams/common/svgDrawCommon.js
Normal file
114
packages/mermaid/src/diagrams/common/svgDrawCommon.js
Normal file
@ -0,0 +1,114 @@
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
|
||||
for (let attrKey in rectData.attrs) {
|
||||
rectElem.attr(attrKey, rectData.attrs[attrKey]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rectData.class !== 'undefined') {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a background rectangle
|
||||
*
|
||||
* @param {any} elem Diagram (reference for bounds)
|
||||
* @param {any} bounds Shape of the rectangle
|
||||
*/
|
||||
export const drawBackgroundRect = function (elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
width: bounds.stopx - bounds.startx,
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
stroke: bounds.stroke,
|
||||
class: 'rect',
|
||||
});
|
||||
rectElem.lower();
|
||||
};
|
||||
|
||||
export const drawText = function (elem, textData) {
|
||||
// Remove and ignore br:s
|
||||
const nText = textData.text.replace(/<br\s*\/?>/gi, ' ');
|
||||
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.attr('class', 'legend');
|
||||
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
|
||||
if (textData.class !== undefined) {
|
||||
textElem.attr('class', textData.class);
|
||||
}
|
||||
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x + textData.textMargin * 2);
|
||||
span.text(nText);
|
||||
|
||||
return textElem;
|
||||
};
|
||||
|
||||
export const drawImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('image');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
var sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', sanitizedLink);
|
||||
};
|
||||
|
||||
export const drawEmbeddedImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('use');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
const sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', '#' + sanitizedLink);
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: '#EDF2AE',
|
||||
stroke: '#666',
|
||||
anchor: 'start',
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const getTextObj = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: undefined,
|
||||
anchor: undefined,
|
||||
'text-anchor': 'start',
|
||||
style: '#666',
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
tspan: true,
|
||||
valign: undefined,
|
||||
};
|
||||
};
|
@ -20,6 +20,7 @@ const Cardinality = {
|
||||
ZERO_OR_MORE: 'ZERO_OR_MORE',
|
||||
ONE_OR_MORE: 'ONE_OR_MORE',
|
||||
ONLY_ONE: 'ONLY_ONE',
|
||||
MD_PARENT: 'MD_PARENT',
|
||||
};
|
||||
|
||||
const Identification = {
|
||||
|
@ -7,6 +7,8 @@ const ERMarkers = {
|
||||
ONE_OR_MORE_END: 'ONE_OR_MORE_END',
|
||||
ZERO_OR_MORE_START: 'ZERO_OR_MORE_START',
|
||||
ZERO_OR_MORE_END: 'ZERO_OR_MORE_END',
|
||||
MD_PARENT_END: 'MD_PARENT_END',
|
||||
MD_PARENT_START: 'MD_PARENT_START',
|
||||
};
|
||||
|
||||
/**
|
||||
@ -18,6 +20,30 @@ const ERMarkers = {
|
||||
const insertMarkers = function (elem, conf) {
|
||||
let marker;
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.MD_PARENT_START)
|
||||
.attr('refX', 0)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 190)
|
||||
.attr('markerHeight', 240)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
.attr('id', ERMarkers.MD_PARENT_END)
|
||||
.attr('refX', 19)
|
||||
.attr('refY', 7)
|
||||
.attr('markerWidth', 20)
|
||||
.attr('markerHeight', 28)
|
||||
.attr('orient', 'auto')
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
|
||||
|
||||
elem
|
||||
.append('defs')
|
||||
.append('marker')
|
||||
|
@ -478,6 +478,9 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
|
||||
case diagObj.db.Cardinality.ONLY_ONE:
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_END + ')');
|
||||
break;
|
||||
case diagObj.db.Cardinality.MD_PARENT:
|
||||
svgPath.attr('marker-end', 'url(' + url + '#' + erMarkers.ERMarkers.MD_PARENT_END + ')');
|
||||
break;
|
||||
}
|
||||
|
||||
switch (rel.relSpec.cardB) {
|
||||
@ -502,6 +505,9 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
|
||||
case diagObj.db.Cardinality.ONLY_ONE:
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.ONLY_ONE_START + ')');
|
||||
break;
|
||||
case diagObj.db.Cardinality.MD_PARENT:
|
||||
svgPath.attr('marker-start', 'url(' + url + '#' + erMarkers.ERMarkers.MD_PARENT_START + ')');
|
||||
break;
|
||||
}
|
||||
|
||||
// Now label the relationship
|
||||
|
@ -57,6 +57,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
||||
o\| return 'ZERO_OR_ONE';
|
||||
o\{ return 'ZERO_OR_MORE';
|
||||
\|\{ return 'ONE_OR_MORE';
|
||||
\s*u return 'MD_PARENT';
|
||||
\.\. return 'NON_IDENTIFYING';
|
||||
\-\- return 'IDENTIFYING';
|
||||
"to" return 'IDENTIFYING';
|
||||
@ -170,6 +171,7 @@ cardinality
|
||||
| 'ZERO_OR_MORE' { $$ = yy.Cardinality.ZERO_OR_MORE; }
|
||||
| 'ONE_OR_MORE' { $$ = yy.Cardinality.ONE_OR_MORE; }
|
||||
| 'ONLY_ONE' { $$ = yy.Cardinality.ONLY_ONE; }
|
||||
| 'MD_PARENT' { $$ = yy.Cardinality.MD_PARENT; }
|
||||
;
|
||||
|
||||
relType
|
||||
|
@ -718,5 +718,14 @@ describe('when parsing ER diagram it...', function () {
|
||||
const rels = erDb.getRelationships();
|
||||
expect(rels[0].roleA).toBe('places');
|
||||
});
|
||||
|
||||
it('should represent parent-child relationship correctly', function () {
|
||||
erDiagram.parser.parse('erDiagram\nPROJECT u--o{ TEAM_MEMBER : "parent"');
|
||||
const rels = erDb.getRelationships();
|
||||
expect(Object.keys(erDb.getEntities()).length).toBe(2);
|
||||
expect(rels.length).toBe(1);
|
||||
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.MD_PARENT);
|
||||
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -33,6 +33,17 @@ const getStyles = (options) =>
|
||||
font-size: 18px;
|
||||
fill: ${options.textColor};
|
||||
}
|
||||
#MD_PARENT_START {
|
||||
fill: #f5f5f5 !important;
|
||||
stroke: ${options.lineColor} !important;
|
||||
stroke-width: 1;
|
||||
}
|
||||
#MD_PARENT_END {
|
||||
fill: #f5f5f5 !important;
|
||||
stroke: ${options.lineColor} !important;
|
||||
stroke-width: 1;
|
||||
}
|
||||
|
||||
`;
|
||||
|
||||
export default getStyles;
|
||||
|
@ -414,7 +414,7 @@ export const addEdges = function (edges, diagObj, graph, svg) {
|
||||
|
||||
edges.forEach(function (edge) {
|
||||
// Identify Link
|
||||
var linkIdBase = 'L-' + edge.start + '-' + edge.end;
|
||||
const linkIdBase = 'L-' + edge.start + '-' + edge.end;
|
||||
// count the links from+to the same node to give unique id
|
||||
if (linkIdCnt[linkIdBase] === undefined) {
|
||||
linkIdCnt[linkIdBase] = 0;
|
||||
@ -425,8 +425,8 @@ export const addEdges = function (edges, diagObj, graph, svg) {
|
||||
}
|
||||
let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase];
|
||||
log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
|
||||
var linkNameStart = 'LS-' + edge.start;
|
||||
var linkNameEnd = 'LE-' + edge.end;
|
||||
const linkNameStart = 'LS-' + edge.start;
|
||||
const linkNameEnd = 'LE-' + edge.end;
|
||||
|
||||
const edgeData = { style: '', labelStyle: '' };
|
||||
edgeData.minlen = edge.length || 1;
|
||||
|
@ -214,7 +214,7 @@ export const addEdges = function (edges, g, diagObj) {
|
||||
cnt++;
|
||||
|
||||
// Identify Link
|
||||
var linkIdBase = 'L-' + edge.start + '-' + edge.end;
|
||||
const linkIdBase = 'L-' + edge.start + '-' + edge.end;
|
||||
// count the links from+to the same node to give unique id
|
||||
if (linkIdCnt[linkIdBase] === undefined) {
|
||||
linkIdCnt[linkIdBase] = 0;
|
||||
@ -225,8 +225,8 @@ export const addEdges = function (edges, g, diagObj) {
|
||||
}
|
||||
let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase];
|
||||
log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]);
|
||||
var linkNameStart = 'LS-' + edge.start;
|
||||
var linkNameEnd = 'LE-' + edge.end;
|
||||
const linkNameStart = 'LS-' + edge.start;
|
||||
const linkNameEnd = 'LE-' + edge.end;
|
||||
|
||||
const edgeData = { style: '', labelStyle: '' };
|
||||
edgeData.minlen = edge.length || 1;
|
||||
|
@ -176,9 +176,9 @@ export const addEdges = function (edges, g, diagObj) {
|
||||
cnt++;
|
||||
|
||||
// Identify Link
|
||||
var linkId = 'L-' + edge.start + '-' + edge.end;
|
||||
var linkNameStart = 'LS-' + edge.start;
|
||||
var linkNameEnd = 'LE-' + edge.end;
|
||||
const linkId = 'L-' + edge.start + '-' + edge.end;
|
||||
const linkNameStart = 'LS-' + edge.start;
|
||||
const linkNameEnd = 'LE-' + edge.end;
|
||||
|
||||
const edgeData = {};
|
||||
|
||||
|
@ -287,7 +287,17 @@ const getStartDate = function (prevTime, dateFormat, str) {
|
||||
log.debug('Invalid date:' + str);
|
||||
log.debug('With date format:' + dateFormat.trim());
|
||||
const d = new Date(str);
|
||||
if (d === undefined || isNaN(d.getTime())) {
|
||||
if (
|
||||
d === undefined ||
|
||||
isNaN(d.getTime()) ||
|
||||
// WebKit browsers can mis-parse invalid dates to be ridiculously
|
||||
// huge numbers, e.g. new Date('202304') gets parsed as January 1, 202304.
|
||||
// This can cause virtually infinite loops while rendering, so for the
|
||||
// purposes of Gantt charts we'll just treat any date beyond 10,000 AD/BC as
|
||||
// invalid.
|
||||
d.getFullYear() < -10000 ||
|
||||
d.getFullYear() > 10000
|
||||
) {
|
||||
throw new Error('Invalid date:' + str);
|
||||
}
|
||||
return d;
|
||||
|
@ -432,4 +432,10 @@ describe('when using the ganttDb', function () {
|
||||
ganttDb.setTodayMarker(expected);
|
||||
expect(ganttDb.getTodayMarker()).toEqual(expected);
|
||||
});
|
||||
|
||||
it('should reject dates with ridiculous years', function () {
|
||||
ganttDb.setDateFormat('YYYYMMDD');
|
||||
ganttDb.addTask('test1', 'id1,202304,1d');
|
||||
expect(() => ganttDb.getTasks()).toThrowError('Invalid date:202304');
|
||||
});
|
||||
});
|
||||
|
@ -70,6 +70,7 @@ const defaultBkg = function (elem, node, section) {
|
||||
.attr('x2', node.width)
|
||||
.attr('y2', node.height);
|
||||
};
|
||||
|
||||
const rectBkg = function (elem, node) {
|
||||
elem
|
||||
.append('rect')
|
||||
@ -78,6 +79,7 @@ const rectBkg = function (elem, node) {
|
||||
.attr('height', node.height)
|
||||
.attr('width', node.width);
|
||||
};
|
||||
|
||||
const cloudBkg = function (elem, node) {
|
||||
const w = node.width;
|
||||
const h = node.height;
|
||||
@ -108,6 +110,7 @@ const cloudBkg = function (elem, node) {
|
||||
H0 V0 Z`
|
||||
);
|
||||
};
|
||||
|
||||
const bangBkg = function (elem, node) {
|
||||
const w = node.width;
|
||||
const h = node.height;
|
||||
@ -139,6 +142,7 @@ const bangBkg = function (elem, node) {
|
||||
H0 V0 Z`
|
||||
);
|
||||
};
|
||||
|
||||
const circleBkg = function (elem, node) {
|
||||
elem
|
||||
.append('circle')
|
||||
|
@ -47,6 +47,7 @@
|
||||
"alt" { this.begin('LINE'); return 'alt'; }
|
||||
"else" { this.begin('LINE'); return 'else'; }
|
||||
"par" { this.begin('LINE'); return 'par'; }
|
||||
"par_over" { this.begin('LINE'); return 'par_over'; }
|
||||
"and" { this.begin('LINE'); return 'and'; }
|
||||
"critical" { this.begin('LINE'); return 'critical'; }
|
||||
"option" { this.begin('LINE'); return 'option'; }
|
||||
@ -190,6 +191,14 @@ statement
|
||||
// End
|
||||
$3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
|
||||
$$=$3;}
|
||||
| par_over restOfLine par_sections end
|
||||
{
|
||||
// Parallel (overlapped) start
|
||||
$3.unshift({type: 'parStart', parText:yy.parseMessage($2), signalType: yy.LINETYPE.PAR_OVER_START});
|
||||
// Content in par is already in $3
|
||||
// End
|
||||
$3.push({type: 'parEnd', signalType: yy.LINETYPE.PAR_END});
|
||||
$$=$3;}
|
||||
| critical restOfLine option_sections end
|
||||
{
|
||||
// critical start
|
||||
|
@ -286,6 +286,7 @@ export const LINETYPE = {
|
||||
CRITICAL_END: 29,
|
||||
BREAK_START: 30,
|
||||
BREAK_END: 31,
|
||||
PAR_OVER_START: 32,
|
||||
};
|
||||
|
||||
export const ARROWTYPE = {
|
||||
|
@ -1126,6 +1126,29 @@ end`;
|
||||
expect(messages[1].from).toBe('Alice');
|
||||
expect(messages[2].from).toBe('Bob');
|
||||
});
|
||||
it('it should handle par_over statements', async () => {
|
||||
const str = `
|
||||
sequenceDiagram
|
||||
par_over Parallel overlap
|
||||
Alice ->> Bob: Message
|
||||
Note left of Alice: Alice note
|
||||
Note right of Bob: Bob note
|
||||
end`;
|
||||
|
||||
await mermaidAPI.parse(str);
|
||||
const actors = diagram.db.getActors();
|
||||
|
||||
expect(actors.Alice.description).toBe('Alice');
|
||||
expect(actors.Bob.description).toBe('Bob');
|
||||
|
||||
const messages = diagram.db.getMessages();
|
||||
|
||||
expect(messages.length).toBe(5);
|
||||
expect(messages[0].message).toBe('Parallel overlap');
|
||||
expect(messages[1].from).toBe('Alice');
|
||||
expect(messages[2].from).toBe('Alice');
|
||||
expect(messages[3].from).toBe('Bob');
|
||||
});
|
||||
it('should handle special characters in signals', async () => {
|
||||
const str = 'sequenceDiagram\n' + 'Alice->Bob: -:<>,;# comment';
|
||||
|
||||
|
@ -3,6 +3,7 @@ import { select, selectAll } from 'd3';
|
||||
import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw.js';
|
||||
import { log } from '../../logger.js';
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import * as configApi from '../../config.js';
|
||||
import assignWithDepth from '../../assignWithDepth.js';
|
||||
import utils from '../../utils.js';
|
||||
@ -130,10 +131,10 @@ export const bounds = {
|
||||
this.activations.forEach(updateFn('activation'));
|
||||
},
|
||||
insert: function (startx, starty, stopx, stopy) {
|
||||
const _startx = Math.min(startx, stopx);
|
||||
const _stopx = Math.max(startx, stopx);
|
||||
const _starty = Math.min(starty, stopy);
|
||||
const _stopy = Math.max(starty, stopy);
|
||||
const _startx = common.getMin(startx, stopx);
|
||||
const _stopx = common.getMax(startx, stopx);
|
||||
const _starty = common.getMin(starty, stopy);
|
||||
const _stopy = common.getMax(starty, stopy);
|
||||
|
||||
this.updateVal(bounds.data, 'startx', _startx, Math.min);
|
||||
this.updateVal(bounds.data, 'starty', _starty, Math.min);
|
||||
@ -183,6 +184,11 @@ export const bounds = {
|
||||
endLoop: function () {
|
||||
return this.sequenceItems.pop();
|
||||
},
|
||||
isLoopOverlap: function () {
|
||||
return this.sequenceItems.length
|
||||
? this.sequenceItems[this.sequenceItems.length - 1].overlap
|
||||
: false;
|
||||
},
|
||||
addSectionToLoop: function (message) {
|
||||
const loop = this.sequenceItems.pop();
|
||||
loop.sections = loop.sections || [];
|
||||
@ -191,9 +197,19 @@ export const bounds = {
|
||||
loop.sectionTitles.push(message);
|
||||
this.sequenceItems.push(loop);
|
||||
},
|
||||
saveVerticalPos: function () {
|
||||
if (this.isLoopOverlap()) {
|
||||
this.savedVerticalPos = this.verticalPos;
|
||||
}
|
||||
},
|
||||
resetVerticalPos: function () {
|
||||
if (this.isLoopOverlap()) {
|
||||
this.verticalPos = this.savedVerticalPos;
|
||||
}
|
||||
},
|
||||
bumpVerticalPos: function (bump) {
|
||||
this.verticalPos = this.verticalPos + bump;
|
||||
this.data.stopy = this.verticalPos;
|
||||
this.data.stopy = common.getMax(this.data.stopy, this.verticalPos);
|
||||
},
|
||||
getVerticalPos: function () {
|
||||
return this.verticalPos;
|
||||
@ -225,7 +241,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
|
||||
bounds.bumpVerticalPos(conf.boxMargin);
|
||||
noteModel.height = conf.boxMargin;
|
||||
noteModel.starty = bounds.getVerticalPos();
|
||||
const rect = svgDraw.getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
rect.x = noteModel.startx;
|
||||
rect.y = noteModel.starty;
|
||||
rect.width = noteModel.width || conf.width;
|
||||
@ -233,7 +249,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
|
||||
|
||||
const g = elem.append('g');
|
||||
const rectElem = svgDraw.drawRect(g, rect);
|
||||
const textObj = svgDraw.getTextObj();
|
||||
const textObj = svgDrawCommon.getTextObj();
|
||||
textObj.x = noteModel.startx;
|
||||
textObj.y = noteModel.starty;
|
||||
textObj.width = rect.width;
|
||||
@ -316,7 +332,7 @@ function boundMessage(_diagram, msgModel): number {
|
||||
lineStartY = bounds.getVerticalPos() + totalOffset;
|
||||
}
|
||||
totalOffset += 30;
|
||||
const dx = Math.max(textWidth / 2, conf.width / 2);
|
||||
const dx = common.getMax(textWidth / 2, conf.width / 2);
|
||||
bounds.insert(
|
||||
startx - dx,
|
||||
bounds.getVerticalPos() - 10 + totalOffset,
|
||||
@ -347,7 +363,7 @@ function boundMessage(_diagram, msgModel): number {
|
||||
const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) {
|
||||
const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel;
|
||||
const textDims = utils.calculateTextDimensions(message, messageFont(conf));
|
||||
const textObj = svgDraw.getTextObj();
|
||||
const textObj = svgDrawCommon.getTextObj();
|
||||
textObj.x = startx;
|
||||
textObj.y = starty + 10;
|
||||
textObj.width = stopx - startx;
|
||||
@ -373,9 +389,9 @@ const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Di
|
||||
.append('path')
|
||||
.attr(
|
||||
'd',
|
||||
`M ${startx},${lineStartY} H ${startx + Math.max(conf.width / 2, textWidth / 2)} V ${
|
||||
lineStartY + 25
|
||||
} H ${startx}`
|
||||
`M ${startx},${lineStartY} H ${
|
||||
startx + common.getMax(conf.width / 2, textWidth / 2)
|
||||
} V ${lineStartY + 25} H ${startx}`
|
||||
);
|
||||
} else {
|
||||
line = diagram
|
||||
@ -509,7 +525,7 @@ export const drawActors = function (
|
||||
|
||||
// Add some rendering data to the object
|
||||
actor.width = actor.width || conf.width;
|
||||
actor.height = Math.max(actor.height || conf.height, conf.height);
|
||||
actor.height = common.getMax(actor.height || conf.height, conf.height);
|
||||
actor.margin = actor.margin || conf.actorMargin;
|
||||
|
||||
actor.x = prevWidth + prevMargin;
|
||||
@ -517,7 +533,7 @@ export const drawActors = function (
|
||||
|
||||
// Draw the box with the attached line
|
||||
const height = svgDraw.drawActor(diagram, actor, conf, isFooter);
|
||||
maxHeight = Math.max(maxHeight, height);
|
||||
maxHeight = common.getMax(maxHeight, height);
|
||||
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
|
||||
|
||||
prevWidth += actor.width + prevMargin;
|
||||
@ -589,10 +605,10 @@ const activationBounds = function (actor, actors) {
|
||||
const activations = actorActivations(actor);
|
||||
|
||||
const left = activations.reduce(function (acc, activation) {
|
||||
return Math.min(acc, activation.startx);
|
||||
return common.getMin(acc, activation.startx);
|
||||
}, actorObj.x + actorObj.width / 2);
|
||||
const right = activations.reduce(function (acc, activation) {
|
||||
return Math.max(acc, activation.stopx);
|
||||
return common.getMax(acc, activation.stopx);
|
||||
}, actorObj.x + actorObj.width / 2);
|
||||
return [left, right];
|
||||
};
|
||||
@ -609,7 +625,7 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop
|
||||
|
||||
// const lines = common.splitBreaks(msg.message).length;
|
||||
const textDims = utils.calculateTextDimensions(msg.message, textConf);
|
||||
const totalOffset = Math.max(textDims.height, conf.labelBoxHeight);
|
||||
const totalOffset = common.getMax(textDims.height, conf.labelBoxHeight);
|
||||
heightAdjust = postMargin + totalOffset;
|
||||
log.debug(`${totalOffset} - ${msg.message}`);
|
||||
}
|
||||
@ -709,6 +725,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
||||
|
||||
switch (msg.type) {
|
||||
case diagObj.db.LINETYPE.NOTE:
|
||||
bounds.resetVerticalPos();
|
||||
noteModel = msg.noteModel;
|
||||
drawNote(diagram, noteModel);
|
||||
break;
|
||||
@ -784,6 +801,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
||||
bounds.models.addLoop(loopModel);
|
||||
break;
|
||||
case diagObj.db.LINETYPE.PAR_START:
|
||||
case diagObj.db.LINETYPE.PAR_OVER_START:
|
||||
adjustLoopHeightForWrap(
|
||||
loopWidths,
|
||||
msg,
|
||||
@ -791,6 +809,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
||||
conf.boxMargin + conf.boxTextMargin,
|
||||
(message) => bounds.newLoop(message)
|
||||
);
|
||||
bounds.saveVerticalPos();
|
||||
break;
|
||||
case diagObj.db.LINETYPE.PAR_AND:
|
||||
adjustLoopHeightForWrap(
|
||||
@ -858,6 +877,7 @@ export const draw = function (_text: string, id: string, _version: string, diagO
|
||||
default:
|
||||
try {
|
||||
// lastMsg = msg
|
||||
bounds.resetVerticalPos();
|
||||
msgModel = msg.msgModel;
|
||||
msgModel.starty = bounds.getVerticalPos();
|
||||
msgModel.sequenceIndex = sequenceIndex;
|
||||
@ -1025,45 +1045,45 @@ function getMaxMessageWidthPerActor(
|
||||
* margin
|
||||
*/
|
||||
if (isMessage && msg.from === actor.nextActor) {
|
||||
maxMessageWidthPerActor[msg.to] = Math.max(
|
||||
maxMessageWidthPerActor[msg.to] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.to] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (isMessage && msg.from === actor.prevActor) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (isMessage && msg.from === msg.to) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth / 2
|
||||
);
|
||||
|
||||
maxMessageWidthPerActor[msg.to] = Math.max(
|
||||
maxMessageWidthPerActor[msg.to] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.to] || 0,
|
||||
messageWidth / 2
|
||||
);
|
||||
} else if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (msg.placement === diagObj.db.PLACEMENT.LEFTOF) {
|
||||
maxMessageWidthPerActor[actor.prevActor] = Math.max(
|
||||
maxMessageWidthPerActor[actor.prevActor] = common.getMax(
|
||||
maxMessageWidthPerActor[actor.prevActor] || 0,
|
||||
messageWidth
|
||||
);
|
||||
} else if (msg.placement === diagObj.db.PLACEMENT.OVER) {
|
||||
if (actor.prevActor) {
|
||||
maxMessageWidthPerActor[actor.prevActor] = Math.max(
|
||||
maxMessageWidthPerActor[actor.prevActor] = common.getMax(
|
||||
maxMessageWidthPerActor[actor.prevActor] || 0,
|
||||
messageWidth / 2
|
||||
);
|
||||
}
|
||||
|
||||
if (actor.nextActor) {
|
||||
maxMessageWidthPerActor[msg.from] = Math.max(
|
||||
maxMessageWidthPerActor[msg.from] = common.getMax(
|
||||
maxMessageWidthPerActor[msg.from] || 0,
|
||||
messageWidth / 2
|
||||
);
|
||||
@ -1119,10 +1139,10 @@ function calculateActorMargins(
|
||||
const actDims = utils.calculateTextDimensions(actor.description, actorFont(conf));
|
||||
actor.width = actor.wrap
|
||||
? conf.width
|
||||
: Math.max(conf.width, actDims.width + 2 * conf.wrapPadding);
|
||||
: common.getMax(conf.width, actDims.width + 2 * conf.wrapPadding);
|
||||
|
||||
actor.height = actor.wrap ? Math.max(actDims.height, conf.height) : conf.height;
|
||||
maxHeight = Math.max(maxHeight, actor.height);
|
||||
actor.height = actor.wrap ? common.getMax(actDims.height, conf.height) : conf.height;
|
||||
maxHeight = common.getMax(maxHeight, actor.height);
|
||||
});
|
||||
|
||||
for (const actorKey in actorToMessageWidth) {
|
||||
@ -1138,14 +1158,14 @@ function calculateActorMargins(
|
||||
if (!nextActor) {
|
||||
const messageWidth = actorToMessageWidth[actorKey];
|
||||
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2;
|
||||
actor.margin = Math.max(actorWidth, conf.actorMargin);
|
||||
actor.margin = common.getMax(actorWidth, conf.actorMargin);
|
||||
continue;
|
||||
}
|
||||
|
||||
const messageWidth = actorToMessageWidth[actorKey];
|
||||
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2 - nextActor.width / 2;
|
||||
|
||||
actor.margin = Math.max(actorWidth, conf.actorMargin);
|
||||
actor.margin = common.getMax(actorWidth, conf.actorMargin);
|
||||
}
|
||||
|
||||
let maxBoxHeight = 0;
|
||||
@ -1161,8 +1181,8 @@ function calculateActorMargins(
|
||||
}
|
||||
|
||||
const boxMsgDimensions = utils.calculateTextDimensions(box.name, textFont);
|
||||
maxBoxHeight = Math.max(boxMsgDimensions.height, maxBoxHeight);
|
||||
const minWidth = Math.max(totalWidth, boxMsgDimensions.width + 2 * conf.wrapPadding);
|
||||
maxBoxHeight = common.getMax(boxMsgDimensions.height, maxBoxHeight);
|
||||
const minWidth = common.getMax(totalWidth, boxMsgDimensions.width + 2 * conf.wrapPadding);
|
||||
box.margin = conf.boxTextMargin;
|
||||
if (totalWidth < minWidth) {
|
||||
const missing = (minWidth - totalWidth) / 2;
|
||||
@ -1171,7 +1191,7 @@ function calculateActorMargins(
|
||||
});
|
||||
boxes.forEach((box) => (box.textMaxHeight = maxBoxHeight));
|
||||
|
||||
return Math.max(maxHeight, conf.height);
|
||||
return common.getMax(maxHeight, conf.height);
|
||||
}
|
||||
|
||||
const buildNoteModel = function (msg, actors, diagObj) {
|
||||
@ -1186,7 +1206,7 @@ const buildNoteModel = function (msg, actors, diagObj) {
|
||||
const noteModel = {
|
||||
width: shouldWrap
|
||||
? conf.width
|
||||
: Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin),
|
||||
: common.getMax(conf.width, textDimensions.width + 2 * conf.noteMargin),
|
||||
height: 0,
|
||||
startx: actors[msg.from].x,
|
||||
stopx: 0,
|
||||
@ -1196,16 +1216,16 @@ const buildNoteModel = function (msg, actors, diagObj) {
|
||||
};
|
||||
if (msg.placement === diagObj.db.PLACEMENT.RIGHTOF) {
|
||||
noteModel.width = shouldWrap
|
||||
? Math.max(conf.width, textDimensions.width)
|
||||
: Math.max(
|
||||
? common.getMax(conf.width, textDimensions.width)
|
||||
: common.getMax(
|
||||
actors[msg.from].width / 2 + actors[msg.to].width / 2,
|
||||
textDimensions.width + 2 * conf.noteMargin
|
||||
);
|
||||
noteModel.startx = startx + (actors[msg.from].width + conf.actorMargin) / 2;
|
||||
} else if (msg.placement === diagObj.db.PLACEMENT.LEFTOF) {
|
||||
noteModel.width = shouldWrap
|
||||
? Math.max(conf.width, textDimensions.width + 2 * conf.noteMargin)
|
||||
: Math.max(
|
||||
? common.getMax(conf.width, textDimensions.width + 2 * conf.noteMargin)
|
||||
: common.getMax(
|
||||
actors[msg.from].width / 2 + actors[msg.to].width / 2,
|
||||
textDimensions.width + 2 * conf.noteMargin
|
||||
);
|
||||
@ -1213,13 +1233,21 @@ const buildNoteModel = function (msg, actors, diagObj) {
|
||||
} else if (msg.to === msg.from) {
|
||||
textDimensions = utils.calculateTextDimensions(
|
||||
shouldWrap
|
||||
? utils.wrapLabel(msg.message, Math.max(conf.width, actors[msg.from].width), noteFont(conf))
|
||||
? utils.wrapLabel(
|
||||
msg.message,
|
||||
common.getMax(conf.width, actors[msg.from].width),
|
||||
noteFont(conf)
|
||||
)
|
||||
: msg.message,
|
||||
noteFont(conf)
|
||||
);
|
||||
noteModel.width = shouldWrap
|
||||
? Math.max(conf.width, actors[msg.from].width)
|
||||
: Math.max(actors[msg.from].width, conf.width, textDimensions.width + 2 * conf.noteMargin);
|
||||
? common.getMax(conf.width, actors[msg.from].width)
|
||||
: common.getMax(
|
||||
actors[msg.from].width,
|
||||
conf.width,
|
||||
textDimensions.width + 2 * conf.noteMargin
|
||||
);
|
||||
noteModel.startx = startx + (actors[msg.from].width - noteModel.width) / 2;
|
||||
} else {
|
||||
noteModel.width =
|
||||
@ -1271,14 +1299,14 @@ const buildMessageModel = function (msg, actors, diagObj) {
|
||||
if (msg.wrap && msg.message) {
|
||||
msg.message = utils.wrapLabel(
|
||||
msg.message,
|
||||
Math.max(boundedWidth + 2 * conf.wrapPadding, conf.width),
|
||||
common.getMax(boundedWidth + 2 * conf.wrapPadding, conf.width),
|
||||
messageFont(conf)
|
||||
);
|
||||
}
|
||||
const msgDims = utils.calculateTextDimensions(msg.message, messageFont(conf));
|
||||
|
||||
return {
|
||||
width: Math.max(
|
||||
width: common.getMax(
|
||||
msg.wrap ? 0 : msgDims.width + 2 * conf.wrapPadding,
|
||||
boundedWidth + 2 * conf.wrapPadding,
|
||||
conf.width
|
||||
@ -1308,6 +1336,7 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
|
||||
case diagObj.db.LINETYPE.ALT_START:
|
||||
case diagObj.db.LINETYPE.OPT_START:
|
||||
case diagObj.db.LINETYPE.PAR_START:
|
||||
case diagObj.db.LINETYPE.PAR_OVER_START:
|
||||
case diagObj.db.LINETYPE.CRITICAL_START:
|
||||
case diagObj.db.LINETYPE.BREAK_START:
|
||||
stack.push({
|
||||
@ -1367,10 +1396,10 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
|
||||
msg.noteModel = noteModel;
|
||||
stack.forEach((stk) => {
|
||||
current = stk;
|
||||
current.from = Math.min(current.from, noteModel.startx);
|
||||
current.to = Math.max(current.to, noteModel.startx + noteModel.width);
|
||||
current.from = common.getMin(current.from, noteModel.startx);
|
||||
current.to = common.getMax(current.to, noteModel.startx + noteModel.width);
|
||||
current.width =
|
||||
Math.max(current.width, Math.abs(current.from - current.to)) - conf.labelBoxWidth;
|
||||
common.getMax(current.width, Math.abs(current.from - current.to)) - conf.labelBoxWidth;
|
||||
});
|
||||
} else {
|
||||
msgModel = buildMessageModel(msg, actors, diagObj);
|
||||
@ -1381,18 +1410,23 @@ const calculateLoopBounds = function (messages, actors, _maxWidthPerActor, diagO
|
||||
if (msgModel.startx === msgModel.stopx) {
|
||||
const from = actors[msg.from];
|
||||
const to = actors[msg.to];
|
||||
current.from = Math.min(
|
||||
current.from = common.getMin(
|
||||
from.x - msgModel.width / 2,
|
||||
from.x - from.width / 2,
|
||||
current.from
|
||||
);
|
||||
current.to = Math.max(to.x + msgModel.width / 2, to.x + from.width / 2, current.to);
|
||||
current.to = common.getMax(
|
||||
to.x + msgModel.width / 2,
|
||||
to.x + from.width / 2,
|
||||
current.to
|
||||
);
|
||||
current.width =
|
||||
Math.max(current.width, Math.abs(current.to - current.from)) - conf.labelBoxWidth;
|
||||
common.getMax(current.width, Math.abs(current.to - current.from)) -
|
||||
conf.labelBoxWidth;
|
||||
} else {
|
||||
current.from = Math.min(msgModel.startx, current.from);
|
||||
current.to = Math.max(msgModel.stopx, current.to);
|
||||
current.width = Math.max(current.width, msgModel.width) - conf.labelBoxWidth;
|
||||
current.from = common.getMin(msgModel.startx, current.from);
|
||||
current.to = common.getMax(msgModel.stopx, current.to);
|
||||
current.width = common.getMax(current.width, msgModel.width) - conf.labelBoxWidth;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
@ -1,33 +1,13 @@
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import { addFunction } from '../../interactionDb.js';
|
||||
import { parseFontSize } from '../../utils.js';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.class !== undefined) {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
return svgDrawCommon.drawRect(elem, rectData);
|
||||
};
|
||||
|
||||
// const sanitizeUrl = function (s) {
|
||||
// return s
|
||||
// .replace(/&/g, '&')
|
||||
// .replace(/</g, '<')
|
||||
// .replace(/javascript:/g, '');
|
||||
// };
|
||||
|
||||
const addPopupInteraction = (id, actorCnt) => {
|
||||
addFunction(() => {
|
||||
const arr = document.querySelectorAll(id);
|
||||
@ -43,6 +23,7 @@ const addPopupInteraction = (id, actorCnt) => {
|
||||
});
|
||||
});
|
||||
};
|
||||
|
||||
export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) {
|
||||
if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) {
|
||||
return { height: 0, width: 0 };
|
||||
@ -107,22 +88,6 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe
|
||||
return { height: rectData.height + linkY, width: menuWidth };
|
||||
};
|
||||
|
||||
export const drawImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('image');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
var sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', sanitizedLink);
|
||||
};
|
||||
|
||||
export const drawEmbeddedImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('use');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
var sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', '#' + sanitizedLink);
|
||||
};
|
||||
|
||||
export const popupMenu = function (popid) {
|
||||
return (
|
||||
"var pu = document.getElementById('" +
|
||||
@ -152,9 +117,10 @@ const popupMenuDownFunc = function (popupId) {
|
||||
pu.style.display = 'none';
|
||||
}
|
||||
};
|
||||
|
||||
export const drawText = function (elem, textData) {
|
||||
let prevTextHeight = 0,
|
||||
textHeight = 0;
|
||||
let prevTextHeight = 0;
|
||||
let textHeight = 0;
|
||||
const lines = textData.text.split(common.lineBreakRegex);
|
||||
|
||||
const [_textFontSize, _textFontSizePx] = parseFontSize(textData.fontSize);
|
||||
@ -188,6 +154,7 @@ export const drawText = function (elem, textData) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (
|
||||
textData.anchor !== undefined &&
|
||||
textData.textMargin !== undefined &&
|
||||
@ -217,6 +184,7 @@ export const drawText = function (elem, textData) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
for (let [i, line] of lines.entries()) {
|
||||
if (
|
||||
textData.textMargin !== undefined &&
|
||||
@ -371,7 +339,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
||||
}
|
||||
}
|
||||
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
var cssclass = 'actor';
|
||||
if (actor.properties != null && actor.properties['class']) {
|
||||
cssclass = actor.properties['class'];
|
||||
@ -391,9 +359,9 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
|
||||
if (actor.properties != null && actor.properties['icon']) {
|
||||
const iconSrc = actor.properties['icon'].trim();
|
||||
if (iconSrc.charAt(0) === '@') {
|
||||
drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
|
||||
svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
|
||||
} else {
|
||||
drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
|
||||
svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
|
||||
}
|
||||
}
|
||||
|
||||
@ -438,7 +406,7 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
||||
const actElem = elem.append('g');
|
||||
actElem.attr('class', 'actor-man');
|
||||
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
rect.x = actor.x;
|
||||
rect.y = actor.y;
|
||||
rect.fill = '#eaeaea';
|
||||
@ -447,7 +415,6 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
|
||||
rect.class = 'actor';
|
||||
rect.rx = 3;
|
||||
rect.ry = 3;
|
||||
// drawRect(actElem, rect);
|
||||
|
||||
actElem
|
||||
.append('line')
|
||||
@ -532,6 +499,7 @@ export const drawBox = function (elem, box, conf) {
|
||||
export const anchorElement = function (elem) {
|
||||
return elem.append('g');
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws an activation in the diagram
|
||||
*
|
||||
@ -542,7 +510,7 @@ export const anchorElement = function (elem) {
|
||||
* @param {any} actorActivations - Number of activations on the actor.
|
||||
*/
|
||||
export const drawActivation = function (elem, bounds, verticalPos, conf, actorActivations) {
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
const g = bounds.anchored;
|
||||
rect.x = bounds.startx;
|
||||
rect.y = bounds.starty;
|
||||
@ -594,7 +562,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
|
||||
});
|
||||
}
|
||||
|
||||
let txt = getTextObj();
|
||||
let txt = svgDrawCommon.getTextObj();
|
||||
txt.text = labelText;
|
||||
txt.x = loopModel.startx;
|
||||
txt.y = loopModel.starty;
|
||||
@ -610,7 +578,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
|
||||
txt.class = 'labelText';
|
||||
|
||||
drawLabel(g, txt);
|
||||
txt = getTextObj();
|
||||
txt = svgDrawCommon.getTextObj();
|
||||
txt.text = loopModel.title;
|
||||
txt.x = loopModel.startx + labelBoxWidth / 2 + (loopModel.stopx - loopModel.startx) / 2;
|
||||
txt.y = loopModel.starty + boxMargin + boxTextMargin;
|
||||
@ -661,16 +629,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
|
||||
* @param {any} bounds Shape of the rectangle
|
||||
*/
|
||||
export const drawBackgroundRect = function (elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
width: bounds.stopx - bounds.startx,
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
stroke: bounds.stroke,
|
||||
class: 'rect',
|
||||
});
|
||||
rectElem.lower();
|
||||
svgDrawCommon.drawBackgroundRect(elem, bounds);
|
||||
};
|
||||
|
||||
export const insertDatabaseIcon = function (elem) {
|
||||
@ -737,6 +696,7 @@ export const insertArrowHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup arrow head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@ -755,6 +715,7 @@ export const insertArrowFilledHead = function (elem) {
|
||||
.append('path')
|
||||
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup node number. The result is appended to the svg.
|
||||
*
|
||||
@ -776,6 +737,7 @@ export const insertSequenceNumber = function (elem) {
|
||||
.attr('r', 6);
|
||||
// .style("fill", '#f00');
|
||||
};
|
||||
|
||||
/**
|
||||
* Setup cross head and define the marker. The result is appended to the svg.
|
||||
*
|
||||
@ -802,37 +764,6 @@ export const insertArrowCrossHead = function (elem) {
|
||||
// this is actual shape for arrowhead
|
||||
};
|
||||
|
||||
export const getTextObj = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: undefined,
|
||||
anchor: undefined,
|
||||
style: '#666',
|
||||
width: undefined,
|
||||
height: undefined,
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
tspan: true,
|
||||
valign: undefined,
|
||||
};
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: '#EDF2AE',
|
||||
stroke: '#666',
|
||||
width: 100,
|
||||
anchor: 'start',
|
||||
height: 100,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
const _drawTextCandidateFunc = (function () {
|
||||
/**
|
||||
* @param {any} content
|
||||
@ -1062,8 +993,6 @@ export default {
|
||||
drawActor,
|
||||
drawBox,
|
||||
drawPopup,
|
||||
drawImage,
|
||||
drawEmbeddedImage,
|
||||
anchorElement,
|
||||
drawActivation,
|
||||
drawLoop,
|
||||
@ -1075,8 +1004,6 @@ export default {
|
||||
insertDatabaseIcon,
|
||||
insertComputerIcon,
|
||||
insertClockIcon,
|
||||
getTextObj,
|
||||
getNoteRect,
|
||||
popupMenu,
|
||||
popdownMenu,
|
||||
fixLifeLineHeights,
|
||||
|
@ -174,16 +174,4 @@ describe('svgDraw', function () {
|
||||
expect(rect.lower).toHaveBeenCalled();
|
||||
});
|
||||
});
|
||||
describe('sanitizeUrl', function () {
|
||||
it('should sanitize malicious urls', function () {
|
||||
const maliciousStr = 'javascript:script:alert(1)';
|
||||
const result = svgDraw.sanitizeUrl(maliciousStr);
|
||||
expect(result).not.toContain('javascript:alert(1)');
|
||||
});
|
||||
it('should not sanitize non dangerous urls', function () {
|
||||
const maliciousStr = 'javajavascript:script:alert(1)';
|
||||
const result = svgDraw.sanitizeUrl(maliciousStr);
|
||||
expect(result).not.toContain('javascript:alert(1)');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -1,21 +1,8 @@
|
||||
import { arc as d3arc } from 'd3';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.class !== undefined) {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
return svgDrawCommon.drawRect(elem, rectData);
|
||||
};
|
||||
|
||||
export const drawFace = function (element, faceData) {
|
||||
@ -128,25 +115,7 @@ export const drawCircle = function (element, circleData) {
|
||||
};
|
||||
|
||||
export const drawText = function (elem, textData) {
|
||||
// Remove and ignore br:s
|
||||
const nText = textData.text.replace(/<br\s*\/?>/gi, ' ');
|
||||
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.attr('class', 'legend');
|
||||
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
|
||||
if (textData.class !== undefined) {
|
||||
textElem.attr('class', textData.class);
|
||||
}
|
||||
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x + textData.textMargin * 2);
|
||||
span.text(nText);
|
||||
|
||||
return textElem;
|
||||
return svgDrawCommon.drawText(elem, textData);
|
||||
};
|
||||
|
||||
export const drawLabel = function (elem, txtObject) {
|
||||
@ -192,7 +161,7 @@ export const drawLabel = function (elem, txtObject) {
|
||||
export const drawSection = function (elem, section, conf) {
|
||||
const g = elem.append('g');
|
||||
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
rect.x = section.x;
|
||||
rect.y = section.y;
|
||||
rect.fill = section.fill;
|
||||
@ -249,7 +218,7 @@ export const drawTask = function (elem, task, conf) {
|
||||
score: task.score,
|
||||
});
|
||||
|
||||
const rect = getNoteRect();
|
||||
const rect = svgDrawCommon.getNoteRect();
|
||||
rect.x = task.x;
|
||||
rect.y = task.y;
|
||||
rect.fill = task.fill;
|
||||
@ -298,41 +267,7 @@ export const drawTask = function (elem, task, conf) {
|
||||
* @param {any} bounds The bounds of the drawing
|
||||
*/
|
||||
export const drawBackgroundRect = function (elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
width: bounds.stopx - bounds.startx,
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
class: 'rect',
|
||||
});
|
||||
rectElem.lower();
|
||||
};
|
||||
|
||||
export const getTextObj = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
fill: undefined,
|
||||
'text-anchor': 'start',
|
||||
width: 100,
|
||||
height: 100,
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
anchor: 'start',
|
||||
height: 100,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
svgDrawCommon.drawBackgroundRect(elem, bounds);
|
||||
};
|
||||
|
||||
const _drawTextCandidateFunc = (function () {
|
||||
@ -475,7 +410,5 @@ export default {
|
||||
drawLabel,
|
||||
drawTask,
|
||||
drawBackgroundRect,
|
||||
getTextObj,
|
||||
getNoteRect,
|
||||
initGraphics,
|
||||
};
|
||||
|
@ -362,9 +362,15 @@ const transformHtml = (filename: string) => {
|
||||
};
|
||||
|
||||
const getGlobs = (globs: string[]): string[] => {
|
||||
globs.push('!**/dist', '!**/redirect.spec.ts', '!**/landing');
|
||||
globs.push(
|
||||
'!**/dist',
|
||||
'!**/redirect.spec.ts',
|
||||
'!**/landing',
|
||||
'!**/node_modules',
|
||||
'!**/user-avatars'
|
||||
);
|
||||
if (!vitepress) {
|
||||
globs.push('!**/.vitepress', '!**/vite.config.ts', '!src/docs/index.md');
|
||||
globs.push('!**/.vitepress', '!**/vite.config.ts', '!src/docs/index.md', '!**/package.json');
|
||||
}
|
||||
return globs;
|
||||
};
|
||||
|
@ -0,0 +1,27 @@
|
||||
<script setup lang="ts">
|
||||
import { contributors } from '../contributors';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div flex="~ wrap gap2" justify-center>
|
||||
<a
|
||||
v-for="{ name, avatar } of contributors"
|
||||
:key="name"
|
||||
:href="`https://github.com/${name}`"
|
||||
m-0
|
||||
rel="noopener noreferrer"
|
||||
:aria-label="`${name} on GitHub`"
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
:src="avatar"
|
||||
width="50"
|
||||
height="50"
|
||||
rounded-full
|
||||
h-12
|
||||
w-12
|
||||
:alt="`${name}'s avatar`"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
</template>
|
26
packages/mermaid/src/docs/.vitepress/components/HomePage.vue
Normal file
26
packages/mermaid/src/docs/.vitepress/components/HomePage.vue
Normal file
@ -0,0 +1,26 @@
|
||||
<script setup lang="ts">
|
||||
import { VPTeamMembers } from 'vitepress/theme';
|
||||
import { teamMembers } from '../contributors';
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div class="content">
|
||||
<div class="content-container">
|
||||
<main class="main">
|
||||
<div class="vp-doc" flex flex-col items-center mt-10>
|
||||
<h2 id="meet-the-team" op50 font-normal p="t-10 b-2">Meet The Team</h2>
|
||||
<div w-full p-10>
|
||||
<VPTeamMembers size="small" :members="teamMembers" />
|
||||
</div>
|
||||
<h2 id="the-team" op50 font-normal pt-5 pb-2>Contributors</h2>
|
||||
<p text-lg max-w-200 text-center leading-7>
|
||||
<Contributors />
|
||||
<br />
|
||||
<a href="https://chat.vitest.dev" rel="noopener noreferrer">Join the community</a> and
|
||||
get involved!
|
||||
</p>
|
||||
</div>
|
||||
</main>
|
||||
</div>
|
||||
</div>
|
||||
</template>
|
148
packages/mermaid/src/docs/.vitepress/contributors.ts
Normal file
148
packages/mermaid/src/docs/.vitepress/contributors.ts
Normal file
@ -0,0 +1,148 @@
|
||||
import contributorUsernamesJson from './contributor-names.json';
|
||||
|
||||
export interface Contributor {
|
||||
name: string;
|
||||
avatar: string;
|
||||
}
|
||||
|
||||
export interface SocialEntry {
|
||||
icon: string | { svg: string };
|
||||
link: string;
|
||||
}
|
||||
|
||||
export interface CoreTeam {
|
||||
name: string;
|
||||
// required to download avatars from GitHub
|
||||
github: string;
|
||||
avatar?: string;
|
||||
twitter?: string;
|
||||
mastodon?: string;
|
||||
sponsor?: string;
|
||||
website?: string;
|
||||
linkedIn?: string;
|
||||
title?: string;
|
||||
org?: string;
|
||||
desc?: string;
|
||||
links?: SocialEntry[];
|
||||
}
|
||||
|
||||
const contributorUsernames: string[] = contributorUsernamesJson;
|
||||
|
||||
export const contributors = contributorUsernames.map((username) => {
|
||||
return { username, avatar: `/user-avatars/${username}.png` };
|
||||
});
|
||||
|
||||
const websiteSVG = {
|
||||
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>',
|
||||
};
|
||||
|
||||
const createLinks = (tm: CoreTeam): CoreTeam => {
|
||||
tm.avatar = `/user-avatars/${tm.github}.png`;
|
||||
tm.links = [{ icon: 'github', link: `https://github.com/${tm.github}` }];
|
||||
if (tm.mastodon) {
|
||||
tm.links.push({ icon: 'mastodon', link: tm.mastodon });
|
||||
}
|
||||
if (tm.twitter) {
|
||||
tm.links.push({ icon: 'twitter', link: `https://twitter.com/${tm.twitter}` });
|
||||
}
|
||||
if (tm.website) {
|
||||
tm.links.push({ icon: websiteSVG, link: tm.website });
|
||||
}
|
||||
if (tm.linkedIn) {
|
||||
tm.links.push({ icon: 'linkedin', link: `https://www.linkedin.com/in/${tm.linkedIn}` });
|
||||
}
|
||||
return tm;
|
||||
};
|
||||
|
||||
const knut: CoreTeam = {
|
||||
github: 'knsv',
|
||||
name: 'Knut Sveidqvist',
|
||||
title: 'Creator',
|
||||
twitter: 'knutsveidqvist',
|
||||
sponsor: 'https://github.com/sponsors/knsv',
|
||||
};
|
||||
|
||||
const plainTeamMembers: CoreTeam[] = [
|
||||
{
|
||||
github: 'NeilCuzon',
|
||||
name: 'Neil Cuzon',
|
||||
title: 'Developer',
|
||||
},
|
||||
{
|
||||
github: 'tylerlong',
|
||||
name: 'Tyler Liu',
|
||||
title: 'Developer',
|
||||
},
|
||||
{
|
||||
github: 'sidharthv96',
|
||||
name: 'Sidharth Vinod',
|
||||
title: 'Developer',
|
||||
twitter: 'sidv42',
|
||||
mastodon: 'https://techhub.social/@sidv',
|
||||
sponsor: 'https://github.com/sponsors/sidharthv96',
|
||||
linkedIn: 'sidharth-vinod',
|
||||
website: 'https://sidharth.dev',
|
||||
},
|
||||
{
|
||||
github: 'ashishjain0512',
|
||||
name: 'Ashish Jain',
|
||||
title: 'Developer',
|
||||
},
|
||||
{
|
||||
github: 'mmorel-35',
|
||||
name: 'Matthieu Morel',
|
||||
title: 'Developer',
|
||||
linkedIn: 'matthieumorel35',
|
||||
},
|
||||
{
|
||||
github: 'aloisklink',
|
||||
name: 'Alois Klink',
|
||||
title: 'Developer',
|
||||
linkedIn: 'aloisklink',
|
||||
website: 'https://aloisklink.com',
|
||||
},
|
||||
{
|
||||
github: 'pbrolin47',
|
||||
name: 'Per Brolin',
|
||||
title: 'Developer',
|
||||
},
|
||||
{
|
||||
github: 'Yash-Singh1',
|
||||
name: 'Yash Singh',
|
||||
title: 'Developer',
|
||||
},
|
||||
{
|
||||
github: 'GDFaber',
|
||||
name: 'Marc Faber',
|
||||
title: 'Developer',
|
||||
linkedIn: 'marc-faber',
|
||||
},
|
||||
{
|
||||
github: 'MindaugasLaganeckas',
|
||||
name: 'Mindaugas Laganeckas',
|
||||
title: 'Developer',
|
||||
},
|
||||
{
|
||||
github: 'jgreywolf',
|
||||
name: 'Justin Greywolf',
|
||||
title: 'Developer',
|
||||
},
|
||||
{
|
||||
github: 'IOrlandoni',
|
||||
name: 'Nacho Orlandoni',
|
||||
title: 'Developer',
|
||||
},
|
||||
{
|
||||
github: 'huynhicode',
|
||||
name: 'Steph Huynh',
|
||||
title: 'Developer',
|
||||
},
|
||||
];
|
||||
|
||||
const teamMembers = plainTeamMembers.map((tm) => createLinks(tm));
|
||||
teamMembers.sort(
|
||||
(a, b) => contributorUsernames.indexOf(a.github) - contributorUsernames.indexOf(b.github)
|
||||
);
|
||||
teamMembers.unshift(createLinks(knut));
|
||||
|
||||
export { teamMembers };
|
@ -0,0 +1,33 @@
|
||||
import { mkdir, writeFile, readFile } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
|
||||
const pathContributors = new URL('../contributor-names.json', import.meta.url);
|
||||
const getAvatarPath = (name: string) =>
|
||||
new URL(`../../public/user-avatars/${name}.png`, import.meta.url);
|
||||
|
||||
let contributors: string[] = [];
|
||||
|
||||
async function download(url: string, fileName: URL) {
|
||||
if (existsSync(fileName)) {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('downloading', fileName);
|
||||
try {
|
||||
const image = await fetch(url);
|
||||
await writeFile(fileName, Buffer.from(await image.arrayBuffer()));
|
||||
} catch {}
|
||||
}
|
||||
|
||||
async function fetchAvatars() {
|
||||
await mkdir(fileURLToPath(new URL('..', getAvatarPath('none'))), { recursive: true });
|
||||
contributors = JSON.parse(await readFile(pathContributors, { encoding: 'utf-8' }));
|
||||
await Promise.allSettled(
|
||||
contributors.map((name) =>
|
||||
download(`https://github.com/${name}.png?size=100`, getAvatarPath(name))
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
fetchAvatars();
|
@ -0,0 +1,39 @@
|
||||
// Adapted from https://github.dev/vitest-dev/vitest/blob/991ff33ab717caee85ef6cbe1c16dc514186b4cc/scripts/update-contributors.ts#L6
|
||||
|
||||
import { writeFile } from 'node:fs/promises';
|
||||
|
||||
const pathContributors = new URL('../contributor-names.json', import.meta.url);
|
||||
|
||||
interface Contributor {
|
||||
login: string;
|
||||
}
|
||||
|
||||
async function fetchContributors() {
|
||||
const collaborators: string[] = [];
|
||||
let page = 1;
|
||||
let data: Contributor[] = [];
|
||||
do {
|
||||
const response = await fetch(
|
||||
`https://api.github.com/repos/mermaid-js/mermaid/contributors?per_page=100&page=${page}`,
|
||||
{
|
||||
method: 'GET',
|
||||
headers: {
|
||||
'content-type': 'application/json',
|
||||
},
|
||||
}
|
||||
);
|
||||
data = await response.json();
|
||||
console.log(response.status, response.statusText);
|
||||
console.log(data);
|
||||
collaborators.push(...data.map((i) => i.login));
|
||||
page++;
|
||||
} while (data.length === 100);
|
||||
return collaborators.filter((name) => !name.includes('[bot]'));
|
||||
}
|
||||
|
||||
async function generate() {
|
||||
const collaborators = await fetchContributors();
|
||||
await writeFile(pathContributors, JSON.stringify(collaborators, null, 2) + '\n', 'utf8');
|
||||
}
|
||||
|
||||
void generate();
|
77
packages/mermaid/src/docs/.vitepress/style/main.css
Normal file
77
packages/mermaid/src/docs/.vitepress/style/main.css
Normal file
@ -0,0 +1,77 @@
|
||||
.dark [img-light] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html:not(.dark) [img-dark] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
/* Overrides */
|
||||
|
||||
.VPSocialLink {
|
||||
transform: scale(0.9);
|
||||
}
|
||||
|
||||
.vp-doc th,
|
||||
.vp-doc td {
|
||||
padding: 6px 10px;
|
||||
border: 1px solid #8882;
|
||||
}
|
||||
|
||||
/* h3 breaks SEO => replaced with h2 with the same size */
|
||||
.home-content h2 {
|
||||
margin-top: 2rem;
|
||||
font-size: 1.35rem;
|
||||
border-bottom: none;
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
img.resizable-img {
|
||||
width: unset;
|
||||
height: unset;
|
||||
}
|
||||
|
||||
/* fix height ~ 2 lines of text: 3 or more cards per row */
|
||||
.VPTeamMembersItem.small .profile .data .affiliation {
|
||||
min-height: 3rem;
|
||||
}
|
||||
.VPTeamMembersItem.small .profile .data .desc {
|
||||
min-height: 3rem;
|
||||
}
|
||||
|
||||
/* fix height ~ 3 lines of text: 4 cards per row */
|
||||
@media (min-width: 1064px) and (max-width: 1143px) {
|
||||
.VPTeamMembersItem.small .profile .data .affiliation {
|
||||
min-height: 4rem;
|
||||
}
|
||||
.VPTeamMembersItem.small .profile .data .desc {
|
||||
min-height: 4rem;
|
||||
}
|
||||
}
|
||||
/* fix height ~ 3 lines of text: 3 cards per row */
|
||||
@media (min-width: 815px) and (max-width: 875px) {
|
||||
.VPTeamMembersItem.small .profile .data .affiliation {
|
||||
min-height: 4rem;
|
||||
}
|
||||
.VPTeamMembersItem.small .profile .data .desc {
|
||||
min-height: 4rem;
|
||||
}
|
||||
}
|
||||
/* fix height ~ 3 lines of text: 2 cards per row */
|
||||
@media (max-width: 612px) {
|
||||
.VPTeamMembersItem.small .profile .data .affiliation {
|
||||
min-height: 4rem;
|
||||
}
|
||||
.VPTeamMembersItem.small .profile .data .desc {
|
||||
min-height: 4rem;
|
||||
}
|
||||
}
|
||||
/* fix height: one card per row */
|
||||
@media (max-width: 568px) {
|
||||
.VPTeamMembersItem.small .profile .data .affiliation {
|
||||
min-height: unset;
|
||||
}
|
||||
.VPTeamMembersItem.small .profile .data .desc {
|
||||
min-height: unset;
|
||||
}
|
||||
}
|
@ -2,13 +2,28 @@ import DefaultTheme from 'vitepress/theme';
|
||||
import './custom.css';
|
||||
// @ts-ignore
|
||||
import Mermaid from './Mermaid.vue';
|
||||
// @ts-ignore
|
||||
import Contributors from '../components/Contributors.vue';
|
||||
// @ts-ignore
|
||||
import HomePage from '../components/HomePage.vue';
|
||||
import { getRedirect } from './redirect.js';
|
||||
|
||||
import { h } from 'vue';
|
||||
import Theme from 'vitepress/theme';
|
||||
import '../style/main.css';
|
||||
import 'uno.css';
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
Layout() {
|
||||
return h(Theme.Layout, null, {
|
||||
'home-features-after': () => h(HomePage),
|
||||
});
|
||||
},
|
||||
enhanceApp({ app, router }) {
|
||||
// register global components
|
||||
app.component('Mermaid', Mermaid);
|
||||
app.component('Contributors', Contributors);
|
||||
router.onBeforeRouteChange = (to) => {
|
||||
try {
|
||||
const newPath = getRedirect(to);
|
||||
@ -20,4 +35,4 @@ export default {
|
||||
} catch (e) {}
|
||||
};
|
||||
},
|
||||
} as typeof DefaultTheme;
|
||||
};
|
||||
|
@ -40,7 +40,7 @@ It is also possible to override site-wide theme settings locally, for a specific
|
||||
|
||||
**Following is an example:**
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
%%{init: {'theme':'base'}}%%
|
||||
graph TD
|
||||
a --> b
|
||||
@ -56,7 +56,7 @@ The easiest way to make a custom theme is to start with the base theme, and just
|
||||
|
||||
Here is an example of overriding `primaryColor` and giving everything a different look, using `%%init%%`.
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%%
|
||||
graph TD
|
||||
A[Christmas] -->|Get money| B(Go shopping)
|
||||
|
@ -75,7 +75,7 @@ When deployed within code, init is called before the graph/diagram description.
|
||||
|
||||
**for example**:
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
%%{init: {"theme": "default", "logLevel": 1 }}%%
|
||||
graph LR
|
||||
a-->b
|
||||
|
@ -84,7 +84,7 @@ Here the directive declaration will set the `logLevel` to `debug` and the `theme
|
||||
|
||||
Note: You can use 'init' or 'initialize' as both acceptable as init directives. Also note that `%%init%%` and `%%initialize%%` directives will be grouped together after they are parsed. This means:
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%%
|
||||
%%{initialize: { 'logLevel': 'fatal', "theme":'dark', 'startOnLoad': true } }%%
|
||||
...
|
||||
|
@ -345,10 +345,10 @@ mermaid.parseError = function (err, hash) {
|
||||
displayErrorInGui(err);
|
||||
};
|
||||
|
||||
const textFieldUpdated = function () {
|
||||
const textFieldUpdated = async function () {
|
||||
const textStr = getTextFromFormField('code');
|
||||
|
||||
if (mermaid.parse(textStr)) {
|
||||
if (await mermaid.parse(textStr)) {
|
||||
reRender(textStr);
|
||||
}
|
||||
};
|
||||
|
@ -141,8 +141,12 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
|
||||
- [Sphinx](https://www.sphinx-doc.org/en/master/)
|
||||
- [sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
|
||||
- [remark.js](https://remark.js.org/)
|
||||
- [remark-mermaid](https://github.com/temando/remark-mermaid)
|
||||
- [remark](https://remark.js.org/)
|
||||
- [remark-mermaidjs](https://github.com/remcohaszing/remark-mermaidjs)
|
||||
- [rehype](https://github.com/rehypejs/rehype)
|
||||
- [rehype-mermaidjs](https://github.com/remcohaszing/rehype-mermaidjs)
|
||||
- [Gatsby](https://www.gatsbyjs.com/)
|
||||
- [gatsby-remark-mermaid](https://github.com/remcohaszing/gatsby-remark-mermaid)
|
||||
- [jSDoc](https://jsdoc.app/)
|
||||
- [jsdoc-mermaid](https://github.com/Jellyvision/jsdoc-mermaid)
|
||||
- [MkDocs](https://www.mkdocs.org)
|
||||
@ -183,6 +187,7 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [bisheng-plugin-mermaid](https://github.com/yct21/bisheng-plugin-mermaid)
|
||||
- [Reveal CK](https://github.com/jedcn/reveal-ck)
|
||||
- [reveal-ck-mermaid-plugin](https://github.com/tmtm/reveal-ck-mermaid-plugin)
|
||||
- [mermaid-isomorphic](https://github.com/remcohaszing/mermaid-isomorphic)
|
||||
- [mermaid-server: Generate diagrams using a HTTP request](https://github.com/TomWright/mermaid-server)
|
||||
- [ExDoc](https://github.com/elixir-lang/ex_doc)
|
||||
- [Rendering Mermaid graphs](https://github.com/elixir-lang/ex_doc#rendering-mermaid-graphs)
|
||||
|
@ -1,5 +1,6 @@
|
||||
---
|
||||
layout: home
|
||||
sidebar: false
|
||||
|
||||
title: Mermaid
|
||||
titleTemplate: Diagramming and charting tool
|
||||
@ -33,162 +34,3 @@ features:
|
||||
details: Mermaid Chart is a major supporter of the Mermaid project.
|
||||
link: https://www.mermaidchart.com/
|
||||
---
|
||||
|
||||
<script setup>
|
||||
import { VPTeamMembers } from 'vitepress/theme'
|
||||
|
||||
const websiteSVG = {
|
||||
svg: '<svg xmlns="http://www.w3.org/2000/svg" width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" class="feather feather-globe"><circle cx="12" cy="12" r="10"></circle><line x1="2" y1="12" x2="22" y2="12"></line><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"></path></svg>'
|
||||
}
|
||||
|
||||
const members = [
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/5837277?v=4",
|
||||
name: "Knut Sveidqvist",
|
||||
title: "Creator",
|
||||
links: [{ icon: "github", link: "https://github.com/knsv" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/58763315?v=4",
|
||||
name: "Neil Cuzon",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/NeilCuzon" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/733544?v=4",
|
||||
name: "Tyler Liu",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/tylerlong" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/10703445?v=4",
|
||||
name: "Sidharth Vinod",
|
||||
title: "Developer",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/sidharthv96" },
|
||||
{ icon: websiteSVG, link: "https://sidharth.dev" },
|
||||
{ icon: "linkedin", link: "https://www.linkedin.com/in/sidharth-vinod/" },
|
||||
],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/16836093?v=4",
|
||||
name: "Ashish Jain",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/ashishjain0512" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/6032561?v=4",
|
||||
name: "Matthieu Morel",
|
||||
title: "Developer",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/mmorel-35" },
|
||||
{
|
||||
icon: "linkedin",
|
||||
link: "https://www.linkedin.com/in/matthieumorel35/",
|
||||
},
|
||||
],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/6552521?v=4",
|
||||
name: "Christian Klemm",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/klemmchr" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/19716675?v=4",
|
||||
name: "Alois Klink",
|
||||
title: "Developer",
|
||||
links: [
|
||||
{ icon: "github", link: "https://github.com/aloisklink" },
|
||||
{ icon: websiteSVG, link: "https://aloisklink.com" },
|
||||
{ icon: "linkedin", link: "https://www.linkedin.com/in/aloisklink/" },
|
||||
],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/114684273?v=4",
|
||||
name: "Per Brolin",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/pbrolin47" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/53054099?v=4",
|
||||
name: "Yash Singh",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/Yash-Singh1" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/1912783?v=4",
|
||||
name: "Marc Faber",
|
||||
title: "Developer",
|
||||
links: [
|
||||
{ icon: "github", link: "https://gdfaber.github.io/" },
|
||||
{ icon: "linkedin", link: "https://www.linkedin.com/in/marc-faber/" },
|
||||
],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/12032557?v=4",
|
||||
name: "Mindaugas Laganeckas",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/MindaugasLaganeckas" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/300077?v=4",
|
||||
name: "Justin Greywolf",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/jgreywolf" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/1564825?v=4",
|
||||
name: "Nacho Orlandoni",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/IOrlandoni" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/19526120?v=4",
|
||||
name: "Adrian Hall",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/spopida" }],
|
||||
},
|
||||
{
|
||||
avatar: "https://avatars.githubusercontent.com/u/35910788?v=4",
|
||||
name: "Steph Huynh",
|
||||
title: "Developer",
|
||||
links: [{ icon: "github", link: "https://github.com/huynhicode" }],
|
||||
},
|
||||
];
|
||||
|
||||
</script>
|
||||
|
||||
<div class="vp-doc" >
|
||||
<h2 id="meet-the-team"> Meet The Team </h2>
|
||||
<VPTeamMembers size="small" :members="members" />
|
||||
</div>
|
||||
|
||||
<style>
|
||||
.image-container .image-src {
|
||||
margin: 1rem auto;
|
||||
max-width: 100%;
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.dark .image-src{
|
||||
filter: invert(1) hue-rotate(217deg) contrast(0.72);
|
||||
max-width: 100%;
|
||||
}
|
||||
|
||||
.vp-doc {
|
||||
align-items: center;
|
||||
flex-direction: column;
|
||||
display: flex;
|
||||
margin: 1.5rem;
|
||||
}
|
||||
|
||||
.vp-doc h2 {
|
||||
margin: 48px 0 16px;
|
||||
border-top: 1px solid var(--vp-c-divider-light);
|
||||
padding-top: 24px;
|
||||
letter-spacing: -.02em;
|
||||
line-height: 32px;
|
||||
font-size: 24px;
|
||||
}
|
||||
</style>
|
||||
|
36
packages/mermaid/src/docs/package.json
Normal file
36
packages/mermaid/src/docs/package.json
Normal file
@ -0,0 +1,36 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"private": true,
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vitepress --port 3333 --open",
|
||||
"build": "pnpm prefetch && vitepress build",
|
||||
"build-no-prefetch": "vitepress build",
|
||||
"serve": "vitepress serve",
|
||||
"preview-https": "pnpm build && serve .vitepress/dist",
|
||||
"preview-https-no-prefetch": "pnpm build-no-prefetch && serve .vitepress/dist",
|
||||
"prefetch": "pnpm fetch-contributors && pnpm fetch-avatars",
|
||||
"fetch-avatars": "ts-node-esm .vitepress/scripts/fetch-avatars.ts",
|
||||
"fetch-contributors": "ts-node-esm .vitepress/scripts/fetch-contributors.ts"
|
||||
},
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^10.1.0",
|
||||
"jiti": "^1.18.2",
|
||||
"vue": "^3.2.47"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/carbon": "^1.1.16",
|
||||
"@unocss/reset": "^0.51.8",
|
||||
"@vite-pwa/vitepress": "^0.0.5",
|
||||
"@vitejs/plugin-vue": "^4.2.1",
|
||||
"fast-glob": "^3.2.12",
|
||||
"https-localhost": "^4.7.1",
|
||||
"pathe": "^1.1.0",
|
||||
"unocss": "^0.51.8",
|
||||
"unplugin-vue-components": "^0.24.1",
|
||||
"vite": "^4.3.3",
|
||||
"vite-plugin-pwa": "^0.14.7",
|
||||
"vitepress": "1.0.0-alpha.74",
|
||||
"workbox-window": "^6.5.4"
|
||||
}
|
||||
}
|
@ -248,7 +248,7 @@ classE o-- classF : aggregation
|
||||
|
||||
Relations can logically represent an N:M association:
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
classDiagram
|
||||
Animal <|--|> Zebra
|
||||
```
|
||||
@ -277,6 +277,23 @@ And `Link` can be one of:
|
||||
| -- | Solid |
|
||||
| .. | Dashed |
|
||||
|
||||
## Define Namespace
|
||||
|
||||
A namespace groups classes.
|
||||
|
||||
Code:
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
namespace BaseShapes {
|
||||
class Triangle
|
||||
class Rectangle {
|
||||
double width
|
||||
double height
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## Cardinality / Multiplicity on relations
|
||||
|
||||
Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies.
|
||||
@ -351,7 +368,7 @@ class Color{
|
||||
|
||||
Comments can be entered within a class diagram, which will be ignored by the parser. Comments need to be on their own line, and must be prefaced with `%%` (double percent signs). Any text until the next newline will be treated as a comment, including any class diagram syntax.
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
classDiagram
|
||||
%% This whole line is a comment classDiagram class Shape <<interface>>
|
||||
class Shape{
|
||||
@ -417,7 +434,7 @@ classDiagram
|
||||
|
||||
_URL Link:_
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Shape
|
||||
link Shape "https://www.github.com" "This is a tooltip for a link"
|
||||
@ -427,7 +444,7 @@ click Shape2 href "https://www.github.com" "This is a tooltip for a link"
|
||||
|
||||
_Callback:_
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
classDiagram
|
||||
class Shape
|
||||
callback Shape "callbackFunction" "This is a tooltip for a callback"
|
||||
|
@ -116,7 +116,7 @@ Relationships may be classified as either _identifying_ or _non-identifying_ and
|
||||
| to | _identifying_ |
|
||||
| optionally to | _non-identifying_ |
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
erDiagram
|
||||
CAR ||--o{ NAMED-DRIVER : allows
|
||||
PERSON ||--o{ NAMED-DRIVER : is
|
||||
|
@ -5,11 +5,11 @@ outline: 'deep' # shows all h3 headings in outline in Vitepress
|
||||
|
||||
# Flowcharts - Basic Syntax
|
||||
|
||||
All Flowcharts are composed of **nodes**, the geometric shapes and **edges**, the arrows or lines. The mermaid code defines the way that these **nodes** and **edges** are made and interact.
|
||||
Flowcharts are composed of **nodes** (geometric shapes) and **edges** (arrows or lines). The Mermaid code defines how nodes and edges are made and accommodates different arrow types, multi-directional arrows, and any linking to and from subgraphs.
|
||||
|
||||
It can also accommodate different arrow types, multi directional arrows, and linking to and from subgraphs.
|
||||
|
||||
> **Important note**: Do not type the word "end" as a Flowchart node. Capitalize all or any one the letters to keep the flowchart from breaking, i.e, "End" or "END". Or you can apply this [workaround](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897).
|
||||
```warning
|
||||
If you are using the word "end" in a Flowchart node, capitalize the entire word or any of the letters (e.g., "End" or "END"), or apply this [workaround](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897). Typing "end" in all lowercase letters will break the Flowchart.
|
||||
```
|
||||
|
||||
### A node (default)
|
||||
|
||||
@ -294,7 +294,7 @@ word of warning, one could go overboard with this making the flowchart harder to
|
||||
markdown form. The Swedish word `lagom` comes to mind. It means, not too much and not too little.
|
||||
This goes for expressive syntaxes as well.
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
flowchart TB
|
||||
A --> C
|
||||
A --> D
|
||||
@ -578,7 +578,7 @@ Beginner's tip—a full example using interactive links in a html context:
|
||||
|
||||
Comments can be entered within a flow diagram, which will be ignored by the parser. Comments need to be on their own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any flow syntax
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
flowchart LR
|
||||
%% this is a comment A -- text --> B{node}
|
||||
A -- text --> B -- text2 --> C
|
||||
@ -704,7 +704,7 @@ flowchart TD
|
||||
B-->E(A fa:fa-camera-retro perhaps?)
|
||||
```
|
||||
|
||||
?> Mermaid is now only compatible with Font Awesome versions 4 and 5. Check that you are using the correct version of Font Awesome.
|
||||
Mermaid is compatible with Font Awesome up to verion 5, Free icons only. Check that the icons you use are from the [supported set of icons](https://fontawesome.com/v5/search?o=r&m=free).
|
||||
|
||||
## Graph declarations with spaces between vertices and link and without semicolon
|
||||
|
||||
|
@ -193,7 +193,7 @@ More info in: [https://github.com/d3/d3-time#interval_every](https://github.com/
|
||||
|
||||
The compact mode allows you to display multiple tasks in the same row. Compact mode can be enabled for a gantt chart by setting the display mode of the graph via preceeding YAML settings.
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
---
|
||||
displayMode: compact
|
||||
---
|
||||
@ -211,7 +211,7 @@ gantt
|
||||
|
||||
Comments can be entered within a gantt chart, which will be ignored by the parser. Comments need to be on their own line and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax.
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
gantt
|
||||
title A Gantt Diagram
|
||||
%% this is a comment
|
||||
|
@ -114,7 +114,7 @@ More shapes will be added, beginning with the shapes available in flowcharts.
|
||||
|
||||
## Icons
|
||||
|
||||
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and fontawesome 4 are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
|
||||
As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and [Font Awesome 5](https://fontawesome.com/v5/search?o=r&m=free) are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change.
|
||||
|
||||
```mermaid-example
|
||||
mindmap
|
||||
|
@ -387,7 +387,7 @@ sequenceDiagram
|
||||
|
||||
Comments can be entered within a sequence diagram, which will be ignored by the parser. Comments need to be on their own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next newline will be treated as a comment, including any diagram syntax
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
Alice->>John: Hello John, how are you?
|
||||
%% this is a comment
|
||||
@ -443,7 +443,7 @@ This can be configured by adding one or more link lines with the format:
|
||||
link <actor>: <link-label> @ <link-url>
|
||||
```
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
sequenceDiagram
|
||||
participant Alice
|
||||
participant John
|
||||
|
@ -249,7 +249,7 @@ Comments can be entered within a state diagram chart, which will be ignored by t
|
||||
own line, and must be prefaced with `%%` (double percent signs). Any text after the start of the comment to the next
|
||||
newline will be treated as a comment, including any diagram syntax
|
||||
|
||||
```mmd
|
||||
```mermaid
|
||||
stateDiagram-v2
|
||||
[*] --> Still
|
||||
Still --> [*]
|
||||
|
@ -1,13 +1,49 @@
|
||||
import { defineConfig, type PluginOption, searchForWorkspaceRoot } from 'vite';
|
||||
import { defineConfig, searchForWorkspaceRoot } from 'vite';
|
||||
import type { PluginOption, Plugin } from 'vite';
|
||||
import path from 'path';
|
||||
// @ts-expect-error This package has an incorrect export map.
|
||||
import { SearchPlugin } from 'vitepress-plugin-search';
|
||||
import fs from 'fs';
|
||||
import Components from 'unplugin-vue-components/vite';
|
||||
import Unocss from 'unocss/vite';
|
||||
import { presetAttributify, presetIcons, presetUno } from 'unocss';
|
||||
import { resolve } from 'pathe';
|
||||
|
||||
const virtualModuleId = 'virtual:mermaid-config';
|
||||
const resolvedVirtualModuleId = '\0' + virtualModuleId;
|
||||
|
||||
export default defineConfig({
|
||||
optimizeDeps: {
|
||||
// vitepress is aliased with replacement `join(DIST_CLIENT_PATH, '/index')`
|
||||
// This needs to be excluded from optimization
|
||||
exclude: ['vitepress'],
|
||||
},
|
||||
plugins: [
|
||||
// @ts-ignore This package has an incorrect exports.
|
||||
Components({
|
||||
include: [/\.vue/, /\.md/],
|
||||
dirs: '.vitepress/components',
|
||||
dts: '.vitepress/components.d.ts',
|
||||
}) as Plugin,
|
||||
// @ts-ignore This package has an incorrect exports.
|
||||
Unocss({
|
||||
shortcuts: [
|
||||
[
|
||||
'btn',
|
||||
'px-4 py-1 rounded inline-flex justify-center gap-2 text-white leading-30px children:mya !no-underline cursor-pointer disabled:cursor-default disabled:bg-gray-600 disabled:opacity-50',
|
||||
],
|
||||
],
|
||||
presets: [
|
||||
presetUno({
|
||||
dark: 'media',
|
||||
}),
|
||||
presetAttributify(),
|
||||
presetIcons({
|
||||
scale: 1.2,
|
||||
}),
|
||||
],
|
||||
}) as unknown as Plugin,
|
||||
IncludesPlugin(),
|
||||
SearchPlugin() as PluginOption,
|
||||
{
|
||||
// TODO: will be fixed in the next vitepress release.
|
||||
@ -48,3 +84,21 @@ export default defineConfig({
|
||||
},
|
||||
},
|
||||
});
|
||||
|
||||
function IncludesPlugin(): Plugin {
|
||||
return {
|
||||
name: 'include-plugin',
|
||||
enforce: 'pre',
|
||||
transform(code: string, id: string): string | undefined {
|
||||
let changed = false;
|
||||
code = code.replace(/\[@@include]\((.*?)\)/, (_: string, url: any): string => {
|
||||
changed = true;
|
||||
const full = resolve(id, url);
|
||||
return fs.readFileSync(full, 'utf-8');
|
||||
});
|
||||
if (changed) {
|
||||
return code;
|
||||
}
|
||||
},
|
||||
} as Plugin;
|
||||
}
|
||||
|
2993
pnpm-lock.yaml
generated
2993
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@ -1,4 +1,5 @@
|
||||
packages:
|
||||
# all packages in direct subdirs of packages/
|
||||
- 'packages/*'
|
||||
- 'packages/mermaid/src/docs'
|
||||
- 'packages/mermaid/src/vitepress'
|
||||
- 'tests/*'
|
||||
|
Loading…
x
Reference in New Issue
Block a user