Merge branch 'develop' into timeline

* develop: (45 commits)
  Showcase section to the docs - keepings docs up to date (#4055)
  bugfix: add missing d3 curves to flowchart and docs
  fix(deps): update dependency dagre-d3-es to v7.0.8
  build(pre-commit): cache eslint in pre-commit
  build(lint): cache eslint with strategy content
  Update cypress/integration/rendering/sequencediagram.spec.js
  feat(er): allow leading underscore for attributes name
  ci(lint): show nice error on lint failure
  chore: add moment to dependencies
  Update docs
  Update mindmap.md
  chore: remove moment-mini
  docs(readme-ch): fix twitter link
  build(lint): cache prettier on `pnpm run lint`
  fix: moment-mini default exporter
  docs(readme): update broken twitter badge
  test(er): improve tests on multiple key constraints
  Fixes Typo, remove console.log
  doc(er): add documentation on multiple key constraints
  feat(er): allow multiple constraints on attributes
  ...
This commit is contained in:
Sidharth Vinod
2023-02-08 15:57:09 +05:30
59 changed files with 1674 additions and 650 deletions

View File

@@ -4,3 +4,4 @@ docs/Setup.md
cypress.config.js cypress.config.js
cypress/plugins/index.js cypress/plugins/index.js
coverage coverage
*.json

150
.eslintrc.cjs Normal file
View File

@@ -0,0 +1,150 @@
module.exports = {
env: {
browser: true,
es6: true,
'jest/globals': true,
node: true,
},
root: true,
parser: '@typescript-eslint/parser',
parserOptions: {
ecmaFeatures: {
experimentalObjectRestSpread: true,
jsx: true,
},
tsconfigRootDir: __dirname,
sourceType: 'module',
ecmaVersion: 2020,
allowAutomaticSingleRunInference: true,
project: ['./tsconfig.eslint.json', './packages/*/tsconfig.json'],
parser: '@typescript-eslint/parser',
},
extends: [
'eslint:recommended',
'plugin:@typescript-eslint/recommended',
'plugin:json/recommended',
'plugin:markdown/recommended',
'plugin:@cspell/recommended',
'prettier',
],
plugins: [
'@typescript-eslint',
'no-only-tests',
'html',
'jest',
'jsdoc',
'json',
'@cspell',
'lodash',
'unicorn',
],
rules: {
curly: 'error',
'no-console': 'error',
'no-prototype-builtins': 'off',
'no-unused-vars': 'off',
'cypress/no-async-tests': 'off',
'@typescript-eslint/no-floating-promises': 'error',
'@typescript-eslint/no-misused-promises': 'error',
'@typescript-eslint/ban-ts-comment': [
'error',
{
'ts-expect-error': 'allow-with-description',
'ts-ignore': 'allow-with-description',
'ts-nocheck': 'allow-with-description',
'ts-check': 'allow-with-description',
minimumDescriptionLength: 10,
},
],
'json/*': ['error', 'allowComments'],
'@cspell/spellchecker': [
'error',
{
checkIdentifiers: false,
checkStrings: false,
checkStringTemplates: false,
},
],
'no-empty': [
'error',
{
allowEmptyCatch: true,
},
],
'no-only-tests/no-only-tests': 'error',
'lodash/import-scope': ['error', 'method'],
'unicorn/better-regex': 'error',
'unicorn/no-abusive-eslint-disable': 'error',
'unicorn/no-array-push-push': 'error',
'unicorn/no-for-loop': 'error',
'unicorn/no-instanceof-array': 'error',
'unicorn/no-typeof-undefined': 'error',
'unicorn/no-unnecessary-await': 'error',
'unicorn/no-unsafe-regex': 'warn',
'unicorn/no-useless-promise-resolve-reject': 'error',
'unicorn/prefer-array-find': 'error',
'unicorn/prefer-array-flat-map': 'error',
'unicorn/prefer-array-index-of': 'error',
'unicorn/prefer-array-some': 'error',
'unicorn/prefer-default-parameters': 'error',
'unicorn/prefer-includes': 'error',
'unicorn/prefer-negative-index': 'error',
'unicorn/prefer-object-from-entries': 'error',
'unicorn/prefer-string-starts-ends-with': 'error',
'unicorn/prefer-string-trim-start-end': 'error',
'unicorn/string-content': 'error',
'unicorn/prefer-spread': 'error',
'unicorn/no-lonely-if': 'error',
},
overrides: [
{
files: ['cypress/**', 'demos/**'],
rules: {
'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: {
'jsdoc/require-jsdoc': 'off',
'@typescript-eslint/no-unused-vars': 'off',
},
},
{
files: ['*.html', '*.md', '**/*.md/*'],
rules: {
'no-var': 'error',
'no-undef': 'off',
'@typescript-eslint/no-unused-vars': 'off',
'@typescript-eslint/no-floating-promises': 'off',
'@typescript-eslint/no-misused-promises': 'off',
},
parserOptions: {
project: null,
},
},
],
};

View File

@@ -1,137 +0,0 @@
{
"env": {
"browser": true,
"es6": true,
"jest/globals": true,
"node": true
},
"parser": "@typescript-eslint/parser",
"parserOptions": {
"ecmaFeatures": {
"experimentalObjectRestSpread": true,
"jsx": true
},
"sourceType": "module"
},
"extends": [
"eslint:recommended",
"plugin:@typescript-eslint/recommended",
"plugin:json/recommended",
"plugin:markdown/recommended",
"plugin:@cspell/recommended",
"prettier"
],
"plugins": [
"@typescript-eslint",
"no-only-tests",
"html",
"jest",
"jsdoc",
"json",
"@cspell",
"lodash",
"unicorn"
],
"rules": {
"curly": "error",
"no-console": "error",
"no-prototype-builtins": "off",
"no-unused-vars": "off",
"cypress/no-async-tests": "off",
"@typescript-eslint/ban-ts-comment": [
"error",
{
"ts-expect-error": "allow-with-description",
"ts-ignore": "allow-with-description",
"ts-nocheck": "allow-with-description",
"ts-check": "allow-with-description",
"minimumDescriptionLength": 10
}
],
"json/*": ["error", "allowComments"],
"@cspell/spellchecker": [
"error",
{
"checkIdentifiers": false,
"checkStrings": false,
"checkStringTemplates": false
}
],
"no-empty": [
"error",
{
"allowEmptyCatch": true
}
],
"no-only-tests/no-only-tests": "error",
"lodash/import-scope": ["error", "method"],
"unicorn/better-regex": "error",
"unicorn/no-abusive-eslint-disable": "error",
"unicorn/no-array-push-push": "error",
"unicorn/no-for-loop": "error",
"unicorn/no-instanceof-array": "error",
"unicorn/no-typeof-undefined": "error",
"unicorn/no-unnecessary-await": "error",
"unicorn/no-unsafe-regex": "warn",
"unicorn/no-useless-promise-resolve-reject": "error",
"unicorn/prefer-array-find": "error",
"unicorn/prefer-array-flat-map": "error",
"unicorn/prefer-array-index-of": "error",
"unicorn/prefer-array-some": "error",
"unicorn/prefer-default-parameters": "error",
"unicorn/prefer-includes": "error",
"unicorn/prefer-negative-index": "error",
"unicorn/prefer-object-from-entries": "error",
"unicorn/prefer-string-starts-ends-with": "error",
"unicorn/prefer-string-trim-start-end": "error",
"unicorn/string-content": "error",
"unicorn/prefer-spread": "error",
"unicorn/no-lonely-if": "error"
},
"overrides": [
{
"files": ["cypress/**", "demos/**"],
"rules": {
"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": {
"jsdoc/require-jsdoc": "off",
"@typescript-eslint/no-unused-vars": "off"
}
},
{
"files": ["*.html", "*.md", "**/*.md/*"],
"rules": {
"no-var": "error",
"no-undef": "off",
"@typescript-eslint/no-unused-vars": "off"
}
}
]
}

View File

@@ -37,7 +37,20 @@ jobs:
CYPRESS_CACHE_FOLDER: .cache/Cypress CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Run Linting - name: Run Linting
run: pnpm run lint shell: bash
run: |
if ! pnpm run lint; then
# print a nice error message on lint failure
ERROR_MESSAGE='Running `pnpm run lint` failed.'
ERROR_MESSAGE+=' Running `pnpm run lint:fix` may fix this issue. '
ERROR_MESSAGE+=" If this error doesn't occur on your local machine,"
ERROR_MESSAGE+=' make sure your packages are up-to-date by running `pnpm install`.'
ERROR_MESSAGE+=' You may also need to delete your prettier cache by running'
ERROR_MESSAGE+=' `rm ./node_modules/.cache/prettier/.prettier-cache`.'
echo "::error title=Lint failure::${ERROR_MESSAGE}"
# make sure to return an error exitcode so that GitHub actions shows a red-cross
exit 1
fi
- name: Verify Docs - name: Verify Docs
id: verifyDocs id: verifyDocs

View File

@@ -1,5 +1,11 @@
export default { export default {
'!(docs/**/*)*.{ts,js,json,html,md,mts}': ['eslint --fix', 'prettier --write'], '!(docs/**/*)*.{ts,js,json,html,md,mts}': [
'eslint --cache --cache-strategy content --fix',
// don't cache prettier yet, since we use `prettier-plugin-jsdoc`,
// and prettier doesn't invalidate cache on plugin updates"
// https://prettier.io/docs/en/cli.html#--cache
'prettier --write',
],
'cSpell.json': ['ts-node-esm scripts/fixCSpell.ts'], 'cSpell.json': ['ts-node-esm scripts/fixCSpell.ts'],
'**/*.jison': ['pnpm -w run lint:jison'], '**/*.jison': ['pnpm -w run lint:jison'],
}; };

View File

@@ -1,6 +1,6 @@
# mermaid # mermaid
[![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) [![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![Twitter Follow](https://img.shields.io/twitter/follow/mermaidjs_?style=social)](https://twitter.com/mermaidjs_) [![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) [![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=twitter)](https://twitter.com/mermaidjs_)
English | [简体中文](./README.zh-CN.md) English | [简体中文](./README.zh-CN.md)

View File

@@ -1,6 +1,6 @@
# mermaid # mermaid
[![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) [![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![Twitter Follow](https://img.shields.io/twitter/follow/mermaidjs_?style=social)](https://twitter.com/mermaidjs_) [![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) [![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=twitter)](https://twitter.com/mermaidjs_)
[English](./README.md) | 简体中文 [English](./README.md) | 简体中文

View File

@@ -3,6 +3,42 @@
import { imgSnapshotTest, renderGraph } from '../../helpers/util'; import { imgSnapshotTest, renderGraph } from '../../helpers/util';
context('Sequence diagram', () => { context('Sequence diagram', () => {
it('should render a sequence diagram with boxes', () => {
renderGraph(
`
sequenceDiagram
box LightGrey Alice and Bob
participant Alice
participant Bob
end
participant John as John<br/>Second Line
Alice ->> Bob: Hello Bob, how are you?
Bob-->>John: How about you John?
Bob--x Alice: I am good thanks!
Bob-x John: I am good thanks!
Note right of John: Bob thinks a long<br/>long time, so long<br/>that the text does<br/>not fit on a row.
Bob-->Alice: Checking with John...
alt either this
Alice->>John: Yes
else or this
Alice->>John: No
else or this will happen
Alice->John: Maybe
end
par this happens in parallel
Alice -->> Bob: Parallel message 1
and
Alice -->> John: Parallel message 2
end
`,
{ sequence: { useMaxWidth: false } }
);
cy.get('svg').should((svg) => {
const width = parseFloat(svg.attr('width'));
expect(width).to.be.within(830 * 0.95, 830 * 1.05);
expect(svg).to.not.have.attr('style');
});
});
it('should render a simple sequence diagram', () => { it('should render a simple sequence diagram', () => {
imgSnapshotTest( imgSnapshotTest(
` `

View File

@@ -151,7 +151,7 @@ if (typeof document !== 'undefined') {
contentLoadedApi(); contentLoadedApi();
} else { } else {
this.console.log('Not using api'); this.console.log('Not using api');
contentLoaded(); void contentLoaded();
} }
}, },
false false

View File

@@ -71,6 +71,44 @@
</pre> </pre>
<hr /> <hr />
<pre class="mermaid">
erDiagram
"HOSPITAL" {
int id PK
int doctor_id PK, FK
string address UK
string name
string phone_number
string fax_number
}
</pre
>
<hr />
<pre class="mermaid">
erDiagram
CAR ||--o{ NAMED-DRIVER : allows
CAR {
string registrationNumber PK
string make
string model
string[] parts
}
PERSON ||--o{ NAMED-DRIVER : is
PERSON {
string driversLicense PK "The license #"
string(99) firstName "Only 99 characters are allowed"
string lastName
string phone UK
int age
}
NAMED-DRIVER {
string carRegistrationNumber PK, FK
string driverLicence PK,FK
}
MANUFACTURER only one to zero or more CAR : makes
</pre>
<script src="./mermaid.js"></script> <script src="./mermaid.js"></script>
<script type="module"> <script type="module">
mermaid.initialize({ mermaid.initialize({

View File

@@ -128,6 +128,22 @@
</pre> </pre>
<hr /> <hr />
<pre class="mermaid">
sequenceDiagram
box lightgreen Alice & John
participant A
participant J
end
box Another Group very very long description not wrapped
participant B
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how are you ?
</pre
>
<hr />
<script src="./mermaid.js"></script> <script src="./mermaid.js"></script>
<script> <script>
mermaid.initialize({ mermaid.initialize({

View File

@@ -161,12 +161,19 @@
First --> Second First --> Second
First --> Third First --> Third
state First { state "the first composite" as First {
[*] --> 1st [*] --> 1st
1st --> [*] state innerFirst {
state "1 in innerFirst" as 1st1st
1st2nd: 2 in innerFirst
[*] --> 1st1st
1st1st --> 1st2nd
%% 1st2nd --> 1st
}
1st --> innerFirst
innerFirst --> 2nd
} }
state Second { state Second {
[*] --> 2nd
2nd --> [*] 2nd --> [*]
} }
state Third { state Third {

View File

@@ -2,7 +2,7 @@
> >
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. > ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
> >
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/misc/faq.md](../../packages/mermaid/src/docs/misc/faq.md). > ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/faq.md](../../packages/mermaid/src/docs/config/faq.md).
# Frequently Asked Questions # Frequently Asked Questions

View File

@@ -2,7 +2,7 @@
> >
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. > ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
> >
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/misc/integrations.md](../../packages/mermaid/src/docs/misc/integrations.md). > ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/integrations.md](../../packages/mermaid/src/docs/ecosystem/integrations.md).
# Integrations # Integrations
@@ -95,9 +95,10 @@ They also serve as proof of concept, for the variety of things that can be built
## Editor Plugins ## Editor Plugins
- [Vs Code](https://code.visualstudio.com/) - [VS Code](https://code.visualstudio.com/)
- [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid) - [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)
- [Mermaid Preview](https://marketplace.visualstudio.com/items?itemName=vstirbu.vscode-mermaid-preview) - [Mermaid Preview](https://marketplace.visualstudio.com/items?itemName=vstirbu.vscode-mermaid-preview)
- [Markdown Preview Enhanced](https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced)
- [Mermaid Markdown Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting) - [Mermaid Markdown Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting)
- [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor) - [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor)
- [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export) - [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export)

View File

@@ -0,0 +1,9 @@
> **Warning**
>
> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT.
>
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/showcases.md](../../packages/mermaid/src/docs/ecosystem/showcases.md).
# Showcases
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts).

View File

@@ -200,14 +200,13 @@ The `type` and `name` values must begin with an alphabetic character and may con
#### Attribute Keys and Comments #### Attribute Keys and Comments
Attributes may also have a `key` or comment defined. Keys can be "PK", "FK" or "UK", for Primary Key, Foreign Key or Unique Key. And a `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them. Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
```mermaid-example ```mermaid-example
erDiagram erDiagram
CAR ||--o{ NAMED-DRIVER : allows CAR ||--o{ NAMED-DRIVER : allows
CAR { CAR {
string allowedDriver FK "The license of the allowed driver" string registrationNumber PK
string registrationNumber UK
string make string make
string model string model
string[] parts string[] parts
@@ -217,17 +216,21 @@ erDiagram
string driversLicense PK "The license #" string driversLicense PK "The license #"
string(99) firstName "Only 99 characters are allowed" string(99) firstName "Only 99 characters are allowed"
string lastName string lastName
string phone UK
int age int age
} }
MANUFACTURER only one to zero or more CAR NAMED-DRIVER {
string carRegistrationNumber PK, FK
string driverLicence PK, FK
}
MANUFACTURER only one to zero or more CAR : makes
``` ```
```mermaid ```mermaid
erDiagram erDiagram
CAR ||--o{ NAMED-DRIVER : allows CAR ||--o{ NAMED-DRIVER : allows
CAR { CAR {
string allowedDriver FK "The license of the allowed driver" string registrationNumber PK
string registrationNumber UK
string make string make
string model string model
string[] parts string[] parts
@@ -237,9 +240,14 @@ erDiagram
string driversLicense PK "The license #" string driversLicense PK "The license #"
string(99) firstName "Only 99 characters are allowed" string(99) firstName "Only 99 characters are allowed"
string lastName string lastName
string phone UK
int age int age
} }
MANUFACTURER only one to zero or more CAR NAMED-DRIVER {
string carRegistrationNumber PK, FK
string driverLicence PK, FK
}
MANUFACTURER only one to zero or more CAR : makes
``` ```
### Other Things ### Other Things

View File

@@ -842,8 +842,8 @@ In the example below the style defined in the linkStyle statement will belong to
### Styling line curves ### Styling line curves
It is possible to style the type of curve used for lines between items, if the default method does not meet your needs. It is possible to style the type of curve used for lines between items, if the default method does not meet your needs.
Available curve styles include `basis`, `bump`, `linear`, `monotoneX`, `monotoneY`, `natural`, `step`, `stepAfter`, Available curve styles include `basis`, `bumpX`, `bumpY`, `cardinal`, `catmullRom`, `linear`, `monotoneX`, `monotoneY`,
and `stepBefore`. `natural`, `step`, `stepAfter`, and `stepBefore`.
In this example, a left-to-right graph uses the `stepBefore` curve style: In this example, a left-to-right graph uses the `stepBefore` curve style:

View File

@@ -88,7 +88,7 @@ In this way we can use a text outline to generate a hierarchical mindmap.
## Different shapes ## Different shapes
Mermaids mindmaps can show node using different shapes. When specifying a shape for a node the syntax for the is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts even though they are not all supported from the start. Mermaid mindmaps can show nodes using different shapes. When specifying a shape for a node the syntax is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts, even though they are not all supported from the start.
Mindmap can show the following shapes: Mindmap can show the following shapes:

View File

@@ -94,6 +94,59 @@ sequenceDiagram
J->>A: Great! J->>A: Great!
``` ```
### Grouping / Box
The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation:
box Aqua Group Description
... actors ...
end
box Group without description
... actors ...
end
box rgb(33,66,99)
... actors ...
end
> **Note**
> If your group name is a color you can force the color to be transparent:
box transparent Aqua
... actors ...
end
```mermaid-example
sequenceDiagram
box Purple Alice & John
participant A
participant J
end
box Another Group
participant B
participant C
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how is Charly ?
B->>C: Hello Charly, how are you?
```
```mermaid
sequenceDiagram
box Purple Alice & John
participant A
participant J
end
box Another Group
participant B
participant C
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how is Charly ?
B->>C: Hello Charly, how are you?
```
## Messages ## Messages
Messages can be of two displayed either solid or with a dotted line. Messages can be of two displayed either solid or with a dotted line.

View File

@@ -4,7 +4,7 @@
"version": "9.3.0-rc1", "version": "9.3.0-rc1",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"type": "module", "type": "module",
"packageManager": "pnpm@7.25.0", "packageManager": "pnpm@7.25.1",
"keywords": [ "keywords": [
"diagram", "diagram",
"markdown", "markdown",
@@ -23,7 +23,7 @@
"build": "pnpm run -r clean && concurrently \"pnpm build:vite\" \"pnpm build:types\"", "build": "pnpm run -r clean && concurrently \"pnpm build:vite\" \"pnpm build:types\"",
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"", "dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"",
"release": "pnpm build", "release": "pnpm build",
"lint": "eslint --cache --ignore-path .gitignore . && pnpm lint:jison && prettier --check .", "lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .",
"lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts", "lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts",
"lint:jison": "ts-node-esm ./scripts/jison/lint.mts", "lint:jison": "ts-node-esm ./scripts/jison/lint.mts",
"cypress": "cypress run", "cypress": "cypress run",
@@ -67,8 +67,8 @@
"@types/node": "^18.11.9", "@types/node": "^18.11.9",
"@types/prettier": "^2.7.1", "@types/prettier": "^2.7.1",
"@types/rollup-plugin-visualizer": "^4.2.1", "@types/rollup-plugin-visualizer": "^4.2.1",
"@typescript-eslint/eslint-plugin": "^5.42.1", "@typescript-eslint/eslint-plugin": "^5.48.2",
"@typescript-eslint/parser": "^5.42.1", "@typescript-eslint/parser": "^5.48.2",
"@vitest/coverage-c8": "^0.27.0", "@vitest/coverage-c8": "^0.27.0",
"@vitest/ui": "^0.27.0", "@vitest/ui": "^0.27.0",
"concurrently": "^7.5.0", "concurrently": "^7.5.0",
@@ -76,8 +76,8 @@
"cypress": "^10.11.0", "cypress": "^10.11.0",
"cypress-image-snapshot": "^4.0.1", "cypress-image-snapshot": "^4.0.1",
"esbuild": "^0.17.0", "esbuild": "^0.17.0",
"eslint": "^8.27.0", "eslint": "^8.32.0",
"eslint-config-prettier": "^8.5.0", "eslint-config-prettier": "^8.6.0",
"eslint-plugin-cypress": "^2.12.1", "eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-html": "^7.1.0", "eslint-plugin-html": "^7.1.0",
"eslint-plugin-jest": "^27.1.5", "eslint-plugin-jest": "^27.1.5",

View File

@@ -47,6 +47,7 @@
"non-layered-tidy-tree-layout": "^2.0.2" "non-layered-tidy-tree-layout": "^2.0.2"
}, },
"devDependencies": { "devDependencies": {
"@types/cytoscape": "^3.19.9",
"concurrently": "^7.5.0", "concurrently": "^7.5.0",
"mermaid": "workspace:*", "mermaid": "workspace:*",
"rimraf": "^3.0.2" "rimraf": "^3.0.2"

View File

@@ -54,12 +54,12 @@
"dependencies": { "dependencies": {
"@braintree/sanitize-url": "^6.0.0", "@braintree/sanitize-url": "^6.0.0",
"d3": "^7.0.0", "d3": "^7.0.0",
"dagre-d3-es": "7.0.6", "dagre-d3-es": "7.0.8",
"dompurify": "2.4.3", "dompurify": "2.4.3",
"elkjs": "^0.8.2", "elkjs": "^0.8.2",
"khroma": "^2.0.0", "khroma": "^2.0.0",
"lodash-es": "^4.17.21", "lodash-es": "^4.17.21",
"moment-mini": "^2.24.0", "moment": "^2.29.4",
"non-layered-tidy-tree-layout": "^2.0.2", "non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.1.2", "stylis": "^4.1.2",
"ts-dedent": "^2.2.0", "ts-dedent": "^2.2.0",
@@ -86,10 +86,10 @@
"js-base64": "^3.7.2", "js-base64": "^3.7.2",
"jsdom": "^20.0.2", "jsdom": "^20.0.2",
"micromatch": "^4.0.5", "micromatch": "^4.0.5",
"moment": "^2.29.4",
"path-browserify": "^1.0.1", "path-browserify": "^1.0.1",
"prettier": "^2.7.1", "prettier": "^2.7.1",
"remark": "^14.0.2", "remark": "^14.0.2",
"remark-frontmatter": "^4.0.1",
"remark-gfm": "^3.0.1", "remark-gfm": "^3.0.1",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"start-server-and-test": "^1.14.0", "start-server-and-test": "^1.14.0",

View File

@@ -6,7 +6,7 @@ import { extractFrontMatter } from './diagram-api/frontmatter';
import { isDetailedError } from './utils'; import { isDetailedError } from './utils';
import type { DetailedError } from './utils'; import type { DetailedError } from './utils';
export type ParseErrorFunction = (err: string | DetailedError, hash?: any) => void; export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void;
export class Diagram { export class Diagram {
type = 'graph'; type = 'graph';

View File

@@ -59,7 +59,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
// Check to see if any of the attributes has a key or a comment // Check to see if any of the attributes has a key or a comment
attributes.forEach((item) => { attributes.forEach((item) => {
if (item.attributeKeyType !== undefined) { if (item.attributeKeyTypeList !== undefined && item.attributeKeyTypeList.length > 0) {
hasKeyType = true; hasKeyType = true;
} }
@@ -112,6 +112,9 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
nodeHeight = Math.max(typeBBox.height, nameBBox.height); nodeHeight = Math.max(typeBBox.height, nameBBox.height);
if (hasKeyType) { if (hasKeyType) {
const keyTypeNodeText =
item.attributeKeyTypeList !== undefined ? item.attributeKeyTypeList.join(',') : '';
const keyTypeNode = groupNode const keyTypeNode = groupNode
.append('text') .append('text')
.classed('er entityLabel', true) .classed('er entityLabel', true)
@@ -122,7 +125,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => {
.style('text-anchor', 'left') .style('text-anchor', 'left')
.style('font-family', getConfig().fontFamily) .style('font-family', getConfig().fontFamily)
.style('font-size', attrFontSize + 'px') .style('font-size', attrFontSize + 'px')
.text(item.attributeKeyType || ''); .text(keyTypeNodeText);
attributeNode.kn = keyTypeNode; attributeNode.kn = keyTypeNode;
const keyTypeBBox = keyTypeNode.node().getBBox(); const keyTypeBBox = keyTypeNode.node().getBBox();

View File

@@ -28,10 +28,11 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
\"[^"]*\" return 'WORD'; \"[^"]*\" return 'WORD';
"erDiagram" return 'ER_DIAGRAM'; "erDiagram" return 'ER_DIAGRAM';
"{" { this.begin("block"); return 'BLOCK_START'; } "{" { this.begin("block"); return 'BLOCK_START'; }
<block>"," return 'COMMA';
<block>\s+ /* skip whitespace in block */ <block>\s+ /* skip whitespace in block */
<block>\b((?:PK)|(?:FK)|(?:UK))\b return 'ATTRIBUTE_KEY' <block>\b((?:PK)|(?:FK)|(?:UK))\b return 'ATTRIBUTE_KEY'
<block>(.*?)[~](.*?)*[~] return 'ATTRIBUTE_WORD'; <block>(.*?)[~](.*?)*[~] return 'ATTRIBUTE_WORD';
<block>[A-Za-z][A-Za-z0-9\-_\[\]\(\)]* return 'ATTRIBUTE_WORD' <block>[A-Za-z_][A-Za-z0-9\-_\[\]\(\)]* return 'ATTRIBUTE_WORD'
<block>\"[^"]*\" return 'COMMENT'; <block>\"[^"]*\" return 'COMMENT';
<block>[\n]+ /* nothing */ <block>[\n]+ /* nothing */
<block>"}" { this.popState(); return 'BLOCK_STOP'; } <block>"}" { this.popState(); return 'BLOCK_STOP'; }
@@ -131,11 +132,12 @@ attributes
attribute attribute
: attributeType attributeName { $$ = { attributeType: $1, attributeName: $2 }; } : attributeType attributeName { $$ = { attributeType: $1, attributeName: $2 }; }
| attributeType attributeName attributeKeyType { $$ = { attributeType: $1, attributeName: $2, attributeKeyType: $3 }; } | attributeType attributeName attributeKeyTypeList { $$ = { attributeType: $1, attributeName: $2, attributeKeyTypeList: $3 }; }
| attributeType attributeName attributeComment { $$ = { attributeType: $1, attributeName: $2, attributeComment: $3 }; } | attributeType attributeName attributeComment { $$ = { attributeType: $1, attributeName: $2, attributeComment: $3 }; }
| attributeType attributeName attributeKeyType attributeComment { $$ = { attributeType: $1, attributeName: $2, attributeKeyType: $3, attributeComment: $4 }; } | attributeType attributeName attributeKeyTypeList attributeComment { $$ = { attributeType: $1, attributeName: $2, attributeKeyTypeList: $3, attributeComment: $4 }; }
; ;
attributeType attributeType
: ATTRIBUTE_WORD { $$=$1; } : ATTRIBUTE_WORD { $$=$1; }
; ;
@@ -144,6 +146,11 @@ attributeName
: ATTRIBUTE_WORD { $$=$1; } : ATTRIBUTE_WORD { $$=$1; }
; ;
attributeKeyTypeList
: attributeKeyType { $$ = [$1]; }
| attributeKeyTypeList COMMA attributeKeyType { $1.push($3); $$ = $1; }
;
attributeKeyType attributeKeyType
: ATTRIBUTE_KEY { $$=$1; } : ATTRIBUTE_KEY { $$=$1; }
; ;

View File

@@ -135,6 +135,37 @@ describe('when parsing ER diagram it...', function () {
}); });
}); });
describe('attribute name', () => {
it('should allow alphanumeric characters, dashes, underscores and brackets (not leading chars)', function () {
const entity = 'BOOK';
const attribute1 = 'string myBookTitle';
const attribute2 = 'string MYBOOKSUBTITLE_1';
const attribute3 = 'string author-ref[name](1)';
erDiagram.parser.parse(
`erDiagram\n${entity} {\n${attribute1}\n${attribute2}\n${attribute3}\n}`
);
const entities = erDb.getEntities();
expect(Object.keys(entities).length).toBe(1);
expect(entities[entity].attributes.length).toBe(3);
expect(entities[entity].attributes[0].attributeName).toBe('myBookTitle');
expect(entities[entity].attributes[1].attributeName).toBe('MYBOOKSUBTITLE_1');
expect(entities[entity].attributes[2].attributeName).toBe('author-ref[name](1)');
});
it('should not allow leading numbers, dashes or brackets', function () {
const entity = 'BOOK';
const nonLeadingChars = '0-[]()';
[...nonLeadingChars].forEach((nonLeadingChar) => {
expect(() => {
const attribute = `string ${nonLeadingChar}author`;
erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute}\n}`);
}).toThrow();
});
});
});
it('should allow an entity with a single attribute to be defined', function () { it('should allow an entity with a single attribute to be defined', function () {
const entity = 'BOOK'; const entity = 'BOOK';
const attribute = 'string title'; const attribute = 'string title';
@@ -190,6 +221,28 @@ describe('when parsing ER diagram it...', function () {
expect(entities[entity].attributes.length).toBe(4); expect(entities[entity].attributes.length).toBe(4);
}); });
it('should allow an entity with attributes that have many constraints and comments', function () {
const entity = 'CUSTOMER';
const attribute1 = 'int customer_number PK, FK "comment1"';
const attribute2 = 'datetime customer_status_start_datetime PK,UK, FK';
const attribute3 = 'datetime customer_status_end_datetime PK , UK "comment3"';
const attribute4 = 'string customer_firstname';
const attribute5 = 'string customer_lastname "comment5"';
erDiagram.parser.parse(
`erDiagram\n${entity} {\n${attribute1}\n${attribute2}\n${attribute3}\n${attribute4}\n${attribute5}\n}`
);
const entities = erDb.getEntities();
expect(entities[entity].attributes[0].attributeKeyTypeList).toEqual(['PK', 'FK']);
expect(entities[entity].attributes[0].attributeComment).toBe('comment1');
expect(entities[entity].attributes[1].attributeKeyTypeList).toEqual(['PK', 'UK', 'FK']);
expect(entities[entity].attributes[2].attributeKeyTypeList).toEqual(['PK', 'UK']);
expect(entities[entity].attributes[2].attributeComment).toBe('comment3');
expect(entities[entity].attributes[3].attributeKeyTypeList).toBeUndefined();
expect(entities[entity].attributes[4].attributeKeyTypeList).toBeUndefined();
expect(entities[entity].attributes[4].attributeComment).toBe('comment5');
});
it('should allow an entity with attribute that has a generic type', function () { it('should allow an entity with attribute that has a generic type', function () {
const entity = 'BOOK'; const entity = 'BOOK';
const attribute1 = 'type~T~ type'; const attribute1 = 'type~T~ type';

View File

@@ -609,7 +609,7 @@ const insertChildren = (nodeArray, parentLookupDb) => {
* @param id * @param id
*/ */
export const draw = function (text, id, _version, diagObj) { export const draw = async function (text, id, _version, diagObj) {
// Add temporary render element // Add temporary render element
diagObj.db.clear(); diagObj.db.clear();
nodeDb = {}; nodeDb = {};
@@ -617,149 +617,128 @@ export const draw = function (text, id, _version, diagObj) {
// Parse the graph definition // Parse the graph definition
diagObj.parser.parse(text); diagObj.parser.parse(text);
return new Promise(function (resolve, reject) { const renderEl = select('body').append('div').attr('style', 'height:400px').attr('id', 'cy');
const renderEl = select('body').append('div').attr('style', 'height:400px').attr('id', 'cy'); let graph = {
// .attr('style', 'display:none') id: 'root',
let graph = { layoutOptions: {
id: 'root', 'elk.hierarchyHandling': 'INCLUDE_CHILDREN',
layoutOptions: { 'org.eclipse.elk.padding': '[top=100, left=100, bottom=110, right=110]',
'elk.hierarchyHandling': 'INCLUDE_CHILDREN', 'elk.layered.spacing.edgeNodeBetweenLayers': '30',
// 'elk.hierarchyHandling': 'SEPARATE_CHILDREN', 'elk.direction': 'DOWN',
'org.eclipse.elk.padding': '[top=100, left=100, bottom=110, right=110]', },
// 'org.eclipse.elk.layered.spacing.nodeNodeBetweenLayers': 120, children: [],
// 'elk.layered.spacing.nodeNodeBetweenLayers': '140', edges: [],
'elk.layered.spacing.edgeNodeBetweenLayers': '30', };
// 'elk.algorithm': 'layered', log.info('Drawing flowchart using v3 renderer');
'elk.direction': 'DOWN',
// 'elk.port.side': 'SOUTH',
// 'nodePlacement.strategy': 'SIMPLE',
// 'org.eclipse.elk.spacing.labelLabel': 120,
// 'org.eclipse.elk.graphviz.concentrate': true,
// 'org.eclipse.elk.spacing.nodeNode': 120,
// 'org.eclipse.elk.spacing.edgeEdge': 120,
// 'org.eclipse.elk.spacing.edgeNode': 120,
// 'org.eclipse.elk.spacing.nodeEdge': 120,
// 'org.eclipse.elk.spacing.componentComponent': 120,
},
children: [],
edges: [],
};
log.info('Drawing flowchart using v3 renderer');
// Set the direction, // Set the direction,
// Fetch the default direction, use TD if none was found // Fetch the default direction, use TD if none was found
let dir = diagObj.db.getDirection(); let dir = diagObj.db.getDirection();
switch (dir) { switch (dir) {
case 'BT': case 'BT':
graph.layoutOptions['elk.direction'] = 'UP'; graph.layoutOptions['elk.direction'] = 'UP';
break; break;
case 'TB': case 'TB':
graph.layoutOptions['elk.direction'] = 'DOWN'; graph.layoutOptions['elk.direction'] = 'DOWN';
break; break;
case 'LR': case 'LR':
graph.layoutOptions['elk.direction'] = 'RIGHT'; graph.layoutOptions['elk.direction'] = 'RIGHT';
break; break;
case 'RL': case 'RL':
graph.layoutOptions['elk.direction'] = 'LEFT'; graph.layoutOptions['elk.direction'] = 'LEFT';
break; break;
}
const { securityLevel, flowchart: conf } = getConfig();
// Find the root dom node to ne used in rendering
// Handle root and document for when rendering in sandbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
const svg = root.select(`[id="${id}"]`);
// Define the supported markers for the diagram
const markers = ['point', 'circle', 'cross'];
// Add the marker definitions to the svg as marker tags
insertMarkers(svg, markers, diagObj.type, diagObj.arrowMarkerAbsolute);
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const vert = diagObj.db.getVertices();
// Setup nodes from the subgraphs with type group, these will be used
// as nodes with children in the subgraph
let subG;
const subGraphs = diagObj.db.getSubGraphs();
log.info('Subgraphs - ', subGraphs);
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
}
// Add an element in the svg to be used to hold the subgraphs container
// elements
const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
// Create the lookup db for the subgraphs and their children to used when creating
// the tree structured graph
const parentLookupDb = addSubGraphs(diagObj.db);
// Add the nodes to the graph, this will entail creating the actual nodes
// in order to get the size of the node. You can't get the size of a node
// that is not in the dom so we need to add it to the dom, get the size
// we will position the nodes when we get the layout from elkjs
graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph);
// Time for the edges, we start with adding an element in the node to hold the edges
const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
// Fetch the edges form the parsed graph definition
const edges = diagObj.db.getEdges();
// Add the edges to the graph, this will entail creating the actual edges
graph = addEdges(edges, diagObj, graph, svg);
// Iterate through all nodes and add the top level nodes to the graph
const nodes = Object.keys(nodeDb);
nodes.forEach((nodeId) => {
const node = nodeDb[nodeId];
if (!node.parent) {
graph.children.push(node);
} }
const { securityLevel, flowchart: conf } = getConfig(); // Subgraph
if (parentLookupDb.childrenById[nodeId] !== undefined) {
// Find the root dom node to ne used in rendering node.labels = [
// Handle root and document for when rendering in sandbox mode {
let sandboxElement; text: node.labelText,
if (securityLevel === 'sandbox') { layoutOptions: {
sandboxElement = select('#i' + id); 'nodeLabels.placement': '[H_CENTER, V_TOP, INSIDE]',
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
const doc = securityLevel === 'sandbox' ? sandboxElement.nodes()[0].contentDocument : document;
const svg = root.select(`[id="${id}"]`);
// Define the supported markers for the diagram
const markers = ['point', 'circle', 'cross'];
// Add the marker definitions to the svg as marker tags
insertMarkers(svg, markers, diagObj.type, diagObj.arrowMarkerAbsolute);
// Fetch the vertices/nodes and edges/links from the parsed graph definition
const vert = diagObj.db.getVertices();
// Setup nodes from the subgraphs with type group, these will be used
// as nodes with children in the subgraph
let subG;
const subGraphs = diagObj.db.getSubGraphs();
log.info('Subgraphs - ', subGraphs);
for (let i = subGraphs.length - 1; i >= 0; i--) {
subG = subGraphs[i];
diagObj.db.addVertex(subG.id, subG.title, 'group', undefined, subG.classes, subG.dir);
}
// Add an element in the svg to be used to hold the subgraphs container
// elements
const subGraphsEl = svg.insert('g').attr('class', 'subgraphs');
// Create the lookup db for the subgraphs and their children to used when creating
// the tree structured graph
const parentLookupDb = addSubGraphs(diagObj.db);
// Add the nodes to the graph, this will entail creating the actual nodes
// in order to get the size of the node. You can't get the size of a node
// that is not in the dom so we need to add it to the dom, get the size
// we will position the nodes when we get the layout from elkjs
graph = addVertices(vert, id, root, doc, diagObj, parentLookupDb, graph);
// Time for the edges, we start with adding an element in the node to hold the edges
const edgesEl = svg.insert('g').attr('class', 'edges edgePath');
// Fetch the edges form the parsed graph definition
const edges = diagObj.db.getEdges();
// Add the edges to the graph, this will entail creating the actual edges
graph = addEdges(edges, diagObj, graph, svg);
// Iterate through all nodes and add the top level nodes to the graph
const nodes = Object.keys(nodeDb);
nodes.forEach((nodeId) => {
const node = nodeDb[nodeId];
if (!node.parent) {
graph.children.push(node);
}
// node.nodePadding = [120, 50, 50, 50];
// node['org.eclipse.elk.spacing.nodeNode'] = 120;
// Subgraph
if (parentLookupDb.childrenById[nodeId] !== undefined) {
node.labels = [
{
text: node.labelText,
layoutOptions: {
'nodeLabels.placement': '[H_CENTER, V_TOP, INSIDE]',
},
width: node.labelData.width,
height: node.labelData.height,
}, },
]; width: node.labelData.width,
delete node.x; height: node.labelData.height,
delete node.y; },
delete node.width; ];
delete node.height; delete node.x;
} delete node.y;
}); delete node.width;
insertChildren(graph.children, parentLookupDb); delete node.height;
elk.layout(graph).then(function (g) { }
drawNodes(0, 0, g.children, svg, subGraphsEl, diagObj, 0);
g.edges.map((edge, id) => {
insertEdge(edgesEl, edge, edge.edgeData, diagObj, parentLookupDb);
});
setupGraphViewbox({}, svg, conf.diagramPadding, conf.useMaxWidth);
resolve();
});
// Remove element after layout
renderEl.remove();
}); });
insertChildren(graph.children, parentLookupDb);
const g = await elk.layout(graph);
drawNodes(0, 0, g.children, svg, subGraphsEl, diagObj, 0);
g.edges?.map((edge) => {
insertEdge(edgesEl, edge, edge.edgeData, diagObj, parentLookupDb);
});
setupGraphViewbox({}, svg, conf.diagramPadding, conf.useMaxWidth);
// Remove element after layout
renderEl.remove();
}; };
const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj, depth) => { const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj, depth) => {

View File

@@ -1,4 +1,4 @@
import moment from 'moment-mini'; import moment from 'moment';
import { sanitizeUrl } from '@braintree/sanitize-url'; import { sanitizeUrl } from '@braintree/sanitize-url';
import { log } from '../../logger'; import { log } from '../../logger';
import * as configApi from '../../config'; import * as configApi from '../../config';

View File

@@ -1,5 +1,5 @@
// @ts-nocheck TODO: Fix TS // @ts-nocheck TODO: Fix TS
import moment from 'moment-mini'; import moment from 'moment';
import ganttDb from './ganttDb'; import ganttDb from './ganttDb';
import { convert } from '../../tests/util'; import { convert } from '../../tests/util';

View File

@@ -1,4 +1,4 @@
import moment from 'moment-mini'; import moment from 'moment';
import { log } from '../../logger'; import { log } from '../../logger';
import { import {
select, select,

View File

@@ -35,8 +35,9 @@
\%%(?!\{)[^\n]* /* skip comments */ \%%(?!\{)[^\n]* /* skip comments */
[^\}]\%\%[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */
[0-9]+(?=[ \n]+) return 'NUM'; [0-9]+(?=[ \n]+) return 'NUM';
"box" { this.begin('LINE'); return 'box'; }
"participant" { this.begin('ID'); return 'participant'; } "participant" { this.begin('ID'); return 'participant'; }
"actor" { this.begin('ID'); return 'participant_actor'; } "actor" { this.begin('ID'); return 'participant_actor'; }
<ID>[^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } <ID>[^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; }
<ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } <ALIAS>"as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; }
<ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; } <ALIAS>(?:) { this.popState(); this.popState(); return 'NEWLINE'; }
@@ -117,16 +118,30 @@ line
| NEWLINE { $$=[]; } | NEWLINE { $$=[]; }
; ;
box_section
: /* empty */ { $$ = [] }
| box_section box_line {$1.push($2);$$ = $1}
;
box_line
: SPACE participant_statement { $$ = $2 }
| participant_statement { $$ = $1 }
| NEWLINE { $$=[]; }
;
directive directive
: openDirective typeDirective closeDirective 'NEWLINE' : openDirective typeDirective closeDirective 'NEWLINE'
| openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE'
; ;
statement statement
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} : participant_statement
| 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;} | 'box' restOfLine box_section end
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;} {
| 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;} $3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) });
$3.push({type: 'boxEnd', boxText:$2});
$$=$3;}
| signal 'NEWLINE' | signal 'NEWLINE'
| autonumber NUM NUM 'NEWLINE' { $$= {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:Number($3), sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};} | autonumber NUM NUM 'NEWLINE' { $$= {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:Number($3), sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};}
| autonumber NUM 'NEWLINE' { $$ = {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:1, sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};} | autonumber NUM 'NEWLINE' { $$ = {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:1, sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};}
@@ -209,6 +224,13 @@ else_sections
{ $$ = $1.concat([{type: 'else', altText:yy.parseMessage($3), signalType: yy.LINETYPE.ALT_ELSE}, $4]); } { $$ = $1.concat([{type: 'else', altText:yy.parseMessage($3), signalType: yy.LINETYPE.ALT_ELSE}, $4]); }
; ;
participant_statement
: 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;}
| 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;}
| 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;}
;
note_statement note_statement
: 'note' placement actor text2 : 'note' placement actor text2
{ {

View File

@@ -14,20 +14,52 @@ import {
let prevActor = undefined; let prevActor = undefined;
let actors = {}; let actors = {};
let boxes = [];
let messages = []; let messages = [];
const notes = []; const notes = [];
let sequenceNumbersEnabled = false; let sequenceNumbersEnabled = false;
let wrapEnabled; let wrapEnabled;
let currentBox = undefined;
export const parseDirective = function (statement, context, type) { export const parseDirective = function (statement, context, type) {
mermaidAPI.parseDirective(this, statement, context, type); mermaidAPI.parseDirective(this, statement, context, type);
}; };
export const addBox = function (data) {
boxes.push({
name: data.text,
wrap: (data.wrap === undefined && autoWrap()) || !!data.wrap,
fill: data.color,
actorKeys: [],
});
currentBox = boxes.slice(-1)[0];
};
export const addActor = function (id, name, description, type) { export const addActor = function (id, name, description, type) {
// Don't allow description nulling let assignedBox = currentBox;
const old = actors[id]; const old = actors[id];
if (old && name === old.name && description == null) { if (old) {
return; // If already set and trying to set to a new one throw error
if (currentBox && old.box && currentBox !== old.box) {
throw new Error(
'A same participant should only be defined in one Box: ' +
old.name +
" can't be in '" +
old.box.name +
"' and in '" +
currentBox.name +
"' at the same time."
);
}
// Don't change the box if already
assignedBox = old.box ? old.box : currentBox;
old.box = assignedBox;
// Don't allow description nulling
if (old && name === old.name && description == null) {
return;
}
} }
// Don't allow null descriptions, either // Don't allow null descriptions, either
@@ -39,6 +71,7 @@ export const addActor = function (id, name, description, type) {
} }
actors[id] = { actors[id] = {
box: assignedBox,
name: name, name: name,
description: description.text, description: description.text,
wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap, wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap,
@@ -53,6 +86,9 @@ export const addActor = function (id, name, description, type) {
actors[prevActor].nextActor = id; actors[prevActor].nextActor = id;
} }
if (currentBox) {
currentBox.actorKeys.push(id);
}
prevActor = id; prevActor = id;
}; };
@@ -111,10 +147,21 @@ export const addSignal = function (
return true; return true;
}; };
export const hasAtLeastOneBox = function () {
return boxes.length > 0;
};
export const hasAtLeastOneBoxWithTitle = function () {
return boxes.some((b) => b.name);
};
export const getMessages = function () { export const getMessages = function () {
return messages; return messages;
}; };
export const getBoxes = function () {
return boxes;
};
export const getActors = function () { export const getActors = function () {
return actors; return actors;
}; };
@@ -147,6 +194,7 @@ export const autoWrap = () => {
export const clear = function () { export const clear = function () {
actors = {}; actors = {};
boxes = [];
messages = []; messages = [];
sequenceNumbersEnabled = false; sequenceNumbersEnabled = false;
commonClear(); commonClear();
@@ -167,6 +215,47 @@ export const parseMessage = function (str) {
return message; return message;
}; };
// We expect the box statement to be color first then description
// The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled
// We extract first segment as color, the rest of the line is considered as text
export const parseBoxData = function (str) {
const match = str.match(/^((?:rgba?|hsla?)\s*\(.*\)|\w*)(.*)$/);
let color = match != null && match[1] ? match[1].trim() : 'transparent';
let title = match != null && match[2] ? match[2].trim() : undefined;
// check that the string is a color
if (window && window.CSS) {
if (!window.CSS.supports('color', color)) {
color = 'transparent';
title = str.trim();
}
} else {
const style = new Option().style;
style.color = color;
if (style.color !== color) {
color = 'transparent';
title = str.trim();
}
}
const boxData = {
color: color,
text:
title !== undefined
? sanitizeText(title.replace(/^:?(?:no)?wrap:/, ''), configApi.getConfig())
: undefined,
wrap:
title !== undefined
? title.match(/^:?wrap:/) !== null
? true
: title.match(/^:?nowrap:/) !== null
? false
: undefined
: undefined,
};
return boxData;
};
export const LINETYPE = { export const LINETYPE = {
SOLID: 0, SOLID: 0,
DOTTED: 1, DOTTED: 1,
@@ -311,6 +400,13 @@ function insertProperties(actor, properties) {
} }
} }
/**
*
*/
function boxEnd() {
currentBox = undefined;
}
export const addDetails = function (actorId, text) { export const addDetails = function (actorId, text) {
// find the actor // find the actor
const actor = getActor(actorId); const actor = getActor(actorId);
@@ -391,6 +487,12 @@ export const apply = function (param) {
case 'addMessage': case 'addMessage':
addSignal(param.from, param.to, param.msg, param.signalType); addSignal(param.from, param.to, param.msg, param.signalType);
break; break;
case 'boxStart':
addBox(param.boxData);
break;
case 'boxEnd':
boxEnd();
break;
case 'loopStart': case 'loopStart':
addSignal(undefined, undefined, param.loopText, param.signalType); addSignal(undefined, undefined, param.loopText, param.signalType);
break; break;
@@ -467,12 +569,14 @@ export default {
getActorKeys, getActorKeys,
getActorProperty, getActorProperty,
getAccTitle, getAccTitle,
getBoxes,
getDiagramTitle, getDiagramTitle,
setDiagramTitle, setDiagramTitle,
parseDirective, parseDirective,
getConfig: () => configApi.getConfig().sequence, getConfig: () => configApi.getConfig().sequence,
clear, clear,
parseMessage, parseMessage,
parseBoxData,
LINETYPE, LINETYPE,
ARROWTYPE, ARROWTYPE,
PLACEMENT, PLACEMENT,
@@ -481,4 +585,6 @@ export default {
apply, apply,
setAccDescription, setAccDescription,
getAccDescription, getAccDescription,
hasAtLeastOneBox,
hasAtLeastOneBoxWithTitle,
}; };

View File

@@ -52,8 +52,16 @@ vi.mock('d3', () => {
curveBasis: 'basis', curveBasis: 'basis',
curveBasisClosed: 'basisClosed', curveBasisClosed: 'basisClosed',
curveBasisOpen: 'basisOpen', curveBasisOpen: 'basisOpen',
curveLinear: 'linear', curveBumpX: 'bumpX',
curveBumpY: 'bumpY',
curveBundle: 'bundle',
curveCardinalClosed: 'cardinalClosed',
curveCardinalOpen: 'cardinalOpen',
curveCardinal: 'cardinal', curveCardinal: 'cardinal',
curveCatmullRomClosed: 'catmullRomClosed',
curveCatmullRomOpen: 'catmullRomOpen',
curveCatmullRom: 'catmullRom',
curveLinear: 'linear',
curveLinearClosed: 'linearClosed', curveLinearClosed: 'linearClosed',
curveMonotoneX: 'monotoneX', curveMonotoneX: 'monotoneX',
curveMonotoneY: 'monotoneY', curveMonotoneY: 'monotoneY',
@@ -1299,8 +1307,76 @@ properties b: {"class": "external-service-actor", "icon": "@computer"}
expect(actors.b.properties['icon']).toBe('@computer'); expect(actors.b.properties['icon']).toBe('@computer');
expect(actors.c.properties['class']).toBe(undefined); expect(actors.c.properties['class']).toBe(undefined);
}); });
});
it('should handle box', function () {
const str = `
sequenceDiagram
box green Group 1
participant a as Alice
participant b as Bob
end
participant c as Charlie
links a: { "Repo": "https://repo.contoso.com/", "Dashboard": "https://dashboard.contoso.com/" }
links b: { "Dashboard": "https://dashboard.contoso.com/" }
links a: { "On-Call": "https://oncall.contoso.com/?svc=alice" }
link a: Endpoint @ https://alice.contoso.com
link a: Swagger @ https://swagger.contoso.com
link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com
`;
mermaidAPI.parse(str);
const boxes = diagram.db.getBoxes();
expect(boxes[0].name).toEqual('Group 1');
expect(boxes[0].actorKeys).toEqual(['a', 'b']);
expect(boxes[0].fill).toEqual('green');
});
it('should handle box without color', function () {
const str = `
sequenceDiagram
box Group 1
participant a as Alice
participant b as Bob
end
participant c as Charlie
links a: { "Repo": "https://repo.contoso.com/", "Dashboard": "https://dashboard.contoso.com/" }
links b: { "Dashboard": "https://dashboard.contoso.com/" }
links a: { "On-Call": "https://oncall.contoso.com/?svc=alice" }
link a: Endpoint @ https://alice.contoso.com
link a: Swagger @ https://swagger.contoso.com
link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com
`;
mermaidAPI.parse(str);
const boxes = diagram.db.getBoxes();
expect(boxes[0].name).toEqual('Group 1');
expect(boxes[0].actorKeys).toEqual(['a', 'b']);
expect(boxes[0].fill).toEqual('transparent');
});
it('should handle box without description', function () {
const str = `
sequenceDiagram
box Aqua
participant a as Alice
participant b as Bob
end
participant c as Charlie
links a: { "Repo": "https://repo.contoso.com/", "Dashboard": "https://dashboard.contoso.com/" }
links b: { "Dashboard": "https://dashboard.contoso.com/" }
links a: { "On-Call": "https://oncall.contoso.com/?svc=alice" }
link a: Endpoint @ https://alice.contoso.com
link a: Swagger @ https://swagger.contoso.com
link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com
`;
mermaidAPI.parse(str);
const boxes = diagram.db.getBoxes();
expect(boxes[0].name).toBeFalsy();
expect(boxes[0].actorKeys).toEqual(['a', 'b']);
expect(boxes[0].fill).toEqual('Aqua');
});
});
describe('when checking the bounds in a sequenceDiagram', function () { describe('when checking the bounds in a sequenceDiagram', function () {
beforeAll(() => { beforeAll(() => {
let conf = { let conf = {
@@ -1573,6 +1649,24 @@ Alice->Bob: Hello Bob, how are you?`;
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin);
expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10);
}); });
it('should handle two actors in a box', function () {
const str = `
sequenceDiagram
box rgb(34, 56, 0) Group1
participant Alice
participant Bob
end
Alice->Bob: Hello Bob, how are you?`;
mermaidAPI.parse(str);
diagram.renderer.draw(str, 'tst', '1.2.3', diagram);
const { bounds, models } = diagram.renderer.bounds.getBounds();
expect(bounds.startx).toBe(0);
expect(bounds.starty).toBe(0);
expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin + conf.boxTextMargin * 2);
expect(bounds.stopy).toBe(models.lastMessage().stopy + 20);
});
it('should handle two actors with init directive', function () { it('should handle two actors with init directive', function () {
const str = ` const str = `
%%{init: {'logLevel': 0}}%% %%{init: {'logLevel': 0}}%%

View File

@@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth';
import utils from '../../utils'; import utils from '../../utils';
import { configureSvgSize } from '../../setupGraphViewbox'; import { configureSvgSize } from '../../setupGraphViewbox';
import Diagram from '../../Diagram'; import Diagram from '../../Diagram';
import { convert } from '../../tests/util';
let conf = {}; let conf = {};
@@ -43,10 +44,14 @@ export const bounds = {
}, },
clear: function () { clear: function () {
this.actors = []; this.actors = [];
this.boxes = [];
this.loops = []; this.loops = [];
this.messages = []; this.messages = [];
this.notes = []; this.notes = [];
}, },
addBox: function (boxModel) {
this.boxes.push(boxModel);
},
addActor: function (actorModel) { addActor: function (actorModel) {
this.actors.push(actorModel); this.actors.push(actorModel);
}, },
@@ -72,6 +77,7 @@ export const bounds = {
return this.notes[this.notes.length - 1]; return this.notes[this.notes.length - 1];
}, },
actors: [], actors: [],
boxes: [],
loops: [], loops: [],
messages: [], messages: [],
notes: [], notes: [],
@@ -465,7 +471,8 @@ export const drawActors = function (
actorKeys, actorKeys,
verticalPos, verticalPos,
configuration, configuration,
messages messages,
isFooter
) { ) {
if (configuration.hideUnusedParticipants === true) { if (configuration.hideUnusedParticipants === true) {
const newActors = new Set(); const newActors = new Set();
@@ -480,8 +487,28 @@ export const drawActors = function (
let prevWidth = 0; let prevWidth = 0;
let prevMargin = 0; let prevMargin = 0;
let maxHeight = 0; let maxHeight = 0;
let prevBox = undefined;
for (const actorKey of actorKeys) { for (const actorKey of actorKeys) {
const actor = actors[actorKey]; const actor = actors[actorKey];
const box = actor.box;
// end of box
if (prevBox && prevBox != box) {
if (!isFooter) {
bounds.models.addBox(prevBox);
}
prevMargin += conf.boxMargin + prevBox.margin;
}
// new box
if (box && box != prevBox) {
if (!isFooter) {
box.x = prevWidth + prevMargin;
box.y = verticalPos;
}
prevMargin += box.margin;
}
// Add some rendering data to the object // Add some rendering data to the object
actor.width = actor.width || conf.width; actor.width = actor.width || conf.width;
@@ -489,18 +516,27 @@ export const drawActors = function (
actor.margin = actor.margin || conf.actorMargin; actor.margin = actor.margin || conf.actorMargin;
actor.x = prevWidth + prevMargin; actor.x = prevWidth + prevMargin;
actor.y = verticalPos; actor.y = bounds.getVerticalPos();
// Draw the box with the attached line // Draw the box with the attached line
const height = svgDraw.drawActor(diagram, actor, conf); const height = svgDraw.drawActor(diagram, actor, conf, isFooter);
maxHeight = Math.max(maxHeight, height); maxHeight = Math.max(maxHeight, height);
bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height); bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height);
prevWidth += actor.width; prevWidth += actor.width + prevMargin;
prevMargin += actor.margin; if (actor.box) {
actor.box.width = prevWidth + box.margin - actor.box.x;
}
prevMargin = actor.margin;
prevBox = actor.box;
bounds.models.addActor(actor); bounds.models.addActor(actor);
} }
// end of box
if (prevBox && !isFooter) {
bounds.models.addBox(prevBox);
}
// Add a margin between the actor boxes and the first arrow // Add a margin between the actor boxes and the first arrow
bounds.bumpVerticalPos(maxHeight); bounds.bumpVerticalPos(maxHeight);
}; };
@@ -614,18 +650,27 @@ export const draw = function (_text: string, id: string, _version: string, diagO
// Fetch data from the parsing // Fetch data from the parsing
const actors = diagObj.db.getActors(); const actors = diagObj.db.getActors();
const boxes = diagObj.db.getBoxes();
const actorKeys = diagObj.db.getActorKeys(); const actorKeys = diagObj.db.getActorKeys();
const messages = diagObj.db.getMessages(); const messages = diagObj.db.getMessages();
const title = diagObj.db.getDiagramTitle(); const title = diagObj.db.getDiagramTitle();
const hasBoxes = diagObj.db.hasAtLeastOneBox();
const hasBoxTitles = diagObj.db.hasAtLeastOneBoxWithTitle();
const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages, diagObj); const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages, diagObj);
conf.height = calculateActorMargins(actors, maxMessageWidthPerActor); conf.height = calculateActorMargins(actors, maxMessageWidthPerActor, boxes);
svgDraw.insertComputerIcon(diagram); svgDraw.insertComputerIcon(diagram);
svgDraw.insertDatabaseIcon(diagram); svgDraw.insertDatabaseIcon(diagram);
svgDraw.insertClockIcon(diagram); svgDraw.insertClockIcon(diagram);
drawActors(diagram, actors, actorKeys, 0, conf, messages); if (hasBoxes) {
bounds.bumpVerticalPos(conf.boxMargin);
if (hasBoxTitles) {
bounds.bumpVerticalPos(boxes[0].textMaxHeight);
}
}
drawActors(diagram, actors, actorKeys, 0, conf, messages, false);
const loopWidths = calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj); const loopWidths = calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj);
// The arrow head definition is attached to the svg once // The arrow head definition is attached to the svg once
@@ -847,11 +892,26 @@ export const draw = function (_text: string, id: string, _version: string, diagO
if (conf.mirrorActors) { if (conf.mirrorActors) {
// Draw actors below diagram // Draw actors below diagram
bounds.bumpVerticalPos(conf.boxMargin * 2); bounds.bumpVerticalPos(conf.boxMargin * 2);
drawActors(diagram, actors, actorKeys, bounds.getVerticalPos(), conf, messages); drawActors(diagram, actors, actorKeys, bounds.getVerticalPos(), conf, messages, true);
bounds.bumpVerticalPos(conf.boxMargin); bounds.bumpVerticalPos(conf.boxMargin);
fixLifeLineHeights(diagram, bounds.getVerticalPos()); fixLifeLineHeights(diagram, bounds.getVerticalPos());
} }
bounds.models.boxes.forEach(function (box) {
box.height = bounds.getVerticalPos() - box.y;
bounds.insert(box.x, box.y, box.x + box.width, box.height);
box.startx = box.x;
box.starty = box.y;
box.stopx = box.startx + box.width;
box.stopy = box.starty + box.height;
box.stroke = 'rgb(0,0,0, 0.5)';
svgDraw.drawBox(diagram, box, conf);
});
if (hasBoxes) {
bounds.bumpVerticalPos(conf.boxMargin);
}
// only draw popups for the top row of actors. // only draw popups for the top row of actors.
const requiredBoxSize = drawActorsPopup(diagram, actors, actorKeys, doc); const requiredBoxSize = drawActorsPopup(diagram, actors, actorKeys, doc);
@@ -1039,10 +1099,12 @@ const getRequiredPopupWidth = function (actor) {
* *
* @param actors - The actors map to calculate margins for * @param actors - The actors map to calculate margins for
* @param actorToMessageWidth - A map of actor key → max message width it holds * @param actorToMessageWidth - A map of actor key → max message width it holds
* @param boxes - The boxes around the actors if any
*/ */
function calculateActorMargins( function calculateActorMargins(
actors: { [id: string]: any }, actors: { [id: string]: any },
actorToMessageWidth: ReturnType<typeof getMaxMessageWidthPerActor> actorToMessageWidth: ReturnType<typeof getMaxMessageWidthPerActor>,
boxes
) { ) {
let maxHeight = 0; let maxHeight = 0;
Object.keys(actors).forEach((prop) => { Object.keys(actors).forEach((prop) => {
@@ -1074,6 +1136,9 @@ function calculateActorMargins(
// No need to space out an actor that doesn't have a next link // No need to space out an actor that doesn't have a next link
if (!nextActor) { if (!nextActor) {
const messageWidth = actorToMessageWidth[actorKey];
const actorWidth = messageWidth + conf.actorMargin - actor.width / 2;
actor.margin = Math.max(actorWidth, conf.actorMargin);
continue; continue;
} }
@@ -1083,6 +1148,29 @@ function calculateActorMargins(
actor.margin = Math.max(actorWidth, conf.actorMargin); actor.margin = Math.max(actorWidth, conf.actorMargin);
} }
let maxBoxHeight = 0;
boxes.forEach((box) => {
const textFont = messageFont(conf);
let totalWidth = box.actorKeys.reduce((total, aKey) => {
return (total += actors[aKey].width + (actors[aKey].margin || 0));
}, 0);
totalWidth -= 2 * conf.boxTextMargin;
if (box.wrap) {
box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont);
}
const boxMsgDimensions = utils.calculateTextDimensions(box.name, textFont);
maxBoxHeight = Math.max(boxMsgDimensions.height, maxBoxHeight);
const minWidth = Math.max(totalWidth, boxMsgDimensions.width + 2 * conf.wrapPadding);
box.margin = conf.boxTextMargin;
if (totalWidth < minWidth) {
const missing = (minWidth - totalWidth) / 2;
box.margin += missing;
}
});
boxes.forEach((box) => (box.textMaxHeight = maxBoxHeight));
return Math.max(maxHeight, conf.height); return Math.max(maxHeight, conf.height);
} }

View File

@@ -341,19 +341,21 @@ export const fixLifeLineHeights = (diagram, bounds) => {
* @param {any} elem - The diagram we'll draw to. * @param {any} elem - The diagram we'll draw to.
* @param {any} actor - The actor to draw. * @param {any} actor - The actor to draw.
* @param {any} conf - DrawText implementation discriminator object * @param {any} conf - DrawText implementation discriminator object
* @param {boolean} isFooter - If the actor is the footer one
*/ */
const drawActorTypeParticipant = function (elem, actor, conf) { const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
const center = actor.x + actor.width / 2; const center = actor.x + actor.width / 2;
const centerY = actor.y + 5;
const boxpluslineGroup = elem.append('g'); const boxpluslineGroup = elem.append('g');
var g = boxpluslineGroup; var g = boxpluslineGroup;
if (actor.y === 0) { if (!isFooter) {
actorCnt++; actorCnt++;
g.append('line') g.append('line')
.attr('id', 'actor' + actorCnt) .attr('id', 'actor' + actorCnt)
.attr('x1', center) .attr('x1', center)
.attr('y1', 5) .attr('y1', centerY)
.attr('x2', center) .attr('x2', center)
.attr('y2', 2000) .attr('y2', 2000)
.attr('class', 'actor-line') .attr('class', 'actor-line')
@@ -416,16 +418,17 @@ const drawActorTypeParticipant = function (elem, actor, conf) {
return height; return height;
}; };
const drawActorTypeActor = function (elem, actor, conf) { const drawActorTypeActor = function (elem, actor, conf, isFooter) {
const center = actor.x + actor.width / 2; const center = actor.x + actor.width / 2;
const centerY = actor.y + 80;
if (actor.y === 0) { if (!isFooter) {
actorCnt++; actorCnt++;
elem elem
.append('line') .append('line')
.attr('id', 'actor' + actorCnt) .attr('id', 'actor' + actorCnt)
.attr('x1', center) .attr('x1', center)
.attr('y1', 80) .attr('y1', centerY)
.attr('x2', center) .attr('x2', center)
.attr('y2', 2000) .attr('y2', 2000)
.attr('class', 'actor-line') .attr('class', 'actor-line')
@@ -498,15 +501,34 @@ const drawActorTypeActor = function (elem, actor, conf) {
return actor.height; return actor.height;
}; };
export const drawActor = function (elem, actor, conf) { export const drawActor = function (elem, actor, conf, isFooter) {
switch (actor.type) { switch (actor.type) {
case 'actor': case 'actor':
return drawActorTypeActor(elem, actor, conf); return drawActorTypeActor(elem, actor, conf, isFooter);
case 'participant': case 'participant':
return drawActorTypeParticipant(elem, actor, conf); return drawActorTypeParticipant(elem, actor, conf, isFooter);
} }
}; };
export const drawBox = function (elem, box, conf) {
const boxplustextGroup = elem.append('g');
const g = boxplustextGroup;
drawBackgroundRect(g, box);
if (box.name) {
_drawTextCandidateFunc(conf)(
box.name,
g,
box.x,
box.y + (box.textMaxHeight || 0) / 2,
box.width,
0,
{ class: 'text' },
conf
);
}
g.lower();
};
export const anchorElement = function (elem) { export const anchorElement = function (elem) {
return elem.append('g'); return elem.append('g');
}; };
@@ -645,6 +667,7 @@ export const drawBackgroundRect = function (elem, bounds) {
width: bounds.stopx - bounds.startx, width: bounds.stopx - bounds.startx,
height: bounds.stopy - bounds.starty, height: bounds.stopy - bounds.starty,
fill: bounds.fill, fill: bounds.fill,
stroke: bounds.stroke,
class: 'rect', class: 'rect',
}); });
rectElem.lower(); rectElem.lower();
@@ -1037,6 +1060,7 @@ export default {
drawText, drawText,
drawLabel, drawLabel,
drawActor, drawActor,
drawBox,
drawPopup, drawPopup,
drawImage, drawImage,
drawEmbeddedImage, drawEmbeddedImage,

View File

@@ -0,0 +1,133 @@
import stateDb from '../stateDb';
import stateDiagram from './stateDiagram';
import { setConfig } from '../../../config';
setConfig({
securityLevel: 'strict',
});
describe('state parser can parse...', () => {
beforeEach(function () {
stateDiagram.parser.yy = stateDb;
stateDiagram.parser.yy.clear();
});
describe('states with id displayed as a (name)', () => {
describe('syntax 1: stateID as "name in quotes"', () => {
it('stateID as "some name"', () => {
const diagramText = `stateDiagram-v2
state "Small State 1" as namedState1`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states['namedState1']).not.toBeUndefined();
expect(states['namedState1'].descriptions.join(' ')).toEqual('Small State 1');
});
});
describe('syntax 2: stateID: "name in quotes" [colon after the id]', () => {
it('space before and after the colon', () => {
const diagramText = `stateDiagram-v2
namedState1 : Small State 1`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states['namedState1']).not.toBeUndefined();
expect(states['namedState1'].descriptions.join(' ')).toEqual('Small State 1');
});
it('no spaces before and after the colon', () => {
const diagramText = `stateDiagram-v2
namedState1:Small State 1`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states['namedState1']).not.toBeUndefined();
expect(states['namedState1'].descriptions.join(' ')).toEqual('Small State 1');
});
});
});
describe('can handle "as" in a state name', () => {
it('assemble, assemblies, state assemble, state assemblies', function () {
const diagramText = `stateDiagram-v2
assemble
assemblies
state assemble
state assemblies
`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states['assemble']).not.toBeUndefined();
expect(states['assemblies']).not.toBeUndefined();
});
it('state "as" as as', function () {
const diagramText = `stateDiagram-v2
state "as" as as
`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states['as']).not.toBeUndefined();
expect(states['as'].descriptions.join(' ')).toEqual('as');
});
});
describe('groups (clusters/containers)', () => {
it('state "Group Name" as stateIdentifier', () => {
const diagramText = `stateDiagram-v2
state "Small State 1" as namedState1
%% Notice that this is named "Big State 1" with an "as"
state "Big State 1" as bigState1 {
bigState1InternalState
}
namedState1 --> bigState1: should point to \\nBig State 1 container
state "Small State 2" as namedState2
%% Notice that bigState2 does not have a name; no "as"
state bigState2 {
bigState2InternalState
}
namedState2 --> bigState2: should point to \\nbigState2 container`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states['namedState1']).not.toBeUndefined();
expect(states['bigState1']).not.toBeUndefined();
expect(states['bigState1'].doc[0].id).toEqual('bigState1InternalState');
expect(states['namedState2']).not.toBeUndefined();
expect(states['bigState2']).not.toBeUndefined();
expect(states['bigState2'].doc[0].id).toEqual('bigState2InternalState');
const relationships = stateDiagram.parser.yy.getRelations();
expect(relationships[0].id1).toEqual('namedState1');
expect(relationships[0].id2).toEqual('bigState1');
expect(relationships[1].id1).toEqual('namedState2');
expect(relationships[1].id2).toEqual('bigState2');
});
it('group has a state with stateID AS "state name" and state2ID: "another state name"', () => {
const diagramText = `stateDiagram-v2
state "Big State 1" as bigState1 {
state "inner state 1" as inner1
inner2: inner state 2
inner1 --> inner2
}`;
stateDiagram.parser.parse(diagramText);
stateDiagram.parser.yy.extract(stateDiagram.parser.yy.getRootDocV2());
const states = stateDiagram.parser.yy.getStates();
expect(states['bigState1']).not.toBeUndefined();
expect(states['bigState1'].doc[0].id).toEqual('inner1');
expect(states['bigState1'].doc[0].description).toEqual('inner state 1');
expect(states['bigState1'].doc[1].id).toEqual('inner2');
expect(states['bigState1'].doc[1].description).toEqual('inner state 2');
});
});
});

View File

@@ -65,14 +65,14 @@
\%%[^\n]* /* skip comments */ \%%[^\n]* /* skip comments */
"scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; } "scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; }
<SCALE>\d+ return 'WIDTH'; <SCALE>\d+ return 'WIDTH';
<SCALE>\s+"width" {this.popState();} <SCALE>\s+"width" { this.popState(); }
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } <acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } <acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; }
accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} accDescr\s*"{"\s* { this.begin("acc_descr_multiline"); }
<acc_descr_multiline>[\}] { this.popState(); } <acc_descr_multiline>[\}] { this.popState(); }
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value"; <acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
<INITIAL,struct>"classDef"\s+ { this.pushState('CLASSDEF'); return 'classDef'; } <INITIAL,struct>"classDef"\s+ { this.pushState('CLASSDEF'); return 'classDef'; }
@@ -81,57 +81,60 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili
<CLASSDEFID>[^\n]* { this.popState(); return 'CLASSDEF_STYLEOPTS' } <CLASSDEFID>[^\n]* { this.popState(); return 'CLASSDEF_STYLEOPTS' }
<INITIAL,struct>"class"\s+ { this.pushState('CLASS'); return 'class'; } <INITIAL,struct>"class"\s+ { this.pushState('CLASS'); return 'class'; }
<CLASS>(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' } <CLASS>(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' }
<CLASS_STYLE>[^\n]* { this.popState(); return 'STYLECLASS' } <CLASS_STYLE>[^\n]* { this.popState(); return 'STYLECLASS' }
"scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; } "scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; }
<SCALE>\d+ return 'WIDTH'; <SCALE>\d+ return 'WIDTH';
<SCALE>\s+"width" {this.popState();} <SCALE>\s+"width" {this.popState();}
<INITIAL,struct>"state"\s+ { /* console.log('Starting STATE '); */ this.pushState('STATE'); }
<INITIAL,struct>"state"\s+ { /*console.log('Starting STATE zxzx'+yy.getDirection());*/this.pushState('STATE'); }
<STATE>.*"<<fork>>" {this.popState();yytext=yytext.slice(0,-8).trim(); /*console.warn('Fork Fork: ',yytext);*/return 'FORK';} <STATE>.*"<<fork>>" {this.popState();yytext=yytext.slice(0,-8).trim(); /*console.warn('Fork Fork: ',yytext);*/return 'FORK';}
<STATE>.*"<<join>>" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';} <STATE>.*"<<join>>" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';}
<STATE>.*"<<choice>>" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';} <STATE>.*"<<choice>>" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';}
<STATE>.*"[[fork]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Fork: ',yytext);*/return 'FORK';} <STATE>.*"[[fork]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Fork: ',yytext);*/return 'FORK';}
<STATE>.*"[[join]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';} <STATE>.*"[[join]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';}
<STATE>.*"[[choice]]" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';} <STATE>.*"[[choice]]" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';}
<struct>.*direction\s+TB[^\n]* { return 'direction_tb';} <struct>.*direction\s+TB[^\n]* { return 'direction_tb';}
<struct>.*direction\s+BT[^\n]* { return 'direction_bt';} <struct>.*direction\s+BT[^\n]* { return 'direction_bt';}
<struct>.*direction\s+RL[^\n]* { return 'direction_rl';} <struct>.*direction\s+RL[^\n]* { return 'direction_rl';}
<struct>.*direction\s+LR[^\n]* { return 'direction_lr';} <struct>.*direction\s+LR[^\n]* { return 'direction_lr';}
<STATE>["] { /*console.log('Starting STATE_STRING zxzx');*/this.begin("STATE_STRING");} <STATE>["] { /* console.log('Starting STATE_STRING'); */ this.pushState("STATE_STRING"); }
<STATE>\s*"as"\s+ {this.popState();this.pushState('STATE_ID');return "AS";} <STATE>\s*"as"\s+ { this.pushState('STATE_ID'); /* console.log('pushState(STATE_ID)'); */ return "AS"; }
<STATE_ID>[^\n\{]* {this.popState();/* console.log('STATE_ID', yytext);*/return "ID";} <STATE_ID>[^\n\{]* { this.popState(); /* console.log('STATE_ID', yytext); */ return "ID"; }
<STATE_STRING>["] this.popState(); <STATE_STRING>["] { this.popState(); }
<STATE_STRING>[^"]* { /*console.log('Long description:', yytext);*/return "STATE_DESCR";} <STATE_STRING>[^"]* { /* console.log('Long description:', yytext); */ return "STATE_DESCR"; }
<STATE>[^\n\s\{]+ {/*console.log('COMPOSIT_STATE', yytext);*/return 'COMPOSIT_STATE';} <STATE>[^\n\s\{]+ { /* console.log('COMPOSIT_STATE', yytext); */ return 'COMPOSIT_STATE'; }
<STATE>\n {this.popState();} <STATE>\n { this.popState(); }
<INITIAL,STATE>\{ {this.popState();this.pushState('struct'); /*console.log('begin struct', yytext);*/return 'STRUCT_START';} <INITIAL,STATE>\{ { this.popState(); this.pushState('struct'); /* console.log('begin struct', yytext); */ return 'STRUCT_START'; }
<struct>\%\%(?!\{)[^\n]* /* skip comments inside state*/ <struct>\%\%(?!\{)[^\n]* /* skip comments inside state*/
<struct>\} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';}} <struct>\} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';} }
<struct>[\n] /* nothing */ <struct>[\n] /* nothing */
<INITIAL,struct>"note"\s+ { this.begin('NOTE'); return 'note'; } <INITIAL,struct>"note"\s+ { this.begin('NOTE'); return 'note'; }
<NOTE>"left of" { this.popState();this.pushState('NOTE_ID');return 'left_of';} <NOTE>"left of" { this.popState(); this.pushState('NOTE_ID'); return 'left_of'; }
<NOTE>"right of" { this.popState();this.pushState('NOTE_ID');return 'right_of';} <NOTE>"right of" { this.popState(); this.pushState('NOTE_ID'); return 'right_of'; }
<NOTE>\" { this.popState();this.pushState('FLOATING_NOTE');} <NOTE>\" { this.popState(); this.pushState('FLOATING_NOTE'); }
<FLOATING_NOTE>\s*"as"\s* {this.popState();this.pushState('FLOATING_NOTE_ID');return "AS";} <FLOATING_NOTE>\s*"as"\s* { this.popState(); this.pushState('FLOATING_NOTE_ID'); return "AS"; }
<FLOATING_NOTE>["] /**/ <FLOATING_NOTE>["] /**/
<FLOATING_NOTE>[^"]* { /*console.log('Floating note text: ', yytext);*/return "NOTE_TEXT";} <FLOATING_NOTE>[^"]* { /* console.log('Floating note text: ', yytext); */ return "NOTE_TEXT"; }
<FLOATING_NOTE_ID>[^\n]* {this.popState();/*console.log('Floating note ID', yytext);*/return "ID";} <FLOATING_NOTE_ID>[^\n]* { this.popState(); /* console.log('Floating note ID', yytext);*/ return "ID"; }
<NOTE_ID>\s*[^:\n\s\-]+ { this.popState();this.pushState('NOTE_TEXT');/*console.log('Got ID for note', yytext);*/return 'ID';} <NOTE_ID>\s*[^:\n\s\-]+ { this.popState(); this.pushState('NOTE_TEXT'); /*console.log('Got ID for note', yytext);*/ return 'ID'; }
<NOTE_TEXT>\s*":"[^:\n;]+ { this.popState();/*console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.substr(2).trim();return 'NOTE_TEXT';} <NOTE_TEXT>\s*":"[^:\n;]+ { this.popState(); /* console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.substr(2).trim(); return 'NOTE_TEXT'; }
<NOTE_TEXT>[\s\S]*?"end note" { this.popState();/*console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.slice(0,-8).trim();return 'NOTE_TEXT';} <NOTE_TEXT>[\s\S]*?"end note" { this.popState(); /* console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.slice(0,-8).trim(); return 'NOTE_TEXT'; }
"stateDiagram"\s+ { /*console.log('Got state diagram', yytext,'#');*/return 'SD'; } "stateDiagram"\s+ { /* console.log('Got state diagram', yytext,'#'); */ return 'SD'; }
"stateDiagram-v2"\s+ { /*console.log('Got state diagram', yytext,'#');*/return 'SD'; } "stateDiagram-v2"\s+ { /* console.log('Got state diagram', yytext,'#'); */ return 'SD'; }
"hide empty description" { /*console.log('HIDE_EMPTY', yytext,'#');*/return 'HIDE_EMPTY'; }
<INITIAL,struct>"[*]" { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';} "hide empty description" { /* console.log('HIDE_EMPTY', yytext,'#'); */ return 'HIDE_EMPTY'; }
<INITIAL,struct>[^:\n\s\-\{]+ { /*console.log('=>ID=',yytext);*/ return 'ID';}
// <INITIAL,struct>\s*":"[^\+\->:\n;]+ { yytext = yytext.trim(); /*console.log('Descr = ', yytext);*/ return 'DESCR'; } <INITIAL,struct>"[*]" { /* console.log('EDGE_STATE=',yytext); */ return 'EDGE_STATE'; }
<INITIAL,struct>\s*":"[^:\n;]+ { yytext = yytext.trim(); /*console.log('Descr = ', yytext);*/ return 'DESCR'; } <INITIAL,struct>[^:\n\s\-\{]+ { /* console.log('=>ID=',yytext); */ return 'ID'; }
// <INITIAL,struct>\s*":"[^\+\->:\n;]+ { yytext = yytext.trim(); /* console.log('Descr = ', yytext); */ return 'DESCR'; }
<INITIAL,struct>\s*":"[^:\n;]+ { yytext = yytext.trim(); /* console.log('Descr = ', yytext); */ return 'DESCR'; }
<INITIAL,struct>"-->" return '-->'; <INITIAL,struct>"-->" return '-->';
<struct>"--" return 'CONCURRENT'; <struct>"--" return 'CONCURRENT';
@@ -201,7 +204,7 @@ statement
| COMPOSIT_STATE | COMPOSIT_STATE
| COMPOSIT_STATE STRUCT_START document STRUCT_STOP | COMPOSIT_STATE STRUCT_START document STRUCT_STOP
{ {
/* console.log('Adding document for state without id ', $1); */ // console.log('Adding document for state without id ', $1);
$$={ stmt: 'state', id: $1, type: 'default', description: '', doc: $3 } $$={ stmt: 'state', id: $1, type: 'default', description: '', doc: $3 }
} }
| STATE_DESCR AS ID { | STATE_DESCR AS ID {
@@ -217,7 +220,7 @@ statement
} }
| STATE_DESCR AS ID STRUCT_START document STRUCT_STOP | STATE_DESCR AS ID STRUCT_START document STRUCT_STOP
{ {
/* console.log('Adding document for state with id zxzx', $3, $4, yy.getDirection()); yy.addDocument($3);*/ // console.log('state with id ', $3,' document = ', $5, );
$$={ stmt: 'state', id: $3, type: 'default', description: $1, doc: $5 } $$={ stmt: 'state', id: $3, type: 'default', description: $1, doc: $5 }
} }
| FORK { | FORK {

View File

@@ -94,9 +94,14 @@ const docTranslator = (parent, node, first) => {
docTranslator(parent, node.state1, true); docTranslator(parent, node.state1, true);
docTranslator(parent, node.state2, false); docTranslator(parent, node.state2, false);
} else { } else {
if (node.stmt === STMT_STATE && node.id === '[*]') { if (node.stmt === STMT_STATE) {
node.id = first ? parent.id + '_start' : parent.id + '_end'; if (node.id === '[*]') {
node.start = first; node.id = first ? parent.id + '_start' : parent.id + '_end';
node.start = first;
} else {
// This is just a plain state, not a start or end
node.id = node.id.trim();
}
} }
if (node.doc) { if (node.doc) {
@@ -170,7 +175,7 @@ const extract = (_doc) => {
switch (item.stmt) { switch (item.stmt) {
case STMT_STATE: case STMT_STATE:
addState( addState(
item.id, item.id.trim(),
item.type, item.type,
item.doc, item.doc,
item.description, item.description,
@@ -184,10 +189,10 @@ const extract = (_doc) => {
addRelation(item.state1, item.state2, item.description); addRelation(item.state1, item.state2, item.description);
break; break;
case STMT_CLASSDEF: case STMT_CLASSDEF:
addStyleClass(item.id, item.classes); addStyleClass(item.id.trim(), item.classes);
break; break;
case STMT_APPLYCLASS: case STMT_APPLYCLASS:
setCssClass(item.id, item.styleClass); setCssClass(item.id.trim(), item.styleClass);
break; break;
} }
}); });
@@ -215,11 +220,12 @@ export const addState = function (
styles = null, styles = null,
textStyles = null textStyles = null
) { ) {
const trimmedId = id?.trim();
// add the state if needed // add the state if needed
if (currentDocument.states[id] === undefined) { if (currentDocument.states[trimmedId] === undefined) {
log.info('Adding state ', id, descr); log.info('Adding state ', trimmedId, descr);
currentDocument.states[id] = { currentDocument.states[trimmedId] = {
id: id, id: trimmedId,
descriptions: [], descriptions: [],
type, type,
doc, doc,
@@ -229,49 +235,49 @@ export const addState = function (
textStyles: [], textStyles: [],
}; };
} else { } else {
if (!currentDocument.states[id].doc) { if (!currentDocument.states[trimmedId].doc) {
currentDocument.states[id].doc = doc; currentDocument.states[trimmedId].doc = doc;
} }
if (!currentDocument.states[id].type) { if (!currentDocument.states[trimmedId].type) {
currentDocument.states[id].type = type; currentDocument.states[trimmedId].type = type;
} }
} }
if (descr) { if (descr) {
log.info('Setting state description', id, descr); log.info('Setting state description', trimmedId, descr);
if (typeof descr === 'string') { if (typeof descr === 'string') {
addDescription(id, descr.trim()); addDescription(trimmedId, descr.trim());
} }
if (typeof descr === 'object') { if (typeof descr === 'object') {
descr.forEach((des) => addDescription(id, des.trim())); descr.forEach((des) => addDescription(trimmedId, des.trim()));
} }
} }
if (note) { if (note) {
currentDocument.states[id].note = note; currentDocument.states[trimmedId].note = note;
currentDocument.states[id].note.text = common.sanitizeText( currentDocument.states[trimmedId].note.text = common.sanitizeText(
currentDocument.states[id].note.text, currentDocument.states[trimmedId].note.text,
configApi.getConfig() configApi.getConfig()
); );
} }
if (classes) { if (classes) {
log.info('Setting state classes', id, classes); log.info('Setting state classes', trimmedId, classes);
const classesList = typeof classes === 'string' ? [classes] : classes; const classesList = typeof classes === 'string' ? [classes] : classes;
classesList.forEach((klass) => setCssClass(id, klass.trim())); classesList.forEach((klass) => setCssClass(trimmedId, klass.trim()));
} }
if (styles) { if (styles) {
log.info('Setting state styles', id, styles); log.info('Setting state styles', trimmedId, styles);
const stylesList = typeof styles === 'string' ? [styles] : styles; const stylesList = typeof styles === 'string' ? [styles] : styles;
stylesList.forEach((style) => setStyle(id, style.trim())); stylesList.forEach((style) => setStyle(trimmedId, style.trim()));
} }
if (textStyles) { if (textStyles) {
log.info('Setting state styles', id, styles); log.info('Setting state styles', trimmedId, styles);
const textStylesList = typeof textStyles === 'string' ? [textStyles] : textStyles; const textStylesList = typeof textStyles === 'string' ? [textStyles] : textStyles;
textStylesList.forEach((textStyle) => setTextStyle(id, textStyle.trim())); textStylesList.forEach((textStyle) => setTextStyle(trimmedId, textStyle.trim()));
} }
}; };
@@ -368,10 +374,10 @@ function endTypeIfNeeded(id = '', type = DEFAULT_STATE_TYPE) {
* @param relationTitle * @param relationTitle
*/ */
export function addRelationObjs(item1, item2, relationTitle) { export function addRelationObjs(item1, item2, relationTitle) {
let id1 = startIdIfNeeded(item1.id); let id1 = startIdIfNeeded(item1.id.trim());
let type1 = startTypeIfNeeded(item1.id, item1.type); let type1 = startTypeIfNeeded(item1.id.trim(), item1.type);
let id2 = startIdIfNeeded(item2.id); let id2 = startIdIfNeeded(item2.id.trim());
let type2 = startTypeIfNeeded(item2.id, item2.type); let type2 = startTypeIfNeeded(item2.id.trim(), item2.type);
addState( addState(
id1, id1,
@@ -412,9 +418,9 @@ export const addRelation = function (item1, item2, title) {
if (typeof item1 === 'object') { if (typeof item1 === 'object') {
addRelationObjs(item1, item2, title); addRelationObjs(item1, item2, title);
} else { } else {
const id1 = startIdIfNeeded(item1); const id1 = startIdIfNeeded(item1.trim());
const type1 = startTypeIfNeeded(item1); const type1 = startTypeIfNeeded(item1);
const id2 = endIdIfNeeded(item2); const id2 = endIdIfNeeded(item2.trim());
const type2 = endTypeIfNeeded(item2); const type2 = endTypeIfNeeded(item2);
addState(id1, type1); addState(id1, type1);

View File

@@ -307,8 +307,8 @@ const setupNode = (g, parent, parsedItem, diagramStates, diagramDb, altFlag) =>
* *
* @param g * @param g
* @param parentParsedItem - parsed Item that is the parent of this document (doc) * @param parentParsedItem - parsed Item that is the parent of this document (doc)
* @param doc - the document to set up * @param doc - the document to set up; it is a list of parsed statements
* @param {object} diagramStates - the list of all known states for the diagram * @param {object[]} diagramStates - the list of all known states for the diagram
* @param diagramDb * @param diagramDb
* @param {boolean} altFlag * @param {boolean} altFlag
* @todo This duplicates some of what is done in stateDb.js extract method * @todo This duplicates some of what is done in stateDb.js extract method

View File

@@ -37,16 +37,14 @@ import { JSDOM } from 'jsdom';
import type { Code, Root } from 'mdast'; import type { Code, Root } from 'mdast';
import { posix, dirname, relative, join } from 'path'; import { posix, dirname, relative, join } from 'path';
import prettier from 'prettier'; import prettier from 'prettier';
import { remark as remarkBuilder } from 'remark'; import { remark } from 'remark';
import remarkFrontmatter from 'remark-frontmatter';
import remarkGfm from 'remark-gfm'; import remarkGfm from 'remark-gfm';
import chokidar from 'chokidar'; import chokidar from 'chokidar';
import mm from 'micromatch'; import mm from 'micromatch';
// @ts-ignore No typescript declaration file // @ts-ignore No typescript declaration file
import flatmap from 'unist-util-flatmap'; import flatmap from 'unist-util-flatmap';
// support tables and other GitHub Flavored Markdown syntax in markdown
const remark = remarkBuilder().use(remarkGfm);
const MERMAID_MAJOR_VERSION = ( const MERMAID_MAJOR_VERSION = (
JSON.parse(readFileSync('../mermaid/package.json', 'utf8')).version as string JSON.parse(readFileSync('../mermaid/package.json', 'utf8')).version as string
).split('.')[0]; ).split('.')[0];
@@ -90,7 +88,7 @@ const filesTransformed: Set<string> = new Set();
const generateHeader = (file: string): string => { const generateHeader = (file: string): string => {
// path from file in docs/* to repo root, e.g ../ or ../../ */ // path from file in docs/* to repo root, e.g ../ or ../../ */
const relativePath = relative(file, SOURCE_DOCS_DIR); const relativePath = relative(file, SOURCE_DOCS_DIR).replaceAll('\\', '/');
const filePathFromRoot = posix.join('/packages/mermaid', file); const filePathFromRoot = posix.join('/packages/mermaid', file);
const sourcePathRelativeToGenerated = posix.join(relativePath, filePathFromRoot); const sourcePathRelativeToGenerated = posix.join(relativePath, filePathFromRoot);
return ` return `
@@ -191,7 +189,7 @@ const transformIncludeStatements = (file: string, text: string): string => {
// resolve includes - src https://github.com/vuejs/vitepress/blob/428eec3750d6b5648a77ac52d88128df0554d4d1/src/node/markdownToVue.ts#L65-L76 // resolve includes - src https://github.com/vuejs/vitepress/blob/428eec3750d6b5648a77ac52d88128df0554d4d1/src/node/markdownToVue.ts#L65-L76
return text.replace(includesRE, (m, m1) => { return text.replace(includesRE, (m, m1) => {
try { try {
const includePath = join(dirname(file), m1); const includePath = join(dirname(file), m1).replaceAll('\\', '/');
const content = readSyncedUTF8file(includePath); const content = readSyncedUTF8file(includePath);
includedFiles.add(changeToFinalDocDir(includePath)); includedFiles.add(changeToFinalDocDir(includePath));
return content; return content;
@@ -201,48 +199,86 @@ const transformIncludeStatements = (file: string, text: string): string => {
}); });
}; };
/** Options for {@link transformMarkdownAst} */
interface TransformMarkdownAstOptions {
/**
* Used to indicate the original/source file.
*/
originalFilename: string;
/** If `true`, add a warning that the file is autogenerated */
addAutogeneratedWarning?: boolean;
/**
* If `true`, remove the YAML metadata from the Markdown input.
* Generally, YAML metadata is only used for Vitepress.
*/
removeYAML?: boolean;
}
/** /**
* Transform code blocks in a Markdown file. * Remark plugin that transforms mermaid repo markdown to Vitepress/GFM markdown.
* Use remark.parse() to turn the given content (a String) into an AST. *
* For any AST node that is a code block: transform it as needed: * For any AST node that is a code block: transform it as needed:
* - blocks marked as MERMAID_DIAGRAM_ONLY will be set to a 'mermaid' code block so it will be rendered as (only) a diagram * - blocks marked as MERMAID_DIAGRAM_ONLY will be set to a 'mermaid' code block so it will be rendered as (only) a diagram
* - blocks marked as MERMAID_EXAMPLE_KEYWORDS will be copied and the original node will be a code only block and the copy with be rendered as the diagram * - blocks marked as MERMAID_EXAMPLE_KEYWORDS will be copied and the original node will be a code only block and the copy with be rendered as the diagram
* - blocks marked as BLOCK_QUOTE_KEYWORDS will be transformed into block quotes * - blocks marked as BLOCK_QUOTE_KEYWORDS will be transformed into block quotes
* *
* Convert the AST back to a string and return it. * If `addAutogeneratedWarning` is `true`, generates a header stating that this file is autogenerated.
* *
* @param content - the contents of a Markdown file * @returns plugin function for Remark
* @returns the contents with transformed code blocks
*/ */
export const transformBlocks = (content: string): string => { export function transformMarkdownAst({
const ast: Root = remark.parse(content); originalFilename,
const astWithTransformedBlocks = flatmap(ast, (node: Code) => { addAutogeneratedWarning,
if (node.type !== 'code' || !node.lang) { removeYAML,
return [node]; // no transformation if this is not a code block }: TransformMarkdownAstOptions) {
return (tree: Root, _file?: any): Root => {
const astWithTransformedBlocks = flatmap(tree, (node: Code) => {
if (node.type !== 'code' || !node.lang) {
return [node]; // no transformation if this is not a code block
}
if (node.lang === MERMAID_DIAGRAM_ONLY) {
// Set the lang to 'mermaid' so it will be rendered as a diagram.
node.lang = MERMAID_KEYWORD;
return [node];
} else if (MERMAID_EXAMPLE_KEYWORDS.includes(node.lang)) {
// Return 2 nodes:
// 1. the original node with the language now set to 'mermaid-example' (will be rendered as code), and
// 2. a copy of the original node with the language set to 'mermaid' (will be rendered as a diagram)
node.lang = MERMAID_CODE_ONLY_KEYWORD;
return [node, Object.assign({}, node, { lang: MERMAID_KEYWORD })];
}
// Transform these blocks into block quotes.
if (BLOCK_QUOTE_KEYWORDS.includes(node.lang)) {
return [remark.parse(transformToBlockQuote(node.value, node.lang, node.meta))];
}
return [node]; // default is to do nothing to the node
}) as Root;
if (addAutogeneratedWarning) {
// Add the header to the start of the file
const headerNode = remark.parse(generateHeader(originalFilename)).children[0];
if (astWithTransformedBlocks.children[0].type === 'yaml') {
// insert header after the YAML frontmatter if it exists
astWithTransformedBlocks.children.splice(1, 0, headerNode);
} else {
astWithTransformedBlocks.children.unshift(headerNode);
}
} }
if (node.lang === MERMAID_DIAGRAM_ONLY) { if (removeYAML) {
// Set the lang to 'mermaid' so it will be rendered as a diagram. const firstNode = astWithTransformedBlocks.children[0];
node.lang = MERMAID_KEYWORD; if (firstNode.type == 'yaml') {
return [node]; // YAML is currently only used for Vitepress metadata, so we should remove it for GFM output
} else if (MERMAID_EXAMPLE_KEYWORDS.includes(node.lang)) { astWithTransformedBlocks.children.shift();
// Return 2 nodes: }
// 1. the original node with the language now set to 'mermaid-example' (will be rendered as code), and
// 2. a copy of the original node with the language set to 'mermaid' (will be rendered as a diagram)
node.lang = MERMAID_CODE_ONLY_KEYWORD;
return [node, Object.assign({}, node, { lang: MERMAID_KEYWORD })];
} }
// Transform these blocks into block quotes. return astWithTransformedBlocks;
if (BLOCK_QUOTE_KEYWORDS.includes(node.lang)) { };
return [remark.parse(transformToBlockQuote(node.value, node.lang, node.meta))]; }
}
return [node]; // default is to do nothing to the node
});
return remark.stringify(astWithTransformedBlocks);
};
/** /**
* Transform a markdown file and write the transformed file to the directory for published * Transform a markdown file and write the transformed file to the directory for published
@@ -260,11 +296,18 @@ export const transformBlocks = (content: string): string => {
*/ */
const transformMarkdown = (file: string) => { const transformMarkdown = (file: string) => {
const doc = injectPlaceholders(transformIncludeStatements(file, readSyncedUTF8file(file))); const doc = injectPlaceholders(transformIncludeStatements(file, readSyncedUTF8file(file)));
let transformed = transformBlocks(doc);
if (!noHeader) { let transformed = remark()
// Add the header to the start of the file .use(remarkGfm)
transformed = `${generateHeader(file)}\n${transformed}`; .use(remarkFrontmatter, ['yaml']) // support YAML front-matter in Markdown
} .use(transformMarkdownAst, {
// mermaid project specific plugin
originalFilename: file,
addAutogeneratedWarning: !noHeader,
removeYAML: !noHeader,
})
.processSync(doc)
.toString();
if (vitepress && file === 'src/docs/index.md') { if (vitepress && file === 'src/docs/index.md') {
// Skip transforming index if vitepress is enabled // Skip transforming index if vitepress is enabled
@@ -331,7 +374,7 @@ const getFilesFromGlobs = async (globs: string[]): Promise<string[]> => {
}; };
/** Main method (entry point) */ /** Main method (entry point) */
(async () => { const main = async () => {
if (verifyOnly) { if (verifyOnly) {
console.log('Verifying that all files are in sync with the source files'); console.log('Verifying that all files are in sync with the source files');
} }
@@ -400,4 +443,6 @@ const getFilesFromGlobs = async (globs: string[]): Promise<string[]> => {
} }
}); });
} }
})(); };
void main();

View File

@@ -1,40 +1,24 @@
import { transformBlocks, transformToBlockQuote } from './docs.mjs'; import { transformMarkdownAst, transformToBlockQuote } from './docs.mjs';
import { remark as remarkBuilder } from 'remark'; // import it this way so we can mock it
import { remark } from 'remark'; // import it this way so we can mock it
import remarkFrontmatter from 'remark-frontmatter';
import { vi, afterEach, describe, it, expect } from 'vitest'; import { vi, afterEach, describe, it, expect } from 'vitest';
const remark = remarkBuilder();
vi.mock('remark', async (importOriginal) => {
const { remark: originalRemarkBuilder } = (await importOriginal()) as {
remark: typeof remarkBuilder;
};
// make sure that both `docs.mts` and this test file are using the same remark
// object so that we can mock it
const sharedRemark = originalRemarkBuilder();
return {
remark: () => sharedRemark,
};
});
afterEach(() => { afterEach(() => {
vi.restoreAllMocks(); vi.restoreAllMocks();
}); });
const originalFilename = 'example-input-filename.md';
const remarkBuilder = remark().use(remarkFrontmatter, ['yaml']); // support YAML front-matter in Markdown
describe('docs.mts', () => { describe('docs.mts', () => {
describe('transformBlocks', () => { describe('transformMarkdownAst', () => {
it('uses remark.parse to create the AST for the file ', () => {
const remarkParseSpy = vi
.spyOn(remark, 'parse')
.mockReturnValue({ type: 'root', children: [] });
const contents = 'Markdown file contents';
transformBlocks(contents);
expect(remarkParseSpy).toHaveBeenCalledWith(contents);
});
describe('checks each AST node', () => { describe('checks each AST node', () => {
it('does no transformation if there are no code blocks', async () => { it('does no transformation if there are no code blocks', async () => {
const contents = 'Markdown file contents\n'; const contents = 'Markdown file contents\n';
const result = transformBlocks(contents); const result = (
await remarkBuilder().use(transformMarkdownAst, { originalFilename }).process(contents)
).toString();
expect(result).toEqual(contents); expect(result).toEqual(contents);
}); });
@@ -46,8 +30,12 @@ describe('docs.mts', () => {
const lang_keyword = 'mermaid-nocode'; const lang_keyword = 'mermaid-nocode';
const contents = beforeCodeLine + '```' + lang_keyword + '\n' + diagram_text + '\n```\n'; const contents = beforeCodeLine + '```' + lang_keyword + '\n' + diagram_text + '\n```\n';
it('changes the language to "mermaid"', () => { it('changes the language to "mermaid"', async () => {
const result = transformBlocks(contents); const result = (
await remarkBuilder()
.use(transformMarkdownAst, { originalFilename })
.process(contents)
).toString();
expect(result).toEqual( expect(result).toEqual(
beforeCodeLine + '\n' + '```' + 'mermaid' + '\n' + diagram_text + '\n```\n' beforeCodeLine + '\n' + '```' + 'mermaid' + '\n' + diagram_text + '\n```\n'
); );
@@ -61,8 +49,12 @@ describe('docs.mts', () => {
const contents = const contents =
beforeCodeLine + '```' + lang_keyword + '\n' + diagram_text + '\n```\n'; beforeCodeLine + '```' + lang_keyword + '\n' + diagram_text + '\n```\n';
it('changes the language to "mermaid-example" and adds a copy of the code block with language = "mermaid"', () => { it('changes the language to "mermaid-example" and adds a copy of the code block with language = "mermaid"', async () => {
const result = transformBlocks(contents); const result = (
await remarkBuilder()
.use(transformMarkdownAst, { originalFilename })
.process(contents)
).toString();
expect(result).toEqual( expect(result).toEqual(
beforeCodeLine + beforeCodeLine +
'\n' + '\n' +
@@ -77,16 +69,40 @@ describe('docs.mts', () => {
}); });
}); });
it('calls transformToBlockQuote with the node information', () => { it('calls transformToBlockQuote with the node information', async () => {
const lang_keyword = 'note'; const lang_keyword = 'note';
const contents = const contents =
beforeCodeLine + '```' + lang_keyword + '\n' + 'This is the text\n' + '```\n'; beforeCodeLine + '```' + lang_keyword + '\n' + 'This is the text\n' + '```\n';
const result = transformBlocks(contents); const result = (
await remarkBuilder().use(transformMarkdownAst, { originalFilename }).process(contents)
).toString();
expect(result).toEqual(beforeCodeLine + '\n> **Note**\n' + '> This is the text\n'); expect(result).toEqual(beforeCodeLine + '\n> **Note**\n' + '> This is the text\n');
}); });
}); });
}); });
it('should remove YAML if `removeYAML` is true', async () => {
const contents = `---
title: Flowcharts Syntax
---
This Markdown should be kept.
`;
const withYaml = (
await remarkBuilder().use(transformMarkdownAst, { originalFilename }).process(contents)
).toString();
// no change
expect(withYaml).toEqual(contents);
const withoutYaml = (
await remarkBuilder()
.use(transformMarkdownAst, { originalFilename, removeYAML: true })
.process(contents)
).toString();
// no change
expect(withoutYaml).toEqual('This Markdown should be kept.\n');
});
}); });
describe('transformToBlockQuote', () => { describe('transformToBlockQuote', () => {

View File

@@ -77,8 +77,8 @@ function sidebarAll() {
], ],
}, },
...sidebarSyntax(), ...sidebarSyntax(),
...sidebarEcosystem(),
...sidebarConfig(), ...sidebarConfig(),
...sidebarMisc(),
...sidebarCommunity(), ...sidebarCommunity(),
]; ];
} }
@@ -126,19 +126,20 @@ function sidebarConfig() {
{ text: 'Accessibility', link: '/config/accessibility' }, { text: 'Accessibility', link: '/config/accessibility' },
{ text: 'Mermaid CLI', link: '/config/mermaidCLI' }, { text: 'Mermaid CLI', link: '/config/mermaidCLI' },
{ text: 'Advanced usage', link: '/config/n00b-advanced' }, { text: 'Advanced usage', link: '/config/n00b-advanced' },
{ text: 'FAQ', link: '/config/faq' },
], ],
}, },
]; ];
} }
function sidebarMisc() { function sidebarEcosystem() {
return [ return [
{ {
text: '📚 Misc', text: '📚 Ecosystem',
collapsible: true, collapsible: true,
items: [ items: [
{ text: 'Use-Cases and Integrations', link: '/misc/integrations' }, { text: 'Showcases', link: '/ecosystem/showcases' },
{ text: 'FAQ', link: '/misc/faq' }, { text: 'Use-Cases and Integrations', link: '/ecosystem/integrations' },
], ],
}, },
]; ];

View File

@@ -10,9 +10,6 @@ export default {
// register global components // register global components
app.component('Mermaid', Mermaid); app.component('Mermaid', Mermaid);
router.onBeforeRouteChange = (to) => { router.onBeforeRouteChange = (to) => {
if (router.route.path !== '/') {
return;
}
try { try {
const newPath = getRedirect(to); const newPath = getRedirect(to);
if (newPath) { if (newPath) {

View File

@@ -5,31 +5,34 @@ import { expect, test } from 'vitest';
import { getRedirect } from './redirect'; import { getRedirect } from './redirect';
test.each([ test.each([
// Old docs, localhost
['http://localhost:1234/mermaid/#/flowchart.md', 'syntax/flowchart.html'], ['http://localhost:1234/mermaid/#/flowchart.md', 'syntax/flowchart.html'],
['http://localhost/mermaid/#/flowchart.md', 'syntax/flowchart.html'], ['http://localhost/mermaid/#/flowchart.md', 'syntax/flowchart.html'],
['https://mermaid-js.github.io/mermaid/#/flowchart.md', 'syntax/flowchart.html'], // Old docs, github pages
['https://mermaid.js.org/#/flowchart.md', 'syntax/flowchart.html'], ['https://mermaid-js.github.io/mermaid/#/flowchart.md', 'syntax/flowchart.html'], // without dot
['https://mermaid-js.github.io/mermaid/#/./flowchart', 'syntax/flowchart.html'], ['https://mermaid-js.github.io/mermaid/#/./flowchart', 'syntax/flowchart.html'], // with dot
['https://mermaid-js.github.io/mermaid/#/flowchart', 'syntax/flowchart.html'], ['https://mermaid-js.github.io/mermaid/#flowchart', 'syntax/flowchart.html'], // without slash
['https://mermaid-js.github.io/mermaid/#flowchart', 'syntax/flowchart.html'], ['https://mermaid-js.github.io/mermaid/#/flowchart', 'syntax/flowchart.html'], // with slash
['https://mermaid-js.github.io/mermaid/#/flowchart', 'syntax/flowchart.html'], ['https://mermaid-js.github.io/mermaid/#/flowchart.md?id=my-id', 'syntax/flowchart.html#my-id'], // with id
['https://mermaid-js.github.io/mermaid/#/flowchart.md?id=my-id', 'syntax/flowchart.html#my-id'], ['https://mermaid-js.github.io/mermaid/#/./flowchart.md?id=my-id', 'syntax/flowchart.html#my-id'], // with id and dot
['https://mermaid-js.github.io/mermaid/#/./flowchart.md?id=my-id', 'syntax/flowchart.html#my-id'],
[ [
'https://mermaid-js.github.io/mermaid/#/flowchart?another=test&id=my-id&one=more', 'https://mermaid-js.github.io/mermaid/#/flowchart?another=test&id=my-id&one=more', // with multiple params
'syntax/flowchart.html#my-id', 'syntax/flowchart.html#my-id',
], ],
['https://mermaid-js.github.io/mermaid/#/n00b-advanced', 'config/n00b-advanced.html'], ['https://mermaid-js.github.io/mermaid/#/n00b-advanced', 'config/n00b-advanced.html'], // without .md
['https://mermaid-js.github.io/mermaid/#/n00b-advanced.md', 'config/n00b-advanced.html'], ['https://mermaid-js.github.io/mermaid/#/n00b-advanced.md', 'config/n00b-advanced.html'], // with .md
[ [
'https://mermaid-js.github.io/mermaid/#/flowchart?id=a-node-in-the-form-of-a-circle', 'https://mermaid-js.github.io/mermaid/#/flowchart?id=a-node-in-the-form-of-a-circle', // with id, without .md
'syntax/flowchart.html#a-node-in-the-form-of-a-circle', 'syntax/flowchart.html#a-node-in-the-form-of-a-circle',
], ],
// Old docs, without base path, new domain
['https://mermaid.js.org/#/flowchart.md', 'syntax/flowchart.html'],
// New docs, without base path, new domain
['https://mermaid.js.org/misc/faq.html', 'configure/faq.html'],
[
'https://mermaid.js.org/misc/faq.html#frequently-asked-questions',
'configure/faq.html#frequently-asked-questions',
], // with hash
])('should process url %s to %s', (link: string, path: string) => { ])('should process url %s to %s', (link: string, path: string) => {
expect(getRedirect(link)).toBe(path); expect(getRedirect(link)).toBe(path);
}); });
test('should throw for invalid URL', () => {
// Not mermaid domain
expect(() => getRedirect('https://www.google.com')).toThrowError();
});

View File

@@ -1,4 +1,4 @@
export interface Redirect { interface Redirect {
path: string; path: string;
id?: string; id?: string;
} }
@@ -7,15 +7,7 @@ export interface Redirect {
* Extracts the base slug from the old URL. * Extracts the base slug from the old URL.
* @param link - The old URL. * @param link - The old URL.
*/ */
const getBaseFile = (link: string): Redirect => { const getBaseFile = (url: URL): Redirect => {
const url = new URL(link);
if (
url.hostname !== 'mermaid-js.github.io' &&
url.hostname !== 'mermaid.js.org' &&
url.hostname !== 'localhost'
) {
throw new Error('Not mermaidjs url');
}
const [path, params, ...rest] = url.hash const [path, params, ...rest] = url.hash
.toLowerCase() .toLowerCase()
.replace('.md', '') .replace('.md', '')
@@ -32,7 +24,7 @@ const getBaseFile = (link: string): Redirect => {
return { path, id }; return { path, id };
}; };
const redirectMap: Record<string, string> = { const idRedirectMap: Record<string, string> = {
'8.6.0_docs': '', '8.6.0_docs': '',
accessibility: 'config/theming', accessibility: 'config/theming',
breakingchanges: '', breakingchanges: '',
@@ -76,15 +68,25 @@ const redirectMap: Record<string, string> = {
'user-journey': 'syntax/userJourney', 'user-journey': 'syntax/userJourney',
}; };
const urlRedirectMap: Record<string, string> = {
'/misc/faq.html': 'configure/faq.html',
};
/** /**
* *
* @param link - The old documentation URL. * @param link - The old documentation URL.
* @returns The new documentation path. * @returns The new documentation path.
*/ */
export const getRedirect = (link: string): string | undefined => { export const getRedirect = (link: string): string | undefined => {
const { path, id } = getBaseFile(link); const url = new URL(link);
if (!(path in redirectMap)) { // Redirects for deprecated vitepress URLs
return; if (url.pathname in urlRedirectMap) {
return `${urlRedirectMap[url.pathname]}${url.hash}`;
}
// Redirects for old docs URLs
const { path, id } = getBaseFile(url);
if (path in idRedirectMap) {
return `${idRedirectMap[path]}.html${id ? `#${id}` : ''}`;
} }
return `${redirectMap[path]}.html${id ? `#${id}` : ''}`;
}; };

View File

@@ -89,9 +89,10 @@ They also serve as proof of concept, for the variety of things that can be built
## Editor Plugins ## Editor Plugins
- [Vs Code](https://code.visualstudio.com/) - [VS Code](https://code.visualstudio.com/)
- [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid) - [Markdown Preview Mermaid Support](https://marketplace.visualstudio.com/items?itemName=bierner.markdown-mermaid)
- [Mermaid Preview](https://marketplace.visualstudio.com/items?itemName=vstirbu.vscode-mermaid-preview) - [Mermaid Preview](https://marketplace.visualstudio.com/items?itemName=vstirbu.vscode-mermaid-preview)
- [Markdown Preview Enhanced](https://marketplace.visualstudio.com/items?itemName=shd101wyy.markdown-preview-enhanced)
- [Mermaid Markdown Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting) - [Mermaid Markdown Syntax Highlighting](https://marketplace.visualstudio.com/items?itemName=bpruitt-goddard.mermaid-markdown-syntax-highlighting)
- [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor) - [Mermaid Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor)
- [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export) - [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export)

View File

@@ -0,0 +1,3 @@
# Showcases
- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts).

View File

@@ -146,14 +146,13 @@ The `type` and `name` values must begin with an alphabetic character and may con
#### Attribute Keys and Comments #### Attribute Keys and Comments
Attributes may also have a `key` or comment defined. Keys can be "PK", "FK" or "UK", for Primary Key, Foreign Key or Unique Key. And a `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them. Attributes may also have a `key` or comment defined. Keys can be `PK`, `FK` or `UK`, for Primary Key, Foreign Key or Unique Key. To specify multiple key constraints on a single attribute, separate them with a comma (e.g., `PK, FK`).. A `comment` is defined by double quotes at the end of an attribute. Comments themselves cannot have double-quote characters in them.
```mermaid-example ```mermaid-example
erDiagram erDiagram
CAR ||--o{ NAMED-DRIVER : allows CAR ||--o{ NAMED-DRIVER : allows
CAR { CAR {
string allowedDriver FK "The license of the allowed driver" string registrationNumber PK
string registrationNumber UK
string make string make
string model string model
string[] parts string[] parts
@@ -163,9 +162,14 @@ erDiagram
string driversLicense PK "The license #" string driversLicense PK "The license #"
string(99) firstName "Only 99 characters are allowed" string(99) firstName "Only 99 characters are allowed"
string lastName string lastName
string phone UK
int age int age
} }
MANUFACTURER only one to zero or more CAR NAMED-DRIVER {
string carRegistrationNumber PK, FK
string driverLicence PK, FK
}
MANUFACTURER only one to zero or more CAR : makes
``` ```
### Other Things ### Other Things

View File

@@ -1,3 +1,8 @@
---
title: Flowcharts Syntax
outline: 'deep' # shows all h3 headings in outline in Vitepress
---
# Flowcharts - Basic Syntax # Flowcharts - Basic Syntax
All Flowcharts are composed of **nodes**, the geometric shapes and **edges**, the arrows or lines. The mermaid code defines the way that these **nodes** and **edges** are made and interact. All Flowcharts are composed of **nodes**, the geometric shapes and **edges**, the arrows or lines. The mermaid code defines the way that these **nodes** and **edges** are made and interact.
@@ -547,8 +552,8 @@ linkStyle 3 stroke:#ff3,stroke-width:4px,color:red;
### Styling line curves ### Styling line curves
It is possible to style the type of curve used for lines between items, if the default method does not meet your needs. It is possible to style the type of curve used for lines between items, if the default method does not meet your needs.
Available curve styles include `basis`, `bump`, `linear`, `monotoneX`, `monotoneY`, `natural`, `step`, `stepAfter`, Available curve styles include `basis`, `bumpX`, `bumpY`, `cardinal`, `catmullRom`, `linear`, `monotoneX`, `monotoneY`,
and `stepBefore`. `natural`, `step`, `stepAfter`, and `stepBefore`.
In this example, a left-to-right graph uses the `stepBefore` curve style: In this example, a left-to-right graph uses the `stepBefore` curve style:

View File

@@ -55,7 +55,7 @@ In this way we can use a text outline to generate a hierarchical mindmap.
## Different shapes ## Different shapes
Mermaids mindmaps can show node using different shapes. When specifying a shape for a node the syntax for the is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts even though they are not all supported from the start. Mermaid mindmaps can show nodes using different shapes. When specifying a shape for a node the syntax is similar to flowchart nodes, with an id followed by the shape definition and with the text within the shape delimiters. Where possible we try/will try to keep the same shapes as for flowcharts, even though they are not all supported from the start.
Mindmap can show the following shapes: Mindmap can show the following shapes:

View File

@@ -58,6 +58,48 @@ sequenceDiagram
J->>A: Great! J->>A: Great!
``` ```
### Grouping / Box
The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation:
```
box Aqua Group Description
... actors ...
end
box Group without description
... actors ...
end
box rgb(33,66,99)
... actors ...
end
```
```note
If your group name is a color you can force the color to be transparent:
```
```
box transparent Aqua
... actors ...
end
```
```mermaid-example
sequenceDiagram
box Purple Alice & John
participant A
participant J
end
box Another Group
participant B
participant C
end
A->>J: Hello John, how are you?
J->>A: Great!
A->>B: Hello Bob, how is Charly ?
B->>C: Hello Charly, how are you?
```
## Messages ## Messages
Messages can be of two displayed either solid or with a dotted line. Messages can be of two displayed either solid or with a dotted line.

View File

@@ -2,7 +2,7 @@
/* eslint-disable @typescript-eslint/no-unused-vars */ /* eslint-disable @typescript-eslint/no-unused-vars */
/* eslint-disable @typescript-eslint/no-empty-function */ /* eslint-disable @typescript-eslint/no-empty-function */
/* eslint-disable no-console */ /* eslint-disable no-console */
import moment from 'moment-mini'; import moment from 'moment';
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';

View File

@@ -17,7 +17,6 @@ import { ExternalDiagramDefinition } from './diagram-api/types';
export type { MermaidConfig, DetailedError, ExternalDiagramDefinition, ParseErrorFunction }; export type { MermaidConfig, DetailedError, ExternalDiagramDefinition, ParseErrorFunction };
let externalDiagramsRegistered = false;
/** /**
* ## init * ## init
* *
@@ -51,12 +50,7 @@ const init = async function (
callback?: Function callback?: Function
) { ) {
try { try {
// Not really sure if we need to check this, or simply call initThrowsErrorsAsync directly. await initThrowsErrorsAsync(config, nodes, callback);
if (externalDiagramsRegistered) {
await initThrowsErrorsAsync(config, nodes, callback);
} else {
initThrowsErrors(config, nodes, callback);
}
} catch (e) { } catch (e) {
log.warn('Syntax Error rendering'); log.warn('Syntax Error rendering');
if (isDetailedError(e)) { if (isDetailedError(e)) {
@@ -68,8 +62,7 @@ const init = async function (
} }
}; };
// eslint-disable-next-line @typescript-eslint/ban-types const handleError = (error: unknown, errors: DetailedError[], parseError?: ParseErrorFunction) => {
const handleError = (error: unknown, errors: DetailedError[], parseError?: Function) => {
log.warn(error); log.warn(error);
if (isDetailedError(error)) { if (isDetailedError(error)) {
// handle case where error string and hash were // handle case where error string and hash were
@@ -225,7 +218,6 @@ const loadExternalDiagrams = async (...diagrams: ExternalDiagramDefinition[]) =>
*/ */
const initThrowsErrorsAsync = async function ( const initThrowsErrorsAsync = async function (
config?: MermaidConfig, config?: MermaidConfig,
// eslint-disable-next-line no-undef
nodes?: string | HTMLElement | NodeListOf<HTMLElement>, nodes?: string | HTMLElement | NodeListOf<HTMLElement>,
// eslint-disable-next-line @typescript-eslint/ban-types // eslint-disable-next-line @typescript-eslint/ban-types
callback?: Function callback?: Function
@@ -348,7 +340,7 @@ const contentLoaded = function () {
if (mermaid.startOnLoad) { if (mermaid.startOnLoad) {
const { startOnLoad } = mermaidAPI.getConfig(); const { startOnLoad } = mermaidAPI.getConfig();
if (startOnLoad) { if (startOnLoad) {
mermaid.init(); mermaid.init().catch((err) => log.error('Mermaid failed to initialize', err));
} }
} }
}; };
@@ -427,7 +419,7 @@ const parseAsync = (txt: string): Promise<boolean> => {
); );
}); });
executionQueue.push(performCall); executionQueue.push(performCall);
executeQueue(); executeQueue().catch(reject);
}); });
}; };
@@ -460,7 +452,7 @@ const renderAsync = (
); );
}); });
executionQueue.push(performCall); executionQueue.push(performCall);
executeQueue(); executeQueue().catch(reject);
}); });
}; };

View File

@@ -4,6 +4,15 @@ import {
curveBasis, curveBasis,
curveBasisClosed, curveBasisClosed,
curveBasisOpen, curveBasisOpen,
curveBumpX,
curveBumpY,
curveBundle,
curveCardinalClosed,
curveCardinalOpen,
curveCardinal,
curveCatmullRomClosed,
curveCatmullRomOpen,
curveCatmullRom,
CurveFactory, CurveFactory,
curveLinear, curveLinear,
curveLinearClosed, curveLinearClosed,
@@ -28,6 +37,15 @@ const d3CurveTypes = {
curveBasis: curveBasis, curveBasis: curveBasis,
curveBasisClosed: curveBasisClosed, curveBasisClosed: curveBasisClosed,
curveBasisOpen: curveBasisOpen, curveBasisOpen: curveBasisOpen,
curveBumpX: curveBumpX,
curveBumpY: curveBumpY,
curveBundle: curveBundle,
curveCardinalClosed: curveCardinalClosed,
curveCardinalOpen: curveCardinalOpen,
curveCardinal: curveCardinal,
curveCatmullRomClosed: curveCatmullRomClosed,
curveCatmullRomOpen: curveCatmullRomOpen,
curveCatmullRom: curveCatmullRom,
curveLinear: curveLinear, curveLinear: curveLinear,
curveLinearClosed: curveLinearClosed, curveLinearClosed: curveLinearClosed,
curveMonotoneX: curveMonotoneX, curveMonotoneX: curveMonotoneX,

342
pnpm-lock.yaml generated
View File

@@ -44,11 +44,11 @@ importers:
specifier: ^4.2.1 specifier: ^4.2.1
version: 4.2.1 version: 4.2.1
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^5.42.1 specifier: ^5.48.2
version: 5.42.1_2udltptbznfmezdozpdoa2aemq version: 5.48.2_iljmjqxcygjq3saipl7gerxpvi
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^5.42.1 specifier: ^5.48.2
version: 5.42.1_rmayb2veg2btbq6mbmnyivgasy version: 5.48.2_yygwinqv3a2io74xmwofqb7uka
'@vitest/coverage-c8': '@vitest/coverage-c8':
specifier: ^0.27.0 specifier: ^0.27.0
version: 0.27.1_6vhkb7zox2ro6wmx3rlvm5i5ce version: 0.27.1_6vhkb7zox2ro6wmx3rlvm5i5ce
@@ -71,32 +71,32 @@ importers:
specifier: ^0.17.0 specifier: ^0.17.0
version: 0.17.0 version: 0.17.0
eslint: eslint:
specifier: ^8.27.0 specifier: ^8.32.0
version: 8.27.0 version: 8.32.0
eslint-config-prettier: eslint-config-prettier:
specifier: ^8.5.0 specifier: ^8.6.0
version: 8.5.0_eslint@8.27.0 version: 8.6.0_eslint@8.32.0
eslint-plugin-cypress: eslint-plugin-cypress:
specifier: ^2.12.1 specifier: ^2.12.1
version: 2.12.1_eslint@8.27.0 version: 2.12.1_eslint@8.32.0
eslint-plugin-html: eslint-plugin-html:
specifier: ^7.1.0 specifier: ^7.1.0
version: 7.1.0 version: 7.1.0
eslint-plugin-jest: eslint-plugin-jest:
specifier: ^27.1.5 specifier: ^27.1.5
version: 27.1.5_kdswgjmqcx7mthqz7ow2zlfevy version: 27.1.5_5rcd23qw3h5vuffwo2owxb3hw4
eslint-plugin-jsdoc: eslint-plugin-jsdoc:
specifier: ^39.6.2 specifier: ^39.6.2
version: 39.6.2_eslint@8.27.0 version: 39.6.2_eslint@8.32.0
eslint-plugin-json: eslint-plugin-json:
specifier: ^3.1.0 specifier: ^3.1.0
version: 3.1.0 version: 3.1.0
eslint-plugin-lodash: eslint-plugin-lodash:
specifier: ^7.4.0 specifier: ^7.4.0
version: 7.4.0_eslint@8.27.0 version: 7.4.0_eslint@8.32.0
eslint-plugin-markdown: eslint-plugin-markdown:
specifier: ^3.0.0 specifier: ^3.0.0
version: 3.0.0_eslint@8.27.0 version: 3.0.0_eslint@8.32.0
eslint-plugin-no-only-tests: eslint-plugin-no-only-tests:
specifier: ^3.1.0 specifier: ^3.1.0
version: 3.1.0 version: 3.1.0
@@ -105,7 +105,7 @@ importers:
version: 0.2.17 version: 0.2.17
eslint-plugin-unicorn: eslint-plugin-unicorn:
specifier: ^45.0.0 specifier: ^45.0.0
version: 45.0.0_eslint@8.27.0 version: 45.0.0_eslint@8.32.0
express: express:
specifier: ^4.18.2 specifier: ^4.18.2
version: 4.18.2 version: 4.18.2
@@ -173,8 +173,8 @@ importers:
specifier: ^7.0.0 specifier: ^7.0.0
version: 7.6.1 version: 7.6.1
dagre-d3-es: dagre-d3-es:
specifier: 7.0.6 specifier: 7.0.8
version: 7.0.6 version: 7.0.8
dompurify: dompurify:
specifier: 2.4.3 specifier: 2.4.3
version: 2.4.3 version: 2.4.3
@@ -187,8 +187,8 @@ importers:
lodash-es: lodash-es:
specifier: ^4.17.21 specifier: ^4.17.21
version: 4.17.21 version: 4.17.21
moment-mini: moment:
specifier: ^2.24.0 specifier: ^2.29.4
version: 2.29.4 version: 2.29.4
non-layered-tidy-tree-layout: non-layered-tidy-tree-layout:
specifier: ^2.0.2 specifier: ^2.0.2
@@ -229,10 +229,10 @@ importers:
version: 8.3.4 version: 8.3.4
'@typescript-eslint/eslint-plugin': '@typescript-eslint/eslint-plugin':
specifier: ^5.42.1 specifier: ^5.42.1
version: 5.42.1_2udltptbznfmezdozpdoa2aemq version: 5.42.1_qxgr6oy2qtsmmpo3f6iejuryuq
'@typescript-eslint/parser': '@typescript-eslint/parser':
specifier: ^5.42.1 specifier: ^5.42.1
version: 5.42.1_rmayb2veg2btbq6mbmnyivgasy version: 5.42.1_yygwinqv3a2io74xmwofqb7uka
chokidar: chokidar:
specifier: ^3.5.3 specifier: ^3.5.3
version: 3.5.3 version: 3.5.3
@@ -263,9 +263,6 @@ importers:
micromatch: micromatch:
specifier: ^4.0.5 specifier: ^4.0.5
version: 4.0.5 version: 4.0.5
moment:
specifier: ^2.29.4
version: 2.29.4
path-browserify: path-browserify:
specifier: ^1.0.1 specifier: ^1.0.1
version: 1.0.1 version: 1.0.1
@@ -275,6 +272,9 @@ importers:
remark: remark:
specifier: ^14.0.2 specifier: ^14.0.2
version: 14.0.2 version: 14.0.2
remark-frontmatter:
specifier: ^4.0.1
version: 4.0.1
remark-gfm: remark-gfm:
specifier: ^3.0.1 specifier: ^3.0.1
version: 3.0.1 version: 3.0.1
@@ -336,6 +336,9 @@ importers:
specifier: ^2.0.2 specifier: ^2.0.2
version: 2.0.2 version: 2.0.2
devDependencies: devDependencies:
'@types/cytoscape':
specifier: ^3.19.9
version: 3.19.9
concurrently: concurrently:
specifier: ^7.5.0 specifier: ^7.5.0
version: 7.5.0 version: 7.5.0
@@ -2160,14 +2163,14 @@ packages:
dev: true dev: true
optional: true optional: true
/@eslint/eslintrc/1.3.3: /@eslint/eslintrc/1.4.1:
resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
ajv: 6.12.6 ajv: 6.12.6
debug: 4.3.4 debug: 4.3.4
espree: 9.4.0 espree: 9.4.0
globals: 13.17.0 globals: 13.19.0
ignore: 5.2.0 ignore: 5.2.0
import-fresh: 3.3.0 import-fresh: 3.3.0
js-yaml: 4.1.0 js-yaml: 4.1.0
@@ -2187,8 +2190,8 @@ packages:
'@hapi/hoek': 9.3.0 '@hapi/hoek': 9.3.0
dev: true dev: true
/@humanwhocodes/config-array/0.11.7: /@humanwhocodes/config-array/0.11.8:
resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==} resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==}
engines: {node: '>=10.10.0'} engines: {node: '>=10.10.0'}
dependencies: dependencies:
'@humanwhocodes/object-schema': 1.2.1 '@humanwhocodes/object-schema': 1.2.1
@@ -2672,6 +2675,10 @@ packages:
'@types/node': 18.11.9 '@types/node': 18.11.9
dev: true dev: true
/@types/cytoscape/3.19.9:
resolution: {integrity: sha512-oqCx0ZGiBO0UESbjgq052vjDAy2X53lZpMrWqiweMpvVwKw/2IiYDdzPFK6+f4tMfdv9YKEM9raO5bAZc3UYBg==}
dev: true
/@types/d3-array/3.0.3: /@types/d3-array/3.0.3:
resolution: {integrity: sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==} resolution: {integrity: sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==}
dev: true dev: true
@@ -3152,7 +3159,7 @@ packages:
dev: true dev: true
optional: true optional: true
/@typescript-eslint/eslint-plugin/5.42.1_2udltptbznfmezdozpdoa2aemq: /@typescript-eslint/eslint-plugin/5.42.1_qxgr6oy2qtsmmpo3f6iejuryuq:
resolution: {integrity: sha512-LyR6x784JCiJ1j6sH5Y0K6cdExqCCm8DJUTcwG5ThNXJj/G8o5E56u5EdG4SLy+bZAwZBswC+GYn3eGdttBVCg==} resolution: {integrity: sha512-LyR6x784JCiJ1j6sH5Y0K6cdExqCCm8DJUTcwG5ThNXJj/G8o5E56u5EdG4SLy+bZAwZBswC+GYn3eGdttBVCg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@@ -3163,12 +3170,12 @@ packages:
typescript: typescript:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/parser': 5.42.1_rmayb2veg2btbq6mbmnyivgasy '@typescript-eslint/parser': 5.42.1_yygwinqv3a2io74xmwofqb7uka
'@typescript-eslint/scope-manager': 5.42.1 '@typescript-eslint/scope-manager': 5.42.1
'@typescript-eslint/type-utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy '@typescript-eslint/type-utils': 5.42.1_yygwinqv3a2io74xmwofqb7uka
'@typescript-eslint/utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy '@typescript-eslint/utils': 5.42.1_yygwinqv3a2io74xmwofqb7uka
debug: 4.3.4 debug: 4.3.4
eslint: 8.27.0 eslint: 8.32.0
ignore: 5.2.0 ignore: 5.2.0
natural-compare-lite: 1.4.0 natural-compare-lite: 1.4.0
regexpp: 3.2.0 regexpp: 3.2.0
@@ -3179,7 +3186,34 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/parser/5.42.1_rmayb2veg2btbq6mbmnyivgasy: /@typescript-eslint/eslint-plugin/5.48.2_iljmjqxcygjq3saipl7gerxpvi:
resolution: {integrity: sha512-sR0Gja9Ky1teIq4qJOl0nC+Tk64/uYdX+mi+5iB//MH8gwyx8e3SOyhEzeLZEFEEfCaLf8KJq+Bd/6je1t+CAg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
'@typescript-eslint/parser': ^5.0.0
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/parser': 5.48.2_yygwinqv3a2io74xmwofqb7uka
'@typescript-eslint/scope-manager': 5.48.2
'@typescript-eslint/type-utils': 5.48.2_yygwinqv3a2io74xmwofqb7uka
'@typescript-eslint/utils': 5.48.2_yygwinqv3a2io74xmwofqb7uka
debug: 4.3.4
eslint: 8.32.0
ignore: 5.2.0
natural-compare-lite: 1.4.0
regexpp: 3.2.0
semver: 7.3.8
tsutils: 3.21.0_typescript@4.8.4
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser/5.42.1_yygwinqv3a2io74xmwofqb7uka:
resolution: {integrity: sha512-kAV+NiNBWVQDY9gDJDToTE/NO8BHi4f6b7zTsVAJoTkmB/zlfOpiEVBzHOKtlgTndCKe8vj9F/PuolemZSh50Q==} resolution: {integrity: sha512-kAV+NiNBWVQDY9gDJDToTE/NO8BHi4f6b7zTsVAJoTkmB/zlfOpiEVBzHOKtlgTndCKe8vj9F/PuolemZSh50Q==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@@ -3193,7 +3227,27 @@ packages:
'@typescript-eslint/types': 5.42.1 '@typescript-eslint/types': 5.42.1
'@typescript-eslint/typescript-estree': 5.42.1_typescript@4.8.4 '@typescript-eslint/typescript-estree': 5.42.1_typescript@4.8.4
debug: 4.3.4 debug: 4.3.4
eslint: 8.27.0 eslint: 8.32.0
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/parser/5.48.2_yygwinqv3a2io74xmwofqb7uka:
resolution: {integrity: sha512-38zMsKsG2sIuM5Oi/olurGwYJXzmtdsHhn5mI/pQogP+BjYVkK5iRazCQ8RGS0V+YLk282uWElN70zAAUmaYHw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/scope-manager': 5.48.2
'@typescript-eslint/types': 5.48.2
'@typescript-eslint/typescript-estree': 5.48.2_typescript@4.8.4
debug: 4.3.4
eslint: 8.32.0
typescript: 4.8.4 typescript: 4.8.4
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -3207,7 +3261,15 @@ packages:
'@typescript-eslint/visitor-keys': 5.42.1 '@typescript-eslint/visitor-keys': 5.42.1
dev: true dev: true
/@typescript-eslint/type-utils/5.42.1_rmayb2veg2btbq6mbmnyivgasy: /@typescript-eslint/scope-manager/5.48.2:
resolution: {integrity: sha512-zEUFfonQid5KRDKoI3O+uP1GnrFd4tIHlvs+sTJXiWuypUWMuDaottkJuR612wQfOkjYbsaskSIURV9xo4f+Fw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
'@typescript-eslint/types': 5.48.2
'@typescript-eslint/visitor-keys': 5.48.2
dev: true
/@typescript-eslint/type-utils/5.42.1_yygwinqv3a2io74xmwofqb7uka:
resolution: {integrity: sha512-WWiMChneex5w4xPIX56SSnQQo0tEOy5ZV2dqmj8Z371LJ0E+aymWD25JQ/l4FOuuX+Q49A7pzh/CGIQflxMVXg==} resolution: {integrity: sha512-WWiMChneex5w4xPIX56SSnQQo0tEOy5ZV2dqmj8Z371LJ0E+aymWD25JQ/l4FOuuX+Q49A7pzh/CGIQflxMVXg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@@ -3218,9 +3280,29 @@ packages:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/typescript-estree': 5.42.1_typescript@4.8.4 '@typescript-eslint/typescript-estree': 5.42.1_typescript@4.8.4
'@typescript-eslint/utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy '@typescript-eslint/utils': 5.42.1_yygwinqv3a2io74xmwofqb7uka
debug: 4.3.4 debug: 4.3.4
eslint: 8.27.0 eslint: 8.32.0
tsutils: 3.21.0_typescript@4.8.4
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/type-utils/5.48.2_yygwinqv3a2io74xmwofqb7uka:
resolution: {integrity: sha512-QVWx7J5sPMRiOMJp5dYshPxABRoZV1xbRirqSk8yuIIsu0nvMTZesKErEA3Oix1k+uvsk8Cs8TGJ6kQ0ndAcew==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: '*'
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/typescript-estree': 5.48.2_typescript@4.8.4
'@typescript-eslint/utils': 5.48.2_yygwinqv3a2io74xmwofqb7uka
debug: 4.3.4
eslint: 8.32.0
tsutils: 3.21.0_typescript@4.8.4 tsutils: 3.21.0_typescript@4.8.4
typescript: 4.8.4 typescript: 4.8.4
transitivePeerDependencies: transitivePeerDependencies:
@@ -3232,6 +3314,11 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/@typescript-eslint/types/5.48.2:
resolution: {integrity: sha512-hE7dA77xxu7ByBc6KCzikgfRyBCTst6dZQpwaTy25iMYOnbNljDT4hjhrGEJJ0QoMjrfqrx+j1l1B9/LtKeuqA==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true
/@typescript-eslint/typescript-estree/5.42.1_typescript@4.8.4: /@typescript-eslint/typescript-estree/5.42.1_typescript@4.8.4:
resolution: {integrity: sha512-qElc0bDOuO0B8wDhhW4mYVgi/LZL+igPwXtV87n69/kYC/7NG3MES0jHxJNCr4EP7kY1XVsRy8C/u3DYeTKQmw==} resolution: {integrity: sha512-qElc0bDOuO0B8wDhhW4mYVgi/LZL+igPwXtV87n69/kYC/7NG3MES0jHxJNCr4EP7kY1XVsRy8C/u3DYeTKQmw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
@@ -3253,7 +3340,28 @@ packages:
- supports-color - supports-color
dev: true dev: true
/@typescript-eslint/utils/5.42.1_rmayb2veg2btbq6mbmnyivgasy: /@typescript-eslint/typescript-estree/5.48.2_typescript@4.8.4:
resolution: {integrity: sha512-bibvD3z6ilnoVxUBFEgkO0k0aFvUc4Cttt0dAreEr+nrAHhWzkO83PEVVuieK3DqcgL6VAK5dkzK8XUVja5Zcg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
typescript: '*'
peerDependenciesMeta:
typescript:
optional: true
dependencies:
'@typescript-eslint/types': 5.48.2
'@typescript-eslint/visitor-keys': 5.48.2
debug: 4.3.4
globby: 11.1.0
is-glob: 4.0.3
semver: 7.3.8
tsutils: 3.21.0_typescript@4.8.4
typescript: 4.8.4
transitivePeerDependencies:
- supports-color
dev: true
/@typescript-eslint/utils/5.42.1_yygwinqv3a2io74xmwofqb7uka:
resolution: {integrity: sha512-Gxvf12xSp3iYZd/fLqiQRD4uKZjDNR01bQ+j8zvhPjpsZ4HmvEFL/tC4amGNyxN9Rq+iqvpHLhlqx6KTxz9ZyQ==} resolution: {integrity: sha512-Gxvf12xSp3iYZd/fLqiQRD4uKZjDNR01bQ+j8zvhPjpsZ4HmvEFL/tC4amGNyxN9Rq+iqvpHLhlqx6KTxz9ZyQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
@@ -3264,9 +3372,29 @@ packages:
'@typescript-eslint/scope-manager': 5.42.1 '@typescript-eslint/scope-manager': 5.42.1
'@typescript-eslint/types': 5.42.1 '@typescript-eslint/types': 5.42.1
'@typescript-eslint/typescript-estree': 5.42.1_typescript@4.8.4 '@typescript-eslint/typescript-estree': 5.42.1_typescript@4.8.4
eslint: 8.27.0 eslint: 8.32.0
eslint-scope: 5.1.1 eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@8.27.0 eslint-utils: 3.0.0_eslint@8.32.0
semver: 7.3.8
transitivePeerDependencies:
- supports-color
- typescript
dev: true
/@typescript-eslint/utils/5.48.2_yygwinqv3a2io74xmwofqb7uka:
resolution: {integrity: sha512-2h18c0d7jgkw6tdKTlNaM7wyopbLRBiit8oAxoP89YnuBOzCZ8g8aBCaCqq7h208qUTroL7Whgzam7UY3HVLow==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies:
'@types/json-schema': 7.0.11
'@types/semver': 7.3.12
'@typescript-eslint/scope-manager': 5.48.2
'@typescript-eslint/types': 5.48.2
'@typescript-eslint/typescript-estree': 5.48.2_typescript@4.8.4
eslint: 8.32.0
eslint-scope: 5.1.1
eslint-utils: 3.0.0_eslint@8.32.0
semver: 7.3.8 semver: 7.3.8
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -3281,6 +3409,14 @@ packages:
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
dev: true dev: true
/@typescript-eslint/visitor-keys/5.48.2:
resolution: {integrity: sha512-z9njZLSkwmjFWUelGEwEbdf4NwKvfHxvGC0OcGN1Hp/XNDIcJ7D5DpPNPv6x6/mFvc1tQHsaWmpD/a4gOvvCJQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies:
'@typescript-eslint/types': 5.48.2
eslint-visitor-keys: 3.3.0
dev: true
/@vitejs/plugin-vue/4.0.0_vite@4.0.1+vue@3.2.45: /@vitejs/plugin-vue/4.0.0_vite@4.0.1+vue@3.2.45:
resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==} resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==}
engines: {node: ^14.18.0 || >=16.0.0} engines: {node: ^14.18.0 || >=16.0.0}
@@ -3677,12 +3813,12 @@ packages:
acorn: 8.8.0 acorn: 8.8.0
dev: true dev: true
/acorn-jsx/5.3.2_acorn@8.8.0: /acorn-jsx/5.3.2_acorn@8.8.1:
resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==} resolution: {integrity: sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==}
peerDependencies: peerDependencies:
acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 acorn: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies: dependencies:
acorn: 8.8.0 acorn: 8.8.1
dev: true dev: true
/acorn-walk/7.2.0: /acorn-walk/7.2.0:
@@ -3986,7 +4122,7 @@ packages:
/axios/0.21.4_debug@4.3.2: /axios/0.21.4_debug@4.3.2:
resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==} resolution: {integrity: sha512-ut5vewkiu8jjGBdqpM44XxjuCjq9LAKeHVmoVfHVzy8eHgxxq8SbAVQNovDA8mVi05kP0Ea/n/UzcSHcTJQfNg==}
dependencies: dependencies:
follow-redirects: 1.15.2_debug@4.3.2 follow-redirects: 1.15.2
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
dev: true dev: true
@@ -4623,7 +4759,7 @@ packages:
dev: true dev: true
/concat-map/0.0.1: /concat-map/0.0.1:
resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=}
dev: true dev: true
/concurrently/7.5.0: /concurrently/7.5.0:
@@ -5406,8 +5542,8 @@ packages:
d3-zoom: 3.0.0 d3-zoom: 3.0.0
dev: false dev: false
/d3/7.7.0: /d3/7.8.2:
resolution: {integrity: sha512-VEwHCMgMjD2WBsxeRGUE18RmzxT9Bn7ghDpzvTEvkLSBAKgTMydJjouZTjspgQfRHpPt/PB3EHWBa6SSyFQq4g==} resolution: {integrity: sha512-WXty7qOGSHb7HR7CfOzwN1Gw04MUOzN8qh9ZUsvwycIMb4DYMpY9xczZ6jUorGtO6bR9BPMPaueIKwiDxu9uiQ==}
engines: {node: '>=12'} engines: {node: '>=12'}
dependencies: dependencies:
d3-array: 3.2.0 d3-array: 3.2.0
@@ -5442,10 +5578,10 @@ packages:
d3-zoom: 3.0.0 d3-zoom: 3.0.0
dev: false dev: false
/dagre-d3-es/7.0.6: /dagre-d3-es/7.0.8:
resolution: {integrity: sha512-CaaE/nZh205ix+Up4xsnlGmpog5GGm81Upi2+/SBHxwNwrccBb3K51LzjZ1U6hgvOlAEUsVWf1xSTzCyKpJ6+Q==} resolution: {integrity: sha512-eykdoYQ4FwCJinEYS0gPL2f2w+BPbSLvnQSJ3Ye1vAoPjdkq6xIMKBv+UkICd3qZE26wBKIn3p+6n0QC7R1LyA==}
dependencies: dependencies:
d3: 7.7.0 d3: 7.8.2
lodash-es: 4.17.21 lodash-es: 4.17.21
dev: false dev: false
@@ -6184,21 +6320,21 @@ packages:
source-map: 0.6.1 source-map: 0.6.1
dev: true dev: true
/eslint-config-prettier/8.5.0_eslint@8.27.0: /eslint-config-prettier/8.6.0_eslint@8.32.0:
resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} resolution: {integrity: sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==}
hasBin: true hasBin: true
peerDependencies: peerDependencies:
eslint: '>=7.0.0' eslint: '>=7.0.0'
dependencies: dependencies:
eslint: 8.27.0 eslint: 8.32.0
dev: true dev: true
/eslint-plugin-cypress/2.12.1_eslint@8.27.0: /eslint-plugin-cypress/2.12.1_eslint@8.32.0:
resolution: {integrity: sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA==} resolution: {integrity: sha512-c2W/uPADl5kospNDihgiLc7n87t5XhUbFDoTl6CfVkmG+kDAb5Ux10V9PoLPu9N+r7znpc+iQlcmAqT1A/89HA==}
peerDependencies: peerDependencies:
eslint: '>= 3.2.1' eslint: '>= 3.2.1'
dependencies: dependencies:
eslint: 8.27.0 eslint: 8.32.0
globals: 11.12.0 globals: 11.12.0
dev: true dev: true
@@ -6208,7 +6344,7 @@ packages:
htmlparser2: 8.0.1 htmlparser2: 8.0.1
dev: true dev: true
/eslint-plugin-jest/27.1.5_kdswgjmqcx7mthqz7ow2zlfevy: /eslint-plugin-jest/27.1.5_5rcd23qw3h5vuffwo2owxb3hw4:
resolution: {integrity: sha512-CK2dekZ5VBdzsOSOH5Fc1rwC+cWXjkcyrmf1RV714nDUDKu+o73TTJiDxpbILG8PtPPpAAl3ywzh5QA7Ft0mjA==} resolution: {integrity: sha512-CK2dekZ5VBdzsOSOH5Fc1rwC+cWXjkcyrmf1RV714nDUDKu+o73TTJiDxpbILG8PtPPpAAl3ywzh5QA7Ft0mjA==}
engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0}
peerDependencies: peerDependencies:
@@ -6221,16 +6357,16 @@ packages:
jest: jest:
optional: true optional: true
dependencies: dependencies:
'@typescript-eslint/eslint-plugin': 5.42.1_2udltptbznfmezdozpdoa2aemq '@typescript-eslint/eslint-plugin': 5.48.2_iljmjqxcygjq3saipl7gerxpvi
'@typescript-eslint/utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy '@typescript-eslint/utils': 5.42.1_yygwinqv3a2io74xmwofqb7uka
eslint: 8.27.0 eslint: 8.32.0
jest: 29.3.1_odkjkoia5xunhxkdrka32ib6vi jest: 29.3.1_odkjkoia5xunhxkdrka32ib6vi
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
- typescript - typescript
dev: true dev: true
/eslint-plugin-jsdoc/39.6.2_eslint@8.27.0: /eslint-plugin-jsdoc/39.6.2_eslint@8.32.0:
resolution: {integrity: sha512-dvgY/W7eUFoAIIiaWHERIMI61ZWqcz9YFjEeyTzdPlrZc3TY/3aZm5aB91NUoTLWYZmO/vFlYSuQi15tF7uE5A==} resolution: {integrity: sha512-dvgY/W7eUFoAIIiaWHERIMI61ZWqcz9YFjEeyTzdPlrZc3TY/3aZm5aB91NUoTLWYZmO/vFlYSuQi15tF7uE5A==}
engines: {node: ^14 || ^16 || ^17 || ^18 || ^19} engines: {node: ^14 || ^16 || ^17 || ^18 || ^19}
peerDependencies: peerDependencies:
@@ -6240,7 +6376,7 @@ packages:
comment-parser: 1.3.1 comment-parser: 1.3.1
debug: 4.3.4 debug: 4.3.4
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint: 8.27.0 eslint: 8.32.0
esquery: 1.4.0 esquery: 1.4.0
semver: 7.3.8 semver: 7.3.8
spdx-expression-parse: 3.0.1 spdx-expression-parse: 3.0.1
@@ -6256,23 +6392,23 @@ packages:
vscode-json-languageservice: 4.2.1 vscode-json-languageservice: 4.2.1
dev: true dev: true
/eslint-plugin-lodash/7.4.0_eslint@8.27.0: /eslint-plugin-lodash/7.4.0_eslint@8.32.0:
resolution: {integrity: sha512-Tl83UwVXqe1OVeBRKUeWcfg6/pCW1GTRObbdnbEJgYwjxp5Q92MEWQaH9+dmzbRt6kvYU1Mp893E79nJiCSM8A==} resolution: {integrity: sha512-Tl83UwVXqe1OVeBRKUeWcfg6/pCW1GTRObbdnbEJgYwjxp5Q92MEWQaH9+dmzbRt6kvYU1Mp893E79nJiCSM8A==}
engines: {node: '>=10'} engines: {node: '>=10'}
peerDependencies: peerDependencies:
eslint: '>=2' eslint: '>=2'
dependencies: dependencies:
eslint: 8.27.0 eslint: 8.32.0
lodash: 4.17.21 lodash: 4.17.21
dev: true dev: true
/eslint-plugin-markdown/3.0.0_eslint@8.27.0: /eslint-plugin-markdown/3.0.0_eslint@8.32.0:
resolution: {integrity: sha512-hRs5RUJGbeHDLfS7ELanT0e29Ocyssf/7kBM+p7KluY5AwngGkDf8Oyu4658/NZSGTTq05FZeWbkxXtbVyHPwg==} resolution: {integrity: sha512-hRs5RUJGbeHDLfS7ELanT0e29Ocyssf/7kBM+p7KluY5AwngGkDf8Oyu4658/NZSGTTq05FZeWbkxXtbVyHPwg==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
peerDependencies: peerDependencies:
eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 eslint: ^6.0.0 || ^7.0.0 || ^8.0.0
dependencies: dependencies:
eslint: 8.27.0 eslint: 8.32.0
mdast-util-from-markdown: 0.8.5 mdast-util-from-markdown: 0.8.5
transitivePeerDependencies: transitivePeerDependencies:
- supports-color - supports-color
@@ -6290,7 +6426,7 @@ packages:
'@microsoft/tsdoc-config': 0.16.2 '@microsoft/tsdoc-config': 0.16.2
dev: true dev: true
/eslint-plugin-unicorn/45.0.0_eslint@8.27.0: /eslint-plugin-unicorn/45.0.0_eslint@8.32.0:
resolution: {integrity: sha512-iP8cMRxXKHonKioOhnCoCcqVhoqhAp6rB+nsoLjXFDxTHz3btWMAp8xwzjHA0B1K6YV/U/Yvqn1bUXZt8sJPuQ==} resolution: {integrity: sha512-iP8cMRxXKHonKioOhnCoCcqVhoqhAp6rB+nsoLjXFDxTHz3btWMAp8xwzjHA0B1K6YV/U/Yvqn1bUXZt8sJPuQ==}
engines: {node: '>=14.18'} engines: {node: '>=14.18'}
peerDependencies: peerDependencies:
@@ -6299,8 +6435,8 @@ packages:
'@babel/helper-validator-identifier': 7.19.1 '@babel/helper-validator-identifier': 7.19.1
ci-info: 3.6.2 ci-info: 3.6.2
clean-regexp: 1.0.0 clean-regexp: 1.0.0
eslint: 8.27.0 eslint: 8.32.0
eslint-utils: 3.0.0_eslint@8.27.0 eslint-utils: 3.0.0_eslint@8.32.0
esquery: 1.4.0 esquery: 1.4.0
indent-string: 4.0.0 indent-string: 4.0.0
is-builtin-module: 3.2.0 is-builtin-module: 3.2.0
@@ -6331,13 +6467,13 @@ packages:
estraverse: 5.3.0 estraverse: 5.3.0
dev: true dev: true
/eslint-utils/3.0.0_eslint@8.27.0: /eslint-utils/3.0.0_eslint@8.32.0:
resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==} resolution: {integrity: sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==}
engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0}
peerDependencies: peerDependencies:
eslint: '>=5' eslint: '>=5'
dependencies: dependencies:
eslint: 8.27.0 eslint: 8.32.0
eslint-visitor-keys: 2.1.0 eslint-visitor-keys: 2.1.0
dev: true dev: true
@@ -6351,13 +6487,13 @@ packages:
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dev: true dev: true
/eslint/8.27.0: /eslint/8.32.0:
resolution: {integrity: sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==} resolution: {integrity: sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
hasBin: true hasBin: true
dependencies: dependencies:
'@eslint/eslintrc': 1.3.3 '@eslint/eslintrc': 1.4.1
'@humanwhocodes/config-array': 0.11.7 '@humanwhocodes/config-array': 0.11.8
'@humanwhocodes/module-importer': 1.0.1 '@humanwhocodes/module-importer': 1.0.1
'@nodelib/fs.walk': 1.2.8 '@nodelib/fs.walk': 1.2.8
ajv: 6.12.6 ajv: 6.12.6
@@ -6367,7 +6503,7 @@ packages:
doctrine: 3.0.0 doctrine: 3.0.0
escape-string-regexp: 4.0.0 escape-string-regexp: 4.0.0
eslint-scope: 7.1.1 eslint-scope: 7.1.1
eslint-utils: 3.0.0_eslint@8.27.0 eslint-utils: 3.0.0_eslint@8.32.0
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
espree: 9.4.0 espree: 9.4.0
esquery: 1.4.0 esquery: 1.4.0
@@ -6376,7 +6512,7 @@ packages:
file-entry-cache: 6.0.1 file-entry-cache: 6.0.1
find-up: 5.0.0 find-up: 5.0.0
glob-parent: 6.0.2 glob-parent: 6.0.2
globals: 13.17.0 globals: 13.19.0
grapheme-splitter: 1.0.4 grapheme-splitter: 1.0.4
ignore: 5.2.0 ignore: 5.2.0
import-fresh: 3.3.0 import-fresh: 3.3.0
@@ -6403,8 +6539,8 @@ packages:
resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==}
engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0}
dependencies: dependencies:
acorn: 8.8.0 acorn: 8.8.1
acorn-jsx: 5.3.2_acorn@8.8.0 acorn-jsx: 5.3.2_acorn@8.8.1
eslint-visitor-keys: 3.3.0 eslint-visitor-keys: 3.3.0
dev: true dev: true
@@ -6679,6 +6815,12 @@ packages:
reusify: 1.0.4 reusify: 1.0.4
dev: true dev: true
/fault/2.0.1:
resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==}
dependencies:
format: 0.2.2
dev: true
/faye-websocket/0.11.4: /faye-websocket/0.11.4:
resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==}
engines: {node: '>=0.8.0'} engines: {node: '>=0.8.0'}
@@ -6778,7 +6920,7 @@ packages:
resolution: {integrity: sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==} resolution: {integrity: sha512-XGozTsMPYkm+6b5QL3Z9wQcJjNYxp0CYn3U1gO7dwD6PAqU1SVWZxI9CCg3z+ml3YfqdPnrBehaBrnH2AGKbNA==}
dev: true dev: true
/follow-redirects/1.15.2_debug@4.3.2: /follow-redirects/1.15.2:
resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==}
engines: {node: '>=4.0'} engines: {node: '>=4.0'}
peerDependencies: peerDependencies:
@@ -6786,8 +6928,6 @@ packages:
peerDependenciesMeta: peerDependenciesMeta:
debug: debug:
optional: true optional: true
dependencies:
debug: 4.3.2
dev: true dev: true
/foreground-child/2.0.0: /foreground-child/2.0.0:
@@ -6829,6 +6969,11 @@ packages:
mime-types: 2.1.35 mime-types: 2.1.35
dev: true dev: true
/format/0.2.2:
resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==}
engines: {node: '>=0.4.x'}
dev: true
/forwarded/0.2.0: /forwarded/0.2.0:
resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==}
engines: {node: '>= 0.6'} engines: {node: '>= 0.6'}
@@ -7066,8 +7211,8 @@ packages:
engines: {node: '>=4'} engines: {node: '>=4'}
dev: true dev: true
/globals/13.17.0: /globals/13.19.0:
resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==} resolution: {integrity: sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==}
engines: {node: '>=8'} engines: {node: '>=8'}
dependencies: dependencies:
type-fest: 0.20.2 type-fest: 0.20.2
@@ -7322,7 +7467,7 @@ packages:
engines: {node: '>=8.0.0'} engines: {node: '>=8.0.0'}
dependencies: dependencies:
eventemitter3: 4.0.7 eventemitter3: 4.0.7
follow-redirects: 1.15.2_debug@4.3.2 follow-redirects: 1.15.2
requires-port: 1.0.0 requires-port: 1.0.0
transitivePeerDependencies: transitivePeerDependencies:
- debug - debug
@@ -8763,6 +8908,12 @@ packages:
- supports-color - supports-color
dev: true dev: true
/mdast-util-frontmatter/1.0.0:
resolution: {integrity: sha512-7itKvp0arEVNpCktOET/eLFAYaZ+0cNjVtFtIPxgQ5tV+3i+D4SDDTjTzPWl44LT59PC+xdx+glNTawBdF98Mw==}
dependencies:
micromark-extension-frontmatter: 1.0.0
dev: true
/mdast-util-gfm-autolink-literal/1.0.2: /mdast-util-gfm-autolink-literal/1.0.2:
resolution: {integrity: sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==} resolution: {integrity: sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==}
dependencies: dependencies:
@@ -8933,6 +9084,14 @@ packages:
uvu: 0.5.6 uvu: 0.5.6
dev: true dev: true
/micromark-extension-frontmatter/1.0.0:
resolution: {integrity: sha512-EXjmRnupoX6yYuUJSQhrQ9ggK0iQtQlpi6xeJzVD5xscyAI+giqco5fdymayZhJMbIFecjnE2yz85S9NzIgQpg==}
dependencies:
fault: 2.0.1
micromark-util-character: 1.1.0
micromark-util-symbol: 1.0.1
dev: true
/micromark-extension-gfm-autolink-literal/1.0.3: /micromark-extension-gfm-autolink-literal/1.0.3:
resolution: {integrity: sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==} resolution: {integrity: sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==}
dependencies: dependencies:
@@ -9267,13 +9426,9 @@ packages:
ufo: 1.0.1 ufo: 1.0.1
dev: true dev: true
/moment-mini/2.29.4:
resolution: {integrity: sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg==}
dev: false
/moment/2.29.4: /moment/2.29.4:
resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==}
dev: true dev: false
/mri/1.2.0: /mri/1.2.0:
resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==}
@@ -10164,6 +10319,15 @@ packages:
jsesc: 0.5.0 jsesc: 0.5.0
dev: true dev: true
/remark-frontmatter/4.0.1:
resolution: {integrity: sha512-38fJrB0KnmD3E33a5jZC/5+gGAC2WKNiPw1/fdXJvijBlhA7RCsvJklrYJakS0HedninvaCYW8lQGf9C918GfA==}
dependencies:
'@types/mdast': 3.0.10
mdast-util-frontmatter: 1.0.0
micromark-extension-frontmatter: 1.0.0
unified: 10.1.2
dev: true
/remark-gfm/3.0.1: /remark-gfm/3.0.1:
resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==}
dependencies: dependencies:

