merge MERMAID/develop into branch; fix ts/es lint errors

This commit is contained in:
Ashley Engelund (weedySeaDragon @ github) 2022-10-25 08:38:57 -07:00
commit c413119064
76 changed files with 1815 additions and 1215 deletions

View File

@ -16,7 +16,6 @@
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:jsdoc/recommended",
"plugin:json/recommended",
"plugin:markdown/recommended",
"plugin:@cspell/recommended",
@ -24,19 +23,10 @@
],
"plugins": ["@typescript-eslint", "no-only-tests", "html", "jest", "jsdoc", "json", "@cspell"],
"rules": {
"curly": "error",
"no-console": "error",
"no-prototype-builtins": "off",
"no-unused-vars": "off",
"jsdoc/check-indentation": "off",
"jsdoc/check-alignment": "off",
"jsdoc/check-line-alignment": "off",
"jsdoc/multiline-blocks": "off",
"jsdoc/newline-after-description": "off",
"jsdoc/tag-lines": "off",
"jsdoc/require-param-description": "off",
"jsdoc/require-param-type": "off",
"jsdoc/require-returns": "off",
"jsdoc/require-returns-description": "off",
"cypress/no-async-tests": "off",
"@typescript-eslint/ban-ts-comment": [
"error",
@ -72,6 +62,29 @@
"no-console": "off"
}
},
{
"files": ["*.{js,jsx,mjs,cjs}"],
"extends": ["plugin:jsdoc/recommended"],
"rules": {
"jsdoc/check-indentation": "off",
"jsdoc/check-alignment": "off",
"jsdoc/check-line-alignment": "off",
"jsdoc/multiline-blocks": "off",
"jsdoc/newline-after-description": "off",
"jsdoc/tag-lines": "off",
"jsdoc/require-param-description": "off",
"jsdoc/require-param-type": "off",
"jsdoc/require-returns": "off",
"jsdoc/require-returns-description": "off"
}
},
{
"files": ["*.{ts,tsx}"],
"plugins": ["tsdoc"],
"rules": {
"tsdoc/syntax": "error"
}
},
{
"files": ["*.spec.{ts,js}", "cypress/**", "demos/**", "**/docs/**"],
"rules": {

View File

@ -164,6 +164,7 @@ Class01 <|-- AveryLongClass : Cool
Class09 --> C2 : Where am I?
Class09 --* C3
Class09 --|> Class07
note "I love this diagram!\nDo you love it?"
Class07 : equals()
Class07 : Object[] elementData
Class01 : size()
@ -174,6 +175,7 @@ class Class10 {
int id
size()
}
note for Class10 "Cool class\nI said it's very cool class!"
```
### 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>]

View File

@ -58,7 +58,9 @@ export const imgSnapshotTest = (graphStr, _options, api = false, validation) =>
const url = mermaidUrl(graphStr, options, api);
cy.visit(url);
if (validation) cy.get('svg').should(validation);
if (validation) {
cy.get('svg').should(validation);
}
cy.get('svg');
// Default name to test title
@ -106,7 +108,9 @@ export const urlSnapshotTest = (url, _options, api = false, validation) => {
}
cy.visit(url);
if (validation) cy.get('svg').should(validation);
if (validation) {
cy.get('svg').should(validation);
}
cy.get('body');
// Default name to test title

View File

@ -478,4 +478,22 @@ describe('Class diagram V2', () => {
);
cy.get('svg');
});
it('18: should render a simple class diagram with notes', () => {
imgSnapshotTest(
`
classDiagram-v2
note "I love this diagram!\nDo you love it?"
class Class10 {
<<service>>
int id
size()
}
note for Class10 "Cool class\nI said it's very cool class!"
`,
{ logLevel: 1, flowchart: { htmlLabels: false } }
);
cy.get('svg');
});
});

View File

@ -407,4 +407,21 @@ describe('Class diagram', () => {
// // expect(svg).to.not.have.attr('style');
// });
// });
it('19: should render a simple class diagram with notes', () => {
imgSnapshotTest(
`
classDiagram
note "I love this diagram!\nDo you love it?"
class Class10 {
<<service>>
int id
size()
}
note for Class10 "Cool class\nI said it's very cool class!"
`,
{ logLevel: 1 }
);
cy.get('svg');
});
});

View File

@ -255,4 +255,22 @@ describe('Entity Relationship Diagram', () => {
);
cy.get('svg');
});
it('should render entities with aliases', () => {
renderGraph(
`
erDiagram
T1 one or zero to one or more T2 : test
T2 one or many optionally to zero or one T3 : test
T3 zero or more to zero or many T4 : test
T4 many(0) to many(1) T5 : test
T5 many optionally to one T6 : test
T6 only one optionally to only one T1 : test
T4 0+ to 1+ T6 : test
T1 1 to 1 T3 : test
`,
{ logLevel: 1 }
);
cy.get('svg');
});
});

View File

@ -56,21 +56,10 @@
<body>
<div>Security check</div>
<pre id="diagram" class="mermaid">
classDiagram
direction LR
class Student {
-idCard : IdCard
}
class IdCard{
-id : int
-name : string
}
class Bike{
-id : int
-name : string
}
Student "1" --o "1" IdCard : carries
Student "1" --o "1" Bike : rides
flowchart TD
A --> B
B --> C
A --> C
</pre>
<pre id="diagram" class="mermaid">
mindmap
@ -93,8 +82,14 @@ mindmap
Pen and paper
Mermaid
</pre>
<pre id="diagram" class="mermaid2">
example-diagram
<pre id="diagram" class="mermaid">
gantt
title Style today marker (vertical line should be 5px wide and half-transparent blue)
dateFormat YYYY-MM-DD
axisFormat %d
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
section Section1
Today: 1, -1h
</pre>
<!-- <div id="cy"></div> -->
@ -111,13 +106,18 @@ mindmap
theme: 'base',
startOnLoad: true,
logLevel: 0,
// basePath: './packages/',
// themeVariables: { darkMode: true },
flowchart: {
useMaxWidth: false,
htmlLabels: true,
},
gantt: {
useMaxWidth: false,
},
useMaxWidth: false,
lazyLoadedDiagrams: [
'./mermaid-mindmap-detector.esm.mjs',
'./mermaid-example-diagram-detector.esm.mjs',
],
// lazyLoadedDiagrams: ['../../mermaid-mindmap/registry.ts'],
});
function callback() {
alert('It worked');

View File

@ -120,7 +120,9 @@ const contentLoadedApi = function () {
(svgCode, bindFunctions) => {
div.innerHTML = svgCode;
if (bindFunctions) bindFunctions(div);
if (bindFunctions) {
bindFunctions(div);
}
},
div
);

View File

@ -74,22 +74,22 @@
<pre class="mermaid">
gantt
title Hide today marker (vertical line should not be visible)
dateFormat YYYY-MM-DD
axisFormat %d
dateFormat Z
axisFormat %d/%m
todayMarker off
section Section1
Today: 1, -1h
Today: 1, -01:00, 5min
</pre>
<hr />
<pre class="mermaid">
gantt
title Style today marker (vertical line should be 5px wide and half-transparent blue)
dateFormat YYYY-MM-DD
axisFormat %d
dateFormat Z
axisFormat %d/%m
todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
section Section1
Today: 1, -1h
Today: 1, -01:00, 5min
</pre>
<hr />

View File

@ -11,7 +11,9 @@ Mermaid can render class diagrams.
```mermaid-example
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
@ -35,7 +37,9 @@ classDiagram
```mermaid
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
@ -549,6 +553,10 @@ You would define these actions on a separate line after all classes have been de
- (_optional_) tooltip is a string to be displayed when hovering over element (note: The styles of the tooltip are set by the class .mermaidTooltip.)
- note: callback function will be called with the nodeId as parameter.
## Notes
It is possible to add notes on digram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"`
### Examples
_URL Link:_

View File

@ -110,10 +110,34 @@ Cardinality is a property that describes how many elements of another entity can
| `}o` | `o{` | Zero or more (no upper limit) |
| `}\|` | `\|{` | One or more (no upper limit) |
**Aliases**
| Value (left) | Value (right) | Alias for |
| :----------: | :-----------: | ------------ |
| one or zero | one or zero | Zero or one |
| zero or one | zero or one | Zero or one |
| one or more | one or more | One or more |
| one or many | one or many | One or more |
| many(1) | many(1) | One or more |
| 1+ | 1+ | One or more |
| zero or more | zero or more | Zero or more |
| zero or many | zero or many | Zero or more |
| many(0) | many(1) | Zero or more |
| 0+ | 0+ | Zero or more |
| only one | only one | Exactly one |
| 1 | 1 | Exactly one |
### Identification
Relationships may be classified as either _identifying_ or _non-identifying_ and these are rendered with either solid or dashed lines respectively. This is relevant when one of the entities in question can not have independent existence without the other. For example a firm that insures people to drive cars might need to store data on `NAMED-DRIVER`s. In modelling this we might start out by observing that a `CAR` can be driven by many `PERSON` instances, and a `PERSON` can drive many `CAR`s - both entities can exist without the other, so this is a non-identifying relationship that we might specify in Mermaid as: `PERSON }|..|{ CAR : "driver"`. Note the two dots in the middle of the relationship that will result in a dashed line being drawn between the two entities. But when this many-to-many relationship is resolved into two one-to-many relationships, we observe that a `NAMED-DRIVER` cannot exist without both a `PERSON` and a `CAR` - the relationships become identifying and would be specified using hyphens, which translate to a solid line:
**Aliases**
| Value | Alias for |
| :-----------: | :---------------: |
| to | _identifying_ |
| optionally to | _non-identifying_ |
```mermaid-example
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
@ -218,6 +242,7 @@ erDiagram
string lastName
int age
}
MANUFACTURER only one to zero or more CAR
```
```mermaid
@ -236,6 +261,7 @@ erDiagram
string lastName
int age
}
MANUFACTURER only one to zero or more CAR
```
### Other Things

View File

@ -40,20 +40,20 @@
};
</script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.1/min/vs/loader.min.js"
integrity="sha512-6bIYsGqvLpAiEBXPdRQeFf5cueeBECtAKJjIHer3BhBZNTV3WLcLA8Tm3pDfxUwTMIS+kAZwTUvJ1IrMdX8C5w=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.nls.min.js"
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.1/min/vs/editor/editor.main.nls.min.js"
integrity="sha512-CCv+DKWw+yZhxf4Z+ExT6HC5G+3S45TeMTYcJyYbdrv4BpK2vyALJ4FoVR/KGWDIPu7w4tNCOC9MJQIkYPR5FA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.js"
integrity="sha512-iVHvVf6TQEoF5oQrocjP88wstImQHQZCxGsa5nKYOs6gXWzsV7QZHyk80HrXhrEWRJ1ADoIV6Uji3UUXtCmBMg=="
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.1/min/vs/editor/editor.main.min.js"
integrity="sha512-u4eMtetNbBJvHXdLXs2kWZvJiVlg3cmkVcxrLzSPa1eNFuHygPYvyMWyK9PsD6Eq2MZSo+mTyjds8uuhPzVxHA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
@ -151,8 +151,9 @@
hook.afterEach(function (html, next) {
next(html);
(async () => {
while (!window.hasOwnProperty('monaco'))
while (!window.hasOwnProperty('monaco')) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
colorizeEverything(html).then(
(newHTML) =>
(document.querySelector('article.markdown-section').innerHTML = newHTML)
@ -170,7 +171,9 @@
startOnLoad: false,
themeCSS: '.label { font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; }',
};
if (isDarkMode) conf.theme = 'dark';
if (isDarkMode) {
conf.theme = 'dark';
}
mermaid.initialize(conf);
</script>
<script>

View File

@ -28,7 +28,7 @@ Drawing a pie chart is really simple in mermaid.
- Start with `pie` keyword to begin the diagram
- `showData` to render the actual data values after the legend text. This is **_OPTIONAL_**
- Followed by `title` keyword and its value in string to give a title to the pie-chart. This is **_OPTIONAL_**
- Followed by dataSet
- Followed by dataSet. Pie slices will be ordered clockwise in the same order as the labels.
- `label` for a section in the pie diagram within `" "` quotes.
- Followed by `:` colon as separator
- Followed by `positive numeric value` (supported upto two decimal places)

View File

@ -97,7 +97,7 @@ Messages can be of two displayed either solid or with a dotted line.
[Actor][Arrow][Actor]:Message text
There are six types of arrows currently supported:
There are eight types of arrows currently supported:
| Type | Description |
| ---- | ------------------------------------------------ |

View File

@ -7,7 +7,7 @@
"module": "dist/mermaid.core.mjs",
"types": "dist/mermaid.d.ts",
"type": "module",
"packageManager": "pnpm@7.13.5",
"packageManager": "pnpm@7.13.6",
"exports": {
".": {
"require": "./dist/mermaid.min.js",
@ -70,7 +70,7 @@
]
},
"dependencies": {
"@braintree/sanitize-url": "6.0.0",
"@braintree/sanitize-url": "6.0.1",
"@types/node": "18.11.0",
"@types/uuid": "8.3.4",
"d3": "7.6.1",
@ -88,21 +88,21 @@
"uuid": "9.0.0"
},
"devDependencies": {
"@applitools/eyes-cypress": "3.27.2",
"@applitools/eyes-cypress": "3.27.5",
"@commitlint/cli": "17.1.2",
"@commitlint/config-conventional": "17.1.0",
"@cspell/eslint-plugin": "^6.12.0",
"@cspell/eslint-plugin": "6.12.0",
"@types/d3": "7.4.0",
"@types/dompurify": "2.3.4",
"@types/eslint": "8.4.6",
"@types/eslint": "8.4.7",
"@types/express": "4.17.14",
"@types/jsdom": "20.0.0",
"@types/lodash": "4.14.186",
"@types/mdast": "3.0.10",
"@types/prettier": "2.7.1",
"@types/stylis": "4.0.2",
"@typescript-eslint/eslint-plugin": "5.40.0",
"@typescript-eslint/parser": "5.40.0",
"@typescript-eslint/eslint-plugin": "5.40.1",
"@typescript-eslint/parser": "5.40.1",
"@vitest/coverage-c8": "0.24.3",
"@vitest/ui": "0.24.3",
"concurrently": "7.4.0",
@ -110,27 +110,28 @@
"cypress": "10.10.0",
"cypress-image-snapshot": "4.0.1",
"documentation": "13.2.5",
"esbuild": "0.15.11",
"esbuild": "0.15.12",
"eslint": "8.25.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-cypress": "2.12.1",
"eslint-plugin-html": "7.1.0",
"eslint-plugin-jest": "27.1.2",
"eslint-plugin-jsdoc": "39.3.6",
"eslint-plugin-jest": "27.1.3",
"eslint-plugin-jsdoc": "39.3.23",
"eslint-plugin-json": "3.1.0",
"eslint-plugin-markdown": "3.0.0",
"eslint-plugin-no-only-tests": "^3.0.0",
"eslint-plugin-no-only-tests": "3.1.0",
"eslint-plugin-tsdoc": "0.2.17",
"express": "4.18.2",
"globby": "13.1.2",
"husky": "8.0.1",
"identity-obj-proxy": "3.0.0",
"jest": "29.2.0",
"jest": "29.2.1",
"jison": "0.4.18",
"jsdom": "20.0.1",
"lint-staged": "13.0.3",
"markdown-it": "13.0.1",
"path-browserify": "1.0.1",
"pnpm": "7.13.5",
"pnpm": "7.13.6",
"prettier": "2.7.1",
"prettier-plugin-jsdoc": "0.4.2",
"remark": "14.0.2",
@ -140,7 +141,7 @@
"typescript": "4.8.4",
"unist-util-flatmap": "1.0.0",
"vite": "3.1.8",
"vitepress": "1.0.0-alpha.21",
"vitepress": "1.0.0-alpha.22",
"vitepress-plugin-mermaid": "2.0.8",
"vitepress-plugin-search": "1.0.4-alpha.11",
"vitest": "0.24.3"

View File

@ -4,7 +4,7 @@ export const id = 'example-diagram';
/**
* Detector function that will be called by mermaid to determine if the diagram is this type of diagram.
*
* @param txt The diagram text will be passed to the detector
* @param txt - The diagram text will be passed to the detector
* @returns True if the diagram text matches a diagram of this type
*/

View File

@ -35,18 +35,19 @@ export let setupGraphViewbox: (
/**
* Function called by mermaid that injects utility functions that help the diagram to be a good citizen.
* @param _log
* @param _setLogLevel
* @param _getConfig
* @param _sanitizeText
* @param _setupGraphViewbox
*
* @param _log - log from mermaid/src/diagramAPI.ts
* @param _setLogLevel - setLogLevel from mermaid/src/diagramAPI.ts
* @param _getConfig - getConfig from mermaid/src/diagramAPI.ts
* @param _sanitizeText - sanitizeText from mermaid/src/diagramAPI.ts
* @param _setupGraphViewbox - setupGraphViewbox from mermaid/src/diagramAPI.ts
*/
export const injectUtils = (
_log: Record<keyof typeof LEVELS, typeof console.log>,
_setLogLevel: any,
_getConfig: any,
_sanitizeText: any,
_setupGraphViewbox: any
_setLogLevel: typeof setLogLevel,
_getConfig: typeof getConfig,
_sanitizeText: typeof sanitizeText,
_setupGraphViewbox: typeof setupGraphViewbox
) => {
_log.debug('Mermaid utils injected into example-diagram');
log.trace = _log.trace;

View File

@ -75,31 +75,31 @@
"stylis": "^4.1.2"
},
"devDependencies": {
"@applitools/eyes-cypress": "3.27.2",
"@applitools/eyes-cypress": "3.27.5",
"@commitlint/cli": "17.1.2",
"@commitlint/config-conventional": "17.1.0",
"@types/d3": "7.4.0",
"@types/dompurify": "2.3.4",
"@types/eslint": "8.4.6",
"@types/eslint": "8.4.7",
"@types/express": "4.17.14",
"@types/jsdom": "20.0.0",
"@types/lodash": "4.14.186",
"@types/prettier": "2.7.1",
"@types/stylis": "4.0.2",
"@typescript-eslint/eslint-plugin": "5.40.0",
"@typescript-eslint/parser": "5.40.0",
"@typescript-eslint/eslint-plugin": "5.40.1",
"@typescript-eslint/parser": "5.40.1",
"concurrently": "7.4.0",
"coveralls": "3.1.1",
"cypress": "10.10.0",
"cypress-image-snapshot": "4.0.1",
"documentation": "13.2.5",
"esbuild": "0.15.11",
"esbuild": "0.15.12",
"eslint": "8.25.0",
"eslint-config-prettier": "8.5.0",
"eslint-plugin-cypress": "2.12.1",
"eslint-plugin-html": "7.1.0",
"eslint-plugin-jest": "27.1.2",
"eslint-plugin-jsdoc": "39.3.6",
"eslint-plugin-jest": "27.1.3",
"eslint-plugin-jsdoc": "39.3.23",
"eslint-plugin-json": "3.1.0",
"eslint-plugin-markdown": "3.0.0",
"express": "4.18.2",

View File

@ -11,10 +11,7 @@ import Diagram, { type ParseErrorFunction } from '../Diagram';
// Normally, we could just do the following to get the original `parse()`
// implementation, however, requireActual returns a promise and it's not documented how to use withing mock file.
/**
* @param text
* @param parseError
*/
/** {@inheritDoc mermaidAPI.parse} */
function parse(text: string, parseError?: ParseErrorFunction): boolean {
addDiagrams();
const diagram = new Diagram(text, parseError);

View File

@ -56,7 +56,7 @@ export const updateCurrentConfig = (siteCfg: MermaidConfig, _directives: any[])
* function _Default value: At default, will mirror Global Config_
*
* @param conf - The base currentConfig to use as siteConfig
* @returns {object} - The siteConfig
* @returns The new siteConfig
*/
export const setSiteConfig = (conf: MermaidConfig): MermaidConfig => {
siteConfig = assignWithDepth({}, defaultConfig);
@ -91,7 +91,7 @@ export const updateSiteConfig = (conf: MermaidConfig): MermaidConfig => {
*
* **Notes**: Returns **any** values in siteConfig.
*
* @returns {object} - The siteConfig
* @returns The siteConfig
*/
export const getSiteConfig = (): MermaidConfig => {
return assignWithDepth({}, siteConfig);
@ -107,8 +107,8 @@ export const getSiteConfig = (): MermaidConfig => {
* keys. Any values found in conf with key found in siteConfig.secure will be replaced with the
* corresponding siteConfig value.
*
* @param {any} conf - The potential currentConfig
* @returns {any} - The currentConfig merged with the sanitized conf
* @param conf - The potential currentConfig
* @returns The currentConfig merged with the sanitized conf
*/
export const setConfig = (conf: MermaidConfig): MermaidConfig => {
// sanitize(conf);
@ -131,7 +131,7 @@ export const setConfig = (conf: MermaidConfig): MermaidConfig => {
*
* **Notes**: Returns **any** the currentConfig
*
* @returns {any} - The currentConfig
* @returns The currentConfig
*/
export const getConfig = (): MermaidConfig => {
return assignWithDepth({}, currentConfig);
@ -146,7 +146,7 @@ export const getConfig = (): MermaidConfig => {
* Ensures options parameter does not attempt to override siteConfig secure keys **Notes**: modifies
* options in-place
*
* @param {any} options - The potential setConfig parameter
* @param options - The potential setConfig parameter
*/
export const sanitize = (options: any) => {
// Checking that options are not in the list of excluded options
@ -186,7 +186,7 @@ export const sanitize = (options: any) => {
/**
* Pushes in a directive to the configuration
*
* @param {object} directive The directive to push in
* @param directive - The directive to push in
*/
export const addDirective = (directive: any) => {
if (directive.fontFamily) {
@ -217,7 +217,8 @@ export const addDirective = (directive: any) => {
*
* **Notes**: (default: current siteConfig ) (optional, default `getSiteConfig()`)
*
* @param config
* @param config - base set of values, which currentConfig could be **reset** to.
* Defaults to the current siteConfig (e.g returned by {@link getSiteConfig}).
*/
export const reset = (config = siteConfig): void => {
// Replace current config with siteConfig

View File

@ -44,7 +44,9 @@ function addHtmlLabel(node) {
const createLabel = (_vertexText, style, isTitle, isNode) => {
let vertexText = _vertexText || '';
if (typeof vertexText === 'object') vertexText = vertexText[0];
if (typeof vertexText === 'object') {
vertexText = vertexText[0];
}
if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
vertexText = vertexText.replace(/\\n|\n/g, '<br />');

View File

@ -336,7 +336,9 @@ const cutPathAtIntersect = (_points, boundryNode) => {
log.warn('abc88 outside', point, lastPointOutside);
lastPointOutside = point;
// points.push(point);
if (!isInside) points.push(point);
if (!isInside) {
points.push(point);
}
}
});
log.warn('abc88 returning points', points);

View File

@ -23,7 +23,9 @@ const isDecendant = (id, ancenstorId) => {
' = ',
decendants[ancenstorId].indexOf(id) >= 0
);
if (decendants[ancenstorId].indexOf(id) >= 0) return true;
if (decendants[ancenstorId].indexOf(id) >= 0) {
return true;
}
return false;
};
@ -32,19 +34,23 @@ const edgeInCluster = (edge, clusterId) => {
log.info('Decendants of ', clusterId, ' is ', decendants[clusterId]);
log.info('Edge is ', edge);
// Edges to/from the cluster is not in the cluster, they are in the parent
if (edge.v === clusterId) return false;
if (edge.w === clusterId) return false;
if (edge.v === clusterId) {
return false;
}
if (edge.w === clusterId) {
return false;
}
if (!decendants[clusterId]) {
log.debug('Tilt, ', clusterId, ',not in decendants');
return false;
}
if (decendants[clusterId].indexOf(edge.v) >= 0) return true;
if (isDecendant(edge.v, clusterId)) return true;
if (isDecendant(edge.w, clusterId)) return true;
if (decendants[clusterId].indexOf(edge.w) >= 0) return true;
return false;
return (
decendants[clusterId].indexOf(edge.v) >= 0 ||
isDecendant(edge.v, clusterId) ||
isDecendant(edge.w, clusterId) ||
decendants[clusterId].indexOf(edge.w) >= 0
);
};
const copy = (clusterId, graph, newGraph, rootId) => {
@ -306,8 +312,12 @@ export const adjustClustersAndEdges = (graph, depth) => {
v = getAnchorId(e.v);
w = getAnchorId(e.w);
graph.removeEdge(e.v, e.w, e.name);
if (v !== e.v) edge.fromCluster = e.v;
if (w !== e.w) edge.toCluster = e.w;
if (v !== e.v) {
edge.fromCluster = e.v;
}
if (w !== e.w) {
edge.toCluster = e.w;
}
log.warn('Fix Replacing with XXX', v, w, e.name);
graph.setEdge(v, w, edge, e.name);
}
@ -446,7 +456,9 @@ export const extractor = (graph, depth) => {
};
const sorter = (graph, nodes) => {
if (nodes.length === 0) return [];
if (nodes.length === 0) {
return [];
}
let result = Object.assign(nodes);
nodes.forEach((node) => {
const children = graph.children(node);

View File

@ -293,9 +293,13 @@ const cylinder = (parent, node) => {
// ellipsis equation: x*x / a*a + y*y / b*b = 1
// solve for y to get adjusted value for pos.y
let y = ry * ry * (1 - (x * x) / (rx * rx));
if (y != 0) y = Math.sqrt(y);
if (y != 0) {
y = Math.sqrt(y);
}
y = ry - y;
if (point.y - node.y > 0) y = -y;
if (point.y - node.y > 0) {
y = -y;
}
pos.y += y;
}

View File

@ -8,21 +8,29 @@ import { MermaidConfig } from './config.type';
*
* These are the default options which can be overridden with the initialization call like so:
*
* **Example 1:**<pre> mermaid.initialize({ flowchart:{ htmlLabels: false } }); </pre>
* **Example 1:**
*
* **Example 2:**<pre> <script> var config = { startOnLoad:true, flowchart:{ useMaxWidth:true,
* htmlLabels:true, curve:'cardinal', },
* ```js
* mermaid.initialize({ flowchart:{ htmlLabels: false } });
* ```
*
* securityLevel:'loose',
* **Example 2:**
*
* }; mermaid.initialize(config); </script> </pre>
* ```html
* <script>
* var config = {
* startOnLoad:true,
* flowchart:{ useMaxWidth:true, htmlLabels:true, curve:'cardinal'},
* securityLevel:'loose',
* };
* mermaid.initialize(config);
* </script>
* ```
*
* A summary of all options and their defaults is found [here](#mermaidapi-configuration-defaults).
* A description of each option follows below.
*
* @name Configuration
*/
const config: MermaidConfig = {
const config: Partial<MermaidConfig> = {
/**
* Theme , the CSS style sheet
*
@ -30,8 +38,16 @@ const config: MermaidConfig = {
* | --------- | --------------- | ------ | -------- | ---------------------------------------------- |
* | theme | Built in Themes | string | Optional | 'default', 'forest', 'dark', 'neutral', 'null' |
*
* **Notes:** To disable any pre-defined mermaid theme, use "null".<pre> "theme": "forest",
* "themeCSS": ".node rect { fill: red; }" </pre>
* **Notes:** To disable any pre-defined mermaid theme, use "null".
*
* @example
*
* ```js
* {
* "theme": "forest",
* "themeCSS": ".node rect { fill: red; }"
* }
* ```
*/
theme: 'default',
themeVariables: theme['default'].getThemeVariables(),
@ -1069,7 +1085,6 @@ const config: MermaidConfig = {
showCommitLabel: true,
showBranches: true,
rotateCommitLabel: true,
arrowMarkerAbsolute: false,
},
/** The object containing configurations specific for c4 diagrams */
@ -1834,6 +1849,13 @@ const config: MermaidConfig = {
fontSize: 16,
};
if (config.class) {
config.class.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
}
if (config.gitGraph) {
config.gitGraph.arrowMarkerAbsolute = config.arrowMarkerAbsolute;
}
const keyify = (obj: any, prefix = ''): string[] =>
Object.keys(obj).reduce((res: string[], el): string[] => {
if (Array.isArray(obj[el])) {

View File

@ -9,10 +9,13 @@ const anyComment = /\s*%%.*\n/gm;
const detectors: Record<string, DetectorRecord> = {};
/**
* @function detectType Detects the type of the graph text. Takes into consideration the possible
* existence of an %%init directive
* Detects the type of the graph text.
*
* ```mermaid
* Takes into consideration the possible existence of an `%%init` directive
*
* @param text - The text defining the graph. For example:
*
* ```mermaid
* %%{initialize: {"startOnLoad": true, logLevel: "fatal" }}%%
* graph LR
* a-->b
@ -23,13 +26,9 @@ const detectors: Record<string, DetectorRecord> = {};
* f-->g
* g-->h
* ```
* @param {string} text The text defining the graph
* @param {{
* class: { defaultRenderer: string } | undefined;
* state: { defaultRenderer: string } | undefined;
* flowchart: { defaultRenderer: string } | undefined;
* }} [config]
* @returns {string} A graph definition key
*
* @param config - The mermaid config.
* @returns A graph definition key
*/
export const detectType = function (text: string, config?: MermaidConfig): string {
text = text.replace(directive, '').replace(anyComment, '\n');

View File

@ -49,8 +49,9 @@ export const addRel = function (type, from, to, label, techn, descr, sprite, tag
to === null ||
label === undefined ||
label === null
)
) {
return;
}
let rel = {};
const old = rels.find((rel) => rel.from === from && rel.to === to);
@ -111,7 +112,9 @@ export const addRel = function (type, from, to, label, techn, descr, sprite, tag
//type, alias, label, ?descr, ?sprite, ?tags, $link
export const addPersonOrSystem = function (typeC4Shape, alias, label, descr, sprite, tags, link) {
// Don't allow label nulling
if (alias === null || label === null) return;
if (alias === null || label === null) {
return;
}
let personOrSystem = {};
const old = c4ShapeArray.find((personOrSystem) => personOrSystem.alias === alias);
@ -166,7 +169,9 @@ export const addPersonOrSystem = function (typeC4Shape, alias, label, descr, spr
//type, alias, label, ?techn, ?descr ?sprite, ?tags, $link
export const addContainer = function (typeC4Shape, alias, label, techn, descr, sprite, tags, link) {
// Don't allow label nulling
if (alias === null || label === null) return;
if (alias === null || label === null) {
return;
}
let container = {};
const old = c4ShapeArray.find((container) => container.alias === alias);
@ -232,7 +237,9 @@ export const addContainer = function (typeC4Shape, alias, label, techn, descr, s
//type, alias, label, ?techn, ?descr ?sprite, ?tags, $link
export const addComponent = function (typeC4Shape, alias, label, techn, descr, sprite, tags, link) {
// Don't allow label nulling
if (alias === null || label === null) return;
if (alias === null || label === null) {
return;
}
let component = {};
const old = c4ShapeArray.find((component) => component.alias === alias);
@ -300,7 +307,9 @@ export const addPersonOrSystemBoundary = function (alias, label, type, tags, lin
// if (parentBoundary === null) return;
// Don't allow label nulling
if (alias === null || label === null) return;
if (alias === null || label === null) {
return;
}
let boundary = {};
const old = boundarys.find((boundary) => boundary.alias === alias);
@ -354,7 +363,9 @@ export const addContainerBoundary = function (alias, label, type, tags, link) {
// if (parentBoundary === null) return;
// Don't allow label nulling
if (alias === null || label === null) return;
if (alias === null || label === null) {
return;
}
let boundary = {};
const old = boundarys.find((boundary) => boundary.alias === alias);
@ -417,7 +428,9 @@ export const addDeploymentNode = function (
// if (parentBoundary === null) return;
// Don't allow label nulling
if (alias === null || label === null) return;
if (alias === null || label === null) {
return;
}
let boundary = {};
const old = boundarys.find((boundary) => boundary.alias === alias);
@ -646,8 +659,12 @@ export const updateLayoutConfig = function (typeC4Shape, c4ShapeInRowParam, c4Bo
c4BoundaryInRowValue = parseInt(c4BoundaryInRowParam);
}
if (c4ShapeInRowValue >= 1) c4ShapeInRow = c4ShapeInRowValue;
if (c4BoundaryInRowValue >= 1) c4BoundaryInRow = c4BoundaryInRowValue;
if (c4ShapeInRowValue >= 1) {
c4ShapeInRow = c4ShapeInRowValue;
}
if (c4BoundaryInRowValue >= 1) {
c4BoundaryInRow = c4BoundaryInRowValue;
}
};
export const getC4ShapeInRow = function () {
@ -665,11 +682,13 @@ export const getParentBoundaryParse = function () {
};
export const getC4ShapeArray = function (parentBoundary) {
if (parentBoundary === undefined || parentBoundary === null) return c4ShapeArray;
else
if (parentBoundary === undefined || parentBoundary === null) {
return c4ShapeArray;
} else {
return c4ShapeArray.filter((personOrSystem) => {
return personOrSystem.parentBoundary === parentBoundary;
});
}
};
export const getC4Shape = function (alias) {
return c4ShapeArray.find((personOrSystem) => personOrSystem.alias === alias);
@ -679,8 +698,11 @@ export const getC4ShapeKeys = function (parentBoundary) {
};
export const getBoundarys = function (parentBoundary) {
if (parentBoundary === undefined || parentBoundary === null) return boundarys;
else return boundarys.filter((boundary) => boundary.parentBoundary === parentBoundary);
if (parentBoundary === undefined || parentBoundary === null) {
return boundarys;
} else {
return boundarys.filter((boundary) => boundary.parentBoundary === parentBoundary);
}
};
export const getRels = function () {

View File

@ -414,7 +414,9 @@ export const drawRels = function (diagram, rels, getC4ShapeObj, diagObj) {
let relTextWrap = rel.wrap && conf.wrap;
let relConf = messageFont(conf);
let diagramType = diagObj.db.getC4Type();
if (diagramType === 'C4Dynamic') rel.label.text = i + ': ' + rel.label.text;
if (diagramType === 'C4Dynamic') {
rel.label.text = i + ': ' + rel.label.text;
}
let textLimitWidth = calculateTextWidth(rel.label.text, relConf);
calcC4ShapeTextWH('label', rel, relTextWrap, relConf, textLimitWidth);
@ -555,7 +557,9 @@ function drawInsideBoundary(
);
}
// draw boundary
if (currentBoundary.alias !== 'global') drawBoundary(diagram, currentBoundary, currentBounds);
if (currentBoundary.alias !== 'global') {
drawBoundary(diagram, currentBoundary, currentBounds);
}
parentBounds.data.stopy = Math.max(
currentBounds.data.stopy + conf.c4ShapeMargin,
parentBounds.data.stopy

View File

@ -13,7 +13,9 @@ export const drawRect = function (elem, rectData) {
rectElem.attr('ry', rectData.ry);
if (rectData.attrs !== 'undefined' && rectData.attrs !== null) {
for (let attrKey in rectData.attrs) rectElem.attr(attrKey, rectData.attrs[attrKey]);
for (let attrKey in rectData.attrs) {
rectElem.attr(attrKey, rectData.attrs[attrKey]);
}
}
if (rectData.class !== 'undefined') {
@ -231,9 +233,12 @@ export const drawRels = (elem, rels, conf) => {
line.attr('stroke-width', '1');
line.attr('stroke', strokeColor);
line.style('fill', 'none');
if (rel.type !== 'rel_b') line.attr('marker-end', 'url(' + url + '#arrowhead)');
if (rel.type === 'birel' || rel.type === 'rel_b')
if (rel.type !== 'rel_b') {
line.attr('marker-end', 'url(' + url + '#arrowhead)');
}
if (rel.type === 'birel' || rel.type === 'rel_b') {
line.attr('marker-start', 'url(' + url + '#arrowend)');
}
i = -1;
} else {
let line = relsElem.append('path');
@ -256,9 +261,12 @@ export const drawRels = (elem, rels, conf) => {
.replaceAll('stopx', rel.endPoint.x)
.replaceAll('stopy', rel.endPoint.y)
);
if (rel.type !== 'rel_b') line.attr('marker-end', 'url(' + url + '#arrowhead)');
if (rel.type === 'birel' || rel.type === 'rel_b')
if (rel.type !== 'rel_b') {
line.attr('marker-end', 'url(' + url + '#arrowhead)');
}
if (rel.type === 'birel' || rel.type === 'rel_b') {
line.attr('marker-start', 'url(' + url + '#arrowend)');
}
}
let messageConf = conf.messageFont();
@ -314,7 +322,9 @@ const drawBoundary = function (elem, boundary, conf) {
let fontColor = boundary.fontColor ? boundary.fontColor : 'black';
let attrsValue = { 'stroke-width': 1.0, 'stroke-dasharray': '7.0,7.0' };
if (boundary.nodeType) attrsValue = { 'stroke-width': 1.0 };
if (boundary.nodeType) {
attrsValue = { 'stroke-width': 1.0 };
}
let rectData = {
x: boundary.x,
y: boundary.y,

View File

@ -16,6 +16,7 @@ const MERMAID_DOM_ID_PREFIX = 'classid-';
let relations = [];
let classes = {};
let notes = [];
let classCounter = 0;
let funs = [];
@ -49,7 +50,9 @@ const splitClassNameAndType = function (id) {
export const addClass = function (id) {
let classId = splitClassNameAndType(id);
// Only add class if not exists
if (typeof classes[classId.className] !== 'undefined') return;
if (typeof classes[classId.className] !== 'undefined') {
return;
}
classes[classId.className] = {
id: classId.className,
@ -82,6 +85,7 @@ export const lookUpDomId = function (id) {
export const clear = function () {
relations = [];
classes = {};
notes = [];
funs = [];
funs.push(setupToolTips);
commonClear();
@ -98,6 +102,10 @@ export const getRelations = function () {
return relations;
};
export const getNotes = function () {
return notes;
};
export const addRelation = function (relation) {
log.debug('Adding relation: ' + JSON.stringify(relation));
addClass(relation.id1);
@ -168,6 +176,15 @@ export const addMembers = function (className, members) {
}
};
export const addNote = function (text, className) {
const note = {
id: `note${notes.length}`,
class: className,
text: text,
};
notes.push(note);
};
export const cleanupLabel = function (label) {
if (label.substring(0, 1) === ':') {
return common.sanitizeText(label.substr(1).trim(), configApi.getConfig());
@ -185,7 +202,9 @@ export const cleanupLabel = function (label) {
export const setCssClass = function (ids, className) {
ids.split(',').forEach(function (_id) {
let id = _id;
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (_id[0].match(/\d/)) {
id = MERMAID_DOM_ID_PREFIX + id;
}
if (typeof classes[id] !== 'undefined') {
classes[id].cssClasses.push(className);
}
@ -220,7 +239,9 @@ export const setLink = function (ids, linkStr, target) {
const config = configApi.getConfig();
ids.split(',').forEach(function (_id) {
let id = _id;
if (_id[0].match(/\d/)) id = MERMAID_DOM_ID_PREFIX + id;
if (_id[0].match(/\d/)) {
id = MERMAID_DOM_ID_PREFIX + id;
}
if (typeof classes[id] !== 'undefined') {
classes[id].link = utils.formatUrl(linkStr, config);
if (config.securityLevel === 'sandbox') {
@ -369,7 +390,9 @@ export default {
clear,
getClass,
getClasses,
getNotes,
addAnnotation,
addNote,
getRelations,
addRelation,
getDirection,

View File

@ -2,8 +2,12 @@ import type { DiagramDetector } from '../../diagram-api/types';
export const classDetectorV2: DiagramDetector = (txt, config) => {
// If we have configured to use dagre-wrapper then we should return true in this function for classDiagram code thus making it use the new class diagram
if (txt.match(/^\s*classDiagram/) !== null && config?.class?.defaultRenderer === 'dagre-wrapper')
if (
txt.match(/^\s*classDiagram/) !== null &&
config?.class?.defaultRenderer === 'dagre-wrapper'
) {
return true;
}
// We have not opted to use the new renderer so we should return true if we detect a class diagram
return txt.match(/^\s*classDiagram-v2/) !== null;
};

View File

@ -2,7 +2,9 @@ import type { DiagramDetector } from '../../diagram-api/types';
export const classDetector: DiagramDetector = (txt, config) => {
// If we have configured to use dagre-wrapper then we should never return true in this function
if (config?.class?.defaultRenderer === 'dagre-wrapper') return false;
if (config?.class?.defaultRenderer === 'dagre-wrapper') {
return false;
}
// We have not opted to use the new renderer so we should return true if we detect a class diagram
return txt.match(/^\s*classDiagram/) !== null;
};

View File

@ -529,6 +529,16 @@ foo()
parser.parse(str);
});
it('should handle "note for"', function () {
const str = 'classDiagram\n' + 'Class11 <|.. Class12\n' + 'note for Class11 "test"\n';
parser.parse(str);
});
it('should handle "note"', function () {
const str = 'classDiagram\n' + 'note "test"\n';
parser.parse(str);
});
});
describe('when fetching data from a classDiagram graph it', function () {

View File

@ -134,6 +134,99 @@ export const addClasses = function (classes, g, _id, diagObj) {
});
};
/**
* Function that adds the additional vertices (notes) found during parsing to the graph to be rendered.
*
* @param {{text: string; class: string; placement: number}[]} notes
* Object containing the additional vertices (notes).
* @param {SVGGElement} g The graph that is to be drawn.
* @param {number} startEdgeId starting index for note edge
* @param classes
*/
export const addNotes = function (notes, g, startEdgeId, classes) {
log.info(notes);
// Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
notes.forEach(function (note, i) {
const vertex = note;
/**
* Variable for storing the classes for the vertex
*
* @type {string}
*/
let cssNoteStr = '';
const styles = { labelStyle: '', style: '' };
// Use vertex id as text in the box if no text is provided by the graph definition
let vertexText = vertex.text;
let radious = 0;
let _shape = 'note';
// Add the node
g.setNode(vertex.id, {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: sanitizeText(vertexText),
noteData: vertex,
rx: radious,
ry: radious,
class: cssNoteStr,
style: styles.style,
id: vertex.id,
domId: vertex.id,
tooltip: '',
type: 'note',
padding: getConfig().flowchart.padding,
});
log.info('setNode', {
labelStyle: styles.labelStyle,
shape: _shape,
labelText: vertexText,
rx: radious,
ry: radious,
style: styles.style,
id: vertex.id,
type: 'note',
padding: getConfig().flowchart.padding,
});
if (!vertex.class || !(vertex.class in classes)) {
return;
}
const edgeId = startEdgeId + i;
const edgeData = {};
//Set relationship style and line type
edgeData.classes = 'relation';
edgeData.pattern = 'dotted';
edgeData.id = `edgeNote${edgeId}`;
// Set link type for rendering
edgeData.arrowhead = 'none';
log.info(`Note edge: ${JSON.stringify(edgeData)}, ${JSON.stringify(vertex)}`);
//Set edge extra labels
edgeData.startLabelRight = '';
edgeData.endLabelLeft = '';
//Set relation arrow types
edgeData.arrowTypeStart = 'none';
edgeData.arrowTypeEnd = 'none';
let style = 'fill:none';
let labelStyle = '';
edgeData.style = style;
edgeData.labelStyle = labelStyle;
edgeData.curve = interpolateToCurve(conf.curve, curveLinear);
// Add the edge to the graph
g.setEdge(vertex.id, vertex.class, edgeData, edgeId);
});
};
/**
* Add edges to graph based on parsed graph definition
*
@ -305,10 +398,12 @@ export const draw = function (text, id, _version, diagObj) {
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const classes = diagObj.db.getClasses();
const relations = diagObj.db.getRelations();
const notes = diagObj.db.getNotes();
log.info(relations);
addClasses(classes, g, id, diagObj);
addRelations(relations, g);
addNotes(notes, g, relations.length + 1, classes);
// Add custom shapes
// flowChartShapes.addToRenderV2(addShape);

View File

@ -208,12 +208,42 @@ export const draw = function (text, id, _version, diagObj) {
);
});
const notes = diagObj.db.getNotes();
notes.forEach(function (note) {
log.debug(`Adding note: ${JSON.stringify(note)}`);
const node = svgDraw.drawNote(diagram, note, conf, diagObj);
idCache[node.id] = node;
// Add nodes to the graph. The first argument is the node id. The second is
// metadata about the node. In this case we're going to add labels to each of
// our nodes.
g.setNode(node.id, node);
if (note.class && note.class in classes) {
g.setEdge(
note.id,
getGraphId(note.class),
{
relation: {
id1: note.id,
id2: note.class,
relation: {
type1: 'none',
type2: 'none',
lineType: 10,
},
},
},
'DEFAULT'
);
}
});
dagre.layout(g);
g.nodes().forEach(function (v) {
if (typeof v !== 'undefined' && typeof g.node(v) !== 'undefined') {
log.debug('Node ' + v + ': ' + JSON.stringify(g.node(v)));
root
.select('#' + diagObj.db.lookUpDomId(v))
.select('#' + (diagObj.db.lookUpDomId(v) || v))
.attr(
'transform',
'translate(' +

View File

@ -56,6 +56,8 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
"callback" return 'CALLBACK';
"link" return 'LINK';
"click" return 'CLICK';
"note for" return 'NOTE_FOR';
"note" return 'NOTE';
"<<" return 'ANNOTATION_START';
">>" return 'ANNOTATION_END';
[~] this.begin("generic");
@ -263,6 +265,7 @@ statement
| annotationStatement
| clickStatement
| cssClassStatement
| noteStatement
| directive
| direction
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
@ -300,6 +303,11 @@ relationStatement
| className STR relation STR className { $$ = {id1:$1, id2:$5, relation:$3, relationTitle1:$2, relationTitle2:$4} }
;
noteStatement
: NOTE_FOR className noteText { yy.addNote($3, $2); }
| NOTE noteText { yy.addNote($2); }
;
relation
: relationType lineType relationType { $$={type1:$1,type2:$3,lineType:$2}; }
| lineType relationType { $$={type1:'none',type2:$2,lineType:$1}; }
@ -351,4 +359,6 @@ alphaNumToken : UNICODE_TEXT | NUM | ALPHA;
classLiteralName : BQUOTE_STR;
noteText : STR;
%%

View File

@ -80,6 +80,10 @@ g.classGroup line {
stroke-dasharray: 3;
}
.dotted-line{
stroke-dasharray: 1 2;
}
#compositionStart, .composition {
fill: ${options.lineColor} !important;
stroke: ${options.lineColor} !important;

View File

@ -9,13 +9,13 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) {
switch (type) {
case diagObj.db.relationType.AGGREGATION:
return 'aggregation';
case diagObj.db.EXTENSION:
case diagObj.db.relationType.EXTENSION:
return 'extension';
case diagObj.db.COMPOSITION:
case diagObj.db.relationType.COMPOSITION:
return 'composition';
case diagObj.db.DEPENDENCY:
case diagObj.db.relationType.DEPENDENCY:
return 'dependency';
case diagObj.db.LOLLIPOP:
case diagObj.db.relationType.LOLLIPOP:
return 'lollipop';
}
};
@ -55,6 +55,9 @@ export const drawEdge = function (elem, path, relation, conf, diagObj) {
if (relation.relation.lineType == 1) {
svgPath.attr('class', 'relation dashed-line');
}
if (relation.relation.lineType == 10) {
svgPath.attr('class', 'relation dotted-line');
}
if (relation.relation.type1 !== 'none') {
svgPath.attr(
'marker-start',
@ -190,7 +193,9 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
let isFirst = true;
classDef.annotations.forEach(function (member) {
const titleText2 = title.append('tspan').text('«' + member + '»');
if (!isFirst) titleText2.attr('dy', conf.textHeight);
if (!isFirst) {
titleText2.attr('dy', conf.textHeight);
}
isFirst = false;
});
@ -203,7 +208,9 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
const classTitle = title.append('tspan').text(classTitleString).attr('class', 'title');
// If class has annotations the title needs to have an offset of the text height
if (!isFirst) classTitle.attr('dy', conf.textHeight);
if (!isFirst) {
classTitle.attr('dy', conf.textHeight);
}
const titleHeight = title.node().getBBox().height;
@ -284,6 +291,69 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
return classInfo;
};
/**
* Renders a note diagram
*
* @param {SVGSVGElement} elem The element to draw it into
* @param {{id: string; text: string; class: string;}} note
* @param conf
* @param diagObj
* @todo Add more information in the JSDOC here
*/
export const drawNote = function (elem, note, conf, diagObj) {
log.debug('Rendering note ', note, conf);
const id = note.id;
const noteInfo = {
id: id,
text: note.text,
width: 0,
height: 0,
};
// add class group
const g = elem.append('g').attr('id', id).attr('class', 'classGroup');
// add text
let text = g
.append('text')
.attr('y', conf.textHeight + conf.padding)
.attr('x', 0);
const lines = JSON.parse(`"${note.text}"`).split('\n');
lines.forEach(function (line) {
log.debug(`Adding line: ${line}`);
text.append('tspan').text(line).attr('class', 'title').attr('dy', conf.textHeight);
});
const noteBox = g.node().getBBox();
const rect = g
.insert('rect', ':first-child')
.attr('x', 0)
.attr('y', 0)
.attr('width', noteBox.width + 2 * conf.padding)
.attr(
'height',
noteBox.height + lines.length * conf.textHeight + conf.padding + 0.5 * conf.dividerMargin
);
const rectWidth = rect.node().getBBox().width;
// Center title
// We subtract the width of each text element from the class box width and divide it by 2
text.node().childNodes.forEach(function (x) {
x.setAttribute('x', (rectWidth - x.getBBox().width) / 2);
});
noteInfo.width = rectWidth;
noteInfo.height =
noteBox.height + lines.length * conf.textHeight + conf.padding + 0.5 * conf.dividerMargin;
return noteInfo;
};
export const parseMember = function (text) {
const fieldRegEx = /^(\+|-|~|#)?(\w+)(~\w+~|\[\])?\s+(\w+) *(\*|\$)?$/;
const methodRegEx = /^([+|\-|~|#])?(\w+) *\( *(.*)\) *(\*|\$)? *(\w*[~|[\]]*\s*\w*~?)$/;
@ -435,5 +505,6 @@ const parseClassifier = function (classifier) {
export default {
drawClass,
drawEdge,
drawNote,
parseMember,
};

View File

@ -4,11 +4,13 @@ import { MermaidConfig } from '../../config.type';
/**
* Gets the rows of lines in a string
*
* @param {string | undefined} s The string to check the lines for
* @returns {string[]} The rows in that string
* @param s - The string to check the lines for
* @returns The rows in that string
*/
export const getRows = (s?: string): string[] => {
if (!s) return [''];
if (!s) {
return [''];
}
const str = breakToPlaceholder(s).replace(/\\n/g, '#br#');
return str.split('#br#');
};
@ -16,8 +18,8 @@ export const getRows = (s?: string): string[] => {
/**
* Removes script tags from a text
*
* @param {string} txt The text to sanitize
* @returns {string} The safer text
* @param txt - The text to sanitize
* @returns The safer text
*/
export const removeScript = (txt: string): string => {
return DOMPurify.sanitize(txt);
@ -39,7 +41,9 @@ const sanitizeMore = (text: string, config: MermaidConfig) => {
};
export const sanitizeText = (text: string, config: MermaidConfig): string => {
if (!text) return text;
if (!text) {
return text;
}
if (config.dompurifyConfig) {
text = DOMPurify.sanitize(sanitizeMore(text, config), config.dompurifyConfig).toString();
} else {
@ -52,7 +56,9 @@ export const sanitizeTextOrArray = (
a: string | string[] | string[][],
config: MermaidConfig
): string | string[] => {
if (typeof a === 'string') return sanitizeText(a, config);
if (typeof a === 'string') {
return sanitizeText(a, config);
}
// TODO: Refactor to avoid flat.
return a.flat().map((x: string) => sanitizeText(x, config));
};
@ -62,8 +68,8 @@ export const lineBreakRegex = /<br\s*\/?>/gi;
/**
* Whether or not a text has any line breaks
*
* @param {string} text The text to test
* @returns {boolean} Whether or not the text has breaks
* @param text - The text to test
* @returns Whether or not the text has breaks
*/
export const hasBreaks = (text: string): boolean => {
return lineBreakRegex.test(text);
@ -72,8 +78,8 @@ export const hasBreaks = (text: string): boolean => {
/**
* Splits on <br> tags
*
* @param {string} text Text to split
* @returns {string[]} List of lines as strings
* @param text - Text to split
* @returns List of lines as strings
*/
export const splitBreaks = (text: string): string[] => {
return text.split(lineBreakRegex);
@ -82,8 +88,8 @@ export const splitBreaks = (text: string): string[] => {
/**
* Converts placeholders to line breaks in HTML
*
* @param {string} s HTML with placeholders
* @returns {string} HTML with breaks instead of placeholders
* @param s - HTML with placeholders
* @returns HTML with breaks instead of placeholders
*/
const placeholderToBreak = (s: string): string => {
return s.replace(/#br#/g, '<br/>');
@ -92,8 +98,8 @@ const placeholderToBreak = (s: string): string => {
/**
* Opposite of `placeholderToBreak`, converts breaks to placeholders
*
* @param {string} s HTML string
* @returns {string} String with placeholders
* @param s - HTML string
* @returns String with placeholders
*/
const breakToPlaceholder = (s: string): string => {
return s.replace(lineBreakRegex, '#br#');
@ -102,8 +108,8 @@ const breakToPlaceholder = (s: string): string => {
/**
* Gets the current URL
*
* @param {boolean} useAbsolute Whether to return the absolute URL or not
* @returns {string} The current URL
* @param useAbsolute - Whether to return the absolute URL or not
* @returns The current URL
*/
const getUrl = (useAbsolute: boolean): string => {
let url = '';
@ -124,8 +130,8 @@ const getUrl = (useAbsolute: boolean): string => {
/**
* Converts a string/boolean into a boolean
*
* @param {string | boolean} val String or boolean to convert
* @returns {boolean} The result from the input
* @param val - String or boolean to convert
* @returns The result from the input
*/
export const evaluate = (val?: string | boolean): boolean =>
val === false || ['false', 'null', '0'].includes(String(val).trim().toLowerCase()) ? false : true;
@ -133,12 +139,15 @@ export const evaluate = (val?: string | boolean): boolean =>
/**
* Makes generics in typescript syntax
*
* @example <caption>Array of array of strings in typescript syntax</caption>
* // returns "Array<Array<string>>"
* parseGenericTypes('Array~Array~string~~');
* @example
* Array of array of strings in typescript syntax
*
* @param {string} text The text to convert
* @returns {string} The converted string
* ```js
* // returns "Array<Array<string>>"
* parseGenericTypes('Array~Array~string~~');
* ```
* @param text - The text to convert
* @returns The converted string
*/
export const parseGenericTypes = function (text: string): string {
let cleanedText = text;

View File

@ -357,7 +357,7 @@ const drawEntities = function (svgNode, entities, graph) {
const rectNode = groupNode
.insert('rect', '#' + textId)
.attr('class', 'er entityBox')
.attr('fill', conf.fill)
.style('fill', conf.fill)
.attr('fill-opacity', '100%')
.attr('stroke', conf.stroke)
.attr('x', 0)

View File

@ -36,15 +36,32 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
<block>[\n]+ /* nothing */
<block>"}" { this.popState(); return 'BLOCK_STOP'; }
<block>. return yytext[0];
"one or zero" return 'ZERO_OR_ONE';
"one or more" return 'ONE_OR_MORE';
"one or many" return 'ONE_OR_MORE';
"1+" return 'ONE_OR_MORE';
\|o return 'ZERO_OR_ONE';
"zero or one" return 'ZERO_OR_ONE';
"zero or more" return 'ZERO_OR_MORE';
"zero or many" return 'ZERO_OR_MORE';
"0+" return 'ZERO_OR_MORE';
\}o return 'ZERO_OR_MORE';
"many(0)" return 'ZERO_OR_MORE';
"many(1)" return 'ONE_OR_MORE';
"many" return 'ZERO_OR_MORE';
\}\| return 'ONE_OR_MORE';
"one" return 'ONLY_ONE';
"only one" return 'ONLY_ONE';
"1" return 'ONLY_ONE';
\|\| return 'ONLY_ONE';
o\| return 'ZERO_OR_ONE';
o\{ return 'ZERO_OR_MORE';
\|\{ return 'ONE_OR_MORE';
\.\. return 'NON_IDENTIFYING';
\-\- return 'IDENTIFYING';
"to" return 'IDENTIFYING';
"optionally to" return 'NON_IDENTIFYING';
\.\- return 'NON_IDENTIFYING';
\-\. return 'NON_IDENTIFYING';
[A-Za-z][A-Za-z0-9\-_]* return 'ALPHANUM';

View File

@ -532,18 +532,100 @@ describe('when parsing ER diagram it...', function () {
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
});
it('should handle zero-or-one-to-zero-or-more relationships (aliases "one or zero" and "zero or many")', function () {
erDiagram.parser.parse('erDiagram\nA one or zero to many B : has');
const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1);
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_ONE);
});
it('should handle one-or-more-to-zero-or-one relationships (aliases "one or many" and "zero or one")', function () {
erDiagram.parser.parse('erDiagram\nA one or many optionally to zero or one B : has');
const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1);
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONE_OR_MORE);
});
it('should handle zero-or-more-to-zero-or-more relationships (aliases "zero or more" and "zero or many")', function () {
erDiagram.parser.parse('erDiagram\nA zero or more to zero or many B : has');
const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1);
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ZERO_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
});
it('should handle zero-or-more-to-one-or-more relationships (aliases "many(0)" and "many(1)")', function () {
erDiagram.parser.parse('erDiagram\nA many(0) to many(1) B : has');
const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1);
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONE_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
});
it('should handle zero-or-more-to-only-one relationships (aliases "many(0)" and "many(1)")', function () {
erDiagram.parser.parse('erDiagram\nA many optionally to one B : has');
const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1);
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONLY_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
});
it('should handle only-one-to-only-one relationships (aliases "only one" and "1+")', function () {
erDiagram.parser.parse('erDiagram\nA only one optionally to 1+ B : has');
const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1);
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONE_OR_MORE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ONLY_ONE);
});
it('should handle zero-or-more-to-only-one relationships (aliases "0+" and "1")', function () {
erDiagram.parser.parse('erDiagram\nA 0+ optionally to 1 B : has');
const rels = erDb.getRelationships();
expect(Object.keys(erDb.getEntities()).length).toBe(2);
expect(rels.length).toBe(1);
expect(rels[0].relSpec.cardA).toBe(erDb.Cardinality.ONLY_ONE);
expect(rels[0].relSpec.cardB).toBe(erDb.Cardinality.ZERO_OR_MORE);
});
it('should represent identifying relationships properly', function () {
erDiagram.parser.parse('erDiagram\nHOUSE ||--|{ ROOM : contains');
const rels = erDb.getRelationships();
expect(rels[0].relSpec.relType).toBe(erDb.Identification.IDENTIFYING);
});
it('should represent identifying relationships properly (alias "to")', function () {
erDiagram.parser.parse('erDiagram\nHOUSE one to one ROOM : contains');
const rels = erDb.getRelationships();
expect(rels[0].relSpec.relType).toBe(erDb.Identification.IDENTIFYING);
});
it('should represent non-identifying relationships properly', function () {
erDiagram.parser.parse('erDiagram\n PERSON ||..o{ POSSESSION : owns');
const rels = erDb.getRelationships();
expect(rels[0].relSpec.relType).toBe(erDb.Identification.NON_IDENTIFYING);
});
it('should represent non-identifying relationships properly (alias "optionally to")', function () {
erDiagram.parser.parse('erDiagram\n PERSON many optionally to many POSSESSION : owns');
const rels = erDb.getRelationships();
expect(rels[0].relSpec.relType).toBe(erDb.Identification.NON_IDENTIFYING);
});
it('should not accept a syntax error', function () {
const doc = 'erDiagram\nA xxx B : has';
expect(() => {

View File

@ -8,7 +8,7 @@ let conf = {};
/**
* Merges the value of `conf` with the passed `cnf`
*
* @param {object} cnf Config to merge
* @param cnf - Config to merge
*/
export const setConf = function (cnf: any) {
conf = { ...conf, ...cnf };
@ -17,11 +17,11 @@ export const setConf = function (cnf: any) {
/**
* Draws a an info picture in the tag with id: id based on the graph definition in text.
*
* @param text
* @param {string} id The text for the error
* @param {string} mermaidVersion The version
* @param _text - Mermaid graph definition.
* @param id - The text for the error
* @param mermaidVersion - The version
*/
export const draw = (text: string, id: string, mermaidVersion: string) => {
export const draw = (_text: string, id: string, mermaidVersion: string) => {
try {
log.debug('Renering svg for syntax error\n');

View File

@ -281,9 +281,13 @@ function cylinder(parent, bbox, node) {
// ellipsis equation: x*x / a*a + y*y / b*b = 1
// solve for y to get adjusted value for pos.y
let y = ry * ry * (1 - (x * x) / (rx * rx));
if (y != 0) y = Math.sqrt(y);
if (y != 0) {
y = Math.sqrt(y);
}
y = ry - y;
if (point.y - node.y > 0) y = -y;
if (point.y - node.y > 0) {
y = -y;
}
pos.y += y;
}

View File

@ -119,7 +119,11 @@ export const addVertex = function (_id, text, type, style, classes, dir, props =
if (typeof dir !== 'undefined') {
vertices[id].dir = dir;
}
vertices[id].props = props;
if (typeof vertices[id].props === 'undefined') {
vertices[id].props = props;
} else if (typeof props !== 'undefined') {
Object.assign(vertices[id].props, props);
}
};
/**
@ -699,7 +703,9 @@ const destructLink = (_str, _startStr) => {
startInfo.type = info.type;
} else {
// x-- xyz --> - not supported
if (startInfo.type !== info.type) return { type: 'INVALID', stroke: 'INVALID' };
if (startInfo.type !== info.type) {
return { type: 'INVALID', stroke: 'INVALID' };
}
startInfo.type = 'double_' + startInfo.type;
}

View File

@ -2,7 +2,8 @@ import type { DiagramDetector } from '../../diagram-api/types';
export const flowDetectorV2: DiagramDetector = (txt, config) => {
// If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper' && txt.match(/^\s*graph/) !== null)
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper' && txt.match(/^\s*graph/) !== null) {
return true;
}
return txt.match(/^\s*flowchart/) !== null;
};

View File

@ -3,6 +3,8 @@ import type { DiagramDetector } from '../../diagram-api/types';
export const flowDetector: DiagramDetector = (txt, config) => {
// If we have conferred to only use new flow charts this function should always return false
// as in not signalling true for a legacy flowchart
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') return false;
if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
return false;
}
return txt.match(/^\s*graph/) !== null;
};

View File

@ -153,7 +153,9 @@ export const isInvalidDate = function (date, dateFormat, excludes, includes) {
};
const checkTaskDates = function (task, dateFormat, excludes, includes) {
if (!excludes.length || task.manualEndTime) return;
if (!excludes.length || task.manualEndTime) {
return;
}
let startTime = moment(task.startTime, dateFormat, true);
startTime.add(1, 'd');
let endTime = moment(task.endTime, dateFormat, true);

View File

@ -427,7 +427,9 @@ export const draw = function (text, id, version, diagObj) {
);
const maxTime = tasks.reduce((max, { endTime }) => (max ? Math.max(max, endTime) : endTime), 0);
const dateFormat = diagObj.db.getDateFormat();
if (!minTime || !maxTime) return;
if (!minTime || !maxTime) {
return;
}
const excludeRanges = [];
let range = null;
@ -552,7 +554,9 @@ export const draw = function (text, id, version, diagObj) {
const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan');
tspan.setAttribute('alignment-baseline', 'central');
tspan.setAttribute('x', '10');
if (j > 0) tspan.setAttribute('dy', '1em');
if (j > 0) {
tspan.setAttribute('dy', '1em');
}
tspan.textContent = rows[j];
svgLabel.appendChild(tspan);
}

View File

@ -399,7 +399,9 @@ function upsert(arr, key, newVal) {
/** @param commitArr */
function prettyPrintCommitHistory(commitArr) {
const commit = commitArr.reduce((out, commit) => {
if (out.seq > commit.seq) return out;
if (out.seq > commit.seq) {
return out;
}
return commit;
}, commitArr[0]);
let line = '';
@ -412,7 +414,9 @@ function prettyPrintCommitHistory(commitArr) {
});
const label = [line, commit.id, commit.seq];
for (let branch in branches) {
if (branches[branch] === commit.id) label.push(branch);
if (branches[branch] === commit.id) {
label.push(branch);
}
}
log.debug(label.join(' '));
if (commit.parents && commit.parents.length == 2) {
@ -452,7 +456,9 @@ export const clear = function () {
export const getBranchesAsObjArray = function () {
const branchesArray = Object.values(branchesConfig)
.map((branchConfig, i) => {
if (branchConfig.order !== null) return branchConfig;
if (branchConfig.order !== null) {
return branchConfig;
}
return {
...branchConfig,
order: parseFloat(`0.${i}`, 10),

View File

@ -357,7 +357,9 @@ export const draw = function (txt, id, ver) {
branchNum++;
}
svg.attr('height', function () {
if (direction === 'BT') return Object.keys(allCommitsDict).length * config.nodeSpacing;
if (direction === 'BT') {
return Object.keys(allCommitsDict).length * config.nodeSpacing;
}
return (branches.length + 1) * config.branchOffset;
});
} catch (e) {

View File

@ -496,7 +496,7 @@ const drawBranches = (svg, branches) => {
export const draw = function (txt, id, ver, diagObj) {
clear();
const conf = getConfig();
const gitGraphConfig = getConfig().gitGraph;
const gitGraphConfig = conf.gitGraph;
// try {
log.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
@ -523,7 +523,12 @@ export const draw = function (txt, id, ver, diagObj) {
drawCommits(diagram, allCommitsDict, true);
// Setup the view box and size of the svg element
setupGraphViewbox(undefined, diagram, gitGraphConfig.diagramPadding, conf.useMaxWidth);
setupGraphViewbox(
undefined,
diagram,
gitGraphConfig.diagramPadding,
gitGraphConfig.useMaxWidth ?? conf.useMaxWidth
);
};
export default {

View File

@ -94,10 +94,22 @@ export const draw = (txt, id, _version, diagObj) => {
var color = scaleOrdinal().range(myGeneratedColors);
// Compute the position of each group on the pie:
var pie = d3pie().value(function (d) {
return d[1];
var pieData = Object.entries(data).map(function (el, idx) {
return {
order: idx,
name: el[0],
value: el[1],
};
});
var dataReady = pie(Object.entries(data));
var pie = d3pie()
.value(function (d) {
return d.value;
})
.sort(function (a, b) {
// Sort slices in clockwise direction
return a.order - b.order;
});
var dataReady = pie(pieData);
// Shape helper to build arcs:
var arcGenerator = arc().innerRadius(0).outerRadius(radius);
@ -110,7 +122,7 @@ export const draw = (txt, id, _version, diagObj) => {
.append('path')
.attr('d', arcGenerator)
.attr('fill', function (d) {
return color(d.data[0]);
return color(d.data.name);
})
.attr('class', 'pieCircle');
@ -122,7 +134,7 @@ export const draw = (txt, id, _version, diagObj) => {
.enter()
.append('text')
.text(function (d) {
return ((d.data[1] / sum) * 100).toFixed(0) + '%';
return ((d.data.value / sum) * 100).toFixed(0) + '%';
})
.attr('transform', function (d) {
return 'translate(' + arcGenerator.centroid(d) + ')';
@ -166,9 +178,9 @@ export const draw = (txt, id, _version, diagObj) => {
.attr('y', legendRectSize - legendSpacing)
.text(function (d) {
if (diagObj.db.getShowData() || conf.showData || conf.pie.showData) {
return d.data[0] + ' [' + d.data[1] + ']';
return d.data.name + ' [' + d.data.value + ']';
} else {
return d.data[0];
return d.data.name;
}
});
} catch (e) {

View File

@ -26,7 +26,9 @@ export const parseDirective = function (statement, context, type) {
export const addActor = function (id, name, description, type) {
// Don't allow description nulling
const old = actors[id];
if (old && name === old.name && description == null) return;
if (old && name === old.name && description == null) {
return;
}
// Don't allow null descriptions, either
if (description == null || description.text == null) {

View File

@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth';
import utils from '../../utils';
import { configureSvgSize } from '../../setupGraphViewbox';
import addSVGAccessibilityFields from '../../accessibility';
import Diagram from '../../Diagram';
let conf = {};
@ -100,8 +101,8 @@ export const bounds = {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _self = this;
let cnt = 0;
/** @param {any} type */
function updateFn(type) {
/** @param type - Either `activation` or `undefined` */
function updateFn(type?: 'activation') {
return function updateItemBounds(item) {
cnt++;
// The loop sequenceItems is a stack so the biggest margins in the beginning of the sequenceItems
@ -200,15 +201,25 @@ export const bounds = {
},
};
/** Options for drawing a note in {@link drawNote} */
interface NoteModel {
/** x axis start position */
startx: number;
/** y axis position */
starty: number;
/** the message to be shown */
message: string;
/** Set this with a custom width to override the default configured width. */
width: number;
}
/**
* Draws an note in the diagram with the attached line
*
* @param {any} elem - The diagram to draw to.
* @param {{ x: number; y: number; message: string; width: number }} noteModel - startX: x axis
* start position, verticalPos: y axis position, message: the message to be shown, width: Set
* this with a custom width to override the default configured width.
* @param elem - The diagram to draw to.
* @param noteModel - Note model options.
*/
const drawNote = function (elem, noteModel) {
const drawNote = function (elem: any, noteModel: NoteModel) {
bounds.bumpVerticalPos(conf.boxMargin);
noteModel.height = conf.boxMargin;
noteModel.starty = bounds.getVerticalPos();
@ -278,11 +289,11 @@ const actorFont = (cnf) => {
* message so it can be drawn later. We do not draw the message at this point so the arrowhead can
* be on top of the activation box.
*
* @param {any} diagram - The parent of the message element
* @param {any} msgModel - The model containing fields describing a message
* @returns {number} lineStartY - The Y coordinate at which the message line starts
* @param _diagram - The parent of the message element.
* @param msgModel - The model containing fields describing a message
* @returns `lineStartY` - The Y coordinate at which the message line starts
*/
const boundMessage = function (diagram, msgModel) {
function boundMessage(_diagram, msgModel): number {
bounds.bumpVerticalPos(10);
const { startx, stopx, message } = msgModel;
const lines = common.splitBreaks(message).length;
@ -321,17 +332,17 @@ const boundMessage = function (diagram, msgModel) {
bounds.insert(msgModel.fromBounds, msgModel.starty, msgModel.toBounds, msgModel.stopy);
return lineStartY;
};
}
/**
* Draws a message. Note that the bounds have previously been updated by boundMessage.
*
* @param {any} diagram - The parent of the message element
* @param {any} msgModel - The model containing fields describing a message
* @param {number} lineStartY - The Y coordinate at which the message line starts
* @param diagObj
* @param diagram - The parent of the message element
* @param msgModel - The model containing fields describing a message
* @param lineStartY - The Y coordinate at which the message line starts
* @param diagObj - The diagram object.
*/
const drawMessage = function (diagram, msgModel, lineStartY, diagObj) {
const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) {
const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel;
const textDims = utils.calculateTextDimensions(message, messageFont(conf));
const textObj = svgDraw.getTextObj();
@ -554,13 +565,6 @@ const activationBounds = function (actor, actors) {
return [left, right];
};
/**
* @param {any} loopWidths
* @param {any} msg
* @param {any} preMargin
* @param {any} postMargin
* @param {any} addLoopFn
*/
function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoopFn) {
bounds.bumpVerticalPos(preMargin);
let heightAdjust = postMargin;
@ -584,12 +588,12 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop
/**
* Draws a sequenceDiagram in the tag with id: id based on the graph definition in text.
*
* @param {any} _text The text of the diagram
* @param {any} id The id of the diagram which will be used as a DOM element id¨
* @param {any} _version Mermaid version from package.json
* @param {any} diagObj A standard diagram containing the db and the text and type etc of the diagram
* @param _text - The text of the diagram
* @param id - The id of the diagram which will be used as a DOM element id¨
* @param _version - Mermaid version from package.json
* @param diagObj - A standard diagram containing the db and the text and type etc of the diagram
*/
export const draw = function (_text, id, _version, diagObj) {
export const draw = function (_text: string, id: string, _version: string, diagObj: Diagram) {
const { securityLevel, sequence } = configApi.getConfig();
conf = sequence;
// Handle root and Document for when rendering in sandbox mode
@ -632,10 +636,10 @@ export const draw = function (_text, id, _version, diagObj) {
svgDraw.insertSequenceNumber(diagram);
/**
* @param {any} msg
* @param {any} verticalPos
* @param msg - The message to draw.
* @param verticalPos - The vertical position of the message.
*/
function activeEnd(msg, verticalPos) {
function activeEnd(msg: any, verticalPos: number) {
const activationData = bounds.endActivation(msg);
if (activationData.starty + 18 > verticalPos) {
activationData.starty = verticalPos - 6;
@ -762,8 +766,11 @@ export const draw = function (_text, id, _version, diagObj) {
case diagObj.db.LINETYPE.AUTONUMBER:
sequenceIndex = msg.message.start || sequenceIndex;
sequenceIndexStep = msg.message.step || sequenceIndexStep;
if (msg.message.visible) diagObj.db.enableSequenceNumbers();
else diagObj.db.disableSequenceNumbers();
if (msg.message.visible) {
diagObj.db.enableSequenceNumbers();
} else {
diagObj.db.disableSequenceNumbers();
}
break;
case diagObj.db.LINETYPE.CRITICAL_START:
adjustLoopHeightForWrap(
@ -907,12 +914,16 @@ export const draw = function (_text, id, _version, diagObj) {
* It will enumerate each given message, and will determine its text width, in relation to the actor
* it originates from, and destined to.
*
* @param {any} actors - The actors map
* @param {Array} messages - A list of message objects to iterate
* @param diagObj
* @returns {any}
* @param actors - The actors map
* @param messages - A list of message objects to iterate
* @param diagObj - The diagram object.
* @returns The max message width of each actor.
*/
const getMaxMessageWidthPerActor = function (actors, messages, diagObj) {
function getMaxMessageWidthPerActor(
actors: { [id: string]: any },
messages: any[],
diagObj: Diagram
): { [id: string]: number } {
const maxMessageWidthPerActor = {};
messages.forEach(function (msg) {
@ -1005,7 +1016,7 @@ const getMaxMessageWidthPerActor = function (actors, messages, diagObj) {
log.debug('maxMessageWidthPerActor:', maxMessageWidthPerActor);
return maxMessageWidthPerActor;
};
}
const getRequiredPopupWidth = function (actor) {
let requiredPopupWidth = 0;
@ -1022,15 +1033,19 @@ const getRequiredPopupWidth = function (actor) {
};
/**
* This will calculate the optimal margin for each given actor, for a given actor->messageWidth map.
* This will calculate the optimal margin for each given actor,
* for a given actor messageWidth map.
*
* An actor's margin is determined by the width of the actor, the width of the largest message that
* originates from it, and the configured conf.actorMargin.
*
* @param {any} actors - The actors map to calculate margins for
* @param {any} actorToMessageWidth - A map of actor key -> max message width it holds
* @param actors - The actors map to calculate margins for
* @param actorToMessageWidth - A map of actor key max message width it holds
*/
const calculateActorMargins = function (actors, actorToMessageWidth) {
function calculateActorMargins(
actors: { [id: string]: any },
actorToMessageWidth: ReturnType<typeof getMaxMessageWidthPerActor>
) {
let maxHeight = 0;
Object.keys(actors).forEach((prop) => {
const actor = actors[prop];
@ -1071,7 +1086,7 @@ const calculateActorMargins = function (actors, actorToMessageWidth) {
}
return Math.max(maxHeight, conf.height);
};
}
const buildNoteModel = function (msg, actors, diagObj) {
const startx = actors[msg.from].x;

View File

@ -31,7 +31,9 @@ const addPopupInteraction = (id, actorCnt) => {
addFunction(() => {
const arr = document.querySelectorAll(id);
// This will be the case when running in sandboxed mode
if (arr.length === 0) return;
if (arr.length === 0) {
return;
}
arr[0].addEventListener('mouseover', function () {
popupMenuUpFunc('actor' + actorCnt + '_popup');
});
@ -322,7 +324,9 @@ export const drawLabel = function (elem, txtObject) {
let actorCnt = -1;
export const fixLifeLineHeights = (diagram, bounds) => {
if (!diagram.selectAll) return;
if (!diagram.selectAll) {
return;
}
diagram
.selectAll('.actor-line')
.attr('class', '200')

View File

@ -217,7 +217,9 @@ export const addTitleAndBox = (g, stateDef, altBkg) => {
.attr('rx', '0');
title.attr('x', startX + pad);
if (titleWidth <= orgWidth) title.attr('x', orgX + (width - dblPad) / 2 - titleWidth / 2 + pad);
if (titleWidth <= orgWidth) {
title.attr('x', orgX + (width - dblPad) / 2 - titleWidth / 2 + pad);
}
// Title background
g.insert('rect', ':first-child')
@ -379,14 +381,27 @@ export const drawState = function (elem, stateDef) {
const g = elem.append('g').attr('id', id).attr('class', 'stateGroup');
if (stateDef.type === 'start') drawStartState(g);
if (stateDef.type === 'end') drawEndState(g);
if (stateDef.type === 'fork' || stateDef.type === 'join') drawForkJoinState(g, stateDef);
if (stateDef.type === 'note') drawNote(stateDef.note.text, g);
if (stateDef.type === 'divider') drawDivider(g);
if (stateDef.type === 'default' && stateDef.descriptions.length === 0)
if (stateDef.type === 'start') {
drawStartState(g);
}
if (stateDef.type === 'end') {
drawEndState(g);
}
if (stateDef.type === 'fork' || stateDef.type === 'join') {
drawForkJoinState(g, stateDef);
}
if (stateDef.type === 'note') {
drawNote(stateDef.note.text, g);
}
if (stateDef.type === 'divider') {
drawDivider(g);
}
if (stateDef.type === 'default' && stateDef.descriptions.length === 0) {
drawSimpleState(g, stateDef);
if (stateDef.type === 'default' && stateDef.descriptions.length > 0) drawDescrState(g, stateDef);
}
if (stateDef.type === 'default' && stateDef.descriptions.length > 0) {
drawDescrState(g, stateDef);
}
const stateBox = g.node().getBBox();
stateInfo.width = stateBox.width + 2 * getConfig().state.padding;

View File

@ -148,7 +148,9 @@ export const addState = function (id, type, doc, descr, note) {
}
if (descr) {
log.info('Adding state ', id, descr);
if (typeof descr === 'string') addDescription(id, descr.trim());
if (typeof descr === 'string') {
addDescription(id, descr.trim());
}
if (typeof descr === 'object') {
descr.forEach((des) => addDescription(id, des.trim()));

View File

@ -1,8 +1,11 @@
import type { DiagramDetector } from '../../diagram-api/types';
export const stateDetectorV2: DiagramDetector = (text, config) => {
if (text.match(/^\s*stateDiagram-v2/) !== null) return true;
if (text.match(/^\s*stateDiagram/) && config?.state?.defaultRenderer === 'dagre-wrapper')
if (text.match(/^\s*stateDiagram-v2/) !== null) {
return true;
}
if (text.match(/^\s*stateDiagram/) && config?.state?.defaultRenderer === 'dagre-wrapper') {
return true;
}
return false;
};

View File

@ -3,6 +3,8 @@ import type { DiagramDetector } from '../../diagram-api/types';
export const stateDetector: DiagramDetector = (txt, config) => {
// If we have confirmed to only use new state diagrams this function should always return false
// as in not signalling true for a legacy state diagram
if (config?.state?.defaultRenderer === 'dagre-wrapper') return false;
if (config?.state?.defaultRenderer === 'dagre-wrapper') {
return false;
}
return txt.match(/^\s*stateDiagram/) !== null;
};

View File

@ -120,7 +120,7 @@ const renderDoc = (doc, diagram, parentId, altBkg, root, domDocument, diagObj) =
}
// Set an object for the graph label
if (parentId)
if (parentId) {
graph.setGraph({
rankdir: 'LR',
multigraph: true,
@ -133,7 +133,7 @@ const renderDoc = (doc, diagram, parentId, altBkg, root, domDocument, diagObj) =
// ranksep: 5,
// nodesep: 1
});
else {
} else {
graph.setGraph({
rankdir: 'TB',
multigraph: true,
@ -268,7 +268,9 @@ const renderDoc = (doc, diagram, parentId, altBkg, root, domDocument, diagObj) =
let pWidth = 0;
let pShift = 0;
if (parent) {
if (parent.parentElement) pWidth = parent.parentElement.getBBox().width;
if (parent.parentElement) {
pWidth = parent.parentElement.getBBox().width;
}
pShift = parseInt(parent.getAttribute('data-x-shift'), 10);
if (Number.isNaN(pShift)) {
pShift = 0;

View File

@ -15,7 +15,7 @@ export const setConf = function (cnf) {
const actors = {};
/** @param {any} diagram */
/** @param diagram - The diagram to draw to. */
function drawActorLegend(diagram) {
const conf = getConfig().journey;
// Draw the actors
@ -74,7 +74,9 @@ export const draw = function (text, id, version, diagObj) {
const title = diagObj.db.getDiagramTitle();
const actorNames = diagObj.db.getActors();
for (const member in actors) delete actors[member];
for (const member in actors) {
delete actors[member];
}
let actorPos = 0;
actorNames.forEach((actorName) => {
actors[actorName] = {
@ -155,8 +157,8 @@ export const bounds = {
// eslint-disable-next-line @typescript-eslint/no-this-alias
const _self = this;
let cnt = 0;
/** @param {any} type */
function updateFn(type) {
/** @param type - Set to `activation` if activation */
function updateFn(type?: 'activation') {
return function updateItemBounds(item) {
cnt++;
// The loop sequenceItems is a stack so the biggest margins in the beginning of the sequenceItems

View File

@ -9,7 +9,9 @@ Mermaid can render class diagrams.
```mermaid-example
classDiagram
note "From Duck till Zebra"
Animal <|-- Duck
note for Duck "can fly\ncan swim\ncan dive\ncan help in debugging"
Animal <|-- Fish
Animal <|-- Zebra
Animal : +int age
@ -375,6 +377,10 @@ click className href "url" "tooltip"
- (_optional_) tooltip is a string to be displayed when hovering over element (note: The styles of the tooltip are set by the class .mermaidTooltip.)
- note: callback function will be called with the nodeId as parameter.
## Notes
It is possible to add notes on digram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"`
### Examples
_URL Link:_

View File

@ -85,10 +85,34 @@ Cardinality is a property that describes how many elements of another entity can
| `}o` | `o{` | Zero or more (no upper limit) |
| `}\|` | `\|{` | One or more (no upper limit) |
**Aliases**
| Value (left) | Value (right) | Alias for |
| :----------: | :-----------: | ------------ |
| one or zero | one or zero | Zero or one |
| zero or one | zero or one | Zero or one |
| one or more | one or more | One or more |
| one or many | one or many | One or more |
| many(1) | many(1) | One or more |
| 1+ | 1+ | One or more |
| zero or more | zero or more | Zero or more |
| zero or many | zero or many | Zero or more |
| many(0) | many(1) | Zero or more |
| 0+ | 0+ | Zero or more |
| only one | only one | Exactly one |
| 1 | 1 | Exactly one |
### Identification
Relationships may be classified as either _identifying_ or _non-identifying_ and these are rendered with either solid or dashed lines respectively. This is relevant when one of the entities in question can not have independent existence without the other. For example a firm that insures people to drive cars might need to store data on `NAMED-DRIVER`s. In modelling this we might start out by observing that a `CAR` can be driven by many `PERSON` instances, and a `PERSON` can drive many `CAR`s - both entities can exist without the other, so this is a non-identifying relationship that we might specify in Mermaid as: `PERSON }|..|{ CAR : "driver"`. Note the two dots in the middle of the relationship that will result in a dashed line being drawn between the two entities. But when this many-to-many relationship is resolved into two one-to-many relationships, we observe that a `NAMED-DRIVER` cannot exist without both a `PERSON` and a `CAR` - the relationships become identifying and would be specified using hyphens, which translate to a solid line:
**Aliases**
| Value | Alias for |
| :-----------: | :---------------: |
| to | _identifying_ |
| optionally to | _non-identifying_ |
```mmd
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
@ -155,6 +179,7 @@ erDiagram
string lastName
int age
}
MANUFACTURER only one to zero or more CAR
```
### Other Things

View File

@ -40,20 +40,20 @@
};
</script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/loader.min.js"
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.1/min/vs/loader.min.js"
integrity="sha512-6bIYsGqvLpAiEBXPdRQeFf5cueeBECtAKJjIHer3BhBZNTV3WLcLA8Tm3pDfxUwTMIS+kAZwTUvJ1IrMdX8C5w=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.nls.min.js"
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.1/min/vs/editor/editor.main.nls.min.js"
integrity="sha512-CCv+DKWw+yZhxf4Z+ExT6HC5G+3S45TeMTYcJyYbdrv4BpK2vyALJ4FoVR/KGWDIPu7w4tNCOC9MJQIkYPR5FA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
<script
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.0/min/vs/editor/editor.main.min.js"
integrity="sha512-iVHvVf6TQEoF5oQrocjP88wstImQHQZCxGsa5nKYOs6gXWzsV7QZHyk80HrXhrEWRJ1ADoIV6Uji3UUXtCmBMg=="
src="https://cdnjs.cloudflare.com/ajax/libs/monaco-editor/0.34.1/min/vs/editor/editor.main.min.js"
integrity="sha512-u4eMtetNbBJvHXdLXs2kWZvJiVlg3cmkVcxrLzSPa1eNFuHygPYvyMWyK9PsD6Eq2MZSo+mTyjds8uuhPzVxHA=="
crossorigin="anonymous"
referrerpolicy="no-referrer"
></script>
@ -151,8 +151,9 @@
hook.afterEach(function (html, next) {
next(html);
(async () => {
while (!window.hasOwnProperty('monaco'))
while (!window.hasOwnProperty('monaco')) {
await new Promise((resolve) => setTimeout(resolve, 1000));
}
colorizeEverything(html).then(
(newHTML) =>
(document.querySelector('article.markdown-section').innerHTML = newHTML)
@ -170,7 +171,9 @@
startOnLoad: false,
themeCSS: '.label { font-family: Source Sans Pro,Helvetica Neue,Arial,sans-serif; }',
};
if (isDarkMode) conf.theme = 'dark';
if (isDarkMode) {
conf.theme = 'dark';
}
mermaid.initialize(conf);
</script>
<script>

View File

@ -19,7 +19,7 @@ Drawing a pie chart is really simple in mermaid.
- Start with `pie` keyword to begin the diagram
- `showData` to render the actual data values after the legend text. This is **_OPTIONAL_**
- Followed by `title` keyword and its value in string to give a title to the pie-chart. This is **_OPTIONAL_**
- Followed by dataSet
- Followed by dataSet. Pie slices will be ordered clockwise in the same order as the labels.
- `label` for a section in the pie diagram within `" "` quotes.
- Followed by `:` colon as separator
- Followed by `positive numeric value` (supported upto two decimal places)

View File

@ -66,7 +66,7 @@ Messages can be of two displayed either solid or with a dotted line.
[Actor][Arrow][Actor]:Message text
```
There are six types of arrows currently supported:
There are eight types of arrows currently supported:
| Type | Description |
| ---- | ------------------------------------------------ |

View File

@ -27,7 +27,7 @@ export const log: Record<keyof typeof LEVELS, typeof console.log> = {
/**
* Sets a log level
*
* @param {LogLevel} [level="fatal"] The level to set the logging to. Default is `"fatal"`
* @param level - The level to set the logging to. Default is `"fatal"`
*/
export const setLogLevel = function (level: keyof typeof LEVELS | number | string = 'fatal') {
let numericLevel: number = LEVELS.fatal;
@ -80,10 +80,10 @@ export const setLogLevel = function (level: keyof typeof LEVELS | number | strin
/**
* Returns a format with the timestamp and the log level
*
* @param {LogLevel} level The level for the log format
* @returns {string} The format with the timestamp and log level
* @param level - The level for the log format
* @returns The format with the timestamp and log level
*/
const format = (level: string): string => {
const format = (level: Uppercase<LogLevel>): string => {
const time = moment().format('ss.SSS');
return `%c${time} : ${level} : `;
};

View File

@ -19,12 +19,6 @@ import type { ParseErrorFunction } from './Diagram';
* elements with the attribute already set. This way the init function can be triggered several
* times.
*
* Optionally, `init` can accept in the second argument one of the following:
*
* - A DOM Node
* - An array of DOM nodes (as would come from a jQuery selector)
* - A W3C selector, a la `.mermaid`
*
* ```mermaid
* graph LR;
* a(Find elements)-->b{Processed}
@ -34,9 +28,12 @@ import type { ParseErrorFunction } from './Diagram';
*
* Renders the mermaid diagrams
*
* @param config
* @param nodes
* @param callback
* @param config - **Deprecated**, please set configuration in {@link initialize}.
* @param nodes - **Default**: `.mermaid`. One of the following:
* - A DOM Node
* - An array of DOM nodes (as would come from a jQuery selector)
* - A W3C selector, a la `.mermaid`
* @param callback - Called once for each rendered diagram's id.
*/
const init = async function (
config?: MermaidConfig,
@ -143,7 +140,9 @@ const initThrowsErrors = async function (
if (typeof callback !== 'undefined') {
callback(id);
}
if (bindFunctions) bindFunctions(element);
if (bindFunctions) {
bindFunctions(element);
}
},
element
);
@ -200,7 +199,7 @@ if (typeof document !== 'undefined') {
* This is provided for environments where the mermaid object can't directly have a new member added
* to it (eg. dart interop wrapper). (Initially there is no parseError member of mermaid).
*
* @param {function (err, hash)} newParseErrorHandler New parseError() callback.
* @param newParseErrorHandler - New parseError() callback.
*/
const setParseErrorHandler = function (newParseErrorHandler: (err: any, hash: any) => void) {
mermaid.parseError = newParseErrorHandler;

View File

@ -279,8 +279,11 @@ describe('mermaidAPI', function () {
function escapeForRegexp(str: string) {
const strChars = str.split(''); // split into array of every char
const strEscaped = strChars.map((char) => {
if (REGEXP_SPECIALS.includes(char)) return `\\${char}`;
else return char;
if (REGEXP_SPECIALS.includes(char)) {
return `\\${char}`;
} else {
return char;
}
});
return strEscaped.join('');
}

View File

@ -10,7 +10,6 @@
*
* In addition to the render function, a number of behavioral configuration options are available.
*
* @name mermaidAPI
*/
import { select } from 'd3';
import { compile, serialize, stringify } from 'stylis';
@ -72,8 +71,8 @@ type D3Element = any;
// ----------------------------------------------------------------------------
/**
* @param text
* @param parseError
* @param text - The mermaid diagram definition.
* @param parseError - If set, handles errors.
*/
function parse(text: string, parseError?: ParseErrorFunction): boolean {
addDiagrams();
@ -83,8 +82,8 @@ function parse(text: string, parseError?: ParseErrorFunction): boolean {
/**
*
* @param {string} text - text to be encoded
* @returns {string}
* @param text - text to be encoded
* @returns
*/
export const encodeEntities = function (text: string): string {
let txt = text;
@ -112,8 +111,8 @@ export const encodeEntities = function (text: string): string {
/**
*
* @param {string} text - text to be decoded
* @returns {string}
* @param text - text to be decoded
* @returns
*/
export const decodeEntities = function (text: string): string {
let txt = text;
@ -136,10 +135,10 @@ export const decodeEntities = function (text: string): string {
/**
* Create a CSS style that starts with the given class name, then the element,
* with an enclosing block that has each of the cssClasses followed by !important;
* @param {string} cssClass
* @param {string} element
* @param {string[]} cssClasses
* @returns {string}
* @param cssClass - CSS class name
* @param element - CSS element
* @param cssClasses - list of CSS styles to append after the element
* @returns - the constructed string
*/
export const cssImportantStyles = (
cssClass: string,
@ -152,10 +151,10 @@ export const cssImportantStyles = (
/**
* Create the user styles
*
* @param {MermaidConfig} config
* @param {string} graphType
* @param {Record<string, DiagramStyleClassDef> | null | undefined} classDefs - the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...)
* @returns {string} the string with all the user styles
* @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,
@ -225,10 +224,10 @@ export const createUserStyles = (
/**
* Clean up svgCode. Do replacements needed
*
* @param {string} svgCode
* @param {boolean} inSandboxMode - security level
* @param {boolean} useArrowMarkerUrls - should arrow marker's use full urls? (vs. just the anchors)
* @returns {string} the cleaned up svgCode
* @param svgCode - the code to clean up
* @param inSandboxMode - security level
* @param useArrowMarkerUrls - should arrow marker's use full urls? (vs. just the anchors)
* @returns the cleaned up svgCode
*/
export const cleanUpSvgCode = (
svgCode = '',
@ -253,10 +252,10 @@ export const cleanUpSvgCode = (
/**
* Put the svgCode into an iFrame. Return the iFrame code
*
* @param {string} svgCode
* @param {D3Element} svgElement - the d3 node that has the current svgElement so we can get the height from it
* @returns {string} - the code with the iFrame that now contains the svgCode
* @todo TODO replace btoa(). Replace with buf.toString('base64')?
* @param svgCode - the svg code to put inside the iFrame
* @param svgElement - the d3 node that has the current svgElement so we can get the height from it
* @returns - the code with the iFrame that now contains the svgCode
* TODO replace btoa(). Replace with buf.toString('base64')?
*/
export const putIntoIFrame = (svgCode = '', svgElement?: D3Element): string => {
const height = svgElement ? svgElement.viewBox.baseVal.height + 'px' : IFRAME_HEIGHT;
@ -272,12 +271,12 @@ export const putIntoIFrame = (svgCode = '', svgElement?: D3Element): string => {
* Only set the xmlns:xlink attribute on svg if svgXlink is given.
* Return the last node appended
*
* @param {D3Element} parentRoot - the d3 node to append things to
* @param {string} id
* @param enclosingDivId
* @param {string} divStyle
* @param {string} svgXlink
* @returns {D3Element} - returns the parentRoot that had nodes appended
* @param parentRoot - the d3 node to append things to
* @param id - the value to set the id attr to
* @param enclosingDivId - the id to set the enclosing div to
* @param divStyle - if given, the style to set the enclosing div to
* @param svgXlink - if given, the link to set the new svg element to
* @returns - returns the parentRoot that had nodes appended
*/
export const appendDivSvgG = (
parentRoot: D3Element,
@ -305,12 +304,13 @@ export const appendDivSvgG = (
return parentRoot;
};
/** Append an iFrame node to the given parentNode and set the id, style, and 'sandbox' attributes
/**
* Append an iFrame node to the given parentNode and set the id, style, and 'sandbox' attributes
* Return the appended iframe d3 node
*
* @param {D3Element} parentNode
* @param {string} iFrameId - id to use for the iFrame
* @returns {D3Element} the appended iframe d3 node
* @param parentNode - the d3 node to append the iFrame node to
* @param iFrameId - id to use for the iFrame
* @returns the appended iframe d3 node
*/
function sandboxedIframe(parentNode: D3Element, iFrameId: string): D3Element {
return parentNode
@ -323,11 +323,11 @@ function sandboxedIframe(parentNode: D3Element, iFrameId: string): D3Element {
/**
* Remove any existing elements from the given document
*
* @param {Document} doc - the document to removed elements from
* @param {string} isSandboxed - whether or not we are in sandboxed mode
* @param {string} id - id for any existing SVG element
* @param {string} divSelector - selector for any existing enclosing div element
* @param {string} iFrameSelector - selector for any existing iFrame element
* @param doc - the document to removed elements from
* @param isSandboxed - whether or not we are in sandboxed mode
* @param id - id for any existing SVG element
* @param divSelector - selector for any existing enclosing div element
* @param iFrameSelector - selector for any existing iFrame element
*/
export const removeExistingElements = (
doc: Document,
@ -365,13 +365,11 @@ export const removeExistingElements = (
* });
* ```
*
* @param {string} id The id for the SVG element (the element to be rendered)
* @param {string} text The text for the graph definition
* @param {(svgCode: string, bindFunctions?: (element: Element) => void) => void} cb Callback which
* is called after rendering is finished with the svg code as in param.
* @param {Element} svgContainingElement HTML element where the svg will be inserted. (Is usually element with the .mermaid class)
* @param id - The id for the SVG element (the element to be rendered)
* @param text - The text for the graph definition
* @param cb - Callback which is called after rendering is finished with the svg code as in param.
* @param svgContainingElement - HTML element where the svg will be inserted. (Is usually element with the .mermaid class)
* inserted. If no svgContainingElement is provided then the SVG element will be appended to the body.
* @returns {void}
*/
const render = async function (
id: string,
@ -420,7 +418,9 @@ const render = async function (
// In regular execution the svgContainingElement will be the element with a mermaid class
if (typeof svgContainingElement !== 'undefined') {
if (svgContainingElement) svgContainingElement.innerHTML = '';
if (svgContainingElement) {
svgContainingElement.innerHTML = '';
}
if (isSandboxed) {
// If we are in sandboxed mode, we do everything mermaid related in a (sandboxed )iFrame
@ -445,7 +445,9 @@ const render = async function (
const iframe = sandboxedIframe(select('body'), iFrameID);
root = select(iframe.nodes()[0]!.contentDocument!.body);
root.node().style.margin = 0;
} else root = select('body');
} else {
root = select('body');
}
appendDivSvgG(root, id, enclosingDivID);
}
@ -569,11 +571,15 @@ const parseDirective = function (p: any, statement: string, context: string, typ
currentDirective = {};
break;
case 'type_directive':
if (!currentDirective) throw new Error('currentDirective is undefined');
if (!currentDirective) {
throw new Error('currentDirective is undefined');
}
currentDirective.type = statement.toLowerCase();
break;
case 'arg_directive':
if (!currentDirective) throw new Error('currentDirective is undefined');
if (!currentDirective) {
throw new Error('currentDirective is undefined');
}
currentDirective.args = JSON.parse(statement);
break;
case 'close_directive':
@ -631,7 +637,9 @@ const handleDirective = function (p: any, directive: any, type: string): void {
}
};
/** @param {MermaidConfig} options */
/**
* @param options - Initial Mermaid options
* */
async function initialize(options: MermaidConfig) {
// Handle legacy location of font-family configuration
if (options?.fontFamily) {

View File

@ -26,6 +26,7 @@ export const calculateSvgSizeAttrs = function (height, width, useMaxWidth) {
attrs.set('width', '100%');
attrs.set('style', `max-width: ${width}px;`);
} else {
attrs.set('height', height);
attrs.set('width', width);
}
return attrs;

View File

@ -50,8 +50,11 @@ export class MockedD3 {
} else {
const idOnly = beforeSelector[0] == '#' ? beforeSelector.substring(1) : beforeSelector;
const foundIndex = this._children.findIndex((child) => child.id === idOnly);
if (foundIndex < 0) this._children.push(newMock);
else this._children.splice(foundIndex, 0, newMock);
if (foundIndex < 0) {
this._children.push(newMock);
} else {
this._children.splice(foundIndex, 0, newMock);
}
}
return newMock;
};
@ -62,8 +65,12 @@ export class MockedD3 {
if (arguments.length === 1) {
return this.attribs.get(attrName);
} else {
if (attrName === 'id') this.id = attrValue; // also set the id explicitly
if (attrValue !== undefined) this.attribs.set(attrName, attrValue);
if (attrName === 'id') {
this.id = attrValue; // also set the id explicitly
}
if (attrValue !== undefined) {
this.attribs.set(attrName, attrValue);
}
return this;
}
}

View File

@ -1,5 +1,5 @@
/**
* @file Values that have been hardcoded in src/diagrams/er/styles.js. These can be used by
* Values that have been hardcoded in src/diagrams/er/styles.js. These can be used by
* theme-_._ files to maintain display styles until themes, styles, renderers are revised. --
* 2022-09-22
*/

View File

@ -4,6 +4,7 @@ import {
curveBasis,
curveBasisClosed,
curveBasisOpen,
CurveFactory,
curveLinear,
curveLinearClosed,
curveMonotoneX,
@ -42,13 +43,13 @@ const directiveWithoutOpen =
/\s*(?:(?:(\w+)(?=:):|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi;
/**
* @function detectInit Detects the init config object from the text
* @param config
* Detects the init config object from the text
*
* ```mermaid
* @param text - The text defining the graph. For example:
*
* %%{init: {"theme": "debug", "logLevel": 1 }}%%
* graph LR
* ```mermaid
* %%{init: {"theme": "debug", "logLevel": 1 }}%%
* graph LR
* a-->b
* b-->c
* c-->d
@ -58,11 +59,11 @@ const directiveWithoutOpen =
* g-->h
* ```
*
* Or
* Or
*
* ```mermaid
* %%{initialize: {"theme": "dark", logLevel: "debug" }}%%
* graph LR
* ```mermaid
* %%{initialize: {"theme": "dark", logLevel: "debug" }}%%
* graph LR
* a-->b
* b-->c
* c-->d
@ -71,8 +72,9 @@ const directiveWithoutOpen =
* f-->g
* g-->h
* ```
* @param {string} text The text defining the graph
* @returns {object} The json object representing the init passed to mermaid.initialize()
*
* @param config - Optional mermaid configuration object.
* @returns The json object representing the init passed to mermaid.initialize()
*/
export const detectInit = function (text: string, config?: MermaidConfig): MermaidConfig {
const inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/);
@ -104,12 +106,14 @@ export const detectInit = function (text: string, config?: MermaidConfig): Merma
};
/**
* @function detectDirective Detects the directive from the text. Text can be single line or
* multiline. If type is null or omitted the first directive encountered in text will be returned
* Detects the directive from the text.
*
* ```mermaid
* graph LR
* %%{someDirective}%%
* Text can be single line or multiline. If type is null or omitted,
* the first directive encountered in text will be returned
*
* ```mermaid
* graph LR
* %%{someDirective}%%
* a-->b
* b-->c
* c-->d
@ -118,13 +122,16 @@ export const detectInit = function (text: string, config?: MermaidConfig): Merma
* f-->g
* g-->h
* ```
* @param {string} text The text defining the graph
* @param {string | RegExp} type The directive to return (default: null)
* @returns {object | Array} An object or Array representing the directive(s): { type: string, args:
* object|null } matched by the input type if a single directive was found, that directive object
* will be returned.
*
* @param text - The text defining the graph
* @param type - The directive to return (default: `null`)
* @returns An object or Array representing the directive(s) matched by the input type.
* If a single directive was found, that directive object will be returned.
*/
export const detectDirective = function (text, type = null) {
export const detectDirective = function (
text: string,
type: string | RegExp = null
): { type?: string; args?: any } | { type?: string; args?: any }[] {
try {
const commentWithoutDirectives = new RegExp(
`[%]{2}(?![{]${directiveWithoutOpen.source})(?=[}][%]{2}).*\n`,
@ -166,14 +173,17 @@ export const detectDirective = function (text, type = null) {
};
/**
* @function isSubstringInArray Detects whether a substring in present in a given array
* @param {string} str The substring to detect
* @param {Array} arr The array to search
* @returns {number} The array index containing the substring or -1 if not present
* Detects whether a substring in present in a given array
*
* @param str - The substring to detect
* @param arr - The array to search
* @returns The array index containing the substring or -1 if not present
*/
export const isSubstringInArray = function (str, arr) {
export const isSubstringInArray = function (str: string, arr: string[]): number {
for (let i = 0; i < arr.length; i++) {
if (arr[i].match(str)) return i;
if (arr[i].match(str)) {
return i;
}
}
return -1;
};
@ -181,26 +191,26 @@ export const isSubstringInArray = function (str, arr) {
/**
* Returns a d3 curve given a curve name
*
* @param {string | undefined} interpolate The interpolation name
* @param {any} defaultCurve The default curve to return
* @returns {import('d3-shape').CurveFactory} The curve factory to use
* @param interpolate - The interpolation name
* @param defaultCurve - The default curve to return
* @returns The curve factory to use
*/
export const interpolateToCurve = (interpolate, defaultCurve) => {
export function interpolateToCurve(interpolate?: string, defaultCurve: CurveFactory): CurveFactory {
if (!interpolate) {
return defaultCurve;
}
const curveName = `curve${interpolate.charAt(0).toUpperCase() + interpolate.slice(1)}`;
return d3CurveTypes[curveName] || defaultCurve;
};
}
/**
* Formats a URL string
*
* @param {string} linkStr String of the URL
* @param {{ securityLevel: string }} config Configuration passed to MermaidJS
* @returns {string | undefined} The formatted URL
* @param linkStr - String of the URL
* @param config - Configuration passed to MermaidJS
* @returns The formatted URL or `undefined`.
*/
export const formatUrl = (linkStr, config) => {
export function formatUrl(linkStr: string, config: { securityLevel: string }): string | undefined {
const url = linkStr.trim();
if (url) {
@ -210,15 +220,15 @@ export const formatUrl = (linkStr, config) => {
return url;
}
};
}
/**
* Runs a function
*
* @param {string} functionName A dot separated path to the function relative to the `window`
* @param {...any} params Parameters to pass to the function
* @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, ...params) => {
export const runFunc = (functionName: string, ...params) => {
const arrPaths = functionName.split('.');
const len = arrPaths.length - 1;
@ -227,34 +237,39 @@ export const runFunc = (functionName, ...params) => {
let obj = window;
for (let i = 0; i < len; i++) {
obj = obj[arrPaths[i]];
if (!obj) return;
if (!obj) {
return;
}
}
obj[fnName](...params);
};
/**
* @typedef {object} Point A (x, y) point
* @property {number} x The x value
* @property {number} y The y value
*/
/** 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
*
* @param {Point} p1 The first point
* @param {Point} p2 The second point
* @returns {number} The distance
* @param p1 - The first point
* @param p2 - The second point
* @returns The distance between the two points.
*/
const distance = (p1, p2) =>
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 {
return p1 && p2 ? Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)) : 0;
}
/**
* @param {Point[]} points List of points
* @returns {Point}
* @todo Give this a description
* TODO: Give this a description
*
* @param points - List of points
*/
const traverseEdge = (points) => {
function traverseEdge(points: Point[]): Point {
let prevPoint;
let totalDistance = 0;
@ -276,8 +291,12 @@ const traverseEdge = (points) => {
// 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) {
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,
@ -289,32 +308,24 @@ const traverseEdge = (points) => {
prevPoint = point;
});
return center;
};
}
/**
* Alias for `traverseEdge`
*
* @param {Point[]} points List of points
* @returns {Point} Return result of `transverseEdge`
* {@inheritdoc traverseEdge}
*/
const calcLabelPosition = (points) => {
function calcLabelPosition(points: Point[]): Point {
if (points.length === 1) {
return points[0];
}
return traverseEdge(points);
};
}
const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition) => {
let prevPoint;
log.info('our points', points);
log.info(`our points ${JSON.stringify(points)}`);
if (points[0] !== initialPosition) {
points = points.reverse();
}
points.forEach((point) => {
totalDistance += distance(point, prevPoint);
prevPoint = point;
});
// Traverse only 25 total distance along points to find cardinality point
const distanceToCardinalityPoint = 25;
@ -330,8 +341,12 @@ const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition)
// 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) {
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,
@ -354,14 +369,18 @@ const calcCardinalityPosition = (isRelationTypePresent, points, initialPosition)
};
/**
* Position ['start_left', 'start_right', 'end_left', 'end_right']
* Calculates the terminal label position.
*
* @param {any} terminalMarkerSize
* @param {any} position
* @param {any} _points
* @returns {any}
* @param terminalMarkerSize - Terminal marker size.
* @param position - Position of label relative to points.
* @param _points - Array of points.
* @returns - The `cardinalityPosition`.
*/
const calcTerminalLabelPosition = (terminalMarkerSize, position, _points) => {
function calcTerminalLabelPosition(
terminalMarkerSize: number,
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;
@ -389,8 +408,12 @@ const calcTerminalLabelPosition = (terminalMarkerSize, position, _points) => {
// 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) {
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,
@ -425,15 +448,15 @@ const calcTerminalLabelPosition = (terminalMarkerSize, position, _points) => {
cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2 - 5;
}
return cardinalityPosition;
};
}
/**
* Gets styles from an array of declarations
*
* @param {string[]} arr Declarations
* @returns {{ style: string; labelStyle: string }} The styles grouped as strings
* @param arr - Declarations
* @returns The styles grouped as strings
*/
export const getStylesFromArray = (arr) => {
export function getStylesFromArray(arr: string[]): { style: string; labelStyle: string } {
let style = '';
let labelStyle = '';
@ -449,7 +472,7 @@ export const getStylesFromArray = (arr) => {
}
return { style: style, labelStyle: labelStyle };
};
}
let cnt = 0;
export const generateId = () => {
@ -458,10 +481,12 @@ export const generateId = () => {
};
/**
* @param {any} length
* @returns {any}
* Generates a random hexadecimal id of the given length.
*
* @param length - Length of ID.
* @returns The generated ID.
*/
function makeid(length) {
function makeid(length: number): string {
let result = '';
const characters = '0123456789abcdef';
const charactersLength = characters.length;
@ -494,22 +519,25 @@ export const getTextObj = function () {
/**
* Adds text to an element
*
* @param {SVGElement} elem Element to add text to
* @param {{
* text: string;
* x: number;
* y: number;
* anchor: 'start' | 'middle' | 'end';
* fontFamily: string;
* fontSize: string | number;
* fontWeight: string | number;
* fill: string;
* class: string | undefined;
* textMargin: number;
* }} textData
* @returns {SVGTextElement} Text element with given styling and content
* @param elem - SVG Element to add text to
* @param textData - Text options.
* @returns Text element with given styling and content
*/
export const drawSimpleText = function (elem, textData) {
export const drawSimpleText = function (
elem: SVGElement,
textData: {
text: string;
x: number;
y: number;
anchor: 'start' | 'middle' | 'end';
fontFamily: string;
fontSize: string | number;
fontWeight: string | number;
fill: string;
class: string | undefined;
textMargin: number;
}
): SVGTextElement {
// Remove and ignore br:s
const nText = textData.text.replace(common.lineBreakRegex, ' ');
@ -607,43 +635,56 @@ const breakString = memoize(
*
* If the wrapped text text has greater height, we extend the height, so it's value won't overflow.
*
* @param {any} text The text to measure
* @param {any} config - The config for fontSize, fontFamily, and fontWeight all impacting the
* @param text - The text to measure
* @param config - The config for fontSize, fontFamily, and fontWeight all impacting the
* resulting size
* @returns {any} - The height for the given text
* @returns The height for the given text
*/
export const calculateTextHeight = function (text, config) {
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;
};
}
/**
* This calculates the width of the given text, font size and family.
*
* @param {any} text - The text to calculate the width of
* @param {any} config - The config for fontSize, fontFamily, and fontWeight all impacting the
* @param text - The text to calculate the width of
* @param config - The config for fontSize, fontFamily, and fontWeight all impacting the
* resulting size
* @returns {any} - The width for the given text
* @returns The width for the given text
*/
export const calculateTextWidth = function (text, config) {
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;
};
}
/**
* This calculates the dimensions of the given text, font size, font family, font weight, and
* margins.
*
* @param {any} text - The text to calculate the width of
* @param {any} config - The config for fontSize, fontFamily, fontWeight, and margin all impacting
* @param text - The text to calculate the width of
* @param config - The config for fontSize, fontFamily, fontWeight, and margin all impacting
* the resulting size
* @returns - The width for the given text
* @returns The dimensions for the given text
*/
export const calculateTextDimensions = memoize(
function (text, config) {
function (
text: string,
config: {
fontSize?: number;
fontWeight?: number;
fontFamily?: string;
}
) {
config = Object.assign({ fontSize: 12, fontWeight: 400, fontFamily: 'Arial' }, config);
const { fontSize, fontFamily, fontWeight } = config;
if (!text) {
@ -712,7 +753,9 @@ export const initIdGenerator = class iterator {
}
next() {
if (!this.deterministic) return Date.now();
if (!this.deterministic) {
return Date.now();
}
return this.count++;
}
@ -723,10 +766,10 @@ let decoder;
/**
* Decodes HTML, source: {@link https://github.com/shrpne/entity-decode/blob/v2.0.1/browser.js}
*
* @param {string} html HTML as a string
* @returns {string} Unescaped HTML
* @param html - HTML as a string
* @returns Unescaped HTML
*/
export const entityDecode = function (html) {
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, ';');
@ -738,9 +781,9 @@ export const entityDecode = function (html) {
/**
* Sanitizes directive objects
*
* @param {object} args Directive's JSON
* @param args - Directive's JSON
*/
export const directiveSanitizer = (args) => {
export const directiveSanitizer = (args: any) => {
log.debug('directiveSanitizer called with', args);
if (typeof args === 'object') {
// check for array
@ -827,14 +870,16 @@ export interface DetailedError {
hash: any;
}
/** @param error */
/** @param error - The error to check */
export function isDetailedError(error: unknown): error is DetailedError {
return 'str' in error;
}
/** @param error */
/** @param error - The error to convert to an error message */
export function getErrorMessage(error: unknown): string {
if (error instanceof Error) return error.message;
if (error instanceof Error) {
return error.message;
}
return String(error);
}

1325
pnpm-lock.yaml generated

File diff suppressed because it is too large Load Diff