feat: Break render and parse types

Both render and parse are async now.
Return type of render contains svg and bindFunctions.
Parse will not throw error if parseOptions.silent is passed.
This commit is contained in:
Sidharth Vinod
2023-02-19 13:08:13 +05:30
parent d22e8d92c6
commit eaa84d2d91
11 changed files with 213 additions and 172 deletions

View File

@@ -1,5 +1,25 @@
# A collection of updates that change the behaviour # A collection of updates that change the behavior
## Async
`init`, `parse`, `render` are now async.
## Lazy loading and asynchronisity ## Lazy loading and asynchronisity
- Invalid dates are rendered as syntax error instead of returning best guess or the current date - Invalid dates are rendered as syntax error instead of returning best guess or the current date
## ParseError is removed
```js
//< v10.0.0
mermaid.parse(text, parseError);
//>= v10.0.0
await mermaid.parse(text).catch(parseError);
// or
try {
await mermaid.parse(text);
} catch (err) {
parseError(err);
}
```

View File

@@ -0,0 +1,78 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/interfaces/mermaidAPI.RenderResult.md](../../../../packages/mermaid/src/docs/config/setup/interfaces/mermaidAPI.RenderResult.md).
# Interface: RenderResult
[mermaidAPI](../modules/mermaidAPI.md).RenderResult
Function that renders an svg with a graph from a chart definition. Usage example below.
```javascript
mermaidAPI.initialize({
startOnLoad: true,
});
$(function () {
const graphDefinition = 'graph TB\na-->b';
const cb = function (svgGraph) {
console.log(svgGraph);
};
mermaidAPI.render('id1', graphDefinition, cb);
});
```
**`Param`**
The id for the SVG element (the element to be rendered)
**`Param`**
The text for the graph definition
**`Param`**
Callback which is called after rendering is finished with the svg code as in param.
**`Param`**
HTML element where the svg will be inserted. (Is usually element with the .mermaid class)
If no svgContainingElement is provided then the SVG element will be appended to the body.
Selector to element in which a div with the graph temporarily will be
inserted. If one is provided a hidden div will be inserted in the body of the page instead. The
element will be removed when rendering is completed.
## Properties
### bindFunctions
`Optional` **bindFunctions**: (`element`: `Element`) => `void`
#### Type declaration
▸ (`element`): `void`
##### Parameters
| Name | Type |
| :-------- | :-------- |
| `element` | `Element` |
##### Returns
`void`
#### Defined in
[mermaidAPI.ts:382](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L382)
---
### svg
**svg**: `string`
#### Defined in
[mermaidAPI.ts:381](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L381)

View File

@@ -6,6 +6,10 @@
# Module: mermaidAPI # Module: mermaidAPI
## Interfaces
- [RenderResult](../interfaces/mermaidAPI.RenderResult.md)
## References ## References
### default ### default
@@ -20,13 +24,13 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
#### Defined in #### Defined in
[mermaidAPI.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L75) [mermaidAPI.ts:71](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L71)
## Variables ## Variables
### mermaidAPI ### mermaidAPI
`Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseError?`: `ParseErrorFunction`) => `Promise`<`boolean`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `cb?`: (`svgCode`: `string`, `bindFunctions?`: (`element`: `Element`) => `void`) => `void`, `svgContainingElement?`: `Element`) => `Promise`<`string`> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: { `silent?`: `boolean` }) => `Promise`<`boolean` | `void`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }>
## mermaidAPI configuration defaults ## mermaidAPI configuration defaults
@@ -90,7 +94,7 @@ mermaid.initialize(config);
#### Defined in #### Defined in
[mermaidAPI.ts:671](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L671) [mermaidAPI.ts:666](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L666)
## Functions ## Functions
@@ -121,7 +125,7 @@ Return the last node appended
#### Defined in #### Defined in
[mermaidAPI.ts:278](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L278) [mermaidAPI.ts:289](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L289)
--- ---
@@ -147,7 +151,7 @@ the cleaned up svgCode
#### Defined in #### Defined in
[mermaidAPI.ts:229](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L229) [mermaidAPI.ts:240](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L240)
--- ---
@@ -173,7 +177,7 @@ the string with all the user styles
#### Defined in #### Defined in
[mermaidAPI.ts:158](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L158) [mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169)
--- ---
@@ -196,7 +200,7 @@ the string with all the user styles
#### Defined in #### Defined in
[mermaidAPI.ts:206](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L206) [mermaidAPI.ts:217](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L217)
--- ---
@@ -223,7 +227,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in #### Defined in
[mermaidAPI.ts:142](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L142) [mermaidAPI.ts:153](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L153)
--- ---
@@ -243,7 +247,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in #### Defined in
[mermaidAPI.ts:122](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L122) [mermaidAPI.ts:133](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L133)
--- ---
@@ -263,7 +267,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in #### Defined in
[mermaidAPI.ts:93](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L93) [mermaidAPI.ts:104](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L104)
--- ---
@@ -289,7 +293,7 @@ Put the svgCode into an iFrame. Return the iFrame code
#### Defined in #### Defined in
[mermaidAPI.ts:257](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L257) [mermaidAPI.ts:268](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L268)
--- ---
@@ -314,4 +318,4 @@ Remove any existing elements from the given document
#### Defined in #### Defined in
[mermaidAPI.ts:328](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L328) [mermaidAPI.ts:339](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L339)

View File

@@ -3,26 +3,24 @@ import { log } from './logger';
import { getDiagram, registerDiagram } from './diagram-api/diagramAPI'; import { getDiagram, registerDiagram } from './diagram-api/diagramAPI';
import { detectType, getDiagramLoader } from './diagram-api/detectType'; import { detectType, getDiagramLoader } from './diagram-api/detectType';
import { extractFrontMatter } from './diagram-api/frontmatter'; import { extractFrontMatter } from './diagram-api/frontmatter';
import { isDetailedError } from './utils'; import { UnknownDiagramError } from './errors';
import type { DetailedError } from './utils'; import { DetailedError } from './utils';
export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void; export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void;
export class Diagram { export class Diagram {
type = 'graph'; type = 'graph';
parser; parser;
renderer; renderer;
db; db;
private detectTypeFailed = false; private detectError?: UnknownDiagramError;
constructor(public txt: string, parseError?: ParseErrorFunction) { constructor(public text: string) {
this.text += '\n';
const cnf = configApi.getConfig(); const cnf = configApi.getConfig();
this.txt = txt;
try { try {
this.type = detectType(txt, cnf); this.type = detectType(text, cnf);
} catch (e) { } catch (e) {
this.handleError(e, parseError);
this.type = 'error'; this.type = 'error';
this.detectTypeFailed = true; this.detectError = e as UnknownDiagramError;
} }
const diagram = getDiagram(this.type); const diagram = getDiagram(this.type);
log.debug('Type ' + this.type); log.debug('Type ' + this.type);
@@ -46,44 +44,19 @@ export class Diagram {
diagram.init(cnf); diagram.init(cnf);
log.info('Initialized diagram ' + this.type, cnf); log.info('Initialized diagram ' + this.type, cnf);
} }
this.txt += '\n'; this.parse();
this.parse(this.txt, parseError);
} }
parse(text: string, parseError?: ParseErrorFunction): boolean { parse() {
if (this.detectTypeFailed) { if (this.detectError) {
return false; throw this.detectError;
} }
try {
text = text + '\n';
this.db.clear?.(); this.db.clear?.();
this.parser.parse(text); this.parser.parse(this.text);
return true;
} catch (error) {
this.handleError(error, parseError);
}
return false;
} }
handleError(error: unknown, parseError?: ParseErrorFunction) { async render(id: string, version: string) {
// Is this the correct way to access mermaid's parseError() await this.renderer.draw(this.text, id, version, this);
// method ? (or global.mermaid.parseError()) ?
if (parseError === undefined) {
// No mermaid.parseError() handler defined, so re-throw it
throw error;
}
if (isDetailedError(error)) {
// Handle case where error string and hash were
// wrapped in object like`const error = { str, hash };`
parseError(error.str, error.hash);
return;
}
// Otherwise, assume it is just an error string and pass it on
parseError(error as string);
} }
getParser() { getParser() {
@@ -95,10 +68,7 @@ export class Diagram {
} }
} }
export const getDiagramFromText = ( export const getDiagramFromText = async (txt: string): Promise<Diagram> => {
txt: string,
parseError?: ParseErrorFunction
): Diagram | Promise<Diagram> => {
const type = detectType(txt, configApi.getConfig()); const type = detectType(txt, configApi.getConfig());
try { try {
// Trying to find the diagram // Trying to find the diagram
@@ -106,19 +76,12 @@ export const getDiagramFromText = (
} catch (error) { } catch (error) {
const loader = getDiagramLoader(type); const loader = getDiagramLoader(type);
if (!loader) { if (!loader) {
throw new Error(`Diagram ${type} not found.`); throw new UnknownDiagramError(`Diagram ${type} not found.`);
} }
// TODO: Uncomment for v10 // Diagram not available, loading it
// // Diagram not available, loading it // new diagram will try getDiagram again and if fails then it is a valid throw
// const { diagram } = await loader(); const { id, diagram } = await loader();
// registerDiagram(type, diagram, undefined, diagram.injectUtils); registerDiagram(id, diagram);
// // new diagram will try getDiagram again and if fails then it is a valid throw
return loader().then(({ diagram }) => {
registerDiagram(type, diagram, undefined);
return new Diagram(txt, parseError);
});
} }
return new Diagram(txt, parseError); return new Diagram(txt);
}; };
export default Diagram;

View File

@@ -7,6 +7,7 @@ import type {
ExternalDiagramDefinition, ExternalDiagramDefinition,
} from './types'; } from './types';
import { frontMatterRegex } from './frontmatter'; import { frontMatterRegex } from './frontmatter';
import { UnknownDiagramError } from '../errors';
const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
const anyComment = /\s*%%.*\n/gm; const anyComment = /\s*%%.*\n/gm;
@@ -44,7 +45,7 @@ export const detectType = function (text: string, config?: MermaidConfig): strin
} }
} }
throw new Error(`No diagram type detected for text: ${text}`); throw new UnknownDiagramError(`No diagram type detected for text: ${text}`);
}; };
export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => { export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => {

View File

@@ -18,6 +18,7 @@ export interface DiagramDb {
setDiagramTitle?: (title: string) => void; setDiagramTitle?: (title: string) => void;
getAccTitle?: () => string; getAccTitle?: () => string;
getAccDescription?: () => string; getAccDescription?: () => string;
bindFunctions?: (element: Element) => void;
} }
export interface DiagramDefinition { export interface DiagramDefinition {

View File

@@ -348,19 +348,6 @@ export const setConf = function (cnf) {
*/ */
export const draw = function (text, id, _version, diagObj) { export const draw = function (text, id, _version, diagObj) {
log.info('Drawing class - ', id); log.info('Drawing class - ', id);
// diagObj.db.clear();
// const parser = diagObj.db.parser;
// parser.yy = classDb;
// Parse the graph definition
// try {
// parser.parse(text);
// } catch (err) {
// log.debug('Parsing failed');
// }
// Fetch the default direction, use TD if none was found
//let dir = 'TD';
const conf = getConfig().flowchart; const conf = getConfig().flowchart;
const securityLevel = getConfig().securityLevel; const securityLevel = getConfig().securityLevel;
@@ -384,15 +371,6 @@ export const draw = function (text, id, _version, diagObj) {
return {}; return {};
}); });
// let subG;
// const subGraphs = flowDb.getSubGraphs();
// log.info('Subgraphs - ', subGraphs);
// for (let i = subGraphs.length - 1; i >= 0; i--) {
// subG = subGraphs[i];
// log.info('Subgraph - ', subG);
// flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes);
// }
// Fetch the vertices/nodes and edges/links from the parsed graph definition // Fetch the vertices/nodes and edges/links from the parsed graph definition
const classes = diagObj.db.getClasses(); const classes = diagObj.db.getClasses();
const relations = diagObj.db.getRelations(); const relations = diagObj.db.getRelations();

View File

@@ -2,15 +2,12 @@
import { select, selectAll } from 'd3'; import { select, selectAll } from 'd3';
import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw'; import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw';
import { log } from '../../logger'; import { log } from '../../logger';
// import { parser } from './parser/sequenceDiagram';
import common from '../common/common'; import common from '../common/common';
// import sequenceDb from './sequenceDb';
import * as configApi from '../../config'; import * as configApi from '../../config';
import assignWithDepth from '../../assignWithDepth'; import assignWithDepth from '../../assignWithDepth';
import utils from '../../utils'; import utils from '../../utils';
import { configureSvgSize } from '../../setupGraphViewbox'; import { configureSvgSize } from '../../setupGraphViewbox';
import Diagram from '../../Diagram'; import { Diagram } from '../../Diagram';
import { convert } from '../../tests/util';
let conf = {}; let conf = {};

View File

@@ -0,0 +1,6 @@
export class UnknownDiagramError extends Error {
constructor(message: string) {
super(message);
this.name = 'UnknownDiagramError';
}
}

View File

@@ -7,7 +7,7 @@ import dedent from 'ts-dedent';
import { MermaidConfig } from './config.type'; import { MermaidConfig } from './config.type';
import { log } from './logger'; import { log } from './logger';
import utils from './utils'; import utils from './utils';
import { mermaidAPI } from './mermaidAPI'; import { mermaidAPI, RenderResult } from './mermaidAPI';
import { registerLazyLoadedDiagrams } from './diagram-api/detectType'; import { registerLazyLoadedDiagrams } from './diagram-api/detectType';
import type { ParseErrorFunction } from './Diagram'; import type { ParseErrorFunction } from './Diagram';
import { isDetailedError } from './utils'; import { isDetailedError } from './utils';
@@ -44,10 +44,8 @@ export type { MermaidConfig, DetailedError, ExternalDiagramDefinition, ParseErro
*/ */
const init = async function ( const init = async function (
config?: MermaidConfig, config?: MermaidConfig,
// eslint-disable-next-line no-undef
nodes?: string | HTMLElement | NodeListOf<HTMLElement>, nodes?: string | HTMLElement | NodeListOf<HTMLElement>,
// eslint-disable-next-line @typescript-eslint/ban-types callback?: (id: string) => unknown
callback?: Function
) { ) {
try { try {
await initThrowsErrors(config, nodes, callback); await initThrowsErrors(config, nodes, callback);
@@ -125,8 +123,7 @@ const loadExternalDiagrams = async (...diagrams: ExternalDiagramDefinition[]) =>
const initThrowsErrors = async function ( const initThrowsErrors = async function (
config?: MermaidConfig, config?: MermaidConfig,
nodes?: string | HTMLElement | NodeListOf<HTMLElement>, nodes?: string | HTMLElement | NodeListOf<HTMLElement>,
// eslint-disable-next-line @typescript-eslint/ban-types callback?: (id: string) => unknown
callback?: Function
) { ) {
const conf = mermaidAPI.getConfig(); const conf = mermaidAPI.getConfig();
@@ -188,20 +185,14 @@ const initThrowsErrors = async function (
log.debug('Detected early reinit: ', init); log.debug('Detected early reinit: ', init);
} }
try { try {
await mermaidAPI.render( const { svg, bindFunctions } = await mermaidAPI.render(id, txt, element);
id, element.innerHTML = svg;
txt, if (callback) {
(svgCode: string, bindFunctions?: (el: Element) => void) => {
element.innerHTML = svgCode;
if (callback !== undefined) {
callback(id); callback(id);
} }
if (bindFunctions) { if (bindFunctions) {
bindFunctions(element); bindFunctions(element);
} }
},
element
);
} catch (error) { } catch (error) {
handleError(error, errors, mermaid.parseError); handleError(error, errors, mermaid.parseError);
} }
@@ -296,15 +287,24 @@ const executeQueue = async () => {
}; };
/** /**
* @param txt - The mermaid code to be parsed. * Parse the text and validate the syntax.
* @param text - The mermaid diagram definition.
* @param parseOptions - Options for parsing.
* @returns true if the diagram is valid, false otherwise if parseOptions.silent is true.
* @throws Error if the diagram is invalid and parseOptions.silent is false.
*/ */
const parse = (txt: string): Promise<boolean> => { const parse = async (
text: string,
parseOptions?: {
silent?: boolean;
}
): Promise<boolean | void> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// This promise will resolve when the mermaidAPI.render call is done. // This promise will resolve when the mermaidAPI.render call is done.
// It will be queued first and will be executed when it is first in line // It will be queued first and will be executed when it is first in line
const performCall = () => const performCall = () =>
new Promise((res, rej) => { new Promise((res, rej) => {
mermaidAPI.parse(txt, mermaid.parseError).then( mermaidAPI.parse(text, parseOptions).then(
(r) => { (r) => {
// This resolves for the promise for the queue handling // This resolves for the promise for the queue handling
res(r); res(r);
@@ -313,6 +313,7 @@ const parse = (txt: string): Promise<boolean> => {
}, },
(e) => { (e) => {
log.error('Error parsing', e); log.error('Error parsing', e);
mermaid.parseError?.(e);
rej(e); rej(e);
reject(e); reject(e);
} }
@@ -323,18 +324,13 @@ const parse = (txt: string): Promise<boolean> => {
}); });
}; };
const render = ( const render = (id: string, text: string, container?: Element): Promise<RenderResult> => {
id: string,
text: string,
cb?: (svgCode: string, bindFunctions?: (element: Element) => void) => void,
container?: Element
): Promise<string> => {
return new Promise((resolve, reject) => { return new Promise((resolve, reject) => {
// This promise will resolve when the mermaidAPI.render call is done. // This promise will resolve when the mermaidAPI.render call is done.
// It will be queued first and will be executed when it is first in line // It will be queued first and will be executed when it is first in line
const performCall = () => const performCall = () =>
new Promise((res, rej) => { new Promise((res, rej) => {
mermaidAPI.render(id, text, cb, container).then( mermaidAPI.render(id, text, container).then(
(r) => { (r) => {
// This resolves for the promise for the queue handling // This resolves for the promise for the queue handling
res(r); res(r);
@@ -343,6 +339,7 @@ const render = (
}, },
(e) => { (e) => {
log.error('Error parsing', e); log.error('Error parsing', e);
mermaid.parseError?.(e);
rej(e); rej(e);
reject(e); reject(e);
} }
@@ -355,7 +352,6 @@ const render = (
const mermaid: { const mermaid: {
startOnLoad: boolean; startOnLoad: boolean;
diagrams: any;
parseError?: ParseErrorFunction; parseError?: ParseErrorFunction;
mermaidAPI: typeof mermaidAPI; mermaidAPI: typeof mermaidAPI;
parse: typeof parse; parse: typeof parse;
@@ -368,7 +364,6 @@ const mermaid: {
setParseErrorHandler: typeof setParseErrorHandler; setParseErrorHandler: typeof setParseErrorHandler;
} = { } = {
startOnLoad: true, startOnLoad: true,
diagrams: {},
mermaidAPI, mermaidAPI,
parse, parse,
render, render,

View File

@@ -17,11 +17,7 @@ import { compile, serialize, stringify } from 'stylis';
import { version } from '../package.json'; import { version } from '../package.json';
import * as configApi from './config'; import * as configApi from './config';
import { addDiagrams } from './diagram-api/diagram-orchestration'; import { addDiagrams } from './diagram-api/diagram-orchestration';
import classDb from './diagrams/class/classDb'; import { Diagram, getDiagramFromText } from './Diagram';
import flowDb from './diagrams/flowchart/flowDb';
import ganttDb from './diagrams/gantt/ganttDb';
import Diagram, { getDiagramFromText } from './Diagram';
import type { ParseErrorFunction } from './Diagram';
import errorRenderer from './diagrams/error/errorRenderer'; import errorRenderer from './diagrams/error/errorRenderer';
import { attachFunctions } from './interactionDb'; import { attachFunctions } from './interactionDb';
import { log, setLogLevel } from './logger'; import { log, setLogLevel } from './logger';
@@ -37,7 +33,7 @@ import { parseDirective } from './directiveUtils';
// diagram names that support classDef statements // diagram names that support classDef statements
const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram', 'stateDiagram-v2']; const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram', 'stateDiagram-v2'];
const MAX_TEXTLENGTH = 50_000;
const MAX_TEXTLENGTH_EXCEEDED_MSG = const MAX_TEXTLENGTH_EXCEEDED_MSG =
'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa'; 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa';
@@ -74,16 +70,34 @@ interface DiagramStyleClassDef {
// @ts-ignore Could replicate the type definition in d3. This also makes it possible to use the untyped info from the js diagram files. // @ts-ignore Could replicate the type definition in d3. This also makes it possible to use the untyped info from the js diagram files.
export type D3Element = any; export type D3Element = any;
// ----------------------------------------------------------------------------
/** /**
* Parse the text and validate the syntax.
* @param text - The mermaid diagram definition. * @param text - The mermaid diagram definition.
* @param parseError - If set, handles errors. * @param parseOptions - Options for parsing.
* @returns true if the diagram is valid, false otherwise if parseOptions.silent is true.
* @throws Error if the diagram is invalid and parseOptions.silent is false.
*/ */
async function parse(text: string, parseError?: ParseErrorFunction): Promise<boolean> {
async function parse(
text: string,
parseOptions?: {
silent?: boolean;
}
): Promise<boolean | void> {
addDiagrams(); addDiagrams();
const diagram = await getDiagramFromText(text, parseError); let error;
return diagram.parse(text, parseError); try {
const diagram = await getDiagramFromText(text);
diagram.parse();
} catch (err) {
error = err;
}
if (parseOptions?.silent) {
return error === undefined;
}
if (error) {
throw error;
}
} }
/** /**
@@ -366,12 +380,16 @@ export const removeExistingElements = (
* @returns Returns the rendered element as a string containing the SVG definition. * @returns Returns the rendered element as a string containing the SVG definition.
*/ */
export interface RenderResult {
svg: string;
bindFunctions?: (element: Element) => void;
}
const render = async function ( const render = async function (
id: string, id: string,
text: string, text: string,
cb?: (svgCode: string, bindFunctions?: (element: Element) => void) => void,
svgContainingElement?: Element svgContainingElement?: Element
): Promise<string> { ): Promise<RenderResult> {
addDiagrams(); addDiagrams();
configApi.reset(); configApi.reset();
@@ -387,8 +405,7 @@ const render = async function (
log.debug(config); log.debug(config);
// Check the maximum allowed text size // Check the maximum allowed text size
// TODO: Remove magic number if (text.length > (config?.maxTextSize ?? MAX_TEXTLENGTH)) {
if (text.length > (config?.maxTextSize ?? 50000)) {
text = MAX_TEXTLENGTH_EXCEEDED_MSG; text = MAX_TEXTLENGTH_EXCEEDED_MSG;
} }
@@ -453,11 +470,10 @@ const render = async function (
// Create the diagram // Create the diagram
// Important that we do not create the diagram until after the directives have been included // Important that we do not create the diagram until after the directives have been included
let diag; let diag: Diagram;
let parseEncounteredException; let parseEncounteredException;
try { try {
// diag = new Diagram(text);
diag = await getDiagramFromText(text); diag = await getDiagramFromText(text);
} catch (error) { } catch (error) {
diag = new Diagram('error'); diag = new Diagram('error');
@@ -510,7 +526,7 @@ const render = async function (
root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD); root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD);
// Fix for when the base tag is used // Fix for when the base tag is used
let svgCode = root.select(enclosingDivID_selector).node().innerHTML; let svgCode: string = root.select(enclosingDivID_selector).node().innerHTML;
log.debug('config.arrowMarkerAbsolute', config.arrowMarkerAbsolute); log.debug('config.arrowMarkerAbsolute', config.arrowMarkerAbsolute);
svgCode = cleanUpSvgCode(svgCode, isSandboxed, evaluate(config.arrowMarkerAbsolute)); svgCode = cleanUpSvgCode(svgCode, isSandboxed, evaluate(config.arrowMarkerAbsolute));
@@ -526,27 +542,6 @@ const render = async function (
}); });
} }
// -------------------------------------------------------------------------------
// Do any callbacks (cb = callback)
if (cb !== undefined) {
switch (graphType) {
case 'flowchart':
case 'flowchart-v2':
cb(svgCode, flowDb.bindFunctions);
break;
case 'gantt':
cb(svgCode, ganttDb.bindFunctions);
break;
case 'class':
case 'classDiagram':
cb(svgCode, classDb.bindFunctions);
break;
default:
cb(svgCode);
}
} else {
log.debug('CB = undefined!');
}
attachFunctions(); attachFunctions();
// ------------------------------------------------------------------------------- // -------------------------------------------------------------------------------
@@ -561,7 +556,10 @@ const render = async function (
throw parseEncounteredException; throw parseEncounteredException;
} }
return svgCode; return {
svg: svgCode,
bindFunctions: diag.db.bindFunctions,
};
}; };
/** /**