Compare commits

...

82 Commits

Author SHA1 Message Date
Knut Sveidqvist
ba7f83019f Fixing applitools batches 2022-11-08 15:25:05 +01:00
Sidharth Vinod
97a7f1fd71 Merge pull request #2739 from Andre601/feature/issue-templates
Use issue templates and add diagram, theme and syntax proposal issue forms
2022-11-03 11:07:00 +05:30
Sidharth Vinod
c86d46eb8a Add Security Policy 2022-11-03 11:06:21 +05:30
Sidharth Vinod
361dd6a96e Add Security Policy 2022-11-03 10:59:57 +05:30
Sidharth Vinod
17adec38af chore: lint 2022-11-03 10:59:56 +05:30
Andre_601
638b9d9aae Discussions are now available 2022-11-03 10:59:56 +05:30
Andre_601
c8f6994895 Make colors required 2022-11-03 10:59:55 +05:30
Andre_601
1117a80500 make setup not required 2022-11-03 10:59:55 +05:30
Andre_601
5d83ec6fa2 Update theme_proposal.yml 2022-11-03 10:59:54 +05:30
Andre_601
bab5937426 Update syntaxt_proposal.yml 2022-11-03 10:59:54 +05:30
Andre_601
099a26977a Update diagram_proposal.yml 2022-11-03 10:59:53 +05:30
Andre_601
0b834485a8 Update bug_report.yml 2022-11-03 10:59:52 +05:30
Andre_601
41f21d4f72 Delete question.md 2022-11-03 10:59:50 +05:30
Andre_601
312e5f3d96 Create syntaxt_proposal.yml 2022-11-03 10:59:35 +05:30
Andre_601
6ef3e7f536 Create theme_proposal.yml 2022-11-03 10:59:34 +05:30
Andre_601
4f5228aec4 Create diagram_proposal.yml 2022-11-03 10:59:32 +05:30
Andre_601
b9daa35558 Switch to bug_report.yml 2022-11-03 10:59:10 +05:30
Andre_601
21304a9677 Create config.yml 2022-11-03 10:57:57 +05:30
Knut Sveidqvist
3f6613ea9f Updated mermaid version for the docs 2022-11-01 15:52:35 +01:00
Knut Sveidqvist
c8635c0b9b Merge branch 'release/9.2.0'
Conflicts:
	.github/ISSUE_TEMPLATE/bug_report.md
2022-11-01 15:37:00 +01:00
Knut Sveidqvist
e78ac9b92a Updated version 2022-11-01 15:27:20 +01:00
Sidharth Vinod
7a47fcfcbc Merge pull request #3727 from gibson042/2022-10-issue-template
chore: Update bug report template
2022-10-29 23:44:02 +05:30
Knut Sveidqvist
d4c19ffa59 Merge branch 'release/9.2.0' of github.com:mermaid-js/mermaid into release/9.2.0 2022-10-28 09:34:36 +02:00
Knut Sveidqvist
120940f9f4 Merge pull request #3731 from aloisklink/fix/load-lazy-loaded-diagrams-in-initThrowsErrors
[9.2.0] Support `lazyLoadedDiagrams` when calling `initThrowsErrorsAsync`
2022-10-28 09:33:58 +02:00
Alois Klink
48b1f489fc fix(mermaid): error if lazyLoadedDiagrams fails
Throws and logs a warning if lazyLoadedDiagrams fails to load properly.

Rendering is still performed, even on a lazyLoadedDiagrams failure.
2022-10-27 17:48:21 +01:00
Alois Klink
e62dd255bc feat: expose initThrowsErrorsAsync publicly
Expose the function `initThrowsErrorsAsync()` publicly
as `mermaid.initThrowsErrorsAsync()`.

It has the TSDoc `@alpha` and `@deprecated` tags, so people should
be warned that it might be modified in Mermaid v10 or earlier.

Needed for `mermaid-cli` to handle `lazyLoadedDiagrams`.
2022-10-27 17:48:12 +01:00
Alois Klink
13110c4ed9 docs(mermaid): document initThrowsErrorsAsync
Add some basic tsdoc for initThrowsErrorsAsync.
2022-10-27 17:02:50 +01:00
Alois Klink
327fcbf902 fix: lazy load diagrams in initThrowsErrorsAsync
Previously, calling initThrowsErrorsAsync would not
load any of the lazyLoadedDiagrams entries.

Adaptated from reverted commit 4601c90904
2022-10-27 16:50:57 +01:00
Alois Klink
81924f72c8 Revert "Merge branch 'release_9.2.0_buggfixes'"
This reverts commit 1a0309fb87, reversing
changes made to 56a8068a7f.

This is because the PR https://github.com/mermaid-js/mermaid/pull/3702
worked fine on the `develop` and `release_9.2.0_buggfixes` branches,
but had a bunch of git merge conflicts on the `release/9.2.0` branch.
2022-10-27 16:43:09 +01:00
Richard Gibson
5d048ce21e chore: Update bug report template
Add common pattern for reproduction with https://mermaid.live/
2022-10-26 23:17:15 -04:00
Knut Sveidqvist
1a0309fb87 Merge branch 'release_9.2.0_buggfixes'
Conflicts:
	packages/mermaid/src/mermaid.ts
2022-10-24 10:28:27 +02:00
Knut Sveidqvist
e793aae0ec Merge pull request #3702 from aloisklink/fix/initThrowErrors-support-lazyLoadedDiagrams
Support `lazyLoadedDiagrams` when calling `initThrowsErrors`
2022-10-24 08:28:50 +02:00
Alois Klink
3a2669e634 fix(mermaid): error if lazyLoadedDiagrams fails
Throw an error if lazyLoadedDiagrams fails to load properly.

Rendering is still performed, even on a lazyLoadedDiagrams failure.
2022-10-23 13:19:40 +01:00
Alois Klink
895a5eb78a docs(mermaid): document initThrowsErrors
Add some basic tsdoc for initThrowsErrors
2022-10-23 13:19:33 +01:00
Alois Klink
4601c90904 fix: load lazyLoadedDiagrams in initThrowsErrors
Previously, calling initThrowsErrors would not
load any of the lazyLoadedDiagrams entries.
2022-10-23 13:19:26 +01:00
Knut Sveidqvist
8ad8d39fe4 Versions for latest rcs 2022-10-20 12:57:29 +02:00
Sidharth Vinod
56a8068a7f fix: Error handling 2022-10-19 20:30:07 +05:30
Sidharth Vinod
d17aa6ecdd Merge branch 'release_9.2.0_buggfixes' into release/9.2.0
* release_9.2.0_buggfixes:
  fix: Add default arg to options
  fix: Build types
  chore: Update creation date
  fix: getElementById type issue.
2022-10-19 19:21:19 +05:30
Knut Sveidqvist
6f27363862 Merge pull request #3683 from mermaid-js/sidv/TSMindmap
fix: Converts mindmapDB to TS
2022-10-19 15:47:08 +02:00
Sidharth Vinod
5192608f7c Merge branch 'release_9.2.0_buggfixes' into sidv/TSMindmap
* release_9.2.0_buggfixes:
  fix: Add default arg to options
2022-10-19 19:13:46 +05:30
Sidharth Vinod
77f5e0d5f3 fix: Add default arg to options 2022-10-19 19:13:05 +05:30
Sidharth Vinod
4c311ea4b1 fix: TS errors 2022-10-19 19:03:29 +05:30
Sidharth Vinod
bbb3712284 Merge branch 'release_9.2.0_buggfixes' into sidv/TSMindmap
* release_9.2.0_buggfixes:
  Color fix for default nodes in mindmap, line uses inv color
2022-10-19 18:42:13 +05:30
Knut Sveidqvist
1388e201e5 Adding queue for async calls in mermaidts and fixing icon color 2022-10-19 14:30:36 +02:00
Sidharth Vinod
125312c114 chore: Add CORS to vite dev 2022-10-19 12:48:09 +02:00
Knut Sveidqvist
ea314cd24a Setting version to rc7 2022-10-19 08:01:21 +02:00
Knut Sveidqvist
8230c8f8b4 Merge remote-tracking branch 'origin/release/9.2.0' into release/9.2.0 2022-10-19 07:52:14 +02:00
Knut Sveidqvist
d115fbc6da Merge branch 'release_9.2.0_buggfixes' into release/9.2.0 2022-10-19 07:52:05 +02:00
Knut Sveidqvist
2ae8bf2987 Color fix for default nodes in mindmap, line uses inv color 2022-10-18 16:04:14 +02:00
Knut Sveidqvist
e86d7894f5 #3680 Add font familiy in a way that does remove other configuration 2022-10-17 10:51:41 +02:00
Knut Sveidqvist
752a6b2cb0 #3687 Separating the render specific data from the data related to parsing 2022-10-17 10:46:46 +02:00
Sidharth Vinod
97a842e651 fix: Build types 2022-10-17 11:45:19 +05:30
Sidharth Vinod
c83e29c6e3 chore: Update creation date 2022-10-16 10:11:19 +05:30
Sidharth Vinod
a4af3704ba fix: getElementById type issue.
Converts mindmapDB to TS
2022-10-16 10:06:44 +05:30
Sidharth Vinod
775fb80c43 Merge branch 'release_9.2.0_buggfixes' into release/9.2.0
* release_9.2.0_buggfixes:
  fix: Remove hardcoded numericLevel
  fix: Diagram load issue
