Merge branch 'develop' into origin/3258_Flowchart_nodeSpacing_Subgraph

This commit is contained in:
rowanfr
2024-03-06 12:06:06 -06:00
committed by GitHub
171 changed files with 4395 additions and 1990 deletions

30
.build/common.ts Normal file
View File

@@ -0,0 +1,30 @@
/**
* Shared common options for both ESBuild and Vite
*/
export const packageOptions = {
parser: {
name: 'mermaid-parser',
packageName: 'parser',
file: 'index.ts',
},
mermaid: {
name: 'mermaid',
packageName: 'mermaid',
file: 'mermaid.ts',
},
'mermaid-example-diagram': {
name: 'mermaid-example-diagram',
packageName: 'mermaid-example-diagram',
file: 'detector.ts',
},
'mermaid-zenuml': {
name: 'mermaid-zenuml',
packageName: 'mermaid-zenuml',
file: 'detector.ts',
},
'mermaid-flowchart-elk': {
name: 'mermaid-flowchart-elk',
packageName: 'mermaid-flowchart-elk',
file: 'detector.ts',
},
} as const;

View File

@@ -0,0 +1,5 @@
import { generate } from 'langium-cli';
export async function generateLangium() {
await generate({ file: `./packages/parser/langium-config.json` });
}

124
.build/jsonSchema.ts Normal file
View File

@@ -0,0 +1,124 @@
import { load, JSON_SCHEMA } from 'js-yaml';
import assert from 'node:assert';
import Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
import type { MermaidConfig, BaseDiagramConfig } from '../packages/mermaid/src/config.type.js';
/**
* All of the keys in the mermaid config that have a mermaid diagram config.
*/
const MERMAID_CONFIG_DIAGRAM_KEYS = [
'flowchart',
'sequence',
'gantt',
'journey',
'class',
'state',
'er',
'pie',
'quadrantChart',
'xyChart',
'requirement',
'mindmap',
'timeline',
'gitGraph',
'c4',
'sankey',
'block',
'packet',
] as const;
/**
* Generate default values from the JSON Schema.
*
* AJV does not support nested default values yet (or default values with $ref),
* so we need to manually find them (this may be fixed in ajv v9).
*
* @param mermaidConfigSchema - The Mermaid JSON Schema to use.
* @returns The default mermaid config object.
*/
function generateDefaults(mermaidConfigSchema: JSONSchemaType<MermaidConfig>) {
const ajv = new Ajv2019({
useDefaults: true,
allowUnionTypes: true,
strict: true,
});
ajv.addKeyword({
keyword: 'meta:enum', // used by jsonschema2md
errors: false,
});
ajv.addKeyword({
keyword: 'tsType', // used by json-schema-to-typescript
errors: false,
});
// ajv currently doesn't support nested default values, see https://github.com/ajv-validator/ajv/issues/1718
// (may be fixed in v9) so we need to manually use sub-schemas
const mermaidDefaultConfig = {};
assert.ok(mermaidConfigSchema.$defs);
const baseDiagramConfig = mermaidConfigSchema.$defs.BaseDiagramConfig;
for (const key of MERMAID_CONFIG_DIAGRAM_KEYS) {
const subSchemaRef = mermaidConfigSchema.properties[key].$ref;
const [root, defs, defName] = subSchemaRef.split('/');
assert.strictEqual(root, '#');
assert.strictEqual(defs, '$defs');
const subSchema = {
$schema: mermaidConfigSchema.$schema,
$defs: mermaidConfigSchema.$defs,
...mermaidConfigSchema.$defs[defName],
} as JSONSchemaType<BaseDiagramConfig>;
const validate = ajv.compile(subSchema);
mermaidDefaultConfig[key] = {};
for (const required of subSchema.required ?? []) {
if (subSchema.properties[required] === undefined && baseDiagramConfig.properties[required]) {
mermaidDefaultConfig[key][required] = baseDiagramConfig.properties[required].default;
}
}
if (!validate(mermaidDefaultConfig[key])) {
throw new Error(
`schema for subconfig ${key} does not have valid defaults! Errors were ${JSON.stringify(
validate.errors,
undefined,
2
)}`
);
}
}
const validate = ajv.compile(mermaidConfigSchema);
if (!validate(mermaidDefaultConfig)) {
throw new Error(
`Mermaid config JSON Schema does not have valid defaults! Errors were ${JSON.stringify(
validate.errors,
undefined,
2
)}`
);
}
return mermaidDefaultConfig;
}
export const loadSchema = (src: string, filename: string): JSONSchemaType<MermaidConfig> => {
const jsonSchema = load(src, {
filename,
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
schema: JSON_SCHEMA,
}) as JSONSchemaType<MermaidConfig>;
return jsonSchema;
};
export const getDefaults = (schema: JSONSchemaType<MermaidConfig>) => {
return `export default ${JSON.stringify(generateDefaults(schema), undefined, 2)};`;
};
export const getSchema = (schema: JSONSchemaType<MermaidConfig>) => {
return `export default ${JSON.stringify(schema, undefined, 2)};`;
};

18
.build/types.ts Normal file
View File

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

View File

@@ -53,6 +53,7 @@ GENERICTYPE
getBoundarys getBoundarys
grammr grammr
graphtype graphtype
iife
interp interp
introdcued introdcued
INVTRAPEND INVTRAPEND
@@ -74,11 +75,13 @@ loglevel
LOGMSG LOGMSG
lookaheads lookaheads
mdast mdast
metafile
minlen minlen
Mstartx Mstartx
MULT MULT
NODIR NODIR
NSTR NSTR
outdir
Qcontrolx Qcontrolx
reinit reinit
rels rels

View File

@@ -36,6 +36,7 @@ jsfiddle
jsonschema jsonschema
katex katex
khroma khroma
langium
mathml mathml
matplotlib matplotlib
mdbook mdbook

65
.esbuild/build.ts Normal file
View File

@@ -0,0 +1,65 @@
import { build } from 'esbuild';
import { mkdir, writeFile } from 'node:fs/promises';
import { packageOptions } from '../.build/common.js';
import { generateLangium } from '../.build/generateLangium.js';
import { MermaidBuildOptions, defaultOptions, getBuildConfig } from './util.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));
}
}
};
const handler = (e) => {
console.error(e);
process.exit(1);
};
const main = async () => {
await generateLangium();
await mkdir('stats').catch(() => {});
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
// it should build `parser` before `mermaid` because it's a dependency
for (const pkg of packageNames) {
await buildPackage(pkg).catch(handler);
}
};
void main();

15
.esbuild/jisonPlugin.ts Normal file
View File

@@ -0,0 +1,15 @@
import { readFile } from 'node:fs/promises';
import { transformJison } from '../.build/jisonTransformer.js';
import { Plugin } from 'esbuild';
export const jisonPlugin: Plugin = {
name: 'jison',
setup(build) {
build.onLoad({ filter: /\.jison$/ }, async (args) => {
// Load the file from the file system
const source = await readFile(args.path, 'utf8');
const contents = transformJison(source);
return { contents, warnings: [] };
});
},
};

View File

@@ -0,0 +1,35 @@
import type { JSONSchemaType } from 'ajv/dist/2019.js';
import type { MermaidConfig } from '../packages/mermaid/src/config.type.js';
import { readFile } from 'node:fs/promises';
import { getDefaults, getSchema, loadSchema } from '../.build/jsonSchema.js';
/**
* ESBuild plugin that handles JSON Schemas saved as a `.schema.yaml` file.
*
* Use `my-example.schema.yaml?only-defaults=true` to only load the default values.
*/
export const jsonSchemaPlugin = {
name: 'json-schema-plugin',
setup(build) {
let schema: JSONSchemaType<MermaidConfig> | undefined = undefined;
let content = '';
build.onLoad({ filter: /config\.schema\.yaml$/ }, async (args) => {
// Load the file from the file system
const source = await readFile(args.path, 'utf8');
const resolvedSchema: JSONSchemaType<MermaidConfig> =
content === source && schema ? schema : loadSchema(source, args.path);
if (content !== source) {
content = source;
schema = resolvedSchema;
}
const contents = args.suffix.includes('only-defaults')
? getDefaults(resolvedSchema)
: getSchema(resolvedSchema);
return { contents, warnings: [] };
});
},
};
export default jsonSchemaPlugin;

102
.esbuild/server.ts Normal file
View File

@@ -0,0 +1,102 @@
import express from 'express';
import type { NextFunction, Request, Response } from 'express';
import cors from 'cors';
import { getBuildConfig, defaultOptions } from './util.js';
import { context } from 'esbuild';
import chokidar from 'chokidar';
import { generateLangium } from '../.build/generateLangium.js';
import { packageOptions } from '../.build/common.js';
const configs = Object.values(packageOptions).map(({ packageName }) =>
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: packageName })
);
const mermaidIIFEConfig = getBuildConfig({
...defaultOptions,
minify: false,
core: false,
entryName: 'mermaid',
format: 'iife',
});
configs.push(mermaidIIFEConfig);
const contexts = await Promise.all(configs.map((config) => context(config)));
const rebuildAll = async () => {
console.time('Rebuild time');
await Promise.all(contexts.map((ctx) => ctx.rebuild())).catch((e) => console.error(e));
console.timeEnd('Rebuild time');
};
let clients: { id: number; response: Response }[] = [];
function eventsHandler(request: Request, response: Response, next: NextFunction) {
const headers = {
'Content-Type': 'text/event-stream',
Connection: 'keep-alive',
'Cache-Control': 'no-cache',
};
response.writeHead(200, headers);
const clientId = Date.now();
clients.push({
id: clientId,
response,
});
request.on('close', () => {
clients = clients.filter((client) => client.id !== clientId);
});
}
let timeoutId: NodeJS.Timeout | undefined = undefined;
/**
* Debounce file change events to avoid rebuilding multiple times.
*/
function handleFileChange() {
if (timeoutId !== undefined) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(async () => {
await rebuildAll();
sendEventsToAll();
timeoutId = undefined;
}, 100);
}
function sendEventsToAll() {
clients.forEach(({ response }) => response.write(`data: ${Date.now()}\n\n`));
}
async function createServer() {
await generateLangium();
handleFileChange();
const app = express();
chokidar
.watch('**/src/**/*.{js,ts,langium,yaml,json}', {
ignoreInitial: true,
ignored: [/node_modules/, /dist/, /docs/, /coverage/],
})
.on('all', async (event, path) => {
// Ignore other events.
if (!['add', 'change'].includes(event)) {
return;
}
if (/\.langium$/.test(path)) {
await generateLangium();
}
console.log(`${path} changed. Rebuilding...`);
handleFileChange();
});
app.use(cors());
app.get('/events', eventsHandler);
for (const { packageName } of Object.values(packageOptions)) {
app.use(express.static(`./packages/${packageName}/dist`));
}
app.use(express.static('demos'));
app.use(express.static('cypress/platform'));
app.listen(9000, () => {
console.log(`Listening on http://localhost:9000`);
});
}
createServer();

101
.esbuild/util.ts Normal file
View File

