diff --git a/README.md b/README.md
index 98741a689..f844e2cb9 100644
--- a/README.md
+++ b/README.md
@@ -165,13 +165,7 @@ class Class10 {
int id
size()
}
-namespace Namespace01 {
- class Class11
- class Class12 {
- int id
- size()
- }
-}
+
```
```mermaid
@@ -191,13 +185,7 @@ class Class10 {
int id
size()
}
-namespace Namespace01 {
- class Class11
- class Class12 {
- int id
- size()
- }
-}
+
```
### State diagram [docs - live editor]
diff --git a/cSpell.json b/cSpell.json
index e8d718316..e67c7d48e 100644
--- a/cSpell.json
+++ b/cSpell.json
@@ -22,6 +22,7 @@
"brkt",
"brolin",
"brotli",
+ "catmull",
"città",
"classdef",
"codedoc",
diff --git a/cypress/helpers/util.ts b/cypress/helpers/util.ts
index 4160f4cbd..c656f638d 100644
--- a/cypress/helpers/util.ts
+++ b/cypress/helpers/util.ts
@@ -52,29 +52,21 @@ export const imgSnapshotTest = (
api = false,
validation?: any
): void => {
- cy.log(JSON.stringify(_options));
- const options: CypressMermaidConfig = Object.assign(_options);
- if (!options.fontFamily) {
- options.fontFamily = 'courier';
- }
- if (!options.sequence) {
- options.sequence = {};
- }
- if (!options.sequence || (options.sequence && !options.sequence.actorFontFamily)) {
- options.sequence.actorFontFamily = 'courier';
- }
- if (options.sequence && !options.sequence.noteFontFamily) {
- options.sequence.noteFontFamily = 'courier';
- }
- options.sequence.actorFontFamily = 'courier';
- options.sequence.noteFontFamily = 'courier';
- options.sequence.messageFontFamily = 'courier';
- if (options.sequence && !options.sequence.actorFontFamily) {
- options.sequence.actorFontFamily = 'courier';
- }
- if (!options.fontSize) {
- options.fontSize = 16;
- }
+ const options: CypressMermaidConfig = {
+ ..._options,
+ fontFamily: _options.fontFamily || 'courier',
+ // @ts-ignore TODO: Fix type of fontSize
+ fontSize: _options.fontSize || '16px',
+ sequence: {
+ ...(_options.sequence || {}),
+ actorFontFamily: 'courier',
+ noteFontFamily:
+ _options.sequence && _options.sequence.noteFontFamily
+ ? _options.sequence.noteFontFamily
+ : 'courier',
+ messageFontFamily: 'courier',
+ },
+ };
const url: string = mermaidUrl(graphStr, options, api);
openURLAndVerifyRendering(url, options, validation);
@@ -82,11 +74,10 @@ export const imgSnapshotTest = (
export const urlSnapshotTest = (
url: string,
- _options: CypressMermaidConfig,
+ options: CypressMermaidConfig,
_api = false,
validation?: any
): void => {
- const options: CypressMermaidConfig = Object.assign(_options);
openURLAndVerifyRendering(url, options, validation);
};
diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js
index 33d3ac9e1..7c1690952 100644
--- a/cypress/integration/rendering/gantt.spec.js
+++ b/cypress/integration/rendering/gantt.spec.js
@@ -330,6 +330,48 @@ describe('Gantt diagram', () => {
);
});
+ it('should render a gantt diagram with tick is 2 milliseconds', () => {
+ imgSnapshotTest(
+ `
+ gantt
+ title A Gantt Diagram
+ dateFormat SSS
+ axisFormat %Lms
+ tickInterval 2millisecond
+ excludes weekends
+
+ section Section
+ A task : a1, 000, 6ms
+ Another task : after a1, 6ms
+ section Another
+ Task in sec : a2, 006, 3ms
+ another task : 3ms
+ `,
+ {}
+ );
+ });
+
+ it('should render a gantt diagram with tick is 2 seconds', () => {
+ imgSnapshotTest(
+ `
+ gantt
+ title A Gantt Diagram
+ dateFormat ss
+ axisFormat %Ss
+ tickInterval 2second
+ excludes weekends
+
+ section Section
+ A task : a1, 00, 6s
+ Another task : after a1, 6s
+ section Another
+ Task in sec : 06, 3s
+ another task : 3s
+ `,
+ {}
+ );
+ });
+
it('should render a gantt diagram with tick is 15 minutes', () => {
imgSnapshotTest(
`
diff --git a/cypress/integration/rendering/marker_unique_id.spec.js b/cypress/integration/rendering/marker_unique_id.spec.js
new file mode 100644
index 000000000..617189db0
--- /dev/null
+++ b/cypress/integration/rendering/marker_unique_id.spec.js
@@ -0,0 +1,10 @@
+import { urlSnapshotTest } from '../../helpers/util.ts';
+
+describe('Marker Unique IDs Per Diagram', () => {
+ it('should render a blue arrow tip in second digram', () => {
+ urlSnapshotTest('http://localhost:9000/marker_unique_id.html', {
+ logLevel: 1,
+ flowchart: { htmlLabels: false },
+ });
+ });
+});
diff --git a/cypress/platform/marker_unique_id.html b/cypress/platform/marker_unique_id.html
new file mode 100644
index 000000000..e49169c55
--- /dev/null
+++ b/cypress/platform/marker_unique_id.html
@@ -0,0 +1,52 @@
+
+
+
+ Example
+
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%%
+ flowchart LR
+ subgraph red
+ A --> B
+ end
+
+
+ %%{init:{"theme":"base", "themeVariables": {"lineColor":"blue"}}}%%
+ flowchart LR
+ subgraph black
+ A --> B
+ end
+
+
+ ---
+ config:
+ theme: base
+ themeVariables:
+ lineColor: yellow
+ ---
+ flowchart LR
+ subgraph red
+ A --> B
+ end
+
+
+ ---
+ config:
+ theme: base
+ themeVariables:
+ lineColor: green
+ ---
+ flowchart LR
+ subgraph black
+ A --> B
+ end
+
+
+
+
diff --git a/docs/config/Tutorials.md b/docs/config/Tutorials.md
index 9ae177954..f3bad9ade 100644
--- a/docs/config/Tutorials.md
+++ b/docs/config/Tutorials.md
@@ -62,10 +62,10 @@ from IPython.display import Image, display
import matplotlib.pyplot as plt
def mm(graph):
- graphbytes = graph.encode("ascii")
- base64_bytes = base64.b64encode(graphbytes)
- base64_string = base64_bytes.decode("ascii")
- display(Image(url="https://mermaid.ink/img/" + base64_string))
+ graphbytes = graph.encode("utf8")
+ base64_bytes = base64.b64encode(graphbytes)
+ base64_string = base64_bytes.decode("ascii")
+ display(Image(url="https://mermaid.ink/img/" + base64_string))
mm("""
graph LR;
diff --git a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md
index 2082a081e..ea390899e 100644
--- a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md
+++ b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md
@@ -16,4 +16,4 @@
#### Defined in
-[mermaidAPI.ts:78](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L78)
+[mermaidAPI.ts:59](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L59)
diff --git a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md
index f84a51b87..18ee5e431 100644
--- a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md
+++ b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md
@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
#### Defined in
-[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98)
+[mermaidAPI.ts:79](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L79)
---
@@ -51,4 +51,4 @@ The svg code for the rendered graph.
#### Defined in
-[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88)
+[mermaidAPI.ts:69](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L69)
diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md
index 25d2d6333..8cb03980d 100644
--- a/docs/config/setup/modules/mermaidAPI.md
+++ b/docs/config/setup/modules/mermaidAPI.md
@@ -25,13 +25,13 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
#### Defined in
-[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82)
+[mermaidAPI.ts:63](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L63)
## Variables
### mermaidAPI
-• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `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 }>
+• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: `Pick`<`DiagramMetadata`, `"title"`>) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }>
## mermaidAPI configuration defaults
@@ -97,7 +97,7 @@ mermaid.initialize(config);
#### Defined in
-[mermaidAPI.ts:685](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L685)
+[mermaidAPI.ts:653](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L653)
## Functions
@@ -128,7 +128,7 @@ Return the last node appended
#### Defined in
-[mermaidAPI.ts:310](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L310)
+[mermaidAPI.ts:299](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L299)
---
@@ -154,13 +154,13 @@ the cleaned up svgCode
#### Defined in
-[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256)
+[mermaidAPI.ts:245](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L245)
---
### createCssStyles
-▸ **createCssStyles**(`config`, `graphType`, `classDefs?`): `string`
+▸ **createCssStyles**(`config`, `classDefs?`): `string`
Create the user styles
@@ -169,7 +169,6 @@ Create the user styles
| Name | Type | Description |
| :---------- | :------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------ |
| `config` | `MermaidConfig` | configuration that has style and theme settings to use |
-| `graphType` | `string` | used for checking if classDefs should be applied |
| `classDefs` | `undefined` \| `null` \| `Record`<`string`, `DiagramStyleClassDef`> | the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...) |
#### Returns
@@ -180,7 +179,7 @@ the string with all the user styles
#### Defined in
-[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185)
+[mermaidAPI.ts:175](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L175)
---
@@ -190,12 +189,12 @@ the string with all the user styles
#### Parameters
-| Name | Type |
-| :---------- | :----------------------------------------- |
-| `config` | `MermaidConfig` |
-| `graphType` | `string` |
-| `classDefs` | `Record`<`string`, `DiagramStyleClassDef`> |
-| `svgId` | `string` |
+| Name | Type |
+| :---------- | :-------------------------------------------------------- |
+| `config` | `MermaidConfig` |
+| `graphType` | `string` |
+| `classDefs` | `undefined` \| `Record`<`string`, `DiagramStyleClassDef`> |
+| `svgId` | `string` |
#### Returns
@@ -203,7 +202,7 @@ the string with all the user styles
#### Defined in
-[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233)
+[mermaidAPI.ts:222](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L222)
---
@@ -230,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
-[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169)
+[mermaidAPI.ts:160](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L160)
---
@@ -250,7 +249,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
-[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155)
+[mermaidAPI.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L146)
---
@@ -270,7 +269,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
#### Defined in
-[mermaidAPI.ts:126](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L126)
+[mermaidAPI.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L117)
---
@@ -296,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
#### Defined in
-[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287)
+[mermaidAPI.ts:276](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L276)
---
@@ -321,4 +320,4 @@ Remove any existing elements from the given document
#### Defined in
-[mermaidAPI.ts:360](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L360)
+[mermaidAPI.ts:349](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L349)
diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md
index d19f71a97..23b05af7f 100644
--- a/docs/syntax/flowchart.md
+++ b/docs/syntax/flowchart.md
@@ -860,8 +860,8 @@ flowchart LR
C-->D
click A callback "Tooltip for a callback"
click B "https://www.github.com" "This is a tooltip for a link"
- click A call callback() "Tooltip for a callback"
- click B href "https://www.github.com" "This is a tooltip for a link"
+ click C call callback() "Tooltip for a callback"
+ click D href "https://www.github.com" "This is a tooltip for a link"
```
```mermaid
@@ -871,13 +871,13 @@ flowchart LR
C-->D
click A callback "Tooltip for a callback"
click B "https://www.github.com" "This is a tooltip for a link"
- click A call callback() "Tooltip for a callback"
- click B href "https://www.github.com" "This is a tooltip for a link"
+ click C call callback() "Tooltip for a callback"
+ click D href "https://www.github.com" "This is a tooltip for a link"
```
> **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2.
-?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/s37cjoau/3/).
+?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/Ogglas/2o73vdez/7).
Links are opened in the same browser tab/window by default. It is possible to change this by adding a link target to the click definition (`_self`, `_blank`, `_parent` and `_top` are supported):
diff --git a/docs/syntax/gantt.md b/docs/syntax/gantt.md
index 8ad438fb1..33c2740e5 100644
--- a/docs/syntax/gantt.md
+++ b/docs/syntax/gantt.md
@@ -241,7 +241,7 @@ The following formatting strings are supported:
More info in:
-### Axis ticks
+### Axis ticks (v10.3.0+)
The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`.
@@ -252,7 +252,7 @@ tickInterval 1day
The pattern is:
```javascript
-/^([1-9][0-9]*)(minute|hour|day|week|month)$/;
+/^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/;
```
More info in:
@@ -271,7 +271,7 @@ gantt
weekday monday
```
-Support: v10.3.0+
+> **Warning** > `millisecond` and `second` support was added in vMERMAID_RELEASE_VERSION
## Output in compact mode
diff --git a/docs/syntax/sankey.md b/docs/syntax/sankey.md
index 6b408441a..43d36f089 100644
--- a/docs/syntax/sankey.md
+++ b/docs/syntax/sankey.md
@@ -8,9 +8,8 @@
> A sankey diagram is a visualization used to depict a flow from one set of values to another.
-::: warning
-This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future.
-:::
+> **Warning**
+> This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future.
The things being connected are called nodes and the connections are called links.
@@ -19,6 +18,11 @@ The things being connected are called nodes and the connections are called links
This example taken from [observable](https://observablehq.com/@d3/sankey/2?collection=@d3/d3-sankey). It may be rendered a little bit differently, though, in terms of size and colors.
```mermaid-example
+---
+config:
+ sankey:
+ showValues: false
+---
sankey-beta
Agricultural 'waste',Bio-conversion,124.729
@@ -92,6 +96,11 @@ Wind,Electricity grid,289.366
```
```mermaid
+---
+config:
+ sankey:
+ showValues: false
+---
sankey-beta
Agricultural 'waste',Bio-conversion,124.729
diff --git a/package.json b/package.json
index 232f23be1..7a6a032d7 100644
--- a/package.json
+++ b/package.json
@@ -4,7 +4,7 @@
"version": "10.2.4",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
- "packageManager": "pnpm@8.7.1",
+ "packageManager": "pnpm@8.7.5",
"keywords": [
"diagram",
"markdown",
@@ -19,6 +19,7 @@
"build:mermaid": "pnpm build:vite --mermaid",
"build:viz": "pnpm build:mermaid --visualize",
"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:types:watch": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly --watch",
"build:watch": "pnpm build:vite --watch",
"build": "pnpm run -r clean && pnpm build:types && pnpm build:vite",
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"",
diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json
index 7b4de70a9..23138003f 100644
--- a/packages/mermaid/package.json
+++ b/packages/mermaid/package.json
@@ -1,6 +1,6 @@
{
"name": "mermaid",
- "version": "10.4.0",
+ "version": "10.5.0-rc.1",
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module",
"module": "./dist/mermaid.core.mjs",
diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts
index 308e141d0..b77091f28 100644
--- a/packages/mermaid/src/Diagram.ts
+++ b/packages/mermaid/src/Diagram.ts
@@ -2,11 +2,9 @@ import * as configApi from './config.js';
import { log } from './logger.js';
import { getDiagram, registerDiagram } from './diagram-api/diagramAPI.js';
import { detectType, getDiagramLoader } from './diagram-api/detectType.js';
-import { extractFrontMatter } from './diagram-api/frontmatter.js';
import { UnknownDiagramError } from './errors.js';
-import { cleanupComments } from './diagram-api/comments.js';
import type { DetailedError } from './utils.js';
-import type { DiagramDefinition } from './diagram-api/types.js';
+import type { DiagramDefinition, DiagramMetadata } from './diagram-api/types.js';
export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void;
@@ -22,7 +20,7 @@ export class Diagram {
private init?: DiagramDefinition['init'];
private detectError?: UnknownDiagramError;
- constructor(public text: string) {
+ constructor(public text: string, public metadata: Pick = {}) {
this.text += '\n';
const cnf = configApi.getConfig();
try {
@@ -37,19 +35,6 @@ export class Diagram {
this.db = diagram.db;
this.renderer = diagram.renderer;
this.parser = diagram.parser;
- const originalParse = this.parser.parse.bind(this.parser);
- // Wrap the jison parse() method to handle extracting frontmatter.
- //
- // This can't be done in this.parse() because some code
- // directly calls diagram.parser.parse(), bypassing this.parse().
- //
- // Similarly, we can't do this in getDiagramFromText() because some code
- // calls diagram.db.clear(), which would reset anything set by
- // extractFrontMatter().
-
- this.parser.parse = (text: string) =>
- originalParse(cleanupComments(extractFrontMatter(text, this.db, configApi.addDirective)));
-
this.parser.parser.yy = this.db;
this.init = diagram.init;
this.parse();
@@ -60,7 +45,12 @@ export class Diagram {
throw this.detectError;
}
this.db.clear?.();
- this.init?.(configApi.getConfig());
+ const config = configApi.getConfig();
+ this.init?.(config);
+ // This block was added for legacy compatibility. Use frontmatter instead of adding more special cases.
+ if (this.metadata.title) {
+ this.db.setDiagramTitle?.(this.metadata.title);
+ }
this.parser.parse(this.text);
}
@@ -82,11 +72,15 @@ export class Diagram {
* **Warning:** This function may be changed in the future.
* @alpha
* @param text - The mermaid diagram definition.
+ * @param metadata - Diagram metadata, defined in YAML.
* @returns A the Promise of a Diagram object.
* @throws {@link UnknownDiagramError} if the diagram type can not be found.
* @privateRemarks This is exported as part of the public mermaidAPI.
*/
-export const getDiagramFromText = async (text: string): Promise => {
+export const getDiagramFromText = async (
+ text: string,
+ metadata: Pick = {}
+): Promise => {
const type = detectType(text, configApi.getConfig());
try {
// Trying to find the diagram
@@ -101,5 +95,5 @@ export const getDiagramFromText = async (text: string): Promise => {
const { id, diagram } = await loader();
registerDiagram(id, diagram);
}
- return new Diagram(text);
+ return new Diagram(text, metadata);
};
diff --git a/packages/mermaid/src/__mocks__/mermaidAPI.ts b/packages/mermaid/src/__mocks__/mermaidAPI.ts
index a2d19ef24..de4cb61df 100644
--- a/packages/mermaid/src/__mocks__/mermaidAPI.ts
+++ b/packages/mermaid/src/__mocks__/mermaidAPI.ts
@@ -13,7 +13,6 @@ export const mermaidAPI = {
svg: '',
}),
parse: mAPI.parse,
- parseDirective: vi.fn(),
initialize: vi.fn(),
getConfig: configApi.getConfig,
setConfig: configApi.setConfig,
diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts
index eb24b6268..ede3a568d 100644
--- a/packages/mermaid/src/config.ts
+++ b/packages/mermaid/src/config.ts
@@ -3,7 +3,7 @@ import { log } from './logger.js';
import theme from './themes/index.js';
import config from './defaultConfig.js';
import type { MermaidConfig } from './config.type.js';
-import { sanitizeDirective } from './utils.js';
+import { sanitizeDirective } from './utils/sanitizeDirective.js';
export const defaultConfig: MermaidConfig = Object.freeze(config);
diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts
index 81dca5a00..17df2deb3 100644
--- a/packages/mermaid/src/config.type.ts
+++ b/packages/mermaid/src/config.type.ts
@@ -1054,7 +1054,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig {
* Pattern is:
*
* ```javascript
- * /^([1-9][0-9]*)(minute|hour|day|week|month)$/
+ * /^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/
* ```
*
*/
diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js
index f89b4422b..1b3e172c0 100644
--- a/packages/mermaid/src/dagre-wrapper/edges.js
+++ b/packages/mermaid/src/dagre-wrapper/edges.js
@@ -5,6 +5,7 @@ import { line, curveBasis, select } from 'd3';
import { getConfig } from '../config.js';
import utils from '../utils.js';
import { evaluate } from '../diagrams/common/common.js';
+import { getLineFunctionsWithOffset } from '../utils/lineWithOffset.js';
let edgeLabels = {};
let terminalLabels = {};
@@ -368,21 +369,7 @@ const cutPathAtIntersect = (_points, boundryNode) => {
return points;
};
-/**
- * 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, id) {
let points = edge.points;
let pointsHasChanged = false;
const tail = graph.node(e.v);
@@ -456,56 +443,8 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
curve = edge.curve;
}
- // We need to draw the lines a bit shorter to avoid drawing
- // under any transparent markers.
- // The offsets are calculated from the markers' dimensions.
- const markerOffsets = {
- aggregation: 18,
- extension: 18,
- composition: 18,
- dependency: 6,
- lollipop: 13.5,
- arrow_point: 5.3,
- };
-
- const lineFunction = line()
- .x(function (d, i, data) {
- let offset = 0;
- if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
- // Handle first point
- // Calculate the angle and delta between the first two points
- const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]);
- // Calculate the offset based on the angle and the marker's dimensions
- offset = markerOffsets[edge.arrowTypeStart] * Math.cos(angle) * (deltaX >= 0 ? 1 : -1) || 0;
- } else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) {
- // Handle last point
- // Calculate the angle and delta between the last two points
- 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, i, data) {
- // Same handling as X above
- 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);
+ const { x, y } = getLineFunctionsWithOffset(edge);
+ const lineFunction = line().x(x).y(y).curve(curve);
// Construct stroke classes based on properties
let strokeClasses;
@@ -569,61 +508,103 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph
switch (edge.arrowTypeStart) {
case 'arrow_cross':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-crossStart' + ')'
+ );
break;
case 'arrow_point':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-pointStart' + ')'
+ );
break;
case 'arrow_barb':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-barbStart' + ')'
+ );
break;
case 'arrow_circle':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-circleStart' + ')'
+ );
break;
case 'aggregation':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-aggregationStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-aggregationStart' + ')'
+ );
break;
case 'extension':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-extensionStart' + ')'
+ );
break;
case 'composition':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-compositionStart' + ')'
+ );
break;
case 'dependency':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-dependencyStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-dependencyStart' + ')'
+ );
break;
case 'lollipop':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-lollipopStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-lollipopStart' + ')'
+ );
break;
default:
}
switch (edge.arrowTypeEnd) {
case 'arrow_cross':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')');
+ svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-crossEnd' + ')');
break;
case 'arrow_point':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')');
+ svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-pointEnd' + ')');
break;
case 'arrow_barb':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')');
+ svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-barbEnd' + ')');
break;
case 'arrow_circle':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')');
+ svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-circleEnd' + ')');
break;
case 'aggregation':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-aggregationEnd' + ')');
+ svgPath.attr(
+ 'marker-end',
+ 'url(' + url + '#' + id + '_' + diagramType + '-aggregationEnd' + ')'
+ );
break;
case 'extension':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')');
+ svgPath.attr(
+ 'marker-end',
+ 'url(' + url + '#' + id + '_' + diagramType + '-extensionEnd' + ')'
+ );
break;
case 'composition':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')');
+ svgPath.attr(
+ 'marker-end',
+ 'url(' + url + '#' + id + '_' + diagramType + '-compositionEnd' + ')'
+ );
break;
case 'dependency':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-dependencyEnd' + ')');
+ svgPath.attr(
+ 'marker-end',
+ 'url(' + url + '#' + id + '_' + diagramType + '-dependencyEnd' + ')'
+ );
break;
case 'lollipop':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-lollipopEnd' + ')');
+ svgPath.attr(
+ 'marker-end',
+ 'url(' + url + '#' + id + '_' + diagramType + '-lollipopEnd' + ')'
+ );
break;
default:
}
diff --git a/packages/mermaid/src/dagre-wrapper/index.js b/packages/mermaid/src/dagre-wrapper/index.js
index 279c5d9dd..9843adb8b 100644
--- a/packages/mermaid/src/dagre-wrapper/index.js
+++ b/packages/mermaid/src/dagre-wrapper/index.js
@@ -14,7 +14,7 @@ import { insertCluster, clear as clearClusters } from './clusters.js';
import { insertEdgeLabel, positionEdgeLabel, insertEdge, clear as clearEdges } from './edges.js';
import { log } from '../logger.js';
-const recursiveRender = async (_elem, graph, diagramtype, parentCluster) => {
+const recursiveRender = async (_elem, graph, diagramtype, id, parentCluster) => {
log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster);
const dir = graph.graph().rankdir;
log.trace('Dir in recursive render - dir:', dir);
@@ -52,7 +52,7 @@ const recursiveRender = async (_elem, graph, diagramtype, parentCluster) => {
if (node && node.clusterNode) {
// const children = graph.children(v);
log.info('Cluster identified', v, node.width, graph.node(v));
- const o = await recursiveRender(nodes, node.graph, diagramtype, graph.node(v));
+ const o = await recursiveRender(nodes, node.graph, diagramtype, id, graph.node(v));
const newEl = o.elem;
updateNodeBounds(node, newEl);
node.diff = o.diff || 0;
@@ -134,7 +134,7 @@ const recursiveRender = async (_elem, graph, diagramtype, parentCluster) => {
const edge = graph.edge(e);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(edge), edge);
- const paths = insertEdge(edgePaths, e, edge, clusterDb, diagramtype, graph);
+ const paths = insertEdge(edgePaths, e, edge, clusterDb, diagramtype, graph, id);
positionEdgeLabel(edge, paths);
});
@@ -159,7 +159,7 @@ export const render = async (elem, graph, markers, diagramtype, id) => {
adjustClustersAndEdges(graph);
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
// log.warn('Graph ever after:', graphlibJson.write(graph.node('A').graph));
- await recursiveRender(elem, graph, diagramtype);
+ await recursiveRender(elem, graph, diagramtype, id);
};
// const shapeDefinitions = {};
diff --git a/packages/mermaid/src/dagre-wrapper/markers.js b/packages/mermaid/src/dagre-wrapper/markers.js
index 051c987f6..91f7ecc80 100644
--- a/packages/mermaid/src/dagre-wrapper/markers.js
+++ b/packages/mermaid/src/dagre-wrapper/markers.js
@@ -14,7 +14,7 @@ const extension = (elem, type, id) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-extensionStart')
+ .attr('id', id + '_' + type + '-extensionStart')
.attr('class', 'marker extension ' + type)
.attr('refX', 18)
.attr('refY', 7)
@@ -27,7 +27,7 @@ const extension = (elem, type, id) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-extensionEnd')
+ .attr('id', id + '_' + type + '-extensionEnd')
.attr('class', 'marker extension ' + type)
.attr('refX', 1)
.attr('refY', 7)
@@ -38,11 +38,11 @@ const extension = (elem, type, id) => {
.attr('d', 'M 1,1 V 13 L18,7 Z'); // this is actual shape for arrowhead
};
-const composition = (elem, type) => {
+const composition = (elem, type, id) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-compositionStart')
+ .attr('id', id + '_' + type + '-compositionStart')
.attr('class', 'marker composition ' + type)
.attr('refX', 18)
.attr('refY', 7)
@@ -55,7 +55,7 @@ const composition = (elem, type) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-compositionEnd')
+ .attr('id', id + '_' + type + '-compositionEnd')
.attr('class', 'marker composition ' + type)
.attr('refX', 1)
.attr('refY', 7)
@@ -65,11 +65,11 @@ const composition = (elem, type) => {
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
};
-const aggregation = (elem, type) => {
+const aggregation = (elem, type, id) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-aggregationStart')
+ .attr('id', id + '_' + type + '-aggregationStart')
.attr('class', 'marker aggregation ' + type)
.attr('refX', 18)
.attr('refY', 7)
@@ -82,7 +82,7 @@ const aggregation = (elem, type) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-aggregationEnd')
+ .attr('id', id + '_' + type + '-aggregationEnd')
.attr('class', 'marker aggregation ' + type)
.attr('refX', 1)
.attr('refY', 7)
@@ -92,11 +92,11 @@ const aggregation = (elem, type) => {
.append('path')
.attr('d', 'M 18,7 L9,13 L1,7 L9,1 Z');
};
-const dependency = (elem, type) => {
+const dependency = (elem, type, id) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-dependencyStart')
+ .attr('id', id + '_' + type + '-dependencyStart')
.attr('class', 'marker dependency ' + type)
.attr('refX', 6)
.attr('refY', 7)
@@ -109,7 +109,7 @@ const dependency = (elem, type) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-dependencyEnd')
+ .attr('id', id + '_' + type + '-dependencyEnd')
.attr('class', 'marker dependency ' + type)
.attr('refX', 13)
.attr('refY', 7)
@@ -119,11 +119,11 @@ const dependency = (elem, type) => {
.append('path')
.attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
};
-const lollipop = (elem, type) => {
+const lollipop = (elem, type, id) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-lollipopStart')
+ .attr('id', id + '_' + type + '-lollipopStart')
.attr('class', 'marker lollipop ' + type)
.attr('refX', 13)
.attr('refY', 7)
@@ -140,7 +140,7 @@ const lollipop = (elem, type) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-lollipopEnd')
+ .attr('id', id + '_' + type + '-lollipopEnd')
.attr('class', 'marker lollipop ' + type)
.attr('refX', 1)
.attr('refY', 7)
@@ -154,10 +154,10 @@ const lollipop = (elem, type) => {
.attr('cy', 7)
.attr('r', 6);
};
-const point = (elem, type) => {
+const point = (elem, type, id) => {
elem
.append('marker')
- .attr('id', type + '-pointEnd')
+ .attr('id', id + '_' + type + '-pointEnd')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 6)
@@ -173,10 +173,10 @@ const point = (elem, type) => {
.style('stroke-dasharray', '1,0');
elem
.append('marker')
- .attr('id', type + '-pointStart')
+ .attr('id', id + '_' + type + '-pointStart')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
- .attr('refX', 0)
+ .attr('refX', 4.5)
.attr('refY', 5)
.attr('markerUnits', 'userSpaceOnUse')
.attr('markerWidth', 12)
@@ -188,10 +188,10 @@ const point = (elem, type) => {
.style('stroke-width', 1)
.style('stroke-dasharray', '1,0');
};
-const circle = (elem, type) => {
+const circle = (elem, type, id) => {
elem
.append('marker')
- .attr('id', type + '-circleEnd')
+ .attr('id', id + '_' + type + '-circleEnd')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', 11)
@@ -210,7 +210,7 @@ const circle = (elem, type) => {
elem
.append('marker')
- .attr('id', type + '-circleStart')
+ .attr('id', id + '_' + type + '-circleStart')
.attr('class', 'marker ' + type)
.attr('viewBox', '0 0 10 10')
.attr('refX', -1)
@@ -227,10 +227,10 @@ const circle = (elem, type) => {
.style('stroke-width', 1)
.style('stroke-dasharray', '1,0');
};
-const cross = (elem, type) => {
+const cross = (elem, type, id) => {
elem
.append('marker')
- .attr('id', type + '-crossEnd')
+ .attr('id', id + '_' + type + '-crossEnd')
.attr('class', 'marker cross ' + type)
.attr('viewBox', '0 0 11 11')
.attr('refX', 12)
@@ -248,7 +248,7 @@ const cross = (elem, type) => {
elem
.append('marker')
- .attr('id', type + '-crossStart')
+ .attr('id', id + '_' + type + '-crossStart')
.attr('class', 'marker cross ' + type)
.attr('viewBox', '0 0 11 11')
.attr('refX', -1)
@@ -264,11 +264,11 @@ const cross = (elem, type) => {
.style('stroke-width', 2)
.style('stroke-dasharray', '1,0');
};
-const barb = (elem, type) => {
+const barb = (elem, type, id) => {
elem
.append('defs')
.append('marker')
- .attr('id', type + '-barbEnd')
+ .attr('id', id + '_' + type + '-barbEnd')
.attr('refX', 19)
.attr('refY', 7)
.attr('markerWidth', 20)
diff --git a/packages/mermaid/src/diagram-api/comments.ts b/packages/mermaid/src/diagram-api/comments.ts
index be39b0a0f..8141deee0 100644
--- a/packages/mermaid/src/diagram-api/comments.ts
+++ b/packages/mermaid/src/diagram-api/comments.ts
@@ -4,5 +4,5 @@
* @returns cleaned text
*/
export const cleanupComments = (text: string): string => {
- return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, '');
+ return text.replace(/^\s*%%(?!{)[^\n]+\n?/gm, '').trimStart();
};
diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts
index 80665cfa2..0357ad7fe 100644
--- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts
+++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts
@@ -43,7 +43,11 @@ export const addDiagrams = () => {
},
},
styles: {}, // should never be used
- renderer: {}, // should never be used
+ renderer: {
+ draw: () => {
+ // should never be used
+ },
+ },
parser: {
parser: { yy: {} },
parse: () => {
diff --git a/packages/mermaid/src/diagram-api/diagramAPI.spec.ts b/packages/mermaid/src/diagram-api/diagramAPI.spec.ts
index b82011f8d..2cafd695b 100644
--- a/packages/mermaid/src/diagram-api/diagramAPI.spec.ts
+++ b/packages/mermaid/src/diagram-api/diagramAPI.spec.ts
@@ -41,7 +41,11 @@ describe('DiagramAPI', () => {
},
parser: { yy: {} },
},
- renderer: {},
+ renderer: {
+ draw: () => {
+ // no-op
+ },
+ },
styles: {},
},
detector
diff --git a/packages/mermaid/src/diagram-api/diagramAPI.ts b/packages/mermaid/src/diagram-api/diagramAPI.ts
index 6f1421527..ea3c10159 100644
--- a/packages/mermaid/src/diagram-api/diagramAPI.ts
+++ b/packages/mermaid/src/diagram-api/diagramAPI.ts
@@ -6,7 +6,6 @@ import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox.js
import { addStylesForDiagram } from '../styles.js';
import type { DiagramDefinition, DiagramDetector } from './types.js';
import * as _commonDb from '../diagrams/common/commonDb.js';
-import { parseDirective as _parseDirective } from '../directiveUtils.js';
/*
Packaging and exposing resources for external diagrams so that they can import
@@ -21,8 +20,6 @@ export const setupGraphViewbox = _setupGraphViewbox;
export const getCommonDb = () => {
return _commonDb;
};
-export const parseDirective = (p: any, statement: string, context: string, type: string) =>
- _parseDirective(p, statement, context, type);
const diagrams: Record = {};
export interface Detectors {
@@ -52,17 +49,18 @@ export const registerDiagram = (
}
addStylesForDiagram(id, diagram.styles);
- if (diagram.injectUtils) {
- diagram.injectUtils(
- log,
- setLogLevel,
- getConfig,
- sanitizeText,
- setupGraphViewbox,
- getCommonDb(),
- parseDirective
- );
- }
+ diagram.injectUtils?.(
+ log,
+ setLogLevel,
+ getConfig,
+ sanitizeText,
+ setupGraphViewbox,
+ getCommonDb(),
+ () => {
+ // parseDirective is removed in https://github.com/mermaid-js/mermaid/pull/4759.
+ // This is a no-op for legacy support.
+ }
+ );
};
export const getDiagram = (name: string): DiagramDefinition => {
diff --git a/packages/mermaid/src/diagram-api/frontmatter.spec.ts b/packages/mermaid/src/diagram-api/frontmatter.spec.ts
index 03d46c300..90ef97cb6 100644
--- a/packages/mermaid/src/diagram-api/frontmatter.spec.ts
+++ b/packages/mermaid/src/diagram-api/frontmatter.spec.ts
@@ -1,84 +1,139 @@
-import { vi } from 'vitest';
import { extractFrontMatter } from './frontmatter.js';
-const dbMock = () => ({ setDiagramTitle: vi.fn() });
-const setConfigMock = vi.fn();
-
describe('extractFrontmatter', () => {
- beforeEach(() => {
- setConfigMock.mockClear();
- });
-
it('returns text unchanged if no frontmatter', () => {
- expect(extractFrontMatter('diagram', dbMock())).toEqual('diagram');
+ expect(extractFrontMatter('diagram')).toMatchInlineSnapshot(`
+ {
+ "metadata": {},
+ "text": "diagram",
+ }
+ `);
});
it('returns text unchanged if frontmatter lacks closing delimiter', () => {
const text = `---\ntitle: foo\ndiagram`;
- expect(extractFrontMatter(text, dbMock())).toEqual(text);
+ expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
+ {
+ "metadata": {},
+ "text": "---
+ title: foo
+ diagram",
+ }
+ `);
});
it('handles empty frontmatter', () => {
- const db = dbMock();
const text = `---\n\n---\ndiagram`;
- expect(extractFrontMatter(text, db)).toEqual('diagram');
- expect(db.setDiagramTitle).not.toHaveBeenCalled();
+ expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
+ {
+ "metadata": {},
+ "text": "diagram",
+ }
+ `);
});
it('handles frontmatter without mappings', () => {
- const db = dbMock();
- const text = `---\n1\n---\ndiagram`;
- expect(extractFrontMatter(text, db)).toEqual('diagram');
- expect(db.setDiagramTitle).not.toHaveBeenCalled();
+ expect(extractFrontMatter(`---\n1\n---\ndiagram`)).toMatchInlineSnapshot(`
+ {
+ "metadata": {},
+ "text": "diagram",
+ }
+ `);
+ expect(extractFrontMatter(`---\n-1\n-2\n---\ndiagram`)).toMatchInlineSnapshot(`
+ {
+ "metadata": {},
+ "text": "diagram",
+ }
+ `);
+ expect(extractFrontMatter(`---\nnull\n---\ndiagram`)).toMatchInlineSnapshot(`
+ {
+ "metadata": {},
+ "text": "diagram",
+ }
+ `);
});
it('does not try to parse frontmatter at the end', () => {
- const db = dbMock();
const text = `diagram\n---\ntitle: foo\n---\n`;
- expect(extractFrontMatter(text, db)).toEqual(text);
- expect(db.setDiagramTitle).not.toHaveBeenCalled();
+ expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
+ {
+ "metadata": {},
+ "text": "diagram
+ ---
+ title: foo
+ ---
+ ",
+ }
+ `);
});
it('handles frontmatter with multiple delimiters', () => {
- const db = dbMock();
const text = `---\ntitle: foo---bar\n---\ndiagram\n---\ntest`;
- expect(extractFrontMatter(text, db)).toEqual('diagram\n---\ntest');
- expect(db.setDiagramTitle).toHaveBeenCalledWith('foo---bar');
+ expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
+ {
+ "metadata": {
+ "title": "foo---bar",
+ },
+ "text": "diagram
+ ---
+ test",
+ }
+ `);
});
it('handles frontmatter with multi-line string and multiple delimiters', () => {
- const db = dbMock();
const text = `---\ntitle: |\n multi-line string\n ---\n---\ndiagram`;
- expect(extractFrontMatter(text, db)).toEqual('diagram');
- expect(db.setDiagramTitle).toHaveBeenCalledWith('multi-line string\n---\n');
+ expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
+ {
+ "metadata": {
+ "title": "multi-line string
+ ---
+ ",
+ },
+ "text": "diagram",
+ }
+ `);
});
it('handles frontmatter with title', () => {
- const db = dbMock();
const text = `---\ntitle: foo\n---\ndiagram`;
- expect(extractFrontMatter(text, db)).toEqual('diagram');
- expect(db.setDiagramTitle).toHaveBeenCalledWith('foo');
+ expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
+ {
+ "metadata": {
+ "title": "foo",
+ },
+ "text": "diagram",
+ }
+ `);
});
it('handles booleans in frontmatter properly', () => {
- const db = dbMock();
const text = `---\ntitle: true\n---\ndiagram`;
- expect(extractFrontMatter(text, db)).toEqual('diagram');
- expect(db.setDiagramTitle).toHaveBeenCalledWith('true');
+ expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
+ {
+ "metadata": {
+ "title": "true",
+ },
+ "text": "diagram",
+ }
+ `);
});
it('ignores unspecified frontmatter keys', () => {
- const db = dbMock();
const text = `---\ninvalid: true\ntitle: foo\ntest: bar\n---\ndiagram`;
- expect(extractFrontMatter(text, db)).toEqual('diagram');
- expect(db.setDiagramTitle).toHaveBeenCalledWith('foo');
+ expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
+ {
+ "metadata": {
+ "title": "foo",
+ },
+ "text": "diagram",
+ }
+ `);
});
it('throws exception for invalid YAML syntax', () => {
const text = `---\n!!!\n---\ndiagram`;
- expect(() => extractFrontMatter(text, dbMock())).toThrow(
- 'tag suffix cannot contain exclamation marks'
- );
+ expect(() => extractFrontMatter(text)).toThrow('tag suffix cannot contain exclamation marks');
});
it('handles frontmatter with config', () => {
@@ -92,9 +147,25 @@ config:
array: [1, 2, 3]
---
diagram`;
- expect(extractFrontMatter(text, {}, setConfigMock)).toEqual('diagram');
- expect(setConfigMock).toHaveBeenCalledWith({
- graph: { string: 'hello', number: 14, boolean: false, array: [1, 2, 3] },
- });
+ expect(extractFrontMatter(text)).toMatchInlineSnapshot(`
+ {
+ "metadata": {
+ "config": {
+ "graph": {
+ "array": [
+ 1,
+ 2,
+ 3,
+ ],
+ "boolean": false,
+ "number": 14,
+ "string": "hello",
+ },
+ },
+ "title": "hello",
+ },
+ "text": "diagram",
+ }
+ `);
});
});
diff --git a/packages/mermaid/src/diagram-api/frontmatter.ts b/packages/mermaid/src/diagram-api/frontmatter.ts
index 0fd2917ea..c95e05f2c 100644
--- a/packages/mermaid/src/diagram-api/frontmatter.ts
+++ b/packages/mermaid/src/diagram-api/frontmatter.ts
@@ -1,6 +1,5 @@
import type { MermaidConfig } from '../config.type.js';
import { frontMatterRegex } from './regexes.js';
-import type { DiagramDB } from './types.js';
// The "* as yaml" part is necessary for tree-shaking
import * as yaml from 'js-yaml';
@@ -11,43 +10,51 @@ interface FrontMatterMetadata {
config?: MermaidConfig;
}
+export interface FrontMatterResult {
+ text: string;
+ metadata: FrontMatterMetadata;
+}
+
/**
* Extract and parse frontmatter from text, if present, and sets appropriate
* properties in the provided db.
* @param text - The text that may have a YAML frontmatter.
- * @param db - Diagram database, could be of any diagram.
- * @param setDiagramConfig - Optional function to set diagram config.
* @returns text with frontmatter stripped out
*/
-export function extractFrontMatter(
- text: string,
- db: DiagramDB,
- setDiagramConfig?: (config: MermaidConfig) => void
-): string {
+export function extractFrontMatter(text: string): FrontMatterResult {
const matches = text.match(frontMatterRegex);
if (!matches) {
- return text;
+ return {
+ text,
+ metadata: {},
+ };
}
- const parsed: FrontMatterMetadata = yaml.load(matches[1], {
- // To support config, we need JSON schema.
- // https://www.yaml.org/spec/1.2/spec.html#id2803231
- schema: yaml.JSON_SCHEMA,
- }) as FrontMatterMetadata;
+ let parsed: FrontMatterMetadata =
+ yaml.load(matches[1], {
+ // To support config, we need JSON schema.
+ // https://www.yaml.org/spec/1.2/spec.html#id2803231
+ schema: yaml.JSON_SCHEMA,
+ }) ?? {};
- if (parsed?.title) {
- // toString() is necessary because YAML could parse the title as a number/boolean
- db.setDiagramTitle?.(parsed.title.toString());
+ // To handle runtime data type changes
+ parsed = typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : {};
+
+ const metadata: FrontMatterMetadata = {};
+
+ // Only add properties that are explicitly supported, if they exist
+ if (parsed.displayMode) {
+ metadata.displayMode = parsed.displayMode.toString();
+ }
+ if (parsed.title) {
+ metadata.title = parsed.title.toString();
+ }
+ if (parsed.config) {
+ metadata.config = parsed.config;
}
- if (parsed?.displayMode) {
- // toString() is necessary because YAML could parse the title as a number/boolean
- db.setDisplayMode?.(parsed.displayMode.toString());
- }
-
- if (parsed?.config) {
- setDiagramConfig?.(parsed.config);
- }
-
- return text.slice(matches[0].length);
+ return {
+ text: text.slice(matches[0].length),
+ metadata,
+ };
}
diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts
index 2ac7fba12..58d98107e 100644
--- a/packages/mermaid/src/diagram-api/types.ts
+++ b/packages/mermaid/src/diagram-api/types.ts
@@ -1,7 +1,13 @@
+/* eslint-disable @typescript-eslint/no-explicit-any */
import type { Diagram } from '../Diagram.js';
import type { BaseDiagramConfig, MermaidConfig } from '../config.type.js';
import type * as d3 from 'd3';
+export interface DiagramMetadata {
+ title?: string;
+ config?: MermaidConfig;
+}
+
export interface InjectUtils {
_log: any;
_setLogLevel: any;
@@ -9,6 +15,7 @@ export interface InjectUtils {
_sanitizeText: any;
_setupGraphViewbox: any;
_commonDb: any;
+ /** @deprecated as directives will be pre-processed since https://github.com/mermaid-js/mermaid/pull/4759 */
_parseDirective: any;
}
@@ -32,9 +39,26 @@ export interface DiagramDB {
bindFunctions?: (element: Element) => void;
}
+// This is what is returned from getClasses(...) methods.
+// It is slightly renamed to ..StyleClassDef instead of just ClassDef because "class" is a greatly ambiguous and overloaded word.
+// It makes it clear we're working with a style class definition, even though defining the type is currently difficult.
+export interface DiagramStyleClassDef {
+ id: string;
+ styles?: string[];
+ textStyles?: string[];
+}
+
+export interface DiagramRenderer {
+ draw: DrawDefinition;
+ getClasses?: (
+ text: string,
+ diagram: Pick
+ ) => Record;
+}
+
export interface DiagramDefinition {
db: DiagramDB;
- renderer: any;
+ renderer: DiagramRenderer;
parser: ParserDefinition;
styles?: any;
init?: (config: MermaidConfig) => void;
@@ -45,6 +69,7 @@ export interface DiagramDefinition {
_sanitizeText: InjectUtils['_sanitizeText'],
_setupGraphViewbox: InjectUtils['_setupGraphViewbox'],
_commonDb: InjectUtils['_commonDb'],
+ /** @deprecated as directives will be pre-processed since https://github.com/mermaid-js/mermaid/pull/4759 */
_parseDirective: InjectUtils['_parseDirective']
) => void;
}
@@ -76,22 +101,13 @@ export type DrawDefinition = (
id: string,
version: string,
diagramObject: Diagram
-) => void;
+) => void | Promise;
export interface ParserDefinition {
parse: (text: string) => void;
parser: { yy: DiagramDB };
}
-/**
- * Type for function parse directive from diagram code.
- *
- * @param statement -
- * @param context -
- * @param type -
- */
-export type ParseDirectiveDefinition = (statement: string, context: string, type: string) => void;
-
export type HTML = d3.Selection;
export type SVG = d3.Selection;
diff --git a/packages/mermaid/src/diagram.spec.ts b/packages/mermaid/src/diagram.spec.ts
index 99ce4e2c6..19a65b716 100644
--- a/packages/mermaid/src/diagram.spec.ts
+++ b/packages/mermaid/src/diagram.spec.ts
@@ -34,7 +34,11 @@ describe('diagram detection', () => {
yy: {},
},
},
- renderer: {},
+ renderer: {
+ draw: () => {
+ // no-op
+ },
+ },
styles: {},
},
})
diff --git a/packages/mermaid/src/diagrams/c4/c4Db.js b/packages/mermaid/src/diagrams/c4/c4Db.js
index 7c450940f..71c178585 100644
--- a/packages/mermaid/src/diagrams/c4/c4Db.js
+++ b/packages/mermaid/src/diagrams/c4/c4Db.js
@@ -1,4 +1,3 @@
-import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import { sanitizeText } from '../common/common.js';
import {
@@ -38,10 +37,6 @@ export const setC4Type = function (c4TypeParam) {
c4Type = sanitizedText;
};
-export const parseDirective = function (statement, context, type) {
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
//type, from, to, label, ?techn, ?descr, ?sprite, ?tags, $link
export const addRel = function (type, from, to, label, techn, descr, sprite, tags, link) {
// Don't allow label nulling
@@ -821,7 +816,6 @@ export default {
getAccTitle,
getAccDescription,
setAccDescription,
- parseDirective,
getConfig: () => configApi.getConfig().c4,
clear,
LINETYPE,
diff --git a/packages/mermaid/src/diagrams/c4/c4Diagram.ts b/packages/mermaid/src/diagrams/c4/c4Diagram.ts
index 4c578b624..9557a0f70 100644
--- a/packages/mermaid/src/diagrams/c4/c4Diagram.ts
+++ b/packages/mermaid/src/diagrams/c4/c4Diagram.ts
@@ -1,17 +1,18 @@
// @ts-ignore: JISON doesn't support types
-import c4Parser from './parser/c4Diagram.jison';
-import c4Db from './c4Db.js';
-import c4Renderer from './c4Renderer.js';
-import c4Styles from './styles.js';
+import parser from './parser/c4Diagram.jison';
+import db from './c4Db.js';
+import renderer from './c4Renderer.js';
+import styles from './styles.js';
import type { MermaidConfig } from '../../config.type.js';
import type { DiagramDefinition } from '../../diagram-api/types.js';
export const diagram: DiagramDefinition = {
- parser: c4Parser,
- db: c4Db,
- renderer: c4Renderer,
- styles: c4Styles,
- init: (cnf: MermaidConfig) => {
- c4Renderer.setConf(cnf.c4);
+ parser,
+ db,
+ renderer,
+ styles,
+ init: ({ c4, wrap }: MermaidConfig) => {
+ renderer.setConf(c4);
+ db.setWrap(wrap);
},
};
diff --git a/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison b/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison
index 1dfa69ef1..a6bd8a6ec 100644
--- a/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison
+++ b/packages/mermaid/src/diagrams/c4/parser/c4Diagram.jison
@@ -72,25 +72,16 @@
%x string_kv_key
%x string_kv_value
-%x open_directive
-%x type_directive
-%x arg_directive
-%x close_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
%%
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
.*direction\s+TB[^\n]* return 'direction_tb';
.*direction\s+BT[^\n]* return 'direction_bt';
.*direction\s+RL[^\n]* return 'direction_rl';
.*direction\s+LR[^\n]* return 'direction_lr';
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
"title"\s[^#\n;]+ return 'title';
@@ -207,7 +198,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
start
: mermaidDoc
| direction
- | directive start
;
direction
@@ -225,26 +215,6 @@ mermaidDoc
: graphConfig
;
-directive
- : openDirective typeDirective closeDirective NEWLINE
- | openDirective typeDirective ':' argDirective closeDirective NEWLINE
- ;
-
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'c4Context'); }
- ;
graphConfig
: C4_CONTEXT NEWLINE statements EOF {yy.setC4Type($1)}
diff --git a/packages/mermaid/src/diagrams/class/classDb.ts b/packages/mermaid/src/diagrams/class/classDb.ts
index c9a202aa4..b2485267a 100644
--- a/packages/mermaid/src/diagrams/class/classDb.ts
+++ b/packages/mermaid/src/diagrams/class/classDb.ts
@@ -4,7 +4,6 @@ import { log } from '../../logger.js';
import * as configApi from '../../config.js';
import common from '../common/common.js';
import utils from '../../utils.js';
-import mermaidAPI from '../../mermaidAPI.js';
import {
setAccTitle,
getAccTitle,
@@ -37,11 +36,6 @@ let functions: any[] = [];
const sanitizeText = (txt: string) => common.sanitizeText(txt, configApi.getConfig());
-export const parseDirective = function (statement: string, context: string, type: string) {
- // @ts-ignore Don't wanna mess it up
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
const splitClassNameAndType = function (id: string) {
let genericType = '';
let className = id;
@@ -460,7 +454,6 @@ export const addClassesToNamespace = function (id: string, classNames: string[])
};
export default {
- parseDirective,
setAccTitle,
getAccTitle,
getAccDescription,
diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts
index b581252bf..5abfd769a 100644
--- a/packages/mermaid/src/diagrams/class/classRenderer-v2.ts
+++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.ts
@@ -8,7 +8,8 @@ import utils from '../../utils.js';
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
import common from '../common/common.js';
-import type { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js';
+import type { ClassRelation, ClassNote, ClassMap, NamespaceMap } from './classTypes.js';
+import type { EdgeData } from '../../types.js';
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
diff --git a/packages/mermaid/src/diagrams/class/classTypes.ts b/packages/mermaid/src/diagrams/class/classTypes.ts
index aa5ec7b70..d372feeba 100644
--- a/packages/mermaid/src/diagrams/class/classTypes.ts
+++ b/packages/mermaid/src/diagrams/class/classTypes.ts
@@ -137,24 +137,6 @@ export interface ClassNote {
text: string;
}
-export interface EdgeData {
- arrowheadStyle?: string;
- labelpos?: string;
- labelType?: string;
- label?: string;
- classes: string;
- pattern: string;
- id: string;
- arrowhead: string;
- startLabelRight: string;
- endLabelLeft: string;
- arrowTypeStart: string;
- arrowTypeEnd: string;
- style: string;
- labelStyle: string;
- curve: any;
-}
-
export type ClassRelation = {
id1: string;
id2: string;
diff --git a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison
index 9c67e306e..b1c2fad0e 100644
--- a/packages/mermaid/src/diagrams/class/parser/classDiagram.jison
+++ b/packages/mermaid/src/diagrams/class/parser/classDiagram.jison
@@ -13,9 +13,6 @@
%x href
%x callback_name
%x callback_args
-%x open_directive
-%x type_directive
-%x arg_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
@@ -24,15 +21,10 @@
%x namespace
%x namespace-body
%%
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
.*direction\s+TB[^\n]* return 'direction_tb';
.*direction\s+BT[^\n]* return 'direction_bt';
.*direction\s+RL[^\n]* return 'direction_rl';
.*direction\s+LR[^\n]* return 'direction_lr';
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)*[^\n]*(\r?\n?)+ /* skip comments */
\%\%[^\n]*(\r?\n)* /* skip comments */
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
@@ -220,7 +212,6 @@ line was introduced with 'click'.
start
: mermaidDoc
- | directive start
| statements
;
@@ -232,27 +223,6 @@ graphConfig
: CLASS_DIAGRAM NEWLINE statements EOF
;
-directive
- : openDirective typeDirective closeDirective NEWLINE
- | openDirective typeDirective ':' argDirective closeDirective NEWLINE
- ;
-
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'class'); }
- ;
-
statements
: statement
| statement NEWLINE
diff --git a/packages/mermaid/src/diagrams/er/erDb.js b/packages/mermaid/src/diagrams/er/erDb.js
index 01bbb585c..9a397597e 100644
--- a/packages/mermaid/src/diagrams/er/erDb.js
+++ b/packages/mermaid/src/diagrams/er/erDb.js
@@ -1,5 +1,4 @@
import { log } from '../../logger.js';
-import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import {
@@ -28,10 +27,6 @@ const Identification = {
IDENTIFYING: 'IDENTIFYING',
};
-export const parseDirective = function (statement, context, type) {
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
const addEntity = function (name, alias = undefined) {
if (entities[name] === undefined) {
entities[name] = { attributes: [], alias: alias };
@@ -88,7 +83,6 @@ const clear = function () {
export default {
Cardinality,
Identification,
- parseDirective,
getConfig: () => configApi.getConfig().er,
addEntity,
addAttributes,
diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison
index f39134607..135efc784 100644
--- a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison
+++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison
@@ -1,7 +1,7 @@
%lex
%options case-insensitive
-%x open_directive type_directive arg_directive block
+%x block
%x acc_title
%x acc_descr
%x acc_descr_multiline
@@ -14,11 +14,6 @@ accDescr\s*":"\s* { this.begin("ac
accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
[\}] { this.popState(); }
[^\}]* return "acc_descr_multiline_value";
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
[\n]+ return 'NEWLINE';
\s+ /* skip whitespace */
[\s]+ return 'SPACE';
@@ -77,7 +72,6 @@ o\{ return 'ZERO_OR_MORE';
start
: 'ER_DIAGRAM' document 'EOF' { /*console.log('finished parsing');*/ }
- | directive start
;
document
@@ -92,14 +86,9 @@ line
| EOF { $$=[];}
;
-directive
- : openDirective typeDirective closeDirective 'NEWLINE'
- | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE'
- ;
statement
- : directive
- | entityName relSpec entityName ':' role
+ : entityName relSpec entityName ':' role
{
yy.addEntity($1);
yy.addEntity($3);
@@ -191,20 +180,4 @@ role
| 'ALPHANUM' { $$ = $1; }
;
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'er'); }
- ;
-
%%
diff --git a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js
index c7bfdf524..737b492fb 100644
--- a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js
+++ b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js
@@ -4,13 +4,14 @@ import insertMarkers from '../../../dagre-wrapper/markers.js';
import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js';
import { findCommonAncestor } from './render-utils.js';
import { labelHelper } from '../../../dagre-wrapper/shapes/util.js';
-import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js';
import { getConfig } from '../../../config.js';
import { log } from '../../../logger.js';
import { setupGraphViewbox } from '../../../setupGraphViewbox.js';
-import common, { evaluate } from '../../common/common.js';
+import common from '../../common/common.js';
import { interpolateToCurve, getStylesFromArray } from '../../../utils.js';
import ELK from 'elkjs/lib/elk.bundled.js';
+import { getLineFunctionsWithOffset } from '../../../utils/lineWithOffset.js';
+
const elk = new ELK();
let portPos = {};
@@ -568,8 +569,9 @@ export const addEdges = function (edges, diagObj, graph, svg) {
* @param edgeData
* @param diagramType
* @param arrowMarkerAbsolute
+ * @param id
*/
-const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAbsolute) {
+const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAbsolute, id) {
let url = '';
// Check configuration for absolute path
if (arrowMarkerAbsolute) {
@@ -586,61 +588,103 @@ const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAb
// look in edge data and decide which marker to use
switch (edgeData.arrowTypeStart) {
case 'arrow_cross':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-crossStart' + ')'
+ );
break;
case 'arrow_point':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-pointStart' + ')'
+ );
break;
case 'arrow_barb':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-barbStart' + ')'
+ );
break;
case 'arrow_circle':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-circleStart' + ')'
+ );
break;
case 'aggregation':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-aggregationStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-aggregationStart' + ')'
+ );
break;
case 'extension':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-extensionStart' + ')'
+ );
break;
case 'composition':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-compositionStart' + ')'
+ );
break;
case 'dependency':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-dependencyStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-dependencyStart' + ')'
+ );
break;
case 'lollipop':
- svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-lollipopStart' + ')');
+ svgPath.attr(
+ 'marker-start',
+ 'url(' + url + '#' + id + '_' + diagramType + '-lollipopStart' + ')'
+ );
break;
default:
}
switch (edgeData.arrowTypeEnd) {
case 'arrow_cross':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')');
+ svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-crossEnd' + ')');
break;
case 'arrow_point':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')');
+ svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-pointEnd' + ')');
break;
case 'arrow_barb':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')');
+ svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-barbEnd' + ')');
break;
case 'arrow_circle':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')');
+ svgPath.attr('marker-end', 'url(' + url + '#' + id + '_' + diagramType + '-circleEnd' + ')');
break;
case 'aggregation':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-aggregationEnd' + ')');
+ svgPath.attr(
+ 'marker-end',
+ 'url(' + url + '#' + id + '_' + diagramType + '-aggregationEnd' + ')'
+ );
break;
case 'extension':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')');
+ svgPath.attr(
+ 'marker-end',
+ 'url(' + url + '#' + id + '_' + diagramType + '-extensionEnd' + ')'
+ );
break;
case 'composition':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')');
+ svgPath.attr(
+ 'marker-end',
+ 'url(' + url + '#' + id + '_' + diagramType + '-compositionEnd' + ')'
+ );
break;
case 'dependency':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-dependencyEnd' + ')');
+ svgPath.attr(
+ 'marker-end',
+ 'url(' + url + '#' + id + '_' + diagramType + '-dependencyEnd' + ')'
+ );
break;
case 'lollipop':
- svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-lollipopEnd' + ')');
+ svgPath.attr(
+ 'marker-end',
+ 'url(' + url + '#' + id + '_' + diagramType + '-lollipopEnd' + ')'
+ );
break;
default:
}
@@ -651,7 +695,7 @@ const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAb
*
* @param text
* @param diagObj
- * @returns {object} ClassDef styles
+ * @returns {Record} ClassDef styles
*/
export const getClasses = function (text, diagObj) {
log.info('Extracting classes');
@@ -691,7 +735,7 @@ const calcOffset = function (src, dest, parentLookupDb) {
return { x: ancestorOffset.posX, y: ancestorOffset.posY };
};
-const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
+const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb, id) {
const offset = calcOffset(edge.sourceId, edge.targetId, parentLookupDb);
const src = edge.sections[0].startPoint;
@@ -705,8 +749,8 @@ const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
[dest.x + offset.x, dest.y + offset.y],
];
- // const curve = line().curve(curveBasis);
- const curve = line().curve(curveLinear);
+ const { x, y } = getLineFunctionsWithOffset(edge.edgeData);
+ const curve = line().x(x).y(y).curve(curveLinear);
const edgePath = edgesEl
.insert('path')
.attr('d', curve(points))
@@ -722,7 +766,7 @@ const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) {
'transform',
`translate(${edge.labels[0].x + offset.x}, ${edge.labels[0].y + offset.y})`
);
- addMarkersToEdge(edgePath, edgeData, diagObj.type, diagObj.arrowMarkerAbsolute);
+ addMarkersToEdge(edgePath, edgeData, diagObj.type, diagObj.arrowMarkerAbsolute, id);
};
/**
@@ -815,7 +859,7 @@ export const draw = async function (text, id, _version, diagObj) {
const markers = ['point', 'circle', 'cross'];
// Add the marker definitions to the svg as marker tags
- insertMarkers(svg, markers, diagObj.type, diagObj.arrowMarkerAbsolute);
+ insertMarkers(svg, markers, diagObj.type, id);
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const vert = diagObj.db.getVertices();
@@ -894,7 +938,7 @@ export const draw = async function (text, id, _version, diagObj) {
drawNodes(0, 0, g.children, svg, subGraphsEl, diagObj, 0);
log.info('after layout', g);
g.edges?.map((edge) => {
- insertEdge(edgesEl, edge, edge.edgeData, diagObj, parentLookupDb);
+ insertEdge(edgesEl, edge, edge.edgeData, diagObj, parentLookupDb, id);
});
setupGraphViewbox({}, svg, conf.diagramPadding, conf.useMaxWidth);
// Remove element after layout
diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.js b/packages/mermaid/src/diagrams/flowchart/flowDb.js
index 2bb50abee..a87bf558d 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowDb.js
+++ b/packages/mermaid/src/diagrams/flowchart/flowDb.js
@@ -2,7 +2,6 @@ import { select } from 'd3';
import utils from '../../utils.js';
import * as configApi from '../../config.js';
import common from '../common/common.js';
-import mermaidAPI from '../../mermaidAPI.js';
import { log } from '../../logger.js';
import {
setAccTitle,
@@ -34,10 +33,6 @@ let funs = [];
const sanitizeText = (txt) => common.sanitizeText(txt, config);
-export const parseDirective = function (statement, context, type) {
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
/**
* Function to lookup domId from id in the graph definition.
*
@@ -771,7 +766,6 @@ export const lex = {
firstGraph,
};
export default {
- parseDirective,
defaultConfig: () => configApi.defaultConfig.flowchart,
setAccTitle,
getAccTitle,
diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
index 4a3b7a8ce..576ee6b34 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
+++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js
@@ -338,7 +338,7 @@ export const addEdges = function (edges, g, diagObj) {
*
* @param text
* @param diagObj
- * @returns {object} ClassDef styles
+ * @returns {Record} ClassDef styles
*/
export const getClasses = function (text, diagObj) {
return diagObj.db.getClasses();
diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js
index fc06cacd4..8394b41e8 100644
--- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js
+++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js
@@ -269,7 +269,7 @@ export const addEdges = function (edges, g, diagObj) {
*
* @param text
* @param diagObj
- * @returns {object} ClassDef styles
+ * @returns {Record} ClassDef styles
*/
export const getClasses = function (text, diagObj) {
log.info('Extracting classes');
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
index 8d746f808..6dad36d25 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
@@ -23,17 +23,8 @@
%x href
%x callbackname
%x callbackargs
-%x open_directive
-%x type_directive
-%x arg_directive
-%x close_directive
%%
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
@@ -272,35 +263,10 @@ that id.
%% /* language grammar */
start
- : mermaidDoc
- | directive start
- ;
-
-directive
- : openDirective typeDirective closeDirective separator
- | openDirective typeDirective ':' argDirective closeDirective separator
- ;
-
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($type_directive, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $arg_directive = $arg_directive.trim().replace(/'/g, '"'); yy.parseDirective($arg_directive, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'flowchart'); }
- ;
-
-mermaidDoc
: graphConfig document
;
+
document
: /* empty */
{ $$ = [];}
diff --git a/packages/mermaid/src/diagrams/gantt/ganttDb.js b/packages/mermaid/src/diagrams/gantt/ganttDb.js
index 3ff7955f8..775494e3d 100644
--- a/packages/mermaid/src/diagrams/gantt/ganttDb.js
+++ b/packages/mermaid/src/diagrams/gantt/ganttDb.js
@@ -6,7 +6,6 @@ import dayjsAdvancedFormat from 'dayjs/plugin/advancedFormat.js';
import { log } from '../../logger.js';
import * as configApi from '../../config.js';
import utils from '../../utils.js';
-import mermaidAPI from '../../mermaidAPI.js';
import {
setAccTitle,
@@ -42,10 +41,6 @@ let weekday = 'sunday';
// The serial order of the task in the script
let lastOrder = 0;
-export const parseDirective = function (statement, context, type) {
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
export const clear = function () {
sections = [];
tasks = [];
@@ -730,7 +725,6 @@ export const bindFunctions = function (element) {
};
export default {
- parseDirective,
getConfig: () => configApi.getConfig().gantt,
clear,
setDateFormat,
diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js
index 522f59e2c..935ecc928 100644
--- a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js
+++ b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js
@@ -10,6 +10,8 @@ import {
axisBottom,
axisTop,
timeFormat,
+ timeMillisecond,
+ timeSecond,
timeMinute,
timeHour,
timeDay,
@@ -573,7 +575,7 @@ export const draw = function (text, id, version, diagObj) {
.tickSize(-h + theTopPad + conf.gridLineStartPadding)
.tickFormat(timeFormat(diagObj.db.getAxisFormat() || conf.axisFormat || '%Y-%m-%d'));
- const reTickInterval = /^([1-9]\d*)(minute|hour|day|week|month)$/;
+ const reTickInterval = /^([1-9]\d*)(millisecond|second|minute|hour|day|week|month)$/;
const resultTickInterval = reTickInterval.exec(
diagObj.db.getTickInterval() || conf.tickInterval
);
@@ -584,6 +586,12 @@ export const draw = function (text, id, version, diagObj) {
const weekday = diagObj.db.getWeekday() || conf.weekday;
switch (interval) {
+ case 'millisecond':
+ bottomXAxis.ticks(timeMillisecond.every(every));
+ break;
+ case 'second':
+ bottomXAxis.ticks(timeSecond.every(every));
+ break;
case 'minute':
bottomXAxis.ticks(timeMinute.every(every));
break;
@@ -625,6 +633,12 @@ export const draw = function (text, id, version, diagObj) {
const weekday = diagObj.db.getWeekday() || conf.weekday;
switch (interval) {
+ case 'millisecond':
+ topXAxis.ticks(timeMillisecond.every(every));
+ break;
+ case 'second':
+ topXAxis.ticks(timeSecond.every(every));
+ break;
case 'minute':
topXAxis.ticks(timeMinute.every(every));
break;
diff --git a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
index f7fd40c1b..b4daab5dc 100644
--- a/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
+++ b/packages/mermaid/src/diagrams/gantt/parser/gantt.jison
@@ -11,19 +11,11 @@
%x href
%x callbackname
%x callbackargs
-%x open_directive
-%x type_directive
-%x arg_directive
-%x close_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
%%
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
@@ -112,8 +104,7 @@ weekday\s+sunday return 'weekday_sunday'
%% /* language grammar */
start
- : directive start
- | gantt document 'EOF' { return $2; }
+ : gantt document 'EOF' { return $2; }
;
document
@@ -155,13 +146,8 @@ statement
| section { yy.addSection($1.substr(8));$$=$1.substr(8); }
| clickStatement
| taskTxt taskData {yy.addTask($1,$2);$$='task';}
- | directive
;
-directive
- : openDirective typeDirective closeDirective 'NL'
- | openDirective typeDirective ':' argDirective closeDirective 'NL'
- ;
/*
click allows any combination of href and call.
@@ -192,20 +178,4 @@ clickStatementDebug
| click href {$$=$1 + ' ' + $2;}
;
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'gantt'); }
- ;
-
%%
diff --git a/packages/mermaid/src/diagrams/git/gitGraphAst.js b/packages/mermaid/src/diagrams/git/gitGraphAst.js
index dc9c32da9..abad68b22 100644
--- a/packages/mermaid/src/diagrams/git/gitGraphAst.js
+++ b/packages/mermaid/src/diagrams/git/gitGraphAst.js
@@ -1,6 +1,5 @@
import { log } from '../../logger.js';
import { random } from '../../utils.js';
-import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import { getConfig } from '../../config.js';
import common from '../common/common.js';
@@ -33,10 +32,6 @@ function getId() {
return random({ length: 7 });
}
-export const parseDirective = function (statement, context, type) {
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
// /**
// * @param currentCommit
// * @param otherCommit
@@ -507,7 +502,6 @@ export const commitType = {
};
export default {
- parseDirective,
getConfig: () => configApi.getConfig().gitGraph,
setDirection,
setOptions,
diff --git a/packages/mermaid/src/diagrams/git/gitGraphParser.spec b/packages/mermaid/src/diagrams/git/gitGraphParser.spec.js
similarity index 62%
rename from packages/mermaid/src/diagrams/git/gitGraphParser.spec
rename to packages/mermaid/src/diagrams/git/gitGraphParser.spec.js
index 0a8256c5b..446f06739 100644
--- a/packages/mermaid/src/diagrams/git/gitGraphParser.spec
+++ b/packages/mermaid/src/diagrams/git/gitGraphParser.spec.js
@@ -1,87 +1,72 @@
+import gitGraphAst from './gitGraphAst.js';
+import { parser } from './parser/gitGraph.jison';
-// Todo reintroduce without cryptoRandomString
-import gitGraphAst from './gitGraphAst';
-import { parser } from './parser/gitGraph';
-import randomString from 'crypto-random-string';
-import cryptoRandomString from 'crypto-random-string';
-
-jest.mock('crypto-random-string');
-
-describe('when parsing a gitGraph', function() {
- let randomNumber;
- beforeEach(function() {
+describe('when parsing a gitGraph', function () {
+ beforeEach(function () {
parser.yy = gitGraphAst;
parser.yy.clear();
- randomNumber = 0;
- cryptoRandomString.mockImplementation(() => {
- randomNumber = randomNumber + 1;
- return String(randomNumber);
- });
});
- afterEach(function() {
- cryptoRandomString.mockReset();
- });
- it('should handle a gitGraph definition', function() {
+ it('should handle a gitGraph definition', function () {
const str = 'gitGraph:\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(1);
- expect(parser.yy.getCurrentBranch()).toBe('master');
+ expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('LR');
expect(Object.keys(parser.yy.getBranches()).length).toBe(1);
});
- it('should handle a gitGraph definition with empty options', function() {
- const str = 'gitGraph:\n' + 'options\n' + 'end\n' + 'commit\n';
+ it('should handle a gitGraph definition with empty options', function () {
+ const str = 'gitGraph:\n' + 'options\n' + ' end\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(parser.yy.getOptions()).toEqual({});
expect(Object.keys(commits).length).toBe(1);
- expect(parser.yy.getCurrentBranch()).toBe('master');
+ expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('LR');
expect(Object.keys(parser.yy.getBranches()).length).toBe(1);
});
- it('should handle a gitGraph definition with valid options', function() {
+ it('should handle a gitGraph definition with valid options', function () {
const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"}\n' + 'end\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(parser.yy.getOptions()['key']).toBe('value');
expect(Object.keys(commits).length).toBe(1);
- expect(parser.yy.getCurrentBranch()).toBe('master');
+ expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('LR');
expect(Object.keys(parser.yy.getBranches()).length).toBe(1);
});
- it('should not fail on a gitGraph with malformed json', function() {
+ it('should not fail on a gitGraph with malformed json', function () {
const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"\n' + 'end\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(1);
- expect(parser.yy.getCurrentBranch()).toBe('master');
+ expect(parser.yy.getCurrentBranch()).toBe('main');
expect(parser.yy.getDirection()).toBe('LR');
expect(Object.keys(parser.yy.getBranches()).length).toBe(1);
});
- it('should handle set direction', function() {
- const str = 'gitGraph BT:\n' + 'commit\n';
+ it('should handle set direction', function () {
+ const str = 'gitGraph TB:\n' + 'commit\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(1);
- expect(parser.yy.getCurrentBranch()).toBe('master');
- expect(parser.yy.getDirection()).toBe('BT');
+ expect(parser.yy.getCurrentBranch()).toBe('main');
+ expect(parser.yy.getDirection()).toBe('TB');
expect(Object.keys(parser.yy.getBranches()).length).toBe(1);
});
- it('should checkout a branch', function() {
+ it('should checkout a branch', function () {
const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n';
parser.parse(str);
@@ -91,7 +76,7 @@ describe('when parsing a gitGraph', function() {
expect(parser.yy.getCurrentBranch()).toBe('new');
});
- it('should add commits to checked out branch', function() {
+ it('should add commits to checked out branch', function () {
const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n' + 'commit\n' + 'commit\n';
parser.parse(str);
@@ -103,7 +88,7 @@ describe('when parsing a gitGraph', function() {
expect(branchCommit).not.toBeNull();
expect(commits[branchCommit].parent).not.toBeNull();
});
- it('should handle commit with args', function() {
+ it('should handle commit with args', function () {
const str = 'gitGraph:\n' + 'commit "a commit"\n';
parser.parse(str);
@@ -112,10 +97,11 @@ describe('when parsing a gitGraph', function() {
expect(Object.keys(commits).length).toBe(1);
const key = Object.keys(commits)[0];
expect(commits[key].message).toBe('a commit');
- expect(parser.yy.getCurrentBranch()).toBe('master');
+ expect(parser.yy.getCurrentBranch()).toBe('main');
});
- it('should reset a branch', function() {
+ // Reset has been commented out in JISON
+ it.skip('should reset a branch', function () {
const str =
'gitGraph:\n' +
'commit\n' +
@@ -123,18 +109,18 @@ describe('when parsing a gitGraph', function() {
'branch newbranch\n' +
'checkout newbranch\n' +
'commit\n' +
- 'reset master\n';
+ 'reset main\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(3);
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
- expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']);
+ expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['main']);
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']);
});
- it('reset can take an argument', function() {
+ it.skip('reset can take an argument', function () {
const str =
'gitGraph:\n' +
'commit\n' +
@@ -142,18 +128,18 @@ describe('when parsing a gitGraph', function() {
'branch newbranch\n' +
'checkout newbranch\n' +
'commit\n' +
- 'reset master^\n';
+ 'reset main^\n';
parser.parse(str);
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(3);
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
- const master = commits[parser.yy.getBranches()['master']];
- expect(parser.yy.getHead().id).toEqual(master.parent);
+ const main = commits[parser.yy.getBranches()['main']];
+ expect(parser.yy.getHead().id).toEqual(main.parent);
});
- it('should handle fast forwardable merges', function() {
+ it.skip('should handle fast forwardable merges', function () {
const str =
'gitGraph:\n' +
'commit\n' +
@@ -161,19 +147,19 @@ describe('when parsing a gitGraph', function() {
'checkout newbranch\n' +
'commit\n' +
'commit\n' +
- 'checkout master\n' +
+ 'checkout main\n' +
'merge newbranch\n';
parser.parse(str);
const commits = parser.yy.getCommits();
- expect(Object.keys(commits).length).toBe(3);
- expect(parser.yy.getCurrentBranch()).toBe('master');
- expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']);
+ expect(Object.keys(commits).length).toBe(4);
+ expect(parser.yy.getCurrentBranch()).toBe('main');
+ expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['main']);
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']);
});
- it('should handle cases when merge is a noop', function() {
+ it('should handle cases when merge is a noop', function () {
const str =
'gitGraph:\n' +
'commit\n' +
@@ -181,18 +167,18 @@ describe('when parsing a gitGraph', function() {
'checkout newbranch\n' +
'commit\n' +
'commit\n' +
- 'merge master\n';
+ 'merge main\n';
parser.parse(str);
const commits = parser.yy.getCommits();
- expect(Object.keys(commits).length).toBe(3);
+ expect(Object.keys(commits).length).toBe(4);
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
- expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']);
+ expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['main']);
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['newbranch']);
});
- it('should handle merge with 2 parents', function() {
+ it('should handle merge with 2 parents', function () {
const str =
'gitGraph:\n' +
'commit\n' +
@@ -200,7 +186,7 @@ describe('when parsing a gitGraph', function() {
'checkout newbranch\n' +
'commit\n' +
'commit\n' +
- 'checkout master\n' +
+ 'checkout main\n' +
'commit\n' +
'merge newbranch\n';
@@ -208,12 +194,12 @@ describe('when parsing a gitGraph', function() {
const commits = parser.yy.getCommits();
expect(Object.keys(commits).length).toBe(5);
- expect(parser.yy.getCurrentBranch()).toBe('master');
- expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['master']);
- expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']);
+ expect(parser.yy.getCurrentBranch()).toBe('main');
+ expect(parser.yy.getBranches()['newbranch']).not.toEqual(parser.yy.getBranches()['main']);
+ expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['main']);
});
- it('should handle ff merge when history walk has two parents (merge commit)', function() {
+ it.skip('should handle ff merge when history walk has two parents (merge commit)', function () {
const str =
'gitGraph:\n' +
'commit\n' +
@@ -221,53 +207,25 @@ describe('when parsing a gitGraph', function() {
'checkout newbranch\n' +
'commit\n' +
'commit\n' +
- 'checkout master\n' +
+ 'checkout main\n' +
'commit\n' +
'merge newbranch\n' +
'commit\n' +
'checkout newbranch\n' +
- 'merge master\n';
+ 'merge main\n';
parser.parse(str);
const commits = parser.yy.getCommits();
- expect(Object.keys(commits).length).toBe(6);
+ expect(Object.keys(commits).length).toBe(7);
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
- expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['master']);
- expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['master']);
+ expect(parser.yy.getBranches()['newbranch']).toEqual(parser.yy.getBranches()['main']);
+ expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches()['main']);
parser.yy.prettyPrint();
});
- it('should generate a secure random ID for commits', function() {
- const str = 'gitGraph:\n' + 'commit\n' + 'commit\n';
- const EXPECTED_LENGTH = 7;
- const EXPECTED_CHARACTERS = '0123456789abcdef';
-
- let idCount = 0;
- randomString.mockImplementation(options => {
- if (
- options.length === EXPECTED_LENGTH &&
- options.characters === EXPECTED_CHARACTERS &&
- Object.keys(options).length === 2
- ) {
- const id = `abcdef${idCount}`;
- idCount += 1;
- return id;
- }
- return 'unexpected-ID';
- });
-
- parser.parse(str);
- const commits = parser.yy.getCommits();
-
- expect(Object.keys(commits)).toEqual(['abcdef0', 'abcdef1']);
- Object.keys(commits).forEach(key => {
- expect(commits[key].id).toEqual(key);
- });
- });
-
- it('should generate an array of known branches', function() {
+ it('should generate an array of known branches', function () {
const str =
'gitGraph:\n' +
'commit\n' +
@@ -281,7 +239,7 @@ describe('when parsing a gitGraph', function() {
const branches = gitGraphAst.getBranchesAsObjArray();
expect(branches).toHaveLength(3);
- expect(branches[0]).toHaveProperty('name', 'master');
+ expect(branches[0]).toHaveProperty('name', 'main');
expect(branches[1]).toHaveProperty('name', 'b1');
expect(branches[2]).toHaveProperty('name', 'b2');
});
diff --git a/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js b/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js
index 764fbb214..df20a5eb5 100644
--- a/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js
+++ b/packages/mermaid/src/diagrams/git/gitGraphParserV2.spec.js
@@ -1,22 +1,11 @@
-/* eslint-env jasmine */
-// Todo reintroduce without cryptoRandomString
import gitGraphAst from './gitGraphAst.js';
import { parser } from './parser/gitGraph.jison';
-//import randomString from 'crypto-random-string';
-//import cryptoRandomString from 'crypto-random-string';
-
-//jest.mock('crypto-random-string');
describe('when parsing a gitGraph', function () {
- let randomNumber;
beforeEach(function () {
parser.yy = gitGraphAst;
parser.yy.clear();
- randomNumber = 0;
});
- // afterEach(function() {
- // cryptoRandomString.mockReset();
- // });
it('should handle a gitGraph commit with NO pararms, get auto-genrated reandom ID', function () {
const str = `gitGraph:
commit
diff --git a/packages/mermaid/src/diagrams/git/parser/gitGraph.jison b/packages/mermaid/src/diagrams/git/parser/gitGraph.jison
index 9ff5623f8..2297db17c 100644
--- a/packages/mermaid/src/diagrams/git/parser/gitGraph.jison
+++ b/packages/mermaid/src/diagrams/git/parser/gitGraph.jison
@@ -9,10 +9,6 @@
%x string
%x options
-%x open_directive
-%x type_directive
-%x arg_directive
-%x close_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
@@ -20,11 +16,6 @@
%%
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
@@ -76,7 +67,6 @@ checkout(?=\s|$) return 'CHECKOUT';
start
: eol start
- | directive start
| GG document EOF{ return $3; }
| GG ':' document EOF{ return $3; }
| GG DIR ':' document EOF {yy.setDirection($2); return $4;}
@@ -240,27 +230,6 @@ commitType
| HIGHLIGHT { $$=yy.commitType.HIGHLIGHT;}
;
-directive
- : openDirective typeDirective closeDirective
- | openDirective typeDirective ':' argDirective closeDirective
- ;
-
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'gitGraph'); }
- ;
-
ref
: ID
| STR
diff --git a/packages/mermaid/src/diagrams/pie/parser/pie.jison b/packages/mermaid/src/diagrams/pie/parser/pie.jison
index e98638aa8..d1f516e75 100644
--- a/packages/mermaid/src/diagrams/pie/parser/pie.jison
+++ b/packages/mermaid/src/diagrams/pie/parser/pie.jison
@@ -8,19 +8,10 @@
%x string
%x title
-%x open_directive
-%x type_directive
-%x arg_directive
-%x close_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
%%
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */{ /*console.log('');*/ }
[\n\r]+ return 'NEWLINE';
@@ -52,7 +43,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
start
: eol start
- | directive start
| PIE document
| PIE showData document {yy.setShowData(true);}
;
@@ -73,34 +63,12 @@ statement
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);}
- | directive
;
-directive
- : openDirective typeDirective closeDirective
- | openDirective typeDirective ':' argDirective closeDirective
- ;
-
eol
: NEWLINE
| ';'
| EOF
;
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'pie'); }
- ;
-
%%
diff --git a/packages/mermaid/src/diagrams/pie/pie.spec.ts b/packages/mermaid/src/diagrams/pie/pie.spec.ts
index 7c8e0809a..564e12f0f 100644
--- a/packages/mermaid/src/diagrams/pie/pie.spec.ts
+++ b/packages/mermaid/src/diagrams/pie/pie.spec.ts
@@ -62,17 +62,6 @@ describe('pie', () => {
expect(sections['bat']).toBe(40);
});
- it('should handle simple pie with a directive', () => {
- parser.parse(`%%{init: {'logLevel':0}}%%
- pie
- "ash" : 60
- "bat" : 40
- `);
- const sections = db.getSections();
- expect(sections['ash']).toBe(60);
- expect(sections['bat']).toBe(40);
- });
-
it('should handle simple pie with a title', () => {
parser.parse(`pie title a 60/40 pie
"ash" : 60
diff --git a/packages/mermaid/src/diagrams/pie/pieDb.ts b/packages/mermaid/src/diagrams/pie/pieDb.ts
index 7f209de46..ce82216dc 100644
--- a/packages/mermaid/src/diagrams/pie/pieDb.ts
+++ b/packages/mermaid/src/diagrams/pie/pieDb.ts
@@ -1,5 +1,4 @@
import { log } from '../../logger.js';
-import { parseDirective as _parseDirective } from '../../directiveUtils.js';
import { getConfig as commonGetConfig } from '../../config.js';
import { sanitizeText } from '../common/common.js';
import {
@@ -11,7 +10,6 @@ import {
setAccDescription,
clear as commonClear,
} from '../common/commonDb.js';
-import type { ParseDirectiveDefinition } from '../../diagram-api/types.js';
import type { PieFields, PieDB, Sections } from './pieTypes.js';
import type { RequiredDeep } from 'type-fest';
import type { PieDiagramConfig } from '../../config.type.js';
@@ -31,10 +29,6 @@ const config: Required = structuredClone(DEFAULT_PIE_CONFIG);
const getConfig = (): Required => structuredClone(config);
-const parseDirective: ParseDirectiveDefinition = (statement, context, type) => {
- _parseDirective(this, statement, context, type);
-};
-
const clear = (): void => {
sections = structuredClone(DEFAULT_PIE_DB.sections);
showData = DEFAULT_PIE_DB.showData;
@@ -67,7 +61,6 @@ const getShowData = (): boolean => showData;
export const db: PieDB = {
getConfig,
- parseDirective,
clear,
setDiagramTitle,
getDiagramTitle,
diff --git a/packages/mermaid/src/diagrams/pie/pieTypes.ts b/packages/mermaid/src/diagrams/pie/pieTypes.ts
index 67fb1dca2..6ba3ab92e 100644
--- a/packages/mermaid/src/diagrams/pie/pieTypes.ts
+++ b/packages/mermaid/src/diagrams/pie/pieTypes.ts
@@ -1,5 +1,5 @@
import type { PieDiagramConfig } from '../../config.type.js';
-import type { DiagramDB, ParseDirectiveDefinition } from '../../diagram-api/types.js';
+import type { DiagramDB } from '../../diagram-api/types.js';
export interface PieFields {
sections: Sections;
@@ -46,7 +46,6 @@ export interface PieDB extends DiagramDB {
getConfig: () => Required;
// common db
- parseDirective: ParseDirectiveDefinition;
clear: () => void;
setDiagramTitle: (title: string) => void;
getDiagramTitle: () => string;
diff --git a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison
index 00c125294..255b30a03 100644
--- a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison
+++ b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison
@@ -5,10 +5,6 @@
%x string
%x md_string
%x title
-%x open_directive
-%x type_directive
-%x arg_directive
-%x close_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
@@ -16,11 +12,6 @@
%x point_x
%x point_y
%%
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[\n\r]+ return 'NEWLINE';
@@ -87,7 +78,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
start
: eol start
| SPACE start
- | directive start
| QUADRANT document
;
@@ -110,7 +100,6 @@ statement
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);}
- | directive
;
points
@@ -133,33 +122,12 @@ quadrantDetails
| QUADRANT_4 text {yy.setQuadrant4Text($2)}
;
-directive
- : openDirective typeDirective closeDirective
- | openDirective typeDirective ':' argDirective closeDirective
- ;
-
eol
: NEWLINE
| SEMI
| EOF
;
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'quadrantChart'); }
- ;
-
text: alphaNumToken
{ $$={text:$1, type: 'text'};}
| text textNoTagsToken
diff --git a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts
index faa9281f0..d10cb2134 100644
--- a/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts
+++ b/packages/mermaid/src/diagrams/quadrant-chart/parser/quadrant.jison.spec.ts
@@ -19,7 +19,6 @@ const mockDB: Record> = {
setYAxisTopText: vi.fn(),
setYAxisBottomText: vi.fn(),
setDiagramTitle: vi.fn(),
- parseDirective: vi.fn(),
addPoint: vi.fn(),
};
@@ -45,23 +44,6 @@ describe('Testing quadrantChart jison file', () => {
expect(parserFnConstructor(str)).not.toThrow();
});
- it('should be able to parse directive', () => {
- const str =
- '%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%% \n quadrantChart';
- expect(parserFnConstructor(str)).not.toThrow();
- expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']);
- expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']);
- expect(mockDB.parseDirective.mock.calls[2]).toEqual([
- '{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }',
- 'arg_directive',
- ]);
- expect(mockDB.parseDirective.mock.calls[3]).toEqual([
- '}%%',
- 'close_directive',
- 'quadrantChart',
- ]);
- });
-
it('should be able to parse xAxis text', () => {
let str = 'quadrantChart\nx-axis urgent --> not urgent';
expect(parserFnConstructor(str)).not.toThrow();
@@ -243,8 +225,7 @@ describe('Testing quadrantChart jison file', () => {
});
it('should be able to parse the whole chart', () => {
- const str = `%%{init: {"quadrantChart": {"chartWidth": 600, "chartHeight": 600} } }%%
- quadrantChart
+ const str = `quadrantChart
title Analytics and Business Intelligence Platforms
x-axis "Completeness of Vision ❤" --> "x-axis-2"
y-axis Ability to Execute --> "y-axis-2"
@@ -258,17 +239,6 @@ describe('Testing quadrantChart jison file', () => {
Incorta: [0.20, 0.30]`;
expect(parserFnConstructor(str)).not.toThrow();
- expect(mockDB.parseDirective.mock.calls[0]).toEqual(['%%{', 'open_directive']);
- expect(mockDB.parseDirective.mock.calls[1]).toEqual(['init', 'type_directive']);
- expect(mockDB.parseDirective.mock.calls[2]).toEqual([
- '{"quadrantChart": {"chartWidth": 600, "chartHeight": 600} }',
- 'arg_directive',
- ]);
- expect(mockDB.parseDirective.mock.calls[3]).toEqual([
- '}%%',
- 'close_directive',
- 'quadrantChart',
- ]);
expect(mockDB.setXAxisLeftText).toHaveBeenCalledWith({
text: 'Completeness of Vision ❤',
type: 'text',
diff --git a/packages/mermaid/src/diagrams/quadrant-chart/quadrantBuilder.ts b/packages/mermaid/src/diagrams/quadrant-chart/quadrantBuilder.ts
index 9c1162762..5b740b0e0 100644
--- a/packages/mermaid/src/diagrams/quadrant-chart/quadrantBuilder.ts
+++ b/packages/mermaid/src/diagrams/quadrant-chart/quadrantBuilder.ts
@@ -4,17 +4,13 @@ import { log } from '../../logger.js';
import type { BaseDiagramConfig, QuadrantChartConfig } from '../../config.type.js';
import defaultConfig from '../../defaultConfig.js';
import { getThemeVariables } from '../../themes/theme-default.js';
+import type { Point } from '../../types.js';
const defaultThemeVariables = getThemeVariables();
export type TextVerticalPos = 'left' | 'center' | 'right';
export type TextHorizontalPos = 'top' | 'middle' | 'bottom';
-export interface Point {
- x: number;
- y: number;
-}
-
export interface QuadrantPointInputType extends Point {
text: string;
}
diff --git a/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts b/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts
index 0dc87a2d4..0dad6dfdd 100644
--- a/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts
+++ b/packages/mermaid/src/diagrams/quadrant-chart/quadrantDb.ts
@@ -1,5 +1,3 @@
-import { log } from '../../logger.js';
-import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import { sanitizeText } from '../common/common.js';
import {
@@ -94,11 +92,6 @@ function getQuadrantData() {
return quadrantBuilder.build();
}
-export const parseDirective = function (statement: string, context: string, type: string) {
- // @ts-ignore: TODO Fix ts errors
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
const clear = function () {
quadrantBuilder.clear();
commonClear();
@@ -117,7 +110,6 @@ export default {
setYAxisBottomText,
addPoint,
getQuadrantData,
- parseDirective,
clear,
setAccTitle,
getAccTitle,
diff --git a/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison b/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison
index 331310283..6d0f7b122 100644
--- a/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison
+++ b/packages/mermaid/src/diagrams/requirement/parser/requirementDiagram.jison
@@ -9,19 +9,10 @@
%x string
%x token
%x unqString
-%x open_directive
-%x type_directive
-%x arg_directive
-%x close_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
%%
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
"title"\s[^#\n;]+ return 'title';
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
@@ -99,23 +90,10 @@ start
| RD NEWLINE diagram EOF;
directive
- : openDirective typeDirective closeDirective
- | openDirective typeDirective ':' argDirective closeDirective
- | 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_multiline_value { $$=$1.trim();yy.setAccDescription($$); }
;
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); };
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); };
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); };
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'pie'); };
diagram
: /* empty */ { $$ = [] }
diff --git a/packages/mermaid/src/diagrams/requirement/requirementDb.js b/packages/mermaid/src/diagrams/requirement/requirementDb.js
index c56c4ca16..325e95ee2 100644
--- a/packages/mermaid/src/diagrams/requirement/requirementDb.js
+++ b/packages/mermaid/src/diagrams/requirement/requirementDb.js
@@ -1,6 +1,5 @@
import * as configApi from '../../config.js';
import { log } from '../../logger.js';
-import mermaidAPI from '../../mermaidAPI.js';
import {
setAccTitle,
@@ -48,10 +47,6 @@ const Relationships = {
TRACES: 'traces',
};
-export const parseDirective = function (statement, context, type) {
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
const addRequirement = (name, type) => {
if (requirements[name] === undefined) {
requirements[name] = {
@@ -149,7 +144,6 @@ export default {
VerifyType,
Relationships,
- parseDirective,
getConfig: () => configApi.getConfig().req,
addRequirement,
diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
index 4e971d989..78b0c9ed9 100644
--- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
+++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison
@@ -16,22 +16,15 @@
// A special state for grabbing text up to the first comment/newline
%x ID ALIAS LINE
-// Directive states
-%x open_directive type_directive arg_directive
%x acc_title
%x acc_descr
%x acc_descr_multiline
%%
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
[\n]+ return 'NEWLINE';
\s+ /* skip all whitespace */
((?!\n)\s)+ /* skip same-line whitespace */
-\#[^\n]* /* skip comments */
+\#[^\n]* /* skip comments */
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[0-9]+(?=[ \n]+) return 'NUM';
@@ -106,7 +99,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
start
: SPACE start
| NEWLINE start
- | directive start
| SD document { yy.apply($2);return $2; }
;
@@ -133,11 +125,6 @@ box_line
;
-directive
- : openDirective typeDirective closeDirective 'NEWLINE'
- | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE'
- ;
-
statement
: participant_statement
| 'create' participant_statement {$2.type='createParticipant'; $$=$2;}
@@ -215,7 +202,6 @@ statement
$3.unshift({type: 'breakStart', breakText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_START});
$3.push({type: 'breakEnd', optText:yy.parseMessage($2), signalType: yy.LINETYPE.BREAK_END});
$$=$3;}
- | directive
;
option_sections
@@ -335,20 +321,4 @@ text2
: TXT {$$ = yy.parseMessage($1.trim().substring(1)) }
;
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'sequence'); }
- ;
-
%%
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.js b/packages/mermaid/src/diagrams/sequence/sequenceDb.js
index 813d9e127..6c3f1f64d 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceDb.js
+++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.js
@@ -1,4 +1,3 @@
-import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import { log } from '../../logger.js';
import { sanitizeText } from '../common/common.js';
@@ -25,10 +24,6 @@ let currentBox = undefined;
let lastCreated = undefined;
let lastDestroyed = undefined;
-export const parseDirective = function (statement, context, type) {
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
export const addBox = function (data) {
boxes.push({
name: data.text,
@@ -634,7 +629,6 @@ export default {
getBoxes,
getDiagramTitle,
setDiagramTitle,
- parseDirective,
getConfig: () => configApi.getConfig().sequence,
clear,
parseMessage,
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
index ed6f07300..77ac7c45c 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
+++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js
@@ -1,5 +1,4 @@
import { vi } from 'vitest';
-
import * as configApi from '../../config.js';
import mermaidAPI from '../../mermaidAPI.js';
import { Diagram, getDiagramFromText } from '../../Diagram.js';
@@ -225,6 +224,7 @@ Bob-->Alice: I am good thanks!`;
diagram.renderer.draw(str, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers
expect(diagram.db.showSequenceNumbers()).toBe(true);
});
+
it('should handle a sequenceDiagram definition with a title:', async () => {
const str = `
sequenceDiagram
@@ -2034,90 +2034,3 @@ participant Alice`;
});
});
});
-
-describe('when rendering a sequenceDiagram with directives', () => {
- beforeAll(function () {
- let conf = {
- diagramMarginX: 50,
- diagramMarginY: 10,
- actorMargin: 50,
- width: 150,
- height: 65,
- boxMargin: 10,
- messageMargin: 40,
- boxTextMargin: 15,
- noteMargin: 25,
- };
- mermaidAPI.initialize({ sequence: conf });
- });
-
- beforeEach(function () {
- mermaidAPI.reset();
- diagram.renderer.bounds.init();
- });
-
- it('should handle one actor, when theme is dark and logLevel is 1 DX1 (dfg1)', async () => {
- const str = `
-%%{init: { "theme": "dark", "logLevel": 1 } }%%
-sequenceDiagram
-%%{wrap}%%
-participant Alice
-`;
- diagram = new Diagram(str);
- diagram.renderer.bounds.init();
- diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
-
- const { bounds, models } = diagram.renderer.bounds.getBounds();
- const mermaid = mermaidAPI.getConfig();
- expect(mermaid.theme).toBe('dark');
- expect(mermaid.logLevel).toBe(1);
- expect(bounds.startx).toBe(0);
- expect(bounds.startx).toBe(0);
- expect(bounds.starty).toBe(0);
- expect(bounds.stopy).toBe(
- models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin
- );
- });
- it('should handle one actor, when logLevel is 3 (dfg0)', async () => {
- const str = `
-%%{initialize: { "logLevel": 3 }}%%
-sequenceDiagram
-participant Alice
-`;
-
- diagram = new Diagram(str);
- diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
-
- const { bounds, models } = diagram.renderer.bounds.getBounds();
- const mermaid = mermaidAPI.getConfig();
- expect(mermaid.logLevel).toBe(3);
- expect(bounds.startx).toBe(0);
- expect(bounds.startx).toBe(0);
- expect(bounds.starty).toBe(0);
- expect(bounds.stopy).toBe(
- models.lastActor().stopy + models.lastActor().height + mermaid.sequence.boxMargin
- );
- });
- it('should hide sequence numbers when autonumber is removed when autonumber is enabled', async () => {
- const str1 = `
-sequenceDiagram
-autonumber
-Alice->Bob:Hello Bob, how are you?
-Note right of Bob: Bob thinks
-Bob-->Alice: I am good thanks!`;
-
- diagram = new Diagram(str1);
- diagram.renderer.draw(str1, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers
- expect(diagram.db.showSequenceNumbers()).toBe(true);
-
- const str2 = `
-sequenceDiagram
-Alice->Bob:Hello Bob, how are you?
-Note right of Bob: Bob thinks
-Bob-->Alice: I am good thanks!`;
-
- diagram = new Diagram(str2);
- diagram.renderer.draw(str2, 'tst', '1.2.3', diagram);
- expect(diagram.db.showSequenceNumbers()).toBe(false);
- });
-});
diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts
index 8779b9cc4..f8d71c95e 100644
--- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts
+++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.ts
@@ -10,4 +10,7 @@ export const diagram: DiagramDefinition = {
db,
renderer,
styles,
+ init: ({ wrap }) => {
+ db.setWrap(wrap);
+ },
};
diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
index dc050b2ff..44235ecd4 100644
--- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
+++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison
@@ -33,10 +33,6 @@
%x FLOATING_NOTE
%x FLOATING_NOTE_ID
%x struct
-%x open_directive
-%x type_directive
-%x arg_directive
-%x close_directive
// A special state for grabbing text up to the first comment/newline
%x LINE
@@ -50,18 +46,13 @@
.*direction\s+RL[^\n]* return 'direction_rl';
.*direction\s+LR[^\n]* return 'direction_lr';
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%\%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */{ /*console.log('Crap after close');*/ }
[\n]+ return 'NL';
[\s]+ /* skip all whitespace */
-((?!\n)\s)+ /* skip same-line whitespace */
-\#[^\n]* /* skip comments */
+((?!\n)\s)+ /* skip same-line whitespace */
+\#[^\n]* /* skip comments */
\%%[^\n]* /* skip comments */
"scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; }
\d+ return 'WIDTH';
@@ -155,7 +146,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
start
: SPACE start
| NL start
- | directive start
| SD document { /* console.log('--> Root document', $2); */ yy.setRootDoc($2); return $2; }
;
@@ -241,7 +231,6 @@ statement
$$={ stmt: 'state', id: $3.trim(), note:{position: $2.trim(), text: $4.trim()}};
}
| note NOTE_TEXT AS ID
- | directive
| direction
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
@@ -264,10 +253,6 @@ cssClassStatement
}
;
-directive
- : openDirective typeDirective closeDirective
- | openDirective typeDirective ':' argDirective closeDirective
- ;
direction
: direction_tb
{ yy.setDirection('TB');$$={stmt:'dir', value:'TB'};}
@@ -308,20 +293,4 @@ notePosition
| right_of
;
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'state'); }
- ;
-
%%
diff --git a/packages/mermaid/src/diagrams/state/stateDb.js b/packages/mermaid/src/diagrams/state/stateDb.js
index f71290ec3..0253c5bcf 100644
--- a/packages/mermaid/src/diagrams/state/stateDb.js
+++ b/packages/mermaid/src/diagrams/state/stateDb.js
@@ -1,6 +1,5 @@
import { log } from '../../logger.js';
import { generateId } from '../../utils.js';
-import mermaidAPI from '../../mermaidAPI.js';
import common from '../common/common.js';
import * as configApi from '../../config.js';
import {
@@ -77,10 +76,6 @@ export const relationType = {
const clone = (o) => JSON.parse(JSON.stringify(o));
-export const parseDirective = function (statement, context, type) {
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
const setRootDoc = (o) => {
log.info('Setting root doc', o);
// rootDoc = { id: 'root', doc: o };
@@ -547,7 +542,6 @@ const setDirection = (dir) => {
const trimColon = (str) => (str && str[0] === ':' ? str.substr(1).trim() : str.trim());
export default {
- parseDirective,
getConfig: () => configApi.getConfig().state,
addState,
clear,
diff --git a/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js b/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js
index e64ecfdf8..c387ff7b3 100644
--- a/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js
+++ b/packages/mermaid/src/diagrams/state/stateDiagram-v2.spec.js
@@ -55,16 +55,6 @@ describe('state diagram V2, ', function () {
const title = stateDb.getAccTitle();
expect(title).toBe('a simple title of the diagram');
});
- it('simple with directive', function () {
- const str = `%%{init: {'logLevel': 0 }}%%
- stateDiagram-v2\n
- State1 : this is another string
- [*] --> State1
- State1 --> [*]
- `;
-
- parser.parse(str);
- });
it('should handle relation definitions', function () {
const str = `stateDiagram-v2\n
[*] --> State1
diff --git a/packages/mermaid/src/diagrams/state/stateDiagram.spec.js b/packages/mermaid/src/diagrams/state/stateDiagram.spec.js
index e6e470140..536031c81 100644
--- a/packages/mermaid/src/diagrams/state/stateDiagram.spec.js
+++ b/packages/mermaid/src/diagrams/state/stateDiagram.spec.js
@@ -66,16 +66,6 @@ describe('state diagram, ', function () {
const title = stateDb.getAccTitle();
expect(title).toBe('a simple title of the diagram');
});
- it('simple with directive', function () {
- const str = `%%{init: {'logLevel': 0 }}%%
- stateDiagram\n
- State1 : this is another string
- [*] --> State1
- State1 --> [*]
- `;
-
- parser.parse(str);
- });
it('should handle relation definitions', function () {
const str = `stateDiagram\n
[*] --> State1
diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js
index 1c9b2d1d3..0d3117b20 100644
--- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js
+++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js
@@ -81,7 +81,7 @@ export const setConf = function (cnf) {
*
* @param {string} text - the diagram text to be parsed
* @param diagramObj
- * @returns {object} ClassDef styles (a Map with keys = strings, values = )
+ * @returns {Record} ClassDef styles (a Map with keys = strings, values = )
*/
export const getClasses = function (text, diagramObj) {
diagramObj.db.extract(diagramObj.db.getRootDocV2());
diff --git a/packages/mermaid/src/diagrams/timeline/parser/timeline.jison b/packages/mermaid/src/diagrams/timeline/parser/timeline.jison
index 59b96516a..348c31fad 100644
--- a/packages/mermaid/src/diagrams/timeline/parser/timeline.jison
+++ b/packages/mermaid/src/diagrams/timeline/parser/timeline.jison
@@ -9,17 +9,8 @@
%x acc_descr
%x acc_descr_multiline
-// Directive states
-%x open_directive type_directive arg_directive
-
-
%%
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[\n]+ return 'NEWLINE';
@@ -55,7 +46,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
start
: timeline document 'EOF' { return $2; }
- | directive start
;
document
@@ -70,11 +60,6 @@ line
| EOF { $$=[];}
;
-directive
- : openDirective typeDirective closeDirective 'NEWLINE'
- | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE'
- ;
-
statement
: title {yy.getCommonDb().setDiagramTitle($1.substr(6));$$=$1.substr(6);}
| acc_title acc_title_value { $$=$2.trim();yy.getCommonDb().setAccTitle($$); }
@@ -83,7 +68,6 @@ statement
| section {yy.addSection($1.substr(8));$$=$1.substr(8);}
| period_statement
| event_statement
- | directive
;
period_statement
: period {yy.addTask($1,0,'');$$=$1;}
@@ -92,21 +76,4 @@ event_statement
: event {yy.addEvent($1.substr(2));$$=$1;}
;
-
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'timeline'); }
- ;
-
%%
diff --git a/packages/mermaid/src/diagrams/timeline/timeline.spec.js b/packages/mermaid/src/diagrams/timeline/timeline.spec.js
index a6a94fd0c..69b9df1ba 100644
--- a/packages/mermaid/src/diagrams/timeline/timeline.spec.js
+++ b/packages/mermaid/src/diagrams/timeline/timeline.spec.js
@@ -1,24 +1,6 @@
import { parser as timeline } from './parser/timeline.jison';
import * as timelineDB from './timelineDb.js';
-// import { injectUtils } from './mermaidUtils.js';
-import { parseDirective as _parseDirective } from '../../directiveUtils.js';
-
-import {
- log,
- setLogLevel,
- getConfig,
- sanitizeText,
- setupGraphViewBox,
-} from '../../diagram-api/diagramAPI.js';
-
-// injectUtils(
-// log,
-// setLogLevel,
-// getConfig,
-// sanitizeText,
-// setupGraphViewBox,
-// _parseDirective
-// );
+import { setLogLevel } from '../../diagram-api/diagramAPI.js';
describe('when parsing a timeline ', function () {
beforeEach(function () {
diff --git a/packages/mermaid/src/diagrams/timeline/timelineDb.js b/packages/mermaid/src/diagrams/timeline/timelineDb.js
index e5e22147d..485cbb3a5 100644
--- a/packages/mermaid/src/diagrams/timeline/timelineDb.js
+++ b/packages/mermaid/src/diagrams/timeline/timelineDb.js
@@ -1,4 +1,3 @@
-import { parseDirective as _parseDirective } from '../../directiveUtils.js';
import * as commonDb from '../common/commonDb.js';
let currentSection = '';
let currentTaskId = 0;
@@ -9,10 +8,6 @@ const rawTasks = [];
export const getCommonDb = () => commonDb;
-export const parseDirective = (statement, context, type) => {
- _parseDirective(this, statement, context, type);
-};
-
export const clear = function () {
sections.length = 0;
tasks.length = 0;
@@ -104,5 +99,4 @@ export default {
addTask,
addTaskOrg,
addEvent,
- parseDirective,
};
diff --git a/packages/mermaid/src/diagrams/user-journey/journeyDb.js b/packages/mermaid/src/diagrams/user-journey/journeyDb.js
index 509c5dc14..4d71c2e9d 100644
--- a/packages/mermaid/src/diagrams/user-journey/journeyDb.js
+++ b/packages/mermaid/src/diagrams/user-journey/journeyDb.js
@@ -1,4 +1,3 @@
-import mermaidAPI from '../../mermaidAPI.js';
import * as configApi from '../../config.js';
import {
setAccTitle,
@@ -16,10 +15,6 @@ const sections = [];
const tasks = [];
const rawTasks = [];
-export const parseDirective = function (statement, context, type) {
- mermaidAPI.parseDirective(this, statement, context, type);
-};
-
export const clear = function () {
sections.length = 0;
tasks.length = 0;
@@ -118,7 +113,6 @@ const getActors = function () {
};
export default {
- parseDirective,
getConfig: () => configApi.getConfig().journey,
clear,
setDiagramTitle,
diff --git a/packages/mermaid/src/diagrams/user-journey/parser/journey.jison b/packages/mermaid/src/diagrams/user-journey/parser/journey.jison
index 4c28d53dc..5567f1417 100644
--- a/packages/mermaid/src/diagrams/user-journey/parser/journey.jison
+++ b/packages/mermaid/src/diagrams/user-journey/parser/journey.jison
@@ -9,17 +9,8 @@
%x acc_descr
%x acc_descr_multiline
-// Directive states
-%x open_directive type_directive arg_directive
-
-
%%
-\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
-((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
-":" { this.popState(); this.begin('arg_directive'); return ':'; }
-\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
-((?:(?!\}\%\%).|\n)*) return 'arg_directive';
\%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */
[\n]+ return 'NEWLINE';
@@ -52,7 +43,6 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
start
: journey document 'EOF' { return $2; }
- | directive start
;
document
@@ -67,11 +57,6 @@ line
| EOF { $$=[];}
;
-directive
- : openDirective typeDirective closeDirective 'NEWLINE'
- | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE'
- ;
-
statement
: title {yy.setDiagramTitle($1.substr(6));$$=$1.substr(6);}
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
@@ -79,23 +64,6 @@ statement
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); }
| section {yy.addSection($1.substr(8));$$=$1.substr(8);}
| taskName taskData {yy.addTask($1, $2);$$='task';}
- | directive
- ;
-
-openDirective
- : open_directive { yy.parseDirective('%%{', 'open_directive'); }
- ;
-
-typeDirective
- : type_directive { yy.parseDirective($1, 'type_directive'); }
- ;
-
-argDirective
- : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); }
- ;
-
-closeDirective
- : close_directive { yy.parseDirective('}%%', 'close_directive', 'journey'); }
;
%%
diff --git a/packages/mermaid/src/directiveUtils.ts b/packages/mermaid/src/directiveUtils.ts
deleted file mode 100644
index baf628e74..000000000
--- a/packages/mermaid/src/directiveUtils.ts
+++ /dev/null
@@ -1,82 +0,0 @@
-import * as configApi from './config.js';
-import { log } from './logger.js';
-
-let currentDirective: { type?: string; args?: any } | undefined = {};
-
-export const parseDirective = function (
- p: any,
- statement: string,
- context: string,
- type: string
-): void {
- log.debug('parseDirective is being called', statement, context, type);
- try {
- if (statement !== undefined) {
- statement = statement.trim();
- switch (context) {
- case 'open_directive':
- currentDirective = {};
- break;
- case 'type_directive':
- if (!currentDirective) {
- throw new Error('currentDirective is undefined');
- }
- currentDirective.type = statement.toLowerCase();
- break;
- case 'arg_directive':
- if (!currentDirective) {
- throw new Error('currentDirective is undefined');
- }
- currentDirective.args = JSON.parse(statement);
- break;
- case 'close_directive':
- handleDirective(p, currentDirective, type);
- currentDirective = undefined;
- break;
- }
- }
- } catch (error) {
- log.error(
- `Error while rendering sequenceDiagram directive: ${statement} jison context: ${context}`
- );
- // @ts-ignore: TODO Fix ts errors
- log.error(error.message);
- }
-};
-
-const handleDirective = function (p: any, directive: any, type: string): void {
- log.info(`Directive type=${directive.type} with args:`, directive.args);
- switch (directive.type) {
- case 'init':
- case 'initialize': {
- ['config'].forEach((prop) => {
- if (directive.args[prop] !== undefined) {
- if (type === 'flowchart-v2') {
- type = 'flowchart';
- }
- directive.args[type] = directive.args[prop];
- delete directive.args[prop];
- }
- });
- configApi.addDirective(directive.args);
- break;
- }
- case 'wrap':
- case 'nowrap':
- if (p && p['setWrap']) {
- p.setWrap(directive.type === 'wrap');
- }
- break;
- case 'themeCss':
- log.warn('themeCss encountered');
- break;
- default:
- log.warn(
- `Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify(
- directive.args ? directive.args : {}
- )}}%%`,
- directive
- );
- break;
- }
-};
diff --git a/packages/mermaid/src/docs/config/Tutorials.md b/packages/mermaid/src/docs/config/Tutorials.md
index af7dbe672..ed0143f05 100644
--- a/packages/mermaid/src/docs/config/Tutorials.md
+++ b/packages/mermaid/src/docs/config/Tutorials.md
@@ -56,10 +56,10 @@ from IPython.display import Image, display
import matplotlib.pyplot as plt
def mm(graph):
- graphbytes = graph.encode("ascii")
- base64_bytes = base64.b64encode(graphbytes)
- base64_string = base64_bytes.decode("ascii")
- display(Image(url="https://mermaid.ink/img/" + base64_string))
+ graphbytes = graph.encode("utf8")
+ base64_bytes = base64.b64encode(graphbytes)
+ base64_string = base64_bytes.decode("ascii")
+ display(Image(url="https://mermaid.ink/img/" + base64_string))
mm("""
graph LR;
diff --git a/packages/mermaid/src/docs/package.json b/packages/mermaid/src/docs/package.json
index 742a28c0a..759d1ffb1 100644
--- a/packages/mermaid/src/docs/package.json
+++ b/packages/mermaid/src/docs/package.json
@@ -32,7 +32,7 @@
"unplugin-vue-components": "^0.25.0",
"vite": "^4.3.9",
"vite-plugin-pwa": "^0.16.0",
- "vitepress": "1.0.0-rc.10",
+ "vitepress": "1.0.0-rc.12",
"workbox-window": "^7.0.0"
}
}
diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md
index a49e974ab..7946d5725 100644
--- a/packages/mermaid/src/docs/syntax/flowchart.md
+++ b/packages/mermaid/src/docs/syntax/flowchart.md
@@ -554,13 +554,13 @@ flowchart LR
C-->D
click A callback "Tooltip for a callback"
click B "https://www.github.com" "This is a tooltip for a link"
- click A call callback() "Tooltip for a callback"
- click B href "https://www.github.com" "This is a tooltip for a link"
+ click C call callback() "Tooltip for a callback"
+ click D href "https://www.github.com" "This is a tooltip for a link"
```
> **Success** The tooltip functionality and the ability to link to urls are available from version 0.5.2.
-?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/s37cjoau/3/).
+?> Due to limitations with how Docsify handles JavaScript callback functions, an alternate working demo for the above code can be viewed at [this jsfiddle](https://jsfiddle.net/Ogglas/2o73vdez/7).
Links are opened in the same browser tab/window by default. It is possible to change this by adding a link target to the click definition (`_self`, `_blank`, `_parent` and `_top` are supported):
diff --git a/packages/mermaid/src/docs/syntax/gantt.md b/packages/mermaid/src/docs/syntax/gantt.md
index 6ddd011f6..a0cebc560 100644
--- a/packages/mermaid/src/docs/syntax/gantt.md
+++ b/packages/mermaid/src/docs/syntax/gantt.md
@@ -173,7 +173,7 @@ The following formatting strings are supported:
More info in: [https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format](https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format)
-### Axis ticks
+### Axis ticks (v10.3.0+)
The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`.
@@ -184,7 +184,7 @@ tickInterval 1day
The pattern is:
```javascript
-/^([1-9][0-9]*)(minute|hour|day|week|month)$/;
+/^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/;
```
More info in: [https://github.com/d3/d3-time#interval_every](https://github.com/d3/d3-time#interval_every)
@@ -197,7 +197,9 @@ gantt
weekday monday
```
-Support: v10.3.0+
+```warning
+`millisecond` and `second` support was added in vMERMAID_RELEASE_VERSION
+```
## Output in compact mode
diff --git a/packages/mermaid/src/docs/syntax/sankey.md b/packages/mermaid/src/docs/syntax/sankey.md
index f5160ea86..c942944d7 100644
--- a/packages/mermaid/src/docs/syntax/sankey.md
+++ b/packages/mermaid/src/docs/syntax/sankey.md
@@ -2,9 +2,9 @@
> A sankey diagram is a visualization used to depict a flow from one set of values to another.
-::: warning
+```warning
This is an experimental diagram. Its syntax are very close to plain CSV, but it is to be extended in the nearest future.
-:::
+```
The things being connected are called nodes and the connections are called links.
@@ -13,6 +13,11 @@ The things being connected are called nodes and the connections are called links
This example taken from [observable](https://observablehq.com/@d3/sankey/2?collection=@d3/d3-sankey). It may be rendered a little bit differently, though, in terms of size and colors.
```mermaid-example
+---
+config:
+ sankey:
+ showValues: false
+---
sankey-beta
Agricultural 'waste',Bio-conversion,124.729
diff --git a/packages/mermaid/src/mermaid.spec.ts b/packages/mermaid/src/mermaid.spec.ts
index 0b4437d74..645b5b39c 100644
--- a/packages/mermaid/src/mermaid.spec.ts
+++ b/packages/mermaid/src/mermaid.spec.ts
@@ -95,8 +95,10 @@ describe('when using mermaid and ', () => {
let loaded = false;
const dummyDiagram: DiagramDefinition = {
db: {},
- renderer: () => {
- // do nothing
+ renderer: {
+ draw: () => {
+ // no-op
+ },
},
parser: {
parse: (_text) => {
diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts
index caf4a2b9b..a6d495471 100644
--- a/packages/mermaid/src/mermaid.ts
+++ b/packages/mermaid/src/mermaid.ts
@@ -136,7 +136,7 @@ const runThrowsErrors = async function (
}
// generate the id of the diagram
- const idGenerator = new utils.initIdGenerator(conf.deterministicIds, conf.deterministicIDSeed);
+ const idGenerator = new utils.InitIDGenerator(conf.deterministicIds, conf.deterministicIDSeed);
let txt: string;
const errors: DetailedError[] = [];
diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts
index d7c16a1cf..a79fd44c4 100644
--- a/packages/mermaid/src/mermaidAPI.spec.ts
+++ b/packages/mermaid/src/mermaidAPI.spec.ts
@@ -287,15 +287,15 @@ describe('mermaidAPI', () => {
};
it('gets the cssStyles from the theme', () => {
- const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', null);
+ const styles = createCssStyles(mocked_config_with_htmlLabels, null);
expect(styles).toMatch(/^\ndefault(.*)/);
});
it('gets the fontFamily from the config', () => {
- const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', {});
+ const styles = createCssStyles(mocked_config_with_htmlLabels, {});
expect(styles).toMatch(/(.*)\n:root { --mermaid-font-family: serif(.*)/);
});
it('gets the alt fontFamily from the config', () => {
- const styles = createCssStyles(mocked_config_with_htmlLabels, 'graphType', undefined);
+ const styles = createCssStyles(mocked_config_with_htmlLabels, undefined);
expect(styles).toMatch(/(.*)\n:root { --mermaid-alt-font-family: sans-serif(.*)/);
});
@@ -306,8 +306,6 @@ describe('mermaidAPI', () => {
const classDefs = { classDef1, classDef2, classDef3 };
describe('the graph supports classDefs', () => {
- const graphType = 'flowchart-v2';
-
const REGEXP_SPECIALS = ['^', '$', '?', '(', '{', '[', '.', '*', '!'];
// prefix any special RegExp characters in the given string with a \ so we can use the literal character in a RegExp
@@ -373,7 +371,7 @@ describe('mermaidAPI', () => {
// @todo TODO Can't figure out how to spy on the cssImportantStyles method.
// That would be a much better approach than manually checking the result
- const styles = createCssStyles(mocked_config, graphType, classDefs);
+ const styles = createCssStyles(mocked_config, classDefs);
htmlElements.forEach((htmlElement) => {
expect_styles_matchesHtmlElements(styles, htmlElement);
});
@@ -411,7 +409,7 @@ describe('mermaidAPI', () => {
it('creates CSS styles for every style and textStyle in every classDef', () => {
// TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result.
- const styles = createCssStyles(mocked_config_no_htmlLabels, graphType, classDefs);
+ const styles = createCssStyles(mocked_config_no_htmlLabels, classDefs);
htmlElements.forEach((htmlElement) => {
expect_styles_matchesHtmlElements(styles, htmlElement);
});
diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts
index 6a6b97735..8eea34d88 100644
--- a/packages/mermaid/src/mermaidAPI.ts
+++ b/packages/mermaid/src/mermaidAPI.ts
@@ -23,24 +23,14 @@ import { attachFunctions } from './interactionDb.js';
import { log, setLogLevel } from './logger.js';
import getStyles from './styles.js';
import theme from './themes/index.js';
-import utils from './utils.js';
import DOMPurify from 'dompurify';
import type { MermaidConfig } from './config.type.js';
import { evaluate } from './diagrams/common/common.js';
import isEmpty from 'lodash-es/isEmpty.js';
import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility.js';
-import { parseDirective } from './directiveUtils.js';
-import { extractFrontMatter } from './diagram-api/frontmatter.js';
+import type { DiagramStyleClassDef } from './diagram-api/types.js';
+import { preprocessDiagram } from './preprocess.js';
-// diagram names that support classDef statements
-const CLASSDEF_DIAGRAMS = [
- 'graph',
- 'flowchart',
- 'flowchart-v2',
- 'flowchart-elk',
- 'stateDiagram',
- 'stateDiagram-v2',
-];
const MAX_TEXTLENGTH = 50_000;
const MAX_TEXTLENGTH_EXCEEDED_MSG =
'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa';
@@ -65,15 +55,6 @@ const IFRAME_NOT_SUPPORTED_MSG = 'The "iframe" tag is not supported by your brow
const DOMPURIFY_TAGS = ['foreignobject'];
const DOMPURIFY_ATTR = ['dominant-baseline'];
-// This is what is returned from getClasses(...) methods.
-// It is slightly renamed to ..StyleClassDef instead of just ClassDef because "class" is a greatly ambiguous and overloaded word.
-// It makes it clear we're working with a style class definition, even though defining the type is currently difficult.
-interface DiagramStyleClassDef {
- id: string;
- styles?: string[];
- textStyles?: string[];
-}
-
export interface ParseOptions {
suppressErrors?: boolean;
}
@@ -98,6 +79,13 @@ export interface RenderResult {
bindFunctions?: (element: Element) => void;
}
+function processAndSetConfigs(text: string) {
+ const processed = preprocessDiagram(text);
+ configApi.reset();
+ configApi.addDirective(processed.config ?? {});
+ return processed;
+}
+
/**
* Parse the text and validate the syntax.
* @param text - The mermaid diagram definition.
@@ -108,6 +96,9 @@ export interface RenderResult {
async function parse(text: string, parseOptions?: ParseOptions): Promise {
addDiagrams();
+
+ text = processAndSetConfigs(text).code;
+
try {
await getDiagramFromText(text);
} catch (error) {
@@ -176,15 +167,13 @@ export const cssImportantStyles = (
/**
* Create the user styles
- *
+ * @internal
* @param config - configuration that has style and theme settings to use
- * @param graphType - used for checking if classDefs should be applied
* @param classDefs - the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...)
* @returns the string with all the user styles
*/
export const createCssStyles = (
config: MermaidConfig,
- graphType: string,
classDefs: Record | null | undefined = {}
): string => {
let cssStyles = '';
@@ -204,7 +193,7 @@ export const createCssStyles = (
}
// classDefs defined in the diagram text
- if (!isEmpty(classDefs) && CLASSDEF_DIAGRAMS.includes(graphType)) {
+ if (!isEmpty(classDefs)) {
const htmlLabels = config.htmlLabels || config.flowchart?.htmlLabels; // TODO why specifically check the Flowchart diagram config?
const cssHtmlElements = ['> *', 'span']; // TODO make a constant
@@ -233,10 +222,10 @@ export const createCssStyles = (
export const createUserStyles = (
config: MermaidConfig,
graphType: string,
- classDefs: Record,
+ classDefs: Record | undefined,
svgId: string
): string => {
- const userCSSstyles = createCssStyles(config, graphType, classDefs);
+ const userCSSstyles = createCssStyles(config, classDefs);
const allStyles = getStyles(graphType, userCSSstyles, config.themeVariables);
// Now turn all of the styles into a (compiled) string that starts with the id
@@ -384,18 +373,8 @@ const render = async function (
): Promise {
addDiagrams();
- configApi.reset();
-
- // We need to add the directives before creating the diagram.
- // So extractFrontMatter is called twice. Once here and once in the diagram parser.
- // This can be fixed in a future refactor.
- extractFrontMatter(text, {}, configApi.addDirective);
-
- // Add Directives.
- const graphInit = utils.detectInit(text);
- if (graphInit) {
- configApi.addDirective(graphInit);
- }
+ const processed = processAndSetConfigs(text);
+ text = processed.code;
const config = configApi.getConfig();
log.debug(config);
@@ -405,15 +384,6 @@ const render = async function (
text = MAX_TEXTLENGTH_EXCEEDED_MSG;
}
- // clean up text CRLFs
- text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;;
-
- // clean up html tags so that all attributes use single quotes, parser throws error on double quotes
- text = text.replace(
- /<(\w+)([^>]*)>/g,
- (match, tag, attributes) => '<' + tag + attributes.replace(/="([^"]*)"/g, "='$1'") + '>'
- );
-
const idSelector = '#' + id;
const iFrameID = 'i' + id;
const iFrameID_selector = '#' + iFrameID;
@@ -486,7 +456,7 @@ const render = async function (
let parseEncounteredException;
try {
- diag = await getDiagramFromText(text);
+ diag = await getDiagramFromText(text, { title: processed.title });
} catch (error) {
if (config.suppressErrorRendering) {
removeTempElements();
@@ -506,9 +476,7 @@ const render = async function (
// Insert an element into svg. This is where we put the styles
const svg = element.firstChild;
const firstChild = svg.firstChild;
- const diagramClassDefs = CLASSDEF_DIAGRAMS.includes(diagramType)
- ? diag.renderer.getClasses(text, diag)
- : {};
+ const diagramClassDefs = diag.renderer.getClasses?.(text, diag);
const rules = createUserStyles(config, diagramType, diagramClassDefs, idSelector);
@@ -685,7 +653,6 @@ function addA11yInfo(
export const mermaidAPI = Object.freeze({
render,
parse,
- parseDirective,
getDiagramFromText,
initialize,
getConfig: configApi.getConfig,
diff --git a/packages/mermaid/src/preprocess.ts b/packages/mermaid/src/preprocess.ts
new file mode 100644
index 000000000..6e386e744
--- /dev/null
+++ b/packages/mermaid/src/preprocess.ts
@@ -0,0 +1,65 @@
+import { cleanupComments } from './diagram-api/comments.js';
+import { extractFrontMatter } from './diagram-api/frontmatter.js';
+import type { DiagramMetadata } from './diagram-api/types.js';
+import utils, { cleanAndMerge, removeDirectives } from './utils.js';
+
+const cleanupText = (code: string) => {
+ return (
+ code
+ // parser problems on CRLF ignore all CR and leave LF;;
+ .replace(/\r\n?/g, '\n')
+ // clean up html tags so that all attributes use single quotes, parser throws error on double quotes
+ .replace(
+ /<(\w+)([^>]*)>/g,
+ (match, tag, attributes) => '<' + tag + attributes.replace(/="([^"]*)"/g, "='$1'") + '>'
+ )
+ );
+};
+
+const processFrontmatter = (code: string) => {
+ const { text, metadata } = extractFrontMatter(code);
+ const { displayMode, title, config = {} } = metadata;
+ if (displayMode) {
+ // Needs to be supported for legacy reasons
+ if (!config.gantt) {
+ config.gantt = {};
+ }
+ config.gantt.displayMode = displayMode;
+ }
+ return { title, config, text };
+};
+
+const processDirectives = (code: string) => {
+ const initDirective = utils.detectInit(code) ?? {};
+ const wrapDirectives = utils.detectDirective(code, 'wrap');
+ if (Array.isArray(wrapDirectives)) {
+ initDirective.wrap = wrapDirectives.some(({ type }) => {
+ type === 'wrap';
+ });
+ } else if (wrapDirectives?.type === 'wrap') {
+ initDirective.wrap = true;
+ }
+ return {
+ text: removeDirectives(code),
+ directive: initDirective,
+ };
+};
+
+/**
+ * Preprocess the given code by cleaning it up, extracting front matter and directives,
+ * cleaning and merging configuration, and removing comments.
+ * @param code - The code to preprocess.
+ * @returns The object containing the preprocessed code, title, and configuration.
+ */
+export function preprocessDiagram(code: string): DiagramMetadata & { code: string } {
+ const cleanedCode = cleanupText(code);
+ const frontMatterResult = processFrontmatter(cleanedCode);
+ const directiveResult = processDirectives(frontMatterResult.text);
+ const config = cleanAndMerge(frontMatterResult.config, directiveResult.directive);
+ code = cleanupComments(directiveResult.text);
+ return {
+ code,
+ title: frontMatterResult.title,
+ config,
+ };
+}
diff --git a/packages/mermaid/src/schemas/config.schema.yaml b/packages/mermaid/src/schemas/config.schema.yaml
index 04aec01ef..9f396e2b6 100644
--- a/packages/mermaid/src/schemas/config.schema.yaml
+++ b/packages/mermaid/src/schemas/config.schema.yaml
@@ -1530,10 +1530,10 @@ $defs: # JSON Schema definition (maybe we should move these to a seperate file)
Pattern is:
```javascript
- /^([1-9][0-9]*)(minute|hour|day|week|month)$/
+ /^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$/
```
type: string
- pattern: ^([1-9][0-9]*)(minute|hour|day|week|month)$
+ pattern: ^([1-9][0-9]*)(millisecond|second|minute|hour|day|week|month)$
topAxis:
description: |
When this flag is set, date labels will be added to the top of the chart
diff --git a/packages/mermaid/src/types.ts b/packages/mermaid/src/types.ts
new file mode 100644
index 000000000..13da88503
--- /dev/null
+++ b/packages/mermaid/src/types.ts
@@ -0,0 +1,34 @@
+export interface Point {
+ x: number;
+ y: number;
+}
+
+export interface TextDimensionConfig {
+ fontSize?: number;
+ fontWeight?: number;
+ fontFamily?: string;
+}
+
+export interface TextDimensions {
+ width: number;
+ height: number;
+ lineHeight?: number;
+}
+
+export interface EdgeData {
+ arrowheadStyle?: string;
+ labelpos?: string;
+ labelType?: string;
+ label?: string;
+ classes: string;
+ pattern: string;
+ id: string;
+ arrowhead: string;
+ startLabelRight: string;
+ endLabelLeft: string;
+ arrowTypeStart: string;
+ arrowTypeEnd: string;
+ style: string;
+ labelStyle: string;
+ curve: any;
+}
diff --git a/packages/mermaid/src/utils.spec.ts b/packages/mermaid/src/utils.spec.ts
index 271dc588c..3be3bc214 100644
--- a/packages/mermaid/src/utils.spec.ts
+++ b/packages/mermaid/src/utils.spec.ts
@@ -1,10 +1,11 @@
import { vi } from 'vitest';
-import utils, { cleanAndMerge } from './utils.js';
+import utils, { calculatePoint, cleanAndMerge, detectDirective } from './utils.js';
import assignWithDepth from './assignWithDepth.js';
import { detectType } from './diagram-api/detectType.js';
import { addDiagrams } from './diagram-api/diagram-orchestration.js';
import memoize from 'lodash-es/memoize.js';
import { MockedD3 } from './tests/MockedD3.js';
+import { preprocessDiagram } from './preprocess.js';
addDiagrams();
@@ -158,13 +159,38 @@ describe('when detecting chart type ', function () {
const type = detectType(str);
expect(type).toBe('flowchart');
});
+ it('should handle a wrap directive', () => {
+ const wrap = { type: 'wrap', args: null };
+ expect(detectDirective('%%{wrap}%%', 'wrap')).toEqual(wrap);
+ expect(
+ detectDirective(
+ `%%{
+ wrap
+ }%%`,
+ 'wrap'
+ )
+ ).toEqual(wrap);
+ expect(
+ detectDirective(
+ `%%{
+
+ wrap
+
+ }%%`,
+ 'wrap'
+ )
+ ).toEqual(wrap);
+ expect(detectDirective('%%{wrap:}%%', 'wrap')).toEqual(wrap);
+ expect(detectDirective('%%{wrap: }%%', 'wrap')).toEqual(wrap);
+ expect(detectDirective('graph', 'wrap')).not.toEqual(wrap);
+ });
it('should handle an initialize definition', function () {
const str = `
%%{initialize: { 'logLevel': 0, 'theme': 'dark' }}%%
sequenceDiagram
Alice->Bob: hi`;
const type = detectType(str);
- const init = utils.detectInit(str);
+ const init = preprocessDiagram(str).config;
expect(type).toBe('sequence');
expect(init).toEqual({ logLevel: 0, theme: 'dark' });
});
@@ -174,7 +200,7 @@ Alice->Bob: hi`;
sequenceDiagram
Alice->Bob: hi`;
const type = detectType(str);
- const init = utils.detectInit(str);
+ const init = preprocessDiagram(str).config;
expect(type).toBe('sequence');
expect(init).toEqual({ logLevel: 0, theme: 'dark' });
});
@@ -184,7 +210,7 @@ Alice->Bob: hi`;
sequenceDiagram
Alice->Bob: hi`;
const type = detectType(str);
- const init = utils.detectInit(str);
+ const init = preprocessDiagram(str).config;
expect(type).toBe('sequence');
expect(init).toEqual({ logLevel: 0, theme: 'dark', sequence: { wrap: true } });
});
@@ -199,7 +225,7 @@ Alice->Bob: hi`;
sequenceDiagram
Alice->Bob: hi`;
const type = detectType(str);
- const init = utils.detectInit(str);
+ const init = preprocessDiagram(str).config;
expect(type).toBe('sequence');
expect(init).toEqual({ logLevel: 0, theme: 'dark' });
});
@@ -214,7 +240,7 @@ Alice->Bob: hi`;
sequenceDiagram
Alice->Bob: hi`;
const type = detectType(str);
- const init = utils.detectInit(str);
+ const init = preprocessDiagram(str).config;
expect(type).toBe('sequence');
expect(init).toEqual({ logLevel: 0, theme: 'dark' });
});
@@ -326,7 +352,7 @@ describe('when initializing the id generator', function () {
});
it('should return a random number generator based on Date', function () {
- const idGenerator = new utils.initIdGenerator(false);
+ const idGenerator = new utils.InitIDGenerator(false);
expect(typeof idGenerator.next).toEqual('function');
const lastId = idGenerator.next();
vi.advanceTimersByTime(1000);
@@ -334,7 +360,7 @@ describe('when initializing the id generator', function () {
});
it('should return a non random number generator', function () {
- const idGenerator = new utils.initIdGenerator(true);
+ const idGenerator = new utils.InitIDGenerator(true);
expect(typeof idGenerator.next).toEqual('function');
const start = 0;
const lastId = idGenerator.next();
@@ -343,7 +369,7 @@ describe('when initializing the id generator', function () {
});
it('should return a non random number generator based on seed', function () {
- const idGenerator = new utils.initIdGenerator(true, 'thisIsASeed');
+ const idGenerator = new utils.InitIDGenerator(true, 'thisIsASeed');
expect(typeof idGenerator.next).toEqual('function');
const start = 11;
const lastId = idGenerator.next();
@@ -464,3 +490,107 @@ describe('cleanAndMerge', () => {
expect(inputDeep).toEqual({ a: { b: 1 } });
});
});
+
+describe('calculatePoint', () => {
+ it('should calculate a point on a straight line', () => {
+ const points = [
+ { x: 0, y: 0 },
+ { x: 0, y: 10 },
+ { x: 0, y: 20 },
+ ];
+ expect(calculatePoint(points, 0)).toEqual({ x: 0, y: 0 });
+ expect(calculatePoint(points, 5)).toEqual({ x: 0, y: 5 });
+ expect(calculatePoint(points, 10)).toEqual({ x: 0, y: 10 });
+ });
+
+ it('should calculate a point on a straight line with slope', () => {
+ const points = [
+ { x: 0, y: 0 },
+ { x: 10, y: 10 },
+ { x: 20, y: 20 },
+ ];
+ expect(calculatePoint(points, 0)).toMatchInlineSnapshot(`
+ {
+ "x": 0,
+ "y": 0,
+ }
+ `);
+ expect(calculatePoint(points, 5)).toMatchInlineSnapshot(`
+ {
+ "x": 3.53553,
+ "y": 3.53553,
+ }
+ `);
+ expect(calculatePoint(points, 10)).toMatchInlineSnapshot(`
+ {
+ "x": 7.07107,
+ "y": 7.07107,
+ }
+ `);
+ });
+
+ it('should calculate a point on a straight line with negative slope', () => {
+ const points = [
+ { x: 20, y: 20 },
+ { x: 10, y: 10 },
+ { x: 15, y: 15 },
+ { x: 0, y: 0 },
+ ];
+ expect(calculatePoint(points, 0)).toMatchInlineSnapshot(`
+ {
+ "x": 20,
+ "y": 20,
+ }
+ `);
+ expect(calculatePoint(points, 5)).toMatchInlineSnapshot(`
+ {
+ "x": 16.46447,
+ "y": 16.46447,
+ }
+ `);
+ expect(calculatePoint(points, 10)).toMatchInlineSnapshot(`
+ {
+ "x": 12.92893,
+ "y": 12.92893,
+ }
+ `);
+ });
+
+ it('should calculate a point on a curved line', () => {
+ const points = [
+ { x: 0, y: 0 },
+ { x: 10, y: 10 },
+ { x: 20, y: 0 },
+ ];
+ expect(calculatePoint(points, 0)).toMatchInlineSnapshot(`
+ {
+ "x": 0,
+ "y": 0,
+ }
+ `);
+ expect(calculatePoint(points, 15)).toMatchInlineSnapshot(`
+ {
+ "x": 10.6066,
+ "y": 9.3934,
+ }
+ `);
+ expect(calculatePoint(points, 20)).toMatchInlineSnapshot(`
+ {
+ "x": 14.14214,
+ "y": 5.85786,
+ }
+ `);
+ });
+
+ it('should throw an error if the new point cannot be found', () => {
+ const points = [
+ { x: 0, y: 0 },
+ { x: 10, y: 10 },
+ { x: 20, y: 20 },
+ ];
+ const distanceToTraverse = 30;
+ expect(() => calculatePoint(points, distanceToTraverse)).toThrow(
+ 'Could not find a suitable point for the given distance'
+ );
+ });
+});
diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts
index 42b4ee67e..e706ef122 100644
--- a/packages/mermaid/src/utils.ts
+++ b/packages/mermaid/src/utils.ts
@@ -1,4 +1,3 @@
-// @ts-nocheck : TODO Fix ts errors
import { sanitizeUrl } from '@braintree/sanitize-url';
import type { CurveFactory } from 'd3';
import {
@@ -25,7 +24,7 @@ import {
select,
} from 'd3';
import common from './diagrams/common/common.js';
-import { configKeys } from './defaultConfig.js';
+import { sanitizeDirective } from './utils/sanitizeDirective.js';
import { log } from './logger.js';
import { detectType } from './diagram-api/detectType.js';
import assignWithDepth from './assignWithDepth.js';
@@ -33,6 +32,8 @@ import type { MermaidConfig } from './config.type.js';
import memoize from 'lodash-es/memoize.js';
import merge from 'lodash-es/merge.js';
import { directiveRegex } from './diagram-api/regexes.js';
+import type { D3Element } from './mermaidAPI.js';
+import type { Point, TextDimensionConfig, TextDimensions } from './types.js';
export const ZERO_WIDTH_SPACE = '\u200b';
@@ -58,11 +59,10 @@ const d3CurveTypes = {
curveStep: curveStep,
curveStepAfter: curveStepAfter,
curveStepBefore: curveStepBefore,
-};
+} as const;
const directiveWithoutOpen =
/\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi;
-
/**
* Detects the init config object from the text
*
@@ -102,14 +102,14 @@ export const detectInit = function (
config?: MermaidConfig
): MermaidConfig | undefined {
const inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
- let results = {};
+ let results: MermaidConfig & { config?: unknown } = {};
if (Array.isArray(inits)) {
const args = inits.map((init) => init.args);
sanitizeDirective(args);
results = assignWithDepth(results, [...args]);
} else {
- results = inits.args;
+ results = inits.args as MermaidConfig;
}
if (!results) {
@@ -117,19 +117,24 @@ export const detectInit = function (
}
let type = detectType(text, config);
- ['config'].forEach((prop) => {
- if (results[prop] !== undefined) {
- if (type === 'flowchart-v2') {
- type = 'flowchart';
- }
- results[type] = results[prop];
- delete results[prop];
+
+ // Move the `config` value to appropriate diagram type value
+ const prop = 'config';
+ if (results[prop] !== undefined) {
+ if (type === 'flowchart-v2') {
+ type = 'flowchart';
}
- });
+ results[type as keyof MermaidConfig] = results[prop];
+ delete results[prop];
+ }
return results;
};
+interface Directive {
+ type?: string;
+ args?: unknown;
+}
/**
* Detects the directive from the text.
*
@@ -155,8 +160,8 @@ export const detectInit = function (
*/
export const detectDirective = function (
text: string,
- type: string | RegExp = null
-): { type?: string; args?: any } | { type?: string; args?: any }[] {
+ type: string | RegExp | null = null
+): Directive | Directive[] {
try {
const commentWithoutDirectives = new RegExp(
`[%]{2}(?![{]${directiveWithoutOpen.source})(?=[}][%]{2}).*\n`,
@@ -166,8 +171,8 @@ export const detectDirective = function (
log.debug(
`Detecting diagram directive${type !== null ? ' type:' + type : ''} based on the text:${text}`
);
- let match;
- const result = [];
+ let match: RegExpExecArray | null;
+ const result: Directive[] = [];
while ((match = directiveRegex.exec(text)) !== null) {
// This is necessary to avoid infinite loops with zero-width matches
if (match.index === directiveRegex.lastIndex) {
@@ -184,19 +189,24 @@ export const detectDirective = function (
}
}
if (result.length === 0) {
- result.push({ type: text, args: null });
+ return { type: text, args: null };
}
return result.length === 1 ? result[0] : result;
} catch (error) {
log.error(
- `ERROR: ${error.message} - Unable to parse directive
- ${type !== null ? ' type:' + type : ''} based on the text:${text}`
+ `ERROR: ${
+ (error as Error).message
+ } - Unable to parse directive type: '${type}' based on the text: '${text}'`
);
- return { type: null, args: null };
+ return { type: undefined, args: null };
}
};
+export const removeDirectives = function (text: string): string {
+ return text.replace(directiveRegex, '');
+};
+
/**
* Detects whether a substring in present in a given array
*
@@ -228,7 +238,9 @@ export function interpolateToCurve(
return defaultCurve;
}
const curveName = `curve${interpolate.charAt(0).toUpperCase() + interpolate.slice(1)}`;
- return d3CurveTypes[curveName] || defaultCurve;
+
+ // @ts-ignore TODO: Fix issue with curve type
+ return d3CurveTypes[curveName as keyof typeof d3CurveTypes] ?? defaultCurve;
}
/**
@@ -241,13 +253,15 @@ export function interpolateToCurve(
export function formatUrl(linkStr: string, config: MermaidConfig): string | undefined {
const url = linkStr.trim();
- if (url) {
- if (config.securityLevel !== 'loose') {
- return sanitizeUrl(url);
- }
-
- return url;
+ if (!url) {
+ return undefined;
}
+
+ if (config.securityLevel !== 'loose') {
+ return sanitizeUrl(url);
+ }
+
+ return url;
}
/**
@@ -256,7 +270,7 @@ export function formatUrl(linkStr: string, config: MermaidConfig): string | unde
* @param functionName - A dot separated path to the function relative to the `window`
* @param params - Parameters to pass to the function
*/
-export const runFunc = (functionName: string, ...params) => {
+export const runFunc = (functionName: string, ...params: unknown[]) => {
const arrPaths = functionName.split('.');
const len = arrPaths.length - 1;
@@ -264,23 +278,16 @@ export const runFunc = (functionName: string, ...params) => {
let obj = window;
for (let i = 0; i < len; i++) {
- obj = obj[arrPaths[i]];
+ obj = obj[arrPaths[i] as keyof typeof obj];
if (!obj) {
+ log.error(`Function name: ${functionName} not found in window`);
return;
}
}
- obj[fnName](...params);
+ obj[fnName as keyof typeof obj](...params);
};
-/** A (x, y) point */
-interface Point {
- /** The x value */
- x: number;
- /** The y value */
- y: number;
-}
-
/**
* Finds the distance between two points using the Distance Formula
*
@@ -288,8 +295,11 @@ interface Point {
* @param p2 - The second point
* @returns The distance between the two points.
*/
-function distance(p1: Point, p2: Point): number {
- return p1 && p2 ? Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) : 0;
+function distance(p1?: Point, p2?: Point): number {
+ if (!p1 || !p2) {
+ return 0;
+ }
+ return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2));
}
/**
@@ -298,7 +308,7 @@ function distance(p1: Point, p2: Point): number {
* @param points - List of points
*/
function traverseEdge(points: Point[]): Point {
- let prevPoint;
+ let prevPoint: Point | undefined;
let totalDistance = 0;
points.forEach((point) => {
@@ -307,35 +317,8 @@ function traverseEdge(points: Point[]): Point {
});
// Traverse half of total distance along points
- let remainingDistance = totalDistance / 2;
- let center = undefined;
- prevPoint = undefined;
- points.forEach((point) => {
- if (prevPoint && !center) {
- const vectorDistance = distance(point, prevPoint);
- if (vectorDistance < remainingDistance) {
- remainingDistance -= vectorDistance;
- } else {
- // The point is remainingDistance from prevPoint in the vector between prevPoint and point
- // Calculate the coordinates
- const distanceRatio = remainingDistance / vectorDistance;
- if (distanceRatio <= 0) {
- center = prevPoint;
- }
- if (distanceRatio >= 1) {
- center = { x: point.x, y: point.y };
- }
- if (distanceRatio > 0 && distanceRatio < 1) {
- center = {
- x: (1 - distanceRatio) * prevPoint.x + distanceRatio * point.x,
- y: (1 - distanceRatio) * prevPoint.y + distanceRatio * point.y,
- };
- }
- }
- }
- prevPoint = point;
- });
- return center;
+ const remainingDistance = totalDistance / 2;
+ return calculatePoint(points, remainingDistance);
}
/**
@@ -348,20 +331,16 @@ function calcLabelPosition(points: Point[]): Point {
return traverseEdge(points);
}
-const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition) => {
- let prevPoint;
- log.info(`our points ${JSON.stringify(points)}`);
- if (points[0] !== initialPosition) {
- points = points.reverse();
- }
- // Traverse only 25 total distance along points to find cardinality point
- const distanceToCardinalityPoint = 25;
+export const roundNumber = (num: number, precision = 2) => {
+ const factor = Math.pow(10, precision);
+ return Math.round(num * factor) / factor;
+};
- let remainingDistance = distanceToCardinalityPoint;
- let center;
- prevPoint = undefined;
- points.forEach((point) => {
- if (prevPoint && !center) {
+export const calculatePoint = (points: Point[], distanceToTraverse: number): Point => {
+ let prevPoint: Point | undefined = undefined;
+ let remainingDistance = distanceToTraverse;
+ for (const point of points) {
+ if (prevPoint) {
const vectorDistance = distance(point, prevPoint);
if (vectorDistance < remainingDistance) {
remainingDistance -= vectorDistance;
@@ -370,27 +349,42 @@ const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition)
// Calculate the coordinates
const distanceRatio = remainingDistance / vectorDistance;
if (distanceRatio <= 0) {
- center = prevPoint;
+ return prevPoint;
}
if (distanceRatio >= 1) {
- center = { x: point.x, y: point.y };
+ return { x: point.x, y: point.y };
}
if (distanceRatio > 0 && distanceRatio < 1) {
- center = {
- x: (1 - distanceRatio) * prevPoint.x + distanceRatio * point.x,
- y: (1 - distanceRatio) * prevPoint.y + distanceRatio * point.y,
+ return {
+ x: roundNumber((1 - distanceRatio) * prevPoint.x + distanceRatio * point.x, 5),
+ y: roundNumber((1 - distanceRatio) * prevPoint.y + distanceRatio * point.y, 5),
};
}
}
}
prevPoint = point;
- });
+ }
+ throw new Error('Could not find a suitable point for the given distance');
+};
+
+const calcCardinalityPosition = (
+ isRelationTypePresent: boolean,
+ points: Point[],
+ initialPosition: Point
+) => {
+ log.info(`our points ${JSON.stringify(points)}`);
+ if (points[0] !== initialPosition) {
+ points = points.reverse();
+ }
+ // Traverse only 25 total distance along points to find cardinality point
+ const distanceToCardinalityPoint = 25;
+ const center = calculatePoint(points, distanceToCardinalityPoint);
// if relation is present (Arrows will be added), change cardinality point off-set distance (d)
const d = isRelationTypePresent ? 10 : 5;
//Calculate Angle for x and y axis
const angle = Math.atan2(points[0].y - center.y, points[0].x - center.x);
const cardinalityPosition = { x: 0, y: 0 };
- //Calculation cardinality position using angle, center point on the line/curve but pendicular and with offset-distance
+ //Calculation cardinality position using angle, center point on the line/curve but perpendicular and with offset-distance
cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2;
cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2;
return cardinalityPosition;
@@ -409,71 +403,36 @@ function calcTerminalLabelPosition(
position: 'start_left' | 'start_right' | 'end_left' | 'end_right',
_points: Point[]
): Point {
- // Todo looking to faster cloning method
- let points = JSON.parse(JSON.stringify(_points));
- let prevPoint;
+ const points = structuredClone(_points);
log.info('our points', points);
if (position !== 'start_left' && position !== 'start_right') {
- points = points.reverse();
+ points.reverse();
}
- points.forEach((point) => {
- prevPoint = point;
- });
-
// Traverse only 25 total distance along points to find cardinality point
const distanceToCardinalityPoint = 25 + terminalMarkerSize;
+ const center = calculatePoint(points, distanceToCardinalityPoint);
- let remainingDistance = distanceToCardinalityPoint;
- let center;
- prevPoint = undefined;
- points.forEach((point) => {
- if (prevPoint && !center) {
- const vectorDistance = distance(point, prevPoint);
- if (vectorDistance < remainingDistance) {
- remainingDistance -= vectorDistance;
- } else {
- // The point is remainingDistance from prevPoint in the vector between prevPoint and point
- // Calculate the coordinates
- const distanceRatio = remainingDistance / vectorDistance;
- if (distanceRatio <= 0) {
- center = prevPoint;
- }
- if (distanceRatio >= 1) {
- center = { x: point.x, y: point.y };
- }
- if (distanceRatio > 0 && distanceRatio < 1) {
- center = {
- x: (1 - distanceRatio) * prevPoint.x + distanceRatio * point.x,
- y: (1 - distanceRatio) * prevPoint.y + distanceRatio * point.y,
- };
- }
- }
- }
- prevPoint = point;
- });
// if relation is present (Arrows will be added), change cardinality point off-set distance (d)
const d = 10 + terminalMarkerSize * 0.5;
//Calculate Angle for x and y axis
const angle = Math.atan2(points[0].y - center.y, points[0].x - center.x);
- const cardinalityPosition = { x: 0, y: 0 };
+ const cardinalityPosition: Point = { x: 0, y: 0 };
+ //Calculation cardinality position using angle, center point on the line/curve but perpendicular and with offset-distance
- //Calculation cardinality position using angle, center point on the line/curve but pendicular and with offset-distance
-
- cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2;
- cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2;
if (position === 'start_left') {
cardinalityPosition.x = Math.sin(angle + Math.PI) * d + (points[0].x + center.x) / 2;
cardinalityPosition.y = -Math.cos(angle + Math.PI) * d + (points[0].y + center.y) / 2;
- }
- if (position === 'end_right') {
+ } else if (position === 'end_right') {
cardinalityPosition.x = Math.sin(angle - Math.PI) * d + (points[0].x + center.x) / 2 - 5;
cardinalityPosition.y = -Math.cos(angle - Math.PI) * d + (points[0].y + center.y) / 2 - 5;
- }
- if (position === 'end_left') {
+ } else if (position === 'end_left') {
cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2 - 5;
cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2 - 5;
+ } else {
+ cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2;
+ cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2;
}
return cardinalityPosition;
}
@@ -499,7 +458,7 @@ export function getStylesFromArray(arr: string[]): { style: string; labelStyle:
}
}
- return { style: style, labelStyle: labelStyle };
+ return { style, labelStyle };
}
let cnt = 0;
@@ -511,10 +470,10 @@ export const generateId = () => {
/**
* Generates a random hexadecimal id of the given length.
*
- * @param length - Length of ID.
- * @returns The generated ID.
+ * @param length - Length of string.
+ * @returns The generated string.
*/
-function makeid(length: number): string {
+function makeRandomHex(length: number): string {
let result = '';
const characters = '0123456789abcdef';
const charactersLength = characters.length;
@@ -524,8 +483,8 @@ function makeid(length: number): string {
return result;
}
-export const random = (options) => {
- return makeid(options.length);
+export const random = (options: { length: number }) => {
+ return makeRandomHex(options.length);
};
export const getTextObj = function () {
@@ -541,6 +500,7 @@ export const getTextObj = function () {
rx: 0,
ry: 0,
valign: undefined,
+ text: '',
};
};
@@ -571,7 +531,7 @@ export const drawSimpleText = function (
const [, _fontSizePx] = parseFontSize(textData.fontSize);
- const textElem = elem.append('text');
+ const textElem = elem.append('text') as any;
textElem.attr('x', textData.x);
textElem.attr('y', textData.y);
textElem.style('text-anchor', textData.anchor);
@@ -579,6 +539,7 @@ export const drawSimpleText = function (
textElem.style('font-size', _fontSizePx);
textElem.style('font-weight', textData.fontWeight);
textElem.attr('fill', textData.fill);
+
if (textData.class !== undefined) {
textElem.attr('class', textData.class);
}
@@ -598,9 +559,9 @@ interface WrapLabelConfig {
joinWith: string;
}
-export const wrapLabel: (label: string, maxWidth: string, config: WrapLabelConfig) => string =
+export const wrapLabel: (label: string, maxWidth: number, config: WrapLabelConfig) => string =
memoize(
- (label: string, maxWidth: string, config: WrapLabelConfig): string => {
+ (label: string, maxWidth: number, config: WrapLabelConfig): string => {
if (!label) {
return label;
}
@@ -612,7 +573,7 @@ export const wrapLabel: (label: string, maxWidth: string, config: WrapLabelConfi
return label;
}
const words = label.split(' ');
- const completedLines = [];
+ const completedLines: string[] = [];
let nextLine = '';
words.forEach((word, index) => {
const wordLength = calculateTextWidth(`${word} `, config);
@@ -697,10 +658,6 @@ export function calculateTextHeight(
text: Parameters[0],
config: Parameters[1]
): ReturnType['height'] {
- config = Object.assign(
- { fontSize: 12, fontWeight: 400, fontFamily: 'Arial', margin: 15 },
- config
- );
return calculateTextDimensions(text, config).height;
}
@@ -716,20 +673,9 @@ export function calculateTextWidth(
text: Parameters[0],
config: Parameters[1]
): ReturnType['width'] {
- config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
return calculateTextDimensions(text, config).width;
}
-interface TextDimensionConfig {
- fontSize?: number;
- fontWeight?: number;
- fontFamily?: string;
-}
-interface TextDimensions {
- width: number;
- height: number;
- lineHeight?: number;
-}
/**
* This calculates the dimensions of the given text, font size, font family, font weight, and
* margins.
@@ -744,8 +690,7 @@ export const calculateTextDimensions: (
config: TextDimensionConfig
) => TextDimensions = memoize(
(text: string, config: TextDimensionConfig): TextDimensions => {
- config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
- const { fontSize, fontFamily, fontWeight } = config;
+ const { fontSize = 12, fontFamily = 'Arial', fontWeight = 400 } = config;
if (!text) {
return { width: 0, height: 0 };
}
@@ -769,12 +714,14 @@ export const calculateTextDimensions: (
const g = body.append('svg');
for (const fontFamily of fontFamilies) {
- let cheight = 0;
+ let cHeight = 0;
const dim = { width: 0, height: 0, lineHeight: 0 };
for (const line of lines) {
const textObj = getTextObj();
textObj.text = line || ZERO_WIDTH_SPACE;
+ // @ts-ignore TODO: Fix D3 types
const textElem = drawSimpleText(g, textObj)
+ // @ts-ignore TODO: Fix D3 types
.style('font-size', _fontSizePx)
.style('font-weight', fontWeight)
.style('font-family', fontFamily);
@@ -784,9 +731,9 @@ export const calculateTextDimensions: (
throw new Error('svg element not in render tree');
}
dim.width = Math.round(Math.max(dim.width, bBox.width));
- cheight = Math.round(bBox.height);
- dim.height += cheight;
- dim.lineHeight = Math.round(Math.max(dim.lineHeight, cheight));
+ cHeight = Math.round(bBox.height);
+ dim.height += cHeight;
+ dim.lineHeight = Math.round(Math.max(dim.lineHeight, cHeight));
}
dims.push(dim);
}
@@ -807,25 +754,18 @@ export const calculateTextDimensions: (
(text, config) => `${text}${config.fontSize}${config.fontWeight}${config.fontFamily}`
);
-export const initIdGenerator = class iterator {
- constructor(deterministic, seed?: any) {
- this.deterministic = deterministic;
+export class InitIDGenerator {
+ private count = 0;
+ public next: () => number;
+ constructor(deterministic = false, seed?: string) {
// TODO: Seed is only used for length?
- this.seed = seed;
-
+ // v11: Use the actual value of seed string to generate an initial value for count.
this.count = seed ? seed.length : 0;
+ this.next = deterministic ? () => this.count++ : () => Date.now();
}
+}
- next() {
- if (!this.deterministic) {
- return Date.now();
- }
-
- return this.count++;
- }
-};
-
-let decoder;
+let decoder: HTMLDivElement;
/**
* Decodes HTML, source: {@link https://github.com/shrpne/entity-decode/blob/v2.0.1/browser.js}
@@ -837,102 +777,23 @@ export const entityDecode = function (html: string): string {
decoder = decoder || document.createElement('div');
// Escape HTML before decoding for HTML Entities
html = escape(html).replace(/%26/g, '&').replace(/%23/g, '#').replace(/%3B/g, ';');
- // decoding
decoder.innerHTML = html;
- return unescape(decoder.textContent);
-};
-
-/**
- * Sanitizes directive objects
- *
- * @param args - Directive's JSON
- */
-export const sanitizeDirective = (args: unknown): void => {
- log.debug('sanitizeDirective called with', args);
-
- // Return if not an object
- if (typeof args !== 'object' || args == null) {
- return;
- }
-
- // Sanitize each element if an array
- if (Array.isArray(args)) {
- args.forEach((arg) => sanitizeDirective(arg));
- return;
- }
-
- // Sanitize each key if an object
- for (const key of Object.keys(args)) {
- log.debug('Checking key', key);
- if (
- key.startsWith('__') ||
- key.includes('proto') ||
- key.includes('constr') ||
- !configKeys.has(key) ||
- args[key] == null
- ) {
- log.debug('sanitize deleting key: ', key);
- delete args[key];
- continue;
- }
-
- // Recurse if an object
- if (typeof args[key] === 'object') {
- log.debug('sanitizing object', key);
- sanitizeDirective(args[key]);
- continue;
- }
-
- const cssMatchers = ['themeCSS', 'fontFamily', 'altFontFamily'];
- for (const cssKey of cssMatchers) {
- if (key.includes(cssKey)) {
- log.debug('sanitizing css option', key);
- args[key] = sanitizeCss(args[key]);
- }
- }
- }
-
- if (args.themeVariables) {
- for (const k of Object.keys(args.themeVariables)) {
- const val = args.themeVariables[k];
- if (val?.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) {
- args.themeVariables[k] = '';
- }
- }
- }
- log.debug('After sanitization', args);
-};
-
-export const sanitizeCss = (str: string): string => {
- let startCnt = 0;
- let endCnt = 0;
-
- for (const element of str) {
- if (startCnt < endCnt) {
- return '{ /* ERROR: Unbalanced CSS */ }';
- }
- if (element === '{') {
- startCnt++;
- } else if (element === '}') {
- endCnt++;
- }
- }
- if (startCnt !== endCnt) {
- return '{ /* ERROR: Unbalanced CSS */ }';
- }
- // Todo add more checks here
- return str;
+ // eslint-disable-next-line @typescript-eslint/no-non-null-assertion
+ return unescape(decoder.textContent!);
};
export interface DetailedError {
str: string;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
hash: any;
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
error?: any;
message?: string;
}
/** @param error - The error to check */
-export function isDetailedError(error: unknown): error is DetailedError {
+// eslint-disable-next-line @typescript-eslint/no-explicit-any
+export function isDetailedError(error: any): error is DetailedError {
return 'str' in error;
}
@@ -953,7 +814,7 @@ export function getErrorMessage(error: unknown): string {
* @param title - The title. If empty, returns immediately.
*/
export const insertTitle = (
- parent,
+ parent: D3Element,
cssClass: string,
titleTopMargin: number,
title?: string
@@ -961,7 +822,10 @@ export const insertTitle = (
if (!title) {
return;
}
- const bounds = parent.node().getBBox();
+ const bounds = parent.node()?.getBBox();
+ if (!bounds) {
+ return;
+ }
parent
.append('text')
.text(title)
@@ -984,7 +848,7 @@ export const parseFontSize = (fontSize: string | number | undefined): [number?,
return [fontSize, fontSize + 'px'];
}
- const fontSizeNumber = parseInt(fontSize, 10);
+ const fontSizeNumber = parseInt(fontSize ?? '', 10);
if (Number.isNaN(fontSizeNumber)) {
// if a number value can't be parsed, return null for both values
return [undefined, undefined];
@@ -1020,9 +884,7 @@ export default {
random,
runFunc,
entityDecode,
- initIdGenerator,
- sanitizeDirective,
- sanitizeCss,
insertTitle,
parseFontSize,
+ InitIDGenerator,
};
diff --git a/packages/mermaid/src/utils/lineWithOffset.ts b/packages/mermaid/src/utils/lineWithOffset.ts
new file mode 100644
index 000000000..f348d3eb3
--- /dev/null
+++ b/packages/mermaid/src/utils/lineWithOffset.ts
@@ -0,0 +1,92 @@
+import type { EdgeData, Point } from '../types.js';
+
+// We need to draw the lines a bit shorter to avoid drawing
+// under any transparent markers.
+// The offsets are calculated from the markers' dimensions.
+const markerOffsets = {
+ aggregation: 18,
+ extension: 18,
+ composition: 18,
+ dependency: 6,
+ lollipop: 13.5,
+ arrow_point: 5.3,
+} as const;
+
+/**
+ * Calculate the deltas and angle between two points
+ * @param point1 - First point
+ * @param point2 - Second point
+ * @returns The angle, deltaX and deltaY
+ */
+function calculateDeltaAndAngle(
+ point1: Point | [number, number],
+ point2: Point | [number, number]
+): { angle: number; deltaX: number; deltaY: number } {
+ point1 = pointTransformer(point1);
+ point2 = pointTransformer(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 };
+}
+
+const pointTransformer = (data: Point | [number, number]) => {
+ if (Array.isArray(data)) {
+ return { x: data[0], y: data[1] };
+ }
+ return data;
+};
+
+export const getLineFunctionsWithOffset = (
+ edge: Pick
+) => {
+ return {
+ x: function (d: Point | [number, number], i: number, data: (Point | [number, number])[]) {
+ let offset = 0;
+ if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
+ // Handle first point
+ // Calculate the angle and delta between the first two points
+ const { angle, deltaX } = calculateDeltaAndAngle(data[0], data[1]);
+ // Calculate the offset based on the angle and the marker's dimensions
+ offset =
+ markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets] *
+ Math.cos(angle) *
+ (deltaX >= 0 ? 1 : -1);
+ } else if (i === data.length - 1 && Object.hasOwn(markerOffsets, edge.arrowTypeEnd)) {
+ // Handle last point
+ // Calculate the angle and delta between the last two points
+ const { angle, deltaX } = calculateDeltaAndAngle(
+ data[data.length - 1],
+ data[data.length - 2]
+ );
+ offset =
+ markerOffsets[edge.arrowTypeEnd as keyof typeof markerOffsets] *
+ Math.cos(angle) *
+ (deltaX >= 0 ? 1 : -1);
+ }
+ return pointTransformer(d).x + offset;
+ },
+ y: function (d: Point | [number, number], i: number, data: (Point | [number, number])[]) {
+ // Same handling as X above
+ let offset = 0;
+ if (i === 0 && Object.hasOwn(markerOffsets, edge.arrowTypeStart)) {
+ const { angle, deltaY } = calculateDeltaAndAngle(data[0], data[1]);
+ offset =
+ markerOffsets[edge.arrowTypeStart as keyof typeof markerOffsets] *
+ 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 as keyof typeof markerOffsets] *
+ Math.abs(Math.sin(angle)) *
+ (deltaY >= 0 ? 1 : -1);
+ }
+ return pointTransformer(d).y + offset;
+ },
+ };
+};
diff --git a/packages/mermaid/src/utils/sanitizeDirective.ts b/packages/mermaid/src/utils/sanitizeDirective.ts
new file mode 100644
index 000000000..9b7e7da5c
--- /dev/null
+++ b/packages/mermaid/src/utils/sanitizeDirective.ts
@@ -0,0 +1,84 @@
+import { configKeys } from '../defaultConfig.js';
+import { log } from '../logger.js';
+
+/**
+ * Sanitizes directive objects
+ *
+ * @param args - Directive's JSON
+ */
+export const sanitizeDirective = (args: any): void => {
+ log.debug('sanitizeDirective called with', args);
+
+ // Return if not an object
+ if (typeof args !== 'object' || args == null) {
+ return;
+ }
+
+ // Sanitize each element if an array
+ if (Array.isArray(args)) {
+ args.forEach((arg) => sanitizeDirective(arg));
+ return;
+ }
+
+ // Sanitize each key if an object
+ for (const key of Object.keys(args)) {
+ log.debug('Checking key', key);
+ if (
+ key.startsWith('__') ||
+ key.includes('proto') ||
+ key.includes('constr') ||
+ !configKeys.has(key) ||
+ args[key] == null
+ ) {
+ log.debug('sanitize deleting key: ', key);
+ delete args[key];
+ continue;
+ }
+
+ // Recurse if an object
+ if (typeof args[key] === 'object') {
+ log.debug('sanitizing object', key);
+ sanitizeDirective(args[key]);
+ continue;
+ }
+
+ const cssMatchers = ['themeCSS', 'fontFamily', 'altFontFamily'];
+ for (const cssKey of cssMatchers) {
+ if (key.includes(cssKey)) {
+ log.debug('sanitizing css option', key);
+ args[key] = sanitizeCss(args[key]);
+ }
+ }
+ }
+
+ if (args.themeVariables) {
+ for (const k of Object.keys(args.themeVariables)) {
+ const val = args.themeVariables[k];
+ if (val?.match && !val.match(/^[\d "#%(),.;A-Za-z]+$/)) {
+ args.themeVariables[k] = '';
+ }
+ }
+ }
+ log.debug('After sanitization', args);
+};
+
+export const sanitizeCss = (str: string): string => {
+ let startCnt = 0;
+ let endCnt = 0;
+
+ for (const element of str) {
+ if (startCnt < endCnt) {
+ return '{ /* ERROR: Unbalanced CSS */ }';
+ }
+ if (element === '{') {
+ startCnt++;
+ } else if (element === '}') {
+ endCnt++;
+ }
+ }
+ if (startCnt !== endCnt) {
+ return '{ /* ERROR: Unbalanced CSS */ }';
+ }
+ // Todo add more checks here
+ return str;
+};
diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml
index b86be8ff1..5a04bb353 100644
--- a/pnpm-lock.yaml
+++ b/pnpm-lock.yaml
@@ -475,8 +475,8 @@ importers:
specifier: ^0.16.0
version: 0.16.0(vite@4.3.9)(workbox-build@7.0.0)(workbox-window@7.0.0)
vitepress:
- specifier: 1.0.0-rc.10
- version: 1.0.0-rc.10(@algolia/client-search@4.19.1)(@types/node@18.16.0)(search-insights@2.6.0)
+ specifier: 1.0.0-rc.12
+ version: 1.0.0-rc.12(@algolia/client-search@4.19.1)(@types/node@18.16.0)(search-insights@2.6.0)
workbox-window:
specifier: ^7.0.0
version: 7.0.0
@@ -13913,6 +13913,15 @@ packages:
vscode-textmate: 8.0.0
dev: true
+ /shiki@0.14.4:
+ resolution: {integrity: sha512-IXCRip2IQzKwxArNNq1S+On4KPML3Yyn8Zzs/xRgcgOWIr8ntIK3IKzjFPfjy/7kt9ZMjc+FItfqHRBg8b6tNQ==}
+ dependencies:
+ ansi-sequence-parser: 1.1.0
+ jsonc-parser: 3.2.0
+ vscode-oniguruma: 1.7.0
+ vscode-textmate: 8.0.0
+ dev: true
+
/side-channel@1.0.4:
resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==}
dependencies:
@@ -15456,8 +15465,8 @@ packages:
- terser
dev: true
- /vitepress@1.0.0-rc.10(@algolia/client-search@4.19.1)(@types/node@18.16.0)(search-insights@2.6.0):
- resolution: {integrity: sha512-+MsahIWqq5WUEmj6MR4obcKYbT7im07jZPCQPdNJExkeOSbOAJ4xypSLx88x7rvtzWHhHc5aXbOhCRvGEGjFrw==}
+ /vitepress@1.0.0-rc.12(@algolia/client-search@4.19.1)(@types/node@18.16.0)(search-insights@2.6.0):
+ resolution: {integrity: sha512-mZknN5l9lgbBjXwumwdOQQDM+gPivswFEykEQeenY0tv7eocS+bb801IpFZT3mFV6YRhSddmbutHlFgPPADjEg==}
hasBin: true
dependencies:
'@docsearch/css': 3.5.2
@@ -15468,7 +15477,7 @@ packages:
focus-trap: 7.5.2
mark.js: 8.11.1
minisearch: 6.1.0
- shiki: 0.14.3
+ shiki: 0.14.4
vite: 4.4.9(@types/node@18.16.0)
vue: 3.3.4
transitivePeerDependencies: