mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-14 06:43:25 +08:00
Merge branch 'develop' into pr/rhysd/4359
* develop: (37 commits) fix: Arrow markers in flowchart-elk Bump version chore: Fix type in 'getLineFunctionsWithOffset' Update cypress/platform/marker_unique_id.html refactor: Add getLineFunctionsWithOffset function refactor: Move EdgeData to types fix: PointStart marker refX Added cypress test chore(deps): update all patch dependencies refactor: Fix typings in utils.ts Give markers unique id's per graph chore: Add @internal to createCSSStyles chore: Bump version refactor: Remove unused variables fix: #4818 support `getClasses` in external diagrams. Remove unnecessary tests Remove optional chaining chore: Update docs refactor: Use `||` instead of `??` Update flowchart.md (#4798) ...
This commit is contained in:
commit
0dbebe953b
16
README.md
16
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 [<a href="https://mermaid-js.github.io/mermaid/#/stateDiagram">docs</a> - <a href="https://mermaid.live/edit#pako:eNpdkEFvgzAMhf8K8nEqpYSNthx22Xbcqcexg0sCiZQQlDhIFeK_L8A6TfXp6fOz9ewJGssFVOAJSbwr7ByadGR1n8T6evpO0vQ1uZDSekOrXGFsPqJPO6q-2-imH8f_0TeHXm50lfelsAMjnEHFY6xpMdRAUhhRQxUlFy0GTTXU_RytYeAx-AdXZB1ULWovdoCB7OXWN1CRC-Ju-r3uz6UtchGHJqDbsPygU57iysb2reoWHpyOWBINvsqypb3vFMlw3TfWZF5xiY7keC6zkpUnZIUojwW-FAVvrvn51LLnvOXHQ84Q5nn-AVtLcwk">live editor</a>]
|
||||
|
@ -22,6 +22,7 @@
|
||||
"brkt",
|
||||
"brolin",
|
||||
"brotli",
|
||||
"catmull",
|
||||
"città",
|
||||
"classdef",
|
||||
"codedoc",
|
||||
|
@ -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);
|
||||
};
|
||||
|
||||
|
@ -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(
|
||||
`
|
||||
|
10
cypress/integration/rendering/marker_unique_id.spec.js
Normal file
10
cypress/integration/rendering/marker_unique_id.spec.js
Normal file
@ -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 },
|
||||
});
|
||||
});
|
||||
});
|
52
cypress/platform/marker_unique_id.html
Normal file
52
cypress/platform/marker_unique_id.html
Normal file
@ -0,0 +1,52 @@
|
||||
<html>
|
||||
<head> </head>
|
||||
<body>
|
||||
<h1>Example</h1>
|
||||
<pre class="mermaid">
|
||||
%%{init:{"theme":"base", "themeVariables": {"lineColor":"red"}}}%%
|
||||
flowchart LR
|
||||
subgraph red
|
||||
A --> B
|
||||
end
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
%%{init:{"theme":"base", "themeVariables": {"lineColor":"blue"}}}%%
|
||||
flowchart LR
|
||||
subgraph black
|
||||
A --> B
|
||||
end
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
lineColor: yellow
|
||||
---
|
||||
flowchart LR
|
||||
subgraph red
|
||||
A --> B
|
||||
end
|
||||
</pre>
|
||||
<pre class="mermaid">
|
||||
---
|
||||
config:
|
||||
theme: base
|
||||
themeVariables:
|
||||
lineColor: green
|
||||
---
|
||||
flowchart LR
|
||||
subgraph black
|
||||
A --> B
|
||||
end
|
||||
</pre>
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
mermaid.initialize({ startOnLoad: true, logLevel: 0 });
|
||||
|
||||
if (window.Cypress) {
|
||||
window.rendered = true;
|
||||
}
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
@ -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;
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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)
|
||||
|
@ -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):
|
||||
|
||||
|
@ -241,7 +241,7 @@ The following formatting strings are supported:
|
||||
|
||||
More info in: <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`.
|
||||
|
||||
@ -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: <https://github.com/d3/d3-time#interval_every>
|
||||
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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\"",
|
||||
|
@ -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",
|
||||
|
@ -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<DiagramMetadata, 'title'> = {}) {
|
||||
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<Diagram> => {
|
||||
export const getDiagramFromText = async (
|
||||
text: string,
|
||||
metadata: Pick<DiagramMetadata, 'title'> = {}
|
||||
): Promise<Diagram> => {
|
||||
const type = detectType(text, configApi.getConfig());
|
||||
try {
|
||||
// Trying to find the diagram
|
||||
@ -101,5 +95,5 @@ export const getDiagramFromText = async (text: string): Promise<Diagram> => {
|
||||
const { id, diagram } = await loader();
|
||||
registerDiagram(id, diagram);
|
||||
}
|
||||
return new Diagram(text);
|
||||
return new Diagram(text, metadata);
|
||||
};
|
||||
|
@ -13,7 +13,6 @@ export const mermaidAPI = {
|
||||
svg: '<svg></svg>',
|
||||
}),
|
||||
parse: mAPI.parse,
|
||||
parseDirective: vi.fn(),
|
||||
initialize: vi.fn(),
|
||||
getConfig: configApi.getConfig,
|
||||
setConfig: configApi.setConfig,
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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)$/
|
||||
* ```
|
||||
*
|
||||
*/
|
||||
|
@ -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:
|
||||
}
|
||||
|
@ -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 = {};
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
};
|
||||
|
@ -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: () => {
|
||||
|
@ -41,7 +41,11 @@ describe('DiagramAPI', () => {
|
||||
},
|
||||
parser: { yy: {} },
|
||||
},
|
||||
renderer: {},
|
||||
renderer: {
|
||||
draw: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
styles: {},
|
||||
},
|
||||
detector
|
||||
|
@ -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<string, DiagramDefinition> = {};
|
||||
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 => {
|
||||
|
@ -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",
|
||||
}
|
||||
`);
|
||||
});
|
||||
});
|
||||
|
@ -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,
|
||||
};
|
||||
}
|
||||
|
@ -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<DiagramDefinition, 'db'>
|
||||
) => Record<string, DiagramStyleClassDef>;
|
||||
}
|
||||
|
||||
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<void>;
|
||||
|
||||
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<HTMLIFrameElement, unknown, Element | null, unknown>;
|
||||
|
||||
export type SVG = d3.Selection<SVGSVGElement, unknown, Element | null, unknown>;
|
||||
|
@ -34,7 +34,11 @@ describe('diagram detection', () => {
|
||||
yy: {},
|
||||
},
|
||||
},
|
||||
renderer: {},
|
||||
renderer: {
|
||||
draw: () => {
|
||||
// no-op
|
||||
},
|
||||
},
|
||||
styles: {},
|
||||
},
|
||||
})
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
},
|
||||
};
|
||||
|
@ -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';
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_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)}
|
||||
|
@ -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,
|
||||
|
@ -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());
|
||||
|
||||
|
@ -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;
|
||||
|
@ -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';
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_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
|
||||
|
@ -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,
|
||||
|
@ -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");}
|
||||
<acc_descr_multiline>[\}] { this.popState(); }
|
||||
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
|
||||
\%\%\{ { this.begin('open_directive'); return 'open_directive'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_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'); }
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -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<string, import('../../../diagram-api/types.js').DiagramStyleClassDef>} 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
|
||||
|
@ -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,
|
||||
|
@ -338,7 +338,7 @@ export const addEdges = function (edges, g, diagObj) {
|
||||
*
|
||||
* @param text
|
||||
* @param diagObj
|
||||
* @returns {object} ClassDef styles
|
||||
* @returns {Record<string, import('../../diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles
|
||||
*/
|
||||
export const getClasses = function (text, diagObj) {
|
||||
return diagObj.db.getClasses();
|
||||
|
@ -269,7 +269,7 @@ export const addEdges = function (edges, g, diagObj) {
|
||||
*
|
||||
* @param text
|
||||
* @param diagObj
|
||||
* @returns {object} ClassDef styles
|
||||
* @returns {Record<string, import('../../diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles
|
||||
*/
|
||||
export const getClasses = function (text, diagObj) {
|
||||
log.info('Extracting classes');
|
||||
|
@ -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'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
|
||||
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
|
||||
<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 */
|
||||
{ $$ = [];}
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
|
||||
|
||||
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
|
||||
<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'); }
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -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,
|
||||
|
@ -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');
|
||||
});
|
@ -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
|
||||
|
@ -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'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
|
||||
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
|
||||
<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
|
||||
|
@ -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'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_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'); }
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -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
|
||||
|
@ -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<PieDiagramConfig> = structuredClone(DEFAULT_PIE_CONFIG);
|
||||
|
||||
const getConfig = (): Required<PieDiagramConfig> => structuredClone(config);
|
||||
|
||||
const parseDirective: ParseDirectiveDefinition = (statement, context, type) => {
|
||||
_parseDirective(this, statement, context, type);
|
||||
};
|
||||
|
||||
const clear = (): void => {
|
||||
sections = structuredClone(DEFAULT_PIE_DB.sections);
|
||||
showData = DEFAULT_PIE_DB.showData;
|
||||
@ -67,7 +61,6 @@ const getShowData = (): boolean => showData;
|
||||
export const db: PieDB = {
|
||||
getConfig,
|
||||
|
||||
parseDirective,
|
||||
clear,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
|
@ -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<PieDiagramConfig>;
|
||||
|
||||
// common db
|
||||
parseDirective: ParseDirectiveDefinition;
|
||||
clear: () => void;
|
||||
setDiagramTitle: (title: string) => void;
|
||||
getDiagramTitle: () => string;
|
||||
|
@ -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'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_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
|
||||
|
@ -19,7 +19,6 @@ const mockDB: Record<string, Mock<any, any>> = {
|
||||
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',
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_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 */ { $$ = [] }
|
||||
|
@ -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,
|
||||
|
@ -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'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
|
||||
[\n]+ return 'NEWLINE';
|
||||
\s+ /* skip all whitespace */
|
||||
<ID,ALIAS,LINE>((?!\n)\s)+ /* skip same-line whitespace */
|
||||
<INITIAL,ID,ALIAS,LINE,arg_directive,type_directive,open_directive>\#[^\n]* /* skip comments */
|
||||
<INITIAL,ID,ALIAS,LINE>\#[^\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'); }
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -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,
|
||||
|
@ -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);
|
||||
});
|
||||
});
|
||||
|
@ -10,4 +10,7 @@ export const diagram: DiagramDefinition = {
|
||||
db,
|
||||
renderer,
|
||||
styles,
|
||||
init: ({ wrap }) => {
|
||||
db.setWrap(wrap);
|
||||
},
|
||||
};
|
||||
|
@ -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'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_directive>((?:(?!\}\%\%).|\n)*) return 'arg_directive';
|
||||
\%\%(?!\{)[^\n]* /* skip comments */
|
||||
[^\}]\%\%[^\n]* /* skip comments */{ /*console.log('Crap after close');*/ }
|
||||
|
||||
[\n]+ return 'NL';
|
||||
[\s]+ /* skip all whitespace */
|
||||
<ID,STATE,struct,LINE,open_directive,type_directive,arg_directive,close_directive>((?!\n)\s)+ /* skip same-line whitespace */
|
||||
<INITIAL,ID,STATE,struct,LINE,open_directive,type_directive,arg_directive,close_directive>\#[^\n]* /* skip comments */
|
||||
<ID,STATE,struct,LINE>((?!\n)\s)+ /* skip same-line whitespace */
|
||||
<INITIAL,ID,STATE,struct,LINE>\#[^\n]* /* skip comments */
|
||||
\%%[^\n]* /* skip comments */
|
||||
"scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; }
|
||||
<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'); }
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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<string, import('../../diagram-api/types.js').DiagramStyleClassDef>} ClassDef styles (a Map with keys = strings, values = )
|
||||
*/
|
||||
export const getClasses = function (text, diagramObj) {
|
||||
diagramObj.db.extract(diagramObj.db.getRootDocV2());
|
||||
|
@ -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'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_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'); }
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -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 () {
|
||||
|
@ -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,
|
||||
};
|
||||
|
@ -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,
|
||||
|
@ -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'; }
|
||||
<open_directive>((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; }
|
||||
<type_directive>":" { this.popState(); this.begin('arg_directive'); return ':'; }
|
||||
<type_directive,arg_directive>\}\%\% { this.popState(); this.popState(); return 'close_directive'; }
|
||||
<arg_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'); }
|
||||
;
|
||||
|
||||
%%
|
||||
|
@ -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;
|
||||
}
|
||||
};
|
@ -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;
|
||||
|
@ -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"
|
||||
}
|
||||
}
|
||||
|
@ -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):
|
||||
|
||||
|
@ -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
|
||||
|
||||
|
@ -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
|
||||
|
@ -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) => {
|
||||
|
@ -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[] = [];
|
||||
|
@ -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);
|
||||
});
|
||||
|
@ -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<boolean> {
|
||||
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<string, DiagramStyleClassDef> | 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<string, DiagramStyleClassDef>,
|
||||
classDefs: Record<string, DiagramStyleClassDef> | 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<RenderResult> {
|
||||
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,
|
||||
|
65
packages/mermaid/src/preprocess.ts
Normal file
65
packages/mermaid/src/preprocess.ts
Normal file
@ -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,
|
||||
};
|
||||
}
|
@ -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
|
||||
|
34
packages/mermaid/src/types.ts
Normal file
34
packages/mermaid/src/types.ts
Normal file
@ -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;
|
||||
}
|
@ -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'
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -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<typeof calculateTextDimensions>[0],
|
||||
config: Parameters<typeof calculateTextDimensions>[1]
|
||||
): ReturnType<typeof calculateTextDimensions>['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<typeof calculateTextDimensions>[0],
|
||||
config: Parameters<typeof calculateTextDimensions>[1]
|
||||
): ReturnType<typeof calculateTextDimensions>['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,
|
||||
};
|
||||
|
92
packages/mermaid/src/utils/lineWithOffset.ts
Normal file
92
packages/mermaid/src/utils/lineWithOffset.ts
Normal file
@ -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<EdgeData, 'arrowTypeStart' | 'arrowTypeEnd'>
|
||||
) => {
|
||||
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;
|
||||
},
|
||||
};
|
||||
};
|
84
packages/mermaid/src/utils/sanitizeDirective.ts
Normal file
84
packages/mermaid/src/utils/sanitizeDirective.ts
Normal file
@ -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;
|
||||
};
|
19
pnpm-lock.yaml
generated
19
pnpm-lock.yaml
generated
@ -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:
|
||||
|
Loading…
x
Reference in New Issue
Block a user