@@ -0,0 +1,101 @@
import { resolve } from 'path';
import { fileURLToPath } from 'url';
import type { BuildOptions } from 'esbuild';
import { readFileSync } from 'fs';
import jsonSchemaPlugin from './jsonSchemaPlugin.js';
import { packageOptions } from '../.build/common.js';
import { jisonPlugin } from './jisonPlugin.js';
const __dirname = fileURLToPath(new URL('.', import.meta.url));
export interface MermaidBuildOptions {
minify: boolean;
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,
minify: true,
keepNames: true,
platform: 'browser',
tsconfig: 'tsconfig.json',
resolveExtensions: ['.ts', '.js', '.json', '.jison', '.yaml'],
external: ['require', 'fs', 'path'],
outdir: 'dist',
plugins: [jisonPlugin, jsonSchemaPlugin],
sourcemap: 'external',
...override,
};
};
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;
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}`,
},
metafile,
minify,
logLevel: 'info',
chunkNames: `chunks/${outFileName}/[name]-[hash]`,
define: {
'import.meta.vitest': 'undefined',
},
});
if (core) {
const { dependencies } = JSON.parse(
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.
// Ignore dependencies and any dependencies of dependencies
external.push(...Object.keys(dependencies));
output.external = external;
}
if (format === 'iife') {
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;',
};
output.outExtension = { '.js': '.js' };
} else {
output.format = 'esm';
output.splitting = true;
output.outExtension = { '.js': '.mjs' };
}
return output;
};

View File

@@ -6,3 +6,6 @@ cypress/plugins/index.js
coverage coverage
*.json *.json
node_modules node_modules
# autogenereated by langium-cli
generated/

View File

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

View File

@@ -15,6 +15,7 @@ on:
permissions: permissions:
contents: read contents: read
pull-requests: write
env: env:
# For PRs and MergeQueues, the target commit is used, and for push events, github.event.previous is used. # For PRs and MergeQueues, the target commit is used, and for push events, github.event.previous is used.
@@ -48,11 +49,26 @@ jobs:
with: with:
ref: ${{ env.targetHash }} ref: ${{ env.targetHash }}
- name: Install dependencies
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
uses: cypress-io/github-action@v6
with:
# just perform install
runTests: false
- name: Build
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' && github.event_name == 'pull_request' }}
run: |
pnpm run build:viz
mkdir -p cypress/snapshots/stats/base
mv stats cypress/snapshots/stats/base
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@v4 uses: cypress-io/github-action@v6
id: cypress-snapshot-gen id: cypress-snapshot-gen
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }} if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
with: with:
install: false
start: pnpm run dev start: pnpm run dev
wait-on: 'http://localhost:9000' wait-on: 'http://localhost:9000'
browser: chrome browser: chrome
@@ -86,15 +102,42 @@ jobs:
path: ./cypress/snapshots path: ./cypress/snapshots
key: ${{ runner.os }}-snapshots-${{ env.targetHash }} key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
- name: Install dependencies
uses: cypress-io/github-action@v6
with:
runTests: false
- name: Build
id: size
if: ${{ github.event_name == 'pull_request' && matrix.containers == 1 }}
run: |
pnpm run build:viz
mv stats cypress/snapshots/stats/head
{
echo 'size_diff<<EOF'
npx tsx scripts/size.ts
echo EOF
} >> "$GITHUB_OUTPUT"
# Size diff only needs to be posted from one job, on PRs.
- name: Comment PR size difference
if: ${{ github.event_name == 'pull_request' && matrix.containers == 1 }}
uses: thollander/actions-comment-pull-request@v2
with:
message: |
${{ steps.size.outputs.size_diff }}
comment_tag: size-diff
# Install NPM dependencies, cache them correctly # Install NPM dependencies, cache them correctly
# and run all Cypress tests # and run all Cypress tests
- name: Cypress run - name: Cypress run
uses: cypress-io/github-action@v4 uses: cypress-io/github-action@v6
id: cypress id: cypress
# If CYPRESS_RECORD_KEY is set, run in parallel on all containers # 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 # Otherwise (e.g. if running from fork), we run on a single container only
if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }} if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }}
with: with:
install: false
start: pnpm run dev:coverage start: pnpm run dev:coverage
wait-on: 'http://localhost:9000' wait-on: 'http://localhost:9000'
browser: chrome browser: chrome

4
.gitignore vendored
View File

@@ -46,4 +46,8 @@ stats/
demos/dev/** demos/dev/**
!/demos/dev/example.html !/demos/dev/example.html
!/demos/dev/reload.js
tsx-0/** tsx-0/**
# autogenereated by langium-cli
generated/

View File

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

View File

@@ -3,11 +3,12 @@ import { resolve } from 'path';
import { fileURLToPath } from 'url'; import { fileURLToPath } from 'url';
import jisonPlugin from './jisonPlugin.js'; import jisonPlugin from './jisonPlugin.js';
import jsonSchemaPlugin from './jsonSchemaPlugin.js'; import jsonSchemaPlugin from './jsonSchemaPlugin.js';
import { readFileSync } from 'fs';
import typescript from '@rollup/plugin-typescript'; import typescript from '@rollup/plugin-typescript';
import { visualizer } from 'rollup-plugin-visualizer'; import { visualizer } from 'rollup-plugin-visualizer';
import type { TemplateType } from 'rollup-plugin-visualizer/dist/plugin/template-types.js'; import type { TemplateType } from 'rollup-plugin-visualizer/dist/plugin/template-types.js';
import istanbul from 'vite-plugin-istanbul'; import istanbul from 'vite-plugin-istanbul';
import { packageOptions } from '../.build/common.js';
import { generateLangium } from '../.build/generateLangium.js';
const visualize = process.argv.includes('--visualize'); const visualize = process.argv.includes('--visualize');
const watch = process.argv.includes('--watch'); const watch = process.argv.includes('--watch');
@@ -36,24 +37,6 @@ const visualizerOptions = (packageName: string, core = false): PluginOption[] =>
); );
}; };
const packageOptions = {
mermaid: {
name: 'mermaid',
packageName: 'mermaid',
file: 'mermaid.ts',
},
'mermaid-example-diagram': {
name: 'mermaid-example-diagram',
packageName: 'mermaid-example-diagram',
file: 'detector.ts',
},
'mermaid-zenuml': {
name: 'mermaid-zenuml',
packageName: 'mermaid-zenuml',
file: 'detector.ts',
},
};
interface BuildOptions { interface BuildOptions {
minify: boolean | 'esbuild'; minify: boolean | 'esbuild';
core?: boolean; core?: boolean;
@@ -72,34 +55,8 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
sourcemap, sourcemap,
entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`, entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`,
}, },
{
name,
format: 'umd',
sourcemap,
entryFileNames: `${name}${minify ? '.min' : ''}.js`,
},
]; ];
if (core) {
const { dependencies } = JSON.parse(
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.
// Ignore dependencies and any dependencies of dependencies
// Adapted from the RegEx used by `rollup-plugin-node`
external.push(new RegExp('^(?:' + Object.keys(dependencies).join('|') + ')(?:/.+)?$'));
// This needs to be an array. Otherwise vite will build esm & umd with same name and overwrite esm with umd.
output = [
{
name,
format: 'esm',
sourcemap,
entryFileNames: `${name}.core.mjs`,
},
];
}
const config: InlineConfig = { const config: InlineConfig = {
configFile: false, configFile: false,
build: { build: {
@@ -129,7 +86,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
// @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite // @ts-expect-error According to the type definitions, rollup plugins are incompatible with vite
typescript({ compilerOptions: { declaration: false } }), typescript({ compilerOptions: { declaration: false } }),
istanbul({ istanbul({
exclude: ['node_modules', 'test/', '__mocks__'], exclude: ['node_modules', 'test/', '__mocks__', 'generated'],
extension: ['.js', '.ts'], extension: ['.js', '.ts'],
requireEnv: true, requireEnv: true,
forceBuildInstrument: coverage, forceBuildInstrument: coverage,
@@ -149,24 +106,28 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
const buildPackage = async (entryName: keyof typeof packageOptions) => { const buildPackage = async (entryName: keyof typeof packageOptions) => {
await build(getBuildConfig({ minify: false, entryName })); await build(getBuildConfig({ minify: false, entryName }));
await build(getBuildConfig({ minify: 'esbuild', entryName }));
await build(getBuildConfig({ minify: false, core: true, entryName }));
}; };
const main = async () => { const main = async () => {
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[]; const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
for (const pkg of packageNames.filter((pkg) => !mermaidOnly || pkg === 'mermaid')) { for (const pkg of packageNames.filter(
(pkg) => !mermaidOnly || pkg === 'mermaid' || pkg === 'parser'
)) {
await buildPackage(pkg); await buildPackage(pkg);
} }
}; };
await generateLangium();
if (watch) { if (watch) {
await build(getBuildConfig({ minify: false, watch, core: false, entryName: 'parser' }));
build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' })); build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' }));
if (!mermaidOnly) { if (!mermaidOnly) {
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' })); build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' })); build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-zenuml' }));
} }
} else if (visualize) { } else if (visualize) {
await build(getBuildConfig({ minify: false, watch, core: false, entryName: 'parser' }));
await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' })); await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' }));
await build(getBuildConfig({ minify: false, core: false, entryName: 'mermaid' })); await build(getBuildConfig({ minify: false, core: false, entryName: 'mermaid' }));
} else { } else {

View File

@@ -1,10 +1,10 @@
import { transformJison } from './jisonTransformer.js'; import { transformJison } from '../.build/jisonTransformer.js';
const fileRegex = /\.(jison)$/; const fileRegex = /\.(jison)$/;
export default function jison() { export default function jison() {
return { return {
name: 'jison', name: 'jison',
transform(src: string, id: string) { transform(src: string, id: string) {
if (fileRegex.test(id)) { if (fileRegex.test(id)) {
return { return {

View File

@@ -1,110 +1,5 @@
import { load, JSON_SCHEMA } from 'js-yaml';
import assert from 'node:assert';
import Ajv2019, { type JSONSchemaType } from 'ajv/dist/2019.js';
import { PluginOption } from 'vite'; import { PluginOption } from 'vite';
import { getDefaults, getSchema, loadSchema } from '../.build/jsonSchema.js';
import type { MermaidConfig, BaseDiagramConfig } from '../packages/mermaid/src/config.type.js';
/**
* All of the keys in the mermaid config that have a mermaid diagram config.
*/
const MERMAID_CONFIG_DIAGRAM_KEYS = [
'flowchart',
'sequence',
'gantt',
'journey',
'class',
'state',
'er',
'pie',
'quadrantChart',
'xyChart',
'requirement',
'mindmap',
'timeline',
'gitGraph',
'c4',
'sankey',
'block',
] as const;
/**
* Generate default values from the JSON Schema.
*
* AJV does not support nested default values yet (or default values with $ref),
* so we need to manually find them (this may be fixed in ajv v9).
*
* @param mermaidConfigSchema - The Mermaid JSON Schema to use.
* @returns The default mermaid config object.
*/
function generateDefaults(mermaidConfigSchema: JSONSchemaType<MermaidConfig>) {
const ajv = new Ajv2019({
useDefaults: true,
allowUnionTypes: true,
strict: true,
});
ajv.addKeyword({
keyword: 'meta:enum', // used by jsonschema2md
errors: false,
});
ajv.addKeyword({
keyword: 'tsType', // used by json-schema-to-typescript
errors: false,
});
// ajv currently doesn't support nested default values, see https://github.com/ajv-validator/ajv/issues/1718
// (may be fixed in v9) so we need to manually use sub-schemas
const mermaidDefaultConfig = {};
assert.ok(mermaidConfigSchema.$defs);
const baseDiagramConfig = mermaidConfigSchema.$defs.BaseDiagramConfig;
for (const key of MERMAID_CONFIG_DIAGRAM_KEYS) {
const subSchemaRef = mermaidConfigSchema.properties[key].$ref;
const [root, defs, defName] = subSchemaRef.split('/');
assert.strictEqual(root, '#');
assert.strictEqual(defs, '$defs');
const subSchema = {
$schema: mermaidConfigSchema.$schema,
$defs: mermaidConfigSchema.$defs,
...mermaidConfigSchema.$defs[defName],
} as JSONSchemaType<BaseDiagramConfig>;
const validate = ajv.compile(subSchema);
mermaidDefaultConfig[key] = {};
for (const required of subSchema.required ?? []) {
if (subSchema.properties[required] === undefined && baseDiagramConfig.properties[required]) {
mermaidDefaultConfig[key][required] = baseDiagramConfig.properties[required].default;
}
}
if (!validate(mermaidDefaultConfig[key])) {
throw new Error(
`schema for subconfig ${key} does not have valid defaults! Errors were ${JSON.stringify(
validate.errors,
undefined,
2
)}`
);
}
}
const validate = ajv.compile(mermaidConfigSchema);
if (!validate(mermaidDefaultConfig)) {
throw new Error(
`Mermaid config JSON Schema does not have valid defaults! Errors were ${JSON.stringify(
validate.errors,
undefined,
2
)}`
);
}
return mermaidDefaultConfig;
}
/** /**
* Vite plugin that handles JSON Schemas saved as a `.schema.yaml` file. * Vite plugin that handles JSON Schemas saved as a `.schema.yaml` file.
@@ -121,32 +16,13 @@ export default function jsonSchemaPlugin(): PluginOption {
return; return;
} }
if (idAsUrl.searchParams.get('only-defaults')) { const jsonSchema = loadSchema(src, idAsUrl.pathname);
const jsonSchema = load(src, {
filename: idAsUrl.pathname,
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
schema: JSON_SCHEMA,
}) as JSONSchemaType<MermaidConfig>;
return { return {
code: `export default ${JSON.stringify(generateDefaults(jsonSchema), undefined, 2)};`, code: idAsUrl.searchParams.get('only-defaults')
? getDefaults(jsonSchema)
: getSchema(jsonSchema),
map: null, // no source map map: null, // no source map
}; };
} else {
return {
code: `export default ${JSON.stringify(
load(src, {
filename: idAsUrl.pathname,
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
schema: JSON_SCHEMA,
}),
undefined,
2
)};`,
map: null, // provide source map if available
};
}
}, },
}; };
} }

View File

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

View File

@@ -1,21 +0,0 @@
/**
* Mocked C4Context diagram renderer
*/
import { vi } from 'vitest';
export const drawPersonOrSystemArray = vi.fn();
export const drawBoundary = vi.fn();
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
drawPersonOrSystemArray,
drawBoundary,
setConf,
draw,
};

View File

@@ -1,16 +0,0 @@
/**
* Mocked class diagram v2 renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
draw,
};

View File

@@ -1,13 +0,0 @@
/**
* Mocked class diagram renderer
*/
import { vi } from 'vitest';
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
draw,
};

View File

@@ -1 +0,0 @@
// DO NOT delete this file. It is used by vitest to mock the dagre-d3 module.

View File

@@ -1,3 +0,0 @@
module.exports = function (txt: string) {
return txt;
};

View File

@@ -1,16 +0,0 @@
/**
* Mocked er diagram renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
draw,
};

View File

@@ -1,24 +0,0 @@
/**
* Mocked flow (flowchart) diagram v2 renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const addVertices = vi.fn();
export const addEdges = vi.fn();
export const getClasses = vi.fn().mockImplementation(() => {
return {};
});
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
addVertices,
addEdges,
getClasses,
draw,
};

View File

@@ -1,16 +0,0 @@
/**
* Mocked gantt diagram renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
draw,
};

View File

@@ -1,13 +0,0 @@
/**
* Mocked git (graph) diagram renderer
*/
import { vi } from 'vitest';
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
draw,
};

View File

@@ -1,15 +0,0 @@
/**
* Mocked pie (picChart) diagram renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
draw,
};

View File

@@ -1,8 +0,0 @@
/**
* Mocked pie (picChart) diagram renderer
*/
import { vi } from 'vitest';
const draw = vi.fn().mockImplementation(() => '');
export const renderer = { draw };

View File

@@ -1,13 +0,0 @@
/**
* Mocked requirement diagram renderer
*/
import { vi } from 'vitest';
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
draw,
};

View File

@@ -1,13 +0,0 @@
/**
* Mocked Sankey diagram renderer
*/
import { vi } from 'vitest';
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
draw,
};

View File

@@ -1,23 +0,0 @@
/**
* Mocked sequence diagram renderer
*/
import { vi } from 'vitest';
export const bounds = vi.fn();
export const drawActors = vi.fn();
export const drawActorsPopup = vi.fn();
export const setConf = vi.fn();
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
bounds,
drawActors,
drawActorsPopup,
setConf,
draw,
};

View File

@@ -1,22 +0,0 @@
/**
* Mocked state diagram v2 renderer
*/
import { vi } from 'vitest';
export const setConf = vi.fn();
export const getClasses = vi.fn().mockImplementation(() => {
return {};
});
export const stateDomId = vi.fn().mockImplementation(() => {
return 'mocked-stateDiagram-stateDomId';
});
export const draw = vi.fn().mockImplementation(() => {
return '';
});
export default {
setConf,
getClasses,
draw,
};

View File

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

View File

@@ -0,0 +1,11 @@
describe('IIFE', () => {
beforeEach(() => {
cy.visit('http://localhost:9000/iife.html');
});
it('should render when using mermaid.min.js', () => {
cy.window().should('have.property', 'rendered', true);
cy.get('svg').should('be.visible');
cy.get('#d2').should('contain', 'Hello');
});
});

View File

@@ -1,16 +0,0 @@
describe('Sequencediagram', () => {
it('should render a simple sequence diagrams', () => {
const url = 'http://localhost:9000/webpackUsage.html';
cy.visit(url);
cy.get('body').find('svg').should('have.length', 1);
});
it('should handle html escapings properly', () => {
const url = 'http://localhost:9000/webpackUsage.html?test-html-escaping=true';
cy.visit(url);
cy.get('body').find('svg').should('have.length', 1);
cy.get('g.label > foreignobject > div').should('not.contain.text', '<b>');
});
});

View File

@@ -0,0 +1,67 @@
import { imgSnapshotTest } from '../../helpers/util';
describe('packet structure', () => {
it('should render a simple packet diagram', () => {
imgSnapshotTest(
`packet-beta
title Hello world
0-10: "hello"
`
);
});
it('should render a complex packet diagram', () => {
imgSnapshotTest(
`packet-beta
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-223: "data"
`
);
});
it('should render a complex packet diagram with showBits false', () => {
imgSnapshotTest(
`
---
title: "Packet Diagram"
config:
packet:
showBits: false
---
packet-beta
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-223: "data"
`
);
});
});

View File

@@ -1,7 +1,7 @@
<html> <html>
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
<script src="./viewer.js" type="module"></script> <script type="module" src="./viewer.js"></script>
<link <link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap" href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"
rel="stylesheet" rel="stylesheet"

View File

@@ -11,8 +11,7 @@ example-diagram
<!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> --> <!-- <script src="//cdn.jsdelivr.net/npm/mermaid@9.1.7/dist/mermaid.min.js"></script> -->
<!-- <script type="module" src="./external-diagrams-mindmap.mjs" /> --> <!-- <script type="module" src="./external-diagrams-mindmap.mjs" /> -->
<script type="module"> <script type="module">
import exampleDiagram from '../../packages/mermaid-example-diagram/dist/mermaid-example-diagram.core.mjs'; import exampleDiagram from './mermaid-example-diagram.esm.mjs';
// import example from '../../packages/mermaid-example-diagram/src/detector';
import mermaid from './mermaid.esm.mjs'; import mermaid from './mermaid.esm.mjs';
await mermaid.registerExternalDiagrams([exampleDiagram]); await mermaid.registerExternalDiagrams([exampleDiagram]);

View File

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

View File

@@ -0,0 +1,29 @@
<html>
<body>
<pre id="diagram" class="mermaid">
graph TB
a --> b
a --> c
b --> d
c --> d
</pre>
<div id="d2"></div>
<script src="/mermaid.min.js"></script>
<script>
mermaid.initialize({
startOnLoad: true,
});
const value = `graph TD\nHello --> World`;
const el = document.getElementById('d2');
mermaid.render('did', value).then(({ svg }) => {
console.log(svg);
el.innerHTML = svg;
if (window.Cypress) {
window.rendered = true;
}
});
</script>
</body>
</html>

View File

@@ -17,20 +17,20 @@
graph TB graph TB
Function-->URL Function-->URL
click Function clickByFlow "Add a div" click Function clickByFlow "Add a div"
click URL "http://localhost:9000/webpackUsage.html" "Visit <strong>mermaid docs</strong>" click URL "http://localhost:9000/info.html" "Visit <strong>mermaid docs</strong>"
</pre> </pre>
<pre id="FirstLine" class="mermaid2"> <pre id="FirstLine" class="mermaid2">
graph TB graph TB
1Function-->2URL 1Function-->2URL
click 1Function clickByFlow "Add a div" click 1Function clickByFlow "Add a div"
click 2URL "http://localhost:9000/webpackUsage.html" "Visit <strong>mermaid docs</strong>" click 2URL "http://localhost:9000/info.html" "Visit <strong>mermaid docs</strong>"
</pre> </pre>
<pre id="FirstLine" class="mermaid2"> <pre id="FirstLine" class="mermaid2">
classDiagram classDiagram
class Test class Test
class ShapeLink class ShapeLink
link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link" link ShapeLink "http://localhost:9000/info.html" "This is a tooltip for a link"
class ShapeCallback class ShapeCallback
callback ShapeCallback "clickByClass" "This is a tooltip for a callback" callback ShapeCallback "clickByClass" "This is a tooltip for a callback"
</pre> </pre>
@@ -42,7 +42,7 @@
<pre id="FirstLine" class="mermaid"> <pre id="FirstLine" class="mermaid">
classDiagram-v2 classDiagram-v2
class ShapeLink class ShapeLink
link ShapeLink "http://localhost:9000/webpackUsage.html" "This is a tooltip for a link" link ShapeLink "http://localhost:9000/info.html" "This is a tooltip for a link"
</pre> </pre>
</div> </div>
@@ -77,7 +77,7 @@
Calling a Callback (look at the console log) :cl2, after cl1, 3d Calling a Callback (look at the console log) :cl2, after cl1, 3d
Calling a Callback with args :cl3, after cl1, 3d Calling a Callback with args :cl3, after cl1, 3d
click cl1 href "http://localhost:9000/webpackUsage.html" click cl1 href "http://localhost:9000/info.html"
click cl2 call clickByGantt() click cl2 call clickByGantt()
click cl3 call clickByGantt("test1", test2, test3) click cl3 call clickByGantt("test1", test2, test3)
@@ -102,9 +102,15 @@
div.className = 'created-by-gant-click'; div.className = 'created-by-gant-click';
div.style = 'padding: 20px; background: green; color: white;'; div.style = 'padding: 20px; background: green; color: white;';
div.innerText = 'Clicked By Gant'; div.innerText = 'Clicked By Gant';
if (arg1) div.innerText += ' ' + arg1; if (arg1) {
if (arg2) div.innerText += ' ' + arg2; div.innerText += ' ' + arg1;
if (arg3) div.innerText += ' ' + arg3; }
if (arg2) {
div.innerText += ' ' + arg2;
}
if (arg3) {
div.innerText += ' ' + arg3;
}
document.getElementsByTagName('body')[0].appendChild(div); document.getElementsByTagName('body')[0].appendChild(div);
} }

View File

@@ -1,6 +1,6 @@
import mermaid2 from './mermaid.esm.mjs'; import mermaid from './mermaid.esm.mjs';
import externalExample from '../../packages/mermaid-example-diagram/dist/mermaid-example-diagram.core.mjs'; import externalExample from './mermaid-example-diagram.esm.mjs';
import zenUml from '../../packages/mermaid-zenuml/dist/mermaid-zenuml.core.mjs'; import zenUml from './mermaid-zenuml.esm.mjs';
function b64ToUtf8(str) { function b64ToUtf8(str) {
return decodeURIComponent(escape(window.atob(str))); return decodeURIComponent(escape(window.atob(str)));
@@ -45,9 +45,9 @@ const contentLoaded = async function () {
document.getElementsByTagName('body')[0].appendChild(div); document.getElementsByTagName('body')[0].appendChild(div);
} }
await mermaid2.registerExternalDiagrams([externalExample, zenUml]); await mermaid.registerExternalDiagrams([externalExample, zenUml]);
mermaid2.initialize(graphObj.mermaid); mermaid.initialize(graphObj.mermaid);
await mermaid2.run(); await mermaid.run();
} }
}; };
@@ -95,18 +95,14 @@ const contentLoadedApi = async function () {
divs[i] = div; divs[i] = div;
} }
const defaultE2eCnf = { theme: 'forest' }; const defaultE2eCnf = { theme: 'forest', startOnLoad: false };
const cnf = merge(defaultE2eCnf, graphObj.mermaid); const cnf = merge(defaultE2eCnf, graphObj.mermaid);
mermaid2.initialize(cnf); mermaid.initialize(cnf);
for (let i = 0; i < numCodes; i++) { for (let i = 0; i < numCodes; i++) {
const { svg, bindFunctions } = await mermaid2.render( const { svg, bindFunctions } = await mermaid.render('newid' + i, graphObj.code[i], divs[i]);
'newid' + i,
graphObj.code[i],
divs[i]
);
div.innerHTML = svg; div.innerHTML = svg;
bindFunctions(div); bindFunctions(div);
} }
@@ -114,18 +110,21 @@ const contentLoadedApi = async function () {
const div = document.createElement('div'); const div = document.createElement('div');
div.id = 'block'; div.id = 'block';
div.className = 'mermaid'; div.className = 'mermaid';
console.warn('graphObj.mermaid', graphObj.mermaid); console.warn('graphObj', graphObj);
document.getElementsByTagName('body')[0].appendChild(div); document.getElementsByTagName('body')[0].appendChild(div);
mermaid2.initialize(graphObj.mermaid); mermaid.initialize(graphObj.mermaid);
const { svg, bindFunctions } = await mermaid.render('newid', graphObj.code, div);
const { svg, bindFunctions } = await mermaid2.render('newid', graphObj.code, div);
div.innerHTML = svg; div.innerHTML = svg;
console.log(div.innerHTML);
bindFunctions(div); bindFunctions(div);
} }
} }
}; };
if (typeof document !== 'undefined') { if (typeof document !== 'undefined') {
mermaid.initialize({
startOnLoad: false,
});
/*! /*!
* Wait for document loaded before starting the execution * Wait for document loaded before starting the execution
*/ */

View File

@@ -1,19 +0,0 @@
<!DOCTYPE html>
<html>
<head>
<style>
/* .mermaid {
font-family: "trebuchet ms", verdana, arial;;
} */
/* .mermaid {
font-family: 'arial';
} */
</style>
</head>
<body>
<div id="graph-to-be"></div>
<script type="module" charset="utf-8">
import './bundle-test.js';
</script>
</body>
</html>

View File

@@ -1,6 +1,5 @@
<html> <html>
<head> <head>
<script src="./viewer.js" type="module"></script>
<link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" /> <link href="https://fonts.googleapis.com/css?family=Montserrat&display=swap" rel="stylesheet" />
<style> <style>
.malware { .malware {
@@ -33,12 +32,6 @@
</script> </script>
</head> </head>
<body> <body>
<script type="module"> <script type="module" src="./viewer.js"></script>
import mermaid from './mermaid.esm.mjs';
mermaid.initialize({
startOnLoad: false,
useMaxWidth: true,
});
</script>
</body> </body>
</html> </html>

View File

@@ -3,6 +3,22 @@
<html> <html>
<head> <head>
<title>Mermaid development page</title> <title>Mermaid development page</title>
<style>
.container {
display: flex;
flex-direction: row;
}
#code {
max-width: 30vw;
width: 30vw;
}
#dynamicDiagram {
padding-left: 2em;
flex: 1;
}
</style>
</head> </head>
<body> <body>
<pre id="diagram" class="mermaid"> <pre id="diagram" class="mermaid">
@@ -13,22 +29,37 @@ graph TB
c --> d c --> d
</pre> </pre>
<hr />
Type code to view diagram:
<div class="container">
<textarea name="code" id="code" cols="30" rows="10"></textarea>
<div id="dynamicDiagram"></div> <div id="dynamicDiagram"></div>
</div>
<pre class="mermaid">info</pre>
<script type="module"> <script type="module">
import mermaid from '/mermaid.esm.mjs'; import mermaid from '/mermaid.esm.mjs';
mermaid.parseError = function (err, hash) { async function render(str) {
console.error('Mermaid error: ', err); const { svg } = await mermaid.render('dynamic', str);
}; document.getElementById('dynamicDiagram').innerHTML = svg;
}
const storeKey = window.location.pathname;
const code = localStorage.getItem(storeKey);
if (code) {
document.getElementById('code').value = code;
await render(code);
}
mermaid.initialize({ mermaid.initialize({
startOnLoad: true, startOnLoad: true,
logLevel: 0, logLevel: 0,
}); });
const value = `graph TD\nHello --> World`; document.getElementById('code').addEventListener('input', async (e) => {
const el = document.getElementById('dynamicDiagram'); const value = e.target.value;
const { svg } = await mermaid.render('dd', value); localStorage.setItem(storeKey, value);
console.log(svg); await render(value);
el.innerHTML = svg; });
</script> </script>
<script src="/dev/reload.js"></script>
</body> </body>
</html> </html>

22
demos/dev/reload.js Normal file
View File

@@ -0,0 +1,22 @@
// 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);

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

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

View File

@@ -81,6 +81,9 @@
<li> <li>
<h2><a href="./sankey.html">Sankey</a></h2> <h2><a href="./sankey.html">Sankey</a></h2>
</li> </li>
<li>
<h2><a href="./packet.html">Packet</a></h2>
</li>
<li> <li>
<h2><a href="./block.html">Layered Blocks</a></h2> <h2><a href="./block.html">Layered Blocks</a></h2>
</li> </li>

141
demos/packet.html Normal file
View File

@@ -0,0 +1,141 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<title>Mermaid Quick Test Page</title>
<link rel="icon" type="image/png" href="data:image/png;base64,iVBORw0KGgo=" />
<style>
div.mermaid {
font-family: 'Courier New', Courier, monospace !important;
}
</style>
</head>
<body>
<h1>Packet diagram demo</h1>
<div class="diagrams">
<pre class="mermaid">
packet-beta
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-223: "data"
</pre
>
<pre class="mermaid">
---
config:
packet:
showBits: false
---
packet-beta
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-223: "data"
</pre
>
<pre class="mermaid">
---
config:
theme: forest
---
packet-beta
title Forest theme
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-223: "data"
</pre
>
<pre class="mermaid" style="background-color: black">
---
config:
theme: dark
---
packet-beta
title Dark theme
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-223: "data"
</pre
>
</div>
<script type="module">
import mermaid from '/mermaid.esm.mjs';
mermaid.initialize({
logLevel: 3,
securityLevel: 'loose',
});
</script>
<style>
.diagrams {
display: flex;
flex-wrap: wrap;
}
pre {
width: 45vw;
padding: 2em;
}
</style>
</body>
</html>

View File

@@ -50,7 +50,7 @@
</pre> </pre>
<script type="module"> <script type="module">
import mermaid from './mermaid.esm.mjs'; import mermaid from '/mermaid.esm.mjs';
mermaid.initialize({ mermaid.initialize({
theme: 'forest', theme: 'forest',
logLevel: 3, logLevel: 3,

View File

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

View File

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

View File

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

View File

@@ -14,7 +14,7 @@
#### Defined in #### Defined in
[defaultConfig.ts:272](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L272) [defaultConfig.ts:275](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L275)
--- ---

View File

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

View File

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

View File

@@ -224,7 +224,7 @@ b. The importing of the Mermaid library through the `mermaid.esm.mjs` or `mermai
```html ```html
<body> <body>
<script type="module"> <script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true }); mermaid.initialize({ startOnLoad: true });
</script> </script>
</body> </body>
@@ -262,7 +262,7 @@ In this example, the `mermaidAPI` is being called through the `CDN`:
</pre> </pre>
<script type="module"> <script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true }); mermaid.initialize({ startOnLoad: true });
</script> </script>
</body> </body>

