mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-22 09:46:42 +02:00
Merge remote-tracking branch 'origin/develop' into knsv-treemap
This commit is contained in:
6
.changeset/quiet-hotels-shine.md
Normal file
6
.changeset/quiet-hotels-shine.md
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
---
|
||||||
|
'mermaid': minor
|
||||||
|
'@mermaid-js/parser': minor
|
||||||
|
---
|
||||||
|
|
||||||
|
feat: Add shorter `+<count>: Label` syntax in packet diagram
|
@@ -1,5 +1,5 @@
|
|||||||
import { build } from 'esbuild';
|
import { build } from 'esbuild';
|
||||||
import { mkdir, writeFile } from 'node:fs/promises';
|
import { mkdir, readFile, rename, writeFile } from 'node:fs/promises';
|
||||||
import { packageOptions } from '../.build/common.js';
|
import { packageOptions } from '../.build/common.js';
|
||||||
import { generateLangium } from '../.build/generateLangium.js';
|
import { generateLangium } from '../.build/generateLangium.js';
|
||||||
import type { MermaidBuildOptions } from './util.js';
|
import type { MermaidBuildOptions } from './util.js';
|
||||||
@@ -31,7 +31,15 @@ const buildPackage = async (entryName: keyof typeof packageOptions) => {
|
|||||||
// mermaid.js
|
// mermaid.js
|
||||||
{ ...iifeOptions },
|
{ ...iifeOptions },
|
||||||
// mermaid.min.js
|
// mermaid.min.js
|
||||||
{ ...iifeOptions, minify: true, metafile: shouldVisualize }
|
{ ...iifeOptions, minify: true, metafile: shouldVisualize },
|
||||||
|
// mermaid.tiny.min.js
|
||||||
|
{
|
||||||
|
...iifeOptions,
|
||||||
|
minify: true,
|
||||||
|
includeLargeFeatures: false,
|
||||||
|
metafile: shouldVisualize,
|
||||||
|
sourcemap: false,
|
||||||
|
}
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
if (entryName === 'mermaid-zenuml') {
|
if (entryName === 'mermaid-zenuml') {
|
||||||
@@ -70,6 +78,20 @@ const handler = (e) => {
|
|||||||
process.exit(1);
|
process.exit(1);
|
||||||
};
|
};
|
||||||
|
|
||||||
|
const buildTinyMermaid = async () => {
|
||||||
|
await mkdir('./packages/tiny/dist', { recursive: true });
|
||||||
|
await rename(
|
||||||
|
'./packages/mermaid/dist/mermaid.tiny.min.js',
|
||||||
|
'./packages/tiny/dist/mermaid.tiny.js'
|
||||||
|
);
|
||||||
|
// Copy version from mermaid's package.json to tiny's package.json
|
||||||
|
const mermaidPkg = JSON.parse(await readFile('./packages/mermaid/package.json', 'utf8'));
|
||||||
|
const tinyPkg = JSON.parse(await readFile('./packages/tiny/package.json', 'utf8'));
|
||||||
|
tinyPkg.version = mermaidPkg.version;
|
||||||
|
|
||||||
|
await writeFile('./packages/tiny/package.json', JSON.stringify(tinyPkg, null, 2) + '\n');
|
||||||
|
};
|
||||||
|
|
||||||
const main = async () => {
|
const main = async () => {
|
||||||
await generateLangium();
|
await generateLangium();
|
||||||
await mkdir('stats', { recursive: true });
|
await mkdir('stats', { recursive: true });
|
||||||
@@ -78,6 +100,7 @@ const main = async () => {
|
|||||||
for (const pkg of packageNames) {
|
for (const pkg of packageNames) {
|
||||||
await buildPackage(pkg).catch(handler);
|
await buildPackage(pkg).catch(handler);
|
||||||
}
|
}
|
||||||
|
await buildTinyMermaid();
|
||||||
};
|
};
|
||||||
|
|
||||||
void main();
|
void main();
|
||||||
|
@@ -14,6 +14,7 @@ export interface MermaidBuildOptions extends BuildOptions {
|
|||||||
metafile: boolean;
|
metafile: boolean;
|
||||||
format: 'esm' | 'iife';
|
format: 'esm' | 'iife';
|
||||||
options: PackageOptions;
|
options: PackageOptions;
|
||||||
|
includeLargeFeatures: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
export const defaultOptions: Omit<MermaidBuildOptions, 'entryName' | 'options'> = {
|
export const defaultOptions: Omit<MermaidBuildOptions, 'entryName' | 'options'> = {
|
||||||
@@ -21,6 +22,7 @@ export const defaultOptions: Omit<MermaidBuildOptions, 'entryName' | 'options'>
|
|||||||
metafile: false,
|
metafile: false,
|
||||||
core: false,
|
core: false,
|
||||||
format: 'esm',
|
format: 'esm',
|
||||||
|
includeLargeFeatures: true,
|
||||||
} as const;
|
} as const;
|
||||||
|
|
||||||
const buildOptions = (override: BuildOptions): BuildOptions => {
|
const buildOptions = (override: BuildOptions): BuildOptions => {
|
||||||
@@ -39,12 +41,18 @@ const buildOptions = (override: BuildOptions): BuildOptions => {
|
|||||||
};
|
};
|
||||||
};
|
};
|
||||||
|
|
||||||
const getFileName = (fileName: string, { core, format, minify }: MermaidBuildOptions) => {
|
const getFileName = (
|
||||||
|
fileName: string,
|
||||||
|
{ core, format, minify, includeLargeFeatures }: MermaidBuildOptions
|
||||||
|
) => {
|
||||||
if (core) {
|
if (core) {
|
||||||
fileName += '.core';
|
fileName += '.core';
|
||||||
} else if (format === 'esm') {
|
} else if (format === 'esm') {
|
||||||
fileName += '.esm';
|
fileName += '.esm';
|
||||||
}
|
}
|
||||||
|
if (!includeLargeFeatures) {
|
||||||
|
fileName += '.tiny';
|
||||||
|
}
|
||||||
if (minify) {
|
if (minify) {
|
||||||
fileName += '.min';
|
fileName += '.min';
|
||||||
}
|
}
|
||||||
@@ -54,25 +62,27 @@ const getFileName = (fileName: string, { core, format, minify }: MermaidBuildOpt
|
|||||||
export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
|
export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
|
||||||
const {
|
const {
|
||||||
core,
|
core,
|
||||||
metafile,
|
|
||||||
format,
|
format,
|
||||||
minify,
|
|
||||||
options: { name, file, packageName },
|
options: { name, file, packageName },
|
||||||
globalName = 'mermaid',
|
globalName = 'mermaid',
|
||||||
|
includeLargeFeatures,
|
||||||
|
...rest
|
||||||
} = options;
|
} = options;
|
||||||
|
|
||||||
const external: string[] = ['require', 'fs', 'path'];
|
const external: string[] = ['require', 'fs', 'path'];
|
||||||
const outFileName = getFileName(name, options);
|
const outFileName = getFileName(name, options);
|
||||||
const output: BuildOptions = buildOptions({
|
const output: BuildOptions = buildOptions({
|
||||||
|
...rest,
|
||||||
absWorkingDir: resolve(__dirname, `../packages/${packageName}`),
|
absWorkingDir: resolve(__dirname, `../packages/${packageName}`),
|
||||||
entryPoints: {
|
entryPoints: {
|
||||||
[outFileName]: `src/${file}`,
|
[outFileName]: `src/${file}`,
|
||||||
},
|
},
|
||||||
metafile,
|
|
||||||
minify,
|
|
||||||
globalName,
|
globalName,
|
||||||
logLevel: 'info',
|
logLevel: 'info',
|
||||||
chunkNames: `chunks/${outFileName}/[name]-[hash]`,
|
chunkNames: `chunks/${outFileName}/[name]-[hash]`,
|
||||||
define: {
|
define: {
|
||||||
|
// This needs to be stringified for esbuild
|
||||||
|
includeLargeFeatures: `${includeLargeFeatures}`,
|
||||||
'import.meta.vitest': 'undefined',
|
'import.meta.vitest': 'undefined',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
2
.github/workflows/e2e-timings.yml
vendored
2
.github/workflows/e2e-timings.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
|||||||
echo "EOF" >> $GITHUB_OUTPUT
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Commit and create pull request
|
- name: Commit and create pull request
|
||||||
uses: peter-evans/create-pull-request@3b1f4bffdc97d7b055dd96732d7348e585ad2c4e
|
uses: peter-evans/create-pull-request@889dce9eaba7900ce30494f5e1ac7220b27e5c81
|
||||||
with:
|
with:
|
||||||
add-paths: |
|
add-paths: |
|
||||||
cypress/timings.json
|
cypress/timings.json
|
||||||
|
@@ -94,6 +94,10 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
|||||||
}),
|
}),
|
||||||
...visualizerOptions(packageName, core),
|
...visualizerOptions(packageName, core),
|
||||||
],
|
],
|
||||||
|
define: {
|
||||||
|
// Needs to be string
|
||||||
|
includeLargeFeatures: 'true',
|
||||||
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
if (watch && config.build) {
|
if (watch && config.build) {
|
||||||
|
@@ -934,4 +934,43 @@ graph TD
|
|||||||
}
|
}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
it('68: should honor subgraph direction when inheritDir is false', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
%%{init: {"flowchart": { "inheritDir": false }}}%%
|
||||||
|
flowchart TB
|
||||||
|
direction LR
|
||||||
|
subgraph A
|
||||||
|
direction TB
|
||||||
|
a --> b
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
c --> d
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
fontFamily: 'courier',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('69: should inherit global direction when inheritDir is true', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
%%{init: {"flowchart": { "inheritDir": true }}}%%
|
||||||
|
flowchart TB
|
||||||
|
direction LR
|
||||||
|
subgraph A
|
||||||
|
direction TB
|
||||||
|
a --> b
|
||||||
|
end
|
||||||
|
subgraph B
|
||||||
|
c --> d
|
||||||
|
end
|
||||||
|
`,
|
||||||
|
{
|
||||||
|
fontFamily: 'courier',
|
||||||
|
}
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
222
demos/er-multiline.html
Normal file
222
demos/er-multiline.html
Normal file
@@ -0,0 +1,222 @@
|
|||||||
|
<html>
|
||||||
|
<head>
|
||||||
|
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
|
||||||
|
<link href="https://unpkg.com/tailwindcss@^1.0/dist/tailwind.min.css" rel="stylesheet" />
|
||||||
|
<link
|
||||||
|
rel="stylesheet"
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
||||||
|
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin />
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Caveat:wght@400..700&family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Kalam:wght@300;400;700&family=Rubik+Mono+One&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
<link
|
||||||
|
href="https://fonts.googleapis.com/css2?family=Recursive:wght@300..1000&display=swap"
|
||||||
|
rel="stylesheet"
|
||||||
|
/>
|
||||||
|
|
||||||
|
<style>
|
||||||
|
.recursive-500 {
|
||||||
|
font-family: 'Recursive', serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-variation-settings:
|
||||||
|
'slnt' 0,
|
||||||
|
'CASL' 0,
|
||||||
|
'CRSV' 0.5,
|
||||||
|
'MONO' 0;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
/* background: rgb(221, 208, 208); */
|
||||||
|
/* background: #333; */
|
||||||
|
/* font-family: 'Arial'; */
|
||||||
|
font-family: 'Recursive', serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-variation-settings:
|
||||||
|
'slnt' 0,
|
||||||
|
'CASL' 0,
|
||||||
|
'CRSV' 0.5,
|
||||||
|
'MONO' 0;
|
||||||
|
/* color: white; */
|
||||||
|
/* font-size: 18px !important; */
|
||||||
|
}
|
||||||
|
.gridify.tiny {
|
||||||
|
background-image:
|
||||||
|
linear-gradient(transparent 11px, rgba(220, 220, 200, 0.8) 12px, transparent 12px),
|
||||||
|
linear-gradient(90deg, transparent 11px, rgba(220, 220, 200, 0.8) 12px, transparent 12px);
|
||||||
|
background-size:
|
||||||
|
100% 12px,
|
||||||
|
12px 100%;
|
||||||
|
}
|
||||||
|
|
||||||
|
.gridify.dots {
|
||||||
|
background-image: radial-gradient(
|
||||||
|
circle at center,
|
||||||
|
rgba(220, 220, 200, 0.8) 1px,
|
||||||
|
transparent 1px
|
||||||
|
);
|
||||||
|
background-size: 24px 24px;
|
||||||
|
}
|
||||||
|
|
||||||
|
h1 {
|
||||||
|
color: grey;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid2 {
|
||||||
|
display: none;
|
||||||
|
}
|
||||||
|
|
||||||
|
.mermaid svg {
|
||||||
|
font-size: 16px !important;
|
||||||
|
font-family: 'Recursive', serif;
|
||||||
|
font-optical-sizing: auto;
|
||||||
|
font-weight: 500;
|
||||||
|
font-style: normal;
|
||||||
|
font-variation-settings:
|
||||||
|
'slnt' 0,
|
||||||
|
'CASL' 0,
|
||||||
|
'CRSV' 0.5,
|
||||||
|
'MONO' 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
pre {
|
||||||
|
width: 100%;
|
||||||
|
/*box-shadow: 4px 4px 0px 0px #0000000F;*/
|
||||||
|
}
|
||||||
|
</style>
|
||||||
|
</head>
|
||||||
|
|
||||||
|
<body class="gridify dots">
|
||||||
|
<div class="w-full h-64">
|
||||||
|
<pre id="diagram4" class="mermaid" style="background: rgb(255, 255, 255)">
|
||||||
|
erDiagram
|
||||||
|
CAR ||--o{ NAMED-DRIVER : allows
|
||||||
|
CAR ::: Pine {
|
||||||
|
string registrationNumber PK "Primary Key<br><strong>Unique registration number</strong>"
|
||||||
|
string make "Car make<br><strong>e.g., Toyota</strong>"
|
||||||
|
string model "Model of the car<br><strong>e.g., Corolla</strong>"
|
||||||
|
string[] parts "List of parts<br><strong>Stored as array</strong>"
|
||||||
|
}
|
||||||
|
PERSON ||--o{ NAMED-DRIVER : is
|
||||||
|
PERSON ::: someclass {
|
||||||
|
string driversLicense PK "The license #<br><strong>Primary Key</strong>"
|
||||||
|
string(99) firstName "Only 99 characters <br>are allowed <br> <strong>e.g., Smith</strong>"
|
||||||
|
string lastName "Last name of person<br><strong>e.g., Smith</strong>"
|
||||||
|
string phone UK "Unique phone number<br><strong>Used for contact</strong>"
|
||||||
|
int age "Age of the person<br><strong>Must be numeric</strong>"
|
||||||
|
}
|
||||||
|
NAMED-DRIVER {
|
||||||
|
string carRegistrationNumber PK, FK, UK, PK "Foreign key to CAR<br><strong>Also part of PK</strong>"
|
||||||
|
string driverLicence PK, FK "Foreign key to PERSON<br><strong>Also part of PK</strong>"
|
||||||
|
}
|
||||||
|
MANUFACTURER only one to zero or more CAR : makesx
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
<pre class="mermaid">
|
||||||
|
erDiagram
|
||||||
|
_**testẽζ➕Ø😀㌕ぼ**_ {
|
||||||
|
*__List~List~int~~sdfds__* **driversLicense** PK "***The l😀icense #***"
|
||||||
|
string last*Name*
|
||||||
|
string __phone__ UK
|
||||||
|
*string(99)~T~~~~~~* firstName "Only __99__ <br>characters are a<br>llowed dsfsdfsdfsdfs"
|
||||||
|
int _age_
|
||||||
|
}
|
||||||
|
</pre>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
<script type="module">
|
||||||
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
import layouts from './mermaid-layout-elk.esm.mjs';
|
||||||
|
|
||||||
|
const staticBellIconPack = {
|
||||||
|
prefix: 'fa6-regular',
|
||||||
|
icons: {
|
||||||
|
bell: {
|
||||||
|
body: '<path fill="currentColor" d="M224 0c-17.7 0-32 14.3-32 32v19.2C119 66 64 130.6 64 208v25.4c0 45.4-15.5 89.5-43.8 124.9L5.3 377c-5.8 7.2-6.9 17.1-2.9 25.4S14.8 416 24 416h400c9.2 0 17.6-5.3 21.6-13.6s2.9-18.2-2.9-25.4l-14.9-18.6c-28.3-35.5-43.8-79.6-43.8-125V208c0-77.4-55-142-128-156.8V32c0-17.7-14.3-32-32-32m0 96c61.9 0 112 50.1 112 112v25.4c0 47.9 13.9 94.6 39.7 134.6H72.3c25.8-40 39.7-86.7 39.7-134.6V208c0-61.9 50.1-112 112-112m64 352H160c0 17 6.7 33.3 18.7 45.3S207 512 224 512s33.3-6.7 45.3-18.7S288 465 288 448"/>',
|
||||||
|
width: 448,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
width: 512,
|
||||||
|
height: 512,
|
||||||
|
};
|
||||||
|
|
||||||
|
mermaid.registerIconPacks([
|
||||||
|
{
|
||||||
|
name: 'logos',
|
||||||
|
loader: () =>
|
||||||
|
fetch('https://unpkg.com/@iconify-json/logos@1/icons.json').then((res) => res.json()),
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: 'fa',
|
||||||
|
loader: () => staticBellIconPack,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
mermaid.registerLayoutLoaders(layouts);
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
console.error('Mermaid error: ', err);
|
||||||
|
};
|
||||||
|
window.callback = function () {
|
||||||
|
alert('A callback was triggered');
|
||||||
|
};
|
||||||
|
function callback() {
|
||||||
|
alert('It worked');
|
||||||
|
}
|
||||||
|
await mermaid.initialize({
|
||||||
|
startOnLoad: false,
|
||||||
|
|
||||||
|
theme: 'forest',
|
||||||
|
look: 'classic',
|
||||||
|
layout: 'dagre',
|
||||||
|
|
||||||
|
// theme: 'default',
|
||||||
|
// look: 'classic',
|
||||||
|
flowchart: { titleTopMargin: 10 },
|
||||||
|
fontFamily: 'Recursive',
|
||||||
|
sequence: {
|
||||||
|
actorFontFamily: 'courier',
|
||||||
|
noteFontFamily: 'courier',
|
||||||
|
messageFontFamily: 'courier',
|
||||||
|
},
|
||||||
|
kanban: {
|
||||||
|
htmlLabels: false,
|
||||||
|
},
|
||||||
|
fontSize: 16,
|
||||||
|
logLevel: 0,
|
||||||
|
securityLevel: 'loose',
|
||||||
|
callback,
|
||||||
|
});
|
||||||
|
// setTimeout(() => {
|
||||||
|
mermaid.init(undefined, document.querySelectorAll('.mermaid'));
|
||||||
|
// }, 1000);
|
||||||
|
mermaid.parseError = function (err, hash) {
|
||||||
|
console.error('In parse error:');
|
||||||
|
console.error(err);
|
||||||
|
};
|
||||||
|
</script>
|
||||||
|
</body>
|
||||||
|
</html>
|
@@ -12,4 +12,4 @@
|
|||||||
|
|
||||||
> `const` **configKeys**: `Set`<`string`>
|
> `const` **configKeys**: `Set`<`string`>
|
||||||
|
|
||||||
Defined in: [packages/mermaid/src/defaultConfig.ts:285](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L285)
|
Defined in: [packages/mermaid/src/defaultConfig.ts:289](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L289)
|
||||||
|
@@ -98,6 +98,12 @@ Mermaid can load multiple diagrams, in the same page.
|
|||||||
> Try it out, save this code as HTML and load it using any browser.
|
> Try it out, save this code as HTML and load it using any browser.
|
||||||
> (Except Internet Explorer, please don't use Internet Explorer.)
|
> (Except Internet Explorer, please don't use Internet Explorer.)
|
||||||
|
|
||||||
|
## Tiny Mermaid
|
||||||
|
|
||||||
|
We offer a smaller version of Mermaid that's approximately half the size of the full library. This tiny version doesn't support Mindmap Diagrams, Architecture Diagrams, KaTeX rendering, or lazy loading.
|
||||||
|
|
||||||
|
If you need a more lightweight version without these features, you can use [Mermaid Tiny](https://github.com/mermaid-js/mermaid/tree/develop/packages/tiny).
|
||||||
|
|
||||||
## Enabling Click Event and Tags in Nodes
|
## Enabling Click Event and Tags in Nodes
|
||||||
|
|
||||||
A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduced in version 8.2 as a security improvement, aimed at preventing malicious use.
|
A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduced in version 8.2 as a security improvement, aimed at preventing malicious use.
|
||||||
|
@@ -354,6 +354,7 @@ To Deploy Mermaid:
|
|||||||
|
|
||||||
- [Mermaid Live Editor](https://github.com/mermaid-js/mermaid-live-editor)
|
- [Mermaid Live Editor](https://github.com/mermaid-js/mermaid-live-editor)
|
||||||
- [Mermaid CLI](https://github.com/mermaid-js/mermaid-cli)
|
- [Mermaid CLI](https://github.com/mermaid-js/mermaid-cli)
|
||||||
|
- [Mermaid Tiny](https://github.com/mermaid-js/mermaid/tree/develop/packages/tiny)
|
||||||
- [Mermaid Webpack Demo](https://github.com/mermaidjs/mermaid-webpack-demo)
|
- [Mermaid Webpack Demo](https://github.com/mermaidjs/mermaid-webpack-demo)
|
||||||
- [Mermaid Parcel Demo](https://github.com/mermaidjs/mermaid-parcel-demo)
|
- [Mermaid Parcel Demo](https://github.com/mermaidjs/mermaid-parcel-demo)
|
||||||
|
|
||||||
|
@@ -545,6 +545,38 @@ It is possible to annotate classes with markers to provide additional metadata a
|
|||||||
|
|
||||||
Annotations are defined within the opening `<<` and closing `>>`. There are two ways to add an annotation to a class, and either way the output will be same:
|
Annotations are defined within the opening `<<` and closing `>>`. There are two ways to add an annotation to a class, and either way the output will be same:
|
||||||
|
|
||||||
|
> **Tip:**\
|
||||||
|
> In Mermaid class diagrams, annotations like `<<interface>>` can be attached in two ways:
|
||||||
|
>
|
||||||
|
> - **Inline with the class definition** (Recommended for consistency):
|
||||||
|
>
|
||||||
|
> ```mermaid-example
|
||||||
|
> classDiagram
|
||||||
|
> class Shape <<interface>>
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> ```mermaid
|
||||||
|
> classDiagram
|
||||||
|
> class Shape <<interface>>
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> - **Separate line after the class definition**:
|
||||||
|
>
|
||||||
|
> ```mermaid-example
|
||||||
|
> classDiagram
|
||||||
|
> class Shape
|
||||||
|
> <<interface>> Shape
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> ```mermaid
|
||||||
|
> classDiagram
|
||||||
|
> class Shape
|
||||||
|
> <<interface>> Shape
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Both methods are fully supported and produce identical diagrams.\
|
||||||
|
> However, it is recommended to use the **inline style** for better readability and consistent formatting across diagrams.
|
||||||
|
|
||||||
- In a **_separate line_** after a class is defined:
|
- In a **_separate line_** after a class is defined:
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
|
@@ -16,13 +16,25 @@ This diagram type is particularly useful for developers, network engineers, educ
|
|||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
```md
|
```
|
||||||
packet-beta
|
packet-beta
|
||||||
start: "Block name" %% Single-bit block
|
start: "Block name" %% Single-bit block
|
||||||
start-end: "Block name" %% Multi-bit blocks
|
start-end: "Block name" %% Multi-bit blocks
|
||||||
... More Fields ...
|
... More Fields ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Bits Syntax (v\<MERMAID_RELEASE_VERSION>+)
|
||||||
|
|
||||||
|
Using start and end bit counts can be difficult, especially when modifying a design. For this we add a bit count field, which starts from the end of the previous field automagically. Use `+<count>` to set the number of bits, thus:
|
||||||
|
|
||||||
|
```
|
||||||
|
packet-beta
|
||||||
|
+1: "Block name" %% Single-bit block
|
||||||
|
+8: "Block name" %% 8-bit block
|
||||||
|
9-15: "Manually set start and end, it's fine to mix and match"
|
||||||
|
... More Fields ...
|
||||||
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
@@ -76,8 +88,8 @@ packet-beta
|
|||||||
```mermaid-example
|
```mermaid-example
|
||||||
packet-beta
|
packet-beta
|
||||||
title UDP Packet
|
title UDP Packet
|
||||||
0-15: "Source Port"
|
+16: "Source Port"
|
||||||
16-31: "Destination Port"
|
+16: "Destination Port"
|
||||||
32-47: "Length"
|
32-47: "Length"
|
||||||
48-63: "Checksum"
|
48-63: "Checksum"
|
||||||
64-95: "Data (variable length)"
|
64-95: "Data (variable length)"
|
||||||
@@ -86,8 +98,8 @@ title UDP Packet
|
|||||||
```mermaid
|
```mermaid
|
||||||
packet-beta
|
packet-beta
|
||||||
title UDP Packet
|
title UDP Packet
|
||||||
0-15: "Source Port"
|
+16: "Source Port"
|
||||||
16-31: "Destination Port"
|
+16: "Destination Port"
|
||||||
32-47: "Length"
|
32-47: "Length"
|
||||||
48-63: "Checksum"
|
48-63: "Checksum"
|
||||||
64-95: "Data (variable length)"
|
64-95: "Data (variable length)"
|
||||||
|
@@ -69,7 +69,7 @@
|
|||||||
"@changesets/cli": "^2.27.12",
|
"@changesets/cli": "^2.27.12",
|
||||||
"@cspell/eslint-plugin": "^8.19.3",
|
"@cspell/eslint-plugin": "^8.19.3",
|
||||||
"@cypress/code-coverage": "^3.12.49",
|
"@cypress/code-coverage": "^3.12.49",
|
||||||
"@eslint/js": "^9.25.1",
|
"@eslint/js": "^9.26.0",
|
||||||
"@rollup/plugin-typescript": "^12.1.2",
|
"@rollup/plugin-typescript": "^12.1.2",
|
||||||
"@types/cors": "^2.8.17",
|
"@types/cors": "^2.8.17",
|
||||||
"@types/express": "^5.0.0",
|
"@types/express": "^5.0.0",
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
"cypress-image-snapshot": "^4.0.1",
|
"cypress-image-snapshot": "^4.0.1",
|
||||||
"cypress-split": "^1.24.14",
|
"cypress-split": "^1.24.14",
|
||||||
"esbuild": "^0.25.0",
|
"esbuild": "^0.25.0",
|
||||||
"eslint": "^9.25.1",
|
"eslint": "^9.26.0",
|
||||||
"eslint-config-prettier": "^10.1.1",
|
"eslint-config-prettier": "^10.1.1",
|
||||||
"eslint-plugin-cypress": "^4.3.0",
|
"eslint-plugin-cypress": "^4.3.0",
|
||||||
"eslint-plugin-html": "^8.1.2",
|
"eslint-plugin-html": "^8.1.2",
|
||||||
@@ -126,7 +126,7 @@
|
|||||||
"tslib": "^2.8.1",
|
"tslib": "^2.8.1",
|
||||||
"tsx": "^4.7.3",
|
"tsx": "^4.7.3",
|
||||||
"typescript": "~5.7.3",
|
"typescript": "~5.7.3",
|
||||||
"typescript-eslint": "^8.31.1",
|
"typescript-eslint": "^8.32.0",
|
||||||
"vite": "^6.1.1",
|
"vite": "^6.1.1",
|
||||||
"vite-plugin-istanbul": "^7.0.0",
|
"vite-plugin-istanbul": "^7.0.0",
|
||||||
"vitest": "^3.0.6"
|
"vitest": "^3.0.6"
|
||||||
|
@@ -295,6 +295,12 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
|
|||||||
*
|
*
|
||||||
*/
|
*/
|
||||||
wrappingWidth?: number;
|
wrappingWidth?: number;
|
||||||
|
/**
|
||||||
|
* If true, subgraphs without explicit direction will inherit the global graph direction
|
||||||
|
* (e.g., LR, TB, RL, BT). Defaults to false to preserve legacy layout behavior.
|
||||||
|
*
|
||||||
|
*/
|
||||||
|
inheritDir?: boolean;
|
||||||
}
|
}
|
||||||
/**
|
/**
|
||||||
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
* This interface was referenced by `MermaidConfig`'s JSON-Schema
|
||||||
|
@@ -71,6 +71,10 @@ const config: RequiredDeep<MermaidConfig> = {
|
|||||||
fontWeight: this.personFontWeight,
|
fontWeight: this.personFontWeight,
|
||||||
};
|
};
|
||||||
},
|
},
|
||||||
|
flowchart: {
|
||||||
|
...defaultConfigJson.flowchart,
|
||||||
|
inheritDir: false, // default to legacy behavior
|
||||||
|
},
|
||||||
|
|
||||||
external_personFont: function () {
|
external_personFont: function () {
|
||||||
return {
|
return {
|
||||||
|
@@ -28,6 +28,7 @@ import architecture from '../diagrams/architecture/architectureDetector.js';
|
|||||||
import { registerLazyLoadedDiagrams } from './detectType.js';
|
import { registerLazyLoadedDiagrams } from './detectType.js';
|
||||||
import { registerDiagram } from './diagramAPI.js';
|
import { registerDiagram } from './diagramAPI.js';
|
||||||
import { treemap } from '../diagrams/treemap/detector.js';
|
import { treemap } from '../diagrams/treemap/detector.js';
|
||||||
|
import '../type.d.ts';
|
||||||
|
|
||||||
let hasLoadedDiagrams = false;
|
let hasLoadedDiagrams = false;
|
||||||
export const addDiagrams = () => {
|
export const addDiagrams = () => {
|
||||||
@@ -70,6 +71,11 @@ export const addDiagrams = () => {
|
|||||||
return text.toLowerCase().trimStart().startsWith('---');
|
return text.toLowerCase().trimStart().startsWith('---');
|
||||||
}
|
}
|
||||||
);
|
);
|
||||||
|
|
||||||
|
if (includeLargeFeatures) {
|
||||||
|
registerLazyLoadedDiagrams(flowchartElk, mindmap, architecture);
|
||||||
|
}
|
||||||
|
|
||||||
// Ordering of detectors is important. The first one to return true will be used.
|
// Ordering of detectors is important. The first one to return true will be used.
|
||||||
registerLazyLoadedDiagrams(
|
registerLazyLoadedDiagrams(
|
||||||
c4,
|
c4,
|
||||||
@@ -82,10 +88,8 @@ export const addDiagrams = () => {
|
|||||||
pie,
|
pie,
|
||||||
requirement,
|
requirement,
|
||||||
sequence,
|
sequence,
|
||||||
flowchartElk,
|
|
||||||
flowchartV2,
|
flowchartV2,
|
||||||
flowchart,
|
flowchart,
|
||||||
mindmap,
|
|
||||||
timeline,
|
timeline,
|
||||||
git,
|
git,
|
||||||
stateV2,
|
stateV2,
|
||||||
@@ -96,7 +100,6 @@ export const addDiagrams = () => {
|
|||||||
packet,
|
packet,
|
||||||
xychart,
|
xychart,
|
||||||
block,
|
block,
|
||||||
architecture,
|
|
||||||
radar,
|
radar,
|
||||||
treemap
|
treemap
|
||||||
);
|
);
|
||||||
|
@@ -341,29 +341,36 @@ export const renderKatex = async (text: string, config: MermaidConfig): Promise<
|
|||||||
return text.replace(katexRegex, 'MathML is unsupported in this environment.');
|
return text.replace(katexRegex, 'MathML is unsupported in this environment.');
|
||||||
}
|
}
|
||||||
|
|
||||||
const { default: katex } = await import('katex');
|
if (includeLargeFeatures) {
|
||||||
const outputMode =
|
const { default: katex } = await import('katex');
|
||||||
config.forceLegacyMathML || (!isMathMLSupported() && config.legacyMathML)
|
const outputMode =
|
||||||
? 'htmlAndMathml'
|
config.forceLegacyMathML || (!isMathMLSupported() && config.legacyMathML)
|
||||||
: 'mathml';
|
? 'htmlAndMathml'
|
||||||
return text
|
: 'mathml';
|
||||||
.split(lineBreakRegex)
|
return text
|
||||||
.map((line) =>
|
.split(lineBreakRegex)
|
||||||
hasKatex(line)
|
.map((line) =>
|
||||||
? `<div style="display: flex; align-items: center; justify-content: center; white-space: nowrap;">${line}</div>`
|
hasKatex(line)
|
||||||
: `<div>${line}</div>`
|
? `<div style="display: flex; align-items: center; justify-content: center; white-space: nowrap;">${line}</div>`
|
||||||
)
|
: `<div>${line}</div>`
|
||||||
.join('')
|
)
|
||||||
.replace(katexRegex, (_, c) =>
|
.join('')
|
||||||
katex
|
.replace(katexRegex, (_, c) =>
|
||||||
.renderToString(c, {
|
katex
|
||||||
throwOnError: true,
|
.renderToString(c, {
|
||||||
displayMode: true,
|
throwOnError: true,
|
||||||
output: outputMode,
|
displayMode: true,
|
||||||
})
|
output: outputMode,
|
||||||
.replace(/\n/g, ' ')
|
})
|
||||||
.replace(/<annotation.*<\/annotation>/g, '')
|
.replace(/\n/g, ' ')
|
||||||
);
|
.replace(/<annotation.*<\/annotation>/g, '')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return text.replace(
|
||||||
|
katexRegex,
|
||||||
|
'Katex is not supported in @mermaid-js/tiny. Please use the full mermaid library.'
|
||||||
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
|
@@ -651,7 +651,8 @@ You have to call mermaid.initialize.`
|
|||||||
const prims: any = { boolean: {}, number: {}, string: {} };
|
const prims: any = { boolean: {}, number: {}, string: {} };
|
||||||
const objs: any[] = [];
|
const objs: any[] = [];
|
||||||
|
|
||||||
let dir; // = undefined; direction.trim();
|
let dir: string | undefined;
|
||||||
|
|
||||||
const nodeList = a.filter(function (item) {
|
const nodeList = a.filter(function (item) {
|
||||||
const type = typeof item;
|
const type = typeof item;
|
||||||
if (item.stmt && item.stmt === 'dir') {
|
if (item.stmt && item.stmt === 'dir') {
|
||||||
@@ -670,7 +671,16 @@ You have to call mermaid.initialize.`
|
|||||||
return { nodeList, dir };
|
return { nodeList, dir };
|
||||||
};
|
};
|
||||||
|
|
||||||
const { nodeList, dir } = uniq(list.flat());
|
const result = uniq(list.flat());
|
||||||
|
const nodeList = result.nodeList;
|
||||||
|
let dir = result.dir;
|
||||||
|
const flowchartConfig = getConfig().flowchart ?? {};
|
||||||
|
dir =
|
||||||
|
dir ??
|
||||||
|
(flowchartConfig.inheritDir
|
||||||
|
? (this.getDirection() ?? (getConfig() as any).direction ?? undefined)
|
||||||
|
: undefined);
|
||||||
|
|
||||||
if (this.version === 'gen-1') {
|
if (this.version === 'gen-1') {
|
||||||
for (let i = 0; i < nodeList.length; i++) {
|
for (let i = 0; i < nodeList.length; i++) {
|
||||||
nodeList[i] = this.lookUpDomId(nodeList[i]);
|
nodeList[i] = this.lookUpDomId(nodeList[i]);
|
||||||
@@ -681,6 +691,7 @@ You have to call mermaid.initialize.`
|
|||||||
title = title || '';
|
title = title || '';
|
||||||
title = this.sanitizeText(title);
|
title = this.sanitizeText(title);
|
||||||
this.subCount = this.subCount + 1;
|
this.subCount = this.subCount + 1;
|
||||||
|
|
||||||
const subGraph = {
|
const subGraph = {
|
||||||
id: id,
|
id: id,
|
||||||
nodes: nodeList,
|
nodes: nodeList,
|
||||||
|
@@ -1,7 +1,9 @@
|
|||||||
import type { InfoFields, InfoDB } from './infoTypes.js';
|
import type { InfoFields, InfoDB } from './infoTypes.js';
|
||||||
import packageJson from '../../../package.json' assert { type: 'json' };
|
import packageJson from '../../../package.json' assert { type: 'json' };
|
||||||
|
|
||||||
export const DEFAULT_INFO_DB: InfoFields = { version: packageJson.version } as const;
|
export const DEFAULT_INFO_DB: InfoFields = {
|
||||||
|
version: packageJson.version + (includeLargeFeatures ? '' : '-tiny'),
|
||||||
|
} as const;
|
||||||
|
|
||||||
export const getVersion = (): string => DEFAULT_INFO_DB.version;
|
export const getVersion = (): string => DEFAULT_INFO_DB.version;
|
||||||
|
|
||||||
|
@@ -30,6 +30,7 @@ describe('packet diagrams', () => {
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 11,
|
||||||
"end": 10,
|
"end": 10,
|
||||||
"label": "test",
|
"label": "test",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
@@ -49,11 +50,13 @@ describe('packet diagrams', () => {
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 11,
|
||||||
"end": 10,
|
"end": 10,
|
||||||
"label": "test",
|
"label": "test",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"bits": 1,
|
||||||
"end": 11,
|
"end": 11,
|
||||||
"label": "single",
|
"label": "single",
|
||||||
"start": 11,
|
"start": 11,
|
||||||
@@ -63,6 +66,58 @@ describe('packet diagrams', () => {
|
|||||||
`);
|
`);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should handle bit counts', async () => {
|
||||||
|
const str = `packet-beta
|
||||||
|
+8: "byte"
|
||||||
|
+16: "word"
|
||||||
|
`;
|
||||||
|
await expect(parser.parse(str)).resolves.not.toThrow();
|
||||||
|
expect(getPacket()).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bits": 8,
|
||||||
|
"end": 7,
|
||||||
|
"label": "byte",
|
||||||
|
"start": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bits": 16,
|
||||||
|
"end": 23,
|
||||||
|
"label": "word",
|
||||||
|
"start": 8,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should handle bit counts with bit or bits', async () => {
|
||||||
|
const str = `packet-beta
|
||||||
|
+8: "byte"
|
||||||
|
+16: "word"
|
||||||
|
`;
|
||||||
|
await expect(parser.parse(str)).resolves.not.toThrow();
|
||||||
|
expect(getPacket()).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"bits": 8,
|
||||||
|
"end": 7,
|
||||||
|
"label": "byte",
|
||||||
|
"start": 0,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"bits": 16,
|
||||||
|
"end": 23,
|
||||||
|
"label": "word",
|
||||||
|
"start": 8,
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
it('should split into multiple rows', async () => {
|
it('should split into multiple rows', async () => {
|
||||||
const str = `packet-beta
|
const str = `packet-beta
|
||||||
0-10: "test"
|
0-10: "test"
|
||||||
@@ -73,11 +128,13 @@ describe('packet diagrams', () => {
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 11,
|
||||||
"end": 10,
|
"end": 10,
|
||||||
"label": "test",
|
"label": "test",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"bits": 20,
|
||||||
"end": 31,
|
"end": 31,
|
||||||
"label": "multiple",
|
"label": "multiple",
|
||||||
"start": 11,
|
"start": 11,
|
||||||
@@ -85,6 +142,7 @@ describe('packet diagrams', () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 31,
|
||||||
"end": 63,
|
"end": 63,
|
||||||
"label": "multiple",
|
"label": "multiple",
|
||||||
"start": 32,
|
"start": 32,
|
||||||
@@ -92,6 +150,7 @@ describe('packet diagrams', () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 26,
|
||||||
"end": 90,
|
"end": 90,
|
||||||
"label": "multiple",
|
"label": "multiple",
|
||||||
"start": 64,
|
"start": 64,
|
||||||
@@ -111,11 +170,13 @@ describe('packet diagrams', () => {
|
|||||||
[
|
[
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 17,
|
||||||
"end": 16,
|
"end": 16,
|
||||||
"label": "test",
|
"label": "test",
|
||||||
"start": 0,
|
"start": 0,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
"bits": 14,
|
||||||
"end": 31,
|
"end": 31,
|
||||||
"label": "multiple",
|
"label": "multiple",
|
||||||
"start": 17,
|
"start": 17,
|
||||||
@@ -123,6 +184,7 @@ describe('packet diagrams', () => {
|
|||||||
],
|
],
|
||||||
[
|
[
|
||||||
{
|
{
|
||||||
|
"bits": 31,
|
||||||
"end": 63,
|
"end": 63,
|
||||||
"label": "multiple",
|
"label": "multiple",
|
||||||
"start": 32,
|
"start": 32,
|
||||||
@@ -142,6 +204,16 @@ describe('packet diagrams', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw error if numbers are not continuous with bit counts', async () => {
|
||||||
|
const str = `packet-beta
|
||||||
|
+16: "test"
|
||||||
|
18-20: "error"
|
||||||
|
`;
|
||||||
|
await expect(parser.parse(str)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`[Error: Packet block 18 - 20 is not contiguous. It should start from 16.]`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw error if numbers are not continuous for single packets', async () => {
|
it('should throw error if numbers are not continuous for single packets', async () => {
|
||||||
const str = `packet-beta
|
const str = `packet-beta
|
||||||
0-16: "test"
|
0-16: "test"
|
||||||
@@ -152,6 +224,16 @@ describe('packet diagrams', () => {
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw error if numbers are not continuous for single packets with bit counts', async () => {
|
||||||
|
const str = `packet-beta
|
||||||
|
+16: "test"
|
||||||
|
18: "error"
|
||||||
|
`;
|
||||||
|
await expect(parser.parse(str)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`[Error: Packet block 18 - 18 is not contiguous. It should start from 16.]`
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
it('should throw error if numbers are not continuous for single packets - 2', async () => {
|
it('should throw error if numbers are not continuous for single packets - 2', async () => {
|
||||||
const str = `packet-beta
|
const str = `packet-beta
|
||||||
0-16: "test"
|
0-16: "test"
|
||||||
@@ -172,4 +254,13 @@ describe('packet diagrams', () => {
|
|||||||
`[Error: Packet block 25 - 20 is invalid. End must be greater than start.]`
|
`[Error: Packet block 25 - 20 is invalid. End must be greater than start.]`
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should throw error if bit count is 0', async () => {
|
||||||
|
const str = `packet-beta
|
||||||
|
+0: "test"
|
||||||
|
`;
|
||||||
|
await expect(parser.parse(str)).rejects.toThrowErrorMatchingInlineSnapshot(
|
||||||
|
`[Error: Packet block 0 is invalid. Cannot have a zero bit field.]`
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -10,26 +10,33 @@ const maxPacketSize = 10_000;
|
|||||||
|
|
||||||
const populate = (ast: Packet) => {
|
const populate = (ast: Packet) => {
|
||||||
populateCommonDb(ast, db);
|
populateCommonDb(ast, db);
|
||||||
let lastByte = -1;
|
let lastBit = -1;
|
||||||
let word: PacketWord = [];
|
let word: PacketWord = [];
|
||||||
let row = 1;
|
let row = 1;
|
||||||
const { bitsPerRow } = db.getConfig();
|
const { bitsPerRow } = db.getConfig();
|
||||||
for (let { start, end, label } of ast.blocks) {
|
|
||||||
if (end && end < start) {
|
for (let { start, end, bits, label } of ast.blocks) {
|
||||||
|
if (start !== undefined && end !== undefined && end < start) {
|
||||||
throw new Error(`Packet block ${start} - ${end} is invalid. End must be greater than start.`);
|
throw new Error(`Packet block ${start} - ${end} is invalid. End must be greater than start.`);
|
||||||
}
|
}
|
||||||
if (start !== lastByte + 1) {
|
start ??= lastBit + 1;
|
||||||
|
if (start !== lastBit + 1) {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
`Packet block ${start} - ${end ?? start} is not contiguous. It should start from ${
|
`Packet block ${start} - ${end ?? start} is not contiguous. It should start from ${
|
||||||
lastByte + 1
|
lastBit + 1
|
||||||
}.`
|
}.`
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
lastByte = end ?? start;
|
if (bits === 0) {
|
||||||
log.debug(`Packet block ${start} - ${lastByte} with label ${label}`);
|
throw new Error(`Packet block ${start} is invalid. Cannot have a zero bit field.`);
|
||||||
|
}
|
||||||
|
end ??= start + (bits ?? 1) - 1;
|
||||||
|
bits ??= end - start + 1;
|
||||||
|
lastBit = end;
|
||||||
|
log.debug(`Packet block ${start} - ${lastBit} with label ${label}`);
|
||||||
|
|
||||||
while (word.length <= bitsPerRow + 1 && db.getPacket().length < maxPacketSize) {
|
while (word.length <= bitsPerRow + 1 && db.getPacket().length < maxPacketSize) {
|
||||||
const [block, nextBlock] = getNextFittingBlock({ start, end, label }, row, bitsPerRow);
|
const [block, nextBlock] = getNextFittingBlock({ start, end, bits, label }, row, bitsPerRow);
|
||||||
word.push(block);
|
word.push(block);
|
||||||
if (block.end + 1 === row * bitsPerRow) {
|
if (block.end + 1 === row * bitsPerRow) {
|
||||||
db.pushWord(word);
|
db.pushWord(word);
|
||||||
@@ -39,7 +46,7 @@ const populate = (ast: Packet) => {
|
|||||||
if (!nextBlock) {
|
if (!nextBlock) {
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
({ start, end, label } = nextBlock);
|
({ start, end, bits, label } = nextBlock);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
db.pushWord(word);
|
db.pushWord(word);
|
||||||
@@ -50,8 +57,11 @@ const getNextFittingBlock = (
|
|||||||
row: number,
|
row: number,
|
||||||
bitsPerRow: number
|
bitsPerRow: number
|
||||||
): [Required<PacketBlock>, PacketBlock | undefined] => {
|
): [Required<PacketBlock>, PacketBlock | undefined] => {
|
||||||
|
if (block.start === undefined) {
|
||||||
|
throw new Error('start should have been set during first phase');
|
||||||
|
}
|
||||||
if (block.end === undefined) {
|
if (block.end === undefined) {
|
||||||
block.end = block.start;
|
throw new Error('end should have been set during first phase');
|
||||||
}
|
}
|
||||||
|
|
||||||
if (block.start > block.end) {
|
if (block.start > block.end) {
|
||||||
@@ -62,16 +72,20 @@ const getNextFittingBlock = (
|
|||||||
return [block as Required<PacketBlock>, undefined];
|
return [block as Required<PacketBlock>, undefined];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const rowEnd = row * bitsPerRow - 1;
|
||||||
|
const rowStart = row * bitsPerRow;
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
start: block.start,
|
start: block.start,
|
||||||
end: row * bitsPerRow - 1,
|
end: rowEnd,
|
||||||
label: block.label,
|
label: block.label,
|
||||||
|
bits: rowEnd - block.start,
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
start: row * bitsPerRow,
|
start: rowStart,
|
||||||
end: block.end,
|
end: block.end,
|
||||||
label: block.label,
|
label: block.label,
|
||||||
|
bits: block.end - rowStart,
|
||||||
},
|
},
|
||||||
];
|
];
|
||||||
};
|
};
|
||||||
|
@@ -92,6 +92,12 @@ Mermaid can load multiple diagrams, in the same page.
|
|||||||
> Try it out, save this code as HTML and load it using any browser.
|
> Try it out, save this code as HTML and load it using any browser.
|
||||||
> (Except Internet Explorer, please don't use Internet Explorer.)
|
> (Except Internet Explorer, please don't use Internet Explorer.)
|
||||||
|
|
||||||
|
## Tiny Mermaid
|
||||||
|
|
||||||
|
We offer a smaller version of Mermaid that's approximately half the size of the full library. This tiny version doesn't support Mindmap Diagrams, Architecture Diagrams, KaTeX rendering, or lazy loading.
|
||||||
|
|
||||||
|
If you need a more lightweight version without these features, you can use [Mermaid Tiny](https://github.com/mermaid-js/mermaid/tree/develop/packages/tiny).
|
||||||
|
|
||||||
## Enabling Click Event and Tags in Nodes
|
## Enabling Click Event and Tags in Nodes
|
||||||
|
|
||||||
A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduced in version 8.2 as a security improvement, aimed at preventing malicious use.
|
A `securityLevel` configuration has to first be cleared. `securityLevel` sets the level of trust for the parsed diagrams and limits click functionality. This was introduced in version 8.2 as a security improvement, aimed at preventing malicious use.
|
||||||
|
@@ -109,6 +109,7 @@ To Deploy Mermaid:
|
|||||||
|
|
||||||
- [Mermaid Live Editor](https://github.com/mermaid-js/mermaid-live-editor)
|
- [Mermaid Live Editor](https://github.com/mermaid-js/mermaid-live-editor)
|
||||||
- [Mermaid CLI](https://github.com/mermaid-js/mermaid-cli)
|
- [Mermaid CLI](https://github.com/mermaid-js/mermaid-cli)
|
||||||
|
- [Mermaid Tiny](https://github.com/mermaid-js/mermaid/tree/develop/packages/tiny)
|
||||||
- [Mermaid Webpack Demo](https://github.com/mermaidjs/mermaid-webpack-demo)
|
- [Mermaid Webpack Demo](https://github.com/mermaidjs/mermaid-webpack-demo)
|
||||||
- [Mermaid Parcel Demo](https://github.com/mermaidjs/mermaid-parcel-demo)
|
- [Mermaid Parcel Demo](https://github.com/mermaidjs/mermaid-parcel-demo)
|
||||||
|
|
||||||
|
@@ -360,6 +360,27 @@ It is possible to annotate classes with markers to provide additional metadata a
|
|||||||
|
|
||||||
Annotations are defined within the opening `<<` and closing `>>`. There are two ways to add an annotation to a class, and either way the output will be same:
|
Annotations are defined within the opening `<<` and closing `>>`. There are two ways to add an annotation to a class, and either way the output will be same:
|
||||||
|
|
||||||
|
> **Tip:**
|
||||||
|
> In Mermaid class diagrams, annotations like `<<interface>>` can be attached in two ways:
|
||||||
|
>
|
||||||
|
> - **Inline with the class definition** (Recommended for consistency):
|
||||||
|
>
|
||||||
|
> ```mermaid-example
|
||||||
|
> classDiagram
|
||||||
|
> class Shape <<interface>>
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> - **Separate line after the class definition**:
|
||||||
|
>
|
||||||
|
> ```mermaid-example
|
||||||
|
> classDiagram
|
||||||
|
> class Shape
|
||||||
|
> <<interface>> Shape
|
||||||
|
> ```
|
||||||
|
>
|
||||||
|
> Both methods are fully supported and produce identical diagrams.
|
||||||
|
> However, it is recommended to use the **inline style** for better readability and consistent formatting across diagrams.
|
||||||
|
|
||||||
- In a **_separate line_** after a class is defined:
|
- In a **_separate line_** after a class is defined:
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
|
@@ -10,13 +10,25 @@ This diagram type is particularly useful for developers, network engineers, educ
|
|||||||
|
|
||||||
## Syntax
|
## Syntax
|
||||||
|
|
||||||
```md
|
```
|
||||||
packet-beta
|
packet-beta
|
||||||
start: "Block name" %% Single-bit block
|
start: "Block name" %% Single-bit block
|
||||||
start-end: "Block name" %% Multi-bit blocks
|
start-end: "Block name" %% Multi-bit blocks
|
||||||
... More Fields ...
|
... More Fields ...
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Bits Syntax (v<MERMAID_RELEASE_VERSION>+)
|
||||||
|
|
||||||
|
Using start and end bit counts can be difficult, especially when modifying a design. For this we add a bit count field, which starts from the end of the previous field automagically. Use `+<count>` to set the number of bits, thus:
|
||||||
|
|
||||||
|
```
|
||||||
|
packet-beta
|
||||||
|
+1: "Block name" %% Single-bit block
|
||||||
|
+8: "Block name" %% 8-bit block
|
||||||
|
9-15: "Manually set start and end, it's fine to mix and match"
|
||||||
|
... More Fields ...
|
||||||
|
```
|
||||||
|
|
||||||
## Examples
|
## Examples
|
||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
@@ -46,8 +58,8 @@ packet-beta
|
|||||||
```mermaid-example
|
```mermaid-example
|
||||||
packet-beta
|
packet-beta
|
||||||
title UDP Packet
|
title UDP Packet
|
||||||
0-15: "Source Port"
|
+16: "Source Port"
|
||||||
16-31: "Destination Port"
|
+16: "Destination Port"
|
||||||
32-47: "Length"
|
32-47: "Length"
|
||||||
48-63: "Checksum"
|
48-63: "Checksum"
|
||||||
64-95: "Data (variable length)"
|
64-95: "Data (variable length)"
|
||||||
|
@@ -5,7 +5,6 @@
|
|||||||
// @ts-ignore TODO: Investigate D3 issue
|
// @ts-ignore TODO: Investigate D3 issue
|
||||||
import { select } from 'd3';
|
import { select } from 'd3';
|
||||||
import { compile, serialize, stringify } from 'stylis';
|
import { compile, serialize, stringify } from 'stylis';
|
||||||
// @ts-ignore: TODO Fix ts errors
|
|
||||||
import DOMPurify from 'dompurify';
|
import DOMPurify from 'dompurify';
|
||||||
import isEmpty from 'lodash-es/isEmpty.js';
|
import isEmpty from 'lodash-es/isEmpty.js';
|
||||||
import packageJson from '../package.json' assert { type: 'json' };
|
import packageJson from '../package.json' assert { type: 'json' };
|
||||||
|
@@ -89,6 +89,7 @@ export async function erBox<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
nameBBox.height += TEXT_PADDING;
|
nameBBox.height += TEXT_PADDING;
|
||||||
let yOffset = 0;
|
let yOffset = 0;
|
||||||
const yOffsets = [];
|
const yOffsets = [];
|
||||||
|
const rows = [];
|
||||||
let maxTypeWidth = 0;
|
let maxTypeWidth = 0;
|
||||||
let maxNameWidth = 0;
|
let maxNameWidth = 0;
|
||||||
let maxKeysWidth = 0;
|
let maxKeysWidth = 0;
|
||||||
@@ -137,12 +138,12 @@ export async function erBox<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
);
|
);
|
||||||
maxCommentWidth = Math.max(maxCommentWidth, commentBBox.width + PADDING);
|
maxCommentWidth = Math.max(maxCommentWidth, commentBBox.width + PADDING);
|
||||||
|
|
||||||
yOffset +=
|
const rowHeight =
|
||||||
Math.max(typeBBox.height, nameBBox.height, keysBBox.height, commentBBox.height) +
|
Math.max(typeBBox.height, nameBBox.height, keysBBox.height, commentBBox.height) +
|
||||||
TEXT_PADDING;
|
TEXT_PADDING;
|
||||||
yOffsets.push(yOffset);
|
rows.push({ yOffset, rowHeight });
|
||||||
|
yOffset += rowHeight;
|
||||||
}
|
}
|
||||||
yOffsets.pop();
|
|
||||||
let totalWidthSections = 4;
|
let totalWidthSections = 4;
|
||||||
|
|
||||||
if (maxKeysWidth <= PADDING) {
|
if (maxKeysWidth <= PADDING) {
|
||||||
@@ -185,8 +186,12 @@ export async function erBox<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
options.fillStyle = 'solid';
|
options.fillStyle = 'solid';
|
||||||
}
|
}
|
||||||
|
|
||||||
|
let totalShapeBBoxHeight = 0;
|
||||||
|
if (rows.length > 0) {
|
||||||
|
totalShapeBBoxHeight = rows.reduce((sum, row) => sum + (row?.rowHeight ?? 0), 0);
|
||||||
|
}
|
||||||
const w = Math.max(shapeBBox.width + PADDING * 2, node?.width || 0, maxWidth);
|
const w = Math.max(shapeBBox.width + PADDING * 2, node?.width || 0, maxWidth);
|
||||||
const h = Math.max(shapeBBox.height + (yOffsets[0] || yOffset) + TEXT_PADDING, node?.height || 0);
|
const h = Math.max((totalShapeBBoxHeight ?? 0) + nameBBox.height, node?.height || 0);
|
||||||
const x = -w / 2;
|
const x = -w / 2;
|
||||||
const y = -h / 2;
|
const y = -h / 2;
|
||||||
|
|
||||||
@@ -232,13 +237,10 @@ export async function erBox<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
|
|
||||||
yOffsets.push(0);
|
yOffsets.push(0);
|
||||||
// Draw row rects
|
// Draw row rects
|
||||||
for (const [i, yOffset] of yOffsets.entries()) {
|
for (const [i, row] of rows.entries()) {
|
||||||
if (i === 0 && yOffsets.length > 1) {
|
const contentRowIndex = i + 1; // Adjusted index to skip the header (name) row
|
||||||
continue;
|
const isEven = contentRowIndex % 2 === 0 && row.yOffset !== 0;
|
||||||
// Skip first row
|
const roughRect = rc.rectangle(x, nameBBox.height + y + row?.yOffset, w, row?.rowHeight, {
|
||||||
}
|
|
||||||
const isEven = i % 2 === 0 && yOffset !== 0;
|
|
||||||
const roughRect = rc.rectangle(x, nameBBox.height + y + yOffset, w, nameBBox.height, {
|
|
||||||
...options,
|
...options,
|
||||||
fill: isEven ? rowEven : rowOdd,
|
fill: isEven ? rowEven : rowOdd,
|
||||||
stroke: nodeBorder,
|
stroke: nodeBorder,
|
||||||
@@ -246,7 +248,7 @@ export async function erBox<T extends SVGGraphicsElement>(parent: D3Selection<T>
|
|||||||
shapeSvg
|
shapeSvg
|
||||||
.insert(() => roughRect, 'g.label')
|
.insert(() => roughRect, 'g.label')
|
||||||
.attr('style', cssStyles!.join(''))
|
.attr('style', cssStyles!.join(''))
|
||||||
.attr('class', `row-rect-${i % 2 === 0 ? 'even' : 'odd'}`);
|
.attr('class', `row-rect-${isEven ? 'even' : 'odd'}`);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Draw divider lines
|
// Draw divider lines
|
||||||
|
@@ -2124,6 +2124,13 @@ $defs: # JSON Schema definition (maybe we should move these to a separate file)
|
|||||||
type: number
|
type: number
|
||||||
default: 200
|
default: 200
|
||||||
|
|
||||||
|
inheritDir:
|
||||||
|
type: boolean
|
||||||
|
default: false
|
||||||
|
description: |
|
||||||
|
If true, subgraphs without explicit direction will inherit the global graph direction
|
||||||
|
(e.g., LR, TB, RL, BT). Defaults to false to preserve legacy layout behavior.
|
||||||
|
|
||||||
SankeyLinkColor:
|
SankeyLinkColor:
|
||||||
description: |
|
description: |
|
||||||
Picks the color of the sankey diagram links, using the colors of the source and/or target of the links.
|
Picks the color of the sankey diagram links, using the colors of the source and/or target of the links.
|
||||||
|
2
packages/mermaid/src/type.d.ts
vendored
Normal file
2
packages/mermaid/src/type.d.ts
vendored
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
// eslint-disable-next-line no-var
|
||||||
|
declare var includeLargeFeatures: boolean;
|
@@ -12,5 +12,10 @@ entry Packet:
|
|||||||
;
|
;
|
||||||
|
|
||||||
PacketBlock:
|
PacketBlock:
|
||||||
start=INT('-' end=INT)? ':' label=STRING EOL
|
(
|
||||||
;
|
start=INT('-' end=INT)?
|
||||||
|
| '+' bits=INT
|
||||||
|
)
|
||||||
|
':' label=STRING
|
||||||
|
EOL
|
||||||
|
;
|
||||||
|
35
packages/tiny/README.md
Normal file
35
packages/tiny/README.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# Tiny Mermaid
|
||||||
|
|
||||||
|
This is a tiny version of mermaid that is optimized for the web. It is a subset of the mermaid library and is designed to be used in the browser via CDN.
|
||||||
|
|
||||||
|
## Lazy loading
|
||||||
|
|
||||||
|
The original mermaid library supports lazy loading, so it will be faster on the initial load, and only load the required diagrams.
|
||||||
|
This is not supported in the tiny mermaid library. So it's always recommended to use the full mermaid library unless you have a very specific reason to reduce the bundle size.
|
||||||
|
|
||||||
|
## Removals from mermaid
|
||||||
|
|
||||||
|
This does not support
|
||||||
|
|
||||||
|
- Mindmap Diagram
|
||||||
|
- Architecture Diagram
|
||||||
|
- Katex rendering
|
||||||
|
- Lazy loading
|
||||||
|
|
||||||
|
## Usage via NPM
|
||||||
|
|
||||||
|
This package is not meant to be installed directly from npm. It is designed to be used via CDN.
|
||||||
|
If you need to use mermaid in your project, please install the full [`mermaid` package](https://www.npmjs.com/package/mermaid) instead.
|
||||||
|
|
||||||
|
## Usage via CDN
|
||||||
|
|
||||||
|
```html
|
||||||
|
<!-- Format -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@mermaid-js/tiny@<MERMAID_MAJOR_VERSION>/dist/mermaid.tiny.js"></script>
|
||||||
|
|
||||||
|
<!-- Pinning major version -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@mermaid-js/tiny@11/dist/mermaid.tiny.js"></script>
|
||||||
|
|
||||||
|
<!-- Pinning specific version -->
|
||||||
|
<script src="https://cdn.jsdelivr.net/npm/@mermaid-js/tiny@11.6.0/dist/mermaid.tiny.js"></script>
|
||||||
|
```
|
25
packages/tiny/package.json
Normal file
25
packages/tiny/package.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"name": "@mermaid-js/tiny",
|
||||||
|
"version": "11.6.0",
|
||||||
|
"description": "Tiny version of mermaid",
|
||||||
|
"type": "commonjs",
|
||||||
|
"main": "./dist/mermaid.tiny.js",
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf dist"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/mermaid-js/mermaid"
|
||||||
|
},
|
||||||
|
"author": "Sidharth Vinod",
|
||||||
|
"license": "MIT",
|
||||||
|
"dependencies": {},
|
||||||
|
"devDependencies": {},
|
||||||
|
"files": [
|
||||||
|
"dist/",
|
||||||
|
"README.md"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
608
pnpm-lock.yaml
generated
608
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -35,6 +35,8 @@ export default defineConfig({
|
|||||||
},
|
},
|
||||||
},
|
},
|
||||||
define: {
|
define: {
|
||||||
|
// Needs to be string
|
||||||
|
includeLargeFeatures: 'true',
|
||||||
'import.meta.vitest': 'undefined',
|
'import.meta.vitest': 'undefined',
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
|
Reference in New Issue
Block a user