2022-10-15 21:38:34 +05:30
Sidharth Vinod
aec1d80966 fix: Remove hardcoded numericLevel 2022-10-15 21:33:09 +05:30
Sidharth Vinod
58a53c0fa8 fix: Diagram load issue 2022-10-15 00:30:02 +05:30
Sidharth Vinod
b486177ca7 fix: Diagram load issue 2022-10-15 00:28:21 +05:30
Knut Sveidqvist
957e64fe8a Docs with lazy loading using rc bundles 2022-10-14 16:10:57 +02:00
Knut Sveidqvist
7881d1e457 Merge branch 'release_9.2.0_buggfixes' into release/9.2.0 2022-10-14 15:12:56 +02:00
Knut Sveidqvist
16be835c9b Removing error thrown disrupting rendering 2022-10-14 15:12:22 +02:00
Knut Sveidqvist
59cf085af5 #448 Fix for root nodes without children 2022-10-14 15:11:29 +02:00
Knut Sveidqvist
fc8db95597 Release 9.20 fixes of docsify 2022-10-14 15:05:00 +02:00
Knut Sveidqvist
cc10e62ebd Merge branch 'release_9.2.0_buggfixes' into release/9.2.0 2022-10-14 10:03:02 +02:00
Knut Sveidqvist
bed95c77a9 Some changes in the docs for mindmap 2022-10-14 10:01:32 +02:00
Knut Sveidqvist
d348f2847c Merge branch 'release_9.2.0_buggfixes' into release/9.2.0 2022-10-12 11:54:22 +02:00
Knut Sveidqvist
4d46ea9801 #3192 Adding link type of the std docs 2022-10-12 11:53:02 +02:00
Knut Sveidqvist
eec97d10af #3192 Adding the ability to create invisible links in flowcharts(v2) 2022-10-12 11:48:51 +02:00
Sidharth Vinod
ebef4a1416 chore: add auto-install-peers to .npmrc 2022-10-12 12:02:08 +05:30
Sidharth Vinod
24605784f2 chore: Update lockfile 2022-10-12 12:01:54 +05:30
Sidharth Vinod
3240f8ae56 feat: loadExternalDiagramsAtStartup 2022-10-12 11:56:39 +05:30
Sidharth Vinod
50f44c5cb0 Merge branch 'release/9.2.0' of https://github.com/mermaid-js/mermaid into release/9.2.0
* 'release/9.2.0' of https://github.com/mermaid-js/mermaid:
  #3252 Handling for trailing whitespaces in subgraph titles
2022-10-11 16:17:00 +05:30
Sidharth Vinod
cc2f4f779a feat: Add initializeAsync 2022-10-11 16:16:54 +05:30
Knut Sveidqvist
00ce0fc034 Merge branch 'release_9.2.0_buggfixes' into release/9.2.0 2022-10-11 12:30:38 +02:00
Knut Sveidqvist
551b37f969 #3252 Handling for trailing whitespaces in subgraph titles 2022-10-11 12:29:39 +02:00
Knut Sveidqvist
bc5ef5fb7d Fix for async issue in parse, adding parseAsync 2022-10-11 11:20:34 +02:00
Knut Sveidqvist
c20b8a0664 Merge pull request #3633 from mermaid-js/sidv/dirtyFixSync
fix: Dirty fix for sync render.
2022-10-11 09:04:43 +02:00
Sidharth Vinod
23e590e09b Revert "fix(test): Rerender"
This reverts commit a017ffc3c9.
2022-10-10 22:04:33 +08:00
Sidharth Vinod
60e4585e20 Revert "fix: use async in render-after-error"
This reverts commit d59f878020.
2022-10-10 22:04:05 +08:00
Sidharth Vinod
ba436cc37a fix: Tests 2022-10-10 21:57:22 +08:00
Sidharth Vinod
960ea450e9 fix: Dirty fix for sync render.
Should be reverted for v10
2022-10-10 21:50:59 +08:00
Knut Sveidqvist
c5d859e52e Merge branch 'release/9.1.7' 2022-09-13 20:12:08 +02:00
47 changed files with 2513 additions and 326 deletions

View File

