Merge branch next into add-pie-langium-parser

This commit is contained in:
Reda Al Sulais 2023-12-31 15:21:47 +03:00
commit c2ea23f01f
92 changed files with 3241 additions and 1083 deletions

View File

@ -22,4 +22,9 @@ export const packageOptions = {
packageName: 'mermaid-zenuml',
file: 'detector.ts',
},
'mermaid-flowchart-elk': {
name: 'mermaid-flowchart-elk',
packageName: 'mermaid-flowchart-elk',
file: 'detector.ts',
},
} as const;

18
.build/types.ts Normal file
View File

@ -0,0 +1,18 @@
import { packageOptions } from './common.js';
import { execSync } from 'child_process';
const buildType = (packageName: string) => {
console.log(`Building types for ${packageName}`);
try {
const out = execSync(`tsc -p ./packages/${packageName}/tsconfig.json --emitDeclarationOnly`);
out.length > 0 && console.log(out.toString());
} catch (e) {
console.error(e);
e.stdout.length > 0 && console.error(e.stdout.toString());
e.stderr.length > 0 && console.error(e.stderr.toString());
}
};
for (const { packageName } of Object.values(packageOptions)) {
buildType(packageName);
}

View File

@ -5,38 +5,25 @@ import { getBuildConfig, defaultOptions } from './util.js';
import { context } from 'esbuild';
import chokidar from 'chokidar';
import { generateLangium } from '../.build/generateLangium.js';
import { packageOptions } from '../.build/common.js';
const parserCtx = await context(
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'parser' })
const configs = Object.values(packageOptions).map(({ packageName }) =>
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: packageName })
);
const mermaidCtx = await context(
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid' })
);
const mermaidIIFECtx = await context(
getBuildConfig({
...defaultOptions,
minify: false,
core: false,
entryName: 'mermaid',
format: 'iife',
})
);
const externalCtx = await context(
getBuildConfig({
...defaultOptions,
minify: false,
core: false,
entryName: 'mermaid-example-diagram',
})
);
const zenumlCtx = await context(
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid-zenuml' })
);
const contexts = [parserCtx, mermaidCtx, mermaidIIFECtx, externalCtx, zenumlCtx];
const mermaidIIFEConfig = getBuildConfig({
...defaultOptions,
minify: false,
core: false,
entryName: 'mermaid',
format: 'iife',
});
configs.push(mermaidIIFEConfig);
const contexts = await Promise.all(configs.map((config) => context(config)));
const rebuildAll = async () => {
console.time('Rebuild time');
await Promise.all(contexts.map((ctx) => ctx.rebuild()));
await Promise.all(contexts.map((ctx) => ctx.rebuild())).catch((e) => console.error(e));
console.timeEnd('Rebuild time');
};
@ -101,10 +88,9 @@ async function createServer() {
app.use(cors());
app.get('/events', eventsHandler);
app.use(express.static('./packages/parser/dist'));
app.use(express.static('./packages/mermaid/dist'));
app.use(express.static('./packages/mermaid-zenuml/dist'));
app.use(express.static('./packages/mermaid-example-diagram/dist'));
for (const { packageName } of Object.values(packageOptions)) {
app.use(express.static(`./packages/${packageName}/dist`));
}
app.use(express.static('demos'));
app.use(express.static('cypress/platform'));

View File

@ -65,6 +65,9 @@ export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
minify,
logLevel: 'info',
chunkNames: `chunks/${outFileName}/[name]-[hash]`,
define: {
'import.meta.vitest': 'undefined',
},
});
if (core) {

View File

@ -15,3 +15,4 @@ coverage:
# Turing off for now as code coverage isn't stable and causes unnecessary build failures.
# default:
# threshold: 2%
patch: off

View File

@ -1,4 +1,22 @@
'Type: Bug / Error': ['bug/*', fix/*]
'Type: Enhancement': ['feature/*', 'feat/*']
'Type: Other': ['other/*', 'chore/*', 'test/*', 'refactor/*']
'Area: Documentation': ['docs/*']
# yaml-language-server: $schema=https://raw.githubusercontent.com/release-drafter/release-drafter/master/schema.json
autolabeler:
- label: 'Type: Bug / Error'
branch:
- '/bug\/.+/'
- '/fix\/.+/'
- label: 'Type: Enhancement'
branch:
- '/feature\/.+/'
- '/feat\/.+/'
- label: 'Type: Other'
branch:
- '/other\/.+/'
- '/chore\/.+/'
- '/test\/.+/'
- '/refactor\/.+/'
- label: 'Area: Documentation'
branch:
- '/docs\/.+/'
template: |
This field is unused, as we only use this config file for labeling PRs.

View File

@ -25,8 +25,6 @@ categories:
change-template: '- $TITLE (#$NUMBER) @$AUTHOR'
sort-by: title
sort-direction: ascending
branches:
- develop
exclude-labels:
- 'Skip changelog'
no-changes-template: 'This release contains minor changes and bugfixes.'

View File

@ -1,23 +0,0 @@
name: Validate PR Labeler Configuration
on:
push:
paths:
- .github/workflows/pr-labeler-config-validator.yml
- .github/workflows/pr-labeler.yml
- .github/pr-labeler.yml
pull_request:
paths:
- .github/workflows/pr-labeler-config-validator.yml
- .github/workflows/pr-labeler.yml
- .github/pr-labeler.yml
jobs:
pr-labeler:
runs-on: ubuntu-latest
steps:
- name: Checkout Repository
uses: actions/checkout@v4
- name: Validate Configuration
uses: Yash-Singh1/pr-labeler-config-validator@releases/v0.0.3
with:
configuration-path: .github/pr-labeler.yml

View File

@ -1,13 +1,31 @@
name: Apply labels to PR
on:
pull_request_target:
types: [opened]
# required for pr-labeler to support PRs from forks
# ===================== ⛔ ☢️ 🚫 ⚠️ Warning ⚠️ 🚫 ☢️ ⛔ =======================
# Be very careful what you put in this GitHub Action workflow file to avoid
# malicious PRs from getting access to the Mermaid-js repo.
#
# Please read the following first before reviewing/merging:
# - https://docs.github.com/en/actions/using-workflows/events-that-trigger-workflows#pull_request_target
# - https://securitylab.github.com/research/github-actions-preventing-pwn-requests/
types: [opened, reopened, synchronize]
permissions:
contents: read
jobs:
pr-labeler:
runs-on: ubuntu-latest
permissions:
contents: read # read permission is required to read config file
pull-requests: write # write permission is required to label PRs
steps:
- name: Label PR
uses: TimonVS/pr-labeler-action@v4
uses: release-drafter/release-drafter@v5
with:
config-name: pr-labeler.yml
disable-autolabeler: false
disable-releaser: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -5,11 +5,19 @@ on:
branches:
- develop
permissions:
contents: read
jobs:
draft-release:
runs-on: ubuntu-latest
permissions:
contents: write # write permission is required to create a github release
pull-requests: read # required to read PR titles/labels
steps:
- name: Draft Release
uses: toolmantim/release-drafter@v5
uses: release-drafter/release-drafter@v5
with:
disable-autolabeler: true
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}

View File

@ -6,6 +6,6 @@ export default {
// https://prettier.io/docs/en/cli.html#--cache
'prettier --write',
],
'cSpell.json': ['ts-node-esm scripts/fixCSpell.ts'],
'cSpell.json': ['tsx scripts/fixCSpell.ts'],
'**/*.jison': ['pnpm -w run lint:jison'],
};

View File

@ -10,6 +10,8 @@ stats
.nyc_output
# Autogenerated by `pnpm run --filter mermaid types:build-config`
packages/mermaid/src/config.type.ts
# autogenereated by langium-cli
generated/
# Ignore the files creates in /demos/dev except for example.html
demos/dev/**
!/demos/dev/example.html

View File

@ -74,6 +74,9 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
output,
},
},
define: {
'import.meta.vitest': 'undefined',
},
resolve: {
extensions: [],
},

View File

@ -1,6 +1,7 @@
import express from 'express';
import cors from 'cors';
import { createServer as createViteServer } from 'vite';
import { packageOptions } from '../.build/common.js';
async function createServer() {
const app = express();
@ -14,10 +15,9 @@ async function createServer() {
});
app.use(cors());
app.use(express.static('./packages/parser/dist'));
app.use(express.static('./packages/mermaid/dist'));
app.use(express.static('./packages/mermaid-zenuml/dist'));
app.use(express.static('./packages/mermaid-example-diagram/dist'));
for (const { packageName } of Object.values(packageOptions)) {
app.use(express.static(`./packages/${packageName}/dist`));
}
app.use(vite.middlewares);
app.use(express.static('demos'));
app.use(express.static('cypress/platform'));

3
.vscode/launch.json vendored
View File

@ -18,7 +18,8 @@
"type": "node",
"request": "launch",
"args": ["scripts/docs.cli.mts"],
"runtimeArgs": ["--loader", "ts-node/esm"],
// we'll need to change this to --import in Node.JS v20.6.0 and up
"runtimeArgs": ["--loader", "tsx/esm"],
"cwd": "${workspaceRoot}/packages/mermaid",
"skipFiles": ["<node_internals>/**", "**/node_modules/**"],
"smartStep": true,

View File

@ -0,0 +1,14 @@
import { urlSnapshotTest, openURLAndVerifyRendering } from '../../helpers/util.ts';
describe('Flowchart elk', () => {
it('should use dagre as fallback', () => {
urlSnapshotTest('http://localhost:9000/flow-elk.html', {
name: 'flow-elk fallback to dagre',
});
});
it('should allow overriding with external package', () => {
urlSnapshotTest('http://localhost:9000/flow-elk.html?elk=true', {
name: 'flow-elk overriding dagre with elk',
});
});
});

View File

@ -729,6 +729,18 @@ A ~~~ B
{}
);
});
it('5064: Should render when subgraph child has links to outside node and subgraph', () => {
imgSnapshotTest(
`flowchart TB
Out --> In
subgraph Sub
In
end
Sub --> In`
);
});
describe('Markdown strings flowchart (#4220)', () => {
describe('html labels', () => {
it('With styling and classes', () => {
@ -874,4 +886,93 @@ end
});
});
});
describe('Subgraph title margins', () => {
it('Should render subgraphs with title margins set (LR)', () => {
imgSnapshotTest(
`flowchart LR
subgraph TOP
direction TB
subgraph B1
direction RL
i1 -->f1
end
subgraph B2
direction BT
i2 -->f2
end
end
A --> TOP --> B
B1 --> B2
`,
{ flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } } }
);
});
it('Should render subgraphs with title margins set (TD)', () => {
imgSnapshotTest(
`flowchart TD
subgraph TOP
direction LR
subgraph B1
direction RL
i1 -->f1
end
subgraph B2
direction BT
i2 -->f2
end
end
A --> TOP --> B
B1 --> B2
`,
{ flowchart: { subGraphTitleMargin: { top: 8, bottom: 16 } } }
);
});
it('Should render subgraphs with title margins set (LR) and htmlLabels set to false', () => {
imgSnapshotTest(
`flowchart LR
subgraph TOP
direction TB
subgraph B1
direction RL
i1 -->f1
end
subgraph B2
direction BT
i2 -->f2
end
end
A --> TOP --> B
B1 --> B2
`,
{
htmlLabels: false,
flowchart: { htmlLabels: false, subGraphTitleMargin: { top: 10, bottom: 5 } },
}
);
});
it('Should render subgraphs with title margins and edge labels', () => {
imgSnapshotTest(
`flowchart LR
subgraph TOP
direction TB
subgraph B1
direction RL
i1 --lb1-->f1
end
subgraph B2
direction BT
i2 --lb2-->f2
end
end
A --lb3--> TOP --lb4--> B
B1 --lb5--> B2
`,
{ flowchart: { subGraphTitleMargin: { top: 10, bottom: 5 } } }
);
});
});
});

View File

@ -701,4 +701,129 @@ gitGraph TB:
{}
);
});
it('34: should render a simple gitgraph with two branches from same commit', () => {
imgSnapshotTest(
`gitGraph
commit id:"1-abcdefg"
commit id:"2-abcdefg"
branch feature-001
commit id:"3-abcdefg"
commit id:"4-abcdefg"
checkout main
branch feature-002
commit id:"5-abcdefg"
checkout feature-001
merge feature-002
`,
{}
);
});
it('35: should render a simple gitgraph with two branches from same commit | Vertical Branch', () => {
imgSnapshotTest(
`gitGraph TB:
commit id:"1-abcdefg"
commit id:"2-abcdefg"
branch feature-001
commit id:"3-abcdefg"
commit id:"4-abcdefg"
checkout main
branch feature-002
commit id:"5-abcdefg"
checkout feature-001
merge feature-002
`,
{}
);
});
it('36: should render GitGraph with branch that is not used immediately', () => {
imgSnapshotTest(
`gitGraph LR:
commit id:"1-abcdefg"
branch x
checkout main
commit id:"2-abcdefg"
checkout x
commit id:"3-abcdefg"
checkout main
merge x
`,
{}
);
});
it('37: should render GitGraph with branch that is not used immediately | Vertical Branch', () => {
imgSnapshotTest(
`gitGraph TB:
commit id:"1-abcdefg"
branch x
checkout main
commit id:"2-abcdefg"
checkout x
commit id:"3-abcdefg"
checkout main
merge x
`,
{}
);
});
it('38: should render GitGraph with branch and sub-branch neither of which used immediately', () => {
imgSnapshotTest(
`gitGraph LR:
commit id:"1-abcdefg"
branch x
checkout main
commit id:"2-abcdefg"
checkout x
commit id:"3-abcdefg"
checkout main
merge x
checkout x
branch y
checkout x
commit id:"4-abcdefg"
checkout y
commit id:"5-abcdefg"
checkout x
merge y
`,
{}
);
});
it('39: should render GitGraph with branch and sub-branch neither of which used immediately | Vertical Branch', () => {
imgSnapshotTest(
`gitGraph TB:
commit id:"1-abcdefg"
branch x
checkout main
commit id:"2-abcdefg"
checkout x
commit id:"3-abcdefg"
checkout main
merge x
checkout x
branch y
checkout x
commit id:"4-abcdefg"
checkout y
commit id:"5-abcdefg"
checkout x
merge y
`,
{}
);
});
it('40: should render a simple gitgraph with cherry pick merge commit', () => {
imgSnapshotTest(
`gitGraph
commit id: "ZERO"
branch feature
branch release
checkout feature
commit id: "A"
commit id: "B"
checkout main
merge feature id: "M"
checkout release
cherry-pick id: "M" parent:"B"`
);
});
});

View File

@ -44,7 +44,7 @@ describe('pie chart', () => {
const style = svg.attr('style');
expect(style).to.match(/^max-width: [\d.]+px;$/);
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
expect(maxWidthValue).to.eq(984);
expect(maxWidthValue).to.be.within(590, 600); // depends on installed fonts: 596.2 on my PC, 597.5 on CI
});
});
@ -59,7 +59,7 @@ describe('pie chart', () => {
);
cy.get('svg').should((svg) => {
const width = parseFloat(svg.attr('width'));
expect(width).to.eq(984);
expect(width).to.be.within(590, 600); // depends on installed fonts: 596.2 on my PC, 597.5 on CI
expect(svg).to.not.have.attr('style');
});
});

View File

@ -0,0 +1,28 @@
<html>
<body>
<pre class="mermaid">
flowchart-elk
a[hello] --> b[world]
b --> c{test}
c --> one
c --> two
c --> three
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import elk from './mermaid-flowchart-elk.esm.min.mjs';
if (window.location.search.includes('elk')) {
await mermaid.registerExternalDiagrams([elk]);
}
mermaid.initialize({
logLevel: 3,
startOnLoad: false,
});
await mermaid.run();
if (window.Cypress) {
window.rendered = true;
}
</script>
</body>
</html>

35
demos/flowchart-elk.html Normal file
View File

@ -0,0 +1,35 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Mermaid Flowchart ELK Test Page</title>
</head>
<body>
<h1>Flowchart ELK</h1>
<pre class="mermaid">
flowchart-elk TD
A([Start]) ==> B[Step 1]
B ==> C{Flow 1}
C -- Choice 1.1 --> D[Step 2.1]
C -- Choice 1.3 --> I[Step 2.3]
C == Choice 1.2 ==> E[Step 2.2]
D --> F{Flow 2}
E ==> F{Flow 2}
F{Flow 2} == Choice 2.1 ==> H[Feedback node]
H[Feedback node] ==> B[Step 1]
F{Flow 2} == Choice 2.2 ==> G((Finish))
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
import flowchartELK from './mermaid-flowchart-elk.esm.mjs';
await mermaid.registerExternalDiagrams([flowchartELK]);
mermaid.initialize({
logLevel: 3,
});
</script>
</body>
</html>

View File

@ -14,30 +14,364 @@
</head>
<body>
<h1>Git diagram demo</h1>
<h1>Git graph demo</h1>
<h2>Simple "branch and merge" graph</h2>
<pre class="mermaid">
---
title: Simple Git diagram
title: Simple "branch and merge" (left-to-right)
---
gitGraph:
options
{
"nodeSpacing": 50,
"nodeRadius": 5
}
end
branch master
gitGraph LR:
commit
branch newbranch
checkout newbranch
commit
checkout main
merge newbranch
</pre>
<pre class="mermaid">
---
title: Simple "branch and merge" (top-to-bottom)
---
gitGraph TB:
commit
checkout master
branch newbranch
checkout newbranch
commit
checkout main
merge newbranch
</pre>
<h2>Continuous development graph</h2>
<pre class="mermaid">
---
title: Continuous development (left-to-right)
---
gitGraph LR:
commit
branch develop
checkout develop
commit
checkout main
merge develop
checkout develop
commit
checkout main
merge develop
</pre>
<pre class="mermaid">
---
title: Continuous development (top-to-bottom)
---
gitGraph TB:
commit
branch develop
checkout develop
commit
checkout main
merge develop
checkout develop
commit
checkout main
merge develop
</pre>
<h2>Merge feature to advanced main graph</h2>
<pre class="mermaid">
---
title: Merge feature to advanced main (left-to-right)
---
gitGraph LR:
commit
branch newbranch
checkout newbranch
commit
checkout main
commit
merge newbranch
</pre>
<pre class="mermaid">
---
title: Merge feature to advanced main (top-to-bottom)
---
gitGraph TB:
commit
branch newbranch
checkout newbranch
commit
checkout main
commit
merge newbranch
</pre>
<h2>Two-way merges</h2>
<pre class="mermaid">
---
title: Two-way merges (left-to-right)
---
gitGraph LR:
commit
branch develop
checkout develop
commit
checkout main
merge develop
commit
checkout develop
merge main
commit
checkout main
merge develop
</pre>
<pre class="mermaid">
---
title: Two-way merges (top-to-bottom)
---
gitGraph TB:
commit
branch develop
checkout develop
commit
checkout main
merge develop
commit
checkout develop
merge main
commit
checkout main
merge develop
</pre>
<h2>Cherry-pick from branch graph</h2>
<pre class="mermaid">
---
title: Cherry-pick from branch (left-to-right)
---
gitGraph LR:
commit
branch newbranch
checkout newbranch
commit id: "Pick me"
checkout main
commit
checkout newbranch
commit
checkout main
cherry-pick id: "Pick me"
</pre>
<pre class="mermaid">
---
title: Cherry-pick from branch (top-to-bottom)
---
gitGraph TB:
commit
branch newbranch
checkout newbranch
commit id: "Pick me"
checkout main
commit
checkout newbranch
commit
checkout main
cherry-pick id: "Pick me"
</pre>
<h2>Cherry-pick from main graph</h2>
<pre class="mermaid">
---
title: Cherry-pick from main (left-to-right)
---
gitGraph LR:
commit
branch develop
commit
checkout main
commit id:"A"
checkout develop
commit
cherry-pick id: "A"
</pre>
<pre class="mermaid">
---
title: Cherry-pick from main (top-to-bottom)
---
gitGraph TB:
commit
branch develop
commit
checkout main
commit id:"A"
checkout develop
commit
cherry-pick id: "A"
</pre>
<h2>Cherry-pick then merge graph</h2>
<pre class="mermaid">
---
title: Cherry-pick then merge (left-to-right)
---
gitGraph LR:
commit
branch newbranch
checkout newbranch
commit id: "Pick me"
checkout main
commit
checkout newbranch
commit
checkout main
cherry-pick id: "Pick me"
merge newbranch
</pre>
<pre class="mermaid">
---
title: Cherry-pick then merge (top-to-bottom)
---
gitGraph TB:
commit
branch newbranch
checkout newbranch
commit id: "Pick me"
checkout main
commit
checkout newbranch
commit
checkout main
cherry-pick id: "Pick me"
merge newbranch
</pre>
<h2>Merge from main onto undeveloped branch graph</h2>
<pre class="mermaid">
---
title: Merge from main onto undeveloped branch (left-to-right)
---
gitGraph LR:
commit
branch develop
commit
checkout main
commit
checkout develop
merge main
</pre>
<pre class="mermaid">
---
title: Merge from main onto undeveloped branch (top-to-bottom)
---
gitGraph TB:
commit
branch develop
commit
checkout main
commit
checkout develop
merge main
</pre>
<h2>Merge from main onto developed branch graph</h2>
<pre class="mermaid">
---
title: Merge from main onto developed branch (left-to-right)
---
gitGraph LR:
commit
branch develop
commit
checkout main
commit
checkout develop
commit
merge main
</pre>
<pre class="mermaid">
---
title: Merge from main onto developed branch (top-to-bottom)
---
gitGraph TB:
commit
branch develop
commit
checkout main
commit
checkout develop
commit
merge main
</pre>
<h2>Two branches from same commit graph</h2>
<pre class="mermaid">
---
title: Two branches from same commit (left-to-right)
---
gitGraph LR:
commit
commit
branch feature-001
commit
commit
checkout main
branch feature-002
commit
checkout feature-001
merge feature-002
</pre>
<pre class="mermaid">
---
title: Two branches from same commit (top-to-bottom)
---
gitGraph TB:
commit
commit
branch feature-001
commit
commit
checkout main
branch feature-002
commit
checkout feature-001
merge feature-002
</pre>
<h2>Three branches and a cherry-pick from each graph</h2>
<pre class="mermaid">
---
title: Three branches and a cherry-pick from each (left-to-right)
---
gitGraph LR:
commit id: "ZERO"
branch develop
commit id:"A"
checkout main
commit id:"ONE"
checkout develop
commit id:"B"
branch featureA
commit id:"FIX"
commit id: "FIX-2"
checkout main
commit id:"TWO"
cherry-pick id:"A"
commit id:"THREE"
cherry-pick id:"FIX"
checkout develop
commit id:"C"
merge featureA
</pre>
<pre class="mermaid">
---
title: Three branches and a cherry-pick from each (top-to-bottom)
---
gitGraph TB:
commit id: "ZERO"
branch develop
commit id:"A"
checkout main
commit id:"ONE"
checkout develop
commit id:"B"
branch featureA
commit id:"FIX"
commit id: "FIX-2"
checkout main
commit id:"TWO"
cherry-pick id:"A"
commit id:"THREE"
cherry-pick id:"FIX"
checkout develop
commit id:"C"
merge featureA
</pre>
<script type="module">
import mermaid from './mermaid.esm.mjs';
const ALLOWED_TAGS = [

View File

@ -33,6 +33,7 @@
---
config:
sankey:
useMaxWidth: true
showValues: false
width: 1200
height: 600

View File

@ -14,6 +14,9 @@
`Optional` **suppressErrors**: `boolean`
If `true`, parse will return `false` instead of throwing error when the diagram is invalid.
The `parseError` function will not be called.
#### Defined in
[mermaidAPI.ts:60](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L60)
[mermaidAPI.ts:64](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L64)

View File

@ -0,0 +1,21 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/interfaces/mermaidAPI.ParseResult.md](../../../../packages/mermaid/src/docs/config/setup/interfaces/mermaidAPI.ParseResult.md).
# Interface: ParseResult
[mermaidAPI](../modules/mermaidAPI.md).ParseResult
## Properties
### diagramType
**diagramType**: `string`
The diagram type, e.g. 'flowchart', 'sequence', etc.
#### Defined in
[mermaidAPI.ts:71](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L71)

View File

@ -39,7 +39,19 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
#### Defined in
[mermaidAPI.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L80)
[mermaidAPI.ts:94](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L94)
---
### diagramType
**diagramType**: `string`
The diagram type, e.g. 'flowchart', 'sequence', etc.
#### Defined in
[mermaidAPI.ts:84](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L84)
---
@ -51,4 +63,4 @@ The svg code for the rendered graph.
#### Defined in
[mermaidAPI.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L70)
[mermaidAPI.ts:80](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L80)

View File

@ -9,6 +9,7 @@
## Interfaces
- [ParseOptions](../interfaces/mermaidAPI.ParseOptions.md)
- [ParseResult](../interfaces/mermaidAPI.ParseResult.md)
- [RenderResult](../interfaces/mermaidAPI.RenderResult.md)
## References
@ -25,13 +26,13 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
#### Defined in
[mermaidAPI.ts:64](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L64)
[mermaidAPI.ts:74](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L74)
## Variables
### mermaidAPI
`Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: `Pick`<`DiagramMetadata`, `"title"`>) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }>
`Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: `Pick`<`DiagramMetadata`, `"title"`>) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md) & { `suppressErrors`: `true` }) => `Promise`<[`ParseResult`](../interfaces/mermaidAPI.ParseResult.md) | `false`>(`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<[`ParseResult`](../interfaces/mermaidAPI.ParseResult.md)> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }>
## mermaidAPI configuration defaults
@ -96,7 +97,7 @@ mermaid.initialize(config);
#### Defined in
[mermaidAPI.ts:603](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L603)
[mermaidAPI.ts:623](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L623)
## Functions
@ -127,7 +128,7 @@ Return the last node appended
#### Defined in
[mermaidAPI.ts:263](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L263)
[mermaidAPI.ts:277](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L277)
---
@ -153,7 +154,7 @@ the cleaned up svgCode
#### Defined in
[mermaidAPI.ts:209](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L209)
[mermaidAPI.ts:223](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L223)
---
@ -178,7 +179,7 @@ the string with all the user styles
#### Defined in
[mermaidAPI.ts:139](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L139)
[mermaidAPI.ts:153](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L153)
---
@ -201,7 +202,7 @@ the string with all the user styles
#### Defined in
[mermaidAPI.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L186)
[mermaidAPI.ts:200](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L200)
---
@ -228,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
[mermaidAPI.ts:124](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L124)
[mermaidAPI.ts:138](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L138)
---
@ -254,7 +255,7 @@ Put the svgCode into an iFrame. Return the iFrame code
#### Defined in
[mermaidAPI.ts:240](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L240)
[mermaidAPI.ts:254](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L254)
---
@ -279,4 +280,4 @@ Remove any existing elements from the given document
#### Defined in
[mermaidAPI.ts:313](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L313)
[mermaidAPI.ts:327](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L327)

View File

@ -331,15 +331,17 @@ module.exports = (options) ->
## Advanced usage
**Syntax validation without rendering (Work in Progress)**
### Syntax validation without rendering
The **mermaid.parse(txt)** function validates graph definitions without rendering a graph. **[This function is still a work in progress](https://github.com/mermaid-js/mermaid/issues/1066), find alternatives below.**
The `mermaid.parse(text, parseOptions)` function validates graph definitions without rendering a graph.
The function **mermaid.parse(txt)**, takes a text string as an argument and returns true if the definition follows mermaid's syntax and
false if it does not. The parseError function will be called when the parse function returns false.
The function `mermaid.parse(text, parseOptions)`, takes a text string as an argument and returns `{ diagramType: string }` if the definition follows mermaid's syntax.
When the parser encounters invalid syntax the **mermaid.parseError** function is called. It is possible to override this
function in order to handle the error in an application-specific way.
If the definition is invalid, the function returns `false` if `parseOptions.suppressErrors` is set to `true`. Otherwise, it throws an error.
The parseError function will be called when the parse function throws an error. It will not be called if `parseOptions.suppressErrors` is set to `true`.
It is possible to override this function in order to handle the error in an application-specific way.
The code-example below in meta code illustrates how this could work:
@ -359,26 +361,10 @@ const textFieldUpdated = async function () {
bindEventHandler('change', 'code', textFieldUpdated);
```
**Alternative to mermaid.parse():**
One effective and more future-proof method of validating your graph definitions, is to paste and render them via the [Mermaid Live Editor](https://mermaid.live/). This will ensure that your code is compliant with the syntax of Mermaid's most recent version.
## Configuration
Mermaid takes a number of options which lets you tweak the rendering of the diagrams. Currently there are three ways of
setting the options in mermaid.
1. Instantiation of the configuration using the initialize call
2. _Using the global mermaid object_ - **Deprecated**
3. _using the global mermaid_config object_ - **Deprecated**
4. Instantiation of the configuration using the **mermaid.init** call- **Deprecated**
The list above has two ways too many of doing this. Three are deprecated and will eventually be removed. The list of
configuration objects are described [in the mermaidAPI documentation](./setup/README.md).
## Using the `mermaidAPI.initialize`/`mermaid.initialize` call
The future proof way of setting the configuration is by using the initialization call to mermaid or mermaidAPI depending
on what kind of integration you use.
You can pass the required configuration to the `mermaid.initialize` call. This is the preferred way of configuring mermaid.
The list of configuration objects are described [in the mermaidAPI documentation](./setup/README.md).
```html
<script type="module">
@ -407,32 +393,3 @@ mermaid.startOnLoad = true;
> **Warning**
> This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.
## Using the mermaid_config
It is possible to set some configuration via the mermaid object. The two parameters that are supported using this
approach are:
- mermaid_config.startOnLoad
- mermaid_config.htmlLabels
```javascript
mermaid_config.startOnLoad = true;
```
> **Warning**
> This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.
## Using the mermaid.init call
To set some configuration via the mermaid object. The two parameters that are supported using this approach are:
- mermaid_config.startOnLoad
- mermaid_config.htmlLabels
```javascript
mermaid_config.startOnLoad = true;
```
> **Warning**
> This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.

View File

@ -111,6 +111,8 @@ Communication tools and platforms
### Wikis
- [PmWiki](https://www.pmwiki.org)
- [MermaidJs Cookbook recipe](https://www.pmwiki.org/wiki/Cookbook/MermaidJs)
- [MediaWiki](https://www.mediawiki.org)
- [Mermaid Extension](https://www.mediawiki.org/wiki/Extension:Mermaid)
- [Flex Diagrams Extension](https://www.mediawiki.org/wiki/Extension:Flex_Diagrams)
@ -179,7 +181,7 @@ Communication tools and platforms
### Document Generation
- [Docusaurus](https://docusaurus.io/docs/markdown-features/diagrams) ✅
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts)
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/features/diagrams-and-charts/#mermaid--swimm--up-to-date-diagrams-)
- [Sphinx](https://www.sphinx-doc.org/en/master/)
- [sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
- [remark](https://remark.js.org/)
@ -234,5 +236,5 @@ Communication tools and platforms
- [ExDoc](https://github.com/elixir-lang/ex_doc)
- [Rendering Mermaid graphs](https://github.com/elixir-lang/ex_doc#rendering-mermaid-graphs)
- [NiceGUI: Let any browser be the frontend of your Python code](https://nicegui.io)
- [ui.mermaid(...)](https://nicegui.io/reference#mermaid_diagrams)
- [ui.markdown(..., extras=\['mermaid'\])](https://nicegui.io/reference#markdown_element)
- [ui.mermaid(...)](https://nicegui.io/documentation/section_text_elements#markdown_element)
- [ui.markdown(..., extras=\['mermaid'\])](https://nicegui.io/documentation/section_text_elements#mermaid_diagrams)

View File

@ -6,18 +6,10 @@
# Announcements
<br />
Check out our latest blog posts below. See more blog posts [here](blog.md).
<a href="https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=416671&theme=light" alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## [5 Reasons You Should Be Using Mermaid Chart As Your Diagram Generator](https://www.mermaidchart.com/blog/posts/5-reasons-you-should-be-using-mermaid-chart-as-your-diagram-generator/)
## Calling all fans of Mermaid and Mermaid Chart! 🎉
14 November 2023 · 5 mins
Weve officially made our Product Hunt debut, and would love any and all support from the community!
[Click here](https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-chart) to check out our Product Hunt launch.
Feel free to drop us a comment and let us know what you think. All new sign ups will receive a 30-day free trial of our Pro subscription, plus 25% off your first year.
Were on a mission to make text-based diagramming fun again. And we need your help to make that happen.
Your support means the world to us. Thank you for being part of the diagramming movement.
Mermaid Chart, a user-friendly, code-based diagram generator with AI integrations, templates, collaborative tools, and plugins for developers, streamlines the process of creating and sharing diagrams, enhancing both creativity and collaboration.

View File

@ -6,6 +6,24 @@
# Blog
## [5 Reasons You Should Be Using Mermaid Chart As Your Diagram Generator](https://www.mermaidchart.com/blog/posts/5-reasons-you-should-be-using-mermaid-chart-as-your-diagram-generator/)
14 November 2023 · 5 mins
Mermaid Chart, a user-friendly, code-based diagram generator with AI integrations, templates, collaborative tools, and plugins for developers, streamlines the process of creating and sharing diagrams, enhancing both creativity and collaboration.
## [How to Use Mermaid Chart as an AI Diagram Generator](https://www.mermaidchart.com/blog/posts/how-to-use-mermaid-chart-as-an-ai-diagram-generator/)
1 November 2023 · 5 mins
Would an AI diagram generator make your life easier?
## [Diagrams, Made Even Easier: Introducing “Code Snippets” in the Mermaid Chart Editor](https://www.mermaidchart.com/blog/posts/easier-diagram-editing-with-code-snippets/)
12 October 2023 · 4 mins
Mermaid Chart introduces Code Snippets in its editor, streamlining the diagramming process for developers and professionals.
## [How to Make a Git Graph with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-make-a-git-graph-with-mermaid-chart/)
22 September 2023 · 7 mins

View File

@ -366,41 +366,49 @@ A few important rules to note here are:
1. You need to provide the `id` for an existing commit to be cherry-picked. If given commit id does not exist it will result in an error. For this, make use of the `commit id:$value` format of declaring commits. See the examples from above.
2. The given commit must not exist on the current branch. The cherry-picked commit must always be a different branch than the current branch.
3. Current branch must have at least one commit, before you can cherry-pick, otherwise it will cause an error is throw.
4. When cherry-picking a merge commit, providing a parent commit ID is mandatory. If the parent attribute is omitted or an invalid parent commit ID is provided, an error will be thrown.
5. The specified parent commit must be an immediate parent of the merge commit being cherry-picked.
Let see an example:
```mermaid-example
gitGraph
commit id: "ZERO"
branch develop
commit id:"A"
checkout main
commit id:"ONE"
checkout develop
commit id:"B"
checkout main
commit id:"TWO"
cherry-pick id:"A"
commit id:"THREE"
checkout develop
commit id:"C"
commit id: "ZERO"
branch develop
branch release
commit id:"A"
checkout main
commit id:"ONE"
checkout develop
commit id:"B"
checkout main
merge develop id:"MERGE"
commit id:"TWO"
checkout release
cherry-pick id:"MERGE" parent:"B"
commit id:"THREE"
checkout develop
commit id:"C"
```
```mermaid
gitGraph
commit id: "ZERO"
branch develop
commit id:"A"
checkout main
commit id:"ONE"
checkout develop
commit id:"B"
checkout main
commit id:"TWO"
cherry-pick id:"A"
commit id:"THREE"
checkout develop
commit id:"C"
commit id: "ZERO"
branch develop
branch release
commit id:"A"
checkout main
commit id:"ONE"
checkout develop
commit id:"B"
checkout main
merge develop id:"MERGE"
commit id:"TWO"
checkout release
cherry-pick id:"MERGE" parent:"B"
commit id:"THREE"
checkout develop
commit id:"C"
```
## Gitgraph specific configuration options

View File

@ -4,7 +4,7 @@
"version": "10.2.4",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"packageManager": "pnpm@8.10.4",
"packageManager": "pnpm@8.11.0",
"keywords": [
"diagram",
"markdown",
@ -16,25 +16,25 @@
],
"scripts": {
"build": "pnpm build:esbuild && pnpm build:types",
"build:esbuild": "pnpm run -r clean && ts-node-esm --transpileOnly .esbuild/build.ts",
"build:esbuild": "pnpm run -r clean && tsx .esbuild/build.ts",
"build:mermaid": "pnpm build:esbuild --mermaid",
"build:viz": "pnpm build:esbuild --visualize",
"build:types": "tsc -p ./packages/parser/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
"build:types": "tsx .build/types.ts",
"build:types:watch": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly --watch",
"dev": "ts-node-esm --transpileOnly .esbuild/server.ts",
"dev:vite": "ts-node-esm --transpileOnly .vite/server.ts",
"dev": "tsx .esbuild/server.ts",
"dev:vite": "tsx .vite/server.ts",
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev:vite",
"release": "pnpm build",
"lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts",
"lint:jison": "ts-node-esm ./scripts/jison/lint.mts",
"contributors": "ts-node-esm scripts/updateContributors.ts",
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && tsx scripts/fixCSpell.ts",
"lint:jison": "tsx ./scripts/jison/lint.mts",
"contributors": "tsx scripts/updateContributors.ts",
"cypress": "cypress run",
"cypress:open": "cypress open",
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
"e2e:coverage": "start-server-and-test dev:coverage http://localhost:9000/ cypress",
"coverage:cypress:clean": "rimraf .nyc_output coverage/cypress",
"coverage:merge": "ts-node-esm scripts/coverage.ts",
"coverage:merge": "tsx scripts/coverage.ts",
"coverage": "pnpm test:coverage --run && pnpm e2e:coverage && pnpm coverage:merge",
"ci": "vitest run",
"test": "pnpm lint && vitest run",
@ -118,9 +118,9 @@
"rimraf": "^5.0.0",
"rollup-plugin-visualizer": "^5.9.2",
"start-server-and-test": "^2.0.0",
"ts-node": "^10.9.1",
"tsx": "^4.6.2",
"typescript": "^5.1.3",
"vite": "^4.3.9",
"vite": "^4.4.12",
"vite-plugin-istanbul": "^4.1.0",
"vitest": "^0.34.0"
},

View File

@ -0,0 +1,45 @@
{
"name": "@mermaid-js/flowchart-elk",
"version": "1.0.0-rc.1",
"description": "Flowchart plugin for mermaid with ELK layout",
"module": "dist/mermaid-flowchart-elk.core.mjs",
"types": "dist/packages/mermaid-flowchart-elk/src/detector.d.ts",
"type": "module",
"exports": {
".": {
"import": "./dist/mermaid-flowchart-elk.core.mjs",
"types": "./dist/packages/mermaid-flowchart-elk/src/detector.d.ts"
},
"./*": "./*"
},
"keywords": [
"diagram",
"markdown",
"flowchart",
"elk",
"mermaid"
],
"scripts": {
"prepublishOnly": "pnpm -w run build"
},
"repository": {
"type": "git",
"url": "https://github.com/mermaid-js/mermaid"
},
"author": "Knut Sveidqvist",
"license": "MIT",
"dependencies": {
"d3": "^7.4.0",
"dagre-d3-es": "7.0.10",
"khroma": "^2.0.0",
"elkjs": "^0.8.2"
},
"devDependencies": {
"concurrently": "^8.0.0",
"rimraf": "^5.0.0",
"mermaid": "workspace:^"
},
"files": [
"dist"
]
}

View File

@ -0,0 +1,32 @@
import type {
ExternalDiagramDefinition,
DiagramDetector,
DiagramLoader,
} from '../../mermaid/src/diagram-api/types.js';
const id = 'flowchart-elk';
const detector: DiagramDetector = (txt, config): boolean => {
if (
// If diagram explicitly states flowchart-elk
/^\s*flowchart-elk/.test(txt) ||
// If a flowchart/graph diagram has their default renderer set to elk
(/^\s*flowchart|graph/.test(txt) && config?.flowchart?.defaultRenderer === 'elk')
) {
return true;
}
return false;
};
const loader: DiagramLoader = async () => {
const { diagram } = await import('./diagram-definition.js');
return { id, diagram };
};
const plugin: ExternalDiagramDefinition = {
id,
detector,
loader,
};
export default plugin;

View File

@ -0,0 +1,12 @@
// @ts-ignore: JISON typing missing
import parser from '../../mermaid/src/diagrams/flowchart/parser/flow.jison';
import * as db from '../../mermaid/src/diagrams/flowchart/flowDb.js';
import styles from '../../mermaid/src/diagrams/flowchart/styles.js';
import renderer from './flowRenderer-elk.js';
export const diagram = {
db,
renderer,
parser,
styles,
};

View File

@ -1,16 +1,17 @@
import { select, line, curveLinear } from 'd3';
import { insertNode } from '../../../dagre-wrapper/nodes.js';
import insertMarkers from '../../../dagre-wrapper/markers.js';
import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js';
import { insertNode } from '../../mermaid/src/dagre-wrapper/nodes.js';
import insertMarkers from '../../mermaid/src/dagre-wrapper/markers.js';
import { insertEdgeLabel } from '../../mermaid/src/dagre-wrapper/edges.js';
import { findCommonAncestor } from './render-utils.js';
import { labelHelper } from '../../../dagre-wrapper/shapes/util.js';
import { getConfig } from '../../../config.js';
import { log } from '../../../logger.js';
import { setupGraphViewbox } from '../../../setupGraphViewbox.js';
import common from '../../common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../../utils.js';
import { labelHelper } from '../../mermaid/src/dagre-wrapper/shapes/util.js';
import { getConfig } from '../../mermaid/src/config.js';
import { log } from '../../mermaid/src/logger.js';
import { setupGraphViewbox } from '../../mermaid/src/setupGraphViewbox.js';
import common from '../../mermaid/src/diagrams/common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../mermaid/src/utils.js';
import ELK from 'elkjs/lib/elk.bundled.js';
import { getLineFunctionsWithOffset } from '../../../utils/lineWithOffset.js';
import { getLineFunctionsWithOffset } from '../../mermaid/src/utils/lineWithOffset.js';
import { addEdgeMarkers } from '../../mermaid/src/dagre-wrapper/edgeMarker.js';
const elk = new ELK();
@ -586,108 +587,7 @@ const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAb
}
// look in edge data and decide which marker to use
switch (edgeData.arrowTypeStart) {
case 'arrow_cross':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-crossStart' + ')'
);
break;
case 'arrow_point':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-pointStart' + ')'
);
break;
case 'arrow_barb':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-barbStart' + ')'
);
break;
case 'arrow_circle':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-circleStart' + ')'
);
break;
case 'aggregation':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-aggregationStart' + ')'
);
break;
case 'extension':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-extensionStart' + ')'
);
break;
case 'composition':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-compositionStart' + ')'
);
break;
case 'dependency':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-dependencyStart' + ')'
);
break;
case 'lollipop':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-lollipopStart' + ')'
);
break;
default:
}
switch (edgeData.arrowTypeEnd) {
case 'arrow_cross':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-crossEnd' + ')');
break;
case 'arrow_point':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-pointEnd' + ')');
break;
case 'arrow_barb':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-barbEnd' + ')');
break;
case 'arrow_circle':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-circleEnd' + ')');
break;
case 'aggregation':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-aggregationEnd' + ')'
);
break;
case 'extension':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-extensionEnd' + ')'
);
break;
case 'composition':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-compositionEnd' + ')'
);
break;
case 'dependency':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-dependencyEnd' + ')'
);
break;
case 'lollipop':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-lollipopEnd' + ')'
);
break;
default:
}
addEdgeMarkers(svgPath, edgeData, url, id, diagramType);
};
/**
@ -695,7 +595,7 @@ const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAb
*
* @param text
* @param diagObj
* @returns {Record<string, import('../../../diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles
* @returns {Record<string, import('../../mermaid/src/diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles
*/
export const getClasses = function (text, diagObj) {
log.info('Extracting classes');

View File

@ -0,0 +1,10 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "../..",
"outDir": "./dist",
"types": ["vitest/importMeta", "vitest/globals"]
},
"include": ["./src/**/*.ts"],
"typeRoots": ["./src/types"]
}