View File

@@ -317,7 +317,7 @@ To select a version:
Replace `<version>` with the desired version number. Replace `<version>` with the desired version number.
Latest Version: <https://cdn.jsdelivr.net/npm/mermaid@10> Latest Version: <https://cdn.jsdelivr.net/npm/mermaid@11>
## Deploying Mermaid ## Deploying Mermaid
@@ -335,7 +335,7 @@ To Deploy Mermaid:
```html ```html
<script type="module"> <script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
mermaid.initialize({ startOnLoad: true }); mermaid.initialize({ startOnLoad: true });
</script> </script>
``` ```

View File

@@ -300,7 +300,7 @@ From version 9.4.0 you can simplify this code to:
```html ```html
<script type="module"> <script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
</script> </script>
``` ```

141
docs/syntax/packet.md Normal file
View File

@@ -0,0 +1,141 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/packet.md](../../packages/mermaid/src/docs/syntax/packet.md).
# Packet Diagram (v\<MERMAID_RELEASE_VERSION>+)
## Introduction
A packet diagram is a visual representation used to illustrate the structure and contents of a network packet. Network packets are the fundamental units of data transferred over a network.
## Usage
This diagram type is particularly useful for network engineers, educators, and students who require a clear and concise way to represent the structure of network packets.
## Syntax
```md
packet-beta
start: "Block name" %% Single-bit block
start-end: "Block name" %% Multi-bit blocks
... More Fields ...
```
## Examples
```mermaid-example
---
title: "TCP Packet"
---
packet-beta
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-255: "Data (variable length)"
```
```mermaid
---
title: "TCP Packet"
---
packet-beta
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
64-95: "Acknowledgment Number"
96-99: "Data Offset"
100-105: "Reserved"
106: "URG"
107: "ACK"
108: "PSH"
109: "RST"
110: "SYN"
111: "FIN"
112-127: "Window"
128-143: "Checksum"
144-159: "Urgent Pointer"
160-191: "(Options and Padding)"
192-255: "Data (variable length)"
```
```mermaid-example
packet-beta
title UDP Packet
0-15: "Source Port"
16-31: "Destination Port"
32-47: "Length"
48-63: "Checksum"
64-95: "Data (variable length)"
```
```mermaid
packet-beta
title UDP Packet
0-15: "Source Port"
16-31: "Destination Port"
32-47: "Length"
48-63: "Checksum"
64-95: "Data (variable length)"
```
## Details of Syntax
- **Ranges**: Each line after the title represents a different field in the packet. The range (e.g., `0-15`) indicates the bit positions in the packet.
- **Field Description**: A brief description of what the field represents, enclosed in quotes.
## Configuration
Please refer to the [configuration](/config/schema-docs/config-defs-packet-diagram-config.html) guide for details.
<!--
Theme variables are not currently working due to a mermaid bug. The passed values are not being propagated into styles function.
## Theme Variables
| Property | Description | Default Value |
| ---------------- | -------------------------- | ------------- |
| byteFontSize | Font size of the bytes | '10px' |
| startByteColor | Color of the starting byte | 'black' |
| endByteColor | Color of the ending byte | 'black' |
| labelColor | Color of the labels | 'black' |
| labelFontSize | Font size of the labels | '12px' |
| titleColor | Color of the title | 'black' |
| titleFontSize | Font size of the title | '14px' |
| blockStrokeColor | Color of the block stroke | 'black' |
| blockStrokeWidth | Width of the block stroke | '1' |
| blockFillColor | Fill color of the block | '#efefef' |
## Example on config and theme
```mermaid-example
---
config:
packet:
showBits: true
themeVariables:
packet:
startByteColor: red
---
packet-beta
0-15: "Source Port"
16-31: "Destination Port"
32-63: "Sequence Number"
```
-->

View File

@@ -469,7 +469,7 @@ You can use this method to add mermaid including the timeline diagram to a web p
```html ```html
<script type="module"> <script type="module">
import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@10/dist/mermaid.esm.min.mjs'; import mermaid from 'https://cdn.jsdelivr.net/npm/mermaid@11/dist/mermaid.esm.min.mjs';
</script> </script>
``` ```

View File