View File

@@ -23,7 +23,7 @@ const lint = async (file: string): Promise<boolean> => {
return result.errorCount === 0; return result.errorCount === 0;
}; };
(async () => { const main = async () => {
const jisonFiles = await globby(['./packages/**/*.jison', '!./**/node_modules/**'], { const jisonFiles = await globby(['./packages/**/*.jison', '!./**/node_modules/**'], {
dot: true, dot: true,
}); });
@@ -31,4 +31,6 @@ const lint = async (file: string): Promise<boolean> => {
if (lintResults.includes(false)) { if (lintResults.includes(false)) {
process.exit(1); process.exit(1);
} }
})(); };
void main();

View File

@@ -13,8 +13,8 @@ const load = async () => {
await mermaid.registerExternalDiagrams([mindmap]); await mermaid.registerExternalDiagrams([mindmap]);
await render('info'); await render('info');
setTimeout(async () => { setTimeout(() => {
await render(`mindmap void render(`mindmap
root((mindmap)) root((mindmap))
Origins Origins
Long history Long history
@@ -35,4 +35,4 @@ const load = async () => {
}, 2500); }, 2500);
}; };
window.addEventListener('load', load, false); window.addEventListener('load', () => void load(), false);

9
tsconfig.eslint.json Normal file
View File

@@ -0,0 +1,9 @@
{
// extend your base config to share compilerOptions, etc
"extends": "./tsconfig.json",
"compilerOptions": {
// ensure that nobody can accidentally use this config for a build
"noEmit": true
},
"include": ["packages", "tests", "scripts", "cypress", "__mocks__", "./.eslintrc.cjs", "./*"]
}