View File

@ -1,6 +1,6 @@
{
"name": "mermaid",
"version": "11.0.0-alpha.2",
"version": "11.0.0-alpha.6",
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
@ -26,18 +26,18 @@
"clean": "rimraf dist",
"dev": "pnpm -w dev",
"docs:code": "typedoc src/defaultConfig.ts src/config.ts src/mermaidAPI.ts && prettier --write ./src/docs/config/setup",
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm scripts/docs.cli.mts",
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm scripts/docs.cli.mts --verify",
"docs:pre:vitepress": "pnpm --filter ./src/docs prefetch && rimraf src/vitepress && pnpm docs:code && ts-node-esm scripts/docs.cli.mts --vitepress && pnpm --filter ./src/vitepress install --no-frozen-lockfile --ignore-scripts",
"docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && tsx scripts/docs.cli.mts",
"docs:verify": "pnpm docs:spellcheck && pnpm docs:code && tsx scripts/docs.cli.mts --verify",
"docs:pre:vitepress": "pnpm --filter ./src/docs prefetch && rimraf src/vitepress && pnpm docs:code && tsx scripts/docs.cli.mts --vitepress && pnpm --filter ./src/vitepress install --no-frozen-lockfile --ignore-scripts",
"docs:build:vitepress": "pnpm docs:pre:vitepress && (cd src/vitepress && 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 scripts/docs.cli.mts --watch --vitepress\"",
"docs:dev:docker": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev:docker\" \"ts-node-esm scripts/docs.cli.mts --watch --vitepress\"",
"docs:dev": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev\" \"tsx scripts/docs.cli.mts --watch --vitepress\"",
"docs:dev:docker": "pnpm docs:pre:vitepress && concurrently \"pnpm --filter ./src/vitepress dev:docker\" \"tsx scripts/docs.cli.mts --watch --vitepress\"",
"docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress",
"docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"",
"docs:release-version": "ts-node-esm scripts/update-release-version.mts",
"docs:verify-version": "ts-node-esm scripts/update-release-version.mts --verify",
"types:build-config": "ts-node-esm --transpileOnly scripts/create-types-from-json-schema.mts",
"types:verify-config": "ts-node-esm scripts/create-types-from-json-schema.mts --verify",
"docs:release-version": "tsx scripts/update-release-version.mts",
"docs:verify-version": "tsx scripts/update-release-version.mts --verify",
"types:build-config": "tsx scripts/create-types-from-json-schema.mts",
"types:verify-config": "tsx scripts/create-types-from-json-schema.mts --verify",
"checkCircle": "npx madge --circular ./src",
"release": "pnpm build",
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm docs:release-version && pnpm -w run build"
@ -60,6 +60,7 @@
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.1",
"@mermaid-js/parser": "workspace:^",
"cytoscape": "^3.23.0",
"cytoscape-cose-bilkent": "^4.1.0",
"cytoscape-fcose": "^2.1.0",
@ -68,11 +69,9 @@
"dagre-d3-es": "7.0.10",
"dayjs": "^1.11.7",
"dompurify": "^3.0.5",
"elkjs": "^0.8.2",
"khroma": "^2.0.0",
"lodash-es": "^4.17.21",
"mdast-util-from-markdown": "^1.3.0",
"mermaid-parser": "workspace:^",
"stylis": "^4.1.3",
"ts-dedent": "^2.2.0",
"uuid": "^9.0.0"

View File

@ -233,6 +233,23 @@ async function generateTypescript(mermaidConfigSchema: JSONSchemaType<MermaidCon
}
}
/**
* Workaround for type duplication when a $ref property has siblings.
*
* @param json - The input JSON object.
*
* @see https://github.com/bcherny/json-schema-to-typescript/issues/193
*/
function removeProp(json: any, name: string) {
for (const prop in json) {
if (prop === name) {
delete json[prop];
} else if (typeof json[prop] === 'object') {
removeProp(json[prop], name);
}
}
}
/** Main function */
async function main() {
if (verifyOnly) {
@ -243,6 +260,8 @@ async function main() {
const configJsonSchema = await loadJsonSchemaFromYaml();
removeProp(configJsonSchema, 'default');
validateSchema(configJsonSchema);
// Generate types from JSON Schema

View File

@ -768,8 +768,8 @@ export interface XYChartConfig extends BaseDiagramConfig {
* Should show the chart title
*/
showTitle?: boolean;
xAxis?: XYChartAxisConfig1;
yAxis?: XYChartAxisConfig2;
xAxis?: XYChartAxisConfig;
yAxis?: XYChartAxisConfig;
/**
* How to plot will be drawn horizontal or vertical
*/
@ -779,104 +779,6 @@ export interface XYChartConfig extends BaseDiagramConfig {
*/
plotReservedSpacePercent?: number;
}
/**
* This object contains configuration for XYChart axis config
*/
export interface XYChartAxisConfig1 {
/**
* Should show the axis labels (tick text)
*/
showLabel?: boolean;
/**
* font size of the axis labels (tick text)
*/
labelFontSize?: number;
/**
* top and bottom space from axis label (tick text)
*/
labelPadding?: number;
/**
* Should show the axis title
*/
showTitle?: boolean;
/**
* font size of the axis title
*/
titleFontSize?: number;
/**
* top and bottom space from axis title
*/
titlePadding?: number;
/**
* Should show the axis tick lines
*/
showTick?: boolean;
/**
* length of the axis tick lines
*/
tickLength?: number;
/**
* width of the axis tick lines
*/
tickWidth?: number;
/**
* Show line across the axis
*/
showAxisLine?: boolean;
/**
* Width of the axis line
*/
axisLineWidth?: number;
}
/**
* This object contains configuration for XYChart axis config
*/
export interface XYChartAxisConfig2 {
/**
* Should show the axis labels (tick text)
*/
showLabel?: boolean;
/**
* font size of the axis labels (tick text)
*/
labelFontSize?: number;
/**
* top and bottom space from axis label (tick text)
*/
labelPadding?: number;
/**
* Should show the axis title
*/
showTitle?: boolean;
/**
* font size of the axis title
*/
titleFontSize?: number;
/**
* top and bottom space from axis title
*/
titlePadding?: number;
/**
* Should show the axis tick lines
*/
showTick?: boolean;
/**
* length of the axis tick lines
*/
tickLength?: number;
/**
* width of the axis tick lines
*/
tickWidth?: number;
/**
* Show line across the axis
*/
showAxisLine?: boolean;
/**
* Width of the axis line
*/
axisLineWidth?: number;
}
/**
* The object containing configurations specific for entity relationship diagrams
*
@ -1396,6 +1298,14 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
* Margin top for the text over the diagram
*/
titleTopMargin?: number;
/**
* Defines a top/bottom margin for subgraph titles
*
*/
subGraphTitleMargin?: {
top?: number;
bottom?: number;
};
arrowMarkerAbsolute?: boolean;
/**
* The amount of padding around the diagram as a whole so that embedded
@ -1464,13 +1374,7 @@ export interface SankeyDiagramConfig extends BaseDiagramConfig {
*
*/
linkColor?: SankeyLinkColor | string;
/**
* Controls the alignment of the Sankey diagrams.
*
* See <https://github.com/d3/d3-sankey#alignments>.
*
*/
nodeAlignment?: 'left' | 'right' | 'center' | 'justify';
nodeAlignment?: SankeyNodeAlignment;
useMaxWidth?: boolean;
/**
* Toggle to display or hide values along with title.

View File

@ -5,9 +5,11 @@ import { createText } from '../rendering-util/createText.js';
import { select } from 'd3';
import { getConfig } from '../diagram-api/diagramAPI.js';
import { evaluate } from '../diagrams/common/common.js';
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
const rect = (parent, node) => {
log.info('Creating subgraph rect for ', node.id, node);
const siteConfig = getConfig();
// Add outer g element
const shapeSvg = parent
@ -18,7 +20,7 @@ const rect = (parent, node) => {
// add the rect
const rect = shapeSvg.insert('rect', ':first-child');
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
const useHtmlLabels = evaluate(siteConfig.flowchart.htmlLabels);
// Create the label and insert it after the rect
const label = shapeSvg.insert('g').attr('class', 'cluster-label');
@ -34,7 +36,7 @@ const rect = (parent, node) => {
// Get the size of the label
let bbox = text.getBBox();
if (evaluate(getConfig().flowchart.htmlLabels)) {
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
@ -63,17 +65,18 @@ const rect = (parent, node) => {
.attr('width', width)
.attr('height', node.height + padding);
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
if (useHtmlLabels) {
label.attr(
'transform',
// This puts the labal on top of the box instead of inside it
'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2) + ')'
`translate(${node.x - bbox.width / 2}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
);
} else {
label.attr(
'transform',
// This puts the labal on top of the box instead of inside it
'translate(' + node.x + ', ' + (node.y - node.height / 2) + ')'
`translate(${node.x}, ${node.y - node.height / 2 + subGraphTitleTopMargin})`
);
}
// Center the label
@ -127,6 +130,8 @@ const noteGroup = (parent, node) => {
return shapeSvg;
};
const roundedWithTitle = (parent, node) => {
const siteConfig = getConfig();
// Add outer g element
const shapeSvg = parent.insert('g').attr('class', node.classes).attr('id', node.id);
@ -143,7 +148,7 @@ const roundedWithTitle = (parent, node) => {
// Get the size of the label
let bbox = text.getBBox();
if (evaluate(getConfig().flowchart.htmlLabels)) {
if (evaluate(siteConfig.flowchart.htmlLabels)) {
const div = text.children[0];
const dv = select(text);
bbox = div.getBoundingClientRect();
@ -175,6 +180,7 @@ const roundedWithTitle = (parent, node) => {
.attr('width', width + padding)
.attr('height', node.height + padding - bbox.height - 3);
const { subGraphTitleTopMargin } = getSubGraphTitleMargins(siteConfig);
// Center the label
label.attr(
'transform',
@ -184,7 +190,8 @@ const roundedWithTitle = (parent, node) => {
(node.y -
node.height / 2 -
node.padding / 3 +
(evaluate(getConfig().flowchart.htmlLabels) ? 5 : 3)) +
(evaluate(siteConfig.flowchart.htmlLabels) ? 5 : 3)) +
subGraphTitleTopMargin +
')'
);

View File

@ -0,0 +1,79 @@
import type { Mocked } from 'vitest';
import type { SVG } from '../diagram-api/types.js';
import { addEdgeMarkers } from './edgeMarker.js';
describe('addEdgeMarker', () => {
const svgPath = {
attr: vitest.fn(),
} as unknown as Mocked<SVG>;
const url = 'http://example.com';
const id = 'test';
const diagramType = 'test';
beforeEach(() => {
svgPath.attr.mockReset();
});
it('should add markers for arrow_cross:arrow_point', () => {
const arrowTypeStart = 'arrow_cross';
const arrowTypeEnd = 'arrow_point';
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-start',
`url(${url}#${id}_${diagramType}-crossStart)`
);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-end',
`url(${url}#${id}_${diagramType}-pointEnd)`
);
});
it('should add markers for aggregation:arrow_point', () => {
const arrowTypeStart = 'aggregation';
const arrowTypeEnd = 'arrow_point';
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-start',
`url(${url}#${id}_${diagramType}-aggregationStart)`
);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-end',
`url(${url}#${id}_${diagramType}-pointEnd)`
);
});
it('should add markers for arrow_point:aggregation', () => {
const arrowTypeStart = 'arrow_point';
const arrowTypeEnd = 'aggregation';
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-start',
`url(${url}#${id}_${diagramType}-pointStart)`
);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-end',
`url(${url}#${id}_${diagramType}-aggregationEnd)`
);
});
it('should add markers for aggregation:composition', () => {
const arrowTypeStart = 'aggregation';
const arrowTypeEnd = 'composition';
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-start',
`url(${url}#${id}_${diagramType}-aggregationStart)`
);
expect(svgPath.attr).toHaveBeenCalledWith(
'marker-end',
`url(${url}#${id}_${diagramType}-compositionEnd)`
);
});
it('should not add invalid markers', () => {
const arrowTypeStart = 'this is an invalid marker';
const arrowTypeEnd = ') url(https://my-malicious-site.example)';
addEdgeMarkers(svgPath, { arrowTypeStart, arrowTypeEnd }, url, id, diagramType);
expect(svgPath.attr).not.toHaveBeenCalled();
});
});

View File

@ -0,0 +1,57 @@
import type { SVG } from '../diagram-api/types.js';
import { log } from '../logger.js';
import type { EdgeData } from '../types.js';
/**
* Adds SVG markers to a path element based on the arrow types specified in the edge.
*
* @param svgPath - The SVG path element to add markers to.
* @param edge - The edge data object containing the arrow types.
* @param url - The URL of the SVG marker definitions.
* @param id - The ID prefix for the SVG marker definitions.
* @param diagramType - The type of diagram being rendered.
*/
export const addEdgeMarkers = (
svgPath: SVG,
edge: Pick<EdgeData, 'arrowTypeStart' | 'arrowTypeEnd'>,
url: string,
id: string,
diagramType: string
) => {
if (edge.arrowTypeStart) {
addEdgeMarker(svgPath, 'start', edge.arrowTypeStart, url, id, diagramType);
}
if (edge.arrowTypeEnd) {
addEdgeMarker(svgPath, 'end', edge.arrowTypeEnd, url, id, diagramType);
}
};
const arrowTypesMap = {
arrow_cross: 'cross',
arrow_point: 'point',
arrow_barb: 'barb',
arrow_circle: 'circle',
aggregation: 'aggregation',
extension: 'extension',
composition: 'composition',
dependency: 'dependency',
lollipop: 'lollipop',
} as const;
const addEdgeMarker = (
svgPath: SVG,
position: 'start' | 'end',
arrowType: string,
url: string,
id: string,
diagramType: string
) => {
const endMarkerType = arrowTypesMap[arrowType as keyof typeof arrowTypesMap];
if (!endMarkerType) {
log.warn(`Unknown arrow type: ${arrowType}`);
return; // unknown arrow type, ignore
}
const suffix = position === 'start' ? 'Start' : 'End';
svgPath.attr(`marker-${position}`, `url(${url}#${id}_${diagramType}-${endMarkerType}${suffix})`);
};

View File

@ -6,6 +6,8 @@ import { getConfig } from '../diagram-api/diagramAPI.js';
import utils from '../utils.js';
import { evaluate } from '../diagrams/common/common.js';
import { getLineFunctionsWithOffset } from '../utils/lineWithOffset.js';
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
import { addEdgeMarkers } from './edgeMarker.js';
let edgeLabels = {};
let terminalLabels = {};
@ -135,6 +137,8 @@ function setTerminalWidth(fo, value) {
export const positionEdgeLabel = (edge, paths) => {
log.info('Moving label abc78 ', edge.id, edge.label, edgeLabels[edge.id]);
let path = paths.updatedPath ? paths.updatedPath : paths.originalPath;
const siteConfig = getConfig();
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
if (edge.label) {
const el = edgeLabels[edge.id];
let x = edge.x;
@ -158,7 +162,7 @@ export const positionEdgeLabel = (edge, paths) => {
y = pos.y;
}
}
el.attr('transform', 'translate(' + x + ', ' + y + ')');
el.attr('transform', `translate(${x}, ${y + subGraphTitleTotalMargin / 2})`);
}
//let path = paths.updatedPath ? paths.updatedPath : paths.originalPath;
@ -172,7 +176,7 @@ export const positionEdgeLabel = (edge, paths) => {
x = pos.x;
y = pos.y;
}
el.attr('transform', 'translate(' + x + ', ' + y + ')');
el.attr('transform', `translate(${x}, ${y})`);
}
if (edge.startLabelRight) {
const el = terminalLabels[edge.id].startRight;
@ -188,7 +192,7 @@ export const positionEdgeLabel = (edge, paths) => {
x = pos.x;
y = pos.y;
}
el.attr('transform', 'translate(' + x + ', ' + y + ')');
el.attr('transform', `translate(${x}, ${y})`);
}
if (edge.endLabelLeft) {
const el = terminalLabels[edge.id].endLeft;
@ -200,7 +204,7 @@ export const positionEdgeLabel = (edge, paths) => {
x = pos.x;
y = pos.y;
}
el.attr('transform', 'translate(' + x + ', ' + y + ')');
el.attr('transform', `translate(${x}, ${y})`);
}
if (edge.endLabelRight) {
const el = terminalLabels[edge.id].endRight;
@ -212,7 +216,7 @@ export const positionEdgeLabel = (edge, paths) => {
x = pos.x;
y = pos.y;
}
el.attr('transform', 'translate(' + x + ', ' + y + ')');
el.attr('transform', `translate(${x}, ${y})`);
}
};
@ -506,108 +510,8 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
log.info('arrowTypeStart', edge.arrowTypeStart);
log.info('arrowTypeEnd', edge.arrowTypeEnd);
switch (edge.arrowTypeStart) {
case 'arrow_cross':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-crossStart' + ')'
);
break;
case 'arrow_point':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-pointStart' + ')'
);
break;
case 'arrow_barb':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-barbStart' + ')'
);
break;
case 'arrow_circle':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-circleStart' + ')'
);
break;
case 'aggregation':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-aggregationStart' + ')'
);
break;
case 'extension':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-extensionStart' + ')'
);
break;
case 'composition':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-compositionStart' + ')'
);
break;
case 'dependency':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-dependencyStart' + ')'
);
break;
case 'lollipop':
svgPath.attr(
'marker-start',
'url(' + url + '#' + id + '_' + diagramType + '-lollipopStart' + ')'
);
break;
default:
}
switch (edge.arrowTypeEnd) {
case 'arrow_cross':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-crossEnd' + ')');
break;
case 'arrow_point':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-pointEnd' + ')');
break;
case 'arrow_barb':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-barbEnd' + ')');
break;
case 'arrow_circle':
svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-circleEnd' + ')');
break;
case 'aggregation':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-aggregationEnd' + ')'
);
break;
case 'extension':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-extensionEnd' + ')'
);
break;
case 'composition':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-compositionEnd' + ')'
);
break;
case 'dependency':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-dependencyEnd' + ')'
);
break;
case 'lollipop':
svgPath.attr(
'marker-end',
'url(' + url + '#' + id + '_' + diagramType + '-lollipopEnd' + ')'
);
break;
default:
}
addEdgeMarkers(svgPath, edge, url, id, diagramType);
let paths = {};
if (pointsHasChanged) {
paths.updatedPath = points;

View File

@ -13,8 +13,10 @@ import { insertNode, positionNode, clear as clearNodes, setNodeElem } from './no
import { insertCluster, clear as clearClusters } from './clusters.js';
import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges.js';
import { log } from '../logger.js';
import { getSubGraphTitleMargins } from '../utils/subGraphTitleMargins.js';
import { getConfig } from '../diagram-api/diagramAPI.js';
const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) => {
const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster, siteConfig) => {
log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster);
const dir = graph.graph().rankdir;
log.trace('Dir in recursive render - dir:', dir);
@ -52,7 +54,14 @@ const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) =>
if (node && node.clusterNode) {
// const children = graph.children(v);
log.info('Cluster identified', v, node.width, graph.node(v));
const o = await recursiveRender(nodes, node.graph, diagramtype, id, graph.node(v));
const o = await recursiveRender(
nodes,
node.graph,
diagramtype,
id,
graph.node(v),
siteConfig
);
const newEl = o.elem;
updateNodeBounds(node, newEl);
node.diff = o.diff || 0;
@ -101,6 +110,7 @@ const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) =>
log.info('Graph after layout:', graphlibJson.write(graph));
// Move the nodes to the correct place
let diff = 0;
const { subGraphTitleTotalMargin } = getSubGraphTitleMargins(siteConfig);
sortNodesByHierarchy(graph).forEach(function (v) {
const node = graph.node(v);
log.info('Position ' + v + ': ' + JSON.stringify(graph.node(v)));
@ -114,16 +124,18 @@ const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) =>
);
if (node && node.clusterNode) {
// clusterDb[node.id].node = node;
node.y += subGraphTitleTotalMargin;
positionNode(node);
} else {
// Non cluster node
if (graph.children(v).length > 0) {
// A cluster in the non-recursive way
// positionCluster(node);
node.height += subGraphTitleTotalMargin;
insertCluster(clusters, node);
clusterDb[node.id].node = node;
} else {
node.y += subGraphTitleTotalMargin / 2;
positionNode(node);
}
}
@ -134,6 +146,7 @@ const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) =>
const edge = graph.edge(e);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
edge.points.forEach((point) => (point.y += subGraphTitleTotalMargin / 2));
const paths = insertEdge(edgePaths, e, edge, clusterDb, diagramtype, graph, id);
positionEdgeLabel(edge, paths);
});
@ -159,7 +172,8 @@ export const render = async (elem, graph, markers, diagramtype, id) => {
adjustClustersAndEdges(graph);
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
// log.warn('Graph ever after:', graphlibJson.write(graph.node('A').graph));
await recursiveRender(elem, graph, diagramtype, id);
const siteConfig = getConfig();
await recursiveRender(elem, graph, diagramtype, id, undefined, siteConfig);
};
// const shapeDefinitions = {};

