Compare commits

..

5 Commits

Author SHA1 Message Date
nirname
082bae36b6 Update docs 2023-08-24 19:02:46 +00:00
Nikolay Rozhkov
b688987f99 Fixed link again 2023-08-24 21:58:09 +03:00
nirname
cef8bfc9e8 Update docs 2023-08-24 18:32:30 +00:00
Nikolay Rozhkov
61fc828059 Fixed link to mermaid-js.github.io after review 2023-08-24 21:24:24 +03:00
nirname
3729772f65 Update docs 2023-08-23 20:38:06 +00:00
188 changed files with 2708 additions and 3861 deletions

View File

@@ -71,8 +71,6 @@ Documentation is necessary for all non bugfix/refactoring changes.
Only make changes to files that are in [`/packages/mermaid/src/docs`](packages/mermaid/src/docs)
**_DO NOT CHANGE FILES IN `/docs` MANUALLY_**
The `/docs` folder will be rebuilt and committed as part of a pre-commit hook.
**_DO NOT CHANGE FILES IN `/docs`_**
[Join our slack community if you want closer contact!](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)

View File

@@ -56,11 +56,11 @@ Mermaid addresses this problem by enabling users to create easily modifiable dia
Mermaid allows even non-programmers to easily create detailed diagrams through the [Mermaid Live Editor](https://mermaid.live/).<br/>
[Tutorials](./docs/config/Tutorials.md) has video tutorials.
Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations-community.md).
Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations.md).
You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations-community.md).
You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations.md).
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/intro/getting-started.md), [Usage](./docs/config/usage.md) and [Tutorials](./docs/config/Tutorials.md).
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/intro/n00b-gettingStarted.md), [Usage](./docs/config/usage.md) and [Tutorials](./docs/config/Tutorials.md).
In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests.
@@ -165,7 +165,13 @@ class Class10 {
int id
size()
}
namespace Namespace01 {
class Class11
class Class12 {
int id
size()
}
}
```
```mermaid
@@ -185,7 +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>]

View File

@@ -53,9 +53,9 @@ Mermaid 是一个基于 Javascript 的图表绘制工具,通过解析类 Markd
Mermaid 通过允许用户创建便于修改的图表来解决这一难题,它也可以作为生产脚本(或其他代码)的一部分。<br/>
<br/>
Mermaid 甚至能让非程序员也能通过 [Mermaid Live Editor](https://mermaid.live/) 轻松创建详细的图表。<br/>
你可以访问 [教程](./docs/config/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/ecosystem/integrations-community.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。
你可以访问 [教程](./docs/config/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/ecosystem/integrations.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。
如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/intro/getting-started.md), [用法](./docs/config/usage.md) 和 [教程](./docs/config/Tutorials.md).
如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/intro/n00b-gettingStarted.md), [用法](./docs/config/usage.md) 和 [教程](./docs/config/Tutorials.md).
<!-- </Main description> -->

View File

@@ -22,7 +22,6 @@
"brkt",
"brolin",
"brotli",
"catmull",
"città",
"classdef",
"codedoc",
@@ -39,10 +38,7 @@
"docsy",
"doku",
"dompurify",
"dont",
"doublecircle",
"edgechromium",
"elems",
"elkjs",
"elle",
"faber",
@@ -158,8 +154,7 @@
"xlink",
"yash",
"yokozuna",
"zenuml",
"zune"
"zenuml"
],
"patterns": [
{ "name": "Markdown links", "pattern": "\\((.*)\\)", "description": "" },

View File

@@ -18,11 +18,7 @@ const utf8ToB64 = (str: string): string => {
return Buffer.from(decodeURIComponent(encodeURIComponent(str))).toString('base64');
};
const batchId: string =
'mermaid-batch-' +
(Cypress.env('useAppli')
? Date.now().toString()
: Cypress.env('CYPRESS_COMMIT') || Date.now().toString());
const batchId: string = 'mermaid-batch-' + Cypress.env('CYPRESS_COMMIT') || Date.now().toString();
export const mermaidUrl = (
graphStr: string,
@@ -52,21 +48,29 @@ export const imgSnapshotTest = (
api = false,
validation?: any
): void => {
const options: CypressMermaidConfig = {
..._options,
fontFamily: _options.fontFamily || 'courier',
// @ts-ignore TODO: Fix type of fontSize
fontSize: _options.fontSize || '16px',
sequence: {
...(_options.sequence || {}),
actorFontFamily: 'courier',
noteFontFamily:
_options.sequence && _options.sequence.noteFontFamily
? _options.sequence.noteFontFamily
: 'courier',
messageFontFamily: 'courier',
},
};
cy.log(JSON.stringify(_options));
const options: CypressMermaidConfig = Object.assign(_options);
if (!options.fontFamily) {
options.fontFamily = 'courier';
}
if (!options.sequence) {
options.sequence = {};
}
if (!options.sequence || (options.sequence && !options.sequence.actorFontFamily)) {
options.sequence.actorFontFamily = 'courier';
}
if (options.sequence && !options.sequence.noteFontFamily) {
options.sequence.noteFontFamily = 'courier';
}
options.sequence.actorFontFamily = 'courier';
options.sequence.noteFontFamily = 'courier';
options.sequence.messageFontFamily = 'courier';
if (options.sequence && !options.sequence.actorFontFamily) {
options.sequence.actorFontFamily = 'courier';
}
if (!options.fontSize) {
options.fontSize = 16;
}
const url: string = mermaidUrl(graphStr, options, api);
openURLAndVerifyRendering(url, options, validation);
@@ -74,10 +78,11 @@ export const imgSnapshotTest = (
export const urlSnapshotTest = (
url: string,
options: CypressMermaidConfig,
_options: CypressMermaidConfig,
_api = false,
validation?: any
): void => {
const options: CypressMermaidConfig = Object.assign(_options);
openURLAndVerifyRendering(url, options, validation);
};

View File

@@ -132,9 +132,4 @@ describe('XSS', () => {
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
it('should sanitize backticks in class names properly', () => {
cy.visit('http://localhost:9000/xss24.html');
cy.wait(1000);
cy.get('#the-malware').should('not.exist');
});
});

View File

@@ -386,6 +386,30 @@ describe('Class diagram V2', () => {
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
});
it('18: should handle the direction statement with LR', () => {
imgSnapshotTest(
`
classDiagram
direction LR
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
});
it('17a: should handle the direction statement with BT', () => {
imgSnapshotTest(
`
@@ -433,31 +457,7 @@ describe('Class diagram V2', () => {
);
});
it('18a: should handle the direction statement with LR', () => {
imgSnapshotTest(
`
classDiagram
direction LR
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
});
it('18b: should render a simple class diagram with notes', () => {
it('18: should render a simple class diagram with notes', () => {
imgSnapshotTest(
`
classDiagram-v2
@@ -562,13 +562,4 @@ class C13["With Città foreign language"]
`
);
});
it('should render a simple class diagram with no members', () => {
imgSnapshotTest(
`
classDiagram-v2
class Class10
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
});
});

View File

@@ -305,21 +305,4 @@ ORDER ||--|{ LINE-ITEM : contains
{}
);
});
it('should render entities with entity name aliases', () => {
imgSnapshotTest(
`
erDiagram
p[Person] {
varchar(64) firstName
varchar(64) lastName
}
c["Customer Account"] {
varchar(128) email
}
p ||--o| c : has
`,
{ logLevel: 1 }
);
});
});

View File

@@ -330,48 +330,6 @@ describe('Gantt diagram', () => {
);
});
it('should render a gantt diagram with tick is 2 milliseconds', () => {
imgSnapshotTest(
`
gantt
title A Gantt Diagram
dateFormat SSS
axisFormat %Lms
tickInterval 2millisecond
excludes weekends
section Section
A task : a1, 000, 6ms
Another task : after a1, 6ms
section Another
Task in sec : a2, 006, 3ms
another task : 3ms
`,
{}
);
});
it('should render a gantt diagram with tick is 2 seconds', () => {
imgSnapshotTest(
`
gantt
title A Gantt Diagram
dateFormat ss
axisFormat %Ss
tickInterval 2second
excludes weekends
section Section
A task : a1, 00, 6s
Another task : after a1, 6s
section Another
Task in sec : 06, 3s
another task : 3s
`,
{}
);
});
it('should render a gantt diagram with tick is 15 minutes', () => {
imgSnapshotTest(
`
@@ -520,25 +478,6 @@ describe('Gantt diagram', () => {
);
});
it('should render a gantt diagram with very large intervals, skipping excludes if interval > 5 years', () => {
imgSnapshotTest(
`gantt
title A long Gantt Diagram
dateFormat YYYY-MM-DD
axisFormat %m-%d
tickInterval 1day
excludes weekends
section Section
A task : a1, 9999-10-01, 30d
Another task : after a1, 20d
section Another
Task in sec : 2022-10-20, 12d
another task : 24d
`
);
});
it('should render when compact is true', () => {
imgSnapshotTest(
`

View File

@@ -1,10 +0,0 @@
import { urlSnapshotTest } from '../../helpers/util.ts';
describe('Marker Unique IDs Per Diagram', () => {
it('should render a blue arrow tip in second digram', () => {
urlSnapshotTest('http://localhost:9000/marker_unique_id.html', {
logLevel: 1,
flowchart: { htmlLabels: false },
});
});
});

View File

@@ -58,10 +58,12 @@
</head>
<body>
<pre id="diagram" class="mermaid">
classDiagram
`Class<img src=x onerror=alert(1)>` <|-- `Class2<img src=x onerror=alert(2)>`
</pre>
<pre id="diagram" class="mermaid2">
flowchart
classDef mainCategories fill:#f9d5e5, stroke:#233d4d,stroke-width:2px, font-weight:bold;
CS(Customer Awareness Journey):::mainCategories
</pre
>
<pre id="diagram" class="mermaid">
flowchart
Node1:::class1 --> Node2:::class2
Node1:::class1 --> Node3:::class2

View File

@@ -1,52 +0,0 @@
<html>
<head> </head>
<body>
<h1>Example</h1>
<pre class="mermaid">
%%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%%
flowchart LR
subgraph red
A --> B
end
</pre>
<pre class="mermaid">
%%{init:{"theme":"base", "themeVariables": {"lineColor":"blue"}}}%%
flowchart LR
subgraph black
A --> B
end
</pre>
<pre class="mermaid">
---
config:
theme: base
themeVariables:
lineColor: yellow
---
flowchart LR
subgraph red
A --> B
end
</pre>
<pre class="mermaid">
---
config:
theme: base
themeVariables:
lineColor: green
---
flowchart LR
subgraph black
A --> B
end
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({ startOnLoad: true, logLevel: 0 });
if (window.Cypress) {
window.rendered = true;
}
</script>
</body>
</html>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -1,109 +0,0 @@
<html>
<head>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
<link
rel="stylesheet"
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
/>
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet"
/>
<style>
body {
/* background: rgb(221, 208, 208); */
/* background:#333; */
font-family: 'Arial';
/* font-size: 18px !important; */
}
h1 {
color: grey;
}
.mermaid2 {
display: none;
}
.mermaid svg {
/* font-size: 18px !important; */
}
.malware {
position: fixed;
bottom: 0;
left: 0;
right: 0;
height: 150px;
background: red;
color: black;
display: flex;
display: flex;
justify-content: center;
align-items: center;
font-family: monospace;
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>
<div class="flex">
<div id="diagram" class="mermaid"></div>
<div id="res" class=""></div>
</div>
<script type="module">
import mermaid from './mermaid.esm.mjs';
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
theme: 'forest',
arrowMarkerAbsolute: true,
// themeCSS: '.edgePath .path {stroke: red;} .arrowheadPath {fill: red;}',
logLevel: 0,
state: {
defaultRenderer: 'dagre-wrapper',
},
flowchart: {
// defaultRenderer: 'dagre-wrapper',
nodeSpacing: 10,
curve: 'cardinal',
htmlLabels: true,
},
htmlLabels: false,
// gantt: { axisFormat: '%m/%d/%Y' },
sequence: { actorFontFamily: 'courier', actorMargin: 50, showSequenceNumbers: false },
// sequenceDiagram: { actorMargin: 300 } // deprecated
// fontFamily: '"times", sans-serif',
// fontFamily: 'courier',
fontSize: 18,
curve: 'basis',
securityLevel: 'strict',
startOnLoad: false,
secure: ['secure', 'securityLevel', 'startOnLoad', 'maxTextSize'],
// themeVariables: {relationLabelColor: 'red'}
});
function callback() {
alert('It worked');
}
let diagram = 'classDiagram\n';
diagram += '`Class<img src=x on';
diagram += 'error=xssAttack()>` <|-- `Class2<img src=x on';
diagram += 'error=xssAttack()>`';
console.log(diagram);
// document.querySelector('#diagram').innerHTML = diagram;
const { svg } = await mermaid.render('diagram', diagram);
document.querySelector('#res').innerHTML = svg;
</script>
</body>
</html>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>
@@ -94,6 +84,14 @@
function callback() {
alert('It worked');
}
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
let diagram = 'graph LR\n';
diagram += 'B-->D("<img onerror=location=`java';
// diagram += "script\u003aalert\u0028document.domain\u0029\` src=x>\"\);\n";

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -42,16 +42,6 @@
font-size: 72px;
}
</style>
<script>
function xssAttack() {
const div = document.createElement('div');
div.id = 'the-malware';
div.className = 'malware';
div.innerHTML = 'XSS Succeeded';
document.getElementsByTagName('body')[0].appendChild(div);
throw new Error('XSS Succeeded');
}
</script>
</head>
<body>
<div>Security check</div>

View File

@@ -38,14 +38,12 @@
+quack()
}
class Fish{
-Listint sizeInFeet
-int sizeInFeet
-canEat()
}
class Zebra{
+bool is_wild
+run(List~T~, List~OT~)
%% +run-composite(List~T, K~)
+run-nested(List~List~OT~~)
+run()
}
</pre>
@@ -82,7 +80,6 @@
Class01 : #size()
Class01 : -int chimp
Class01 : +int gorilla
Class01 : +abstractAttribute string*
class Class10~T~ {
&lt;&lt;service&gt;&gt;
int id
@@ -125,8 +122,6 @@
classDiagram
direction LR
Animal ()-- Dog
Animal ()-- Cat
note for Cat "should have no members area"
Dog : bark()
Dog : species()
</pre>
@@ -156,7 +151,6 @@
~InternalProperty : string
~AnotherInternalProperty : List~List~string~~
}
class People List~List~Person~~
</pre>
<hr />

View File

@@ -110,35 +110,6 @@
}
MANUFACTURER only one to zero or more CAR : makes
</pre>
<hr />
<pre class="mermaid">
erDiagram
p[Person] {
string firstName
string lastName
}
a["Customer Account"] {
string email
}
p ||--o| a : has
</pre>
<hr />
<pre class="mermaid">
erDiagram
_customer_order {
bigint id PK
bigint customer_id FK
text shipping_address
text delivery_method
timestamp_with_time_zone ordered_at
numeric total_tax_amount
numeric total_price
text payment_method
}
</pre>
<hr />
<script type="module">
import mermaid from './mermaid.esm.mjs';

View File

@@ -5,7 +5,7 @@ services:
stdin_open: true
tty: true
working_dir: /mermaid
mem_limit: '4G'
mem_limit: '2G'
environment:
- NODE_OPTIONS=--max_old_space_size=4096
volumes:

View File

@@ -42,7 +42,7 @@ Once the release happens we add a tag to the `release` branch and merge it with
2. Check out the `develop` branch
3. Create a new branch for your work. Please name the branch following our naming convention below.
We use the following naming convention for branches:
We use the follow naming convention for branches:
```txt
[feature | bug | chore | docs]/[issue number]_[short description using dashes ('-') or underscores ('_') instead of spaces]

View File

@@ -22,7 +22,7 @@ In GitHub, you first **fork** a repository when you are going to make changes an
Then you **clone** a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with.
[Fork mermaid](https://github.com/mermaid-js/mermaid/fork) to start contributing to the main project and its documentation, or [search for other repositories](https://github.com/orgs/mermaid-js/repositories).
[Fork mermaid](https://github.com/mermaid-js/mermaid/fork) to start contributing to the main project and its documentaion, or [search for other repositories](https://github.com/orgs/mermaid-js/repositories).
[Here is a GitHub document that gives an overview of the process.](https://docs.github.com/en/get-started/quickstart/fork-a-repo)

View File

@@ -22,7 +22,7 @@ In GitHub, you first **fork** a repository when you are going to make changes an
Then you **clone** a copy to your local development machine (e.g. where you code) to make a copy with all the files to work with.
[Fork mermaid](https://github.com/mermaid-js/mermaid/fork) to start contributing to the main project and its documentation, or [search for other repositories](https://github.com/orgs/mermaid-js/repositories).
[Fork mermaid](https://github.com/mermaid-js/mermaid/fork) to start contributing to the main project and its documentaion, or [search for other repositories](https://github.com/orgs/mermaid-js/repositories).
[Here is a GitHub document that gives an overview of the process.](https://docs.github.com/en/get-started/quickstart/fork-a-repo)

View File

@@ -19,7 +19,7 @@ For instance:
#### Store data found during parsing
There are some jison specific sub steps here where the parser stores the data encountered when parsing the diagram, this data is later used by the renderer. You can during the parsing call an object provided to the parser by the user of the parser. This object can be called during parsing for storing data.
There are some jison specific sub steps here where the parser stores the data encountered when parsing the diagram, this data is later used by the renderer. You can during the parsing call a object provided to the parser by the user of the parser. This object can be called during parsing for storing data.
```jison
statement
@@ -35,7 +35,7 @@ In the extract of the grammar above, it is defined that a call to the setTitle m
> **Note**
> Make sure that the `parseError` function for the parser is defined and calling `mermaid.parseError`. This way a common way of detecting parse errors is provided for the end-user.
For more info look at the example diagram type:
For more info look in the example diagram type:
The `yy` object has the following function:
@@ -54,7 +54,7 @@ parser.yy = db;
### Step 2: Rendering
Write a renderer that given the data found during parsing renders the diagram. To look at an example look at sequenceRenderer.js rather than the flowchart renderer as this is a more generic example.
Write a renderer that given the data found during parsing renders the diagram. To look at an example look at sequenceRenderer.js rather then the flowchart renderer as this is a more generic example.
Place the renderer in the diagram folder.
@@ -62,7 +62,7 @@ Place the renderer in the diagram folder.
The second thing to do is to add the capability to detect the new diagram to type to the detectType in `diagram-api/detectType.ts`. The detection should return a key for the new diagram type.
[This key will be used to as the aria roledescription](#aria-roledescription), so it should be a word that clearly describes the diagram type.
For example, if your new diagram uses a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader
For example, if your new diagram use a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader
would voice that as "U-M-L Deployment diagram." Another good key would be "deploymentDiagram" because that would be voiced as "Deployment Diagram." A bad key would be "deployment" because that would not sufficiently describe the diagram.
Note that the diagram type key does not have to be the same as the diagram keyword chosen for the [grammar](#grammar), but it is helpful if they are the same.
@@ -122,7 +122,7 @@ There are a few features that are common between the different types of diagrams
- Themes, there is a common way to modify the styling of diagrams in Mermaid.
- Comments should follow mermaid standards
Here are some pointers on how to handle these different areas.
Here some pointers on how to handle these different areas.
## Accessibility
@@ -140,7 +140,7 @@ See [the definition of aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/
### accessible title and description
The syntax for accessible titles and descriptions is described in [the Accessibility documentation section.](../config/accessibility.md)
The syntax for accessible titles and descriptions is described in [the Accessibility documenation section.](../config/accessibility.md)
As a design goal, the jison syntax should be similar between the diagrams.

View File

@@ -62,10 +62,10 @@ from IPython.display import Image, display
import matplotlib.pyplot as plt
def mm(graph):
graphbytes = graph.encode("utf8")
base64_bytes = base64.b64encode(graphbytes)
base64_string = base64_bytes.decode("ascii")
display(Image(url="https://mermaid.ink/img/" + base64_string))
graphbytes = graph.encode("ascii")
base64_bytes = base64.b64encode(graphbytes)
base64_string = base64_bytes.decode("ascii")
display(Image(url="https://mermaid.ink/img/" + base64_string))
mm("""
graph LR;

View File

@@ -10,7 +10,7 @@ When mermaid starts, configuration is extracted to determine a configuration to
- The default configuration
- Overrides at the site level are set by the initialize call, and will be applied to all diagrams in the site/app. The term for this is the **siteConfig**.
- Frontmatter (v10.5.0+) - diagram authors can update select configuration parameters in the frontmatter of the diagram. These are applied to the render config.
- Frontmatter (v\<MERMAID_RELEASE_VERSION>+) - diagram authors can update select configuration parameters in the frontmatter of the diagram. These are applied to the render config.
- Directives (Deprecated by Frontmatter) - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
**The render config** is configuration that is used when rendering by applying these configurations.

View File

@@ -7,7 +7,7 @@
# Directives
> **Warning**
> Directives are deprecated from v10.5.0. Please use the `config` key in frontmatter to pass configuration. See [Configuration](./configuration.md) for more details.
> Directives are deprecated from v\<MERMAID_RELEASE_VERSION>. Please use the `config` key in frontmatter to pass configuration. See [Configuration](./configuration.md) for more details.
## Directives
@@ -126,7 +126,7 @@ The following code snippet changes `theme` to `forest`:
`%%{init: { "theme": "forest" } }%%`
Possible theme values are: `default`, `base`, `dark`, `forest` and `neutral`.
Possible theme values are: `default`,`base`, `dark`, `forest` and `neutral`.
Default Value is `default`.
Example:
@@ -291,7 +291,7 @@ Let us see an example:
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion?
Alice->Bob: Good.
Bob->Alice: Cool
```
@@ -300,7 +300,7 @@ Bob->Alice: Cool
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion?
Alice->Bob: Good.
Bob->Alice: Cool
```
@@ -317,7 +317,7 @@ By applying that snippet to the diagram above, `wrap` will be enabled:
%%{init: { "sequence": { "wrap": true, "width":300 } } }%%
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion?
Alice->Bob: Good.
Bob->Alice: Cool
```
@@ -326,7 +326,7 @@ Bob->Alice: Cool
%%{init: { "sequence": { "wrap": true, "width":300 } } }%%
sequenceDiagram
Alice->Bob: Hello Bob, how are you?
Bob->Alice: Fine, how did your mother like the book I suggested? And did you catch the new book about alien invasion?
Bob->Alice: Fine, how did you mother like the book I suggested? And did you catch the new book about alien invasion?
Alice->Bob: Good.
Bob->Alice: Cool
```

View File

@@ -16,4 +16,4 @@
#### Defined in
[mermaidAPI.ts:59](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L59)
[mermaidAPI.ts:78](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L78)

View File

@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
#### Defined in
[mermaidAPI.ts:79](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L79)
[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98)
---
@@ -51,4 +51,4 @@ The svg code for the rendered graph.
#### Defined in
[mermaidAPI.ts:69](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L69)
[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88)

View File

@@ -25,13 +25,13 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
#### Defined in
[mermaidAPI.ts:63](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L63)
[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82)
## Variables
### mermaidAPI
`Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: `Pick`<`DiagramMetadata`, `"title"`>) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }>
`Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }>
## mermaidAPI configuration defaults
@@ -96,7 +96,7 @@ mermaid.initialize(config);
#### Defined in
[mermaidAPI.ts:641](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L641)
[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673)
## Functions
@@ -127,7 +127,7 @@ Return the last node appended
#### Defined in
[mermaidAPI.ts:299](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L299)
[mermaidAPI.ts:310](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L310)
---
@@ -153,13 +153,13 @@ the cleaned up svgCode
#### Defined in
[mermaidAPI.ts:245](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L245)
[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256)
---
### createCssStyles
**createCssStyles**(`config`, `classDefs?`): `string`
**createCssStyles**(`config`, `graphType`, `classDefs?`): `string`
Create the user styles
@@ -168,6 +168,7 @@ Create the user styles
| Name | Type | Description |
| :---------- | :------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------ |
| `config` | `MermaidConfig` | configuration that has style and theme settings to use |
| `graphType` | `string` | used for checking if classDefs should be applied |
| `classDefs` | `undefined` \| `null` \| `Record`<`string`, `DiagramStyleClassDef`> | the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...) |
#### Returns
@@ -178,7 +179,7 @@ the string with all the user styles
#### Defined in
[mermaidAPI.ts:175](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L175)
[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185)
---
@@ -188,12 +189,12 @@ the string with all the user styles
#### Parameters
| Name | Type |
| :---------- | :-------------------------------------------------------- |
| `config` | `MermaidConfig` |
| `graphType` | `string` |
| `classDefs` | `undefined` \| `Record`<`string`, `DiagramStyleClassDef`> |
| `svgId` | `string` |
| Name | Type |
| :---------- | :----------------------------------------- |
| `config` | `MermaidConfig` |
| `graphType` | `string` |
| `classDefs` | `Record`<`string`, `DiagramStyleClassDef`> |
| `svgId` | `string` |
#### Returns
@@ -201,7 +202,7 @@ the string with all the user styles
#### Defined in
[mermaidAPI.ts:222](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L222)
[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233)
---
@@ -228,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
[mermaidAPI.ts:160](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L160)
[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169)
---
@@ -248,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
[mermaidAPI.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L146)
[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155)
---
@@ -268,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
[mermaidAPI.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L117)
[mermaidAPI.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L126)
---
@@ -294,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
#### Defined in
[mermaidAPI.ts:276](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L276)
[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287)
---
@@ -319,4 +320,4 @@ Remove any existing elements from the given document
#### Defined in
[mermaidAPI.ts:349](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L349)
[mermaidAPI.ts:360](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L360)

View File

@@ -41,7 +41,7 @@ pnpm add mermaid
**Hosting mermaid on a web page:**
> Note: This topic is explored in greater depth in the [User Guide for Beginners](../intro/getting-started.md)
> Note:This topic explored in greater depth in the [User Guide for Beginners](../intro/getting-started.md)
The easiest way to integrate mermaid on a web page requires two elements:
@@ -100,7 +100,7 @@ Mermaid can load multiple diagrams, in the same page.
## Enabling Click Event and Tags in Nodes
A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduced in version 8.2 as a security improvement, aimed at preventing malicious use.
A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduce in version 8.2 as a security improvement, aimed at preventing malicious use.
**It is the site owner's responsibility to discriminate between trustworthy and untrustworthy user-bases and we encourage the use of discretion.**
@@ -115,13 +115,13 @@ Values:
- **strict**: (**default**) HTML tags in the text are encoded and click functionality is disabled.
- **antiscript**: HTML tags in text are allowed (only script elements are removed) and click functionality is enabled.
- **loose**: HTML tags in text are allowed and click functionality is enabled.
- **sandbox**: With this security level, all rendering takes place in a sandboxed iframe. This prevents any JavaScript from running in the context. This may hinder interactive functionality of the diagram, like scripts, popups in the sequence diagram, links to other tabs or targets, etc.
- **sandbox**: With this security level, all rendering takes place in a sandboxed iframe. This prevent any JavaScript from running in the context. This may hinder interactive functionality of the diagram, like scripts, popups in the sequence diagram, links to other tabs or targets, etc.
> **Note**
> This changes the default behaviour of mermaid so that after upgrade to 8.2, unless the `securityLevel` is not changed, tags in flowcharts are encoded as tags and clicking is disabled.
> **sandbox** security level is still in the beta version.
**If you are taking responsibility for the diagram source security you can set the `securityLevel` to a value of your choosing. This allows clicks and tags are allowed.**
**If you are taking responsibility for the diagram source security you can set the `securityLevel` to a value of your choosing . This allows clicks and tags are allowed.**
**To change `securityLevel`, you have to call `mermaid.initialize`:**

View File

@@ -1,31 +0,0 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/integrations-create.md](../../packages/mermaid/src/docs/ecosystem/integrations-create.md).
# Integrations - create
## Recommendations
Below are recommendations for creating plugins and integrations with Mermaid.
### File Extension
Applications that support Mermaid files [SHOULD](https://datatracker.ietf.org/doc/html/rfc2119#section-3) use `.mermaid` or `.mmd` file extensions.
### MIME Type
The recommended [MIME type](https://www.iana.org/assignments/media-types/media-types.xhtml) for Mermaid media is `text/vnd.mermaid`.
Currently pending [IANA](https://www.iana.org/) recognition.
## Showcase
### Mermaid Slack workspace
We would love to see what you create with Mermaid. Please share your creations with us in our [Slack](https://join.slack.com/t/mermaid-talk/shared_invite/zt-22p2r8p9y-qiyP1H38GjFQ6S6jbBkOxQ) workspace [#community-showcase](https://mermaid-talk.slack.com/archives/C05NK37LT40) channel.
### Add to Mermaid Ecosystem
If you have a plugin or integration that you'd like to add to our [Community integrations](/ecosystem/integrations-community) list, please [open a pull request](https://github.com/mermaid-js/mermaid).

View File

@@ -2,46 +2,53 @@
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/integrations-community.md](../../packages/mermaid/src/docs/ecosystem/integrations-community.md).
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/integrations.md](../../packages/mermaid/src/docs/ecosystem/integrations.md).
# Integrations
## Official integration: [Mermaid Chart](./mermaid-chart.md)
## Recommendations
We're excited about the growth of the Mermaid community, and the number of plugins and integrations that have been created with Mermaid.
### File Extension
## Community integrations
Applications that support mermaid files [SHOULD](https://datatracker.ietf.org/doc/html/rfc2119#section-3) use `.mermaid` or `.mmd` file extensions.
Below are a list of community plugins and integrations created with Mermaid.
### MIME Type
### Productivity tools
The recommended [MIME type](https://www.iana.org/assignments/media-types/media-types.xhtml) for mermaid media is `text/vnd.mermaid`.
✅ = Native support
[IANA](https://www.iana.org/) recognition pending.
- [GitHub](https://github.com) ✅
- [Using code blocks](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) ✅
---
The following list is a compilation of different integrations and plugins that allow the rendering of mermaid definitions within other applications.
They also serve as proof of concept, for the variety of things that can be built with mermaid.
## Productivity
- [GitHub](https://github.com) (**Native support**)
- [Using code blocks](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) (**Native support**)
- [GitHub action: Compile mermaid to image](https://github.com/neenjaw/compile-mermaid-markdown-action)
- [svg-generator](https://github.com/SimonKenyonShepard/mermaidjs-github-svg-generator)
- [GitHub Writer](https://github.com/ckeditor/github-writer)
- [GitLab](https://docs.gitlab.com/ee/user/markdown.html#diagrams-and-flowcharts)
- [Gitea](https://gitea.io)
- [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page)
- [Tuleap](https://docs.tuleap.org/user-guide/writing-in-tuleap.html#graphs)
- [Mermaid Flow Visual Editor](https://www.mermaidflow.app)
- [Deepdwn](https://billiam.itch.io/deepdwn)
- [Joplin](https://joplinapp.org)
- [Slab](https://slab.com)
- [Swimm](https://swimm.io)
- [Notion](https://notion.so)
- [Observable](https://observablehq.com/@observablehq/mermaid)
- [Obsidian](https://help.obsidian.md/Editing+and+formatting/Advanced+formatting+syntax#Diagram)
- [GitLab](https://docs.gitlab.com/ee/user/markdown.html#diagrams-and-flowcharts) (**Native support**)
- [Gitea](https://gitea.io) (**Native support**)
- [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) (**Native support**)
- [Tuleap](https://docs.tuleap.org/user-guide/writing-in-tuleap.html#graphs) (**Native support**)
- [Mermaid Flow Visual Editor](https://www.mermaidflow.app) (**Native support**)
- [Deepdwn](https://billiam.itch.io/deepdwn) (**Native support**)
- [Joplin](https://joplinapp.org) (**Native support**)
- [Slab](https://slab.com) (**Native support**)
- [Swimm](https://swimm.io) (**Native support**)
- [Notion](https://notion.so) (**Native support**)
- [Observable](https://observablehq.com/@observablehq/mermaid) (**Native support**)
- [Obsidian](https://help.obsidian.md/Editing+and+formatting/Advanced+formatting+syntax#Diagram) (**Native support**)
- [GitBook](https://gitbook.com)
- [Mermaid Plugin](https://github.com/JozoVilcek/gitbook-plugin-mermaid)
- [Markdown with Mermaid CLI](https://github.com/miao1007/gitbook-plugin-mermaid-cli)
- [Mermaid plugin for GitBook](https://github.com/wwformat/gitbook-plugin-mermaid-pdf)
- [LiveBook](https://livebook.dev)
- [LiveBook](https://livebook.dev) (**Native support**)
- [Atlassian Products](https://www.atlassian.com)
- [Mermaid Live Editor for Confluence Cloud](https://marketplace.atlassian.com/apps/1231571/mermaid-live-editor-for-confluence?hosting=cloud&tab=overview)
- [Mermaid Plugin for Confluence](https://marketplace.atlassian.com/apps/1214124/mermaid-plugin-for-confluence?hosting=server&tab=overview)
- [CloudScript.io Addon](https://marketplace.atlassian.com/apps/1219878/cloudscript-io-mermaid-addon?hosting=cloud&tab=overview)
- [Auto convert diagrams in Jira](https://github.com/coddingtonbear/jirafs-mermaid)
@@ -53,15 +60,11 @@ Below are a list of community plugins and integrations created with Mermaid.
- [mermerd](https://github.com/KarnerTh/mermerd)
- Visual Studio Code [Polyglot Interactive Notebooks](https://github.com/dotnet/interactive#net-interactive)
### CRM/ERP
Customer Relationship Management/Enterprise Resource Planning
## CRM/ERP/Similar
- [coreBOS](https://blog.corebos.org/blog/december2019)
### Blogging
Blogging frameworks and platforms
## Blogs
- [WordPress](https://wordpress.org)
- [WordPress Markdown Editor](https://wordpress.org/plugins/wp-githuber-md)
@@ -73,9 +76,7 @@ Blogging frameworks and platforms
- [Nextra](https://nextra.site/)
- [Mermaid](https://nextra.site/docs/guide/mermaid)
### CMS/ECM
Content Management Systems/Enterprise Content Management
## CMS
- [VitePress](https://vitepress.vuejs.org/)
- [Plugin for Mermaid.js](https://emersonbottero.github.io/vitepress-plugin-mermaid/)
@@ -85,9 +86,7 @@ Content Management Systems/Enterprise Content Management
- [Mermaid Diagrams](https://github.com/DanielFlaum/grav-plugin-mermaid-diagrams)
- [GitLab Markdown Adapter](https://github.com/Goutte/grav-plugin-gitlab-markdown-adapter)
### Communication
Communication tools and platforms
## Communication
- [Discourse](https://discourse.org)
- [Mermaid Plugin](https://github.com/pnewell/discourse-mermaid), [And](https://github.com/unfoldingWord-dev/discourse-mermaid)
@@ -98,7 +97,7 @@ Communication tools and platforms
- [NodeBB](https://nodebb.org)
- [Mermaid Plugin](https://www.npmjs.com/package/nodebb-plugin-mermaid)
### Wikis
## Wikis
- [MediaWiki](https://www.mediawiki.org)
- [Mermaid Extension](https://www.mediawiki.org/wiki/Extension:Mermaid)
@@ -114,7 +113,7 @@ Communication tools and platforms
- [mermaid-tw5: full js library](https://github.com/efurlanm/mermaid-tw5)
- [tw5-mermaid: wrapper for Mermaid Live](https://github.com/jasonmhoule/tw5-mermaid)
### Editor Plugins
## Editor Plugins
- [VS Code](https://code.visualstudio.com/)
- [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)
@@ -165,9 +164,8 @@ Communication tools and platforms
- [Standard Notes](https://standardnotes.com/)
- [sn-mermaid](https://github.com/nienow/sn-mermaid)
### Document Generation
## Document Generation
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts)
- [Sphinx](https://www.sphinx-doc.org/en/master/)
- [sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
- [remark](https://remark.js.org/)
@@ -183,15 +181,15 @@ Communication tools and platforms
- [mkdocs-material](https://github.com/squidfunk/mkdocs-material), check the [docs](https://squidfunk.github.io/mkdocs-material/reference/diagrams/)
- [Type Doc](https://typedoc.org/)
- [typedoc-plugin-mermaid](https://www.npmjs.com/package/typedoc-plugin-mermaid)
- [Docsy Hugo Theme](https://www.docsy.dev/docs/adding-content/lookandfeel/#diagrams-with-mermaid) (native support in theme)
- [Docsy Hugo Theme](https://www.docsy.dev/docs/adding-content/lookandfeel/#diagrams-with-mermaid) (Native support in theme)
- [Codedoc](https://codedoc.cc/)
- [codedoc-mermaid-plugin](https://www.npmjs.com/package/codedoc-mermaid-plugin)
- [mdbook](https://rust-lang.github.io/mdBook/index.html)
- [mdbook-mermaid](https://github.com/badboy/mdbook-mermaid)
- [Quarto](https://quarto.org/)
- [Typora](https://typora.io/) ([native support](https://support.typora.io/Draw-Diagrams-With-Markdown/#mermaid))
- [Typora](https://typora.io/) ([Native support](https://support.typora.io/Draw-Diagrams-With-Markdown/#mermaid))
### Browser Extensions
## Browser Extensions
| Name | Chrome Web Store | Firefox Add-ons | Opera | Edge | Source/Repository |
| ------------------------ | ------------------------------------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ------------------------------------------------------------------------------ | ---------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------- |
@@ -206,7 +204,7 @@ Communication tools and platforms
| Monkeys | [🎡🔗](https://chrome.google.com/webstore/detail/monkeys-mermaid-for-githu/cplfdpoajbclbgphaphphcldamfkjlgi) | - | - | - | - |
| Mermaid Previewer | [🎡🔗](https://chrome.google.com/webstore/detail/mermaid-previewer/oidjnlhbegipkcklbdfnbkikplpghfdl) | - | - | - | - |
### Other
## Other
- [Jekyll](https://jekyllrb.com/)
- [jekyll-mermaid](https://rubygems.org/gems/jekyll-mermaid)

View File

@@ -1,47 +0,0 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/mermaid-chart.md](../../packages/mermaid/src/docs/ecosystem/mermaid-chart.md).
# Mermaid Chart
<br />
<a href="https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=416671&theme=light" alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## About
[Mermaid Chart](https://www.mermaidchart.com) was born out of the Mermaid open source project and was founded by Knut Sveidqvist together with Open Core Ventures. The lead developers from Mermaid have joined the company and there is a strong connection between the project we all love and Mermaid Chart. Mermaid Chart brings resources to the open source development of Mermaid and makes it possible to work with Mermaid professionally.
## Features
- **Editor** - A web based editor for creating and editing Mermaid diagrams.
- **Presentation** - A presentation mode for viewing Mermaid diagrams in a slideshow format.
- **Collaboration** - A web based collaboration feature for multi-user editing on Mermaid diagrams in real-time (Pro plan).
- **Plugins** - A plugin system for extending the functionality of Mermaid. Currently includes [VS Code](https://marketplace.visualstudio.com/items?itemName=MermaidChart.vscode-mermaid-chart) and [ChatGPT](https://www.mermaidchart.com/plugins/chatgpt).
- **AI** - An AI chatbot that can generate Mermaid diagrams from text (Pro plan).
- **More** - To learn more, visit our [Product](https://www.mermaidchart.com/product) page.
## Plans
- **Free** - A free plan that includes five diagrams.
- **Pro** - A paid plan that includes unlimited diagrams, access to the collaboration feature, and more.
- **Enterprise** - A paid plan for enterprise use that includes all Pro features, and more.
## Access
Sign up for a free account at [Mermaid Chart](https://www.mermaidchart.com/app/sign-up).
Mermaid Chart is currently offering a 30-day free trial of our newly-launched Pro tier. To learn more, visit our [Pricing](https://mermaidchart.com/pricing) page.
## Mermaid JS contributions
First time contributors are eligible for a free Pro tier account for 1 year.

View File

@@ -0,0 +1,9 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/showcases.md](../../packages/mermaid/src/docs/ecosystem/showcases.md).
# Showcases
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts).

View File

@@ -17,7 +17,7 @@ This section talks about the different ways to deploy Mermaid. Learning the [Syn
## Four ways of using mermaid:
1. Using the Mermaid Live Editor at [mermaid.live](https://mermaid.live).
2. Using [mermaid plugins](../ecosystem/integrations-community.md) with programs you are familiar with.
2. Using [mermaid plugins](../ecosystem/integrations.md) with programs you are familiar with.
3. Calling the Mermaid JavaScript API.
4. Deploying Mermaid as a dependency.
@@ -85,7 +85,7 @@ and to View, <https://mermaid.live/view?gist=https://gist.github.com/sidharthv96
## 2. Using Mermaid Plugins:
You can generate mermaid diagrams from within popular applications using plug-ins. It can be done in the same way, you would use the Live Editor. Here's a list of [Mermaid Plugins](../ecosystem/integrations-community.md).
You can generate mermaid diagrams from within popular applications using plug-ins. It can be done in the same way, you would use the Live Editor. Here's a list of [Mermaid Plugins](../ecosystem/integrations.md).
**This is covered in greater detail in the [Usage section](../config/usage.md)**
@@ -103,7 +103,7 @@ When writing the .html file, we give two instructions inside the html code to th
a. The mermaid code for the diagram we want to create.
b. The importing of mermaid library through the `mermaid.esm.mjs` or `mermaid.esm.min.mjs` and the `mermaid.initialize()` call, which dictates the appearance of diagrams and also starts the rendering process.
b. The importing of mermaid library through the `mermaid.esm.mjs` or `mermaid.esm.min.mjs` and the `mermaid.initialize()` call, which dictates the appearance of diagrams and also starts the rendering process .
**a. The embedded mermaid diagram definition inside a `<pre class="mermaid">`:**
@@ -221,4 +221,4 @@ In this example mermaid.js is referenced in `src` as a separate JavaScript file,
**Comments from Knut Sveidqvist, creator of mermaid:**
- In early versions of mermaid, the `<script>` tag was invoked in the `<head>` part of the web page. Nowadays we can place it in the `<body>` as seen above. Older parts of the documentation frequently reflect the previous way which still works.
- In early versions of mermaid, the `<script>` tag was invoked in the `<head>` part of the web page. Nowadays we can place it in the `<body>` as seen above. Older parts of the documentation frequently reflects the previous way which still works.

View File

@@ -42,12 +42,11 @@ But not having diagrams or docs ruins productivity and hurts organizational lear
Mermaid addresses this problem by enabling users to create easily modifiable diagrams, it can also be made part of production scripts (and other pieces of code).<br/> <br/>
Mermaid allows even non-programmers to easily create detailed and diagrams through the [Mermaid Live Editor](https://mermaid.live/).<br/>
[Tutorials](../config/Tutorials.md) has video tutorials.
Use Mermaid with your favorite applications, check out the list of [Community Integrations](../ecosystem/integrations-community.md).
Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](../ecosystem/integrations.md).
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](../intro/getting-started.md) and [Usage](../config/usage.md).
🌐 [CDN](https://www.jsdelivr.com/package/npm/mermaid) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](../community/development.md) | 🔌 [Plug-Ins](../ecosystem/integrations-community.md)
🌐 [CDN](https://www.jsdelivr.com/package/npm/mermaid) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](../community/development.md) | 🔌 [Plug-Ins](../ecosystem/integrations.md)
> 🖖 Keep a steady pulse: mermaid needs more Collaborators, [Read More](https://github.com/knsv/mermaid/issues/866).
@@ -403,7 +402,7 @@ Update version number in `package.json`.
npm publish
```
The above command generates files into the `dist` folder and publishes them to [npmjs.com](https://www.npmjs.com/).
The above command generates files into the `dist` folder and publishes them to [npmjs.org](npmjs.org).
## Security and safe diagrams

View File

@@ -72,7 +72,7 @@ The following are the most commonly used methods, and they are all tied to Merma
Here you can edit certain values to change the behavior and appearance of the diagram.
### [The initialize() call](./getting-started.md#_3-calling-the-javascript-api)
### [The initialize() call](./getting-started#_3-calling-the-javascript-api)
Used when Mermaid is called via an API, or through a `<script>` tag.

View File

@@ -6,18 +6,8 @@
# Announcements
<br />
## [From Chaos to Clarity: Exploring Mind Maps with MermaidJS](https://www.mermaidchart.com/blog/posts/from-chaos-to-clarity-exploring-mind-maps-with-mermaidjs)
<a href="https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=416671&theme=light" alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
24 July 2023 · 4 mins
## Calling all fans of Mermaid and Mermaid Chart! 🎉
Weve officially made our Product Hunt debut, and would love any and all support from the community!
[Click here](https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-chart) to check out our Product Hunt launch.
Feel free to drop us a comment and let us know what you think. All new sign ups will receive a 30-day free trial of our Pro subscription, plus 25% off your first year.
Were on a mission to make text-based diagramming fun again. And we need your help to make that happen.
Your support means the world to us. Thank you for being part of the diagramming movement.
Introducing the concept of mind mapping as a tool for organizing complex information, and highlights Mermaid as a user-friendly software that simplifies the creation and editing of mind maps for applications in IT solution design, business decision-making, and knowledge organization.

View File

@@ -6,36 +6,6 @@
# Blog
## [How to Make a Git Graph with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-make-a-git-graph-with-mermaid-chart/)
22 September 2023 · 7 mins
A git graph is one of the more useful forms of diagrams for developers and DevOps professionals.
## [Present flow data using Sankey diagrams in Mermaid, thanks to Nikolay Rozhkov](https://www.mermaidchart.com/blog/posts/present-flow-data-using-sankey-diagrams/)
8 September 2023 · 4 mins
Sankey diagrams are a powerful tool for visualizing flow data.
## [Special cases broke Microsoft Zune and can ruin your code base too](https://www.mermaidchart.com/blog/posts/special-cases-broke-microsoft-zune-and-can-ruin-your-code-base-too/)
23 August 2023 · 15 mins
Read about the pitfalls of special cases in programming, illustrating how they can lead to complexity, diminish readability, and create maintenance challenges.
## [New AI chatbot now available on Mermaid Chart to simplify text-based diagram creation](https://www.mermaidchart.com/blog/posts/ai-chatbot-now-available-on-mermaid-chart-to-simplify-text-based-diagram-creation/)
14 August 2023 · 4 mins
Introducing Mermaid Charts new AI chatbot, a diagramming assistant that simplifies text-based diagram creation for everyone, from developers to educators, offering features to start, edit, and fix diagrams, and embodying our vision to make diagramming accessible, user-friendly, and fun.
## [Believe It or Not, You Still Need an Online UML Diagram Tool](https://www.mermaidchart.com/blog/posts/uml-diagram-tool/)
14 August 2023 · 8 mins
A UML diagram tool helps developers and other professionals quickly create and share UML diagrams that communicate information about complex software systems.
## [From Chaos to Clarity: Exploring Mind Maps with MermaidJS](https://www.mermaidchart.com/blog/posts/from-chaos-to-clarity-exploring-mind-maps-with-mermaidjs)
24 July 2023 · 4 mins

View File

@@ -90,7 +90,7 @@ Mermaid syntax for ER diagrams is compatible with PlantUML, with an extension to
Where:
- `first-entity` is the name of an entity. Names must begin with an alphabetic character or an underscore (from v10.5.0+), and may also contain digits and hyphens.
- `first-entity` is the name of an entity. Names must begin with an alphabetic character and may also contain digits, hyphens, and underscores.
- `relationship` describes the way that both entities inter-relate. See below.
- `second-entity` is the name of the other entity.
- `relationship-label` describes the relationship from the perspective of the first entity.
@@ -198,34 +198,6 @@ erDiagram
The `type` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The `name` values follow a similar format to `type`, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
### Entity Name Aliases (v10.5.0+)
An alias can be added to an entity using square brackets. If provided, the alias will be showed in the diagram instead of the entity name.
```mermaid-example
erDiagram
p[Person] {
string firstName
string lastName
}
a["Customer Account"] {
string email
}
p ||--o| a : has
```
```mermaid
erDiagram
p[Person] {
string firstName
string lastName
}
a["Customer Account"] {
string email
}
p ||--o| a : has
```
#### Attribute Keys and Comments
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.

View File

@@ -860,8 +860,8 @@ flowchart LR
C-->D
click A callback "Tooltip for a callback"
click B "https://www.github.com" "This is a tooltip for a link"
click C call callback() "Tooltip for a callback"
click D href "https://www.github.com" "This is a tooltip for a link"
click A call callback() "Tooltip for a callback"
click B href "https://www.github.com" "This is a tooltip for a link"
```
```mermaid
@@ -871,13 +871,13 @@ flowchart LR
C-->D
click A callback "Tooltip for a callback"
click B "https://www.github.com" "This is a tooltip for a link"
click C call callback() "Tooltip for a callback"
click D href "https://www.github.com" "This is a tooltip for a link"
click A call callback() "Tooltip for a callback"
click B href "https://www.github.com" "This is a tooltip for a link"
```
> **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2.
?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/Ogglas/2o73vdez/7).
?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/s37cjoau/3/).
Links are opened in the same browser tab/window by default. It is possible to change this by adding a link target to the click definition (`_self`, `_blank`, `_parent` and `_top` are supported):
@@ -1051,9 +1051,9 @@ flowchart LR
classDef foobar stroke:#00f
```
### CSS classes
### Css classes
It is also possible to predefine classes in CSS styles that can be applied from the graph definition as in the example
It is also possible to predefine classes in css styles that can be applied from the graph definition as in the example
below:
**Example style**
@@ -1098,7 +1098,7 @@ The icons are accessed via the syntax fa:#icon class name#.
```mermaid-example
flowchart TD
B["fa:fa-twitter for peace"]
B["fab:fa-twitter for peace"]
B-->C[fa:fa-ban forbidden]
B-->D(fa:fa-spinner)
B-->E(A fa:fa-camera-retro perhaps?)
@@ -1106,7 +1106,7 @@ flowchart TD
```mermaid
flowchart TD
B["fa:fa-twitter for peace"]
B["fab:fa-twitter for peace"]
B-->C[fa:fa-ban forbidden]
B-->D(fa:fa-spinner)
B-->E(A fa:fa-camera-retro perhaps?)

View File

@@ -241,7 +241,7 @@ The following formatting strings are supported:
More info in: <https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format>
### Axis ticks (v10.3.0+)
### Axis ticks
The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`.
@@ -252,7 +252,7 @@ tickInterval 1day
The pattern is:
```javascript
/^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/;
/^([1-9][0-9]*)(minute|hour|day|week|month)$/;
```
More info in: <https://github.com/d3/d3-time#interval_every>
@@ -271,7 +271,7 @@ gantt
weekday monday
```
> **Warning** > `millisecond` and `second` support was added in vMERMAID_RELEASE_VERSION
Support: v10.3.0+
## Output in compact mode

View File

@@ -58,7 +58,7 @@ mindmap
The syntax for creating Mindmaps is simple and relies on indentation for setting the levels in the hierarchy.
In the following example you can see how there are 3 different levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further than the previous lines defining the nodes B and C.
In the following example you can see how there are 3 different levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further then the previous lines defining the nodes B and C.
mindmap
Root
@@ -66,7 +66,7 @@ In the following example you can see how there are 3 different levels. One with
B
C
In summary is a simple text outline where there is one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
In summary is a simple text outline where there are one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
```mermaid-example
mindmap
@@ -228,7 +228,7 @@ _These classes need to be supplied by the site administrator._
## Unclear indentation
The actual indentation does not really matter only compared with the previous rows. If we take the previous example and disrupt it a little we can see how the calculations are performed. Let us start with placing C with a smaller indentation than `B` but larger then `A`.
The actual indentation does not really matter only compared with the previous rows. If we take the previous example and disrupt it a little we can se how the calculations are performed. Let us start with placing C with a smaller indentation than `B`but larger then `A`.
mindmap
Root

View File

@@ -47,8 +47,8 @@ quadrantChart
## Syntax
> **Note**
> If there are no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
> If there are points **x-axis** labels will rendered from the left of the respective quadrant also they will be displayed at the bottom of the chart, and **y-axis** labels will be rendered at the bottom of the respective quadrant, the quadrant text will render at the top of the respective quadrant.
> If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
> If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
> **Note**
> For points x and y value min value is 0 and max value is 1.
@@ -64,7 +64,7 @@ The title is a short description of the chart and it will always render on top o
### x-axis
The x-axis determines what text would be displayed in the x-axis. In x-axis there is two part **left** and **right** you can pass **both** or you can pass only **left**. The statement should start with `x-axis` then the `left axis text` followed by the delimiter `-->` then `right axis text`.
The x-axis determine what text would be displayed in the x-axis. In x-axis there is two part **left** and **right** you can pass **both** or you can pass only **left**. The statement should start with `x-axis` then the `left axis text` followed by the delimiter `-->` then `right axis text`.
#### Example
@@ -73,7 +73,7 @@ The x-axis determines what text would be displayed in the x-axis. In x-axis ther
### y-axis
The y-axis determines what text would be displayed in the y-axis. In y-axis there is two part **top** and **bottom** you can pass **both** or you can pass only **bottom**. The statement should start with `y-axis` then the `bottom axis text` followed by the delimiter `-->` then `top axis text`.
The y-axis determine what text would be displayed in the y-axis. In y-axis there is two part **top** and **bottom** you can pass **both** or you can pass only **bottom**. The statement should start with `y-axis` then the `bottom axis text` followed by the delimiter `-->` then `top axis text`.
#### Example

View File

@@ -8,8 +8,9 @@
> A sankey diagram is a visualization used to depict a flow from one set of values to another.
> **Warning**
> This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future.
::: warning
This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future.
:::
The things being connected are called nodes and the connections are called links.
@@ -18,11 +19,6 @@ The things being connected are called nodes and the connections are called links
This example taken from [observable](https://observablehq.com/@d3/sankey/2?collection=@d3/d3-sankey). It may be rendered a little bit differently, though, in terms of size and colors.
```mermaid-example
---
config:
sankey:
showValues: false
---
sankey-beta
Agricultural 'waste',Bio-conversion,124.729
@@ -96,11 +92,6 @@ Wind,Electricity grid,289.366
```
```mermaid
---
config:
sankey:
showValues: false
---
sankey-beta
Agricultural 'waste',Bio-conversion,124.729
@@ -206,7 +197,7 @@ Electricity grid,H2 conversion,27.14
### Empty Lines
CSV does not support empty lines without comma delimiters by default. But you can add them if needed:
CSV does not support empty lines without comma delimeters by default. But you can add them if needed:
```mermaid-example
sankey-beta

View File

@@ -164,7 +164,7 @@ The actor(s) can be grouped in vertical boxes. You can define a color (if not, i
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how is Charly?
A->>B: Hello Bob, how is Charly ?
B->>C: Hello Charly, how are you?
```
@@ -180,7 +180,7 @@ The actor(s) can be grouped in vertical boxes. You can define a color (if not, i
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how is Charly?
A->>B: Hello Bob, how is Charly ?
B->>C: Hello Charly, how are you?
```

View File

@@ -1,18 +0,0 @@
# Settings in the [build] context are global and are applied to
# all contexts unless otherwise overridden by more specific contexts.
[build]
# Directory where the build system installs dependencies
# and runs your build. Store your package.json, .nvmrc, etc here.
# If not set, defaults to the root directory.
base = ""
# Directory that contains the deploy-ready HTML files and
# assets generated by the build. This is an absolute path relative
# to the base directory, which is the root by default (/).
# This sample publishes the directory located at the absolute
# path "root/project/build-output"
publish = "mermaid-live-editor/docs"
# Default build command.
command = "./scripts/editor.bash"

View File

@@ -4,7 +4,7 @@
"version": "10.2.4",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"packageManager": "pnpm@8.7.5",
"packageManager": "pnpm@8.6.12",
"keywords": [
"diagram",
"markdown",
@@ -19,7 +19,6 @@
"build:mermaid": "pnpm build:vite --mermaid",
"build:viz": "pnpm build:mermaid --visualize",
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
"build:types:watch": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly --watch",
"build:watch": "pnpm build:vite --watch",
"build": "pnpm run -r clean && pnpm build:types && pnpm build:vite",
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"",

View File

@@ -1 +1 @@
../mermaid/src/docs/syntax/zenuml.md
../mermaid/src/docs/syntax/zenuml.md

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "10.5.0",
"version": "10.3.1",
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
@@ -40,7 +40,7 @@
"types:verify-config": "ts-node-esm scripts/create-types-from-json-schema.mts --verify",
"checkCircle": "npx madge --circular ./src",
"release": "pnpm build",
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm docs:release-version && pnpm -w run build"
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build"
},
"repository": {
"type": "git",
@@ -117,7 +117,7 @@
"rimraf": "^5.0.0",
"start-server-and-test": "^2.0.0",
"type-fest": "^4.1.0",
"typedoc": "^0.25.0",
"typedoc": "^0.24.5",
"typedoc-plugin-markdown": "^3.15.2",
"typescript": "^5.0.4",
"unist-util-flatmap": "^1.0.0",

View File

@@ -21,7 +21,7 @@ const versionPlaceholder = '<MERMAID_RELEASE_VERSION>';
const main = async () => {
const sourceDirGlob = posix.join('.', SOURCE_DOCS_DIR, '**');
const mdFileGlobs = getGlobs([posix.join(sourceDirGlob, '*.md')]);
mdFileGlobs.push('!**/community/development.md', '!**/community/code.md');
mdFileGlobs.push('!**/community/development.md');
const mdFiles = await getFilesFromGlobs(mdFileGlobs);
mdFiles.sort();
const mdFilesWithPlaceholder: string[] = [];

View File

@@ -2,9 +2,11 @@ import * as configApi from './config.js';
import { log } from './logger.js';
import { getDiagram, registerDiagram } from './diagram-api/diagramAPI.js';
import { detectType, getDiagramLoader } from './diagram-api/detectType.js';
import { extractFrontMatter } from './diagram-api/frontmatter.js';
import { UnknownDiagramError } from './errors.js';
import { cleanupComments } from './diagram-api/comments.js';
import type { DetailedError } from './utils.js';
import type { DiagramDefinition, DiagramMetadata } from './diagram-api/types.js';
import type { DiagramDefinition } from './diagram-api/types.js';
export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void;
@@ -20,7 +22,7 @@ export class Diagram {
private init?: DiagramDefinition['init'];
private detectError?: UnknownDiagramError;
constructor(public text: string, public metadata: Pick<DiagramMetadata, 'title'> = {}) {
constructor(public text: string) {
this.text += '\n';
const cnf = configApi.getConfig();
try {
@@ -35,6 +37,19 @@ export class Diagram {
this.db = diagram.db;
this.renderer = diagram.renderer;
this.parser = diagram.parser;
const originalParse = this.parser.parse.bind(this.parser);
// Wrap the jison parse() method to handle extracting frontmatter.
//
// This can't be done in this.parse() because some code
// directly calls diagram.parser.parse(), bypassing this.parse().
//
// Similarly, we can't do this in getDiagramFromText() because some code
// calls diagram.db.clear(), which would reset anything set by
// extractFrontMatter().
this.parser.parse = (text: string) =>
originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective)));
this.parser.parser.yy = this.db;
this.init = diagram.init;
this.parse();
@@ -45,12 +60,7 @@ export class Diagram {
throw this.detectError;
}
this.db.clear?.();
const config = configApi.getConfig();
this.init?.(config);
// This block was added for legacy compatibility. Use frontmatter instead of adding more special cases.
if (this.metadata.title) {
this.db.setDiagramTitle?.(this.metadata.title);
}
this.init?.(configApi.getConfig());
this.parser.parse(this.text);
}
@@ -72,15 +82,11 @@ export class Diagram {
* **Warning:** This function may be changed in the future.
* @alpha
* @param text - The mermaid diagram definition.
* @param metadata - Diagram metadata, defined in YAML.
* @returns A the Promise of a Diagram object.
* @throws {@link UnknownDiagramError} if the diagram type can not be found.
* @privateRemarks This is exported as part of the public mermaidAPI.
*/
export const getDiagramFromText = async (
text: string,
metadata: Pick<DiagramMetadata, 'title'> = {}
): Promise<Diagram> => {
export const getDiagramFromText = async (text: string): Promise<Diagram> => {
const type = detectType(text, configApi.getConfig());
try {
// Trying to find the diagram
@@ -95,5 +101,5 @@ export const getDiagramFromText = async (
const { id, diagram } = await loader();
registerDiagram(id, diagram);
}
return new Diagram(text, metadata);
return new Diagram(text);
};

View File

@@ -13,6 +13,7 @@ export const mermaidAPI = {
svg: '<svg></svg>',
}),
parse: mAPI.parse,
parseDirective: vi.fn(),
initialize: vi.fn(),
getConfig: configApi.getConfig,
setConfig: configApi.setConfig,

View File

@@ -0,0 +1,47 @@
import { sanitizeText as _sanitizeText } from './diagrams/common/common.js';
import { getConfig } from './config.js';
let title = '';
let diagramTitle = '';
let description = '';
const sanitizeText = (txt: string): string => _sanitizeText(txt, getConfig());
export const clear = function (): void {
title = '';
description = '';
diagramTitle = '';
};
export const setAccTitle = function (txt: string): void {
title = sanitizeText(txt).replace(/^\s+/g, '');
};
export const getAccTitle = function (): string {
return title || diagramTitle;
};
export const setAccDescription = function (txt: string): void {
description = sanitizeText(txt).replace(/\n\s+/g, '\n');
};
export const getAccDescription = function (): string {
return description;
};
export const setDiagramTitle = function (txt: string): void {
diagramTitle = sanitizeText(txt);
};
export const getDiagramTitle = function (): string {
return diagramTitle;
};
export default {
getAccTitle,
setAccTitle,
getDiagramTitle,
setDiagramTitle,
getAccDescription,
setAccDescription,
clear,
};

View File

@@ -3,7 +3,7 @@ import { log } from './logger.js';
import theme from './themes/index.js';
import config from './defaultConfig.js';
import type { MermaidConfig } from './config.type.js';
import { sanitizeDirective } from './utils/sanitizeDirective.js';
import { sanitizeDirective } from './utils.js';
export const defaultConfig: MermaidConfig = Object.freeze(config);

View File

@@ -1048,7 +1048,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
* Pattern is:
*
* ```javascript
* /^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/
* /^([1-9][0-9]*)(minute|hour|day|week|month)$/
* ```
*
*/

View File

@@ -5,7 +5,6 @@ import { line, curveBasis, select } from 'd3';
import { getConfig } from '../config.js';
import utils from '../utils.js';
import { evaluate } from '../diagrams/common/common.js';
import { getLineFunctionsWithOffset } from '../utils/lineWithOffset.js';
let edgeLabels = {};
let terminalLabels = {};
@@ -369,7 +368,8 @@ const cutPathAtIntersect = (_points, boundryNode) => {
return points;
};
export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph, id) {
//(edgePaths, e, edge, clusterDb, diagramtype, graph)
export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph) {
let points = edge.points;
let pointsHasChanged = false;
const tail = graph.node(e.v);
@@ -435,16 +435,24 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
const lineData = points.filter((p) => !Number.isNaN(p.y));
// This is the accessor function we talked about above
let curve = curveBasis;
let curve;
// Currently only flowcharts get the curve from the settings, perhaps this should
// be expanded to a common setting? Restricting it for now in order not to cause side-effects that
// have not been thought through
if (edge.curve && (diagramType === 'graph' || diagramType === 'flowchart')) {
curve = edge.curve;
if (diagramType === 'graph' || diagramType === 'flowchart') {
curve = edge.curve || curveBasis;
} else {
curve = curveBasis;
}
const { x, y } = getLineFunctionsWithOffset(edge);
const lineFunction = line().x(x).y(y).curve(curve);
// curve = curveLinear;
const lineFunction = line()
.x(function (d) {
return d.x;
})
.y(function (d) {
return d.y;
})
.curve(curve);
// Construct stroke classes based on properties
let strokeClasses;
@@ -508,103 +516,61 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
switch (edge.arrowTypeStart) {
case 'arrow_cross':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-crossStart' + ')'
);
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')');
break;
case 'arrow_point':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-pointStart' + ')'
);
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')');
break;
case 'arrow_barb':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-barbStart' + ')'
);
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')');
break;
case 'arrow_circle':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-circleStart' + ')'
);
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')');
break;
case 'aggregation':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-aggregationStart' + ')'
);
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-aggregationStart' + ')');
break;
case 'extension':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-extensionStart' + ')'
);
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')');
break;
case 'composition':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-compositionStart' + ')'
);
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')');
break;
case 'dependency':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-dependencyStart' + ')'
);
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-dependencyStart' + ')');
break;
case 'lollipop':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-lollipopStart' + ')'
);
svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-lollipopStart' + ')');
break;
default:
}
switch (edge.arrowTypeEnd) {
case 'arrow_cross':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-crossEnd' + ')');
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')');
break;
case 'arrow_point':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-pointEnd' + ')');
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')');
break;
case 'arrow_barb':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-barbEnd' + ')');
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')');
break;
case 'arrow_circle':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-circleEnd' + ')');
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')');
break;
case 'aggregation':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-aggregationEnd' + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-aggregationEnd' + ')');
break;
case 'extension':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-extensionEnd' + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')');
break;
case 'composition':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-compositionEnd' + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')');
break;
case 'dependency':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-dependencyEnd' + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-dependencyEnd' + ')');
break;
case 'lollipop':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-lollipopEnd' + ')'
);
svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-lollipopEnd' + ')');
break;
default:
}

View File

@@ -14,7 +14,7 @@ import { insertCluster, clear as clearClusters } from './clusters.js';
import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges.js';
import { log } from '../logger.js';
const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) => {
const recursiveRender = async (_elem, graph, diagramtype, parentCluster) => {
log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster);
const dir = graph.graph().rankdir;
log.trace('Dir in recursive render - dir:', dir);
@@ -52,7 +52,7 @@ const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) =>
if (node && node.clusterNode) {
// const children = graph.children(v);
log.info('Cluster identified', v, node.width, graph.node(v));
const o = await recursiveRender(nodes, node.graph, diagramtype, id, graph.node(v));
const o = await recursiveRender(nodes, node.graph, diagramtype, graph.node(v));
const newEl = o.elem;
updateNodeBounds(node, newEl);
node.diff = o.diff || 0;
@@ -134,7 +134,7 @@ const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) =>
const edge = graph.edge(e);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
const paths = insertEdge(edgePaths, e, edge, clusterDb, diagramtype, graph, id);
const paths = insertEdge(edgePaths, e, edge, clusterDb, diagramtype, graph);
positionEdgeLabel(edge, paths);
});
@@ -155,11 +155,11 @@ export const render = async (elem, graph, markers, diagramtype, id) => {
clearClusters();
clearGraphlib();
log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph)));
log.warn('Graph at first:', graphlibJson.write(graph));
adjustClustersAndEdges(graph);
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
log.warn('Graph after:', graphlibJson.write(graph));
// log.warn('Graph ever after:', graphlibJson.write(graph.node('A').graph));
await recursiveRender(elem, graph, diagramtype, id);
await recursiveRender(elem, graph, diagramtype);
};
// const shapeDefinitions = {};

View File

@@ -14,9 +14,9 @@ const extension = (elem, type, id) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-extensionStart')
.attr('id', type + '-extensionStart')
.attr('class', 'marker extension ' + type)
.attr('refX', 18)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
@@ -27,9 +27,9 @@ const extension = (elem, type, id) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-extensionEnd')
.attr('id', type + '-extensionEnd')
.attr('class', 'marker extension ' + type)
.attr('refX', 1)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
@@ -38,13 +38,13 @@ const extension = (elem, type, id) => {
.attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead
};
const composition = (elem, type, id) => {
const composition = (elem, type) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-compositionStart')
.attr('id', type + '-compositionStart')
.attr('class', 'marker composition ' + type)
.attr('refX', 18)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
@@ -55,9 +55,9 @@ const composition = (elem, type, id) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-compositionEnd')
.attr('id', type + '-compositionEnd')
.attr('class', 'marker composition ' + type)
.attr('refX', 1)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
@@ -65,13 +65,13 @@ const composition = (elem, type, id) => {
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
};
const aggregation = (elem, type, id) => {
const aggregation = (elem, type) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-aggregationStart')
.attr('id', type + '-aggregationStart')
.attr('class', 'marker aggregation ' + type)
.attr('refX', 18)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
@@ -82,9 +82,9 @@ const aggregation = (elem, type, id) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-aggregationEnd')
.attr('id', type + '-aggregationEnd')
.attr('class', 'marker aggregation ' + type)
.attr('refX', 1)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
@@ -92,13 +92,13 @@ const aggregation = (elem, type, id) => {
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
};
const dependency = (elem, type, id) => {
const dependency = (elem, type) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-dependencyStart')
.attr('id', type + '-dependencyStart')
.attr('class', 'marker dependency ' + type)
.attr('refX', 6)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
@@ -109,9 +109,9 @@ const dependency = (elem, type, id) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-dependencyEnd')
.attr('id', type + '-dependencyEnd')
.attr('class', 'marker dependency ' + type)
.attr('refX', 13)
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
.attr('markerHeight', 28)
@@ -119,48 +119,31 @@ const dependency = (elem, type, id) => {
.append('path')
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
};
const lollipop = (elem, type, id) => {
const lollipop = (elem, type) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-lollipopStart')
.attr('id', type + '-lollipopStart')
.attr('class', 'marker lollipop ' + type)
.attr('refX', 13)
.attr('refX', 0)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('circle')
.attr('stroke', 'black')
.attr('fill', 'transparent')
.attr('cx', 7)
.attr('cy', 7)
.attr('r', 6);
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-lollipopEnd')
.attr('class', 'marker lollipop ' + type)
.attr('refX', 1)
.attr('refY', 7)
.attr('markerWidth', 190)
.attr('markerHeight', 240)
.attr('orient', 'auto')
.append('circle')
.attr('stroke', 'black')
.attr('fill', 'transparent')
.attr('cx', 7)
.attr('fill', 'white')
.attr('cx', 6)
.attr('cy', 7)
.attr('r', 6);
};
const point = (elem, type, id) => {
const point = (elem, type) => {
elem
.append('marker')
.attr('id', id + '_' + type + '-pointEnd')
.attr('id', type + '-pointEnd')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 6)
.attr('refX', 10)
.attr('refY', 5)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
@@ -173,10 +156,10 @@ const point = (elem, type, id) => {
.style('stroke-dasharray', '1,0');
elem
.append('marker')
.attr('id', id + '_' + type + '-pointStart')
.attr('id', type + '-pointStart')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 4.5)
.attr('refX', 0)
.attr('refY', 5)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
@@ -188,10 +171,10 @@ const point = (elem, type, id) => {
.style('stroke-width', 1)
.style('stroke-dasharray', '1,0');
};
const circle = (elem, type, id) => {
const circle = (elem, type) => {
elem
.append('marker')
.attr('id', id + '_' + type + '-circleEnd')
.attr('id', type + '-circleEnd')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 11)
@@ -210,7 +193,7 @@ const circle = (elem, type, id) => {
elem
.append('marker')
.attr('id', id + '_' + type + '-circleStart')
.attr('id', type + '-circleStart')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', -1)
@@ -227,10 +210,10 @@ const circle = (elem, type, id) => {
.style('stroke-width', 1)
.style('stroke-dasharray', '1,0');
};
const cross = (elem, type, id) => {
const cross = (elem, type) => {
elem
.append('marker')
.attr('id', id + '_' + type + '-crossEnd')
.attr('id', type + '-crossEnd')
.attr('class', 'marker cross ' + type)
.attr('viewBox', '0 0 11 11')
.attr('refX', 12)
@@ -248,7 +231,7 @@ const cross = (elem, type, id) => {
elem
.append('marker')
.attr('id', id + '_' + type + '-crossStart')
.attr('id', type + '-crossStart')
.attr('class', 'marker cross ' + type)
.attr('viewBox', '0 0 11 11')
.attr('refX', -1)
@@ -264,11 +247,11 @@ const cross = (elem, type, id) => {
.style('stroke-width', 2)
.style('stroke-dasharray', '1,0');
};
const barb = (elem, type, id) => {
const barb = (elem, type) => {
elem
.append('defs')
.append('marker')
.attr('id', id + '_' + type + '-barbEnd')
.attr('id', type + '-barbEnd')
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)

View File

@@ -291,8 +291,8 @@ export const adjustClustersAndEdges = (graph, depth) => {
shape: 'labelRect',
style: '',
});
const edge1 = structuredClone(edge);
const edge2 = structuredClone(edge);
const edge1 = JSON.parse(JSON.stringify(edge));
const edge2 = JSON.parse(JSON.stringify(edge));
edge1.label = '';
edge1.arrowTypeEnd = 'none';
edge2.label = '';

View File

@@ -5,6 +5,7 @@ import { getConfig } from '../config.js';
import intersect from './intersect/index.js';
import createLabel from './createLabel.js';
import note from './shapes/note.js';
import { parseMember } from '../diagrams/class/svgDraw.js';
import { evaluate } from '../diagrams/common/common.js';
const formatClass = (str) => {
@@ -879,8 +880,8 @@ const class_box = (parent, node) => {
maxWidth = classTitleBBox.width;
}
const classAttributes = [];
node.classData.members.forEach((member) => {
const parsedInfo = member.getDisplayDetails();
node.classData.members.forEach((str) => {
const parsedInfo = parseMember(str);
let parsedText = parsedInfo.displayText;
if (getConfig().flowchart.htmlLabels) {
parsedText = parsedText.replace(/</g, '&lt;').replace(/>/g, '&gt;');
@@ -913,8 +914,8 @@ const class_box = (parent, node) => {
maxHeight += lineHeight;
const classMethods = [];
node.classData.methods.forEach((member) => {
const parsedInfo = member.getDisplayDetails();
node.classData.methods.forEach((str) => {
const parsedInfo = parseMember(str);
let displayText = parsedInfo.displayText;
if (getConfig().flowchart.htmlLabels) {
displayText = displayText.replace(/</g, '&lt;').replace(/>/g, '&gt;');

View File

@@ -4,5 +4,5 @@
* @returns cleaned text
*/
export const cleanupComments = (text: string): string => {
return text.replace(/^\s*%%(?!{)[^\n]+\n?/gm, '').trimStart();
return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, '');
};

View File

@@ -43,11 +43,7 @@ export const addDiagrams = () => {
},
},
styles: {}, // should never be used
renderer: {
draw: () => {
// should never be used
},
},
renderer: {}, // should never be used
parser: {
parser: { yy: {} },
parse: () => {

View File

@@ -41,11 +41,7 @@ describe('DiagramAPI', () => {
},
parser: { yy: {} },
},
renderer: {
draw: () => {
// no-op
},
},
renderer: {},
styles: {},
},
detector

View File

@@ -5,7 +5,8 @@ import { sanitizeText as _sanitizeText } from '../diagrams/common/common.js';
import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox.js';
import { addStylesForDiagram } from '../styles.js';
import type { DiagramDefinition, DiagramDetector } from './types.js';
import * as _commonDb from '../diagrams/common/commonDb.js';
import * as _commonDb from '../commonDb.js';
import { parseDirective as _parseDirective } from '../directiveUtils.js';
/*
Packaging and exposing resources for external diagrams so that they can import
@@ -20,6 +21,8 @@ export const setupGraphViewbox = _setupGraphViewbox;
export const getCommonDb = () => {
return _commonDb;
};
export const parseDirective = (p: any, statement: string, context: string, type: string) =>
_parseDirective(p, statement, context, type);
const diagrams: Record<string, DiagramDefinition> = {};
export interface Detectors {
@@ -49,18 +52,17 @@ export const registerDiagram = (
}
addStylesForDiagram(id, diagram.styles);
diagram.injectUtils?.(
log,
setLogLevel,
getConfig,
sanitizeText,
setupGraphViewbox,
getCommonDb(),
() => {
// parseDirective is removed in https://github.com/mermaid-js/mermaid/pull/4759.
// This is a no-op for legacy support.
}
);
if (diagram.injectUtils) {
diagram.injectUtils(
log,
setLogLevel,
getConfig,
sanitizeText,
setupGraphViewbox,
getCommonDb(),
parseDirective
);
}
};
export const getDiagram = (name: string): DiagramDefinition => {

View File

@@ -1,139 +1,84 @@
import { vi } from 'vitest';
import { extractFrontMatter } from './frontmatter.js';
const dbMock = () => ({ setDiagramTitle: vi.fn() });
const setConfigMock = vi.fn();
describe('extractFrontmatter', () => {
beforeEach(() => {
setConfigMock.mockClear();
});
it('returns text unchanged if no frontmatter', () => {
expect(extractFrontMatter('diagram')).toMatchInlineSnapshot(`
{
"metadata": {},
"text": "diagram",
}
`);
expect(extractFrontMatter('diagram', dbMock())).toEqual('diagram');
});
it('returns text unchanged if frontmatter lacks closing delimiter', () => {
const text = `---\ntitle: foo\ndiagram`;
expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
{
"metadata": {},
"text": "---
title: foo
diagram",
}
`);
expect(extractFrontMatter(text, dbMock())).toEqual(text);
});
it('handles empty frontmatter', () => {
const db = dbMock();
const text = `---\n\n---\ndiagram`;
expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
{
"metadata": {},
"text": "diagram",
}
`);
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).not.toHaveBeenCalled();
});
it('handles frontmatter without mappings', () => {
expect(extractFrontMatter(`---\n1\n---\ndiagram`)).toMatchInlineSnapshot(`
{
"metadata": {},
"text": "diagram",
}
`);
expect(extractFrontMatter(`---\n-1\n-2\n---\ndiagram`)).toMatchInlineSnapshot(`
{
"metadata": {},
"text": "diagram",
}
`);
expect(extractFrontMatter(`---\nnull\n---\ndiagram`)).toMatchInlineSnapshot(`
{
"metadata": {},
"text": "diagram",
}
`);
const db = dbMock();
const text = `---\n1\n---\ndiagram`;
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).not.toHaveBeenCalled();
});
it('does not try to parse frontmatter at the end', () => {
const db = dbMock();
const text = `diagram\n---\ntitle: foo\n---\n`;
expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
{
"metadata": {},
"text": "diagram
---
title: foo
---
",
}
`);
expect(extractFrontMatter(text, db)).toEqual(text);
expect(db.setDiagramTitle).not.toHaveBeenCalled();
});
it('handles frontmatter with multiple delimiters', () => {
const db = dbMock();
const text = `---\ntitle: foo---bar\n---\ndiagram\n---\ntest`;
expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
{
"metadata": {
"title": "foo---bar",
},
"text": "diagram
---
test",
}
`);
expect(extractFrontMatter(text, db)).toEqual('diagram\n---\ntest');
expect(db.setDiagramTitle).toHaveBeenCalledWith('foo---bar');
});
it('handles frontmatter with multi-line string and multiple delimiters', () => {
const db = dbMock();
const text = `---\ntitle: |\n multi-line string\n ---\n---\ndiagram`;
expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
{
"metadata": {
"title": "multi-line string
---
",
},
"text": "diagram",
}
`);
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).toHaveBeenCalledWith('multi-line string\n---\n');
});
it('handles frontmatter with title', () => {
const db = dbMock();
const text = `---\ntitle: foo\n---\ndiagram`;
expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
{
"metadata": {
"title": "foo",
},
"text": "diagram",
}
`);
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).toHaveBeenCalledWith('foo');
});
it('handles booleans in frontmatter properly', () => {
const db = dbMock();
const text = `---\ntitle: true\n---\ndiagram`;
expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
{
"metadata": {
"title": "true",
},
"text": "diagram",
}
`);
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).toHaveBeenCalledWith('true');
});
it('ignores unspecified frontmatter keys', () => {
const db = dbMock();
const text = `---\ninvalid: true\ntitle: foo\ntest: bar\n---\ndiagram`;
expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
{
"metadata": {
"title": "foo",
},
"text": "diagram",
}
`);
expect(extractFrontMatter(text, db)).toEqual('diagram');
expect(db.setDiagramTitle).toHaveBeenCalledWith('foo');
});
it('throws exception for invalid YAML syntax', () => {
const text = `---\n!!!\n---\ndiagram`;
expect(() => extractFrontMatter(text)).toThrow('tag suffix cannot contain exclamation marks');
expect(() => extractFrontMatter(text, dbMock())).toThrow(
'tag suffix cannot contain exclamation marks'
);
});
it('handles frontmatter with config', () => {
@@ -147,25 +92,9 @@ config:
array: [1, 2, 3]
---
diagram`;
expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
{
"metadata": {
"config": {
"graph": {
"array": [
1,
2,
3,
],
"boolean": false,
"number": 14,
"string": "hello",
},
},
"title": "hello",
},
"text": "diagram",
}
`);
expect(extractFrontMatter(text, {}, setConfigMock)).toEqual('diagram');
expect(setConfigMock).toHaveBeenCalledWith({
graph: { string: 'hello', number: 14, boolean: false, array: [1, 2, 3] },
});
});
});

View File

@@ -1,5 +1,6 @@
import type { MermaidConfig } from '../config.type.js';
import { frontMatterRegex } from './regexes.js';
import type { DiagramDB } from './types.js';
// The "* as yaml" part is necessary for tree-shaking
import * as yaml from 'js-yaml';
@@ -10,51 +11,43 @@ interface FrontMatterMetadata {
config?: MermaidConfig;
}
export interface FrontMatterResult {
text: string;
metadata: FrontMatterMetadata;
}
/**
* Extract and parse frontmatter from text, if present, and sets appropriate
* properties in the provided db.
* @param text - The text that may have a YAML frontmatter.
* @param db - Diagram database, could be of any diagram.
* @param setDiagramConfig - Optional function to set diagram config.
* @returns text with frontmatter stripped out
*/
export function extractFrontMatter(text: string): FrontMatterResult {
export function extractFrontMatter(
text: string,
db: DiagramDB,
setDiagramConfig?: (config: MermaidConfig) => void
): string {
const matches = text.match(frontMatterRegex);
if (!matches) {
return {
text,
metadata: {},
};
return text;
}
let parsed: FrontMatterMetadata =
yaml.load(matches[1], {
// To support config, we need JSON schema.
// https://www.yaml.org/spec/1.2/spec.html#id2803231
schema: yaml.JSON_SCHEMA,
}) ?? {};
const parsed: FrontMatterMetadata = yaml.load(matches[1], {
// To support config, we need JSON schema.
// https://www.yaml.org/spec/1.2/spec.html#id2803231
schema: yaml.JSON_SCHEMA,
}) as FrontMatterMetadata;
// To handle runtime data type changes
parsed = typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
const metadata: FrontMatterMetadata = {};
// Only add properties that are explicitly supported, if they exist
if (parsed.displayMode) {
metadata.displayMode = parsed.displayMode.toString();
}
if (parsed.title) {
metadata.title = parsed.title.toString();
}
if (parsed.config) {
metadata.config = parsed.config;
if (parsed?.title) {
// toString() is necessary because YAML could parse the title as a number/boolean
db.setDiagramTitle?.(parsed.title.toString());
}
return {
text: text.slice(matches[0].length),
metadata,
};
if (parsed?.displayMode) {
// toString() is necessary because YAML could parse the title as a number/boolean
db.setDisplayMode?.(parsed.displayMode.toString());
}
if (parsed?.config) {
setDiagramConfig?.(parsed.config);
}
return text.slice(matches[0].length);
}

View File

@@ -1,13 +1,7 @@
/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Diagram } from '../Diagram.js';
import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js';
import type * as d3 from 'd3';
export interface DiagramMetadata {
title?: string;
config?: MermaidConfig;
}
export interface InjectUtils {
_log: any;
_setLogLevel: any;
@@ -15,7 +9,6 @@ export interface InjectUtils {
_sanitizeText: any;
_setupGraphViewbox: any;
_commonDb: any;
/** @deprecated as directives will be pre-processed since https://github.com/mermaid-js/mermaid/pull/4759 */
_parseDirective: any;
}
@@ -39,26 +32,9 @@ export interface DiagramDB {
bindFunctions?: (element: Element) => void;
}
// This is what is returned from getClasses(...) methods.
// It is slightly renamed to ..StyleClassDef instead of just ClassDef because "class" is a greatly ambiguous and overloaded word.
// It makes it clear we're working with a style class definition, even though defining the type is currently difficult.
export interface DiagramStyleClassDef {
id: string;
styles?: string[];
textStyles?: string[];
}
export interface DiagramRenderer {
draw: DrawDefinition;
getClasses?: (
text: string,
diagram: Pick<DiagramDefinition, 'db'>
) => Record<string, DiagramStyleClassDef>;
}
export interface DiagramDefinition {
db: DiagramDB;
renderer: DiagramRenderer;
renderer: any;
parser: ParserDefinition;
styles?: any;
init?: (config: MermaidConfig) => void;
@@ -69,7 +45,6 @@ export interface DiagramDefinition {
_sanitizeText: InjectUtils['_sanitizeText'],
_setupGraphViewbox: InjectUtils['_setupGraphViewbox'],
_commonDb: InjectUtils['_commonDb'],
/** @deprecated as directives will be pre-processed since https://github.com/mermaid-js/mermaid/pull/4759 */
_parseDirective: InjectUtils['_parseDirective']
) => void;
}
@@ -101,13 +76,22 @@ export type DrawDefinition = (
id: string,
version: string,
diagramObject: Diagram
) => void | Promise<void>;
) => void;
export interface ParserDefinition {
parse: (text: string) => void;
parser: { yy: DiagramDB };
}
/**
* Type for function parse directive from diagram code.
*
* @param statement -
* @param context -
* @param type -
*/
export type ParseDirectiveDefinition = (statement: string, context: string, type: string) => void;
export type HTML = d3.Selection<HTMLIFrameElement, unknown, Element | null, unknown>;
export type SVG = d3.Selection<SVGSVGElement, unknown, Element | null, unknown>;

View File

@@ -34,11 +34,7 @@ describe('diagram detection', () => {
yy: {},
},
},
renderer: {
draw: () => {
// no-op
},
},
renderer: {},
styles: {},
},
})

View File

@@ -1,11 +1,7 @@
import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import { sanitizeText } from '../common/common.js';
import {
setAccTitle,
getAccTitle,
getAccDescription,
setAccDescription,
} from '../common/commonDb.js';
import { setAccTitle, getAccTitle, getAccDescription, setAccDescription } from '../../commonDb.js';
let c4ShapeArray = [];
let boundaryParseStack = [''];
@@ -37,6 +33,10 @@ export const setC4Type = function (c4TypeParam) {
c4Type = sanitizedText;
};
export const parseDirective = function (statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
//type, from, to, label, ?techn, ?descr, ?sprite, ?tags, $link
export const addRel = function (type, from, to, label, techn, descr, sprite, tags, link) {
// Don't allow label nulling
@@ -816,6 +816,7 @@ export default {
getAccTitle,
getAccDescription,
setAccDescription,
parseDirective,
getConfig: () => configApi.getConfig().c4,
clear,
LINETYPE,

View File

@@ -1,18 +1,17 @@
// @ts-ignore: JISON doesn't support types
import parser from './parser/c4Diagram.jison';
import db from './c4Db.js';
import renderer from './c4Renderer.js';
import styles from './styles.js';
import c4Parser from './parser/c4Diagram.jison';
import c4Db from './c4Db.js';
import c4Renderer from './c4Renderer.js';
import c4Styles from './styles.js';
import type { MermaidConfig } from '../../config.type.js';
import type { DiagramDefinition } from '../../diagram-api/types.js';
export const diagram: DiagramDefinition = {
parser,
db,
renderer,
styles,
init: ({ c4, wrap }: MermaidConfig) => {
renderer.setConf(c4);
db.setWrap(wrap);
parser: c4Parser,
db: c4Db,
renderer: c4Renderer,
styles: c4Styles,
init: (cnf: MermaidConfig) => {
c4Renderer.setConf(cnf.c4);
},
};

View File

@@ -72,16 +72,25 @@
%x string_kv_key
%x string_kv_value
%x open_directive
%x type_directive
%x arg_directive
%x close_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
.*direction\s+TB[^\n]* return 'direction_tb';
.*direction\s+BT[^\n]* return 'direction_bt';
.*direction\s+RL[^\n]* return 'direction_rl';
.*direction\s+LR[^\n]* return 'direction_lr';
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
"title"\s[^#\n;]+ return 'title';
@@ -198,6 +207,7 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
start
: mermaidDoc
| direction
| directive start
;
direction
@@ -215,6 +225,26 @@ mermaidDoc
: graphConfig
;
directive
: openDirective typeDirective closeDirective NEWLINE
| openDirective typeDirective ':' argDirective closeDirective NEWLINE
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'c4Context'); }
;
graphConfig
: C4_CONTEXT NEWLINE statements EOF {yy.setC4Type($1)}

View File

@@ -1,9 +1,11 @@
// @ts-nocheck - don't check until handle it
import type { Selection } from 'd3';
import { select } from 'd3';
import { log } from '../../logger.js';
import * as configApi from '../../config.js';
import common from '../common/common.js';
import utils from '../../utils.js';
import mermaidAPI from '../../mermaidAPI.js';
import {
setAccTitle,
getAccTitle,
@@ -12,8 +14,7 @@ import {
clear as commonClear,
setDiagramTitle,
getDiagramTitle,
} from '../common/commonDb.js';
import { ClassMember } from './classTypes.js';
} from '../../commonDb.js';
import type {
ClassRelation,
ClassNode,
@@ -36,8 +37,12 @@ let functions: any[] = [];
const sanitizeText = (txt: string) => common.sanitizeText(txt, configApi.getConfig());
const splitClassNameAndType = function (_id: string) {
const id = common.sanitizeText(_id, configApi.getConfig());
export const parseDirective = function (statement: string, context: string, type: string) {
// @ts-ignore Don't wanna mess it up
mermaidAPI.parseDirective(this, statement, context, type);
};
const splitClassNameAndType = function (id: string) {
let genericType = '';
let className = id;
@@ -50,8 +55,7 @@ const splitClassNameAndType = function (_id: string) {
return { className: className, type: genericType };
};
export const setClassLabel = function (_id: string, label: string) {
const id = common.sanitizeText(_id, configApi.getConfig());
export const setClassLabel = function (id: string, label: string) {
if (label) {
label = sanitizeText(label);
}
@@ -66,25 +70,22 @@ export const setClassLabel = function (_id: string, label: string) {
* @param id - Id of the class to add
* @public
*/
export const addClass = function (_id: string) {
const id = common.sanitizeText(_id, configApi.getConfig());
const { className, type } = splitClassNameAndType(id);
export const addClass = function (id: string) {
const classId = splitClassNameAndType(id);
// Only add class if not exists
if (Object.hasOwn(classes, className)) {
if (classes[classId.className] !== undefined) {
return;
}
// alert('Adding class: ' + className);
const name = common.sanitizeText(className, configApi.getConfig());
// alert('Adding class after: ' + name);
classes[name] = {
id: name,
type: type,
label: name,
classes[classId.className] = {
id: classId.className,
type: classId.type,
label: classId.className,
cssClasses: [],
methods: [],
members: [],
annotations: [],
domId: MERMAID_DOM_ID_PREFIX + name + '-' + classCounter,
domId: MERMAID_DOM_ID_PREFIX + classId.className + '-' + classCounter,
} as ClassNode;
classCounter++;
@@ -96,8 +97,7 @@ export const addClass = function (_id: string) {
* @param id - class ID to lookup
* @public
*/
export const lookUpDomId = function (_id: string): string {
const id = common.sanitizeText(_id, configApi.getConfig());
export const lookUpDomId = function (id: string): string {
if (id in classes) {
return classes[id].domId;
}
@@ -115,11 +115,11 @@ export const clear = function () {
commonClear();
};
export const getClass = function (id: string): ClassNode {
export const getClass = function (id: string) {
return classes[id];
};
export const getClasses = function (): ClassMap {
export const getClasses = function () {
return classes;
};
@@ -175,8 +175,6 @@ export const addAnnotation = function (className: string, annotation: string) {
* @public
*/
export const addMember = function (className: string, member: string) {
addClass(className);
const validatedClassName = splitClassNameAndType(className).className;
const theClass = classes[validatedClassName];
@@ -189,9 +187,9 @@ export const addMember = function (className: string, member: string) {
theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2)));
} else if (memberString.indexOf(')') > 0) {
//its a method
theClass.methods.push(new ClassMember(memberString, 'method'));
theClass.methods.push(sanitizeText(memberString));
} else if (memberString) {
theClass.members.push(new ClassMember(memberString, 'attribute'));
theClass.members.push(sanitizeText(memberString));
}
}
};
@@ -258,7 +256,6 @@ export const getTooltip = function (id: string, namespace?: string) {
return classes[id].tooltip;
};
/**
* Called by parser when a link is found. Adds the URL to the vertex data.
*
@@ -302,8 +299,7 @@ export const setClickEvent = function (ids: string, functionName: string, functi
setCssClass(ids, 'clickable');
};
const setClickFunc = function (_domId: string, functionName: string, functionArgs: string) {
const domId = common.sanitizeText(_domId, configApi.getConfig());
const setClickFunc = function (domId: string, functionName: string, functionArgs: string) {
const config = configApi.getConfig();
if (config.securityLevel !== 'loose') {
return;
@@ -372,7 +368,6 @@ export const relationType = {
const setupToolTips = function (element: Element) {
let tooltipElem: Selection<HTMLDivElement, unknown, HTMLElement, unknown> =
select('.mermaidTooltip');
// @ts-expect-error - Incorrect types
if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
tooltipElem = select('body').append('div').attr('class', 'mermaidTooltip').style('opacity', 0);
}
@@ -382,6 +377,7 @@ const setupToolTips = function (element: Element) {
const nodes = svg.selectAll('g.node');
nodes
.on('mouseover', function () {
// @ts-expect-error - select is not part of the d3 type definition
const el = select(this);
const title = el.attr('title');
// Don't try to draw a tooltip if no data is provided
@@ -391,7 +387,6 @@ const setupToolTips = function (element: Element) {
// @ts-ignore - getBoundingClientRect is not part of the d3 type definition
const rect = this.getBoundingClientRect();
// @ts-expect-error - Incorrect types
tooltipElem.transition().duration(200).style('opacity', '.9');
tooltipElem
.text(el.attr('title'))
@@ -401,8 +396,8 @@ const setupToolTips = function (element: Element) {
el.classed('hover', true);
})
.on('mouseout', function () {
// @ts-expect-error - Incorrect types
tooltipElem.transition().duration(500).style('opacity', 0);
// @ts-expect-error - select is not part of the d3 type definition
const el = select(this);
el.classed('hover', false);
});
@@ -461,6 +456,7 @@ export const addClassesToNamespace = function (id: string, classNames: string[])
};
export default {
parseDirective,
setAccTitle,
getAccTitle,
getAccDescription,

View File

@@ -4,9 +4,6 @@ import classDb from './classDb.js';
import { vi, describe, it, expect } from 'vitest';
const spyOn = vi.spyOn;
const staticCssStyle = 'text-decoration:underline;';
const abstractCssStyle = 'font-style:italic;';
describe('given a basic class diagram, ', function () {
describe('when parsing class definition', function () {
beforeEach(function () {
@@ -130,7 +127,7 @@ describe('given a basic class diagram, ', function () {
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1);
expect(c1.members[0].getDisplayDetails().displayText).toBe('member1');
expect(c1.members[0]).toBe('member1');
});
it('should parse a class with a text label, member and annotation', () => {
@@ -145,7 +142,7 @@ describe('given a basic class diagram, ', function () {
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1);
expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
expect(c1.members[0]).toBe('int member1');
expect(c1.annotations.length).toBe(1);
expect(c1.annotations[0]).toBe('interface');
});
@@ -171,7 +168,7 @@ describe('given a basic class diagram, ', function () {
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.members[0].getDisplayDetails().displayText).toBe('int member1');
expect(c1.members[0]).toBe('int member1');
expect(c1.cssClasses[0]).toBe('styleClass');
});
@@ -374,74 +371,6 @@ class C13["With Città foreign language"]
note ${keyword}`;
expect(() => parser.parse(str)).toThrowError(/(Expecting\s'STR'|Unrecognized\stext)/);
});
it('should parse diagram with direction', () => {
parser.parse(`classDiagram
direction TB
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides`);
expect(Object.keys(classDb.getClasses()).length).toBe(3);
expect(classDb.getClasses().Student).toMatchInlineSnapshot(`
{
"annotations": [],
"cssClasses": [],
"domId": "classId-Student-134",
"id": "Student",
"label": "Student",
"members": [
ClassMember {
"classifier": "",
"id": "idCard : IdCard",
"memberType": "attribute",
"visibility": "-",
},
],
"methods": [],
"type": "",
}
`);
expect(classDb.getRelations().length).toBe(2);
expect(classDb.getRelations()).toMatchInlineSnapshot(`
[
{
"id1": "Student",
"id2": "IdCard",
"relation": {
"lineType": 0,
"type1": "none",
"type2": 0,
},
"relationTitle1": "1",
"relationTitle2": "1",
"title": "carries",
},
{
"id1": "Student",
"id2": "Bike",
"relation": {
"lineType": 0,
"type1": "none",
"type2": 0,
},
"relationTitle1": "1",
"relationTitle2": "1",
"title": "rides",
},
]
`);
});
});
describe('when parsing class defined in brackets', function () {
@@ -493,8 +422,8 @@ class C13["With Città foreign language"]
'classDiagram\n' +
'class Class1 {\n' +
'int testMember\n' +
'test()\n' +
'string fooMember\n' +
'test()\n' +
'foo()\n' +
'}';
parser.parse(str);
@@ -502,10 +431,10 @@ class C13["With Città foreign language"]
const actual = parser.yy.getClass('Class1');
expect(actual.members.length).toBe(2);
expect(actual.methods.length).toBe(2);
expect(actual.members[0].getDisplayDetails().displayText).toBe('int testMember');
expect(actual.members[1].getDisplayDetails().displayText).toBe('string fooMember');
expect(actual.methods[0].getDisplayDetails().displayText).toBe('test()');
expect(actual.methods[1].getDisplayDetails().displayText).toBe('foo()');
expect(actual.members[0]).toBe('int testMember');
expect(actual.members[1]).toBe('string fooMember');
expect(actual.methods[0]).toBe('test()');
expect(actual.methods[1]).toBe('foo()');
});
it('should parse a class with a text label and members', () => {
@@ -515,7 +444,7 @@ class C13["With Città foreign language"]
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1);
expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1');
expect(c1.members[0]).toBe('+member1');
});
it('should parse a class with a text label, members and annotation', () => {
@@ -530,7 +459,7 @@ class C13["With Città foreign language"]
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1);
expect(c1.members[0].getDisplayDetails().displayText).toBe('+member1');
expect(c1.members[0]).toBe('+member1');
expect(c1.annotations.length).toBe(1);
expect(c1.annotations[0]).toBe('interface');
});
@@ -813,20 +742,6 @@ describe('given a class diagram with members and methods ', function () {
parser.parse(str);
});
it('should handle direct member declaration', function () {
parser.parse('classDiagram\n' + 'Car : wheels');
const car = classDb.getClass('Car');
expect(car.members.length).toBe(1);
expect(car.members[0].id).toBe('wheels');
});
it('should handle direct member declaration with type', function () {
parser.parse('classDiagram\n' + 'Car : int wheels');
const car = classDb.getClass('Car');
expect(car.members.length).toBe(1);
expect(car.members[0].id).toBe('int wheels');
});
it('should handle simple member declaration with type', function () {
const str = 'classDiagram\n' + 'class Car\n' + 'Car : int wheels';
@@ -847,10 +762,10 @@ describe('given a class diagram with members and methods ', function () {
const actual = parser.yy.getClass('actual');
expect(actual.members.length).toBe(4);
expect(actual.methods.length).toBe(0);
expect(actual.members[0].getDisplayDetails().displayText).toBe('-int privateMember');
expect(actual.members[1].getDisplayDetails().displayText).toBe('+int publicMember');
expect(actual.members[2].getDisplayDetails().displayText).toBe('#int protectedMember');
expect(actual.members[3].getDisplayDetails().displayText).toBe('~int privatePackage');
expect(actual.members[0]).toBe('-int privateMember');
expect(actual.members[1]).toBe('+int publicMember');
expect(actual.members[2]).toBe('#int protectedMember');
expect(actual.members[3]).toBe('~int privatePackage');
});
it('should handle generic types', function () {
@@ -903,9 +818,7 @@ describe('given a class diagram with members and methods ', function () {
expect(actual.annotations.length).toBe(0);
expect(actual.members.length).toBe(0);
expect(actual.methods.length).toBe(1);
const method = actual.methods[0];
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
expect(method.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
expect(actual.methods[0]).toBe('someMethod()*');
});
it('should handle static methods', function () {
@@ -916,9 +829,7 @@ describe('given a class diagram with members and methods ', function () {
expect(actual.annotations.length).toBe(0);
expect(actual.members.length).toBe(0);
expect(actual.methods.length).toBe(1);
const method = actual.methods[0];
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
expect(method.getDisplayDetails().cssStyle).toBe(staticCssStyle);
expect(actual.methods[0]).toBe('someMethod()$');
});
it('should handle generic types in arguments', function () {
@@ -1044,6 +955,53 @@ foo()
parser.parse(str);
});
});
describe('when parsing invalid generic classes', function () {
beforeEach(function () {
classDb.clear();
parser.yy = classDb;
});
it('should break when another `{`is encountered before closing the first one while defining generic class with brackets', function () {
const str =
'classDiagram\n' +
'class Dummy_Class~T~ {\n' +
'String data\n' +
' void methods()\n' +
'}\n' +
'\n' +
'class Dummy_Class {\n' +
'class Flight {\n' +
' flightNumber : Integer\n' +
' departureTime : Date\n' +
'}';
let testPassed = false;
try {
parser.parse(str);
} catch (error) {
testPassed = true;
}
expect(testPassed).toBe(true);
});
it('should break when EOF is encountered before closing the first `{` while defining generic class with brackets', function () {
const str =
'classDiagram\n' +
'class Dummy_Class~T~ {\n' +
'String data\n' +
' void methods()\n' +
'}\n' +
'\n' +
'class Dummy_Class {\n';
let testPassed = false;
try {
parser.parse(str);
} catch (error) {
testPassed = true;
}
expect(testPassed).toBe(true);
});
});
});
describe('given a class diagram with relationships, ', function () {
@@ -1316,10 +1274,10 @@ describe('given a class diagram with relationships, ', function () {
const testClass = parser.yy.getClass('Class1');
expect(testClass.members.length).toBe(2);
expect(testClass.methods.length).toBe(2);
expect(testClass.members[0].getDisplayDetails().displayText).toBe('int : test');
expect(testClass.members[1].getDisplayDetails().displayText).toBe('string : foo');
expect(testClass.methods[0].getDisplayDetails().displayText).toBe('test()');
expect(testClass.methods[1].getDisplayDetails().displayText).toBe('foo()');
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 () {
@@ -1330,9 +1288,7 @@ describe('given a class diagram with relationships, ', function () {
expect(testClass.annotations.length).toBe(0);
expect(testClass.members.length).toBe(0);
expect(testClass.methods.length).toBe(1);
const method = testClass.methods[0];
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
expect(method.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
expect(testClass.methods[0]).toBe('someMethod()*');
});
it('should handle static methods', function () {
@@ -1343,9 +1299,7 @@ describe('given a class diagram with relationships, ', function () {
expect(testClass.annotations.length).toBe(0);
expect(testClass.members.length).toBe(0);
expect(testClass.methods.length).toBe(1);
const method = testClass.methods[0];
expect(method.getDisplayDetails().displayText).toBe('someMethod()');
expect(method.getDisplayDetails().cssStyle).toBe(staticCssStyle);
expect(testClass.methods[0]).toBe('someMethod()$');
});
it('should associate link and css appropriately', function () {
@@ -1560,19 +1514,11 @@ class Class2
const testClasses = parser.yy.getClasses();
const testRelations = parser.yy.getRelations();
expect(Object.keys(testNamespaceA.classes).length).toBe(2);
expect(testNamespaceA.classes['A1'].members[0].getDisplayDetails().displayText).toBe(
'+foo : string'
);
expect(testNamespaceA.classes['A2'].members[0].getDisplayDetails().displayText).toBe(
'+bar : int'
);
expect(testNamespaceA.classes['A1'].members[0]).toBe('+foo : string');
expect(testNamespaceA.classes['A2'].members[0]).toBe('+bar : int');
expect(Object.keys(testNamespaceB.classes).length).toBe(2);
expect(testNamespaceB.classes['B1'].members[0].getDisplayDetails().displayText).toBe(
'+foo : bool'
);
expect(testNamespaceB.classes['B2'].members[0].getDisplayDetails().displayText).toBe(
'+bar : float'
);
expect(testNamespaceB.classes['B1'].members[0]).toBe('+foo : bool');
expect(testNamespaceB.classes['B2'].members[0]).toBe('+bar : float');
expect(Object.keys(testClasses).length).toBe(4);
expect(testClasses['A1'].parent).toBe('A');
expect(testClasses['A2'].parent).toBe('A');
@@ -1624,8 +1570,8 @@ class Class2
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1);
const member = c1.members[0];
expect(member.getDisplayDetails().displayText).toBe('+member1');
expect(c1.members[0]).toBe('+member1');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('C2');
});
@@ -1641,10 +1587,9 @@ class Class2
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.members.length).toBe(1);
expect(c1.members[0]).toBe('+member1');
expect(c1.annotations.length).toBe(1);
expect(c1.annotations[0]).toBe('interface');
const member = c1.members[0];
expect(member.getDisplayDetails().displayText).toBe('+member1');
const c2 = classDb.getClass('C2');
expect(c2.label).toBe('C2');
@@ -1661,9 +1606,8 @@ C1 --> C2
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1);
expect(c1.members[0]).toBe('+member1');
expect(c1.cssClasses[0]).toBe('styleClass');
const member = c1.members[0];
expect(member.getDisplayDetails().displayText).toBe('+member1');
});
it('should parse a class with text label and css class', () => {
@@ -1678,9 +1622,8 @@ cssClass "C1" styleClass
const c1 = classDb.getClass('C1');
expect(c1.label).toBe('Class 1 with text label');
expect(c1.cssClasses.length).toBe(1);
expect(c1.members[0]).toBe('+member1');
expect(c1.cssClasses[0]).toBe('styleClass');
const member = c1.members[0];
expect(member.getDisplayDetails().displayText).toBe('+member1');
});
it('should parse two classes with text labels and css classes', () => {

View File

@@ -0,0 +1,78 @@
import { setConfig } from '../../config.js';
import classDB from './classDb.js';
// @ts-ignore - no types in jison
import classDiagram from './parser/classDiagram.jison';
setConfig({
securityLevel: 'strict',
});
describe('when parsing class diagram', function () {
beforeEach(function () {
classDiagram.parser.yy = classDB;
classDiagram.parser.yy.clear();
});
it('should parse diagram with direction', () => {
classDiagram.parser.parse(`classDiagram
direction TB
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides`);
expect(Object.keys(classDB.getClasses()).length).toBe(3);
expect(classDB.getClasses().Student).toMatchInlineSnapshot(`
{
"annotations": [],
"cssClasses": [],
"domId": "classId-Student-0",
"id": "Student",
"label": "Student",
"members": [
"-idCard : IdCard",
],
"methods": [],
"type": "",
}
`);
expect(classDB.getRelations().length).toBe(2);
expect(classDB.getRelations()).toMatchInlineSnapshot(`
[
{
"id1": "Student",
"id2": "IdCard",
"relation": {
"lineType": 0,
"type1": "none",
"type2": 0,
},
"relationTitle1": "1",
"relationTitle2": "1",
"title": "carries",
},
{
"id1": "Student",
"id2": "Bike",
"relation": {
"lineType": 0,
"type1": "none",
"type2": 0,
},
"relationTitle1": "1",
"relationTitle2": "1",
"title": "rides",
},
]
`);
});
});

View File

@@ -8,8 +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 type { ClassRelation, ClassNote, ClassMap, NamespaceMap } from './classTypes.js';
import type { EdgeData } from '../../types.js';
import type { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js';
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
@@ -157,17 +156,24 @@ export const addNotes = function (
) {
log.info(notes);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
notes.forEach(function (note, i) {
const vertex = note;
/**
* Variable for storing the classes for the vertex
*
*/
const cssNoteStr = '';
const styles = { labelStyle: '', style: '' };
// Use vertex id as text in the box if no text is provided by the graph definition
const vertexText = vertex.text;
const radius = 0;
const shape = 'note';
// Add the node
const node = {
labelStyle: styles.labelStyle,
shape: shape,
@@ -295,7 +301,7 @@ export const setConf = function (cnf: any) {
};
/**
* Draws a class diagram in the tag with id: id based on the definition in text.
* Draws a flowchart in the tag with id: id based on the graph definition in text.
*
* @param text -
* @param id -

View File

@@ -1,683 +0,0 @@
import { ClassMember } from './classTypes.js';
import { vi, describe, it, expect } from 'vitest';
const spyOn = vi.spyOn;
const staticCssStyle = 'text-decoration:underline;';
const abstractCssStyle = 'font-style:italic;';
describe('given text representing a method, ', function () {
describe('when method has no parameters', function () {
it('should parse correctly', function () {
const str = `getTime()`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(str);
});
it('should handle public visibility', function () {
const str = `+getTime()`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('+getTime()');
});
it('should handle private visibility', function () {
const str = `-getTime()`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('-getTime()');
});
it('should handle protected visibility', function () {
const str = `#getTime()`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('#getTime()');
});
it('should handle internal visibility', function () {
const str = `~getTime()`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('~getTime()');
});
it('should return correct css for static classifier', function () {
const str = `getTime()$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime()');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTime()*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime()');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method has single parameter value', function () {
it('should parse correctly', function () {
const str = `getTime(int)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(str);
});
it('should handle public visibility', function () {
const str = `+getTime(int)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('+getTime(int)');
});
it('should handle private visibility', function () {
const str = `-getTime(int)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('-getTime(int)');
});
it('should handle protected visibility', function () {
const str = `#getTime(int)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('#getTime(int)');
});
it('should handle internal visibility', function () {
const str = `~getTime(int)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('~getTime(int)');
});
it('should return correct css for static classifier', function () {
const str = `getTime(int)$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTime(int)*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method has single parameter type and name (type first)', function () {
it('should parse correctly', function () {
const str = `getTime(int count)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(str);
});
it('should handle public visibility', function () {
const str = `+getTime(int count)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('+getTime(int count)');
});
it('should handle private visibility', function () {
const str = `-getTime(int count)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('-getTime(int count)');
});
it('should handle protected visibility', function () {
const str = `#getTime(int count)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('#getTime(int count)');
});
it('should handle internal visibility', function () {
const str = `~getTime(int count)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('~getTime(int count)');
});
it('should return correct css for static classifier', function () {
const str = `getTime(int count)$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTime(int count)*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(int count)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method has single parameter type and name (name first)', function () {
it('should parse correctly', function () {
const str = `getTime(count int)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(str);
});
it('should handle public visibility', function () {
const str = `+getTime(count int)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('+getTime(count int)');
});
it('should handle private visibility', function () {
const str = `-getTime(count int)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('-getTime(count int)');
});
it('should handle protected visibility', function () {
const str = `#getTime(count int)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('#getTime(count int)');
});
it('should handle internal visibility', function () {
const str = `~getTime(count int)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('~getTime(count int)');
});
it('should return correct css for static classifier', function () {
const str = `getTime(count int)$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTime(count int)*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(count int)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method has multiple parameters', function () {
it('should parse correctly', function () {
const str = `getTime(string text, int count)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(str);
});
it('should handle public visibility', function () {
const str = `+getTime(string text, int count)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(str);
});
it('should handle private visibility', function () {
const str = `-getTime(string text, int count)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(str);
});
it('should handle protected visibility', function () {
const str = `#getTime(string text, int count)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(str);
});
it('should handle internal visibility', function () {
const str = `~getTime(string text, int count)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(str);
});
it('should return correct css for static classifier', function () {
const str = `getTime(string text, int count)$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTime(string text, int count)*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime(string text, int count)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method has return type', function () {
it('should parse correctly', function () {
const str = `getTime() DateTime`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime');
});
it('should handle public visibility', function () {
const str = `+getTime() DateTime`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('+getTime() : DateTime');
});
it('should handle private visibility', function () {
const str = `-getTime() DateTime`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('-getTime() : DateTime');
});
it('should handle protected visibility', function () {
const str = `#getTime() DateTime`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('#getTime() : DateTime');
});
it('should handle internal visibility', function () {
const str = `~getTime() DateTime`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('~getTime() : DateTime');
});
it('should return correct css for static classifier', function () {
const str = `getTime() DateTime$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTime() DateTime*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTime() : DateTime');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method parameter is generic', function () {
it('should parse correctly', function () {
const str = `getTimes(List~T~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>)');
});
it('should handle public visibility', function () {
const str = `+getTimes(List~T~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('+getTimes(List<T>)');
});
it('should handle private visibility', function () {
const str = `-getTimes(List~T~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('-getTimes(List<T>)');
});
it('should handle protected visibility', function () {
const str = `#getTimes(List~T~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('#getTimes(List<T>)');
});
it('should handle internal visibility', function () {
const str = `~getTimes(List~T~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('~getTimes(List<T>)');
});
it('should return correct css for static classifier', function () {
const str = `getTimes(List~T~)$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTimes(List~T~)*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method parameter contains two generic', function () {
it('should parse correctly', function () {
const str = `getTimes(List~T~, List~OT~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>, List<OT>)');
});
it('should handle public visibility', function () {
const str = `+getTimes(List~T~, List~OT~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('+getTimes(List<T>, List<OT>)');
});
it('should handle private visibility', function () {
const str = `-getTimes(List~T~, List~OT~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('-getTimes(List<T>, List<OT>)');
});
it('should handle protected visibility', function () {
const str = `#getTimes(List~T~, List~OT~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('#getTimes(List<T>, List<OT>)');
});
it('should handle internal visibility', function () {
const str = `~getTimes(List~T~, List~OT~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('~getTimes(List<T>, List<OT>)');
});
it('should return correct css for static classifier', function () {
const str = `getTimes(List~T~, List~OT~)$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>, List<OT>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTimes(List~T~, List~OT~)*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes(List<T>, List<OT>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method parameter is a nested generic', function () {
it('should parse correctly', function () {
const str = `getTimetableList(List~List~T~~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List<List<T>>)');
});
it('should handle public visibility', function () {
const str = `+getTimetableList(List~List~T~~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('+getTimetableList(List<List<T>>)');
});
it('should handle private visibility', function () {
const str = `-getTimetableList(List~List~T~~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('-getTimetableList(List<List<T>>)');
});
it('should handle protected visibility', function () {
const str = `#getTimetableList(List~List~T~~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('#getTimetableList(List<List<T>>)');
});
it('should handle internal visibility', function () {
const str = `~getTimetableList(List~List~T~~)`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('~getTimetableList(List<List<T>>)');
});
it('should return correct css for static classifier', function () {
const str = `getTimetableList(List~List~T~~)$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List<List<T>>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTimetableList(List~List~T~~)*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimetableList(List<List<T>>)');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method parameter is a composite generic', function () {
const methodNameAndParameters = 'getTimes(List~K, V~)';
const expectedMethodNameAndParameters = 'getTimes(List<K, V>)';
it('should parse correctly', function () {
const str = methodNameAndParameters;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters);
});
it('should handle public visibility', function () {
const str = '+' + methodNameAndParameters;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'+' + expectedMethodNameAndParameters
);
});
it('should handle private visibility', function () {
const str = '-' + methodNameAndParameters;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'-' + expectedMethodNameAndParameters
);
});
it('should handle protected visibility', function () {
const str = '#' + methodNameAndParameters;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'#' + expectedMethodNameAndParameters
);
});
it('should handle internal visibility', function () {
const str = '~' + methodNameAndParameters;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'~' + expectedMethodNameAndParameters
);
});
it('should return correct css for static classifier', function () {
const str = methodNameAndParameters + '$';
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters);
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = methodNameAndParameters + '*';
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(expectedMethodNameAndParameters);
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method return type is generic', function () {
it('should parse correctly', function () {
const str = `getTimes() List~T~`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List<T>');
});
it('should handle public visibility', function () {
const str = `+getTimes() List~T~`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('+getTimes() : List<T>');
});
it('should handle private visibility', function () {
const str = `-getTimes() List~T~`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('-getTimes() : List<T>');
});
it('should handle protected visibility', function () {
const str = `#getTimes() List~T~`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('#getTimes() : List<T>');
});
it('should handle internal visibility', function () {
const str = `~getTimes() List~T~`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('~getTimes() : List<T>');
});
it('should return correct css for static classifier', function () {
const str = `getTimes() List~T~$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List<T>');
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTimes() List~T~*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe('getTimes() : List<T>');
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('when method return type is a nested generic', function () {
it('should parse correctly', function () {
const str = `getTimetableList() List~List~T~~`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'getTimetableList() : List<List<T>>'
);
});
it('should handle public visibility', function () {
const str = `+getTimetableList() List~List~T~~`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'+getTimetableList() : List<List<T>>'
);
});
it('should handle private visibility', function () {
const str = `-getTimetableList() List~List~T~~`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'-getTimetableList() : List<List<T>>'
);
});
it('should handle protected visibility', function () {
const str = `#getTimetableList() List~List~T~~`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'#getTimetableList() : List<List<T>>'
);
});
it('should handle internal visibility', function () {
const str = `~getTimetableList() List~List~T~~`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'~getTimetableList() : List<List<T>>'
);
});
it('should return correct css for static classifier', function () {
const str = `getTimetableList() List~List~T~~$`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'getTimetableList() : List<List<T>>'
);
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
it('should return correct css for abstract classifier', function () {
const str = `getTimetableList() List~List~T~~*`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'getTimetableList() : List<List<T>>'
);
expect(classMember.getDisplayDetails().cssStyle).toBe(abstractCssStyle);
});
});
describe('--uncategorized tests--', function () {
it('member name should handle double colons', function () {
const str = `std::map ~int,string~ pMap;`;
const classMember = new ClassMember(str, 'attribute');
expect(classMember.getDisplayDetails().displayText).toBe('std::map <int,string> pMap;');
});
it('member name should handle generic type', function () {
const str = `getTime~T~(this T, int seconds)$ DateTime`;
const classMember = new ClassMember(str, 'method');
expect(classMember.getDisplayDetails().displayText).toBe(
'getTime<T>(this T, int seconds) : DateTime'
);
expect(classMember.getDisplayDetails().cssStyle).toBe(staticCssStyle);
});
});
});

View File

@@ -1,13 +1,10 @@
import { getConfig } from '../../config.js';
import { parseGenericTypes, sanitizeText } from '../common/common.js';
export interface ClassNode {
id: string;
type: string;
label: string;
cssClasses: string[];
methods: ClassMember[];
members: ClassMember[];
methods: string[];
members: string[];
annotations: string[];
domId: string;
parent?: string;
@@ -17,126 +14,30 @@ export interface ClassNode {
tooltip?: string;
}
export type Visibility = '#' | '+' | '~' | '-' | '';
export const visibilityValues = ['#', '+', '~', '-', ''];
/**
* Parses and stores class diagram member variables/methods.
*
*/
export class ClassMember {
id!: string;
cssStyle!: string;
memberType!: 'method' | 'attribute';
visibility!: Visibility;
/**
* denote if static or to determine which css class to apply to the node
* @defaultValue ''
*/
classifier!: string;
/**
* parameters for method
* @defaultValue ''
*/
parameters!: string;
/**
* return type for method
* @defaultValue ''
*/
returnType!: string;
constructor(input: string, memberType: 'method' | 'attribute') {
this.memberType = memberType;
this.visibility = '';
this.classifier = '';
const sanitizedInput = sanitizeText(input, getConfig());
this.parseMember(sanitizedInput);
}
getDisplayDetails() {
let displayText = this.visibility + parseGenericTypes(this.id);
if (this.memberType === 'method') {
displayText += `(${parseGenericTypes(this.parameters.trim())})`;
if (this.returnType) {
displayText += ' : ' + parseGenericTypes(this.returnType);
}
}
displayText = displayText.trim();
const cssStyle = this.parseClassifier();
return {
displayText,
cssStyle,
};
}
parseMember(input: string) {
let potentialClassifier = '';
if (this.memberType === 'method') {
const methodRegEx = /([#+~-])?(.+)\((.*)\)([\s$*])?(.*)([$*])?/;
const match = input.match(methodRegEx);
if (match) {
const detectedVisibility = match[1] ? match[1].trim() : '';
if (visibilityValues.includes(detectedVisibility)) {
this.visibility = detectedVisibility as Visibility;
}
this.id = match[2].trim();
this.parameters = match[3] ? match[3].trim() : '';
potentialClassifier = match[4] ? match[4].trim() : '';
this.returnType = match[5] ? match[5].trim() : '';
if (potentialClassifier === '') {
const lastChar = this.returnType.substring(this.returnType.length - 1);
if (lastChar.match(/[$*]/)) {
potentialClassifier = lastChar;
this.returnType = this.returnType.substring(0, this.returnType.length - 1);
}
}
}
} else {
const length = input.length;
const firstChar = input.substring(0, 1);
const lastChar = input.substring(length - 1);
if (visibilityValues.includes(firstChar)) {
this.visibility = firstChar as Visibility;
}
if (lastChar.match(/[*?]/)) {
potentialClassifier = lastChar;
}
this.id = input.substring(
this.visibility === '' ? 0 : 1,
potentialClassifier === '' ? length : length - 1
);
}
this.classifier = potentialClassifier;
}
parseClassifier() {
switch (this.classifier) {
case '*':
return 'font-style:italic;';
case '$':
return 'text-decoration:underline;';
default:
return '';
}
}
}
export interface ClassNote {
id: string;
class: string;
text: string;
}
export interface EdgeData {
arrowheadStyle?: string;
labelpos?: string;
labelType?: string;
label?: string;
classes: string;
pattern: string;
id: string;
arrowhead: string;
startLabelRight: string;
endLabelLeft: string;
arrowTypeStart: string;
arrowTypeEnd: string;
style: string;
labelStyle: string;
curve: any;
}
export type ClassRelation = {
id1: string;
id2: string;

View File

@@ -13,6 +13,9 @@
%x href
%x callback_name
%x callback_args
%x open_directive
%x type_directive
%x arg_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
@@ -21,10 +24,15 @@
%x namespace
%x namespace-body
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
.*direction\s+TB[^\n]* return 'direction_tb';
.*direction\s+BT[^\n]* return 'direction_bt';
.*direction\s+RL[^\n]* return 'direction_rl';
.*direction\s+LR[^\n]* return 'direction_lr';
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)*[^\n]*(\r?\n?)+ /* skip comments */
\%\%[^\n]*(\r?\n)* /* skip comments */
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
@@ -212,13 +220,46 @@ line was introduced with 'click'.
start
: mermaidDoc
| directive start
| statements
;
direction
: direction_tb
{ yy.setDirection('TB');}
| direction_bt
{ yy.setDirection('BT');}
| direction_rl
{ yy.setDirection('RL');}
| direction_lr
{ yy.setDirection('LR');}
;
mermaidDoc
: graphConfig
;
directive
: openDirective typeDirective closeDirective NEWLINE
| openDirective typeDirective ':' argDirective closeDirective NEWLINE
;
openDirective
: open_directive { yy.parseDirective('%%{', 'open_directive'); }
;
typeDirective
: type_directive { yy.parseDirective($1, 'type_directive'); }
;
argDirective
: arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
;
closeDirective
: close_directive { yy.parseDirective('}%%', 'close_directive', 'class'); }
;
graphConfig
: CLASS_DIAGRAM NEWLINE statements EOF
;
@@ -251,7 +292,7 @@ statement
| relationStatement LABEL { $1.title = yy.cleanupLabel($2); yy.addRelation($1); }
| namespaceStatement
| classStatement
| memberStatement
| methodStatement
| annotationStatement
| clickStatement
| cssClassStatement
@@ -298,7 +339,7 @@ members
| MEMBER members { $2.push($1);$$=$2;}
;
memberStatement
methodStatement
: className {/*console.log('Rel found',$1);*/}
| className LABEL {yy.addMember($1,yy.cleanupLabel($2));}
| MEMBER {/*console.warn('Member',$1);*/}
@@ -317,17 +358,6 @@ noteStatement
| NOTE noteText { yy.addNote($2); }
;
direction
: direction_tb
{ yy.setDirection('TB');}
| direction_bt
{ yy.setDirection('BT');}
| direction_rl
{ yy.setDirection('RL');}
| direction_lr
{ yy.setDirection('LR');}
;
relation
: relationType lineType relationType { $$={type1:$1,type2:$3,lineType:$2}; }
| lineType relationType { $$={type1:'none',type2:$2,lineType:$1}; }

View File

@@ -109,25 +109,25 @@ g.classGroup line {
}
#extensionStart, .extension {
fill: transparent !important;
fill: ${options.mainBkg} !important;
stroke: ${options.lineColor} !important;
stroke-width: 1;
}
#extensionEnd, .extension {
fill: transparent !important;
fill: ${options.mainBkg} !important;
stroke: ${options.lineColor} !important;
stroke-width: 1;
}
#aggregationStart, .aggregation {
fill: transparent !important;
fill: ${options.mainBkg} !important;
stroke: ${options.lineColor} !important;
stroke-width: 1;
}
#aggregationEnd, .aggregation {
fill: transparent !important;
fill: ${options.mainBkg} !important;
stroke: ${options.lineColor} !important;
stroke-width: 1;
}

View File

@@ -172,6 +172,7 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
// add class group
const g = elem.append('g').attr('id', diagObj.db.lookUpDomId(id)).attr('class', 'classGroup');
// add title
let title;
if (classDef.link) {
title = g
@@ -208,56 +209,47 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
}
const titleHeight = title.node().getBBox().height;
let membersLine;
let membersBox;
let methodsLine;
// don't draw box if no members
if (classDef.members.length > 0) {
membersLine = g
.append('line') // text label for the x axis
.attr('x1', 0)
.attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2);
const membersLine = g
.append('line') // text label for the x axis
.attr('x1', 0)
.attr('y1', conf.padding + titleHeight + conf.dividerMargin / 2)
.attr('y2', conf.padding + titleHeight + conf.dividerMargin / 2);
const members = g
.append('text') // text label for the x axis
.attr('x', conf.padding)
.attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
.attr('fill', 'white')
.attr('class', 'classText');
const members = g
.append('text') // text label for the x axis
.attr('x', conf.padding)
.attr('y', titleHeight + conf.dividerMargin + conf.textHeight)
.attr('fill', 'white')
.attr('class', 'classText');
isFirst = true;
classDef.members.forEach(function (member) {
addTspan(members, member, isFirst, conf);
isFirst = false;
});
isFirst = true;
classDef.members.forEach(function (member) {
addTspan(members, member, isFirst, conf);
isFirst = false;
});
membersBox = members.node().getBBox();
}
const membersBox = members.node().getBBox();
// don't draw box if no methods
if (classDef.methods.length > 0) {
methodsLine = g
.append('line') // text label for the x axis
.attr('x1', 0)
.attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
.attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height);
const methodsLine = g
.append('line') // text label for the x axis
.attr('x1', 0)
.attr('y1', conf.padding + titleHeight + conf.dividerMargin + membersBox.height)
.attr('y2', conf.padding + titleHeight + conf.dividerMargin + membersBox.height);
const methods = g
.append('text') // text label for the x axis
.attr('x', conf.padding)
.attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
.attr('fill', 'white')
.attr('class', 'classText');
const methods = g
.append('text') // text label for the x axis
.attr('x', conf.padding)
.attr('y', titleHeight + 2 * conf.dividerMargin + membersBox.height + conf.textHeight)
.attr('fill', 'white')
.attr('class', 'classText');
isFirst = true;
isFirst = true;
classDef.methods.forEach(function (method) {
addTspan(methods, method, isFirst, conf);
isFirst = false;
});
}
classDef.methods.forEach(function (method) {
addTspan(methods, method, isFirst, conf);
isFirst = false;
});
const classBox = g.node().getBBox();
var cssClassStr = ' ';
@@ -286,12 +278,8 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
title.insert('title').text(classDef.tooltip);
}
if (membersLine) {
membersLine.attr('x2', rectWidth);
}
if (methodsLine) {
methodsLine.attr('x2', rectWidth);
}
membersLine.attr('x2', rectWidth);
methodsLine.attr('x2', rectWidth);
classInfo.width = rectWidth;
classInfo.height = classBox.height + conf.padding + 0.5 * conf.dividerMargin;
@@ -303,7 +291,7 @@ export const getClassTitleString = function (classDef) {
let classTitleString = classDef.id;
if (classDef.type) {
classTitleString += '<' + parseGenericTypes(classDef.type) + '>';
classTitleString += '<' + classDef.type + '>';
}
return classTitleString;
@@ -372,19 +360,82 @@ export const drawNote = function (elem, note, conf, diagObj) {
return noteInfo;
};
export const parseMember = function (text) {
let displayText = '';
let cssStyle = '';
let returnType = '';
let visibility = '';
let firstChar = text.substring(0, 1);
let lastChar = text.substring(text.length - 1, text.length);
if (firstChar.match(/[#+~-]/)) {
visibility = firstChar;
}
let noClassifierRe = /[\s\w)~]/;
if (!lastChar.match(noClassifierRe)) {
cssStyle = parseClassifier(lastChar);
}
const startIndex = visibility === '' ? 0 : 1;
let endIndex = cssStyle === '' ? text.length : text.length - 1;
text = text.substring(startIndex, endIndex);
const methodStart = text.indexOf('(');
const methodEnd = text.indexOf(')');
const isMethod = methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length;
if (isMethod) {
let methodName = text.substring(0, methodStart).trim();
const parameters = text.substring(methodStart + 1, methodEnd);
displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')';
if (methodEnd < text.length) {
// special case: classifier after the closing parenthesis
let potentialClassifier = text.substring(methodEnd + 1, methodEnd + 2);
if (cssStyle === '' && !potentialClassifier.match(noClassifierRe)) {
cssStyle = parseClassifier(potentialClassifier);
returnType = text.substring(methodEnd + 2).trim();
} else {
returnType = text.substring(methodEnd + 1).trim();
}
if (returnType !== '') {
if (returnType.charAt(0) === ':') {
returnType = returnType.substring(1).trim();
}
returnType = ' : ' + parseGenericTypes(returnType);
displayText += returnType;
}
}
} else {
// finally - if all else fails, just send the text back as written (other than parsing for generic types)
displayText = visibility + parseGenericTypes(text);
}
return {
displayText,
cssStyle,
};
};
/**
* Adds a <tspan> for a member in a diagram
*
* @param {SVGElement} textEl The element to append to
* @param {string} member The member
* @param {string} txt The member
* @param {boolean} isFirst
* @param {{ padding: string; textHeight: string }} conf The configuration for the member
*/
const addTspan = function (textEl, member, isFirst, conf) {
const { displayText, cssStyle } = member.getDisplayDetails();
const tSpan = textEl.append('tspan').attr('x', conf.padding).text(displayText);
const addTspan = function (textEl, txt, isFirst, conf) {
let member = parseMember(txt);
if (cssStyle !== '') {
const tSpan = textEl.append('tspan').attr('x', conf.padding).text(member.displayText);
if (member.cssStyle !== '') {
tSpan.attr('style', member.cssStyle);
}
@@ -393,9 +444,27 @@ const addTspan = function (textEl, member, isFirst, conf) {
}
};
/**
* Gives the styles for a classifier
*
* @param {'+' | '-' | '#' | '~' | '*' | '$'} classifier The classifier string
* @returns {string} Styling for the classifier
*/
const parseClassifier = function (classifier) {
switch (classifier) {
case '*':
return 'font-style:italic;';
case '$':
return 'text-decoration:underline;';
default:
return '';
}
};
export default {
getClassTitleString,
drawClass,
drawEdge,
drawNote,
parseMember,
};

View File

@@ -1,27 +1,330 @@
import svgDraw from './svgDraw.js';
import { JSDOM } from 'jsdom';
describe('given a string representing a class, ', function () {
describe('when class name includes generic, ', function () {
it('should return correct text for generic', function () {
const classDef = {
id: 'Car',
type: 'T',
label: 'Car',
};
describe('given a string representing class method, ', function () {
it('should handle class names with generics', function () {
const classDef = {
id: 'Car',
type: 'T',
label: 'Car',
};
let actual = svgDraw.getClassTitleString(classDef);
expect(actual).toBe('Car<T>');
let actual = svgDraw.getClassTitleString(classDef);
expect(actual).toBe('Car<T>');
});
describe('when parsing base method declaration', function () {
it('should handle simple declaration', function () {
const str = 'foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('');
});
it('should return correct text for nested generics', function () {
const classDef = {
id: 'Car',
type: 'T~T~',
label: 'Car',
};
let actual = svgDraw.getClassTitleString(classDef);
expect(actual).toBe('Car<T<T>>');
it('should handle declaration with parameters', function () {
const str = 'foo(int id)';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(int id)');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with multiple parameters', function () {
const str = 'foo(int id, object thing)';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(int id, object thing)');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with single item in parameters', function () {
const str = 'foo(id)';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id)');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with single item in parameters with extra spaces', function () {
const str = ' foo ( id) ';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id)');
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with generic parameter', function () {
const str = 'foo(List~int~)';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(List<int>)');
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with normal and generic parameter', function () {
const str = 'foo(int, List~int~)';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(int, List<int>)');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with return value', function () {
const str = 'foo(id) int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id) : int');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with colon return value', function () {
const str = 'foo(id) : int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id) : int');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with generic return value', function () {
const str = 'foo(id) List~int~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id) : List<int>');
expect(actual.cssStyle).toBe('');
});
it('should handle declaration with colon generic return value', function () {
const str = 'foo(id) : List~int~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(id) : List<int>');
expect(actual.cssStyle).toBe('');
});
it('should handle method declaration with all possible markup', function () {
const str = '+foo ( List~int~ ids )* List~Item~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('+foo(List<int> ids) : List<Item>');
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle method declaration with nested generics', function () {
const str = '+foo ( List~List~int~~ ids )* List~List~Item~~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('+foo(List<List<int>> ids) : List<List<Item>>');
expect(actual.cssStyle).toBe('font-style:italic;');
});
});
describe('when parsing method visibility', function () {
it('should correctly handle public', function () {
const str = '+foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('+foo()');
expect(actual.cssStyle).toBe('');
});
it('should correctly handle private', function () {
const str = '-foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('-foo()');
expect(actual.cssStyle).toBe('');
});
it('should correctly handle protected', function () {
const str = '#foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('#foo()');
expect(actual.cssStyle).toBe('');
});
it('should correctly handle package/internal', function () {
const str = '~foo()';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('~foo()');
expect(actual.cssStyle).toBe('');
});
});
describe('when parsing method classifier', function () {
it('should handle abstract method', function () {
const str = 'foo()*';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle abstract method with return type', function () {
const str = 'foo(name: String) int*';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(name: String) : int');
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle abstract method classifier after parenthesis with return type', function () {
const str = 'foo(name: String)* int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(name: String) : int');
expect(actual.cssStyle).toBe('font-style:italic;');
});
it('should handle static method classifier', function () {
const str = 'foo()$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static method classifier with return type', function () {
const str = 'foo(name: String) int$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(name: String) : int');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static method classifier with colon and return type', function () {
const str = 'foo(name: String): int$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(name: String) : int');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static method classifier after parenthesis with return type', function () {
const str = 'foo(name: String)$ int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo(name: String) : int');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should ignore unknown character for classifier', function () {
const str = 'foo()!';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo()');
expect(actual.cssStyle).toBe('');
});
});
});
describe('given a string representing class member, ', function () {
describe('when parsing member declaration', function () {
it('should handle simple field', function () {
const str = 'id';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('id');
expect(actual.cssStyle).toBe('');
});
it('should handle field with type', function () {
const str = 'int id';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('int id');
expect(actual.cssStyle).toBe('');
});
it('should handle field with type (name first)', function () {
const str = 'id: int';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('id: int');
expect(actual.cssStyle).toBe('');
});
it('should handle array field', function () {
const str = 'int[] ids';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('int[] ids');
expect(actual.cssStyle).toBe('');
});
it('should handle array field (name first)', function () {
const str = 'ids: int[]';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('ids: int[]');
expect(actual.cssStyle).toBe('');
});
it('should handle field with generic type', function () {
const str = 'List~int~ ids';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('List<int> ids');
expect(actual.cssStyle).toBe('');
});
it('should handle field with generic type (name first)', function () {
const str = 'ids: List~int~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('ids: List<int>');
expect(actual.cssStyle).toBe('');
});
});
describe('when parsing classifiers', function () {
it('should handle static field', function () {
const str = 'String foo$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('String foo');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static field (name first)', function () {
const str = 'foo: String$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo: String');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static field with generic type', function () {
const str = 'List~String~ foo$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('List<String> foo');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle static field with generic type (name first)', function () {
const str = 'foo: List~String~$';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('foo: List<String>');
expect(actual.cssStyle).toBe('text-decoration:underline;');
});
it('should handle field with nested generic type', function () {
const str = 'List~List~int~~ idLists';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('List<List<int>> idLists');
expect(actual.cssStyle).toBe('');
});
it('should handle field with nested generic type (name first)', function () {
const str = 'idLists: List~List~int~~';
let actual = svgDraw.parseMember(str);
expect(actual.displayText).toBe('idLists: List<List<int>>');
expect(actual.cssStyle).toBe('');
});
});
});

View File

@@ -1,4 +1,4 @@
import { sanitizeText, removeScript, parseGenericTypes, countOccurrence } from './common.js';
import { sanitizeText, removeScript, parseGenericTypes } from './common.js';
describe('when securityLevel is antiscript, all script must be removed', () => {
/**
@@ -59,29 +59,16 @@ describe('Sanitize text', () => {
});
describe('generic parser', () => {
it.each([
['test~T~', 'test<T>'],
['test~Array~Array~string~~~', 'test<Array<Array<string>>>'],
['test~Array~Array~string[]~~~', 'test<Array<Array<string[]>>>'],
['test ~Array~Array~string[]~~~', 'test <Array<Array<string[]>>>'],
['~test', '~test'],
['~test~T~', '~test<T>'],
])('should parse generic types: %s to %s', (input: string, expected: string) => {
expect(parseGenericTypes(input)).toEqual(expected);
it('should parse generic types', () => {
expect(parseGenericTypes('test~T~')).toEqual('test<T>');
expect(parseGenericTypes('test~Array~Array~string~~~')).toEqual('test<Array<Array<string>>>');
expect(parseGenericTypes('test~Array~Array~string[]~~~')).toEqual(
'test<Array<Array<string[]>>>'
);
expect(parseGenericTypes('test ~Array~Array~string[]~~~')).toEqual(
'test <Array<Array<string[]>>>'
);
expect(parseGenericTypes('~test')).toEqual('~test');
expect(parseGenericTypes('~test Array~string~')).toEqual('~test Array<string>');
});
});
it.each([
['', '', 0],
['', 'x', 0],
['test', 'x', 0],
['test', 't', 2],
['test', 'te', 1],
['test~T~', '~', 2],
['test~Array~Array~string~~~', '~', 6],
])(
'should count `%s` to contain occurrences of `%s` to be `%i`',
(str: string, substring: string, count: number) => {
expect(countOccurrence(str, substring)).toEqual(count);
}
);

View File

@@ -178,80 +178,23 @@ export const getMin = function (...values: number[]): number {
* @param text - The text to convert
* @returns The converted string
*/
export const parseGenericTypes = function (input: string): string {
const inputSets = input.split(/(,)/);
const output = [];
export const parseGenericTypes = function (text: string): string {
let cleanedText = text;
for (let i = 0; i < inputSets.length; i++) {
let thisSet = inputSets[i];
if (text.split('~').length - 1 >= 2) {
let newCleanedText = cleanedText;
// if the original input included a value such as "~K, V~"", these will be split into
// an array of ["~K",","," V~"].
// This means that on each call of processSet, there will only be 1 ~ present
// To account for this, if we encounter a ",", we are checking the previous and next sets in the array
// to see if they contain matching ~'s
// in which case we are assuming that they should be rejoined and sent to be processed
if (thisSet === ',' && i > 0 && i + 1 < inputSets.length) {
const previousSet = inputSets[i - 1];
const nextSet = inputSets[i + 1];
// use a do...while loop instead of replaceAll to detect recursion
// e.g. Array~Array~T~~
do {
cleanedText = newCleanedText;
newCleanedText = cleanedText.replace(/~([^\s,:;]+)~/, '<$1>');
} while (newCleanedText != cleanedText);
if (shouldCombineSets(previousSet, nextSet)) {
thisSet = previousSet + ',' + nextSet;
i++; // Move the index forward to skip the next iteration since we're combining sets
output.pop();
}
}
output.push(processSet(thisSet));
return parseGenericTypes(newCleanedText);
} else {
return cleanedText;
}
return output.join('');
};
export const countOccurrence = (string: string, substring: string): number => {
return Math.max(0, string.split(substring).length - 1);
};
const shouldCombineSets = (previousSet: string, nextSet: string): boolean => {
const prevCount = countOccurrence(previousSet, '~');
const nextCount = countOccurrence(nextSet, '~');
return prevCount === 1 && nextCount === 1;
};
const processSet = (input: string): string => {
const tildeCount = countOccurrence(input, '~');
let hasStartingTilde = false;
if (tildeCount <= 1) {
return input;
}
// If there is an odd number of tildes, and the input starts with a tilde, we need to remove it and add it back in later
if (tildeCount % 2 !== 0 && input.startsWith('~')) {
input = input.substring(1);
hasStartingTilde = true;
}
const chars = [...input];
let first = chars.indexOf('~');
let last = chars.lastIndexOf('~');
while (first !== -1 && last !== -1 && first !== last) {
chars[first] = '<';
chars[last] = '>';
first = chars.indexOf('~');
last = chars.lastIndexOf('~');
}
// Add the starting tilde back in if we removed it
if (hasStartingTilde) {
chars.unshift('~');
}
return chars.join('');
};
export default {

View File

@@ -1,32 +0,0 @@
import { sanitizeText as _sanitizeText } from './common.js';
import { getConfig } from '../../config.js';
let accTitle = '';
let diagramTitle = '';
let accDescription = '';
const sanitizeText = (txt: string): string => _sanitizeText(txt, getConfig());
export const clear = (): void => {
accTitle = '';
accDescription = '';
diagramTitle = '';
};
export const setAccTitle = (txt: string): void => {
accTitle = sanitizeText(txt).replace(/^\s+/g, '');
};
export const getAccTitle = (): string => accTitle;
export const setAccDescription = (txt: string): void => {
accDescription = sanitizeText(txt).replace(/\n\s+/g, '\n');
};
export const getAccDescription = (): string => accDescription;
export const setDiagramTitle = (txt: string): void => {
diagramTitle = sanitizeText(txt);
};
export const getDiagramTitle = (): string => diagramTitle;

View File

@@ -1,4 +1,5 @@
import { log } from '../../logger.js';
import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import {
@@ -9,7 +10,7 @@ import {
clear as commonClear,
setDiagramTitle,
getDiagramTitle,
} from '../common/commonDb.js';
} from '../../commonDb.js';
let entities = {};
let relationships = [];
@@ -27,13 +28,14 @@ const Identification = {
IDENTIFYING: 'IDENTIFYING',
};
const addEntity = function (name, alias = undefined) {
export const parseDirective = function (statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type);
};
const addEntity = function (name) {
if (entities[name] === undefined) {
entities[name] = { attributes: [], alias: alias };
entities[name] = { attributes: [] };
log.info('Added new entity :', name);
} else if (entities[name] && !entities[name].alias && alias) {
entities[name].alias = alias;
log.info(`Add alias '${alias}' to entity '${name}'`);
}
return entities[name];
@@ -83,6 +85,7 @@ const clear = function () {
export default {
Cardinality,
Identification,
parseDirective,
getConfig: () => configApi.getConfig().er,
addEntity,
addAttributes,

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