mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-10-12 18:49:37 +02:00
Compare commits
3 Commits
sidv/runCo
...
sidv/liveR
Author | SHA1 | Date | |
---|---|---|---|
![]() |
5e210753b7 | ||
![]() |
350aaf9b51 | ||
![]() |
5cd792f027 |
@@ -82,8 +82,8 @@ export function generateDefaults(mermaidConfigSchema: JSONSchemaType<MermaidConf
|
||||
`schema for subconfig ${key} does not have valid defaults! Errors were ${JSON.stringify(
|
||||
validate.errors,
|
||||
undefined,
|
||||
2,
|
||||
)}`,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -95,8 +95,8 @@ export function generateDefaults(mermaidConfigSchema: JSONSchemaType<MermaidConf
|
||||
`Mermaid config JSON Schema does not have valid defaults! Errors were ${JSON.stringify(
|
||||
validate.errors,
|
||||
undefined,
|
||||
2,
|
||||
)}`,
|
||||
2
|
||||
)}`
|
||||
);
|
||||
}
|
||||
|
||||
|
@@ -1,49 +1,21 @@
|
||||
import { build } from 'esbuild';
|
||||
import { mkdir, writeFile } from 'node:fs/promises';
|
||||
import { MermaidBuildOptions, defaultOptions, getBuildConfig } from './util.js';
|
||||
import { getBuildConfig } from './util.js';
|
||||
import { packageOptions } from '../.build/common.js';
|
||||
|
||||
const shouldVisualize = process.argv.includes('--visualize');
|
||||
|
||||
const buildPackage = async (entryName: keyof typeof packageOptions) => {
|
||||
const commonOptions = { ...defaultOptions, entryName } as const;
|
||||
const buildConfigs = [
|
||||
// package.mjs
|
||||
{ ...commonOptions },
|
||||
// package.min.mjs
|
||||
{
|
||||
...commonOptions,
|
||||
minify: true,
|
||||
metafile: shouldVisualize,
|
||||
},
|
||||
// package.core.mjs
|
||||
{ ...commonOptions, core: true },
|
||||
];
|
||||
|
||||
if (entryName === 'mermaid') {
|
||||
const iifeOptions: MermaidBuildOptions = { ...commonOptions, format: 'iife' };
|
||||
buildConfigs.push(
|
||||
// mermaid.js
|
||||
{ ...iifeOptions },
|
||||
// mermaid.min.js
|
||||
{ ...iifeOptions, minify: true, metafile: shouldVisualize },
|
||||
);
|
||||
}
|
||||
|
||||
const results = await Promise.all(buildConfigs.map((option) => build(getBuildConfig(option))));
|
||||
|
||||
if (shouldVisualize) {
|
||||
for (const { metafile } of results) {
|
||||
if (!metafile) {
|
||||
continue;
|
||||
}
|
||||
const fileName = Object.keys(metafile.outputs)
|
||||
.filter((file) => !file.includes('chunks') && file.endsWith('js'))[0]
|
||||
.replace('dist/', '');
|
||||
// Upload metafile into https://esbuild.github.io/analyze/
|
||||
await writeFile(`stats/${fileName}.meta.json`, JSON.stringify(metafile));
|
||||
}
|
||||
await build(getBuildConfig({ entryName, minify: false }));
|
||||
const { metafile } = await build(
|
||||
getBuildConfig({ entryName, minify: true, metafile: shouldVisualize })
|
||||
);
|
||||
if (metafile) {
|
||||
// Upload metafile into https://esbuild.github.io/analyze/
|
||||
await writeFile(`stats/meta-${entryName}.json`, JSON.stringify(metafile));
|
||||
}
|
||||
await build(getBuildConfig({ entryName, minify: false, core: true }));
|
||||
await build(getBuildConfig({ entryName, minify: true, format: 'iife' }));
|
||||
};
|
||||
|
||||
const handler = (e) => {
|
||||
@@ -54,7 +26,9 @@ const handler = (e) => {
|
||||
const main = async () => {
|
||||
await mkdir('stats').catch(() => {});
|
||||
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
|
||||
await Promise.allSettled(packageNames.map((pkg) => buildPackage(pkg).catch(handler)));
|
||||
for (const pkg of packageNames) {
|
||||
await buildPackage(pkg).catch(handler);
|
||||
}
|
||||
};
|
||||
|
||||
void main();
|
||||
|
@@ -1,39 +1,26 @@
|
||||
import express from 'express';
|
||||
import type { NextFunction, Request, Response } from 'express';
|
||||
import cors from 'cors';
|
||||
import { getBuildConfig, defaultOptions } from './util.js';
|
||||
import { getBuildConfig } from './util.js';
|
||||
import { context } from 'esbuild';
|
||||
import chokidar from 'chokidar';
|
||||
|
||||
const mermaidCtx = await context(
|
||||
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid' }),
|
||||
getBuildConfig({ minify: false, core: false, entryName: 'mermaid' })
|
||||
);
|
||||
const mermaidIIFECtx = await context(
|
||||
getBuildConfig({
|
||||
...defaultOptions,
|
||||
minify: false,
|
||||
core: false,
|
||||
entryName: 'mermaid',
|
||||
format: 'iife',
|
||||
}),
|
||||
getBuildConfig({ minify: false, core: false, entryName: 'mermaid', format: 'iife' })
|
||||
);
|
||||
const externalCtx = await context(
|
||||
getBuildConfig({
|
||||
...defaultOptions,
|
||||
minify: false,
|
||||
core: false,
|
||||
entryName: 'mermaid-example-diagram',
|
||||
}),
|
||||
getBuildConfig({ minify: false, core: false, entryName: 'mermaid-example-diagram' })
|
||||
);
|
||||
const zenumlCtx = await context(
|
||||
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid-zenuml' }),
|
||||
getBuildConfig({ minify: false, core: false, entryName: 'mermaid-zenuml' })
|
||||
);
|
||||
const contexts = [mermaidCtx, mermaidIIFECtx, externalCtx, zenumlCtx];
|
||||
|
||||
const rebuildAll = async () => {
|
||||
console.time('Rebuild time');
|
||||
await Promise.all(contexts.map((ctx) => ctx.rebuild()));
|
||||
console.timeEnd('Rebuild time');
|
||||
};
|
||||
|
||||
let clients: { id: number; response: Response }[] = [];
|
||||
@@ -75,7 +62,6 @@ function sendEventsToAll() {
|
||||
}
|
||||
|
||||
async function createServer() {
|
||||
handleFileChange();
|
||||
const app = express();
|
||||
chokidar
|
||||
.watch('**/src/**/*.{js,ts,yaml,json}', {
|
||||
|
@@ -8,21 +8,14 @@ import { jisonPlugin } from './jisonPlugin.js';
|
||||
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
|
||||
export interface MermaidBuildOptions {
|
||||
interface MermaidBuildOptions {
|
||||
minify: boolean;
|
||||
core: boolean;
|
||||
metafile: boolean;
|
||||
format: 'esm' | 'iife';
|
||||
core?: boolean;
|
||||
metafile?: boolean;
|
||||
format?: 'esm' | 'iife';
|
||||
entryName: keyof typeof packageOptions;
|
||||
}
|
||||
|
||||
export const defaultOptions: Omit<MermaidBuildOptions, 'entryName'> = {
|
||||
minify: false,
|
||||
metafile: false,
|
||||
core: false,
|
||||
format: 'esm',
|
||||
} as const;
|
||||
|
||||
const buildOptions = (override: BuildOptions): BuildOptions => {
|
||||
return {
|
||||
bundle: true,
|
||||
@@ -39,37 +32,29 @@ const buildOptions = (override: BuildOptions): BuildOptions => {
|
||||
};
|
||||
};
|
||||
|
||||
const getFileName = (fileName: string, { core, format, minify }: MermaidBuildOptions) => {
|
||||
if (core) {
|
||||
fileName += '.core';
|
||||
} else if (format === 'esm') {
|
||||
fileName += '.esm';
|
||||
}
|
||||
if (minify) {
|
||||
fileName += '.min';
|
||||
}
|
||||
return fileName;
|
||||
};
|
||||
|
||||
export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
|
||||
const { core, entryName, metafile, format, minify } = options;
|
||||
export const getBuildConfig = ({
|
||||
minify,
|
||||
core,
|
||||
entryName,
|
||||
metafile,
|
||||
format,
|
||||
}: MermaidBuildOptions): BuildOptions => {
|
||||
const external: string[] = ['require', 'fs', 'path'];
|
||||
const { name, file, packageName } = packageOptions[entryName];
|
||||
const outFileName = getFileName(name, options);
|
||||
let output: BuildOptions = buildOptions({
|
||||
absWorkingDir: resolve(__dirname, `../packages/${packageName}`),
|
||||
entryPoints: {
|
||||
[outFileName]: `src/${file}`,
|
||||
[`${name}${core ? '.core' : format === 'iife' ? '' : '.esm'}${
|
||||
minify ? '.min' : ''
|
||||
}`]: `src/${file}`,
|
||||
},
|
||||
metafile,
|
||||
minify,
|
||||
logLevel: 'info',
|
||||
chunkNames: `chunks/${outFileName}/[name]-[hash]`,
|
||||
});
|
||||
|
||||
if (core) {
|
||||
const { dependencies } = JSON.parse(
|
||||
readFileSync(resolve(__dirname, `../packages/${packageName}/package.json`), 'utf-8'),
|
||||
readFileSync(resolve(__dirname, `../packages/${packageName}/package.json`), 'utf-8')
|
||||
);
|
||||
// Core build is used to generate file without bundled dependencies.
|
||||
// This is used by downstream projects to bundle dependencies themselves.
|
||||
@@ -82,8 +67,6 @@ export const getBuildConfig = (options: MermaidBuildOptions): BuildOptions => {
|
||||
output.format = 'iife';
|
||||
output.splitting = false;
|
||||
output.globalName = '__esbuild_esm_mermaid';
|
||||
// Workaround for removing the .default access in esbuild IIFE.
|
||||
// https://github.com/mermaid-js/mermaid/pull/4109#discussion_r1292317396
|
||||
output.footer = {
|
||||
js: 'globalThis.mermaid = globalThis.__esbuild_esm_mermaid.default;',
|
||||
};
|
||||
|
@@ -48,8 +48,6 @@ module.exports = {
|
||||
'no-prototype-builtins': 'off',
|
||||
'no-unused-vars': 'off',
|
||||
'cypress/no-async-tests': 'off',
|
||||
'@typescript-eslint/no-unused-vars': 'warn',
|
||||
'@typescript-eslint/no-explicit-any': 'warn',
|
||||
'@typescript-eslint/no-floating-promises': 'error',
|
||||
'@typescript-eslint/no-misused-promises': 'error',
|
||||
'@typescript-eslint/ban-ts-comment': [
|
||||
|
15
.github/release-drafter.yml
vendored
15
.github/release-drafter.yml
vendored
@@ -1,27 +1,14 @@
|
||||
name-template: '$NEXT_PATCH_VERSION'
|
||||
tag-template: '$NEXT_PATCH_VERSION'
|
||||
categories:
|
||||
- title: '🚨 **Breaking Changes**'
|
||||
labels:
|
||||
- 'Breaking Change'
|
||||
- title: '🚀 Features'
|
||||
labels:
|
||||
- 'Type: Enhancement'
|
||||
- 'feature' # deprecated, new PRs shouldn't have this
|
||||
- title: '🐛 Bug Fixes'
|
||||
labels:
|
||||
- 'Type: Bug / Error'
|
||||
- 'fix' # deprecated, new PRs shouldn't have this
|
||||
- title: '🧰 Maintenance'
|
||||
labels:
|
||||
- 'Type: Other'
|
||||
- 'chore' # deprecated, new PRs shouldn't have this
|
||||
- title: '⚡️ Performance'
|
||||
labels:
|
||||
- 'Type: Performance'
|
||||
- title: '📚 Documentation'
|
||||
labels:
|
||||
- 'Area: Documentation'
|
||||
label: 'Type: Other'
|
||||
change-template: '- $TITLE (#$NUMBER) @$AUTHOR'
|
||||
sort-by: title
|
||||
sort-direction: ascending
|
||||
|
15
.github/workflows/e2e.yml
vendored
15
.github/workflows/e2e.yml
vendored
@@ -30,7 +30,7 @@ jobs:
|
||||
# Install NPM dependencies, cache them correctly
|
||||
# and run all Cypress tests
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v6
|
||||
uses: cypress-io/github-action@v4
|
||||
id: cypress
|
||||
# If CYPRESS_RECORD_KEY is set, run in parallel on all containers
|
||||
# Otherwise (e.g. if running from fork), we run on a single container only
|
||||
@@ -44,19 +44,14 @@ jobs:
|
||||
parallel: ${{ secrets.CYPRESS_RECORD_KEY != '' }}
|
||||
env:
|
||||
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}
|
||||
VITE_COVERAGE: true
|
||||
CYPRESS_COVERAGE: true
|
||||
VITEST_COVERAGE: true
|
||||
CYPRESS_COMMIT: ${{ github.sha }}
|
||||
|
||||
- name: Check coverage files
|
||||
run: |
|
||||
ls -lh ./coverage
|
||||
ls -lh ./coverage/cypress
|
||||
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
# Run step only pushes to develop and pull_requests
|
||||
if: ${{ steps.cypress.conclusion == 'success' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop')}}
|
||||
with:
|
||||
files: ./coverage/cypress/lcov.info
|
||||
files: coverage/cypress/lcov.info
|
||||
flags: e2e
|
||||
name: mermaid-codecov
|
||||
fail_ci_if_error: false
|
||||
|
18
.github/workflows/lint.yml
vendored
18
.github/workflows/lint.yml
vendored
@@ -62,22 +62,8 @@ jobs:
|
||||
ERROR_MESSAGE+=' `pnpm run --filter mermaid types:build-config`'
|
||||
ERROR_MESSAGE+=' on your local machine.'
|
||||
echo "::error title=Lint failure::${ERROR_MESSAGE}"
|
||||
# make sure to return an error exitcode so that GitHub actions shows a red-cross
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify no circular dependencies
|
||||
working-directory: ./packages/mermaid
|
||||
shell: bash
|
||||
run: |
|
||||
if ! pnpm run --filter mermaid checkCircle; then
|
||||
ERROR_MESSAGE='Circular dependency detected.'
|
||||
ERROR_MESSAGE+=' This should be fixed by removing the circular dependency.'
|
||||
ERROR_MESSAGE+=' Run `pnpm run --filter mermaid checkCircle` on your local machine'
|
||||
ERROR_MESSAGE+=' to see the circular dependency.'
|
||||
echo "::error title=Lint failure::${ERROR_MESSAGE}"
|
||||
# make sure to return an error exitcode so that GitHub actions shows a red-cross
|
||||
exit 1
|
||||
# make sure to return an error exitcode so that GitHub actions shows a red-cross
|
||||
exit 1
|
||||
fi
|
||||
|
||||
- name: Verify Docs
|
||||
|
2
.github/workflows/test.yml
vendored
2
.github/workflows/test.yml
vendored
@@ -43,6 +43,8 @@ jobs:
|
||||
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v3
|
||||
# Run step only pushes to develop and pull_requests
|
||||
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
|
||||
with:
|
||||
files: ./coverage/vitest/lcov.info
|
||||
flags: unit
|
||||
|
1
.gitignore
vendored
1
.gitignore
vendored
@@ -46,4 +46,3 @@ stats/
|
||||
|
||||
demos/dev/**
|
||||
!/demos/dev/example.html
|
||||
!/demos/dev/reload.js
|
||||
|
@@ -32,7 +32,7 @@ const visualizerOptions = (packageName: string, core = false): PluginOption[] =>
|
||||
template: chartType as TemplateType,
|
||||
gzipSize: true,
|
||||
brotliSize: true,
|
||||
}) as PluginOption,
|
||||
}) as PluginOption
|
||||
);
|
||||
};
|
||||
|
||||
|
13
__mocks__/pieRenderer.js
Normal file
13
__mocks__/pieRenderer.js
Normal file
@@ -0,0 +1,13 @@
|
||||
/**
|
||||
* Mocked pie (picChart) diagram renderer
|
||||
*/
|
||||
|
||||
import { vi } from 'vitest';
|
||||
|
||||
export const draw = vi.fn().mockImplementation(() => {
|
||||
return '';
|
||||
});
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
@@ -1,8 +0,0 @@
|
||||
/**
|
||||
* Mocked pie (picChart) diagram renderer
|
||||
*/
|
||||
import { vi } from 'vitest';
|
||||
|
||||
const draw = vi.fn().mockImplementation(() => '');
|
||||
|
||||
export const renderer = { draw };
|
@@ -44,7 +44,6 @@
|
||||
"faber",
|
||||
"flatmap",
|
||||
"foswiki",
|
||||
"frontmatter",
|
||||
"ftplugin",
|
||||
"gantt",
|
||||
"gitea",
|
||||
@@ -57,7 +56,6 @@
|
||||
"gzipped",
|
||||
"huynh",
|
||||
"huynhicode",
|
||||
"iife",
|
||||
"inkdrop",
|
||||
"jaoude",
|
||||
"jgreywolf",
|
||||
@@ -95,7 +93,6 @@
|
||||
"nikolay",
|
||||
"nirname",
|
||||
"orlandoni",
|
||||
"outdir",
|
||||
"pathe",
|
||||
"pbrolin",
|
||||
"phpbb",
|
||||
@@ -110,7 +107,6 @@
|
||||
"rects",
|
||||
"reda",
|
||||
"redmine",
|
||||
"regexes",
|
||||
"rehype",
|
||||
"roledescription",
|
||||
"rozhkov",
|
||||
|
@@ -14,6 +14,7 @@ describe('Configuration and directives - nodes should be light blue', () => {
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Settings from initialize - nodes should be green', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -27,6 +28,7 @@ graph TD
|
||||
end `,
|
||||
{ theme: 'forest' }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Settings from initialize overriding themeVariable - nodes should be red', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -44,6 +46,7 @@ graph TD
|
||||
`,
|
||||
{ theme: 'base', themeVariables: { primaryColor: '#ff0000' }, logLevel: 0 }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Settings from directive - nodes should be grey', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -59,24 +62,7 @@ graph TD
|
||||
`,
|
||||
{}
|
||||
);
|
||||
});
|
||||
it('Settings from frontmatter - nodes should be grey', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
---
|
||||
config:
|
||||
theme: neutral
|
||||
---
|
||||
graph TD
|
||||
A(Start) --> B[/Another/]
|
||||
A[/Another/] --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('Settings from directive overriding theme variable - nodes should be red', () => {
|
||||
@@ -93,6 +79,7 @@ graph TD
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Settings from initialize and directive - nodes should be grey', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -108,6 +95,7 @@ graph TD
|
||||
`,
|
||||
{ theme: 'forest' }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Theme from initialize, directive overriding theme variable - nodes should be red', () => {
|
||||
imgSnapshotTest(
|
||||
@@ -123,71 +111,8 @@ graph TD
|
||||
`,
|
||||
{ theme: 'base' }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
it('Theme from initialize, frontmatter overriding theme variable - nodes should be red', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
---
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: '#ff0000'
|
||||
---
|
||||
graph TD
|
||||
A(Start) --> B[/Another/]
|
||||
A[/Another/] --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
`,
|
||||
{ theme: 'forest' }
|
||||
);
|
||||
});
|
||||
it('Theme from initialize, frontmatter overriding theme variable, directive overriding primaryColor - nodes should be red', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
---
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: '#00ff00'
|
||||
---
|
||||
%%{init: {'theme': 'base', 'themeVariables':{ 'primaryColor': '#ff0000'}}}%%
|
||||
graph TD
|
||||
A(Start) --> B[/Another/]
|
||||
A[/Another/] --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
`,
|
||||
{ theme: 'forest' }
|
||||
);
|
||||
});
|
||||
|
||||
it('should render if values are not quoted properly', () => {
|
||||
// #ff0000 is not quoted properly, and will evaluate to null.
|
||||
// This test ensures that the rendering still works.
|
||||
imgSnapshotTest(
|
||||
`---
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: #ff0000
|
||||
---
|
||||
graph TD
|
||||
A(Start) --> B[/Another/]
|
||||
A[/Another/] --> C[End]
|
||||
subgraph section
|
||||
B
|
||||
C
|
||||
end
|
||||
`,
|
||||
{ theme: 'forest' }
|
||||
);
|
||||
});
|
||||
|
||||
it('Theme variable from initialize, theme from directive - nodes should be red', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
@@ -202,11 +127,13 @@ graph TD
|
||||
`,
|
||||
{ themeVariables: { primaryColor: '#ff0000' } }
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
describe('when rendering several diagrams', () => {
|
||||
it('diagrams should not taint later diagrams', () => {
|
||||
const url = 'http://localhost:9000/theme-directives.html';
|
||||
cy.visit(url);
|
||||
cy.get('svg');
|
||||
cy.matchImageSnapshot('conf-and-directives.spec-when-rendering-several-diagrams-diagram-1');
|
||||
});
|
||||
});
|
||||
|
@@ -1,85 +1,89 @@
|
||||
import { imgSnapshotTest, renderGraph } from '../../helpers/util.ts';
|
||||
|
||||
describe('pie chart', () => {
|
||||
describe('Pie Chart', () => {
|
||||
it('should render a simple pie diagram', () => {
|
||||
imgSnapshotTest(
|
||||
`pie title Sports in Sweden
|
||||
"Bandy": 40
|
||||
"Ice-Hockey": 80
|
||||
"Football": 90
|
||||
`
|
||||
pie title Sports in Sweden
|
||||
"Bandy" : 40
|
||||
"Ice-Hockey" : 80
|
||||
"Football" : 90
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a simple pie diagram with long labels', () => {
|
||||
imgSnapshotTest(
|
||||
`pie title NETFLIX
|
||||
"Time spent looking for movie": 90
|
||||
"Time spent watching it": 10
|
||||
`
|
||||
pie title NETFLIX
|
||||
"Time spent looking for movie" : 90
|
||||
"Time spent watching it" : 10
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a simple pie diagram with capital letters for labels', () => {
|
||||
imgSnapshotTest(
|
||||
`pie title What Voldemort doesn't have?
|
||||
"FRIENDS": 2
|
||||
"FAMILY": 3
|
||||
"NOSE": 45
|
||||
`
|
||||
pie title What Voldemort doesn't have?
|
||||
"FRIENDS" : 2
|
||||
"FAMILY" : 3
|
||||
"NOSE" : 45
|
||||
`,
|
||||
{}
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('should render a pie diagram when useMaxWidth is true (default)', () => {
|
||||
renderGraph(
|
||||
`pie title Sports in Sweden
|
||||
"Bandy": 40
|
||||
"Ice-Hockey": 80
|
||||
"Football": 90
|
||||
`
|
||||
pie title Sports in Sweden
|
||||
"Bandy" : 40
|
||||
"Ice-Hockey" : 80
|
||||
"Football" : 90
|
||||
`,
|
||||
{ pie: { useMaxWidth: true } }
|
||||
);
|
||||
cy.get('svg').should((svg) => {
|
||||
expect(svg).to.have.attr('width', '100%');
|
||||
// expect(svg).to.have.attr('height');
|
||||
// const height = parseFloat(svg.attr('height'));
|
||||
// expect(height).to.eq(450);
|
||||
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);
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a pie diagram when useMaxWidth is false', () => {
|
||||
renderGraph(
|
||||
`pie title Sports in Sweden
|
||||
"Bandy": 40
|
||||
"Ice-Hockey": 80
|
||||
"Football": 90
|
||||
`
|
||||
pie title Sports in Sweden
|
||||
"Bandy" : 40
|
||||
"Ice-Hockey" : 80
|
||||
"Football" : 90
|
||||
`,
|
||||
{ pie: { useMaxWidth: false } }
|
||||
);
|
||||
cy.get('svg').should((svg) => {
|
||||
// const height = parseFloat(svg.attr('height'));
|
||||
const width = parseFloat(svg.attr('width'));
|
||||
// expect(height).to.eq(450);
|
||||
expect(width).to.eq(984);
|
||||
expect(svg).to.not.have.attr('style');
|
||||
});
|
||||
});
|
||||
|
||||
it('should render a pie diagram when textPosition is setted', () => {
|
||||
it('should render a pie diagram when textPosition is set', () => {
|
||||
imgSnapshotTest(
|
||||
`pie
|
||||
"Dogs": 50
|
||||
"Cats": 25
|
||||
`,
|
||||
`
|
||||
pie
|
||||
"Dogs": 50
|
||||
"Cats": 25
|
||||
`,
|
||||
{ logLevel: 1, pie: { textPosition: 0.9 } }
|
||||
);
|
||||
});
|
||||
|
||||
it('should render a pie diagram with showData', () => {
|
||||
imgSnapshotTest(
|
||||
`pie showData
|
||||
"Dogs": 50
|
||||
"Cats": 25
|
||||
`
|
||||
);
|
||||
cy.get('svg');
|
||||
});
|
||||
});
|
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
|
@@ -139,6 +139,6 @@ if (typeof document !== 'undefined') {
|
||||
void contentLoaded().finally(markRendered);
|
||||
}
|
||||
},
|
||||
false,
|
||||
false
|
||||
);
|
||||
}
|
||||
|
@@ -5,8 +5,6 @@
|
||||
<title>Mermaid development page</title>
|
||||
</head>
|
||||
<body>
|
||||
<pre class="mermaid">info</pre>
|
||||
|
||||
<pre id="diagram" class="mermaid">
|
||||
graph TB
|
||||
a --> b
|
||||
@@ -33,6 +31,32 @@ graph TB
|
||||
el.innerHTML = svg;
|
||||
</script>
|
||||
|
||||
<script src="/dev/reload.js"></script>
|
||||
<script>
|
||||
// Set to false to disable live reload
|
||||
const liveReload = true;
|
||||
// Connect to the server and reload the page if the server sends a reload message
|
||||
const connectToEvents = () => {
|
||||
const events = new EventSource('/events');
|
||||
const loadTime = Date.now();
|
||||
events.onmessage = (event) => {
|
||||
const time = JSON.parse(event.data);
|
||||
if (time && time > loadTime) {
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
events.onerror = (error) => {
|
||||
console.error(error);
|
||||
events.close();
|
||||
// Try to reconnect after 1 second in case of errors
|
||||
setTimeout(connectToEvents, 1000);
|
||||
};
|
||||
events.onopen = () => {
|
||||
console.log('Connected to live reload server');
|
||||
};
|
||||
};
|
||||
if (liveReload) {
|
||||
connectToEvents();
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
|
@@ -1,22 +0,0 @@
|
||||
// Connect to the server and reload the page if the server sends a reload message
|
||||
const connectToEvents = () => {
|
||||
const events = new EventSource('/events');
|
||||
const loadTime = Date.now();
|
||||
events.onmessage = (event) => {
|
||||
const time = JSON.parse(event.data);
|
||||
if (time && time > loadTime) {
|
||||
location.reload();
|
||||
}
|
||||
};
|
||||
events.onerror = (error) => {
|
||||
console.error(error);
|
||||
events.close();
|
||||
// Try to reconnect after 1 second in case of errors
|
||||
setTimeout(connectToEvents, 1000);
|
||||
};
|
||||
events.onopen = () => {
|
||||
console.log('Connected to live reload server');
|
||||
};
|
||||
};
|
||||
|
||||
setTimeout(connectToEvents, 500);
|
@@ -21,8 +21,6 @@
|
||||
<pre class="mermaid">
|
||||
---
|
||||
title: This is a title
|
||||
config:
|
||||
theme: forest
|
||||
---
|
||||
erDiagram
|
||||
%% title This is a title
|
||||
|
@@ -123,13 +123,6 @@
|
||||
|
||||
<h3>flowchart</h3>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
title: This is another complicated flow
|
||||
config:
|
||||
theme: base
|
||||
flowchart:
|
||||
curve: cardinal
|
||||
---
|
||||
flowchart LR
|
||||
sid-B3655226-6C29-4D00-B685-3D5C734DC7E1["
|
||||
|
||||
|
@@ -1,4 +1,4 @@
|
||||
<!doctype html>
|
||||
<!DOCTYPE html>
|
||||
<html lang="en">
|
||||
<head>
|
||||
<meta charset="utf-8" />
|
||||
@@ -7,6 +7,7 @@
|
||||
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
|
||||
<style>
|
||||
div.mermaid {
|
||||
/* font-family: 'trebuchet ms', verdana, arial; */
|
||||
font-family: 'Courier New', Courier, monospace !important;
|
||||
}
|
||||
</style>
|
||||
@@ -16,32 +17,37 @@
|
||||
<h1>Pie chart demos</h1>
|
||||
<pre class="mermaid">
|
||||
pie title Pets adopted by volunteers
|
||||
accTitle: simple pie char demo
|
||||
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
||||
"Dogs": 386
|
||||
"Cats": 85
|
||||
"Rats": 15
|
||||
accTitle: simple pie char demo
|
||||
accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs.
|
||||
"Dogs" : 386
|
||||
"Cats" : 85
|
||||
"Rats" : 15
|
||||
</pre>
|
||||
|
||||
<hr />
|
||||
<pre class="mermaid">
|
||||
%%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}}}%%
|
||||
pie
|
||||
title Key elements in Product X
|
||||
%%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}} }%%
|
||||
pie
|
||||
title Key elements in Product X
|
||||
accTitle: Key elements in Product X
|
||||
accDescr: This is a pie chart showing the key elements in Product X.
|
||||
"Calcium": 42.96
|
||||
"Potassium": 50.05
|
||||
"Magnesium": 10.01
|
||||
"Iron": 5
|
||||
accDescr: This is a pie chart showing the key elements in Product X.
|
||||
"Calcium" : 42.96
|
||||
"Potassium" : 50.05
|
||||
"Magnesium" : 10.01
|
||||
"Iron" : 5
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from '/mermaid.esm.mjs';
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
// themeCSS: '.node rect { fill: red; }',
|
||||
logLevel: 3,
|
||||
securityLevel: 'loose',
|
||||
// flowchart: { curve: 'basis' },
|
||||
// gantt: { axisFormat: '%m/%d/%Y' },
|
||||
sequence: { actorMargin: 50 },
|
||||
// sequenceDiagram: { actorMargin: 300 } // deprecated
|
||||
});
|
||||
</script>
|
||||
</body>
|
||||
|
@@ -1,7 +1,7 @@
|
||||
version: '3.9'
|
||||
services:
|
||||
mermaid:
|
||||
image: node:18.17.1-alpine3.18
|
||||
image: node:18.17.0-alpine3.18
|
||||
stdin_open: true
|
||||
tty: true
|
||||
working_dir: /mermaid
|
||||
@@ -17,7 +17,7 @@ services:
|
||||
- 9000:9000
|
||||
- 3333:3333
|
||||
cypress:
|
||||
image: cypress/included:12.17.4
|
||||
image: cypress/included:12.17.2
|
||||
stdin_open: true
|
||||
tty: true
|
||||
working_dir: /mermaid
|
||||
|
@@ -10,41 +10,10 @@ When mermaid starts, configuration is extracted to determine a configuration to
|
||||
|
||||
- The default configuration
|
||||
- Overrides at the site level are set by the initialize call, and will be applied to all diagrams in the site/app. The term for this is the **siteConfig**.
|
||||
- Frontmatter (v\<MERMAID_RELEASE_VERSION>+) - diagram authors can update select configuration parameters in the frontmatter of the diagram. These are applied to the render config.
|
||||
- Directives (Deprecated by Frontmatter) - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
|
||||
- Directives - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
|
||||
|
||||
**The render config** is configuration that is used when rendering by applying these configurations.
|
||||
|
||||
## Frontmatter config
|
||||
|
||||
The entire mermaid configuration (except the secure configs) can be overridden by the diagram author in the frontmatter of the diagram. The frontmatter is a YAML block at the top of the diagram.
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
title: Hello Title
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: "#00ff00"
|
||||
---
|
||||
flowchart
|
||||
Hello --> World
|
||||
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
title: Hello Title
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: "#00ff00"
|
||||
---
|
||||
flowchart
|
||||
Hello --> World
|
||||
|
||||
```
|
||||
|
||||
## Theme configuration
|
||||
|
||||
## Starting mermaid
|
||||
|
@@ -6,9 +6,6 @@
|
||||
|
||||
# Directives
|
||||
|
||||
> **Warning**
|
||||
> Directives are deprecated from v\<MERMAID_RELEASE_VERSION>. Please use the `config` key in frontmatter to pass configuration. See [Configuration](./configuration.md) for more details.
|
||||
|
||||
## Directives
|
||||
|
||||
Directives give a diagram author the capability to alter the appearance of a diagram before rendering by changing the applied configuration.
|
||||
|
@@ -16,4 +16,4 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:78](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L78)
|
||||
[mermaidAPI.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L77)
|
||||
|
@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98)
|
||||
[mermaidAPI.ts:97](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L97)
|
||||
|
||||
---
|
||||
|
||||
@@ -51,4 +51,4 @@ The svg code for the rendered graph.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88)
|
||||
[mermaidAPI.ts:87](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L87)
|
||||
|
@@ -14,7 +14,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:8](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L8)
|
||||
[config.ts:7](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L7)
|
||||
|
||||
## Functions
|
||||
|
||||
@@ -26,9 +26,9 @@ Pushes in a directive to the configuration
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type | Description |
|
||||
| :---------- | :-------------- | :----------------------- |
|
||||
| `directive` | `MermaidConfig` | The directive to push in |
|
||||
| Name | Type | Description |
|
||||
| :---------- | :---- | :----------------------- |
|
||||
| `directive` | `any` | The directive to push in |
|
||||
|
||||
#### Returns
|
||||
|
||||
@@ -36,7 +36,7 @@ Pushes in a directive to the configuration
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L188)
|
||||
[config.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L191)
|
||||
|
||||
---
|
||||
|
||||
@@ -60,7 +60,7 @@ The currentConfig
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L131)
|
||||
[config.ts:137](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L137)
|
||||
|
||||
---
|
||||
|
||||
@@ -118,7 +118,7 @@ The siteConfig
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:218](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L218)
|
||||
[config.ts:223](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L223)
|
||||
|
||||
---
|
||||
|
||||
@@ -147,7 +147,7 @@ options in-place
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L146)
|
||||
[config.ts:152](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L152)
|
||||
|
||||
---
|
||||
|
||||
@@ -242,10 +242,10 @@ The new siteConfig
|
||||
|
||||
#### Parameters
|
||||
|
||||
| Name | Type |
|
||||
| :------------ | :----------------- |
|
||||
| `siteCfg` | `MermaidConfig` |
|
||||
| `_directives` | `MermaidConfig`\[] |
|
||||
| Name | Type |
|
||||
| :------------ | :-------------- |
|
||||
| `siteCfg` | `MermaidConfig` |
|
||||
| `_directives` | `any`\[] |
|
||||
|
||||
#### Returns
|
||||
|
||||
@@ -253,7 +253,7 @@ The new siteConfig
|
||||
|
||||
#### Defined in
|
||||
|
||||
[config.ts:15](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L15)
|
||||
[config.ts:14](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.ts#L14)
|
||||
|
||||
---
|
||||
|
||||
|
@@ -10,17 +10,17 @@
|
||||
|
||||
### configKeys
|
||||
|
||||
• `Const` **configKeys**: `Set`<`string`>
|
||||
• `Const` **configKeys**: `string`\[]
|
||||
|
||||
#### Defined in
|
||||
|
||||
[defaultConfig.ts:268](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L268)
|
||||
[defaultConfig.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L266)
|
||||
|
||||
---
|
||||
|
||||
### default
|
||||
|
||||
• `Const` **default**: `RequiredDeep`<`MermaidConfig`>
|
||||
• `Const` **default**: `Partial`<`MermaidConfig`>
|
||||
|
||||
Default mermaid configuration options.
|
||||
|
||||
@@ -30,4 +30,4 @@ Non-JSON JS default values are listed in this file, e.g. functions, or
|
||||
|
||||
#### Defined in
|
||||
|
||||
[defaultConfig.ts:18](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L18)
|
||||
[defaultConfig.ts:16](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L16)
|
||||
|
@@ -25,7 +25,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82)
|
||||
[mermaidAPI.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L81)
|
||||
|
||||
## Variables
|
||||
|
||||
@@ -96,7 +96,7 @@ mermaid.initialize(config);
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673)
|
||||
[mermaidAPI.ts:668](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L668)
|
||||
|
||||
## Functions
|
||||
|
||||
@@ -127,7 +127,7 @@ Return the last node appended
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:310](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L310)
|
||||
[mermaidAPI.ts:309](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L309)
|
||||
|
||||
---
|
||||
|
||||
@@ -153,7 +153,7 @@ the cleaned up svgCode
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256)
|
||||
[mermaidAPI.ts:255](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L255)
|
||||
|
||||
---
|
||||
|
||||
@@ -179,7 +179,7 @@ the string with all the user styles
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185)
|
||||
[mermaidAPI.ts:184](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L184)
|
||||
|
||||
---
|
||||
|
||||
@@ -202,7 +202,7 @@ the string with all the user styles
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233)
|
||||
[mermaidAPI.ts:232](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L232)
|
||||
|
||||
---
|
||||
|
||||
@@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169)
|
||||
[mermaidAPI.ts:168](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L168)
|
||||
|
||||
---
|
||||
|
||||
@@ -249,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155)
|
||||
[mermaidAPI.ts:154](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L154)
|
||||
|
||||
---
|
||||
|
||||
@@ -269,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L126)
|
||||
[mermaidAPI.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L125)
|
||||
|
||||
---
|
||||
|
||||
@@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287)
|
||||
[mermaidAPI.ts:286](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L286)
|
||||
|
||||
---
|
||||
|
||||
@@ -320,4 +320,4 @@ Remove any existing elements from the given document
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:360](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L360)
|
||||
[mermaidAPI.ts:359](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L359)
|
||||
|
@@ -14,12 +14,8 @@ It is a JavaScript based diagramming and charting tool that renders Markdown-ins
|
||||
|
||||
<img src="/header.png" alt="" />
|
||||
|
||||
<div class='badges'>
|
||||
|
||||
[](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [](https://www.npmjs.com/package/mermaid) [](https://bundlephobia.com/package/mermaid) [](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [](https://www.jsdelivr.com/package/npm/mermaid) [](https://www.npmjs.com/package/mermaid) [](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [](https://twitter.com/mermaidjs_)
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Mermaid book banner -->
|
||||
|
||||
[](https://mermaid-js.github.io/mermaid/landing/)
|
||||
@@ -393,12 +389,8 @@ The above command generates files into the `dist` folder and publishes them to \
|
||||
|
||||
## Contributors
|
||||
|
||||
<div class='badges'>
|
||||
|
||||
[](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%21%22) [](https://github.com/mermaid-js/mermaid/graphs/contributors) [](https://github.com/mermaid-js/mermaid/graphs/contributors)
|
||||
|
||||
</div>
|
||||
|
||||
Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out.
|
||||
|
||||
Detailed information about how to contribute can be found in the [contribution guide](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md)
|
||||
@@ -432,14 +424,20 @@ A quick note from Knut Sveidqvist:
|
||||
_Mermaid was created by Knut Sveidqvist for easier documentation._
|
||||
|
||||
<style scoped>
|
||||
.badges > p {
|
||||
display: flex;
|
||||
}
|
||||
.badges > p > a {
|
||||
margin: 0 0.5rem;
|
||||
#contributors + p,
|
||||
#about-mermaid + p + p + blockquote + img + p
|
||||
{
|
||||
display: flex
|
||||
}
|
||||
|
||||
.dark #VPContent > div > div > div.content > div > main > div > div > img {
|
||||
#contributors + p a,
|
||||
#about-mermaid + p + p + blockquote + img + p a
|
||||
{
|
||||
margin: 0 0.5rem
|
||||
}
|
||||
|
||||
.dark #VPContent > div > div > div.content > div > main > div > div > img
|
||||
{
|
||||
filter: invert(1) hue-rotate(217deg) contrast(0.72);
|
||||
}
|
||||
</style>
|
||||
|
@@ -748,48 +748,6 @@ flowchart LR
|
||||
B1 --> B2
|
||||
```
|
||||
|
||||
#### Limitation
|
||||
|
||||
If any of a subgraph's nodes are linked to the outside, subgraph direction will be ignored. Instead the subgraph will inherit the direction of the parent graph:
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
subgraph subgraph1
|
||||
direction TB
|
||||
top1[top] --> bottom1[bottom]
|
||||
end
|
||||
subgraph subgraph2
|
||||
direction TB
|
||||
top2[top] --> bottom2[bottom]
|
||||
end
|
||||
%% ^ These subgraphs are identical, except for the links to them:
|
||||
|
||||
%% Link *to* subgraph1: subgraph1 direction is mantained
|
||||
outside --> subgraph1
|
||||
%% Link *within* subgraph2:
|
||||
%% subgraph2 inherits the direction of the top-level graph (LR)
|
||||
outside ---> top2
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
subgraph subgraph1
|
||||
direction TB
|
||||
top1[top] --> bottom1[bottom]
|
||||
end
|
||||
subgraph subgraph2
|
||||
direction TB
|
||||
top2[top] --> bottom2[bottom]
|
||||
end
|
||||
%% ^ These subgraphs are identical, except for the links to them:
|
||||
|
||||
%% Link *to* subgraph1: subgraph1 direction is mantained
|
||||
outside --> subgraph1
|
||||
%% Link *within* subgraph2:
|
||||
%% subgraph2 inherits the direction of the top-level graph (LR)
|
||||
outside ---> top2
|
||||
```
|
||||
|
||||
## Markdown Strings
|
||||
|
||||
The "Markdown Strings" feature enhances flowcharts and mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels.
|
||||
|
46
package.json
46
package.json
@@ -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.6.12",
|
||||
"packageManager": "pnpm@8.6.11",
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
@@ -60,37 +60,37 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@applitools/eyes-cypress": "^3.37.0",
|
||||
"@applitools/eyes-cypress": "^3.33.1",
|
||||
"@commitlint/cli": "^17.6.1",
|
||||
"@commitlint/config-conventional": "^17.6.1",
|
||||
"@cspell/eslint-plugin": "^7.0.1",
|
||||
"@cypress/code-coverage": "^3.11.0",
|
||||
"@rollup/plugin-typescript": "^11.1.2",
|
||||
"@cspell/eslint-plugin": "^6.31.1",
|
||||
"@cypress/code-coverage": "^3.10.7",
|
||||
"@rollup/plugin-typescript": "^11.1.1",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/eslint": "^8.44.2",
|
||||
"@types/eslint": "^8.37.0",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/mdast": "^3.0.11",
|
||||
"@types/node": "^18.16.0",
|
||||
"@types/prettier": "^3.0.0",
|
||||
"@types/prettier": "^2.7.2",
|
||||
"@types/rollup-plugin-visualizer": "^4.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^6.4.1",
|
||||
"@typescript-eslint/parser": "^6.4.1",
|
||||
"@vitest/coverage-v8": "^0.34.2",
|
||||
"@vitest/spy": "^0.34.0",
|
||||
"@vitest/ui": "^0.34.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@vitest/coverage-v8": "^0.33.0",
|
||||
"@vitest/spy": "^0.33.0",
|
||||
"@vitest/ui": "^0.33.0",
|
||||
"ajv": "^8.12.0",
|
||||
"chokidar": "^3.5.3",
|
||||
"concurrently": "^8.0.1",
|
||||
"cors": "^2.8.5",
|
||||
"cypress": "^12.17.4",
|
||||
"cypress": "^12.10.0",
|
||||
"cypress-image-snapshot": "^4.0.1",
|
||||
"esbuild": "^0.19.2",
|
||||
"eslint": "^8.47.0",
|
||||
"eslint-config-prettier": "^9.0.0",
|
||||
"eslint-plugin-cypress": "^2.14.0",
|
||||
"esbuild": "^0.19.0",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-cypress": "^2.13.2",
|
||||
"eslint-plugin-html": "^7.1.0",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"eslint-plugin-jsdoc": "^46.0.0",
|
||||
@@ -111,19 +111,19 @@
|
||||
"nyc": "^15.1.0",
|
||||
"path-browserify": "^1.0.1",
|
||||
"pnpm": "^8.6.8",
|
||||
"prettier": "^3.0.2",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier-plugin-jsdoc": "^0.4.2",
|
||||
"rimraf": "^5.0.0",
|
||||
"rollup-plugin-visualizer": "^5.9.2",
|
||||
"start-server-and-test": "^2.0.0",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.1.6",
|
||||
"vite": "^4.4.9",
|
||||
"vite-plugin-istanbul": "^5.0.0",
|
||||
"vitest": "^0.34.2"
|
||||
"typescript": "^5.1.3",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-istanbul": "^4.1.0",
|
||||
"vitest": "^0.33.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.17.1"
|
||||
"node": "18.17.0"
|
||||
},
|
||||
"nyc": {
|
||||
"report-dir": "coverage/cypress"
|
||||
|
@@ -19,7 +19,6 @@
|
||||
"mermaid"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
},
|
||||
"repository": {
|
||||
|
@@ -1,22 +0,0 @@
|
||||
{
|
||||
"detectiveOptions": {
|
||||
"ts": {
|
||||
"skipTypeImports": true
|
||||
},
|
||||
"es6": {
|
||||
"skipTypeImports": true
|
||||
}
|
||||
},
|
||||
"fileExtensions": [
|
||||
"js",
|
||||
"ts"
|
||||
],
|
||||
"excludeRegExp": [
|
||||
"node_modules",
|
||||
"docs",
|
||||
"vitepress",
|
||||
"detector",
|
||||
"Detector"
|
||||
],
|
||||
"tsConfig": "./tsconfig.json"
|
||||
}
|
@@ -38,7 +38,6 @@
|
||||
"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",
|
||||
"checkCircle": "npx madge --circular ./src",
|
||||
"release": "pnpm build",
|
||||
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build"
|
||||
},
|
||||
@@ -78,13 +77,12 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@adobe/jsonschema2md": "^7.1.4",
|
||||
"@types/d3-scale": "^4.0.3",
|
||||
"@types/d3-scale-chromatic": "^3.0.0",
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/d3-sankey": "^0.12.1",
|
||||
"@types/d3-scale": "^4.0.3",
|
||||
"@types/d3-scale-chromatic": "^3.0.0",
|
||||
"@types/d3-selection": "^3.0.5",
|
||||
"@types/d3-shape": "^3.1.1",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
@@ -113,7 +111,6 @@
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rimraf": "^5.0.0",
|
||||
"start-server-and-test": "^2.0.0",
|
||||
"type-fest": "^4.1.0",
|
||||
"typedoc": "^0.24.5",
|
||||
"typedoc-plugin-markdown": "^3.15.2",
|
||||
"typescript": "^5.0.4",
|
||||
|
@@ -24,7 +24,6 @@ import _Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
|
||||
|
||||
// Workaround for wrong AJV types, see
|
||||
// https://github.com/ajv-validator/ajv/issues/2132#issuecomment-1290409907
|
||||
// @ts-ignore Incorrect types
|
||||
const Ajv2019 = _Ajv2019 as unknown as typeof _Ajv2019.default;
|
||||
|
||||
// !!! -- The config.type.js file is created by this script -- !!!
|
||||
|
@@ -48,7 +48,7 @@ export class Diagram {
|
||||
// extractFrontMatter().
|
||||
|
||||
this.parser.parse = (text: string) =>
|
||||
originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective)));
|
||||
originalParse(cleanupComments(extractFrontMatter(text, this.db)));
|
||||
|
||||
this.parser.parser.yy = this.db;
|
||||
this.init = diagram.init;
|
||||
|
@@ -1,7 +1,7 @@
|
||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||
|
||||
/**
|
||||
* assignWithDepth Extends the functionality of {@link Object.assign} with the
|
||||
* assignWithDepth Extends the functionality of {@link ObjectConstructor.assign} with the
|
||||
* ability to merge arbitrary-depth objects For each key in src with path `k` (recursively)
|
||||
* performs an Object.assign(dst[`k`], src[`k`]) with a slight change from the typical handling of
|
||||
* undefined for dst[`k`]: instead of raising an error, dst[`k`] is auto-initialized to `{}` and
|
||||
|
@@ -1,13 +1,11 @@
|
||||
/* eslint-disable @typescript-eslint/no-non-null-assertion */
|
||||
import * as configApi from './config.js';
|
||||
import type { MermaidConfig } from './config.type.js';
|
||||
|
||||
describe('when working with site config', () => {
|
||||
describe('when working with site config', function () {
|
||||
beforeEach(() => {
|
||||
// Resets the site config to default config
|
||||
configApi.setSiteConfig({});
|
||||
});
|
||||
it('should set site config and config properly', () => {
|
||||
it('should set site config and config properly', function () {
|
||||
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
const config_1 = configApi.getSiteConfig();
|
||||
@@ -16,26 +14,19 @@ describe('when working with site config', () => {
|
||||
expect(config_1.fontSize).toEqual(config_0.fontSize);
|
||||
expect(config_1).toEqual(config_2);
|
||||
});
|
||||
it('should respect secure keys when applying directives', () => {
|
||||
const config_0: MermaidConfig = {
|
||||
it('should respect secure keys when applying directives', function () {
|
||||
const config_0 = {
|
||||
fontFamily: 'foo-font',
|
||||
securityLevel: 'strict', // can't be changed
|
||||
fontSize: 12345, // can't be changed
|
||||
secure: [...configApi.defaultConfig.secure!, 'fontSize'],
|
||||
};
|
||||
configApi.setSiteConfig(config_0);
|
||||
const directive: MermaidConfig = {
|
||||
fontFamily: 'baf',
|
||||
// fontSize and securityLevel shouldn't be changed
|
||||
fontSize: 54321,
|
||||
securityLevel: 'loose',
|
||||
};
|
||||
const cfg: MermaidConfig = configApi.updateCurrentConfig(config_0, [directive]);
|
||||
const directive = { fontFamily: 'baf', fontSize: 54321 /* fontSize shouldn't be changed */ };
|
||||
const cfg = configApi.updateCurrentConfig(config_0, [directive]);
|
||||
expect(cfg.fontFamily).toEqual(directive.fontFamily);
|
||||
expect(cfg.fontSize).toBe(config_0.fontSize);
|
||||
expect(cfg.securityLevel).toBe(config_0.securityLevel);
|
||||
});
|
||||
it('should allow setting partial options', () => {
|
||||
it('should allow setting partial options', function () {
|
||||
const defaultConfig = configApi.getConfig();
|
||||
|
||||
configApi.setConfig({
|
||||
@@ -51,7 +42,7 @@ describe('when working with site config', () => {
|
||||
updatedConfig.quadrantChart!.chartWidth
|
||||
);
|
||||
});
|
||||
it('should set reset config properly', () => {
|
||||
it('should set reset config properly', function () {
|
||||
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
const config_1 = { fontFamily: 'baf' };
|
||||
@@ -64,7 +55,7 @@ describe('when working with site config', () => {
|
||||
const config_4 = configApi.getSiteConfig();
|
||||
expect(config_4.fontFamily).toEqual(config_0.fontFamily);
|
||||
});
|
||||
it('should set global reset config properly', () => {
|
||||
it('should set global reset config properly', function () {
|
||||
const config_0 = { fontFamily: 'foo-font', fontSize: 150 };
|
||||
configApi.setSiteConfig(config_0);
|
||||
const config_1 = configApi.getSiteConfig();
|
||||
|
@@ -3,16 +3,15 @@ import { log } from './logger.js';
|
||||
import theme from './themes/index.js';
|
||||
import config from './defaultConfig.js';
|
||||
import type { MermaidConfig } from './config.type.js';
|
||||
import { sanitizeDirective } from './utils.js';
|
||||
|
||||
export const defaultConfig: MermaidConfig = Object.freeze(config);
|
||||
|
||||
let siteConfig: MermaidConfig = assignWithDepth({}, defaultConfig);
|
||||
let configFromInitialize: MermaidConfig;
|
||||
let directives: MermaidConfig[] = [];
|
||||
let directives: any[] = [];
|
||||
let currentConfig: MermaidConfig = assignWithDepth({}, defaultConfig);
|
||||
|
||||
export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: MermaidConfig[]) => {
|
||||
export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: any[]) => {
|
||||
// start with config being the siteConfig
|
||||
let cfg: MermaidConfig = assignWithDepth({}, siteCfg);
|
||||
// let sCfg = assignWithDepth(defaultConfig, siteConfigDelta);
|
||||
@@ -21,6 +20,7 @@ export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: Mermaid
|
||||
let sumOfDirectives: MermaidConfig = {};
|
||||
for (const d of _directives) {
|
||||
sanitize(d);
|
||||
|
||||
// Apply the data from the directive where the the overrides the themeVariables
|
||||
sumOfDirectives = assignWithDepth(sumOfDirectives, d);
|
||||
}
|
||||
@@ -111,6 +111,12 @@ export const getSiteConfig = (): MermaidConfig => {
|
||||
* @returns The currentConfig merged with the sanitized conf
|
||||
*/
|
||||
export const setConfig = (conf: MermaidConfig): MermaidConfig => {
|
||||
// sanitize(conf);
|
||||
// Object.keys(conf).forEach(key => {
|
||||
// const manipulator = manipulators[key];
|
||||
// conf[key] = manipulator ? manipulator(conf[key]) : conf[key];
|
||||
// });
|
||||
|
||||
checkConfig(conf);
|
||||
assignWithDepth(currentConfig, conf);
|
||||
|
||||
@@ -144,12 +150,9 @@ export const getConfig = (): MermaidConfig => {
|
||||
* @param options - The potential setConfig parameter
|
||||
*/
|
||||
export const sanitize = (options: any) => {
|
||||
if (!options) {
|
||||
return;
|
||||
}
|
||||
// Checking that options are not in the list of excluded options
|
||||
['secure', ...(siteConfig.secure ?? [])].forEach((key) => {
|
||||
if (Object.hasOwn(options, key)) {
|
||||
if (options[key] !== undefined) {
|
||||
// DO NOT attempt to print options[key] within `${}` as a malicious script
|
||||
// can exploit the logger's attempt to stringify the value and execute arbitrary code
|
||||
log.debug(`Denied attempt to modify a secure key ${key}`, options[key]);
|
||||
@@ -159,7 +162,7 @@ export const sanitize = (options: any) => {
|
||||
|
||||
// Check that there no attempts of prototype pollution
|
||||
Object.keys(options).forEach((key) => {
|
||||
if (key.startsWith('__')) {
|
||||
if (key.indexOf('__') === 0) {
|
||||
delete options[key];
|
||||
}
|
||||
});
|
||||
@@ -185,14 +188,16 @@ export const sanitize = (options: any) => {
|
||||
*
|
||||
* @param directive - The directive to push in
|
||||
*/
|
||||
export const addDirective = (directive: MermaidConfig) => {
|
||||
sanitizeDirective(directive);
|
||||
|
||||
// If the directive has a fontFamily, but no themeVariables, add the fontFamily to the themeVariables
|
||||
if (directive.fontFamily && (!directive.themeVariables || !directive.themeVariables.fontFamily)) {
|
||||
directive.themeVariables = { fontFamily: directive.fontFamily };
|
||||
export const addDirective = (directive: any) => {
|
||||
if (directive.fontFamily) {
|
||||
if (!directive.themeVariables) {
|
||||
directive.themeVariables = { fontFamily: directive.fontFamily };
|
||||
} else {
|
||||
if (!directive.themeVariables.fontFamily) {
|
||||
directive.themeVariables = { fontFamily: directive.fontFamily };
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
directives.push(directive);
|
||||
updateCurrentConfig(siteConfig, directives);
|
||||
};
|
||||
|
@@ -1,7 +1,5 @@
|
||||
import type { RequiredDeep } from 'type-fest';
|
||||
|
||||
import theme from './themes/index.js';
|
||||
import type { MermaidConfig } from './config.type.js';
|
||||
import { type MermaidConfig } from './config.type.js';
|
||||
|
||||
// Uses our custom Vite jsonSchemaPlugin to load only the default values from
|
||||
// our JSON Schema
|
||||
@@ -15,7 +13,7 @@ import defaultConfigJson from './schemas/config.schema.yaml?only-defaults=true';
|
||||
* Non-JSON JS default values are listed in this file, e.g. functions, or
|
||||
* `undefined` (explicitly set so that `configKeys` finds them).
|
||||
*/
|
||||
const config: RequiredDeep<MermaidConfig> = {
|
||||
const config: Partial<MermaidConfig> = {
|
||||
...defaultConfigJson,
|
||||
// Set, even though they're `undefined` so that `configKeys` finds these keys
|
||||
// TODO: Should we replace these with `null` so that they can go in the JSON Schema?
|
||||
@@ -234,7 +232,7 @@ const config: RequiredDeep<MermaidConfig> = {
|
||||
},
|
||||
pie: {
|
||||
...defaultConfigJson.pie,
|
||||
useWidth: 984,
|
||||
useWidth: undefined,
|
||||
},
|
||||
requirement: {
|
||||
...defaultConfigJson.requirement,
|
||||
@@ -265,5 +263,5 @@ const keyify = (obj: any, prefix = ''): string[] =>
|
||||
return [...res, prefix + el];
|
||||
}, []);
|
||||
|
||||
export const configKeys: Set<string> = new Set(keyify(config, ''));
|
||||
export const configKeys: string[] = keyify(config, '');
|
||||
export default config;
|
||||
|
@@ -6,10 +6,14 @@ import type {
|
||||
DiagramLoader,
|
||||
ExternalDiagramDefinition,
|
||||
} from './types.js';
|
||||
import { anyCommentRegex, directiveRegex, frontMatterRegex } from './regexes.js';
|
||||
import { frontMatterRegex } from './frontmatter.js';
|
||||
import { getDiagram, registerDiagram } from './diagramAPI.js';
|
||||
import { UnknownDiagramError } from '../errors.js';
|
||||
|
||||
export const detectors: Record<string, DetectorRecord> = {};
|
||||
const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
|
||||
const anyComment = /\s*%%.*\n/gm;
|
||||
|
||||
const detectors: Record<string, DetectorRecord> = {};
|
||||
|
||||
/**
|
||||
* Detects the type of the graph text.
|
||||
@@ -34,10 +38,7 @@ export const detectors: Record<string, DetectorRecord> = {};
|
||||
* @returns A graph definition key
|
||||
*/
|
||||
export const detectType = function (text: string, config?: MermaidConfig): string {
|
||||
text = text
|
||||
.replace(frontMatterRegex, '')
|
||||
.replace(directiveRegex, '')
|
||||
.replace(anyCommentRegex, '\n');
|
||||
text = text.replace(frontMatterRegex, '').replace(directive, '').replace(anyComment, '\n');
|
||||
for (const [key, { detector }] of Object.entries(detectors)) {
|
||||
const diagram = detector(text, config);
|
||||
if (diagram) {
|
||||
@@ -69,6 +70,39 @@ export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinitio
|
||||
}
|
||||
};
|
||||
|
||||
export const loadRegisteredDiagrams = async () => {
|
||||
log.debug(`Loading registered diagrams`);
|
||||
// Load all lazy loaded diagrams in parallel
|
||||
const results = await Promise.allSettled(
|
||||
Object.entries(detectors).map(async ([key, { detector, loader }]) => {
|
||||
if (loader) {
|
||||
try {
|
||||
getDiagram(key);
|
||||
} catch (error) {
|
||||
try {
|
||||
// Register diagram if it is not already registered
|
||||
const { diagram, id } = await loader();
|
||||
registerDiagram(id, diagram, detector);
|
||||
} catch (err) {
|
||||
// Remove failed diagram from detectors
|
||||
log.error(`Failed to load external diagram with key ${key}. Removing from detectors.`);
|
||||
delete detectors[key];
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
const failed = results.filter((result) => result.status === 'rejected');
|
||||
if (failed.length > 0) {
|
||||
log.error(`Failed to load ${failed.length} external diagrams`);
|
||||
for (const res of failed) {
|
||||
log.error(res);
|
||||
}
|
||||
throw new Error(`Failed to load ${failed.length} external diagrams`);
|
||||
}
|
||||
};
|
||||
|
||||
export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => {
|
||||
if (detectors[key]) {
|
||||
log.error(`Detector with key ${key} already exists`);
|
||||
|
@@ -5,7 +5,7 @@ import er from '../diagrams/er/erDetector.js';
|
||||
import git from '../diagrams/git/gitGraphDetector.js';
|
||||
import gantt from '../diagrams/gantt/ganttDetector.js';
|
||||
import { info } from '../diagrams/info/infoDetector.js';
|
||||
import { pie } from '../diagrams/pie/pieDetector.js';
|
||||
import pie from '../diagrams/pie/pieDetector.js';
|
||||
import quadrantChart from '../diagrams/quadrant-chart/quadrantDetector.js';
|
||||
import requirement from '../diagrams/requirement/requirementDetector.js';
|
||||
import sequence from '../diagrams/sequence/sequenceDetector.js';
|
||||
|
@@ -4,7 +4,7 @@ import { getConfig as _getConfig } from '../config.js';
|
||||
import { sanitizeText as _sanitizeText } from '../diagrams/common/common.js';
|
||||
import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox.js';
|
||||
import { addStylesForDiagram } from '../styles.js';
|
||||
import type { DiagramDefinition, DiagramDetector } from './types.js';
|
||||
import { DiagramDefinition, DiagramDetector } from './types.js';
|
||||
import * as _commonDb from '../commonDb.js';
|
||||
import { parseDirective as _parseDirective } from '../directiveUtils.js';
|
||||
|
||||
|
@@ -2,13 +2,8 @@ import { vi } from 'vitest';
|
||||
import { extractFrontMatter } from './frontmatter.js';
|
||||
|
||||
const dbMock = () => ({ setDiagramTitle: vi.fn() });
|
||||
const setConfigMock = vi.fn();
|
||||
|
||||
describe('extractFrontmatter', () => {
|
||||
beforeEach(() => {
|
||||
setConfigMock.mockClear();
|
||||
});
|
||||
|
||||
it('returns text unchanged if no frontmatter', () => {
|
||||
expect(extractFrontMatter('diagram', dbMock())).toEqual('diagram');
|
||||
});
|
||||
@@ -80,21 +75,4 @@ describe('extractFrontmatter', () => {
|
||||
'tag suffix cannot contain exclamation marks'
|
||||
);
|
||||
});
|
||||
|
||||
it('handles frontmatter with config', () => {
|
||||
const text = `---
|
||||
title: hello
|
||||
config:
|
||||
graph:
|
||||
string: hello
|
||||
number: 14
|
||||
boolean: false
|
||||
array: [1, 2, 3]
|
||||
---
|
||||
diagram`;
|
||||
expect(extractFrontMatter(text, {}, setConfigMock)).toEqual('diagram');
|
||||
expect(setConfigMock).toHaveBeenCalledWith({
|
||||
graph: { string: 'hello', number: 14, boolean: false, array: [1, 2, 3] },
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,53 +1,46 @@
|
||||
import type { MermaidConfig } from '../config.type.js';
|
||||
import { frontMatterRegex } from './regexes.js';
|
||||
import type { DiagramDB } from './types.js';
|
||||
import { DiagramDB } from './types.js';
|
||||
// The "* as yaml" part is necessary for tree-shaking
|
||||
import * as yaml from 'js-yaml';
|
||||
|
||||
interface FrontMatterMetadata {
|
||||
// Match Jekyll-style front matter blocks (https://jekyllrb.com/docs/front-matter/).
|
||||
// Based on regex used by Jekyll: https://github.com/jekyll/jekyll/blob/6dd3cc21c40b98054851846425af06c64f9fb466/lib/jekyll/document.rb#L10
|
||||
// Note that JS doesn't support the "\A" anchor, which means we can't use
|
||||
// multiline mode.
|
||||
// Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents
|
||||
export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s;
|
||||
|
||||
type FrontMatterMetadata = {
|
||||
title?: string;
|
||||
// Allows custom display modes. Currently used for compact mode in gantt charts.
|
||||
displayMode?: string;
|
||||
config?: MermaidConfig;
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* Extract and parse frontmatter from text, if present, and sets appropriate
|
||||
* properties in the provided db.
|
||||
* @param text - The text that may have a YAML frontmatter.
|
||||
* @param db - Diagram database, could be of any diagram.
|
||||
* @param setDiagramConfig - Optional function to set diagram config.
|
||||
* @returns text with frontmatter stripped out
|
||||
*/
|
||||
export function extractFrontMatter(
|
||||
text: string,
|
||||
db: DiagramDB,
|
||||
setDiagramConfig?: (config: MermaidConfig) => void
|
||||
): string {
|
||||
export function extractFrontMatter(text: string, db: DiagramDB): string {
|
||||
const matches = text.match(frontMatterRegex);
|
||||
if (!matches) {
|
||||
if (matches) {
|
||||
const parsed: FrontMatterMetadata = yaml.load(matches[1], {
|
||||
// To keep things simple, only allow strings, arrays, and plain objects.
|
||||
// https://www.yaml.org/spec/1.2/spec.html#id2802346
|
||||
schema: yaml.FAILSAFE_SCHEMA,
|
||||
}) as FrontMatterMetadata;
|
||||
|
||||
if (parsed?.title) {
|
||||
db.setDiagramTitle?.(parsed.title);
|
||||
}
|
||||
|
||||
if (parsed?.displayMode) {
|
||||
db.setDisplayMode?.(parsed.displayMode);
|
||||
}
|
||||
|
||||
return text.slice(matches[0].length);
|
||||
} else {
|
||||
return text;
|
||||
}
|
||||
|
||||
const parsed: FrontMatterMetadata = yaml.load(matches[1], {
|
||||
// To support config, we need JSON schema.
|
||||
// https://www.yaml.org/spec/1.2/spec.html#id2803231
|
||||
schema: yaml.JSON_SCHEMA,
|
||||
}) as FrontMatterMetadata;
|
||||
|
||||
if (parsed?.title) {
|
||||
// toString() is necessary because YAML could parse the title as a number/boolean
|
||||
db.setDiagramTitle?.(parsed.title.toString());
|
||||
}
|
||||
|
||||
if (parsed?.displayMode) {
|
||||
// toString() is necessary because YAML could parse the title as a number/boolean
|
||||
db.setDisplayMode?.(parsed.displayMode.toString());
|
||||
}
|
||||
|
||||
if (parsed?.config) {
|
||||
setDiagramConfig?.(parsed.config);
|
||||
}
|
||||
|
||||
return text.slice(matches[0].length);
|
||||
}
|
||||
|
@@ -1,36 +0,0 @@
|
||||
import { log } from '../logger.js';
|
||||
import { detectors } from './detectType.js';
|
||||
import { getDiagram, registerDiagram } from './diagramAPI.js';
|
||||
|
||||
export const loadRegisteredDiagrams = async () => {
|
||||
log.debug(`Loading registered diagrams`);
|
||||
// Load all lazy loaded diagrams in parallel
|
||||
const results = await Promise.allSettled(
|
||||
Object.entries(detectors).map(async ([key, { detector, loader }]) => {
|
||||
if (loader) {
|
||||
try {
|
||||
getDiagram(key);
|
||||
} catch (error) {
|
||||
try {
|
||||
// Register diagram if it is not already registered
|
||||
const { diagram, id } = await loader();
|
||||
registerDiagram(id, diagram, detector);
|
||||
} catch (err) {
|
||||
// Remove failed diagram from detectors
|
||||
log.error(`Failed to load external diagram with key ${key}. Removing from detectors.`);
|
||||
delete detectors[key];
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
);
|
||||
const failed = results.filter((result) => result.status === 'rejected');
|
||||
if (failed.length > 0) {
|
||||
log.error(`Failed to load ${failed.length} external diagrams`);
|
||||
for (const res of failed) {
|
||||
log.error(res);
|
||||
}
|
||||
throw new Error(`Failed to load ${failed.length} external diagrams`);
|
||||
}
|
||||
};
|
@@ -1,11 +0,0 @@
|
||||
// Match Jekyll-style front matter blocks (https://jekyllrb.com/docs/front-matter/).
|
||||
// Based on regex used by Jekyll: https://github.com/jekyll/jekyll/blob/6dd3cc21c40b98054851846425af06c64f9fb466/lib/jekyll/document.rb#L10
|
||||
// Note that JS doesn't support the "\A" anchor, which means we can't use
|
||||
// multiline mode.
|
||||
// Relevant YAML spec: https://yaml.org/spec/1.2.2/#914-explicit-documents
|
||||
export const frontMatterRegex = /^-{3}\s*[\n\r](.*?)[\n\r]-{3}\s*[\n\r]+/s;
|
||||
|
||||
export const directiveRegex =
|
||||
/%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
|
||||
|
||||
export const anyCommentRegex = /\s*%%.*\n/gm;
|
@@ -1,5 +1,5 @@
|
||||
import { Diagram } from '../Diagram.js';
|
||||
import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js';
|
||||
import type { MermaidConfig } from '../config.type.js';
|
||||
import type * as d3 from 'd3';
|
||||
|
||||
export interface InjectUtils {
|
||||
@@ -16,19 +16,11 @@ export interface InjectUtils {
|
||||
* Generic Diagram DB that may apply to any diagram type.
|
||||
*/
|
||||
export interface DiagramDB {
|
||||
// config
|
||||
getConfig?: () => BaseDiagramConfig | undefined;
|
||||
|
||||
// db
|
||||
clear?: () => void;
|
||||
setDiagramTitle?: (title: string) => void;
|
||||
getDiagramTitle?: () => string;
|
||||
setAccTitle?: (title: string) => void;
|
||||
getAccTitle?: () => string;
|
||||
setAccDescription?: (describetion: string) => void;
|
||||
getAccDescription?: () => string;
|
||||
|
||||
setDisplayMode?: (title: string) => void;
|
||||
getAccTitle?: () => string;
|
||||
getAccDescription?: () => string;
|
||||
bindFunctions?: (element: Element) => void;
|
||||
}
|
||||
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
|
@@ -1,15 +1,15 @@
|
||||
import { sanitizeText, removeScript, parseGenericTypes } from './common.js';
|
||||
|
||||
describe('when securityLevel is antiscript, all script must be removed', () => {
|
||||
describe('when securityLevel is antiscript, all script must be removed', function () {
|
||||
/**
|
||||
* @param original - The original text
|
||||
* @param result - The expected sanitized text
|
||||
* @param {string} original The original text
|
||||
* @param {string} result The expected sanitized text
|
||||
*/
|
||||
function compareRemoveScript(original: string, result: string) {
|
||||
function compareRemoveScript(original, result) {
|
||||
expect(removeScript(original).trim()).toEqual(result);
|
||||
}
|
||||
|
||||
it('should remove all script block, script inline.', () => {
|
||||
it('should remove all script block, script inline.', function () {
|
||||
const labelString = `1
|
||||
Act1: Hello 1<script src="http://abc.com/script1.js"></script>1
|
||||
<b>Act2</b>:
|
||||
@@ -25,7 +25,7 @@ describe('when securityLevel is antiscript, all script must be removed', () => {
|
||||
compareRemoveScript(labelString, exactlyString);
|
||||
});
|
||||
|
||||
it('should remove all javascript urls', () => {
|
||||
it('should remove all javascript urls', function () {
|
||||
compareRemoveScript(
|
||||
`This is a <a href="javascript:runHijackingScript();">clean link</a> + <a href="javascript:runHijackingScript();">clean link</a>
|
||||
and <a href="javascript:bipassedMining();">me too</a>`,
|
||||
@@ -34,11 +34,11 @@ describe('when securityLevel is antiscript, all script must be removed', () => {
|
||||
);
|
||||
});
|
||||
|
||||
it('should detect malicious images', () => {
|
||||
it('should detect malicious images', function () {
|
||||
compareRemoveScript(`<img onerror="alert('hello');">`, `<img>`);
|
||||
});
|
||||
|
||||
it('should detect iframes', () => {
|
||||
it('should detect iframes', function () {
|
||||
compareRemoveScript(
|
||||
`<iframe src="http://abc.com/script1.js"></iframe>
|
||||
<iframe src="http://example.com/iframeexample"></iframe>`,
|
||||
@@ -47,8 +47,8 @@ describe('when securityLevel is antiscript, all script must be removed', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('Sanitize text', () => {
|
||||
it('should remove script tag', () => {
|
||||
describe('Sanitize text', function () {
|
||||
it('should remove script tag', function () {
|
||||
const maliciousStr = 'javajavascript:script:alert(1)';
|
||||
const result = sanitizeText(maliciousStr, {
|
||||
securityLevel: 'strict',
|
||||
@@ -58,8 +58,8 @@ describe('Sanitize text', () => {
|
||||
});
|
||||
});
|
||||
|
||||
describe('generic parser', () => {
|
||||
it('should parse generic types', () => {
|
||||
describe('generic parser', function () {
|
||||
it('should parse generic types', function () {
|
||||
expect(parseGenericTypes('test~T~')).toEqual('test<T>');
|
||||
expect(parseGenericTypes('test~Array~Array~string~~~')).toEqual('test<Array<Array<string>>>');
|
||||
expect(parseGenericTypes('test~Array~Array~string[]~~~')).toEqual(
|
@@ -1,7 +1,6 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import { MermaidConfig } from '../../config.type.js';
|
||||
|
||||
// Remove and ignore br:s
|
||||
export const lineBreakRegex = /<br\s*\/?>/gi;
|
||||
|
||||
/**
|
||||
|
@@ -1,58 +0,0 @@
|
||||
export interface RectData {
|
||||
x: number;
|
||||
y: number;
|
||||
fill: string;
|
||||
width: number;
|
||||
height: number;
|
||||
stroke: string;
|
||||
class?: string;
|
||||
color?: string;
|
||||
rx?: number;
|
||||
ry?: number;
|
||||
attrs?: Record<string, string | number>;
|
||||
anchor?: string;
|
||||
}
|
||||
|
||||
export interface Bound {
|
||||
startx: number;
|
||||
stopx: number;
|
||||
starty: number;
|
||||
stopy: number;
|
||||
fill: string;
|
||||
stroke: string;
|
||||
}
|
||||
|
||||
export interface TextData {
|
||||
x: number;
|
||||
y: number;
|
||||
anchor: string;
|
||||
text: string;
|
||||
textMargin: number;
|
||||
class?: string;
|
||||
}
|
||||
|
||||
export interface TextObject {
|
||||
x: number;
|
||||
y: number;
|
||||
width: number;
|
||||
height: number;
|
||||
fill?: string;
|
||||
anchor?: string;
|
||||
'text-anchor': string;
|
||||
style: string;
|
||||
textMargin: number;
|
||||
rx: number;
|
||||
ry: number;
|
||||
tspan: boolean;
|
||||
valign?: string;
|
||||
}
|
||||
|
||||
export type D3RectElement = d3.Selection<SVGRectElement, unknown, Element | null, unknown>;
|
||||
|
||||
export type D3UseElement = d3.Selection<SVGUseElement, unknown, Element | null, unknown>;
|
||||
|
||||
export type D3ImageElement = d3.Selection<SVGImageElement, unknown, Element | null, unknown>;
|
||||
|
||||
export type D3TextElement = d3.Selection<SVGTextElement, unknown, Element | null, unknown>;
|
||||
|
||||
export type D3TSpanElement = d3.Selection<SVGTSpanElement, unknown, Element | null, unknown>;
|
114
packages/mermaid/src/diagrams/common/svgDrawCommon.js
Normal file
114
packages/mermaid/src/diagrams/common/svgDrawCommon.js
Normal file
@@ -0,0 +1,114 @@
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
const rectElem = elem.append('rect');
|
||||
rectElem.attr('x', rectData.x);
|
||||
rectElem.attr('y', rectData.y);
|
||||
rectElem.attr('fill', rectData.fill);
|
||||
rectElem.attr('stroke', rectData.stroke);
|
||||
rectElem.attr('width', rectData.width);
|
||||
rectElem.attr('height', rectData.height);
|
||||
rectElem.attr('rx', rectData.rx);
|
||||
rectElem.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
|
||||
for (let attrKey in rectData.attrs) {
|
||||
rectElem.attr(attrKey, rectData.attrs[attrKey]);
|
||||
}
|
||||
}
|
||||
|
||||
if (rectData.class !== 'undefined') {
|
||||
rectElem.attr('class', rectData.class);
|
||||
}
|
||||
|
||||
return rectElem;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a background rectangle
|
||||
*
|
||||
* @param {any} elem Diagram (reference for bounds)
|
||||
* @param {any} bounds Shape of the rectangle
|
||||
*/
|
||||
export const drawBackgroundRect = function (elem, bounds) {
|
||||
const rectElem = drawRect(elem, {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
width: bounds.stopx - bounds.startx,
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
stroke: bounds.stroke,
|
||||
class: 'rect',
|
||||
});
|
||||
rectElem.lower();
|
||||
};
|
||||
|
||||
export const drawText = function (elem, textData) {
|
||||
// Remove and ignore br:s
|
||||
const nText = textData.text.replace(/<br\s*\/?>/gi, ' ');
|
||||
|
||||
const textElem = elem.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.attr('class', 'legend');
|
||||
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
|
||||
if (textData.class !== undefined) {
|
||||
textElem.attr('class', textData.class);
|
||||
}
|
||||
|
||||
const span = textElem.append('tspan');
|
||||
span.attr('x', textData.x + textData.textMargin * 2);
|
||||
span.text(nText);
|
||||
|
||||
return textElem;
|
||||
};
|
||||
|
||||
export const drawImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('image');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
var sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', sanitizedLink);
|
||||
};
|
||||
|
||||
export const drawEmbeddedImage = function (elem, x, y, link) {
|
||||
const imageElem = elem.append('use');
|
||||
imageElem.attr('x', x);
|
||||
imageElem.attr('y', y);
|
||||
const sanitizedLink = sanitizeUrl(link);
|
||||
imageElem.attr('xlink:href', '#' + sanitizedLink);
|
||||
};
|
||||
|
||||
export const getNoteRect = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: '#EDF2AE',
|
||||
stroke: '#666',
|
||||
anchor: 'start',
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
};
|
||||
|
||||
export const getTextObj = function () {
|
||||
return {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: undefined,
|
||||
anchor: undefined,
|
||||
'text-anchor': 'start',
|
||||
style: '#666',
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
tspan: true,
|
||||
valign: undefined,
|
||||
};
|
||||
};
|
@@ -1,126 +0,0 @@
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
import type { Group, SVG } from '../../diagram-api/types.js';
|
||||
import type {
|
||||
Bound,
|
||||
D3ImageElement,
|
||||
D3RectElement,
|
||||
D3TSpanElement,
|
||||
D3TextElement,
|
||||
D3UseElement,
|
||||
RectData,
|
||||
TextData,
|
||||
TextObject,
|
||||
} from './commonTypes.js';
|
||||
import { lineBreakRegex } from './common.js';
|
||||
|
||||
export const drawRect = (element: SVG | Group, rectData: RectData): D3RectElement => {
|
||||
const rectElement: D3RectElement = element.append('rect');
|
||||
rectElement.attr('x', rectData.x);
|
||||
rectElement.attr('y', rectData.y);
|
||||
rectElement.attr('fill', rectData.fill);
|
||||
rectElement.attr('stroke', rectData.stroke);
|
||||
rectElement.attr('width', rectData.width);
|
||||
rectElement.attr('height', rectData.height);
|
||||
rectData.rx !== undefined && rectElement.attr('rx', rectData.rx);
|
||||
rectData.ry !== undefined && rectElement.attr('ry', rectData.ry);
|
||||
|
||||
if (rectData.attrs !== undefined) {
|
||||
for (const attrKey in rectData.attrs) {
|
||||
rectElement.attr(attrKey, rectData.attrs[attrKey]);
|
||||
}
|
||||
}
|
||||
|
||||
rectData.class !== undefined && rectElement.attr('class', rectData.class);
|
||||
|
||||
return rectElement;
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a background rectangle
|
||||
*
|
||||
* @param element - Diagram (reference for bounds)
|
||||
* @param bounds - Shape of the rectangle
|
||||
*/
|
||||
export const drawBackgroundRect = (element: SVG | Group, bounds: Bound): void => {
|
||||
const rectData: RectData = {
|
||||
x: bounds.startx,
|
||||
y: bounds.starty,
|
||||
width: bounds.stopx - bounds.startx,
|
||||
height: bounds.stopy - bounds.starty,
|
||||
fill: bounds.fill,
|
||||
stroke: bounds.stroke,
|
||||
class: 'rect',
|
||||
};
|
||||
const rectElement: D3RectElement = drawRect(element, rectData);
|
||||
rectElement.lower();
|
||||
};
|
||||
|
||||
export const drawText = (element: SVG | Group, textData: TextData): D3TextElement => {
|
||||
const nText: string = textData.text.replace(lineBreakRegex, ' ');
|
||||
|
||||
const textElem: D3TextElement = element.append('text');
|
||||
textElem.attr('x', textData.x);
|
||||
textElem.attr('y', textData.y);
|
||||
textElem.attr('class', 'legend');
|
||||
|
||||
textElem.style('text-anchor', textData.anchor);
|
||||
textData.class !== undefined && textElem.attr('class', textData.class);
|
||||
|
||||
const tspan: D3TSpanElement = textElem.append('tspan');
|
||||
tspan.attr('x', textData.x + textData.textMargin * 2);
|
||||
tspan.text(nText);
|
||||
|
||||
return textElem;
|
||||
};
|
||||
|
||||
export const drawImage = (elem: SVG | Group, x: number, y: number, link: string): void => {
|
||||
const imageElement: D3ImageElement = elem.append('image');
|
||||
imageElement.attr('x', x);
|
||||
imageElement.attr('y', y);
|
||||
const sanitizedLink: string = sanitizeUrl(link);
|
||||
imageElement.attr('xlink:href', sanitizedLink);
|
||||
};
|
||||
|
||||
export const drawEmbeddedImage = (
|
||||
element: SVG | Group,
|
||||
x: number,
|
||||
y: number,
|
||||
link: string
|
||||
): void => {
|
||||
const imageElement: D3UseElement = element.append('use');
|
||||
imageElement.attr('x', x);
|
||||
imageElement.attr('y', y);
|
||||
const sanitizedLink: string = sanitizeUrl(link);
|
||||
imageElement.attr('xlink:href', `#${sanitizedLink}`);
|
||||
};
|
||||
|
||||
export const getNoteRect = (): RectData => {
|
||||
const noteRectData: RectData = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
fill: '#EDF2AE',
|
||||
stroke: '#666',
|
||||
anchor: 'start',
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
};
|
||||
return noteRectData;
|
||||
};
|
||||
|
||||
export const getTextObj = (): TextObject => {
|
||||
const testObject: TextObject = {
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: 100,
|
||||
height: 100,
|
||||
'text-anchor': 'start',
|
||||
style: '#666',
|
||||
textMargin: 0,
|
||||
rx: 0,
|
||||
ry: 0,
|
||||
tspan: true,
|
||||
};
|
||||
return testObject;
|
||||
};
|
10
packages/mermaid/src/diagrams/pie/amonts.csv
Normal file
10
packages/mermaid/src/diagrams/pie/amonts.csv
Normal file
@@ -0,0 +1,10 @@
|
||||
name,amounts
|
||||
Foo, 33
|
||||
Rishab, 12
|
||||
Alexis, 41
|
||||
Tom, 16
|
||||
Courtney, 59
|
||||
Christina, 38
|
||||
Jack, 21
|
||||
Mickey, 25
|
||||
Paul, 30
|
|
132
packages/mermaid/src/diagrams/pie/parser/pie.spec.js
Normal file
132
packages/mermaid/src/diagrams/pie/parser/pie.spec.js
Normal file
@@ -0,0 +1,132 @@
|
||||
import pieDb from '../pieDb.js';
|
||||
import pie from './pie.jison';
|
||||
import { setConfig } from '../../../config.js';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
});
|
||||
|
||||
describe('when parsing pie', function () {
|
||||
beforeEach(function () {
|
||||
pie.parser.yy = pieDb;
|
||||
pie.parser.yy.clear();
|
||||
});
|
||||
it('should handle very simple pie', function () {
|
||||
const res = pie.parser.parse(`pie
|
||||
"ash" : 100
|
||||
`);
|
||||
const sections = pieDb.getSections();
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(100);
|
||||
});
|
||||
it('should handle simple pie', function () {
|
||||
const res = pie.parser.parse(`pie
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
const sections = pieDb.getSections();
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(60);
|
||||
});
|
||||
it('should handle simple pie with comments', function () {
|
||||
const res = pie.parser.parse(`pie
|
||||
%% comments
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
const sections = pieDb.getSections();
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(60);
|
||||
});
|
||||
|
||||
it('should handle simple pie with a directive', function () {
|
||||
const res = pie.parser.parse(`%%{init: {'logLevel':0}}%%
|
||||
pie
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
const sections = pieDb.getSections();
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(60);
|
||||
});
|
||||
|
||||
it('should handle simple pie with a title', function () {
|
||||
const res = pie.parser.parse(`pie title a 60/40 pie
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
const sections = pieDb.getSections();
|
||||
const title = pieDb.getDiagramTitle();
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(60);
|
||||
expect(title).toBe('a 60/40 pie');
|
||||
});
|
||||
|
||||
it('should handle simple pie without an acc description (accDescr)', function () {
|
||||
const res = pie.parser.parse(`pie title a neat chart
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
const sections = pieDb.getSections();
|
||||
const title = pieDb.getDiagramTitle();
|
||||
const description = pieDb.getAccDescription();
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(60);
|
||||
expect(title).toBe('a neat chart');
|
||||
expect(description).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple pie with an acc description (accDescr)', function () {
|
||||
const res = pie.parser.parse(`pie title a neat chart
|
||||
accDescr: a neat description
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
const sections = pieDb.getSections();
|
||||
const title = pieDb.getDiagramTitle();
|
||||
const description = pieDb.getAccDescription();
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(60);
|
||||
expect(title).toBe('a neat chart');
|
||||
expect(description).toBe('a neat description');
|
||||
});
|
||||
it('should handle simple pie with a multiline acc description (accDescr)', function () {
|
||||
const res = pie.parser.parse(`pie title a neat chart
|
||||
accDescr {
|
||||
a neat description
|
||||
on multiple lines
|
||||
}
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
const sections = pieDb.getSections();
|
||||
const title = pieDb.getDiagramTitle();
|
||||
const description = pieDb.getAccDescription();
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(60);
|
||||
expect(title).toBe('a neat chart');
|
||||
expect(description).toBe('a neat description\non multiple lines');
|
||||
});
|
||||
|
||||
it('should handle simple pie with positive decimal', function () {
|
||||
const res = pie.parser.parse(`pie
|
||||
"ash" : 60.67
|
||||
"bat" : 40
|
||||
`);
|
||||
const sections = pieDb.getSections();
|
||||
const section1 = sections['ash'];
|
||||
expect(section1).toBe(60.67);
|
||||
});
|
||||
|
||||
it('should handle simple pie with negative decimal', function () {
|
||||
expect(() => {
|
||||
pie.parser.parse(`pie
|
||||
"ash" : 60.67
|
||||
"bat" : 40..12
|
||||
`);
|
||||
}).toThrowError();
|
||||
});
|
||||
});
|
@@ -1,180 +0,0 @@
|
||||
// @ts-ignore: JISON doesn't support types
|
||||
import { parser } from './parser/pie.jison';
|
||||
import { DEFAULT_PIE_DB, db } from './pieDb.js';
|
||||
import { setConfig } from '../../config.js';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
});
|
||||
|
||||
describe('pie', () => {
|
||||
beforeAll(() => {
|
||||
parser.yy = db;
|
||||
});
|
||||
|
||||
beforeEach(() => {
|
||||
parser.yy.clear();
|
||||
});
|
||||
|
||||
describe('parse', () => {
|
||||
it('should handle very simple pie', () => {
|
||||
parser.parse(`pie
|
||||
"ash": 100
|
||||
`);
|
||||
|
||||
const sections = db.getSections();
|
||||
expect(sections['ash']).toBe(100);
|
||||
});
|
||||
|
||||
it('should handle simple pie', () => {
|
||||
parser.parse(`pie
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
const sections = db.getSections();
|
||||
expect(sections['ash']).toBe(60);
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with showData', () => {
|
||||
parser.parse(`pie showData
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
expect(db.getShowData()).toBeTruthy();
|
||||
|
||||
const sections = db.getSections();
|
||||
expect(sections['ash']).toBe(60);
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with comments', () => {
|
||||
parser.parse(`pie
|
||||
%% comments
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
const sections = db.getSections();
|
||||
expect(sections['ash']).toBe(60);
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with a directive', () => {
|
||||
parser.parse(`%%{init: {'logLevel':0}}%%
|
||||
pie
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
const sections = db.getSections();
|
||||
expect(sections['ash']).toBe(60);
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with a title', () => {
|
||||
parser.parse(`pie title a 60/40 pie
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
expect(db.getDiagramTitle()).toBe('a 60/40 pie');
|
||||
|
||||
const sections = db.getSections();
|
||||
expect(sections['ash']).toBe(60);
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with an acc title (accTitle)', () => {
|
||||
parser.parse(`pie title a neat chart
|
||||
accTitle: a neat acc title
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
expect(db.getDiagramTitle()).toBe('a neat chart');
|
||||
|
||||
expect(db.getAccTitle()).toBe('a neat acc title');
|
||||
|
||||
const sections = db.getSections();
|
||||
expect(sections['ash']).toBe(60);
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with an acc description (accDescr)', () => {
|
||||
parser.parse(`pie title a neat chart
|
||||
accDescr: a neat description
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
expect(db.getDiagramTitle()).toBe('a neat chart');
|
||||
|
||||
expect(db.getAccDescription()).toBe('a neat description');
|
||||
|
||||
const sections = db.getSections();
|
||||
expect(sections['ash']).toBe(60);
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with a multiline acc description (accDescr)', () => {
|
||||
parser.parse(`pie title a neat chart
|
||||
accDescr {
|
||||
a neat description
|
||||
on multiple lines
|
||||
}
|
||||
"ash" : 60
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
expect(db.getDiagramTitle()).toBe('a neat chart');
|
||||
|
||||
expect(db.getAccDescription()).toBe('a neat description\non multiple lines');
|
||||
|
||||
const sections = db.getSections();
|
||||
expect(sections['ash']).toBe(60);
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with positive decimal', () => {
|
||||
parser.parse(`pie
|
||||
"ash" : 60.67
|
||||
"bat" : 40
|
||||
`);
|
||||
|
||||
const sections = db.getSections();
|
||||
expect(sections['ash']).toBe(60.67);
|
||||
expect(sections['bat']).toBe(40);
|
||||
});
|
||||
|
||||
it('should handle simple pie with negative decimal', () => {
|
||||
expect(() => {
|
||||
parser.parse(`pie
|
||||
"ash" : -60.67
|
||||
"bat" : 40.12
|
||||
`);
|
||||
}).toThrowError();
|
||||
});
|
||||
});
|
||||
|
||||
describe('config', () => {
|
||||
it.todo('setConfig', () => {
|
||||
// db.setConfig({ useWidth: 850, useMaxWidth: undefined });
|
||||
|
||||
const config = db.getConfig();
|
||||
expect(config.useWidth).toBe(850);
|
||||
expect(config.useMaxWidth).toBeTruthy();
|
||||
});
|
||||
|
||||
it('getConfig', () => {
|
||||
expect(db.getConfig()).toStrictEqual(DEFAULT_PIE_DB.config);
|
||||
});
|
||||
|
||||
it.todo('resetConfig', () => {
|
||||
// db.setConfig({ textPosition: 0 });
|
||||
// db.resetConfig();
|
||||
expect(db.getConfig().textPosition).toStrictEqual(DEFAULT_PIE_DB.config.textPosition);
|
||||
});
|
||||
});
|
||||
});
|
69
packages/mermaid/src/diagrams/pie/pieDb.js
Normal file
69
packages/mermaid/src/diagrams/pie/pieDb.js
Normal file
@@ -0,0 +1,69 @@
|
||||
import { log } from '../../logger.js';
|
||||
import mermaidAPI from '../../mermaidAPI.js';
|
||||
import * as configApi from '../../config.js';
|
||||
import common from '../common/common.js';
|
||||
import {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
clear as commonClear,
|
||||
} from '../../commonDb.js';
|
||||
|
||||
let sections = {};
|
||||
let showData = false;
|
||||
|
||||
export const parseDirective = function (statement, context, type) {
|
||||
mermaidAPI.parseDirective(this, statement, context, type);
|
||||
};
|
||||
|
||||
const addSection = function (id, value) {
|
||||
id = common.sanitizeText(id, configApi.getConfig());
|
||||
if (sections[id] === undefined) {
|
||||
sections[id] = value;
|
||||
log.debug('Added new section :', id);
|
||||
}
|
||||
};
|
||||
const getSections = () => sections;
|
||||
|
||||
const setShowData = function (toggle) {
|
||||
showData = toggle;
|
||||
};
|
||||
|
||||
const getShowData = function () {
|
||||
return showData;
|
||||
};
|
||||
|
||||
const cleanupValue = function (value) {
|
||||
if (value.substring(0, 1) === ':') {
|
||||
value = value.substring(1).trim();
|
||||
return Number(value.trim());
|
||||
} else {
|
||||
return Number(value.trim());
|
||||
}
|
||||
};
|
||||
|
||||
const clear = function () {
|
||||
sections = {};
|
||||
showData = false;
|
||||
commonClear();
|
||||
};
|
||||
|
||||
export default {
|
||||
parseDirective,
|
||||
getConfig: () => configApi.getConfig().pie,
|
||||
addSection,
|
||||
getSections,
|
||||
cleanupValue,
|
||||
clear,
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
setShowData,
|
||||
getShowData,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
};
|
@@ -1,84 +0,0 @@
|
||||
import { log } from '../../logger.js';
|
||||
import { parseDirective as _parseDirective } from '../../directiveUtils.js';
|
||||
import { getConfig as commonGetConfig } from '../../config.js';
|
||||
import { sanitizeText } from '../common/common.js';
|
||||
import {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
clear as commonClear,
|
||||
} from '../../commonDb.js';
|
||||
import type { ParseDirectiveDefinition } from '../../diagram-api/types.js';
|
||||
import type { PieFields, PieDB, Sections } from './pieTypes.js';
|
||||
import type { RequiredDeep } from 'type-fest';
|
||||
import type { PieDiagramConfig } from '../../config.type.js';
|
||||
import DEFAULT_CONFIG from '../../defaultConfig.js';
|
||||
|
||||
export const DEFAULT_PIE_CONFIG: Required<PieDiagramConfig> = DEFAULT_CONFIG.pie;
|
||||
|
||||
export const DEFAULT_PIE_DB: RequiredDeep<PieFields> = {
|
||||
sections: {},
|
||||
showData: false,
|
||||
config: DEFAULT_PIE_CONFIG,
|
||||
} as const;
|
||||
|
||||
let sections: Sections = DEFAULT_PIE_DB.sections;
|
||||
let showData: boolean = DEFAULT_PIE_DB.showData;
|
||||
const config: Required<PieDiagramConfig> = structuredClone(DEFAULT_PIE_CONFIG);
|
||||
|
||||
const getConfig = (): Required<PieDiagramConfig> => structuredClone(config);
|
||||
|
||||
const parseDirective: ParseDirectiveDefinition = (statement, context, type) => {
|
||||
_parseDirective(this, statement, context, type);
|
||||
};
|
||||
|
||||
const clear = (): void => {
|
||||
sections = structuredClone(DEFAULT_PIE_DB.sections);
|
||||
showData = DEFAULT_PIE_DB.showData;
|
||||
commonClear();
|
||||
};
|
||||
|
||||
const addSection = (label: string, value: number): void => {
|
||||
label = sanitizeText(label, commonGetConfig());
|
||||
if (sections[label] === undefined) {
|
||||
sections[label] = value;
|
||||
log.debug(`added new section: ${label}, with value: ${value}`);
|
||||
}
|
||||
};
|
||||
|
||||
const getSections = (): Sections => sections;
|
||||
|
||||
const cleanupValue = (value: string): number => {
|
||||
if (value.substring(0, 1) === ':') {
|
||||
value = value.substring(1).trim();
|
||||
}
|
||||
return Number(value.trim());
|
||||
};
|
||||
|
||||
const setShowData = (toggle: boolean): void => {
|
||||
showData = toggle;
|
||||
};
|
||||
|
||||
const getShowData = (): boolean => showData;
|
||||
|
||||
export const db: PieDB = {
|
||||
getConfig,
|
||||
|
||||
parseDirective,
|
||||
clear,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
setAccDescription,
|
||||
getAccDescription,
|
||||
|
||||
addSection,
|
||||
getSections,
|
||||
cleanupValue,
|
||||
setShowData,
|
||||
getShowData,
|
||||
};
|
@@ -15,8 +15,10 @@ const loader: DiagramLoader = async () => {
|
||||
return { id, diagram };
|
||||
};
|
||||
|
||||
export const pie: ExternalDiagramDefinition = {
|
||||
const plugin: ExternalDiagramDefinition = {
|
||||
id,
|
||||
detector,
|
||||
loader,
|
||||
};
|
||||
|
||||
export default plugin;
|
||||
|
@@ -1,9 +1,9 @@
|
||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
import { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
// @ts-ignore: JISON doesn't support types
|
||||
import parser from './parser/pie.jison';
|
||||
import { db } from './pieDb.js';
|
||||
import styles from './pieStyles.js';
|
||||
import { renderer } from './pieRenderer.js';
|
||||
import db from './pieDb.js';
|
||||
import styles from './styles.js';
|
||||
import renderer from './pieRenderer.js';
|
||||
|
||||
export const diagram: DiagramDefinition = {
|
||||
parser,
|
||||
|
204
packages/mermaid/src/diagrams/pie/pieRenderer.js
Normal file
204
packages/mermaid/src/diagrams/pie/pieRenderer.js
Normal file
@@ -0,0 +1,204 @@
|
||||
/** Created by AshishJ on 11-09-2019. */
|
||||
import { select, scaleOrdinal, pie as d3pie, arc } from 'd3';
|
||||
import { log } from '../../logger.js';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||
import * as configApi from '../../config.js';
|
||||
import { parseFontSize } from '../../utils.js';
|
||||
|
||||
let conf = configApi.getConfig();
|
||||
|
||||
/**
|
||||
* Draws a Pie Chart with the data given in text.
|
||||
*
|
||||
* @param text
|
||||
* @param id
|
||||
*/
|
||||
let width;
|
||||
const height = 450;
|
||||
export const draw = (txt, id, _version, diagObj) => {
|
||||
try {
|
||||
conf = configApi.getConfig();
|
||||
log.debug('Rendering info diagram\n' + txt);
|
||||
|
||||
const securityLevel = configApi.getConfig().securityLevel;
|
||||
// Handle root and Document for when rendering in sandbox mode
|
||||
let sandboxElement;
|
||||
if (securityLevel === 'sandbox') {
|
||||
sandboxElement = select('#i' + id);
|
||||
}
|
||||
const root =
|
||||
securityLevel === 'sandbox'
|
||||
? select(sandboxElement.nodes()[0].contentDocument.body)
|
||||
: select('body');
|
||||
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
|
||||
|
||||
// Parse the Pie Chart definition
|
||||
const elem = doc.getElementById(id);
|
||||
width = elem.parentElement.offsetWidth;
|
||||
|
||||
if (width === undefined) {
|
||||
width = 1200;
|
||||
}
|
||||
|
||||
if (conf.useWidth !== undefined) {
|
||||
width = conf.useWidth;
|
||||
}
|
||||
if (conf.pie.useWidth !== undefined) {
|
||||
width = conf.pie.useWidth;
|
||||
}
|
||||
|
||||
const diagram = root.select('#' + id);
|
||||
configureSvgSize(diagram, height, width, conf.pie.useMaxWidth);
|
||||
|
||||
// Set viewBox
|
||||
elem.setAttribute('viewBox', '0 0 ' + width + ' ' + height);
|
||||
|
||||
// Fetch the default direction, use TD if none was found
|
||||
var margin = 40;
|
||||
var legendRectSize = 18;
|
||||
var legendSpacing = 4;
|
||||
|
||||
var radius = Math.min(width, height) / 2 - margin;
|
||||
|
||||
var svg = diagram
|
||||
.append('g')
|
||||
.attr('transform', 'translate(' + width / 2 + ',' + height / 2 + ')');
|
||||
|
||||
var data = diagObj.db.getSections();
|
||||
var sum = 0;
|
||||
Object.keys(data).forEach(function (key) {
|
||||
sum += data[key];
|
||||
});
|
||||
|
||||
const themeVariables = conf.themeVariables;
|
||||
var myGeneratedColors = [
|
||||
themeVariables.pie1,
|
||||
themeVariables.pie2,
|
||||
themeVariables.pie3,
|
||||
themeVariables.pie4,
|
||||
themeVariables.pie5,
|
||||
themeVariables.pie6,
|
||||
themeVariables.pie7,
|
||||
themeVariables.pie8,
|
||||
themeVariables.pie9,
|
||||
themeVariables.pie10,
|
||||
themeVariables.pie11,
|
||||
themeVariables.pie12,
|
||||
];
|
||||
|
||||
const textPosition = conf.pie?.textPosition ?? 0.75;
|
||||
let [outerStrokeWidth] = parseFontSize(themeVariables.pieOuterStrokeWidth);
|
||||
outerStrokeWidth ??= 2;
|
||||
|
||||
// Set the color scale
|
||||
var color = scaleOrdinal().range(myGeneratedColors);
|
||||
|
||||
// Compute the position of each group on the pie:
|
||||
var pieData = Object.entries(data).map(function (el, idx) {
|
||||
return {
|
||||
order: idx,
|
||||
name: el[0],
|
||||
value: el[1],
|
||||
};
|
||||
});
|
||||
var pie = d3pie()
|
||||
.value(function (d) {
|
||||
return d.value;
|
||||
})
|
||||
.sort(function (a, b) {
|
||||
// Sort slices in clockwise direction
|
||||
return a.order - b.order;
|
||||
});
|
||||
var dataReady = pie(pieData);
|
||||
|
||||
// Shape helper to build arcs:
|
||||
var arcGenerator = arc().innerRadius(0).outerRadius(radius);
|
||||
var labelArcGenerator = arc()
|
||||
.innerRadius(radius * textPosition)
|
||||
.outerRadius(radius * textPosition);
|
||||
|
||||
svg
|
||||
.append('circle')
|
||||
.attr('cx', 0)
|
||||
.attr('cy', 0)
|
||||
.attr('r', radius + outerStrokeWidth / 2)
|
||||
.attr('class', 'pieOuterCircle');
|
||||
|
||||
// Build the pie chart: each part of the pie is a path that we build using the arc function.
|
||||
svg
|
||||
.selectAll('mySlices')
|
||||
.data(dataReady)
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('d', arcGenerator)
|
||||
.attr('fill', function (d) {
|
||||
return color(d.data.name);
|
||||
})
|
||||
.attr('class', 'pieCircle');
|
||||
|
||||
// Now add the percentage.
|
||||
// Use the centroid method to get the best coordinates.
|
||||
svg
|
||||
.selectAll('mySlices')
|
||||
.data(dataReady)
|
||||
.enter()
|
||||
.append('text')
|
||||
.text(function (d) {
|
||||
return ((d.data.value / sum) * 100).toFixed(0) + '%';
|
||||
})
|
||||
.attr('transform', function (d) {
|
||||
return 'translate(' + labelArcGenerator.centroid(d) + ')';
|
||||
})
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('class', 'slice');
|
||||
|
||||
svg
|
||||
.append('text')
|
||||
.text(diagObj.db.getDiagramTitle())
|
||||
.attr('x', 0)
|
||||
.attr('y', -(height - 50) / 2)
|
||||
.attr('class', 'pieTitleText');
|
||||
|
||||
// Add the legends/annotations for each section
|
||||
var legend = svg
|
||||
.selectAll('.legend')
|
||||
.data(color.domain())
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'legend')
|
||||
.attr('transform', function (d, i) {
|
||||
const height = legendRectSize + legendSpacing;
|
||||
const offset = (height * color.domain().length) / 2;
|
||||
const horizontal = 12 * legendRectSize;
|
||||
const vertical = i * height - offset;
|
||||
return 'translate(' + horizontal + ',' + vertical + ')';
|
||||
});
|
||||
|
||||
legend
|
||||
.append('rect')
|
||||
.attr('width', legendRectSize)
|
||||
.attr('height', legendRectSize)
|
||||
.style('fill', color)
|
||||
.style('stroke', color);
|
||||
|
||||
legend
|
||||
.data(dataReady)
|
||||
.append('text')
|
||||
.attr('x', legendRectSize + legendSpacing)
|
||||
.attr('y', legendRectSize - legendSpacing)
|
||||
.text(function (d) {
|
||||
if (diagObj.db.getShowData() || conf.showData || conf.pie.showData) {
|
||||
return d.data.name + ' [' + d.data.value + ']';
|
||||
} else {
|
||||
return d.data.name;
|
||||
}
|
||||
});
|
||||
} catch (e) {
|
||||
log.error('Error while rendering info diagram');
|
||||
log.error(e);
|
||||
}
|
||||
};
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
@@ -1,179 +0,0 @@
|
||||
import d3, { scaleOrdinal, pie as d3pie, arc } from 'd3';
|
||||
|
||||
import { log } from '../../logger.js';
|
||||
import { configureSvgSize } from '../../setupGraphViewbox.js';
|
||||
import { getConfig } from '../../config.js';
|
||||
import { cleanAndMerge, parseFontSize } from '../../utils.js';
|
||||
import type { DrawDefinition, Group, SVG } from '../../diagram-api/types.js';
|
||||
import type { D3Sections, PieDB, Sections } from './pieTypes.js';
|
||||
import type { MermaidConfig, PieDiagramConfig } from '../../config.type.js';
|
||||
import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
||||
|
||||
const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Sections>[] => {
|
||||
// Compute the position of each group on the pie:
|
||||
const pieData: D3Sections[] = Object.entries(sections).map(
|
||||
(element: [string, number]): D3Sections => {
|
||||
return {
|
||||
label: element[0],
|
||||
value: element[1],
|
||||
};
|
||||
}
|
||||
);
|
||||
const pie: d3.Pie<unknown, D3Sections> = d3pie<D3Sections>().value(
|
||||
(d3Section: D3Sections): number => d3Section.value
|
||||
);
|
||||
return pie(pieData);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a Pie Chart with the data given in text.
|
||||
*
|
||||
* @param text - pie chart code
|
||||
* @param id - diagram id
|
||||
* @param _version - MermaidJS version from package.json.
|
||||
* @param diagObj - A standard diagram containing the DB and the text and type etc of the diagram.
|
||||
*/
|
||||
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 group: Group = svg.append('g');
|
||||
group.attr('transform', 'translate(' + width / 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;
|
||||
// Shape helper to build arcs:
|
||||
const arcGenerator: d3.Arc<unknown, d3.PieArcDatum<D3Sections>> = arc<
|
||||
d3.PieArcDatum<D3Sections>
|
||||
>()
|
||||
.innerRadius(0)
|
||||
.outerRadius(radius);
|
||||
const labelArcGenerator: d3.Arc<unknown, d3.PieArcDatum<D3Sections>> = arc<
|
||||
d3.PieArcDatum<D3Sections>
|
||||
>()
|
||||
.innerRadius(radius * textPosition)
|
||||
.outerRadius(radius * textPosition);
|
||||
|
||||
group
|
||||
.append('circle')
|
||||
.attr('cx', 0)
|
||||
.attr('cy', 0)
|
||||
.attr('r', radius + outerStrokeWidth / 2)
|
||||
.attr('class', 'pieOuterCircle');
|
||||
|
||||
const sections: Sections = db.getSections();
|
||||
const arcs: d3.PieArcDatum<D3Sections>[] = createPieArcs(sections);
|
||||
|
||||
const myGeneratedColors = [
|
||||
themeVariables.pie1,
|
||||
themeVariables.pie2,
|
||||
themeVariables.pie3,
|
||||
themeVariables.pie4,
|
||||
themeVariables.pie5,
|
||||
themeVariables.pie6,
|
||||
themeVariables.pie7,
|
||||
themeVariables.pie8,
|
||||
themeVariables.pie9,
|
||||
themeVariables.pie10,
|
||||
themeVariables.pie11,
|
||||
themeVariables.pie12,
|
||||
];
|
||||
// Set the color scale
|
||||
const color: d3.ScaleOrdinal<string, 12, never> = scaleOrdinal(myGeneratedColors);
|
||||
|
||||
// Build the pie chart: each part of the pie is a path that we build using the arc function.
|
||||
group
|
||||
.selectAll('mySlices')
|
||||
.data(arcs)
|
||||
.enter()
|
||||
.append('path')
|
||||
.attr('d', arcGenerator)
|
||||
.attr('fill', (datum: d3.PieArcDatum<D3Sections>) => {
|
||||
return color(datum.data.label);
|
||||
})
|
||||
.attr('class', 'pieCircle');
|
||||
|
||||
let sum = 0;
|
||||
Object.keys(sections).forEach((key: string): void => {
|
||||
sum += sections[key];
|
||||
});
|
||||
// Now add the percentage.
|
||||
// Use the centroid method to get the best coordinates.
|
||||
group
|
||||
.selectAll('mySlices')
|
||||
.data(arcs)
|
||||
.enter()
|
||||
.append('text')
|
||||
.text((datum: d3.PieArcDatum<D3Sections>): string => {
|
||||
return ((datum.data.value / sum) * 100).toFixed(0) + '%';
|
||||
})
|
||||
.attr('transform', (datum: d3.PieArcDatum<D3Sections>): string => {
|
||||
return 'translate(' + labelArcGenerator.centroid(datum) + ')';
|
||||
})
|
||||
.style('text-anchor', 'middle')
|
||||
.attr('class', 'slice');
|
||||
|
||||
group
|
||||
.append('text')
|
||||
.text(db.getDiagramTitle())
|
||||
.attr('x', 0)
|
||||
.attr('y', -(height - 50) / 2)
|
||||
.attr('class', 'pieTitleText');
|
||||
|
||||
// Add the legends/annotations for each section
|
||||
const legend = group
|
||||
.selectAll('.legend')
|
||||
.data(color.domain())
|
||||
.enter()
|
||||
.append('g')
|
||||
.attr('class', 'legend')
|
||||
.attr('transform', (_datum, index: number): string => {
|
||||
const height = LEGEND_RECT_SIZE + LEGEND_SPACING;
|
||||
const offset = (height * color.domain().length) / 2;
|
||||
const horizontal = 12 * LEGEND_RECT_SIZE;
|
||||
const vertical = index * height - offset;
|
||||
return 'translate(' + horizontal + ',' + vertical + ')';
|
||||
});
|
||||
|
||||
legend
|
||||
.append('rect')
|
||||
.attr('width', LEGEND_RECT_SIZE)
|
||||
.attr('height', LEGEND_RECT_SIZE)
|
||||
.style('fill', color)
|
||||
.style('stroke', color);
|
||||
|
||||
legend
|
||||
.data(arcs)
|
||||
.append('text')
|
||||
.attr('x', LEGEND_RECT_SIZE + LEGEND_SPACING)
|
||||
.attr('y', LEGEND_RECT_SIZE - LEGEND_SPACING)
|
||||
.text((datum: d3.PieArcDatum<D3Sections>): string => {
|
||||
const { label, value } = datum.data;
|
||||
if (db.getShowData()) {
|
||||
return `${label} [${value}]`;
|
||||
}
|
||||
return label;
|
||||
});
|
||||
};
|
||||
|
||||
export const renderer = { draw };
|
@@ -1,64 +0,0 @@
|
||||
import type { PieDiagramConfig } from '../../config.type.js';
|
||||
import type { DiagramDB, ParseDirectiveDefinition } from '../../diagram-api/types.js';
|
||||
|
||||
export interface PieFields {
|
||||
sections: Sections;
|
||||
showData: boolean;
|
||||
config: PieDiagramConfig;
|
||||
}
|
||||
|
||||
export interface PieStyleOptions {
|
||||
fontFamily: string;
|
||||
pie1: string;
|
||||
pie2: string;
|
||||
pie3: string;
|
||||
pie4: string;
|
||||
pie5: string;
|
||||
pie6: string;
|
||||
pie7: string;
|
||||
pie8: string;
|
||||
pie9: string;
|
||||
pie10: string;
|
||||
pie11: string;
|
||||
pie12: string;
|
||||
pieTitleTextSize: string;
|
||||
pieTitleTextColor: string;
|
||||
pieSectionTextSize: string;
|
||||
pieSectionTextColor: string;
|
||||
pieLegendTextSize: string;
|
||||
pieLegendTextColor: string;
|
||||
pieStrokeColor: string;
|
||||
pieStrokeWidth: string;
|
||||
pieOuterStrokeWidth: string;
|
||||
pieOuterStrokeColor: string;
|
||||
pieOpacity: string;
|
||||
}
|
||||
|
||||
export type Sections = Record<string, number>;
|
||||
|
||||
export interface D3Sections {
|
||||
label: string;
|
||||
value: number;
|
||||
}
|
||||
|
||||
export interface PieDB extends DiagramDB {
|
||||
// config
|
||||
getConfig: () => Required<PieDiagramConfig>;
|
||||
|
||||
// common db
|
||||
parseDirective: ParseDirectiveDefinition;
|
||||
clear: () => void;
|
||||
setDiagramTitle: (title: string) => void;
|
||||
getDiagramTitle: () => string;
|
||||
setAccTitle: (title: string) => void;
|
||||
getAccTitle: () => string;
|
||||
setAccDescription: (describetion: string) => void;
|
||||
getAccDescription: () => string;
|
||||
|
||||
// diagram db
|
||||
addSection: (label: string, value: number) => void;
|
||||
getSections: () => Sections;
|
||||
cleanupValue: (value: string) => number;
|
||||
setShowData: (toggle: boolean) => void;
|
||||
getShowData: () => boolean;
|
||||
}
|
@@ -1,7 +1,4 @@
|
||||
import type { DiagramStylesProvider } from '../../diagram-api/types.js';
|
||||
import type { PieStyleOptions } from './pieTypes.js';
|
||||
|
||||
const getStyles: DiagramStylesProvider = (options: PieStyleOptions) =>
|
||||
const getStyles = (options) =>
|
||||
`
|
||||
.pieCircle{
|
||||
stroke: ${options.pieStrokeColor};
|
@@ -3,7 +3,7 @@ import { select, selectAll } from 'd3';
|
||||
import svgDraw, { ACTOR_TYPE_WIDTH, drawText, fixLifeLineHeights } from './svgDraw.js';
|
||||
import { log } from '../../logger.js';
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import * as configApi from '../../config.js';
|
||||
import assignWithDepth from '../../assignWithDepth.js';
|
||||
import utils from '../../utils.js';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import common from '../common/common.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
import { addFunction } from '../../interactionDb.js';
|
||||
import { ZERO_WIDTH_SPACE, parseFontSize } from '../../utils.js';
|
||||
import { sanitizeUrl } from '@braintree/sanitize-url';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { arc as d3arc } from 'd3';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon.js';
|
||||
import * as svgDrawCommon from '../common/svgDrawCommon';
|
||||
|
||||
export const drawRect = function (elem, rectData) {
|
||||
return svgDrawCommon.drawRect(elem, rectData);
|
||||
|
@@ -1,5 +1,7 @@
|
||||
import * as configApi from './config.js';
|
||||
|
||||
import { log } from './logger.js';
|
||||
import { directiveSanitizer } from './utils.js';
|
||||
|
||||
let currentDirective: { type?: string; args?: any } | undefined = {};
|
||||
|
||||
@@ -58,6 +60,9 @@ const handleDirective = function (p: any, directive: any, type: string): void {
|
||||
delete directive.args[prop];
|
||||
}
|
||||
});
|
||||
log.info('sanitize in handleDirective', directive.args);
|
||||
directiveSanitizer(directive.args);
|
||||
log.info('sanitize in handleDirective (done)', directive.args);
|
||||
configApi.addDirective(directive.args);
|
||||
break;
|
||||
}
|
||||
|
@@ -101,7 +101,7 @@ function sidebarAll() {
|
||||
return [
|
||||
{
|
||||
text: '📔 Introduction',
|
||||
collapsed: false,
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: 'About Mermaid', link: '/intro/' },
|
||||
{ text: 'Deployment', link: '/intro/n00b-gettingStarted' },
|
||||
@@ -123,7 +123,7 @@ function sidebarSyntax() {
|
||||
return [
|
||||
{
|
||||
text: '📊 Diagram Syntax',
|
||||
collapsed: false,
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: 'Flowchart', link: '/syntax/flowchart' },
|
||||
{ text: 'Sequence Diagram', link: '/syntax/sequenceDiagram' },
|
||||
@@ -154,7 +154,7 @@ function sidebarConfig() {
|
||||
return [
|
||||
{
|
||||
text: '⚙️ Deployment and Configuration',
|
||||
collapsed: false,
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: 'Configuration', link: '/config/configuration' },
|
||||
{ text: 'Tutorials', link: '/config/Tutorials' },
|
||||
@@ -176,7 +176,7 @@ function sidebarEcosystem() {
|
||||
return [
|
||||
{
|
||||
text: '📚 Ecosystem',
|
||||
collapsed: false,
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: 'Showcases', link: '/ecosystem/showcases' },
|
||||
{ text: 'Use-Cases and Integrations', link: '/ecosystem/integrations' },
|
||||
@@ -189,7 +189,7 @@ function sidebarCommunity() {
|
||||
return [
|
||||
{
|
||||
text: '🙌 Contributions and Community',
|
||||
collapsed: false,
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: 'Overview for Beginners', link: '/community/n00b-overview' },
|
||||
...sidebarCommunityDevelopContribute(),
|
||||
@@ -207,7 +207,7 @@ function sidebarCommunityDevelopContribute() {
|
||||
{
|
||||
text: 'Contributing to Mermaid',
|
||||
link: page_path + '#contributing-to-mermaid',
|
||||
collapsed: false,
|
||||
collapsible: true,
|
||||
items: [
|
||||
{
|
||||
text: 'Technical Requirements and Setup',
|
||||
@@ -238,7 +238,7 @@ function sidebarNews() {
|
||||
return [
|
||||
{
|
||||
text: '📰 Latest News',
|
||||
collapsed: false,
|
||||
collapsible: true,
|
||||
items: [
|
||||
{ text: 'Announcements', link: '/news/announcements' },
|
||||
{ text: 'Blog', link: '/news/blog' },
|
||||
|
@@ -24,9 +24,6 @@ const getBaseFile = (url: URL): Redirect => {
|
||||
return { path, id };
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to redirect old documentation pages to corresponding new pages.
|
||||
*/
|
||||
const idRedirectMap: Record<string, string> = {
|
||||
'8.6.0_docs': '',
|
||||
accessibility: 'config/theming',
|
||||
@@ -71,9 +68,6 @@ const idRedirectMap: Record<string, string> = {
|
||||
'user-journey': 'syntax/userJourney',
|
||||
};
|
||||
|
||||
/**
|
||||
* Used to redirect pages that have been moved in the vitepress site.
|
||||
*/
|
||||
const urlRedirectMap: Record<string, string> = {
|
||||
'/misc/faq.html': 'configure/faq.html',
|
||||
'/syntax/c4c.html': 'syntax/c4.html',
|
||||
|
@@ -4,28 +4,10 @@ When mermaid starts, configuration is extracted to determine a configuration to
|
||||
|
||||
- The default configuration
|
||||
- Overrides at the site level are set by the initialize call, and will be applied to all diagrams in the site/app. The term for this is the **siteConfig**.
|
||||
- Frontmatter (v<MERMAID_RELEASE_VERSION>+) - diagram authors can update select configuration parameters in the frontmatter of the diagram. These are applied to the render config.
|
||||
- Directives (Deprecated by Frontmatter) - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
|
||||
- Directives - diagram authors can update select configuration parameters directly in the diagram code via directives. These are applied to the render config.
|
||||
|
||||
**The render config** is configuration that is used when rendering by applying these configurations.
|
||||
|
||||
## Frontmatter config
|
||||
|
||||
The entire mermaid configuration (except the secure configs) can be overridden by the diagram author in the frontmatter of the diagram. The frontmatter is a YAML block at the top of the diagram.
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
title: Hello Title
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
primaryColor: "#00ff00"
|
||||
---
|
||||
flowchart
|
||||
Hello --> World
|
||||
|
||||
```
|
||||
|
||||
## Theme configuration
|
||||
|
||||
## Starting mermaid
|
||||
|
@@ -1,9 +1,5 @@
|
||||
# Directives
|
||||
|
||||
```warning
|
||||
Directives are deprecated from v<MERMAID_RELEASE_VERSION>. Please use the `config` key in frontmatter to pass configuration. See [Configuration](./configuration.md) for more details.
|
||||
```
|
||||
|
||||
## Directives
|
||||
|
||||
Directives give a diagram author the capability to alter the appearance of a diagram before rendering by changing the applied configuration.
|
||||
|
@@ -8,12 +8,8 @@ It is a JavaScript based diagramming and charting tool that renders Markdown-ins
|
||||
|
||||
<img src="/header.png" alt="" />
|
||||
|
||||
<div class='badges'>
|
||||
|
||||
[](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [](https://www.npmjs.com/package/mermaid) [](https://bundlephobia.com/package/mermaid) [](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [](https://www.jsdelivr.com/package/npm/mermaid) [](https://www.npmjs.com/package/mermaid) [](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [](https://twitter.com/mermaidjs_)
|
||||
|
||||
</div>
|
||||
|
||||
<!-- Mermaid book banner -->
|
||||
|
||||
[](https://mermaid-js.github.io/mermaid/landing/)
|
||||
@@ -170,12 +166,8 @@ The above command generates files into the `dist` folder and publishes them to <
|
||||
|
||||
## Contributors
|
||||
|
||||
<div class='badges'>
|
||||
|
||||
[](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%21%22) [](https://github.com/mermaid-js/mermaid/graphs/contributors) [](https://github.com/mermaid-js/mermaid/graphs/contributors)
|
||||
|
||||
</div>
|
||||
|
||||
Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out.
|
||||
|
||||
Detailed information about how to contribute can be found in the [contribution guide](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md)
|
||||
@@ -209,14 +201,20 @@ A quick note from Knut Sveidqvist:
|
||||
_Mermaid was created by Knut Sveidqvist for easier documentation._
|
||||
|
||||
<style scoped>
|
||||
.badges > p {
|
||||
display: flex;
|
||||
}
|
||||
.badges > p > a {
|
||||
margin: 0 0.5rem;
|
||||
#contributors + p,
|
||||
#about-mermaid + p + p + blockquote + img + p
|
||||
{
|
||||
display: flex
|
||||
}
|
||||
|
||||
.dark #VPContent > div > div > div.content > div > main > div > div > img {
|
||||
#contributors + p a,
|
||||
#about-mermaid + p + p + blockquote + img + p a
|
||||
{
|
||||
margin: 0 0.5rem
|
||||
}
|
||||
|
||||
.dark #VPContent > div > div > div.content > div > main > div > div > img
|
||||
{
|
||||
filter: invert(1) hue-rotate(217deg) contrast(0.72);
|
||||
}
|
||||
</style>
|
||||
|
@@ -17,22 +17,21 @@
|
||||
"dependencies": {
|
||||
"@vueuse/core": "^10.1.0",
|
||||
"jiti": "^1.18.2",
|
||||
"vue": "^3.3",
|
||||
"mermaid": "workspace:^"
|
||||
"vue": "^3.2.47"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@iconify-json/carbon": "^1.1.16",
|
||||
"@unocss/reset": "^0.55.2",
|
||||
"@unocss/reset": "^0.54.0",
|
||||
"@vite-pwa/vitepress": "^0.2.0",
|
||||
"@vitejs/plugin-vue": "^4.2.1",
|
||||
"fast-glob": "^3.2.12",
|
||||
"https-localhost": "^4.7.1",
|
||||
"pathe": "^1.1.0",
|
||||
"unocss": "^0.55.2",
|
||||
"unocss": "^0.54.0",
|
||||
"unplugin-vue-components": "^0.25.0",
|
||||
"vite": "^4.3.9",
|
||||
"vite-plugin-pwa": "^0.16.0",
|
||||
"vitepress": "1.0.0-rc.4",
|
||||
"vitepress": "1.0.0-beta.7",
|
||||
"workbox-window": "^7.0.0"
|
||||
}
|
||||
}
|
||||
|
@@ -471,29 +471,6 @@ flowchart LR
|
||||
B1 --> B2
|
||||
```
|
||||
|
||||
#### Limitation
|
||||
|
||||
If any of a subgraph's nodes are linked to the outside, subgraph direction will be ignored. Instead the subgraph will inherit the direction of the parent graph:
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
subgraph subgraph1
|
||||
direction TB
|
||||
top1[top] --> bottom1[bottom]
|
||||
end
|
||||
subgraph subgraph2
|
||||
direction TB
|
||||
top2[top] --> bottom2[bottom]
|
||||
end
|
||||
%% ^ These subgraphs are identical, except for the links to them:
|
||||
|
||||
%% Link *to* subgraph1: subgraph1 direction is mantained
|
||||
outside --> subgraph1
|
||||
%% Link *within* subgraph2:
|
||||
%% subgraph2 inherits the direction of the top-level graph (LR)
|
||||
outside ---> top2
|
||||
```
|
||||
|
||||
## Markdown Strings
|
||||
|
||||
The "Markdown Strings" feature enhances flowcharts and mind maps by offering a more versatile string type, which supports text formatting options such as bold and italics, and automatically wraps text within labels.
|
||||
|
@@ -7,8 +7,11 @@ import { MermaidConfig } from './config.type.js';
|
||||
import { log } from './logger.js';
|
||||
import utils from './utils.js';
|
||||
import { mermaidAPI, ParseOptions, RenderResult } from './mermaidAPI.js';
|
||||
import { registerLazyLoadedDiagrams, detectType } from './diagram-api/detectType.js';
|
||||
import { loadRegisteredDiagrams } from './diagram-api/loadDiagram.js';
|
||||
import {
|
||||
registerLazyLoadedDiagrams,
|
||||
loadRegisteredDiagrams,
|
||||
detectType,
|
||||
} from './diagram-api/detectType.js';
|
||||
import type { ParseErrorFunction } from './Diagram.js';
|
||||
import { isDetailedError } from './utils.js';
|
||||
import type { DetailedError } from './utils.js';
|
||||
|
@@ -23,14 +23,13 @@ import { attachFunctions } from './interactionDb.js';
|
||||
import { log, setLogLevel } from './logger.js';
|
||||
import getStyles from './styles.js';
|
||||
import theme from './themes/index.js';
|
||||
import utils from './utils.js';
|
||||
import utils, { directiveSanitizer } from './utils.js';
|
||||
import DOMPurify from 'dompurify';
|
||||
import { 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 { parseDirective } from './directiveUtils.js';
|
||||
import { extractFrontMatter } from './diagram-api/frontmatter.js';
|
||||
|
||||
// diagram names that support classDef statements
|
||||
const CLASSDEF_DIAGRAMS = [
|
||||
@@ -386,14 +385,10 @@ const render = async function (
|
||||
|
||||
configApi.reset();
|
||||
|
||||
// We need to add the directives before creating the diagram.
|
||||
// So extractFrontMatter is called twice. Once here and once in the diagram parser.
|
||||
// This can be fixed in a future refactor.
|
||||
extractFrontMatter(text, {}, configApi.addDirective);
|
||||
|
||||
// Add Directives.
|
||||
// Add Directives. Must do this before getting the config and before creating the diagram.
|
||||
const graphInit = utils.detectInit(text);
|
||||
if (graphInit) {
|
||||
directiveSanitizer(graphInit);
|
||||
configApi.addDirective(graphInit);
|
||||
}
|
||||
|
||||
|
@@ -7,21 +7,21 @@
|
||||
# - `scripts/create-types-from-json-schema.mjs`
|
||||
# Used to generate the `src/config.type.ts` TypeScript types for MermaidConfig
|
||||
# with the `json-schema-to-typescript` NPM package.
|
||||
# - `.build/jsonSchema.ts`
|
||||
# - `.vite/jsonSchemaPlugin.ts`
|
||||
# Used to generate the default values from the `default` keys in this
|
||||
# JSON Schema using the `ajv` NPM package.
|
||||
# Non-JSON values, like functions or `undefined`, still need to be manually
|
||||
# set in `src/defaultConfig.ts`)
|
||||
# - `src/docs.mts`
|
||||
# Used to generate Markdown documentation for this JSON Schema by using
|
||||
# Used to genereate Markdown documentation for this JSON Schema by using
|
||||
# the `@adobe/jsonschema2md` NPM package.
|
||||
|
||||
# Useful things to know when editing this file
|
||||
# Useful things to know when editting this file
|
||||
# - Use the `|` character for multi-line strings
|
||||
# - 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`
|
||||
# you may need to add it to `.vite/jsonSchemaPlugin.ts` and `src/docs.mts`
|
||||
# to get the docs/default values to generate properly.
|
||||
$id: https://mermaid-js.github.io/schemas/config.schema.json
|
||||
$schema: https://json-schema.org/draft/2019-09/schema
|
||||
@@ -1851,7 +1851,7 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
|
||||
The color of the links in the sankey diagram.
|
||||
anyOf:
|
||||
- $ref: '#/$defs/SankeyLinkColor'
|
||||
- description: An arbitrary [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value)
|
||||
- description: An arbtirary [CSS color](https://developer.mozilla.org/en-US/docs/Web/CSS/color_value)
|
||||
type: string
|
||||
default: gradient
|
||||
nodeAlignment:
|
||||
|
@@ -1,4 +1,5 @@
|
||||
import { log } from './logger.js';
|
||||
import { SVG } from './diagram-api/types.js';
|
||||
|
||||
/**
|
||||
* Applies d3 attributes
|
||||
@@ -35,7 +36,7 @@ export const calculateSvgSizeAttrs = function (height, width, useMaxWidth) {
|
||||
/**
|
||||
* Applies attributes from `calculateSvgSizeAttrs`
|
||||
*
|
||||
* @param {import('./diagram-api/types.js').SVG} svgElem The SVG Element to configure
|
||||
* @param {SVG} svgElem The SVG Element to configure
|
||||
* @param {number} height The height of the SVG
|
||||
* @param {number} width The width of the SVG
|
||||
* @param {boolean} useMaxWidth Whether or not to use max-width and set width to 100%
|
||||
|
@@ -21,7 +21,7 @@ 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';
|
||||
import pie from './diagrams/pie/pieStyles.js';
|
||||
import pie from './diagrams/pie/styles.js';
|
||||
import requirement from './diagrams/requirement/styles.js';
|
||||
import sequence from './diagrams/sequence/styles.js';
|
||||
import state from './diagrams/state/styles.js';
|
||||
|
@@ -1,5 +1,5 @@
|
||||
import { vi } from 'vitest';
|
||||
import utils, { cleanAndMerge } from './utils.js';
|
||||
import utils from './utils.js';
|
||||
import assignWithDepth from './assignWithDepth.js';
|
||||
import { detectType } from './diagram-api/detectType.js';
|
||||
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
|
||||
@@ -10,51 +10,51 @@ addDiagrams();
|
||||
|
||||
describe('when assignWithDepth: should merge objects within objects', function () {
|
||||
it('should handle simple, depth:1 types (identity)', function () {
|
||||
const config_0 = { foo: 'bar', bar: 0 };
|
||||
const config_1 = { foo: 'bar', bar: 0 };
|
||||
const result = assignWithDepth(config_0, config_1);
|
||||
let config_0 = { foo: 'bar', bar: 0 };
|
||||
let config_1 = { foo: 'bar', bar: 0 };
|
||||
let result = assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual(config_1);
|
||||
});
|
||||
it('should handle simple, depth:1 types (dst: undefined)', function () {
|
||||
const config_0 = undefined;
|
||||
const config_1 = { foo: 'bar', bar: 0 };
|
||||
const result = assignWithDepth(config_0, config_1);
|
||||
let config_0 = undefined;
|
||||
let config_1 = { foo: 'bar', bar: 0 };
|
||||
let result = assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual(config_1);
|
||||
});
|
||||
it('should handle simple, depth:1 types (src: undefined)', function () {
|
||||
const config_0 = { foo: 'bar', bar: 0 };
|
||||
const config_1 = undefined;
|
||||
const result = assignWithDepth(config_0, config_1);
|
||||
let config_0 = { foo: 'bar', bar: 0 };
|
||||
let config_1 = undefined;
|
||||
let result = assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual(config_0);
|
||||
});
|
||||
it('should handle simple, depth:1 types (merge)', function () {
|
||||
const config_0 = { foo: 'bar', bar: 0 };
|
||||
const config_1 = { foo: 'foo' };
|
||||
const result = assignWithDepth(config_0, config_1);
|
||||
let config_0 = { foo: 'bar', bar: 0 };
|
||||
let config_1 = { foo: 'foo' };
|
||||
let result = assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual({ foo: 'foo', bar: 0 });
|
||||
});
|
||||
it('should handle depth:2 types (dst: orphan)', function () {
|
||||
const config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||
const config_1 = { foo: 'bar' };
|
||||
const result = assignWithDepth(config_0, config_1);
|
||||
let config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||
let config_1 = { foo: 'bar' };
|
||||
let result = assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual(config_0);
|
||||
});
|
||||
it('should handle depth:2 types (dst: object, src: simple type)', function () {
|
||||
const config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||
const config_1 = { foo: 'foo', bar: 'should NOT clobber' };
|
||||
const result = assignWithDepth(config_0, config_1);
|
||||
let config_0 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||
let config_1 = { foo: 'foo', bar: 'should NOT clobber' };
|
||||
let result = assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual({ foo: 'foo', bar: { foo: 'bar' } });
|
||||
});
|
||||
it('should handle depth:2 types (src: orphan)', function () {
|
||||
const config_0 = { foo: 'bar' };
|
||||
const config_1 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||
const result = assignWithDepth(config_0, config_1);
|
||||
let config_0 = { foo: 'bar' };
|
||||
let config_1 = { foo: 'bar', bar: { foo: 'bar' } };
|
||||
let result = assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual(config_1);
|
||||
});
|
||||
it('should handle depth:2 types (merge)', function () {
|
||||
const config_0 = { foo: 'bar', bar: { foo: 'bar' }, boofar: 1 };
|
||||
const config_1 = { foo: 'foo', bar: { bar: 0 }, foobar: 'foobar' };
|
||||
const result = assignWithDepth(config_0, config_1);
|
||||
let config_0 = { foo: 'bar', bar: { foo: 'bar' }, boofar: 1 };
|
||||
let config_1 = { foo: 'foo', bar: { bar: 0 }, foobar: 'foobar' };
|
||||
let result = assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual({
|
||||
foo: 'foo',
|
||||
bar: { foo: 'bar', bar: 0 },
|
||||
@@ -63,17 +63,17 @@ describe('when assignWithDepth: should merge objects within objects', function (
|
||||
});
|
||||
});
|
||||
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 2)', function () {
|
||||
const config_0 = {
|
||||
let config_0 = {
|
||||
foo: 'bar',
|
||||
bar: { foo: 'bar', bar: { foo: { message: 'this', willbe: 'clobbered' } } },
|
||||
boofar: 1,
|
||||
};
|
||||
const config_1 = {
|
||||
let config_1 = {
|
||||
foo: 'foo',
|
||||
bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } },
|
||||
foobar: 'foobar',
|
||||
};
|
||||
const result = assignWithDepth(config_0, config_1);
|
||||
let result = assignWithDepth(config_0, config_1);
|
||||
expect(result).toEqual({
|
||||
foo: 'foo',
|
||||
bar: { foo: 'foo', bar: { foo: { message: 'clobbered other foo' } } },
|
||||
@@ -82,7 +82,7 @@ describe('when assignWithDepth: should merge objects within objects', function (
|
||||
});
|
||||
});
|
||||
it('should handle depth:3 types (merge with clobber because assignWithDepth::depth == 1)', function () {
|
||||
const config_0 = {
|
||||
let config_0 = {
|
||||
foo: 'bar',
|
||||
bar: {
|
||||
foo: 'bar',
|
||||
@@ -90,12 +90,12 @@ describe('when assignWithDepth: should merge objects within objects', function (
|
||||
},
|
||||
boofar: 1,
|
||||
};
|
||||
const config_1 = {
|
||||
let config_1 = {
|
||||
foo: 'foo',
|
||||
bar: { foo: 'foo', bar: { foo: { message: 'this' } } },
|
||||
foobar: 'foobar',
|
||||
};
|
||||
const result = assignWithDepth(config_0, config_1, { depth: 1 });
|
||||
let result = assignWithDepth(config_0, config_1, { depth: 1 });
|
||||
expect(result).toEqual({
|
||||
foo: 'foo',
|
||||
bar: { foo: 'foo', bar: { foo: { message: 'this' } } },
|
||||
@@ -104,17 +104,17 @@ describe('when assignWithDepth: should merge objects within objects', function (
|
||||
});
|
||||
});
|
||||
it('should handle depth:3 types (merge with no clobber because assignWithDepth::depth == 3)', function () {
|
||||
const config_0 = {
|
||||
let config_0 = {
|
||||
foo: 'bar',
|
||||
bar: { foo: 'bar', bar: { foo: { message: '', willbe: 'present' } } },
|
||||
boofar: 1,
|
||||
};
|
||||
const config_1 = {
|
||||
let config_1 = {
|
||||
foo: 'foo',
|
||||
bar: { foo: 'foo', bar: { foo: { message: 'this' } } },
|
||||
foobar: 'foobar',
|
||||
};
|
||||
const result = assignWithDepth(config_0, config_1, { depth: 3 });
|
||||
let result = assignWithDepth(config_0, config_1, { depth: 3 });
|
||||
expect(result).toEqual({
|
||||
foo: 'foo',
|
||||
bar: { foo: 'foo', bar: { foo: { message: 'this', willbe: 'present' } } },
|
||||
@@ -125,8 +125,8 @@ describe('when assignWithDepth: should merge objects within objects', function (
|
||||
});
|
||||
describe('when memoizing', function () {
|
||||
it('should return the same value', function () {
|
||||
const fib: any = memoize(
|
||||
function (n: number, x: string, canary: { flag: boolean }) {
|
||||
const fib = memoize(
|
||||
function (n, x, canary) {
|
||||
canary.flag = true;
|
||||
if (n < 2) {
|
||||
return 1;
|
||||
@@ -260,7 +260,7 @@ describe('when formatting urls', function () {
|
||||
it('should handle links', function () {
|
||||
const url = 'https://mermaid-js.github.io/mermaid/#/';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
expect(result).toEqual(url);
|
||||
|
||||
@@ -271,7 +271,7 @@ describe('when formatting urls', function () {
|
||||
it('should handle anchors', function () {
|
||||
const url = '#interaction';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
expect(result).toEqual(url);
|
||||
|
||||
@@ -282,7 +282,7 @@ describe('when formatting urls', function () {
|
||||
it('should handle mailto', function () {
|
||||
const url = 'mailto:user@user.user';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
expect(result).toEqual(url);
|
||||
|
||||
@@ -293,7 +293,7 @@ describe('when formatting urls', function () {
|
||||
it('should handle other protocols', function () {
|
||||
const url = 'notes://do-your-thing/id';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
expect(result).toEqual(url);
|
||||
|
||||
@@ -304,7 +304,7 @@ describe('when formatting urls', function () {
|
||||
it('should handle scripts', function () {
|
||||
const url = 'javascript:alert("test")';
|
||||
|
||||
const config = { securityLevel: 'loose' };
|
||||
let config = { securityLevel: 'loose' };
|
||||
let result = utils.formatUrl(url, config);
|
||||
expect(result).toEqual(url);
|
||||
|
||||
@@ -425,42 +425,6 @@ describe('when parsing font sizes', function () {
|
||||
});
|
||||
|
||||
it('handles unparseable input', function () {
|
||||
// @ts-expect-error Explicitly testing unparsable input
|
||||
expect(utils.parseFontSize({ fontSize: 14 })).toEqual([undefined, undefined]);
|
||||
});
|
||||
});
|
||||
|
||||
describe('cleanAndMerge', () => {
|
||||
test('should merge objects', () => {
|
||||
expect(cleanAndMerge({ a: 1, b: 2 }, { b: 3 })).toEqual({ a: 1, b: 3 });
|
||||
expect(cleanAndMerge({ a: 1 }, { a: 2 })).toEqual({ a: 2 });
|
||||
});
|
||||
|
||||
test('should remove undefined values', () => {
|
||||
expect(cleanAndMerge({ a: 1, b: 2 }, { b: undefined })).toEqual({ a: 1, b: 2 });
|
||||
expect(cleanAndMerge({ a: 1, b: 2 }, { a: 2, b: undefined })).toEqual({ a: 2, b: 2 });
|
||||
expect(cleanAndMerge({ a: 1, b: { c: 2 } }, { a: 2, b: undefined })).toEqual({
|
||||
a: 2,
|
||||
b: { c: 2 },
|
||||
});
|
||||
// @ts-expect-error Explicitly testing different type
|
||||
expect(cleanAndMerge({ a: 1, b: { c: 2 } }, { a: 2, b: { c: undefined } })).toEqual({
|
||||
a: 2,
|
||||
b: { c: 2 },
|
||||
});
|
||||
});
|
||||
|
||||
test('should create deep copies of object', () => {
|
||||
const input: { a: number; b?: number } = { a: 1 };
|
||||
const output = cleanAndMerge(input, { b: 2 });
|
||||
expect(output).toEqual({ a: 1, b: 2 });
|
||||
output.b = 3;
|
||||
expect(input).toEqual({ a: 1 });
|
||||
|
||||
const inputDeep = { a: { b: 1 } };
|
||||
const outputDeep = cleanAndMerge(inputDeep, { a: { b: 2 } });
|
||||
expect(outputDeep).toEqual({ a: { b: 2 } });
|
||||
outputDeep.a.b = 3;
|
||||
expect(inputDeep).toEqual({ a: { b: 1 } });
|
||||
});
|
||||
});
|
@@ -31,8 +31,6 @@ import { detectType } from './diagram-api/detectType.js';
|
||||
import assignWithDepth from './assignWithDepth.js';
|
||||
import { MermaidConfig } from './config.type.js';
|
||||
import memoize from 'lodash-es/memoize.js';
|
||||
import merge from 'lodash-es/merge.js';
|
||||
import { directiveRegex } from './diagram-api/regexes.js';
|
||||
|
||||
export const ZERO_WIDTH_SPACE = '\u200b';
|
||||
|
||||
@@ -59,7 +57,7 @@ const d3CurveTypes = {
|
||||
curveStepAfter: curveStepAfter,
|
||||
curveStepBefore: curveStepBefore,
|
||||
};
|
||||
|
||||
const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
|
||||
const directiveWithoutOpen =
|
||||
/\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
|
||||
|
||||
@@ -97,36 +95,32 @@ const directiveWithoutOpen =
|
||||
* @param config - Optional mermaid configuration object.
|
||||
* @returns The json object representing the init passed to mermaid.initialize()
|
||||
*/
|
||||
export const detectInit = function (
|
||||
text: string,
|
||||
config?: MermaidConfig
|
||||
): MermaidConfig | undefined {
|
||||
export const detectInit = function (text: string, config?: MermaidConfig): MermaidConfig {
|
||||
const inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
|
||||
let results = {};
|
||||
|
||||
if (Array.isArray(inits)) {
|
||||
const args = inits.map((init) => init.args);
|
||||
sanitizeDirective(args);
|
||||
directiveSanitizer(args);
|
||||
|
||||
results = assignWithDepth(results, [...args]);
|
||||
} else {
|
||||
results = inits.args;
|
||||
}
|
||||
|
||||
if (!results) {
|
||||
return;
|
||||
if (results) {
|
||||
let type = detectType(text, config);
|
||||
['config'].forEach((prop) => {
|
||||
if (results[prop] !== undefined) {
|
||||
if (type === 'flowchart-v2') {
|
||||
type = 'flowchart';
|
||||
}
|
||||
results[type] = results[prop];
|
||||
delete results[prop];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
let type = detectType(text, config);
|
||||
['config'].forEach((prop) => {
|
||||
if (results[prop] !== undefined) {
|
||||
if (type === 'flowchart-v2') {
|
||||
type = 'flowchart';
|
||||
}
|
||||
results[type] = results[prop];
|
||||
delete results[prop];
|
||||
}
|
||||
});
|
||||
|
||||
// Todo: refactor this, these results are never used
|
||||
return results;
|
||||
};
|
||||
|
||||
@@ -168,10 +162,10 @@ export const detectDirective = function (
|
||||
);
|
||||
let match;
|
||||
const result = [];
|
||||
while ((match = directiveRegex.exec(text)) !== null) {
|
||||
while ((match = directive.exec(text)) !== null) {
|
||||
// This is necessary to avoid infinite loops with zero-width matches
|
||||
if (match.index === directiveRegex.lastIndex) {
|
||||
directiveRegex.lastIndex++;
|
||||
if (match.index === directive.lastIndex) {
|
||||
directive.lastIndex++;
|
||||
}
|
||||
if (
|
||||
(match && !type) ||
|
||||
@@ -808,7 +802,7 @@ export const calculateTextDimensions: (
|
||||
);
|
||||
|
||||
export const initIdGenerator = class iterator {
|
||||
constructor(deterministic, seed?: any) {
|
||||
constructor(deterministic, seed) {
|
||||
this.deterministic = deterministic;
|
||||
// TODO: Seed is only used for length?
|
||||
this.seed = seed;
|
||||
@@ -847,63 +841,67 @@ export const entityDecode = function (html: string): string {
|
||||
*
|
||||
* @param args - Directive's JSON
|
||||
*/
|
||||
export const sanitizeDirective = (args: unknown): void => {
|
||||
log.debug('sanitizeDirective called with', args);
|
||||
export const directiveSanitizer = (args: any) => {
|
||||
log.debug('directiveSanitizer called with', args);
|
||||
if (typeof args === 'object') {
|
||||
// check for array
|
||||
if (args.length) {
|
||||
args.forEach((arg) => directiveSanitizer(arg));
|
||||
} else {
|
||||
// This is an object
|
||||
Object.keys(args).forEach((key) => {
|
||||
log.debug('Checking key', key);
|
||||
if (key.startsWith('__')) {
|
||||
log.debug('sanitize deleting __ option', key);
|
||||
delete args[key];
|
||||
}
|
||||
|
||||
// Return if not an object
|
||||
if (typeof args !== 'object' || args == null) {
|
||||
return;
|
||||
}
|
||||
if (key.includes('proto')) {
|
||||
log.debug('sanitize deleting proto option', key);
|
||||
delete args[key];
|
||||
}
|
||||
|
||||
// Sanitize each element if an array
|
||||
if (Array.isArray(args)) {
|
||||
args.forEach((arg) => sanitizeDirective(arg));
|
||||
return;
|
||||
}
|
||||
if (key.includes('constr')) {
|
||||
log.debug('sanitize deleting constr option', key);
|
||||
delete args[key];
|
||||
}
|
||||
|
||||
// Sanitize each key if an object
|
||||
for (const key of Object.keys(args)) {
|
||||
log.debug('Checking key', key);
|
||||
if (
|
||||
key.startsWith('__') ||
|
||||
key.includes('proto') ||
|
||||
key.includes('constr') ||
|
||||
!configKeys.has(key) ||
|
||||
args[key] == null
|
||||
) {
|
||||
log.debug('sanitize deleting key: ', key);
|
||||
delete args[key];
|
||||
continue;
|
||||
}
|
||||
|
||||
// Recurse if an object
|
||||
if (typeof args[key] === 'object') {
|
||||
log.debug('sanitizing object', key);
|
||||
sanitizeDirective(args[key]);
|
||||
continue;
|
||||
}
|
||||
|
||||
const cssMatchers = ['themeCSS', 'fontFamily', 'altFontFamily'];
|
||||
for (const cssKey of cssMatchers) {
|
||||
if (key.includes(cssKey)) {
|
||||
log.debug('sanitizing css option', key);
|
||||
args[key] = sanitizeCss(args[key]);
|
||||
}
|
||||
if (key.includes('themeCSS')) {
|
||||
log.debug('sanitizing themeCss option');
|
||||
args[key] = sanitizeCss(args[key]);
|
||||
}
|
||||
if (key.includes('fontFamily')) {
|
||||
log.debug('sanitizing fontFamily option');
|
||||
args[key] = sanitizeCss(args[key]);
|
||||
}
|
||||
if (key.includes('altFontFamily')) {
|
||||
log.debug('sanitizing altFontFamily option');
|
||||
args[key] = sanitizeCss(args[key]);
|
||||
}
|
||||
if (!configKeys.includes(key)) {
|
||||
log.debug('sanitize deleting option', key);
|
||||
delete args[key];
|
||||
} else {
|
||||
if (typeof args[key] === 'object') {
|
||||
log.debug('sanitize deleting object', key);
|
||||
directiveSanitizer(args[key]);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
if (args.themeVariables) {
|
||||
for (const k of Object.keys(args.themeVariables)) {
|
||||
const kArr = Object.keys(args.themeVariables);
|
||||
for (const k of kArr) {
|
||||
const val = args.themeVariables[k];
|
||||
if (val?.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) {
|
||||
if (val && val.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) {
|
||||
args.themeVariables[k] = '';
|
||||
}
|
||||
}
|
||||
}
|
||||
log.debug('After sanitization', args);
|
||||
};
|
||||
|
||||
export const sanitizeCss = (str: string): string => {
|
||||
export const sanitizeCss = (str) => {
|
||||
let startCnt = 0;
|
||||
let endCnt = 0;
|
||||
|
||||
@@ -996,17 +994,12 @@ export const parseFontSize = (fontSize: string | number | undefined): [number?,
|
||||
}
|
||||
};
|
||||
|
||||
export function cleanAndMerge<T>(defaultData: T, data?: Partial<T>): T {
|
||||
return merge({}, defaultData, data);
|
||||
}
|
||||
|
||||
export default {
|
||||
assignWithDepth,
|
||||
wrapLabel,
|
||||
calculateTextHeight,
|
||||
calculateTextWidth,
|
||||
calculateTextDimensions,
|
||||
cleanAndMerge,
|
||||
detectInit,
|
||||
detectDirective,
|
||||
isSubstringInArray,
|
||||
@@ -1020,8 +1013,8 @@ export default {
|
||||
random,
|
||||
runFunc,
|
||||
entityDecode,
|
||||
initIdGenerator,
|
||||
sanitizeDirective,
|
||||
initIdGenerator: initIdGenerator,
|
||||
directiveSanitizer,
|
||||
sanitizeCss,
|
||||
insertTitle,
|
||||
parseFontSize,
|
||||
|
2796
pnpm-lock.yaml
generated
2796
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -5,24 +5,20 @@
|
||||
* (i.e. the root of the Mermaid project).
|
||||
*/
|
||||
|
||||
import { readFile, writeFile } from 'node:fs/promises';
|
||||
import { readFileSync, writeFileSync } from 'node:fs';
|
||||
import prettier from 'prettier';
|
||||
|
||||
const main = async () => {
|
||||
const filepath = './cSpell.json';
|
||||
const cSpell: { words: string[] } = JSON.parse(await readFile(filepath, 'utf8'));
|
||||
const filepath = './cSpell.json';
|
||||
const cSpell: { words: string[] } = JSON.parse(readFileSync(filepath, 'utf8'));
|
||||
|
||||
cSpell.words = [...new Set(cSpell.words.map((word) => word.toLowerCase()))];
|
||||
cSpell.words.sort((a, b) => a.localeCompare(b));
|
||||
cSpell.words = [...new Set(cSpell.words.map((word) => word.toLowerCase()))];
|
||||
cSpell.words.sort((a, b) => a.localeCompare(b));
|
||||
|
||||
const prettierConfig = (await prettier.resolveConfig(filepath)) ?? {};
|
||||
await writeFile(
|
||||
const prettierConfig = prettier.resolveConfig.sync(filepath) ?? {};
|
||||
writeFileSync(
|
||||
filepath,
|
||||
prettier.format(JSON.stringify(cSpell), {
|
||||
...prettierConfig,
|
||||
filepath,
|
||||
await prettier.format(JSON.stringify(cSpell), {
|
||||
...prettierConfig,
|
||||
filepath,
|
||||
}),
|
||||
);
|
||||
};
|
||||
|
||||
void main();
|
||||
})
|
||||
);
|
||||
|
@@ -12,7 +12,7 @@
|
||||
"author": "",
|
||||
"license": "ISC",
|
||||
"devDependencies": {
|
||||
"webpack": "^5.88.2",
|
||||
"webpack": "^5.74.0",
|
||||
"webpack-cli": "^4.10.0",
|
||||
"webpack-dev-server": "^4.11.1"
|
||||
},
|
||||
|
@@ -5,14 +5,5 @@
|
||||
// ensure that nobody can accidentally use this config for a build
|
||||
"noEmit": true
|
||||
},
|
||||
"include": [
|
||||
"packages",
|
||||
"tests",
|
||||
"scripts",
|
||||
"cypress",
|
||||
"__mocks__",
|
||||
"./.eslintrc.cjs",
|
||||
"./*",
|
||||
"demos/dev"
|
||||
]
|
||||
"include": ["packages", "tests", "scripts", "cypress", "__mocks__", "./.eslintrc.cjs", "./*"]
|
||||
}
|
||||
|
Reference in New Issue
Block a user