mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-15 05:19:58 +02:00
Compare commits
110 Commits
docs/2910_
...
sidv/class
Author | SHA1 | Date | |
---|---|---|---|
![]() |
345b75cc0e | ||
![]() |
0ebea7744b | ||
![]() |
44b93c039a | ||
![]() |
4d5313699e | ||
![]() |
cd198290d7 | ||
![]() |
60ed7d3273 | ||
![]() |
9bcfba6620 | ||
![]() |
7ea3c64268 | ||
![]() |
2b6a34e9e0 | ||
![]() |
458b90c78d | ||
![]() |
dd284c0986 | ||
![]() |
ed819e96d5 | ||
![]() |
21539dfb6a | ||
![]() |
91785b8284 | ||
![]() |
f202770b70 | ||
![]() |
8186a54962 | ||
![]() |
979dcb073d | ||
![]() |
ecf14cd4e3 | ||
![]() |
92d9f35f89 | ||
![]() |
15c7c37227 | ||
![]() |
267935713c | ||
![]() |
8ef5f38861 | ||
![]() |
47af4760bc | ||
![]() |
8e5275d844 | ||
![]() |
c51afa4dd2 | ||
![]() |
671ad82ad3 | ||
![]() |
1bfa1a8c88 | ||
![]() |
ab140a0fc7 | ||
![]() |
7d395e176e | ||
![]() |
3d35015ee9 | ||
![]() |
2676130146 | ||
![]() |
fb3814bf85 | ||
![]() |
2dd1415849 | ||
![]() |
866909b803 | ||
![]() |
9fa6dc23ca | ||
![]() |
9dccf4d9c9 | ||
![]() |
f248a22c71 | ||
![]() |
6563a6ea26 | ||
![]() |
87d2899e4e | ||
![]() |
a3a6eb9bf5 | ||
![]() |
c28c285711 | ||
![]() |
5f8f79fc3e | ||
![]() |
a4ba192aad | ||
![]() |
f553fa5eb8 | ||
![]() |
c3939d3fb3 | ||
![]() |
32d1783390 | ||
![]() |
58afdd1baa | ||
![]() |
eba5fcca6b | ||
![]() |
52cbf9fd28 | ||
![]() |
fc19bf93aa | ||
![]() |
54de763a66 | ||
![]() |
56419f7459 | ||
![]() |
2537152c7a | ||
![]() |
14c1c1cc59 | ||
![]() |
72b4a74bee | ||
![]() |
408910e6e8 | ||
![]() |
24c8e575f4 | ||
![]() |
4a06bc1a68 | ||
![]() |
8d0ca2c876 | ||
![]() |
fc96ebefd4 | ||
![]() |
394330175f | ||
![]() |
a7ae1b6890 | ||
![]() |
f946c3da06 | ||
![]() |
156fbd1958 | ||
![]() |
7dd0d126e2 | ||
![]() |
cc8b4572d5 | ||
![]() |
e5315f2933 | ||
![]() |
205360c109 | ||
![]() |
984a0e6d06 | ||
![]() |
eb63568ceb | ||
![]() |
cc6f896b69 | ||
![]() |
83e47a7216 | ||
![]() |
1d64549cce | ||
![]() |
4ae361bd1f | ||
![]() |
6502496a2c | ||
![]() |
8678ceeb3c | ||
![]() |
9cb62f4d2e | ||
![]() |
6c0ef54e18 | ||
![]() |
fd731c5ccd | ||
![]() |
cbe9490dc0 | ||
![]() |
82054bfabc | ||
![]() |
963dd75c39 | ||
![]() |
1c24617f98 | ||
![]() |
1559c2ca21 | ||
![]() |
6141722b1f | ||
![]() |
222d8eed4e | ||
![]() |
718d52a72c | ||
![]() |
fe1a06271a | ||
![]() |
bd2370555b | ||
![]() |
86c9ee4e90 | ||
![]() |
b26bcf1343 | ||
![]() |
5d5c6275f9 | ||
![]() |
9c1a47d1fc | ||
![]() |
13852b7f4e | ||
![]() |
4fd7a88a15 | ||
![]() |
5c2a6b5eb1 | ||
![]() |
9cbebbb8a0 | ||
![]() |
e26d987c4e | ||
![]() |
53669efaf8 | ||
![]() |
b68f45ef59 | ||
![]() |
8f44de651b | ||
![]() |
2ede244da0 | ||
![]() |
77a181978e | ||
![]() |
170bbce0d3 | ||
![]() |
fc99d9be41 | ||
![]() |
9fb9bed806 | ||
![]() |
01b2f80a95 | ||
![]() |
da7ff777d1 | ||
![]() |
65c93807d8 | ||
![]() |
8ed9ddc7aa |
25
.build/common.ts
Normal file
25
.build/common.ts
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
/**
|
||||||
|
* 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',
|
||||||
|
},
|
||||||
|
} as const;
|
5
.build/generateLangium.ts
Normal file
5
.build/generateLangium.ts
Normal file
@@ -0,0 +1,5 @@
|
|||||||
|
import { generate } from 'langium-cli';
|
||||||
|
|
||||||
|
export async function generateLangium() {
|
||||||
|
await generate({ file: `./packages/parser/langium-config.json` });
|
||||||
|
}
|
122
.build/jsonSchema.ts
Normal file
122
.build/jsonSchema.ts
Normal file
@@ -0,0 +1,122 @@
|
|||||||
|
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',
|
||||||
|
'requirement',
|
||||||
|
'mindmap',
|
||||||
|
'timeline',
|
||||||
|
'gitGraph',
|
||||||
|
'c4',
|
||||||
|
'sankey',
|
||||||
|
] 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.
|
||||||
|
*/
|
||||||
|
export 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)};`;
|
||||||
|
};
|
9
.build/langium-cli.d.ts
vendored
Normal file
9
.build/langium-cli.d.ts
vendored
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
declare module 'langium-cli' {
|
||||||
|
export interface GenerateOptions {
|
||||||
|
file?: string;
|
||||||
|
mode?: 'development' | 'production';
|
||||||
|
watch?: boolean;
|
||||||
|
}
|
||||||
|
|
||||||
|
export function generate(options: GenerateOptions): Promise<boolean>;
|
||||||
|
}
|
65
.esbuild/build.ts
Normal file
65
.esbuild/build.ts
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
import { build } from 'esbuild';
|
||||||
|
import { mkdir, writeFile } from 'node:fs/promises';
|
||||||
|
import { MermaidBuildOptions, defaultOptions, getBuildConfig } from './util.js';
|
||||||
|
import { packageOptions } from '../.build/common.js';
|
||||||
|
import { generateLangium } from '../.build/generateLangium.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 dependecy
|
||||||
|
for (const pkg of packageNames) {
|
||||||
|
await buildPackage(pkg).catch(handler);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
void main();
|
15
.esbuild/jisonPlugin.ts
Normal file
15
.esbuild/jisonPlugin.ts
Normal 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: [] };
|
||||||
|
});
|
||||||
|
},
|
||||||
|
};
|
35
.esbuild/jsonSchemaPlugin.ts
Normal file
35
.esbuild/jsonSchemaPlugin.ts
Normal 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;
|
116
.esbuild/server.ts
Normal file
116
.esbuild/server.ts
Normal file
@@ -0,0 +1,116 @@
|
|||||||
|
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';
|
||||||
|
|
||||||
|
const parserCtx = await context(
|
||||||
|
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'parser' })
|
||||||
|
);
|
||||||
|
const mermaidCtx = await context(
|
||||||
|
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid' })
|
||||||
|
);
|
||||||
|
const mermaidIIFECtx = await context(
|
||||||
|
getBuildConfig({
|
||||||
|
...defaultOptions,
|
||||||
|
minify: false,
|
||||||
|
core: false,
|
||||||
|
entryName: 'mermaid',
|
||||||
|
format: 'iife',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const externalCtx = await context(
|
||||||
|
getBuildConfig({
|
||||||
|
...defaultOptions,
|
||||||
|
minify: false,
|
||||||
|
core: false,
|
||||||
|
entryName: 'mermaid-example-diagram',
|
||||||
|
})
|
||||||
|
);
|
||||||
|
const zenumlCtx = await context(
|
||||||
|
getBuildConfig({ ...defaultOptions, minify: false, core: false, entryName: 'mermaid-zenuml' })
|
||||||
|
);
|
||||||
|
const contexts = [parserCtx, mermaidCtx, mermaidIIFECtx, externalCtx, zenumlCtx];
|
||||||
|
|
||||||
|
const rebuildAll = async () => {
|
||||||
|
console.time('Rebuild time');
|
||||||
|
await Promise.all(contexts.map((ctx) => ctx.rebuild()));
|
||||||
|
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);
|
||||||
|
app.use(express.static('./packages/parser/dist'));
|
||||||
|
app.use(express.static('./packages/mermaid/dist'));
|
||||||
|
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
||||||
|
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
||||||
|
app.use(express.static('demos'));
|
||||||
|
app.use(express.static('cypress/platform'));
|
||||||
|
|
||||||
|
app.listen(9000, () => {
|
||||||
|
console.log(`Listening on http://localhost:9000`);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
createServer();
|
98
.esbuild/util.ts
Normal file
98
.esbuild/util.ts
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
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]`,
|
||||||
|
});
|
||||||
|
|
||||||
|
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;
|
||||||
|
};
|
@@ -6,3 +6,6 @@ cypress/plugins/index.js
|
|||||||
coverage
|
coverage
|
||||||
*.json
|
*.json
|
||||||
node_modules
|
node_modules
|
||||||
|
|
||||||
|
# autogenereated by langium-cli
|
||||||
|
generated/
|
||||||
|
4
.gitignore
vendored
4
.gitignore
vendored
@@ -46,3 +46,7 @@ stats/
|
|||||||
|
|
||||||
demos/dev/**
|
demos/dev/**
|
||||||
!/demos/dev/example.html
|
!/demos/dev/example.html
|
||||||
|
!/demos/dev/reload.js
|
||||||
|
|
||||||
|
# autogenereated by langium-cli
|
||||||
|
generated/
|
||||||
|
@@ -10,3 +10,6 @@ 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/
|
||||||
|
@@ -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: {
|
||||||
@@ -126,7 +83,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,
|
||||||
@@ -146,24 +103,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 {
|
||||||
|
@@ -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 {
|
||||||
|
@@ -1,108 +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',
|
|
||||||
'requirement',
|
|
||||||
'mindmap',
|
|
||||||
'timeline',
|
|
||||||
'gitGraph',
|
|
||||||
'c4',
|
|
||||||
'sankey',
|
|
||||||
] 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.
|
||||||
@@ -119,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, {
|
return {
|
||||||
filename: idAsUrl.pathname,
|
code: idAsUrl.searchParams.get('only-defaults')
|
||||||
// only allow JSON types in our YAML doc (will probably be default in YAML 1.3)
|
? getDefaults(jsonSchema)
|
||||||
// e.g. `true` will be parsed a boolean `true`, `True` will be parsed as string `"True"`.
|
: getSchema(jsonSchema),
|
||||||
schema: JSON_SCHEMA,
|
map: null, // no source map
|
||||||
}) as JSONSchemaType<MermaidConfig>;
|
};
|
||||||
return {
|
|
||||||
code: `export default ${JSON.stringify(generateDefaults(jsonSchema), undefined, 2)};`,
|
|
||||||
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
|
|
||||||
};
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@@ -14,6 +14,7 @@ async function createServer() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
app.use(cors());
|
app.use(cors());
|
||||||
|
app.use(express.static('./packages/parser/dist'));
|
||||||
app.use(express.static('./packages/mermaid/dist'));
|
app.use(express.static('./packages/mermaid/dist'));
|
||||||
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
app.use(express.static('./packages/mermaid-zenuml/dist'));
|
||||||
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
app.use(express.static('./packages/mermaid-example-diagram/dist'));
|
||||||
|
@@ -60,7 +60,7 @@ Use Mermaid with your favorite applications, check out the list of [Integrations
|
|||||||
|
|
||||||
You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations.md).
|
You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations.md).
|
||||||
|
|
||||||
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/intro/n00b-gettingStarted.md), [Usage](./docs/config/usage.md) and [Tutorials](./docs/config/Tutorials.md).
|
For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/intro/getting-started.md), [Usage](./docs/config/usage.md) and [Tutorials](./docs/config/Tutorials.md).
|
||||||
|
|
||||||
In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests.
|
In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests.
|
||||||
|
|
||||||
|
@@ -55,7 +55,7 @@ Mermaid 通过允许用户创建便于修改的图表来解决这一难题,它
|
|||||||
Mermaid 甚至能让非程序员也能通过 [Mermaid Live Editor](https://mermaid.live/) 轻松创建详细的图表。<br/>
|
Mermaid 甚至能让非程序员也能通过 [Mermaid Live Editor](https://mermaid.live/) 轻松创建详细的图表。<br/>
|
||||||
你可以访问 [教程](./docs/config/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/ecosystem/integrations.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。
|
你可以访问 [教程](./docs/config/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/ecosystem/integrations.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。
|
||||||
|
|
||||||
如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/intro/n00b-gettingStarted.md), [用法](./docs/config/usage.md) 和 [教程](./docs/config/Tutorials.md).
|
如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/intro/getting-started.md), [用法](./docs/config/usage.md) 和 [教程](./docs/config/Tutorials.md).
|
||||||
|
|
||||||
<!-- </Main description> -->
|
<!-- </Main description> -->
|
||||||
|
|
||||||
|
@@ -57,6 +57,7 @@
|
|||||||
"gzipped",
|
"gzipped",
|
||||||
"huynh",
|
"huynh",
|
||||||
"huynhicode",
|
"huynhicode",
|
||||||
|
"iife",
|
||||||
"inkdrop",
|
"inkdrop",
|
||||||
"jaoude",
|
"jaoude",
|
||||||
"jgreywolf",
|
"jgreywolf",
|
||||||
@@ -70,6 +71,7 @@
|
|||||||
"knut",
|
"knut",
|
||||||
"knutsveidqvist",
|
"knutsveidqvist",
|
||||||
"laganeckas",
|
"laganeckas",
|
||||||
|
"langium",
|
||||||
"linetype",
|
"linetype",
|
||||||
"lintstagedrc",
|
"lintstagedrc",
|
||||||
"logmsg",
|
"logmsg",
|
||||||
@@ -81,6 +83,7 @@
|
|||||||
"mdbook",
|
"mdbook",
|
||||||
"mermaidjs",
|
"mermaidjs",
|
||||||
"mermerd",
|
"mermerd",
|
||||||
|
"metafile",
|
||||||
"mindaugas",
|
"mindaugas",
|
||||||
"mindmap",
|
"mindmap",
|
||||||
"mindmaps",
|
"mindmaps",
|
||||||
@@ -94,6 +97,7 @@
|
|||||||
"nirname",
|
"nirname",
|
||||||
"npmjs",
|
"npmjs",
|
||||||
"orlandoni",
|
"orlandoni",
|
||||||
|
"outdir",
|
||||||
"pathe",
|
"pathe",
|
||||||
"pbrolin",
|
"pbrolin",
|
||||||
"phpbb",
|
"phpbb",
|
||||||
@@ -154,7 +158,8 @@
|
|||||||
"xlink",
|
"xlink",
|
||||||
"yash",
|
"yash",
|
||||||
"yokozuna",
|
"yokozuna",
|
||||||
"zenuml"
|
"zenuml",
|
||||||
|
"zune"
|
||||||
],
|
],
|
||||||
"patterns": [
|
"patterns": [
|
||||||
{ "name": "Markdown links", "pattern": "\\((.*)\\)", "description": "" },
|
{ "name": "Markdown links", "pattern": "\\((.*)\\)", "description": "" },
|
||||||
|
@@ -18,7 +18,11 @@ const utf8ToB64 = (str: string): string => {
|
|||||||
return Buffer.from(decodeURIComponent(encodeURIComponent(str))).toString('base64');
|
return Buffer.from(decodeURIComponent(encodeURIComponent(str))).toString('base64');
|
||||||
};
|
};
|
||||||
|
|
||||||
const batchId: string = 'mermaid-batch-' + Cypress.env('CYPRESS_COMMIT') || Date.now().toString();
|
const batchId: string =
|
||||||
|
'mermaid-batch-' +
|
||||||
|
(Cypress.env('useAppli')
|
||||||
|
? Date.now().toString()
|
||||||
|
: Cypress.env('CYPRESS_COMMIT') || Date.now().toString());
|
||||||
|
|
||||||
export const mermaidUrl = (
|
export const mermaidUrl = (
|
||||||
graphStr: string,
|
graphStr: string,
|
||||||
|
11
cypress/integration/other/iife.spec.js
Normal file
11
cypress/integration/other/iife.spec.js
Normal 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');
|
||||||
|
});
|
||||||
|
});
|
@@ -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>');
|
|
||||||
});
|
|
||||||
});
|
|
@@ -305,4 +305,21 @@ ORDER ||--|{ LINE-ITEM : contains
|
|||||||
{}
|
{}
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should render entities with entity name aliases', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`
|
||||||
|
erDiagram
|
||||||
|
p[Person] {
|
||||||
|
varchar(64) firstName
|
||||||
|
varchar(64) lastName
|
||||||
|
}
|
||||||
|
c["Customer Account"] {
|
||||||
|
varchar(128) email
|
||||||
|
}
|
||||||
|
p ||--o| c : has
|
||||||
|
`,
|
||||||
|
{ logLevel: 1 }
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
@@ -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"
|
||||||
|
@@ -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]);
|
||||||
|
29
cypress/platform/iife.html
Normal file
29
cypress/platform/iife.html
Normal 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>
|
@@ -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);
|
||||||
}
|
}
|
||||||
|
@@ -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
|
||||||
*/
|
*/
|
||||||
|
@@ -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>
|
|
@@ -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>
|
||||||
|
@@ -5,6 +5,8 @@
|
|||||||
<title>Mermaid development page</title>
|
<title>Mermaid development page</title>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
<pre class="mermaid">info</pre>
|
||||||
|
|
||||||
<pre id="diagram" class="mermaid">
|
<pre id="diagram" class="mermaid">
|
||||||
graph TB
|
graph TB
|
||||||
a --> b
|
a --> b
|
||||||
@@ -30,5 +32,7 @@ graph TB
|
|||||||
console.log(svg);
|
console.log(svg);
|
||||||
el.innerHTML = svg;
|
el.innerHTML = svg;
|
||||||
</script>
|
</script>
|
||||||
|
|
||||||
|
<script src="/dev/reload.js"></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
22
demos/dev/reload.js
Normal file
22
demos/dev/reload.js
Normal 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);
|
@@ -110,6 +110,20 @@
|
|||||||
}
|
}
|
||||||
MANUFACTURER only one to zero or more CAR : makes
|
MANUFACTURER only one to zero or more CAR : makes
|
||||||
</pre>
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
|
<pre class="mermaid">
|
||||||
|
erDiagram
|
||||||
|
p[Person] {
|
||||||
|
string firstName
|
||||||
|
string lastName
|
||||||
|
}
|
||||||
|
a["Customer Account"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
p ||--o| a : has
|
||||||
|
</pre>
|
||||||
|
<hr />
|
||||||
|
|
||||||
<script type="module">
|
<script type="module">
|
||||||
import mermaid from './mermaid.esm.mjs';
|
import mermaid from './mermaid.esm.mjs';
|
||||||
|
@@ -37,7 +37,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,
|
||||||
|
@@ -5,7 +5,7 @@ services:
|
|||||||
stdin_open: true
|
stdin_open: true
|
||||||
tty: true
|
tty: true
|
||||||
working_dir: /mermaid
|
working_dir: /mermaid
|
||||||
mem_limit: '2G'
|
mem_limit: '4G'
|
||||||
environment:
|
environment:
|
||||||
- NODE_OPTIONS=--max_old_space_size=4096
|
- NODE_OPTIONS=--max_old_space_size=4096
|
||||||
volumes:
|
volumes:
|
||||||
|
@@ -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>
|
||||||
|
@@ -128,7 +128,7 @@ b. The importing of mermaid library through the `mermaid.esm.mjs` or `mermaid.es
|
|||||||
```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>
|
||||||
@@ -168,7 +168,7 @@ Rendering in Mermaid is initialized by `mermaid.initialize()` call. However, doi
|
|||||||
</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>
|
||||||
|
@@ -296,7 +296,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
|
||||||
|
|
||||||
@@ -314,7 +314,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>
|
||||||
```
|
```
|
||||||
@@ -402,7 +402,7 @@ Update version number in `package.json`.
|
|||||||
npm publish
|
npm publish
|
||||||
```
|
```
|
||||||
|
|
||||||
The above command generates files into the `dist` folder and publishes them to [npmjs.org](npmjs.org).
|
The above command generates files into the `dist` folder and publishes them to [npmjs.com](https://www.npmjs.com/).
|
||||||
|
|
||||||
## Security and safe diagrams
|
## Security and safe diagrams
|
||||||
|
|
||||||
|
@@ -72,7 +72,7 @@ The following are the most commonly used methods, and they are all tied to Merma
|
|||||||
|
|
||||||
Here you can edit certain values to change the behavior and appearance of the diagram.
|
Here you can edit certain values to change the behavior and appearance of the diagram.
|
||||||
|
|
||||||
### [The initialize() call](./getting-started#_3-calling-the-javascript-api)
|
### [The initialize() call](./getting-started.md#_3-calling-the-javascript-api)
|
||||||
|
|
||||||
Used when Mermaid is called via an API, or through a `<script>` tag.
|
Used when Mermaid is called via an API, or through a `<script>` tag.
|
||||||
|
|
||||||
|
@@ -6,8 +6,8 @@
|
|||||||
|
|
||||||
# Announcements
|
# Announcements
|
||||||
|
|
||||||
## [From Chaos to Clarity: Exploring Mind Maps with MermaidJS](https://www.mermaidchart.com/blog/posts/from-chaos-to-clarity-exploring-mind-maps-with-mermaidjs)
|
## [Special cases broke Microsoft Zune and can ruin your code base too](https://www.mermaidchart.com/blog/posts/special-cases-broke-microsoft-zune-and-can-ruin-your-code-base-too/)
|
||||||
|
|
||||||
24 July 2023 · 4 mins
|
23 August 2023 · 15 mins
|
||||||
|
|
||||||
Introducing the concept of mind mapping as a tool for organizing complex information, and highlights Mermaid as a user-friendly software that simplifies the creation and editing of mind maps for applications in IT solution design, business decision-making, and knowledge organization.
|
Read about the pitfalls of special cases in programming, illustrating how they can lead to complexity, diminish readability, and create maintenance challenges.
|
||||||
|
@@ -6,6 +6,24 @@
|
|||||||
|
|
||||||
# Blog
|
# Blog
|
||||||
|
|
||||||
|
## [Special cases broke Microsoft Zune and can ruin your code base too](https://www.mermaidchart.com/blog/posts/special-cases-broke-microsoft-zune-and-can-ruin-your-code-base-too/)
|
||||||
|
|
||||||
|
23 August 2023 · 15 mins
|
||||||
|
|
||||||
|
Read about the pitfalls of special cases in programming, illustrating how they can lead to complexity, diminish readability, and create maintenance challenges.
|
||||||
|
|
||||||
|
## [New AI chatbot now available on Mermaid Chart to simplify text-based diagram creation](https://www.mermaidchart.com/blog/posts/ai-chatbot-now-available-on-mermaid-chart-to-simplify-text-based-diagram-creation/)
|
||||||
|
|
||||||
|
14 August 2023 · 4 mins
|
||||||
|
|
||||||
|
Introducing Mermaid Chart’s new AI chatbot, a diagramming assistant that simplifies text-based diagram creation for everyone, from developers to educators, offering features to start, edit, and fix diagrams, and embodying our vision to make diagramming accessible, user-friendly, and fun.
|
||||||
|
|
||||||
|
## [Believe It or Not, You Still Need an Online UML Diagram Tool](https://www.mermaidchart.com/blog/posts/uml-diagram-tool/)
|
||||||
|
|
||||||
|
14 August 2023 · 8 mins
|
||||||
|
|
||||||
|
A UML diagram tool helps developers and other professionals quickly create and share UML diagrams that communicate information about complex software systems.
|
||||||
|
|
||||||
## [From Chaos to Clarity: Exploring Mind Maps with MermaidJS](https://www.mermaidchart.com/blog/posts/from-chaos-to-clarity-exploring-mind-maps-with-mermaidjs)
|
## [From Chaos to Clarity: Exploring Mind Maps with MermaidJS](https://www.mermaidchart.com/blog/posts/from-chaos-to-clarity-exploring-mind-maps-with-mermaidjs)
|
||||||
|
|
||||||
24 July 2023 · 4 mins
|
24 July 2023 · 4 mins
|
||||||
|
@@ -198,6 +198,34 @@ erDiagram
|
|||||||
|
|
||||||
The `type` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The `name` values follow a similar format to `type`, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
|
The `type` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The `name` values follow a similar format to `type`, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
|
||||||
|
|
||||||
|
### Entity Name Aliases (v\<MERMAID_RELEASE_VERSION>+)
|
||||||
|
|
||||||
|
An alias can be added to an entity using square brackets. If provided, the alias will be showed in the diagram instead of the entity name.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
erDiagram
|
||||||
|
p[Person] {
|
||||||
|
string firstName
|
||||||
|
string lastName
|
||||||
|
}
|
||||||
|
a["Customer Account"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
p ||--o| a : has
|
||||||
|
```
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
erDiagram
|
||||||
|
p[Person] {
|
||||||
|
string firstName
|
||||||
|
string lastName
|
||||||
|
}
|
||||||
|
a["Customer Account"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
p ||--o| a : has
|
||||||
|
```
|
||||||
|
|
||||||
#### Attribute Keys and Comments
|
#### Attribute Keys and Comments
|
||||||
|
|
||||||
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
|
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
|
||||||
|
@@ -1098,7 +1098,7 @@ The icons are accessed via the syntax fa:#icon class name#.
|
|||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
flowchart TD
|
flowchart TD
|
||||||
B["fab:fa-twitter for peace"]
|
B["fa:fa-twitter for peace"]
|
||||||
B-->C[fa:fa-ban forbidden]
|
B-->C[fa:fa-ban forbidden]
|
||||||
B-->D(fa:fa-spinner)
|
B-->D(fa:fa-spinner)
|
||||||
B-->E(A fa:fa-camera-retro perhaps?)
|
B-->E(A fa:fa-camera-retro perhaps?)
|
||||||
@@ -1106,7 +1106,7 @@ flowchart TD
|
|||||||
|
|
||||||
```mermaid
|
```mermaid
|
||||||
flowchart TD
|
flowchart TD
|
||||||
B["fab:fa-twitter for peace"]
|
B["fa:fa-twitter for peace"]
|
||||||
B-->C[fa:fa-ban forbidden]
|
B-->C[fa:fa-ban forbidden]
|
||||||
B-->D(fa:fa-spinner)
|
B-->D(fa:fa-spinner)
|
||||||
B-->E(A fa:fa-camera-retro perhaps?)
|
B-->E(A fa:fa-camera-retro perhaps?)
|
||||||
|
@@ -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>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
@@ -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>
|
||||||
```
|
```
|
||||||
|
|
||||||
|
18
netlify.toml
Normal file
18
netlify.toml
Normal file
@@ -0,0 +1,18 @@
|
|||||||
|
# Settings in the [build] context are global and are applied to
|
||||||
|
# all contexts unless otherwise overridden by more specific contexts.
|
||||||
|
[build]
|
||||||
|
# Directory where the build system installs dependencies
|
||||||
|
# and runs your build. Store your package.json, .nvmrc, etc here.
|
||||||
|
# If not set, defaults to the root directory.
|
||||||
|
base = ""
|
||||||
|
|
||||||
|
# Directory that contains the deploy-ready HTML files and
|
||||||
|
# assets generated by the build. This is an absolute path relative
|
||||||
|
# to the base directory, which is the root by default (/).
|
||||||
|
# This sample publishes the directory located at the absolute
|
||||||
|
# path "root/project/build-output"
|
||||||
|
|
||||||
|
publish = "mermaid-live-editor/docs"
|
||||||
|
|
||||||
|
# Default build command.
|
||||||
|
command = "./scripts/editor.bash"
|
20
package.json
20
package.json
@@ -15,14 +15,14 @@
|
|||||||
"git graph"
|
"git graph"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build:vite": "ts-node-esm --transpileOnly .vite/build.ts",
|
"build": "pnpm build:esbuild && pnpm build:types",
|
||||||
"build:mermaid": "pnpm build:vite --mermaid",
|
"build:esbuild": "pnpm run -r clean && ts-node-esm --transpileOnly .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:watch": "pnpm build:vite --watch",
|
"build:types": "tsc -p ./packages/parser/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-zenuml/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly",
|
||||||
"build": "pnpm run -r clean && pnpm build:types && pnpm build:vite",
|
"dev": "ts-node-esm --transpileOnly .esbuild/server.ts",
|
||||||
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"",
|
"dev:vite": "ts-node-esm --transpileOnly .vite/server.ts",
|
||||||
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev",
|
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev:vite",
|
||||||
"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 . && ts-node-esm scripts/fixCSpell.ts",
|
"lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts",
|
||||||
@@ -31,8 +31,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": "ts-node-esm scripts/coverage.ts",
|
"coverage:merge": "ts-node-esm 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",
|
||||||
@@ -82,6 +82,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",
|
||||||
"cypress": "^12.10.0",
|
"cypress": "^12.10.0",
|
||||||
@@ -106,6 +107,7 @@
|
|||||||
"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": "2.0.1",
|
||||||
"lint-staged": "^13.2.1",
|
"lint-staged": "^13.2.1",
|
||||||
"nyc": "^15.1.0",
|
"nyc": "^15.1.0",
|
||||||
"path-browserify": "^1.0.1",
|
"path-browserify": "^1.0.1",
|
||||||
|
@@ -43,8 +43,7 @@
|
|||||||
"cytoscape-cose-bilkent": "^4.1.0",
|
"cytoscape-cose-bilkent": "^4.1.0",
|
||||||
"cytoscape-fcose": "^2.1.0",
|
"cytoscape-fcose": "^2.1.0",
|
||||||
"d3": "^7.0.0",
|
"d3": "^7.0.0",
|
||||||
"khroma": "^2.0.0",
|
"khroma": "^2.0.0"
|
||||||
"non-layered-tidy-tree-layout": "^2.0.2"
|
|
||||||
},
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@types/cytoscape": "^3.19.9",
|
"@types/cytoscape": "^3.19.9",
|
||||||
|
@@ -19,6 +19,7 @@
|
|||||||
"mermaid"
|
"mermaid"
|
||||||
],
|
],
|
||||||
"scripts": {
|
"scripts": {
|
||||||
|
"clean": "rimraf dist",
|
||||||
"prepublishOnly": "pnpm -w run build"
|
"prepublishOnly": "pnpm -w run build"
|
||||||
},
|
},
|
||||||
"repository": {
|
"repository": {
|
||||||
|
@@ -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
|
||||||
},
|
},
|
||||||
|
@@ -1,6 +1,6 @@
|
|||||||
{
|
{
|
||||||
"name": "mermaid",
|
"name": "mermaid",
|
||||||
"version": "10.3.1",
|
"version": "11.0.0-alpha.2",
|
||||||
"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,6 @@
|
|||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@braintree/sanitize-url": "^6.0.1",
|
"@braintree/sanitize-url": "^6.0.1",
|
||||||
"@types/d3-scale": "^4.0.3",
|
|
||||||
"@types/d3-scale-chromatic": "^3.0.0",
|
|
||||||
"cytoscape": "^3.23.0",
|
"cytoscape": "^3.23.0",
|
||||||
"cytoscape-cose-bilkent": "^4.1.0",
|
"cytoscape-cose-bilkent": "^4.1.0",
|
||||||
"cytoscape-fcose": "^2.1.0",
|
"cytoscape-fcose": "^2.1.0",
|
||||||
@@ -74,11 +72,10 @@
|
|||||||
"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",
|
"mermaid-parser": "workspace:^",
|
||||||
"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",
|
||||||
|
@@ -50,7 +50,10 @@ export class Diagram {
|
|||||||
this.parser.parse = (text: string) =>
|
this.parser.parse = (text: string) =>
|
||||||
originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective)));
|
originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective)));
|
||||||
|
|
||||||
this.parser.parser.yy = this.db;
|
if (this.parser.parser !== undefined) {
|
||||||
|
// The parser.parser.yy is only present in JISON parsers. So, we'll only set if required.
|
||||||
|
this.parser.parser.yy = this.db;
|
||||||
|
}
|
||||||
this.init = diagram.init;
|
this.init = diagram.init;
|
||||||
this.parse();
|
this.parse();
|
||||||
}
|
}
|
||||||
|
@@ -368,7 +368,20 @@ const cutPathAtIntersect = (_points, boundryNode) => {
|
|||||||
return points;
|
return points;
|
||||||
};
|
};
|
||||||
|
|
||||||
//(edgePaths, e, edge, clusterDb, diagramtype, graph)
|
/**
|
||||||
|
* Calculate the deltas and angle between two points
|
||||||
|
* @param {{x: number, y:number}} point1
|
||||||
|
* @param {{x: number, y:number}} point2
|
||||||
|
* @returns {{angle: number, deltaX: number, deltaY: number}}
|
||||||
|
*/
|
||||||
|
function calculateDeltaAndAngle(point1, point2) {
|
||||||
|
const [x1, y1] = [point1.x, point1.y];
|
||||||
|
const [x2, y2] = [point2.x, point2.y];
|
||||||
|
const deltaX = x2 - x1;
|
||||||
|
const deltaY = y2 - y1;
|
||||||
|
return { angle: Math.atan(deltaY / deltaX), deltaX, deltaY };
|
||||||
|
}
|
||||||
|
|
||||||
export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph) {
|
export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph) {
|
||||||
let points = edge.points;
|
let points = edge.points;
|
||||||
let pointsHasChanged = false;
|
let pointsHasChanged = false;
|
||||||
@@ -435,22 +448,52 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
|
|||||||
const lineData = points.filter((p) => !Number.isNaN(p.y));
|
const lineData = points.filter((p) => !Number.isNaN(p.y));
|
||||||
|
|
||||||
// This is the accessor function we talked about above
|
// This is the accessor function we talked about above
|
||||||
let curve;
|
let curve = curveBasis;
|
||||||
// Currently only flowcharts get the curve from the settings, perhaps this should
|
// Currently only flowcharts get the curve from the settings, perhaps this should
|
||||||
// be expanded to a common setting? Restricting it for now in order not to cause side-effects that
|
// be expanded to a common setting? Restricting it for now in order not to cause side-effects that
|
||||||
// have not been thought through
|
// have not been thought through
|
||||||
if (diagramType === 'graph' || diagramType === 'flowchart') {
|
if (edge.curve && (diagramType === 'graph' || diagramType === 'flowchart')) {
|
||||||
curve = edge.curve || curveBasis;
|
curve = edge.curve;
|
||||||
} else {
|
|
||||||
curve = curveBasis;
|
|
||||||
}
|
}
|
||||||
// curve = curveLinear;
|
|
||||||
|
const markerOffsets = {
|
||||||
|
aggregation: 18,
|
||||||
|
extension: 18,
|
||||||
|
composition: 18,
|
||||||
|
dependency: 6,
|
||||||
|
lollipop: 13.5,
|
||||||
|
};
|
||||||
|
|
||||||
const lineFunction = line()
|
const lineFunction = line()
|
||||||
.x(function (d) {
|
.x(function (d, i, data) {
|
||||||
return d.x;
|
let offset = 0;
|
||||||
|
if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
|
||||||
|
const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]);
|
||||||
|
offset = markerOffsets[edge.arrowTypeStart] * Math.cos(angle) * (deltaX >= 0 ? 1 : -1) || 0;
|
||||||
|
} else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) {
|
||||||
|
const { angle, deltaX } = calculateDeltaAndAngle(
|
||||||
|
data[data.length - 1],
|
||||||
|
data[data.length - 2]
|
||||||
|
);
|
||||||
|
offset = markerOffsets[edge.arrowTypeEnd] * Math.cos(angle) * (deltaX >= 0 ? 1 : -1) || 0;
|
||||||
|
}
|
||||||
|
return d.x + offset;
|
||||||
})
|
})
|
||||||
.y(function (d) {
|
.y(function (d, i, data) {
|
||||||
return d.y;
|
let offset = 0;
|
||||||
|
if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
|
||||||
|
const { angle, deltaY } = calculateDeltaAndAngle(data[0], data[1]);
|
||||||
|
offset =
|
||||||
|
markerOffsets[edge.arrowTypeStart] * Math.abs(Math.sin(angle)) * (deltaY >= 0 ? 1 : -1);
|
||||||
|
} else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) {
|
||||||
|
const { angle, deltaY } = calculateDeltaAndAngle(
|
||||||
|
data[data.length - 1],
|
||||||
|
data[data.length - 2]
|
||||||
|
);
|
||||||
|
offset =
|
||||||
|
markerOffsets[edge.arrowTypeEnd] * Math.abs(Math.sin(angle)) * (deltaY >= 0 ? 1 : -1);
|
||||||
|
}
|
||||||
|
return d.y + offset;
|
||||||
})
|
})
|
||||||
.curve(curve);
|
.curve(curve);
|
||||||
|
|
||||||
@@ -489,15 +532,15 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
|
|||||||
.attr('style', edge.style);
|
.attr('style', edge.style);
|
||||||
|
|
||||||
// DEBUG code, adds a red circle at each edge coordinate
|
// DEBUG code, adds a red circle at each edge coordinate
|
||||||
// edge.points.forEach((point) => {
|
edge.points.forEach((point) => {
|
||||||
// elem
|
elem
|
||||||
// .append('circle')
|
.append('circle')
|
||||||
// .style('stroke', 'red')
|
.style('stroke', 'red')
|
||||||
// .style('fill', 'red')
|
.style('fill', 'red')
|
||||||
// .attr('r', 1)
|
.attr('r', 1)
|
||||||
// .attr('cx', point.x)
|
.attr('cx', point.x)
|
||||||
// .attr('cy', point.y);
|
.attr('cy', point.y);
|
||||||
// });
|
});
|
||||||
|
|
||||||
let url = '';
|
let url = '';
|
||||||
// // TODO: Can we load this config only from the rendered graph type?
|
// // TODO: Can we load this config only from the rendered graph type?
|
||||||
|
@@ -155,7 +155,7 @@ export const render = async (elem, graph, markers, diagramtype, id) => {
|
|||||||
clearClusters();
|
clearClusters();
|
||||||
clearGraphlib();
|
clearGraphlib();
|
||||||
|
|
||||||
log.warn('Graph at first:', graphlibJson.write(graph));
|
log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph)));
|
||||||
adjustClustersAndEdges(graph);
|
adjustClustersAndEdges(graph);
|
||||||
log.warn('Graph after:', graphlibJson.write(graph));
|
log.warn('Graph after:', graphlibJson.write(graph));
|
||||||
// log.warn('Graph ever after:', graphlibJson.write(graph.node('A').graph));
|
// log.warn('Graph ever after:', graphlibJson.write(graph.node('A').graph));
|
||||||
|
@@ -16,7 +16,7 @@ const extension = (elem, type, id) => {
|
|||||||
.append('marker')
|
.append('marker')
|
||||||
.attr('id', type + '-extensionStart')
|
.attr('id', type + '-extensionStart')
|
||||||
.attr('class', 'marker extension ' + type)
|
.attr('class', 'marker extension ' + type)
|
||||||
.attr('refX', 0)
|
.attr('refX', 18)
|
||||||
.attr('refY', 7)
|
.attr('refY', 7)
|
||||||
.attr('markerWidth', 190)
|
.attr('markerWidth', 190)
|
||||||
.attr('markerHeight', 240)
|
.attr('markerHeight', 240)
|
||||||
@@ -29,7 +29,7 @@ const extension = (elem, type, id) => {
|
|||||||
.append('marker')
|
.append('marker')
|
||||||
.attr('id', type + '-extensionEnd')
|
.attr('id', type + '-extensionEnd')
|
||||||
.attr('class', 'marker extension ' + type)
|
.attr('class', 'marker extension ' + type)
|
||||||
.attr('refX', 19)
|
.attr('refX', 1)
|
||||||
.attr('refY', 7)
|
.attr('refY', 7)
|
||||||
.attr('markerWidth', 20)
|
.attr('markerWidth', 20)
|
||||||
.attr('markerHeight', 28)
|
.attr('markerHeight', 28)
|
||||||
@@ -44,7 +44,7 @@ const composition = (elem, type) => {
|
|||||||
.append('marker')
|
.append('marker')
|
||||||
.attr('id', type + '-compositionStart')
|
.attr('id', type + '-compositionStart')
|
||||||
.attr('class', 'marker composition ' + type)
|
.attr('class', 'marker composition ' + type)
|
||||||
.attr('refX', 0)
|
.attr('refX', 18)
|
||||||
.attr('refY', 7)
|
.attr('refY', 7)
|
||||||
.attr('markerWidth', 190)
|
.attr('markerWidth', 190)
|
||||||
.attr('markerHeight', 240)
|
.attr('markerHeight', 240)
|
||||||
@@ -57,7 +57,7 @@ const composition = (elem, type) => {
|
|||||||
.append('marker')
|
.append('marker')
|
||||||
.attr('id', type + '-compositionEnd')
|
.attr('id', type + '-compositionEnd')
|
||||||
.attr('class', 'marker composition ' + type)
|
.attr('class', 'marker composition ' + type)
|
||||||
.attr('refX', 19)
|
.attr('refX', 1)
|
||||||
.attr('refY', 7)
|
.attr('refY', 7)
|
||||||
.attr('markerWidth', 20)
|
.attr('markerWidth', 20)
|
||||||
.attr('markerHeight', 28)
|
.attr('markerHeight', 28)
|
||||||
@@ -71,7 +71,7 @@ const aggregation = (elem, type) => {
|
|||||||
.append('marker')
|
.append('marker')
|
||||||
.attr('id', type + '-aggregationStart')
|
.attr('id', type + '-aggregationStart')
|
||||||
.attr('class', 'marker aggregation ' + type)
|
.attr('class', 'marker aggregation ' + type)
|
||||||
.attr('refX', 0)
|
.attr('refX', 18)
|
||||||
.attr('refY', 7)
|
.attr('refY', 7)
|
||||||
.attr('markerWidth', 190)
|
.attr('markerWidth', 190)
|
||||||
.attr('markerHeight', 240)
|
.attr('markerHeight', 240)
|
||||||
@@ -84,7 +84,7 @@ const aggregation = (elem, type) => {
|
|||||||
.append('marker')
|
.append('marker')
|
||||||
.attr('id', type + '-aggregationEnd')
|
.attr('id', type + '-aggregationEnd')
|
||||||
.attr('class', 'marker aggregation ' + type)
|
.attr('class', 'marker aggregation ' + type)
|
||||||
.attr('refX', 19)
|
.attr('refX', 1)
|
||||||
.attr('refY', 7)
|
.attr('refY', 7)
|
||||||
.attr('markerWidth', 20)
|
.attr('markerWidth', 20)
|
||||||
.attr('markerHeight', 28)
|
.attr('markerHeight', 28)
|
||||||
@@ -98,7 +98,7 @@ const dependency = (elem, type) => {
|
|||||||
.append('marker')
|
.append('marker')
|
||||||
.attr('id', type + '-dependencyStart')
|
.attr('id', type + '-dependencyStart')
|
||||||
.attr('class', 'marker dependency ' + type)
|
.attr('class', 'marker dependency ' + type)
|
||||||
.attr('refX', 0)
|
.attr('refX', 6)
|
||||||
.attr('refY', 7)
|
.attr('refY', 7)
|
||||||
.attr('markerWidth', 190)
|
.attr('markerWidth', 190)
|
||||||
.attr('markerHeight', 240)
|
.attr('markerHeight', 240)
|
||||||
@@ -111,7 +111,7 @@ const dependency = (elem, type) => {
|
|||||||
.append('marker')
|
.append('marker')
|
||||||
.attr('id', type + '-dependencyEnd')
|
.attr('id', type + '-dependencyEnd')
|
||||||
.attr('class', 'marker dependency ' + type)
|
.attr('class', 'marker dependency ' + type)
|
||||||
.attr('refX', 19)
|
.attr('refX', 13)
|
||||||
.attr('refY', 7)
|
.attr('refY', 7)
|
||||||
.attr('markerWidth', 20)
|
.attr('markerWidth', 20)
|
||||||
.attr('markerHeight', 28)
|
.attr('markerHeight', 28)
|
||||||
@@ -125,15 +125,31 @@ const lollipop = (elem, type) => {
|
|||||||
.append('marker')
|
.append('marker')
|
||||||
.attr('id', type + '-lollipopStart')
|
.attr('id', type + '-lollipopStart')
|
||||||
.attr('class', 'marker lollipop ' + type)
|
.attr('class', 'marker lollipop ' + type)
|
||||||
.attr('refX', 0)
|
.attr('refX', 13)
|
||||||
.attr('refY', 7)
|
.attr('refY', 7)
|
||||||
.attr('markerWidth', 190)
|
.attr('markerWidth', 190)
|
||||||
.attr('markerHeight', 240)
|
.attr('markerHeight', 240)
|
||||||
.attr('orient', 'auto')
|
.attr('orient', 'auto')
|
||||||
.append('circle')
|
.append('circle')
|
||||||
.attr('stroke', 'black')
|
.attr('stroke', 'black')
|
||||||
.attr('fill', 'white')
|
.attr('fill', 'transparent')
|
||||||
.attr('cx', 6)
|
.attr('cx', 7)
|
||||||
|
.attr('cy', 7)
|
||||||
|
.attr('r', 6);
|
||||||
|
elem
|
||||||
|
.append('defs')
|
||||||
|
.append('marker')
|
||||||
|
.attr('id', type + '-lollipopEnd')
|
||||||
|
.attr('class', 'marker lollipop ' + type)
|
||||||
|
.attr('refX', 1)
|
||||||
|
.attr('refY', 7)
|
||||||
|
.attr('markerWidth', 190)
|
||||||
|
.attr('markerHeight', 240)
|
||||||
|
.attr('orient', 'auto')
|
||||||
|
.append('circle')
|
||||||
|
.attr('stroke', 'black')
|
||||||
|
.attr('fill', 'transparent')
|
||||||
|
.attr('cx', 7)
|
||||||
.attr('cy', 7)
|
.attr('cy', 7)
|
||||||
.attr('r', 6);
|
.attr('r', 6);
|
||||||
};
|
};
|
||||||
|
@@ -291,8 +291,8 @@ export const adjustClustersAndEdges = (graph, depth) => {
|
|||||||
shape: 'labelRect',
|
shape: 'labelRect',
|
||||||
style: '',
|
style: '',
|
||||||
});
|
});
|
||||||
const edge1 = JSON.parse(JSON.stringify(edge));
|
const edge1 = structuredClone(edge);
|
||||||
const edge2 = JSON.parse(JSON.stringify(edge));
|
const edge2 = structuredClone(edge);
|
||||||
edge1.label = '';
|
edge1.label = '';
|
||||||
edge1.arrowTypeEnd = 'none';
|
edge1.arrowTypeEnd = 'none';
|
||||||
edge2.label = '';
|
edge2.label = '';
|
||||||
|
@@ -45,7 +45,6 @@ export const addDiagrams = () => {
|
|||||||
styles: {}, // should never be used
|
styles: {}, // should never be used
|
||||||
renderer: {}, // should never be used
|
renderer: {}, // should never be used
|
||||||
parser: {
|
parser: {
|
||||||
parser: { yy: {} },
|
|
||||||
parse: () => {
|
parse: () => {
|
||||||
throw new Error(
|
throw new Error(
|
||||||
'Diagrams beginning with --- are not valid. ' +
|
'Diagrams beginning with --- are not valid. ' +
|
||||||
|
@@ -39,7 +39,6 @@ describe('DiagramAPI', () => {
|
|||||||
parse: (_text) => {
|
parse: (_text) => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
parser: { yy: {} },
|
|
||||||
},
|
},
|
||||||
renderer: {},
|
renderer: {},
|
||||||
styles: {},
|
styles: {},
|
||||||
|
@@ -80,7 +80,7 @@ export type DrawDefinition = (
|
|||||||
|
|
||||||
export interface ParserDefinition {
|
export interface ParserDefinition {
|
||||||
parse: (text: string) => void;
|
parse: (text: string) => void;
|
||||||
parser: { yy: DiagramDB };
|
parser?: { yy: DiagramDB };
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -30,9 +30,6 @@ describe('diagram detection', () => {
|
|||||||
parse: () => {
|
parse: () => {
|
||||||
// no-op
|
// no-op
|
||||||
},
|
},
|
||||||
parser: {
|
|
||||||
yy: {},
|
|
||||||
},
|
|
||||||
},
|
},
|
||||||
renderer: {},
|
renderer: {},
|
||||||
styles: {},
|
styles: {},
|
||||||
|
@@ -91,7 +91,7 @@ g.classGroup line {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#compositionEnd, .composition {
|
#compositionEnd, .composition {
|
||||||
fill: ${options.lineColor} !important;
|
fill: transparent !important;
|
||||||
stroke: ${options.lineColor} !important;
|
stroke: ${options.lineColor} !important;
|
||||||
stroke-width: 1;
|
stroke-width: 1;
|
||||||
}
|
}
|
||||||
|
15
packages/mermaid/src/diagrams/common/populateCommonDb.ts
Normal file
15
packages/mermaid/src/diagrams/common/populateCommonDb.ts
Normal file
@@ -0,0 +1,15 @@
|
|||||||
|
import type { DiagramAST } from 'mermaid-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);
|
||||||
|
}
|
||||||
|
}
|
@@ -32,10 +32,13 @@ export const parseDirective = function (statement, context, type) {
|
|||||||
mermaidAPI.parseDirective(this, statement, context, type);
|
mermaidAPI.parseDirective(this, statement, context, type);
|
||||||
};
|
};
|
||||||
|
|
||||||
const addEntity = function (name) {
|
const addEntity = function (name, alias = undefined) {
|
||||||
if (entities[name] === undefined) {
|
if (entities[name] === undefined) {
|
||||||
entities[name] = { attributes: [] };
|
entities[name] = { attributes: [], alias: alias };
|
||||||
log.info('Added new entity :', name);
|
log.info('Added new entity :', name);
|
||||||
|
} else if (entities[name] && !entities[name].alias && alias) {
|
||||||
|
entities[name].alias = alias;
|
||||||
|
log.info(`Add alias '${alias}' to entity '${name}'`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return entities[name];
|
return entities[name];
|
||||||
|
@@ -326,7 +326,7 @@ const drawEntities = function (svgNode, entities, graph) {
|
|||||||
.style('text-anchor', 'middle')
|
.style('text-anchor', 'middle')
|
||||||
.style('font-family', getConfig().fontFamily)
|
.style('font-family', getConfig().fontFamily)
|
||||||
.style('font-size', conf.fontSize + 'px')
|
.style('font-size', conf.fontSize + 'px')
|
||||||
.text(entityName);
|
.text(entities[entityName].alias ?? entityName);
|
||||||
|
|
||||||
const { width: entityWidth, height: entityHeight } = drawAttributes(
|
const { width: entityWidth, height: entityHeight } = drawAttributes(
|
||||||
groupNode,
|
groupNode,
|
||||||
|
@@ -35,6 +35,8 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
|
|||||||
<block>[\n]+ /* nothing */
|
<block>[\n]+ /* nothing */
|
||||||
<block>"}" { this.popState(); return 'BLOCK_STOP'; }
|
<block>"}" { this.popState(); return 'BLOCK_STOP'; }
|
||||||
<block>. return yytext[0];
|
<block>. return yytext[0];
|
||||||
|
"[" return 'SQS';
|
||||||
|
"]" return 'SQE';
|
||||||
|
|
||||||
"one or zero" return 'ZERO_OR_ONE';
|
"one or zero" return 'ZERO_OR_ONE';
|
||||||
"one or more" return 'ONE_OR_MORE';
|
"one or more" return 'ONE_OR_MORE';
|
||||||
@@ -102,17 +104,21 @@ statement
|
|||||||
yy.addEntity($1);
|
yy.addEntity($1);
|
||||||
yy.addEntity($3);
|
yy.addEntity($3);
|
||||||
yy.addRelationship($1, $5, $3, $2);
|
yy.addRelationship($1, $5, $3, $2);
|
||||||
/*console.log($1 + $2 + $3 + ':' + $5);*/
|
|
||||||
}
|
}
|
||||||
| entityName BLOCK_START attributes BLOCK_STOP
|
| entityName BLOCK_START attributes BLOCK_STOP
|
||||||
{
|
{
|
||||||
/* console.log('detected block'); */
|
|
||||||
yy.addEntity($1);
|
yy.addEntity($1);
|
||||||
yy.addAttributes($1, $3);
|
yy.addAttributes($1, $3);
|
||||||
/* console.log('handled block'); */
|
|
||||||
}
|
}
|
||||||
| entityName BLOCK_START BLOCK_STOP { yy.addEntity($1); }
|
| entityName BLOCK_START BLOCK_STOP { yy.addEntity($1); }
|
||||||
| entityName { yy.addEntity($1); }
|
| entityName { yy.addEntity($1); }
|
||||||
|
| entityName SQS entityName SQE BLOCK_START attributes BLOCK_STOP
|
||||||
|
{
|
||||||
|
yy.addEntity($1, $3);
|
||||||
|
yy.addAttributes($1, $6);
|
||||||
|
}
|
||||||
|
| entityName SQS entityName SQE BLOCK_START BLOCK_STOP { yy.addEntity($1, $3); }
|
||||||
|
| entityName SQS entityName SQE { yy.addEntity($1, $3); }
|
||||||
| title title_value { $$=$2.trim();yy.setAccTitle($$); }
|
| title title_value { $$=$2.trim();yy.setAccTitle($$); }
|
||||||
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
|
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
|
||||||
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
||||||
|
@@ -133,6 +133,43 @@ describe('when parsing ER diagram it...', function () {
|
|||||||
const entities = erDb.getEntities();
|
const entities = erDb.getEntities();
|
||||||
expect(entities.hasOwnProperty(hyphensUnderscore)).toBe(true);
|
expect(entities.hasOwnProperty(hyphensUnderscore)).toBe(true);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('can have an alias', function () {
|
||||||
|
const entity = 'foo';
|
||||||
|
const alias = 'bar';
|
||||||
|
erDiagram.parser.parse(`erDiagram\n${entity}["${alias}"]\n`);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(entity)).toBe(true);
|
||||||
|
expect(entities[entity].alias).toBe(alias);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can have an alias even if the relationship is defined before class', function () {
|
||||||
|
const firstEntity = 'foo';
|
||||||
|
const secondEntity = 'bar';
|
||||||
|
const alias = 'batman';
|
||||||
|
erDiagram.parser.parse(
|
||||||
|
`erDiagram\n${firstEntity} ||--o| ${secondEntity} : rel\nclass ${firstEntity}["${alias}"]\n`
|
||||||
|
);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(firstEntity)).toBe(true);
|
||||||
|
expect(entities.hasOwnProperty(secondEntity)).toBe(true);
|
||||||
|
expect(entities[firstEntity].alias).toBe(alias);
|
||||||
|
expect(entities[secondEntity].alias).toBeUndefined();
|
||||||
|
});
|
||||||
|
|
||||||
|
it('can have an alias even if the relationship is defined after class', function () {
|
||||||
|
const firstEntity = 'foo';
|
||||||
|
const secondEntity = 'bar';
|
||||||
|
const alias = 'batman';
|
||||||
|
erDiagram.parser.parse(
|
||||||
|
`erDiagram\nclass ${firstEntity}["${alias}"]\n${firstEntity} ||--o| ${secondEntity} : rel\n`
|
||||||
|
);
|
||||||
|
const entities = erDb.getEntities();
|
||||||
|
expect(entities.hasOwnProperty(firstEntity)).toBe(true);
|
||||||
|
expect(entities.hasOwnProperty(secondEntity)).toBe(true);
|
||||||
|
expect(entities[firstEntity].alias).toBe(alias);
|
||||||
|
expect(entities[secondEntity].alias).toBeUndefined();
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe('attribute name', () => {
|
describe('attribute name', () => {
|
||||||
|
@@ -5,7 +5,6 @@ const diagram: DiagramDefinition = {
|
|||||||
db: {},
|
db: {},
|
||||||
renderer,
|
renderer,
|
||||||
parser: {
|
parser: {
|
||||||
parser: { yy: {} },
|
|
||||||
parse: (): void => {
|
parse: (): void => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
|
@@ -1,24 +1,31 @@
|
|||||||
// @ts-ignore - jison doesn't export types
|
import { parser } from './infoParser.js';
|
||||||
import { parser } from './parser/info.jison';
|
|
||||||
import { db } from './infoDb.js';
|
|
||||||
|
|
||||||
describe('info diagram', () => {
|
|
||||||
beforeEach(() => {
|
|
||||||
parser.yy = db;
|
|
||||||
parser.yy.clear();
|
|
||||||
});
|
|
||||||
|
|
||||||
|
describe('info', () => {
|
||||||
it('should handle an info definition', () => {
|
it('should handle an info definition', () => {
|
||||||
const str = `info`;
|
const str = `info`;
|
||||||
parser.parse(str);
|
expect(() => {
|
||||||
|
parser.parse(str);
|
||||||
expect(db.getInfo()).toBeFalsy();
|
}).not.toThrow();
|
||||||
});
|
});
|
||||||
|
|
||||||
it('should handle an info definition with showInfo', () => {
|
it('should handle an info definition with showInfo', () => {
|
||||||
const str = `info showInfo`;
|
const str = `info showInfo`;
|
||||||
parser.parse(str);
|
expect(() => {
|
||||||
|
parser.parse(str);
|
||||||
|
}).not.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
expect(db.getInfo()).toBeTruthy();
|
it('should throw because of unsupported info grammar', () => {
|
||||||
|
const str = `info unsupported`;
|
||||||
|
expect(() => {
|
||||||
|
parser.parse(str);
|
||||||
|
}).toThrow('Parsing failed: unexpected character: ->u<- at offset: 5, skipped 11 characters.');
|
||||||
|
});
|
||||||
|
|
||||||
|
it('should throw because of unsupported info grammar', () => {
|
||||||
|
const str = `info unsupported`;
|
||||||
|
expect(() => {
|
||||||
|
parser.parse(str);
|
||||||
|
}).toThrow('Parsing failed: unexpected character: ->u<- at offset: 5, skipped 11 characters.');
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
@@ -1,23 +1,10 @@
|
|||||||
import type { InfoFields, InfoDB } from './infoTypes.js';
|
import type { InfoFields, InfoDB } from './infoTypes.js';
|
||||||
|
import { version } from '../../../package.json';
|
||||||
|
|
||||||
export const DEFAULT_INFO_DB: InfoFields = {
|
export const DEFAULT_INFO_DB: InfoFields = { version } as const;
|
||||||
info: false,
|
|
||||||
} as const;
|
|
||||||
|
|
||||||
let info: boolean = DEFAULT_INFO_DB.info;
|
export const getVersion = (): string => DEFAULT_INFO_DB.version;
|
||||||
|
|
||||||
export const setInfo = (toggle: boolean): void => {
|
|
||||||
info = toggle;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const getInfo = (): boolean => info;
|
|
||||||
|
|
||||||
const clear = (): void => {
|
|
||||||
info = DEFAULT_INFO_DB.info;
|
|
||||||
};
|
|
||||||
|
|
||||||
export const db: InfoDB = {
|
export const db: InfoDB = {
|
||||||
clear,
|
getVersion,
|
||||||
setInfo,
|
|
||||||
getInfo,
|
|
||||||
};
|
};
|
||||||
|
@@ -1,6 +1,5 @@
|
|||||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||||
// @ts-ignore - jison doesn't export types
|
import { parser } from './infoParser.js';
|
||||||
import parser from './parser/info.jison';
|
|
||||||
import { db } from './infoDb.js';
|
import { db } from './infoDb.js';
|
||||||
import { renderer } from './infoRenderer.js';
|
import { renderer } from './infoRenderer.js';
|
||||||
|
|
||||||
|
12
packages/mermaid/src/diagrams/info/infoParser.ts
Normal file
12
packages/mermaid/src/diagrams/info/infoParser.ts
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
import type { Info } from 'mermaid-parser';
|
||||||
|
import { parse } from 'mermaid-parser';
|
||||||
|
|
||||||
|
import { log } from '../../logger.js';
|
||||||
|
import type { ParserDefinition } from '../../diagram-api/types.js';
|
||||||
|
|
||||||
|
export const parser: ParserDefinition = {
|
||||||
|
parse: (input: string): void => {
|
||||||
|
const ast: Info = parse('info', input);
|
||||||
|
log.debug(ast);
|
||||||
|
},
|
||||||
|
};
|
@@ -1,11 +1,9 @@
|
|||||||
import type { DiagramDB } from '../../diagram-api/types.js';
|
import type { DiagramDB } from '../../diagram-api/types.js';
|
||||||
|
|
||||||
export interface InfoFields {
|
export interface InfoFields {
|
||||||
info: boolean;
|
version: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface InfoDB extends DiagramDB {
|
export interface InfoDB extends DiagramDB {
|
||||||
clear: () => void;
|
getVersion: () => string;
|
||||||
setInfo: (info: boolean) => void;
|
|
||||||
getInfo: () => boolean;
|
|
||||||
}
|
}
|
||||||
|
@@ -1,48 +0,0 @@
|
|||||||
/** mermaid
|
|
||||||
* https://knsv.github.io/mermaid
|
|
||||||
* (c) 2015 Knut Sveidqvist
|
|
||||||
* MIT license.
|
|
||||||
*/
|
|
||||||
%lex
|
|
||||||
|
|
||||||
%options case-insensitive
|
|
||||||
|
|
||||||
%{
|
|
||||||
// Pre-lexer code can go here
|
|
||||||
%}
|
|
||||||
|
|
||||||
%%
|
|
||||||
|
|
||||||
"info" return 'info' ;
|
|
||||||
[\s\n\r]+ return 'NL' ;
|
|
||||||
[\s]+ return 'space';
|
|
||||||
"showInfo" return 'showInfo';
|
|
||||||
<<EOF>> return 'EOF' ;
|
|
||||||
. return 'TXT' ;
|
|
||||||
|
|
||||||
/lex
|
|
||||||
|
|
||||||
%start start
|
|
||||||
|
|
||||||
%% /* language grammar */
|
|
||||||
|
|
||||||
start
|
|
||||||
// %{ : info document 'EOF' { return yy; } }
|
|
||||||
: info document 'EOF' { return yy; }
|
|
||||||
;
|
|
||||||
|
|
||||||
document
|
|
||||||
: /* empty */
|
|
||||||
| document line
|
|
||||||
;
|
|
||||||
|
|
||||||
line
|
|
||||||
: statement { }
|
|
||||||
| 'NL'
|
|
||||||
;
|
|
||||||
|
|
||||||
statement
|
|
||||||
: showInfo { yy.setInfo(true); }
|
|
||||||
;
|
|
||||||
|
|
||||||
%%
|
|
@@ -12,14 +12,16 @@ import { selectSvgElement } from '../../rendering-util/selectSvgElement.js';
|
|||||||
|
|
||||||
const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Sections>[] => {
|
const createPieArcs = (sections: Sections): d3.PieArcDatum<D3Sections>[] => {
|
||||||
// Compute the position of each group on the pie:
|
// Compute the position of each group on the pie:
|
||||||
const pieData: D3Sections[] = Object.entries(sections).map(
|
const pieData: D3Sections[] = Object.entries(sections)
|
||||||
(element: [string, number]): D3Sections => {
|
.map((element: [string, number]): D3Sections => {
|
||||||
return {
|
return {
|
||||||
label: element[0],
|
label: element[0],
|
||||||
value: element[1],
|
value: element[1],
|
||||||
};
|
};
|
||||||
}
|
})
|
||||||
);
|
.sort((a: D3Sections, b: D3Sections): number => {
|
||||||
|
return b.value - a.value;
|
||||||
|
});
|
||||||
const pie: d3.Pie<unknown, D3Sections> = d3pie<D3Sections>().value(
|
const pie: d3.Pie<unknown, D3Sections> = d3pie<D3Sections>().value(
|
||||||
(d3Section: D3Sections): number => d3Section.value
|
(d3Section: D3Sections): number => d3Section.value
|
||||||
);
|
);
|
||||||
|
@@ -21,6 +21,15 @@ test.each([
|
|||||||
],
|
],
|
||||||
['https://mermaid-js.github.io/mermaid/#/n00b-advanced', 'config/advanced.html'], // without .md
|
['https://mermaid-js.github.io/mermaid/#/n00b-advanced', 'config/advanced.html'], // without .md
|
||||||
['https://mermaid-js.github.io/mermaid/#/n00b-advanced.md', 'config/advanced.html'], // with .md
|
['https://mermaid-js.github.io/mermaid/#/n00b-advanced.md', 'config/advanced.html'], // with .md
|
||||||
|
|
||||||
|
['https://mermaid-js.github.io/mermaid/#/n00b-gettingstarted', 'intro/getting-started.html'],
|
||||||
|
['https://mermaid-js.github.io/mermaid/#/n00b-gettingstarted.md', 'intro/getting-started.html'],
|
||||||
|
['https://mermaid-js.github.io/mermaid/#/n00b-overview', 'intro/getting-started.html'],
|
||||||
|
['https://mermaid-js.github.io/mermaid/#/n00b-overview.md', 'intro/getting-started.html'],
|
||||||
|
['https://mermaid-js.github.io/mermaid/#/n00b-syntaxreference', 'intro/syntax-reference.html'],
|
||||||
|
['https://mermaid-js.github.io/mermaid/#/n00b-syntaxreference.md', 'intro/syntax-reference.html'],
|
||||||
|
['https://mermaid-js.github.io/mermaid/#/quickstart', 'intro/getting-started.html'],
|
||||||
|
['https://mermaid-js.github.io/mermaid/#/quickstart.md', 'intro/getting-started.html'],
|
||||||
[
|
[
|
||||||
'https://mermaid-js.github.io/mermaid/#/flowchart?id=a-node-in-the-form-of-a-circle', // with id, without .md
|
'https://mermaid-js.github.io/mermaid/#/flowchart?id=a-node-in-the-form-of-a-circle', // with id, without .md
|
||||||
'syntax/flowchart.html#a-node-in-the-form-of-a-circle',
|
'syntax/flowchart.html#a-node-in-the-form-of-a-circle',
|
||||||
|
@@ -25,9 +25,13 @@ const getBaseFile = (url: URL): Redirect => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to redirect old documentation pages to corresponding new pages.
|
* Used to redirect old (pre-vitepress) documentation pages to corresponding new pages.
|
||||||
|
* The key is the old documentation ID, and the value is the new documentation path.
|
||||||
|
* No key should be added here as it already has all the old documentation IDs.
|
||||||
|
* If you are changing a documentation page, you should update the corresponding value here, and add an entry in the urlRedirectMap below.
|
||||||
*/
|
*/
|
||||||
const idRedirectMap: Record<string, string> = {
|
const idRedirectMap: Record<string, string> = {
|
||||||
|
// ID of the old documentation page: Path of the new documentation page
|
||||||
'8.6.0_docs': '',
|
'8.6.0_docs': '',
|
||||||
accessibility: 'config/theming',
|
accessibility: 'config/theming',
|
||||||
breakingchanges: '',
|
breakingchanges: '',
|
||||||
@@ -51,13 +55,9 @@ const idRedirectMap: Record<string, string> = {
|
|||||||
mindmap: 'syntax/mindmap',
|
mindmap: 'syntax/mindmap',
|
||||||
'more-pages': '',
|
'more-pages': '',
|
||||||
'n00b-advanced': 'config/advanced',
|
'n00b-advanced': 'config/advanced',
|
||||||
'config/n00b-advanced': 'config/advanced',
|
|
||||||
'n00b-gettingstarted': 'intro/getting-started',
|
'n00b-gettingstarted': 'intro/getting-started',
|
||||||
'intro/n00b-gettingStarted': 'intro/getting-started',
|
|
||||||
'n00b-overview': 'intro/getting-started',
|
'n00b-overview': 'intro/getting-started',
|
||||||
'n00b-syntaxreference': 'intro/syntax-reference',
|
'n00b-syntaxreference': 'intro/syntax-reference',
|
||||||
'intro/n00b-syntaxReference': 'intro/syntax-reference',
|
|
||||||
'community/n00b-overview': 'intro/getting-started',
|
|
||||||
newdiagram: 'community/newDiagram',
|
newdiagram: 'community/newDiagram',
|
||||||
pie: 'syntax/pie',
|
pie: 'syntax/pie',
|
||||||
plugins: '',
|
plugins: '',
|
||||||
@@ -77,10 +77,19 @@ const idRedirectMap: Record<string, string> = {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Used to redirect pages that have been moved in the vitepress site.
|
* Used to redirect pages that have been moved in the vitepress site.
|
||||||
|
* No keys should be deleted from here.
|
||||||
|
* If you are changing a documentation page, you should update the corresponding value here,
|
||||||
|
* and update the entry in the idRedirectMap above if it was present
|
||||||
|
* (No need to add new keys in idRedirectMap).
|
||||||
*/
|
*/
|
||||||
const urlRedirectMap: Record<string, string> = {
|
const urlRedirectMap: Record<string, string> = {
|
||||||
|
// Old URL: New URL
|
||||||
'/misc/faq.html': 'configure/faq.html',
|
'/misc/faq.html': 'configure/faq.html',
|
||||||
'/syntax/c4c.html': 'syntax/c4.html',
|
'/syntax/c4c.html': 'syntax/c4.html',
|
||||||
|
'/config/n00b-advanced.html': 'config/advanced',
|
||||||
|
'/intro/n00b-gettingStarted.html': 'intro/getting-started',
|
||||||
|
'/intro/n00b-syntaxReference.html': 'intro/syntax-reference',
|
||||||
|
'/community/n00b-overview.html': 'intro/getting-started',
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@@ -179,7 +179,7 @@ Update version number in `package.json`.
|
|||||||
npm publish
|
npm publish
|
||||||
```
|
```
|
||||||
|
|
||||||
The above command generates files into the `dist` folder and publishes them to [npmjs.org](npmjs.org).
|
The above command generates files into the `dist` folder and publishes them to [npmjs.com](https://www.npmjs.com/).
|
||||||
|
|
||||||
## Security and safe diagrams
|
## Security and safe diagrams
|
||||||
|
|
||||||
|
@@ -54,7 +54,7 @@ The following are the most commonly used methods, and they are all tied to Merma
|
|||||||
|
|
||||||
Here you can edit certain values to change the behavior and appearance of the diagram.
|
Here you can edit certain values to change the behavior and appearance of the diagram.
|
||||||
|
|
||||||
### [The initialize() call](./getting-started#_3-calling-the-javascript-api)
|
### [The initialize() call](./getting-started.md#_3-calling-the-javascript-api)
|
||||||
|
|
||||||
Used when Mermaid is called via an API, or through a `<script>` tag.
|
Used when Mermaid is called via an API, or through a `<script>` tag.
|
||||||
|
|
||||||
|
@@ -1,7 +1,7 @@
|
|||||||
# Announcements
|
# Announcements
|
||||||
|
|
||||||
## [From Chaos to Clarity: Exploring Mind Maps with MermaidJS](https://www.mermaidchart.com/blog/posts/from-chaos-to-clarity-exploring-mind-maps-with-mermaidjs)
|
## [Special cases broke Microsoft Zune and can ruin your code base too](https://www.mermaidchart.com/blog/posts/special-cases-broke-microsoft-zune-and-can-ruin-your-code-base-too/)
|
||||||
|
|
||||||
24 July 2023 · 4 mins
|
23 August 2023 · 15 mins
|
||||||
|
|
||||||
Introducing the concept of mind mapping as a tool for organizing complex information, and highlights Mermaid as a user-friendly software that simplifies the creation and editing of mind maps for applications in IT solution design, business decision-making, and knowledge organization.
|
Read about the pitfalls of special cases in programming, illustrating how they can lead to complexity, diminish readability, and create maintenance challenges.
|
||||||
|
@@ -1,5 +1,23 @@
|
|||||||
# Blog
|
# Blog
|
||||||
|
|
||||||
|
## [Special cases broke Microsoft Zune and can ruin your code base too](https://www.mermaidchart.com/blog/posts/special-cases-broke-microsoft-zune-and-can-ruin-your-code-base-too/)
|
||||||
|
|
||||||
|
23 August 2023 · 15 mins
|
||||||
|
|
||||||
|
Read about the pitfalls of special cases in programming, illustrating how they can lead to complexity, diminish readability, and create maintenance challenges.
|
||||||
|
|
||||||
|
## [New AI chatbot now available on Mermaid Chart to simplify text-based diagram creation](https://www.mermaidchart.com/blog/posts/ai-chatbot-now-available-on-mermaid-chart-to-simplify-text-based-diagram-creation/)
|
||||||
|
|
||||||
|
14 August 2023 · 4 mins
|
||||||
|
|
||||||
|
Introducing Mermaid Chart’s new AI chatbot, a diagramming assistant that simplifies text-based diagram creation for everyone, from developers to educators, offering features to start, edit, and fix diagrams, and embodying our vision to make diagramming accessible, user-friendly, and fun.
|
||||||
|
|
||||||
|
## [Believe It or Not, You Still Need an Online UML Diagram Tool](https://www.mermaidchart.com/blog/posts/uml-diagram-tool/)
|
||||||
|
|
||||||
|
14 August 2023 · 8 mins
|
||||||
|
|
||||||
|
A UML diagram tool helps developers and other professionals quickly create and share UML diagrams that communicate information about complex software systems.
|
||||||
|
|
||||||
## [From Chaos to Clarity: Exploring Mind Maps with MermaidJS](https://www.mermaidchart.com/blog/posts/from-chaos-to-clarity-exploring-mind-maps-with-mermaidjs)
|
## [From Chaos to Clarity: Exploring Mind Maps with MermaidJS](https://www.mermaidchart.com/blog/posts/from-chaos-to-clarity-exploring-mind-maps-with-mermaidjs)
|
||||||
|
|
||||||
24 July 2023 · 4 mins
|
24 July 2023 · 4 mins
|
||||||
|
@@ -144,6 +144,22 @@ erDiagram
|
|||||||
|
|
||||||
The `type` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The `name` values follow a similar format to `type`, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
|
The `type` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. The `name` values follow a similar format to `type`, but may start with an asterisk as another option to indicate an attribute is a primary key. Other than that, there are no restrictions, and there is no implicit set of valid data types.
|
||||||
|
|
||||||
|
### Entity Name Aliases (v<MERMAID_RELEASE_VERSION>+)
|
||||||
|
|
||||||
|
An alias can be added to an entity using square brackets. If provided, the alias will be showed in the diagram instead of the entity name.
|
||||||
|
|
||||||
|
```mermaid-example
|
||||||
|
erDiagram
|
||||||
|
p[Person] {
|
||||||
|
string firstName
|
||||||
|
string lastName
|
||||||
|
}
|
||||||
|
a["Customer Account"] {
|
||||||
|
string email
|
||||||
|
}
|
||||||
|
p ||--o| a : has
|
||||||
|
```
|
||||||
|
|
||||||
#### Attribute Keys and Comments
|
#### Attribute Keys and Comments
|
||||||
|
|
||||||
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
|
Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
|
||||||
|
@@ -751,7 +751,7 @@ The icons are accessed via the syntax fa:#icon class name#.
|
|||||||
|
|
||||||
```mermaid-example
|
```mermaid-example
|
||||||
flowchart TD
|
flowchart TD
|
||||||
B["fab:fa-twitter for peace"]
|
B["fa:fa-twitter for peace"]
|
||||||
B-->C[fa:fa-ban forbidden]
|
B-->C[fa:fa-ban forbidden]
|
||||||
B-->D(fa:fa-spinner)
|
B-->D(fa:fa-spinner)
|
||||||
B-->E(A fa:fa-camera-retro perhaps?)
|
B-->E(A fa:fa-camera-retro perhaps?)
|
||||||
|
@@ -102,7 +102,6 @@ describe('when using mermaid and ', () => {
|
|||||||
parse: (_text) => {
|
parse: (_text) => {
|
||||||
return;
|
return;
|
||||||
},
|
},
|
||||||
parser: { yy: {} },
|
|
||||||
},
|
},
|
||||||
styles: () => {
|
styles: () => {
|
||||||
// do nothing
|
// do nothing
|
||||||
|
@@ -7,7 +7,7 @@
|
|||||||
# - `scripts/create-types-from-json-schema.mjs`
|
# - `scripts/create-types-from-json-schema.mjs`
|
||||||
# Used to generate the `src/config.type.ts` TypeScript types for MermaidConfig
|
# Used to generate the `src/config.type.ts` TypeScript types for MermaidConfig
|
||||||
# with the `json-schema-to-typescript` NPM package.
|
# with the `json-schema-to-typescript` NPM package.
|
||||||
# - `.vite/jsonSchemaPlugin.ts`
|
# - `.build/jsonSchema.ts`
|
||||||
# Used to generate the default values from the `default` keys in this
|
# Used to generate the default values from the `default` keys in this
|
||||||
# JSON Schema using the `ajv` NPM package.
|
# JSON Schema using the `ajv` NPM package.
|
||||||
# Non-JSON values, like functions or `undefined`, still need to be manually
|
# Non-JSON values, like functions or `undefined`, still need to be manually
|
||||||
@@ -21,7 +21,7 @@
|
|||||||
# - Use `meta:enum` to document enum values (from jsonschema2md)
|
# - Use `meta:enum` to document enum values (from jsonschema2md)
|
||||||
# - Use `tsType` to override the TypeScript type (from json-schema-to-typescript)
|
# - Use `tsType` to override the TypeScript type (from json-schema-to-typescript)
|
||||||
# - If adding a new object to `MermaidConfig` (e.g. a new diagram type),
|
# - If adding a new object to `MermaidConfig` (e.g. a new diagram type),
|
||||||
# you may need to add it to `.vite/jsonSchemaPlugin.ts` and `src/docs.mts`
|
# you may need to add it to `.build/jsonSchema.ts` and `src/docs.mts`
|
||||||
# to get the docs/default values to generate properly.
|
# to get the docs/default values to generate properly.
|
||||||
$id: https://mermaid-js.github.io/schemas/config.schema.json
|
$id: https://mermaid-js.github.io/schemas/config.schema.json
|
||||||
$schema: https://json-schema.org/draft/2019-09/schema
|
$schema: https://json-schema.org/draft/2019-09/schema
|
||||||
|
21
packages/parser/LICENSE
Normal file
21
packages/parser/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
|||||||
|
The MIT License (MIT)
|
||||||
|
|
||||||
|
Copyright (c) 2023 Yokozuna59
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
63
packages/parser/README.md
Normal file
63
packages/parser/README.md
Normal file
@@ -0,0 +1,63 @@
|
|||||||
|
<p align="center">
|
||||||
|
<img src="https://raw.githubusercontent.com/mermaid-js/mermaid/develop/docs/public/favicon.svg" height="150">
|
||||||
|
|
||||||
|
</p>
|
||||||
|
<h1 align="center">
|
||||||
|
Mermaid Parser
|
||||||
|
</h1>
|
||||||
|
|
||||||
|
<p align="center">
|
||||||
|
Mermaid parser package
|
||||||
|
<p>
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/mermaid-parser)
|
||||||
|
|
||||||
|
## How the package works
|
||||||
|
|
||||||
|
The package exports a `parse` function that has two parameters:
|
||||||
|
|
||||||
|
```ts
|
||||||
|
declare function parse<T extends DiagramAST>(
|
||||||
|
diagramType: keyof typeof initializers,
|
||||||
|
text: string
|
||||||
|
): T;
|
||||||
|
```
|
||||||
|
|
||||||
|
## How does a Langium-based parser work?
|
||||||
|
|
||||||
|
```mermaid
|
||||||
|
sequenceDiagram
|
||||||
|
actor Package
|
||||||
|
participant Module
|
||||||
|
participant TokenBuilder
|
||||||
|
participant Lexer
|
||||||
|
participant Parser
|
||||||
|
participant ValueConverter
|
||||||
|
|
||||||
|
|
||||||
|
Package ->> Module: Create services
|
||||||
|
Module ->> TokenBuilder: Override or/and<br>reorder rules
|
||||||
|
TokenBuilder ->> Lexer: Read the string and transform<br>it into a token stream
|
||||||
|
Lexer ->> Parser: Parse token<br>stream into AST
|
||||||
|
Parser ->> ValueConverter: Clean/modify tokenized<br>rules returned value
|
||||||
|
ValueConverter -->> Package: Return AST
|
||||||
|
```
|
||||||
|
|
||||||
|
- When to override `TokenBuilder`?
|
||||||
|
|
||||||
|
- To override keyword rules.
|
||||||
|
- To override terminal rules that need a custom function.
|
||||||
|
- To manually reorder the list of rules.
|
||||||
|
|
||||||
|
- When to override `Lexer`?
|
||||||
|
|
||||||
|
- To modify input before tokenizing.
|
||||||
|
- To insert/modify tokens that cannot or have not been parsed.
|
||||||
|
|
||||||
|
- When to override `LangiumParser`?
|
||||||
|
|
||||||
|
- To insert or modify attributes that can't be parsed.
|
||||||
|
|
||||||
|
- When to override `ValueConverter`?
|
||||||
|
|
||||||
|
- To modify the returned value from the parser.
|
13
packages/parser/langium-config.json
Normal file
13
packages/parser/langium-config.json
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
{
|
||||||
|
"projectName": "Mermaid",
|
||||||
|
"languages": [
|
||||||
|
{
|
||||||
|
"id": "info",
|
||||||
|
"grammar": "src/language/info/info.langium",
|
||||||
|
"fileExtensions": [".mmd", ".mermaid"]
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"mode": "production",
|
||||||
|
"importExtension": ".js",
|
||||||
|
"out": "src/language/generated"
|
||||||
|
}
|
48
packages/parser/package.json
Normal file
48
packages/parser/package.json
Normal file
@@ -0,0 +1,48 @@
|
|||||||
|
{
|
||||||
|
"name": "mermaid-parser",
|
||||||
|
"version": "0.2.0",
|
||||||
|
"description": "MermaidJS parser",
|
||||||
|
"author": "Yokozuna59",
|
||||||
|
"contributors": [
|
||||||
|
"Yokozuna59",
|
||||||
|
"Sidharth Vinod (https://sidharth.dev)"
|
||||||
|
],
|
||||||
|
"homepage": "https://github.com/mermaid-js/mermaid/tree/develop/packages/mermaid/parser/#readme",
|
||||||
|
"types": "dist/src/index.d.ts",
|
||||||
|
"type": "module",
|
||||||
|
"exports": {
|
||||||
|
".": {
|
||||||
|
"import": "./dist/mermaid-parser.esm.mjs",
|
||||||
|
"types": "./dist/src/index.d.ts"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"clean": "rimraf dist src/language/generated",
|
||||||
|
"langium:generate": "langium generate",
|
||||||
|
"langium:watch": "langium generate --watch",
|
||||||
|
"prepublishOnly": "pnpm -w run build"
|
||||||
|
},
|
||||||
|
"repository": {
|
||||||
|
"type": "git",
|
||||||
|
"url": "https://github.com/mermaid-js/mermaid.git",
|
||||||
|
"directory": "packages/parser"
|
||||||
|
},
|
||||||
|
"license": "MIT",
|
||||||
|
"keywords": [
|
||||||
|
"mermaid",
|
||||||
|
"parser",
|
||||||
|
"ast"
|
||||||
|
],
|
||||||
|
"dependencies": {
|
||||||
|
"langium": "2.0.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"langium-cli": "2.0.1"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"dist/"
|
||||||
|
],
|
||||||
|
"publishConfig": {
|
||||||
|
"access": "public"
|
||||||
|
}
|
||||||
|
}
|
3
packages/parser/src/index.ts
Normal file
3
packages/parser/src/index.ts
Normal file
@@ -0,0 +1,3 @@
|
|||||||
|
export type { Info } from './language/index.js';
|
||||||
|
export type { DiagramAST } from './parse.js';
|
||||||
|
export { parse, MermaidParseError } from './parse.js';
|
51
packages/parser/src/language/chevrotainWrapper.ts
Normal file
51
packages/parser/src/language/chevrotainWrapper.ts
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
|
|
||||||
|
type CustomPatternMatcherReturn = [string] & { payload?: any };
|
||||||
|
|
||||||
|
export type CustomPatternMatcherFunc = (
|
||||||
|
text: string,
|
||||||
|
offset: number,
|
||||||
|
tokens: IToken[],
|
||||||
|
groups: {
|
||||||
|
[groupName: string]: IToken[];
|
||||||
|
}
|
||||||
|
) => CustomPatternMatcherReturn | RegExpExecArray | null;
|
||||||
|
|
||||||
|
interface ICustomPattern {
|
||||||
|
exec: CustomPatternMatcherFunc;
|
||||||
|
}
|
||||||
|
|
||||||
|
type TokenPattern = RegExp | string | CustomPatternMatcherFunc | ICustomPattern;
|
||||||
|
|
||||||
|
export interface IToken {
|
||||||
|
image: string;
|
||||||
|
startOffset: number;
|
||||||
|
startLine?: number;
|
||||||
|
startColumn?: number;
|
||||||
|
endOffset?: number;
|
||||||
|
endLine?: number;
|
||||||
|
endColumn?: number;
|
||||||
|
isInsertedInRecovery?: boolean;
|
||||||
|
tokenTypeIdx: number;
|
||||||
|
tokenType: TokenType;
|
||||||
|
payload?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface TokenType {
|
||||||
|
name: string;
|
||||||
|
GROUP?: string;
|
||||||
|
PATTERN?: TokenPattern;
|
||||||
|
LABEL?: string;
|
||||||
|
LONGER_ALT?: TokenType | TokenType[];
|
||||||
|
POP_MODE?: boolean;
|
||||||
|
PUSH_MODE?: string;
|
||||||
|
LINE_BREAKS?: boolean;
|
||||||
|
CATEGORIES?: TokenType[];
|
||||||
|
tokenTypeIdx?: number;
|
||||||
|
categoryMatches?: number[];
|
||||||
|
categoryMatchesMap?: {
|
||||||
|
[tokType: number]: boolean;
|
||||||
|
};
|
||||||
|
isParent?: boolean;
|
||||||
|
START_CHARS_HINT?: (string | number)[];
|
||||||
|
}
|
14
packages/parser/src/language/common/common.langium
Normal file
14
packages/parser/src/language/common/common.langium
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
fragment TitleAndAccessibilities:
|
||||||
|
((accDescr=ACC_DESCR | accTitle=ACC_TITLE | title=TITLE) NEWLINE+)+
|
||||||
|
;
|
||||||
|
|
||||||
|
terminal NEWLINE: /\r?\n/;
|
||||||
|
terminal ACC_DESCR: /accDescr(?:[\t ]*:[\t ]*[^\n\r]*?(?=%%)|\s*{[^}]*})|accDescr(?:[\t ]*:[\t ]*[^\n\r]*|\s*{[^}]*})/;
|
||||||
|
terminal ACC_TITLE: /accTitle[\t ]*:[\t ]*[^\n\r]*?(?=%%)|accTitle[\t ]*:[\t ]*[^\n\r]*/;
|
||||||
|
terminal TITLE: /title(?:[\t ]+[^\n\r]*?|)(?=%%)|title(?:[\t ]+[^\n\r]*|)/;
|
||||||
|
|
||||||
|
hidden terminal WHITESPACE: /[\t ]+/;
|
||||||
|
// TODO: add YAML_COMMENT hidden rule without interfere actual grammar
|
||||||
|
hidden terminal YAML: /---[\t ]*\r?\n[\S\s]*?---[\t ]*(?!.)/;
|
||||||
|
hidden terminal DIRECTIVE: /[\t ]*%%{[\S\s]*?}%%\s*/;
|
||||||
|
hidden terminal SINGLE_LINE_COMMENT: /[\t ]*%%[^\n\r]*/;
|
8
packages/parser/src/language/common/commonLexer.ts
Normal file
8
packages/parser/src/language/common/commonLexer.ts
Normal file
@@ -0,0 +1,8 @@
|
|||||||
|
import type { LexerResult } from 'langium';
|
||||||
|
import { DefaultLexer } from 'langium';
|
||||||
|
|
||||||
|
export class CommonLexer extends DefaultLexer {
|
||||||
|
public override tokenize(text: string): LexerResult {
|
||||||
|
return super.tokenize(text + '\n');
|
||||||
|
}
|
||||||
|
}
|
14
packages/parser/src/language/common/commonMatcher.ts
Normal file
14
packages/parser/src/language/common/commonMatcher.ts
Normal file
@@ -0,0 +1,14 @@
|
|||||||
|
/**
|
||||||
|
* Matches single and multiline accessible description
|
||||||
|
*/
|
||||||
|
export const accessibilityDescrRegex = /accDescr(?:[\t ]*:[\t ]*([^\n\r]*)|\s*{([^}]*)})/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches single line accessible title
|
||||||
|
*/
|
||||||
|
export const accessibilityTitleRegex = /accTitle[\t ]*:[\t ]*([^\n\r]*)/;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Matches a single line title
|
||||||
|
*/
|
||||||
|
export const titleRegex = /title([\t ]+([^\n\r]*)|)/;
|
74
packages/parser/src/language/common/commonValueConverters.ts
Normal file
74
packages/parser/src/language/common/commonValueConverters.ts
Normal file
@@ -0,0 +1,74 @@
|
|||||||
|
import type { CstNode, GrammarAST, ValueType } from 'langium';
|
||||||
|
import { DefaultValueConverter } from 'langium';
|
||||||
|
|
||||||
|
import { accessibilityDescrRegex, accessibilityTitleRegex, titleRegex } from './commonMatcher.js';
|
||||||
|
|
||||||
|
export class CommonValueConverter extends DefaultValueConverter {
|
||||||
|
protected override runConverter(
|
||||||
|
rule: GrammarAST.AbstractRule,
|
||||||
|
input: string,
|
||||||
|
cstNode: CstNode
|
||||||
|
): ValueType {
|
||||||
|
const value: ValueType | undefined = CommonValueConverter.customRunConverter(
|
||||||
|
rule,
|
||||||
|
input,
|
||||||
|
cstNode
|
||||||
|
);
|
||||||
|
if (value === undefined) {
|
||||||
|
return super.runConverter(rule, input, cstNode);
|
||||||
|
} else {
|
||||||
|
return value;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A method contains convert logic to be used by class itself or `MermaidValueConverter`.
|
||||||
|
*
|
||||||
|
* @param rule - Parsed rule.
|
||||||
|
* @param input - Matched string.
|
||||||
|
* @param _cstNode - Node in the Concrete Syntax Tree (CST).
|
||||||
|
* @returns converted the value if it's common rule or `undefined` if it's not.
|
||||||
|
*/
|
||||||
|
public static customRunConverter(
|
||||||
|
rule: GrammarAST.AbstractRule,
|
||||||
|
input: string,
|
||||||
|
// eslint-disable-next-line @typescript-eslint/no-unused-vars
|
||||||
|
_cstNode: CstNode
|
||||||
|
): ValueType | undefined {
|
||||||
|
let regex: RegExp | undefined;
|
||||||
|
switch (rule.name) {
|
||||||
|
case 'ACC_DESCR': {
|
||||||
|
regex = new RegExp(accessibilityDescrRegex.source);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'ACC_TITLE': {
|
||||||
|
regex = new RegExp(accessibilityTitleRegex.source);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
case 'TITLE': {
|
||||||
|
regex = new RegExp(titleRegex.source);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (regex === undefined) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
const match = regex.exec(input);
|
||||||
|
if (match === null) {
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
// single line title, accTitle, accDescr
|
||||||
|
if (match[1] !== undefined) {
|
||||||
|
return match[1].trim().replaceAll(/[\t ]{2,}/gm, ' ');
|
||||||
|
}
|
||||||
|
// multi line accDescr
|
||||||
|
if (match[2] !== undefined) {
|
||||||
|
return match[2]
|
||||||
|
.replaceAll(/^\s*/gm, '')
|
||||||
|
.replaceAll(/\s+$/gm, '')
|
||||||
|
.replaceAll(/[\t ]{2,}/gm, ' ')
|
||||||
|
.replaceAll(/[\n\r]{2,}/gm, '\n');
|
||||||
|
}
|
||||||
|
return undefined;
|
||||||
|
}
|
||||||
|
}
|
2
packages/parser/src/language/common/index.ts
Normal file
2
packages/parser/src/language/common/index.ts
Normal file
@@ -0,0 +1,2 @@
|
|||||||
|
export * from './commonLexer.js';
|
||||||
|
export * from './commonValueConverters.js';
|
6
packages/parser/src/language/index.ts
Normal file
6
packages/parser/src/language/index.ts
Normal file
@@ -0,0 +1,6 @@
|
|||||||
|
export * from './generated/ast.js';
|
||||||
|
export * from './generated/grammar.js';
|
||||||
|
export * from './generated/module.js';
|
||||||
|
|
||||||
|
export * from './common/index.js';
|
||||||
|
export * from './info/index.js';
|
1
packages/parser/src/language/info/index.ts
Normal file
1
packages/parser/src/language/info/index.ts
Normal file
@@ -0,0 +1 @@
|
|||||||
|
export * from './infoModule.js';
|
9
packages/parser/src/language/info/info.langium
Normal file
9
packages/parser/src/language/info/info.langium
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
grammar Info
|
||||||
|
import "../common/common";
|
||||||
|
|
||||||
|
entry Info:
|
||||||
|
NEWLINE*
|
||||||
|
"info" NEWLINE*
|
||||||
|
("showInfo" NEWLINE*)?
|
||||||
|
TitleAndAccessibilities?
|
||||||
|
;
|
72
packages/parser/src/language/info/infoModule.ts
Normal file
72
packages/parser/src/language/info/infoModule.ts
Normal file
@@ -0,0 +1,72 @@
|
|||||||
|
import type {
|
||||||
|
DefaultSharedModuleContext,
|
||||||
|
LangiumServices,
|
||||||
|
LangiumSharedServices,
|
||||||
|
Module,
|
||||||
|
PartialLangiumServices,
|
||||||
|
} from 'langium';
|
||||||
|
import { EmptyFileSystem, createDefaultModule, createDefaultSharedModule, inject } from 'langium';
|
||||||
|
|
||||||
|
import { MermaidGeneratedSharedModule, InfoGeneratedModule } from '../generated/module.js';
|
||||||
|
import { CommonLexer } from '../common/commonLexer.js';
|
||||||
|
import { CommonValueConverter } from '../common/commonValueConverters.js';
|
||||||
|
import { InfoTokenBuilder } from './infoTokenBuilder.js';
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Declaration of `Info` services.
|
||||||
|
*/
|
||||||
|
type InfoAddedServices = {
|
||||||
|
parser: {
|
||||||
|
Lexer: CommonLexer;
|
||||||
|
TokenBuilder: InfoTokenBuilder;
|
||||||
|
ValueConverter: CommonValueConverter;
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Union of Langium default services and `Info` services.
|
||||||
|
*/
|
||||||
|
export type InfoServices = LangiumServices & InfoAddedServices;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Dependency injection module that overrides Langium default services and
|
||||||
|
* contributes the declared `Info` services.
|
||||||
|
*/
|
||||||
|
export const InfoModule: Module<InfoServices, PartialLangiumServices & InfoAddedServices> = {
|
||||||
|
parser: {
|
||||||
|
Lexer: (services) => new CommonLexer(services),
|
||||||
|
TokenBuilder: () => new InfoTokenBuilder(),
|
||||||
|
ValueConverter: () => new CommonValueConverter(),
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create the full set of services required by Langium.
|
||||||
|
*
|
||||||
|
* First inject the shared services by merging two modules:
|
||||||
|
* - Langium default shared services
|
||||||
|
* - Services generated by langium-cli
|
||||||
|
*
|
||||||
|
* Then inject the language-specific services by merging three modules:
|
||||||
|
* - Langium default language-specific services
|
||||||
|
* - Services generated by langium-cli
|
||||||
|
* - Services specified in this file
|
||||||
|
* @param context - Optional module context with the LSP connection
|
||||||
|
* @returns An object wrapping the shared services and the language-specific services
|
||||||
|
*/
|
||||||
|
export function createInfoServices(context: DefaultSharedModuleContext = EmptyFileSystem): {
|
||||||
|
shared: LangiumSharedServices;
|
||||||
|
Info: InfoServices;
|
||||||
|
} {
|
||||||
|
const shared: LangiumSharedServices = inject(
|
||||||
|
createDefaultSharedModule(context),
|
||||||
|
MermaidGeneratedSharedModule
|
||||||
|
);
|
||||||
|
const Info: InfoServices = inject(
|
||||||
|
createDefaultModule({ shared }),
|
||||||
|
InfoGeneratedModule,
|
||||||
|
InfoModule
|
||||||
|
);
|
||||||
|
shared.ServiceRegistry.register(Info);
|
||||||
|
return { shared, Info };
|
||||||
|
}
|
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user