@@ -15,15 +15,15 @@
"git graph" "git graph"
], ],
"scripts": { "scripts": {
"build:vite": "tsx .vite/build.ts", "build": "pnpm build:esbuild && pnpm build:types",
"build:mermaid": "pnpm build:vite --mermaid", "build:esbuild": "pnpm run -r clean && tsx .esbuild/build.ts",
"build:viz": "pnpm build:mermaid --visualize", "build:mermaid": "pnpm build:esbuild --mermaid",
"build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly", "build:viz": "pnpm build:esbuild --visualize",
"build:types": "tsx .build/types.ts",
"build:types:watch": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly --watch", "build:types:watch": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly --watch",
"build:watch": "pnpm build:vite --watch", "dev": "tsx .esbuild/server.ts",
"build": "pnpm run -r clean && pnpm build:types && pnpm build:vite", "dev:vite": "tsx .vite/server.ts",
"dev": "concurrently \"pnpm build:vite --watch\" \"tsx .vite/server.ts\"", "dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev:vite",
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev",
"release": "pnpm build", "release": "pnpm build",
"lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .", "lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && tsx scripts/fixCSpell.ts", "lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && tsx scripts/fixCSpell.ts",
@@ -32,8 +32,8 @@
"cypress": "cypress run", "cypress": "cypress run",
"cypress:open": "cypress open", "cypress:open": "cypress open",
"e2e": "start-server-and-test dev http://localhost:9000/ cypress", "e2e": "start-server-and-test dev http://localhost:9000/ cypress",
"e2e:coverage": "start-server-and-test dev:coverage http://localhost:9000/ cypress",
"coverage:cypress:clean": "rimraf .nyc_output coverage/cypress", "coverage:cypress:clean": "rimraf .nyc_output coverage/cypress",
"e2e:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm e2e",
"coverage:merge": "tsx scripts/coverage.ts", "coverage:merge": "tsx scripts/coverage.ts",
"coverage": "pnpm test:coverage --run && pnpm e2e:coverage && pnpm coverage:merge", "coverage": "pnpm test:coverage --run && pnpm e2e:coverage && pnpm coverage:merge",
"ci": "vitest run", "ci": "vitest run",
@@ -74,7 +74,7 @@
"@types/jsdom": "^21.1.1", "@types/jsdom": "^21.1.1",
"@types/lodash": "^4.14.194", "@types/lodash": "^4.14.194",
"@types/mdast": "^3.0.11", "@types/mdast": "^3.0.11",
"@types/node": "^20.11.10", "@types/node": "^20.11.17",
"@types/prettier": "^2.7.2", "@types/prettier": "^2.7.2",
"@types/rollup-plugin-visualizer": "^4.2.1", "@types/rollup-plugin-visualizer": "^4.2.1",
"@typescript-eslint/eslint-plugin": "^6.7.2", "@typescript-eslint/eslint-plugin": "^6.7.2",
@@ -83,6 +83,7 @@
"@vitest/spy": "^0.34.0", "@vitest/spy": "^0.34.0",
"@vitest/ui": "^0.34.0", "@vitest/ui": "^0.34.0",
"ajv": "^8.12.0", "ajv": "^8.12.0",
"chokidar": "^3.5.3",
"concurrently": "^8.0.1", "concurrently": "^8.0.1",
"cors": "^2.8.5", "cors": "^2.8.5",
"cspell": "^8.3.2", "cspell": "^8.3.2",
@@ -108,7 +109,9 @@
"jison": "^0.4.18", "jison": "^0.4.18",
"js-yaml": "^4.1.0", "js-yaml": "^4.1.0",
"jsdom": "^22.0.0", "jsdom": "^22.0.0",
"langium-cli": "3.0.1",
"lint-staged": "^13.2.1", "lint-staged": "^13.2.1",
"markdown-table": "^3.0.3",
"nyc": "^15.1.0", "nyc": "^15.1.0",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"pnpm": "^8.6.8", "pnpm": "^8.6.8",

View File

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

View File

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

View File

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

View File

@@ -1,17 +1,17 @@
import { select, line, curveLinear } from 'd3'; import { select, line, curveLinear } from 'd3';
import { insertNode } from '../../../dagre-wrapper/nodes.js'; import { insertNode } from '../../mermaid/src/dagre-wrapper/nodes.js';
import insertMarkers from '../../../dagre-wrapper/markers.js'; import insertMarkers from '../../mermaid/src/dagre-wrapper/markers.js';
import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js'; import { insertEdgeLabel } from '../../mermaid/src/dagre-wrapper/edges.js';
import { findCommonAncestor } from './render-utils.js'; import { findCommonAncestor } from './render-utils.js';
import { labelHelper } from '../../../dagre-wrapper/shapes/util.js'; import { labelHelper } from '../../mermaid/src/dagre-wrapper/shapes/util.js';
import { getConfig } from '../../../config.js'; import { getConfig } from '../../mermaid/src/config.js';
import { log } from '../../../logger.js'; import { log } from '../../mermaid/src/logger.js';
import { setupGraphViewbox } from '../../../setupGraphViewbox.js'; import { setupGraphViewbox } from '../../mermaid/src/setupGraphViewbox.js';
import common from '../../common/common.js'; import common from '../../mermaid/src/diagrams/common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../../utils.js'; import { interpolateToCurve, getStylesFromArray } from '../../mermaid/src/utils.js';
import ELK from 'elkjs/lib/elk.bundled.js'; import ELK from 'elkjs/lib/elk.bundled.js';
import { getLineFunctionsWithOffset } from '../../../utils/lineWithOffset.js'; import { getLineFunctionsWithOffset } from '../../mermaid/src/utils/lineWithOffset.js';
import { addEdgeMarkers } from '../../../dagre-wrapper/edgeMarker.js'; import { addEdgeMarkers } from '../../mermaid/src/dagre-wrapper/edgeMarker.js';
const elk = new ELK(); const elk = new ELK();
@@ -594,7 +594,7 @@ const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAb
* *
* @param text * @param text
* @param diagObj * @param diagObj
* @returns {Record<string, import('../../../diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles * @returns {Record<string, import('../../mermaid/src/diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles
*/ */
export const getClasses = function (text, diagObj) { export const getClasses = function (text, diagObj) {
log.info('Extracting classes'); log.info('Extracting classes');

View File

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

View File

@@ -19,6 +19,7 @@
"mermaid" "mermaid"
], ],
"scripts": { "scripts": {
"clean": "rimraf dist",
"prepublishOnly": "pnpm -w run build" "prepublishOnly": "pnpm -w run build"
}, },
"repository": { "repository": {

View File

@@ -5,7 +5,6 @@
* This is a dummy parser that satisfies the mermaid API logic. * This is a dummy parser that satisfies the mermaid API logic.
*/ */
export default { export default {
parser: { yy: {} },
parse: () => { parse: () => {
// no op // no op
}, },

View File

@@ -1,6 +1,6 @@
{ {
"name": "mermaid", "name": "mermaid",
"version": "10.8.0", "version": "11.0.0-alpha.6",
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module", "type": "module",
"module": "./dist/mermaid.core.mjs", "module": "./dist/mermaid.core.mjs",
@@ -60,8 +60,7 @@
}, },
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^6.0.1", "@braintree/sanitize-url": "^6.0.1",
"@types/d3-scale": "^4.0.3", "@mermaid-js/parser": "workspace:^",
"@types/d3-scale-chromatic": "^3.0.0",
"cytoscape": "^3.28.1", "cytoscape": "^3.28.1",
"cytoscape-cose-bilkent": "^4.1.0", "cytoscape-cose-bilkent": "^4.1.0",
"d3": "^7.4.0", "d3": "^7.4.0",
@@ -74,11 +73,9 @@
"khroma": "^2.0.0", "khroma": "^2.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"mdast-util-from-markdown": "^1.3.0", "mdast-util-from-markdown": "^1.3.0",
"non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.1.3", "stylis": "^4.1.3",
"ts-dedent": "^2.2.0", "ts-dedent": "^2.2.0",
"uuid": "^9.0.0", "uuid": "^9.0.0"
"web-worker": "^1.2.0"
}, },
"devDependencies": { "devDependencies": {
"@adobe/jsonschema2md": "^7.1.4", "@adobe/jsonschema2md": "^7.1.4",
@@ -86,6 +83,7 @@
"@types/d3": "^7.4.0", "@types/d3": "^7.4.0",
"@types/d3-sankey": "^0.12.1", "@types/d3-sankey": "^0.12.1",
"@types/d3-scale": "^4.0.3", "@types/d3-scale": "^4.0.3",
"@types/d3-scale-chromatic": "^3.0.0",
"@types/d3-selection": "^3.0.5", "@types/d3-selection": "^3.0.5",
"@types/d3-shape": "^3.1.1", "@types/d3-shape": "^3.1.1",
"@types/dompurify": "^3.0.2", "@types/dompurify": "^3.0.2",

View File

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

View File

@@ -1,10 +1,8 @@
import * as configApi from './config.js'; import * as configApi from './config.js';
import { log } from './logger.js';
import { getDiagram, registerDiagram } from './diagram-api/diagramAPI.js'; import { getDiagram, registerDiagram } from './diagram-api/diagramAPI.js';
import { detectType, getDiagramLoader } from './diagram-api/detectType.js'; import { detectType, getDiagramLoader } from './diagram-api/detectType.js';
import { UnknownDiagramError } from './errors.js'; import { UnknownDiagramError } from './errors.js';
import { encodeEntities } from './utils.js'; import { encodeEntities } from './utils.js';
import type { DetailedError } from './utils.js'; import type { DetailedError } from './utils.js';
import type { DiagramDefinition, DiagramMetadata } from './diagram-api/types.js'; import type { DiagramDefinition, DiagramMetadata } from './diagram-api/types.js';
@@ -15,48 +13,45 @@ export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?:
* @privateRemarks This is exported as part of the public mermaidAPI. * @privateRemarks This is exported as part of the public mermaidAPI.
*/ */
export class Diagram { export class Diagram {
type = 'graph'; public static async fromText(text: string, metadata: Pick<DiagramMetadata, 'title'> = {}) {
parser: DiagramDefinition['parser'];
renderer: DiagramDefinition['renderer'];
db: DiagramDefinition['db'];
private init?: DiagramDefinition['init'];
private detectError?: UnknownDiagramError;
constructor(public text: string, public metadata: Pick<DiagramMetadata, 'title'> = {}) {
this.text = encodeEntities(text);
this.text += '\n';
const cnf = configApi.getConfig();
try {
this.type = detectType(text, cnf);
} catch (e) {
this.type = 'error';
this.detectError = e as UnknownDiagramError;
}
const diagram = getDiagram(this.type);
log.debug('Type ' + this.type);
// Setup diagram
this.db = diagram.db;
this.renderer = diagram.renderer;
this.parser = diagram.parser;
this.parser.parser.yy = this.db;
this.init = diagram.init;
this.parse();
}
parse() {
if (this.detectError) {
throw this.detectError;
}
this.db.clear?.();
const config = configApi.getConfig(); const config = configApi.getConfig();
this.init?.(config); const type = detectType(text, config);
text = encodeEntities(text) + '\n';
try {
getDiagram(type);
} catch (e) {
const loader = getDiagramLoader(type);
if (!loader) {
throw new UnknownDiagramError(`Diagram ${type} not found.`);
}
// Diagram not available, loading it.
// new diagram will try getDiagram again and if fails then it is a valid throw
const { id, diagram } = await loader();
registerDiagram(id, diagram);
}
const { db, parser, renderer, init } = getDiagram(type);
if (parser.parser) {
// The parser.parser.yy is only present in JISON parsers. So, we'll only set if required.
parser.parser.yy = db;
}
db.clear?.();
init?.(config);
// This block was added for legacy compatibility. Use frontmatter instead of adding more special cases. // This block was added for legacy compatibility. Use frontmatter instead of adding more special cases.
if (this.metadata.title) { if (metadata.title) {
this.db.setDiagramTitle?.(this.metadata.title); db.setDiagramTitle?.(metadata.title);
} }
this.parser.parse(this.text); await parser.parse(text);
return new Diagram(type, text, db, parser, renderer);
} }
private constructor(
public type: string,
public text: string,
public db: DiagramDefinition['db'],
public parser: DiagramDefinition['parser'],
public renderer: DiagramDefinition['renderer']
) {}
async render(id: string, version: string) { async render(id: string, version: string) {
await this.renderer.draw(this.text, id, version, this); await this.renderer.draw(this.text, id, version, this);
} }
@@ -69,34 +64,3 @@ export class Diagram {
return this.type; return this.type;
} }
} }
/**
* Parse the text asynchronously and generate a Diagram object asynchronously.
* **Warning:** This function may be changed in the future.
* @alpha
* @param text - The mermaid diagram definition.
* @param metadata - Diagram metadata, defined in YAML.
* @returns A the Promise of a Diagram object.
* @throws {@link UnknownDiagramError} if the diagram type can not be found.
* @privateRemarks This is exported as part of the public mermaidAPI.
*/
export const getDiagramFromText = async (
text: string,
metadata: Pick<DiagramMetadata, 'title'> = {}
): Promise<Diagram> => {
const type = detectType(text, configApi.getConfig());
try {
// Trying to find the diagram
getDiagram(type);
} catch (error) {
const loader = getDiagramLoader(type);
if (!loader) {
throw new UnknownDiagramError(`Diagram ${type} not found.`);
}
// Diagram not available, loading it.
// new diagram will try getDiagram again and if fails then it is a valid throw
const { id, diagram } = await loader();
registerDiagram(id, diagram);
}
return new Diagram(text, metadata);
};

View File

@@ -61,7 +61,7 @@ export interface MermaidConfig {
* You may also use `themeCSS` to override this value. * You may also use `themeCSS` to override this value.
* *
*/ */
theme?: string | 'default' | 'forest' | 'dark' | 'neutral' | 'null'; theme?: 'default' | 'forest' | 'dark' | 'neutral' | 'null';
themeVariables?: any; themeVariables?: any;
themeCSS?: string; themeCSS?: string;
/** /**
@@ -87,26 +87,11 @@ export interface MermaidConfig {
* This option decides the amount of logging to be used by mermaid. * This option decides the amount of logging to be used by mermaid.
* *
*/ */
logLevel?: logLevel?: 'trace' | 0 | 'debug' | 1 | 'info' | 2 | 'warn' | 3 | 'error' | 4 | 'fatal' | 5;
| number
| string
| 0
| 2
| 1
| 'trace'
| 'debug'
| 'info'
| 'warn'
| 'error'
| 'fatal'
| 3
| 4
| 5
| undefined;
/** /**
* Level of trust for parsed diagram * Level of trust for parsed diagram
*/ */
securityLevel?: string | 'strict' | 'loose' | 'antiscript' | 'sandbox' | undefined; securityLevel?: 'strict' | 'loose' | 'antiscript' | 'sandbox';
/** /**
* Dictates whether mermaid starts on Page load * Dictates whether mermaid starts on Page load
*/ */
@@ -169,19 +154,43 @@ export interface MermaidConfig {
gitGraph?: GitGraphDiagramConfig; gitGraph?: GitGraphDiagramConfig;
c4?: C4DiagramConfig; c4?: C4DiagramConfig;
sankey?: SankeyDiagramConfig; sankey?: SankeyDiagramConfig;
packet?: PacketDiagramConfig;
block?: BlockDiagramConfig; block?: BlockDiagramConfig;
dompurifyConfig?: DOMPurifyConfiguration; dompurifyConfig?: DOMPurifyConfiguration;
wrap?: boolean; wrap?: boolean;
fontSize?: number; fontSize?: number;
} }
/** /**
* The object containing configurations specific for block diagrams. * The object containing configurations specific for packet diagrams.
* *
* This interface was referenced by `MermaidConfig`'s JSON-Schema * This interface was referenced by `MermaidConfig`'s JSON-Schema
* via the `definition` "BlockDiagramConfig". * via the `definition` "PacketDiagramConfig".
*/ */
export interface BlockDiagramConfig extends BaseDiagramConfig { export interface PacketDiagramConfig extends BaseDiagramConfig {
padding?: number; /**
* The height of each row in the packet diagram.
*/
rowHeight?: number;
/**
* The width of each bit in the packet diagram.
*/
bitWidth?: number;
/**
* The number of bits to display per row.
*/
bitsPerRow?: number;
/**
* Toggle to display or hide bit numbers.
*/
showBits?: boolean;
/**
* The horizontal padding between the blocks in a row.
*/
paddingX?: number;
/**
* The vertical padding between the rows.
*/
paddingY?: number;
} }
/** /**
* This interface was referenced by `MermaidConfig`'s JSON-Schema * This interface was referenced by `MermaidConfig`'s JSON-Schema
@@ -197,6 +206,15 @@ export interface BaseDiagramConfig {
*/ */
useMaxWidth?: boolean; useMaxWidth?: boolean;
} }
/**
* The object containing configurations specific for block diagrams.
*
* This interface was referenced by `MermaidConfig`'s JSON-Schema
* via the `definition` "BlockDiagramConfig".
*/
export interface BlockDiagramConfig extends BaseDiagramConfig {
padding?: number;
}
/** /**
* The object containing configurations specific for c4 diagrams * The object containing configurations specific for c4 diagrams
* *
@@ -807,8 +825,8 @@ export interface XYChartConfig extends BaseDiagramConfig {
* Should show the chart title * Should show the chart title
*/ */
showTitle?: boolean; showTitle?: boolean;
xAxis?: XYChartAxisConfig1; xAxis?: XYChartAxisConfig;
yAxis?: XYChartAxisConfig2; yAxis?: XYChartAxisConfig;
/** /**
* How to plot will be drawn horizontal or vertical * How to plot will be drawn horizontal or vertical
*/ */
@@ -818,104 +836,6 @@ export interface XYChartConfig extends BaseDiagramConfig {
*/ */
plotReservedSpacePercent?: number; plotReservedSpacePercent?: number;
} }
/**
* This object contains configuration for XYChart axis config
*/
export interface XYChartAxisConfig1 {
/**
* Should show the axis labels (tick text)
*/
showLabel?: boolean;
/**
* font size of the axis labels (tick text)
*/
labelFontSize?: number;
/**
* top and bottom space from axis label (tick text)
*/
labelPadding?: number;
/**
* Should show the axis title
*/
showTitle?: boolean;
/**
* font size of the axis title
*/
titleFontSize?: number;
/**
* top and bottom space from axis title
*/
titlePadding?: number;
/**
* Should show the axis tick lines
*/
showTick?: boolean;
/**
* length of the axis tick lines
*/
tickLength?: number;
/**
* width of the axis tick lines
*/
tickWidth?: number;
/**
* Show line across the axis
*/
showAxisLine?: boolean;
/**
* Width of the axis line
*/
axisLineWidth?: number;
}
/**
* This object contains configuration for XYChart axis config
*/
export interface XYChartAxisConfig2 {
/**
* Should show the axis labels (tick text)
*/
showLabel?: boolean;
/**
* font size of the axis labels (tick text)
*/
labelFontSize?: number;
/**
* top and bottom space from axis label (tick text)
*/
labelPadding?: number;
/**
* Should show the axis title
*/
showTitle?: boolean;
/**
* font size of the axis title
*/
titleFontSize?: number;
/**
* top and bottom space from axis title
*/
titlePadding?: number;
/**
* Should show the axis tick lines
*/
showTick?: boolean;
/**
* length of the axis tick lines
*/
tickLength?: number;
/**
* width of the axis tick lines
*/
tickWidth?: number;
/**
* Show line across the axis
*/
showAxisLine?: boolean;
/**
* Width of the axis line
*/
axisLineWidth?: number;
}
/** /**
* The object containing configurations specific for entity relationship diagrams * The object containing configurations specific for entity relationship diagrams
* *
@@ -936,7 +856,7 @@ export interface ErDiagramConfig extends BaseDiagramConfig {
/** /**
* Directional bias for layout of entities * Directional bias for layout of entities
*/ */
layoutDirection?: string | 'TB' | 'BT' | 'LR' | 'RL'; layoutDirection?: 'TB' | 'BT' | 'LR' | 'RL';
/** /**
* The minimum width of an entity box. Expressed in pixels. * The minimum width of an entity box. Expressed in pixels.
*/ */
@@ -1001,7 +921,7 @@ export interface StateDiagramConfig extends BaseDiagramConfig {
* Decides which rendering engine that is to be used for the rendering. * Decides which rendering engine that is to be used for the rendering.
* *
*/ */
defaultRenderer?: string | 'dagre-d3' | 'dagre-wrapper' | 'elk'; defaultRenderer?: 'dagre-d3' | 'dagre-wrapper' | 'elk';
} }
/** /**
* This interface was referenced by `MermaidConfig`'s JSON-Schema * This interface was referenced by `MermaidConfig`'s JSON-Schema
@@ -1025,7 +945,7 @@ export interface ClassDiagramConfig extends BaseDiagramConfig {
* Decides which rendering engine that is to be used for the rendering. * Decides which rendering engine that is to be used for the rendering.
* *
*/ */
defaultRenderer?: string | 'dagre-d3' | 'dagre-wrapper' | 'elk'; defaultRenderer?: 'dagre-d3' | 'dagre-wrapper' | 'elk';
nodeSpacing?: number; nodeSpacing?: number;
rankSpacing?: number; rankSpacing?: number;
/** /**
@@ -1085,7 +1005,7 @@ export interface JourneyDiagramConfig extends BaseDiagramConfig {
/** /**
* Multiline message alignment * Multiline message alignment
*/ */
messageAlign?: string | 'left' | 'center' | 'right'; messageAlign?: 'left' | 'center' | 'right';
/** /**
* Prolongs the edge of the diagram downwards. * Prolongs the edge of the diagram downwards.
* *
@@ -1164,7 +1084,7 @@ export interface TimelineDiagramConfig extends BaseDiagramConfig {
/** /**
* Multiline message alignment * Multiline message alignment
*/ */
messageAlign?: string | 'left' | 'center' | 'right'; messageAlign?: 'left' | 'center' | 'right';
/** /**
* Prolongs the edge of the diagram downwards. * Prolongs the edge of the diagram downwards.
* *
@@ -1275,7 +1195,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
* Controls the display mode. * Controls the display mode.
* *
*/ */
displayMode?: string | 'compact'; displayMode?: '' | 'compact';
/** /**
* On which day a week-based interval should start * On which day a week-based interval should start
* *
@@ -1334,7 +1254,7 @@ export interface SequenceDiagramConfig extends BaseDiagramConfig {
/** /**
* Multiline message alignment * Multiline message alignment
*/ */
messageAlign?: string | 'left' | 'center' | 'right'; messageAlign?: 'left' | 'center' | 'right';
/** /**
* Mirror actors under diagram * Mirror actors under diagram
* *
@@ -1391,7 +1311,7 @@ export interface SequenceDiagramConfig extends BaseDiagramConfig {
/** /**
* This sets the text alignment of actor-attached notes * This sets the text alignment of actor-attached notes
*/ */
noteAlign?: string | 'left' | 'center' | 'right'; noteAlign?: 'left' | 'center' | 'right';
/** /**
* This sets the font size of actor messages * This sets the font size of actor messages
*/ */
@@ -1475,7 +1395,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
* Defines how mermaid renders curves for flowcharts. * Defines how mermaid renders curves for flowcharts.
* *
*/ */
curve?: string | 'basis' | 'linear' | 'cardinal'; curve?: 'basis' | 'linear' | 'cardinal';
/** /**
* Represents the padding between the labels and the shape * Represents the padding between the labels and the shape
* *
@@ -1487,7 +1407,7 @@ export interface FlowchartDiagramConfig extends BaseDiagramConfig {
* Decides which rendering engine that is to be used for the rendering. * Decides which rendering engine that is to be used for the rendering.
* *
*/ */
defaultRenderer?: string | 'dagre-d3' | 'dagre-wrapper' | 'elk'; defaultRenderer?: 'dagre-d3' | 'dagre-wrapper' | 'elk';
/** /**
* Width of nodes where text is wrapped. * Width of nodes where text is wrapped.
* *
@@ -1511,13 +1431,7 @@ export interface SankeyDiagramConfig extends BaseDiagramConfig {
* *
*/ */
linkColor?: SankeyLinkColor | string; linkColor?: SankeyLinkColor | string;
/** nodeAlignment?: SankeyNodeAlignment;
* Controls the alignment of the Sankey diagrams.
*
* See <https://github.com/d3/d3-sankey#alignments>.
*
*/
nodeAlignment?: 'left' | 'right' | 'center' | 'justify';
useMaxWidth?: boolean; useMaxWidth?: boolean;
/** /**
* Toggle to display or hide values along with title. * Toggle to display or hide values along with title.

View File

@@ -257,6 +257,9 @@ const config: RequiredDeep<MermaidConfig> = {
// TODO: can we make this default to `true` instead? // TODO: can we make this default to `true` instead?
useMaxWidth: false, useMaxWidth: false,
}, },
packet: {
...defaultConfigJson.packet,
},
}; };
const keyify = (obj: any, prefix = ''): string[] => const keyify = (obj: any, prefix = ''): string[] =>

View File

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

View File

@@ -20,6 +20,7 @@ import flowchartElk from '../diagrams/flowchart/elk/detector.js';
import timeline from '../diagrams/timeline/detector.js'; import timeline from '../diagrams/timeline/detector.js';
import mindmap from '../diagrams/mindmap/detector.js'; import mindmap from '../diagrams/mindmap/detector.js';
import sankey from '../diagrams/sankey/sankeyDetector.js'; import sankey from '../diagrams/sankey/sankeyDetector.js';
import { packet } from '../diagrams/packet/detector.js';
import block from '../diagrams/block/blockDetector.js'; import block from '../diagrams/block/blockDetector.js';
import { registerLazyLoadedDiagrams } from './detectType.js'; import { registerLazyLoadedDiagrams } from './detectType.js';
import { registerDiagram } from './diagramAPI.js'; import { registerDiagram } from './diagramAPI.js';
@@ -51,7 +52,6 @@ export const addDiagrams = () => {
}, },
}, },
parser: { parser: {
parser: { yy: {} },
parse: () => { parse: () => {
throw new Error( throw new Error(
'Diagrams beginning with --- are not valid. ' + 'Diagrams beginning with --- are not valid. ' +
@@ -88,6 +88,7 @@ export const addDiagrams = () => {
journey, journey,
quadrantChart, quadrantChart,
sankey, sankey,
packet,
xychart, xychart,
block block
); );

View File

@@ -2,12 +2,12 @@ import { detectType } from './detectType.js';
import { getDiagram, registerDiagram } from './diagramAPI.js'; import { getDiagram, registerDiagram } from './diagramAPI.js';
import { addDiagrams } from './diagram-orchestration.js'; import { addDiagrams } from './diagram-orchestration.js';
import type { DiagramDetector } from './types.js'; import type { DiagramDetector } from './types.js';
import { getDiagramFromText } from '../Diagram.js'; import { Diagram } from '../Diagram.js';
import { it, describe, expect, beforeAll } from 'vitest'; import { it, describe, expect, beforeAll } from 'vitest';
addDiagrams(); addDiagrams();
beforeAll(async () => { beforeAll(async () => {
await getDiagramFromText('sequenceDiagram'); await Diagram.fromText('sequenceDiagram');
}); });
describe('DiagramAPI', () => { describe('DiagramAPI', () => {
@@ -39,7 +39,6 @@ describe('DiagramAPI', () => {
parse: (_text) => { parse: (_text) => {
return; return;
}, },
parser: { yy: {} },
}, },
renderer: { renderer: {
draw: () => { draw: () => {

View File

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

View File

@@ -1,4 +1,4 @@
import type { MermaidConfig } from '../config.type.js'; import type { GanttDiagramConfig, MermaidConfig } from '../config.type.js';
import { frontMatterRegex } from './regexes.js'; import { frontMatterRegex } from './regexes.js';
// The "* as yaml" part is necessary for tree-shaking // The "* as yaml" part is necessary for tree-shaking
import * as yaml from 'js-yaml'; import * as yaml from 'js-yaml';
@@ -6,7 +6,7 @@ import * as yaml from 'js-yaml';
interface FrontMatterMetadata { interface FrontMatterMetadata {
title?: string; title?: string;
// Allows custom display modes. Currently used for compact mode in gantt charts. // Allows custom display modes. Currently used for compact mode in gantt charts.
displayMode?: string; displayMode?: GanttDiagramConfig['displayMode'];
config?: MermaidConfig; config?: MermaidConfig;
} }
@@ -44,7 +44,7 @@ export function extractFrontMatter(text: string): FrontMatterResult {
// Only add properties that are explicitly supported, if they exist // Only add properties that are explicitly supported, if they exist
if (parsed.displayMode) { if (parsed.displayMode) {
metadata.displayMode = parsed.displayMode.toString(); metadata.displayMode = parsed.displayMode.toString() as GanttDiagramConfig['displayMode'];
} }
if (parsed.title) { if (parsed.title) {
metadata.title = parsed.title.toString(); metadata.title = parsed.title.toString();

View File

@@ -1,7 +1,8 @@
/* eslint-disable @typescript-eslint/no-explicit-any */ /* eslint-disable @typescript-eslint/no-explicit-any */
import type * as d3 from 'd3';
import type { SetRequired } from 'type-fest';
import type { Diagram } from '../Diagram.js'; import type { Diagram } from '../Diagram.js';
import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js'; import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js';
import type * as d3 from 'd3';
export interface DiagramMetadata { export interface DiagramMetadata {
title?: string; title?: string;
@@ -39,6 +40,22 @@ export interface DiagramDB {
bindFunctions?: (element: Element) => void; bindFunctions?: (element: Element) => void;
} }
/**
* DiagramDB with fields that is required for all new diagrams.
*/
export type DiagramDBBase<T extends BaseDiagramConfig> = {
getConfig: () => Required<T>;
} & SetRequired<
DiagramDB,
| 'clear'
| 'getAccTitle'
| 'getDiagramTitle'
| 'getAccDescription'
| 'setAccDescription'
| 'setAccTitle'
| 'setDiagramTitle'
>;
// This is what is returned from getClasses(...) methods. // This is what is returned from getClasses(...) methods.
// It is slightly renamed to ..StyleClassDef instead of just ClassDef because "class" is a greatly ambiguous and overloaded word. // It is slightly renamed to ..StyleClassDef instead of just ClassDef because "class" is a greatly ambiguous and overloaded word.
// It makes it clear we're working with a style class definition, even though defining the type is currently difficult. // It makes it clear we're working with a style class definition, even though defining the type is currently difficult.
@@ -104,8 +121,8 @@ export type DrawDefinition = (
) => void | Promise<void>; ) => void | Promise<void>;
export interface ParserDefinition { export interface ParserDefinition {
parse: (text: string) => void; parse: (text: string) => void | Promise<void>;
parser: { yy: DiagramDB }; parser?: { yy: DiagramDB };
} }
export type HTML = d3.Selection<HTMLIFrameElement, unknown, Element | null, unknown>; export type HTML = d3.Selection<HTMLIFrameElement, unknown, Element | null, unknown>;

View File

@@ -1,16 +1,39 @@
import { describe, test, expect } from 'vitest'; import { describe, test, expect } from 'vitest';
import { Diagram, getDiagramFromText } from './Diagram.js'; import { Diagram } from './Diagram.js';
import { addDetector } from './diagram-api/detectType.js'; import { addDetector } from './diagram-api/detectType.js';
import { addDiagrams } from './diagram-api/diagram-orchestration.js'; import { addDiagrams } from './diagram-api/diagram-orchestration.js';
import type { DiagramLoader } from './diagram-api/types.js';
addDiagrams(); addDiagrams();
const getDummyDiagram = (id: string, title?: string): Awaited<ReturnType<DiagramLoader>> => {
return {
id,
diagram: {
db: {
getDiagramTitle: () => title ?? id,
},
parser: {
parse: () => {
// no-op
},
},
renderer: {
draw: () => {
// no-op
},
},
styles: {},
},
};
};
describe('diagram detection', () => { describe('diagram detection', () => {
test('should detect inbuilt diagrams', async () => { test('should detect inbuilt diagrams', async () => {
const graph = (await getDiagramFromText('graph TD; A-->B')) as Diagram; const graph = (await Diagram.fromText('graph TD; A-->B')) as Diagram;
expect(graph).toBeInstanceOf(Diagram); expect(graph).toBeInstanceOf(Diagram);
expect(graph.type).toBe('flowchart-v2'); expect(graph.type).toBe('flowchart-v2');
const sequence = (await getDiagramFromText( const sequence = (await Diagram.fromText(
'sequenceDiagram; Alice->>+John: Hello John, how are you?' 'sequenceDiagram; Alice->>+John: Hello John, how are you?'
)) as Diagram; )) as Diagram;
expect(sequence).toBeInstanceOf(Diagram); expect(sequence).toBeInstanceOf(Diagram);
@@ -21,41 +44,33 @@ describe('diagram detection', () => {
addDetector( addDetector(
'loki', 'loki',
(str) => str.startsWith('loki'), (str) => str.startsWith('loki'),
() => () => Promise.resolve(getDummyDiagram('loki'))
Promise.resolve({
id: 'loki',
diagram: {
db: {},
parser: {
parse: () => {
// no-op
},
parser: {
yy: {},
},
},
renderer: {
draw: () => {
// no-op
},
},
styles: {},
},
})
); );
const diagram = (await getDiagramFromText('loki TD; A-->B')) as Diagram; const diagram = await Diagram.fromText('loki TD; A-->B');
expect(diagram).toBeInstanceOf(Diagram); expect(diagram).toBeInstanceOf(Diagram);
expect(diagram.type).toBe('loki'); expect(diagram.type).toBe('loki');
}); });
test('should allow external diagrams to override internal ones with same ID', async () => {
const title = 'overridden';
addDetector(
'flowchart-elk',
(str) => str.startsWith('flowchart-elk'),
() => Promise.resolve(getDummyDiagram('flowchart-elk', title))
);
const diagram = await Diagram.fromText('flowchart-elk TD; A-->B');
expect(diagram).toBeInstanceOf(Diagram);
expect(diagram.db.getDiagramTitle?.()).toBe(title);
});
test('should throw the right error for incorrect diagram', async () => { test('should throw the right error for incorrect diagram', async () => {
await expect(getDiagramFromText('graph TD; A-->')).rejects.toThrowErrorMatchingInlineSnapshot(` await expect(Diagram.fromText('graph TD; A-->')).rejects.toThrowErrorMatchingInlineSnapshot(`
"Parse error on line 2: "Parse error on line 2:
graph TD; A--> graph TD; A-->
--------------^ --------------^
Expecting 'AMP', 'COLON', 'PIPE', 'TESTSTR', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', got 'EOF'" Expecting 'AMP', 'COLON', 'PIPE', 'TESTSTR', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'NODE_STRING', 'BRKT', 'MINUS', 'MULT', 'UNICODE_TEXT', got 'EOF'"
`); `);
await expect(getDiagramFromText('sequenceDiagram; A-->B')).rejects await expect(Diagram.fromText('sequenceDiagram; A-->B')).rejects
.toThrowErrorMatchingInlineSnapshot(` .toThrowErrorMatchingInlineSnapshot(`
"Parse error on line 1: "Parse error on line 1:
...quenceDiagram; A-->B ...quenceDiagram; A-->B
@@ -65,13 +80,13 @@ Expecting 'TXT', got 'NEWLINE'"
}); });
test('should throw the right error for unregistered diagrams', async () => { test('should throw the right error for unregistered diagrams', async () => {
await expect(getDiagramFromText('thor TD; A-->B')).rejects.toThrowErrorMatchingInlineSnapshot( await expect(Diagram.fromText('thor TD; A-->B')).rejects.toThrowErrorMatchingInlineSnapshot(
'"No diagram type detected matching given configuration for text: thor TD; A-->B"' '"No diagram type detected matching given configuration for text: thor TD; A-->B"'
); );
}); });
test('should consider entity codes when present in diagram defination', async () => { test('should consider entity codes when present in diagram defination', async () => {
const diagram = await getDiagramFromText(`sequenceDiagram const diagram = await Diagram.fromText(`sequenceDiagram
A->>B: I #9829; you! A->>B: I #9829; you!
B->>A: I #9829; you #infin; times more!`); B->>A: I #9829; you #infin; times more!`);
// @ts-ignore: we need to add types for sequenceDb which will be done in separate PR // @ts-ignore: we need to add types for sequenceDb which will be done in separate PR

View File

@@ -1,9 +1,9 @@
import type { DiagramDB } from '../../diagram-api/types.js';
import type { BlockConfig, BlockType, Block, ClassDef } from './blockTypes.js';
import * as configApi from '../../config.js';
import { clear as commonClear } from '../common/commonDb.js';
import { log } from '../../logger.js';
import clone from 'lodash-es/clone.js'; import clone from 'lodash-es/clone.js';
import * as configApi from '../../config.js';
import type { DiagramDB } from '../../diagram-api/types.js';
import { log } from '../../logger.js';
import { clear as commonClear } from '../common/commonDb.js';
import type { Block, ClassDef } from './blockTypes.js';
// Initialize the node database for simple lookups // Initialize the node database for simple lookups
let blockDatabase: Record<string, Block> = {}; let blockDatabase: Record<string, Block> = {};

View File

@@ -1,19 +1,17 @@
import type { Diagram } from '../../Diagram.js';
import * as configApi from '../../config.js';
import { calculateBlockSizes, insertBlocks, insertEdges } from './renderHelpers.js';
import { layout } from './layout.js';
import type { MermaidConfig, BaseDiagramConfig } from '../../config.type.js';
import insertMarkers from '../../dagre-wrapper/markers.js';
import { import {
select as d3select,
scaleOrdinal as d3scaleOrdinal, scaleOrdinal as d3scaleOrdinal,
schemeTableau10 as d3schemeTableau10, schemeTableau10 as d3schemeTableau10,
select as d3select,
} from 'd3'; } from 'd3';
import type { ContainerElement } from 'd3'; import type { Diagram } from '../../Diagram.js';
import * as configApi from '../../config.js';
import type { MermaidConfig } from '../../config.type.js';
import insertMarkers from '../../dagre-wrapper/markers.js';
import { log } from '../../logger.js'; import { log } from '../../logger.js';
import type { BlockDB } from './blockDB.js';
import type { Block } from './blockTypes.js';
import { configureSvgSize } from '../../setupGraphViewbox.js'; import { configureSvgSize } from '../../setupGraphViewbox.js';
import type { BlockDB } from './blockDB.js';
import { layout } from './layout.js';
import { calculateBlockSizes, insertBlocks, insertEdges } from './renderHelpers.js';
/** /**
* Returns the all the styles from classDef statements in the graph definition. * Returns the all the styles from classDef statements in the graph definition.

View File

@@ -1,15 +1,10 @@
import { getStylesFromArray } from '../../utils.js';
import { insertNode, positionNode } from '../../dagre-wrapper/nodes.js';
import { insertEdge, insertEdgeLabel, positionEdgeLabel } from '../../dagre-wrapper/edges.js';
import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import * as graphlib from 'dagre-d3-es/src/graphlib/index.js';
import { getConfig } from '../../config.js'; import { getConfig } from '../../config.js';
import type { ContainerElement } from 'd3'; import { insertEdge, insertEdgeLabel, positionEdgeLabel } from '../../dagre-wrapper/edges.js';
import type { Block } from './blockTypes.js'; import { insertNode, positionNode } from '../../dagre-wrapper/nodes.js';
import { getStylesFromArray } from '../../utils.js';
import type { BlockDB } from './blockDB.js'; import type { BlockDB } from './blockDB.js';
import type { Block } from './blockTypes.js';
interface Node {
classes: string;
}
function getNodeFromBlock(block: Block, db: BlockDB, positioned = false) { function getNodeFromBlock(block: Block, db: BlockDB, positioned = false) {
const vertex = block; const vertex = block;

View File

@@ -0,0 +1,14 @@
import type { DiagramAST } from '@mermaid-js/parser';
import type { DiagramDB } from '../../diagram-api/types.js';
export function populateCommonDb(ast: DiagramAST, db: DiagramDB) {
if (ast.accDescr) {
db.setAccDescription?.(ast.accDescr);
}
if (ast.accTitle) {
db.setAccTitle?.(ast.accTitle);
}
if (ast.title) {
db.setDiagramTitle?.(ast.title);
}
}

View File

@@ -5,7 +5,6 @@ const diagram: DiagramDefinition = {
db: {}, db: {},
renderer, renderer,
parser: { parser: {
parser: { yy: {} },
parse: (): void => { parse: (): void => {
return; return;
}, },

View File

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

View File

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

View File

@@ -1,14 +1,15 @@
import flowDb from './flowDb.js'; import flowDb from './flowDb.js';
import type { FlowSubGraph } from './types.js';
describe('flow db subgraphs', () => { describe('flow db subgraphs', () => {
let subgraphs; let subgraphs: FlowSubGraph[];
beforeEach(() => { beforeEach(() => {
subgraphs = [ subgraphs = [
{ nodes: ['a', 'b', 'c', 'e'] }, { nodes: ['a', 'b', 'c', 'e'] },
{ nodes: ['f', 'g', 'h'] }, { nodes: ['f', 'g', 'h'] },
{ nodes: ['i', 'j'] }, { nodes: ['i', 'j'] },
{ nodes: ['k'] }, { nodes: ['k'] },
]; ] as FlowSubGraph[];
}); });
describe('exist', () => { describe('exist', () => {
it('should return true when the is exists in a subgraph', () => { it('should return true when the is exists in a subgraph', () => {
@@ -25,17 +26,17 @@ describe('flow db subgraphs', () => {
describe('makeUniq', () => { describe('makeUniq', () => {
it('should remove ids from sungraph that already exists in another subgraph even if it gets empty', () => { it('should remove ids from sungraph that already exists in another subgraph even if it gets empty', () => {
const subgraph = flowDb.makeUniq({ nodes: ['i', 'j'] }, subgraphs); const subgraph = flowDb.makeUniq({ nodes: ['i', 'j'] } as FlowSubGraph, subgraphs);
expect(subgraph.nodes).toEqual([]); expect(subgraph.nodes).toEqual([]);
}); });
it('should remove ids from sungraph that already exists in another subgraph', () => { it('should remove ids from sungraph that already exists in another subgraph', () => {
const subgraph = flowDb.makeUniq({ nodes: ['i', 'j', 'o'] }, subgraphs); const subgraph = flowDb.makeUniq({ nodes: ['i', 'j', 'o'] } as FlowSubGraph, subgraphs);
expect(subgraph.nodes).toEqual(['o']); expect(subgraph.nodes).toEqual(['o']);
}); });
it('should not remove ids from subgraph if they are unique', () => { it('should not remove ids from subgraph if they are unique', () => {
const subgraph = flowDb.makeUniq({ nodes: ['q', 'r', 's'] }, subgraphs); const subgraph = flowDb.makeUniq({ nodes: ['q', 'r', 's'] } as FlowSubGraph, subgraphs);
expect(subgraph.nodes).toEqual(['q', 'r', 's']); expect(subgraph.nodes).toEqual(['q', 'r', 's']);
}); });

View File

@@ -12,34 +12,34 @@ import {
setDiagramTitle, setDiagramTitle,
getDiagramTitle, getDiagramTitle,
} from '../common/commonDb.js'; } from '../common/commonDb.js';
import type { FlowVertex, FlowClass, FlowSubGraph, FlowText, FlowEdge, FlowLink } from './types.js';
const MERMAID_DOM_ID_PREFIX = 'flowchart-'; const MERMAID_DOM_ID_PREFIX = 'flowchart-';
let vertexCounter = 0; let vertexCounter = 0;
let config = getConfig(); let config = getConfig();
let vertices = {}; let vertices: Record<string, FlowVertex> = {};
let edges = []; let edges: FlowEdge[] & { defaultInterpolate?: string; defaultStyle?: string[] } = [];
let classes = {}; let classes: Record<string, FlowClass> = {};
let subGraphs = []; let subGraphs: FlowSubGraph[] = [];
let subGraphLookup = {}; let subGraphLookup: Record<string, FlowSubGraph> = {};
let tooltips = {}; let tooltips: Record<string, string> = {};
let subCount = 0; let subCount = 0;
let firstGraphFlag = true; let firstGraphFlag = true;
let direction; let direction: string;
let version; // As in graph let version: string; // As in graph
// Functions to be run after graph rendering // Functions to be run after graph rendering
let funs = []; // cspell:ignore funs let funs: ((element: Element) => void)[] = []; // cspell:ignore funs
const sanitizeText = (txt) => common.sanitizeText(txt, config); const sanitizeText = (txt: string) => common.sanitizeText(txt, config);
/** /**
* Function to lookup domId from id in the graph definition. * Function to lookup domId from id in the graph definition.
* *
* @param id * @param id - id of the node
* @public
*/ */
export const lookUpDomId = function (id) { export const lookUpDomId = function (id: string) {
const vertexKeys = Object.keys(vertices); const vertexKeys = Object.keys(vertices);
for (const vertexKey of vertexKeys) { for (const vertexKey of vertexKeys) {
if (vertices[vertexKey].id === id) { if (vertices[vertexKey].id === id) {
@@ -52,30 +52,24 @@ export const lookUpDomId = function (id) {
/** /**
* Function called by parser when a node definition has been found * Function called by parser when a node definition has been found
* *
* @param _id
* @param text
* @param textObj
* @param type
* @param style
* @param classes
* @param dir
* @param props
*/ */
export const addVertex = function (_id, textObj, type, style, classes, dir, props = {}) { export const addVertex = function (
id: string,
textObj: FlowText,
type: 'group',
style: string[],
classes: string[],
dir: string,
props = {}
) {
if (!id || id.trim().length === 0) {
return;
}
let txt; let txt;
let id = _id;
if (id === undefined) {
return;
}
if (id.trim().length === 0) {
return;
}
// if (id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (vertices[id] === undefined) { if (vertices[id] === undefined) {
vertices[id] = { vertices[id] = {
id: id, id,
labelType: 'text', labelType: 'text',
domId: MERMAID_DOM_ID_PREFIX + id + '-' + vertexCounter, domId: MERMAID_DOM_ID_PREFIX + id + '-' + vertexCounter,
styles: [], styles: [],
@@ -94,7 +88,7 @@ export const addVertex = function (_id, textObj, type, style, classes, dir, prop
vertices[id].text = txt; vertices[id].text = txt;
} else { } else {
if (vertices[id].text === undefined) { if (vertices[id].text === undefined) {
vertices[id].text = _id; vertices[id].text = id;
} }
} }
if (type !== undefined) { if (type !== undefined) {
@@ -123,20 +117,12 @@ export const addVertex = function (_id, textObj, type, style, classes, dir, prop
/** /**
* Function called by parser when a link/edge definition has been found * Function called by parser when a link/edge definition has been found
* *
* @param _start
* @param _end
* @param type
* @param linkText
* @param linkTextObj
*/ */
export const addSingleLink = function (_start, _end, type) { export const addSingleLink = function (_start: string, _end: string, type: any) {
let start = _start; const start = _start;
let end = _end; const end = _end;
// if (start[0].match(/\d/)) start = MERMAID_DOM_ID_PREFIX + start;
// if (end[0].match(/\d/)) end = MERMAID_DOM_ID_PREFIX + end;
// log.info('Got edge...', start, end);
const edge = { start: start, end: end, type: undefined, text: '', labelType: 'text' }; const edge: FlowEdge = { start: start, end: end, type: undefined, text: '', labelType: 'text' };
log.info('abc78 Got edge...', edge); log.info('abc78 Got edge...', edge);
const linkTextObj = type.text; const linkTextObj = type.text;
@@ -153,13 +139,11 @@ export const addSingleLink = function (_start, _end, type) {
if (type !== undefined) { if (type !== undefined) {
edge.type = type.type; edge.type = type.type;
edge.stroke = type.stroke; edge.stroke = type.stroke;
edge.length = type.length; edge.length = type.length > 10 ? 10 : type.length;
}
if (edge?.length > 10) {
edge.length = 10;
} }
if (edges.length < (config.maxEdges ?? 500)) { if (edges.length < (config.maxEdges ?? 500)) {
log.info('abc78 pushing edge...'); log.info('Pushing edge...');
edges.push(edge); edges.push(edge);
} else { } else {
throw new Error( throw new Error(
@@ -171,12 +155,12 @@ You have to call mermaid.initialize.`
); );
} }
}; };
export const addLink = function (_start, _end, type) {
log.info('addLink (abc78)', _start, _end, type); export const addLink = function (_start: string[], _end: string[], type: unknown) {
let i, j; log.info('addLink', _start, _end, type);
for (i = 0; i < _start.length; i++) { for (const start of _start) {
for (j = 0; j < _end.length; j++) { for (const end of _end) {
addSingleLink(_start[i], _end[j], type); addSingleLink(start, end, type);
} }
} }
}; };
@@ -184,15 +168,16 @@ export const addLink = function (_start, _end, type) {
/** /**
* Updates a link's line interpolation algorithm * Updates a link's line interpolation algorithm
* *
* @param positions
* @param interp
*/ */
export const updateLinkInterpolate = function (positions, interp) { export const updateLinkInterpolate = function (
positions: ('default' | number)[],
interpolate: string
) {
positions.forEach(function (pos) { positions.forEach(function (pos) {
if (pos === 'default') { if (pos === 'default') {
edges.defaultInterpolate = interp; edges.defaultInterpolate = interpolate;
} else { } else {
edges[pos].interpolate = interp; edges[pos].interpolate = interpolate;
} }
}); });
}; };
@@ -200,12 +185,10 @@ export const updateLinkInterpolate = function (positions, interp) {
/** /**
* Updates a link with a style * Updates a link with a style
* *
* @param positions
* @param style
*/ */
export const updateLink = function (positions, style) { export const updateLink = function (positions: ('default' | number)[], style: string[]) {
positions.forEach(function (pos) { positions.forEach(function (pos) {
if (pos >= edges.length) { if (typeof pos === 'number' && pos >= edges.length) {
throw new Error( throw new Error(
`The index ${pos} for linkStyle is out of bounds. Valid indices for linkStyle are between 0 and ${ `The index ${pos} for linkStyle is out of bounds. Valid indices for linkStyle are between 0 and ${
edges.length - 1 edges.length - 1
@@ -223,7 +206,7 @@ export const updateLink = function (positions, style) {
}); });
}; };
export const addClass = function (ids, style) { export const addClass = function (ids: string, style: string[]) {
ids.split(',').forEach(function (id) { ids.split(',').forEach(function (id) {
if (classes[id] === undefined) { if (classes[id] === undefined) {
classes[id] = { id, styles: [], textStyles: [] }; classes[id] = { id, styles: [], textStyles: [] };
@@ -244,9 +227,8 @@ export const addClass = function (ids, style) {
/** /**
* Called by parser when a graph definition is found, stores the direction of the chart. * Called by parser when a graph definition is found, stores the direction of the chart.
* *
* @param dir
*/ */
export const setDirection = function (dir) { export const setDirection = function (dir: string) {
direction = dir; direction = dir;
if (direction.match(/.*</)) { if (direction.match(/.*</)) {
direction = 'RL'; direction = 'RL';
@@ -268,34 +250,32 @@ export const setDirection = function (dir) {
/** /**
* Called by parser when a special node is found, e.g. a clickable element. * Called by parser when a special node is found, e.g. a clickable element.
* *
* @param ids Comma separated list of ids * @param ids - Comma separated list of ids
* @param className Class to add * @param className - Class to add
*/ */
export const setClass = function (ids, className) { export const setClass = function (ids: string, className: string) {
ids.split(',').forEach(function (_id) { for (const id of ids.split(',')) {
// let id = version === 'gen-2' ? lookUpDomId(_id) : _id; if (vertices[id]) {
let id = _id;
// if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (vertices[id] !== undefined) {
vertices[id].classes.push(className); vertices[id].classes.push(className);
} }
if (subGraphLookup[id]) {
if (subGraphLookup[id] !== undefined) {
subGraphLookup[id].classes.push(className); subGraphLookup[id].classes.push(className);
} }
});
};
const setTooltip = function (ids, tooltip) {
ids.split(',').forEach(function (id) {
if (tooltip !== undefined) {
tooltips[version === 'gen-1' ? lookUpDomId(id) : id] = sanitizeText(tooltip);
} }
});
}; };
const setClickFun = function (id, functionName, functionArgs) { const setTooltip = function (ids: string, tooltip: string) {
let domId = lookUpDomId(id); if (tooltip === undefined) {
return;
}
tooltip = sanitizeText(tooltip);
for (const id of ids.split(',')) {
tooltips[version === 'gen-1' ? lookUpDomId(id) : id] = tooltip;
}
};
const setClickFun = function (id: string, functionName: string, functionArgs: string) {
const domId = lookUpDomId(id);
// if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id; // if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (getConfig().securityLevel !== 'loose') { if (getConfig().securityLevel !== 'loose') {
return; return;
@@ -303,7 +283,7 @@ const setClickFun = function (id, functionName, functionArgs) {
if (functionName === undefined) { if (functionName === undefined) {
return; return;
} }
let argList = []; let argList: string[] = [];
if (typeof functionArgs === 'string') { if (typeof functionArgs === 'string') {
/* Splits functionArgs by ',', ignoring all ',' in double quoted strings */ /* Splits functionArgs by ',', ignoring all ',' in double quoted strings */
argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/); argList = functionArgs.split(/,(?=(?:(?:[^"]*"){2})*[^"]*$)/);
@@ -343,11 +323,11 @@ const setClickFun = function (id, functionName, functionArgs) {
/** /**
* Called by parser when a link is found. Adds the URL to the vertex data. * Called by parser when a link is found. Adds the URL to the vertex data.
* *
* @param ids Comma separated list of ids * @param ids - Comma separated list of ids
* @param linkStr URL to create a link for * @param linkStr - URL to create a link for
* @param target * @param target - Target attribute for the link
*/ */
export const setLink = function (ids, linkStr, target) { export const setLink = function (ids: string, linkStr: string, target: string) {
ids.split(',').forEach(function (id) { ids.split(',').forEach(function (id) {
if (vertices[id] !== undefined) { if (vertices[id] !== undefined) {
vertices[id].link = utils.formatUrl(linkStr, config); vertices[id].link = utils.formatUrl(linkStr, config);
@@ -356,7 +336,8 @@ export const setLink = function (ids, linkStr, target) {
}); });
setClass(ids, 'clickable'); setClass(ids, 'clickable');
}; };
export const getTooltip = function (id) {
export const getTooltip = function (id: string) {
if (tooltips.hasOwnProperty(id)) { if (tooltips.hasOwnProperty(id)) {
return tooltips[id]; return tooltips[id];
} }
@@ -366,18 +347,18 @@ export const getTooltip = function (id) {
/** /**
* Called by parser when a click definition is found. Registers an event handler. * Called by parser when a click definition is found. Registers an event handler.
* *
* @param ids Comma separated list of ids * @param ids - Comma separated list of ids
* @param functionName Function to be called on click * @param functionName - Function to be called on click
* @param functionArgs * @param functionArgs - Arguments to be passed to the function
*/ */
export const setClickEvent = function (ids, functionName, functionArgs) { export const setClickEvent = function (ids: string, functionName: string, functionArgs: string) {
ids.split(',').forEach(function (id) { ids.split(',').forEach(function (id) {
setClickFun(id, functionName, functionArgs); setClickFun(id, functionName, functionArgs);
}); });
setClass(ids, 'clickable'); setClass(ids, 'clickable');
}; };
export const bindFunctions = function (element) { export const bindFunctions = function (element: Element) {
funs.forEach(function (fun) { funs.forEach(function (fun) {
fun(element); fun(element);
}); });
@@ -388,7 +369,6 @@ export const getDirection = function () {
/** /**
* Retrieval function for fetching the found nodes after parsing has completed. * Retrieval function for fetching the found nodes after parsing has completed.
* *
* @returns {{} | any | vertices}
*/ */
export const getVertices = function () { export const getVertices = function () {
return vertices; return vertices;
@@ -397,7 +377,6 @@ export const getVertices = function () {
/** /**
* Retrieval function for fetching the found links after parsing has completed. * Retrieval function for fetching the found links after parsing has completed.
* *
* @returns {{} | any | edges}
*/ */
export const getEdges = function () { export const getEdges = function () {
return edges; return edges;
@@ -406,15 +385,16 @@ export const getEdges = function () {
/** /**
* Retrieval function for fetching the found class definitions after parsing has completed. * Retrieval function for fetching the found class definitions after parsing has completed.
* *
* @returns {{} | any | classes}
*/ */
export const getClasses = function () { export const getClasses = function () {
return classes; return classes;
}; };
const setupToolTips = function (element) { const setupToolTips = function (element: Element) {
let tooltipElem = select('.mermaidTooltip'); let tooltipElem = select('.mermaidTooltip');
// @ts-ignore TODO: fix this
if ((tooltipElem._groups || tooltipElem)[0][0] === null) { if ((tooltipElem._groups || tooltipElem)[0][0] === null) {
// @ts-ignore TODO: fix this
tooltipElem = select('body').append('div').attr('class', 'mermaidTooltip').style('opacity', 0); tooltipElem = select('body').append('div').attr('class', 'mermaidTooltip').style('opacity', 0);
} }
@@ -430,8 +410,9 @@ const setupToolTips = function (element) {
if (title === null) { if (title === null) {
return; return;
} }
const rect = this.getBoundingClientRect(); const rect = (this as Element)?.getBoundingClientRect();
// @ts-ignore TODO: fix this
tooltipElem.transition().duration(200).style('opacity', '.9'); tooltipElem.transition().duration(200).style('opacity', '.9');
tooltipElem tooltipElem
.text(el.attr('title')) .text(el.attr('title'))
@@ -441,6 +422,7 @@ const setupToolTips = function (element) {
el.classed('hover', true); el.classed('hover', true);
}) })
.on('mouseout', function () { .on('mouseout', function () {
// @ts-ignore TODO: fix this
tooltipElem.transition().duration(500).style('opacity', 0); tooltipElem.transition().duration(500).style('opacity', 0);
const el = select(this); const el = select(this);
el.classed('hover', false); el.classed('hover', false);
@@ -451,7 +433,6 @@ funs.push(setupToolTips);
/** /**
* Clears the internal graph db so that a new graph can be parsed. * Clears the internal graph db so that a new graph can be parsed.
* *
* @param ver
*/ */
export const clear = function (ver = 'gen-1') { export const clear = function (ver = 'gen-1') {
vertices = {}; vertices = {};
@@ -467,31 +448,29 @@ export const clear = function (ver = 'gen-1') {
config = getConfig(); config = getConfig();
commonClear(); commonClear();
}; };
export const setGen = (ver) => {
export const setGen = (ver: string) => {
version = ver || 'gen-2'; version = ver || 'gen-2';
}; };
/** @returns {string} */
export const defaultStyle = function () { export const defaultStyle = function () {
return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;'; return 'fill:#ffa;stroke: #f66; stroke-width: 3px; stroke-dasharray: 5, 5;fill:#ffa;stroke: #666;';
}; };
/** export const addSubGraph = function (
* Clears the internal graph db so that a new graph can be parsed. _id: { text: string },
* list: string[],
* @param _id _title: { text: string; type: string }
* @param list ) {
* @param _title let id: string | undefined = _id.text.trim();
*/
export const addSubGraph = function (_id, list, _title) {
let id = _id.text.trim();
let title = _title.text; let title = _title.text;
if (_id === _title && _title.text.match(/\s/)) { if (_id === _title && _title.text.match(/\s/)) {
id = undefined; id = undefined;
} }
/** @param a */
function uniq(a) { function uniq(a: any[]) {
const prims = { boolean: {}, number: {}, string: {} }; const prims: any = { boolean: {}, number: {}, string: {} };
const objs = []; const objs: any[] = [];
let dir; // = undefined; direction.trim(); let dir; // = undefined; direction.trim();
const nodeList = a.filter(function (item) { const nodeList = a.filter(function (item) {
@@ -512,10 +491,7 @@ export const addSubGraph = function (_id, list, _title) {
return { nodeList, dir }; return { nodeList, dir };
} }
let nodeList = []; const { nodeList, dir } = uniq(list.flat());
const { nodeList: nl, dir } = uniq(nodeList.concat.apply(nodeList, list));
nodeList = nl;
if (version === 'gen-1') { if (version === 'gen-1') {
for (let i = 0; i < nodeList.length; i++) { for (let i = 0; i < nodeList.length; i++) {
nodeList[i] = lookUpDomId(nodeList[i]); nodeList[i] = lookUpDomId(nodeList[i]);
@@ -523,7 +499,6 @@ export const addSubGraph = function (_id, list, _title) {
} }
id = id || 'subGraph' + subCount; id = id || 'subGraph' + subCount;
// if (id[0].match(/\d/)) id = lookUpDomId(id);
title = title || ''; title = title || '';
title = sanitizeText(title); title = sanitizeText(title);
subCount = subCount + 1; subCount = subCount + 1;
@@ -538,19 +513,6 @@ export const addSubGraph = function (_id, list, _title) {
log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir); log.info('Adding', subGraph.id, subGraph.nodes, subGraph.dir);
/** Deletes an id from all subgraphs */
// const del = _id => {
// subGraphs.forEach(sg => {
// const pos = sg.nodes.indexOf(_id);
// if (pos >= 0) {
// sg.nodes.splice(pos, 1);
// }
// });
// };
// // Removes the members of this subgraph from any other subgraphs, a node only belong to one subgraph
// subGraph.nodes.forEach(_id => del(_id));
// Remove the members in the new subgraph if they already belong to another subgraph // Remove the members in the new subgraph if they already belong to another subgraph
subGraph.nodes = makeUniq(subGraph, subGraphs).nodes; subGraph.nodes = makeUniq(subGraph, subGraphs).nodes;
subGraphs.push(subGraph); subGraphs.push(subGraph);
@@ -558,7 +520,7 @@ export const addSubGraph = function (_id, list, _title) {
return id; return id;
}; };
const getPosForId = function (id) { const getPosForId = function (id: string) {
for (const [i, subGraph] of subGraphs.entries()) { for (const [i, subGraph] of subGraphs.entries()) {
if (subGraph.id === id) { if (subGraph.id === id) {
return i; return i;
@@ -567,12 +529,15 @@ const getPosForId = function (id) {
return -1; return -1;
}; };
let secCount = -1; let secCount = -1;
const posCrossRef = []; const posCrossRef: number[] = [];
const indexNodes2 = function (id, pos) { const indexNodes2 = function (id: string, pos: number): { result: boolean; count: number } {
const nodes = subGraphs[pos].nodes; const nodes = subGraphs[pos].nodes;
secCount = secCount + 1; secCount = secCount + 1;
if (secCount > 2000) { if (secCount > 2000) {
return; return {
result: false,
count: 0,
};
} }
posCrossRef[secCount] = pos; posCrossRef[secCount] = pos;
// Check if match // Check if match
@@ -608,13 +573,13 @@ const indexNodes2 = function (id, pos) {
}; };
}; };
export const getDepthFirstPos = function (pos) { export const getDepthFirstPos = function (pos: number) {
return posCrossRef[pos]; return posCrossRef[pos];
}; };
export const indexNodes = function () { export const indexNodes = function () {
secCount = -1; secCount = -1;
if (subGraphs.length > 0) { if (subGraphs.length > 0) {
indexNodes2('none', subGraphs.length - 1, 0); indexNodes2('none', subGraphs.length - 1);
} }
}; };
@@ -630,7 +595,7 @@ export const firstGraph = () => {
return false; return false;
}; };
const destructStartLink = (_str) => { const destructStartLink = (_str: string): FlowLink => {
let str = _str.trim(); let str = _str.trim();
let type = 'arrow_open'; let type = 'arrow_open';
@@ -662,7 +627,7 @@ const destructStartLink = (_str) => {
return { type, stroke }; return { type, stroke };
}; };
const countChar = (char, str) => { const countChar = (char: string, str: string) => {
const length = str.length; const length = str.length;
let count = 0; let count = 0;
for (let i = 0; i < length; ++i) { for (let i = 0; i < length; ++i) {
@@ -673,7 +638,7 @@ const countChar = (char, str) => {
return count; return count;
}; };
const destructEndLink = (_str) => { const destructEndLink = (_str: string) => {
const str = _str.trim(); const str = _str.trim();
let line = str.slice(0, -1); let line = str.slice(0, -1);
let type = 'arrow_open'; let type = 'arrow_open';
@@ -713,7 +678,7 @@ const destructEndLink = (_str) => {
stroke = 'invisible'; stroke = 'invisible';
} }
let dots = countChar('.', line); const dots = countChar('.', line);
if (dots) { if (dots) {
stroke = 'dotted'; stroke = 'dotted';
@@ -723,7 +688,7 @@ const destructEndLink = (_str) => {
return { type, stroke, length }; return { type, stroke, length };
}; };
export const destructLink = (_str, _startStr) => { export const destructLink = (_str: string, _startStr: string) => {
const info = destructEndLink(_str); const info = destructEndLink(_str);
let startInfo; let startInfo;
if (_startStr) { if (_startStr) {
@@ -757,7 +722,7 @@ export const destructLink = (_str, _startStr) => {
}; };
// Todo optimizer this by caching existing nodes // Todo optimizer this by caching existing nodes
const exists = (allSgs, _id) => { const exists = (allSgs: FlowSubGraph[], _id: string) => {
let res = false; let res = false;
allSgs.forEach((sg) => { allSgs.forEach((sg) => {
const pos = sg.nodes.indexOf(_id); const pos = sg.nodes.indexOf(_id);
@@ -770,11 +735,9 @@ const exists = (allSgs, _id) => {
/** /**
* Deletes an id from all subgraphs * Deletes an id from all subgraphs
* *
* @param sg
* @param allSubgraphs
*/ */
const makeUniq = (sg, allSubgraphs) => { const makeUniq = (sg: FlowSubGraph, allSubgraphs: FlowSubGraph[]) => {
const res = []; const res: string[] = [];
sg.nodes.forEach((_id, pos) => { sg.nodes.forEach((_id, pos) => {
if (!exists(allSubgraphs, _id)) { if (!exists(allSubgraphs, _id)) {
res.push(sg.nodes[pos]); res.push(sg.nodes[pos]);
@@ -786,6 +749,7 @@ const makeUniq = (sg, allSubgraphs) => {
export const lex = { export const lex = {
firstGraph, firstGraph,
}; };
export default { export default {
defaultConfig: () => defaultConfig.flowchart, defaultConfig: () => defaultConfig.flowchart,
setAccTitle, setAccTitle,

View File

@@ -1,503 +0,0 @@
/** mermaid
* https://mermaidjs.github.io/
* (c) 2015 Knut Sveidqvist
* MIT license.
*/
/* lexical grammar */
%lex
%x string
%%
\%\%[^\n]* /* do nothing */
["] this.begin("string");
<string>["] this.popState();
<string>[^"]* return "STR";
"style" return 'STYLE';
"default" return 'DEFAULT';
"linkStyle" return 'LINKSTYLE';
"interpolate" return 'INTERPOLATE';
"classDef" return 'CLASSDEF';
"class" return 'CLASS';
"click" return 'CLICK';
"graph" return 'GRAPH';
"subgraph" return 'subgraph';
"end"\b\s* return 'end';
"LR" return 'DIR';
"RL" return 'DIR';
"TB" return 'DIR';
"BT" return 'DIR';
"TD" return 'DIR';
"BR" return 'DIR';
[0-9]+ return 'NUM';
\# return 'BRKT';
":" return 'COLON';
";" return 'SEMI';
"," return 'COMMA';
"*" return 'MULT';
\s*\-\-[x]\s* return 'ARROW_CROSS';
\s*[x]\-\-[x]\s* return 'DOUBLE_ARROW_CROSS';
\s*\-\-\>\s* return 'ARROW_POINT';
\s*\<\-\-\>\s* return 'DOUBLE_ARROW_POINT';
\s*\-\-[o]\s* return 'ARROW_CIRCLE';
\s*[o]\-\-[o]\s* return 'DOUBLE_ARROW_CIRCLE';
\s*\-\-\-\s* return 'ARROW_OPEN';
\s*\-\.\-[x]\s* return 'DOTTED_ARROW_CROSS';
\s*[x]\-\.\-[x]\s* return 'DOUBLE_DOTTED_ARROW_CROSS';
\s*\-\.\-\>\s* return 'DOTTED_ARROW_POINT';
\s*\<\-\.\-\>\s* return 'DOUBLE_DOTTED_ARROW_POINT';
\s*\-\.\-[o]\s* return 'DOTTED_ARROW_CIRCLE';
\s*[o]\-\.\-[o]\s* return 'DOUBLE_DOTTED_ARROW_CIRCLE';
\s*\-\.\-\s* return 'DOTTED_ARROW_OPEN';
\s*.\-[x]\s* return 'DOTTED_ARROW_CROSS';
\s*[x].\-[x]\s* return 'DOUBLE_DOTTED_ARROW_CROSS';
\s*\.\-\>\s* return 'DOTTED_ARROW_POINT';
\s*\<\.\-\>\s* return 'DOUBLE_DOTTED_ARROW_POINT';
\s*\.\-[o]\s* return 'DOTTED_ARROW_CIRCLE';
\s*[o]\.\-[o]\s* return 'DOUBLE_DOTTED_ARROW_CIRCLE';
\s*\.\-\s* return 'DOTTED_ARROW_OPEN';
\s*\=\=[x]\s* return 'THICK_ARROW_CROSS';
\s*[x]\=\=[x]\s* return 'DOUBLE_THICK_ARROW_CROSS';
\s*\=\=\>\s* return 'THICK_ARROW_POINT';
\s*\<\=\=\>\s* return 'DOUBLE_THICK_ARROW_POINT';
\s*\=\=[o]\s* return 'THICK_ARROW_CIRCLE';
\s*[o]\=\=[o]\s* return 'DOUBLE_THICK_ARROW_CIRCLE';
\s*\=\=[\=]\s* return 'THICK_ARROW_OPEN';
\s*\-\-\s* return '--';
\s*\-\.\s* return '-.';
\s*\=\=\s* return '==';
%\s*\<\-\-\s* return 'START_DOUBLE_ARROW_POINT';
%\s*\[x]\-\-\s* return 'START_DOUBLE_ARROW_CROSS';
%\s*\[o]\-\-\s* return 'START_DOUBLE_ARROW_CIRCLE';
%\s*\<\-\.\s* return 'START_DOUBLE_DOTTED_ARROW_POINT';
%\s*\[x]\-\.\s* return 'START_DOUBLE_DOTTED_ARROW_CROSS';
%\s*\[o]\-\.\s* return 'START_DOUBLE_DOTTED_ARROW_CIRCLE';
%\s*\<\=\=\s* return 'START_DOUBLE_THICK_ARROW_POINT';
%\s*\[x]\=\=\s* return 'START_DOUBLE_THICK_ARROW_CROSS';
%\s*\[o]\=\=\s* return 'START_DOUBLE_THICK_ARROW_CIRCLE';
"(-" return '(-';
"-)" return '-)';
\- return 'MINUS';
"." return 'DOT';
\+ return 'PLUS';
\% return 'PCT';
"=" return 'EQUALS';
\= return 'EQUALS';
"<" return 'TAGSTART';
">" return 'TAGEND';
"^" return 'UP';
"v" return 'DOWN';
[A-Za-z]+ return 'ALPHA';
[!"#$%&'*+,-.`?\\_/] return 'PUNCTUATION';
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|
[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|
[\u05F0-\u05F2\u0620-\u064A\u066E\u066F\u0671-\u06D3\u06D5\u06E5\u06E6\u06EE]|
[\u06EF\u06FA-\u06FC\u06FF\u0710\u0712-\u072F\u074D-\u07A5\u07B1\u07CA-\u07EA]|
[\u07F4\u07F5\u07FA\u0800-\u0815\u081A\u0824\u0828\u0840-\u0858\u08A0]|
[\u08A2-\u08AC\u0904-\u0939\u093D\u0950\u0958-\u0961\u0971-\u0977]|
[\u0979-\u097F\u0985-\u098C\u098F\u0990\u0993-\u09A8\u09AA-\u09B0\u09B2]|
[\u09B6-\u09B9\u09BD\u09CE\u09DC\u09DD\u09DF-\u09E1\u09F0\u09F1\u0A05-\u0A0A]|
[\u0A0F\u0A10\u0A13-\u0A28\u0A2A-\u0A30\u0A32\u0A33\u0A35\u0A36\u0A38\u0A39]|
[\u0A59-\u0A5C\u0A5E\u0A72-\u0A74\u0A85-\u0A8D\u0A8F-\u0A91\u0A93-\u0AA8]|
[\u0AAA-\u0AB0\u0AB2\u0AB3\u0AB5-\u0AB9\u0ABD\u0AD0\u0AE0\u0AE1\u0B05-\u0B0C]|
[\u0B0F\u0B10\u0B13-\u0B28\u0B2A-\u0B30\u0B32\u0B33\u0B35-\u0B39\u0B3D\u0B5C]|
[\u0B5D\u0B5F-\u0B61\u0B71\u0B83\u0B85-\u0B8A\u0B8E-\u0B90\u0B92-\u0B95\u0B99]|
[\u0B9A\u0B9C\u0B9E\u0B9F\u0BA3\u0BA4\u0BA8-\u0BAA\u0BAE-\u0BB9\u0BD0]|
[\u0C05-\u0C0C\u0C0E-\u0C10\u0C12-\u0C28\u0C2A-\u0C33\u0C35-\u0C39\u0C3D]|
[\u0C58\u0C59\u0C60\u0C61\u0C85-\u0C8C\u0C8E-\u0C90\u0C92-\u0CA8\u0CAA-\u0CB3]|
[\u0CB5-\u0CB9\u0CBD\u0CDE\u0CE0\u0CE1\u0CF1\u0CF2\u0D05-\u0D0C\u0D0E-\u0D10]|
[\u0D12-\u0D3A\u0D3D\u0D4E\u0D60\u0D61\u0D7A-\u0D7F\u0D85-\u0D96\u0D9A-\u0DB1]|
[\u0DB3-\u0DBB\u0DBD\u0DC0-\u0DC6\u0E01-\u0E30\u0E32\u0E33\u0E40-\u0E46\u0E81]|
[\u0E82\u0E84\u0E87\u0E88\u0E8A\u0E8D\u0E94-\u0E97\u0E99-\u0E9F\u0EA1-\u0EA3]|
[\u0EA5\u0EA7\u0EAA\u0EAB\u0EAD-\u0EB0\u0EB2\u0EB3\u0EBD\u0EC0-\u0EC4\u0EC6]|
[\u0EDC-\u0EDF\u0F00\u0F40-\u0F47\u0F49-\u0F6C\u0F88-\u0F8C\u1000-\u102A]|
[\u103F\u1050-\u1055\u105A-\u105D\u1061\u1065\u1066\u106E-\u1070\u1075-\u1081]|
[\u108E\u10A0-\u10C5\u10C7\u10CD\u10D0-\u10FA\u10FC-\u1248\u124A-\u124D]|
[\u1250-\u1256\u1258\u125A-\u125D\u1260-\u1288\u128A-\u128D\u1290-\u12B0]|
[\u12B2-\u12B5\u12B8-\u12BE\u12C0\u12C2-\u12C5\u12C8-\u12D6\u12D8-\u1310]|
[\u1312-\u1315\u1318-\u135A\u1380-\u138F\u13A0-\u13F4\u1401-\u166C]|
[\u166F-\u167F\u1681-\u169A\u16A0-\u16EA\u1700-\u170C\u170E-\u1711]|
[\u1720-\u1731\u1740-\u1751\u1760-\u176C\u176E-\u1770\u1780-\u17B3\u17D7]|
[\u17DC\u1820-\u1877\u1880-\u18A8\u18AA\u18B0-\u18F5\u1900-\u191C]|
[\u1950-\u196D\u1970-\u1974\u1980-\u19AB\u19C1-\u19C7\u1A00-\u1A16]|
[\u1A20-\u1A54\u1AA7\u1B05-\u1B33\u1B45-\u1B4B\u1B83-\u1BA0\u1BAE\u1BAF]|
[\u1BBA-\u1BE5\u1C00-\u1C23\u1C4D-\u1C4F\u1C5A-\u1C7D\u1CE9-\u1CEC]|
[\u1CEE-\u1CF1\u1CF5\u1CF6\u1D00-\u1DBF\u1E00-\u1F15\u1F18-\u1F1D]|
[\u1F20-\u1F45\u1F48-\u1F4D\u1F50-\u1F57\u1F59\u1F5B\u1F5D\u1F5F-\u1F7D]|
[\u1F80-\u1FB4\u1FB6-\u1FBC\u1FBE\u1FC2-\u1FC4\u1FC6-\u1FCC\u1FD0-\u1FD3]|
[\u1FD6-\u1FDB\u1FE0-\u1FEC\u1FF2-\u1FF4\u1FF6-\u1FFC\u2071\u207F]|
[\u2090-\u209C\u2102\u2107\u210A-\u2113\u2115\u2119-\u211D\u2124\u2126\u2128]|
[\u212A-\u212D\u212F-\u2139\u213C-\u213F\u2145-\u2149\u214E\u2183\u2184]|
[\u2C00-\u2C2E\u2C30-\u2C5E\u2C60-\u2CE4\u2CEB-\u2CEE\u2CF2\u2CF3]|
[\u2D00-\u2D25\u2D27\u2D2D\u2D30-\u2D67\u2D6F\u2D80-\u2D96\u2DA0-\u2DA6]|
[\u2DA8-\u2DAE\u2DB0-\u2DB6\u2DB8-\u2DBE\u2DC0-\u2DC6\u2DC8-\u2DCE]|
[\u2DD0-\u2DD6\u2DD8-\u2DDE\u2E2F\u3005\u3006\u3031-\u3035\u303B\u303C]|
[\u3041-\u3096\u309D-\u309F\u30A1-\u30FA\u30FC-\u30FF\u3105-\u312D]|
[\u3131-\u318E\u31A0-\u31BA\u31F0-\u31FF\u3400-\u4DB5\u4E00-\u9FCC]|
[\uA000-\uA48C\uA4D0-\uA4FD\uA500-\uA60C\uA610-\uA61F\uA62A\uA62B]|
[\uA640-\uA66E\uA67F-\uA697\uA6A0-\uA6E5\uA717-\uA71F\uA722-\uA788]|
[\uA78B-\uA78E\uA790-\uA793\uA7A0-\uA7AA\uA7F8-\uA801\uA803-\uA805]|
[\uA807-\uA80A\uA80C-\uA822\uA840-\uA873\uA882-\uA8B3\uA8F2-\uA8F7\uA8FB]|
[\uA90A-\uA925\uA930-\uA946\uA960-\uA97C\uA984-\uA9B2\uA9CF\uAA00-\uAA28]|
[\uAA40-\uAA42\uAA44-\uAA4B\uAA60-\uAA76\uAA7A\uAA80-\uAAAF\uAAB1\uAAB5]|
[\uAAB6\uAAB9-\uAABD\uAAC0\uAAC2\uAADB-\uAADD\uAAE0-\uAAEA\uAAF2-\uAAF4]|
[\uAB01-\uAB06\uAB09-\uAB0E\uAB11-\uAB16\uAB20-\uAB26\uAB28-\uAB2E]|
[\uABC0-\uABE2\uAC00-\uD7A3\uD7B0-\uD7C6\uD7CB-\uD7FB\uF900-\uFA6D]|
[\uFA70-\uFAD9\uFB00-\uFB06\uFB13-\uFB17\uFB1D\uFB1F-\uFB28\uFB2A-\uFB36]|
[\uFB38-\uFB3C\uFB3E\uFB40\uFB41\uFB43\uFB44\uFB46-\uFBB1\uFBD3-\uFD3D]|
[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|
[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|
[\uFFD2-\uFFD7\uFFDA-\uFFDC]
return 'UNICODE_TEXT';
"|" return 'PIPE';
"(" return 'PS';
")" return 'PE';
"[" return 'SQS';
"]" return 'SQE';
"{" return 'DIAMOND_START'
"}" return 'DIAMOND_STOP'
"\"" return 'QUOTE';
\n+ return 'NEWLINE';
\s return 'SPACE';
<<EOF>> return 'EOF';
/lex
/* operator associations and precedence */
%left '^'
%start mermaidDoc
%% /* language grammar */
mermaidDoc: graphConfig document;
document
: /* empty */
{ $$ = [];}
| document line
{
if($2 !== []){
$1.push($2);
}
$$=$1;}
;
line
: statement
{$$=$1;}
| SEMI
| NEWLINE
| SPACE
| EOF
;
graphConfig
: SPACE graphConfig
| NEWLINE graphConfig
| GRAPH SPACE DIR FirstStmtSeperator
{ yy.setDirection($3);$$ = $3;}
| GRAPH SPACE TAGEND FirstStmtSeperator
{ yy.setDirection("LR");$$ = $3;}
| GRAPH SPACE TAGSTART FirstStmtSeperator
{ yy.setDirection("RL");$$ = $3;}
| GRAPH SPACE UP FirstStmtSeperator
{ yy.setDirection("BT");$$ = $3;}
| GRAPH SPACE DOWN FirstStmtSeperator
{ yy.setDirection("TB");$$ = $3;}
;
ending: endToken ending
| endToken
;
endToken: NEWLINE | SPACE | EOF;
FirstStmtSeperator
: SEMI | NEWLINE | spaceList NEWLINE ;
spaceListNewline
: SPACE spaceListNewline
| NEWLINE spaceListNewline
| NEWLINE
| SPACE
;
spaceList
: SPACE spaceList
| SPACE
;
statement
: verticeStatement separator
{$$=$1}
| styleStatement separator
{$$=[];}
| linkStyleStatement separator
{$$=[];}
| classDefStatement separator
{$$=[];}
| classStatement separator
{$$=[];}
| clickStatement separator
{$$=[];}
| subgraph SPACE alphaNum SQS text SQE separator document end
{$$=yy.addSubGraph($3,$8,$5);}
| subgraph SPACE STR separator document end
{$$=yy.addSubGraph(undefined,$5,$3);}
| subgraph SPACE alphaNum separator document end
{$$=yy.addSubGraph($3,$5,$3);}
| subgraph separator document end
{$$=yy.addSubGraph(undefined,$3,undefined);}
;
separator: NEWLINE | SEMI | EOF ;
verticeStatement:
vertex link vertex
{ yy.addLink($1,$3,$2);$$ = [$1,$3];}
| vertex
{$$ = [$1];}
;
vertex: alphaNum SQS text SQE
{$$ = $1;yy.addVertex($1,$3,'square');}
| alphaNum SQS text SQE spaceList
{$$ = $1;yy.addVertex($1,$3,'square');}
| alphaNum PS PS text PE PE
{$$ = $1;yy.addVertex($1,$4,'circle');}
| alphaNum PS PS text PE PE spaceList
{$$ = $1;yy.addVertex($1,$4,'circle');}
| alphaNum '(-' text '-)'
{$$ = $1;yy.addVertex($1,$3,'ellipse');}
| alphaNum '(-' text '-)' spaceList
{$$ = $1;yy.addVertex($1,$3,'ellipse');}
| alphaNum PS text PE
{$$ = $1;yy.addVertex($1,$3,'round');}
| alphaNum PS text PE spaceList
{$$ = $1;yy.addVertex($1,$3,'round');}
| alphaNum DIAMOND_START text DIAMOND_STOP
{$$ = $1;yy.addVertex($1,$3,'diamond');}
| alphaNum DIAMOND_START text DIAMOND_STOP spaceList
{$$ = $1;yy.addVertex($1,$3,'diamond');}
| alphaNum TAGEND text SQE
{$$ = $1;yy.addVertex($1,$3,'odd');}
| alphaNum TAGEND text SQE spaceList
{$$ = $1;yy.addVertex($1,$3,'odd');}
/* | alphaNum SQS text TAGSTART
{$$ = $1;yy.addVertex($1,$3,'odd_right');}
| alphaNum SQS text TAGSTART spaceList
{$$ = $1;yy.addVertex($1,$3,'odd_right');} */
| alphaNum
{$$ = $1;yy.addVertex($1);}
| alphaNum spaceList
{$$ = $1;yy.addVertex($1);}
;
alphaNum
: alphaNumStatement
{$$=$1;}
| alphaNum alphaNumStatement
{$$=$1+''+$2;}
;
alphaNumStatement
: DIR
{$$=$1;}
| alphaNumToken
{$$=$1;}
| DOWN
{$$='v';}
| MINUS
{$$='-';}
;
link: linkStatement arrowText
{$1.text = $2;$$ = $1;}
| linkStatement TESTSTR SPACE
{$1.text = $2;$$ = $1;}
| linkStatement arrowText SPACE
{$1.text = $2;$$ = $1;}
| linkStatement
{$$ = $1;}
| '--' text ARROW_POINT
{$$ = {"type":"arrow","stroke":"normal","text":$2};}
| 'START_DOUBLE_ARROW_POINT' text ARROW_POINT
{$$ = {"type":"double_arrow_point","stroke":"normal","text":$2};}
| '--' text ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"normal","text":$2};}
| '--' text ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"normal","text":$2};}
| '--' text ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"normal","text":$2};}
| '-.' text DOTTED_ARROW_POINT
{$$ = {"type":"arrow","stroke":"dotted","text":$2};}
| '-.' text DOTTED_ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"dotted","text":$2};}
| '-.' text DOTTED_ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"dotted","text":$2};}
| '-.' text DOTTED_ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"dotted","text":$2};}
| '==' text THICK_ARROW_POINT
{$$ = {"type":"arrow","stroke":"thick","text":$2};}
| '==' text THICK_ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"thick","text":$2};}
| '==' text THICK_ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"thick","text":$2};}
| '==' text THICK_ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"thick","text":$2};}
;
linkStatement: ARROW_POINT
{$$ = {"type":"arrow","stroke":"normal"};}
| DOUBLE_ARROW_POINT
{$$ = {"type":"double_arrow_point","stroke":"normal"};}
| ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"normal"};}
% | DOUBLE_ARROW_CIRCLE
% {$$ = {"type":"double_arrow_circle","stroke":"normal"};}
| ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"normal"};}
% | DOUBLE_ARROW_CROSS
% {$$ = {"type":"double_arrow_cross","stroke":"normal"};}
| ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"normal"};}
| DOTTED_ARROW_POINT
{$$ = {"type":"arrow","stroke":"dotted"};}
% | DOUEBL_DOTTED_ARROW_POINT
% {$$ = {"type":"doueble_arrow_point","stroke":"dotted"};}
| DOTTED_ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"dotted"};}
% | DOTTED_ARROW_CIRCLE
% {$$ = {"type":"double_arrow_circle","stroke":"dotted"};}
| DOTTED_ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"dotted"};}
% | DOTTED_ARROW_CROSS
% {$$ = {"type":"double_arrow_cross","stroke":"dotted"};}
| DOTTED_ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"dotted"};}
| THICK_ARROW_POINT
{$$ = {"type":"arrow","stroke":"thick"};}
% | THICK_ARROW_POINT
% {$$ = {"type":"double_arrow_point","stroke":"thick"};}
| THICK_ARROW_CIRCLE
{$$ = {"type":"arrow_circle","stroke":"thick"};}
% | THICK_ARROW_CIRCLE
% {$$ = {"type":"double_arrow_circle","stroke":"thick"};}
| THICK_ARROW_CROSS
{$$ = {"type":"arrow_cross","stroke":"thick"};}
% | THICK_ARROW_CROSS
% {$$ = {"type":"double_arrow_cross","stroke":"thick"};}
| THICK_ARROW_OPEN
{$$ = {"type":"arrow_open","stroke":"thick"};}
;
arrowText:
PIPE text PIPE
{$$ = $2;}
;
text: textToken
{$$=$1;}
| text textToken
{$$=$1+''+$2;}
| STR
{$$=$1;}
;
commentText: commentToken
{$$=$1;}
| commentText commentToken
{$$=$1+''+$2;}
;
keywords
: STYLE | LINKSTYLE | CLASSDEF | CLASS | CLICK | GRAPH | DIR | subgraph | end | DOWN | UP;
textNoTags: textNoTagsToken
{$$=$1;}
| textNoTags textNoTagsToken
{$$=$1+''+$2;}
;
classDefStatement:CLASSDEF SPACE DEFAULT SPACE stylesOpt
{$$ = $1;yy.addClass($3,$5);}
| CLASSDEF SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.addClass($3,$5);}
;
classStatement:CLASS SPACE alphaNum SPACE alphaNum
{$$ = $1;yy.setClass($3, $5);}
;
clickStatement
: CLICK SPACE alphaNum SPACE alphaNum {$$ = $1;yy.setClickEvent($3, $5, undefined);}
| CLICK SPACE alphaNum SPACE alphaNum SPACE STR {$$ = $1;yy.setClickEvent($3, $5, $7) ;}
| CLICK SPACE alphaNum SPACE STR {$$ = $1;yy.setLink($3, $5, undefined);}
| CLICK SPACE alphaNum SPACE STR SPACE STR {$$ = $1;yy.setLink($3, $5, $7 );}
;
styleStatement:STYLE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.addVertex($3,undefined,undefined,$5);}
| STYLE SPACE HEX SPACE stylesOpt
{$$ = $1;yy.updateLink($3,$5);}
;
linkStyleStatement
: LINKSTYLE SPACE DEFAULT SPACE stylesOpt
{$$ = $1;yy.updateLink([$3],$5);}
| LINKSTYLE SPACE numList SPACE stylesOpt
{$$ = $1;yy.updateLink($3,$5);}
| LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.updateLinkInterpolate([$3],$7);yy.updateLink([$3],$9);}
| LINKSTYLE SPACE numList SPACE INTERPOLATE SPACE alphaNum SPACE stylesOpt
{$$ = $1;yy.updateLinkInterpolate($3,$7);yy.updateLink($3,$9);}
| LINKSTYLE SPACE DEFAULT SPACE INTERPOLATE SPACE alphaNum
{$$ = $1;yy.updateLinkInterpolate([$3],$7);}
| LINKSTYLE SPACE numList SPACE INTERPOLATE SPACE alphaNum
{$$ = $1;yy.updateLinkInterpolate($3,$7);}
;
commentStatement: PCT PCT commentText;
numList: NUM
{$$ = [$1]}
| numList COMMA NUM
{$1.push($3);$$ = $1;}
;
stylesOpt: style
{$$ = [$1]}
| stylesOpt COMMA style
{$1.push($3);$$ = $1;}
;
style: styleComponent
|style styleComponent
{$$ = $1 + $2;}
;
styleComponent: ALPHA | COLON | MINUS | NUM | UNIT | SPACE | HEX | BRKT | DOT | STYLE | PCT ;
/* Token lists */
commentToken : textToken | graphCodeTokens ;
textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFAULT;
textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ;
alphaNumToken : ALPHA | PUNCTUATION | UNICODE_TEXT | NUM | COLON | COMMA | PLUS | EQUALS | MULT | DOT | BRKT ;
graphCodeTokens: PIPE | PS | PE | SQS | SQE | DIAMOND_START | DIAMOND_STOP | TAG_START | TAG_END | ARROW_CROSS | ARROW_POINT | ARROW_CIRCLE | ARROW_OPEN | QUOTE | SEMI ;
%%

View File

@@ -0,0 +1,53 @@
export interface FlowVertex {
classes: string[];
dir?: string;
domId: string;
haveCallback?: boolean;
id: string;
labelType: 'text';
link?: string;
linkTarget?: string;
props?: any;
styles: string[];
text?: string;
type?: string;
}
export interface FlowText {
text: string;
type: 'text';
}
export interface FlowEdge {
start: string;
end: string;
interpolate?: string;
type?: string;
stroke?: string;
style?: string[];
length?: number;
text: string;
labelType: 'text';
}
export interface FlowClass {
id: string;
styles: string[];
textStyles: string[];
}
export interface FlowSubGraph {
classes: string[];
dir?: string;
id: string;
labelType: string;
nodes: string[];
title: string;
}
export interface FlowLink {
length?: number;
stroke: string;
type: string;
text?: string;
}

Some files were not shown because too many files have changed in this diff Show More