View File

@ -80,7 +80,9 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
? getConfig().fontSize
: window.getComputedStyle(document.body).fontSize;
const enlargingFactor = 5;
img.style.width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px';
const width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px';
img.style.minWidth = width;
img.style.maxWidth = width;
} else {
img.style.width = '100%';
}

View File

@ -71,10 +71,9 @@ export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinitio
export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
if (detectors[key]) {
log.error(`Detector with key ${key} already exists`);
} else {
detectors[key] = { detector, loader };
log.warn(`Detector with key ${key} already exists. Overwriting.`);
}
detectors[key] = { detector, loader };
log.debug(`Detector with key ${key} added${loader ? ' with loader' : ''}`);
};

View File

@ -49,7 +49,7 @@ export const registerDiagram = (
detector?: DiagramDetector
) => {
if (diagrams[id]) {
throw new Error(`Diagram ${id} already registered.`);
log.warn(`Diagram with id ${id} already registered. Overwriting.`);
}
diagrams[id] = diagram;
if (detector) {

View File

@ -2,9 +2,32 @@ import { describe, test, expect } from 'vitest';
import { Diagram, getDiagramFromText } from './Diagram.js';
import { addDetector } from './diagram-api/detectType.js';
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
import type { DiagramLoader } from './diagram-api/types.js';
addDiagrams();
const getDummyDiagram = (id: string, title?: string): Awaited<ReturnType<DiagramLoader>> => {
return {
id,
diagram: {
db: {
getDiagramTitle: () => title ?? id,
},
parser: {
parse: () => {
// no-op
},
},
renderer: {
draw: () => {
// no-op
},
},
styles: {},
},
};
};
describe('diagram detection', () => {
test('should detect inbuilt diagrams', async () => {
const graph = (await getDiagramFromText('graph TD; A-->B')) as Diagram;
@ -21,30 +44,25 @@ describe('diagram detection', () => {
addDetector(
'loki',
(str) => str.startsWith('loki'),
() =>
Promise.resolve({
id: 'loki',
diagram: {
db: {},
parser: {
parse: () => {
// no-op
},
},
renderer: {
draw: () => {
// no-op
},
},
styles: {},
},
})
() => Promise.resolve(getDummyDiagram('loki'))
);
const diagram = (await getDiagramFromText('loki TD; A-->B')) as Diagram;
const diagram = await getDiagramFromText('loki TD; A-->B');
expect(diagram).toBeInstanceOf(Diagram);
expect(diagram.type).toBe('loki');
});
test('should allow external diagrams to override internal ones with same ID', async () => {
const title = 'overridden';
addDetector(
'flowchart-elk',
(str) => str.startsWith('flowchart-elk'),
() => Promise.resolve(getDummyDiagram('flowchart-elk', title))
);
const diagram = await getDiagramFromText('flowchart-elk TD; A-->B');
expect(diagram).toBeInstanceOf(Diagram);
expect(diagram.db.getDiagramTitle?.()).toBe(title);
});
test('should throw the right error for incorrect diagram', async () => {
await expect(getDiagramFromText('graph TD; A-->')).rejects.toThrowErrorMatchingInlineSnapshot(`
"Parse error on line 2:

View File

@ -446,11 +446,13 @@ const getNamespaces = function (): NamespaceMap {
* @public
*/
export const addClassesToNamespace = function (id: string, classNames: string[]) {
if (namespaces[id] !== undefined) {
classNames.map((className) => {
classes[className].parent = id;
namespaces[id].classes[className] = classes[className];
});
if (namespaces[id] === undefined) {
return;
}
for (const name of classNames) {
const { className } = splitClassNameAndType(name);
classes[className].parent = id;
namespaces[id].classes[className] = classes[className];
}
};

View File

@ -1043,6 +1043,19 @@ foo()
`;
parser.parse(str);
});
it('should handle namespace with generic types', () => {
parser.parse(`classDiagram
namespace space {
class Square~Shape~{
int id
List~int~ position
setPoints(List~int~ points)
getPoints() List~int~
}
}`);
});
});
});

View File

@ -681,3 +681,82 @@ describe('given text representing a method, ', function () {
});
});
});
describe('given text representing an attribute', () => {
describe('when the attribute has no modifiers', () => {
it('should parse the display text correctly', () => {
const str = 'name String';
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
expect(displayDetails.displayText).toBe('name String');
expect(displayDetails.cssStyle).toBe('');
});
});
describe('when the attribute has public "+" modifier', () => {
it('should parse the display text correctly', () => {
const str = '+name String';
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
expect(displayDetails.displayText).toBe('+name String');
expect(displayDetails.cssStyle).toBe('');
});
});
describe('when the attribute has protected "#" modifier', () => {
it('should parse the display text correctly', () => {
const str = '#name String';
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
expect(displayDetails.displayText).toBe('#name String');
expect(displayDetails.cssStyle).toBe('');
});
});
describe('when the attribute has private "-" modifier', () => {
it('should parse the display text correctly', () => {
const str = '-name String';
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
expect(displayDetails.displayText).toBe('-name String');
expect(displayDetails.cssStyle).toBe('');
});
});
describe('when the attribute has internal "~" modifier', () => {
it('should parse the display text correctly', () => {
const str = '~name String';
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
expect(displayDetails.displayText).toBe('~name String');
expect(displayDetails.cssStyle).toBe('');
});
});
describe('when the attribute has static "$" modifier', () => {
it('should parse the display text correctly and apply static css style', () => {
const str = 'name String$';
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
expect(displayDetails.displayText).toBe('name String');
expect(displayDetails.cssStyle).toBe(staticCssStyle);
});
});
describe('when the attribute has abstract "*" modifier', () => {
it('should parse the display text correctly and apply abstract css style', () => {
const str = 'name String*';
const displayDetails = new ClassMember(str, 'attribute').getDisplayDetails();
expect(displayDetails.displayText).toBe('name String');
expect(displayDetails.cssStyle).toBe(abstractCssStyle);
});
});
});