@@ -1,43 +0,0 @@
---
name: Bug report
about: Create a report to help us improve
title: ''
labels: 'Status: Triage, Type: Bug / Error'
assignees: ''
---
**Describe the bug**
A clear and concise description of what the bug is.
**To Reproduce**
Steps to reproduce the behavior:
1. Go to '...'
2. Click on '....'
3. Scroll down to '....'
4. See error
**Expected behavior**
A clear and concise description of what you expected to happen.
**Screenshots**
If applicable, add screenshots to help explain your problem.
**Code Sample**
If applicable, add the code sample or a link to the [live editor](https://mermaid.live).
**Desktop (please complete the following information):**
- OS: [e.g. iOS]
- Browser [e.g. chrome, safari]
- Version [e.g. 22]
**Smartphone (please complete the following information):**
- Device: [e.g. iPhone6]
- OS: [e.g. iOS8.1]
- Browser [e.g. stock browser, safari]
- Version [e.g. 22]
**Additional context**
Add any other context about the problem here.

69
.github/ISSUE_TEMPLATE/bug_report.yml vendored Normal file
View File

@@ -0,0 +1,69 @@
name: Bug Report
description: Create a report to help us improve
labels:
- 'Status: Triage'
- 'Type: Bug / Error'
body:
- type: markdown
attributes:
value: |-
## Security vulnerabilities
Please refer our [Security Policy](https://github.com/mermaid-js/.github/blob/main/SECURITY.md) and report to keep vulnerabilities confidential so we can release fixes first.
## Before you submit...
We like to help you, but in order to do that should you make a few things first:
- 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!
- type: textarea
attributes:
label: Description
description: Give a clear and concise description of what the bug is.
placeholder: When I do ... does ... happen.
validations:
required: true
- type: textarea
attributes:
label: Steps to reproduce
description: Give a step-by-step example on how to reproduce the bug.
placeholder: |-
1. Do this
2. Do that
3. ...
4. Bug!
validations:
required: true
- type: textarea
attributes:
label: Screenshots
description: If applicable, add screenshots to help explain your issue.
- type: textarea
attributes:
label: Code Sample
description: |-
If applicable, add the code sample or a link to the [Live Editor](https://mermaid.live).
Any text pasted here will be rendered as a Code block.
render: text
- type: textarea
attributes:
label: Setup
description: |-
Please fill out the below info.
Note that you only need to fill out one and not both sections.
value: |-
**Desktop**
- OS and Version: [Windows, Linux, Mac, ...]
- Browser and Version: [Chrome, Edge, Firefox]
**Smartphone**
- Device: [Samsung, iPhone, ...]
- OS and Version: [Android, iOS, ...]
- Browser and Version: [Chrome, Safari, ...]
- type: textarea
attributes:
label: Additional Context
description: Anything else to add?

17
.github/ISSUE_TEMPLATE/config.yml vendored Normal file
View File

@@ -0,0 +1,17 @@
blank_issues_enabled: false
contact_links:
- name: GitHub Discussions
url: https://github.com/mermaid-js/mermaid/discussions
about: Ask the Community questions or share your own graphs in our discussions.
- name: Security Vulnerability
url: https://github.com/mermaid-js/.github/blob/main/SECURITY.md
about: Report security issues confidentially.
- name: Slack
url: https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE
about: Join our Community on Slack for Help and a casual chat.
- name: Documentation
url: https://mermaid-js.github.io
about: Read our documentation for all that Mermaid.js can offer.
- name: Live Editor
url: https://mermaid.live
about: Try the live editor to preview graphs in no time.

View File

@@ -0,0 +1,42 @@
name: Diagram Proposal
description: Suggest a new Diagram Type to add to Mermaid.
labels:
- 'Status: Triage'
- 'Type: Enhancement'
body:
- type: markdown
attributes:
value: |-
## Before you submit...
First of all, thank you for proposing a new Diagram to us.
We are always happy about new ideas to improve Mermaid.js wherever possible.
To get the fastest and best response possible, make sure you do the following:
- 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!
- type: textarea
attributes:
label: Proposal
description: A clear and concise description of what should be added to Mermaid.js.
placeholder: Mermaid.js should add ... because ...
validations:
required: true
- type: textarea
attributes:
label: Use Cases
description: If applicable, give some use cases for where this diagram would be useful.
placeholder: The Diagram could be used for ...
- type: textarea
attributes:
label: Screenshots
description: If applicable, add screenshots to show possible examples of how the diagram may look like.
- type: textarea
attributes:
label: Code Sample
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

View File

@@ -1,19 +0,0 @@
---
name: Feature request
about: Suggest an idea for this project
title: ''
labels: 'Status: Triage, Type: Enhancement'
assignees: ''
---
**Is your feature request related to a problem? Please describe.**
A clear and concise description of what the problem is. Ex. I'm always frustrated when [...]
**Describe the solution you'd like**
A clear and concise description of what you want to happen.
**Describe alternatives you've considered**
A clear and concise description of any alternative solutions or features you've considered.
**Additional context**
Add any other context or screenshots about the feature request here.

View File

@@ -1,16 +0,0 @@
---
name: Question
about: Get some help from the community.
title: ''
labels: 'Help wanted!, Type: Other'
assignees: ''
---
## Help us help you!
You want an answer. Here are some ways to get it quicker:
- Use a clear and concise title.
- Try to pose a clear and concise question.
- Include as much, or as little, code as necessary.
- Don't be shy to give us some screenshots, if it helps!

View File

@@ -0,0 +1,34 @@
name: Syntax Proposal
description: Suggest a new Syntax to add to Mermaid.js.
labels:
- 'Status: Triage'
- 'Type: Enhancement'
body:
- type: markdown
attributes:
value: |-
## Before you submit...
First of all, thank you for proposing a new Syntax to us.
We are always happy about new ideas to improve Mermaid.js wherever possible.
To get the fastest and best response possible, make sure you do the following:
- Use a clear and concise title
- Fill out the text fields with as much detail as possible. Examples are always welcome.
- Never be shy to give us screenshots and/or code samples. It will help!
- type: textarea
attributes:
label: Proposal
description: A clear and concise description of what Syntax should be added to Mermaid.js.
placeholder: Mermaid.js should add ... because ...
validations:
required: true
- type: textarea
attributes:
label: Example
description: If applicable, provide an example of the new Syntax.
- type: textarea
attributes:
label: Screenshots
description: If applicable, add screenshots to show possible examples of how the theme may look like.

View File

@@ -0,0 +1,42 @@
name: Theme Proposal
description: Suggest a new theme to add to Mermaid.js.
labels:
- 'Status: Triage'
- 'Type: Enhancement'
body:
- type: markdown
attributes:
value: |-
## Before you submit...
First of all, thank you for proposing a new Theme to us.
We are always happy about new ideas to improve Mermaid.js wherever possible.
To get the fastest and best response possible, make sure you do the following:
- Use a clear and concise title
- Fill out the text fields with as much detail as possible. Examples are always welcome!
- Never be shy to give us screenshots and/or code samples. It will help!
- type: textarea
attributes:
label: Proposal
description: A clear and concise description of what theme should be added to Mermaid.js.
placeholder: Mermaid.js should add ... because ...
validations:
required: true
- type: textarea
attributes:
label: Colors
description: |-
A detailed list of the different colour values to use.
A list of currently used variable names can be found [here](https://mermaid-js.github.io/mermaid/#/theming?id=theme-variables-reference-table)
placeholder: |-
- background: #f4f4f4
- primaryColor: #fff4dd
- ...
validations:
required: true
- type: textarea
attributes:
label: Screenshots
description: If applicable, add screenshots to show possible examples of how the theme may look like.

3
.gitignore vendored
View File

@@ -32,3 +32,6 @@ cypress/snapshots/
.eslintcache
.tsbuildinfo
tsconfig.tsbuildinfo
knsv*.html
local*.html

1
.npmrc Normal file
View File

@@ -0,0 +1 @@
auto-install-peers=true

View File

@@ -6,6 +6,7 @@ import pkg from '../package.json' assert { type: 'json' };
const { dependencies } = pkg;
const watch = process.argv.includes('--watch');
const mermaidOnly = process.argv.includes('--mermaid');
const __dirname = fileURLToPath(new URL('.', import.meta.url));
type OutputOptions = Exclude<
@@ -129,14 +130,19 @@ const buildPackage = async (entryName: keyof typeof packageOptions) => {
const main = async () => {
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
for (const pkg of packageNames) {
if (mermaidOnly && pkg !== 'mermaid') {
continue;
}
await buildPackage(pkg);
}
};
if (watch) {
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid' }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
build(getBuildConfig({ minify: false, watch, core: true, entryName: 'mermaid' }));
if (!mermaidOnly) {
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
}
} else {
void main();
}

View File

@@ -1,7 +1,15 @@
import express from 'express';
import express, { NextFunction, Request, Response } from 'express';
import { createServer as createViteServer } from 'vite';
// import { getBuildConfig } from './build';
const cors = (req: Request, res: Response, next: NextFunction) => {
res.header('Access-Control-Allow-Origin', '*');
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
res.header('Access-Control-Allow-Headers', 'Content-Type');
next();
};
async function createServer() {
const app = express();
@@ -12,6 +20,7 @@ async function createServer() {
appType: 'custom', // don't include Vite's default HTML handling middlewares
});
app.use(cors);
app.use(express.static('./packages/mermaid/dist'));
app.use(express.static('./packages/mermaid-example-diagram/dist'));
app.use(express.static('./packages/mermaid-mindmap/dist'));

View File

@@ -15,5 +15,5 @@ module.exports = defineConfig({
// { deviceName: 'Pixel 2', screenOrientation: 'portrait' },
],
// set batch name to the configuration
batchName: `Mermaid ${process.env.APPLI_BRANCH ?? "'no APPLI_BRANCH set'"}`,
// batchName: `Mermaid ${process.env.APPLI_BRANCH ?? "'no APPLI_BRANCH set'"}`,
});

View File

@@ -2,6 +2,8 @@ const utf8ToB64 = (str) => {
return window.btoa(unescape(encodeURIComponent(str)));
};
const batchId = 'mermid-batch' + new Date().getTime();
export const mermaidUrl = (graphStr, options, api) => {
const obj = {
code: graphStr,
@@ -50,9 +52,12 @@ export const imgSnapshotTest = (graphStr, _options, api = false, validation) =>
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
if (useAppli) {
cy.log('Opening eyes ' + Cypress.spec.name + ' --- ' + name);
cy.eyesOpen({
appName: 'Mermaid',
testName: name,
batchName: Cypress.spec.name,
batchId: batchId + Cypress.spec.name,
});
}
@@ -64,7 +69,9 @@ export const imgSnapshotTest = (graphStr, _options, api = false, validation) =>
// Default name to test title
if (useAppli) {
cy.log('Check eyes' + Cypress.spec.name);
cy.eyesCheckWindow('Click!');
cy.log('Closing eyes: ' + Cypress.spec.name);
cy.eyesClose();
} else {
cy.matchImageSnapshot(name);
@@ -100,9 +107,12 @@ export const urlSnapshotTest = (url, _options, api = false, validation) => {
const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-');
if (useAppli) {
cy.log('Opening eyes 2' + Cypress.spec.name);
cy.eyesOpen({
appName: 'Mermaid',
testName: name,
batchName: Cypress.spec.name,
batchId: batchId + Cypress.spec.name,
});
}
@@ -112,7 +122,9 @@ export const urlSnapshotTest = (url, _options, api = false, validation) => {
// Default name to test title
if (useAppli) {
cy.log('Check eyes 2' + Cypress.spec.name);
cy.eyesCheckWindow('Click!');
cy.log('Closing eyes 2' + Cypress.spec.name);
cy.eyesClose();
} else {
cy.matchImageSnapshot(name);

View File

@@ -75,16 +75,6 @@ classDiagram
<pre id="diagram" class="mermaid2">
mindmap
root
A
B
C
D
E
A2
B2
C2
D2
E2
child1((Circle))
grandchild 1
grandchild 2

View File

@@ -14,16 +14,14 @@
mermaid.init({ startOnLoad: false });
mermaid.mermaidAPI.initialize({ securityLevel: 'strict' });
(async () => {
try {
console.log('rendering');
await mermaid.mermaidAPI.render('graphDiv', `>`);
} catch (e) {}
try {
console.log('rendering');
mermaid.mermaidAPI.render('graphDiv', `>`);
} catch (e) {}
await mermaid.mermaidAPI.render('graphDiv', `graph LR\n a --> b`, (html) => {
document.getElementById('graph').innerHTML = html;
});
})();
mermaid.mermaidAPI.render('graphDiv', `graph LR\n a --> b`, (html) => {
document.getElementById('graph').innerHTML = html;
});
</script>
</body>
</html>

View File

@@ -19,10 +19,9 @@
function rerender(text) {
const graphText = `graph TD
A[${text}] -->|Get money| B(Go shopping)`;
mermaid.mermaidAPI.render('id', graphText).then((svg) => {
console.log('\x1b[35m%s\x1b[0m', '>> graph', svg);
document.getElementById('graph').innerHTML = svg;
});
const graph = mermaid.mermaidAPI.render('id', graphText);
console.log('\x1b[35m%s\x1b[0m', '>> graph', graph);
document.getElementById('graph').innerHTML = graph;
}
</script>
<button id="rerender" onclick="rerender('Saturday')">Rerender</button>

View File

@@ -264,6 +264,20 @@ flowchart LR
A --- B
```
### An invisisble link
This can be a usefull tool in some instances where you want to alter the default positining of a node.
```mermaid-example
flowchart LR
A ~~~ B
```
```mermaid
flowchart LR
A ~~~ B
```
### Text on links
```mermaid-example

View File

@@ -49,14 +49,39 @@
<body>
<div id="app"></div>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs';
// import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9/dist/mermaid.esm.min.mjs';
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9.2.0/dist/mermaid.esm.min.mjs';
// import mermaid from 'http://localhost:9000/mermaid.esm.mjs';
console.log(mermaid); // eslint-disable-line
window.mermaid = mermaid;
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const conf = {
logLevel: 4,
startOnLoad: true,
themeCSS: '.label { font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; }',
lazyLoadedDiagrams: [
'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-mindmap@9.2.0/dist/mermaid-mindmap-detector.esm.mjs',
// 'http://localhost:9000/mermaid-mindmap-detector.esm.mjs',
],
};
if (isDarkMode) conf.theme = 'dark';
async function loadMermaid() {
await mermaid.initialize(conf);
console.log('mermaid initialized'); // eslint-disable-line
}
mermaid.parseError = (e) => {
console.log('parse error', e); // eslint-disable-line
};
await loadMermaid();
</script>
<script>
let initEditor = exports.default;
let parser = new DOMParser();
let currentCodeExample = 0;
let colorize = [];
let num = 0;
function colorizeEverything(html) {
initEditor(monaco);
@@ -97,14 +122,12 @@
renderer: {
code: function (code, lang) {
if (lang === 'mermaid-example') {
console.log('An example'); // eslint-disable-line
currentCodeExample++;
colorize.push(currentCodeExample);
return '<pre id="code' + currentCodeExample + '">' + escapeHTML(code) + '</pre>';
} else if (lang === 'mermaid') {
// TODO: This will need to be updated when render is async.
return (
'<pre class="mermaid">' + mermaid.render('mermaid-svg-' + num++, code) + '</pre>'
);
return '<pre class="mermaid">' + code + '</pre>';
}
return this.origin.code.apply(this, arguments);
},
@@ -123,6 +146,10 @@
const editHtml = '[:memo: Edit this Page](' + url + ')\n';
return editHtml + html;
});
// Invoked on each page load after new HTML has been appended to the DOM
hook.doneEach(function () {
window.mermaid.init();
});
hook.afterEach(function (html, next) {
next(html);
@@ -138,28 +165,17 @@
},
],
};
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const conf = {
logLevel: 4,
startOnLoad: false,
themeCSS: '.label { font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; }',
};
if (isDarkMode) conf.theme = 'dark';
mermaid.initialize(conf);
</script>
<script>
window.onhashchange = function (a) {
//code
if (location) {
ga('send', 'pageview', location.hash);
}
// if (location && ga) {
// ga('send', 'pageview', location.hash);
// }
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/ga.min.js"></script>
<!-- <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/ga.min.js"></script> -->
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-coffeescript.min.js"></script>
</body>
</html>

View File

@@ -26,7 +26,6 @@ mindmap
Tools
Pen and paper
Mermaid
```
```mermaid
@@ -47,14 +46,13 @@ mindmap
Tools
Pen and paper
Mermaid
```
## Syntax
The syntax for creating Mindmaps is simple and relies on indentation for setting the levels in the hierarchy.
In the following example you can see how there are 3 different levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further then the previous lines defining the nodes B and C.
In the following example you can see how there are 3 different levels of indentation. The leftmost indentation is the root of the mindmap. There can only be one root and if you by misstake add two of them on the same level there will be a syntax error. Rows with larger indentation will be connected as children to the previous row with lower indentation. Based on that you can see in the example how the nodes B and C both are children to node A whci in turn is a child of the node Root.
mindmap
Root
@@ -62,7 +60,7 @@ In the following example you can see how there are 3 different levels. One with
B
C
In summary is a simple text outline where there are one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
In the diagram below you can see the example rendered as a mindmap.
```mermaid-example
mindmap
@@ -220,7 +218,7 @@ The actual indentation does not really matter only compared with the previous ro
B
C
This outline is unclear as `B` clearly is a child of `A` but when we move on to `C` the clarity is lost. `C` is not a child of `B` with a higher indentation nor does it have the same indentation as `B`. The only thing that is clear is that the first node with smaller indentation, indicating a parent, is A. Then Mermaid relies on this known truth and compensates for the unclear indentation and selects `A` as a parent of `C` leading till the same diagram with `B` and `C` as siblings.
This outline is unclear as `B` clearly is a child of `A` but when we move on to `C` the clarity is lost. `C` is not a child of `B` with a higher indentation nor does it have the same indentation as `B`. The only thing that is clear is that the first node with smaller indentation, indicating a parent, is A. Mermaid will rely on this known truth and compensates for the unclear indentation and selects `A` as a parent of `C` leading till the same diagram with `B` and `C` as siblings.
```mermaid-example
mindmap

View File

@@ -1,7 +1,7 @@
{
"name": "mermaid-monorepo",
"private": true,
"version": "9.2.0-rc2",
"version": "9.2.0-rc4",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid.core.mjs",
"module": "dist/mermaid.core.mjs",
@@ -26,8 +26,9 @@
"git graph"
],
"scripts": {
"build:mermaid": "ts-node-esm --transpileOnly --project=.vite/tsconfig.json .vite/build.ts --mermaid",
"build:vite": "ts-node-esm --transpileOnly --project=.vite/tsconfig.json .vite/build.ts",
"build:types": "concurrently \"tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly\" \"tsc -p ./packages/mermaid-mindmap/tsconfig.json --emitDeclarationOnly\"",
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-mindmap/tsconfig.json --emitDeclarationOnly",
"build:watch": "pnpm build:vite --watch",
"build": "pnpm run -r clean && concurrently \"pnpm build:vite\" \"pnpm build:types\"",
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"",

View File

@@ -1,6 +1,6 @@
{
"name": "@mermaid-js/mermaid-mindmap",
"version": "9.2.0-rc2",
"version": "9.2.0",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid-mindmap.core.mjs",
"module": "dist/mermaid-mindmap.core.mjs",
@@ -58,6 +58,7 @@
},
"devDependencies": {
"concurrently": "^7.4.0",
"mermaid": "workspace:*",
"rimraf": "^3.0.2"
},
"resolutions": {

View File

@@ -1,3 +1,5 @@
import type { MermaidConfig } from 'mermaid';
const warning = (s: string) => {
// Todo remove debug code
console.error('Log function was called before initialization', s); // eslint-disable-line
@@ -24,7 +26,7 @@ export const log: Record<keyof typeof LEVELS, typeof console.log> = {
};
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
export let getConfig: () => object;
export let getConfig: () => MermaidConfig;
export let sanitizeText: (str: string) => string;
// eslint-disable @typescript-eslint/no-explicit-any
export let setupGraphViewbox: (

View File

@@ -1,16 +1,28 @@
/** Created by knut on 15-01-14. */
/** Created by knut on 23-07-2022. */
import { sanitizeText, getConfig, log } from './mermaidUtils';
import type { DetailedError } from 'mermaid';
let nodes = [];
interface Node {
id: number;
nodeId: string;
level: number;
descr: string;
type: number;
children: Node[];
width: number;
padding: number;
icon?: string;
class?: string;
}
let nodes: Node[] = [];
let cnt = 0;
let elements = {};
export const clear = () => {
nodes = [];
cnt = 0;
elements = {};
};
const getParent = function (level) {
const getParent = function (level: number) {
for (let i = nodes.length - 1; i >= 0; i--) {
if (nodes[i].level < level) {
return nodes[i];
@@ -23,28 +35,21 @@ const getParent = function (level) {
export const getMindmap = () => {
return nodes.length > 0 ? nodes[0] : null;
};
export const addNode = (level, id, descr, type) => {
export const addNode = (level: number, id: string, descr: string, type: number) => {
log.info('addNode', level, id, descr, type);
const conf = getConfig();
const node = {
const padding = conf.mindmap?.padding ?? 15;
const node: Node = {
id: cnt++,
nodeId: sanitizeText(id),
level,
descr: sanitizeText(descr),
type,
children: [],
width: getConfig().mindmap.maxNodeWidth,
width: getConfig().mindmap?.maxNodeWidth ?? 200,
padding: type === nodeType.ROUNDED_RECT || type === nodeType.RECT ? 2 * padding : padding,
};
switch (node.type) {
case nodeType.ROUNDED_RECT:
node.padding = 2 * conf.mindmap.padding;
break;
case nodeType.RECT:
node.padding = 2 * conf.mindmap.padding;
break;
default:
node.padding = conf.mindmap.padding;
}
const parent = getParent(level);
if (parent) {
parent.children.push(node);
@@ -56,9 +61,10 @@ export const addNode = (level, id, descr, type) => {
nodes.push(node);
} else {
// Syntax error ... there can only bee one root
let error = new Error(
const error = new Error(
'There can be only one root. No parent could be found for ("' + node.descr + '")'
);
// @ts-ignore TODO: Add mermaid error
error.hash = {
text: 'branch ' + name,
token: 'branch ' + name,
@@ -81,7 +87,7 @@ export const nodeType = {
BANG: 5,
};
export const getType = (startStr, endStr) => {
export const getType = (startStr: string, endStr: string): number => {
log.debug('In get type', startStr, endStr);
switch (startStr) {
case '[':
@@ -99,11 +105,7 @@ export const getType = (startStr, endStr) => {
}
};
export const setElementForId = (id, element) => {
elements[id] = element;
};
export const decorateNode = (decoration) => {
export const decorateNode = (decoration: { icon: string; class: string }) => {
const node = nodes[nodes.length - 1];
if (decoration && decoration.icon) {
node.icon = sanitizeText(decoration.icon);
@@ -113,7 +115,7 @@ export const decorateNode = (decoration) => {
}
};
export const type2Str = (type) => {
export const type2Str = (type: number) => {
switch (type) {
case nodeType.DEFAULT:
return 'no-border';
@@ -132,13 +134,13 @@ export const type2Str = (type) => {
}
};
export let parseError;
export const setErrorHandler = (handler) => {
export type ParseErrorFunction = (err: string | DetailedError, hash?: any) => void;
export let parseError: ParseErrorFunction;
export const setErrorHandler = (handler: ParseErrorFunction) => {
parseError = handler;
};
// Expose logger to grammar
export const getLogger = () => log;
export const getNodeById = (id) => nodes[id];
export const getElementById = (id) => elements[id];
export const getNodeById = (id: number): Node => nodes[id];

View File

@@ -1,10 +1,9 @@
/** Created by knut on 14-12-11. */
/** Created by knut on 23-07-2022. */
import { select } from 'd3';
import { log, getConfig, setupGraphViewbox } from './mermaidUtils';
import svgDraw from './svgDraw';
import svgDraw, { getElementById, clearElementRefs } from './svgDraw';
import cytoscape from 'cytoscape';
import coseBilkent from 'cytoscape-cose-bilkent';
import * as db from './mindmapDb';
// Inject the layout algorithm into cytoscape
cytoscape.use(coseBilkent);
@@ -34,7 +33,7 @@ function drawNodes(svg, mindmap, section, conf) {
* @param cy
*/
function drawEdges(edgesEl, cy) {
cy.edges().map((edge, id) => {
cy?.edges().map((edge, id) => {
const data = edge.data();
if (edge[0]._private.bodyBounds) {
const bounds = edge[0]._private.rscratch;
@@ -100,9 +99,10 @@ function addNodes(mindmap, cy, conf, level) {
*/
function layoutMindmap(node, conf) {
return new Promise((resolve) => {
if (node.children.length === 0) {
return node;
}
// if (node.children.length === 0) {
// resolve(node);
// return;
// }
// Add temporary render element
const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none');
@@ -154,7 +154,7 @@ function positionNodes(cy) {
data.x = node.position().x;
data.y = node.position().y;
svgDraw.positionNode(data);
const el = db.getElementById(data.nodeId);
const el = getElementById(data.nodeId);
log.info('Id:', id, 'Position: (', node.position().x, ', ', node.position().y, ')', data);
el.attr(
'transform',
@@ -178,6 +178,7 @@ export const draw = async (text, id, version, diagObj) => {
// This is done only for throwing the error if the text is not valid.
diagObj.db.clear();
clearElementRefs();
// Parse the graph definition
diagObj.parser.parse(text);

View File

@@ -22,12 +22,10 @@ const genSections = (options) => {
}
.section-${i - 1} text {
fill: ${options['cScaleLabel' + i]};
// fill: ${options['gitInv' + i]};
}
.node-icon-${i - 1} {
font-size: 40px;
color: ${options['cScaleLabel' + i]};
// color: ${options['gitInv' + i]};
}
.section-edge-${i - 1}{
stroke: ${options['cScale' + i]};
@@ -36,7 +34,7 @@ const genSections = (options) => {
stroke-width: ${sw};
}
.section-${i - 1} line {
stroke: ${options['lineColor' + i]} ;
stroke: ${options['cScaleInv' + i]} ;
stroke-width: 3;
}

View File

@@ -259,7 +259,7 @@ export const drawNode = function (elem, node, fullSection, conf) {
// if (typeof node.x !== 'undefined' && typeof node.y !== 'undefined') {
// nodeElem.attr('transform', 'translate(' + node.x + ',' + node.y + ')');
// }
db.setElementForId(node.id, nodeElem);
setElementById(node.id, nodeElem);
return node.height;
};
@@ -286,7 +286,7 @@ export const drawEdge = function drawEdge(edgesElem, mindmap, parent, depth, ful
};
export const positionNode = function (node) {
const nodeElem = db.getElementById(node.id);
const nodeElem = getElementById(node.id);
const x = node.x || 0;
const y = node.y || 0;
@@ -294,4 +294,18 @@ export const positionNode = function (node) {
nodeElem.attr('transform', 'translate(' + x + ',' + y + ')');
};
let elements = {};
const setElementById = (id, element) => {
elements[id] = element;
};
export const getElementById = (id) => {
return elements[id];
};
export const clearElementRefs = () => {
elements = {};
};
export default { drawNode, positionNode, drawEdge };

View File

@@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "9.2.0-rc2",
"version": "9.2.0",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "./dist/mermaid.core.mjs",
"module": "./dist/mermaid.core.mjs",
@@ -72,7 +72,8 @@
"lodash": "^4.17.21",
"moment-mini": "^2.24.0",
"non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.1.2"
"stylis": "^4.1.2",
"uuid": "^9.0.0"
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.25.7",
@@ -86,6 +87,7 @@
"@types/lodash": "^4.14.185",
"@types/prettier": "^2.7.0",
"@types/stylis": "^4.0.2",
"@types/uuid": "^8.3.4",
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.37.0",
"concurrently": "^7.4.0",

View File

@@ -1,6 +1,6 @@
import * as configApi from './config';
import { log } from './logger';
import { getDiagram, registerDiagram } from './diagram-api/diagramAPI';
import { DiagramNotFoundError, getDiagram, registerDiagram } from './diagram-api/diagramAPI';
import { detectType, getDiagramLoader } from './diagram-api/detectType';
import { isDetailedError } from './utils';
export class Diagram {
@@ -81,25 +81,36 @@ export class Diagram {
}
}
// eslint-disable-next-line @typescript-eslint/ban-types
export const getDiagramFromText = async (txt: string, parseError?: Function): Promise<Diagram> => {
export const getDiagramFromText = (
txt: string,
// eslint-disable-next-line @typescript-eslint/ban-types
parseError?: Function
): Diagram | Promise<Diagram> => {
const type = detectType(txt, configApi.getConfig());
try {
// Trying to find the diagram
getDiagram(type);
return new Diagram(txt, parseError);
} catch (error) {
if (!(error instanceof DiagramNotFoundError)) {
log.error(error);
throw error;
}
const loader = getDiagramLoader(type);
if (!loader) {
throw new Error(`Diagram ${type} not found.`);
throw new Error(`Loader for ${type} not found.`);
}
// Diagram not available, loading it
const { diagram } = await loader();
registerDiagram(type, diagram, undefined, diagram.injectUtils);
// new diagram will try getDiagram again and if fails then it is a valid throw
// TODO: Uncomment for v10
// // Diagram not available, loading it
// const { diagram } = await loader();
// registerDiagram(type, diagram, undefined, diagram.injectUtils);
// // new diagram will try getDiagram again and if fails then it is a valid throw
return loader().then(({ diagram }) => {
registerDiagram(type, diagram, undefined, diagram.injectUtils);
return new Diagram(txt, parseError);
});
}
// If either of the above worked, we have the diagram
// logic and can continue
return new Diagram(txt, parseError);
// return new Diagram(txt, parseError);
};
export default Diagram;

View File

@@ -25,6 +25,7 @@ function parse(text: string, parseError?: Function): boolean {
// original version cannot be modified since it was frozen with `Object.freeze()`
export const mermaidAPI = {
render: vi.fn(),
renderAsync: vi.fn(),
parse,
parseDirective: vi.fn(),
initialize: vi.fn(),

View File

@@ -4,6 +4,7 @@ import DOMPurify from 'dompurify';
export interface MermaidConfig {
lazyLoadedDiagrams?: string[];
loadExternalDiagramsAtStartup?: boolean;
theme?: string;
themeVariables?: any;
themeCSS?: string;

View File

@@ -438,6 +438,9 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
case 'thick':
strokeClasses = 'edge-thickness-thick';
break;
case 'invisible':
strokeClasses = 'edge-thickness-thick';
break;
default:
strokeClasses = '';
}

View File

@@ -34,8 +34,14 @@ export const registerDiagram = (
_setupGraphViewbox: any
) => void
) => {
log.debug(`Registering diagram ${id}`);
if (diagrams[id]) {
throw new Error(`Diagram ${id} already registered.`);
log.warn(`Diagram ${id} already registered.`);
// The error throw is commented out to as it breaks pages where you have multiple diagrams,
// it can happen that rendering of the same type of diagram is initiated while the previous
// one is still being imported. import deals with this and only one diagram is imported in
// the end.
// throw new Error(`Diagram ${id} already registered.`);
}
diagrams[id] = diagram;
if (detector) {
@@ -45,11 +51,19 @@ export const registerDiagram = (
if (typeof callback !== 'undefined') {
callback(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox);
}
log.debug(`Registered diagram ${id}. ${Object.keys(diagrams).join(', ')} diagrams registered.`);
};
export const getDiagram = (name: string): DiagramDefinition => {
log.debug(`Getting diagram ${name}. ${Object.keys(diagrams).join(', ')} diagrams registered.`);
if (name in diagrams) {
return diagrams[name];
}
throw new Error(`Diagram ${name} not found.`);
throw new DiagramNotFoundError(name);
};
export class DiagramNotFoundError extends Error {
constructor(message: string) {
super(`Diagram ${message} not found.`);
}
}

View File

@@ -456,8 +456,8 @@ export const defaultStyle = function () {
export const addSubGraph = function (_id, list, _title) {
// console.log('addSubGraph', _id, list, _title);
let id = _id.trim();
let title = _title;
if (_id === _title && _title.match(/\s/)) {
let title = _title.trim();
if (id === title && title.match(/\s/)) {
id = undefined;
}
/** @param a */
@@ -674,6 +674,10 @@ const destructEndLink = (_str) => {
stroke = 'thick';
}
if (line[0] === '~') {
stroke = 'invisible';
}
let dots = countChar('.', line);
if (dots) {

View File

@@ -280,6 +280,11 @@ export const addEdges = function (edges, g, diagObj) {
edgeData.pattern = 'solid';
edgeData.style = 'stroke-width: 3.5px;fill:none;';
break;
case 'invisible':
edgeData.thickness = 'invisible';
edgeData.pattern = 'solid';
edgeData.style = 'stroke-width: 0;fill:none;';
break;
}
if (typeof edge.style !== 'undefined') {
const styles = getStylesFromArray(edge.style);

View File

@@ -120,6 +120,7 @@ that id.
\s*[xo<]?\-\-+[-xo>]\s* return 'LINK';
\s*[xo<]?\=\=+[=xo>]\s* return 'LINK';
\s*[xo<]?\-?\.+\-[xo>]?\s* return 'LINK';
\s*\~\~[\~]+\s* return 'LINK';
\s*[xo<]?\-\-\s* return 'START_LINK';
\s*[xo<]?\=\=\s* return 'START_LINK';
\s*[xo<]?\-\.\s* return 'START_LINK';

View File

@@ -167,6 +167,15 @@ flowchart LR
A --- B
```
### An invisisble link
This can be a usefull tool in some instances where you want to alter the default positining of a node.
```mermaid-example
flowchart LR
A ~~~ B
```
### Text on links
```mermaid-example

View File

@@ -49,14 +49,39 @@
<body>
<div id="app"></div>
<script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@<MERMAID_VERSION>/dist/mermaid.esm.min.mjs';
// import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@<MERMAID_VERSION>/dist/mermaid.esm.min.mjs';
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@9.2.0-rc6/dist/mermaid.esm.min.mjs';
// import mermaid from 'http://localhost:9000/mermaid.esm.mjs';
console.log(mermaid); // eslint-disable-line
window.mermaid = mermaid;
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const conf = {
logLevel: 4,
startOnLoad: true,
themeCSS: '.label { font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; }',
lazyLoadedDiagrams: [
'https://cdn.jsdelivr.net/npm/@mermaid-js/mermaid-mindmap@9.2.0-rc3/dist/mermaid-mindmap-detector.esm.mjs',
// 'http://localhost:9000/mermaid-mindmap-detector.esm.mjs',
],
};
if (isDarkMode) conf.theme = 'dark';
async function loadMermaid() {
await mermaid.initialize(conf);
console.log('mermaid initialized'); // eslint-disable-line
}
mermaid.parseError = (e) => {
console.log('parse error', e); // eslint-disable-line
};
await loadMermaid();
</script>
<script>
let initEditor = exports.default;
let parser = new DOMParser();
let currentCodeExample = 0;
let colorize = [];
let num = 0;
function colorizeEverything(html) {
initEditor(monaco);
@@ -97,14 +122,12 @@
renderer: {
code: function (code, lang) {
if (lang === 'mermaid-example') {
console.log('An example'); // eslint-disable-line
currentCodeExample++;
colorize.push(currentCodeExample);
return '<pre id="code' + currentCodeExample + '">' + escapeHTML(code) + '</pre>';
} else if (lang === 'mermaid') {
// TODO: This will need to be updated when render is async.
return (
'<pre class="mermaid">' + mermaid.render('mermaid-svg-' + num++, code) + '</pre>'
);
return '<pre class="mermaid">' + code + '</pre>';
}
return this.origin.code.apply(this, arguments);
},
@@ -123,6 +146,10 @@
const editHtml = '[:memo: Edit this Page](' + url + ')\n';
return editHtml + html;
});
// Invoked on each page load after new HTML has been appended to the DOM
hook.doneEach(function () {
window.mermaid.init();
});
hook.afterEach(function (html, next) {
next(html);
@@ -138,28 +165,17 @@
},
],
};
const isDarkMode = window.matchMedia('(prefers-color-scheme: dark)').matches;
const conf = {
logLevel: 4,
startOnLoad: false,
themeCSS: '.label { font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; }',
};
if (isDarkMode) conf.theme = 'dark';
mermaid.initialize(conf);
</script>
<script>
window.onhashchange = function (a) {
//code
if (location) {
ga('send', 'pageview', location.hash);
}
// if (location && ga) {
// ga('send', 'pageview', location.hash);
// }
};
</script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/docsify.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/search.min.js"></script>
<script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/ga.min.js"></script>
<!-- <script src="//cdn.jsdelivr.net/npm/docsify/lib/plugins/ga.min.js"></script> -->
<script src="//cdn.jsdelivr.net/npm/prismjs@1/components/prism-coffeescript.min.js"></script>
</body>
</html>

View File

@@ -24,14 +24,13 @@ mindmap
Tools
Pen and paper
Mermaid
```
## Syntax
The syntax for creating Mindmaps is simple and relies on indentation for setting the levels in the hierarchy.
In the following example you can see how there are 3 different levels. One with starting at the left of the text and another level with two rows starting at the same column, defining the node A. At the end there is one more level where the text is indented further then the previous lines defining the nodes B and C.
In the following example you can see how there are 3 different levels of indentation. The leftmost indentation is the root of the mindmap. There can only be one root and if you by misstake add two of them on the same level there will be a syntax error. Rows with larger indentation will be connected as children to the previous row with lower indentation. Based on that you can see in the example how the nodes B and C both are children to node A whci in turn is a child of the node Root.
```
mindmap
@@ -41,7 +40,7 @@ mindmap
C
```
In summary is a simple text outline where there are one node at the root level called `Root` which has one child `A`. `A` in turn has two children `B`and `C`. In the diagram below we can see this rendered as a mindmap.
In the diagram below you can see the example rendered as a mindmap.
```mermaid
mindmap
@@ -145,7 +144,7 @@ mindmap
C
```
This outline is unclear as `B` clearly is a child of `A` but when we move on to `C` the clarity is lost. `C` is not a child of `B` with a higher indentation nor does it have the same indentation as `B`. The only thing that is clear is that the first node with smaller indentation, indicating a parent, is A. Then Mermaid relies on this known truth and compensates for the unclear indentation and selects `A` as a parent of `C` leading till the same diagram with `B` and `C` as siblings.
This outline is unclear as `B` clearly is a child of `A` but when we move on to `C` the clarity is lost. `C` is not a child of `B` with a higher indentation nor does it have the same indentation as `B`. The only thing that is clear is that the first node with smaller indentation, indicating a parent, is A. Mermaid will rely on this known truth and compensates for the unclear indentation and selects `A` as a parent of `C` leading till the same diagram with `B` and `C` as siblings.
```mermaid
mindmap

View File

@@ -30,6 +30,8 @@ export const log: Record<keyof typeof LEVELS, typeof console.log> = {
* @param {LogLevel} [level="fatal"] The level to set the logging to. Default is `"fatal"`
*/
export const setLogLevel = function (level: keyof typeof LEVELS | number | string = 'fatal') {
// TODO: Even if we call initialize with loglevel 0, many initial logs are skipped as LL is set to 5 initially.
let numericLevel: number = LEVELS.fatal;
if (typeof level === 'string') {
level = level.toLowerCase();
@@ -39,12 +41,14 @@ export const setLogLevel = function (level: keyof typeof LEVELS | number | strin
} else if (typeof level === 'number') {
numericLevel = level;
}
log.trace = () => {};
log.debug = () => {};
log.info = () => {};
log.warn = () => {};
log.error = () => {};
log.fatal = () => {};
if (numericLevel <= LEVELS.fatal) {
log.fatal = console.error
? console.error.bind(console, format('FATAL'), 'color: orange')

View File

@@ -54,6 +54,29 @@ describe('when using mermaid and ', function () {
expect(mermaidAPI.render).toHaveBeenCalled();
});
});
describe('when using #initThrowsErrorsAsync', function () {
it('should throw error (but still render) if lazyLoadedDiagram fails', async () => {
const node = document.createElement('div');
node.appendChild(document.createTextNode('graph TD;\na;'));
mermaidAPI.setConfig({
lazyLoadedDiagrams: ['this-file-does-not-exist.mjs'],
});
await expect(mermaid.initThrowsErrorsAsync(undefined, node)).rejects.toThrowError(
// this error message is probably different on every platform
// this one is just for vite-note (node/jest/browser may be different)
'Failed to load this-file-does-not-exist.mjs'
);
// should still render, even if lazyLoadedDiagrams fails
expect(mermaidAPI.renderAsync).toHaveBeenCalled();
});
afterEach(() => {
// we modify mermaid config in some tests, so we need to make sure to reset them
mermaidAPI.reset();
});
});
describe('checking validity of input ', function () {
it('should throw for an invalid definition', function () {

View File

@@ -2,13 +2,15 @@
* Web page integration module for the mermaid framework. It uses the mermaidAPI for mermaid
* functionality and to render the diagrams to svg code!
*/
import { MermaidConfig } from './config.type';
import type { MermaidConfig } from './config.type';
import { log } from './logger';
import utils from './utils';
import { mermaidAPI } from './mermaidAPI';
import { addDetector } from './diagram-api/detectType';
import { isDetailedError } from './utils';
import { isDetailedError, type DetailedError } from './utils';
import { registerDiagram } from './diagram-api/diagramAPI';
export type { MermaidConfig, DetailedError };
/**
* ## init
*
@@ -47,15 +49,10 @@ const init = async function (
try {
const conf = mermaidAPI.getConfig();
if (conf?.lazyLoadedDiagrams && conf.lazyLoadedDiagrams.length > 0) {
// Load all lazy loaded diagrams in parallel
await Promise.allSettled(
conf.lazyLoadedDiagrams.map(async (diagram: string) => {
const { id, detector, loadDiagram } = await import(diagram);
addDetector(id, detector, loadDiagram);
})
);
await initThrowsErrorsAsync(config, nodes, callback);
} else {
initThrowsErrors(config, nodes, callback);
}
await initThrowsErrors(config, nodes, callback);
} catch (e) {
log.warn('Syntax Error rendering');
if (isDetailedError(e)) {
@@ -67,7 +64,32 @@ const init = async function (
}
};
const initThrowsErrors = async function (
// eslint-disable-next-line @typescript-eslint/ban-types
const handleError = (error: unknown, errors: DetailedError[], parseError?: Function) => {
log.warn(error);
if (isDetailedError(error)) {
// handle case where error string and hash were
// wrapped in object like`const error = { str, hash };`
if (parseError) {
parseError(error.str, error.hash);
}
errors.push({ ...error, message: error.str, error });
} else {
// assume it is just error string and pass it on
if (parseError) {
parseError(error);
}
if (error instanceof Error) {
errors.push({
str: error.message,
message: error.message,
hash: error.name,
error,
});
}
}
};
const initThrowsErrors = function (
config?: MermaidConfig,
// eslint-disable-next-line no-undef
nodes?: string | HTMLElement | NodeListOf<HTMLElement>,
@@ -75,7 +97,6 @@ const initThrowsErrors = async function (
callback?: Function
) {
const conf = mermaidAPI.getConfig();
// console.log('Starting rendering diagrams (init) - mermaid.init', conf);
if (config) {
// This is a legacy way of setting config. It is not documented and should be removed in the future.
// @ts-ignore: TODO Fix ts errors
@@ -107,7 +128,7 @@ const initThrowsErrors = async function (
const idGenerator = new utils.initIdGenerator(conf.deterministicIds, conf.deterministicIDSeed);
let txt: string;
const errors = [];
const errors: DetailedError[] = [];
// element is the current div with mermaid class
for (const element of Array.from(nodesToProcess)) {
@@ -134,7 +155,7 @@ const initThrowsErrors = async function (
log.debug('Detected early reinit: ', init);
}
try {
await mermaidAPI.render(
mermaidAPI.render(
id,
txt,
(svgCode: string, bindFunctions?: (el: Element) => void) => {
@@ -147,13 +168,7 @@ const initThrowsErrors = async function (
element
);
} catch (error) {
log.warn('Catching Error (bootstrap)', error);
// @ts-ignore: TODO Fix ts errors
const mermaidError = { error, str: error.str, hash: error.hash, message: error.str };
if (typeof mermaid.parseError === 'function') {
mermaid.parseError(mermaidError);
}
errors.push(mermaidError);
handleError(error, errors, mermaid.parseError);
}
}
if (errors.length > 0) {
@@ -162,8 +177,174 @@ const initThrowsErrors = async function (
}
};
const initialize = async function (config: MermaidConfig) {
await mermaidAPI.initialize(config);
let lazyLoadingPromise: Promise<PromiseSettledResult<void>[]> | undefined = undefined;
/**
* This is an internal function and should not be made public, as it will likely change.
* @internal
* @param conf - Mermaid config.
* @returns An array of {@link PromiseSettledResult}, showing the status of imports.
*/
const registerLazyLoadedDiagrams = async (conf: MermaidConfig) => {
// Only lazy load once
// TODO: This is a hack. We should either throw error when new diagrams are added, or load them anyway.
if (lazyLoadingPromise === undefined) {
// Load all lazy loaded diagrams in parallel
lazyLoadingPromise = Promise.allSettled(
(conf?.lazyLoadedDiagrams ?? []).map(async (diagram: string) => {
const { id, detector, loadDiagram } = await import(diagram);
addDetector(id, detector, loadDiagram);
})
);
}
return await lazyLoadingPromise;
};
let loadingPromise: Promise<unknown> | undefined = undefined;
const loadExternalDiagrams = async (conf: MermaidConfig) => {
// Only lazy load once
// TODO: This is a hack. We should either throw error when new diagrams are added, or load them anyway.
if (loadingPromise === undefined) {
log.debug(`Loading ${conf?.lazyLoadedDiagrams?.length} external diagrams`);
// Load all lazy loaded diagrams in parallel
loadingPromise = Promise.allSettled(
(conf?.lazyLoadedDiagrams ?? []).map(async (url: string) => {
const { id, detector, loadDiagram } = await import(url);
const { diagram } = await loadDiagram();
registerDiagram(id, diagram, detector, diagram.injectUtils);
})
);
}
await loadingPromise;
};
/**
* Equivalent to {@link init()}, except an error will be thrown on error.
*
* @alpha
* @deprecated This is an internal function and will very likely be modified in v10, or earlier.
* We recommend staying with {@link initThrowsErrors} if you don't need `lazyLoadedDiagrams`.
*
* @param config - **Deprecated** Mermaid sequenceConfig.
* @param nodes - One of:
* - A DOM Node
* - An array of DOM nodes (as would come from a jQuery selector)
* - A W3C selector, a la `.mermaid` (default)
* @param callback - Function that is called with the id of each generated mermaid diagram.
* @returns Resolves on success, otherwise the {@link Promise} will be rejected.
*/
const initThrowsErrorsAsync = async function (
config?: MermaidConfig,
// eslint-disable-next-line no-undef
nodes?: string | HTMLElement | NodeListOf<HTMLElement>,
// eslint-disable-next-line @typescript-eslint/ban-types
callback?: Function
) {
const conf = mermaidAPI.getConfig();
const registerLazyLoadedDiagramsErrors: Error[] = [];
for (const registerResult of await registerLazyLoadedDiagrams(conf)) {
if (registerResult.status == 'rejected') {
registerLazyLoadedDiagramsErrors.push(registerResult.reason);
}
}
if (config) {
// This is a legacy way of setting config. It is not documented and should be removed in the future.
// @ts-ignore: TODO Fix ts errors
mermaid.sequenceConfig = config;
}
// if last argument is a function this is the callback function
log.debug(`${!callback ? 'No ' : ''}Callback function found`);
let nodesToProcess: ArrayLike<HTMLElement>;
if (typeof nodes === 'undefined') {
nodesToProcess = document.querySelectorAll('.mermaid');
} else if (typeof nodes === 'string') {
nodesToProcess = document.querySelectorAll(nodes);
} else if (nodes instanceof HTMLElement) {
nodesToProcess = [nodes];
} else if (nodes instanceof NodeList) {
nodesToProcess = nodes;
} else {
throw new Error('Invalid argument nodes for mermaid.init');
}
log.debug(`Found ${nodesToProcess.length} diagrams`);
if (typeof config?.startOnLoad !== 'undefined') {
log.debug('Start On Load: ' + config?.startOnLoad);
mermaidAPI.updateSiteConfig({ startOnLoad: config?.startOnLoad });
}
// generate the id of the diagram
const idGenerator = new utils.initIdGenerator(conf.deterministicIds, conf.deterministicIDSeed);
let txt: string;
const errors: DetailedError[] = [];
// element is the current div with mermaid class
for (const element of Array.from(nodesToProcess)) {
log.info('Rendering diagram: ' + element.id);
/*! Check if previously processed */
if (element.getAttribute('data-processed')) {
continue;
}
element.setAttribute('data-processed', 'true');
const id = `mermaid-${idGenerator.next()}`;
// Fetch the graph definition including tags
txt = element.innerHTML;
// transforms the html to pure text
txt = utils
.entityDecode(txt)
.trim()
.replace(/<br\s*\/?>/gi, '<br/>');
const init = utils.detectInit(txt);
if (init) {
log.debug('Detected early reinit: ', init);
}
try {
await mermaidAPI.renderAsync(
id,
txt,
(svgCode: string, bindFunctions?: (el: Element) => void) => {
element.innerHTML = svgCode;
if (typeof callback !== 'undefined') {
callback(id);
}
if (bindFunctions) bindFunctions(element);
},
element
);
} catch (error) {
handleError(error, errors, mermaid.parseError);
}
}
const allErrors = [...registerLazyLoadedDiagramsErrors, ...errors];
if (allErrors.length > 0) {
// TODO: We should be throwing an error object.
throw allErrors[0];
}
};
const initialize = function (config: MermaidConfig) {
mermaidAPI.initialize(config);
};
/**
* @param config
* @deprecated This is an internal function and should not be used. Will be removed in v10.
*/
const initializeAsync = async function (config: MermaidConfig) {
if (config.loadExternalDiagramsAtStartup) {
await loadExternalDiagrams(config);
} else {
await registerLazyLoadedDiagrams(config);
}
mermaidAPI.initialize(config);
};
/**
@@ -199,7 +380,7 @@ if (typeof document !== 'undefined') {
* This is provided for environments where the mermaid object can't directly have a new member added
* to it (eg. dart interop wrapper). (Initially there is no parseError member of mermaid).
*
* @param {function (err, hash)} newParseErrorHandler New parseError() callback.
* @param newParseErrorHandler New parseError() callback.
*/
const setParseErrorHandler = function (newParseErrorHandler: (err: any, hash: any) => void) {
mermaid.parseError = newParseErrorHandler;
@@ -209,6 +390,125 @@ const parse = (txt: string) => {
return mermaidAPI.parse(txt, mermaid.parseError);
};
const executionQueue: (() => Promise<unknown>)[] = [];
let executionQueueRunning = false;
const executeQueue = async () => {
if (executionQueueRunning) {
return;
}
executionQueueRunning = true;
while (executionQueue.length > 0) {
const f = executionQueue.shift();
if (f) {
try {
await f();
} catch (e) {
log.error('Error executing queue', e);
}
}
}
executionQueueRunning = false;
};
/**
* @param txt
* @deprecated This is an internal function and should not be used. Will be removed in v10.
*/
const parseAsync = (txt: string) => {
return new Promise((resolve, reject) => {
// This promise will resolve when the mermaidAPI.render call is done.
// It will be queued first and will be executed when it is first in line
const performCall = () =>
new Promise((res, rej) => {
mermaidAPI.parseAsync(txt, mermaid.parseError).then(
(r) => {
// This resolves for the promise for the queue handling
res(r);
// This fullfills the promise sent to the value back to the original caller
resolve(r);
},
(e) => {
log.error('Error parsing', e);
rej(e);
reject(e);
}
);
});
executionQueue.push(performCall);
executeQueue();
});
};
// const asynco = (id: string, delay: number) =>
// new Promise((res) => {
// setTimeout(() => {
// // This resolves for the promise for the queue handling
// res(id);
// }, delay);
// });
/**
* @param txt
* @param id
* @param delay
* @deprecated This is an internal function and should not be used. Will be removed in v10.
*/
// const test1 = (id: string, delay: number) => {
// const p = new Promise((resolve, reject) => {
// // This promise will resolve when the mermaidAPI.render call is done.
// // It will be queued first and will be executed when it is first in line
// const performCall = () =>
// new Promise((res) => {
// asynco(id, delay).then((r) => {
// // This resolves for the promise for the queue handling
// res(r);
// // This fullfills the promise sent to the value back to the original caller
// resolve(r + ' result to caller');
// });
// });
// executionQueue.push(performCall);
// });
// return p;
// };
/**
* @param txt
* @param id
* @param text
* @param cb
* @param container
* @deprecated This is an internal function and should not be used. Will be removed in v10.
*/
const renderAsync = (
id: string,
text: string,
cb: (svgCode: string, bindFunctions?: (element: Element) => void) => void,
container?: Element
): Promise<void> => {
return new Promise((resolve, reject) => {
// This promise will resolve when the mermaidAPI.render call is done.
// It will be queued first and will be executed when it is first in line
const performCall = () =>
new Promise((res, rej) => {
mermaidAPI.renderAsync(id, text, cb, container).then(
(r) => {
// This resolves for the promise for the queue handling
res(r);
// This fullfills the promise sent to the value back to the original caller
resolve(r);
},
(e) => {
log.error('Error parsing', e);
rej(e);
reject(e);
}
);
});
executionQueue.push(performCall);
executeQueue();
});
};
const mermaid: {
startOnLoad: boolean;
diagrams: any;
@@ -216,10 +516,14 @@ const mermaid: {
parseError?: Function;
mermaidAPI: typeof mermaidAPI;
parse: typeof parse;
parseAsync: typeof parseAsync;
render: typeof mermaidAPI.render;
renderAsync: typeof renderAsync;
init: typeof init;
initThrowsErrors: typeof initThrowsErrors;
initThrowsErrorsAsync: typeof initThrowsErrorsAsync;
initialize: typeof initialize;
initializeAsync: typeof initializeAsync;
contentLoaded: typeof contentLoaded;
setParseErrorHandler: typeof setParseErrorHandler;
} = {
@@ -227,10 +531,14 @@ const mermaid: {
diagrams: {},
mermaidAPI,
parse,
parseAsync,
render: mermaidAPI.render,
renderAsync,
init,
initThrowsErrors,
initThrowsErrorsAsync,
initialize,
initializeAsync,
parseError: undefined,
contentLoaded,
setParseErrorHandler,

View File

@@ -44,6 +44,18 @@ function parse(text: string, parseError?: Function): boolean {
return diagram.parse(text, parseError);
}
/**
*
* @param text
* @param parseError
*/
// eslint-disable-next-line @typescript-eslint/ban-types
async function parseAsync(text: string, parseError?: Function): Promise<boolean> {
addDiagrams();
const diagram = await getDiagramFromText(text, parseError);
return diagram.parse(text, parseError);
}
export const encodeEntities = function (text: string): string {
let txt = text;
@@ -110,7 +122,283 @@ export const decodeEntities = function (text: string): string {
* element will be removed when rendering is completed.
* @returns {void}
*/
const render = async function (
const render = function (
id: string,
text: string,
cb: (svgCode: string, bindFunctions?: (element: Element) => void) => void,
container?: Element
): void {
addDiagrams();
configApi.reset();
text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;;
const graphInit = utils.detectInit(text);
if (graphInit) {
directiveSanitizer(graphInit);
configApi.addDirective(graphInit);
}
const cnf = configApi.getConfig();
log.debug(cnf);
// Check the maximum allowed text size
if (text.length > cnf.maxTextSize!) {
text = 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa';
}
let root: any = select('body');
// In regular execution the container will be the div with a mermaid class
if (typeof container !== 'undefined') {
// A container was provided by the caller
if (container) {
container.innerHTML = '';
}
if (cnf.securityLevel === 'sandbox') {
// IF we are in sandboxed mode, we do everyting mermaid related
// in a sandboxed div
const iframe = select(container)
.append('iframe')
.attr('id', 'i' + id)
.attr('style', 'width: 100%; height: 100%;')
.attr('sandbox', '');
// const iframeBody = ;
root = select(iframe.nodes()[0]!.contentDocument!.body);
root.node().style.margin = 0;
} else {
root = select(container);
}
root
.append('div')
.attr('id', 'd' + id)
.attr('style', 'font-family: ' + cnf.fontFamily)
.append('svg')
.attr('id', id)
.attr('width', '100%')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.attr('xmlns:xlink', 'http://www.w3.org/1999/xlink')
.append('g');
} else {
// No container was provided
// If there is an existing element with the id, we remove it
// this likely a previously rendered diagram
const existingSvg = document.getElementById(id);
if (existingSvg) {
existingSvg.remove();
}
// Remove previous tpm element if it exists
let element;
if (cnf.securityLevel === 'sandbox') {
element = document.querySelector('#i' + id);
} else {
element = document.querySelector('#d' + id);
}
if (element) {
element.remove();
}
// Add the tmp div used for rendering with the id `d${id}`
// d+id it will contain a svg with the id "id"
if (cnf.securityLevel === 'sandbox') {
// IF we are in sandboxed mode, we do everyting mermaid related
// in a sandboxed div
const iframe = select('body')
.append('iframe')
.attr('id', 'i' + id)
.attr('style', 'width: 100%; height: 100%;')
.attr('sandbox', '');
root = select(iframe.nodes()[0]!.contentDocument!.body);
root.node().style.margin = 0;
} else {
root = select('body');
}
// This is the temporary div
root
.append('div')
.attr('id', 'd' + id)
// this is the seed of the svg to be rendered
.append('svg')
.attr('id', id)
.attr('width', '100%')
.attr('xmlns', 'http://www.w3.org/2000/svg')
.append('g');
}
text = encodeEntities(text);
// Important that we do not create the diagram until after the directives have been included
let diag;
let parseEncounteredException;
try {
// diag = new Diagram(text);
diag = getDiagramFromText(text);
if ('then' in diag) {
throw new Error('Diagram is a promise');
}
} catch (error) {
diag = new Diagram('error');
parseEncounteredException = error;
}
// Get the tmp element containing the the svg
const element = root.select('#d' + id).node();
const graphType = diag.type;
// insert inline style into svg
const svg = element.firstChild;
const firstChild = svg.firstChild;
let userStyles = '';
// user provided theme CSS
// If you add more configuration driven data into the user styles make sure that the value is
// sanitized bye the santiizeCSS function
if (cnf.themeCSS !== undefined) {
userStyles += `\n${cnf.themeCSS}`;
}
// user provided theme CSS
if (cnf.fontFamily !== undefined) {
userStyles += `\n:root { --mermaid-font-family: ${cnf.fontFamily}}`;
}
// user provided theme CSS
if (cnf.altFontFamily !== undefined) {
userStyles += `\n:root { --mermaid-alt-font-family: ${cnf.altFontFamily}}`;
}
// classDef
if (graphType === 'flowchart' || graphType === 'flowchart-v2' || graphType === 'graph') {
const classes: any = flowRenderer.getClasses(text, diag);
const htmlLabels = cnf.htmlLabels || cnf.flowchart?.htmlLabels;
for (const className in classes) {
if (htmlLabels) {
userStyles += `\n.${className} > * { ${classes[className].styles.join(
' !important; '
)} !important; }`;
userStyles += `\n.${className} span { ${classes[className].styles.join(
' !important; '
)} !important; }`;
} else {
userStyles += `\n.${className} path { ${classes[className].styles.join(
' !important; '
)} !important; }`;
userStyles += `\n.${className} rect { ${classes[className].styles.join(
' !important; '
)} !important; }`;
userStyles += `\n.${className} polygon { ${classes[className].styles.join(
' !important; '
)} !important; }`;
userStyles += `\n.${className} ellipse { ${classes[className].styles.join(
' !important; '
)} !important; }`;
userStyles += `\n.${className} circle { ${classes[className].styles.join(
' !important; '
)} !important; }`;
if (classes[className].textStyles) {
userStyles += `\n.${className} tspan { ${classes[className].textStyles.join(
' !important; '
)} !important; }`;
}
}
}
}
const stylis = (selector: string, styles: string) =>
serialize(compile(`${selector}{${styles}}`), stringify);
const rules = stylis(`#${id}`, getStyles(graphType, userStyles, cnf.themeVariables));
const style1 = document.createElement('style');
style1.innerHTML = `#${id} ` + rules;
svg.insertBefore(style1, firstChild);
try {
diag.renderer.draw(text, id, pkg.version, diag);
} catch (e) {
errorRenderer.draw(text, id, pkg.version);
throw e;
}
root
.select(`[id="${id}"]`)
.selectAll('foreignobject > *')
.attr('xmlns', 'http://www.w3.org/1999/xhtml');
// Fix for when the base tag is used
let svgCode = root.select('#d' + id).node().innerHTML;
log.debug('cnf.arrowMarkerAbsolute', cnf.arrowMarkerAbsolute);
if (!evaluate(cnf.arrowMarkerAbsolute) && cnf.securityLevel !== 'sandbox') {
svgCode = svgCode.replace(/marker-end="url\(.*?#/g, 'marker-end="url(#', 'g');
}
svgCode = decodeEntities(svgCode);
// Fix for when the br tag is used
svgCode = svgCode.replace(/<br>/g, '<br/>');
if (cnf.securityLevel === 'sandbox') {
const svgEl = root.select('#d' + id + ' svg').node();
const width = '100%';
let height = '100%';
if (svgEl) {
height = svgEl.viewBox.baseVal.height + 'px';
}
svgCode = `<iframe style="width:${width};height:${height};border:0;margin:0;" src="data:text/html;base64,${btoa(
'<body style="margin:0">' + svgCode + '</body>'
)}" sandbox="allow-top-navigation-by-user-activation allow-popups">
The “iframe” tag is not supported by your browser.
</iframe>`;
} else {
if (cnf.securityLevel !== 'loose') {
svgCode = DOMPurify.sanitize(svgCode, {
ADD_TAGS: ['foreignobject'],
ADD_ATTR: ['dominant-baseline'],
});
}
}
if (typeof cb !== 'undefined') {
switch (graphType) {
case 'flowchart':
case 'flowchart-v2':
cb(svgCode, flowDb.bindFunctions);
break;
case 'gantt':
cb(svgCode, ganttDb.bindFunctions);
break;
case 'class':
case 'classDiagram':
cb(svgCode, classDb.bindFunctions);
break;
default:
cb(svgCode);
}
} else {
log.debug('CB = undefined!');
}
attachFunctions();
const tmpElementSelector = cnf.securityLevel === 'sandbox' ? '#i' + id : '#d' + id;
const node = select(tmpElementSelector).node();
if (node && 'remove' in node) {
node.remove();
}
if (parseEncounteredException) {
throw parseEncounteredException;
}
return svgCode;
};
/**
* @deprecated This is an internal function and should not be used. Will be removed in v10.
*/
const renderAsync = async function (
id: string,
text: string,
cb: (svgCode: string, bindFunctions?: (element: Element) => void) => void,
@@ -302,7 +590,7 @@ const render = async function (
try {
await diag.renderer.draw(text, id, pkg.version, diag);
} catch (e) {
await errorRenderer.draw(text, id, pkg.version);
errorRenderer.draw(text, id, pkg.version);
throw e;
}
@@ -453,12 +741,13 @@ const handleDirective = function (p: any, directive: any, type: string): void {
};
/** @param {MermaidConfig} options */
async function initialize(options: MermaidConfig) {
function initialize(options: MermaidConfig = {}) {
// Handle legacy location of font-family configuration
if (options?.fontFamily) {
if (!options.themeVariables?.fontFamily) {
options.themeVariables = { fontFamily: options.fontFamily };
if (options.fontFamily) {
if (!options.themeVariables) {
options.themeVariables = {};
}
options.themeVariables.fontFamily = options.fontFamily;
}
// Set default options
@@ -482,7 +771,9 @@ async function initialize(options: MermaidConfig) {
export const mermaidAPI = Object.freeze({
render,
renderAsync,
parse,
parseAsync,
parseDirective,
initialize,
getConfig: configApi.getConfig,
@@ -501,6 +792,7 @@ export const mermaidAPI = Object.freeze({
setLogLevel(configApi.getConfig().logLevel);
configApi.reset(configApi.getConfig());
export default mermaidAPI;
/**
* ## mermaidAPI configuration defaults
*

View File

@@ -147,11 +147,18 @@ class Theme {
this['cScaleInv' + i] = this['cScaleInv' + i] || adjust(this['cScale' + i], { h: 180 });
}
// Setup teh label color for the set
this.scaleLabelColor = this.scaleLabelColor || (this.darkMode ? 'black' : this.labelTextColor);
// Setup the label color for the set
this.scaleLabelColor =
this.scaleLabelColor !== 'calculated' && this.scaleLabelColor
? this.scaleLabelColor
: this.labelTextColor;
for (let i = 0; i < this.THEME_COLOR_LIMIT; i++) {
this['cScaleLabel' + i] = this['cScaleLabel' + i] || this.scaleLabelColor;
if (this.labelTextColor !== 'calculated') {
this.cScaleLabel0 = this.cScaleLabel0 || invert(this.labelTextColor);
this.cScaleLabel3 = this.cScaleLabel3 || invert(this.labelTextColor);
for (let i = 0; i < this.THEME_COLOR_LIMIT; i++) {
this['cScaleLabel' + i] = this['cScaleLabel' + i] || this.labelTextColor;
}
}
/* Flowchart variables */

View File

@@ -121,7 +121,10 @@ class Theme {
}
// Setup teh label color for the set
this.scaleLabelColor = this.scaleLabelColor || (this.darkMode ? 'black' : this.labelTextColor);
this.scaleLabelColor =
this.scaleLabelColor !== 'calculated' && this.scaleLabelColor
? this.scaleLabelColor
: this.labelTextColor;
for (let i = 0; i < this.THEME_COLOR_LIMIT; i++) {
this['cScaleLabel' + i] = this['cScaleLabel' + i] || this.scaleLabelColor;

View File

@@ -825,6 +825,8 @@ export const sanitizeCss = (str) => {
export interface DetailedError {
str: string;
hash: any;
error?: any;
message?: string;
}
/** @param error */

1435
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff