mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08:00
Merge branch 'develop' into UpdateClassMemberHandling
This commit is contained in:
commit
f4ffd5c965
@ -4,4 +4,5 @@ docs/Setup.md
|
||||
cypress.config.js
|
||||
cypress/plugins/index.js
|
||||
coverage
|
||||
*.json
|
||||
*.json
|
||||
node_modules
|
||||
|
26
.github/ISSUE_TEMPLATE/diagram_proposal.yml
vendored
26
.github/ISSUE_TEMPLATE/diagram_proposal.yml
vendored
@ -3,6 +3,7 @@ description: Suggest a new Diagram Type to add to Mermaid.
|
||||
labels:
|
||||
- 'Status: Triage'
|
||||
- 'Type: Enhancement'
|
||||
- 'Type: New Diagram'
|
||||
|
||||
body:
|
||||
- type: markdown
|
||||
@ -17,6 +18,14 @@ body:
|
||||
- Use a clear and concise title
|
||||
- Fill out the text fields with as much detail as possible.
|
||||
- Never be shy to give us screenshots and/or code samples. It will help!
|
||||
|
||||
## Example issues
|
||||
|
||||
Refer to the discussions here to get an idea of how the diagram syntax is created.
|
||||
|
||||
- https://github.com/mermaid-js/mermaid/issues/4269
|
||||
- https://github.com/mermaid-js/mermaid/issues/4282
|
||||
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Proposal
|
||||
@ -35,8 +44,17 @@ body:
|
||||
description: If applicable, add screenshots to show possible examples of how the diagram may look like.
|
||||
- type: textarea
|
||||
attributes:
|
||||
label: Code Sample
|
||||
label: Syntax
|
||||
description: |-
|
||||
If applicable, add a code sample for how to implement this new diagram.
|
||||
The text will automatically be rendered as JavaScript code.
|
||||
render: javascript
|
||||
If possible, include a syntax which could be used to write the diagram.
|
||||
Try to add one or two examples of valid use-cases here.
|
||||
- type: dropdown
|
||||
id: implementation
|
||||
attributes:
|
||||
label: Implementation
|
||||
description: |-
|
||||
Would you like to implement this yourself, or is it a proposal for the community?
|
||||
If there is no corresponding PR from your side after 30 days, the diagram will be open for everyone to implement.
|
||||
options:
|
||||
- I will try and implement it myself.
|
||||
- This is a proposal which I'd love to see built into mermaid by the wonderful community.
|
||||
|
@ -44,6 +44,11 @@ const packageOptions = {
|
||||
packageName: 'mermaid-example-diagram',
|
||||
file: 'detector.ts',
|
||||
},
|
||||
'mermaid-zenuml': {
|
||||
name: 'mermaid-zenuml',
|
||||
packageName: 'mermaid-zenuml',
|
||||
file: 'detector.ts',
|
||||
},
|
||||
};
|
||||
|
||||
interface BuildOptions {
|
||||
@ -146,6 +151,7 @@ if (watch) {
|
||||
build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
|
||||
if (!mermaidOnly) {
|
||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
|
||||
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' }));
|
||||
}
|
||||
} else if (visualize) {
|
||||
await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' }));
|
||||
|
@ -15,6 +15,7 @@ async function createServer() {
|
||||
|
||||
app.use(cors());
|
||||
app.use(express.static('./packages/mermaid/dist'));
|
||||
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
||||
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
||||
app.use(vite.middlewares);
|
||||
app.use(express.static('demos'));
|
||||
|
@ -7,6 +7,7 @@
|
||||
"alois",
|
||||
"aloisklink",
|
||||
"antiscript",
|
||||
"antlr",
|
||||
"appli",
|
||||
"applitools",
|
||||
"asciidoctor",
|
||||
@ -86,6 +87,7 @@
|
||||
"mkdocs",
|
||||
"mmorel",
|
||||
"mult",
|
||||
"nextra",
|
||||
"orlandoni",
|
||||
"pathe",
|
||||
"pbrolin",
|
||||
@ -137,7 +139,8 @@
|
||||
"vitepress",
|
||||
"vueuse",
|
||||
"xlink",
|
||||
"yash"
|
||||
"yash",
|
||||
"zenuml"
|
||||
],
|
||||
"patterns": [
|
||||
{ "name": "Markdown links", "pattern": "\\((.*)\\)", "description": "" },
|
||||
|
@ -60,7 +60,7 @@ export const renderGraph = (graphStr, options, api) => {
|
||||
openURLAndVerifyRendering(url, options);
|
||||
};
|
||||
|
||||
const openURLAndVerifyRendering = (url, options, validation = undefined) => {
|
||||
export const openURLAndVerifyRendering = (url, options, validation = undefined) => {
|
||||
const useAppli = Cypress.env('useAppli');
|
||||
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import { urlSnapshotTest } from '../../helpers/util.js';
|
||||
import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.js';
|
||||
|
||||
describe('CSS injections', () => {
|
||||
it('should not allow CSS injections outside of the diagram', () => {
|
||||
@ -13,4 +13,11 @@ describe('CSS injections', () => {
|
||||
flowchart: { htmlLabels: false },
|
||||
});
|
||||
});
|
||||
it('should not allow manipulating styletags using arrowheads', () => {
|
||||
openURLAndVerifyRendering('http://localhost:9000/xss23-css.html', {
|
||||
logLevel: 1,
|
||||
arrowMarkerAbsolute: false,
|
||||
flowchart: { htmlLabels: true },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@ -684,6 +684,20 @@ A --> B
|
||||
{ titleTopMargin: 0 }
|
||||
);
|
||||
});
|
||||
it('elk: should include classes on the edges', () => {
|
||||
renderGraph(
|
||||
`flowchart-elk TD
|
||||
A --> B --> C --> D
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg').should((svg) => {
|
||||
const edges = svg.querySelectorAll('.edges > path');
|
||||
edges.forEach((edge) => {
|
||||
expect(edge).to.have.class('flowchart-link');
|
||||
});
|
||||
});
|
||||
});
|
||||
describe('Markdown strings flowchart-elk (#4220)', () => {
|
||||
describe('html labels', () => {
|
||||
it('With styling and classes', () => {
|
||||
|
@ -52,6 +52,17 @@ root[A root with a long text that wraps to keep the node size in check]
|
||||
);
|
||||
});
|
||||
|
||||
it('a root with wrapping text and long words that exceed width', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
root[A few smaller words but then averylongsetofcharacterswithoutwhitespacetoseparate that we expect to wrapontonextlinesandnotexceedwidthparameters]
|
||||
`,
|
||||
{},
|
||||
undefined,
|
||||
shouldHaveRoot
|
||||
);
|
||||
});
|
||||
|
||||
it('a root with an icon', () => {
|
||||
imgSnapshotTest(
|
||||
`mindmap
|
||||
|
@ -88,6 +88,16 @@ context('Sequence diagram', () => {
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should handle empty lines', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
sequenceDiagram
|
||||
Alice->>John: Hello John<br/>
|
||||
John-->>Alice: Great<br/><br/>day!
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('should handle line breaks and wrap annotations', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
|
19
cypress/integration/rendering/zenuml.spec.js
Normal file
19
cypress/integration/rendering/zenuml.spec.js
Normal file
@ -0,0 +1,19 @@
|
||||
import { imgSnapshotTest } from '../../helpers/util.js';
|
||||
|
||||
describe('Zen UML', () => {
|
||||
it('Basic Zen UML diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
zenuml
|
||||
A.method() {
|
||||
if(x) {
|
||||
B.method() {
|
||||
selfCall() { return X }
|
||||
}
|
||||
}
|
||||
}
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
});
|
@ -1,5 +1,6 @@
|
||||
import mermaid2 from './mermaid.esm.mjs';
|
||||
import externalExample from '../../packages/mermaid-example-diagram/dist/mermaid-example-diagram.core.mjs';
|
||||
import zenUml from '../../packages/mermaid-zenuml/dist/mermaid-zenuml.core.mjs';
|
||||
|
||||
function b64ToUtf8(str) {
|
||||
return decodeURIComponent(escape(window.atob(str)));
|
||||
@ -44,7 +45,7 @@ const contentLoaded = async function () {
|
||||
document.getElementsByTagName('body')[0].appendChild(div);
|
||||
}
|
||||
|
||||
await mermaid2.registerExternalDiagrams([externalExample]);
|
||||
await mermaid2.registerExternalDiagrams([externalExample, zenUml]);
|
||||
mermaid2.initialize(graphObj.mermaid);
|
||||
await mermaid2.run();
|
||||
}
|
||||
|
85
cypress/platform/xss23-css.html
Normal file
85
cypress/platform/xss23-css.html
Normal file
@ -0,0 +1,85 @@
|
||||
<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>
|
||||
</head>
|
||||
<body>
|
||||
<div>Security check</div>
|
||||
<div class="flex">
|
||||
<div id="diagram" class="mermaid"></div>
|
||||
<div id="graph-div"></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: 'base',
|
||||
startOnLoad: false,
|
||||
flowcharts: { htmlLabels: true },
|
||||
});
|
||||
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 TD
|
||||
A[["a marker-end=#quot;url(<s title='#<style>*{background:red}</style>'>b"]]
|
||||
`;
|
||||
const el = document.querySelector('#graph-div');
|
||||
console.log(diagram);
|
||||
const { svg } = await mermaid.render('graph-div', diagram);
|
||||
document.querySelector('#res').innerHTML = svg;
|
||||
window.rendered = true;
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -66,6 +66,9 @@
|
||||
<li>
|
||||
<h2><a href="./state.html">State</a></h2>
|
||||
</li>
|
||||
<li>
|
||||
<h2><a href="./zenuml.html">ZenUML</a></h2>
|
||||
</li>
|
||||
</ul>
|
||||
</body>
|
||||
</html>
|
||||
|
53
demos/zenuml.html
Normal file
53
demos/zenuml.html
Normal file
@ -0,0 +1,53 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
|
||||
<title>Mermaid Zenuml Test Page</title>
|
||||
</head>
|
||||
|
||||
<body>
|
||||
<h1>Zenuml demos</h1>
|
||||
<pre class="mermaid">
|
||||
zenuml
|
||||
title Sync Messages (Design Pattern: Adapter)
|
||||
@Starter(Client)
|
||||
Adapter.interfaceMethod() {
|
||||
translateParameter(parameter)
|
||||
|
||||
result = Implementation.implementationMethod()
|
||||
|
||||
translateResult()
|
||||
return translatedResult
|
||||
}
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
zenuml
|
||||
title Async Messages (SPA Authentication)
|
||||
// ```
|
||||
// GET https://${account.namespace}/authorize/?
|
||||
// response_type=token
|
||||
// &client_id=${account.clientId}
|
||||
// &redirect_url=YOUR_CALLBACK_URL
|
||||
// &state=VALUE_THAT_SURVIVES_REDIRECTS
|
||||
// &scope=openid
|
||||
// ```
|
||||
Browser->Auth0: 1. initiate the authentication
|
||||
Auth0->"Identity Provider": 2. OAuth2 / SAML, etc
|
||||
"Identity Provider"->"Identity Provider": 3. user gets authenticated
|
||||
Auth0->Browser: 4. redirect to ${YOUR_CALLBACK_URL}/#id_token=e68...
|
||||
Browser->Auth0: 5. validate id_token and get user profile
|
||||
Browser->"Your API": 6. call API sending JWT in Authorization header
|
||||
"Your API"->"Your API": 7. validate token
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import zenuml from './mermaid-zenuml.esm.mjs';
|
||||
await mermaid.registerExternalDiagrams([zenuml]);
|
||||
mermaid.initialize({
|
||||
logLevel: 3,
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
1054
docs/CHANGELOG.md
1054
docs/CHANGELOG.md
File diff suppressed because it is too large
Load Diff
@ -96,7 +96,7 @@ mermaid.initialize(config);
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:667](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L667)
|
||||
[mermaidAPI.ts:670](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L670)
|
||||
|
||||
## Functions
|
||||
|
||||
@ -127,7 +127,7 @@ Return the last node appended
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:306](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L306)
|
||||
[mermaidAPI.ts:309](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L309)
|
||||
|
||||
---
|
||||
|
||||
@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:285](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L285)
|
||||
[mermaidAPI.ts:288](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L288)
|
||||
|
||||
---
|
||||
|
||||
@ -320,4 +320,4 @@ Remove any existing elements from the given document
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:356](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L356)
|
||||
[mermaidAPI.ts:359](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L359)
|
||||
|
@ -16,6 +16,7 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [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) (**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**)
|
||||
@ -58,6 +59,8 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [hexo-filter-mermaid-diagrams](https://github.com/webappdevelp/hexo-filter-mermaid-diagrams)
|
||||
- [hexo-tag-mermaid](https://github.com/JameChou/hexo-tag-mermaid)
|
||||
- [hexo-mermaid-diagrams](https://github.com/mslxl/hexo-mermaid-diagrams)
|
||||
- [Nextra](https://nextra.site/)
|
||||
- [Mermaid](https://nextra.site/docs/guide/mermaid)
|
||||
|
||||
## CMS
|
||||
|
||||
@ -142,6 +145,8 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [Named block =Diagram](https://github.com/zag/podlite/tree/main/packages/podlite-diagrams)
|
||||
- [GNU Nano](https://www.nano-editor.org/)
|
||||
- [Nano Mermaid](https://github.com/Yash-Singh1/nano-mermaid)
|
||||
- [CKEditor](https://github.com/ckeditor/ckeditor5)
|
||||
- [CKEditor 5 Mermaid plugin](https://github.com/ckeditor/ckeditor5-mermaid)
|
||||
|
||||
## Document Generation
|
||||
|
||||
|
BIN
docs/syntax/img/zenuml-participant-annotators.png
Normal file
BIN
docs/syntax/img/zenuml-participant-annotators.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 255 KiB |
@ -46,9 +46,6 @@ quadrantChart
|
||||
|
||||
## Syntax
|
||||
|
||||
> **Note**
|
||||
> In place of `<text>` you can use text like `this is a sample text` or inside **double quotes** like `"This type of text may contain unicode like ❤"`.
|
||||
|
||||
> **Note**
|
||||
> If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
|
||||
> If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
|
||||
@ -152,7 +149,7 @@ Points are used to plot a circle inside the quadrantChart. The syntax is `<text>
|
||||
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
||||
quadrantChart
|
||||
x-axis Urgent --> Not Urgent
|
||||
y-axis Not Important --> important
|
||||
y-axis Not Important --> "Important ❤"
|
||||
quadrant-1 Plan
|
||||
quadrant-2 Do
|
||||
quadrant-3 Deligate
|
||||
@ -163,7 +160,7 @@ quadrantChart
|
||||
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
||||
quadrantChart
|
||||
x-axis Urgent --> Not Urgent
|
||||
y-axis Not Important --> important
|
||||
y-axis Not Important --> "Important ❤"
|
||||
quadrant-1 Plan
|
||||
quadrant-2 Do
|
||||
quadrant-3 Deligate
|
||||
|
472
docs/syntax/zenuml.md
Normal file
472
docs/syntax/zenuml.md
Normal file
@ -0,0 +1,472 @@
|
||||
> **Warning**
|
||||
>
|
||||
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/zenuml.md](../../packages/mermaid/src/docs/syntax/zenuml.md).
|
||||
|
||||
# ZenUML
|
||||
|
||||
> A Sequence diagram is an interaction diagram that shows how processes operate with one another and in what order.
|
||||
|
||||
Mermaid can render sequence diagrams with [ZenUML](https://zenuml.com). Note that ZenUML uses a different
|
||||
syntax than the original Sequence Diagram in mermaid.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Demo
|
||||
Alice->John: Hello John, how are you?
|
||||
John->Alice: Great!
|
||||
Alice->John: See you later!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Demo
|
||||
Alice->John: Hello John, how are you?
|
||||
John->Alice: Great!
|
||||
Alice->John: See you later!
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
### Participants
|
||||
|
||||
The participants can be defined implicitly as in the first example on this page. The participants or actors are
|
||||
rendered in order of appearance in the diagram source text. Sometimes you might want to show the participants in a
|
||||
different order than how they appear in the first message. It is possible to specify the actor's order of
|
||||
appearance by doing the following:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Declare participant (optional)
|
||||
Bob
|
||||
Alice
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Declare participant (optional)
|
||||
Bob
|
||||
Alice
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
### Annotators
|
||||
|
||||
If you specifically want to use symbols instead of just rectangles with text you can do so by using the annotator syntax to declare participants as per below.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Annotators
|
||||
@Actor Alice
|
||||
@Database Bob
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Annotators
|
||||
@Actor Alice
|
||||
@Database Bob
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
Here are the available annotators:
|
||||
![img.png](img/zenuml-participant-annotators.png)
|
||||
|
||||
### Aliases
|
||||
|
||||
The participants can have a convenient identifier and a descriptive label.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Aliases
|
||||
A as Alice
|
||||
J as John
|
||||
A->J: Hello John, how are you?
|
||||
J->A: Great!
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Aliases
|
||||
A as Alice
|
||||
J as John
|
||||
A->J: Hello John, how are you?
|
||||
J->A: Great!
|
||||
```
|
||||
|
||||
## Messages
|
||||
|
||||
Messages can be one of:
|
||||
|
||||
1. Sync message
|
||||
2. Async message
|
||||
3. Creation message
|
||||
4. Reply message
|
||||
|
||||
### Sync message
|
||||
|
||||
You can think of a sync (blocking) method in a programming language.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Sync message
|
||||
A.SyncMessage
|
||||
A.SyncMessage(with, parameters) {
|
||||
B.nestedSyncMessage()
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Sync message
|
||||
A.SyncMessage
|
||||
A.SyncMessage(with, parameters) {
|
||||
B.nestedSyncMessage()
|
||||
}
|
||||
```
|
||||
|
||||
### Async message
|
||||
|
||||
You can think of an async (non-blocking) method in a programming language.
|
||||
Fire an event and forget about it.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Async message
|
||||
Alice->Bob: How are you?
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Async message
|
||||
Alice->Bob: How are you?
|
||||
```
|
||||
|
||||
### Creation message
|
||||
|
||||
We use `new` keyword to create an object.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
new A1
|
||||
new A2(with, parameters)
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
new A1
|
||||
new A2(with, parameters)
|
||||
```
|
||||
|
||||
### Reply message
|
||||
|
||||
There are three ways to express a reply message:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
// 1. assign a variable from a sync message.
|
||||
a = A.SyncMessage()
|
||||
|
||||
// 1.1. optionally give the variable a type
|
||||
SomeType a = A.SyncMessage()
|
||||
|
||||
// 2. use return keyword
|
||||
A.SyncMessage() {
|
||||
return result
|
||||
}
|
||||
|
||||
// 3. use @return or @reply annotator on an async message
|
||||
@return
|
||||
A->B: result
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
// 1. assign a variable from a sync message.
|
||||
a = A.SyncMessage()
|
||||
|
||||
// 1.1. optionally give the variable a type
|
||||
SomeType a = A.SyncMessage()
|
||||
|
||||
// 2. use return keyword
|
||||
A.SyncMessage() {
|
||||
return result
|
||||
}
|
||||
|
||||
// 3. use @return or @reply annotator on an async message
|
||||
@return
|
||||
A->B: result
|
||||
```
|
||||
|
||||
The third way `@return` is rarely used, but it is useful when you want to return to one level up.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Reply message
|
||||
Client->A.method() {
|
||||
B.method() {
|
||||
if(condition) {
|
||||
return x1
|
||||
// return early
|
||||
@return
|
||||
A->Client: x11
|
||||
}
|
||||
}
|
||||
return x2
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
title Reply message
|
||||
Client->A.method() {
|
||||
B.method() {
|
||||
if(condition) {
|
||||
return x1
|
||||
// return early
|
||||
@return
|
||||
A->Client: x11
|
||||
}
|
||||
}
|
||||
return x2
|
||||
}
|
||||
```
|
||||
|
||||
## Nesting
|
||||
|
||||
Sync messages and Creation messages are naturally nestable with `{}`.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
A.method() {
|
||||
B.nested_sync_method()
|
||||
B->C: nested async message
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
A.method() {
|
||||
B.nested_sync_method()
|
||||
B->C: nested async message
|
||||
}
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
It is possible to add comments to a sequence diagram with `// comment` syntax.
|
||||
Comments will be rendered above the messages or fragments. Comments on other places
|
||||
are ignored. Markdown is supported.
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
// a comment on a participant will not be rendered
|
||||
BookService
|
||||
// a comment on a message.
|
||||
// **Markdown** is supported.
|
||||
BookService.getBook()
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
// a comment on a participant will not be rendered
|
||||
BookService
|
||||
// a comment on a message.
|
||||
// **Markdown** is supported.
|
||||
BookService.getBook()
|
||||
```
|
||||
|
||||
## Loops
|
||||
|
||||
It is possible to express loops in a ZenUML diagram. This is done by any of the
|
||||
following notations:
|
||||
|
||||
1. while
|
||||
2. for
|
||||
3. forEach, foreach
|
||||
4. loop
|
||||
|
||||
```zenuml
|
||||
while(condition) {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->John: Hello John, how are you?
|
||||
while(true) {
|
||||
John->Alice: Great!
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
Alice->John: Hello John, how are you?
|
||||
while(true) {
|
||||
John->Alice: Great!
|
||||
}
|
||||
```
|
||||
|
||||
## Alt
|
||||
|
||||
It is possible to express alternative paths in a sequence diagram. This is done by the notation
|
||||
|
||||
```zenuml
|
||||
if(condition1) {
|
||||
...statements...
|
||||
} else if(condition2) {
|
||||
...statements...
|
||||
} else {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
if(is_sick) {
|
||||
Bob->Alice: Not so good :(
|
||||
} else {
|
||||
Bob->Alice: Feeling fresh like a daisy
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
if(is_sick) {
|
||||
Bob->Alice: Not so good :(
|
||||
} else {
|
||||
Bob->Alice: Feeling fresh like a daisy
|
||||
}
|
||||
```
|
||||
|
||||
## Opt
|
||||
|
||||
It is possible to render an `opt` fragment. This is done by the notation
|
||||
|
||||
```zenuml
|
||||
opt {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Not so good :(
|
||||
opt {
|
||||
Bob->Alice: Thanks for asking
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Not so good :(
|
||||
opt {
|
||||
Bob->Alice: Thanks for asking
|
||||
}
|
||||
```
|
||||
|
||||
## Parallel
|
||||
|
||||
It is possible to show actions that are happening in parallel.
|
||||
|
||||
This is done by the notation
|
||||
|
||||
```zenuml
|
||||
par {
|
||||
statement1
|
||||
statement2
|
||||
statement3
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
par {
|
||||
Alice->Bob: Hello guys!
|
||||
Alice->John: Hello guys!
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
par {
|
||||
Alice->Bob: Hello guys!
|
||||
Alice->John: Hello guys!
|
||||
}
|
||||
```
|
||||
|
||||
## Try/Catch/Finally (Break)
|
||||
|
||||
It is possible to indicate a stop of the sequence within the flow (usually used to model exceptions).
|
||||
|
||||
This is done by the notation
|
||||
|
||||
try {
|
||||
...statements...
|
||||
} catch {
|
||||
...statements...
|
||||
} finally {
|
||||
...statements...
|
||||
}
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
try {
|
||||
Consumer->API: Book something
|
||||
API->BookingService: Start booking process
|
||||
} catch {
|
||||
API->Consumer: show failure
|
||||
} finally {
|
||||
API->BookingService: rollback status
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
zenuml
|
||||
try {
|
||||
Consumer->API: Book something
|
||||
API->BookingService: Start booking process
|
||||
} catch {
|
||||
API->Consumer: show failure
|
||||
} finally {
|
||||
API->BookingService: rollback status
|
||||
}
|
||||
```
|
||||
|
||||
## Integrating with your library/website.
|
||||
|
||||
Zenuml uses the experimental lazy loading & async rendering features which could change in the future.
|
||||
|
||||
You can use this method to add mermaid including the zenuml diagram to a web page:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import zenuml from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml@0.1.0/dist/mermaid-zenuml.esm.min.mjs';
|
||||
await mermaid.registerExternalDiagrams([zenuml]);
|
||||
</script>
|
||||
```
|
@ -1,7 +1,7 @@
|
||||
{
|
||||
"name": "mermaid-monorepo",
|
||||
"private": true,
|
||||
"version": "10.2.0",
|
||||
"version": "10.2.3",
|
||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@8.5.1",
|
||||
@ -18,7 +18,7 @@
|
||||
"build:vite": "ts-node-esm --transpileOnly .vite/build.ts",
|
||||
"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-example-diagram/tsconfig.json --emitDeclarationOnly",
|
||||
"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: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\"",
|
||||
|
1
packages/mermaid-zenuml/README.md
Symbolic link
1
packages/mermaid-zenuml/README.md
Symbolic link
@ -0,0 +1 @@
|
||||
../mermaid/src/docs/syntax/zenuml.md
|
47
packages/mermaid-zenuml/package.json
Normal file
47
packages/mermaid-zenuml/package.json
Normal file
@ -0,0 +1,47 @@
|
||||
{
|
||||
"name": "@mermaid-js/mermaid-zenuml",
|
||||
"version": "0.1.0",
|
||||
"description": "MermaidJS plugin for ZenUML integration",
|
||||
"module": "dist/mermaid-zenuml.core.mjs",
|
||||
"types": "dist/detector.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/mermaid-zenuml.core.mjs",
|
||||
"types": "./dist/detector.d.ts"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
"zenuml",
|
||||
"mermaid"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mermaid-js/mermaid",
|
||||
"directory": "packages/mermaid-zenuml"
|
||||
},
|
||||
"contributors": [
|
||||
"Peng Xiao (https://github.com/MrCoder)",
|
||||
"Sidharth Vinod (https://sidharth.dev)",
|
||||
"Dong Cai (https://github.com/dontry)"
|
||||
],
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@zenuml/core": "^3.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"mermaid": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mermaid": "workspace:>=10.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
]
|
||||
}
|
21
packages/mermaid-zenuml/src/detector.ts
Normal file
21
packages/mermaid-zenuml/src/detector.ts
Normal file
@ -0,0 +1,21 @@
|
||||
import type { ExternalDiagramDefinition } from 'mermaid';
|
||||
|
||||
const id = 'zenuml';
|
||||
const regexp = /^\s*zenuml/;
|
||||
|
||||
const detector = (txt: string) => {
|
||||
return txt.match(regexp) !== null;
|
||||
};
|
||||
|
||||
const loader = async () => {
|
||||
const { diagram } = await import('./zenuml-definition.js');
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
58
packages/mermaid-zenuml/src/mermaidUtils.ts
Normal file
58
packages/mermaid-zenuml/src/mermaidUtils.ts
Normal file
@ -0,0 +1,58 @@
|
||||
import type { MermaidConfig } from 'mermaid';
|
||||
|
||||
const warning = (s: string) => {
|
||||
// Todo remove debug code
|
||||
// eslint-disable-next-line no-console
|
||||
console.error('Log function was called before initialization', s);
|
||||
};
|
||||
|
||||
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
||||
|
||||
export const LEVELS: Record<LogLevel, number> = {
|
||||
trace: 0,
|
||||
debug: 1,
|
||||
info: 2,
|
||||
warn: 3,
|
||||
error: 4,
|
||||
fatal: 5,
|
||||
};
|
||||
|
||||
export const log: Record<keyof typeof LEVELS, typeof console.log> = {
|
||||
trace: warning,
|
||||
debug: warning,
|
||||
info: warning,
|
||||
warn: warning,
|
||||
error: warning,
|
||||
fatal: warning,
|
||||
};
|
||||
|
||||
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
|
||||
export let getConfig: () => MermaidConfig;
|
||||
export let sanitizeText: (str: string) => string;
|
||||
// eslint-disable @typescript-eslint/no-explicit-any
|
||||
export let setupGraphViewbox: (
|
||||
graph: any,
|
||||
svgElem: any,
|
||||
padding: any,
|
||||
useMaxWidth: boolean
|
||||
) => void;
|
||||
|
||||
export const injectUtils = (
|
||||
_log: Record<keyof typeof LEVELS, typeof console.log>,
|
||||
_setLogLevel: any,
|
||||
_getConfig: any,
|
||||
_sanitizeText: any,
|
||||
_setupGraphViewbox: any
|
||||
) => {
|
||||
_log.info('Mermaid utils injected');
|
||||
log.trace = _log.trace;
|
||||
log.debug = _log.debug;
|
||||
log.info = _log.info;
|
||||
log.warn = _log.warn;
|
||||
log.error = _log.error;
|
||||
log.fatal = _log.fatal;
|
||||
setLogLevel = _setLogLevel;
|
||||
getConfig = _getConfig;
|
||||
sanitizeText = _sanitizeText;
|
||||
setupGraphViewbox = _setupGraphViewbox;
|
||||
};
|
12
packages/mermaid-zenuml/src/parser.ts
Normal file
12
packages/mermaid-zenuml/src/parser.ts
Normal file
@ -0,0 +1,12 @@
|
||||
/**
|
||||
* ZenUML manage parsing internally. It uses Antlr4 to parse the DSL.
|
||||
* The parser is defined in https://github.com/ZenUml/vue-sequence/blob/main/src/parser/index.js
|
||||
*
|
||||
* This is a dummy parser that satisfies the mermaid API logic.
|
||||
*/
|
||||
export default {
|
||||
parser: { yy: {} },
|
||||
parse: () => {
|
||||
// no op
|
||||
},
|
||||
};
|
17
packages/mermaid-zenuml/src/zenuml-definition.ts
Normal file
17
packages/mermaid-zenuml/src/zenuml-definition.ts
Normal file
@ -0,0 +1,17 @@
|
||||
import { injectUtils } from './mermaidUtils.js';
|
||||
import parser from './parser.js';
|
||||
import renderer from './zenumlRenderer.js';
|
||||
|
||||
export const diagram = {
|
||||
db: {
|
||||
clear: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
renderer,
|
||||
parser,
|
||||
styles: () => {
|
||||
// no-op
|
||||
},
|
||||
injectUtils,
|
||||
};
|
68
packages/mermaid-zenuml/src/zenumlRenderer.ts
Normal file
68
packages/mermaid-zenuml/src/zenumlRenderer.ts
Normal file
@ -0,0 +1,68 @@
|
||||
import { getConfig, log } from './mermaidUtils.js';
|
||||
import ZenUml from '@zenuml/core';
|
||||
|
||||
const regexp = /^\s*zenuml/;
|
||||
|
||||
// Create a Zen UML container outside the svg first for rendering, otherwise the Zen UML diagram cannot be rendered properly
|
||||
function createTemporaryZenumlContainer(id: string) {
|
||||
const container = document.createElement('div');
|
||||
container.id = `container-${id}`;
|
||||
container.style.display = 'flex';
|
||||
container.innerHTML = `<div id="zenUMLApp-${id}"></div>`;
|
||||
const app = container.querySelector(`#zenUMLApp-${id}`) as HTMLElement;
|
||||
return { container, app };
|
||||
}
|
||||
|
||||
// Create a foreignObject to wrap the Zen UML container in the svg
|
||||
function createForeignObject(id: string) {
|
||||
const foreignObject = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
|
||||
foreignObject.setAttribute('x', '0');
|
||||
foreignObject.setAttribute('y', '0');
|
||||
foreignObject.setAttribute('width', '100%');
|
||||
foreignObject.setAttribute('height', '100%');
|
||||
const { container, app } = createTemporaryZenumlContainer(id);
|
||||
foreignObject.appendChild(container);
|
||||
return { foreignObject, container, app };
|
||||
}
|
||||
|
||||
/**
|
||||
* Draws a Zen UML in the tag with id: id based on the graph definition in text.
|
||||
*
|
||||
* @param text - The text of the diagram
|
||||
* @param id - The id of the diagram which will be used as a DOM element id¨
|
||||
*/
|
||||
export const draw = async function (text: string, id: string) {
|
||||
log.info('draw with Zen UML renderer', ZenUml);
|
||||
|
||||
text = text.replace(regexp, '');
|
||||
const { securityLevel } = getConfig();
|
||||
// Handle root and Document for when rendering in sandbox mode
|
||||
let sandboxElement: HTMLIFrameElement | null = null;
|
||||
if (securityLevel === 'sandbox') {
|
||||
sandboxElement = document.getElementById('i' + id) as HTMLIFrameElement;
|
||||
}
|
||||
|
||||
const root = securityLevel === 'sandbox' ? sandboxElement?.contentWindow?.document : document;
|
||||
|
||||
const svgContainer = root?.querySelector(`svg#${id}`);
|
||||
|
||||
if (!root || !svgContainer) {
|
||||
log.error('Cannot find root or svgContainer');
|
||||
return;
|
||||
}
|
||||
|
||||
const { foreignObject, container, app } = createForeignObject(id);
|
||||
svgContainer.appendChild(foreignObject);
|
||||
// @ts-expect-error @zenuml/core@3.0.0 exports the wrong type for ZenUml
|
||||
const zenuml = new ZenUml(app);
|
||||
// default is a theme name. More themes to be added and will be configurable in the future
|
||||
await zenuml.render(text, 'theme-mermaid');
|
||||
|
||||
const { width, height } = window.getComputedStyle(container);
|
||||
log.debug('zenuml diagram size', width, height);
|
||||
svgContainer.setAttribute('style', `width: ${width}; height: ${height};`);
|
||||
};
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
9
packages/mermaid-zenuml/tsconfig.json
Normal file
9
packages/mermaid-zenuml/tsconfig.json
Normal file
@ -0,0 +1,9 @@
|
||||
{
|
||||
"extends": "../../tsconfig.json",
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "./dist"
|
||||
},
|
||||
"include": ["./src/**/*.ts"],
|
||||
"typeRoots": ["./src/types"]
|
||||
}
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "10.2.0",
|
||||
"version": "10.2.3",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
@ -28,7 +28,7 @@
|
||||
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts",
|
||||
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify",
|
||||
"docs:pre:vitepress": "rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress",
|
||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm --filter ./ install --no-frozen-lockfile && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && pnpm --filter ./ install --no-frozen-lockfile --ignore-scripts && pnpm run build) && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing",
|
||||
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./ src/vitepress dev\" \"ts-node-esm src/docs.mts --watch --vitepress\"",
|
||||
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
|
||||
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
|
||||
|
@ -717,7 +717,7 @@ const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
|
||||
const edgePath = edgesEl
|
||||
.insert('path')
|
||||
.attr('d', curve(points))
|
||||
.attr('class', 'path')
|
||||
.attr('class', 'path ' + edgeData.classes)
|
||||
.attr('fill', 'none');
|
||||
const edgeG = edgesEl.insert('g').attr('class', 'edgeLabel');
|
||||
const edgeWithLabel = select(edgeG.node().appendChild(edge.labelEl));
|
||||
|
@ -1,8 +1,8 @@
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
import dayjs from 'dayjs/esm/index.js';
|
||||
import dayjsIsoWeek from 'dayjs/esm/plugin/isoWeek/index.js';
|
||||
import dayjsCustomParseFormat from 'dayjs/esm/plugin/customParseFormat/index.js';
|
||||
import dayjsAdvancedFormat from 'dayjs/esm/plugin/advancedFormat/index.js';
|
||||
import dayjs from 'dayjs';
|
||||
import dayjsIsoWeek from 'dayjs/plugin/isoWeek.js';
|
||||
import dayjsCustomParseFormat from 'dayjs/plugin/customParseFormat.js';
|
||||
import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat.js';
|
||||
import { log } from '../../logger.js';
|
||||
import * as configApi from '../../config.js';
|
||||
import utils from '../../utils.js';
|
||||
|
@ -1,5 +1,5 @@
|
||||
// @ts-nocheck TODO: Fix TS
|
||||
import dayjs from 'dayjs/esm/index.js';
|
||||
import dayjs from 'dayjs';
|
||||
import ganttDb from './ganttDb.js';
|
||||
import { convert } from '../../tests/util.js';
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
import dayjs from 'dayjs/esm/index.js';
|
||||
import dayjs from 'dayjs';
|
||||
import { log } from '../../logger.js';
|
||||
import {
|
||||
select,
|
||||
|
@ -119,10 +119,10 @@ points
|
||||
|
||||
axisDetails
|
||||
: X-AXIS text AXIS-TEXT-DELIMITER text {yy.setXAxisLeftText($2); yy.setXAxisRightText($4);}
|
||||
| X-AXIS text AXIS-TEXT-DELIMITER {$2.text += $3; yy.setXAxisLeftText($2);}
|
||||
| X-AXIS text AXIS-TEXT-DELIMITER {$2.text += " ⟶ "; yy.setXAxisLeftText($2);}
|
||||
| X-AXIS text {yy.setXAxisLeftText($2);}
|
||||
| Y-AXIS text AXIS-TEXT-DELIMITER text {yy.setYAxisBottomText($2); yy.setYAxisTopText($4);}
|
||||
| Y-AXIS text AXIS-TEXT-DELIMITER {$2.text += $3; yy.setYAxisBottomText($2);}
|
||||
| Y-AXIS text AXIS-TEXT-DELIMITER {$2.text += " ⟶ "; yy.setYAxisBottomText($2);}
|
||||
| Y-AXIS text {yy.setYAxisBottomText($2);}
|
||||
;
|
||||
|
||||
|
@ -93,7 +93,7 @@ describe('Testing quadrantChart jison file', () => {
|
||||
str = 'quadrantChart\n x-AxIs "Urgent(* +=[❤" --> ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
|
||||
text: 'Urgent(* +=[❤ --> ',
|
||||
text: 'Urgent(* +=[❤ ⟶ ',
|
||||
type: 'text',
|
||||
});
|
||||
expect(mockDB.setXAxisRightText).not.toHaveBeenCalled();
|
||||
@ -131,7 +131,7 @@ describe('Testing quadrantChart jison file', () => {
|
||||
str = 'quadrantChart\n y-AxIs "Urgent(* +=[❤" --> ';
|
||||
expect(parserFnConstructor(str)).not.toThrow();
|
||||
expect(mockDB.setYAxisBottomText).toHaveBeenCalledWith({
|
||||
text: 'Urgent(* +=[❤ --> ',
|
||||
text: 'Urgent(* +=[❤ ⟶ ',
|
||||
type: 'text',
|
||||
});
|
||||
expect(mockDB.setYAxisTopText).not.toHaveBeenCalled();
|
||||
|
@ -1,7 +1,7 @@
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import { addFunction } from '../../interactionDb.js';
|
||||
import { parseFontSize } from '../../utils.js';
|
||||
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
@ -224,15 +224,16 @@ export const drawText = function (elem, textData) {
|
||||
textElem.attr('dy', dy);
|
||||
}
|
||||
|
||||
const text = line || ZERO_WIDTH_SPACE;
|
||||
if (textData.tspan) {
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x);
|
||||
if (textData.fill !== undefined) {
|
||||
span.attr('fill', textData.fill);
|
||||
}
|
||||
span.text(line);
|
||||
span.text(text);
|
||||
} else {
|
||||
textElem.text(line);
|
||||
textElem.text(text);
|
||||
}
|
||||
if (
|
||||
textData.valign !== undefined &&
|
||||
|
@ -5,12 +5,12 @@ import { contributors } from '../contributors';
|
||||
<template>
|
||||
<div flex="~ wrap gap2" justify-center>
|
||||
<a
|
||||
v-for="{ name, avatar } of contributors"
|
||||
:key="name"
|
||||
:href="`https://github.com/${name}`"
|
||||
v-for="{ username, avatar } of contributors"
|
||||
:key="username"
|
||||
:href="`https://github.com/${username}`"
|
||||
m-0
|
||||
rel="noopener noreferrer"
|
||||
:aria-label="`${name} on GitHub`"
|
||||
:aria-label="`${username} on GitHub`"
|
||||
>
|
||||
<img
|
||||
loading="lazy"
|
||||
@ -20,7 +20,7 @@ import { contributors } from '../contributors';
|
||||
rounded-full
|
||||
h-12
|
||||
w-12
|
||||
:alt="`${name}'s avatar`"
|
||||
:alt="`${username}'s avatar`"
|
||||
/>
|
||||
</a>
|
||||
</div>
|
||||
|
@ -121,6 +121,7 @@ function sidebarSyntax() {
|
||||
{ text: 'C4C Diagram (Context) Diagram 🦺⚠️', link: '/syntax/c4c' },
|
||||
{ text: 'Mindmaps 🔥', link: '/syntax/mindmap' },
|
||||
{ text: 'Timeline 🔥', link: '/syntax/timeline' },
|
||||
{ text: 'Zenuml 🔥', link: '/syntax/zenuml' },
|
||||
{ text: 'Other Examples', link: '/syntax/examples' },
|
||||
],
|
||||
},
|
||||
|
@ -1,3 +1,4 @@
|
||||
/* eslint-disable no-console */
|
||||
import { mkdir, writeFile, readFile } from 'node:fs/promises';
|
||||
import { existsSync } from 'node:fs';
|
||||
import { fileURLToPath } from 'url';
|
||||
@ -12,22 +13,23 @@ async function download(url: string, fileName: URL) {
|
||||
if (existsSync(fileName)) {
|
||||
return;
|
||||
}
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('downloading', fileName);
|
||||
console.log('downloading', url);
|
||||
try {
|
||||
const image = await fetch(url);
|
||||
await writeFile(fileName, Buffer.from(await image.arrayBuffer()));
|
||||
} catch {}
|
||||
} catch (error) {
|
||||
console.error(error);
|
||||
}
|
||||
}
|
||||
|
||||
async function fetchAvatars() {
|
||||
await mkdir(fileURLToPath(new URL('..', getAvatarPath('none'))), { recursive: true });
|
||||
await mkdir(fileURLToPath(new URL(getAvatarPath('none'))).replace('none.png', ''), {
|
||||
recursive: true,
|
||||
});
|
||||
contributors = JSON.parse(await readFile(pathContributors, { encoding: 'utf-8' }));
|
||||
await Promise.allSettled(
|
||||
contributors.map((name) =>
|
||||
download(`https://github.com/${name}.png?size=100`, getAvatarPath(name))
|
||||
)
|
||||
);
|
||||
for (const name of contributors) {
|
||||
await download(`https://github.com/${name}.png?size=100`, getAvatarPath(name));
|
||||
}
|
||||
}
|
||||
|
||||
fetchAvatars();
|
||||
|
@ -23,9 +23,8 @@ async function fetchContributors() {
|
||||
}
|
||||
);
|
||||
data = await response.json();
|
||||
console.log(response.status, response.statusText);
|
||||
console.log(data);
|
||||
collaborators.push(...data.map((i) => i.login));
|
||||
console.log(`Fetched page ${page}`);
|
||||
page++;
|
||||
} while (data.length === 100);
|
||||
return collaborators.filter((name) => !name.includes('[bot]'));
|
||||
|
@ -1,6 +1,10 @@
|
||||
import mermaid, { type MermaidConfig } from 'mermaid';
|
||||
import zenuml from '../../../../../mermaid-zenuml/dist/mermaid-zenuml.core.mjs';
|
||||
|
||||
const init = mermaid.registerExternalDiagrams([zenuml]);
|
||||
|
||||
export const render = async (id: string, code: string, config: MermaidConfig): Promise<string> => {
|
||||
await init;
|
||||
mermaid.initialize(config);
|
||||
const { svg } = await mermaid.render(id, code);
|
||||
return svg;
|
||||
|
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [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) (**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**)
|
||||
@ -52,6 +53,8 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [hexo-filter-mermaid-diagrams](https://github.com/webappdevelp/hexo-filter-mermaid-diagrams)
|
||||
- [hexo-tag-mermaid](https://github.com/JameChou/hexo-tag-mermaid)
|
||||
- [hexo-mermaid-diagrams](https://github.com/mslxl/hexo-mermaid-diagrams)
|
||||
- [Nextra](https://nextra.site/)
|
||||
- [Mermaid](https://nextra.site/docs/guide/mermaid)
|
||||
|
||||
## CMS
|
||||
|
||||
@ -136,6 +139,8 @@ They also serve as proof of concept, for the variety of things that can be built
|
||||
- [Named block =Diagram](https://github.com/zag/podlite/tree/main/packages/podlite-diagrams)
|
||||
- [GNU Nano](https://www.nano-editor.org/)
|
||||
- [Nano Mermaid](https://github.com/Yash-Singh1/nano-mermaid)
|
||||
- [CKEditor](https://github.com/ckeditor/ckeditor5)
|
||||
- [CKEditor 5 Mermaid plugin](https://github.com/ckeditor/ckeditor5-mermaid)
|
||||
|
||||
## Document Generation
|
||||
|
||||
|
@ -30,7 +30,7 @@
|
||||
"unplugin-vue-components": "^0.24.1",
|
||||
"vite": "^4.3.3",
|
||||
"vite-plugin-pwa": "^0.15.0",
|
||||
"vitepress": "1.0.0-alpha.76",
|
||||
"vitepress": "1.0.0-beta.1",
|
||||
"workbox-window": "^6.5.4"
|
||||
}
|
||||
}
|
||||
|
Binary file not shown.
After Width: | Height: | Size: 255 KiB |
@ -23,10 +23,6 @@ quadrantChart
|
||||
|
||||
## Syntax
|
||||
|
||||
```note
|
||||
In place of `<text>` you can use text like `this is a sample text` or inside **double quotes** like `"This type of text may contain unicode like ❤"`.
|
||||
```
|
||||
|
||||
```note
|
||||
If there is no points available in the chart both **axis** text and **quadrant** will be rendered in the center of the respective quadrant.
|
||||
If there are points **x-axis** labels will rendered from left of the respective quadrant also they will be displayed in bottom of the chart, and **y-axis** lables will be rendered in bottom of the respective quadrant, the quadrant text will render at top of the respective quadrant.
|
||||
@ -134,7 +130,7 @@ Points are used to plot a circle inside the quadrantChart. The syntax is `<text>
|
||||
%%{init: {"quadrantChart": {"chartWidth": 400, "chartHeight": 400}, "themeVariables": {"quadrant1TextFill": "#ff0000"} }}%%
|
||||
quadrantChart
|
||||
x-axis Urgent --> Not Urgent
|
||||
y-axis Not Important --> important
|
||||
y-axis Not Important --> "Important ❤"
|
||||
quadrant-1 Plan
|
||||
quadrant-2 Do
|
||||
quadrant-3 Deligate
|
||||
|
314
packages/mermaid/src/docs/syntax/zenuml.md
Normal file
314
packages/mermaid/src/docs/syntax/zenuml.md
Normal file
@ -0,0 +1,314 @@
|
||||
# ZenUML
|
||||
|
||||
> A Sequence diagram is an interaction diagram that shows how processes operate with one another and in what order.
|
||||
|
||||
Mermaid can render sequence diagrams with [ZenUML](https://zenuml.com). Note that ZenUML uses a different
|
||||
syntax than the original Sequence Diagram in mermaid.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Demo
|
||||
Alice->John: Hello John, how are you?
|
||||
John->Alice: Great!
|
||||
Alice->John: See you later!
|
||||
```
|
||||
|
||||
## Syntax
|
||||
|
||||
### Participants
|
||||
|
||||
The participants can be defined implicitly as in the first example on this page. The participants or actors are
|
||||
rendered in order of appearance in the diagram source text. Sometimes you might want to show the participants in a
|
||||
different order than how they appear in the first message. It is possible to specify the actor's order of
|
||||
appearance by doing the following:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Declare participant (optional)
|
||||
Bob
|
||||
Alice
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
### Annotators
|
||||
|
||||
If you specifically want to use symbols instead of just rectangles with text you can do so by using the annotator syntax to declare participants as per below.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Annotators
|
||||
@Actor Alice
|
||||
@Database Bob
|
||||
Alice->Bob: Hi Bob
|
||||
Bob->Alice: Hi Alice
|
||||
```
|
||||
|
||||
Here are the available annotators:
|
||||
![img.png](img/zenuml-participant-annotators.png)
|
||||
|
||||
### Aliases
|
||||
|
||||
The participants can have a convenient identifier and a descriptive label.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Aliases
|
||||
A as Alice
|
||||
J as John
|
||||
A->J: Hello John, how are you?
|
||||
J->A: Great!
|
||||
```
|
||||
|
||||
## Messages
|
||||
|
||||
Messages can be one of:
|
||||
|
||||
1. Sync message
|
||||
2. Async message
|
||||
3. Creation message
|
||||
4. Reply message
|
||||
|
||||
### Sync message
|
||||
|
||||
You can think of a sync (blocking) method in a programming language.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Sync message
|
||||
A.SyncMessage
|
||||
A.SyncMessage(with, parameters) {
|
||||
B.nestedSyncMessage()
|
||||
}
|
||||
```
|
||||
|
||||
### Async message
|
||||
|
||||
You can think of an async (non-blocking) method in a programming language.
|
||||
Fire an event and forget about it.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Async message
|
||||
Alice->Bob: How are you?
|
||||
```
|
||||
|
||||
### Creation message
|
||||
|
||||
We use `new` keyword to create an object.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
new A1
|
||||
new A2(with, parameters)
|
||||
```
|
||||
|
||||
### Reply message
|
||||
|
||||
There are three ways to express a reply message:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
// 1. assign a variable from a sync message.
|
||||
a = A.SyncMessage()
|
||||
|
||||
// 1.1. optionally give the variable a type
|
||||
SomeType a = A.SyncMessage()
|
||||
|
||||
// 2. use return keyword
|
||||
A.SyncMessage() {
|
||||
return result
|
||||
}
|
||||
|
||||
// 3. use @return or @reply annotator on an async message
|
||||
@return
|
||||
A->B: result
|
||||
```
|
||||
|
||||
The third way `@return` is rarely used, but it is useful when you want to return to one level up.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
title Reply message
|
||||
Client->A.method() {
|
||||
B.method() {
|
||||
if(condition) {
|
||||
return x1
|
||||
// return early
|
||||
@return
|
||||
A->Client: x11
|
||||
}
|
||||
}
|
||||
return x2
|
||||
}
|
||||
```
|
||||
|
||||
## Nesting
|
||||
|
||||
Sync messages and Creation messages are naturally nestable with `{}`.
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
A.method() {
|
||||
B.nested_sync_method()
|
||||
B->C: nested async message
|
||||
}
|
||||
```
|
||||
|
||||
## Comments
|
||||
|
||||
It is possible to add comments to a sequence diagram with `// comment` syntax.
|
||||
Comments will be rendered above the messages or fragments. Comments on other places
|
||||
are ignored. Markdown is supported.
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
// a comment on a participant will not be rendered
|
||||
BookService
|
||||
// a comment on a message.
|
||||
// **Markdown** is supported.
|
||||
BookService.getBook()
|
||||
```
|
||||
|
||||
## Loops
|
||||
|
||||
It is possible to express loops in a ZenUML diagram. This is done by any of the
|
||||
following notations:
|
||||
|
||||
1. while
|
||||
2. for
|
||||
3. forEach, foreach
|
||||
4. loop
|
||||
|
||||
```zenuml
|
||||
while(condition) {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->John: Hello John, how are you?
|
||||
while(true) {
|
||||
John->Alice: Great!
|
||||
}
|
||||
```
|
||||
|
||||
## Alt
|
||||
|
||||
It is possible to express alternative paths in a sequence diagram. This is done by the notation
|
||||
|
||||
```zenuml
|
||||
if(condition1) {
|
||||
...statements...
|
||||
} else if(condition2) {
|
||||
...statements...
|
||||
} else {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
if(is_sick) {
|
||||
Bob->Alice: Not so good :(
|
||||
} else {
|
||||
Bob->Alice: Feeling fresh like a daisy
|
||||
}
|
||||
```
|
||||
|
||||
## Opt
|
||||
|
||||
It is possible to render an `opt` fragment. This is done by the notation
|
||||
|
||||
```zenuml
|
||||
opt {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
Alice->Bob: Hello Bob, how are you?
|
||||
Bob->Alice: Not so good :(
|
||||
opt {
|
||||
Bob->Alice: Thanks for asking
|
||||
}
|
||||
```
|
||||
|
||||
## Parallel
|
||||
|
||||
It is possible to show actions that are happening in parallel.
|
||||
|
||||
This is done by the notation
|
||||
|
||||
```zenuml
|
||||
par {
|
||||
statement1
|
||||
statement2
|
||||
statement3
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
par {
|
||||
Alice->Bob: Hello guys!
|
||||
Alice->John: Hello guys!
|
||||
}
|
||||
```
|
||||
|
||||
## Try/Catch/Finally (Break)
|
||||
|
||||
It is possible to indicate a stop of the sequence within the flow (usually used to model exceptions).
|
||||
|
||||
This is done by the notation
|
||||
|
||||
```
|
||||
try {
|
||||
...statements...
|
||||
} catch {
|
||||
...statements...
|
||||
} finally {
|
||||
...statements...
|
||||
}
|
||||
```
|
||||
|
||||
See the example below:
|
||||
|
||||
```mermaid-example
|
||||
zenuml
|
||||
try {
|
||||
Consumer->API: Book something
|
||||
API->BookingService: Start booking process
|
||||
} catch {
|
||||
API->Consumer: show failure
|
||||
} finally {
|
||||
API->BookingService: rollback status
|
||||
}
|
||||
```
|
||||
|
||||
## Integrating with your library/website.
|
||||
|
||||
Zenuml uses the experimental lazy loading & async rendering features which could change in the future.
|
||||
|
||||
You can use this method to add mermaid including the zenuml diagram to a web page:
|
||||
|
||||
```html
|
||||
<script type="module">
|
||||
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs';
|
||||
import zenuml from 'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-zenuml@0.1.0/dist/mermaid-zenuml.esm.min.mjs';
|
||||
await mermaid.registerExternalDiagrams([zenuml]);
|
||||
</script>
|
||||
```
|
@ -2,7 +2,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
/* eslint-disable @typescript-eslint/no-empty-function */
|
||||
/* eslint-disable no-console */
|
||||
import dayjs from 'dayjs/esm/index.js';
|
||||
import dayjs from 'dayjs';
|
||||
|
||||
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
|
||||
|
||||
|
@ -263,7 +263,10 @@ export const cleanUpSvgCode = (
|
||||
|
||||
// Replace marker-end urls with just the # anchor (remove the preceding part of the URL)
|
||||
if (!useArrowMarkerUrls && !inSandboxMode) {
|
||||
cleanedUpSvg = cleanedUpSvg.replace(/marker-end="url\(.*?#/g, 'marker-end="url(#');
|
||||
cleanedUpSvg = cleanedUpSvg.replace(
|
||||
/marker-end="url\([\d+./:=?A-Za-z-]*?#/g,
|
||||
'marker-end="url(#'
|
||||
);
|
||||
}
|
||||
|
||||
cleanedUpSvg = decodeEntities(cleanedUpSvg);
|
||||
|
@ -78,6 +78,22 @@ function createTspan(textElement, lineIndex, lineHeight) {
|
||||
.attr('dy', lineHeight + 'em');
|
||||
}
|
||||
|
||||
/**
|
||||
* Compute the width of rendered text
|
||||
* @param {object} parentNode
|
||||
* @param {number} lineHeight
|
||||
* @param {string} text
|
||||
* @returns {number}
|
||||
*/
|
||||
function computeWidthOfText(parentNode, lineHeight, text) {
|
||||
const testElement = parentNode.append('text');
|
||||
const testSpan = createTspan(testElement, 1, lineHeight);
|
||||
updateTextContentAndStyles(testSpan, [{ content: text, type: 'normal' }]);
|
||||
const textLength = testSpan.node().getComputedTextLength();
|
||||
testElement.remove();
|
||||
return textLength;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a formatted text element by breaking lines and applying styles based on
|
||||
* the given structuredText.
|
||||
@ -95,31 +111,44 @@ function createFormattedText(width, g, structuredText, addBackground = false) {
|
||||
// .attr('dominant-baseline', 'middle')
|
||||
// .attr('text-anchor', 'middle');
|
||||
// .attr('text-anchor', 'middle');
|
||||
let lineIndex = -1;
|
||||
let lineIndex = 0;
|
||||
structuredText.forEach((line) => {
|
||||
lineIndex++;
|
||||
let tspan = createTspan(textElement, lineIndex, lineHeight);
|
||||
|
||||
let words = [...line].reverse();
|
||||
let currentWord;
|
||||
let wrappedLine = [];
|
||||
|
||||
while (words.length) {
|
||||
currentWord = words.pop();
|
||||
wrappedLine.push(currentWord);
|
||||
|
||||
updateTextContentAndStyles(tspan, wrappedLine);
|
||||
|
||||
if (tspan.node().getComputedTextLength() > width) {
|
||||
wrappedLine.pop();
|
||||
words.push(currentWord);
|
||||
|
||||
updateTextContentAndStyles(tspan, wrappedLine);
|
||||
|
||||
wrappedLine = [];
|
||||
lineIndex++;
|
||||
tspan = createTspan(textElement, lineIndex, lineHeight);
|
||||
/**
|
||||
* Preprocess raw string content of line data
|
||||
* Creating an array of strings pre-split to satisfy width limit
|
||||
*/
|
||||
let fullStr = line.map((data) => data.content).join(' ');
|
||||
let tempStr = '';
|
||||
let linesUnderWidth = [];
|
||||
let prevIndex = 0;
|
||||
if (computeWidthOfText(labelGroup, lineHeight, fullStr) <= width) {
|
||||
linesUnderWidth.push(fullStr);
|
||||
} else {
|
||||
for (let i = 0; i <= fullStr.length; i++) {
|
||||
tempStr = fullStr.slice(prevIndex, i);
|
||||
log.info(tempStr, prevIndex, i);
|
||||
if (computeWidthOfText(labelGroup, lineHeight, tempStr) > width) {
|
||||
const subStr = fullStr.slice(prevIndex, i);
|
||||
// Break at space if any
|
||||
const lastSpaceIndex = subStr.lastIndexOf(' ');
|
||||
if (lastSpaceIndex > -1) {
|
||||
i = prevIndex + lastSpaceIndex + 1;
|
||||
}
|
||||
linesUnderWidth.push(fullStr.slice(prevIndex, i).trim());
|
||||
prevIndex = i;
|
||||
tempStr = null;
|
||||
}
|
||||
}
|
||||
if (tempStr != null) {
|
||||
linesUnderWidth.push(tempStr);
|
||||
}
|
||||
}
|
||||
/** Add each prepared line as a tspan to the parent node */
|
||||
const preparedLines = linesUnderWidth.map((w) => ({ content: w, type: line.type }));
|
||||
for (const preparedLine of preparedLines) {
|
||||
let tspan = createTspan(textElement, lineIndex, lineHeight);
|
||||
updateTextContentAndStyles(tspan, [preparedLine]);
|
||||
lineIndex++;
|
||||
}
|
||||
});
|
||||
if (addBackground) {
|
||||
|
@ -70,7 +70,6 @@ export const setupGraphViewbox = function (graph, svgElem, padding, useMaxWidth)
|
||||
height = sHeight + padding * 2;
|
||||
// }
|
||||
|
||||
// width =
|
||||
log.info(`Calculated bounds: ${width}x${height}`);
|
||||
configureSvgSize(svgElem, height, width, useMaxWidth);
|
||||
|
||||
|
@ -32,6 +32,8 @@ import assignWithDepth from './assignWithDepth.js';
|
||||
import { MermaidConfig } from './config.type.js';
|
||||
import memoize from 'lodash-es/memoize.js';
|
||||
|
||||
export const ZERO_WIDTH_SPACE = '\u200b';
|
||||
|
||||
// Effectively an enum of the supported curve types, accessible by name
|
||||
const d3CurveTypes = {
|
||||
curveBasis: curveBasis,
|
||||
@ -765,7 +767,7 @@ export const calculateTextDimensions: (
|
||||
const dim = { width: 0, height: 0, lineHeight: 0 };
|
||||
for (const line of lines) {
|
||||
const textObj = getTextObj();
|
||||
textObj.text = line;
|
||||
textObj.text = line || ZERO_WIDTH_SPACE;
|
||||
const textElem = drawSimpleText(g, textObj)
|
||||
.style('font-size', _fontSizePx)
|
||||
.style('font-weight', fontWeight)
|
||||
|
552
pnpm-lock.yaml
generated
552
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
Loading…
x
Reference in New Issue
Block a user