View File

@ -106,7 +106,7 @@ export class ClassMember {
this.visibility = firstChar as Visibility;
}
if (lastChar.match(/[*?]/)) {
if (lastChar.match(/[$*]/)) {
potentialClassifier = lastChar;
}

View File

@ -145,6 +145,7 @@ g.classGroup line {
.edgeTerminals {
font-size: 11px;
line-height: initial;
}
.classTitleText {

View File

@ -1,5 +1,4 @@
import type { DiagramAST } from 'mermaid-parser';
import type { DiagramAST } from '@mermaid-js/parser';
import type { DiagramDB } from '../../diagram-api/types.js';
export function populateCommonDb(ast: DiagramAST, db: DiagramDB) {

View File

@ -3,6 +3,7 @@ import type {
DiagramDetector,
DiagramLoader,
} from '../../../diagram-api/types.js';
import { log } from '../../../logger.js';
const id = 'flowchart-elk';
@ -13,13 +14,21 @@ const detector: DiagramDetector = (txt, config): boolean => {
// If a flowchart/graph diagram has their default renderer set to elk
(/^\s*flowchart|graph/.test(txt) && config?.flowchart?.defaultRenderer === 'elk')
) {
// This will log at the end, hopefully.
setTimeout(
() =>
log.warn(
'flowchart-elk was moved to an external package in Mermaid v11. Please refer [release notes](link) for more details. This diagram will be rendered using `dagre` layout as a fallback.'
),
500
);
return true;
}
return false;
};
const loader: DiagramLoader = async () => {
const { diagram } = await import('./flowchart-elk-definition.js');
const { diagram } = await import('../flowDiagram-v2.js');
return { id, diagram };
};

View File

@ -1,13 +0,0 @@
// @ts-ignore: JISON typing missing
import parser from '../parser/flow.jison';
import * as db from '../flowDb.js';
import renderer from './flowRenderer-elk.js';
import styles from './styles.js';
export const diagram = {
db,
renderer,
parser,
styles,
};

View File

@ -255,11 +255,12 @@ export const merge = function (otherBranch, custom_id, override_type, custom_tag
log.debug('in mergeBranch');
};
export const cherryPick = function (sourceId, targetId, tag) {
export const cherryPick = function (sourceId, targetId, tag, parentCommitId) {
log.debug('Entering cherryPick:', sourceId, targetId, tag);
sourceId = common.sanitizeText(sourceId, getConfig());
targetId = common.sanitizeText(targetId, getConfig());
tag = common.sanitizeText(tag, getConfig());
parentCommitId = common.sanitizeText(parentCommitId, getConfig());
if (!sourceId || commits[sourceId] === undefined) {
let error = new Error(
@ -274,20 +275,21 @@ export const cherryPick = function (sourceId, targetId, tag) {
};
throw error;
}
let sourceCommit = commits[sourceId];
let sourceCommitBranch = sourceCommit.branch;
if (sourceCommit.type === commitType.MERGE) {
if (
parentCommitId &&
!(Array.isArray(sourceCommit.parents) && sourceCommit.parents.includes(parentCommitId))
) {
let error = new Error(
'Incorrect usage of "cherryPick". Source commit should not be a merge commit'
'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
);
throw error;
}
if (sourceCommit.type === commitType.MERGE && !parentCommitId) {
let error = new Error(
'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.'
);
error.hash = {
text: 'cherryPick ' + sourceId + ' ' + targetId,
token: 'cherryPick ' + sourceId + ' ' + targetId,
line: '1',
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
expected: ['cherry-pick abc'],
};
throw error;
}
if (!targetId || commits[targetId] === undefined) {
@ -327,7 +329,11 @@ export const cherryPick = function (sourceId, targetId, tag) {
parents: [head == null ? null : head.id, sourceCommit.id],
branch: curBranch,
type: commitType.CHERRY_PICK,
tag: tag ?? 'cherry-pick:' + sourceCommit.id,
tag:
tag ??
`cherry-pick:${sourceCommit.id}${
sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : ''
}`,
};
head = commit;
commits[commit.id] = commit;

View File

@ -673,6 +673,145 @@ describe('when parsing a gitGraph', function () {
expect(commits[cherryPickCommitID].branch).toBe('main');
});
it('should support cherry-picking of merge commits', function () {
const str = `gitGraph
commit id: "ZERO"
branch feature
branch release
checkout feature
commit id: "A"
commit id: "B"
checkout main
merge feature id: "M"
checkout release
cherry-pick id: "M" parent:"B"
`;
parser.parse(str);
const commits = parser.yy.getCommits();
const cherryPickCommitID = Object.keys(commits)[4];
expect(commits[cherryPickCommitID].tag).toBe('cherry-pick:M|parent:B');
expect(commits[cherryPickCommitID].branch).toBe('release');
});
it('should support cherry-picking of merge commits with tag', function () {
const str = `gitGraph
commit id: "ZERO"
branch feature
branch release
checkout feature
commit id: "A"
commit id: "B"
checkout main
merge feature id: "M"
checkout release
cherry-pick id: "M" parent:"ZERO" tag: "v1.0"
`;
parser.parse(str);
const commits = parser.yy.getCommits();
const cherryPickCommitID = Object.keys(commits)[4];
expect(commits[cherryPickCommitID].tag).toBe('v1.0');
expect(commits[cherryPickCommitID].branch).toBe('release');
});
it('should support cherry-picking of merge commits with additional commit', function () {
const str = `gitGraph
commit id: "ZERO"
branch feature
branch release
checkout feature
commit id: "A"
commit id: "B"
checkout main
merge feature id: "M"
checkout release
commit id: "C"
cherry-pick id: "M" tag: "v2.1:ZERO" parent:"ZERO"
commit id: "D"
`;
parser.parse(str);
const commits = parser.yy.getCommits();
const cherryPickCommitID = Object.keys(commits)[5];
expect(commits[cherryPickCommitID].tag).toBe('v2.1:ZERO');
expect(commits[cherryPickCommitID].branch).toBe('release');
});
it('should support cherry-picking of merge commits with empty tag', function () {
const str = `gitGraph
commit id: "ZERO"
branch feature
branch release
checkout feature
commit id: "A"
commit id: "B"
checkout main
merge feature id: "M"
checkout release
commit id: "C"
cherry-pick id:"M" parent: "ZERO" tag:""
commit id: "D"
cherry-pick id:"M" tag:"" parent: "B"
`;
parser.parse(str);
const commits = parser.yy.getCommits();
const cherryPickCommitID = Object.keys(commits)[5];
const cherryPickCommitID2 = Object.keys(commits)[7];
expect(commits[cherryPickCommitID].tag).toBe('');
expect(commits[cherryPickCommitID2].tag).toBe('');
expect(commits[cherryPickCommitID].branch).toBe('release');
});
it('should fail cherry-picking of merge commits if the parent of merge commits is not specified', function () {
expect(() =>
parser
.parse(
`gitGraph
commit id: "ZERO"
branch feature
branch release
checkout feature
commit id: "A"
commit id: "B"
checkout main
merge feature id: "M"
checkout release
commit id: "C"
cherry-pick id:"M"
`
)
.toThrow(
'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.'
)
);
});
it('should fail cherry-picking of merge commits when the parent provided is not an immediate parent of cherry picked commit', function () {
expect(() =>
parser
.parse(
`gitGraph
commit id: "ZERO"
branch feature
branch release
checkout feature
commit id: "A"
commit id: "B"
checkout main
merge feature id: "M"
checkout release
commit id: "C"
cherry-pick id:"M" parent: "A"
`
)
.toThrow(
'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
)
);
});
it('should throw error when try to branch existing branch: main', function () {
const str = `gitGraph
commit

View File

@ -338,26 +338,34 @@ const drawCommits = (svg, commits, modifyGraph) => {
};
/**
* Detect if there are other commits between commit1's x-position and commit2's x-position on the
* same branch as commit2.
* Detect if there are commits
* between commitA's x-position
* and commitB's x-position on the
* same branch as commitA, where
* commitA isn't main
*
* @param {any} commit1
* @param {any} commit2
* @param {any} commitA
* @param {any} commitB
* @param branchToGetCurve
* @param p1
* @param p2
* @param allCommits
* @returns {boolean} If there are commits between commit1's x-position and commit2's x-position
* @returns {boolean}
* If there are commits between
* commitA's x-position
* and commitB's x-position
* on the source branch, where
* source branch is not main
* return true
*/
const hasOverlappingCommits = (commit1, commit2, allCommits) => {
// Find commits on the same branch as commit2
const keys = Object.keys(allCommits);
const overlappingComits = keys.filter((key) => {
return (
allCommits[key].branch === commit2.branch &&
allCommits[key].seq > commit1.seq &&
allCommits[key].seq < commit2.seq
);
const shouldRerouteArrow = (commitA, commitB, p1, p2, allCommits) => {
const commitBIsFurthest = dir === 'TB' ? p1.x < p2.x : p1.y < p2.y;
const branchToGetCurve = commitBIsFurthest ? commitB.branch : commitA.branch;
const isOnBranchToGetCurve = (x) => x.branch === branchToGetCurve;
const isBetweenCommits = (x) => x.seq > commitA.seq && x.seq < commitB.seq;
return Object.values(allCommits).some((commitX) => {
return isBetweenCommits(commitX) && isOnBranchToGetCurve(commitX);
});
return overlappingComits.length > 0;
};
/**
@ -388,49 +396,61 @@ const findLane = (y1, y2, depth = 0) => {
* Draw the lines between the commits. They were arrows initially.
*
* @param {any} svg
* @param {any} commit1
* @param {any} commit2
* @param {any} commitA
* @param {any} commitB
* @param {any} allCommits
*/
const drawArrow = (svg, commit1, commit2, allCommits) => {
const p1 = commitPos[commit1.id];
const p2 = commitPos[commit2.id];
const overlappingCommits = hasOverlappingCommits(commit1, commit2, allCommits);
// log.debug('drawArrow', p1, p2, overlappingCommits, commit1.id, commit2.id);
const drawArrow = (svg, commitA, commitB, allCommits) => {
const p1 = commitPos[commitA.id]; // arrowStart
const p2 = commitPos[commitB.id]; // arrowEnd
const arrowNeedsRerouting = shouldRerouteArrow(commitA, commitB, p1, p2, allCommits);
// log.debug('drawArrow', p1, p2, arrowNeedsRerouting, commitA.id, commitB.id);
// Lower-right quadrant logic; top-left is 0,0
let arc = '';
let arc2 = '';
let radius = 0;
let offset = 0;
let colorClassNum = branchPos[commit2.branch].index;
let colorClassNum = branchPos[commitB.branch].index;
let lineDef;
if (overlappingCommits) {
if (arrowNeedsRerouting) {
arc = 'A 10 10, 0, 0, 0,';
arc2 = 'A 10 10, 0, 0, 1,';
radius = 10;
offset = 10;
// Figure out the color of the arrow,arrows going down take the color from the destination branch
colorClassNum = branchPos[commit2.branch].index;
const lineY = p1.y < p2.y ? findLane(p1.y, p2.y) : findLane(p2.y, p1.y);
const lineX = p1.x < p2.x ? findLane(p1.x, p2.x) : findLane(p2.x, p1.x);
if (dir === 'TB') {
if (p1.x < p2.x) {
// Source commit is on branch position left of destination commit
// so render arrow rightward with colour of destination branch
colorClassNum = branchPos[commitB.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${lineX - radius} ${p1.y} ${arc2} ${lineX} ${
p1.y + offset
} L ${lineX} ${p2.y - radius} ${arc} ${lineX + offset} ${p2.y} L ${p2.x} ${p2.y}`;
} else {
// Source commit is on branch position right of destination commit
// so render arrow leftward with colour of source branch
colorClassNum = branchPos[commitA.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${lineX + radius} ${p1.y} ${arc} ${lineX} ${
p1.y + offset
} L ${lineX} ${p2.y - radius} ${arc2} ${lineX - offset} ${p2.y} L ${p2.x} ${p2.y}`;
}
} else {
if (p1.y < p2.y) {
// Source commit is on branch positioned above destination commit
// so render arrow downward with colour of destination branch
colorClassNum = branchPos[commitB.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY - radius} ${arc} ${
p1.x + offset
} ${lineY} L ${p2.x - radius} ${lineY} ${arc2} ${p2.x} ${lineY + offset} L ${p2.x} ${p2.y}`;
} else {
// Source commit is on branch positioned below destination commit
// so render arrow upward with colour of source branch
colorClassNum = branchPos[commitA.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY + radius} ${arc2} ${
p1.x + offset
} ${lineY} L ${p2.x - radius} ${lineY} ${arc} ${p2.x} ${lineY - offset} L ${p2.x} ${p2.y}`;
@ -445,7 +465,7 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
offset = 20;
// Figure out the color of the arrow,arrows going down take the color from the destination branch
colorClassNum = branchPos[commit2.branch].index;
colorClassNum = branchPos[commitB.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc2} ${p2.x} ${
p1.y + offset
@ -458,14 +478,14 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
offset = 20;
// Arrows going up take the color from the source branch
colorClassNum = branchPos[commit1.branch].index;
colorClassNum = branchPos[commitA.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc2} ${p1.x - offset} ${
p2.y
} L ${p2.x} ${p2.y}`;
}
if (p1.x === p2.x) {
colorClassNum = branchPos[commit1.branch].index;
colorClassNum = branchPos[commitA.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p1.x + radius} ${p1.y} ${arc} ${p1.x + offset} ${
p2.y + radius
} L ${p2.x} ${p2.y}`;
@ -475,10 +495,8 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
arc = 'A 20 20, 0, 0, 0,';
radius = 20;
offset = 20;
// Figure out the color of the arrow,arrows going down take the color from the destination branch
colorClassNum = branchPos[commit2.branch].index;
// Arrows going up take the color from the target branch
colorClassNum = branchPos[commitB.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${p2.y} L ${
p2.x
} ${p2.y}`;
@ -487,16 +505,15 @@ const drawArrow = (svg, commit1, commit2, allCommits) => {
arc = 'A 20 20, 0, 0, 0,';
radius = 20;
offset = 20;
// Arrows going up take the color from the source branch
colorClassNum = branchPos[commit1.branch].index;
colorClassNum = branchPos[commitA.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${p1.y - offset} L ${
p2.x
} ${p2.y}`;
}
if (p1.y === p2.y) {
colorClassNum = branchPos[commit1.branch].index;
colorClassNum = branchPos[commitA.branch].index;
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${p2.y} L ${
p2.x
} ${p2.y}`;

View File

@ -39,6 +39,7 @@ branch(?=\s|$) return 'BRANCH';
"order:" return 'ORDER';
merge(?=\s|$) return 'MERGE';
cherry\-pick(?=\s|$) return 'CHERRY_PICK';
"parent:" return 'PARENT_COMMIT'
// "reset" return 'RESET';
checkout(?=\s|$) return 'CHECKOUT';
"LR" return 'DIR';
@ -109,10 +110,17 @@ branchStatement
cherryPickStatement
: CHERRY_PICK COMMIT_ID STR {yy.cherryPick($3, '', undefined)}
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($3, '', undefined,$5)}
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG STR {yy.cherryPick($3, '', $5)}
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG EMPTYSTR {yy.cherryPick($3, '', '')}
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR COMMIT_TAG STR {yy.cherryPick($3, '', $7,$5)}
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG STR PARENT_COMMIT STR {yy.cherryPick($3, '', $5,$7)}
| CHERRY_PICK COMMIT_TAG STR COMMIT_ID STR {yy.cherryPick($5, '', $3)}
| CHERRY_PICK COMMIT_TAG EMPTYSTR COMMIT_ID STR {yy.cherryPick($3, '', '')}
| CHERRY_PICK COMMIT_TAG EMPTYSTR COMMIT_ID STR {yy.cherryPick($5, '', '')}
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG EMPTYSTR {yy.cherryPick($3, '', '')}
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR COMMIT_TAG EMPTYSTR {yy.cherryPick($3, '', '',$5)}
| CHERRY_PICK COMMIT_ID STR COMMIT_TAG EMPTYSTR PARENT_COMMIT STR {yy.cherryPick($3, '', '',$7)}
| CHERRY_PICK COMMIT_TAG STR COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($5, '', $3,$7)}
| CHERRY_PICK COMMIT_TAG EMPTYSTR COMMIT_ID STR PARENT_COMMIT STR{yy.cherryPick($5, '', '',$7)}
;
mergeStatement

View File

@ -1,5 +1,5 @@
import type { Info } from 'mermaid-parser';
import { parse } from 'mermaid-parser';
import type { Info } from '@mermaid-js/parser';
import { parse } from '@mermaid-js/parser';
import { log } from '../../logger.js';
import type { ParserDefinition } from '../../diagram-api/types.js';

View File

@ -1,5 +1,5 @@
import type { Pie } from 'mermaid-parser';
import { parse } from 'mermaid-parser';
import type { Pie } from '@mermaid-js/parser';
import { parse } from '@mermaid-js/parser';
import { log } from '../../logger.js';
import type { ParserDefinition } from '../../diagram-api/types.js';
import { populateCommonDb } from '../common/populateCommonDb.js';

View File

@ -1,6 +1,5 @@
import type d3 from 'd3';
import { scaleOrdinal, pie as d3pie, arc } from 'd3';
import { log } from '../../logger.js';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import { getConfig } from '../../diagram-api/diagramAPI.js';
@ -38,33 +37,24 @@ const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Section>[] => {
*/
export const draw: DrawDefinition = (text, id, _version, diagObj) => {
log.debug('rendering pie chart\n' + text);
const db = diagObj.db as PieDB;
const globalConfig: MermaidConfig = getConfig();
const pieConfig: Required<PieDiagramConfig> = cleanAndMerge(db.getConfig(), globalConfig.pie);
const height = 450;
// TODO: remove document width
const width: number =
document.getElementById(id)?.parentElement?.offsetWidth ?? pieConfig.useWidth;
const svg: SVG = selectSvgElement(id);
// Set viewBox
svg.attr('viewBox', `0 0 ${width} ${height}`);
configureSvgSize(svg, height, width, pieConfig.useMaxWidth);
const MARGIN = 40;
const LEGEND_RECT_SIZE = 18;
const LEGEND_SPACING = 4;
const height = 450;
const pieWidth: number = height;
const svg: SVG = selectSvgElement(id);
const group: Group = svg.append('g');
group.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
group.attr('transform', 'translate(' + pieWidth / 2 + ',' + height / 2 + ')');
const { themeVariables } = globalConfig;
let [outerStrokeWidth] = parseFontSize(themeVariables.pieOuterStrokeWidth);
outerStrokeWidth ??= 2;
const textPosition: number = pieConfig.textPosition;
const radius: number = Math.min(width, height) / 2 - MARGIN;
const radius: number = Math.min(pieWidth, height) / 2 - MARGIN;
// Shape helper to build arcs:
const arcGenerator: d3.Arc<unknown, d3.PieArcDatum<D3Section>> = arc<d3.PieArcDatum<D3Section>>()
.innerRadius(0)
@ -175,6 +165,19 @@ export const draw: DrawDefinition = (text, id, _version, diagObj) => {
}
return label;
});
const longestTextWidth = Math.max(
...legend
.selectAll('text')
.nodes()
.map((node) => (node as Element)?.getBoundingClientRect().width ?? 0)
);
const totalWidth = pieWidth + MARGIN + LEGEND_RECT_SIZE + LEGEND_SPACING + longestTextWidth;
// Set viewBox
svg.attr('viewBox', `0 0 ${totalWidth} ${height}`);
configureSvgSize(svg, height, totalWidth, pieConfig.useMaxWidth);
};
export const renderer = { draw };

View File

@ -16,7 +16,7 @@ import {
sankeyCenter as d3SankeyCenter,
sankeyJustify as d3SankeyJustify,
} from 'd3-sankey';
import { configureSvgSize } from '../../setupGraphViewbox.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import { Uid } from '../../rendering-util/uid.js';
import type { SankeyNodeAlignment } from '../../config.type.js';
@ -70,12 +70,6 @@ export const draw = function (text: string, id: string, _version: string, diagOb
const suffix = conf?.suffix ?? defaultSankeyConfig.suffix!;
const showValues = conf?.showValues ?? defaultSankeyConfig.showValues!;
// FIX: using max width prevents height from being set, is it intended?
// to add height directly one can use `svg.attr('height', height)`
//
// @ts-ignore TODO: svg type vs selection mismatch
configureSvgSize(svg, height, width, useMaxWidth);
// Prepare data for construction based on diagObj.db
// This must be a mutable object with `nodes` and `links` properties:
//
@ -208,6 +202,8 @@ export const draw = function (text: string, id: string, _version: string, diagOb
.attr('d', d3SankeyLinkHorizontal())
.attr('stroke', coloring)
.attr('stroke-width', (d: any) => Math.max(1, d.width));
setupGraphViewbox(undefined, svg, 0, useMaxWidth);
};
export default {

View File

@ -4,7 +4,10 @@ import { defineConfig, MarkdownOptions } from 'vitepress';
const allMarkdownTransformers: MarkdownOptions = {
// the shiki theme to highlight code blocks
theme: 'github-dark',
theme: {
light: 'github-light',
dark: 'github-dark',
},
config: async (md) => {
await MermaidExample(md);
},

View File

@ -328,15 +328,17 @@ module.exports = (options) ->
## Advanced usage
**Syntax validation without rendering (Work in Progress)**
### Syntax validation without rendering
The **mermaid.parse(txt)** function validates graph definitions without rendering a graph. **[This function is still a work in progress](https://github.com/mermaid-js/mermaid/issues/1066), find alternatives below.**
The `mermaid.parse(text, parseOptions)` function validates graph definitions without rendering a graph.
The function **mermaid.parse(txt)**, takes a text string as an argument and returns true if the definition follows mermaid's syntax and
false if it does not. The parseError function will be called when the parse function returns false.
The function `mermaid.parse(text, parseOptions)`, takes a text string as an argument and returns `{ diagramType: string }` if the definition follows mermaid's syntax.
When the parser encounters invalid syntax the **mermaid.parseError** function is called. It is possible to override this
function in order to handle the error in an application-specific way.
If the definition is invalid, the function returns `false` if `parseOptions.suppressErrors` is set to `true`. Otherwise, it throws an error.
The parseError function will be called when the parse function throws an error. It will not be called if `parseOptions.suppressErrors` is set to `true`.
It is possible to override this function in order to handle the error in an application-specific way.
The code-example below in meta code illustrates how this could work:
@ -356,26 +358,10 @@ const textFieldUpdated = async function () {
bindEventHandler('change', 'code', textFieldUpdated);
```
**Alternative to mermaid.parse():**
One effective and more future-proof method of validating your graph definitions, is to paste and render them via the [Mermaid Live Editor](https://mermaid.live/). This will ensure that your code is compliant with the syntax of Mermaid's most recent version.
## Configuration
Mermaid takes a number of options which lets you tweak the rendering of the diagrams. Currently there are three ways of
setting the options in mermaid.
1. Instantiation of the configuration using the initialize call
2. _Using the global mermaid object_ - **Deprecated**
3. _using the global mermaid_config object_ - **Deprecated**
4. Instantiation of the configuration using the **mermaid.init** call- **Deprecated**
The list above has two ways too many of doing this. Three are deprecated and will eventually be removed. The list of
configuration objects are described [in the mermaidAPI documentation](./setup/README.md).
## Using the `mermaidAPI.initialize`/`mermaid.initialize` call
The future proof way of setting the configuration is by using the initialization call to mermaid or mermaidAPI depending
on what kind of integration you use.
You can pass the required configuration to the `mermaid.initialize` call. This is the preferred way of configuring mermaid.
The list of configuration objects are described [in the mermaidAPI documentation](./setup/README.md).
```html
<script type="module">
@ -406,34 +392,3 @@ mermaid.startOnLoad = true;
```warning
This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.
```
## Using the mermaid_config
It is possible to set some configuration via the mermaid object. The two parameters that are supported using this
approach are:
- mermaid_config.startOnLoad
- mermaid_config.htmlLabels
```javascript
mermaid_config.startOnLoad = true;
```
```warning
This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.
```
## Using the mermaid.init call
To set some configuration via the mermaid object. The two parameters that are supported using this approach are:
- mermaid_config.startOnLoad
- mermaid_config.htmlLabels
```javascript
mermaid_config.startOnLoad = true;
```
```warning
This way of setting the configuration is deprecated. Instead the preferred way is to use the initialize method. This functionality is only kept for backwards compatibility.
```

View File

@ -109,6 +109,8 @@ Communication tools and platforms
### Wikis
- [PmWiki](https://www.pmwiki.org)
- [MermaidJs Cookbook recipe](https://www.pmwiki.org/wiki/Cookbook/MermaidJs)
- [MediaWiki](https://www.mediawiki.org)
- [Mermaid Extension](https://www.mediawiki.org/wiki/Extension:Mermaid)
- [Flex Diagrams Extension](https://www.mediawiki.org/wiki/Extension:Flex_Diagrams)
@ -177,7 +179,7 @@ Communication tools and platforms
### Document Generation
- [Docusaurus](https://docusaurus.io/docs/markdown-features/diagrams) ✅
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts)
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/features/diagrams-and-charts/#mermaid--swimm--up-to-date-diagrams-)
- [Sphinx](https://www.sphinx-doc.org/en/master/)
- [sphinxcontrib-mermaid](https://github.com/mgaitan/sphinxcontrib-mermaid)
- [remark](https://remark.js.org/)
@ -232,5 +234,5 @@ Communication tools and platforms
- [ExDoc](https://github.com/elixir-lang/ex_doc)
- [Rendering Mermaid graphs](https://github.com/elixir-lang/ex_doc#rendering-mermaid-graphs)
- [NiceGUI: Let any browser be the frontend of your Python code](https://nicegui.io)
- [ui.mermaid(...)](https://nicegui.io/reference#mermaid_diagrams)
- [ui.markdown(..., extras=['mermaid'])](https://nicegui.io/reference#markdown_element)
- [ui.mermaid(...)](https://nicegui.io/documentation/section_text_elements#markdown_element)
- [ui.markdown(..., extras=['mermaid'])](https://nicegui.io/documentation/section_text_elements#mermaid_diagrams)

View File

@ -1,17 +1,9 @@
# Announcements
<br />
Check out our latest blog posts below. See more blog posts [here](blog.md).
<a href="https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid&#0045;chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=416671&theme=light" alt="Mermaid&#0032;Chart - A&#0032;smarter&#0032;way&#0032;to&#0032;create&#0032;diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
## [5 Reasons You Should Be Using Mermaid Chart As Your Diagram Generator](https://www.mermaidchart.com/blog/posts/5-reasons-you-should-be-using-mermaid-chart-as-your-diagram-generator/)
## Calling all fans of Mermaid and Mermaid Chart! 🎉
14 November 2023 · 5 mins
Weve officially made our Product Hunt debut, and would love any and all support from the community!
[Click here](https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-chart) to check out our Product Hunt launch.
Feel free to drop us a comment and let us know what you think. All new sign ups will receive a 30-day free trial of our Pro subscription, plus 25% off your first year.
Were on a mission to make text-based diagramming fun again. And we need your help to make that happen.
Your support means the world to us. Thank you for being part of the diagramming movement.
Mermaid Chart, a user-friendly, code-based diagram generator with AI integrations, templates, collaborative tools, and plugins for developers, streamlines the process of creating and sharing diagrams, enhancing both creativity and collaboration.

View File

@ -1,5 +1,23 @@
# Blog
## [5 Reasons You Should Be Using Mermaid Chart As Your Diagram Generator](https://www.mermaidchart.com/blog/posts/5-reasons-you-should-be-using-mermaid-chart-as-your-diagram-generator/)
14 November 2023 · 5 mins
Mermaid Chart, a user-friendly, code-based diagram generator with AI integrations, templates, collaborative tools, and plugins for developers, streamlines the process of creating and sharing diagrams, enhancing both creativity and collaboration.
## [How to Use Mermaid Chart as an AI Diagram Generator](https://www.mermaidchart.com/blog/posts/how-to-use-mermaid-chart-as-an-ai-diagram-generator/)
1 November 2023 · 5 mins
Would an AI diagram generator make your life easier?
## [Diagrams, Made Even Easier: Introducing “Code Snippets” in the Mermaid Chart Editor](https://www.mermaidchart.com/blog/posts/easier-diagram-editing-with-code-snippets/)
12 October 2023 · 4 mins
Mermaid Chart introduces Code Snippets in its editor, streamlining the diagramming process for developers and professionals.
## [How to Make a Git Graph with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-make-a-git-graph-with-mermaid-chart/)
22 September 2023 · 7 mins

View File

@ -11,8 +11,8 @@
"preview-https": "pnpm build && serve .vitepress/dist",
"preview-https-no-prefetch": "pnpm build-no-prefetch && serve .vitepress/dist",
"prefetch": "pnpm fetch-contributors && pnpm fetch-avatars",
"fetch-avatars": "ts-node-esm .vitepress/scripts/fetch-avatars.ts",
"fetch-contributors": "ts-node-esm .vitepress/scripts/fetch-contributors.ts"
"fetch-avatars": "tsx .vitepress/scripts/fetch-avatars.ts",
"fetch-contributors": "tsx .vitepress/scripts/fetch-contributors.ts"
},
"dependencies": {
"@vueuse/core": "^10.1.0",
@ -22,17 +22,17 @@
},
"devDependencies": {
"@iconify-json/carbon": "^1.1.16",
"@unocss/reset": "^0.57.0",
"@vite-pwa/vitepress": "^0.2.0",
"@unocss/reset": "^0.58.0",
"@vite-pwa/vitepress": "^0.3.0",
"@vitejs/plugin-vue": "^4.2.1",
"fast-glob": "^3.2.12",
"https-localhost": "^4.7.1",
"pathe": "^1.1.0",
"unocss": "^0.57.0",
"unplugin-vue-components": "^0.25.0",
"vite": "^4.3.9",
"vite-plugin-pwa": "^0.16.0",
"vitepress": "1.0.0-rc.25",
"unocss": "^0.58.0",
"unplugin-vue-components": "^0.26.0",
"vite": "^4.4.12",
"vite-plugin-pwa": "^0.17.0",
"vitepress": "1.0.0-rc.31",
"workbox-window": "^7.0.0"
}
}

View File

@ -244,24 +244,29 @@ A few important rules to note here are:
1. You need to provide the `id` for an existing commit to be cherry-picked. If given commit id does not exist it will result in an error. For this, make use of the `commit id:$value` format of declaring commits. See the examples from above.
2. The given commit must not exist on the current branch. The cherry-picked commit must always be a different branch than the current branch.
3. Current branch must have at least one commit, before you can cherry-pick, otherwise it will cause an error is throw.
4. When cherry-picking a merge commit, providing a parent commit ID is mandatory. If the parent attribute is omitted or an invalid parent commit ID is provided, an error will be thrown.
5. The specified parent commit must be an immediate parent of the merge commit being cherry-picked.
Let see an example:
```mermaid-example
gitGraph
commit id: "ZERO"
branch develop
commit id:"A"
checkout main
commit id:"ONE"
checkout develop
commit id:"B"
checkout main
commit id:"TWO"
cherry-pick id:"A"
commit id:"THREE"
checkout develop
commit id:"C"
commit id: "ZERO"
branch develop
branch release
commit id:"A"
checkout main
commit id:"ONE"
checkout develop
commit id:"B"
checkout main
merge develop id:"MERGE"
commit id:"TWO"
checkout release
cherry-pick id:"MERGE" parent:"B"
commit id:"THREE"
checkout develop
commit id:"C"
```
## Gitgraph specific configuration options

View File

@ -2,7 +2,7 @@ import mermaid from './mermaid.js';
import { mermaidAPI } from './mermaidAPI.js';
import './diagram-api/diagram-orchestration.js';
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
import { beforeAll, describe, it, expect, vi } from 'vitest';
import { beforeAll, describe, it, expect, vi, afterEach } from 'vitest';
import type { DiagramDefinition } from './diagram-api/types.js';
beforeAll(async () => {
@ -89,7 +89,7 @@ describe('when using mermaid and ', () => {
).resolves.not.toThrow();
// should still render, even if lazyLoadedDiagrams fails
expect(mermaidAPI.render).toHaveBeenCalled();
});
}, 20_000);
it('should defer diagram load based on parameter', async () => {
let loaded = false;

View File

@ -6,7 +6,7 @@ import { dedent } from 'ts-dedent';
import type { MermaidConfig } from './config.type.js';
import { log } from './logger.js';
import utils from './utils.js';
import type { ParseOptions, RenderResult } from './mermaidAPI.js';
import type { ParseOptions, ParseResult, RenderResult } from './mermaidAPI.js';
import { mermaidAPI } from './mermaidAPI.js';
import { registerLazyLoadedDiagrams, detectType } from './diagram-api/detectType.js';
import { loadRegisteredDiagrams } from './diagram-api/loadDiagram.js';
@ -15,6 +15,7 @@ import { isDetailedError } from './utils.js';
import type { DetailedError } from './utils.js';
import type { ExternalDiagramDefinition } from './diagram-api/types.js';
import type { UnknownDiagramError } from './errors.js';
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
export type {
MermaidConfig,
@ -23,6 +24,7 @@ export type {
ParseErrorFunction,
RenderResult,
ParseOptions,
ParseResult,
UnknownDiagramError,
};
@ -243,6 +245,7 @@ const registerExternalDiagrams = async (
lazyLoad?: boolean;
} = {}
) => {
addDiagrams();
registerLazyLoadedDiagrams(...diagrams);
if (lazyLoad === false) {
await loadRegisteredDiagrams();
@ -311,11 +314,23 @@ const executeQueue = async () => {
/**
* Parse the text and validate the syntax.
* @param text - The mermaid diagram definition.
* @param parseOptions - Options for parsing.
* @returns true if the diagram is valid, false otherwise if parseOptions.suppressErrors is true.
* @throws Error if the diagram is invalid and parseOptions.suppressErrors is false.
* @param parseOptions - Options for parsing. @see {@link ParseOptions}
* @returns If valid, {@link ParseResult} otherwise `false` if parseOptions.suppressErrors is `true`.
* @throws Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
*
* @example
* ```js
* console.log(await mermaid.parse('flowchart \n a --> b'));
* // { diagramType: 'flowchart-v2' }
* console.log(await mermaid.parse('wrong \n a --> b', { suppressErrors: true }));
* // false
* console.log(await mermaid.parse('wrong \n a --> b', { suppressErrors: false }));
* // throws Error
* console.log(await mermaid.parse('wrong \n a --> b'));
* // throws Error
* ```
*/
const parse = async (text: string, parseOptions?: ParseOptions): Promise<boolean | void> => {
const parse: typeof mermaidAPI.parse = async (text, parseOptions) => {
return new Promise((resolve, reject) => {
// This promise will resolve when the render call is done.
// It will be queued first and will be executed when it is first in line
@ -364,7 +379,7 @@ const parse = async (text: string, parseOptions?: ParseOptions): Promise<boolean
* element will be removed when rendering is completed.
* @returns Returns the SVG Definition and BindFunctions.
*/
const render = (id: string, text: string, container?: Element): Promise<RenderResult> => {
const render: typeof mermaidAPI.render = (id, text, container) => {
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

View File

@ -1,5 +1,5 @@
'use strict';
import { vi } from 'vitest';
import { vi, it, expect, describe, beforeEach } from 'vitest';
// -------------------------------------
// Mocks and mocking
@ -67,6 +67,7 @@ vi.mock('stylis', () => {
});
import { compile, serialize } from 'stylis';
import { decodeEntities, encodeEntities } from './utils.js';
import { Diagram } from './Diagram.js';
/**
* @see https://vitest.dev/guide/mocking.html Mock part of a module
@ -684,14 +685,21 @@ describe('mermaidAPI', () => {
).resolves.toBe(false);
});
it('resolves for valid definition', async () => {
await expect(
mermaidAPI.parse('graph TD;A--x|text including URL space|B;')
).resolves.toBeTruthy();
await expect(mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).resolves
.toMatchInlineSnapshot(`
{
"diagramType": "flowchart-v2",
}
`);
});
it('returns true for valid definition with silent option', async () => {
await expect(
mermaidAPI.parse('graph TD;A--x|text including URL space|B;', { suppressErrors: true })
).resolves.toBe(true);
).resolves.toMatchInlineSnapshot(`
{
"diagramType": "flowchart-v2",
}
`);
});
});
@ -733,7 +741,8 @@ describe('mermaidAPI', () => {
it('should set aria-roledscription to the diagram type AND should call addSVGa11yTitleDescription', async () => {
const a11yDiagramInfo_spy = vi.spyOn(accessibility, 'setA11yDiagramInfo');
const a11yTitleDesc_spy = vi.spyOn(accessibility, 'addSVGa11yTitleDescription');
await mermaidAPI.render(id, diagramText);
const result = await mermaidAPI.render(id, diagramText);
expect(result.diagramType).toBe(expectedDiagramType);
expect(a11yDiagramInfo_spy).toHaveBeenCalledWith(
expect.anything(),
expectedDiagramType
@ -744,4 +753,16 @@ describe('mermaidAPI', () => {
});
});
});
describe('getDiagramFromText', () => {
it('should clean up comments when present in diagram definition', async () => {
const diagram = await mermaidAPI.getDiagramFromText(
`flowchart LR
%% This is a comment A -- text --> B{node}
A -- text --> B -- text2 --> C`
);
expect(diagram).toBeInstanceOf(Diagram);
expect(diagram.type).toBe('flowchart-v2');
});
});
});

View File

@ -17,7 +17,7 @@ import { compile, serialize, stringify } from 'stylis';
import { version } from '../package.json';
import * as configApi from './config.js';
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
import { Diagram, getDiagramFromText } from './Diagram.js';
import { Diagram, getDiagramFromText as getDiagramFromTextInternal } from './Diagram.js';
import errorRenderer from './diagrams/error/errorRenderer.js';
import { attachFunctions } from './interactionDb.js';
import { log, setLogLevel } from './logger.js';
@ -28,7 +28,7 @@ import type { MermaidConfig } from './config.type.js';
import { evaluate } from './diagrams/common/common.js';
import isEmpty from 'lodash-es/isEmpty.js';
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
import type { DiagramStyleClassDef } from './diagram-api/types.js';
import type { DiagramMetadata, DiagramStyleClassDef } from './diagram-api/types.js';
import { preprocessDiagram } from './preprocess.js';
import { decodeEntities } from './utils.js';
@ -57,9 +57,19 @@ const DOMPURIFY_TAGS = ['foreignobject'];
const DOMPURIFY_ATTR = ['dominant-baseline'];
export interface ParseOptions {
/**
* If `true`, parse will return `false` instead of throwing error when the diagram is invalid.
* The `parseError` function will not be called.
*/
suppressErrors?: boolean;
}
export interface ParseResult {
/**
* The diagram type, e.g. 'flowchart', 'sequence', etc.
*/
diagramType: string;
}
// This makes it clear that we're working with a d3 selected element of some kind, even though it's hard to specify the exact type.
export type D3Element = any;
@ -68,6 +78,10 @@ export interface RenderResult {
* The svg code for the rendered graph.
*/
svg: string;
/**
* The diagram type, e.g. 'flowchart', 'sequence', etc.
*/
diagramType: string;
/**
* Bind function to be called after the svg has been inserted into the DOM.
* This is necessary for adding event listeners to the elements in the svg.
@ -90,29 +104,29 @@ function processAndSetConfigs(text: string) {
/**
* Parse the text and validate the syntax.
* @param text - The mermaid diagram definition.
* @param parseOptions - Options for parsing.
* @returns true if the diagram is valid, false otherwise if parseOptions.suppressErrors is true.
* @throws Error if the diagram is invalid and parseOptions.suppressErrors is false.
* @param parseOptions - Options for parsing. @see {@link ParseOptions}
* @returns An object with the `diagramType` set to type of the diagram if valid. Otherwise `false` if parseOptions.suppressErrors is `true`.
* @throws Error if the diagram is invalid and parseOptions.suppressErrors is false or not set.
*/
async function parse(text: string, parseOptions?: ParseOptions): Promise<boolean> {
async function parse(
text: string,
parseOptions: ParseOptions & { suppressErrors: true }
): Promise<ParseResult | false>;
async function parse(text: string, parseOptions?: ParseOptions): Promise<ParseResult>;
async function parse(text: string, parseOptions?: ParseOptions): Promise<ParseResult | false> {
addDiagrams();
text = processAndSetConfigs(text).code;
try {
await getDiagramFromText(text);
const { code } = processAndSetConfigs(text);
const diagram = await getDiagramFromText(code);
return { diagramType: diagram.type };
} catch (error) {
if (parseOptions?.suppressErrors) {
return false;
}
throw error;
}
return true;
}
// append !important; to each cssClass followed by a final !important, all enclosed in { }
//
/**
* Create a CSS style that starts with the given class name, then the element,
* with an enclosing block that has each of the cssClasses followed by !important;
@ -483,6 +497,7 @@ const render = async function (
}
return {
diagramType,
svg: svgCode,
bindFunctions: diag.db.bindFunctions,
};
@ -519,6 +534,11 @@ function initialize(options: MermaidConfig = {}) {
addDiagrams();
}
const getDiagramFromText = (text: string, metadata: Pick<DiagramMetadata, 'title'> = {}) => {
const { code } = preprocessDiagram(text);
return getDiagramFromTextInternal(code, metadata);
};
/**
* Add accessibility (a11y) information to the diagram.
*

View File

@ -21,8 +21,9 @@
# - Use `meta:enum` to document enum values (from jsonschema2md)
# - Use `tsType` to override the TypeScript type (from json-schema-to-typescript)
# - If adding a new object to `MermaidConfig` (e.g. a new diagram type),
# you may need to add it to `.build/jsonSchema.ts` and `src/docs.mts`
# to get the docs/default values to generate properly.
# you may need to add it to `.build/jsonSchema.ts`, `src/docs.mts`
# and `scripts/create-types-from-json-schema.mjs`
# to get the default values/docs/types to generate properly.
$id: https://mermaid-js.github.io/schemas/config.schema.json
$schema: https://json-schema.org/draft/2019-09/schema
title: Mermaid Config
@ -1844,6 +1845,7 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
unevaluatedProperties: false
required:
- titleTopMargin
- subGraphTitleMargin
- diagramPadding
- htmlLabels
- nodeSpacing
@ -1856,6 +1858,20 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
titleTopMargin:
$ref: '#/$defs/GitGraphDiagramConfig/properties/titleTopMargin'
default: 25
subGraphTitleMargin:
description: |
Defines a top/bottom margin for subgraph titles
type: object
properties:
top:
type: integer
minimum: 0
bottom:
type: integer
minimum: 0
default:
top: 0
bottom: 0
arrowMarkerAbsolute:
type: boolean # TODO, is this actually used here (it has no default value but was in types)
diagramPadding:
@ -1966,10 +1982,12 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
$ref: '#/$defs/SankeyNodeAlignment'
default: justify
useMaxWidth:
type: boolean
default: false
showValues:
description: |
Toggle to display or hide values along with title.
type: boolean
default: true
prefix:
description: |

View File

@ -44,6 +44,8 @@ export const configureSvgSize = function (svgElem, height, width, useMaxWidth) {
const attrs = calculateSvgSizeAttrs(height, width, useMaxWidth);
d3Attrs(svgElem, attrs);
};
// TODO v11: Remove the graph parameter. It is not used.
export const setupGraphViewbox = function (graph, svgElem, padding, useMaxWidth) {
const svgBounds = svgElem.node().getBBox();
const sWidth = svgBounds.width;
@ -55,26 +57,13 @@ export const setupGraphViewbox = function (graph, svgElem, padding, useMaxWidth)
let height = 0;
log.info(`Graph bounds: ${width}x${height}`, graph);
// let tx = 0;
// let ty = 0;
// if (sWidth > width) {
// tx = (sWidth - width) / 2 + padding;
width = sWidth + padding * 2;
// } else {
// if (Math.abs(sWidth - width) >= 2 * padding + 1) {
// width = width - padding;
// }
// }
// if (sHeight > height) {
// ty = (sHeight - height) / 2 + padding;
height = sHeight + padding * 2;
// }
log.info(`Calculated bounds: ${width}x${height}`);
configureSvgSize(svgElem, height, width, useMaxWidth);
// Ensure the viewBox includes the whole svgBounds area with extra space for padding
// const vBox = `0 0 ${width} ${height}`;
const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${
svgBounds.width + 2 * padding
} ${svgBounds.height + 2 * padding}`;

View File

@ -17,7 +17,6 @@ import theme from './themes/index.js';
import c4 from './diagrams/c4/styles.js';
import classDiagram from './diagrams/class/styles.js';
import flowchart from './diagrams/flowchart/styles.js';
import flowchartElk from './diagrams/flowchart/elk/styles.js';
import er from './diagrams/er/styles.js';
import git from './diagrams/git/styles.js';
import gantt from './diagrams/gantt/styles.js';
@ -86,7 +85,6 @@ describe('styles', () => {
classDiagram,
er,
flowchart,
flowchartElk,
gantt,
git,
journey,

View File

@ -19,9 +19,12 @@ const markerOffsets = {
* @returns The angle, deltaX and deltaY
*/
function calculateDeltaAndAngle(
point1: Point | [number, number],
point2: Point | [number, number]
point1?: Point | [number, number],
point2?: Point | [number, number]
): { angle: number; deltaX: number; deltaY: number } {
if (point1 === undefined || point2 === undefined) {
return { angle: 0, deltaX: 0, deltaY: 0 };
}
point1 = pointTransformer(point1);
point2 = pointTransformer(point2);
const [x1, y1] = [point1.x, point1.y];
@ -90,3 +93,44 @@ export const getLineFunctionsWithOffset = (
},
};
};
if (import.meta.vitest) {
const { it, expect, describe } = import.meta.vitest;
describe('calculateDeltaAndAngle', () => {
it('should calculate the angle and deltas between two points', () => {
expect(calculateDeltaAndAngle([0, 0], [0, 1])).toStrictEqual({
angle: 1.5707963267948966,
deltaX: 0,
deltaY: 1,
});
expect(calculateDeltaAndAngle([1, 0], [0, -1])).toStrictEqual({
angle: 0.7853981633974483,
deltaX: -1,
deltaY: -1,
});
expect(calculateDeltaAndAngle({ x: 1, y: 0 }, [0, -1])).toStrictEqual({
angle: 0.7853981633974483,
deltaX: -1,
deltaY: -1,
});
expect(calculateDeltaAndAngle({ x: 1, y: 0 }, { x: 1, y: 0 })).toStrictEqual({
angle: NaN,
deltaX: 0,
deltaY: 0,
});
});
it('should calculate the angle and deltas if one point in undefined', () => {
expect(calculateDeltaAndAngle(undefined, [0, 1])).toStrictEqual({
angle: 0,
deltaX: 0,
deltaY: 0,
});
expect(calculateDeltaAndAngle([0, 1], undefined)).toStrictEqual({
angle: 0,
deltaX: 0,
deltaY: 0,
});
});
});
}

View File

@ -0,0 +1,22 @@
import { getSubGraphTitleMargins } from './subGraphTitleMargins.js';
import * as configApi from '../config.js';
describe('getSubGraphTitleMargins', () => {
it('should get subgraph title margins after config has been set', () => {
const config_0 = {
flowchart: {
subGraphTitleMargin: {
top: 10,
bottom: 5,
},
},
};
configApi.setSiteConfig(config_0);
expect(getSubGraphTitleMargins(config_0)).toEqual({
subGraphTitleTopMargin: 10,
subGraphTitleBottomMargin: 5,
subGraphTitleTotalMargin: 15,
});
});
});

View File

@ -0,0 +1,21 @@
import type { FlowchartDiagramConfig } from '../config.type.js';
export const getSubGraphTitleMargins = ({
flowchart,
}: {
flowchart: FlowchartDiagramConfig;
}): {
subGraphTitleTopMargin: number;
subGraphTitleBottomMargin: number;
subGraphTitleTotalMargin: number;
} => {
const subGraphTitleTopMargin = flowchart?.subGraphTitleMargin?.top ?? 0;
const subGraphTitleBottomMargin = flowchart?.subGraphTitleMargin?.bottom ?? 0;
const subGraphTitleTotalMargin = subGraphTitleTopMargin + subGraphTitleBottomMargin;
return {
subGraphTitleTopMargin,
subGraphTitleBottomMargin,
subGraphTitleTotalMargin,
};
};

View File

@ -2,7 +2,8 @@
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
"outDir": "./dist",
"types": ["vitest/importMeta", "vitest/globals"]
},
"include": ["./src/**/*.ts", "./package.json"]
}

View File

@ -10,7 +10,7 @@ Mermaid Parser
Mermaid parser package
<p>
[![NPM](https://img.shields.io/npm/v/mermaid-parser)](https://www.npmjs.com/package/mermaid-parser)
[![NPM](https://img.shields.io/npm/v/@mermaid-js/parser)](https://www.npmjs.com/package/@mermaid-js/parser)
## How the package works

View File

@ -1,6 +1,6 @@
{
"name": "mermaid-parser",
"version": "0.2.0",
"name": "@mermaid-js/parser",
"version": "0.1.0-rc.1",
"description": "MermaidJS parser",
"author": "Yokozuna59",
"contributors": [

File diff suppressed because it is too large Load Diff

View File

@ -24,6 +24,7 @@ export default defineConfig({
reportsDirectory: './coverage/vitest',
exclude: [...defaultExclude, './tests/**', '**/__mocks__/**', '**/generated/'],
},
includeSource: ['packages/*/src/**/*.{js,ts}'],
},
build: {
/** If you set esmExternals to true, this plugins assumes that
@ -33,4 +34,7 @@ export default defineConfig({
esmExternals: true,
},
},
define: {
'import.meta.vitest': 'undefined',
},
});