diff --git a/.eslintignore b/.eslintignore index e1957aef9..04348c410 100644 --- a/.eslintignore +++ b/.eslintignore @@ -3,4 +3,5 @@ dist/** docs/Setup.md cypress.config.js cypress/plugins/index.js -coverage \ No newline at end of file +coverage +*.json \ No newline at end of file diff --git a/.eslintrc.cjs b/.eslintrc.cjs new file mode 100644 index 000000000..e6f99a8bf --- /dev/null +++ b/.eslintrc.cjs @@ -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, + }, + }, + ], +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 9d7eacecd..000000000 --- a/.eslintrc.json +++ /dev/null @@ -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" - } - } - ] -} diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index a3427c2f4..b7e5d38d9 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,6 +1,8 @@ # These are supported funding model platforms -github: [knsv] +github: + - knsv + - sidharthv96 #patreon: # Replace with a single Patreon username #open_collective: # Replace with a single Open Collective username #ko_fi: # Replace with a single Ko-fi username diff --git a/.github/pull_request_template.md b/.github/pull_request_template.md index 41d9c4cff..3574c3599 100644 --- a/.github/pull_request_template.md +++ b/.github/pull_request_template.md @@ -14,4 +14,5 @@ Make sure you - [ ] :book: have read the [contribution guidelines](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) - [ ] :computer: have added unit/e2e tests (if appropriate) +- [ ] :notebook: have added documentation (if appropriate) - [ ] :bookmark: targeted `develop` branch diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 845c763e8..34b14c395 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,4 +17,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v3 - name: 'Dependency Review' - uses: actions/dependency-review-action@v2 + uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/e2e.yml b/.github/workflows/e2e.yml index 55e06f46d..aff5852db 100644 --- a/.github/workflows/e2e.yml +++ b/.github/workflows/e2e.yml @@ -32,6 +32,7 @@ jobs: # and run all Cypress tests - name: Cypress run uses: cypress-io/github-action@v4 + id: cypress # If CYPRESS_RECORD_KEY is set, run in parallel on all containers # Otherwise (e.g. if running from fork), we run on a single container only if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }} @@ -44,3 +45,10 @@ jobs: parallel: ${{ secrets.CYPRESS_RECORD_KEY != '' }} env: CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }} + + - name: Upload Artifacts + uses: actions/upload-artifact@v3 + if: ${{ failure() && steps.cypress.conclusion == 'failure' }} + with: + name: error-snapshots + path: cypress/snapshots/**/__diff_output__/* diff --git a/.github/workflows/link-checker.yml b/.github/workflows/link-checker.yml index fbf03cb39..566548ecf 100644 --- a/.github/workflows/link-checker.yml +++ b/.github/workflows/link-checker.yml @@ -13,11 +13,10 @@ on: - master pull_request: branches: - - develop - master schedule: # * is a special character in YAML so you have to quote this string - - cron: '30 8 * * 5' + - cron: '30 8 * * *' jobs: linkChecker: diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index 8ba06989d..a21fbc005 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -7,9 +7,10 @@ on: - opened - synchronize - ready_for_review + workflow_dispatch: permissions: - contents: read + contents: write jobs: lint: @@ -36,8 +37,35 @@ jobs: CYPRESS_CACHE_FOLDER: .cache/Cypress - 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 + id: verifyDocs working-directory: ./packages/mermaid + continue-on-error: ${{ github.event_name == 'push' }} run: pnpm run docs:verify + + - name: Rebuild Docs + if: ${{ steps.verifyDocs.outcome == 'failure' && github.event_name == 'push' }} + working-directory: ./packages/mermaid + run: pnpm run docs:build + + - name: Commit changes + uses: EndBug/add-and-commit@v9 + if: ${{ steps.verifyDocs.outcome == 'failure' && github.event_name == 'push' }} + with: + message: 'Update docs' + add: 'docs/*' diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 107743c6a..0a53c6e42 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -8,6 +8,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Label PR - uses: TimonVS/pr-labeler-action@v3 + uses: TimonVS/pr-labeler-action@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 85bc79f6e..cdd6167b7 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -5,6 +5,7 @@ on: push: branches: - master + pull_request: # Allows you to run this workflow manually from the Actions tab workflow_dispatch: @@ -40,7 +41,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Setup Pages - uses: actions/configure-pages@v2 + uses: actions/configure-pages@v3 - name: Run Build run: pnpm --filter mermaid run docs:build:vitepress @@ -52,6 +53,7 @@ jobs: # Deployment job deploy: + if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }} environment: name: github-pages runs-on: ubuntu-latest diff --git a/.github/workflows/release-publish.yml b/.github/workflows/release-publish.yml index 6f0806de1..28094453e 100644 --- a/.github/workflows/release-publish.yml +++ b/.github/workflows/release-publish.yml @@ -11,18 +11,21 @@ jobs: - uses: actions/checkout@v3 - uses: fregante/setup-git-user@v1 - - name: Setup Node.js + - uses: pnpm/action-setup@v2 + # uses version from "packageManager" field in package.json + + - name: Setup Node.js v18 uses: actions/setup-node@v3 with: + cache: pnpm node-version: 18.x - - name: Install Yarn - run: npm i yarn --global - - - name: Install Json - run: npm i json --global - name: Install Packages - run: yarn install --frozen-lockfile + run: | + pnpm install --frozen-lockfile + npm i json --global + env: + CYPRESS_CACHE_FOLDER: .cache/Cypress - name: Prepare release run: | @@ -31,7 +34,7 @@ jobs: git checkout -t origin/release/$VERSION npm version --no-git-tag-version --allow-same-version $VERSION git add package.json - git commit -m "Bump version $VERSION" + git commit -nm "Bump version $VERSION" git checkout -t origin/master git merge -m "Release $VERSION" --no-ff release/$VERSION git push --no-verify diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs index ff1d8c107..ac2623093 100644 --- a/.lintstagedrc.mjs +++ b/.lintstagedrc.mjs @@ -1,5 +1,11 @@ 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'], '**/*.jison': ['pnpm -w run lint:jison'], }; diff --git a/.lycheeignore b/.lycheeignore index 767906b16..79cf4428b 100644 --- a/.lycheeignore +++ b/.lycheeignore @@ -9,5 +9,8 @@ https://mkdocs.org/ https://osawards.com/javascript/#nominees https://osawards.com/javascript/2019 +# Timeout error, maybe Twitter has anti-bot defences against GitHub's CI servers? +https://twitter.com/mermaidjs_ + # Don't check files that are generated during the build via `pnpm docs:code` packages/mermaid/src/docs/config/setup/* diff --git a/.percy.yml b/.percy.yml deleted file mode 100644 index f56df3d5e..000000000 --- a/.percy.yml +++ /dev/null @@ -1,6 +0,0 @@ -version: 2 -snapshot: - widths: - - 1280 -discovery: - disable-cache: true diff --git a/.tern-project b/.tern-project deleted file mode 100644 index 1209b33da..000000000 --- a/.tern-project +++ /dev/null @@ -1,15 +0,0 @@ -{ - "ecmaVersion": 6, - "libs": ["browser"], - "loadEagerly": [], - "dontLoad": ["node_modules/**"], - "plugins": { - "modules": {}, - "es_modules": {}, - "node": {}, - "doc_comment": { - "fullDocs": true, - "strong": true - } - } -} diff --git a/.vite/build.ts b/.vite/build.ts index 1be46ad5a..268db3270 100644 --- a/.vite/build.ts +++ b/.vite/build.ts @@ -20,13 +20,14 @@ const visualizerOptions = (packageName: string, core = false): PluginOption[] => if (packageName !== 'mermaid' || !visualize) { return []; } - return ['network', 'treemap', 'sunburst'].map((chartType) => - visualizer({ - filename: `./stats/${chartType}${core ? '.core' : ''}.html`, - template: chartType as TemplateType, - gzipSize: true, - brotliSize: true, - }) + return ['network', 'treemap', 'sunburst'].map( + (chartType) => + visualizer({ + filename: `./stats/${chartType}${core ? '.core' : ''}.html`, + template: chartType as TemplateType, + gzipSize: true, + brotliSize: true, + }) as PluginOption ); }; @@ -36,16 +37,11 @@ const packageOptions = { packageName: 'mermaid', file: 'mermaid.ts', }, - 'mermaid-mindmap': { - name: 'mermaid-mindmap', - packageName: 'mermaid-mindmap', + 'mermaid-example-diagram': { + name: 'mermaid-example-diagram', + packageName: 'mermaid-example-diagram', file: 'detector.ts', }, - // 'mermaid-example-diagram-detector': { - // name: 'mermaid-example-diagram-detector', - // packageName: 'mermaid-example-diagram', - // file: 'detector.ts', - // }, }; interface BuildOptions { @@ -66,12 +62,6 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions) sourcemap: true, entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`, }, - { - name, - format: 'umd', - sourcemap: true, - entryFileNames: `${name}${minify ? '.min' : ''}.js`, - }, ]; if (core) { @@ -119,11 +109,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions) if (watch && config.build) { config.build.watch = { - include: [ - 'packages/mermaid-mindmap/src/**', - 'packages/mermaid/src/**', - // 'packages/mermaid-example-diagram/src/**', - ], + include: ['packages/mermaid-example-diagram/src/**', 'packages/mermaid/src/**'], }; } @@ -131,11 +117,9 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions) }; const buildPackage = async (entryName: keyof typeof packageOptions) => { - return Promise.allSettled([ - build(getBuildConfig({ minify: false, entryName })), - build(getBuildConfig({ minify: 'esbuild', entryName })), - build(getBuildConfig({ minify: false, core: true, entryName })), - ]); + await build(getBuildConfig({ minify: false, entryName })); + await build(getBuildConfig({ minify: 'esbuild', entryName })); + await build(getBuildConfig({ minify: false, core: true, entryName })); }; const main = async () => { @@ -146,10 +130,9 @@ const main = async () => { }; if (watch) { - build(getBuildConfig({ minify: false, watch, core: true, entryName: 'mermaid' })); + build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' })); if (!mermaidOnly) { - build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' })); - // build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' })); + build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' })); } } else if (visualize) { await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' })); diff --git a/.vite/server.ts b/.vite/server.ts index ddd6afdd6..82b75232d 100644 --- a/.vite/server.ts +++ b/.vite/server.ts @@ -1,14 +1,6 @@ -import express, { NextFunction, Request, Response } from 'express'; +import express from 'express'; +import cors from 'cors'; import { createServer as createViteServer } from 'vite'; -// import { getBuildConfig } from './build'; - -const cors = (req: Request, res: Response, next: NextFunction) => { - res.header('Access-Control-Allow-Origin', '*'); - res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); - res.header('Access-Control-Allow-Headers', 'Content-Type'); - - next(); -}; async function createServer() { const app = express(); @@ -21,10 +13,9 @@ async function createServer() { appType: 'custom', // don't include Vite's default HTML handling middlewares }); - app.use(cors); + app.use(cors()); app.use(express.static('./packages/mermaid/dist')); app.use(express.static('./packages/mermaid-example-diagram/dist')); - app.use(express.static('./packages/mermaid-mindmap/dist')); app.use(vite.middlewares); app.use(express.static('demos')); app.use(express.static('cypress/platform')); @@ -34,5 +25,4 @@ async function createServer() { }); } -// build(getBuildConfig({ minify: false, watch: true })); createServer(); diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index cf199c39b..b0320b36e 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -63,6 +63,28 @@ flowchart LR ``` +You can use `note`, `tip`, `warning` and `danger` in triple backticks to add a note, tip, warning or danger box. +Do not use vitepress specific markdown syntax `::: warning` as it will not be processed correctly. + +```` +```note +Note content +``` + +```tip +Tip content +``` + +```warning +Warning content +``` + +```danger +Danger content +``` + +```` + **_DO NOT CHANGE FILES IN `/docs`_** ### The official documentation site diff --git a/README.md b/README.md index 059940a02..f4cf8e105 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,37 @@ -# mermaid +

+ +

+

+Mermaid +

+

+Generate diagrams from markdown-like text. +

+

+ +

-[![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_) +

+Live Editor! +

+

+ 📖 Documentation | 🚀 Getting Started | 🌐 CDN | 🙌 Join Us +

+

+简体中文 +

-English | [简体中文](./README.zh-CN.md) +
+
+ +[![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/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 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 Downloads](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_) @@ -10,7 +39,7 @@ English | [简体中文](./README.zh-CN.md) **Thanks to all involved, people committing pull requests, people answering questions! 🙏** -Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! +Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! ## About @@ -27,14 +56,12 @@ Mermaid addresses this problem by enabling users to create easily modifiable dia Mermaid allows even non-programmers to easily create detailed diagrams through the [Mermaid Live Editor](https://mermaid.live/).
[Tutorials](./docs/config/Tutorials.md) has video tutorials. -Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](./docs/misc/integrations.md). +Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations.md). -You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/misc/integrations.md). +You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations.md). For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/community/n00b-overview.md), [Usage](./docs/config/usage.md) and [Tutorials](./docs/config/Tutorials.md). -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) | 📜 [Changelog](./docs/CHANGELOG.md) - In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests. diff --git a/README.zh-CN.md b/README.zh-CN.md index 4bdbc4ae7..65cf38645 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,8 +1,37 @@ -# mermaid +

+ +

+

+Mermaid +

+

+通过解析类 Markdown 的文本语法来实现图表的创建和动态修改。 +

+

+ +

-[![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_) +

+Live Editor! +

+

+ 📖 文档 | 🚀 入门 | 🌐 CDN | 🙌 加入我们 +

+

+English +

-[English](./README.md) | 简体中文 +
+
+ +[![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/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 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 Downloads](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_) @@ -10,7 +39,7 @@ **感谢所有参与进来提交 PR,解答疑问的人们! 🙏** -Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! +Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! ## 关于 Mermaid @@ -24,12 +53,10 @@ Mermaid 是一个基于 Javascript 的图表绘制工具,通过解析类 Markd Mermaid 通过允许用户创建便于修改的图表来解决这一难题,它也可以作为生产脚本(或其他代码)的一部分。

Mermaid 甚至能让非程序员也能通过 [Mermaid Live Editor](https://mermaid.live/) 轻松创建详细的图表。
-你可以访问 [教程](./docs/config/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/misc/integrations.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。 +你可以访问 [教程](./docs/config/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/ecosystem/integrations.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。 如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/community/n00b-overview.md), [用法](./docs/config/usage.md) 和 [教程](./docs/config/Tutorials.md). -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [文档](https://mermaidjs.github.io) | 🙌 [贡献](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) | 📜 [更新日志](./docs/CHANGELOG.md) - ## 示例 @@ -325,7 +352,7 @@ _很不幸的是,鱼与熊掌不可兼得,在这个场景下它意味着在 来自 Knut Sveidqvist: -> _特别感谢 [d3](https://d3js.org/) 和 [dagre-d3](https://github.com/cpettitt/dagre-d3) 这两个优秀的项目,它们提供了图形布局和绘图工具库! _ >_同样感谢 [js-sequence-diagram](https://bramp.github.io/js-sequence-diagrams) 提供了时序图语法的使用。 感谢 Jessica Peter 提供了甘特图渲染的灵感。_ >_感谢 [Tyler Long](https://github.com/tylerlong) 从 2017 年四月开始成为了项目的合作者。_ +> _特别感谢 [d3](https://d3js.org/) 和 [dagre-d3](https://github.com/cpettitt/dagre-d3) 这两个优秀的项目,它们提供了图形布局和绘图工具库!_ > _同样感谢 [js-sequence-diagram](https://bramp.github.io/js-sequence-diagrams) 提供了时序图语法的使用。 感谢 Jessica Peter 提供了甘特图渲染的灵感。_ > _感谢 [Tyler Long](https://github.com/tylerlong) 从 2017 年四月开始成为了项目的合作者。_ > > _感谢越来越多的 [贡献者们](https://github.com/knsv/mermaid/graphs/contributors),没有你们,就没有这个项目的今天!_ diff --git a/V10-BreakingChanges.md b/V10-BreakingChanges.md index bd9110d1a..121fdd596 100644 --- a/V10-BreakingChanges.md +++ b/V10-BreakingChanges.md @@ -1,5 +1,58 @@ -# A collection of updates that change the behaviour +# A collection of updates that change the behavior + +## Async + +`parse`, `render` are now async. ## Lazy loading and asynchronisity - Invalid dates are rendered as syntax error instead of returning best guess or the current date + +## ParseError is removed + +```js +//< v10.0.0 +mermaid.parse(text, parseError); + +//>= v10.0.0 +await mermaid.parse(text).catch(parseError); +// or +try { + await mermaid.parse(text); +} catch (err) { + parseError(err); +} +``` + +## Init deprecated and InitThrowsErrors removed + +The config passed to `init` was not being used eariler. +It will now be used. +The `init` function is deprecated and will be removed in the next major release. +init currently works as a wrapper to `initialize` and `run`. + +```js +//< v10.0.0 +mermaid.init(config, selector, cb); + +//>= v10.0.0 +mermaid.initialize(config); +mermaid.run({ + querySelector: selector, + postRenderCallback: cb, + suppressErrors: true, +}); +``` + +```js +//< v10.0.0 +mermaid.initThrowsErrors(config, selector, cb); + +//>= v10.0.0 +mermaid.initialize(config); +mermaid.run({ + querySelector: selector, + postRenderCallback: cb, + suppressErrors: false, +}); +``` diff --git a/__mocks__/c4Renderer.js b/__mocks__/c4Renderer.js new file mode 100644 index 000000000..576d5d863 --- /dev/null +++ b/__mocks__/c4Renderer.js @@ -0,0 +1,21 @@ +/** + * Mocked C4Context diagram renderer + */ + +import { vi } from 'vitest'; + +export const drawPersonOrSystemArray = vi.fn(); +export const drawBoundary = vi.fn(); + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + drawPersonOrSystemArray, + drawBoundary, + setConf, + draw, +}; diff --git a/__mocks__/classRenderer-v2.js b/__mocks__/classRenderer-v2.js new file mode 100644 index 000000000..1ad95806f --- /dev/null +++ b/__mocks__/classRenderer-v2.js @@ -0,0 +1,16 @@ +/** + * Mocked class diagram v2 renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/classRenderer.js b/__mocks__/classRenderer.js new file mode 100644 index 000000000..1c20de4b1 --- /dev/null +++ b/__mocks__/classRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked class diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/d3.ts b/__mocks__/d3.ts index f90d93557..af35020c5 100644 --- a/__mocks__/d3.ts +++ b/__mocks__/d3.ts @@ -1,79 +1,14 @@ // @ts-nocheck TODO: Fix TS -import { vi } from 'vitest'; - -const NewD3 = function () { - /** - * - */ - function returnThis() { - return this; - } - return { - append: function () { - return NewD3(); - }, - lower: returnThis, - attr: returnThis, - style: returnThis, - text: returnThis, - 0: { - 0: { - getBBox: function () { - return { - height: 10, - width: 20, - }; - }, - }, - }, - }; -}; +import { MockedD3 } from '../packages/mermaid/src/tests/MockedD3'; export const select = function () { - return new NewD3(); + return new MockedD3(); }; export const selectAll = function () { - return new NewD3(); + return new MockedD3(); }; export const curveBasis = 'basis'; export const curveLinear = 'linear'; export const curveCardinal = 'cardinal'; - -export const MockD3 = (name, parent) => { - const children = []; - const elem = { - get __children() { - return children; - }, - get __name() { - return name; - }, - get __parent() { - return parent; - }, - node() { - return { - getBBox() { - return { - x: 5, - y: 10, - height: 15, - width: 20, - }; - }, - }; - }, - }; - elem.append = (name) => { - const mockElem = MockD3(name, elem); - children.push(mockElem); - return mockElem; - }; - elem.lower = vi.fn(() => elem); - elem.attr = vi.fn(() => elem); - elem.text = vi.fn(() => elem); - elem.style = vi.fn(() => elem); - return elem; -}; diff --git a/__mocks__/erRenderer.js b/__mocks__/erRenderer.js new file mode 100644 index 000000000..845d641f7 --- /dev/null +++ b/__mocks__/erRenderer.js @@ -0,0 +1,16 @@ +/** + * Mocked er diagram renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/flowRenderer-v2.js b/__mocks__/flowRenderer-v2.js new file mode 100644 index 000000000..89cc86031 --- /dev/null +++ b/__mocks__/flowRenderer-v2.js @@ -0,0 +1,24 @@ +/** + * Mocked flow (flowchart) diagram v2 renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); +export const addVertices = vi.fn(); +export const addEdges = vi.fn(); +export const getClasses = vi.fn().mockImplementation(() => { + return {}; +}); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + addVertices, + addEdges, + getClasses, + draw, +}; diff --git a/__mocks__/ganttRenderer.js b/__mocks__/ganttRenderer.js new file mode 100644 index 000000000..957249832 --- /dev/null +++ b/__mocks__/ganttRenderer.js @@ -0,0 +1,16 @@ +/** + * Mocked gantt diagram renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/gitGraphRenderer.js b/__mocks__/gitGraphRenderer.js new file mode 100644 index 000000000..1daa82ca4 --- /dev/null +++ b/__mocks__/gitGraphRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked git (graph) diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/journeyRenderer.js b/__mocks__/journeyRenderer.js new file mode 100644 index 000000000..2bc77c0b1 --- /dev/null +++ b/__mocks__/journeyRenderer.js @@ -0,0 +1,15 @@ +/** + * Mocked pie (picChart) diagram renderer + */ + +import { vi } from 'vitest'; +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + draw, +}; diff --git a/__mocks__/pieRenderer.js b/__mocks__/pieRenderer.js new file mode 100644 index 000000000..317c69901 --- /dev/null +++ b/__mocks__/pieRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked pie (picChart) diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/requirementRenderer.js b/__mocks__/requirementRenderer.js new file mode 100644 index 000000000..48d8997ac --- /dev/null +++ b/__mocks__/requirementRenderer.js @@ -0,0 +1,13 @@ +/** + * Mocked requirement diagram renderer + */ + +import { vi } from 'vitest'; + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + draw, +}; diff --git a/__mocks__/sequenceRenderer.js b/__mocks__/sequenceRenderer.js new file mode 100644 index 000000000..11080c6bb --- /dev/null +++ b/__mocks__/sequenceRenderer.js @@ -0,0 +1,23 @@ +/** + * Mocked sequence diagram renderer + */ + +import { vi } from 'vitest'; + +export const bounds = vi.fn(); +export const drawActors = vi.fn(); +export const drawActorsPopup = vi.fn(); + +export const setConf = vi.fn(); + +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + bounds, + drawActors, + drawActorsPopup, + setConf, + draw, +}; diff --git a/__mocks__/stateRenderer-v2.js b/__mocks__/stateRenderer-v2.js new file mode 100644 index 000000000..a2d103b50 --- /dev/null +++ b/__mocks__/stateRenderer-v2.js @@ -0,0 +1,22 @@ +/** + * Mocked state diagram v2 renderer + */ + +import { vi } from 'vitest'; + +export const setConf = vi.fn(); +export const getClasses = vi.fn().mockImplementation(() => { + return {}; +}); +export const stateDomId = vi.fn().mockImplementation(() => { + return 'mocked-stateDiagram-stateDomId'; +}); +export const draw = vi.fn().mockImplementation(() => { + return ''; +}); + +export default { + setConf, + getClasses, + draw, +}; diff --git a/cSpell.json b/cSpell.json index 64187e1ca..6f93af103 100644 --- a/cSpell.json +++ b/cSpell.json @@ -6,6 +6,7 @@ "adamiecki", "alois", "antiscript", + "appli", "applitools", "asciidoctor", "ashish", @@ -13,7 +14,9 @@ "bbox", "bilkent", "bisheng", + "blrs", "braintree", + "brkt", "brolin", "brotli", "classdef", @@ -30,6 +33,7 @@ "doku", "dompurify", "edgechromium", + "elkjs", "faber", "flatmap", "ftplugin", @@ -38,6 +42,7 @@ "gitgraph", "globby", "graphlib", + "graphviz", "grav", "greywolf", "inkdrop", @@ -51,8 +56,10 @@ "knut", "laganeckas", "lintstagedrc", + "logmsg", "lucida", "matthieu", + "mdast", "mdbook", "mermerd", "mindaugas", @@ -60,26 +67,38 @@ "mindmaps", "mitigations", "mkdocs", + "mult", "orlandoni", "phpbb", "plantuml", "playfair", + "pnpm", "podlite", + "quence", + "radious", "ranksep", + "rect", + "rects", "redmine", + "roledescription", "sandboxed", "setupgraphviewbox", "shiki", "sidharth", + "sidharthv", "sphinxcontrib", "statediagram", "stylis", "substate", "sveidqvist", + "swimm", "techn", + "teststr", + "textlength", "treemap", "ts-nocheck", "tuleap", + "ugge", "unist", "verdana", "viewports", diff --git a/cypress/helpers/util.js b/cypress/helpers/util.js index 5213f634a..533cca499 100644 --- a/cypress/helpers/util.js +++ b/cypress/helpers/util.js @@ -2,7 +2,7 @@ const utf8ToB64 = (str) => { return window.btoa(unescape(encodeURIComponent(str))); }; -const batchId = 'mermid-batch' + new Date().getTime(); +const batchId = 'mermaid-batch' + new Date().getTime(); export const mermaidUrl = (graphStr, options, api) => { const obj = { @@ -46,8 +46,22 @@ export const imgSnapshotTest = (graphStr, _options, api = false, validation) => if (!options.fontSize) { options.fontSize = '16px'; } + const url = mermaidUrl(graphStr, options, api); + openURLAndVerifyRendering(url, options, validation); +}; + +export const urlSnapshotTest = (url, _options, api = false, validation) => { + const options = Object.assign(_options); + openURLAndVerifyRendering(url, options, validation); +}; + +export const renderGraph = (graphStr, options, api) => { + const url = mermaidUrl(graphStr, options, api); + openURLAndVerifyRendering(url, options); +}; + +const openURLAndVerifyRendering = (url, options, validation = undefined) => { const useAppli = Cypress.env('useAppli'); - cy.log('Hello ' + useAppli ? 'Appli' : 'image-snapshot'); const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-'); if (useAppli) { @@ -60,82 +74,20 @@ export const imgSnapshotTest = (graphStr, _options, api = false, validation) => }); } - const url = mermaidUrl(graphStr, options, api); - cy.visit(url); + cy.window().should('have.property', 'rendered', true); + cy.get('svg').should('be.visible'); + if (validation) { cy.get('svg').should(validation); } - cy.get('svg'); - // Default name to test title if (useAppli) { cy.log('Check eyes' + Cypress.spec.name); cy.eyesCheckWindow('Click!'); - cy.log('Closing eyes: ' + Cypress.spec.name); + cy.log('Closing eyes' + Cypress.spec.name); cy.eyesClose(); } else { cy.matchImageSnapshot(name); } }; - -export const urlSnapshotTest = (url, _options, api = false, validation) => { - cy.log(_options); - const options = Object.assign(_options); - if (!options.fontFamily) { - options.fontFamily = 'courier'; - } - if (!options.sequence) { - options.sequence = {}; - } - if (!options.sequence || (options.sequence && !options.sequence.actorFontFamily)) { - options.sequence.actorFontFamily = 'courier'; - } - if (options.sequence && !options.sequence.noteFontFamily) { - options.sequence.noteFontFamily = 'courier'; - } - options.sequence.actorFontFamily = 'courier'; - options.sequence.noteFontFamily = 'courier'; - options.sequence.messageFontFamily = 'courier'; - if (options.sequence && !options.sequence.actorFontFamily) { - options.sequence.actorFontFamily = 'courier'; - } - if (!options.fontSize) { - options.fontSize = '16px'; - } - const useAppli = Cypress.env('useAppli'); - cy.log('Hello ' + useAppli ? 'Appli' : 'image-snapshot'); - const name = (options.name || cy.state('runnable').fullTitle()).replace(/\s+/g, '-'); - - if (useAppli) { - cy.log('Opening eyes 2' + Cypress.spec.name); - cy.eyesOpen({ - appName: 'Mermaid', - testName: name, - batchName: Cypress.spec.name, - batchId: batchId + Cypress.spec.name, - }); - } - - cy.visit(url); - if (validation) { - cy.get('svg').should(validation); - } - cy.get('body'); - // Default name to test title - - if (useAppli) { - cy.log('Check eyes 2' + Cypress.spec.name); - cy.eyesCheckWindow('Click!'); - cy.log('Closing eyes 2' + Cypress.spec.name); - cy.eyesClose(); - } else { - cy.matchImageSnapshot(name); - } -}; - -export const renderGraph = (graphStr, options, api) => { - const url = mermaidUrl(graphStr, options, api); - - cy.visit(url); -}; diff --git a/cypress/integration/other/external-diagrams.spec.js b/cypress/integration/other/external-diagrams.spec.js index 3a6c37e88..c94235162 100644 --- a/cypress/integration/other/external-diagrams.spec.js +++ b/cypress/integration/other/external-diagrams.spec.js @@ -1,13 +1,10 @@ +import { urlSnapshotTest } from '../../helpers/util'; + describe('mermaid', () => { describe('registerDiagram', () => { - it('should work on @mermaid-js/mermaid-mindmap and mermaid-example-diagram', () => { - const url = 'http://localhost:9000/external-diagrams-mindmap.html'; - cy.visit(url); - - cy.get('svg', { - // may be a bit slower than normal, since vite might need to re-compile mermaid/mermaid-mindmap/mermaid-example-diagram - timeout: 10000, - }).matchImageSnapshot(); + it('should work on @mermaid-js/mermaid-example-diagram', () => { + const url = 'http://localhost:9000/external-diagrams-example-diagram.html'; + urlSnapshotTest(url, {}, false, false); }); }); }); diff --git a/cypress/integration/other/ghsa.spec.js b/cypress/integration/other/ghsa.spec.js index 5b168a8a8..4fadc7855 100644 --- a/cypress/integration/other/ghsa.spec.js +++ b/cypress/integration/other/ghsa.spec.js @@ -7,4 +7,10 @@ describe('CSS injections', () => { flowchart: { htmlLabels: false }, }); }); + it('should not allow adding styletags affecting the page', () => { + urlSnapshotTest('http://localhost:9000/ghsa3.html', { + logLevel: 1, + flowchart: { htmlLabels: false }, + }); + }); }); diff --git a/cypress/integration/rendering/c4.spec.js b/cypress/integration/rendering/c4.spec.js new file mode 100644 index 000000000..0cf128ff6 --- /dev/null +++ b/cypress/integration/rendering/c4.spec.js @@ -0,0 +1,122 @@ +import { imgSnapshotTest, renderGraph } from '../../helpers/util.js'; + +describe('C4 diagram', () => { + it('should render a simple C4Context diagram', () => { + imgSnapshotTest( + ` + C4Context + accTitle: C4 context demo + accDescr: Many large C4 diagrams + + title System Context diagram for Internet Banking System + + Enterprise_Boundary(b0, "BankBoundary0") { + Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.") + + System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.") + + Enterprise_Boundary(b1, "BankBoundary") { + System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") + } + } + + BiRel(customerA, SystemAA, "Uses") + Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") + Rel(SystemC, customerA, "Sends e-mails to") + + UpdateElementStyle(customerA, $fontColor="red", $bgColor="grey", $borderColor="red") + UpdateRelStyle(customerA, SystemAA, $textColor="blue", $lineColor="blue", $offsetX="5") + UpdateRelStyle(SystemC, customerA, $textColor="red", $lineColor="red", $offsetX="-50", $offsetY="20") + `, + {} + ); + cy.get('svg'); + }); + it('should render a simple C4Container diagram', () => { + imgSnapshotTest( + ` + C4Container + title Container diagram for Internet Banking System + + System_Ext(email_system, "E-Mail System", "The internal Microsoft Exchange system", $tags="v1.0") + Person(customer, Customer, "A customer of the bank, with personal bank accounts", $tags="v1.0") + + Container_Boundary(c1, "Internet Banking") { + Container(spa, "Single-Page App", "JavaScript, Angular", "Provides all the Internet banking functionality to customers via their web browser") + } + + Rel(customer, spa, "Uses", "HTTPS") + Rel(email_system, customer, "Sends e-mails to") + `, + {} + ); + cy.get('svg'); + }); + it('should render a simple C4Component diagram', () => { + imgSnapshotTest( + ` + C4Component + title Component diagram for Internet Banking System - API Application + + Container(spa, "Single Page Application", "javascript and angular", "Provides all the internet banking functionality to customers via their web browser.") + + Container_Boundary(api, "API Application") { + Component(sign, "Sign In Controller", "MVC Rest Controller", "Allows users to sign in to the internet banking system") + } + + Rel_Back(spa, sign, "Uses", "JSON/HTTPS") + UpdateRelStyle(spa, sign, $offsetY="-40") + `, + {} + ); + cy.get('svg'); + }); + it('should render a simple C4Dynamic diagram', () => { + imgSnapshotTest( + ` + C4Dynamic + title Dynamic diagram for Internet Banking System - API Application + + ContainerDb(c4, "Database", "Relational Database Schema", "Stores user registration information, hashed authentication credentials, access logs, etc.") + Container(c1, "Single-Page Application", "JavaScript and Angular", "Provides all of the Internet banking functionality to customers via their web browser.") + Container_Boundary(b, "API Application") { + Component(c3, "Security Component", "Spring Bean", "Provides functionality Related to signing in, changing passwords, etc.") + Component(c2, "Sign In Controller", "Spring MVC Rest Controller", "Allows users to sign in to the Internet Banking System.") + } + Rel(c1, c2, "Submits credentials to", "JSON/HTTPS") + Rel(c2, c3, "Calls isAuthenticated() on") + Rel(c3, c4, "select * from users where username = ?", "JDBC") + + UpdateRelStyle(c1, c2, $textColor="red", $offsetY="-40") + UpdateRelStyle(c2, c3, $textColor="red", $offsetX="-40", $offsetY="60") + UpdateRelStyle(c3, c4, $textColor="red", $offsetY="-40", $offsetX="10") + `, + {} + ); + cy.get('svg'); + }); + it('should render a simple C4Deployment diagram', () => { + imgSnapshotTest( + ` + C4Deployment + title Deployment Diagram for Internet Banking System - Live + + Deployment_Node(mob, "Customer's mobile device", "Apple IOS or Android"){ + Container(mobile, "Mobile App", "Xamarin", "Provides a limited subset of the Internet Banking functionality to customers via their mobile device.") + } + + Deployment_Node(plc, "Big Bank plc", "Big Bank plc data center"){ + Deployment_Node(dn, "bigbank-api*** x8", "Ubuntu 16.04 LTS"){ + Deployment_Node(apache, "Apache Tomcat", "Apache Tomcat 8.x"){ + Container(api, "API Application", "Java and Spring MVC", "Provides Internet Banking functionality via a JSON/HTTPS API.") + } + } + } + + Rel(mobile, api, "Makes API calls to", "json/HTTPS") + `, + {} + ); + cy.get('svg'); + }); +}); diff --git a/cypress/integration/rendering/classDiagram-v2.spec.js b/cypress/integration/rendering/classDiagram-v2.spec.js index f97458857..9536a074d 100644 --- a/cypress/integration/rendering/classDiagram-v2.spec.js +++ b/cypress/integration/rendering/classDiagram-v2.spec.js @@ -485,8 +485,7 @@ describe('Class diagram V2', () => { classDiagram-v2 note "I love this diagram!\nDo you love it?" class Class10 { - <> - int id + int id size() } note for Class10 "Cool class\nI said it's very cool class!" diff --git a/cypress/integration/rendering/classDiagram.spec.js b/cypress/integration/rendering/classDiagram.spec.js index 16601652d..e21be67ec 100644 --- a/cypress/integration/rendering/classDiagram.spec.js +++ b/cypress/integration/rendering/classDiagram.spec.js @@ -414,7 +414,6 @@ describe('Class diagram', () => { classDiagram note "I love this diagram!\nDo you love it?" class Class10 { - <> int id size() } diff --git a/cypress/integration/rendering/current.spec.js b/cypress/integration/rendering/current.spec.js index 56b5f774b..033752c66 100644 --- a/cypress/integration/rendering/current.spec.js +++ b/cypress/integration/rendering/current.spec.js @@ -1,6 +1,6 @@ import { imgSnapshotTest } from '../../helpers/util'; -describe('State diagram', () => { +describe('Current diagram', () => { it('should render a state with states in it', () => { imgSnapshotTest( ` diff --git a/cypress/integration/rendering/erDiagram.spec.js b/cypress/integration/rendering/erDiagram.spec.js index 8e8946170..df1fac0cd 100644 --- a/cypress/integration/rendering/erDiagram.spec.js +++ b/cypress/integration/rendering/erDiagram.spec.js @@ -182,6 +182,20 @@ describe('Entity Relationship Diagram', () => { cy.get('svg'); }); + it('should render entities with length in attributes type', () => { + renderGraph( + ` + erDiagram + CLUSTER { + varchar(99) name + string(255) description + } + `, + { logLevel: 1 } + ); + cy.get('svg'); + }); + it('should render entities and attributes with big and small entity names', () => { renderGraph( ` diff --git a/cypress/integration/rendering/flowchart-elk.spec.js b/cypress/integration/rendering/flowchart-elk.spec.js new file mode 100644 index 000000000..9b9c76378 --- /dev/null +++ b/cypress/integration/rendering/flowchart-elk.spec.js @@ -0,0 +1,687 @@ +import { imgSnapshotTest, renderGraph } from '../../helpers/util'; + +describe('Flowchart ELK', () => { + it('1-elk: should render a simple flowchart', () => { + imgSnapshotTest( + `flowchart-elk TD + A[Christmas] -->|Get money| B(Go shopping) + B --> C{Let me think} + C -->|One| D[Laptop] + C -->|Two| E[iPhone] + C -->|Three| F[fa:fa-car Car] + `, + {} + ); + imgSnapshotTest( + `flowchart TD + A[Christmas] -->|Get money| B(Go shopping) + B --> C{Let me think} + C -->|One| D[Laptop] + C -->|Two| E[iPhone] + C -->|Three| F[fa:fa-car Car] + `, + { flowchart: { defaultRenderer: 'elk' } } + ); + }); + + it('2-elk: should render a simple flowchart with diagramPadding set to 0', () => { + imgSnapshotTest( + `flowchart-elk TD + A[Christmas] -->|Get money| B(Go shopping) + B --> C{Let me think} + %% this is a comment + C -->|One| D[Laptop] + C -->|Two| E[iPhone] + C -->|Three| F[fa:fa-car Car] + `, + { flowchart: { diagramPadding: 0 } } + ); + }); + + it('3-elk: a link with correct arrowhead to a subgraph', () => { + imgSnapshotTest( + `flowchart-elk TD + P1 + P1 -->P1.5 + subgraph P1.5 + P2 + P2.5(( A )) + P3 + end + P2 --> P4 + P3 --> P6 + P1.5 --> P5 + `, + {} + ); + }); + + it('4-elk: Length of edges', () => { + imgSnapshotTest( + `flowchart-elk TD + L1 --- L2 + L2 --- C + M1 ---> C + R1 .-> R2 + R2 <.-> C + C -->|Label 1| E1 + C <-- Label 2 ---> E2 + C ----> E3 + C <-...-> E4 + C ======> E5 + `, + {} + ); + }); + it('5-elk: should render escaped without html labels', () => { + imgSnapshotTest( + `flowchart-elk TD + a["Haiya"]---->b + `, + { htmlLabels: false, flowchart: { htmlLabels: false } } + ); + }); + it('6-elk: should render non-escaped with html labels', () => { + imgSnapshotTest( + `flowchart-elk TD + a["Haiya"]===>b + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('7-elk: should render a flowchart when useMaxWidth is true (default)', () => { + renderGraph( + `flowchart-elk TD + A[Christmas] -->|Get money| B(Go shopping) + B --> C{Let me think} + C -->|One| D[Laptop] + C -->|Two| E[iPhone] + C -->|Three| F[fa:fa-car Car] + `, + { flowchart: { useMaxWidth: true } } + ); + cy.get('svg').should((svg) => { + expect(svg).to.have.attr('width', '100%'); + // expect(svg).to.have.attr('height'); + // use within because the absolute value can be slightly different depending on the environment ±5% + // const height = parseFloat(svg.attr('height')); + // expect(height).to.be.within(446 * 0.95, 446 * 1.05); + const style = svg.attr('style'); + expect(style).to.match(/^max-width: [\d.]+px;$/); + const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join('')); + expect(maxWidthValue).to.be.within(230 * 0.95, 230 * 1.05); + }); + }); + it('8-elk: should render a flowchart when useMaxWidth is false', () => { + renderGraph( + `flowchart-elk TD + A[Christmas] -->|Get money| B(Go shopping) + B --> C{Let me think} + C -->|One| D[Laptop] + C -->|Two| E[iPhone] + C -->|Three| F[fa:fa-car Car] + `, + { flowchart: { useMaxWidth: false } } + ); + cy.get('svg').should((svg) => { + // const height = parseFloat(svg.attr('height')); + const width = parseFloat(svg.attr('width')); + // use within because the absolute value can be slightly different depending on the environment ±5% + // expect(height).to.be.within(446 * 0.95, 446 * 1.05); + expect(width).to.be.within(230 * 0.95, 230 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); + + it.skip('V2 elk - 16: Render Stadium shape', () => { + imgSnapshotTest( + ` flowchart-elk TD + A([stadium shape test]) + A -->|Get money| B([Go shopping]) + B --> C([Let me think...
Do I want something for work,
something to spend every free second with,
or something to get around?]) + C -->|One| D([Laptop]) + C -->|Two| E([iPhone]) + C -->|Three| F([Car
wroom wroom]) + click A "index.html#link-clicked" "link test" + click B testClick "click test" + classDef someclass fill:#f96; + class A someclass; + class C someclass; + `, + { flowchart: { htmlLabels: false }, fontFamily: 'courier' } + ); + }); + + it('50-elk: handle nested subgraphs in reverse order', () => { + imgSnapshotTest( + `flowchart-elk LR + a -->b + subgraph A + B + end + subgraph B + b + end + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + + it('51-elk: handle nested subgraphs in reverse order', () => { + imgSnapshotTest( + `flowchart-elk LR + a -->b + subgraph A + B + end + subgraph B + b + end + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + + it('52-elk: handle nested subgraphs in several levels', () => { + imgSnapshotTest( + `flowchart-elk TB + b-->B + a-->c + subgraph O + A + end + subgraph B + c + end + subgraph A + a + b + B + end + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + + it('53-elk: handle nested subgraphs with edges in and out', () => { + imgSnapshotTest( + `flowchart-elk TB + internet + nat + routeur + lb1 + lb2 + compute1 + compute2 + subgraph project + routeur + nat + subgraph subnet1 + compute1 + lb1 + end + subgraph subnet2 + compute2 + lb2 + end + end + internet --> routeur + routeur --> subnet1 & subnet2 + subnet1 & subnet2 --> nat --> internet + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + + it('54-elk: handle nested subgraphs with outgoing links', () => { + imgSnapshotTest( + `flowchart-elk TD + subgraph main + subgraph subcontainer + subcontainer-child + end + subcontainer-child--> subcontainer-sibling + end + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + + it('55-elk: handle nested subgraphs with outgoing links 2', () => { + imgSnapshotTest( + `flowchart-elk TD + +subgraph one[One] + subgraph sub_one[Sub One] + _sub_one + end + subgraph sub_two[Sub Two] + _sub_two + end + _one +end + +%% here, either the first or the second one +sub_one --> sub_two +_one --> b + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + + it('56-elk: handle nested subgraphs with outgoing links 3', () => { + imgSnapshotTest( + `flowchart-elk TB + subgraph container_Beta + process_C-->Process_D + end + subgraph container_Alpha + process_A-->process_B + process_A-->|messages|process_C + end + process_B-->|via_AWSBatch|container_Beta + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it.skip('57-elk: handle nested subgraphs with outgoing links 4', () => { + imgSnapshotTest( + `flowchart-elk LR +subgraph A +a -->b +end +subgraph B +b +end + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + + it('57-elk: handle nested subgraphs with outgoing links 2', () => { + imgSnapshotTest( + `flowchart-elk TB + c1-->a2 + subgraph one + a1-->a2 + end + subgraph two + b1-->b2 + end + subgraph three + c1-->c2 + end + one --> two + three --> two + two --> c2 + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('57.x: handle nested subgraphs with outgoing links 5', () => { + imgSnapshotTest( + `%% this does not produce the desired result +flowchart-elk TB + subgraph container_Beta + process_C-->Process_D + end + subgraph container_Alpha + process_A-->process_B + process_B-->|via_AWSBatch|container_Beta + process_A-->|messages|process_C + end + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('58-elk: handle styling with style expressions', () => { + imgSnapshotTest( + ` + flowchart-elk LR + id1(Start)-->id2(Stop) + style id1 fill:#f9f,stroke:#333,stroke-width:4px + style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5 + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('59-elk: handle styling of subgraphs and links', () => { + imgSnapshotTest( + ` +flowchart-elk TD + A[Christmas] ==> D + A[Christmas] -->|Get money| B(Go shopping) + A[Christmas] ==> C + subgraph T ["Test"] + A + B + C + end + + classDef Test fill:#F84E68,stroke:#333,color:white; + class A,T Test + classDef TestSub fill:green; + class T TestSub + linkStyle 0,1 color:orange, stroke: orange; + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('60-elk: handle styling for all node shapes - v2', () => { + imgSnapshotTest( + ` + flowchart-elk LR + A[red text] -->|default style| B(blue text) + C([red text]) -->|default style| D[[blue text]] + E[(red text)] -->|default style| F((blue text)) + G>red text] -->|default style| H{blue text} + I{{red text}} -->|default style| J[/blue text/] + K[\\ red text\\] -->|default style| L[/blue text\\] + M[\\ red text/] -->|default style| N[blue text]; + O(((red text))) -->|default style| P(((blue text))); + linkStyle default color:Sienna; + style A stroke:#ff0000,fill:#ffcccc,color:#ff0000; + style B stroke:#0000ff,fill:#ccccff,color:#0000ff; + style C stroke:#ff0000,fill:#ffcccc,color:#ff0000; + style D stroke:#0000ff,fill:#ccccff,color:#0000ff; + style E stroke:#ff0000,fill:#ffcccc,color:#ff0000; + style F stroke:#0000ff,fill:#ccccff,color:#0000ff; + style G stroke:#ff0000,fill:#ffcccc,color:#ff0000; + style H stroke:#0000ff,fill:#ccccff,color:#0000ff; + style I stroke:#ff0000,fill:#ffcccc,color:#ff0000; + style J stroke:#0000ff,fill:#ccccff,color:#0000ff; + style K stroke:#ff0000,fill:#ffcccc,color:#ff0000; + style L stroke:#0000ff,fill:#ccccff,color:#0000ff; + style M stroke:#ff0000,fill:#ffcccc,color:#ff0000; + style N stroke:#0000ff,fill:#ccccff,color:#0000ff; + style O stroke:#ff0000,fill:#ffcccc,color:#ff0000; + style P stroke:#0000ff,fill:#ccccff,color:#0000ff; + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose', logLevel: 2 } + ); + }); + it('61-elk: fontawesome icons in edge labels', () => { + imgSnapshotTest( + ` + flowchart-elk TD + C -->|fa:fa-car Car| F[fa:fa-car Car] + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('62-elk: should render styled subgraphs', () => { + imgSnapshotTest( + ` + flowchart-elk TB + A + B + subgraph foo[Foo SubGraph] + C + D + end + subgraph bar[Bar SubGraph] + E + F + end + G + + A-->B + B-->C + C-->D + B-->D + D-->E + E-->A + E-->F + F-->D + F-->G + B-->G + G-->D + + style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred + style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('63-elk: title on subgraphs should be themable', () => { + imgSnapshotTest( + ` + %%{init:{"theme":"base", "themeVariables": {"primaryColor":"#411d4e", "titleColor":"white", "darkMode":true}}}%% + flowchart-elk LR + subgraph A + a --> b + end + subgraph B + i -->f + end + A --> B + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('65-elk: text-color from classes', () => { + imgSnapshotTest( + ` + flowchart-elk LR + classDef dark fill:#000,stroke:#000,stroke-width:4px,color:#fff + Lorem --> Ipsum --> Dolor + class Lorem,Dolor dark + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('66-elk: More nested subgraph cases (TB)', () => { + imgSnapshotTest( + ` +flowchart-elk TB + subgraph two + b1 + end + subgraph three + c2 + end + + three --> two + two --> c2 + + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('67-elk: More nested subgraph cases (RL)', () => { + imgSnapshotTest( + ` +flowchart-elk RL + subgraph two + b1 + end + subgraph three + c2 + end + + three --> two + two --> c2 + + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('68-elk: More nested subgraph cases (BT)', () => { + imgSnapshotTest( + ` +flowchart-elk BT + subgraph two + b1 + end + subgraph three + c2 + end + + three --> two + two --> c2 + + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('69-elk: More nested subgraph cases (LR)', () => { + imgSnapshotTest( + ` +flowchart-elk LR + subgraph two + b1 + end + subgraph three + c2 + end + + three --> two + two --> c2 + + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('70-elk: Handle nested subgraph cases (TB) link out and link between subgraphs', () => { + imgSnapshotTest( + ` +flowchart-elk TB + subgraph S1 + sub1 -->sub2 + end + subgraph S2 + sub4 + end + S1 --> S2 + sub1 --> sub4 + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('71-elk: Handle nested subgraph cases (RL) link out and link between subgraphs', () => { + imgSnapshotTest( + ` +flowchart-elk RL + subgraph S1 + sub1 -->sub2 + end + subgraph S2 + sub4 + end + S1 --> S2 + sub1 --> sub4 + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('72-elk: Handle nested subgraph cases (BT) link out and link between subgraphs', () => { + imgSnapshotTest( + ` +flowchart-elk BT + subgraph S1 + sub1 -->sub2 + end + subgraph S2 + sub4 + end + S1 --> S2 + sub1 --> sub4 + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('74-elk: Handle nested subgraph cases (RL) link out and link between subgraphs', () => { + imgSnapshotTest( + ` +flowchart-elk RL + subgraph S1 + sub1 -->sub2 + end + subgraph S2 + sub4 + end + S1 --> S2 + sub1 --> sub4 + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('74-elk: Handle labels for multiple edges from and to the same couple of nodes', () => { + imgSnapshotTest( + ` +flowchart-elk RL + subgraph one + a1 -- l1 --> a2 + a1 -- l2 --> a2 + end + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + + it('76-elk: handle unicode encoded character with HTML labels true', () => { + imgSnapshotTest( + `flowchart-elk TB + a{{"Lorem 'ipsum' dolor 'sit' amet, 'consectetur' adipiscing 'elit'."}} + --> b{{"Lorem #quot;ipsum#quot; dolor #quot;sit#quot; amet,#quot;consectetur#quot; adipiscing #quot;elit#quot;."}} + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + + it('2050-elk: handling of different rendering direction in subgraphs', () => { + imgSnapshotTest( + ` + flowchart-elk LR + + subgraph TOP + direction TB + subgraph B1 + direction RL + i1 -->f1 + end + subgraph B2 + direction BT + i2 -->f2 + end + end + A --> TOP --> B + B1 --> B2 + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + + it('2388-elk: handling default in the node name', () => { + imgSnapshotTest( + ` + flowchart-elk LR + default-index.js --> dot.template.js + index.js --> module-utl.js + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('2824-elk: Clipping of edges', () => { + imgSnapshotTest( + ` + flowchart-elk TD + A --> B + A --> C + B --> C + `, + { htmlLabels: true, flowchart: { htmlLabels: true }, securityLevel: 'loose' } + ); + }); + it('1433-elk: should render a titled flowchart with titleTopMargin set to 0', () => { + imgSnapshotTest( + `--- +title: Simple flowchart +--- +flowchart-elk TD +A --> B +`, + { titleTopMargin: 0 } + ); + }); +}); diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index 325cca065..c0156eee3 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -310,38 +310,6 @@ describe('Gantt diagram', () => { ); }); - it('should render accessibility tags', function () { - const expectedTitle = 'Gantt Diagram'; - const expectedAccDescription = 'Tasks for Q4'; - renderGraph( - ` - gantt - accTitle: ${expectedTitle} - accDescr: ${expectedAccDescription} - dateFormat YYYY-MM-DD - section Section - A task :a1, 2014-01-01, 30d - `, - {} - ); - cy.get('svg').should((svg) => { - const el = svg.get(0); - const children = [...el.children]; - - const titleEl = children.find(function (node) { - return node.tagName === 'title'; - }); - const descriptionEl = children.find(function (node) { - return node.tagName === 'desc'; - }); - - expect(titleEl).to.exist; - expect(titleEl.textContent).to.equal(expectedTitle); - expect(descriptionEl).to.exist; - expect(descriptionEl.textContent).to.equal(expectedAccDescription); - }); - }); - it('should render a gantt diagram with tick is 15 minutes', () => { imgSnapshotTest( ` diff --git a/cypress/integration/rendering/mindmap.spec.ts b/cypress/integration/rendering/mindmap.spec.ts index 62c7e785b..4663f6225 100644 --- a/cypress/integration/rendering/mindmap.spec.ts +++ b/cypress/integration/rendering/mindmap.spec.ts @@ -1,4 +1,4 @@ -import { imgSnapshotTest, renderGraph } from '../../helpers/util.js'; +import { imgSnapshotTest } from '../../helpers/util.js'; /** * Check whether the SVG Element has a Mindmap root @@ -158,7 +158,6 @@ mindmap undefined, shouldHaveRoot ); - cy.get('svg'); }); it('rounded rect shape', () => { imgSnapshotTest( @@ -172,7 +171,6 @@ mindmap undefined, shouldHaveRoot ); - cy.get('svg'); }); it('circle shape', () => { imgSnapshotTest( @@ -186,7 +184,6 @@ mindmap undefined, shouldHaveRoot ); - cy.get('svg'); }); it('default shape', () => { imgSnapshotTest( @@ -198,7 +195,6 @@ mindmap undefined, shouldHaveRoot ); - cy.get('svg'); }); it('adding children', () => { imgSnapshotTest( @@ -212,7 +208,6 @@ mindmap undefined, shouldHaveRoot ); - cy.get('svg'); }); it('adding grand children', () => { imgSnapshotTest( @@ -227,7 +222,6 @@ mindmap undefined, shouldHaveRoot ); - cy.get('svg'); }); /* The end */ }); diff --git a/cypress/integration/rendering/requirement.spec.js b/cypress/integration/rendering/requirement.spec.js index 8a8d188ff..0bf9014bf 100644 --- a/cypress/integration/rendering/requirement.spec.js +++ b/cypress/integration/rendering/requirement.spec.js @@ -46,69 +46,4 @@ describe('Requirement diagram', () => { ); cy.get('svg'); }); - - it('should render accessibility tags', function () { - const expectedTitle = 'Gantt Diagram'; - const expectedAccDescription = 'Tasks for Q4'; - renderGraph( - ` - requirementDiagram - accTitle: ${expectedTitle} - accDescr: ${expectedAccDescription} - - requirement test_req { - id: 1 - text: the test text. - risk: high - verifymethod: test - } - - functionalRequirement test_req2 { - id: 1.1 - text: the second test text. - risk: low - verifymethod: inspection - } - - performanceRequirement test_req3 { - id: 1.2 - text: the third test text. - risk: medium - verifymethod: demonstration - } - - element test_entity { - type: simulation - } - - element test_entity2 { - type: word doc - docRef: reqs/test_entity - } - - - test_entity - satisfies -> test_req2 - test_req - traces -> test_req2 - test_req - contains -> test_req3 - test_req <- copies - test_entity2 - `, - {} - ); - cy.get('svg').should((svg) => { - const el = svg.get(0); - const children = [...el.children]; - - const titleEl = children.find(function (node) { - return node.tagName === 'title'; - }); - const descriptionEl = children.find(function (node) { - return node.tagName === 'desc'; - }); - - expect(titleEl).to.exist; - expect(titleEl.textContent).to.equal(expectedTitle); - expect(descriptionEl).to.exist; - expect(descriptionEl.textContent).to.equal(expectedAccDescription); - }); - }); }); diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index b5ff92c8c..1f063c13e 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -3,6 +3,42 @@ import { imgSnapshotTest, renderGraph } from '../../helpers/util'; 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
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
long time, so long
that the text does
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', () => { imgSnapshotTest( ` diff --git a/cypress/integration/rendering/stateDiagram-v2.spec.js b/cypress/integration/rendering/stateDiagram-v2.spec.js index 0eca01873..047e240fc 100644 --- a/cypress/integration/rendering/stateDiagram-v2.spec.js +++ b/cypress/integration/rendering/stateDiagram-v2.spec.js @@ -328,7 +328,7 @@ describe('State diagram', () => { } ); }); - it('v2 it should be possibel to use a choice', () => { + it('v2 it should be possible to use a choice', () => { imgSnapshotTest( ` stateDiagram-v2 diff --git a/cypress/integration/rendering/timeline.spec.ts b/cypress/integration/rendering/timeline.spec.ts new file mode 100644 index 000000000..6fae82fb4 --- /dev/null +++ b/cypress/integration/rendering/timeline.spec.ts @@ -0,0 +1,164 @@ +import { imgSnapshotTest } from '../../helpers/util.js'; + +describe('Timeline diagram', () => { + it('1: should render a simple timeline with no specific sections', () => { + imgSnapshotTest( + `timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + `, + {} + ); + }); + it('2: should render a timeline diagram with sections', () => { + imgSnapshotTest( + `timeline + title Timeline of Industrial Revolution + section 17th-20th century + Industry 1.0 : Machinery, Water power, Steam
power + Industry 2.0 : Electricity, Internal combustion engine, Mass production + Industry 3.0 : Electronics, Computers, Automation + section 21st century + Industry 4.0 : Internet, Robotics, Internet of Things + Industry 5.0 : Artificial intelligence, Big data,3D printing + `, + {} + ); + }); + it('3: should render a complex timeline with sections, and long events text with
', () => { + imgSnapshotTest( + `timeline + title England's History Timeline + section Stone Age + 7600 BC : Britain's oldest known house was built in Orkney, Scotland + 6000 BC : Sea levels rise and Britain becomes an island.
The people who live here are hunter-gatherers. + section Broze Age + 2300 BC : People arrive from Europe and settle in Britain.
They bring farming and metalworking. + : New styles of pottery and ways of burying the dead appear. + 2200 BC : The last major building works are completed at Stonehenge.
People now bury their dead in stone circles. + : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive. + `, + {} + ); + }); + it('4: should render a simple timeline with directives and disableMultiColor:true ', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + `, + {} + ); + }); + it('5: should render a simple timeline with directive overriden colors', () => { + imgSnapshotTest( + ` %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { + 'cScale0': '#ff0000', + 'cScale1': '#00ff00', + 'cScale2': '#0000ff' + } } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); + it('6: should render a simple timeline in base theme', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); + + it('7: should render a simple timeline in default theme', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); + + it('8: should render a simple timeline in dark theme', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); + + it('9: should render a simple timeline in neutral theme', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); + + it('10: should render a simple timeline in forest theme', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); +}); diff --git a/cypress/platform/ashish2.html b/cypress/platform/ashish2.html new file mode 100644 index 000000000..bcea4f4cc --- /dev/null +++ b/cypress/platform/ashish2.html @@ -0,0 +1,231 @@ + + + + + + + + + + +
Security check
+
+ timeline
+        title My day
+        section section with no tasks
+        section Go to work at the dog office
+          1930 : first step : second step is a long step
+               : third step
+          1940 : fourth step : fifth step
+        section Go home
+          1950 : India got independent and already won war against Pakistan
+          1960 : India fights poverty, looses war to China and gets nuclear weapons from USA and USSR
+          1970 : Green Revolution comes to india
+        section Another section with no tasks
+          I am a big big big tasks
+          I am not so big tasks
+    
+
+ timeline
+        title MermaidChart 2023 Timeline
+        section 2023 Q1 
Release Personal Tier + Buttet 1 : sub-point 1a : sub-point 1b + : sub-point 1c + Bullet 2 : sub-point 2a : sub-point 2b + section 2023 Q2
Release XYZ Tier + Buttet 3 : sub-point
3a : sub-point 3b + : sub-point 3c + Bullet 4 : sub-point 4a : sub-point 4b + +
+ +
+ timeline
+        title England's History Timeline
+        section Stone Age
+          7600 BC : Britain's oldest known house was built in Orkney, Scotland
+          6000 BC : Sea levels rise and Britain becomes an island. The people who live here are hunter-gatherers.
+        section Broze Age
+          2300 BC : People arrive from Europe and settle in Britain. They bring farming and metalworking.
+               : New styles of pottery and ways of burying the dead appear.
+          2200 BC : The last major building works are completed at Stonehenge. People now bury their dead in stone circles.
+                  : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.
+
+    
+
+      %%{'init': { 'logLevel': 'debug', 'theme': 'default', 'timeline': {'disableMulticolor':false} } }%%
+ timeline
+        title History of Social Media Platform
+          2002 : LinkedIn
+          2004 : Facebook : Google : Pixar
+          2005 : Youtube
+          2006 : Twitter
+          2007 : Tumblr
+          2008s : Instagram
+          2010 : Pinterest
+    
+
+      %%{init: { 'logLevel': 'debug', 'theme': 'base', 'themeVariables': {
+              'cScale0': '#ff0000',
+              'cScale1': '#00ff00',
+              'cScale2': '#ff0000'
+              } } }%%
+ timeline
+        title History of Social Media Platform
+          2002 : LinkedIn
+          2004 : Facebook : Google : Pixar
+          2005 : Youtube
+          2006 : Twitter
+          2007 : Tumblr
+          2008s : Instagram
+          2010 : Pinterest
+    
+ +
+          %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
+              'cScale0': '#ff0000',
+              'cScale1': '#00ff00',
+              'cScale2': '#0000ff'
+       } } }%%
+       timeline
+        title History of Social Media Platform
+          2002 : LinkedIn
+          2004 : Facebook : Google
+          2005 : Youtube
+          2006 : Twitter
+          2007 : Tumblr
+          2008 : Instagram
+          2010 : Pinterest
+
+    
+ +
+ timeline
+        title History of Social Media Platform
+          2002 : LinkedIn
+          2004 : Facebook : Google
+          2005 : Youtube
+          2006 : Twitter
+          2007 : Tumblr
+          2008s : Instagram
+          2010 : Pinterest
+    
+
+mindmap
+  root
+    child1((Circle))
+        grandchild 1
+        grandchild 2
+    child2(Round rectangle)
+        grandchild 3
+        grandchild 4
+    child3[Square]
+        grandchild 5
+        ::icon(mdi mdi-fire)
+        gc6((grand
child 6)) + ::icon(mdi mdi-fire) + gc7((grand
grand
child 8)) +
+
+      flowchart-elk TB
+      a --> b
+      a --> c
+      b --> d
+      c --> d
+    
+ + + + + + + + diff --git a/cypress/platform/bundle-test.js b/cypress/platform/bundle-test.js index a991918c4..edd3dfbc4 100644 --- a/cypress/platform/bundle-test.js +++ b/cypress/platform/bundle-test.js @@ -49,13 +49,9 @@ mermaid.initialize({ ], }, }); -mermaid.render( - 'the-id-of-the-svg', - code, - (svg) => { - console.log(svg); - const elem = document.querySelector('#graph-to-be'); - elem.innerHTML = svg; - } - // ,document.querySelector('#tmp') -); +void (async () => { + const { svg } = await mermaid.render('the-id-of-the-svg', code); + console.log(svg); + const elem = document.querySelector('#graph-to-be'); + elem.innerHTML = svg; +})(); diff --git a/cypress/platform/class.html b/cypress/platform/class.html index 85fae2a77..052dd18b9 100644 --- a/cypress/platform/class.html +++ b/cypress/platform/class.html @@ -46,13 +46,9 @@
       %%{init: {'theme': 'base',  'fontFamily': 'courier', 'themeVariables': {  'primaryColor': '#fff000'}}}%%
       classDiagram-v2
-       class BankAccount{
-        +String owner
-        +BigDecimal balance
-        +deposit(amount) bool
-        +withdrawl(amount) int
-       }
-       cssClass "BankAccount" customCss
+classA <|-- classB : implements
+classC *-- classD : composition
+classE o-- classF : aggregation
     
         %%{init: {'theme': 'base',  'fontFamily': 'courier', 'themeVariables': {  'primaryColor': '#fff000'}}}%%
@@ -117,8 +113,8 @@
         callback Shape "callbackFunction" "This is a tooltip for a callback"
 
     
- - + diff --git a/cypress/platform/click_security_other.html b/cypress/platform/click_security_other.html index 5338cac06..7dc75ea88 100644 --- a/cypress/platform/click_security_other.html +++ b/cypress/platform/click_security_other.html @@ -59,8 +59,8 @@ Add another diagram to demo page : 48h - - - - - - - - - - - + diff --git a/cypress/platform/external-diagrams-mindmap.html b/cypress/platform/external-diagrams-example-diagram.html similarity index 55% rename from cypress/platform/external-diagrams-mindmap.html rename to cypress/platform/external-diagrams-example-diagram.html index e5eded4ba..b5b716ff8 100644 --- a/cypress/platform/external-diagrams-mindmap.html +++ b/cypress/platform/external-diagrams-example-diagram.html @@ -2,34 +2,8 @@

Should correctly load a third-party diagram using registerDiagram

-mindmap
-  root
-    A
-    B
-    C
-    D
-    E
-    A2
-    B2
-    C2
-    D2
-    E2
-    child1((Circle))
-        grandchild 1
-        grandchild 2
-    child2(Round rectangle)
-        grandchild 3
-        grandchild 4
-    child3[Square]
-        grandchild 5
-        ::icon(mdi mdi-fire)
-        gc6((grand
child 6)) - ::icon(mdi mdi-fire) - gc7((grand
grand
child 8)) +example-diagram
- @@ -37,13 +11,16 @@ mindmap diff --git a/cypress/platform/flow.html b/cypress/platform/flow.html index ed70f80d0..0060ac3cb 100644 --- a/cypress/platform/flow.html +++ b/cypress/platform/flow.html @@ -29,8 +29,8 @@ click a_a "http://www.aftonbladet.se" "apa" - -
-

This element does not belong to the SVG but we can style it

+

Background should be yellow!!!

- - diff --git a/cypress/platform/ghsa2.html b/cypress/platform/ghsa2.html index 6d4dccca3..b10f0bda0 100644 --- a/cypress/platform/ghsa2.html +++ b/cypress/platform/ghsa2.html @@ -8,8 +8,8 @@ - - diff --git a/cypress/platform/ghsa3.html b/cypress/platform/ghsa3.html new file mode 100644 index 000000000..2170a808e --- /dev/null +++ b/cypress/platform/ghsa3.html @@ -0,0 +1,101 @@ + + + + + + + + + +

PAGE SHOULD NOT BE RED

+
+
+
+
+ + + diff --git a/cypress/platform/git-graph.html b/cypress/platform/git-graph.html index da6025f4b..4cb2656f1 100644 --- a/cypress/platform/git-graph.html +++ b/cypress/platform/git-graph.html @@ -43,8 +43,8 @@ cssClass "BankAccount" customCss - - - - - - - - diff --git a/cypress/platform/per.html b/cypress/platform/per.html index 4fca4c808..56e7918fd 100644 --- a/cypress/platform/per.html +++ b/cypress/platform/per.html @@ -59,7 +59,7 @@ A-->B > - + - -

Example

@@ -26,4 +16,12 @@ sequenceDiagram
     Note left of Ernie: Cookies are good
     
+ diff --git a/cypress/platform/render-after-error.html b/cypress/platform/render-after-error.html index f5165e0ee..2334158c2 100644 --- a/cypress/platform/render-after-error.html +++ b/cypress/platform/render-after-error.html @@ -9,19 +9,19 @@
- - diff --git a/cypress/platform/rerender.html b/cypress/platform/rerender.html index ab1b8e009..d9dbc4a5e 100644 --- a/cypress/platform/rerender.html +++ b/cypress/platform/rerender.html @@ -9,20 +9,20 @@
- - diff --git a/cypress/platform/showcase_base.html b/cypress/platform/showcase_base.html index 227d79cf7..32a2ae72a 100644 --- a/cypress/platform/showcase_base.html +++ b/cypress/platform/showcase_base.html @@ -313,8 +313,8 @@ requirementDiagram merge release - - - - - - - - diff --git a/cypress/platform/subgraph.html b/cypress/platform/subgraph.html index 24d85079f..6213fff9a 100644 --- a/cypress/platform/subgraph.html +++ b/cypress/platform/subgraph.html @@ -34,8 +34,8 @@ 9e122290-->82072290_1ec3_e711_8c5a_005056ad0002 style 9e122290 fill:#F99,stroke-width:2px,stroke:#F0F - - - - - - - - diff --git a/cypress/platform/xss11.html b/cypress/platform/xss11.html index ca97aeaab..302f39ee9 100644 --- a/cypress/platform/xss11.html +++ b/cypress/platform/xss11.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss12.html b/cypress/platform/xss12.html index eb1bce327..b1e2c1d0a 100644 --- a/cypress/platform/xss12.html +++ b/cypress/platform/xss12.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss13.html b/cypress/platform/xss13.html index f2d90cddb..9f505ea7b 100644 --- a/cypress/platform/xss13.html +++ b/cypress/platform/xss13.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss14.html b/cypress/platform/xss14.html index f429b355a..e68b87b78 100644 --- a/cypress/platform/xss14.html +++ b/cypress/platform/xss14.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss15.html b/cypress/platform/xss15.html index 70ebe9f86..3fa6b7151 100644 --- a/cypress/platform/xss15.html +++ b/cypress/platform/xss15.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss16.html b/cypress/platform/xss16.html index 9325a70aa..6f8a734eb 100644 --- a/cypress/platform/xss16.html +++ b/cypress/platform/xss16.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss17.html b/cypress/platform/xss17.html index c498f3f3e..bd7e1c57e 100644 --- a/cypress/platform/xss17.html +++ b/cypress/platform/xss17.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss18.html b/cypress/platform/xss18.html index 3e9cfd35c..ccacfadbb 100644 --- a/cypress/platform/xss18.html +++ b/cypress/platform/xss18.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss19.html b/cypress/platform/xss19.html index ca747b39e..7966abb8c 100644 --- a/cypress/platform/xss19.html +++ b/cypress/platform/xss19.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss2.html b/cypress/platform/xss2.html index 18b4be6fc..da95e3797 100644 --- a/cypress/platform/xss2.html +++ b/cypress/platform/xss2.html @@ -48,8 +48,8 @@ Alice->>Bob: Hi Bob Bob->>Alice: Hi Alice - - - diff --git a/cypress/platform/xss21.html b/cypress/platform/xss21.html index b2f67cd93..7cfa17c9e 100644 --- a/cypress/platform/xss21.html +++ b/cypress/platform/xss21.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss22.html b/cypress/platform/xss22.html index d7e47cd02..50ff3b732 100644 --- a/cypress/platform/xss22.html +++ b/cypress/platform/xss22.html @@ -8,8 +8,8 @@ graph TD A --> B["<a href='javascript#9;t#colon;alert(document.location)'>AAA</a>"] - - diff --git a/cypress/platform/xss3.html b/cypress/platform/xss3.html index 78fabc4aa..f01aab37e 100644 --- a/cypress/platform/xss3.html +++ b/cypress/platform/xss3.html @@ -36,8 +36,8 @@ graph LR A --> B - - - diff --git a/cypress/platform/xss5.html b/cypress/platform/xss5.html index f87e65505..f7abf7a45 100644 --- a/cypress/platform/xss5.html +++ b/cypress/platform/xss5.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss6.html b/cypress/platform/xss6.html index bc0f78561..7d7ae18d1 100644 --- a/cypress/platform/xss6.html +++ b/cypress/platform/xss6.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss7.html b/cypress/platform/xss7.html index d8b2a7620..177b4342c 100644 --- a/cypress/platform/xss7.html +++ b/cypress/platform/xss7.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss8.html b/cypress/platform/xss8.html index cf2969f39..5852c2693 100644 --- a/cypress/platform/xss8.html +++ b/cypress/platform/xss8.html @@ -49,8 +49,8 @@
- - diff --git a/cypress/platform/xss9.html b/cypress/platform/xss9.html index 916f1506d..cf2ad1359 100644 --- a/cypress/platform/xss9.html +++ b/cypress/platform/xss9.html @@ -49,8 +49,8 @@
- - diff --git a/demos/c4context.html b/demos/c4context.html index e085e611c..cf358b550 100644 --- a/demos/c4context.html +++ b/demos/c4context.html @@ -217,8 +217,8 @@
- - - - diff --git a/demos/classchart.html b/demos/classchart.html index 031f3b608..b20dda2a3 100644 --- a/demos/classchart.html +++ b/demos/classchart.html @@ -17,10 +17,10 @@

Class diagram demos

----
-title: Demo Class Diagram
----
-		classDiagram
+    ---
+    title: Demo Class Diagram
+    ---
+    classDiagram
       accTitle: Demo Class Diagram
       accDescr: This class diagram show the abstract Animal class, and 3 classes that inherit from it: Duck, Fish, and Zebra.
 
@@ -138,11 +138,24 @@ title: Demo Class Diagram
     Pineapple : -int leafCount()
     Pineapple : -int spikeCount()
     
-
- - - - - - - - - - - + + diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index 520adc7d9..c854a139c 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -497,7 +497,7 @@ mermaidAPI.initialize({ - Clarify the need for a CSS stylesheet [#413](https://github.com/knsv/mermaid/pull/413) ([sifb](https://github.com/sifb)) - Added hads downstream project [#412](https://github.com/knsv/mermaid/pull/412) ([sinedied](https://github.com/sinedied)) - update usage and fix #273 [#406](https://github.com/knsv/mermaid/pull/406) ([jinntrance](https://github.com/jinntrance)) -- Add https://github.com/raghur/mermaid-filter to downstream projects docs page [#404](https://github.com/knsv/mermaid/pull/404) ([raghur](https://github.com/raghur)) +- Add to downstream projects docs page [#404](https://github.com/knsv/mermaid/pull/404) ([raghur](https://github.com/raghur)) - New neutral theme [#395](https://github.com/knsv/mermaid/pull/395) ([sinedied](https://github.com/sinedied)) - fix cli issues [#390](https://github.com/knsv/mermaid/pull/390) ([ben-page](https://github.com/ben-page)) - Add missing space for 'Labels out of bounds' section [#386](https://github.com/knsv/mermaid/pull/386) ([The-Alchemist](https://github.com/The-Alchemist)) @@ -661,7 +661,7 @@ mermaidAPI.initialize({ **Closed issues:** -- Installing “atom-mermaid@0.1.3” failed [#218](https://github.com/knsv/mermaid/issues/218) +- Installing “atom-mermaid\@0.1.3” failed [#218](https://github.com/knsv/mermaid/issues/218) - node feature request [#211](https://github.com/knsv/mermaid/issues/211) - Please add prefix for styles [#208](https://github.com/knsv/mermaid/issues/208) - Bad handling of block arguments [#207](https://github.com/knsv/mermaid/issues/207) @@ -671,7 +671,7 @@ mermaidAPI.initialize({ - Broken CLI Graphs? (v0.5.1) [#196](https://github.com/knsv/mermaid/issues/196) - Static site does not render under HTTPS [#194](https://github.com/knsv/mermaid/issues/194) - Error on simple graph [#192](https://github.com/knsv/mermaid/issues/192) -- Escape "~" [#191](https://github.com/knsv/mermaid/issues/191) +- Escape "\~" [#191](https://github.com/knsv/mermaid/issues/191) - Trying to add link using 'click' to flowchart [#188](https://github.com/knsv/mermaid/issues/188) - cli: no lines and arrowheads rendered / only dotted lines [#187](https://github.com/knsv/mermaid/issues/187) - text of mermaid div displayed on page [#186](https://github.com/knsv/mermaid/issues/186) @@ -793,7 +793,7 @@ mermaidAPI.initialize({ **Closed issues:** - subgraph background is black in rendered flowchart PNG via CLI [#121](https://github.com/knsv/mermaid/issues/121) -- Integrate editor at https://github.com/naseer/mermaid-webapp [#110](https://github.com/knsv/mermaid/issues/110) +- Integrate editor at [#110](https://github.com/knsv/mermaid/issues/110) - Internet Explorer Support [#99](https://github.com/knsv/mermaid/issues/99) ## [0.3.5](https://github.com/knsv/mermaid/tree/0.3.5) (2015-02-15) diff --git a/docs/community/newDiagram.md b/docs/community/newDiagram.md index da86f9838..bb7e2a961 100644 --- a/docs/community/newDiagram.md +++ b/docs/community/newDiagram.md @@ -14,8 +14,8 @@ This would be to define a jison grammar for the new diagram type. That should st For instance: -- the flowchart starts with the keyword graph. -- the sequence diagram starts with the keyword sequenceDiagram +- the flowchart starts with the keyword _graph_ +- the sequence diagram starts with the keyword _sequenceDiagram_ #### Store data found during parsing @@ -60,7 +60,12 @@ Place the renderer in the diagram folder. ### Step 3: Detection of the new diagram type -The second thing to do is to add the capability to detect the new new diagram to type to the detectType in utils.js. The detection should return a key for the new diagram type. +The second thing to do is to add the capability to detect the new diagram to type to the detectType in utils.js. The detection should return a key for the new diagram type. +[This key will be used to as the aria roledescription](#aria-roledescription), so it should be a word that clearly describes the diagram type. +For example, if your new diagram use a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader +would voice that as "U-M-L Deployment diagram." Another good key would be "deploymentDiagram" because that would be voiced as "Deployment Diagram." A bad key would be "deployment" because that would not sufficiently describe the diagram. + +Note that the diagram type key does not have to be the same as the diagram keyword chosen for the [grammar](#grammar), but it is helpful if they are the same. ### Step 4: The final piece - triggering the rendering @@ -168,17 +173,23 @@ It is probably a good idea to keep the handling similar to this in your new diag ## Accessibility -The syntax for adding title and description looks like this: +Mermaid automatically adds the following accessibility information for the diagram SVG HTML element: - accTitle: The title - accDescr: The description +- aria-roledescription +- accessible title +- accessible description - accDescr { - Syntax for a description text - written on multiple lines. - } +### aria-roledescription -In a similar way to the directives the jison syntax are quite similar between the diagrams. +The aria-roledescription is automatically set to [the diagram type](#step-3--detection-of-the-new-diagram-type) and inserted into the SVG element. + +See [the definition of aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) in [the Accessible Rich Internet Applications W3 standard.](https://www.w3.org/WAI/standards-guidelines/aria/) + +### accessible title and description + +The syntax for accessible titles and descriptions is described in [the Accessibility documenation section.](../config/accessibility.md) + +In a similar way to the directives, the jison syntax are quite similar between the diagrams. ```jison @@ -214,18 +225,7 @@ The functions for setting title and description are provided by a common module. clear as commonClear, } from '../../commonDb'; -For rendering the accessibility tags you have again an existing function you can use. - -**In the renderer:** - -```js -import addSVGAccessibilityFields from '../../accessibility'; - -/* ... */ - -// Adds title and description to the flow chart -addSVGAccessibilityFields(parser.yy, svg, id); -``` +The accessibility title and description are inserted into the SVG element in the `render` function in mermaidAPI. ## Theming diff --git a/docs/config/8.6.0_docs.md b/docs/config/8.6.0_docs.md index abd158712..0b1d64112 100644 --- a/docs/config/8.6.0_docs.md +++ b/docs/config/8.6.0_docs.md @@ -8,7 +8,7 @@ ## [New Mermaid Live-Editor Beta](https://mermaid-js.github.io/docs/mermaid-live-editor-beta/#/edit/eyJjb2RlIjoiJSV7aW5pdDoge1widGhlbWVcIjogXCJmb3Jlc3RcIiwgXCJsb2dMZXZlbFwiOiAxIH19JSVcbmdyYXBoIFREXG4gIEFbQ2hyaXN0bWFzXSAtLT58R2V0IG1vbmV5fCBCKEdvIHNob3BwaW5nKVxuICBCIC0tPiBDe0xldCBtZSB0aGlua31cbiAgQyAtLT58T25lfCBEW0xhcHRvcF1cbiAgQyAtLT58VHdvfCBFW2lQaG9uZV1cbiAgQyAtLT58VGhyZWV8IEZbZmE6ZmEtY2FyIENhcl1cblx0XHQiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGFyayJ9fQ) -## [CDN](https://unpkg.com/mermaid/) +## [CDN](https://www.jsdelivr.com/package/npm/mermaid) With version 8.6.0 comes the release of directives for mermaid, a new system for modifying configurations, with the aim of establishing centralized, sane defaults and simple implementation. diff --git a/docs/config/Tutorials.md b/docs/config/Tutorials.md index 41e0508cb..7dda89801 100644 --- a/docs/config/Tutorials.md +++ b/docs/config/Tutorials.md @@ -32,15 +32,15 @@ Examples are provided in [Getting Started](../intro/n00b-gettingStarted.md) **CodePen Examples:** -https://codepen.io/CarlBoneri/pen/BQwZzq + -https://codepen.io/tdkn/pen/vZxQzd + -https://codepen.io/janzeteachesit/pen/OWWZKN + ## Mermaid with Text Area -https://codepen.io/Ryuno-Ki/pen/LNxwgR + ## Mermaid in open source docs diff --git a/docs/config/accessibility.md b/docs/config/accessibility.md index 8fa4aa3ac..bf8b3e591 100644 --- a/docs/config/accessibility.md +++ b/docs/config/accessibility.md @@ -10,118 +10,172 @@ Now with Mermaid library in much wider use, we have started to work towards more accessible features, based on the feedback from the community. -To begin with, we have added a new feature to Mermaid library, which is to support accessibility options, **Accessibility Title** and **Accessibility Description**. +Adding accessibility means that the rich information communicated by visual diagrams can be made available to those using assistive technologies (and of course to search engines). +[Read more about Accessible Rich Internet Applications and the W3 standards.](https://www.w3.org/WAI/standards-guidelines/aria/) -This support for accessibility options is available for all the diagrams/chart types. Also, we have tired to keep the same format for the accessibility options, so that it is easy to understand and maintain. +Mermaid will automatically insert the [aria-roledescription](#aria-roledescription) and, if provided in the diagram text by the diagram author, the [accessible title and description.](#accessible-title-and-description) -## Defining Accessibility Options +### aria-roledescription -### Single line accessibility values +The [aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) for the SVG HTML element is set to the diagram type key. (Note this may be slightly different than the keyword used for the diagram in the diagram text.) -The diagram authors can now add the accessibility options in the diagram definition, using the `accTitle` and `accDescr` keywords, where each keyword is followed by `:` and the string value for title and description like: +For example: The diagram type key for a state diagram is "stateDiagram". Here (a part of) the HTML of the SVG tag that shows the automatically inserted aria-roledscription set to "stateDiagram". _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ -- `accTitle: "Your Accessibility Title"` or -- `accDescr: "Your Accessibility Description"` +```html + +``` -**When these two options are defined, they will add a corresponding `` and `<desc>` tag in the SVG.** +### Accessible Title and Description -Let us take a look at the following example with a flowchart diagram: +Support for accessible titles and descriptions is available for all diagrams/chart types. We have tried to keep the same keywords and format for all diagrams so that it is easy to understand and maintain. + +The accessible title and description will add `<title>` and `<desc>` elements within the SVG element and the [aria-labelledby](https://www.w3.org/TR/wai-aria/#aria-labelledby) and [aria-describedby](https://www.w3.org/TR/wai-aria/#aria-describedby) attributes in the SVG tag. + +Here is HTML that is generated, showing that the SVG element is labelled by the accessible title (id = `chart-title-mermaid-1668725057758`) +and described by the accessible description (id = `chart-desc-mermaid-1668725057758` ); +and the accessible title element (text = "This is the accessible title") +and the accessible description element (text = "This is an accessible description"). + +_(Note that some of the SVG attributes and the SVG contents are omitted for clarity.)_ + +```html +<svg + aria-labelledby="chart-title-mermaid-1668725057758" + aria-describedby="chart-desc-mermaid-1668725057758" + xmlns="http://www.w3.org/2000/svg" + width="100%" + id="mermaid-1668725057758" +> + <title id="chart-title-mermaid-1668725057758">This is the accessible title + This is an accessible description + +``` + +Details for the syntax follow. + +#### accessible title + +The **accessible title** is specified with the **accTitle** _keyword_, followed by a colon (`:`), and the string value for the title. +The string value ends at the end of the line. (It can only be a single line.) + +Ex: `accTitle: This is a single line title` + +See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. + +#### accessible description + +An accessible description can be 1 line long (a single line) or many lines long. + +The **single line accessible description** is specified with the **accDescr** _keyword_, followed by a colon (`:`), followed by the string value for the description. + +Ex: `accDescr: This is a single line description.` + +A **multiple line accessible description** _does not have a colon (`:`) after the accDescr keyword_ and is surrounded by curly brackets (`{}`). + +Ex: + +```markdown +accDescr { +This is a multiple line accessible description. +It does not have a colon and is surrounded by curly brackets. +} +``` + +See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. + +#### accTitle and accDescr Usage Examples + +- Flowchart with the accessible title "Big Decisions" and the single-line accessible description "Bob's Burgers process for making big decisions" ```mermaid-example - graph LR - accTitle: Big decisions - accDescr: Flow chart of the decision making process - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] - + graph LR + accTitle: Big Decisions + accDescr: Bob's Burgers process for making big decisions + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] ``` ```mermaid - graph LR - accTitle: Big decisions - accDescr: Flow chart of the decision making process - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] - + graph LR + accTitle: Big Decisions + accDescr: Bob's Burgers process for making big decisions + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] ``` -See in the code snippet above, the `accTitle` and `accDescr` are defined in the diagram definition. They result in the following tags in SVG code: +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ -![Accessibility options rendered inside SVG](img/accessibility-div-example.png) +```html + + Big decisions + Bob's Burgers process for making big decisions + +``` -### Multi-line Accessibility title/description - -You can also define the accessibility options in a multi-line format, where the keyword is followed by opening curly bracket `{` and then multiple lines, followed by a closing `}`. - -`accTitle: My single line title value` (**_single line format_**) - -vs - -`accDescr: { My multi-line description of the diagram }` (**_multi-line format_**) - -Let us look at it in the following example, with same flowchart: +- Flowchart with the accessible title "Bob's Burger's Making Big Decisions" and the multiple line accessible description "The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision." ```mermaid-example - graph LR - accTitle: Big decisions - + graph LR + accTitle: Bob's Burger's Making Big Decisions accDescr { - My multi-line description - of the diagram - } - - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] - + The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision. + } + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] ``` ```mermaid - graph LR - accTitle: Big decisions - + graph LR + accTitle: Bob's Burger's Making Big Decisions accDescr { - My multi-line description - of the diagram - } - - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] - + The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision. + } + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] ``` -See in the code snippet above, the `accTitle` and `accDescr` are defined in the diagram definition. They result in the following tags in SVG code: +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ -![Accessibility options rendered inside SVG](img/accessibility-div-example-2.png) - -### Sample Code Snippet for other diagram types - -#### Sequence Diagram - -```mermaid-example - sequenceDiagram - accTitle: My Sequence Diagram - accDescr: My Sequence Diagram Description - - Alice->>John: Hello John, how are you? - John-->>Alice: Great! - Alice-)John: See you later! +```html + + Big decisions + + The official Bob's Burgers corporate processes that are used for making very, very big + decisions. This is actually a very simple flow: identify the big decision and then make the big + decision. + + ``` -```mermaid - sequenceDiagram - accTitle: My Sequence Diagram - accDescr: My Sequence Diagram Description +#### Sample Code Snippets for other diagram types - Alice->>John: Hello John, how are you? - John-->>Alice: Great! - Alice-)John: See you later! -``` - -#### Class Diagram +##### Class Diagram ```mermaid-example classDiagram @@ -139,27 +193,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the Vehicle <|-- Car ``` -#### State Diagram - -```mermaid-example - stateDiagram - accTitle: My State Diagram - accDescr: My State Diagram Description - - s1 --> s2 - -``` - -```mermaid - stateDiagram - accTitle: My State Diagram - accDescr: My State Diagram Description - - s1 --> s2 - -``` - -#### Entity Relationship Diagram +##### Entity Relationship Diagram ```mermaid-example erDiagram @@ -183,41 +217,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### User Journey Diagram - -```mermaid-example - journey - accTitle: My User Journey Diagram - accDescr: My User Journey Diagram Description - - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me - -``` - -```mermaid - journey - accTitle: My User Journey Diagram - accDescr: My User Journey Diagram Description - - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me - -``` - -#### Gantt Chart +##### Gantt Chart ```mermaid-example gantt @@ -251,7 +251,45 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Pie Chart +##### Gitgraph + +```mermaid-example + gitGraph + accTitle: My Gitgraph Accessibility Title + accDescr: My Gitgraph Accessibility Description + + commit + commit + branch develop + checkout develop + commit + commit + checkout main + merge develop + commit + commit + +``` + +```mermaid + gitGraph + accTitle: My Gitgraph Accessibility Title + accDescr: My Gitgraph Accessibility Description + + commit + commit + branch develop + checkout develop + commit + commit + checkout main + merge develop + commit + commit + +``` + +##### Pie Chart ```mermaid-example pie @@ -279,7 +317,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Requirement Diagram +##### Requirement Diagram ```mermaid-example requirementDiagram @@ -321,40 +359,78 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Gitgraph +##### Sequence Diagram ```mermaid-example - gitGraph - accTitle: My Gitgraph Accessibility Title - accDescr: My Gitgraph Accessibility Description + sequenceDiagram + accTitle: My Sequence Diagram + accDescr: My Sequence Diagram Description - commit - commit - branch develop - checkout develop - commit - commit - checkout main - merge develop - commit - commit + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later! +``` + +```mermaid + sequenceDiagram + accTitle: My Sequence Diagram + accDescr: My Sequence Diagram Description + + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later! +``` + +##### State Diagram + +```mermaid-example + stateDiagram + accTitle: My State Diagram + accDescr: My State Diagram Description + + s1 --> s2 ``` ```mermaid - gitGraph - accTitle: My Gitgraph Accessibility Title - accDescr: My Gitgraph Accessibility Description + stateDiagram + accTitle: My State Diagram + accDescr: My State Diagram Description - commit - commit - branch develop - checkout develop - commit - commit - checkout main - merge develop - commit - commit + s1 --> s2 + +``` + +##### User Journey Diagram + +```mermaid-example + journey + accTitle: My User Journey Diagram + accDescr: My User Journey Diagram Description + + title My working day + section Go to work + Make tea: 5: Me + Go upstairs: 3: Me + Do work: 1: Me, Cat + section Go home + Go downstairs: 5: Me + Sit down: 5: Me + +``` + +```mermaid + journey + accTitle: My User Journey Diagram + accDescr: My User Journey Diagram Description + + title My working day + section Go to work + Make tea: 5: Me + Go upstairs: 3: Me + Do work: 1: Me, Cat + section Go home + Go downstairs: 5: Me + Sit down: 5: Me ``` diff --git a/docs/config/directives.md b/docs/config/directives.md index 550707080..e845596ad 100644 --- a/docs/config/directives.md +++ b/docs/config/directives.md @@ -32,7 +32,8 @@ Mermaid basically supports two types of configuration options to be overridden b **NOTE:** These options listed here are not all the configuration options. To get hold of all the configuration options, please refer to the [defaultConfig.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/defaultConfig.ts) in the source code. - Soon we plan to publish a complete list of top-level configurations & all the diagram specific configurations, with their possible values in the docs +> **Note** +> We plan to publish a complete list of top-level configurations & all the diagram specific configurations, with their possible values in the docs soon. ## Declaring directives diff --git a/docs/misc/faq.md b/docs/config/faq.md similarity index 94% rename from docs/misc/faq.md rename to docs/config/faq.md index c7155a5b0..76132762a 100644 --- a/docs/misc/faq.md +++ b/docs/config/faq.md @@ -2,7 +2,7 @@ > > ## 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 diff --git a/docs/config/n00b-advanced.md b/docs/config/n00b-advanced.md index 5dd907429..39c20d632 100644 --- a/docs/config/n00b-advanced.md +++ b/docs/config/n00b-advanced.md @@ -10,17 +10,21 @@ A more condensed html code can be achieved by embedding the mermaid code in its own .js file, which is referenced like so: - stuff stuff - - - +```html +... + + + +``` The actual mermaid file could for example look like this: - mermaid content... - ---- +```javascript +mermaid content ... +``` ## mermaid configuration options -... +```markdown +(coming soon) +``` diff --git a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md new file mode 100644 index 000000000..93708863c --- /dev/null +++ b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md @@ -0,0 +1,19 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md](../../../../packages/mermaid/src/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md). + +# Interface: ParseOptions + +[mermaidAPI](../modules/mermaidAPI.md).ParseOptions + +## Properties + +### suppressErrors + +• `Optional` **suppressErrors**: `boolean` + +#### Defined in + +[mermaidAPI.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L70) diff --git a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md new file mode 100644 index 000000000..de4f763a0 --- /dev/null +++ b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md @@ -0,0 +1,78 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/setup/interfaces/mermaidAPI.RenderResult.md](../../../../packages/mermaid/src/docs/config/setup/interfaces/mermaidAPI.RenderResult.md). + +# Interface: RenderResult + +[mermaidAPI](../modules/mermaidAPI.md).RenderResult + +Function that renders an svg with a graph from a chart definition. Usage example below. + +```javascript +mermaidAPI.initialize({ + startOnLoad: true, +}); +$(function () { + const graphDefinition = 'graph TB\na-->b'; + const cb = function (svgGraph) { + console.log(svgGraph); + }; + mermaidAPI.render('id1', graphDefinition, cb); +}); +``` + +**`Param`** + +The id for the SVG element (the element to be rendered) + +**`Param`** + +The text for the graph definition + +**`Param`** + +Callback which is called after rendering is finished with the svg code as in param. + +**`Param`** + +HTML element where the svg will be inserted. (Is usually element with the .mermaid class) +If no svgContainingElement is provided then the SVG element will be appended to the body. +Selector to element in which a div with the graph temporarily will be +inserted. If one is provided a hidden div will be inserted in the body of the page instead. The +element will be removed when rendering is completed. + +## Properties + +### bindFunctions + +• `Optional` **bindFunctions**: (`element`: `Element`) => `void` + +#### Type declaration + +▸ (`element`): `void` + +##### Parameters + +| Name | Type | +| :-------- | :-------- | +| `element` | `Element` | + +##### Returns + +`void` + +#### Defined in + +[mermaidAPI.ts:384](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L384) + +--- + +### svg + +• **svg**: `string` + +#### Defined in + +[mermaidAPI.ts:383](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L383) diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index 05f6f8a2c..302bd51e1 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[defaultConfig.ts:1933](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L1933) +[defaultConfig.ts:2084](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2084) --- diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index df818730a..aa0885707 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -6,17 +6,32 @@ # Module: mermaidAPI +## Interfaces + +- [ParseOptions](../interfaces/mermaidAPI.ParseOptions.md) +- [RenderResult](../interfaces/mermaidAPI.RenderResult.md) + ## References ### default Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) +## Type Aliases + +### D3Element + +Ƭ **D3Element**: `any` + +#### Defined in + +[mermaidAPI.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L75) + ## Variables ### mermaidAPI -• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseError?`: `ParseErrorFunction`) => `boolean` ; `parseAsync`: (`text`: `string`, `parseError?`: `ParseErrorFunction`) => `Promise`<`boolean`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `cb?`: (`svgCode`: `string`, `bindFunctions?`: (`element`: `Element`) => `void`) => `void`, `svgContainingElement?`: `Element`) => `string` ; `renderAsync`: (`id`: `string`, `text`: `string`, `cb?`: (`svgCode`: `string`, `bindFunctions?`: (`element`: `Element`) => `void`) => `void`, `svgContainingElement?`: `Element`) => `Promise`<`string`> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> +• `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean` | `void`> ; `parseDirective`: (`p`: `any`, `statement`: `string`, `context`: `string`, `type`: `string`) => `void` ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> ## mermaidAPI configuration defaults @@ -80,7 +95,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:939](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L939) +[mermaidAPI.ts:668](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L668) ## Functions @@ -111,7 +126,7 @@ Return the last node appended #### Defined in -[mermaidAPI.ts:284](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L284) +[mermaidAPI.ts:291](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L291) --- @@ -137,7 +152,7 @@ the cleaned up svgCode #### Defined in -[mermaidAPI.ts:235](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L235) +[mermaidAPI.ts:242](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L242) --- @@ -149,11 +164,11 @@ Create the user styles #### Parameters -| Name | Type | Description | -| :---------- | :-------------- | :----------------------------------------------------- | ------------------------------------------ | ------------------------------------------------------------------------------------------------------------------------- | -| `config` | `MermaidConfig` | configuration that has style and theme settings to use | -| `graphType` | `string` | used for checking if classDefs should be applied | -| `classDefs` | `undefined` | `null` | `Record`<`string`, `DiagramStyleClassDef`> | the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...) | +| Name | Type | Description | +| :---------- | :------------------------------------------------------------------ | :------------------------------------------------------------------------------------------------------------------------ | +| `config` | `MermaidConfig` | configuration that has style and theme settings to use | +| `graphType` | `string` | used for checking if classDefs should be applied | +| `classDefs` | `undefined` \| `null` \| `Record`<`string`, `DiagramStyleClassDef`> | the classDefs in the diagram text. Might be null if none were defined. Usually is the result of a call to getClasses(...) | #### Returns @@ -163,7 +178,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:164](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L164) +[mermaidAPI.ts:171](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L171) --- @@ -186,7 +201,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:212](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L212) +[mermaidAPI.ts:219](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L219) --- @@ -213,7 +228,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:148](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L148) +[mermaidAPI.ts:155](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L155) --- @@ -233,7 +248,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L125) +[mermaidAPI.ts:135](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L135) --- @@ -253,7 +268,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:96](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L96) +[mermaidAPI.ts:106](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L106) --- @@ -279,25 +294,24 @@ Put the svgCode into an iFrame. Return the iFrame code #### Defined in -[mermaidAPI.ts:263](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L263) +[mermaidAPI.ts:270](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L270) --- ### removeExistingElements -▸ **removeExistingElements**(`doc`, `isSandboxed`, `id`, `divSelector`, `iFrameSelector`): `void` +▸ **removeExistingElements**(`doc`, `id`, `divId`, `iFrameId`): `void` Remove any existing elements from the given document #### Parameters -| Name | Type | Description | -| :--------------- | :--------- | :---------------------------------------------- | -| `doc` | `Document` | the document to removed elements from | -| `isSandboxed` | `boolean` | whether or not we are in sandboxed mode | -| `id` | `string` | id for any existing SVG element | -| `divSelector` | `string` | selector for any existing enclosing div element | -| `iFrameSelector` | `string` | selector for any existing iFrame element | +| Name | Type | Description | +| :--------- | :--------- | :------------------------------------ | +| `doc` | `Document` | the document to removed elements from | +| `id` | `string` | id for any existing SVG element | +| `divId` | `string` | - | +| `iFrameId` | `string` | - | #### Returns @@ -305,4 +319,4 @@ Remove any existing elements from the given document #### Defined in -[mermaidAPI.ts:335](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L335) +[mermaidAPI.ts:341](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L341) diff --git a/docs/config/theming.md b/docs/config/theming.md index cfd86caa0..014ac1374 100644 --- a/docs/config/theming.md +++ b/docs/config/theming.md @@ -6,31 +6,27 @@ # Theme Configuration -With Version 8.7.0 Mermaid comes out with a system for dynamic and integrated configuration of themes. The intent is to increase the customizability and ease of styling for mermaid diagrams. +Dynamic and integrated theme configuration was introduced in Mermaid version 8.7.0. -The theme can be altered by changing the root level variable `theme` variable in the configuration. To change it for the whole site you must use the `initialize` call. To do it for just for a single diagram you can use the `%%init%%` directive +Themes can now be customized at the site-wide level, or on individual Mermaid diagrams. For site-wide theme customization, the `initialize` call is used. For diagram specific customization, the `init` directive is used. -Themes follow and build upon the Levels of Configuration, and employ `directives` to modify and create custom configurations, as they were introduced in Version [8.6.0](./8.6.0_docs.md). +## Available Themes -## Deployable Themes +1. [**default**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-default.js) - This is the default theme for all diagrams. -The following are a list of **Deployable themes**, sample `%%init%%` directives and `initialize` calls. +2. [**neutral**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-neutral.js) - This theme is great for black and white documents that will be printed. -1. **base**- Designed to be modified, as the name implies it is supposed to be used as the base for making custom themes. +3. [**dark**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-dark.js) - This theme goes well with dark-colored elements or dark-mode. -2. **forest**- A theme full of light greens that is easy on the eyes. +4. [**forest**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-forest.js) - This theme contains shades of green. -3. **dark**- A theme that would go well with other dark-colored elements. +5. [**base**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-base.js) - This is the only theme that can be modified. Use this theme as the base for customizations. -4. **default**- The default theme for all diagrams. +## Site-wide Theme -5. **neutral**- The theme to be used for black and white printing. +To customize themes site-wide, call the `initialize` method on the `mermaidAPI`. -## Site-wide Themes - -Site-wide themes are declared via `initialize` by site owners. - -Example of `Initialize` call setting `theme` to `base`: +Example of `initialize` call setting `theme` to `base`: ```javascript mermaidAPI.initialize({ @@ -39,28 +35,64 @@ mermaidAPI.initialize({ }); ``` -**Notes**: Only site owners can use the `mermaidAPI.initialize` call, to set values. Site-Users will have to use `%%init%%` to modify or create the theme for their diagrams. +## Diagram-specific Themes -## Themes at the Local or Current Level +To customize the theme of an individual diagram, use the `init` directive. -When Generating a diagram using on a webpage that supports mermaid. It is also possible to override site-wide theme settings locally, for a specific diagram, using directives, as long as it is not prohibited by the `secure` array. +Example of `init` directive setting the `theme` to `forest`: ```mermaid-example -%%{init: {'theme':'base'}}%% +%%{init: {'theme':'forest'}}%% graph TD a --> b ``` ```mermaid -%%{init: {'theme':'base'}}%% +%%{init: {'theme':'forest'}}%% graph TD a --> b ``` -Here is an example of how `%%init%%` can set the theme to 'base', this assumes that `themeVariables` are set to default: +```mermaid-example +%%{init: {'theme':'forest'}}%% + graph TD + a --> b +``` + +```mermaid +%%{init: {'theme':'forest'}}%% + graph TD + a --> b +``` + +> **Reminder**: the only theme that can be customed is the `base` theme. The following section covers how to use `themeVariables` for customizations. + +## Customizing Themes with `themeVariables` + +To make a custom theme, modify `themeVariables` via `init`. + +You will need to use the [base](#available-themes) theme as it is the only modifiable theme. + +| Parameter | Description | Type | Properties | +| -------------- | ------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- | +| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables-reference-table)) | + +Example of modifying `themeVariables` using the `init` directive: ```mermaid-example -%%{init: {'theme':'base'}}%% +%%{ + init: { + 'theme': 'base', + 'themeVariables': { + 'primaryColor': '#BB2528', + 'primaryTextColor': '#fff', + 'primaryBorderColor': '#7C0000', + 'lineColor': '#F8B229', + 'secondaryColor': '#006100', + 'tertiaryColor': '#fff' + } + } +}%% graph TD A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me think} @@ -78,7 +110,19 @@ Here is an example of how `%%init%%` can set the theme to 'base', this assumes t ``` ```mermaid -%%{init: {'theme':'base'}}%% +%%{ + init: { + 'theme': 'base', + 'themeVariables': { + 'primaryColor': '#BB2528', + 'primaryTextColor': '#fff', + 'primaryBorderColor': '#7C0000', + 'lineColor': '#F8B229', + 'secondaryColor': '#006100', + 'tertiaryColor': '#fff' + } + } +}%% graph TD A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me think} @@ -95,20 +139,20 @@ Here is an example of how `%%init%%` can set the theme to 'base', this assumes t end ``` -# List of Themes - -# Customizing Themes with `themeVariables` - -The easiest way to make a custom theme is to start with the base theme, and just modify theme variables through `themeVariables`, via `%%init%%`. - -| Parameter | Description | Type | Required | Objects contained | -| -------------- | ------------------------------------------------------------------ | ----- | -------- | ---------------------------------- | -| themeVariables | Array containing objects, modifiable with the `%%init%%` directive | Array | Required | primaryColor, lineColor, textColor | - -**Here is an example of overriding `primaryColor` through `themeVariables` and giving everything a different look, using `%%init%%`.** - ```mermaid-example -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%% +%%{ + init: { + 'theme': 'base', + 'themeVariables': { + 'primaryColor': '#BB2528', + 'primaryTextColor': '#fff', + 'primaryBorderColor': '#7C0000', + 'lineColor': '#F8B229', + 'secondaryColor': '#006100', + 'tertiaryColor': '#fff' + } + } +}%% graph TD A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me think} @@ -126,7 +170,19 @@ The easiest way to make a custom theme is to start with the base theme, and just ``` ```mermaid -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%% +%%{ + init: { + 'theme': 'base', + 'themeVariables': { + 'primaryColor': '#BB2528', + 'primaryTextColor': '#fff', + 'primaryBorderColor': '#7C0000', + 'lineColor': '#F8B229', + 'secondaryColor': '#006100', + 'tertiaryColor': '#fff' + } + } +}%% graph TD A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me think} @@ -143,601 +199,90 @@ The easiest way to make a custom theme is to start with the base theme, and just end ``` -**Notes:** -Leaving it empty will set all variable values to default. - -## Color and Color Calculation: - -Color definitions have certain interactions in mermaid, this is in order to ensure visibility for diagrams. Mermaid will adjust some variables automatically, when colors are changed in order to compensate and maintain readability. - -**The Default Value Column** to the right of the Variable column will denote the Variable paired/associated with the Variable on the left and the nature of this pairing or association. If it for instance says primaryColor it means that it gets primaryColor as default value. If it says "based on primaryColor" it means that it is calculated/ derived from primaryColor. This calculation can be primary color inversion, a change of hue, darkening or lightening by 10%, etc. - -You can create your own themes, by changing any of the given variables below. If you are using a dark background, set dark mode to true to adjust the colors. It is possible to override the calculations using the variable names below, with `%%init%%` if you wish to style it differently. - -## Theme Variables Reference Table - -> **Note** -> Variables that are unique to some diagrams can be affected by changes in Theme Variables - -| Variable | Default/Base/Factor value | Calc | Description | -| -------------------- | ------------------------------ | ---- | -------------------------------------------------------------------------------------------------------------------------------- | -| darkMode | false | | Boolean Value that dictates how to calculate colors. "true" will activate darkmode. | -| background | #f4f4f4 | | Used to calculate color for items that should either be background colored or contrasting to the background. | -| fontFamily | "trebuchet ms", verdana, arial | | | -| fontSize | 16px | | Font Size, in pixels | -| primaryColor | #fff4dd | | Color to be used as background in nodes, other colors will be derived from this | -| primaryBorderColor | based on primaryColor | \* | Color to be used as border in nodes using primaryColor | -| primaryTextColor | based on darkMode #ddd/#333 | \* | Color to be used as text color in nodes using primaryColor | -| secondaryColor | based on primaryColor | \* | | -| secondaryBorderColor | based on secondaryColor | \* | Color to be used as border in nodes using secondaryColor | -| secondaryTextColor | based on secondaryColor | \* | Color to be used as text color in nodes using secondaryColor | -| tertiaryColor | based on primaryColor | \* | | -| tertiaryBorderColor | based on tertiaryColor | \* | Color to be used as border in nodes using tertiaryColor | -| tertiaryTextColor | based on tertiaryColor | \* | Color to be used as text color in nodes using tertiaryColor | -| noteBkgColor | #fff5ad | | Color used as background in notes | -| noteTextColor | #333 | | Text color in note rectangles. | -| noteBorderColor | based on noteBkgColor | \* | Border color in note rectangles. | -| lineColor | based on background | \* | | -| textColor | based on primaryTextColor | \* | Text in diagram over the background for instance text on labels and on signals in sequence diagram or the title in gantt diagram | -| mainBkg | based on primaryColor | \* | Background in flowchart objects like rects/circles, class diagram classes, sequence diagram etc | -| errorBkgColor | tertiaryColor | \* | Color for syntax error message | -| errorTextColor | tertiaryTextColor | \* | Color for syntax error message | - -# What follows are Variables, specific to different diagrams and charts. - -## Some Theme Variables serve as, or affect the Default Values for Specific Diagram Variables, unless changed using `%%init%%` . - -## Flowchart - -| Variable | Default/ Associated Value | Calc | Description | -| ------------------- | ------------------------- | ---- | ---------------------------- | -| nodeBorder | primaryBorderColor | \* | Node Border Color | -| clusterBkg | tertiaryColor | \* | Background in subgraphs | -| clusterBorder | tertiaryBorderColor | \* | Cluster Border Color | -| defaultLinkColor | lineColor | \* | Link Color | -| titleColor | tertiaryTextColor | \* | Title Color | -| edgeLabelBackground | based on secondaryColor | \* | | -| nodeTextColor | primaryTextColor | \* | Color for text inside Nodes. | - -# sequence diagram - -| name | Default value | Calc | Description | -| --------------------- | ----------------------- | ---- | --------------------------- | -| actorBorder | primaryBorderColor | \* | Actor Border Color | -| actorBkg | mainBkg | \* | Actor Background Color | -| actorTextColor | primaryTextColor | \* | Actor Text Color | -| actorLineColor | grey | \* | Actor Line Color | -| signalColor | textColor | \* | Signal Color | -| signalTextColor | textColor | \* | Signal Text Color | -| labelBoxBkgColor | actorBkg | \* | Label Box Background Color | -| labelBoxBorderColor | actorBorder | \* | Label Box Border Color | -| labelTextColor | actorTextColor | \* | Label Text Color | -| loopTextColor | actorTextColor | \* | Loop ext Color | -| activationBorderColor | based on secondaryColor | \* | Activation Border Color | -| activationBkgColor | secondaryColor | \* | Activation Background Color | -| sequenceNumberColor | based on lineColor | \* | Sequence Number Color | - -# state colors - -| name | Default value | Calc | Description | -| ------------- | ---------------- | ---- | -------------------------------------------- | -| labelColor | primaryTextColor | \* | | -| altBackground | tertiaryColor | \* | Used for background in deep composite states | - -# class colors - -| name | Default value | Calc | Description | -| --------- | ------------- | ---- | ------------------------------- | -| classText | textColor | \* | Color of Text in class diagrams | - -# User journey colors - -| name | Default value | Calc | Description | -| --------- | ----------------------- | ---- | --------------------------------------- | -| fillType0 | primaryColor | \* | Fill for 1st section in journey diagram | -| fillType1 | secondaryColor | \* | Fill for 2nd section in journey diagram | -| fillType2 | based on primaryColor | \* | Fill for 3rd section in journey diagram | -| fillType3 | based on secondaryColor | \* | Fill for 4th section in journey diagram | -| fillType4 | based on primaryColor | \* | Fill for 5th section in journey diagram | -| fillType5 | based on secondaryColor | \* | Fill for 6th section in journey diagram | -| fillType6 | based on primaryColor | \* | Fill for 7th section in journey diagram | -| fillType7 | based on secondaryColor | \* | Fill for 8th section in journey diagram | - -\*\*Notes: Values are meant to create an alternating look. - -# Here is an example of overriding `primaryColor` and giving everything a different look, using `%%init%%`. - -```mermaid-example -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -```mermaid -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -\*\*This got a bit too dark and bit too colorful. With some easy steps this can be fixed: - -- Make the primary color a little lighter -- set the tertiary color to a reddish shade as well -- make the edge label background differ from the subgraph by setting the edgeLabelBackground - -```mermaid-example -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ffcccc', 'edgeLabelBackground':'#ffffee', 'tertiaryColor': '#fff0f0'}}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -```mermaid -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ffcccc', 'edgeLabelBackground':'#ffffee', 'tertiaryColor': '#fff0f0'}}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -The Theming Engine does not admit color codes and will only accept proper color values. Color Names is not supported so for instance, the color value 'red' will not work, but '#ff0000' will work. - -# Common theming activities - -## How to change the color of the arrows - -# Examples: - -When adjusting a theme it might be helpful to look at how your preferred theme goes with the diagrams, to evaluate whether everything is visible and looks good. -In the following examples, the directive `init` is used, with the `theme` being declared as `base`. For more information on using directives, read the documentation for [Version 8.6.0](/8.6.0_docs.md) - -### Flowchart - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -```mermaid -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -### Flowchart (beta) - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - flowchart TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[Another] - C ==>|One| D[Laptop] - C x--x|Two| E[iPhone] - C o--o|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -```mermaid -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - flowchart TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[Another] - C ==>|One| D[Laptop] - C x--x|Two| E[iPhone] - C o--o|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -### Sequence diagram - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - sequenceDiagram - autonumber - par Action 1 - Alice->>John: Hello John, how are you? - and Action 2 - Alice->>Bob: Hello Bob, how are you? - end - Alice->>+John: Hello John, how are you? - Alice->>+John: John, can you hear me? - John-->>-Alice: Hi Alice, I can hear you! - Note right of John: John is perceptive - John-->>-Alice: I feel great! - loop Every minute - John-->Alice: Great! - end -``` - -```mermaid -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - sequenceDiagram - autonumber - par Action 1 - Alice->>John: Hello John, how are you? - and Action 2 - Alice->>Bob: Hello Bob, how are you? - end - Alice->>+John: Hello John, how are you? - Alice->>+John: John, can you hear me? - John-->>-Alice: Hi Alice, I can hear you! - Note right of John: John is perceptive - John-->>-Alice: I feel great! - loop Every minute - John-->Alice: Great! - end -``` - -### Class diagram - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - -classDiagram - Animal "1" <|-- Duck - Animal <|-- Fish - Animal <--o Zebra - Animal : +int age - Animal : +String gender - Animal: +isMammal() - Animal: +mate() - class Duck{ - +String beakColor - +swim() - +quack() - } - class Fish{ - -int sizeInFeet - -canEat() - } - class Zebra{ - +bool is_wild - +run() - } -``` - -```mermaid -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - -classDiagram - Animal "1" <|-- Duck - Animal <|-- Fish - Animal <--o Zebra - Animal : +int age - Animal : +String gender - Animal: +isMammal() - Animal: +mate() - class Duck{ - +String beakColor - +swim() - +quack() - } - class Fish{ - -int sizeInFeet - -canEat() - } - class Zebra{ - +bool is_wild - +run() - } -``` - -### Gantt - -```mermaid-example -gantt - dateFormat YYYY-MM-DD - title Adding GANTT diagram functionality to mermaid - excludes :excludes the named dates/days from being included in a charted task.. - section A section - Completed task :done, des1, 2014-01-06,2014-01-08 - Active task :active, des2, 2014-01-09, 3d - Future task : des3, after des2, 5d - Future task2 : des4, after des3, 5d - - section Critical tasks - Completed task in the critical line :crit, done, 2014-01-06,24h - Implement parser and jison :crit, done, after des1, 2d - Create tests for parser :crit, active, 3d - Future task in critical line :crit, 5d - Create tests for renderer :2d - Add to mermaid :1d - - section Documentation - Describe gantt syntax :active, a1, after des1, 3d - Add gantt diagram to demo page :after a1 , 20h - Add another diagram to demo page :doc1, after a1 , 48h - - section Last section - Describe gantt syntax :after doc1, 3d - Add gantt diagram to demo page :20h - Add another diagram to demo page :48h -``` - -```mermaid -gantt - dateFormat YYYY-MM-DD - title Adding GANTT diagram functionality to mermaid - excludes :excludes the named dates/days from being included in a charted task.. - section A section - Completed task :done, des1, 2014-01-06,2014-01-08 - Active task :active, des2, 2014-01-09, 3d - Future task : des3, after des2, 5d - Future task2 : des4, after des3, 5d - - section Critical tasks - Completed task in the critical line :crit, done, 2014-01-06,24h - Implement parser and jison :crit, done, after des1, 2d - Create tests for parser :crit, active, 3d - Future task in critical line :crit, 5d - Create tests for renderer :2d - Add to mermaid :1d - - section Documentation - Describe gantt syntax :active, a1, after des1, 3d - Add gantt diagram to demo page :after a1 , 20h - Add another diagram to demo page :doc1, after a1 , 48h - - section Last section - Describe gantt syntax :after doc1, 3d - Add gantt diagram to demo page :20h - Add another diagram to demo page :48h -``` - -### State diagram - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - stateDiagram - [*] --> Active - - state Active { - [*] --> NumLockOff - NumLockOff --> NumLockOn : EvNumLockPressed - NumLockOn --> NumLockOff : EvNumLockPressed - -- - [*] --> CapsLockOff - CapsLockOff --> CapsLockOn : EvCapsLockPressed - CapsLockOn --> CapsLockOff : EvCapsLockPressed - -- - [*] --> ScrollLockOff - ScrollLockOff --> ScrollLockOn : EvCapsLockPressed - ScrollLockOn --> ScrollLockOff : EvCapsLockPressed - } - state SomethingElse { - A --> B - B --> A - } - - Active --> SomethingElse - note right of SomethingElse : This is the note to the right. - - SomethingElse --> [*] - -``` - -```mermaid -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - stateDiagram - [*] --> Active - - state Active { - [*] --> NumLockOff - NumLockOff --> NumLockOn : EvNumLockPressed - NumLockOn --> NumLockOff : EvNumLockPressed - -- - [*] --> CapsLockOff - CapsLockOff --> CapsLockOn : EvCapsLockPressed - CapsLockOn --> CapsLockOff : EvCapsLockPressed - -- - [*] --> ScrollLockOff - ScrollLockOff --> ScrollLockOn : EvCapsLockPressed - ScrollLockOn --> ScrollLockOff : EvCapsLockPressed - } - state SomethingElse { - A --> B - B --> A - } - - Active --> SomethingElse - note right of SomethingElse : This is the note to the right. - - SomethingElse --> [*] - -``` - -### State diagram (beta) - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% -stateDiagram-v2 - [*] --> Active - - state Active { - [*] --> NumLockOff - NumLockOff --> NumLockOn : EvNumLockPressed - NumLockOn --> NumLockOff : EvNumLockPressed - -- - [*] --> CapsLockOff - CapsLockOff --> CapsLockOn : EvCapsLockPressed - CapsLockOn --> CapsLockOff : EvCapsLockPressed - -- - [*] --> ScrollLockOff - ScrollLockOff --> ScrollLockOn : EvCapsLockPressed - ScrollLockOn --> ScrollLockOff : EvCapsLockPressed - } - state SomethingElse { - A --> B - B --> A - } - - Active --> SomethingElse2 - note right of SomethingElse2 : This is the note to the right. - - SomethingElse2 --> [*] -``` - -```mermaid -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% -stateDiagram-v2 - [*] --> Active - - state Active { - [*] --> NumLockOff - NumLockOff --> NumLockOn : EvNumLockPressed - NumLockOn --> NumLockOff : EvNumLockPressed - -- - [*] --> CapsLockOff - CapsLockOff --> CapsLockOn : EvCapsLockPressed - CapsLockOn --> CapsLockOff : EvCapsLockPressed - -- - [*] --> ScrollLockOff - ScrollLockOff --> ScrollLockOn : EvCapsLockPressed - ScrollLockOn --> ScrollLockOff : EvCapsLockPressed - } - state SomethingElse { - A --> B - B --> A - } - - Active --> SomethingElse2 - note right of SomethingElse2 : This is the note to the right. - - SomethingElse2 --> [*] -``` - -### Entity Relations diagram - -```mermaid-example - erDiagram - CUSTOMER }|..|{ DELIVERY-ADDRESS : has - CUSTOMER ||--o{ ORDER : places - CUSTOMER ||--o{ INVOICE : "liable for" - DELIVERY-ADDRESS ||--o{ ORDER : receives - INVOICE ||--|{ ORDER : covers - ORDER ||--|{ ORDER-ITEM : includes - PRODUCT-CATEGORY ||--|{ PRODUCT : contains - PRODUCT ||--o{ ORDER-ITEM : "ordered in" -``` - -```mermaid - erDiagram - CUSTOMER }|..|{ DELIVERY-ADDRESS : has - CUSTOMER ||--o{ ORDER : places - CUSTOMER ||--o{ INVOICE : "liable for" - DELIVERY-ADDRESS ||--o{ ORDER : receives - INVOICE ||--|{ ORDER : covers - ORDER ||--|{ ORDER-ITEM : includes - PRODUCT-CATEGORY ||--|{ PRODUCT : contains - PRODUCT ||--o{ ORDER-ITEM : "ordered in" -``` - -### User journey diagram - -```mermaid-example -journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me -``` - -```mermaid -journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me -``` +## Color and Color Calculation + +To ensure diagram readability, the default value of certain variables is calculated or derived from other variables. For example, `primaryBorderColor` is derived from the `primaryColor` variable. So if the `primaryColor` variable is customized, Mermaid will adjust `primaryBorderColor` automatically. Adjustments can mean a color inversion, a hue change, a darkening/lightening by 10%, etc. + +The theming engine will only recognize hex colors and not color names. So, the value `#ff0000` will work, but `red` will not. + +## Theme Variables + +| Variable | Default value | Description | +| -------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| darkMode | false | Affects how derived colors are calculated. Set value to `true` for dark mode. | +| background | #f4f4f4 | Used to calculate color for items that should either be background colored or contrasting to the background | +| fontFamily | trebuchet ms, verdana, arial | | +| fontSize | 16px | Font size in pixels | +| primaryColor | #fff4dd | Color to be used as background in nodes, other colors will be derived from this | +| primaryTextColor | calculated from darkMode #ddd/#333 | Color to be used as text color in nodes using `primaryColor` | +| secondaryColor | calculated from primaryColor | | +| primaryBorderColor | calculated from primaryColor | Color to be used as border in nodes using `primaryColor` | +| secondaryBorderColor | calculated from secondaryColor | Color to be used as border in nodes using `secondaryColor` | +| secondaryTextColor | calculated from secondaryColor | Color to be used as text color in nodes using `secondaryColor` | +| tertiaryColor | calculated from primaryColor | | +| tertiaryBorderColor | calculated from tertiaryColor | Color to be used as border in nodes using `tertiaryColor` | +| tertiaryTextColor | calculated from tertiaryColor | Color to be used as text color in nodes using `tertiaryColor` | +| noteBkgColor | #fff5ad | Color used as background in notes | +| noteTextColor | #333 | Text color in note rectangles | +| noteBorderColor | calculated from noteBkgColor | Border color in note rectangles | +| lineColor | calculated from background | | +| textColor | calculated from primaryTextColor | Text in diagram over the background for instance text on labels and on signals in sequence diagram or the title in Gantt diagram | +| mainBkg | calculated from primaryColor | Background in flowchart objects like rects/circles, class diagram classes, sequence diagram etc | +| errorBkgColor | tertiaryColor | Color for syntax error message | +| errorTextColor | tertiaryTextColor | Color for syntax error message | + +## Flowchart Variables + +| Variable | Default value | Description | +| ------------------- | ------------------------------ | --------------------------- | +| nodeBorder | primaryBorderColor | Node Border Color | +| clusterBkg | tertiaryColor | Background in subgraphs | +| clusterBorder | tertiaryBorderColor | Cluster Border Color | +| defaultLinkColor | lineColor | Link Color | +| titleColor | tertiaryTextColor | Title Color | +| edgeLabelBackground | calculated from secondaryColor | | +| nodeTextColor | primaryTextColor | Color for text inside Nodes | + +## Sequence Diagram Variables + +| Variable | Default value | Description | +| --------------------- | ------------------------------ | --------------------------- | +| actorBkg | mainBkg | Actor Background Color | +| actorBorder | primaryBorderColor | Actor Border Color | +| actorTextColor | primaryTextColor | Actor Text Color | +| actorLineColor | grey | Actor Line Color | +| signalColor | textColor | Signal Color | +| signalTextColor | textColor | Signal Text Color | +| labelBoxBkgColor | actorBkg | Label Box Background Color | +| labelBoxBorderColor | actorBorder | Label Box Border Color | +| labelTextColor | actorTextColor | Label Text Color | +| loopTextColor | actorTextColor | Loop Text Color | +| activationBorderColor | calculated from secondaryColor | Activation Border Color | +| activationBkgColor | secondaryColor | Activation Background Color | +| sequenceNumberColor | calculated from lineColor | Sequence Number Color | + +## State Colors + +| Variable | Default value | Description | +| ------------- | ---------------- | -------------------------------------------- | +| labelColor | primaryTextColor | | +| altBackground | tertiaryColor | Used for background in deep composite states | + +## Class Colors + +| Variable | Default value | Description | +| --------- | ------------- | ------------------------------- | +| classText | textColor | Color of Text in class diagrams | + +## User Journey Colors + +| Variable | Default value | Description | +| --------- | ------------------------------ | --------------------------------------- | +| fillType0 | primaryColor | Fill for 1st section in journey diagram | +| fillType1 | secondaryColor | Fill for 2nd section in journey diagram | +| fillType2 | calculated from primaryColor | Fill for 3rd section in journey diagram | +| fillType3 | calculated from secondaryColor | Fill for 4th section in journey diagram | +| fillType4 | calculated from primaryColor | Fill for 5th section in journey diagram | +| fillType5 | calculated from secondaryColor | Fill for 6th section in journey diagram | +| fillType6 | calculated from primaryColor | Fill for 7th section in journey diagram | +| fillType7 | calculated from secondaryColor | Fill for 8th section in journey diagram | diff --git a/docs/config/usage.md b/docs/config/usage.md index 29273e9b5..866dd6be7 100644 --- a/docs/config/usage.md +++ b/docs/config/usage.md @@ -12,7 +12,7 @@ Diagrams can be re-rendered/modified by modifying their descriptions. ### CDN - + Please note that you can switch versions through the dropdown box at the top right. @@ -22,24 +22,21 @@ For the majority of users, Using the [Live Editor](https://mermaid.live/) would We have compiled some Video [Tutorials](./Tutorials.md) on how to use the mermaid Live Editor. -**Installing and Hosting Mermaid on a Webpage** +### Installing and Hosting Mermaid on a Webpage -**Using the npm package** +**Using the npm package:** - 1. You will need to install node v16, which would have npm. +1. You will need to install `node v16`, which would have npm. - 2. download yarn using npm. +2. Download `yarn` using npm. - 3. enter the following command: - yarn add mermaid +3. Enter the following command: `yarn add mermaid`. - 4. At this point, you can add mermaid as a dev dependency using this command: - yarn add --dev mermaid +4. At this point, you can add mermaid as a dev dependency using this command: `yarn add --dev mermaid`. - 5. Alternatively, you can also deploy mermaid using the script tag in an HTML file with mermaid diagram descriptions. - as is shown in the example below +5. Alternatively, you can also deploy mermaid using the script tag in an HTML file with mermaid diagram descriptions as is shown in the example below. -**Hosting mermaid on a web page.** +**Hosting mermaid on a web page:** > Note:This topic explored in greater depth in the [User Guide for Beginners](../intro/n00b-gettingStarted.md) @@ -62,7 +59,7 @@ Example: ```html ``` @@ -85,7 +82,7 @@ Example: B-->D(fa:fa-spinner); @@ -152,7 +149,7 @@ $(document).ready(function () { }); ``` -Not doing so will most likely result in mermaid rendering graphs that have labels out of bounds. The default integration in mermaid uses the window.load event to start rendering. +Not doing so will most likely result in mermaid rendering graphs that have labels out of bounds. The default integration in mermaid uses the window\.load event to start rendering. If your page has other fonts in its body those might be used instead of the mermaid font. Specifying the font in your styling is a workaround for this. @@ -334,8 +331,8 @@ The future proof way of setting the configuration is by using the initialization on what kind of integration you use. ```html - - diff --git a/packages/mermaid/src/docs/misc/integrations.md b/docs/ecosystem/integrations.md similarity index 95% rename from packages/mermaid/src/docs/misc/integrations.md rename to docs/ecosystem/integrations.md index 06d09634f..33b6b966e 100644 --- a/packages/mermaid/src/docs/misc/integrations.md +++ b/docs/ecosystem/integrations.md @@ -1,3 +1,9 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/integrations.md](../../packages/mermaid/src/docs/ecosystem/integrations.md). + # Integrations The following list is a compilation of different integrations and plugins that allow the rendering of mermaid definitions within other applications. @@ -15,6 +21,7 @@ They also serve as proof of concept, for the variety of things that can be built - [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) (**Native support**) - [Tuleap](https://docs.tuleap.org/user-guide/writing-in-tuleap.html#graphs) (**Native support**) - [Joplin](https://joplinapp.org) (**Native support**) +- [Swimm](https://swimm.io) (**Native support**) - [Notion](https://notion.so) (**Native support**) - [Observable](https://observablehq.com/@observablehq/mermaid) (**Native support**) - [Obsidian](https://help.obsidian.md/How+to/Format+your+notes#Diagram) (**Native support**) @@ -88,9 +95,10 @@ They also serve as proof of concept, for the variety of things that can be built ## 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) - [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 Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor) - [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export) @@ -103,10 +111,10 @@ They also serve as proof of concept, for the variety of things that can be built - [md-it-mermaid](https://github.com/iamcco/md-it-mermaid) - [markdown-it-mermaid-fence-new](https://github.com/Revomatico/markdown-it-mermaid-fence-new) - [markdown-it-mermaid-less](https://github.com/searKing/markdown-it-mermaid-less) -- [Atom](https://atom.io) - - [Markdown Preview Enhanced](https://atom.io/packages/markdown-preview-enhanced) - - [Atom Mermaid](https://atom.io/packages/atom-mermaid) - - [Language Mermaid Syntax Highlighter](https://atom.io/packages/language-mermaid) +- Atom _(Atom has been [archived.](https://github.blog/2022-06-08-sunsetting-atom/))_ + - [Markdown Preview Enhanced](https://github.com/shd101wyy/markdown-preview-enhanced) + - [Atom Mermaid](https://github.com/y-takey/atom-mermaid) + - [Language Mermaid Syntax Highlighter](https://github.com/ytisf/language-mermaid) - [Sublime Text 3](https://sublimetext.com) - [Mermaid Package](https://packagecontrol.io/packages/Mermaid) - [Astah](https://astah.net) diff --git a/docs/ecosystem/showcases.md b/docs/ecosystem/showcases.md new file mode 100644 index 000000000..9f18103b9 --- /dev/null +++ b/docs/ecosystem/showcases.md @@ -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). diff --git a/docs/intro/index.md b/docs/intro/index.md index b8a27acff..848df4301 100644 --- a/docs/intro/index.md +++ b/docs/intro/index.md @@ -31,11 +31,11 @@ But not having diagrams or docs ruins productivity and hurts organizational lear Mermaid addresses this problem by enabling users to create easily modifiable diagrams, it can also be made part of production scripts (and other pieces of code).

Mermaid allows even non-programmers to easily create detailed and diagrams through the [Mermaid Live Editor](https://mermaid.live/).
[Tutorials](../config/Tutorials.md) has video tutorials. -Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](../misc/integrations.md). +Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](../ecosystem/integrations.md). For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](../community/n00b-overview.md) and [Usage](../config/usage.md). -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](../community/development.md) | 🔌 [Plug-Ins](../misc/integrations.md) +🌐 [CDN](https://www.jsdelivr.com/package/npm/mermaid) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](../community/development.md) | 🔌 [Plug-Ins](../ecosystem/integrations.md) > 🖖 Keep a steady pulse: mermaid needs more Collaborators, [Read More](https://github.com/knsv/mermaid/issues/866). @@ -243,13 +243,13 @@ journey ### CDN - https://unpkg.com/mermaid@/dist/ + https://cdn.jsdelivr.net/npm/mermaid@/dist/ To select a version: Replace `` with the desired version number. -Latest Version: +Latest Version: ## Deploying Mermaid @@ -267,7 +267,7 @@ To Deploy Mermaid: ```html ``` @@ -367,7 +367,7 @@ _Unfortunately you can not have a cake and eat it at the same time which in this ## Reporting vulnerabilities -To report a vulnerability, please e-mail security@mermaid.live with a description of the issue, the steps you took to create the issue, affected versions, and if known, mitigations for the issue. +To report a vulnerability, please e-mail with a description of the issue, the steps you took to create the issue, affected versions, and if known, mitigations for the issue. ## Appreciation diff --git a/docs/intro/n00b-gettingStarted.md b/docs/intro/n00b-gettingStarted.md index 69bb80e54..87592ba27 100644 --- a/docs/intro/n00b-gettingStarted.md +++ b/docs/intro/n00b-gettingStarted.md @@ -17,7 +17,7 @@ This section talks about the different ways to deploy Mermaid. Learning the [Syn ## Four ways of using mermaid: 1. Using the Mermaid Live Editor at [mermaid.live](https://mermaid.live). -2. Using [mermaid plugins](../misc/integrations.md) with programs you are familiar with. +2. Using [mermaid plugins](../ecosystem/integrations.md) with programs you are familiar with. 3. Calling the Mermaid JavaScript API. 4. Deploying Mermaid as a dependency. @@ -79,13 +79,13 @@ Editing is as easy as pasting your **Diagram code**, into the `code` section of The Gist you create should have a code.mmd file and optionally a config.json. [Example](https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a) -To load a gist into the Editor, you can use https://mermaid.live/edit?gist=https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a +To load a gist into the Editor, you can use -and to View, https://mermaid.live/view?gist=https://gist.github.com/sidharthv96/6268a23e673a533dcb198f241fd7012a +and to View, ## 2. Using Mermaid Plugins: -You can generate mermaid diagrams from within popular applications using plug-ins. It can be done in the same way, you would use the Live Editor. Here's a list of [Mermaid Plugins](../misc/integrations.md). +You can generate mermaid diagrams from within popular applications using plug-ins. It can be done in the same way, you would use the Live Editor. Here's a list of [Mermaid Plugins](../ecosystem/integrations.md). **This is covered in greater detail in the [Usage section](../config/usage.md)** @@ -128,14 +128,14 @@ b. The importing of mermaid library through the `mermaid.esm.js` or `mermaid.esm ```html ``` **Notes**: -Rendering in Mermaid is initialized by `mermaid.initialize()` call. You can place `mermaid.initialize()` inside `mermaid.min.js` for brevity. However, doing the opposite lets you control when it starts looking for `
`tags inside the web page with `mermaid.initialize()`. This is useful when you think that not all `
` tags may have loaded on the execution of `mermaid.min.js` file. +Rendering in Mermaid is initialized by `mermaid.initialize()` call. You can place `mermaid.initialize()` inside `mermaid.esm.min.mjs` for brevity. However, doing the opposite lets you control when it starts looking for `
`tags inside the web page with `mermaid.initialize()`. This is useful when you think that not all `
` tags may have loaded on the execution of `mermaid.esm.min.mjs` file. `startOnLoad` is one of the parameters that can be defined by `mermaid.initialize()` @@ -172,7 +172,7 @@ Please refer to the [Mindmap](../syntax/mindmap.md?id=integrating-with-your-libr diff --git a/docs/syntax/c4c.md b/docs/syntax/c4c.md index e946aedb6..dd5fa21b0 100644 --- a/docs/syntax/c4c.md +++ b/docs/syntax/c4c.md @@ -130,121 +130,121 @@ The number of shapes per row and the number of boundaries can be adjusted using The following unfinished features are not supported in the short term. -- \[ ] sprite +- [ ] sprite -- \[ ] tags +- [ ] tags -- \[ ] link +- [ ] link -- \[ ] Legend +- [ ] Legend -- \[x] System Context +- [x] System Context -- - \[x] Person(alias, label, ?descr, ?sprite, ?tags, $link) +- - [x] Person(alias, label, ?descr, ?sprite, ?tags, $link) -- - \[x] Person_Ext +- - [x] Person_Ext -- - \[x] System(alias, label, ?descr, ?sprite, ?tags, $link) +- - [x] System(alias, label, ?descr, ?sprite, ?tags, $link) -- - \[x] SystemDb +- - [x] SystemDb -- - \[x] SystemQueue +- - [x] SystemQueue -- - \[x] System_Ext +- - [x] System_Ext -- - \[x] SystemDb_Ext +- - [x] SystemDb_Ext -- - \[x] SystemQueue_Ext +- - [x] SystemQueue_Ext -- - \[x] Boundary(alias, label, ?type, ?tags, $link) +- - [x] Boundary(alias, label, ?type, ?tags, $link) -- - \[x] Enterprise_Boundary(alias, label, ?tags, $link) +- - [x] Enterprise_Boundary(alias, label, ?tags, $link) -- - \[x] System_Boundary +- - [x] System_Boundary -- \[x] Container diagram +- [x] Container diagram -- - \[x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link) +- - [x] Container(alias, label, ?techn, ?descr, ?sprite, ?tags, $link) -- - \[x] ContainerDb +- - [x] ContainerDb -- - \[x] ContainerQueue +- - [x] ContainerQueue -- - \[x] Container_Ext +- - [x] Container_Ext -- - \[x] ContainerDb_Ext +- - [x] ContainerDb_Ext -- - \[x] ContainerQueue_Ext +- - [x] ContainerQueue_Ext -- - \[x] Container_Boundary(alias, label, ?tags, $link) +- - [x] Container_Boundary(alias, label, ?tags, $link) -- \[x] Component diagram +- [x] Component diagram -- - \[x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link) +- - [x] Component(alias, label, ?techn, ?descr, ?sprite, ?tags, $link) -- - \[x] ComponentDb +- - [x] ComponentDb -- - \[x] ComponentQueue +- - [x] ComponentQueue -- - \[x] Component_Ext +- - [x] Component_Ext -- - \[x] ComponentDb_Ext +- - [x] ComponentDb_Ext -- - \[x] ComponentQueue_Ext +- - [x] ComponentQueue_Ext -- \[x] Dynamic diagram +- [x] Dynamic diagram -- - \[x] RelIndex(index, from, to, label, ?tags, $link) +- - [x] RelIndex(index, from, to, label, ?tags, $link) -- \[x] Deployment diagram +- [x] Deployment diagram -- - \[x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link) +- - [x] Deployment_Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link) -- - \[x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node() +- - [x] Node(alias, label, ?type, ?descr, ?sprite, ?tags, $link): short name of Deployment_Node() -- - \[x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node() +- - [x] Node_L(alias, label, ?type, ?descr, ?sprite, ?tags, $link): left aligned Node() -- - \[x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node() +- - [x] Node_R(alias, label, ?type, ?descr, ?sprite, ?tags, $link): right aligned Node() -- \[x] Relationship Types +- [x] Relationship Types -- - \[x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link) +- - [x] Rel(from, to, label, ?techn, ?descr, ?sprite, ?tags, $link) -- - \[x] BiRel (bidirectional relationship) +- - [x] BiRel (bidirectional relationship) -- - \[x] Rel_U, Rel_Up +- - [x] Rel_U, Rel_Up -- - \[x] Rel_D, Rel_Down +- - [x] Rel_D, Rel_Down -- - \[x] Rel_L, Rel_Left +- - [x] Rel_L, Rel_Left -- - \[x] Rel_R, Rel_Right +- - [x] Rel_R, Rel_Right -- - \[x] Rel_Back +- - [x] Rel_Back -- - \[x] RelIndex \* Compatible with C4-Plantuml syntax, but ignores the index parameter. The sequence number is determined by the order in which the rel statements are written. +- - [x] RelIndex \* Compatible with C4-Plantuml syntax, but ignores the index parameter. The sequence number is determined by the order in which the rel statements are written. -- \[ ] Custom tags/stereotypes support and skin param updates +- [ ] Custom tags/stereotypes support and skin param updates -- - \[ ] AddElementTag(tagStereo, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new element tag. The styles of the tagged elements are updated and the tag is displayed in the calculated legend. +- - [ ] AddElementTag(tagStereo, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new element tag. The styles of the tagged elements are updated and the tag is displayed in the calculated legend. -- - \[ ] AddRelTag(tagStereo, ?textColor, ?lineColor, ?lineStyle, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new Relationship tag. The styles of the tagged relationships are updated and the tag is displayed in the calculated legend. +- - [ ] AddRelTag(tagStereo, ?textColor, ?lineColor, ?lineStyle, ?sprite, ?techn, ?legendText, ?legendSprite): Introduces a new Relationship tag. The styles of the tagged relationships are updated and the tag is displayed in the calculated legend. -- - \[x] UpdateElementStyle(elementName, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): This call updates the default style of the elements (component, ...) and creates no additional legend entry. +- - [x] UpdateElementStyle(elementName, ?bgColor, ?fontColor, ?borderColor, ?shadowing, ?shape, ?sprite, ?techn, ?legendText, ?legendSprite): This call updates the default style of the elements (component, ...) and creates no additional legend entry. -- - \[x] UpdateRelStyle(from, to, ?textColor, ?lineColor, ?offsetX, ?offsetY): This call updates the default relationship colors and creates no additional legend entry. Two new parameters, offsetX and offsetY, are added to set the offset of the original position of the text. +- - [x] UpdateRelStyle(from, to, ?textColor, ?lineColor, ?offsetX, ?offsetY): This call updates the default relationship colors and creates no additional legend entry. Two new parameters, offsetX and offsetY, are added to set the offset of the original position of the text. -- - \[ ] RoundedBoxShape(): This call returns the name of the rounded box shape and can be used as ?shape argument. +- - [ ] RoundedBoxShape(): This call returns the name of the rounded box shape and can be used as ?shape argument. -- - \[ ] EightSidedShape(): This call returns the name of the eight sided shape and can be used as ?shape argument. +- - [ ] EightSidedShape(): This call returns the name of the eight sided shape and can be used as ?shape argument. -- - \[ ] DashedLine(): This call returns the name of the dashed line and can be used as ?lineStyle argument. +- - [ ] DashedLine(): This call returns the name of the dashed line and can be used as ?lineStyle argument. -- - \[ ] DottedLine(): This call returns the name of the dotted line and can be used as ?lineStyle argument. +- - [ ] DottedLine(): This call returns the name of the dotted line and can be used as ?lineStyle argument. -- - \[ ] BoldLine(): This call returns the name of the bold line and can be used as ?lineStyle argument. +- - [ ] BoldLine(): This call returns the name of the bold line and can be used as ?lineStyle argument. -- - \[x] UpdateLayoutConfig(?c4ShapeInRow, ?c4BoundaryInRow): New. This call updates the default c4ShapeInRow(4) and c4BoundaryInRow(2). +- - [x] UpdateLayoutConfig(?c4ShapeInRow, ?c4BoundaryInRow): New. This call updates the default c4ShapeInRow(4) and c4BoundaryInRow(2). There are two ways to assign parameters with question marks. One uses the non-named parameter assignment method in the order of the parameters, and the other uses the named parameter assignment method, where the name must start with a $ symbol. diff --git a/docs/syntax/classDiagram.md b/docs/syntax/classDiagram.md index 5870d0743..db612fb4f 100644 --- a/docs/syntax/classDiagram.md +++ b/docs/syntax/classDiagram.md @@ -7,7 +7,8 @@ # Class diagrams > "In software engineering, a class diagram in the Unified Modeling Language (UML) is a type of static structure diagram that describes the structure of a system by showing the system's classes, their attributes, operations (or methods), and the relationships among objects." -> Wikipedia +> +> \-Wikipedia The class diagram is the main building block of object-oriented modeling. It is used for general conceptual modeling of the structure of the application, and for detailed modeling to translate the models into programming code. Class diagrams can also be used for data modeling. The classes in a class diagram represent both the main elements, interactions in the application, and the classes to be programmed. @@ -205,7 +206,7 @@ class BankAccount{ #### Generic Types -Members can be defined using generic types, such as `List`, for fields, parameters, and return types by enclosing the type within `~` (**tilde**). Note: **nested** type declarations such as `List>` are not currently supported. +Members can be defined using generic types, such as `List`, for fields, parameters, and return types by enclosing the type within `~` (**tilde**). **Nested** type declarations such as `List>` are supported. Generics can be represented as part of a class definition and also in the parameters or the return value of a method/function: @@ -221,6 +222,7 @@ class Square~Shape~{ Square : -List~string~ messages Square : +setMessages(List~string~ messages) Square : +getMessages() List~string~ +Square : +getDistanceMatrix() List~List~int~~ ``` ```mermaid @@ -235,12 +237,9 @@ class Square~Shape~{ Square : -List~string~ messages Square : +setMessages(List~string~ messages) Square : +getMessages() List~string~ +Square : +getDistanceMatrix() List~List~int~~ ``` -#### Return Type - -Optionally you can end the method/function definition with the data type that will be returned. - #### Visibility To describe the visibility (or encapsulation) of an attribute or method/function that is a part of a class (i.e. a class member), optional notation may be placed before that members' name: @@ -270,7 +269,7 @@ There are eight different types of relations defined for classes under UML which | Type | Description | | ------- | ------------- | | `<\|--` | Inheritance | -| `\*--` | Composition | +| `*--` | Composition | | `o--` | Aggregation | | `-->` | Association | | `--` | Link (Solid) | diff --git a/docs/syntax/entityRelationshipDiagram.md b/docs/syntax/entityRelationshipDiagram.md index 9b938bc36..9fa5fa517 100644 --- a/docs/syntax/entityRelationshipDiagram.md +++ b/docs/syntax/entityRelationshipDiagram.md @@ -162,7 +162,7 @@ erDiagram ### Attributes -Attributes can be defined for entities by specifying the entity name followed by a block containing multiple `type name` pairs, where a block is delimited by an opening `{` and a closing `}`. For example: +Attributes can be defined for entities by specifying the entity name followed by a block containing multiple `type name` pairs, where a block is delimited by an opening `{` and a closing `}`. The attributes are rendered inside the entity boxes. For example: ```mermaid-example erDiagram @@ -196,82 +196,58 @@ erDiagram } ``` -The attributes are rendered inside the entity boxes: - -```mermaid-example -erDiagram - CAR ||--o{ NAMED-DRIVER : allows - CAR { - string registrationNumber - string make - string model - } - PERSON ||--o{ NAMED-DRIVER : is - PERSON { - string firstName - string lastName - int age - } -``` - -```mermaid -erDiagram - CAR ||--o{ NAMED-DRIVER : allows - CAR { - string registrationNumber - string make - string model - } - PERSON ||--o{ NAMED-DRIVER : is - PERSON { - string firstName - string lastName - int age - } -``` - -The `type` and `name` values must begin with an alphabetic character and may contain digits, hyphens or underscores. Other than that, there are no restrictions, and there is no implicit set of valid data types. +The `type` and `name` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. Other than that, there are no restrictions, and there is no implicit set of valid data types. #### Attribute Keys and Comments -Attributes may also have a `key` or comment defined. Keys can be "PK" or "FK", for Primary Key or Foreign 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 erDiagram CAR ||--o{ NAMED-DRIVER : allows CAR { - string allowedDriver FK "The license of the allowed driver" - string registrationNumber + string registrationNumber PK string make string model + string[] parts } PERSON ||--o{ NAMED-DRIVER : is PERSON { string driversLicense PK "The license #" - string firstName + string(99) firstName "Only 99 characters are allowed" string lastName + string phone UK 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 erDiagram CAR ||--o{ NAMED-DRIVER : allows CAR { - string allowedDriver FK "The license of the allowed driver" - string registrationNumber + string registrationNumber PK string make string model + string[] parts } PERSON ||--o{ NAMED-DRIVER : is PERSON { string driversLicense PK "The license #" - string firstName + string(99) firstName "Only 99 characters are allowed" string lastName + string phone UK 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 diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md index 8b3859f8a..fd0408c45 100644 --- a/docs/syntax/flowchart.md +++ b/docs/syntax/flowchart.md @@ -30,7 +30,8 @@ flowchart LR id ``` -> **Note** The id is what is displayed in the box. +> **Note** +> The id is what is displayed in the box. ### A node with text @@ -842,8 +843,8 @@ In the example below the style defined in the linkStyle statement will belong to ### 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. -Available curve styles include `basis`, `bump`, `linear`, `monotoneX`, `monotoneY`, `natural`, `step`, `stepAfter`, -and `stepBefore`. +Available curve styles include `basis`, `bumpX`, `bumpY`, `cardinal`, `catmullRom`, `linear`, `monotoneX`, `monotoneY`, +`natural`, `step`, `stepAfter`, and `stepBefore`. In this example, a left-to-right graph uses the `stepBefore` curve style: @@ -990,7 +991,22 @@ flowchart LR C -->|Two| E[Result two] ``` -## Configuration... +## Configuration + +### Renderer + +The layout of the diagram is done with the renderer. The default renderer is dagre. + +Starting with Mermaid version 9.4, you can use an alternate renderer named elk. The elk renderer is better for larger and/or more complex diagrams. + +The _elk_ renderer is an experimenal feature. +You can change the renderer to elk by adding this directive: + + %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% + +Note that the site needs to use mermaid version 9.4+ for this to work and have this featured enabled in the lazy-loading configuration. + +### Width It is possible to adjust the width of the rendered flowchart. diff --git a/docs/syntax/gantt.md b/docs/syntax/gantt.md index b20b6b776..8e9ad3cfd 100644 --- a/docs/syntax/gantt.md +++ b/docs/syntax/gantt.md @@ -172,65 +172,72 @@ Final milestone : milestone, m2, 18:14, 2min The default input date format is `YYYY-MM-DD`. You can define your custom `dateFormat`. - dateFormat YYYY-MM-DD +```markdown +dateFormat YYYY-MM-DD +``` The following formatting options are supported: - Input Example Description: - YYYY 2014 4 digit year - YY 14 2 digit year - Q 1..4 Quarter of year. Sets month to first month in quarter. - M MM 1..12 Month number - MMM MMMM January..Dec Month name in locale set by moment.locale() - D DD 1..31 Day of month - Do 1st..31st Day of month with ordinal - DDD DDDD 1..365 Day of year - X 1410715640.579 Unix timestamp - x 1410715640579 Unix ms timestamp - H HH 0..23 24 hour time - h hh 1..12 12 hour time used with a A. - a A am pm Post or ante meridiem - m mm 0..59 Minutes - s ss 0..59 Seconds - S 0..9 Tenths of a second - SS 0..99 Hundreds of a second - SSS 0..999 Thousandths of a second - Z ZZ +12:00 Offset from UTC as +-HH:mm, +-HHmm, or Z +| Input | Example | Description | +| ---------- | -------------- | ------------------------------------------------------ | +| `YYYY` | 2014 | 4 digit year | +| `YY` | 14 | 2 digit year | +| `Q` | 1..4 | Quarter of year. Sets month to first month in quarter. | +| `M MM` | 1..12 | Month number | +| `MMM MMMM` | January..Dec | Month name in locale set by `moment.locale()` | +| `D DD` | 1..31 | Day of month | +| `Do` | 1st..31st | Day of month with ordinal | +| `DDD DDDD` | 1..365 | Day of year | +| `X` | 1410715640.579 | Unix timestamp | +| `x` | 1410715640579 | Unix ms timestamp | +| `H HH` | 0..23 | 24 hour time | +| `h hh` | 1..12 | 12 hour time used with `a A`. | +| `a A` | am pm | Post or ante meridiem | +| `m mm` | 0..59 | Minutes | +| `s ss` | 0..59 | Seconds | +| `S` | 0..9 | Tenths of a second | +| `SS` | 0..99 | Hundreds of a second | +| `SSS` | 0..999 | Thousandths of a second | +| `Z ZZ` | +12:00 | Offset from UTC as +-HH:mm, +-HHmm, or Z | -More info in: https://momentjs.com/docs/#/parsing/string-format/ +More info in: ### Output date format on the axis The default output date format is `YYYY-MM-DD`. You can define your custom `axisFormat`, like `2020-Q1` for the first quarter of the year 2020. - axisFormat %Y-%m-%d +```markdown +axisFormat %Y-%m-%d +``` The following formatting strings are supported: - %a - abbreviated weekday name. - %A - full weekday name. - %b - abbreviated month name. - %B - full month name. - %c - date and time, as "%a %b %e %H:%M:%S %Y". - %d - zero-padded day of the month as a decimal number [01,31]. - %e - space-padded day of the month as a decimal number [ 1,31]; equivalent to %_d. - %H - hour (24-hour clock) as a decimal number [00,23]. - %I - hour (12-hour clock) as a decimal number [01,12]. - %j - day of the year as a decimal number [001,366]. - %m - month as a decimal number [01,12]. - %M - minute as a decimal number [00,59]. - %L - milliseconds as a decimal number [000, 999]. - %p - either AM or PM. - %S - second as a decimal number [00,61]. - %U - week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. - %w - weekday as a decimal number [0(Sunday),6]. - %W - week number of the year (Monday as the first day of the week) as a decimal number [00,53]. - %x - date, as "%m/%d/%Y". - %X - time, as "%H:%M:%S". - %y - year without century as a decimal number [00,99]. - %Y - year with century as a decimal number. - %Z - time zone offset, such as "-0700". - %% - a literal "%" character. +| Format | Definition | +| ------ | ------------------------------------------------------------------------------------------ | +| %a | abbreviated weekday name | +| %A | full weekday name | +| %b | abbreviated month name | +| %B | full month name | +| %c | date and time, as "%a %b %e %H:%M:%S %Y" | +| %d | zero-padded day of the month as a decimal number \[01,31] | +| %e | space-padded day of the month as a decimal number \[ 1,31]; equivalent to %\_d | +| %H | hour (24-hour clock) as a decimal number \[00,23] | +| %I | hour (12-hour clock) as a decimal number \[01,12] | +| %j | day of the year as a decimal number \[001,366] | +| %m | month as a decimal number \[01,12] | +| %M | minute as a decimal number \[00,59] | +| %L | milliseconds as a decimal number \[000, 999] | +| %p | either AM or PM | +| %S | second as a decimal number \[00,61] | +| %U | week number of the year (Sunday as the first day of the week) as a decimal number \[00,53] | +| %w | weekday as a decimal number \[0(Sunday),6] | +| %W | week number of the year (Monday as the first day of the week) as a decimal number \[00,53] | +| %x | date, as "%m/%d/%Y" | +| %X | time, as "%H:%M:%S" | +| %y | year without century as a decimal number \[00,99] | +| %Y | year with century as a decimal number | +| %Z | time zone offset, such as "-0700" | +| %% | a literal "%" character | More info in: @@ -238,11 +245,15 @@ More info in: The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`. - tickInterval 1day +```markdown +tickInterval 1day +``` The pattern is: - /^([1-9][0-9]*)(minute|hour|day|week|month)$/ +```javascript +/^([1-9][0-9]*)(minute|hour|day|week|month)$/; +``` More info in: diff --git a/docs/syntax/mindmap.md b/docs/syntax/mindmap.md index e789646bc..ad8aab77f 100644 --- a/docs/syntax/mindmap.md +++ b/docs/syntax/mindmap.md @@ -88,7 +88,7 @@ In this way we can use a text outline to generate a hierarchical mindmap. ## 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: @@ -180,7 +180,7 @@ More shapes will be added, beginning with the shapes available in flowcharts. # Icons and classes -## icons +## Icons As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and fontawesome 4 are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change. @@ -256,14 +256,24 @@ Root ## Integrating with your library/website. -Mindmap uses the experimental lazy loading & async rendering features which could change in the future. +Mindmap uses the experimental lazy loading & async rendering features which could change in the future. From version 9.4.0 this diagram is included in mermaid but use lazy loading in order to keep the size of mermaid down. This is important in order to be able to add additional diagrams going forward. + +You can still use the pre 9.4.0 method to add mermaid with mindmaps to a web page: ```html ``` -You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/fcf53c98c25604c90a218104268c339be53035a6/src/lib/util/mermaid.ts) to see how the async loading is done. +From version 9.4.0 you can simplify this code to: + +```html + +``` + +You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/develop/src/lib/util/mermaid.ts) to see how the async loading is done. diff --git a/docs/syntax/sequenceDiagram.md b/docs/syntax/sequenceDiagram.md index ad88249be..5ca9a55f8 100644 --- a/docs/syntax/sequenceDiagram.md +++ b/docs/syntax/sequenceDiagram.md @@ -94,6 +94,59 @@ sequenceDiagram 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 can be of two displayed either solid or with a dotted line. @@ -102,16 +155,16 @@ Messages can be of two displayed either solid or with a dotted line. There are six types of arrows currently supported: -| Type | Description | -| ---- | ------------------------------------------------ | -| -> | Solid line without arrow | -| --> | Dotted line without arrow | -| ->> | Solid line with arrowhead | -| -->> | Dotted line with arrowhead | -| -x | Solid line with a cross at the end | -| --x | Dotted line with a cross at the end. | -| -) | Solid line with an open arrow at the end (async) | -| --) | Dotted line with a open arrow at the end (async) | +| Type | Description | +| ------ | ------------------------------------------------ | +| `->` | Solid line without arrow | +| `-->` | Dotted line without arrow | +| `->>` | Solid line with arrowhead | +| `-->>` | Dotted line with arrowhead | +| `-x` | Solid line with a cross at the end | +| `--x` | Dotted line with a cross at the end. | +| `-)` | Solid line with an open arrow at the end (async) | +| `--)` | Dotted line with a open arrow at the end (async) | ## Activations @@ -198,6 +251,20 @@ sequenceDiagram Note over Alice,John: A typical interaction ``` +It is also possible to add a line break (applies to text input in general): + +```mermaid-example +sequenceDiagram + Alice->John: Hello John, how are you? + Note over Alice,John: A typical interaction
But now in two lines +``` + +```mermaid +sequenceDiagram + Alice->John: Hello John, how are you? + Note over Alice,John: A typical interaction
But now in two lines +``` + ## Loops It is possible to express loops in a sequence diagram. This is done by the notation diff --git a/docs/syntax/stateDiagram.md b/docs/syntax/stateDiagram.md index 72d7ec63b..807d6149a 100644 --- a/docs/syntax/stateDiagram.md +++ b/docs/syntax/stateDiagram.md @@ -501,19 +501,19 @@ There are two ways to apply a `classDef` style to a state: A `class` statement tells Mermaid to apply the named classDef to one or more classes. The form is: -```text +```txt class [one or more state names, separated by commas] [name of a style defined with classDef] ``` Here is an example applying the `badBadEvent` style to a state named `Crash`: -```text +```txt class Crash badBadEvent ``` Here is an example applying the `movement` style to the two states `Moving` and `Crash`: -```text +```txt class Moving, Crash movement ``` @@ -572,7 +572,7 @@ and `badBadEvent` You can apply a classDef style to a state using the `:::` (three colons) operator. The syntax is -```text +```txt [state]:::[style name] ``` diff --git a/docs/syntax/timeline.md b/docs/syntax/timeline.md new file mode 100644 index 000000000..58b12313d --- /dev/null +++ b/docs/syntax/timeline.md @@ -0,0 +1,474 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/timeline.md](../../packages/mermaid/src/docs/syntax/timeline.md). + +# Timeline Diagram + +> Timeline: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part. + +"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life." Wikipedia + +### An example of a timeline. + +```mermaid-example +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook + : Google + 2005 : Youtube + 2006 : Twitter +``` + +```mermaid +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook + : Google + 2005 : Youtube + 2006 : Twitter +``` + +## Syntax + +The syntax for creating Timeline diagram is simple. You always start with the `timeline` keyword to let mermaid know that you want to create a timeline diagram. + +After that there is a possibility to add a title to the timeline. This is done by adding a line with the keyword `title` followed by the title text. + +Then you add the timeline data, where you always start with a time period, followed by a colon and then the text for the event. Optionally you can add a second colon and then the text for the event. So, you can have one or more events per time period. + +```json +{time period} : {event} +``` + +or + +```json +{time period} : {event} : {event} +``` + +or + +```json +{time period} : {event} + : {event} + : {event} +``` + +NOTE: Both time period and event are simple text, and not limited to numbers. + +Let us look at the syntax for the example above. + +```mermaid-example +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter +``` + +```mermaid +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter +``` + +In this way we can use a text outline to generate a timeline diagram. +The sequence of time period and events is important, as it will be used to draw the timeline. The first time period will be placed at the left side of the timeline, and the last time period will be placed at the right side of the timeline. + +Similarly, the first event will be placed at the top for that specific time period, and the last event will be placed at the bottom. + +## Grouping of time periods in sections/ages + +You can group time periods in sections/ages. This is done by adding a line with the keyword `section` followed by the section name. + +All subsequent time periods will be placed in this section until a new section is defined. + +If no section is defined, all time periods will be placed in the default section. + +Let us look at an example, where we have grouped the time periods in sections. + +```mermaid-example +timeline + title Timeline of Industrial Revolution + section 17th-20th century + Industry 1.0 : Machinery, Water power, Steam
power + Industry 2.0 : Electricity, Internal combustion engine, Mass production + Industry 3.0 : Electronics, Computers, Automation + section 21st century + Industry 4.0 : Internet, Robotics, Internet of Things + Industry 5.0 : Artificial intelligence, Big data,3D printing +``` + +```mermaid +timeline + title Timeline of Industrial Revolution + section 17th-20th century + Industry 1.0 : Machinery, Water power, Steam
power + Industry 2.0 : Electricity, Internal combustion engine, Mass production + Industry 3.0 : Electronics, Computers, Automation + section 21st century + Industry 4.0 : Internet, Robotics, Internet of Things + Industry 5.0 : Artificial intelligence, Big data,3D printing +``` + +As you can see, the time periods are placed in the sections, and the sections are placed in the order they are defined. + +All time periods and events under a given section follow a similar color scheme. This is done to make it easier to see the relationship between time periods and events. + +## Wrapping of text for long time-periods or events + +By default, the text for time-periods and events will be wrapped if it is too long. This is done to avoid that the text is drawn outside the diagram. + +You can also use `
` to force a line break. + +Let us look at another example, where we have a long time period, and a long event. + +```mermaid-example +timeline + title England's History Timeline + section Stone Age + 7600 BC : Britain's oldest known house was built in Orkney, Scotland + 6000 BC : Sea levels rise and Britain becomes an island.
The people who live here are hunter-gatherers. + section Broze Age + 2300 BC : People arrive from Europe and settle in Britain.
They bring farming and metalworking. + : New styles of pottery and ways of burying the dead appear. + 2200 BC : The last major building works are completed at Stonehenge.
People now bury their dead in stone circles. + : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive. + +``` + +```mermaid +timeline + title England's History Timeline + section Stone Age + 7600 BC : Britain's oldest known house was built in Orkney, Scotland + 6000 BC : Sea levels rise and Britain becomes an island.
The people who live here are hunter-gatherers. + section Broze Age + 2300 BC : People arrive from Europe and settle in Britain.
They bring farming and metalworking. + : New styles of pottery and ways of burying the dead appear. + 2200 BC : The last major building works are completed at Stonehenge.
People now bury their dead in stone circles. + : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive. + +``` + +```mermaid-example +timeline + title MermaidChart 2023 Timeline + section 2023 Q1
Release Personal Tier + Buttet 1 : sub-point 1a : sub-point 1b + : sub-point 1c + Bullet 2 : sub-point 2a : sub-point 2b + section 2023 Q2
Release XYZ Tier + Buttet 3 : sub-point
3a : sub-point 3b + : sub-point 3c + Bullet 4 : sub-point 4a : sub-point 4b +``` + +```mermaid +timeline + title MermaidChart 2023 Timeline + section 2023 Q1
Release Personal Tier + Buttet 1 : sub-point 1a : sub-point 1b + : sub-point 1c + Bullet 2 : sub-point 2a : sub-point 2b + section 2023 Q2
Release XYZ Tier + Buttet 3 : sub-point
3a : sub-point 3b + : sub-point 3c + Bullet 4 : sub-point 4a : sub-point 4b +``` + +## Styling of time periods and events + +As explained earlier, each section has a color scheme, and each time period and event under a section follow the similar color scheme. + +However, if there is no section defined, then we have two possibilities: + +1. Style time periods individually, i.e. each time period(and its coressponding events) will have its own color scheme. This is the DEFAULT behavior. + +```mermaid-example + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +```mermaid + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +Note that this is no, section defined, and each time period and its corresponding events will have its own color scheme. + +2. Disable the multiColor option using the `disableMultiColor` option. This will make all time periods and events follow the same color scheme. + +You will need to add this option either via mermaid.intialize function or directives. + +```javascript +mermaid.initialize({ + theme: 'base', + startOnLoad: true, + logLevel: 0, + timeline: { + disableMulticolor: false, + }, + ... + ... +``` + +let us look at same example, where we have disabled the multiColor option. + +```mermaid-example + %%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +```mermaid + %%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +### Customizing Color scheme + +You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on. +In case you have more than 12 sections, the color scheme will start to repeat. + +NOTE: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values. + +Example: + +Now let's override the default values for the `cScale0` to `cScale2` variables: + +```mermaid-example + %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { + 'cScale0': '#ff0000', + 'cScale1': '#00ff00', + 'cScale2': '#0000ff' + } } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + +``` + +```mermaid + %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { + 'cScale0': '#ff0000', + 'cScale1': '#00ff00', + 'cScale2': '#0000ff' + } } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + +``` + +See how the colors are changed to the values specified in the theme variables. + +## Themes + +Mermaid supports a bunch of pre-defined themes which you can use to find the right one for you. PS: you can actually override an existing theme's variable to get your own custom theme going. Learn more about theming your diagram [here](../config/theming.md). + +The following are the different pre-defined theme options: + +- `base` +- `forest` +- `dark` +- `default` +- `neutral` + +**NOTE**: To change theme you can either use the `initialize` call or _directives_. Learn more about [directives](../config/directives.md) +Let's put them to use, and see how our sample diagram looks in different themes: + +### Base Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Forest Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Dark Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Default Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Neutral Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +## Integrating with your library/website. + +Timeline uses experimental lazy loading & async rendering features which could change in the future.The lazy loading is important in order to be able to add additional diagrams going forward. + +You can use this method to add mermaid including the timeline diagram to a web page: + +```html + +``` + +You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/develop/src/lib/util/mermaid.ts) to see how the async loading is done. diff --git a/package.json b/package.json index 1faa1628d..8e4c50118 100644 --- a/package.json +++ b/package.json @@ -1,10 +1,10 @@ { "name": "mermaid-monorepo", "private": true, - "version": "9.2.2", + "version": "9.4.0", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", - "packageManager": "pnpm@7.17.1", + "packageManager": "pnpm@7.27.0", "keywords": [ "diagram", "markdown", @@ -18,13 +18,13 @@ "build:vite": "ts-node-esm --transpileOnly .vite/build.ts", "build:mermaid": "pnpm build:vite --mermaid", "build:viz": "pnpm build:mermaid --visualize", - "build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-mindmap/tsconfig.json --emitDeclarationOnly", + "build:types": "tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly && tsc -p ./packages/mermaid-example-diagram/tsconfig.json --emitDeclarationOnly", "build:watch": "pnpm build:vite --watch", - "build": "pnpm run -r clean && concurrently \"pnpm build:vite\" \"pnpm build:types\"", + "build": "pnpm run -r clean && pnpm build:types && pnpm build:vite", "dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"", "release": "pnpm build", - "lint": "eslint --cache --ignore-path .gitignore . && pnpm lint:jison && prettier --check .", - "lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts", + "lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .", + "lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts", "lint:jison": "ts-node-esm ./scripts/jison/lint.mts", "cypress": "cypress run", "cypress:open": "cypress open", @@ -34,7 +34,7 @@ "test:watch": "vitest --watch", "test:coverage": "vitest --coverage", "prepublishOnly": "pnpm build && pnpm test", - "prepare": "concurrently \"husky install\" \"pnpm build\"", + "prepare": "husky install && pnpm build", "pre-commit": "lint-staged" }, "repository": { @@ -58,26 +58,29 @@ "@commitlint/cli": "^17.2.0", "@commitlint/config-conventional": "^17.2.0", "@cspell/eslint-plugin": "^6.14.2", + "@types/cors": "^2.8.13", "@types/eslint": "^8.4.10", - "@types/express": "^4.17.14", + "@types/express": "^4.17.17", "@types/js-yaml": "^4.0.5", - "@types/jsdom": "^20.0.1", + "@types/jsdom": "^21.0.0", "@types/lodash": "^4.14.188", "@types/mdast": "^3.0.10", "@types/node": "^18.11.9", "@types/prettier": "^2.7.1", "@types/rollup-plugin-visualizer": "^4.2.1", - "@typescript-eslint/eslint-plugin": "^5.42.1", - "@typescript-eslint/parser": "^5.42.1", - "@vitest/coverage-c8": "^0.25.1", - "@vitest/ui": "^0.25.1", + "@typescript-eslint/eslint-plugin": "^5.48.2", + "@typescript-eslint/parser": "^5.48.2", + "@vitest/coverage-c8": "^0.28.4", + "@vitest/spy": "^0.28.4", + "@vitest/ui": "^0.28.4", "concurrently": "^7.5.0", + "cors": "^2.8.5", "coveralls": "^3.1.1", - "cypress": "^10.11.0", + "cypress": "^12.0.0", "cypress-image-snapshot": "^4.0.1", - "esbuild": "^0.15.13", - "eslint": "^8.27.0", - "eslint-config-prettier": "^8.5.0", + "esbuild": "^0.17.0", + "eslint": "^8.32.0", + "eslint-config-prettier": "^8.6.0", "eslint-plugin-cypress": "^2.12.1", "eslint-plugin-html": "^7.1.0", "eslint-plugin-jest": "^27.1.5", @@ -94,21 +97,21 @@ "jest": "^29.3.1", "jison": "^0.4.18", "js-yaml": "^4.1.0", - "jsdom": "^20.0.2", + "jsdom": "^21.0.0", "lint-staged": "^13.0.3", "path-browserify": "^1.0.1", "pnpm": "^7.15.0", "prettier": "^2.7.1", "prettier-plugin-jsdoc": "^0.4.2", - "rimraf": "^3.0.2", + "rimraf": "^4.0.0", "rollup-plugin-visualizer": "^5.8.3", - "start-server-and-test": "^1.14.0", + "start-server-and-test": "^1.15.4", "ts-node": "^10.9.1", "typescript": "^4.8.4", - "vite": "^3.2.3", - "vitest": "^0.25.3" + "vite": "^4.1.1", + "vitest": "^0.28.5" }, "volta": { - "node": "18.12.1" + "node": "18.14.0" } } diff --git a/packages/mermaid-example-diagram/Readme.md b/packages/mermaid-example-diagram/Readme.md deleted file mode 100644 index 38056e3c7..000000000 --- a/packages/mermaid-example-diagram/Readme.md +++ /dev/null @@ -1,3 +0,0 @@ -### Do not refer this package. It is not ready. - -### Refer mermaid-mindmap instead. diff --git a/packages/mermaid-example-diagram/package.json b/packages/mermaid-example-diagram/package.json index 8e958806a..3c71a2ac3 100644 --- a/packages/mermaid-example-diagram/package.json +++ b/packages/mermaid-example-diagram/package.json @@ -1,36 +1,25 @@ { "name": "@mermaid-js/mermaid-example-diagram", - "version": "9.2.0-rc2", - "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", - "main": "dist/mermaid-mindmap.core.mjs", - "module": "dist/mermaid-mindmap.core.mjs", + "version": "9.3.0", + "description": "Example of external diagram module for MermaidJS.", + "module": "dist/mermaid-example-diagram.core.mjs", + "types": "dist/detector.d.ts", "type": "module", "exports": { ".": { - "require": "./dist/mermaid-example-diagram.min.js", - "import": "./dist/mermaid-example-diagram.core.mjs" + "import": "./dist/mermaid-example-diagram.core.mjs", + "types": "./dist/detector.d.ts" }, "./*": "./*" }, "keywords": [ "diagram", "markdown", - "mindmap", + "example", "mermaid" ], "scripts": { - "clean": "rimraf dist", - "build:types": "tsc -p ./tsconfig.json --emitDeclarationOnly", - "build:watch": "yarn build:code --watch", - "build:esbuild": "concurrently \"yarn build:code\" \"yarn build:types\"", - "build": "yarn clean; yarn build:esbuild", - "dev": "node .esbuild/serve.cjs", - "release": "yarn build", - "lint": "eslint --cache --ignore-path .gitignore . && yarn lint:jison && prettier --check .", - "lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .", - "lint:jison": "ts-node-esm src/jison/lint.mts", - "todo-prepare": "concurrently \"husky install ../../.husky\" \"yarn build\"", - "todo-pre-commit": "lint-staged" + "prepublishOnly": "pnpm -w run build" }, "repository": { "type": "git", @@ -48,10 +37,20 @@ "page" ] }, - "dependencies": {}, + "dependencies": { + "@braintree/sanitize-url": "^6.0.0", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.0.0", + "khroma": "^2.0.0", + "non-layered-tidy-tree-layout": "^2.0.2" + }, "devDependencies": { + "@types/cytoscape": "^3.19.9", "concurrently": "^7.5.0", - "rimraf": "^3.0.2" + "rimraf": "^4.0.0", + "mermaid": "workspace:*" }, "resolutions": { "d3": "^7.0.0" diff --git a/packages/mermaid-example-diagram/src/detector.ts b/packages/mermaid-example-diagram/src/detector.ts index d30b99fba..93fd42762 100644 --- a/packages/mermaid-example-diagram/src/detector.ts +++ b/packages/mermaid-example-diagram/src/detector.ts @@ -1,18 +1,20 @@ -// @ts-ignore: TODO Fix ts errors -export const id = 'example-diagram'; +import type { ExternalDiagramDefinition } from 'mermaid'; -/** - * Detector function that will be called by mermaid to determine if the diagram is this type of diagram. - * - * @param txt - The diagram text will be passed to the detector - * @returns True if the diagram text matches a diagram of this type - */ +const id = 'example-diagram'; -export const detector = (txt: string) => { +const detector = (txt: string) => { return txt.match(/^\s*example-diagram/) !== null; }; -export const loadDiagram = async () => { - const { diagram } = await import('./diagram-definition'); +const loader = async () => { + const { diagram } = await import('./diagram-definition.js'); return { id, diagram }; }; + +const plugin: ExternalDiagramDefinition = { + id, + detector, + loader, +}; + +export default plugin; diff --git a/packages/mermaid-example-diagram/src/diagram-definition.ts b/packages/mermaid-example-diagram/src/diagram-definition.ts index 95f7cc11d..c31b3d6e7 100644 --- a/packages/mermaid-example-diagram/src/diagram-definition.ts +++ b/packages/mermaid-example-diagram/src/diagram-definition.ts @@ -12,5 +12,3 @@ export const diagram = { styles, injectUtils, }; - -export { detector, id } from './detector'; diff --git a/packages/mermaid-example-diagram/src/exampleDiagram.spec.js b/packages/mermaid-example-diagram/src/exampleDiagram.spec.js index db539aac0..96c8cd5b2 100644 --- a/packages/mermaid-example-diagram/src/exampleDiagram.spec.js +++ b/packages/mermaid-example-diagram/src/exampleDiagram.spec.js @@ -1,5 +1,17 @@ import { parser } from './parser/exampleDiagram'; -import db from './exampleDiagramDb'; +import * as db from './exampleDiagramDb'; +import { injectUtils } from './mermaidUtils'; +// Todo fix utils functions for tests +import { + log, + setLogLevel, + getConfig, + sanitizeText, + setupGraphViewBox, +} from '../../mermaid/src/diagram-api/diagramAPI'; + +injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewBox); + describe('when parsing an info graph it', function () { let ex; beforeEach(function () { diff --git a/packages/mermaid-example-diagram/src/mermaidUtils.ts b/packages/mermaid-example-diagram/src/mermaidUtils.ts index 8894abdff..44cc85f73 100644 --- a/packages/mermaid-example-diagram/src/mermaidUtils.ts +++ b/packages/mermaid-example-diagram/src/mermaidUtils.ts @@ -1,4 +1,8 @@ -const warning = () => null; +const warning = (s: string) => { + // Todo remove debug code + // eslint-disable-next-line no-console + console.error('Log function was called before initialization', s); +}; export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; @@ -19,12 +23,11 @@ export const log: Record = { error: warning, fatal: warning, }; + export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void; export let getConfig: () => object; export let sanitizeText: (str: string) => string; -/** - * Placeholder for the real function that will be injected by mermaid. - */ +export let commonDb: () => object; // eslint-disable @typescript-eslint/no-explicit-any export let setupGraphViewbox: ( graph: any, @@ -33,23 +36,15 @@ export let setupGraphViewbox: ( useMaxWidth: boolean ) => void; -/** - * Function called by mermaid that injects utility functions that help the diagram to be a good citizen. - * - * @param _log - log from mermaid/src/diagramAPI.ts - * @param _setLogLevel - setLogLevel from mermaid/src/diagramAPI.ts - * @param _getConfig - getConfig from mermaid/src/diagramAPI.ts - * @param _sanitizeText - sanitizeText from mermaid/src/diagramAPI.ts - * @param _setupGraphViewbox - setupGraphViewbox from mermaid/src/diagramAPI.ts - */ export const injectUtils = ( _log: Record, - _setLogLevel: typeof setLogLevel, - _getConfig: typeof getConfig, - _sanitizeText: typeof sanitizeText, - _setupGraphViewbox: typeof setupGraphViewbox + _setLogLevel: any, + _getConfig: any, + _sanitizeText: any, + _setupGraphViewbox: any, + _commonDb: any ) => { - _log.debug('Mermaid utils injected into example-diagram'); + _log.info('Mermaid utils injected'); log.trace = _log.trace; log.debug = _log.debug; log.info = _log.info; @@ -60,4 +55,5 @@ export const injectUtils = ( getConfig = _getConfig; sanitizeText = _sanitizeText; setupGraphViewbox = _setupGraphViewbox; + commonDb = _commonDb; }; diff --git a/packages/mermaid-example-diagram/tsconfig.json b/packages/mermaid-example-diagram/tsconfig.json index 45076b7b5..310137cc0 100644 --- a/packages/mermaid-example-diagram/tsconfig.json +++ b/packages/mermaid-example-diagram/tsconfig.json @@ -1,5 +1,6 @@ { "extends": "../../tsconfig.json", + "module": "esnext", "compilerOptions": { "rootDir": "./src", "outDir": "./dist" diff --git a/packages/mermaid-mindmap/package.json b/packages/mermaid-mindmap/package.json deleted file mode 100644 index 852c0871b..000000000 --- a/packages/mermaid-mindmap/package.json +++ /dev/null @@ -1,64 +0,0 @@ -{ - "name": "@mermaid-js/mermaid-mindmap", - "version": "9.2.2", - "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", - "module": "dist/mermaid-mindmap.core.mjs", - "types": "dist/detector.d.ts", - "type": "module", - "exports": { - ".": { - "import": "./dist/mermaid-mindmap.core.mjs", - "types": "./dist/detector.d.ts" - }, - "./*": "./*" - }, - "keywords": [ - "diagram", - "markdown", - "mindmap", - "mermaid" - ], - "scripts": { - "prepublishOnly": "pnpm -w run build" - }, - "repository": { - "type": "git", - "url": "https://github.com/mermaid-js/mermaid" - }, - "author": "Knut Sveidqvist", - "license": "MIT", - "standard": { - "ignore": [ - "**/parser/*.js", - "dist/**/*.js", - "cypress/**/*.js" - ], - "globals": [ - "page" - ] - }, - "dependencies": { - "@braintree/sanitize-url": "^6.0.0", - "cytoscape": "^3.23.0", - "cytoscape-cose-bilkent": "^4.1.0", - "cytoscape-fcose": "^2.1.0", - "d3": "^7.0.0", - "khroma": "^2.0.0", - "non-layered-tidy-tree-layout": "^2.0.2" - }, - "devDependencies": { - "concurrently": "^7.5.0", - "mermaid": "workspace:*", - "rimraf": "^3.0.2" - }, - "resolutions": { - "d3": "^7.0.0" - }, - "files": [ - "dist" - ], - "sideEffects": [ - "**/*.css", - "**/*.scss" - ] -} diff --git a/packages/mermaid-mindmap/src/mermaidUtils.ts b/packages/mermaid-mindmap/src/mermaidUtils.ts deleted file mode 100644 index b575c201b..000000000 --- a/packages/mermaid-mindmap/src/mermaidUtils.ts +++ /dev/null @@ -1,56 +0,0 @@ -const warning = (s: string) => { - // Todo remove debug code - // eslint-disable-next-line no-console - console.error('Log function was called before initialization', s); -}; - -export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal'; - -export const LEVELS: Record = { - trace: 0, - debug: 1, - info: 2, - warn: 3, - error: 4, - fatal: 5, -}; - -export const log: Record = { - trace: warning, - debug: warning, - info: warning, - warn: warning, - error: warning, - fatal: warning, -}; - -export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void; -export let getConfig: () => object; -export let sanitizeText: (str: string) => string; -// eslint-disable @typescript-eslint/no-explicit-any -export let setupGraphViewbox: ( - graph: any, - svgElem: any, - padding: any, - useMaxWidth: boolean -) => void; - -export const injectUtils = ( - _log: Record, - _setLogLevel: any, - _getConfig: any, - _sanitizeText: any, - _setupGraphViewbox: any -) => { - _log.info('Mermaid utils injected'); - log.trace = _log.trace; - log.debug = _log.debug; - log.info = _log.info; - log.warn = _log.warn; - log.error = _log.error; - log.fatal = _log.fatal; - setLogLevel = _setLogLevel; - getConfig = _getConfig; - sanitizeText = _sanitizeText; - setupGraphViewbox = _setupGraphViewbox; -}; diff --git a/packages/mermaid-mindmap/src/types/index.d.ts b/packages/mermaid-mindmap/src/types/index.d.ts deleted file mode 100644 index 999ff2f49..000000000 --- a/packages/mermaid-mindmap/src/types/index.d.ts +++ /dev/null @@ -1,7 +0,0 @@ -export {}; - -declare global { - interface Window { - mermaid: any; // 👈️ turn off type checking - } -} diff --git a/packages/mermaid-mindmap/tsconfig.json b/packages/mermaid-mindmap/tsconfig.json deleted file mode 100644 index 310137cc0..000000000 --- a/packages/mermaid-mindmap/tsconfig.json +++ /dev/null @@ -1,10 +0,0 @@ -{ - "extends": "../../tsconfig.json", - "module": "esnext", - "compilerOptions": { - "rootDir": "./src", - "outDir": "./dist" - }, - "include": ["./src/**/*.ts"], - "typeRoots": ["./src/types"] -} diff --git a/packages/mermaid/.gitignore b/packages/mermaid/.gitignore index 1a961ffe0..6ed32bcf7 100644 --- a/packages/mermaid/.gitignore +++ b/packages/mermaid/.gitignore @@ -1,2 +1,3 @@ src/vitepress -src/docs/config/setup \ No newline at end of file +src/docs/config/setup +README.* \ No newline at end of file diff --git a/packages/mermaid/README.md b/packages/mermaid/README.md deleted file mode 100644 index 91c2d1640..000000000 --- a/packages/mermaid/README.md +++ /dev/null @@ -1,346 +0,0 @@ -# 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_) - -English | [简体中文](./README.zh-CN.md) - - - -:trophy: **Mermaid was nominated and won the [JS Open Source Awards (2019)](https://osawards.com/javascript/2019) in the category "The most exciting use of technology"!!!** - -**Thanks to all involved, people committing pull requests, people answering questions! 🙏** - -Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! - -## About - - - -Mermaid is a JavaScript-based diagramming and charting tool that uses Markdown-inspired text definitions and a renderer to create and modify complex diagrams. The main purpose of Mermaid is to help documentation catch up with development. - -> Doc-Rot is a Catch-22 that Mermaid helps to solve. - -Diagramming and documentation costs precious developer time and gets outdated quickly. -But not having diagrams or docs ruins productivity and hurts organizational learning.
-Mermaid addresses this problem by enabling users to create easily modifiable diagrams. It can also be made part of production scripts (and other pieces of code).
-
- -Mermaid allows even non-programmers to easily create detailed diagrams through the [Mermaid Live Editor](https://mermaid.live/).
-[Tutorials](./docs/Tutorials.md) has video tutorials. -Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](./docs/integrations.md). - -You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/integrations.md). - -For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/n00b-overview.md), [Usage](./docs/usage.md) and [Tutorials](./docs/Tutorials.md). - -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) | 📜 [Changelog](./docs/CHANGELOG.md) - -In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests. - - - - - - - -## Examples - -**The following are some examples of the diagrams, charts and graphs that can be made using Mermaid. Click here to jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).** - - - -### Flowchart [docs - live editor] - -``` -flowchart LR - -A[Hard] -->|Text| B(Round) -B --> C{Decision} -C -->|One| D[Result 1] -C -->|Two| E[Result 2] -``` - -```mermaid -flowchart LR - -A[Hard] -->|Text| B(Round) -B --> C{Decision} -C -->|One| D[Result 1] -C -->|Two| E[Result 2] -``` - -### Sequence diagram [docs - live editor] - -``` -sequenceDiagram -Alice->>John: Hello John, how are you? -loop Healthcheck - John->>John: Fight against hypochondria -end -Note right of John: Rational thoughts! -John-->>Alice: Great! -John->>Bob: How about you? -Bob-->>John: Jolly good! -``` - -```mermaid -sequenceDiagram -Alice->>John: Hello John, how are you? -loop Healthcheck - John->>John: Fight against hypochondria -end -Note right of John: Rational thoughts! -John-->>Alice: Great! -John->>Bob: How about you? -Bob-->>John: Jolly good! -``` - -### Gantt chart [docs - live editor] - -``` -gantt - section Section - Completed :done, des1, 2014-01-06,2014-01-08 - Active :active, des2, 2014-01-07, 3d - Parallel 1 : des3, after des1, 1d - Parallel 2 : des4, after des1, 1d - Parallel 3 : des5, after des3, 1d - Parallel 4 : des6, after des4, 1d -``` - -```mermaid -gantt - section Section - Completed :done, des1, 2014-01-06,2014-01-08 - Active :active, des2, 2014-01-07, 3d - Parallel 1 : des3, after des1, 1d - Parallel 2 : des4, after des1, 1d - Parallel 3 : des5, after des3, 1d - Parallel 4 : des6, after des4, 1d -``` - -### Class diagram [docs - live editor] - -``` -classDiagram -Class01 <|-- AveryLongClass : Cool -<> Class01 -Class09 --> C2 : Where am I? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -class Class10 { - <> - int id - size() -} -``` - -```mermaid -classDiagram -Class01 <|-- AveryLongClass : Cool -<> Class01 -Class09 --> C2 : Where am I? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -class Class10 { - <> - int id - size() -} -``` - -### State diagram [docs - live editor] - -``` -stateDiagram-v2 -[*] --> Still -Still --> [*] -Still --> Moving -Moving --> Still -Moving --> Crash -Crash --> [*] -``` - -```mermaid -stateDiagram-v2 -[*] --> Still -Still --> [*] -Still --> Moving -Moving --> Still -Moving --> Crash -Crash --> [*] -``` - -### Pie chart [docs - live editor] - -``` -pie -"Dogs" : 386 -"Cats" : 85.9 -"Rats" : 15 -``` - -```mermaid -pie -"Dogs" : 386 -"Cats" : 85.9 -"Rats" : 15 -``` - -### Git graph [experimental - live editor] - -### User Journey diagram [docs - live editor] - -``` - journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 3: Me -``` - -```mermaid - journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 3: Me -``` - -### C4 diagram [docs] - -``` -C4Context -title System Context diagram for Internet Banking System - -Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.") -Person(customerB, "Banking Customer B") -Person_Ext(customerC, "Banking Customer C") -System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.") - -Person(customerD, "Banking Customer D", "A customer of the bank,
with personal bank accounts.") - -Enterprise_Boundary(b1, "BankBoundary") { - - SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") - - System_Boundary(b2, "BankBoundary2") { - System(SystemA, "Banking System A") - System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") - } - - System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") - SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") - - Boundary(b3, "BankBoundary3", "boundary") { - SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") - SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") - } -} - -BiRel(customerA, SystemAA, "Uses") -BiRel(SystemAA, SystemE, "Uses") -Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") -Rel(SystemC, customerA, "Sends e-mails to") -``` - -```mermaid -C4Context -title System Context diagram for Internet Banking System - -Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.") -Person(customerB, "Banking Customer B") -Person_Ext(customerC, "Banking Customer C") -System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.") - -Person(customerD, "Banking Customer D", "A customer of the bank,
with personal bank accounts.") - -Enterprise_Boundary(b1, "BankBoundary") { - - SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") - - System_Boundary(b2, "BankBoundary2") { - System(SystemA, "Banking System A") - System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") - } - - System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") - SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") - - Boundary(b3, "BankBoundary3", "boundary") { - SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") - SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") - } -} - -BiRel(customerA, SystemAA, "Uses") -BiRel(SystemAA, SystemE, "Uses") -Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") -Rel(SystemC, customerA, "Sends e-mails to") -``` - -## Release - -For those who have the permission to do so: - -Update version number in `package.json`. - -```sh -npm publish -``` - -The above command generates files into the `dist` folder and publishes them to npmjs.org. - -## Related projects - -- [Command Line Interface](https://github.com/mermaid-js/mermaid-cli) -- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor) -- [HTTP Server](https://github.com/TomWright/mermaid-server) - -## Contributors [![Good first issue](https://img.shields.io/github/labels/mermaid-js/mermaid/Good%20first%20issue%21)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) - -Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out. - -Detailed information about how to contribute can be found in the [contribution guide](CONTRIBUTING.md) - -## Security and safe diagrams - -For public sites, it can be precarious to retrieve text from users on the internet, storing that content for presentation in a browser at a later stage. The reason is that the user content can contain embedded malicious scripts that will run when the data is presented. For Mermaid this is a risk, specially as mermaid diagrams contain many characters that are used in html which makes the standard sanitation unusable as it also breaks the diagrams. We still make an effort to sanitise the incoming code and keep refining the process but it is hard to guarantee that there are no loop holes. - -As an extra level of security for sites with external users we are happy to introduce a new security level in which the diagram is rendered in a sandboxed iframe preventing javascript in the code from being executed. This is a great step forward for better security. - -_Unfortunately you can not have a cake and eat it at the same time which in this case means that some of the interactive functionality gets blocked along with the possible malicious code._ - -## Reporting vulnerabilities - -To report a vulnerability, please e-mail security@mermaid.live with a description of the issue, the steps you took to create the issue, affected versions, and if known, mitigations for the issue. - -## Appreciation - -A quick note from Knut Sveidqvist: - -> _Many thanks to the [d3](https://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!_ >_Thanks also to the [js-sequence-diagram](https://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering._ >_Thank you to [Tyler Long](https://github.com/tylerlong) who has been a collaborator since April 2017._ -> -> _Thank you to the ever-growing list of [contributors](https://github.com/knsv/mermaid/graphs/contributors) that brought the project this far!_ - ---- - -_Mermaid was created by Knut Sveidqvist for easier documentation._ diff --git a/packages/mermaid/README.zh-CN.md b/packages/mermaid/README.zh-CN.md deleted file mode 100644 index 0ccef27e4..000000000 --- a/packages/mermaid/README.zh-CN.md +++ /dev/null @@ -1,334 +0,0 @@ -# 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_) - -[English](./README.md) | 简体中文 - - - -:trophy: **Mermaid 被提名并获得了 [JS Open Source Awards (2019)](https://osawards.com/javascript/2019) 的 "The most exciting use of technology" 奖项!!!** - -**感谢所有参与进来提交 PR,解答疑问的人们! 🙏** - -Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! - -## 关于 Mermaid - - - -Mermaid 是一个基于 Javascript 的图表绘制工具,通过解析类 Markdown 的文本语法来实现图表的创建和动态修改。Mermaid 诞生的主要目的是让文档的更新能够及时跟上开发进度。 - -> Doc-Rot 是 Mermaid 致力于解决的一个难题。 - -绘图和编写文档花费了开发者宝贵的开发时间,而且随着业务的变更,它很快就会过期。 但是如果缺少了图表或文档,对于生产力和团队新人的业务学习都会产生巨大的阻碍。
-Mermaid 通过允许用户创建便于修改的图表来解决这一难题,它也可以作为生产脚本(或其他代码)的一部分。
-
-Mermaid 甚至能让非程序员也能通过 [Mermaid Live Editor](https://mermaid.live/) 轻松创建详细的图表。
-你可以访问 [教程](./docs/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/integrations.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。 - -如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/n00b-overview.md), [用法](./docs/usage.md) 和 [教程](./docs/Tutorials.md). - -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [文档](https://mermaidjs.github.io) | 🙌 [贡献](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) | 📜 [更新日志](./docs/CHANGELOG.md) - - - -## 示例 - -**下面是一些可以使用 Mermaid 创建的图表示例。点击 [语法](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference) 查看详情。** - - - - -### 流程图 [文档 - live editor] - -``` -flowchart LR -A[Hard] -->|Text| B(Round) -B --> C{Decision} -C -->|One| D[Result 1] -C -->|Two| E[Result 2] -``` - -```mermaid -flowchart LR -A[Hard] -->|Text| B(Round) -B --> C{Decision} -C -->|One| D[Result 1] -C -->|Two| E[Result 2] -``` - -### 时序图 [文档 - live editor] - -``` -sequenceDiagram -Alice->>John: Hello John, how are you? -loop Healthcheck - John->>John: Fight against hypochondria -end -Note right of John: Rational thoughts! -John-->>Alice: Great! -John->>Bob: How about you? -Bob-->>John: Jolly good! -``` - -```mermaid -sequenceDiagram -Alice->>John: Hello John, how are you? -loop Healthcheck - John->>John: Fight against hypochondria -end -Note right of John: Rational thoughts! -John-->>Alice: Great! -John->>Bob: How about you? -Bob-->>John: Jolly good! -``` - -### 甘特图 [文档 - live editor] - -``` -gantt - section Section - Completed :done, des1, 2014-01-06,2014-01-08 - Active :active, des2, 2014-01-07, 3d - Parallel 1 : des3, after des1, 1d - Parallel 2 : des4, after des1, 1d - Parallel 3 : des5, after des3, 1d - Parallel 4 : des6, after des4, 1d -``` - -```mermaid -gantt - section Section - Completed :done, des1, 2014-01-06,2014-01-08 - Active :active, des2, 2014-01-07, 3d - Parallel 1 : des3, after des1, 1d - Parallel 2 : des4, after des1, 1d - Parallel 3 : des5, after des3, 1d - Parallel 4 : des6, after des4, 1d -``` - -### 类图 [文档 - live editor] - -``` -classDiagram -Class01 <|-- AveryLongClass : Cool -<> Class01 -Class09 --> C2 : Where am I? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -class Class10 { - <> - int id - size() -} -``` - -```mermaid -classDiagram -Class01 <|-- AveryLongClass : Cool -<> Class01 -Class09 --> C2 : Where am I? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -class Class10 { - <> - int id - size() -} -``` - -### 状态图 [[docs - live editor] - -``` -stateDiagram-v2 -[*] --> Still -Still --> [*] -Still --> Moving -Moving --> Still -Moving --> Crash -Crash --> [*] -``` - -```mermaid -stateDiagram-v2 -[*] --> Still -Still --> [*] -Still --> Moving -Moving --> Still -Moving --> Crash -Crash --> [*] -``` - -### 饼图 [文档 - live editor] - -``` -pie -"Dogs" : 386 -"Cats" : 85 -"Rats" : 15 -``` - -```mermaid -pie -"Dogs" : 386 -"Cats" : 85 -"Rats" : 15 -``` - -### Git 图 [实验特性 - live editor] - -### 用户体验旅程图 [文档 - live editor] - -``` - journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 3: Me -``` - -```mermaid - journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 3: Me -``` - -### C4 图 [文档] - -``` -C4Context -title System Context diagram for Internet Banking System - -Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.") -Person(customerB, "Banking Customer B") -Person_Ext(customerC, "Banking Customer C") -System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.") - -Person(customerD, "Banking Customer D", "A customer of the bank,
with personal bank accounts.") - -Enterprise_Boundary(b1, "BankBoundary") { - - SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") - - System_Boundary(b2, "BankBoundary2") { - System(SystemA, "Banking System A") - System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") - } - - System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") - SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") - - Boundary(b3, "BankBoundary3", "boundary") { - SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") - SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") - } -} - -BiRel(customerA, SystemAA, "Uses") -BiRel(SystemAA, SystemE, "Uses") -Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") -Rel(SystemC, customerA, "Sends e-mails to") -``` - -```mermaid -C4Context -title System Context diagram for Internet Banking System - -Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.") -Person(customerB, "Banking Customer B") -Person_Ext(customerC, "Banking Customer C") -System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.") - -Person(customerD, "Banking Customer D", "A customer of the bank,
with personal bank accounts.") - -Enterprise_Boundary(b1, "BankBoundary") { - - SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") - - System_Boundary(b2, "BankBoundary2") { - System(SystemA, "Banking System A") - System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") - } - - System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") - SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") - - Boundary(b3, "BankBoundary3", "boundary") { - SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") - SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") - } -} - -BiRel(customerA, SystemAA, "Uses") -BiRel(SystemAA, SystemE, "Uses") -Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") -Rel(SystemC, customerA, "Sends e-mails to") -``` - -## 发布 - -对于有权限的同学来说,你可以通过以下步骤来完成发布操作: - -更新 `package.json` 中的版本号,然后执行如下命令: - -```sh -npm publish -``` - -以上的命令会将文件打包到 `dist` 目录并发布至 npmjs.org. - -## 相关项目 - -- [Command Line Interface](https://github.com/mermaid-js/mermaid-cli) -- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor) -- [HTTP Server](https://github.com/TomWright/mermaid-server) - -## 贡献者 [![Good first issue](https://img.shields.io/github/labels/mermaid-js/mermaid/Good%20first%20issue%21)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) - -Mermaid 是一个不断发展中的社区,并且还在接收新的贡献者。有很多不同的方式可以参与进来,而且我们还在寻找额外的帮助。如果你想知道如何开始贡献,请查看 [这个 issue](https://github.com/mermaid-js/mermaid/issues/866)。 - -关于如何贡献的详细信息可以在 [贡献指南](CONTRIBUTING.md) 中找到。 - -## 安全 - -对于公开网站来说,从互联网上的用户处检索文本、存储供后续在浏览器中展示的内容可能是不安全的,理由是用户的内容可能嵌入一些数据加载完成之后就会运行的恶意脚本,这些对于 Mermaid 来说毫无疑问是一个风险,尤其是 mermaid 图表还包含了许多在 html 中使用的字符,这意味着我们难以使用常规的手段来过滤不安全代码,因为这些常规手段会造成图表损坏。我们仍然在努力对获取到的代码进行安全过滤并不断完善我们的程序,但很难保证没有漏洞。 - -作为拥有外部用户的网站的额外安全级别,我们很高兴推出一个新的安全级别,其中的图表在沙盒 iframe 中渲染,防止代码中的 javascript 被执行,这是在安全性方面迈出的一大步。 - -_很不幸的是,鱼与熊掌不可兼得,在这个场景下它意味着在可能的恶意代码被阻止时,也会损失部分交互能力_。 - -## 报告漏洞 - -如果想要报告漏洞,请发送邮件到 security@mermaid.live, 并附上问题的描述、复现问题的步骤、受影响的版本,以及解决问题的方案(如果有的话)。 - -## 鸣谢 - -来自 Knut Sveidqvist: - -> _特别感谢 [d3](https://d3js.org/) 和 [dagre-d3](https://github.com/cpettitt/dagre-d3) 这两个优秀的项目,它们提供了图形布局和绘图工具库! _ >_同样感谢 [js-sequence-diagram](https://bramp.github.io/js-sequence-diagrams) 提供了时序图语法的使用。 感谢 Jessica Peter 提供了甘特图渲染的灵感。_ >_感谢 [Tyler Long](https://github.com/tylerlong) 从 2017 年四月开始成为了项目的合作者。_ -> -> _感谢越来越多的 [贡献者们](https://github.com/knsv/mermaid/graphs/contributors),没有你们,就没有这个项目的今天!_ - ---- - -_Mermaid 是由 Knut Sveidqvist 创建,它为了更简单的文档编写而生。_ diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 56533e564..c069672b5 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -1,13 +1,12 @@ { "name": "mermaid", - "version": "9.2.3-rc.1", + "version": "10.0.0-rc.3", "description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", - "main": "./dist/mermaid.min.js", + "type": "module", "module": "./dist/mermaid.core.mjs", "types": "./dist/mermaid.d.ts", "exports": { ".": { - "require": "./dist/mermaid.min.js", "import": "./dist/mermaid.core.mjs", "types": "./dist/mermaid.d.ts" }, @@ -28,12 +27,12 @@ "docs:build": "rimraf ../../docs && pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts", "docs:verify": "pnpm docs:spellcheck && pnpm docs:code && ts-node-esm src/docs.mts --verify", "docs:pre:vitepress": "rimraf src/vitepress && pnpm docs:code && ts-node-esm src/docs.mts --vitepress", - "docs:build:vitepress": "pnpm docs:pre:vitepress && vitepress build src/vitepress", + "docs:build:vitepress": "pnpm docs:pre:vitepress && vitepress build src/vitepress && cpy --flat src/docs/landing/ ./src/vitepress/.vitepress/dist/landing", "docs:dev": "pnpm docs:pre:vitepress && concurrently \"vitepress dev src/vitepress\" \"ts-node-esm src/docs.mts --watch --vitepress\"", "docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress", "docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"", "release": "pnpm build", - "prepublishOnly": "pnpm -w run build" + "prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build" }, "repository": { "type": "git", @@ -53,55 +52,61 @@ }, "dependencies": { "@braintree/sanitize-url": "^6.0.0", - "d3": "^7.0.0", - "dagre-d3-es": "7.0.4", - "dompurify": "2.4.1", + "cytoscape": "^3.23.0", + "cytoscape-cose-bilkent": "^4.1.0", + "cytoscape-fcose": "^2.1.0", + "d3": "^7.4.0", + "dagre-d3-es": "7.0.8", + "dompurify": "2.4.3", + "elkjs": "^0.8.2", "khroma": "^2.0.0", "lodash-es": "^4.17.21", - "moment-mini": "^2.24.0", + "moment-mini": "^2.29.4", "non-layered-tidy-tree-layout": "^2.0.2", "stylis": "^4.1.2", - "uuid": "^9.0.0" + "ts-dedent": "^2.2.0", + "uuid": "^9.0.0", + "web-worker": "^1.2.0" }, "devDependencies": { + "@types/cytoscape": "^3.19.9", "@types/d3": "^7.4.0", "@types/dompurify": "^2.4.0", - "@types/jsdom": "^20.0.1", + "@types/jsdom": "^21.0.0", "@types/lodash-es": "^4.17.6", "@types/micromatch": "^4.0.2", "@types/prettier": "^2.7.1", "@types/stylis": "^4.0.2", - "@types/uuid": "^8.3.4", + "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.42.1", "@typescript-eslint/parser": "^5.42.1", "chokidar": "^3.5.3", "concurrently": "^7.5.0", "coveralls": "^3.1.1", + "cpy-cli": "^4.2.0", "cspell": "^6.14.3", "globby": "^13.1.2", "jison": "^0.4.18", "js-base64": "^3.7.2", - "jsdom": "^20.0.2", + "jsdom": "^21.0.0", "micromatch": "^4.0.5", - "moment": "^2.29.4", "path-browserify": "^1.0.1", "prettier": "^2.7.1", "remark": "^14.0.2", - "rimraf": "^3.0.2", + "remark-frontmatter": "^4.0.1", + "remark-gfm": "^3.0.1", + "rimraf": "^4.0.0", "start-server-and-test": "^1.14.0", "typedoc": "^0.23.18", "typedoc-plugin-markdown": "^3.13.6", "typescript": "^4.8.4", "unist-util-flatmap": "^1.0.0", - "vitepress": "^1.0.0-alpha.28", - "vitepress-plugin-search": "^1.0.4-alpha.15" + "vitepress": "^1.0.0-alpha.46", + "vitepress-plugin-search": "^1.0.4-alpha.19" }, "files": [ "dist", "README.md" ], - "sideEffects": [ - "**/*.css", - "**/*.scss" - ] -} \ No newline at end of file + "sideEffects": false +} diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index 439ac9453..3a0818854 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -3,25 +3,24 @@ import { log } from './logger'; import { getDiagram, registerDiagram } from './diagram-api/diagramAPI'; import { detectType, getDiagramLoader } from './diagram-api/detectType'; import { extractFrontMatter } from './diagram-api/frontmatter'; -import { isDetailedError, type DetailedError } from './utils'; - -export type ParseErrorFunction = (err: string | DetailedError, hash?: any) => void; +import { UnknownDiagramError } from './errors'; +import { DetailedError } from './utils'; +export type ParseErrorFunction = (err: string | DetailedError | unknown, hash?: any) => void; export class Diagram { type = 'graph'; parser; renderer; - db: any; - private detectTypeFailed = false; - constructor(public txt: string, parseError?: ParseErrorFunction) { + db; + private detectError?: UnknownDiagramError; + constructor(public text: string) { + this.text += '\n'; const cnf = configApi.getConfig(); - this.txt = txt; try { - this.type = detectType(txt, cnf); + this.type = detectType(text, cnf); } catch (e) { - this.handleError(e, parseError); this.type = 'error'; - this.detectTypeFailed = true; + this.detectError = e as UnknownDiagramError; } const diagram = getDiagram(this.type); log.debug('Type ' + this.type); @@ -43,46 +42,21 @@ export class Diagram { this.parser.parser.yy = this.db; if (diagram.init) { diagram.init(cnf); - log.debug('Initialized diagram ' + this.type, cnf); + log.info('Initialized diagram ' + this.type, cnf); } - this.txt += '\n'; - - this.parse(this.txt, parseError); + this.parse(); } - parse(text: string, parseError?: ParseErrorFunction): boolean { - if (this.detectTypeFailed) { - return false; + parse() { + if (this.detectError) { + throw this.detectError; } - try { - text = text + '\n'; - this.db.clear?.(); - this.parser.parse(text); - return true; - } catch (error) { - this.handleError(error, parseError); - } - return false; + this.db.clear?.(); + this.parser.parse(this.text); } - handleError(error: unknown, parseError?: ParseErrorFunction) { - // Is this the correct way to access mermaid's parseError() - // method ? (or global.mermaid.parseError()) ? - - if (parseError === undefined) { - // No mermaid.parseError() handler defined, so re-throw it - throw error; - } - - if (isDetailedError(error)) { - // Handle case where error string and hash were - // wrapped in object like`const error = { str, hash };` - parseError(error.str, error.hash); - return; - } - - // Otherwise, assume it is just an error string and pass it on - parseError(error as string); + async render(id: string, version: string) { + await this.renderer.draw(this.text, id, version, this); } getParser() { @@ -94,30 +68,20 @@ export class Diagram { } } -export const getDiagramFromText = ( - txt: string, - parseError?: ParseErrorFunction -): Diagram | Promise => { +export const getDiagramFromText = async (txt: string): Promise => { const type = detectType(txt, configApi.getConfig()); try { // Trying to find the diagram getDiagram(type); - return new Diagram(txt, parseError); } catch (error) { const loader = getDiagramLoader(type); if (!loader) { - throw new Error(`Diagram ${type} not found.`); + throw new UnknownDiagramError(`Diagram ${type} not found.`); } - // TODO: Uncomment for v10 - // // Diagram not available, loading it - // const { diagram } = await loader(); - // registerDiagram(type, diagram, undefined, diagram.injectUtils); - // // new diagram will try getDiagram again and if fails then it is a valid throw - return loader().then(({ diagram }) => { - registerDiagram(type, diagram, undefined); - return new Diagram(txt, parseError); - }); + // Diagram not available, loading it + // new diagram will try getDiagram again and if fails then it is a valid throw + const { id, diagram } = await loader(); + registerDiagram(id, diagram); } + return new Diagram(txt); }; - -export default Diagram; diff --git a/packages/mermaid/src/__mocks__/mermaidAPI.ts b/packages/mermaid/src/__mocks__/mermaidAPI.ts index 12c1652bc..95b87d990 100644 --- a/packages/mermaid/src/__mocks__/mermaidAPI.ts +++ b/packages/mermaid/src/__mocks__/mermaidAPI.ts @@ -5,24 +5,14 @@ */ import * as configApi from '../config'; import { vi } from 'vitest'; -import { addDiagrams } from '../diagram-api/diagram-orchestration'; -import Diagram, { type ParseErrorFunction } from '../Diagram'; - -// Normally, we could just do the following to get the original `parse()` -// implementation, however, requireActual returns a promise and it's not documented how to use withing mock file. - -/** {@inheritDoc mermaidAPI.parse} */ -function parse(text: string, parseError?: ParseErrorFunction): boolean { - addDiagrams(); - const diagram = new Diagram(text, parseError); - return diagram.parse(text, parseError); -} +import { mermaidAPI as mAPI } from '../mermaidAPI'; // original version cannot be modified since it was frozen with `Object.freeze()` export const mermaidAPI = { - render: vi.fn(), - renderAsync: vi.fn(), - parse, + render: vi.fn().mockResolvedValue({ + svg: '', + }), + parse: mAPI.parse, parseDirective: vi.fn(), initialize: vi.fn(), getConfig: configApi.getConfig, diff --git a/packages/mermaid/src/accessibility.js b/packages/mermaid/src/accessibility.js deleted file mode 100644 index 4d4837fff..000000000 --- a/packages/mermaid/src/accessibility.js +++ /dev/null @@ -1,29 +0,0 @@ -/** - * This method will add a basic title and description element to a chart. The yy parser will need to - * respond to getAccTitle and getAccDescription, where the title is the title element on the chart, - * which is generally not displayed and the accDescription is the description element on the chart, - * which is never displayed. - * - * The following charts display their title as a visual and accessibility element: gantt - * - * @param yy_parser - * @param svg - * @param id - */ -export default function addSVGAccessibilityFields(yy_parser, svg, id) { - if (svg.insert === undefined) { - return; - } - - let title_string = yy_parser.getAccTitle(); - let description = yy_parser.getAccDescription(); - svg.attr('role', 'img').attr('aria-labelledby', 'chart-title-' + id + ' chart-desc-' + id); - svg - .insert('desc', ':first-child') - .attr('id', 'chart-desc-' + id) - .text(description); - svg - .insert('title', ':first-child') - .attr('id', 'chart-title-' + id) - .text(title_string); -} diff --git a/packages/mermaid/src/accessibility.spec.ts b/packages/mermaid/src/accessibility.spec.ts new file mode 100644 index 000000000..60415ea37 --- /dev/null +++ b/packages/mermaid/src/accessibility.spec.ts @@ -0,0 +1,227 @@ +import { MockedD3 } from './tests/MockedD3'; +import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility'; +import { D3Element } from './mermaidAPI'; + +describe('accessibility', () => { + const fauxSvgNode = new MockedD3(); + + describe('setA11yDiagramInfo', () => { + it('sets the svg element role to "graphics-document document"', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + setA11yDiagramInfo(fauxSvgNode, 'flowchart'); + expect(svgAttrSpy).toHaveBeenCalledWith('role', 'graphics-document document'); + }); + + it('sets the aria-roledescription to the diagram type', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + setA11yDiagramInfo(fauxSvgNode, 'flowchart'); + expect(svgAttrSpy).toHaveBeenCalledWith('aria-roledescription', 'flowchart'); + }); + + it('does not set the aria-roledescription if the diagram type is empty', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + setA11yDiagramInfo(fauxSvgNode, ''); + expect(svgAttrSpy).toHaveBeenCalledTimes(1); + expect(svgAttrSpy).toHaveBeenCalledWith('role', expect.anything()); // only called to set the role + }); + }); + + describe('addSVGa11yTitleDescription', () => { + const givenId = 'theBaseId'; + + describe('with the given svg d3 object:', () => { + it('does nothing if there is no insert defined', () => { + const noInsertSvg = { + attr: vi.fn(), + }; + const noInsertAttrSpy = vi.spyOn(noInsertSvg, 'attr').mockReturnValue(noInsertSvg); + addSVGa11yTitleDescription(noInsertSvg, 'some title', 'some desc', givenId); + expect(noInsertAttrSpy).not.toHaveBeenCalled(); + }); + + // ---------------- + // Convenience functions to DRY up the spec + + function expectAriaLabelledByIsTitleId( + svgD3Node: D3Element, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string + ) { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node); + addSVGa11yTitleDescription(svgD3Node, title, desc, givenId); + expect(svgAttrSpy).toHaveBeenCalledWith('aria-labelledby', `chart-title-${givenId}`); + } + + function expectAriaDescribedByIsDescId( + svgD3Node: D3Element, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string + ) { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(svgD3Node, 'attr').mockReturnValue(svgD3Node); + addSVGa11yTitleDescription(svgD3Node, title, desc, givenId); + expect(svgAttrSpy).toHaveBeenCalledWith('aria-describedby', `chart-desc-${givenId}`); + } + + function a11yTitleTagInserted( + svgD3Node: D3Element, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string, + callNumber: number + ) { + a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'title', title); + } + + function a11yDescTagInserted( + svgD3Node: D3Element, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string, + callNumber: number + ) { + a11yTagInserted(svgD3Node, title, desc, givenId, callNumber, 'desc', desc); + } + + function a11yTagInserted( + svgD3Node: D3Element, + title: string | null | undefined, + desc: string | null | undefined, + givenId: string, + callNumber: number, + expectedPrefix: string, + expectedText: string | null | undefined + ) { + const fauxInsertedD3 = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxInsertedD3); + // @ts-ignore Required to easily handle the d3 select types + const titleAttrSpy = vi.spyOn(fauxInsertedD3, 'attr').mockReturnValue(fauxInsertedD3); + const titleTextSpy = vi.spyOn(fauxInsertedD3, 'text'); + + addSVGa11yTitleDescription(fauxSvgNode, title, desc, givenId); + expect(svgInsertSpy).toHaveBeenCalledWith(expectedPrefix, ':first-child'); + expect(titleAttrSpy).toHaveBeenCalledWith('id', `chart-${expectedPrefix}-${givenId}`); + expect(titleTextSpy).toHaveBeenNthCalledWith(callNumber, expectedText); + } + // ---------------- + + describe('given an a11y title', () => { + const a11yTitle = 'a11y title'; + + describe('given an a11y description', () => { + const a11yDesc = 'a11y description'; + + it('sets aria-labelledby to the title id inserted as a child', () => { + expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId); + }); + + it('sets aria-describedby to the description id inserted as a child', () => { + expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId); + }); + + it('inserts a title tag as the first child with the text set to the accTitle given', () => { + a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 2); + }); + + it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => { + a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1); + }); + }); + + describe(`no a11y description`, () => { + const a11yDesc = undefined; + + it('sets aria-labelledby to the title id inserted as a child', () => { + expectAriaLabelledByIsTitleId(fauxSvgNode, a11yTitle, a11yDesc, givenId); + }); + + it('no aria-describedby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything()); + }); + + it('inserts a title tag as the first child with the text set to the accTitle given', () => { + a11yTitleTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1); + }); + + it('no description tag is inserted', () => { + const fauxTitle = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child'); + }); + }); + }); + + describe('no a11y title', () => { + const a11yTitle = undefined; + + describe('given an a11y description', () => { + const a11yDesc = 'a11y description'; + + it('no aria-labelledby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything()); + }); + + it('no title tag inserted', () => { + const fauxTitle = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child'); + }); + + it('sets aria-describedby to the description id inserted as a child', () => { + expectAriaDescribedByIsDescId(fauxSvgNode, a11yTitle, a11yDesc, givenId); + }); + + it('inserts a desc tag as the 2nd child with the text set to accDescription given', () => { + a11yDescTagInserted(fauxSvgNode, a11yTitle, a11yDesc, givenId, 1); + }); + }); + + describe('no a11y description', () => { + const a11yDesc = undefined; + + it('no aria-labelledby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-labelledby', expect.anything()); + }); + + it('no aria-describedby is set', () => { + // @ts-ignore Required to easily handle the d3 select types + const svgAttrSpy = vi.spyOn(fauxSvgNode, 'attr').mockReturnValue(fauxSvgNode); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgAttrSpy).not.toHaveBeenCalledWith('aria-describedby', expect.anything()); + }); + + it('no title tag inserted', () => { + const fauxTitle = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxTitle); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgInsertSpy).not.toHaveBeenCalledWith('title', ':first-child'); + }); + + it('no description tag inserted', () => { + const fauxDesc = new MockedD3(); + const svgInsertSpy = vi.spyOn(fauxSvgNode, 'insert').mockReturnValue(fauxDesc); + addSVGa11yTitleDescription(fauxSvgNode, a11yTitle, a11yDesc, givenId); + expect(svgInsertSpy).not.toHaveBeenCalledWith('desc', ':first-child'); + }); + }); + }); + }); + }); +}); diff --git a/packages/mermaid/src/accessibility.ts b/packages/mermaid/src/accessibility.ts new file mode 100644 index 000000000..8e073aa76 --- /dev/null +++ b/packages/mermaid/src/accessibility.ts @@ -0,0 +1,70 @@ +/** + * Accessibility (a11y) functions, types, helpers + * @see https://www.w3.org/WAI/ + * @see https://www.w3.org/TR/wai-aria-1.1/ + * @see https://www.w3.org/TR/svg-aam-1.0/ + * + */ +import { D3Element } from './mermaidAPI'; + +import isEmpty from 'lodash-es/isEmpty.js'; + +/** + * SVG element role: + * The SVG element role _should_ be set to 'graphics-document' per SVG standard + * but in practice is not always done by browsers, etc. (As of 2022-12-08). + * A fallback role of 'document' should be set for those browsers, etc., that only support ARIA 1.0. + * + * @see https://www.w3.org/TR/svg-aam-1.0/#roleMappingGeneralRules + * @see https://www.w3.org/TR/graphics-aria-1.0/#graphics-document + */ +const SVG_ROLE = 'graphics-document document'; + +/** + * Add role and aria-roledescription to the svg element + * + * @param svg - d3 object that contains the SVG HTML element + * @param diagramType - diagram name for to the aria-roledescription + */ +export function setA11yDiagramInfo(svg: D3Element, diagramType: string | null | undefined) { + svg.attr('role', SVG_ROLE); + if (!isEmpty(diagramType)) { + svg.attr('aria-roledescription', diagramType); + } +} +/** + * Add an accessible title and/or description element to a chart. + * The title is usually not displayed and the description is never displayed. + * + * The following charts display their title as a visual and accessibility element: gantt + * + * @param svg - d3 node to insert the a11y title and desc info + * @param a11yTitle - a11y title. null and undefined are meaningful: means to skip it + * @param a11yDesc - a11y description. null and undefined are meaningful: means to skip it + * @param baseId - id used to construct the a11y title and description id + */ +export function addSVGa11yTitleDescription( + svg: D3Element, + a11yTitle: string | null | undefined, + a11yDesc: string | null | undefined, + baseId: string +) { + if (svg.insert === undefined) { + return; + } + + if (a11yTitle || a11yDesc) { + if (a11yDesc) { + const descId = 'chart-desc-' + baseId; + svg.attr('aria-describedby', descId); + svg.insert('desc', ':first-child').attr('id', descId).text(a11yDesc); + } + if (a11yTitle) { + const titleId = 'chart-title-' + baseId; + svg.attr('aria-labelledby', titleId); + svg.insert('title', ':first-child').attr('id', titleId).text(a11yTitle); + } + } else { + return; + } +} diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index ff199ca8b..474075824 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -26,6 +26,7 @@ export interface MermaidConfig { sequence?: SequenceDiagramConfig; gantt?: GanttDiagramConfig; journey?: JourneyDiagramConfig; + timeline?: TimelineDiagramConfig; class?: ClassDiagramConfig; state?: StateDiagramConfig; er?: ErDiagramConfig; @@ -292,6 +293,30 @@ export interface JourneyDiagramConfig extends BaseDiagramConfig { sectionColours?: string[]; } +export interface TimelineDiagramConfig extends BaseDiagramConfig { + diagramMarginX?: number; + diagramMarginY?: number; + leftMargin?: number; + width?: number; + height?: number; + boxMargin?: number; + boxTextMargin?: number; + noteMargin?: number; + messageMargin?: number; + messageAlign?: string; + bottomMarginAdj?: number; + rightAngles?: boolean; + taskFontSize?: string | number; + taskFontFamily?: string; + taskMargin?: number; + activationWidth?: number; + textPlacement?: string; + actorColours?: string[]; + sectionFills?: string[]; + sectionColours?: string[]; + disableMulticolor?: boolean; +} + export interface GanttDiagramConfig extends BaseDiagramConfig { titleTopMargin?: number; barHeight?: number; diff --git a/packages/mermaid/src/dagre-wrapper/clusters.js b/packages/mermaid/src/dagre-wrapper/clusters.js index 40729dead..57c3ff513 100644 --- a/packages/mermaid/src/dagre-wrapper/clusters.js +++ b/packages/mermaid/src/dagre-wrapper/clusters.js @@ -59,11 +59,9 @@ const rect = (parent, node) => { // Center the label label.attr( 'transform', - 'translate(' + - (node.x - bbox.width / 2) + - ', ' + - (node.y - node.height / 2 + node.padding / 3) + - ')' + // This puts the labal on top of the box instead of inside it + // 'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2 - bbox.height) + ')' + 'translate(' + (node.x - bbox.width / 2) + ', ' + (node.y - node.height / 2) + ')' ); const rectBox = rect.node().getBBox(); diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index 5213d0684..71183eab5 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -107,6 +107,7 @@ export const insertEdgeLabel = (elem, edge) => { terminalLabels[edge.id].endRight = endEdgeLabelRight; setTerminalWidth(fo, edge.endLabelRight); } + return labelElement; }; /** @@ -130,9 +131,21 @@ export const positionEdgeLabel = (edge, paths) => { if (path) { // // debugger; const pos = utils.calcLabelPosition(path); - log.info('Moving label from (', x, ',', y, ') to (', pos.x, ',', pos.y, ') abc78'); - // x = pos.x; - // y = pos.y; + log.info( + 'Moving label ' + edge.label + ' from (', + x, + ',', + y, + ') to (', + pos.x, + ',', + pos.y, + ') abc78' + ); + if (paths.updatedPath) { + x = pos.x; + y = pos.y; + } } el.attr('transform', 'translate(' + x + ', ' + y + ')'); } @@ -463,7 +476,7 @@ export const insertEdge = function (elem, e, edge, clusterDb, diagramType, graph .attr('style', edge.style); // DEBUG code, adds a red circle at each edge coordinate - // edge.points.forEach(point => { + // edge.points.forEach((point) => { // elem // .append('circle') // .style('stroke', 'red') diff --git a/packages/mermaid/src/dagre-wrapper/index.js b/packages/mermaid/src/dagre-wrapper/index.js index e2d7d51f4..ce3ef6014 100644 --- a/packages/mermaid/src/dagre-wrapper/index.js +++ b/packages/mermaid/src/dagre-wrapper/index.js @@ -1,5 +1,5 @@ import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js'; -import * as graphlibJson from 'dagre-d3-es/src/graphlib/json'; +import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js'; import insertMarkers from './markers'; import { updateNodeBounds } from './shapes/util'; import { diff --git a/packages/mermaid/src/dagre-wrapper/markers.js b/packages/mermaid/src/dagre-wrapper/markers.js index c231eb3e5..1a3f74bee 100644 --- a/packages/mermaid/src/dagre-wrapper/markers.js +++ b/packages/mermaid/src/dagre-wrapper/markers.js @@ -142,7 +142,7 @@ const point = (elem, type) => { .append('marker') .attr('id', type + '-pointEnd') .attr('class', 'marker ' + type) - .attr('viewBox', '0 0 10 10') + .attr('viewBox', '0 0 12 20') .attr('refX', 10) .attr('refY', 5) .attr('markerUnits', 'userSpaceOnUse') diff --git a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js index 5722f7cc0..875ac4def 100644 --- a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js +++ b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.js @@ -1,7 +1,7 @@ /** Decorates with functions required by mermaids dagre-wrapper. */ import { log } from '../logger'; -import * as graphlibJson from 'dagre-d3-es/src/graphlib/json'; -import * as graphlib from 'dagre-d3-es/src/graphlib'; +import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; export let clusterDb = {}; let descendants = {}; diff --git a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js index f594e3430..25fb75d64 100644 --- a/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js +++ b/packages/mermaid/src/dagre-wrapper/mermaid-graphlib.spec.js @@ -1,5 +1,5 @@ -import * as graphlibJson from 'dagre-d3-es/src/graphlib/json'; -import * as graphlib from 'dagre-d3-es/src/graphlib'; +import * as graphlibJson from 'dagre-d3-es/src/graphlib/json.js'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { validate, adjustClustersAndEdges, diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js index fda789323..5bd18d077 100644 --- a/packages/mermaid/src/dagre-wrapper/nodes.js +++ b/packages/mermaid/src/dagre-wrapper/nodes.js @@ -1007,6 +1007,7 @@ const class_box = (parent, node) => { }; const shapes = { + rhombus: question, question, rect, labelRect, @@ -1064,6 +1065,7 @@ export const insertNode = (elem, node, dir) => { if (node.haveCallback) { nodeElems[node.id].attr('class', nodeElems[node.id].attr('class') + ' clickable'); } + return newEl; }; export const setNodeElem = (elem, node) => { nodeElems[node.id] = elem; diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index 37d4f71ff..ec741e908 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -247,12 +247,13 @@ const config: Partial = { /** * | Parameter | Description | Type | Required | Values | * | --------------- | ----------- | ------- | -------- | ----------------------- | - * | defaultRenderer | See notes | boolean | 4 | dagre-d3, dagre-wrapper | + * | defaultRenderer | See notes | boolean | 4 | dagre-d3, dagre-wrapper, elk | * * **Notes:** * * Decides which rendering engine that is to be used for the rendering. Legal values are: - * dagre-d3 dagre-wrapper - wrapper for dagre implemented in mermaid + * dagre-d3 dagre-wrapper - wrapper for dagre implemented in mermaid, elk for layout using + * elkjs * * Default value: 'dagre-wrapper' */ @@ -861,6 +862,156 @@ const config: Partial = { sectionFills: ['#191970', '#8B008B', '#4B0082', '#2F4F4F', '#800000', '#8B4513', '#00008B'], sectionColours: ['#fff'], }, + /** The object containing configurations specific for timeline diagrams */ + timeline: { + /** + * | Parameter | Description | Type | Required | Values | + * | -------------- | ---------------------------------------------------- | ------- | -------- | ------------------ | + * | diagramMarginX | Margin to the right and left of the sequence diagram | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 50 + */ + diagramMarginX: 50, + + /** + * | Parameter | Description | Type | Required | Values | + * | -------------- | -------------------------------------------------- | ------- | -------- | ------------------ | + * | diagramMarginY | Margin to the over and under the sequence diagram. | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 10 + */ + diagramMarginY: 10, + + /** + * | Parameter | Description | Type | Required | Values | + * | ----------- | --------------------- | ------- | -------- | ------------------ | + * | actorMargin | Margin between actors | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 50 + */ + leftMargin: 150, + + /** + * | Parameter | Description | Type | Required | Values | + * | --------- | -------------------- | ------- | -------- | ------------------ | + * | width | Width of actor boxes | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 150 + */ + width: 150, + + /** + * | Parameter | Description | Type | Required | Values | + * | --------- | --------------------- | ------- | -------- | ------------------ | + * | height | Height of actor boxes | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 65 + */ + height: 50, + + /** + * | Parameter | Description | Type | Required | Values | + * | --------- | ------------------------ | ------- | -------- | ------------------ | + * | boxMargin | Margin around loop boxes | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 10 + */ + boxMargin: 10, + + /** + * | Parameter | Description | Type | Required | Values | + * | ------------- | -------------------------------------------- | ------- | -------- | ------------------ | + * | boxTextMargin | Margin around the text in loop/alt/opt boxes | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 5 + */ + boxTextMargin: 5, + + /** + * | Parameter | Description | Type | Required | Values | + * | ---------- | ------------------- | ------- | -------- | ------------------ | + * | noteMargin | Margin around notes | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 10 + */ + noteMargin: 10, + + /** + * | Parameter | Description | Type | Required | Values | + * | ------------- | ----------------------- | ------- | -------- | ------------------ | + * | messageMargin | Space between messages. | Integer | Required | Any Positive Value | + * + * **Notes:** + * + * Space between messages. + * + * Default value: 35 + */ + messageMargin: 35, + + /** + * | Parameter | Description | Type | Required | Values | + * | ------------ | --------------------------- | ---- | -------- | ------------------------- | + * | messageAlign | Multiline message alignment | 3 | 4 | 'left', 'center', 'right' | + * + * **Notes:** Default value: 'center' + */ + messageAlign: 'center', + + /** + * | Parameter | Description | Type | Required | Values | + * | --------------- | ------------------------------------------ | ------- | -------- | ------------------ | + * | bottomMarginAdj | Prolongs the edge of the diagram downwards | Integer | 4 | Any Positive Value | + * + * **Notes:** + * + * Depending on css styling this might need adjustment. + * + * Default value: 1 + */ + bottomMarginAdj: 1, + + /** + * | Parameter | Description | Type | Required | Values | + * | ----------- | ----------- | ------- | -------- | ----------- | + * | useMaxWidth | See notes | boolean | 4 | true, false | + * + * **Notes:** + * + * When this flag is set the height and width is set to 100% and is then scaling with the + * available space if not the absolute space required is used. + * + * Default value: true + */ + useMaxWidth: true, + + /** + * | Parameter | Description | Type | Required | Values | + * | ----------- | --------------------------------- | ---- | -------- | ----------- | + * | rightAngles | Curved Arrows become Right Angles | 3 | 4 | true, false | + * + * **Notes:** + * + * This will display arrows that start and begin at the same node as right angles, rather than a + * curves + * + * Default value: false + */ + rightAngles: false, + taskFontSize: 14, + taskFontFamily: '"Open Sans", sans-serif', + taskMargin: 50, + // width of activation box + activationWidth: 10, + + // text placement as: tspan | fo | old only text as before + textPlacement: 'fo', + actorColours: ['#8FBC8F', '#7CFC00', '#00FFFF', '#20B2AA', '#B0E0E6', '#FFFFE0'], + + sectionFills: ['#191970', '#8B008B', '#4B0082', '#2F4F4F', '#800000', '#8B4513', '#00008B'], + sectionColours: ['#fff'], + disableMulticolor: false, + }, class: { /** * ### titleTopMargin diff --git a/packages/mermaid/src/diagram-api/detectType.ts b/packages/mermaid/src/diagram-api/detectType.ts index 92e777065..3c9237e5b 100644 --- a/packages/mermaid/src/diagram-api/detectType.ts +++ b/packages/mermaid/src/diagram-api/detectType.ts @@ -1,8 +1,13 @@ import { MermaidConfig } from '../config.type'; import { log } from '../logger'; -import { DetectorRecord, DiagramDetector, DiagramLoader } from './types'; -import { ExternalDiagramDefinition } from '../diagram-api/types'; +import type { + DetectorRecord, + DiagramDetector, + DiagramLoader, + ExternalDiagramDefinition, +} from './types'; import { frontMatterRegex } from './frontmatter'; +import { UnknownDiagramError } from '../errors'; const directive = /%{2}{\s*(?:(\w+)\s*:|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; const anyComment = /\s*%%.*\n/gm; @@ -40,7 +45,7 @@ export const detectType = function (text: string, config?: MermaidConfig): strin } } - throw new Error(`No diagram type detected for text: ${text}`); + throw new UnknownDiagramError(`No diagram type detected for text: ${text}`); }; export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinition[]) => { @@ -51,9 +56,10 @@ export const registerLazyLoadedDiagrams = (...diagrams: ExternalDiagramDefinitio export const addDetector = (key: string, detector: DiagramDetector, loader?: DiagramLoader) => { if (detectors[key]) { - throw new Error(`Detector with key ${key} already exists`); + log.error(`Detector with key ${key} already exists`); + } else { + detectors[key] = { detector, loader }; } - detectors[key] = { detector, loader }; log.debug(`Detector with key ${key} added${loader ? ' with loader' : ''}`); }; diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index c6b8b579a..6b37addf4 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -14,8 +14,12 @@ import state from '../diagrams/state/stateDetector'; import stateV2 from '../diagrams/state/stateDetector-V2'; import journey from '../diagrams/user-journey/journeyDetector'; import error from '../diagrams/error/errorDetector'; +import flowchartElk from '../diagrams/flowchart/elk/detector'; +import timeline from '../diagrams/timeline/detector'; +import mindmap from '../diagrams/mindmap/detector'; import { registerLazyLoadedDiagrams } from './detectType'; + let hasLoadedDiagrams = false; export const addDiagrams = () => { if (hasLoadedDiagrams) { @@ -37,6 +41,9 @@ export const addDiagrams = () => { sequence, flowchart, flowchartV2, + flowchartElk, + mindmap, + timeline, git, state, stateV2, diff --git a/packages/mermaid/src/diagram-api/diagramAPI.ts b/packages/mermaid/src/diagram-api/diagramAPI.ts index c6f901726..105c60e60 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.ts @@ -5,6 +5,8 @@ import { sanitizeText as _sanitizeText } from '../diagrams/common/common'; import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox'; import { addStylesForDiagram } from '../styles'; import { DiagramDefinition, DiagramDetector, ExternalDiagramDefinition } from './types'; +import * as _commonDb from '../commonDb'; +import { parseDirective as _parseDirective } from '../directiveUtils'; /* Packaging and exposing resources for external diagrams so that they can import @@ -16,6 +18,11 @@ export const setLogLevel = _setLogLevel; export const getConfig = _getConfig; export const sanitizeText = (text: string) => _sanitizeText(text, getConfig()); export const setupGraphViewbox = _setupGraphViewbox; +export const getCommonDb = () => { + return _commonDb; +}; +export const parseDirective = (p: any, statement: string, context: string, type: string) => + _parseDirective(p, statement, context, type); const diagrams: Record = {}; export interface Detectors { @@ -46,7 +53,15 @@ export const registerDiagram = ( addStylesForDiagram(id, diagram.styles); if (diagram.injectUtils) { - diagram.injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox); + diagram.injectUtils( + log, + setLogLevel, + getConfig, + sanitizeText, + setupGraphViewbox, + getCommonDb(), + parseDirective + ); } }; diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index 23810d133..081136563 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -6,6 +6,8 @@ export interface InjectUtils { _getConfig: any; _sanitizeText: any; _setupGraphViewbox: any; + _commonDb: any; + _parseDirective: any; } /** @@ -14,6 +16,9 @@ export interface InjectUtils { export interface DiagramDb { clear?: () => void; setDiagramTitle?: (title: string) => void; + getAccTitle?: () => string; + getAccDescription?: () => string; + bindFunctions?: (element: Element) => void; } export interface DiagramDefinition { @@ -27,7 +32,9 @@ export interface DiagramDefinition { _setLogLevel: InjectUtils['_setLogLevel'], _getConfig: InjectUtils['_getConfig'], _sanitizeText: InjectUtils['_sanitizeText'], - _setupGraphViewbox: InjectUtils['_setupGraphViewbox'] + _setupGraphViewbox: InjectUtils['_setupGraphViewbox'], + _commonDb: InjectUtils['_commonDb'], + _parseDirective: InjectUtils['_parseDirective'] ) => void; } diff --git a/packages/mermaid/src/diagram.spec.ts b/packages/mermaid/src/diagram.spec.ts new file mode 100644 index 000000000..a862c7936 --- /dev/null +++ b/packages/mermaid/src/diagram.spec.ts @@ -0,0 +1,68 @@ +import { describe, test, expect } from 'vitest'; +import { Diagram, getDiagramFromText } from './Diagram'; +import { addDetector } from './diagram-api/detectType'; +import { addDiagrams } from './diagram-api/diagram-orchestration'; + +addDiagrams(); + +describe('diagram detection', () => { + test('should detect inbuilt diagrams', async () => { + const graph = (await getDiagramFromText('graph TD; A-->B')) as Diagram; + expect(graph).toBeInstanceOf(Diagram); + expect(graph.type).toBe('flowchart-v2'); + const sequence = (await getDiagramFromText( + 'sequenceDiagram; Alice->>+John: Hello John, how are you?' + )) as Diagram; + expect(sequence).toBeInstanceOf(Diagram); + expect(sequence.type).toBe('sequence'); + }); + + test('should detect external diagrams', async () => { + addDetector( + 'loki', + (str) => str.startsWith('loki'), + () => + Promise.resolve({ + id: 'loki', + diagram: { + db: {}, + parser: { + parse: () => { + // no-op + }, + parser: { + yy: {}, + }, + }, + renderer: {}, + styles: {}, + }, + }) + ); + const diagram = (await getDiagramFromText('loki TD; A-->B')) as Diagram; + expect(diagram).toBeInstanceOf(Diagram); + expect(diagram.type).toBe('loki'); + }); + + test('should throw the right error for incorrect diagram', async () => { + await expect(getDiagramFromText('graph TD; A-->')).rejects.toThrowErrorMatchingInlineSnapshot(` + "Parse error on line 2: + graph TD; A--> + --------------^ + Expecting 'AMP', 'ALPHA', 'COLON', 'PIPE', 'TESTSTR', 'DOWN', 'DEFAULT', 'NUM', 'COMMA', 'MINUS', 'BRKT', 'DOT', 'PUNCTUATION', 'UNICODE_TEXT', 'PLUS', 'EQUALS', 'MULT', 'UNDERSCORE', got 'EOF'" + `); + await expect(getDiagramFromText('sequenceDiagram; A-->B')).rejects + .toThrowErrorMatchingInlineSnapshot(` +"Parse error on line 1: +...quenceDiagram; A-->B +-----------------------^ +Expecting 'TXT', got 'NEWLINE'" + `); + }); + + test('should throw the right error for unregistered diagrams', async () => { + await expect(getDiagramFromText('thor TD; A-->B')).rejects.toThrowError( + 'No diagram type detected for text: thor TD; A-->B' + ); + }); +}); diff --git a/packages/mermaid/src/diagrams/c4/c4Renderer.js b/packages/mermaid/src/diagrams/c4/c4Renderer.js index 6490a8e19..a51fe0b6a 100644 --- a/packages/mermaid/src/diagrams/c4/c4Renderer.js +++ b/packages/mermaid/src/diagrams/c4/c4Renderer.js @@ -8,7 +8,6 @@ import * as configApi from '../../config'; import assignWithDepth from '../../assignWithDepth'; import { wrapLabel, calculateTextWidth, calculateTextHeight } from '../../utils'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; let globalBoundaryMaxX = 0, globalBoundaryMaxY = 0; @@ -675,7 +674,6 @@ export const draw = function (_text, id, _version, diagObj) { (height + extraVertForTitle) ); - addSVGAccessibilityFields(parser.yy, diagram, id); log.debug(`models:`, box); }; diff --git a/packages/mermaid/src/diagrams/c4/svgDraw.js b/packages/mermaid/src/diagrams/c4/svgDraw.js index d9727f074..690dd26ad 100644 --- a/packages/mermaid/src/diagrams/c4/svgDraw.js +++ b/packages/mermaid/src/diagrams/c4/svgDraw.js @@ -35,183 +35,6 @@ export const drawImage = function (elem, width, height, x, y, link) { imageElem.attr('xlink:href', sanitizedLink); }; -export const drawEmbeddedImage = function (elem, x, y, link) { - const imageElem = elem.append('use'); - imageElem.attr('x', x); - imageElem.attr('y', y); - var sanitizedLink = sanitizeUrl(link); - imageElem.attr('xlink:href', '#' + sanitizedLink); -}; - -export const drawText = function (elem, textData) { - let prevTextHeight = 0, - textHeight = 0; - const lines = textData.text.split(common.lineBreakRegex); - - let textElems = []; - let dy = 0; - let yfunc = () => textData.y; - if ( - textData.valign !== undefined && - textData.textMargin !== undefined && - textData.textMargin > 0 - ) { - switch (textData.valign) { - case 'top': - case 'start': - yfunc = () => Math.round(textData.y + textData.textMargin); - break; - case 'middle': - case 'center': - yfunc = () => - Math.round(textData.y + (prevTextHeight + textHeight + textData.textMargin) / 2); - break; - case 'bottom': - case 'end': - yfunc = () => - Math.round( - textData.y + - (prevTextHeight + textHeight + 2 * textData.textMargin) - - textData.textMargin - ); - break; - } - } - if ( - textData.anchor !== undefined && - textData.textMargin !== undefined && - textData.width !== undefined - ) { - switch (textData.anchor) { - case 'left': - case 'start': - textData.x = Math.round(textData.x + textData.textMargin); - textData.anchor = 'start'; - textData.dominantBaseline = 'text-after-edge'; - textData.alignmentBaseline = 'middle'; - break; - case 'middle': - case 'center': - textData.x = Math.round(textData.x + textData.width / 2); - textData.anchor = 'middle'; - textData.dominantBaseline = 'middle'; - textData.alignmentBaseline = 'middle'; - break; - case 'right': - case 'end': - textData.x = Math.round(textData.x + textData.width - textData.textMargin); - textData.anchor = 'end'; - textData.dominantBaseline = 'text-before-edge'; - textData.alignmentBaseline = 'middle'; - break; - } - } - for (let [i, line] of lines.entries()) { - if ( - textData.textMargin !== undefined && - textData.textMargin === 0 && - textData.fontSize !== undefined - ) { - dy = i * textData.fontSize; - } - - const textElem = elem.append('text'); - textElem.attr('x', textData.x); - textElem.attr('y', yfunc()); - if (textData.anchor !== undefined) { - textElem - .attr('text-anchor', textData.anchor) - .attr('dominant-baseline', textData.dominantBaseline) - .attr('alignment-baseline', textData.alignmentBaseline); - } - if (textData.fontFamily !== undefined) { - textElem.style('font-family', textData.fontFamily); - } - if (textData.fontSize !== undefined) { - textElem.style('font-size', textData.fontSize); - } - if (textData.fontWeight !== undefined) { - textElem.style('font-weight', textData.fontWeight); - } - if (textData.fill !== undefined) { - textElem.attr('fill', textData.fill); - } - if (textData.class !== undefined) { - textElem.attr('class', textData.class); - } - if (textData.dy !== undefined) { - textElem.attr('dy', textData.dy); - } else if (dy !== 0) { - textElem.attr('dy', dy); - } - - if (textData.tspan) { - const span = textElem.append('tspan'); - span.attr('x', textData.x); - if (textData.fill !== undefined) { - span.attr('fill', textData.fill); - } - span.text(line); - } else { - textElem.text(line); - } - if ( - textData.valign !== undefined && - textData.textMargin !== undefined && - textData.textMargin > 0 - ) { - textHeight += (textElem._groups || textElem)[0][0].getBBox().height; - prevTextHeight = textHeight; - } - - textElems.push(textElem); - } - - return textElems; -}; - -export const drawLabel = function (elem, txtObject) { - /** - * @param {any} x - * @param {any} y - * @param {any} width - * @param {any} height - * @param {any} cut - * @returns {any} - */ - function genPoints(x, y, width, height, cut) { - return ( - x + - ',' + - y + - ' ' + - (x + width) + - ',' + - y + - ' ' + - (x + width) + - ',' + - (y + height - cut) + - ' ' + - (x + width - cut * 1.2) + - ',' + - (y + height) + - ' ' + - x + - ',' + - (y + height) - ); - } - const polygon = elem.append('polygon'); - polygon.attr('points', genPoints(txtObject.x, txtObject.y, txtObject.width, txtObject.height, 7)); - polygon.attr('class', 'labelBox'); - - txtObject.y = txtObject.y + txtObject.height / 2; - - drawText(elem, txtObject); - return polygon; -}; - export const drawRels = (elem, rels, conf) => { const relsElem = elem.append('g'); let i = 0; @@ -411,7 +234,7 @@ export const drawC4Shape = function (elem, c4Shape, conf) { const c4ShapeElem = elem.append('g'); c4ShapeElem.attr('class', 'person-man'); - // + // // draw rect of c4Shape const rect = getNoteRect(); switch (c4Shape.typeC4Shape.text) { @@ -428,9 +251,10 @@ export const drawC4Shape = function (elem, c4Shape, conf) { rect.fill = fillColor; rect.width = c4Shape.width; rect.height = c4Shape.height; - rect.style = 'stroke:' + strokeColor + ';stroke-width:0.5;'; + rect.stroke = strokeColor; rect.rx = 2.5; rect.ry = 2.5; + rect.attrs = { 'stroke-width': 0.5 }; drawRect(c4ShapeElem, rect); break; case 'system_db': @@ -548,12 +372,12 @@ export const drawC4Shape = function (elem, c4Shape, conf) { textFontConf = conf[c4Shape.typeC4Shape.text + 'Font'](); textFontConf.fontColor = fontColor; - if (c4Shape.thchn && c4Shape.thchn.text !== '') { + if (c4Shape.techn && c4Shape.techn?.text !== '') { _drawTextCandidateFunc(conf)( - c4Shape.thchn.text, + c4Shape.techn.text, c4ShapeElem, c4Shape.x, - c4Shape.y + c4Shape.thchn.Y, + c4Shape.y + c4Shape.techn.Y, c4Shape.width, c4Shape.height, { fill: fontColor, 'font-style': 'italic' }, @@ -744,23 +568,6 @@ export const insertArrowCrossHead = function (elem) { // this is actual shape for arrowhead }; -export const getTextObj = function () { - return { - x: 0, - y: 0, - fill: undefined, - anchor: undefined, - style: '#666', - width: undefined, - height: undefined, - textMargin: 0, - rx: 0, - ry: 0, - tspan: true, - valign: undefined, - }; -}; - export const getNoteRect = function () { return { x: 0, @@ -895,13 +702,10 @@ const _drawTextCandidateFunc = (function () { export default { drawRect, - drawText, - drawLabel, drawBoundary, drawC4Shape, drawRels, drawImage, - drawEmbeddedImage, insertArrowHead, insertArrowEnd, insertArrowFilledHead, @@ -910,7 +714,6 @@ export default { insertDatabaseIcon, insertComputerIcon, insertClockIcon, - getTextObj, getNoteRect, - sanitizeUrl, + sanitizeUrl, // TODO why is this exported? }; diff --git a/packages/mermaid/src/diagrams/class/classRenderer-v2.js b/packages/mermaid/src/diagrams/class/classRenderer-v2.js index c4e7e0291..b7e538583 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer-v2.js +++ b/packages/mermaid/src/diagrams/class/classRenderer-v2.js @@ -1,5 +1,5 @@ import { select } from 'd3'; -import * as graphlib from 'dagre-d3-es/src/graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { log } from '../../logger'; import { getConfig } from '../../config'; import { render } from '../../dagre-wrapper/index.js'; @@ -8,7 +8,6 @@ import { curveLinear } from 'd3'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import common from '../common/common'; -import addSVGAccessibilityFields from '../../accessibility'; const sanitizeText = (txt) => common.sanitizeText(txt, getConfig()); @@ -349,19 +348,6 @@ export const setConf = function (cnf) { */ export const draw = function (text, id, _version, diagObj) { log.info('Drawing class - ', id); - // diagObj.db.clear(); - // const parser = diagObj.db.parser; - // parser.yy = classDb; - - // Parse the graph definition - // try { - // parser.parse(text); - // } catch (err) { - // log.debug('Parsing failed'); - // } - - // Fetch the default direction, use TD if none was found - //let dir = 'TD'; const conf = getConfig().flowchart; const securityLevel = getConfig().securityLevel; @@ -385,15 +371,6 @@ export const draw = function (text, id, _version, diagObj) { return {}; }); - // let subG; - // const subGraphs = flowDb.getSubGraphs(); - // log.info('Subgraphs - ', subGraphs); - // for (let i = subGraphs.length - 1; i >= 0; i--) { - // subG = subGraphs[i]; - // log.info('Subgraph - ', subG); - // flowDb.addVertex(subG.id, subG.title, 'group', undefined, subG.classes); - // } - // Fetch the vertices/nodes and edges/links from the parsed graph definition const classes = diagObj.db.getClasses(); const relations = diagObj.db.getRelations(); @@ -451,7 +428,6 @@ export const draw = function (text, id, _version, diagObj) { } } - addSVGAccessibilityFields(diagObj.db, svg, id); // If node has a link, wrap it in an anchor SVG object. // const keys = Object.keys(classes); // keys.forEach(function(key) { diff --git a/packages/mermaid/src/diagrams/class/classRenderer.js b/packages/mermaid/src/diagrams/class/classRenderer.js index c500a73a7..80a7f26e4 100644 --- a/packages/mermaid/src/diagrams/class/classRenderer.js +++ b/packages/mermaid/src/diagrams/class/classRenderer.js @@ -5,7 +5,6 @@ import { log } from '../../logger'; import svgDraw from './svgDraw'; import { configureSvgSize } from '../../setupGraphViewbox'; import { getConfig } from '../../config'; -import addSVGAccessibilityFields from '../../accessibility'; let idCache = {}; const padding = 20; @@ -272,7 +271,6 @@ export const draw = function (text, id, _version, diagObj) { const vBox = `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`; log.debug(`viewBox ${vBox}`); diagram.attr('viewBox', vBox); - addSVGAccessibilityFields(diagObj.db, diagram, id); }; export default { diff --git a/packages/mermaid/src/diagrams/common/common.spec.js b/packages/mermaid/src/diagrams/common/common.spec.js index 68f5138e7..8fd6229da 100644 --- a/packages/mermaid/src/diagrams/common/common.spec.js +++ b/packages/mermaid/src/diagrams/common/common.spec.js @@ -68,5 +68,7 @@ describe('generic parser', function () { expect(parseGenericTypes('test ~Array~Array~string[]~~~')).toEqual( 'test >>' ); + expect(parseGenericTypes('~test')).toEqual('~test'); + expect(parseGenericTypes('~test Array~string~')).toEqual('~test Array'); }); }); diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 194a9a4c0..d34a2df68 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -47,7 +47,9 @@ export const sanitizeText = (text: string, config: MermaidConfig): string => { if (config.dompurifyConfig) { text = DOMPurify.sanitize(sanitizeMore(text, config), config.dompurifyConfig).toString(); } else { - text = DOMPurify.sanitize(sanitizeMore(text, config)); + text = DOMPurify.sanitize(sanitizeMore(text, config), { + FORBID_TAGS: ['style'], + }).toString(); } return text; }; @@ -152,11 +154,17 @@ export const evaluate = (val?: string | boolean): boolean => export const parseGenericTypes = function (text: string): string { let cleanedText = text; - if (text.includes('~')) { - cleanedText = cleanedText.replace(/~([^~].*)/, '<$1'); - cleanedText = cleanedText.replace(/~([^~]*)$/, '>$1'); + if (text.split('~').length - 1 >= 2) { + let newCleanedText = cleanedText; - return parseGenericTypes(cleanedText); + // use a do...while loop instead of replaceAll to detect recursion + // e.g. Array~Array~T~~ + do { + cleanedText = newCleanedText; + newCleanedText = cleanedText.replace(/~([^\s,:;]+)~/, '<$1>'); + } while (newCleanedText != cleanedText); + + return parseGenericTypes(newCleanedText); } else { return cleanedText; } diff --git a/packages/mermaid/src/diagrams/er/erRenderer.js b/packages/mermaid/src/diagrams/er/erRenderer.js index 101beebb9..5dd5024bb 100644 --- a/packages/mermaid/src/diagrams/er/erRenderer.js +++ b/packages/mermaid/src/diagrams/er/erRenderer.js @@ -1,4 +1,4 @@ -import * as graphlib from 'dagre-d3-es/src/graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { line, curveBasis, select } from 'd3'; import { layout as dagreLayout } from 'dagre-d3-es/src/dagre/index.js'; import { getConfig } from '../../config'; @@ -6,9 +6,8 @@ import { log } from '../../logger'; import utils from '../../utils'; import erMarkers from './erMarkers'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; import { parseGenericTypes } from '../common/common'; -import { v4 as uuid4 } from 'uuid'; +import { v5 as uuid5 } from 'uuid'; /** Regex used to remove chars from the entity name so the result can be used in an id */ const BAD_ID_CHARS_REGEXP = /[^\dA-Za-z](\W)*/g; @@ -60,7 +59,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => { // Check to see if any of the attributes has a key or a comment attributes.forEach((item) => { - if (item.attributeKeyType !== undefined) { + if (item.attributeKeyTypeList !== undefined && item.attributeKeyTypeList.length > 0) { hasKeyType = true; } @@ -113,6 +112,9 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => { nodeHeight = Math.max(typeBBox.height, nameBBox.height); if (hasKeyType) { + const keyTypeNodeText = + item.attributeKeyTypeList !== undefined ? item.attributeKeyTypeList.join(',') : ''; + const keyTypeNode = groupNode .append('text') .classed('er entityLabel', true) @@ -123,7 +125,7 @@ const drawAttributes = (groupNode, entityTextNode, attributes) => { .style('text-anchor', 'left') .style('font-family', getConfig().fontFamily) .style('font-size', attrFontSize + 'px') - .text(item.attributeKeyType || ''); + .text(keyTypeNodeText); attributeNode.kn = keyTypeNode; const keyTypeBBox = keyTypeNode.node().getBBox(); @@ -642,13 +644,26 @@ export const draw = function (text, id, _version, diagObj) { configureSvgSize(svg, height, width, conf.useMaxWidth); svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`); - - addSVGAccessibilityFields(diagObj.db, svg, id); }; // draw +/** + * UUID namespace for ER diagram IDs + * + * This can be generated via running: + * + * ```js + * const { v5: uuid5 } = await import('uuid'); + * uuid5( + * 'https://mermaid-js.github.io/mermaid/syntax/entityRelationshipDiagram.html', + * uuid5.URL + * ); + * ``` + */ +const MERMAID_ERDIAGRAM_UUID = '28e9f9db-3c8d-5aa5-9faf-44286ae5937c'; + /** * Return a unique id based on the given string. Start with the prefix, then a hyphen, then the - * simplified str, then a hyphen, then a unique uuid. (Hyphens are only included if needed.) + * simplified str, then a hyphen, then a unique uuid based on the str. (Hyphens are only included if needed.) * Although the official XML standard for ids says that many more characters are valid in the id, * this keeps things simple by accepting only A-Za-z0-9. * @@ -659,7 +674,11 @@ export const draw = function (text, id, _version, diagObj) { */ export function generateId(str = '', prefix = '') { const simplifiedStr = str.replace(BAD_ID_CHARS_REGEXP, ''); - return `${strWithHyphen(prefix)}${strWithHyphen(simplifiedStr)}${uuid4()}`; + // we use `uuid v5` so that UUIDs are consistent given a string. + return `${strWithHyphen(prefix)}${strWithHyphen(simplifiedStr)}${uuid5( + str, + MERMAID_ERDIAGRAM_UUID + )}`; } /** diff --git a/packages/mermaid/src/diagrams/er/erRenderer.spec.ts b/packages/mermaid/src/diagrams/er/erRenderer.spec.ts new file mode 100644 index 000000000..ca0f62bd2 --- /dev/null +++ b/packages/mermaid/src/diagrams/er/erRenderer.spec.ts @@ -0,0 +1,12 @@ +import { generateId } from './erRenderer'; + +describe('erRenderer', () => { + describe('generateId', () => { + it('should be deterministic', () => { + const id1 = generateId('hello world', 'my-prefix'); + const id2 = generateId('hello world', 'my-prefix'); + + expect(id1).toBe(id2); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison index f0411fd72..6aaca81d6 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.jison +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.jison @@ -28,10 +28,11 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili \"[^"]*\" return 'WORD'; "erDiagram" return 'ER_DIAGRAM'; "{" { this.begin("block"); return 'BLOCK_START'; } +"," return 'COMMA'; \s+ /* skip whitespace in block */ -\b((?:PK)|(?:FK))\b return 'ATTRIBUTE_KEY' +\b((?:PK)|(?:FK)|(?:UK))\b return 'ATTRIBUTE_KEY' (.*?)[~](.*?)*[~] return 'ATTRIBUTE_WORD'; -[A-Za-z][A-Za-z0-9\-_\[\]]* return 'ATTRIBUTE_WORD' +[A-Za-z_][A-Za-z0-9\-_\[\]\(\)]* return 'ATTRIBUTE_WORD' \"[^"]*\" return 'COMMENT'; [\n]+ /* nothing */ "}" { this.popState(); return 'BLOCK_STOP'; } @@ -131,11 +132,12 @@ attributes attribute : 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 attributeKeyType attributeComment { $$ = { attributeType: $1, attributeName: $2, attributeKeyType: $3, attributeComment: $4 }; } + | attributeType attributeName attributeKeyTypeList attributeComment { $$ = { attributeType: $1, attributeName: $2, attributeKeyTypeList: $3, attributeComment: $4 }; } ; + attributeType : ATTRIBUTE_WORD { $$=$1; } ; @@ -144,6 +146,11 @@ attributeName : ATTRIBUTE_WORD { $$=$1; } ; +attributeKeyTypeList + : attributeKeyType { $$ = [$1]; } + | attributeKeyTypeList COMMA attributeKeyType { $1.push($3); $$ = $1; } + ; + attributeKeyType : ATTRIBUTE_KEY { $$=$1; } ; diff --git a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js index eb738fe4b..ca497a2ac 100644 --- a/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js +++ b/packages/mermaid/src/diagrams/er/parser/erDiagram.spec.js @@ -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 () { const entity = 'BOOK'; const attribute = 'string title'; @@ -176,17 +207,40 @@ describe('when parsing ER diagram it...', function () { expect(entities[entity].attributes.length).toBe(1); }); - it('should allow an entity with attribute starting with fk or pk and a comment', function () { + it('should allow an entity with attribute starting with fk, pk or uk and a comment', function () { const entity = 'BOOK'; const attribute1 = 'int fk_title FK'; const attribute2 = 'string pk_author PK'; - const attribute3 = 'float pk_price PK "comment"'; + const attribute3 = 'string uk_address UK'; + const attribute4 = 'float pk_price PK "comment"'; erDiagram.parser.parse( - `erDiagram\n${entity} {\n${attribute1} \n\n${attribute2}\n${attribute3}\n}` + `erDiagram\n${entity} {\n${attribute1} \n\n${attribute2}\n${attribute3}\n${attribute4}\n}` ); const entities = erDb.getEntities(); - expect(entities[entity].attributes.length).toBe(3); + 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 () { @@ -214,6 +268,19 @@ describe('when parsing ER diagram it...', function () { expect(entities[entity].attributes.length).toBe(2); }); + it('should allow an entity with attribute that is a limited length string', function () { + const entity = 'BOOK'; + const attribute1 = 'character(10) isbn FK'; + const attribute2 = 'varchar(5) postal_code "Five digits"'; + + erDiagram.parser.parse(`erDiagram\n${entity} {\n${attribute1}\n${attribute2}\n}`); + const entities = erDb.getEntities(); + expect(Object.keys(entities).length).toBe(1); + expect(entities[entity].attributes.length).toBe(2); + expect(entities[entity].attributes[0].attributeType).toBe('character(10)'); + expect(entities[entity].attributes[1].attributeType).toBe('varchar(5)'); + }); + it('should allow an entity with multiple attributes to be defined', function () { const entity = 'BOOK'; const attribute1 = 'string title'; @@ -323,34 +390,34 @@ describe('when parsing ER diagram it...', function () { expect(Object.keys(erDb.getEntities()).length).toBe(1); }); - it('should allow for a accessibility title and description (accDescr)', function () { + describe('accessible title and description', () => { const teacherRole = 'is teacher of'; const line1 = `TEACHER }o--o{ STUDENT : "${teacherRole}"`; - erDiagram.parser.parse( - `erDiagram + it('should allow for a accessibility title and description (accDescr)', function () { + erDiagram.parser.parse( + `erDiagram accTitle: graph title accDescr: this graph is about stuff ${line1}` - ); - expect(erDb.getAccTitle()).toBe('graph title'); - expect(erDb.getAccDescription()).toBe('this graph is about stuff'); - }); + ); + expect(erDb.getAccTitle()).toBe('graph title'); + expect(erDb.getAccDescription()).toBe('this graph is about stuff'); + }); - it('should allow for a accessibility title and multi line description (accDescr)', function () { - const teacherRole = 'is teacher of'; - const line1 = `TEACHER }o--o{ STUDENT : "${teacherRole}"`; - - erDiagram.parser.parse( - `erDiagram + it('parses a multi line description (accDescr)', function () { + erDiagram.parser.parse( + `erDiagram accTitle: graph title - accDescr { - this graph is about stuff - }\n + accDescr { this graph is + about + stuff + }\n ${line1}` - ); - expect(erDb.getAccTitle()).toBe('graph title'); - expect(erDb.getAccDescription()).toBe('this graph is about stuff'); + ); + expect(erDb.getAccTitle()).toEqual('graph title'); + expect(erDb.getAccDescription()).toEqual('this graph is\nabout\nstuff'); + }); }); it('should allow more than one relationship between the same two entities', function () { diff --git a/packages/mermaid/src/diagrams/error/errorRenderer.ts b/packages/mermaid/src/diagrams/error/errorRenderer.ts index b4e267684..60877cb8d 100644 --- a/packages/mermaid/src/diagrams/error/errorRenderer.ts +++ b/packages/mermaid/src/diagrams/error/errorRenderer.ts @@ -1,4 +1,5 @@ /** Created by knut on 14-12-11. */ +// @ts-ignore TODO: Investigate D3 issue import { select } from 'd3'; import { log } from '../../logger'; import { getErrorMessage } from '../../utils'; diff --git a/packages/mermaid/src/diagrams/flowchart/elk/detector.spec.ts b/packages/mermaid/src/diagrams/flowchart/elk/detector.spec.ts new file mode 100644 index 000000000..17cd5c0ea --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/elk/detector.spec.ts @@ -0,0 +1,55 @@ +import plugin from './detector'; +import { describe, it } from 'vitest'; + +const { detector } = plugin; + +describe('flowchart-elk detector', () => { + it('should fail for dagre-d3', () => { + expect( + detector('flowchart', { + flowchart: { + defaultRenderer: 'dagre-d3', + }, + }) + ).toBe(false); + }); + it('should fail for dagre-wrapper', () => { + expect( + detector('flowchart', { + flowchart: { + defaultRenderer: 'dagre-wrapper', + }, + }) + ).toBe(false); + }); + it('should succeed for elk', () => { + expect( + detector('flowchart', { + flowchart: { + defaultRenderer: 'elk', + }, + }) + ).toBe(true); + expect( + detector('graph', { + flowchart: { + defaultRenderer: 'elk', + }, + }) + ).toBe(true); + }); + + it('should detect flowchart-elk', () => { + expect(detector('flowchart-elk')).toBe(true); + }); + + it('should not detect class with defaultRenderer set to elk', () => { + expect( + detector('class', { + flowchart: { + defaultRenderer: 'elk', + }, + }) + ).toBe(false); + }); +}); diff --git a/packages/mermaid/src/diagrams/flowchart/elk/detector.ts b/packages/mermaid/src/diagrams/flowchart/elk/detector.ts new file mode 100644 index 000000000..c6fa77957 --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/elk/detector.ts @@ -0,0 +1,29 @@ +import type { MermaidConfig } from '../../../config.type'; +import type { ExternalDiagramDefinition, DiagramDetector } from '../../../diagram-api/types'; + +const id = 'flowchart-elk'; + +const detector: DiagramDetector = (txt: string, config?: MermaidConfig): boolean => { + if ( + // If diagram explicitly states flowchart-elk + txt.match(/^\s*flowchart-elk/) || + // If a flowchart/graph diagram has their default renderer set to elk + (txt.match(/^\s*flowchart|graph/) && config?.flowchart?.defaultRenderer === 'elk') + ) { + return true; + } + return false; +}; + +const loader = async () => { + const { diagram } = await import('./flowchart-elk-definition.js'); + return { id, diagram }; +}; + +const plugin: ExternalDiagramDefinition = { + id, + detector, + loader, +}; + +export default plugin; diff --git a/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js new file mode 100644 index 000000000..b8458efeb --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/elk/flowRenderer-elk.js @@ -0,0 +1,956 @@ +import { select, line, curveLinear } from 'd3'; +import { insertNode } from '../../../dagre-wrapper/nodes.js'; +import insertMarkers from '../../../dagre-wrapper/markers.js'; +import { insertEdgeLabel } from '../../../dagre-wrapper/edges.js'; +import { findCommonAncestor } from './render-utils'; +import { addHtmlLabel } from 'dagre-d3-es/src/dagre-js/label/add-html-label.js'; +import { getConfig } from '../../../config'; +import { log } from '../../../logger'; +import { setupGraphViewbox } from '../../../setupGraphViewbox'; +import common, { evaluate } from '../../common/common'; +import { interpolateToCurve, getStylesFromArray } from '../../../utils'; + +let elk; + +const portPos = {}; + +const conf = {}; +export const setConf = function (cnf) { + const keys = Object.keys(cnf); + for (const key of keys) { + conf[key] = cnf[key]; + } +}; + +let nodeDb = {}; + +// /** +// * Function that adds the vertices found during parsing to the graph to be rendered. +// * +// * @param vert Object containing the vertices. +// * @param g The graph that is to be drawn. +// * @param svgId +// * @param root +// * @param doc +// * @param diagObj +// */ +export const addVertices = function (vert, svgId, root, doc, diagObj, parentLookupDb, graph) { + const svg = root.select(`[id="${svgId}"]`); + const nodes = svg.insert('g').attr('class', 'nodes'); + const keys = Object.keys(vert); + + // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition + keys.forEach(function (id) { + const vertex = vert[id]; + + /** + * Variable for storing the classes for the vertex + * + * @type {string} + */ + let classStr = 'default'; + if (vertex.classes.length > 0) { + classStr = vertex.classes.join(' '); + } + + const styles = getStylesFromArray(vertex.styles); + + // Use vertex id as text in the box if no text is provided by the graph definition + let vertexText = vertex.text !== undefined ? vertex.text : vertex.id; + + // We create a SVG label, either by delegating to addHtmlLabel or manually + let vertexNode; + const labelData = { width: 0, height: 0 }; + if (evaluate(getConfig().flowchart.htmlLabels)) { + // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? + const node = { + label: vertexText.replace( + /fa[blrs]?:fa-[\w-]+/g, + (s) => `` + ), + }; + vertexNode = addHtmlLabel(svg, node).node(); + const bbox = vertexNode.getBBox(); + labelData.width = bbox.width; + labelData.height = bbox.height; + labelData.labelNode = vertexNode; + vertexNode.parentNode.removeChild(vertexNode); + } else { + const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text'); + svgLabel.setAttribute('style', styles.labelStyle.replace('color:', 'fill:')); + + const rows = vertexText.split(common.lineBreakRegex); + + for (const row of rows) { + const tspan = doc.createElementNS('http://www.w3.org/2000/svg', 'tspan'); + tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); + tspan.setAttribute('dy', '1em'); + tspan.setAttribute('x', '1'); + tspan.textContent = row; + svgLabel.appendChild(tspan); + } + vertexNode = svgLabel; + const bbox = vertexNode.getBBox(); + labelData.width = bbox.width; + labelData.height = bbox.height; + labelData.labelNode = vertexNode; + } + + const ports = [ + { + id: vertex.id + '-west', + layoutOptions: { + 'port.side': 'WEST', + }, + }, + { + id: vertex.id + '-east', + layoutOptions: { + 'port.side': 'EAST', + }, + }, + { + id: vertex.id + '-south', + layoutOptions: { + 'port.side': 'SOUTH', + }, + }, + { + id: vertex.id + '-north', + layoutOptions: { + 'port.side': 'NORTH', + }, + }, + ]; + + let radious = 0; + let _shape = ''; + let layoutOptions = {}; + // Set the shape based parameters + switch (vertex.type) { + case 'round': + radious = 5; + _shape = 'rect'; + break; + case 'square': + _shape = 'rect'; + break; + case 'diamond': + _shape = 'question'; + layoutOptions = { + portConstraints: 'FIXED_SIDE', + }; + break; + case 'hexagon': + _shape = 'hexagon'; + break; + case 'odd': + _shape = 'rect_left_inv_arrow'; + break; + case 'lean_right': + _shape = 'lean_right'; + break; + case 'lean_left': + _shape = 'lean_left'; + break; + case 'trapezoid': + _shape = 'trapezoid'; + break; + case 'inv_trapezoid': + _shape = 'inv_trapezoid'; + break; + case 'odd_right': + _shape = 'rect_left_inv_arrow'; + break; + case 'circle': + _shape = 'circle'; + break; + case 'ellipse': + _shape = 'ellipse'; + break; + case 'stadium': + _shape = 'stadium'; + break; + case 'subroutine': + _shape = 'subroutine'; + break; + case 'cylinder': + _shape = 'cylinder'; + break; + case 'group': + _shape = 'rect'; + break; + case 'doublecircle': + _shape = 'doublecircle'; + break; + default: + _shape = 'rect'; + } + // Add the node + const node = { + labelStyle: styles.labelStyle, + shape: _shape, + labelText: vertexText, + rx: radious, + ry: radious, + class: classStr, + style: styles.style, + id: vertex.id, + link: vertex.link, + linkTarget: vertex.linkTarget, + tooltip: diagObj.db.getTooltip(vertex.id) || '', + domId: diagObj.db.lookUpDomId(vertex.id), + haveCallback: vertex.haveCallback, + width: vertex.type === 'group' ? 500 : undefined, + dir: vertex.dir, + type: vertex.type, + props: vertex.props, + padding: getConfig().flowchart.padding, + }; + let boundingBox; + let nodeEl; + if (node.type !== 'group') { + nodeEl = insertNode(nodes, node, vertex.dir); + boundingBox = nodeEl.node().getBBox(); + } + + const data = { + id: vertex.id, + ports: vertex.type === 'diamond' ? ports : [], + // labelStyle: styles.labelStyle, + // shape: _shape, + layoutOptions, + labelText: vertexText, + labelData, + // labels: [{ text: vertexText }], + // rx: radius, + // ry: radius, + // class: classStr, + // style: styles.style, + // link: vertex.link, + // linkTarget: vertex.linkTarget, + // tooltip: diagObj.db.getTooltip(vertex.id) || '', + domId: diagObj.db.lookUpDomId(vertex.id), + // haveCallback: vertex.haveCallback, + width: boundingBox?.width, + height: boundingBox?.height, + // dir: vertex.dir, + type: vertex.type, + // props: vertex.props, + // padding: getConfig().flowchart.padding, + // boundingBox, + el: nodeEl, + parent: parentLookupDb.parentById[vertex.id], + }; + // if (!Object.keys(parentLookupDb.childrenById).includes(vertex.id)) { + // graph.children.push({ + // ...data, + // }); + // } + nodeDb[node.id] = data; + // log.trace('setNode', { + // labelStyle: styles.labelStyle, + // shape: _shape, + // labelText: vertexText, + // rx: radius, + // ry: radius, + // class: classStr, + // style: styles.style, + // id: vertex.id, + // domId: diagObj.db.lookUpDomId(vertex.id), + // width: vertex.type === 'group' ? 500 : undefined, + // type: vertex.type, + // dir: vertex.dir, + // props: vertex.props, + // padding: getConfig().flowchart.padding, + // parent: parentLookupDb.parentById[vertex.id], + // }); + }); + return graph; +}; + +const getNextPosition = (position, edgeDirection, graphDirection) => { + const portPos = { + TB: { + in: { + north: 'north', + }, + out: { + south: 'west', + west: 'east', + east: 'south', + }, + }, + LR: { + in: { + west: 'west', + }, + out: { + east: 'south', + south: 'north', + north: 'east', + }, + }, + RL: { + in: { + east: 'east', + }, + out: { + west: 'north', + north: 'south', + south: 'west', + }, + }, + BT: { + in: { + south: 'south', + }, + out: { + north: 'east', + east: 'west', + west: 'north', + }, + }, + }; + portPos.TD = portPos.TB; + log.info('abc88', graphDirection, edgeDirection, position); + return portPos[graphDirection][edgeDirection][position]; + // return 'south'; +}; + +const getNextPort = (node, edgeDirection, graphDirection) => { + log.info('getNextPort abc88', { node, edgeDirection, graphDirection }); + if (!portPos[node]) { + switch (graphDirection) { + case 'TB': + case 'TD': + portPos[node] = { + inPosition: 'north', + outPosition: 'south', + }; + break; + case 'BT': + portPos[node] = { + inPosition: 'south', + outPosition: 'north', + }; + break; + case 'RL': + portPos[node] = { + inPosition: 'east', + outPosition: 'west', + }; + break; + case 'LR': + portPos[node] = { + inPosition: 'west', + outPosition: 'east', + }; + break; + } + } + const result = edgeDirection === 'in' ? portPos[node].inPosition : portPos[node].outPosition; + + if (edgeDirection === 'in') { + portPos[node].inPosition = getNextPosition( + portPos[node].inPosition, + edgeDirection, + graphDirection + ); + } else { + portPos[node].outPosition = getNextPosition( + portPos[node].outPosition, + edgeDirection, + graphDirection + ); + } + return result; +}; + +const getEdgeStartEndPoint = (edge, dir) => { + let source = edge.start; + let target = edge.end; + + const startNode = nodeDb[source]; + const endNode = nodeDb[target]; + + if (!startNode || !endNode) { + return { source, target }; + } + + if (startNode.type === 'diamond') { + source = `${source}-${getNextPort(source, 'out', dir)}`; + } + + if (endNode.type === 'diamond') { + target = `${target}-${getNextPort(target, 'in', dir)}`; + } + + // Add the edge to the graph + return { source, target }; +}; + +/** + * Add edges to graph based on parsed graph definition + * + * @param {object} edges The edges to add to the graph + * @param {object} g The graph object + * @param cy + * @param diagObj + * @param graph + * @param svg + */ +export const addEdges = function (edges, diagObj, graph, svg) { + log.info('abc78 edges = ', edges); + const labelsEl = svg.insert('g').attr('class', 'edgeLabels'); + let linkIdCnt = {}; + let dir = diagObj.db.getDirection(); + let defaultStyle; + let defaultLabelStyle; + + if (edges.defaultStyle !== undefined) { + const defaultStyles = getStylesFromArray(edges.defaultStyle); + defaultStyle = defaultStyles.style; + defaultLabelStyle = defaultStyles.labelStyle; + } + + edges.forEach(function (edge) { + // Identify Link + var linkIdBase = 'L-' + edge.start + '-' + edge.end; + // count the links from+to the same node to give unique id + if (linkIdCnt[linkIdBase] === undefined) { + linkIdCnt[linkIdBase] = 0; + log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]); + } else { + linkIdCnt[linkIdBase]++; + log.info('abc78 new entry', linkIdBase, linkIdCnt[linkIdBase]); + } + let linkId = linkIdBase + '-' + linkIdCnt[linkIdBase]; + log.info('abc78 new link id to be used is', linkIdBase, linkId, linkIdCnt[linkIdBase]); + var linkNameStart = 'LS-' + edge.start; + var linkNameEnd = 'LE-' + edge.end; + + const edgeData = { style: '', labelStyle: '' }; + edgeData.minlen = edge.length || 1; + //edgeData.id = 'id' + cnt; + + // Set link type for rendering + if (edge.type === 'arrow_open') { + edgeData.arrowhead = 'none'; + } else { + edgeData.arrowhead = 'normal'; + } + + // Check of arrow types, placed here in order not to break old rendering + edgeData.arrowTypeStart = 'arrow_open'; + edgeData.arrowTypeEnd = 'arrow_open'; + + /* eslint-disable no-fallthrough */ + switch (edge.type) { + case 'double_arrow_cross': + edgeData.arrowTypeStart = 'arrow_cross'; + case 'arrow_cross': + edgeData.arrowTypeEnd = 'arrow_cross'; + break; + case 'double_arrow_point': + edgeData.arrowTypeStart = 'arrow_point'; + case 'arrow_point': + edgeData.arrowTypeEnd = 'arrow_point'; + break; + case 'double_arrow_circle': + edgeData.arrowTypeStart = 'arrow_circle'; + case 'arrow_circle': + edgeData.arrowTypeEnd = 'arrow_circle'; + break; + } + + let style = ''; + let labelStyle = ''; + + switch (edge.stroke) { + case 'normal': + style = 'fill:none;'; + if (defaultStyle !== undefined) { + style = defaultStyle; + } + if (defaultLabelStyle !== undefined) { + labelStyle = defaultLabelStyle; + } + edgeData.thickness = 'normal'; + edgeData.pattern = 'solid'; + break; + case 'dotted': + edgeData.thickness = 'normal'; + edgeData.pattern = 'dotted'; + edgeData.style = 'fill:none;stroke-width:2px;stroke-dasharray:3;'; + break; + case 'thick': + edgeData.thickness = 'thick'; + edgeData.pattern = 'solid'; + edgeData.style = 'stroke-width: 3.5px;fill:none;'; + break; + } + if (edge.style !== undefined) { + const styles = getStylesFromArray(edge.style); + style = styles.style; + labelStyle = styles.labelStyle; + } + + edgeData.style = edgeData.style += style; + edgeData.labelStyle = edgeData.labelStyle += labelStyle; + + if (edge.interpolate !== undefined) { + edgeData.curve = interpolateToCurve(edge.interpolate, curveLinear); + } else if (edges.defaultInterpolate !== undefined) { + edgeData.curve = interpolateToCurve(edges.defaultInterpolate, curveLinear); + } else { + edgeData.curve = interpolateToCurve(conf.curve, curveLinear); + } + + if (edge.text === undefined) { + if (edge.style !== undefined) { + edgeData.arrowheadStyle = 'fill: #333'; + } + } else { + edgeData.arrowheadStyle = 'fill: #333'; + edgeData.labelpos = 'c'; + } + + edgeData.labelType = 'text'; + edgeData.label = edge.text.replace(common.lineBreakRegex, '\n'); + + if (edge.style === undefined) { + edgeData.style = edgeData.style || 'stroke: #333; stroke-width: 1.5px;fill:none;'; + } + + edgeData.labelStyle = edgeData.labelStyle.replace('color:', 'fill:'); + + edgeData.id = linkId; + edgeData.classes = 'flowchart-link ' + linkNameStart + ' ' + linkNameEnd; + + const labelEl = insertEdgeLabel(labelsEl, edgeData); + + // calculate start and end points of the edge + const { source, target } = getEdgeStartEndPoint(edge, dir); + log.debug('abc78 source and target', source, target); + // Add the edge to the graph + graph.edges.push({ + id: 'e' + edge.start + edge.end, + sources: [source], + targets: [target], + labelEl: labelEl, + labels: [ + { + width: edgeData.width, + height: edgeData.height, + orgWidth: edgeData.width, + orgHeight: edgeData.height, + text: edgeData.label, + layoutOptions: { + 'edgeLabels.inline': 'true', + 'edgeLabels.placement': 'CENTER', + }, + }, + ], + edgeData, + }); + }); + return graph; +}; + +// TODO: break out and share with dagre wrapper. The current code in dagre wrapper also adds +// adds the line to the graph, but we don't need that here. This is why we cant use the dagre +// wrapper directly for this +/** + * Add the markers to the edge depending on the type of arrow is + * @param svgPath + * @param edgeData + * @param diagramType + * @param arrowMarkerAbsolute + */ +const addMarkersToEdge = function (svgPath, edgeData, diagramType, arrowMarkerAbsolute) { + let url = ''; + // Check configuration for absolute path + if (arrowMarkerAbsolute) { + url = + window.location.protocol + + '//' + + window.location.host + + window.location.pathname + + window.location.search; + url = url.replace(/\(/g, '\\('); + url = url.replace(/\)/g, '\\)'); + } + + // look in edge data and decide which marker to use + switch (edgeData.arrowTypeStart) { + case 'arrow_cross': + svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-crossStart' + ')'); + break; + case 'arrow_point': + svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-pointStart' + ')'); + break; + case 'arrow_barb': + svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-barbStart' + ')'); + break; + case 'arrow_circle': + svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-circleStart' + ')'); + break; + case 'aggregation': + svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-aggregationStart' + ')'); + break; + case 'extension': + svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-extensionStart' + ')'); + break; + case 'composition': + svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-compositionStart' + ')'); + break; + case 'dependency': + svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-dependencyStart' + ')'); + break; + case 'lollipop': + svgPath.attr('marker-start', 'url(' + url + '#' + diagramType + '-lollipopStart' + ')'); + break; + default: + } + switch (edgeData.arrowTypeEnd) { + case 'arrow_cross': + svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-crossEnd' + ')'); + break; + case 'arrow_point': + svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-pointEnd' + ')'); + break; + case 'arrow_barb': + svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-barbEnd' + ')'); + break; + case 'arrow_circle': + svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-circleEnd' + ')'); + break; + case 'aggregation': + svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-aggregationEnd' + ')'); + break; + case 'extension': + svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-extensionEnd' + ')'); + break; + case 'composition': + svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-compositionEnd' + ')'); + break; + case 'dependency': + svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-dependencyEnd' + ')'); + break; + case 'lollipop': + svgPath.attr('marker-end', 'url(' + url + '#' + diagramType + '-lollipopEnd' + ')'); + break; + default: + } +}; + +/** + * Returns the all the styles from classDef statements in the graph definition. + * + * @param text + * @param diagObj + * @returns {object} ClassDef styles + */ +export const getClasses = function (text, diagObj) { + log.info('Extracting classes'); + diagObj.db.clear('ver-2'); + try { + // Parse the graph definition + diagObj.parse(text); + return diagObj.db.getClasses(); + } catch (e) { + return {}; + } +}; + +const addSubGraphs = function (db) { + const parentLookupDb = { parentById: {}, childrenById: {} }; + const subgraphs = db.getSubGraphs(); + log.info('Subgraphs - ', subgraphs); + subgraphs.forEach(function (subgraph) { + subgraph.nodes.forEach(function (node) { + parentLookupDb.parentById[node] = subgraph.id; + if (parentLookupDb.childrenById[subgraph.id] === undefined) { + parentLookupDb.childrenById[subgraph.id] = []; + } + parentLookupDb.childrenById[subgraph.id].push(node); + }); + }); + + subgraphs.forEach(function (subgraph) { + const data = { id: subgraph.id }; + if (parentLookupDb.parentById[subgraph.id] !== undefined) { + data.parent = parentLookupDb.parentById[subgraph.id]; + } + }); + return parentLookupDb; +}; + +const calcOffset = function (src, dest, parentLookupDb) { + const ancestor = findCommonAncestor(src, dest, parentLookupDb); + if (ancestor === undefined || ancestor === 'root') { + return { x: 0, y: 0 }; + } + + const ancestorOffset = nodeDb[ancestor].offset; + return { x: ancestorOffset.posX, y: ancestorOffset.posY }; +}; + +const insertEdge = function (edgesEl, edge, edgeData, diagObj, parentLookupDb) { + const offset = calcOffset(edge.sources[0], edge.targets[0], parentLookupDb); + + const src = edge.sections[0].startPoint; + const dest = edge.sections[0].endPoint; + const segments = edge.sections[0].bendPoints ? edge.sections[0].bendPoints : []; + + const segPoints = segments.map((segment) => [segment.x + offset.x, segment.y + offset.y]); + const points = [ + [src.x + offset.x, src.y + offset.y], + ...segPoints, + [dest.x + offset.x, dest.y + offset.y], + ]; + + // const curve = line().curve(curveBasis); + const curve = line().curve(curveLinear); + const edgePath = edgesEl + .insert('path') + .attr('d', curve(points)) + .attr('class', 'path') + .attr('fill', 'none'); + const edgeG = edgesEl.insert('g').attr('class', 'edgeLabel'); + const edgeWithLabel = select(edgeG.node().appendChild(edge.labelEl)); + const box = edgeWithLabel.node().firstChild.getBoundingClientRect(); + edgeWithLabel.attr('width', box.width); + edgeWithLabel.attr('height', box.height); + + edgeG.attr( + 'transform', + `translate(${edge.labels[0].x + offset.x}, ${edge.labels[0].y + offset.y})` + ); + addMarkersToEdge(edgePath, edgeData, diagObj.type, diagObj.arrowMarkerAbsolute); +}; + +/** + * Recursive function that iterates over an array of nodes and inserts the children of each node. + * It also recursively populates the inserts the children of the children and so on. + * @param {*} graph + * @param nodeArray + * @param parentLookupDb + */ +const insertChildren = (nodeArray, parentLookupDb) => { + nodeArray.forEach((node) => { + // Check if we have reached the end of the tree + if (!node.children) { + node.children = []; + } + // Check if the node has children + const childIds = parentLookupDb.childrenById[node.id]; + // If the node has children, add them to the node + if (childIds) { + childIds.forEach((childId) => { + node.children.push(nodeDb[childId]); + }); + } + // Recursive call + insertChildren(node.children, parentLookupDb); + }); +}; + +/** + * Draws a flowchart in the tag with id: id based on the graph definition in text. + * + * @param text + * @param id + */ + +export const draw = async function (text, id, _version, diagObj) { + if (!elk) { + const ELK = (await import('elkjs/lib/elk.bundled.js')).default; + elk = new ELK(); + } + // Add temporary render element + diagObj.db.clear(); + nodeDb = {}; + diagObj.db.setGen('gen-2'); + // Parse the graph definition + diagObj.parser.parse(text); + + const renderEl = select('body').append('div').attr('style', 'height:400px').attr('id', 'cy'); + let graph = { + id: 'root', + layoutOptions: { + 'elk.hierarchyHandling': 'INCLUDE_CHILDREN', + 'org.eclipse.elk.padding': '[top=100, left=100, bottom=110, right=110]', + 'elk.layered.spacing.edgeNodeBetweenLayers': '30', + // 'elk.layered.mergeEdges': 'true', + 'elk.direction': 'DOWN', + // 'elk.ports.sameLayerEdges': true, + // 'nodePlacement.strategy': 'SIMPLE', + }, + children: [], + edges: [], + }; + log.info('Drawing flowchart using v3 renderer', elk); + + // Set the direction, + // Fetch the default direction, use TD if none was found + let dir = diagObj.db.getDirection(); + switch (dir) { + case 'BT': + graph.layoutOptions['elk.direction'] = 'UP'; + break; + case 'TB': + graph.layoutOptions['elk.direction'] = 'DOWN'; + break; + case 'LR': + graph.layoutOptions['elk.direction'] = 'RIGHT'; + break; + case 'RL': + graph.layoutOptions['elk.direction'] = 'LEFT'; + 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); + } + // 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, + }, + ]; + delete node.x; + delete node.y; + delete node.width; + delete node.height; + } + }); + insertChildren(graph.children, parentLookupDb); + log.info('after layout', JSON.stringify(graph, null, 2)); + const g = await elk.layout(graph); + drawNodes(0, 0, g.children, svg, subGraphsEl, diagObj, 0); + log.info('after layout', g); + g.edges?.map((edge) => { + insertEdge(edgesEl, edge, edge.edgeData, diagObj, parentLookupDb); + }); + setupGraphViewbox({}, svg, conf.diagramPadding, conf.useMaxWidth); + // Remove element after layout + renderEl.remove(); +}; + +const drawNodes = (relX, relY, nodeArray, svg, subgraphsEl, diagObj, depth) => { + nodeArray.forEach(function (node) { + if (node) { + nodeDb[node.id].offset = { + posX: node.x + relX, + posY: node.y + relY, + x: relX, + y: relY, + depth, + width: node.width, + height: node.height, + }; + if (node.type === 'group') { + const subgraphEl = subgraphsEl.insert('g').attr('class', 'subgraph'); + subgraphEl + .insert('rect') + .attr('class', 'subgraph subgraph-lvl-' + (depth % 5) + ' node') + .attr('x', node.x + relX) + .attr('y', node.y + relY) + .attr('width', node.width) + .attr('height', node.height); + const label = subgraphEl.insert('g').attr('class', 'label'); + label.attr( + 'transform', + `translate(${node.labels[0].x + relX + node.x}, ${node.labels[0].y + relY + node.y})` + ); + label.node().appendChild(node.labelData.labelNode); + + log.info('Id (UGH)= ', node.type, node.labels); + } else { + log.info('Id (UGH)= ', node.id); + node.el.attr( + 'transform', + `translate(${node.x + relX + node.width / 2}, ${node.y + relY + node.height / 2})` + ); + } + } + }); + nodeArray.forEach(function (node) { + if (node && node.type === 'group') { + drawNodes(relX + node.x, relY + node.y, node.children, svg, subgraphsEl, diagObj, depth + 1); + } + }); +}; + +export default { + getClasses, + draw, +}; diff --git a/packages/mermaid/src/diagrams/flowchart/elk/flowchart-elk-definition.ts b/packages/mermaid/src/diagrams/flowchart/elk/flowchart-elk-definition.ts new file mode 100644 index 000000000..c45873238 --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/elk/flowchart-elk-definition.ts @@ -0,0 +1,13 @@ +// @ts-ignore: JISON typing missing +import parser from '../parser/flow'; + +import * as db from '../flowDb'; +import renderer from './flowRenderer-elk'; +import styles from './styles'; + +export const diagram = { + db, + renderer, + parser, + styles, +}; diff --git a/packages/mermaid/src/diagrams/flowchart/elk/render-utils.spec.ts b/packages/mermaid/src/diagrams/flowchart/elk/render-utils.spec.ts new file mode 100644 index 000000000..d845fa697 --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/elk/render-utils.spec.ts @@ -0,0 +1,40 @@ +import { findCommonAncestor, TreeData } from './render-utils'; +describe('when rendering a flowchart using elk ', () => { + let lookupDb: TreeData; + beforeEach(() => { + lookupDb = { + parentById: { + B4: 'inner', + B5: 'inner', + C4: 'inner2', + C5: 'inner2', + B2: 'Ugge', + B3: 'Ugge', + inner: 'Ugge', + inner2: 'Ugge', + B6: 'outer', + }, + childrenById: { + inner: ['B4', 'B5'], + inner2: ['C4', 'C5'], + Ugge: ['B2', 'B3', 'inner', 'inner2'], + outer: ['B6'], + }, + }; + }); + it('to find parent of siblings in a subgraph', () => { + expect(findCommonAncestor('B4', 'B5', lookupDb)).toBe('inner'); + }); + it('to find an uncle', () => { + expect(findCommonAncestor('B4', 'B2', lookupDb)).toBe('Ugge'); + }); + it('to find a cousin', () => { + expect(findCommonAncestor('B4', 'C4', lookupDb)).toBe('Ugge'); + }); + it('to find a grandparent', () => { + expect(findCommonAncestor('B4', 'B6', lookupDb)).toBe('root'); + }); + it('to find ancestor of siblings in the root', () => { + expect(findCommonAncestor('B1', 'outer', lookupDb)).toBe('root'); + }); +}); diff --git a/packages/mermaid/src/diagrams/flowchart/elk/render-utils.ts b/packages/mermaid/src/diagrams/flowchart/elk/render-utils.ts new file mode 100644 index 000000000..ebdc01cf7 --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/elk/render-utils.ts @@ -0,0 +1,25 @@ +export interface TreeData { + parentById: Record; + childrenById: Record; +} + +export const findCommonAncestor = (id1: string, id2: string, treeData: TreeData) => { + const { parentById } = treeData; + const visited = new Set(); + let currentId = id1; + while (currentId) { + visited.add(currentId); + if (currentId === id2) { + return currentId; + } + currentId = parentById[currentId]; + } + currentId = id2; + while (currentId) { + if (visited.has(currentId)) { + return currentId; + } + currentId = parentById[currentId]; + } + return 'root'; +}; diff --git a/packages/mermaid/src/diagrams/flowchart/elk/styles.ts b/packages/mermaid/src/diagrams/flowchart/elk/styles.ts new file mode 100644 index 000000000..e8e7065a0 --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/elk/styles.ts @@ -0,0 +1,138 @@ +/** Returns the styles given options */ +export interface FlowChartStyleOptions { + arrowheadColor: string; + border2: string; + clusterBkg: string; + clusterBorder: string; + edgeLabelBackground: string; + fontFamily: string; + lineColor: string; + mainBkg: string; + nodeBorder: string; + nodeTextColor: string; + tertiaryColor: string; + textColor: string; + titleColor: string; + [key: string]: string; +} + +const genSections = (options: FlowChartStyleOptions) => { + let sections = ''; + + for (let i = 0; i < 5; i++) { + sections += ` + .subgraph-lvl-${i} { + fill: ${options[`surface${i}`]}; + stroke: ${options[`surfacePeer${i}`]}; + } + `; + } + return sections; +}; + +const getStyles = (options: FlowChartStyleOptions) => + `.label { + font-family: ${options.fontFamily}; + color: ${options.nodeTextColor || options.textColor}; + } + .cluster-label text { + fill: ${options.titleColor}; + } + .cluster-label span { + color: ${options.titleColor}; + } + + .label text,span { + fill: ${options.nodeTextColor || options.textColor}; + color: ${options.nodeTextColor || options.textColor}; + } + + .node rect, + .node circle, + .node ellipse, + .node polygon, + .node path { + fill: ${options.mainBkg}; + stroke: ${options.nodeBorder}; + stroke-width: 1px; + } + + .node .label { + text-align: center; + } + .node.clickable { + cursor: pointer; + } + + .arrowheadPath { + fill: ${options.arrowheadColor}; + } + + .edgePath .path { + stroke: ${options.lineColor}; + stroke-width: 2.0px; + } + + .flowchart-link { + stroke: ${options.lineColor}; + fill: none; + } + + .edgeLabel { + background-color: ${options.edgeLabelBackground}; + rect { + opacity: 0.5; + background-color: ${options.edgeLabelBackground}; + fill: ${options.edgeLabelBackground}; + } + text-align: center; + } + + .cluster rect { + fill: ${options.clusterBkg}; + stroke: ${options.clusterBorder}; + stroke-width: 1px; + } + + .cluster text { + fill: ${options.titleColor}; + } + + .cluster span { + color: ${options.titleColor}; + } + /* .cluster div { + color: ${options.titleColor}; + } */ + + div.mermaidTooltip { + position: absolute; + text-align: center; + max-width: 200px; + padding: 2px; + font-family: ${options.fontFamily}; + font-size: 12px; + background: ${options.tertiaryColor}; + border: 1px solid ${options.border2}; + border-radius: 2px; + pointer-events: none; + z-index: 100; + } + + .flowchartTitleText { + text-anchor: middle; + font-size: 18px; + fill: ${options.textColor}; + } + .subgraph { + stroke-width:2; + rx:3; + } + // .subgraph-lvl-1 { + // fill:#ccc; + // // stroke:black; + // } + ${genSections(options)} +`; + +export default getStyles; diff --git a/packages/mermaid/src/diagrams/flowchart/flowDb.js b/packages/mermaid/src/diagrams/flowchart/flowDb.js index 9181ab9cc..2e6b840b5 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDb.js +++ b/packages/mermaid/src/diagrams/flowchart/flowDb.js @@ -238,6 +238,9 @@ export const setDirection = function (dir) { if (direction.match(/.*v/)) { direction = 'TB'; } + if (direction === 'TD') { + direction = 'TB'; + } }; /** @@ -439,7 +442,7 @@ export const clear = function (ver = 'gen-1') { commonClear(); }; export const setGen = (ver) => { - version = ver || 'gen-1'; + version = ver || 'gen-2'; }; /** @returns {string} */ export const defaultStyle = function () { @@ -684,7 +687,7 @@ const destructEndLink = (_str) => { return { type, stroke, length }; }; -const destructLink = (_str, _startStr) => { +export const destructLink = (_str, _startStr) => { const info = destructEndLink(_str); let startInfo; if (_startStr) { @@ -744,6 +747,9 @@ const makeUniq = (sg, allSubgraphs) => { return { nodes: res }; }; +export const lex = { + firstGraph, +}; export default { parseDirective, defaultConfig: () => configApi.defaultConfig.flowchart, @@ -776,9 +782,7 @@ export default { indexNodes, getSubGraphs, destructLink, - lex: { - firstGraph, - }, + lex, exists, makeUniq, setDiagramTitle, diff --git a/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts b/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts index 34d57ec18..6162cc828 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDetector-v2.ts @@ -4,8 +4,15 @@ import type { ExternalDiagramDefinition } from '../../diagram-api/types'; const id = 'flowchart-v2'; const detector: DiagramDetector = (txt, config) => { + if (config?.flowchart?.defaultRenderer === 'dagre-d3') { + return false; + } + if (config?.flowchart?.defaultRenderer === 'elk') { + return false; + } + // If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram - if (config?.flowchart?.defaultRenderer === 'dagre-wrapper' && txt.match(/^\s*graph/) !== null) { + if (txt.match(/^\s*graph/) !== null) { return true; } return txt.match(/^\s*flowchart/) !== null; diff --git a/packages/mermaid/src/diagrams/flowchart/flowDetector.ts b/packages/mermaid/src/diagrams/flowchart/flowDetector.ts index 2fafdddfa..9457ff469 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowDetector.ts +++ b/packages/mermaid/src/diagrams/flowchart/flowDetector.ts @@ -8,6 +8,9 @@ const detector: DiagramDetector = (txt, config) => { if (config?.flowchart?.defaultRenderer === 'dagre-wrapper') { return false; } + if (config?.flowchart?.defaultRenderer === 'elk') { + return false; + } return txt.match(/^\s*graph/) !== null; }; diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js index be3fffa0c..03bda7611 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer-v2.js @@ -1,4 +1,4 @@ -import * as graphlib from 'dagre-d3-es/src/graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { select, curveLinear, selectAll } from 'd3'; import flowDb from './flowDb'; @@ -11,7 +11,6 @@ import { log } from '../../logger'; import common, { evaluate } from '../common/common'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; const conf = {}; export const setConf = function (cnf) { @@ -409,7 +408,7 @@ export const draw = function (text, id, _version, diagObj) { const edges = diagObj.db.getEdges(); - log.info(edges); + log.info('Edges', edges); let i = 0; for (i = subGraphs.length - 1; i >= 0; i--) { // for (let i = 0; i < subGraphs.length; i++) { @@ -431,9 +430,6 @@ export const draw = function (text, id, _version, diagObj) { // Set up an SVG group so that we can translate the final graph. const svg = root.select(`[id="${id}"]`); - // Adds title and description to the flow chart - addSVGAccessibilityFields(diagObj.db, svg, id); - // Run the renderer. This is what draws the final graph. const element = root.select('#' + id + ' g'); render(element, g, ['point', 'circle', 'cross'], 'flowchart', id); diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js index 41868e203..01b6163cb 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.addEdges.spec.js @@ -1,7 +1,7 @@ import flowDb from './flowDb'; import flowParser from './parser/flow'; import flowRenderer from './flowRenderer'; -import Diagram from '../../Diagram'; +import { Diagram } from '../../Diagram'; import { addDiagrams } from '../../diagram-api/diagram-orchestration'; addDiagrams(); diff --git a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js index 4b3232189..63234b57c 100644 --- a/packages/mermaid/src/diagrams/flowchart/flowRenderer.js +++ b/packages/mermaid/src/diagrams/flowchart/flowRenderer.js @@ -1,4 +1,4 @@ -import * as graphlib from 'dagre-d3-es/src/graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { select, curveLinear, selectAll } from 'd3'; import { getConfig } from '../../config'; import { render as Render } from 'dagre-d3-es'; @@ -9,7 +9,6 @@ import common, { evaluate } from '../common/common'; import { interpolateToCurve, getStylesFromArray } from '../../utils'; import { setupGraphViewbox } from '../../setupGraphViewbox'; import flowChartShapes from './flowChartShapes'; -import addSVGAccessibilityFields from '../../accessibility'; const conf = {}; export const setConf = function (cnf) { @@ -417,9 +416,6 @@ export const draw = function (text, id, _version, diagObj) { // Set up an SVG group so that we can translate the final graph. const svg = root.select(`[id="${id}"]`); - // Adds title and description to the flow chart - addSVGAccessibilityFields(diagObj.db, svg, id); - // Run the renderer. This is what draws the final graph. const element = root.select('#' + id + ' g'); render(element, g); diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js index 5c2094737..7726ce0f7 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-direction.spec.js @@ -1,6 +1,5 @@ import flowDb from '../flowDb'; import flow from './flow'; -import filter from 'lodash-es/filter'; import { setConfig } from '../../../config'; setConfig({ diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison index fae7c6cf2..05473b4a0 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison @@ -82,6 +82,7 @@ that id. [\s\n] this.popState(); [^\s\n]* return 'CLICK'; +"flowchart-elk" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';} "graph" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';} "flowchart" {if(yy.lex.firstGraph()){this.begin("dir");} return 'GRAPH';} "subgraph" return 'subgraph'; diff --git a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js index 5ba6a5361..ae6f178b8 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js +++ b/packages/mermaid/src/diagrams/flowchart/parser/subgraph.spec.js @@ -1,6 +1,5 @@ import flowDb from '../flowDb'; import flow from './flow'; -import filter from 'lodash-es/filter'; import { setConfig } from '../../../config'; setConfig({ diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js index ab2407ecd..faec35a86 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js +++ b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js @@ -19,7 +19,6 @@ import { import common from '../common/common'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; export const setConf = function () { log.debug('Something is calling, setConf, remove the call'); @@ -116,8 +115,6 @@ export const draw = function (text, id, version, diagObj) { .attr('y', conf.titleTopMargin) .attr('class', 'titleText'); - addSVGAccessibilityFields(diagObj.db, svg, id); - /** * @param tasks * @param pageWidth diff --git a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js index 6874363ad..787eb2490 100644 --- a/packages/mermaid/src/diagrams/git/gitGraphRenderer.js +++ b/packages/mermaid/src/diagrams/git/gitGraphRenderer.js @@ -2,7 +2,6 @@ import { select } from 'd3'; import { getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI'; import { log } from '../../logger'; import utils from '../../utils'; -import addSVGAccessibilityFields from '../../accessibility'; let allCommitsDict = {}; @@ -506,9 +505,6 @@ export const draw = function (txt, id, ver, diagObj) { const diagram = select(`[id="${id}"]`); - // Adds title and description to the flow chart - addSVGAccessibilityFields(diagObj.db, diagram, id); - drawCommits(diagram, allCommitsDict, false); if (gitGraphConfig.showBranches) { drawBranches(diagram, branches); diff --git a/packages/mermaid-mindmap/src/detector.ts b/packages/mermaid/src/diagrams/mindmap/detector.ts similarity index 65% rename from packages/mermaid-mindmap/src/detector.ts rename to packages/mermaid/src/diagrams/mindmap/detector.ts index da3caf51e..2e2b1c7d6 100644 --- a/packages/mermaid-mindmap/src/detector.ts +++ b/packages/mermaid/src/diagrams/mindmap/detector.ts @@ -1,5 +1,4 @@ -import type { ExternalDiagramDefinition } from 'mermaid'; - +import type { ExternalDiagramDefinition } from '../../diagram-api/types'; const id = 'mindmap'; const detector = (txt: string) => { @@ -7,7 +6,7 @@ const detector = (txt: string) => { }; const loader = async () => { - const { diagram } = await import('./diagram-definition'); + const { diagram } = await import('./mindmap-definition.js'); return { id, diagram }; }; diff --git a/packages/mermaid-mindmap/src/diagram-definition.ts b/packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts similarity index 84% rename from packages/mermaid-mindmap/src/diagram-definition.ts rename to packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts index e7856289d..61b41d347 100644 --- a/packages/mermaid-mindmap/src/diagram-definition.ts +++ b/packages/mermaid/src/diagrams/mindmap/mindmap-definition.ts @@ -3,12 +3,10 @@ import mindmapParser from './parser/mindmap'; import * as mindmapDb from './mindmapDb'; import mindmapRenderer from './mindmapRenderer'; import mindmapStyles from './styles'; -import { injectUtils } from './mermaidUtils'; export const diagram = { db: mindmapDb, renderer: mindmapRenderer, parser: mindmapParser, styles: mindmapStyles, - injectUtils, }; diff --git a/packages/mermaid-mindmap/src/mindmap.spec.js b/packages/mermaid/src/diagrams/mindmap/mindmap.spec.js similarity index 88% rename from packages/mermaid-mindmap/src/mindmap.spec.js rename to packages/mermaid/src/diagrams/mindmap/mindmap.spec.js index e3f018350..e8793e86a 100644 --- a/packages/mermaid-mindmap/src/mindmap.spec.js +++ b/packages/mermaid/src/diagrams/mindmap/mindmap.spec.js @@ -1,16 +1,7 @@ import { parser as mindmap } from './parser/mindmap'; import * as mindmapDB from './mindmapDb'; -import { injectUtils } from './mermaidUtils'; // Todo fix utils functions for tests -import { - log, - setLogLevel, - getConfig, - sanitizeText, - setupGraphViewBox, -} from '../../mermaid/src/diagram-api/diagramAPI'; - -injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewBox); +import { setLogLevel } from '../../diagram-api/diagramAPI'; describe('when parsing a mindmap ', function () { beforeEach(function () { @@ -347,4 +338,40 @@ root expect(child.children.length).toEqual(2); expect(child.children[1].nodeId).toEqual('b'); }); + it('MMP-23 Rows with only spaces should not interfere', function () { + let str = 'mindmap\nroot\n A\n \n\n B'; + mindmap.parse(str); + const mm = mindmap.yy.getMindmap(); + expect(mm.nodeId).toEqual('root'); + expect(mm.children.length).toEqual(2); + + const child = mm.children[0]; + expect(child.nodeId).toEqual('A'); + const child2 = mm.children[1]; + expect(child2.nodeId).toEqual('B'); + }); + it('MMP-24 Handle rows above the mindmap declarations', function () { + let str = '\n \nmindmap\nroot\n A\n \n\n B'; + mindmap.parse(str); + const mm = mindmap.yy.getMindmap(); + expect(mm.nodeId).toEqual('root'); + expect(mm.children.length).toEqual(2); + + const child = mm.children[0]; + expect(child.nodeId).toEqual('A'); + const child2 = mm.children[1]; + expect(child2.nodeId).toEqual('B'); + }); + it('MMP-25 Handle rows above the mindmap declarations, no space', function () { + let str = '\n\n\nmindmap\nroot\n A\n \n\n B'; + mindmap.parse(str); + const mm = mindmap.yy.getMindmap(); + expect(mm.nodeId).toEqual('root'); + expect(mm.children.length).toEqual(2); + + const child = mm.children[0]; + expect(child.nodeId).toEqual('A'); + const child2 = mm.children[1]; + expect(child2.nodeId).toEqual('B'); + }); }); diff --git a/packages/mermaid-mindmap/src/mindmapDb.js b/packages/mermaid/src/diagrams/mindmap/mindmapDb.js similarity index 93% rename from packages/mermaid-mindmap/src/mindmapDb.js rename to packages/mermaid/src/diagrams/mindmap/mindmapDb.js index 16861cd23..71aa449d9 100644 --- a/packages/mermaid-mindmap/src/mindmapDb.js +++ b/packages/mermaid/src/diagrams/mindmap/mindmapDb.js @@ -1,5 +1,8 @@ -/** Created by knut on 15-01-14. */ -import { sanitizeText, getConfig, log } from './mermaidUtils'; +import { getConfig } from '../../config'; +import { sanitizeText as _sanitizeText } from '../../diagrams/common/common'; +import { log } from '../../logger'; + +export const sanitizeText = (text) => _sanitizeText(text, getConfig()); let nodes = []; let cnt = 0; diff --git a/packages/mermaid-mindmap/src/mindmapRenderer.js b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js similarity index 91% rename from packages/mermaid-mindmap/src/mindmapRenderer.js rename to packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js index 9fd557e51..0d814212e 100644 --- a/packages/mermaid-mindmap/src/mindmapRenderer.js +++ b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js @@ -1,14 +1,12 @@ /** Created by knut on 14-12-11. */ import { select } from 'd3'; -import { log, getConfig, setupGraphViewbox } from './mermaidUtils'; +import { log } from '../../logger'; +import { getConfig } from '../../config'; +import { setupGraphViewbox } from '../../setupGraphViewbox'; import svgDraw from './svgDraw'; -import cytoscape from 'cytoscape'; -import coseBilkent from 'cytoscape-cose-bilkent'; import * as db from './mindmapDb'; -// Inject the layout algorithm into cytoscape -cytoscape.use(coseBilkent); - +let cytoscape; /** * @param {any} svg The svg element to draw the diagram onto * @param {object} mindmap The mindmap data and hierarchy @@ -89,8 +87,16 @@ function addNodes(mindmap, cy, conf, level) { /** * @param node * @param conf + * @param cy */ -function layoutMindmap(node, conf) { +async function layoutMindmap(node, conf) { + if (!cytoscape) { + cytoscape = (await import('cytoscape')).default; + const coseBilkent = (await import('cytoscape-cose-bilkent')).default; + // Inject the layout algorithm into cytoscape + cytoscape.use(coseBilkent); + } + return new Promise((resolve) => { // Add temporary render element const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none'); @@ -131,7 +137,10 @@ function layoutMindmap(node, conf) { }); } /** + * @param node * @param cy + * @param positionedMindmap + * @param conf */ function positionNodes(cy) { cy.nodes().map((node, id) => { diff --git a/packages/mermaid-mindmap/src/parser/mindmap.jison b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison similarity index 89% rename from packages/mermaid-mindmap/src/parser/mindmap.jison rename to packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison index a96ee6261..d2f6bbf1a 100644 --- a/packages/mermaid-mindmap/src/parser/mindmap.jison +++ b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison @@ -25,6 +25,7 @@ \n { this.popState();} // [\s]*"::icon(" { this.begin('ICON'); } "::icon(" { yy.getLogger().trace('Begin icon');this.begin('ICON'); } +[\s]+[\n] {yy.getLogger().trace('SPACELINE');return 'SPACELINE' /* skip all whitespace */ ;} [\n]+ return 'NL'; [^\)]+ { return 'ICON'; } \) {yy.getLogger().trace('end icon');this.popState();} @@ -64,14 +65,25 @@ start // %{ : info document 'EOF' { return yy; } } - : MINDMAP document { return yy; } - | MINDMAP NL document { return yy; } - | SPACELIST MINDMAP document { return yy; } - ; + : mindMap + | spaceLines mindMap + ; + +spaceLines + : SPACELINE + | spaceLines SPACELINE + | spaceLines NL + ; + +mindMap + : MINDMAP document { return yy; } + | MINDMAP NL document { return yy; } + ; stop : NL {yy.getLogger().trace('Stop NL ');} | EOF {yy.getLogger().trace('Stop EOF ');} + | SPACELINE | stop NL {yy.getLogger().trace('Stop NL2 ');} | stop EOF {yy.getLogger().trace('Stop EOF2 ');} ; @@ -81,9 +93,10 @@ document ; statement - : SPACELIST node { yy.getLogger().trace('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); } + : SPACELIST node { yy.getLogger().info('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); } | SPACELIST ICON { yy.getLogger().trace('Icon: ',$2);yy.decorateNode({icon: $2}); } | SPACELIST CLASS { yy.decorateNode({class: $2}); } + | SPACELINE { yy.getLogger().trace('SPACELIST');} | node { yy.getLogger().trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type); } | ICON { yy.decorateNode({icon: $1}); } | CLASS { yy.decorateNode({class: $1}); } diff --git a/packages/mermaid-mindmap/src/styles.js b/packages/mermaid/src/diagrams/mindmap/styles.js similarity index 100% rename from packages/mermaid-mindmap/src/styles.js rename to packages/mermaid/src/diagrams/mindmap/styles.js diff --git a/packages/mermaid-mindmap/src/svgDraw.js b/packages/mermaid/src/diagrams/mindmap/svgDraw.js similarity index 96% rename from packages/mermaid-mindmap/src/svgDraw.js rename to packages/mermaid/src/diagrams/mindmap/svgDraw.js index d4f57f1f1..2b1aa021e 100644 --- a/packages/mermaid-mindmap/src/svgDraw.js +++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.js @@ -203,15 +203,14 @@ const roundedRectBkg = function (elem, node) { * @returns {number} The height nodes dom element */ export const drawNode = function (elem, node, fullSection, conf) { - const section = (fullSection % MAX_SECTIONS) - 1; + const section = fullSection % (MAX_SECTIONS - 1); const nodeElem = elem.append('g'); node.section = section; - nodeElem.attr( - 'class', - (node.class ? node.class + ' ' : '') + - 'mindmap-node ' + - (section < 0 ? 'section-root' : 'section-' + section) - ); + let sectionClass = 'section-' + section; + if (section < 0) { + sectionClass += ' section-root'; + } + nodeElem.attr('class', (node.class ? node.class + ' ' : '') + 'mindmap-node ' + sectionClass); const bkgElem = nodeElem.append('g'); // Create the wrapped text element @@ -305,7 +304,7 @@ export const drawNode = function (elem, node, fullSection, conf) { }; export const drawEdge = function drawEdge(edgesElem, mindmap, parent, depth, fullSection) { - const section = (fullSection % MAX_SECTIONS) - 1; + const section = fullSection % (MAX_SECTIONS - 1); const sx = parent.x + parent.width / 2; const sy = parent.y + parent.height / 2; const ex = mindmap.x + mindmap.width / 2; diff --git a/packages/mermaid/src/diagrams/pie/amonts.csv b/packages/mermaid/src/diagrams/pie/amonts.csv new file mode 100644 index 000000000..25cf919dd --- /dev/null +++ b/packages/mermaid/src/diagrams/pie/amonts.csv @@ -0,0 +1,10 @@ +name,amounts +Foo, 33 +Rishab, 12 +Alexis, 41 +Tom, 16 +Courtney, 59 +Christina, 38 +Jack, 21 +Mickey, 25 +Paul, 30 diff --git a/packages/mermaid/src/diagrams/pie/pieRenderer.js b/packages/mermaid/src/diagrams/pie/pieRenderer.js index c5d86ad65..83f301207 100644 --- a/packages/mermaid/src/diagrams/pie/pieRenderer.js +++ b/packages/mermaid/src/diagrams/pie/pieRenderer.js @@ -3,7 +3,6 @@ import { select, scaleOrdinal, pie as d3pie, arc } from 'd3'; import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; import * as configApi from '../../config'; -import addSVGAccessibilityFields from '../../accessibility'; let conf = configApi.getConfig(); @@ -53,7 +52,6 @@ export const draw = (txt, id, _version, diagObj) => { const diagram = root.select('#' + id); configureSvgSize(diagram, height, width, conf.pie.useMaxWidth); - addSVGAccessibilityFields(diagObj.db, diagram, id); // Set viewBox elem.setAttribute('viewBox', '0 0 ' + width + ' ' + height); diff --git a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js index a0019f46b..9fd746bd1 100644 --- a/packages/mermaid/src/diagrams/requirement/requirementRenderer.js +++ b/packages/mermaid/src/diagrams/requirement/requirementRenderer.js @@ -6,7 +6,6 @@ import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; import markers from './requirementMarkers'; import { getConfig } from '../../config'; -import addSVGAccessibilityFields from '../../accessibility'; let conf = {}; let relCnt = 0; @@ -363,8 +362,6 @@ export const draw = (text, id, _version, diagObj) => { configureSvgSize(svg, height, width, conf.useMaxWidth); svg.attr('viewBox', `${svgBounds.x - padding} ${svgBounds.y - padding} ${width} ${height}`); - // Adds title and description to the requirements diagram - addSVGAccessibilityFields(diagObj.db, svg, id); }; export default { diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index d5ba18b63..bbc72adcb 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -35,8 +35,9 @@ \%%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ [0-9]+(?=[ \n]+) return 'NUM'; +"box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } -"actor" { this.begin('ID'); return 'participant_actor'; } +"actor" { this.begin('ID'); return 'participant_actor'; } [^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } (?:) { this.popState(); this.popState(); return 'NEWLINE'; } @@ -117,16 +118,30 @@ line | NEWLINE { $$=[]; } ; +box_section + : /* empty */ { $$ = [] } + | box_section box_line {$1.push($2);$$ = $1} + ; + +box_line + : SPACE participant_statement { $$ = $2 } + | participant_statement { $$ = $1 } + | NEWLINE { $$=[]; } + ; + + directive : openDirective typeDirective closeDirective 'NEWLINE' | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' ; 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;} + : participant_statement + | 'box' restOfLine box_section end + { + $3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) }); + $3.push({type: 'boxEnd', boxText:$2}); + $$=$3;} | signal 'NEWLINE' | 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};} @@ -209,6 +224,13 @@ else_sections { $$ = $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' placement actor text2 { diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.js b/packages/mermaid/src/diagrams/sequence/sequenceDb.js index fadd9f391..5c5554c72 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.js @@ -14,20 +14,52 @@ import { let prevActor = undefined; let actors = {}; +let boxes = []; let messages = []; const notes = []; let sequenceNumbersEnabled = false; let wrapEnabled; +let currentBox = undefined; export const parseDirective = function (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) { - // Don't allow description nulling + let assignedBox = currentBox; const old = actors[id]; - if (old && name === old.name && description == null) { - return; + if (old) { + // 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 @@ -39,6 +71,7 @@ export const addActor = function (id, name, description, type) { } actors[id] = { + box: assignedBox, name: name, description: description.text, wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap, @@ -53,6 +86,9 @@ export const addActor = function (id, name, description, type) { actors[prevActor].nextActor = id; } + if (currentBox) { + currentBox.actorKeys.push(id); + } prevActor = id; }; @@ -111,10 +147,21 @@ export const addSignal = function ( return true; }; +export const hasAtLeastOneBox = function () { + return boxes.length > 0; +}; + +export const hasAtLeastOneBoxWithTitle = function () { + return boxes.some((b) => b.name); +}; + export const getMessages = function () { return messages; }; +export const getBoxes = function () { + return boxes; +}; export const getActors = function () { return actors; }; @@ -147,6 +194,7 @@ export const autoWrap = () => { export const clear = function () { actors = {}; + boxes = []; messages = []; sequenceNumbersEnabled = false; commonClear(); @@ -167,6 +215,47 @@ export const parseMessage = function (str) { 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 = { SOLID: 0, DOTTED: 1, @@ -311,6 +400,13 @@ function insertProperties(actor, properties) { } } +/** + * + */ +function boxEnd() { + currentBox = undefined; +} + export const addDetails = function (actorId, text) { // find the actor const actor = getActor(actorId); @@ -391,6 +487,12 @@ export const apply = function (param) { case 'addMessage': addSignal(param.from, param.to, param.msg, param.signalType); break; + case 'boxStart': + addBox(param.boxData); + break; + case 'boxEnd': + boxEnd(); + break; case 'loopStart': addSignal(undefined, undefined, param.loopText, param.signalType); break; @@ -467,12 +569,14 @@ export default { getActorKeys, getActorProperty, getAccTitle, + getBoxes, getDiagramTitle, setDiagramTitle, parseDirective, getConfig: () => configApi.getConfig().sequence, clear, parseMessage, + parseBoxData, LINETYPE, ARROWTYPE, PLACEMENT, @@ -481,4 +585,6 @@ export default { apply, setAccDescription, getAccDescription, + hasAtLeastOneBox, + hasAtLeastOneBoxWithTitle, }; diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index 9422a5f37..72daca932 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -1,8 +1,80 @@ +import { vi } from 'vitest'; + import * as configApi from '../../config'; import mermaidAPI from '../../mermaidAPI'; -import Diagram from '../../Diagram'; +import { Diagram } from '../../Diagram'; import { addDiagrams } from '../../diagram-api/diagram-orchestration'; + +/** + * Sequence diagrams require their own very special version of a mocked d3 module + * diagrams/sequence/svgDraw uses statements like this with d3 nodes: (note the [0][0]) + * + * // in drawText(...) + * textHeight += (textElem._groups || textElem)[0][0].getBBox().height; + */ +vi.mock('d3', () => { + const NewD3 = function () { + function returnThis() { + return this; + } + return { + append: function () { + return NewD3(); + }, + lower: returnThis, + attr: returnThis, + style: returnThis, + text: returnThis, + // [0][0] (below) is required by drawText() in packages/mermaid/src/diagrams/sequence/svgDraw.js + 0: { + 0: { + getBBox: function () { + return { + height: 10, + width: 20, + }; + }, + }, + }, + }; + }; + + return { + select: function () { + return new NewD3(); + }, + + selectAll: function () { + return new NewD3(); + }, + + // TODO: In d3 these are CurveFactory types, not strings + curveBasis: 'basis', + curveBasisClosed: 'basisClosed', + curveBasisOpen: 'basisOpen', + curveBumpX: 'bumpX', + curveBumpY: 'bumpY', + curveBundle: 'bundle', + curveCardinalClosed: 'cardinalClosed', + curveCardinalOpen: 'cardinalOpen', + curveCardinal: 'cardinal', + curveCatmullRomClosed: 'catmullRomClosed', + curveCatmullRomOpen: 'catmullRomOpen', + curveCatmullRom: 'catmullRom', + curveLinear: 'linear', + curveLinearClosed: 'linearClosed', + curveMonotoneX: 'monotoneX', + curveMonotoneY: 'monotoneY', + curveNatural: 'natural', + curveStep: 'step', + curveStepAfter: 'stepAfter', + curveStepBefore: 'stepBefore', + }; +}); +// ------------------------------- + addDiagrams(); + /** * @param conf * @param key @@ -104,14 +176,14 @@ Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`); diagram.db.clear(); }); - it('should handle a sequenceDiagram definition', function () { + it('should handle a sequenceDiagram definition', async function () { const str = ` sequenceDiagram Alice->Bob:Hello Bob, how are you? Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str, diagram); + await mermaidAPI.parse(str, diagram); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -122,18 +194,18 @@ Bob-->Alice: I am good thanks!`; expect(messages[0].from).toBe('Alice'); expect(messages[2].from).toBe('Bob'); }); - it('should not show sequence numbers per default', function () { + it('should not show sequence numbers per default', async () => { const str = ` sequenceDiagram Alice->Bob:Hello Bob, how are you? Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers expect(diagram.db.showSequenceNumbers()).toBe(false); }); - it('should show sequence numbers when autonumber is enabled', function () { + it('should show sequence numbers when autonumber is enabled', async () => { const str = ` sequenceDiagram autonumber @@ -141,11 +213,11 @@ Alice->Bob:Hello Bob, how are you? Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers expect(diagram.db.showSequenceNumbers()).toBe(true); }); - it('should handle a sequenceDiagram definition with a title:', function () { + it('should handle a sequenceDiagram definition with a title:', async () => { const str = ` sequenceDiagram title: Diagram Title @@ -153,7 +225,7 @@ Alice->Bob:Hello Bob, how are you? Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -168,7 +240,7 @@ Bob-->Alice: I am good thanks!`; expect(title).toBe('Diagram Title'); }); - it('should handle a sequenceDiagram definition with a title without a :', function () { + it('should handle a sequenceDiagram definition with a title without a :', async () => { const str = ` sequenceDiagram title Diagram Title @@ -176,7 +248,7 @@ Alice->Bob:Hello Bob, how are you? Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -191,7 +263,7 @@ Bob-->Alice: I am good thanks!`; expect(title).toBe('Diagram Title'); }); - it('should handle a sequenceDiagram definition with a accessibility title and description (accDescr)', function () { + it('should handle a sequenceDiagram definition with a accessibility title and description (accDescr)', async () => { const str = ` sequenceDiagram title: Diagram Title @@ -200,13 +272,13 @@ accDescr: Accessibility Description Alice->Bob:Hello Bob, how are you? `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); expect(diagram.db.getDiagramTitle()).toBe('Diagram Title'); expect(diagram.db.getAccTitle()).toBe('This is the title'); expect(diagram.db.getAccDescription()).toBe('Accessibility Description'); const messages = diagram.db.getMessages(); }); - it('should handle a sequenceDiagram definition with a accessibility title and multiline description (accDescr)', function () { + it('should handle a sequenceDiagram definition with a accessibility title and multiline description (accDescr)', async () => { const str = ` sequenceDiagram accTitle: This is the title @@ -217,19 +289,19 @@ Description Alice->Bob:Hello Bob, how are you? `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); expect(diagram.db.getAccTitle()).toBe('This is the title'); expect(diagram.db.getAccDescription()).toBe('Accessibility\nDescription'); const messages = diagram.db.getMessages(); }); - it('should space in actor names', function () { + it('should space in actor names', async () => { const str = ` sequenceDiagram Alice->Bob:Hello Bob, how are - you? Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -240,13 +312,13 @@ Bob-->Alice: I am good thanks!`; expect(messages[0].from).toBe('Alice'); expect(messages[1].from).toBe('Bob'); }); - it('should handle dashes in actor names', function () { + it('should handle dashes in actor names', async () => { const str = ` sequenceDiagram Alice-in-Wonderland->Bob:Hello Bob, how are - you? Bob-->Alice-in-Wonderland:I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors['Alice-in-Wonderland'].description).toBe('Alice-in-Wonderland'); expect(actors.Bob.description).toBe('Bob'); @@ -258,7 +330,7 @@ Bob-->Alice-in-Wonderland:I am good thanks!`; expect(messages[1].from).toBe('Bob'); }); - it('should handle dashes in participant names', function () { + it('should handle dashes in participant names', async () => { const str = ` sequenceDiagram participant Alice-in-Wonderland @@ -266,7 +338,7 @@ participant Bob Alice-in-Wonderland->Bob:Hello Bob, how are - you? Bob-->Alice-in-Wonderland:I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(Object.keys(actors)).toEqual(['Alice-in-Wonderland', 'Bob']); expect(actors['Alice-in-Wonderland'].description).toBe('Alice-in-Wonderland'); @@ -279,7 +351,7 @@ Bob-->Alice-in-Wonderland:I am good thanks!`; expect(messages[1].from).toBe('Bob'); }); - it('should alias participants', function () { + it('should alias participants', async () => { const str = ` sequenceDiagram participant A as Alice @@ -287,7 +359,7 @@ participant B as Bob A->B:Hello Bob, how are you? B-->A: I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); @@ -300,7 +372,7 @@ B-->A: I am good thanks!`; expect(messages[0].from).toBe('A'); expect(messages[1].from).toBe('B'); }); - it('should alias a mix of actors and participants apa12', function () { + it('should alias a mix of actors and participants apa12', async () => { const str = ` sequenceDiagram actor Alice as Alice2 @@ -313,7 +385,7 @@ sequenceDiagram John->>Mandy: Hi Mandy Mandy ->>Joan: Hi Joan`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(Object.keys(actors)).toEqual(['Alice', 'Bob', 'John', 'Mandy', 'Joan']); @@ -328,7 +400,7 @@ sequenceDiagram expect(messages[0].from).toBe('Alice'); expect(messages[4].to).toBe('Joan'); }); - it('should alias actors apa13', function () { + it('should alias actors apa13', async () => { const str = ` sequenceDiagram actor A as Alice @@ -336,7 +408,7 @@ actor B as Bob A->B:Hello Bob, how are you? B-->A: I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(Object.keys(actors)).toEqual(['A', 'B']); @@ -348,12 +420,12 @@ B-->A: I am good thanks!`; expect(messages[0].from).toBe('A'); expect(messages[1].from).toBe('B'); }); - it('should handle in async messages', function () { + it('should handle in async messages', async () => { const str = ` sequenceDiagram Alice-xBob:Hello Bob, how are you?`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -363,12 +435,12 @@ Alice-xBob:Hello Bob, how are you?`; expect(messages.length).toBe(1); expect(messages[0].type).toBe(diagram.db.LINETYPE.SOLID_CROSS); }); - it('should handle in async dotted messages', function () { + it('should handle in async dotted messages', async () => { const str = ` sequenceDiagram Alice--xBob:Hello Bob, how are you?`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -378,12 +450,12 @@ Alice--xBob:Hello Bob, how are you?`; expect(messages.length).toBe(1); expect(messages[0].type).toBe(diagram.db.LINETYPE.DOTTED_CROSS); }); - it('should handle in sync messages', function () { + it('should handle in sync messages', async () => { const str = ` sequenceDiagram Alice-)Bob:Hello Bob, how are you?`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -393,12 +465,12 @@ Alice-)Bob:Hello Bob, how are you?`; expect(messages.length).toBe(1); expect(messages[0].type).toBe(diagram.db.LINETYPE.SOLID_POINT); }); - it('should handle in sync dotted messages', function () { + it('should handle in sync dotted messages', async () => { const str = ` sequenceDiagram Alice--)Bob:Hello Bob, how are you?`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -408,12 +480,12 @@ Alice--)Bob:Hello Bob, how are you?`; expect(messages.length).toBe(1); expect(messages[0].type).toBe(diagram.db.LINETYPE.DOTTED_POINT); }); - it('should handle in arrow messages', function () { + it('should handle in arrow messages', async () => { const str = ` sequenceDiagram Alice->>Bob:Hello Bob, how are you?`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -423,10 +495,10 @@ Alice->>Bob:Hello Bob, how are you?`; expect(messages.length).toBe(1); expect(messages[0].type).toBe(diagram.db.LINETYPE.SOLID); }); - it('should handle in arrow messages', function () { + it('should handle in arrow messages', async () => { const str = 'sequenceDiagram\n' + 'Alice-->>Bob:Hello Bob, how are you?'; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -436,7 +508,7 @@ Alice->>Bob:Hello Bob, how are you?`; expect(messages.length).toBe(1); expect(messages[0].type).toBe(diagram.db.LINETYPE.DOTTED); }); - it('should handle actor activation', function () { + it('should handle actor activation', async () => { const str = ` sequenceDiagram Alice-->>Bob:Hello Bob, how are you? @@ -444,7 +516,7 @@ activate Bob Bob-->>Alice:Hello Alice, I'm fine and you? deactivate Bob`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -459,13 +531,13 @@ deactivate Bob`; expect(messages[3].type).toBe(diagram.db.LINETYPE.ACTIVE_END); expect(messages[3].from.actor).toBe('Bob'); }); - it('should handle actor one line notation activation', function () { + it('should handle actor one line notation activation', async () => { const str = ` sequenceDiagram Alice-->>+Bob:Hello Bob, how are you? Bob-->>- Alice:Hello Alice, I'm fine and you?`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -480,7 +552,7 @@ deactivate Bob`; expect(messages[3].type).toBe(diagram.db.LINETYPE.ACTIVE_END); expect(messages[3].from.actor).toBe('Bob'); }); - it('should handle stacked activations', function () { + it('should handle stacked activations', async () => { const str = ` sequenceDiagram Alice-->>+Bob:Hello Bob, how are you? @@ -488,7 +560,7 @@ deactivate Bob`; Bob-->>- Alice:Hello Alice, please meet Carol? Carol->>- Bob:Oh Bob, I'm so happy to be here!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); expect(actors.Bob.description).toBe('Bob'); @@ -507,7 +579,7 @@ deactivate Bob`; expect(messages[7].type).toBe(diagram.db.LINETYPE.ACTIVE_END); expect(messages[7].from.actor).toBe('Carol'); }); - it('should handle fail parsing when activating an inactive participant', function () { + it('should handle fail parsing when activating an inactive participant', async () => { const str = ` sequenceDiagram participant user as End User @@ -526,14 +598,14 @@ deactivate Bob`; let error = false; try { - mermaidAPI.parse(str); + await mermaidAPI.parse(str); } catch (e) { error = true; } expect(error).toBe(true); }); - it('should handle comments in a sequenceDiagram', function () { + it('should handle comments in a sequenceDiagram', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -541,7 +613,7 @@ deactivate Bob`; Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -552,7 +624,7 @@ deactivate Bob`; expect(messages[0].from).toBe('Alice'); expect(messages[2].from).toBe('Bob'); }); - it('should handle new lines in a sequenceDiagram', function () { + it('should handle new lines in a sequenceDiagram', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -562,7 +634,7 @@ deactivate Bob`; Bob-->Alice: I am good thanks! `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -573,11 +645,11 @@ deactivate Bob`; expect(messages[0].from).toBe('Alice'); expect(messages[2].from).toBe('Bob'); }); - it('should handle semicolons', function () { + it('should handle semicolons', async () => { const str = ` sequenceDiagram;Alice->Bob: Hello Bob, how are you?;Note right of Bob: Bob thinks;Bob-->Alice: I am good thanks!;`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -588,7 +660,7 @@ sequenceDiagram;Alice->Bob: Hello Bob, how are you?;Note right of Bob: Bob think expect(messages[0].from).toBe('Alice'); expect(messages[2].from).toBe('Bob'); }); - it('should handle one leading space in lines in a sequenceDiagram', function () { + it('should handle one leading space in lines in a sequenceDiagram', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -597,7 +669,7 @@ sequenceDiagram Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -608,7 +680,7 @@ Bob-->Alice: I am good thanks!`; expect(messages[0].from).toBe('Alice'); expect(messages[2].from).toBe('Bob'); }); - it('should handle several leading spaces in lines in a sequenceDiagram', function () { + it('should handle several leading spaces in lines in a sequenceDiagram', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -617,7 +689,7 @@ sequenceDiagram Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -628,7 +700,7 @@ Bob-->Alice: I am good thanks!`; expect(messages[0].from).toBe('Alice'); expect(messages[2].from).toBe('Bob'); }); - it('should handle several leading spaces in lines in a sequenceDiagram', function () { + it('should handle several leading spaces in lines in a sequenceDiagram', async () => { const str = ` sequenceDiagram participant Alice @@ -642,7 +714,7 @@ Note right of John: Rational thoughts
prevail... John->Bob: How about you? Bob-->John: Jolly good!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -653,7 +725,7 @@ Bob-->John: Jolly good!`; expect(messages[0].from).toBe('Alice'); expect(messages[2].from).toBe('John'); }); - it('should handle different line breaks', function () { + it('should handle different line breaks', async () => { const str = ` sequenceDiagram participant 1 as multiline
text @@ -670,7 +742,7 @@ note right of 4: multiline
text note right of 1: multiline
text `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors['1'].description).toBe('multiline
text'); @@ -688,7 +760,7 @@ note right of 1: multiline
text expect(messages[6].message).toBe('multiline
text'); expect(messages[7].message).toBe('multiline
text'); }); - it('should handle notes and messages without wrap specified', function () { + it('should handle notes and messages without wrap specified', async () => { const str = ` sequenceDiagram participant 1 @@ -705,7 +777,7 @@ note right of 4: multiline
text note right of 1:nowrap: multiline
text `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[0].message).toBe('single-line text'); @@ -727,7 +799,7 @@ note right of 1:nowrap: multiline
text expect(messages[6].wrap).toBe(false); expect(messages[7].wrap).toBe(false); }); - it('should handle notes and messages with wrap specified', function () { + it('should handle notes and messages with wrap specified', async () => { const str = ` sequenceDiagram participant 1 @@ -740,7 +812,7 @@ note right of 2:wrap: single-line text note right of 3:wrap: multiline
text `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[0].message).toBe('single-line text'); @@ -752,7 +824,7 @@ note right of 3:wrap: multiline
text expect(messages[2].wrap).toBe(true); expect(messages[3].wrap).toBe(true); }); - it('should handle notes and messages with nowrap or line breaks', function () { + it('should handle notes and messages with nowrap or line breaks', async () => { const str = ` sequenceDiagram participant 1 @@ -761,7 +833,7 @@ participant 2 note right of 2: single-line text `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[0].message).toBe('single-line text'); @@ -769,20 +841,20 @@ note right of 2: single-line text expect(messages[0].wrap).toBe(false); expect(messages[1].wrap).toBe(false); }); - it('should handle notes over a single actor', function () { + it('should handle notes over a single actor', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? Note over Bob: Bob thinks `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].from).toBe('Bob'); expect(messages[1].to).toBe('Bob'); }); - it('should handle notes over multiple actors', function () { + it('should handle notes over multiple actors', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -790,7 +862,7 @@ Note over Alice,Bob: confusion Note over Bob,Alice: resolution `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].from).toBe('Alice'); @@ -798,7 +870,7 @@ Note over Bob,Alice: resolution expect(messages[2].from).toBe('Bob'); expect(messages[2].to).toBe('Alice'); }); - it('should handle loop statements', function () { + it('should handle loop statements', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -810,7 +882,7 @@ loop Multiple happy responses Bob-->Alice: I am good thanks! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -821,7 +893,7 @@ end`; expect(messages[0].from).toBe('Alice'); expect(messages[1].from).toBe('Bob'); }); - it('should add a rect around sequence', function () { + it('should add a rect around sequence', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -832,7 +904,7 @@ end`; end `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -845,7 +917,7 @@ end`; expect(messages[4].type).toEqual(diagram.db.LINETYPE.RECT_END); }); - it('should allow for nested rects', function () { + it('should allow for nested rects', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -857,7 +929,7 @@ end`; Bob-->Alice: I am good thanks end `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -872,7 +944,7 @@ end`; expect(messages[5].type).toEqual(diagram.db.LINETYPE.DOTTED_OPEN); expect(messages[6].type).toEqual(diagram.db.LINETYPE.RECT_END); }); - it('should handle opt statements', function () { + it('should handle opt statements', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -884,7 +956,7 @@ opt Perhaps a happy response Bob-->Alice: I am good thanks! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); actors.Bob.description = 'Bob'; @@ -895,7 +967,7 @@ end`; expect(messages[0].from).toBe('Alice'); expect(messages[1].from).toBe('Bob'); }); - it('should handle alt statements', function () { + it('should handle alt statements', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -909,7 +981,7 @@ else isSick Bob-->Alice: Feel sick... end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); @@ -921,7 +993,7 @@ end`; expect(messages[0].from).toBe('Alice'); expect(messages[1].from).toBe('Bob'); }); - it('should handle alt statements with multiple elses', function () { + it('should handle alt statements with multiple elses', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -936,7 +1008,7 @@ Bob-->Alice: Feel sick... else default Bob-->Alice: :-) end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages.length).toBe(9); expect(messages[1].from).toBe('Bob'); @@ -948,14 +1020,14 @@ end`; expect(messages[7].from).toBe('Bob'); expect(messages[8].type).toBe(diagram.db.LINETYPE.ALT_END); }); - it('should handle critical statements without options', function () { + it('should handle critical statements without options', async () => { const str = ` sequenceDiagram critical Establish a connection to the DB Service-->DB: connect end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Service.description).toBe('Service'); @@ -968,7 +1040,7 @@ sequenceDiagram expect(messages[1].from).toBe('Service'); expect(messages[2].type).toBe(diagram.db.LINETYPE.CRITICAL_END); }); - it('should handle critical statements with options', function () { + it('should handle critical statements with options', async () => { const str = ` sequenceDiagram critical Establish a connection to the DB @@ -979,7 +1051,7 @@ sequenceDiagram Service-->Service: Log different error end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Service.description).toBe('Service'); @@ -996,7 +1068,7 @@ sequenceDiagram expect(messages[5].from).toBe('Service'); expect(messages[6].type).toBe(diagram.db.LINETYPE.CRITICAL_END); }); - it('should handle break statements', function () { + it('should handle break statements', async () => { const str = ` sequenceDiagram Consumer-->API: Book something @@ -1006,7 +1078,7 @@ sequenceDiagram end API-->BillingService: Start billing process`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Consumer.description).toBe('Consumer'); @@ -1022,7 +1094,7 @@ sequenceDiagram expect(messages[4].type).toBe(diagram.db.LINETYPE.BREAK_END); expect(messages[5].from).toBe('API'); }); - it('should handle par statements a sequenceDiagram', function () { + it('should handle par statements a sequenceDiagram', async () => { const str = ` sequenceDiagram par Parallel one @@ -1036,7 +1108,7 @@ Alice->>Bob: What do you think about it? Bob-->>Alice: It's good! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.Alice.description).toBe('Alice'); @@ -1049,26 +1121,26 @@ end`; expect(messages[1].from).toBe('Alice'); expect(messages[2].from).toBe('Bob'); }); - it('should handle special characters in signals', function () { + it('should handle special characters in signals', async () => { const str = 'sequenceDiagram\n' + 'Alice->Bob: -:<>,;# comment'; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[0].message).toBe('-:<>,'); }); - it('should handle special characters in notes', function () { + it('should handle special characters in notes', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? Note right of Bob: -:<>,;# comment`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].message).toBe('-:<>,'); }); - it('should handle special characters in loop', function () { + it('should handle special characters in loop', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -1076,12 +1148,12 @@ loop -:<>,;# comment Bob-->Alice: I am good thanks! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].message).toBe('-:<>,'); }); - it('should handle special characters in opt', function () { + it('should handle special characters in opt', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -1089,12 +1161,12 @@ opt -:<>,;# comment Bob-->Alice: I am good thanks! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].message).toBe('-:<>,'); }); - it('should handle special characters in alt', function () { + it('should handle special characters in alt', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -1104,13 +1176,13 @@ else ,<>:-#; comment Bob-->Alice: I am good thanks! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].message).toBe('-:<>,'); expect(messages[3].message).toBe(',<>:-'); }); - it('should handle special characters in par', function () { + it('should handle special characters in par', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -1120,13 +1192,13 @@ and ,<>:-#; comment Bob-->Alice: I am good thanks! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].message).toBe('-:<>,'); expect(messages[3].message).toBe(',<>:-'); }); - it('should handle no-label loop', function () { + it('should handle no-label loop', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -1134,13 +1206,13 @@ loop Bob-->Alice: I am good thanks! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].message).toBe(''); expect(messages[2].message).toBe('I am good thanks!'); }); - it('should handle no-label opt', function () { + it('should handle no-label opt', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -1148,13 +1220,13 @@ opt # comment Bob-->Alice: I am good thanks! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].message).toBe(''); expect(messages[2].message).toBe('I am good thanks!'); }); - it('should handle no-label alt', function () { + it('should handle no-label alt', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -1163,7 +1235,7 @@ else # comment Bob-->Alice: I am good thanks! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].message).toBe(''); @@ -1171,7 +1243,7 @@ end`; expect(messages[3].message).toBe(''); expect(messages[4].message).toBe('I am good thanks!'); }); - it('should handle no-label par', function () { + it('should handle no-label par', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -1180,7 +1252,7 @@ and # comment Bob-->Alice: I am good thanks! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const messages = diagram.db.getMessages(); expect(messages[1].message).toBe(''); @@ -1189,7 +1261,7 @@ end`; expect(messages[4].message).toBe('I am good thanks!'); }); - it('should handle links', function () { + it('should handle links', async () => { const str = ` sequenceDiagram participant a as Alice @@ -1203,7 +1275,7 @@ link a: Swagger @ https://swagger.contoso.com link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.a.links['Repo']).toBe('https://repo.contoso.com/'); expect(actors.b.links['Repo']).toBe(undefined); @@ -1216,7 +1288,7 @@ link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com expect(actors.a.links['Tests']).toBe('https://tests.contoso.com/?svc=alice@contoso.com'); }); - it('should handle properties EXPERIMENTAL: USE WITH CAUTION', function () { + it('should handle properties EXPERIMENTAL: USE WITH CAUTION', async () => { //Be aware that the syntax for "properties" is likely to be changed. const str = ` sequenceDiagram @@ -1227,7 +1299,7 @@ properties a: {"class": "internal-service-actor", "icon": "@clock"} properties b: {"class": "external-service-actor", "icon": "@computer"} `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(actors.a.properties['class']).toBe('internal-service-actor'); expect(actors.b.properties['class']).toBe('external-service-actor'); @@ -1235,8 +1307,76 @@ properties b: {"class": "external-service-actor", "icon": "@computer"} expect(actors.b.properties['icon']).toBe('@computer'); expect(actors.c.properties['class']).toBe(undefined); }); -}); + it('should handle box', async () => { + 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 +`; + + await 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', async () => { + 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 + `; + + await 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', async () => { + 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 + `; + + await 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 () { beforeAll(() => { let conf = { @@ -1263,7 +1403,7 @@ describe('when checking the bounds in a sequenceDiagram', function () { diagram.renderer.bounds.init(); conf = diagram.db.getConfig(); }); - it('should handle a simple bound call', function () { + it('should handle a simple bound call', async () => { diagram.renderer.bounds.insert(100, 100, 200, 200); const { bounds } = diagram.renderer.bounds.getBounds(); @@ -1272,7 +1412,7 @@ describe('when checking the bounds in a sequenceDiagram', function () { expect(bounds.stopx).toBe(200); expect(bounds.stopy).toBe(200); }); - it('should handle an expanding bound', function () { + it('should handle an expanding bound', async () => { diagram.renderer.bounds.insert(100, 100, 200, 200); diagram.renderer.bounds.insert(25, 50, 300, 400); @@ -1282,7 +1422,7 @@ describe('when checking the bounds in a sequenceDiagram', function () { expect(bounds.stopx).toBe(300); expect(bounds.stopy).toBe(400); }); - it('should handle inserts within the bound without changing the outer bounds', function () { + it('should handle inserts within the bound without changing the outer bounds', async () => { diagram.renderer.bounds.insert(100, 100, 200, 200); diagram.renderer.bounds.insert(25, 50, 300, 400); diagram.renderer.bounds.insert(125, 150, 150, 200); @@ -1293,7 +1433,7 @@ describe('when checking the bounds in a sequenceDiagram', function () { expect(bounds.stopx).toBe(300); expect(bounds.stopy).toBe(400); }); - it('should handle a loop without expanding the area', function () { + it('should handle a loop without expanding the area', async () => { diagram.renderer.bounds.insert(25, 50, 300, 400); diagram.renderer.bounds.verticalPos = 150; diagram.renderer.bounds.newLoop(); @@ -1314,7 +1454,7 @@ describe('when checking the bounds in a sequenceDiagram', function () { expect(bounds.stopx).toBe(300); expect(bounds.stopy).toBe(400); }); - it('should handle multiple loops withtout expanding the bounds', function () { + it('should handle multiple loops withtout expanding the bounds', async () => { diagram.renderer.bounds.insert(100, 100, 1000, 1000); diagram.renderer.bounds.verticalPos = 200; diagram.renderer.bounds.newLoop(); @@ -1345,7 +1485,7 @@ describe('when checking the bounds in a sequenceDiagram', function () { expect(bounds.stopx).toBe(1000); expect(bounds.stopy).toBe(1000); }); - it('should handle a loop that expands the area', function () { + it('should handle a loop that expands the area', async () => { diagram.renderer.bounds.insert(100, 100, 200, 200); diagram.renderer.bounds.verticalPos = 200; diagram.renderer.bounds.newLoop(); @@ -1415,13 +1555,13 @@ Bob-->Alice: I am good thanks!`); }); ['tspan', 'fo', 'old', undefined].forEach(function (textPlacement) { it(` -it should handle one actor, when textPlacement is ${textPlacement}`, function () { +it should handle one actor, when textPlacement is ${textPlacement}`, async () => { const str = ` sequenceDiagram participant Alice`; // mermaidAPI.reinitialize({ sequence: { textPlacement: textPlacement } }); - mermaidAPI.parse(str); + await mermaidAPI.parse(str); // diagram.renderer.setConf(mermaidAPI.getConfig().sequence); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); @@ -1432,7 +1572,7 @@ participant Alice`; expect(bounds.stopy).toBe(conf.height); }); }); - it('should handle same actor with different whitespace properly', function () { + it('should handle same actor with different whitespace properly', async () => { const str = ` sequenceDiagram participant Alice @@ -1440,12 +1580,12 @@ participant Alice participant Alice `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); const actors = diagram.db.getActors(); expect(Object.keys(actors)).toEqual(['Alice']); }); - it('should handle one actor and a centered note', function () { + it('should handle one actor and a centered note', async () => { const str = ` sequenceDiagram participant Alice @@ -1453,7 +1593,7 @@ Note over Alice: Alice thinks `; expect(mermaidAPI.getConfig().sequence.mirrorActors).toBeFalsy(); - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1463,13 +1603,13 @@ Note over Alice: Alice thinks // 10 comes from mock of text height expect(bounds.stopy).toBe(models.lastNote().stopy); }); - it('should handle one actor and a note to the left', function () { + it('should handle one actor and a note to the left', async () => { const str = ` sequenceDiagram participant Alice Note left of Alice: Alice thinks`; - mermaidAPI.parse(str, diagram); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1479,13 +1619,13 @@ Note left of Alice: Alice thinks`; // 10 comes from mock of text height expect(bounds.stopy).toBe(models.lastNote().stopy); }); - it('should handle one actor and a note to the right', function () { + it('should handle one actor and a note to the right', async () => { const str = ` sequenceDiagram participant Alice Note right of Alice: Alice thinks`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1495,12 +1635,12 @@ Note right of Alice: Alice thinks`; // 10 comes from mock of text height expect(bounds.stopy).toBe(models.lastNote().stopy); }); - it('should handle two actors', function () { + it('should handle two actors', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you?`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1509,13 +1649,31 @@ Alice->Bob: Hello Bob, how are you?`; expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); }); - it('should handle two actors with init directive', function () { + it('should handle two actors in a box', async () => { + const str = ` +sequenceDiagram +box rgb(34, 56, 0) Group1 +participant Alice +participant Bob +end +Alice->Bob: Hello Bob, how are you?`; + + await 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', async () => { const str = ` %%{init: {'logLevel': 0}}%% sequenceDiagram Alice->Bob: Hello Bob, how are you?`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1526,7 +1684,7 @@ Alice->Bob: Hello Bob, how are you?`; expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); }); - it('should handle two actors with init directive with multiline directive', function () { + it('should handle two actors with init directive with multiline directive', async () => { const str = ` %%{init: { 'logLevel': 0}}%% sequenceDiagram @@ -1535,7 +1693,7 @@ wrap }%% Alice->Bob: Hello Bob, how are you?`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const msgs = diagram.db.getMessages(); @@ -1548,7 +1706,7 @@ Alice->Bob: Hello Bob, how are you?`; expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); expect(msgs.every((v) => v.wrap)).toBe(true); }); - it('should handle two actors and two centered shared notes', function () { + it('should handle two actors and two centered shared notes', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? @@ -1556,7 +1714,7 @@ Note over Alice,Bob: Looks Note over Bob,Alice: Looks back `; // mermaidAPI.initialize({logLevel:0}) - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1565,13 +1723,13 @@ Note over Bob,Alice: Looks back expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastNote().stopy); }); - it('should draw two actors and two messages', function () { + it('should draw two actors and two messages', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? Bob->Alice: Fine!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1580,14 +1738,14 @@ Bob->Alice: Fine!`; expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); }); - it('should draw two actors notes to the right', function () { + it('should draw two actors notes to the right', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? Note right of Bob: Bob thinks Bob->Alice: Fine!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1599,14 +1757,14 @@ Bob->Alice: Fine!`; expect(bounds.stopx).toBe(expStopX); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); }); - it('should draw two actors notes to the left', function () { + it('should draw two actors notes to the left', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? Note left of Alice: Bob thinks Bob->Alice: Fine!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1616,14 +1774,14 @@ Bob->Alice: Fine!`; expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); }); - it('should draw two actors notes to the left with text wrapped (inline)', function () { + it('should draw two actors notes to the left with text wrapped (inline)', async () => { const str = ` sequenceDiagram Alice->>Bob:wrap: Hello Bob, how are you? If you are not available right now, I can leave you a message. Please get back to me as soon as you can! Note left of Alice: Bob thinks Bob->>Alice: Fine!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1635,7 +1793,7 @@ Bob->>Alice: Fine!`; expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); }); - it('should draw two actors notes to the left with text wrapped (directive)', function () { + it('should draw two actors notes to the left with text wrapped (directive)', async () => { const str = ` %%{init: { 'theme': 'dark' } }%% sequenceDiagram @@ -1644,7 +1802,7 @@ Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can l Note left of Alice: Bob thinks Bob->>Alice: Fine!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1658,7 +1816,7 @@ Bob->>Alice: Fine!`; expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); }); - it('should draw two actors notes to the left with text wrapped and the init directive sets the theme to dark', function () { + it('should draw two actors notes to the left with text wrapped and the init directive sets the theme to dark', async () => { const str = ` %%{init:{'theme':'dark'}}%% sequenceDiagram @@ -1667,7 +1825,7 @@ Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can l Note left of Alice: Bob thinks Bob->>Alice: Fine!`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1681,7 +1839,7 @@ Bob->>Alice: Fine!`; expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); }); - it('should draw two actors, notes to the left with text wrapped and the init directive sets the theme to dark and fontFamily to Menlo, fontSize to 18, and fontWeight to 800', function () { + it('should draw two actors, notes to the left with text wrapped and the init directive sets the theme to dark and fontFamily to Menlo, fontSize to 18, and fontWeight to 800', async () => { const str = ` %%{init: { "theme": "dark", 'config': { "fontFamily": "Menlo", "fontSize": 18, "messageFontWeight": 400, "wrap": true }}}%% sequenceDiagram @@ -1689,7 +1847,7 @@ Alice->>Bob: Hello Bob, how are you? If you are not available right now, I can l Note left of Alice: Bob thinks Bob->>Alice: Fine!`; // mermaidAPI.initialize({ logLevel: 0 }); - mermaidAPI.parse(str, diagram); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1706,14 +1864,14 @@ Bob->>Alice: Fine!`; expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); }); - it('should draw two loops', function () { + it('should draw two loops', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, how are you? loop Cheers Bob->Alice: Fine! end`; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1723,7 +1881,7 @@ end`; expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastLoop().stopy); }); - it('should draw background rect', function () { + it('should draw background rect', async () => { const str = ` sequenceDiagram Alice->Bob: Hello Bob, are you alright? @@ -1731,7 +1889,7 @@ end`; Bob->Alice: I feel surrounded by darkness end `; - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); expect(bounds.startx).toBe(0); @@ -1742,7 +1900,7 @@ end`; }); }); -describe('when rendering a sequenceDiagram with actor mirror activated', function () { +describe('when rendering a sequenceDiagram with actor mirror activated', () => { beforeAll(() => { let conf = { diagramMarginX: 50, @@ -1773,14 +1931,14 @@ describe('when rendering a sequenceDiagram with actor mirror activated', functio diagram.renderer.bounds.init(); }); ['tspan', 'fo', 'old', undefined].forEach(function (textPlacement) { - it('should handle one actor, when textPlacement is' + textPlacement, function () { + it('should handle one actor, when textPlacement is' + textPlacement, async () => { mermaidAPI.initialize(addConf(conf, 'textPlacement', textPlacement)); diagram.renderer.bounds.init(); const str = ` sequenceDiagram participant Alice`; diagram.renderer.bounds.init(); - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); const { bounds, models } = diagram.renderer.bounds.getBounds(); @@ -1792,7 +1950,7 @@ participant Alice`; }); }); -describe('when rendering a sequenceDiagram with directives', function () { +describe('when rendering a sequenceDiagram with directives', () => { beforeAll(function () { let conf = { diagramMarginX: 50, @@ -1817,7 +1975,7 @@ describe('when rendering a sequenceDiagram with directives', function () { diagram.renderer.bounds.init(); }); - it('should handle one actor, when theme is dark and logLevel is 1 DX1 (dfg1)', function () { + it('should handle one actor, when theme is dark and logLevel is 1 DX1 (dfg1)', async () => { const str = ` %%{init: { "theme": "dark", "logLevel": 1 } }%% sequenceDiagram @@ -1827,7 +1985,7 @@ participant Alice diagram = new Diagram(str); diagram.renderer.bounds.init(); - mermaidAPI.parse(str); + await mermaidAPI.parse(str); diagram.renderer.draw(str, 'tst', '1.2.3', diagram); @@ -1842,7 +2000,7 @@ participant Alice models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin ); }); - it('should handle one actor, when logLevel is 3 (dfg0)', function () { + it('should handle one actor, when logLevel is 3 (dfg0)', async () => { const str = ` %%{initialize: { "logLevel": 3 }}%% sequenceDiagram @@ -1862,7 +2020,7 @@ participant Alice models.lastActor().y + models.lastActor().height + mermaid.sequence.boxMargin ); }); - it('should hide sequence numbers when autonumber is removed when autonumber is enabled', function () { + it('should hide sequence numbers when autonumber is removed when autonumber is enabled', async () => { const str1 = ` sequenceDiagram autonumber @@ -1870,7 +2028,7 @@ Alice->Bob:Hello Bob, how are you? Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str1, diagram); + await mermaidAPI.parse(str1); diagram.renderer.draw(str1, 'tst', '1.2.3', diagram); // needs to be rendered for the correct value of visibility auto numbers expect(diagram.db.showSequenceNumbers()).toBe(true); @@ -1880,7 +2038,7 @@ Alice->Bob:Hello Bob, how are you? Note right of Bob: Bob thinks Bob-->Alice: I am good thanks!`; - mermaidAPI.parse(str2, diagram); + await mermaidAPI.parse(str2); diagram.renderer.draw(str2, 'tst', '1.2.3', diagram); expect(diagram.db.showSequenceNumbers()).toBe(false); }); diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 738b86540..acee7bbe5 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -2,15 +2,12 @@ import { select, selectAll } from 'd3'; import svgDraw, { drawText, fixLifeLineHeights } from './svgDraw'; import { log } from '../../logger'; -// import { parser } from './parser/sequenceDiagram'; import common from '../common/common'; -// import sequenceDb from './sequenceDb'; import * as configApi from '../../config'; import assignWithDepth from '../../assignWithDepth'; import utils from '../../utils'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; -import Diagram from '../../Diagram'; +import { Diagram } from '../../Diagram'; let conf = {}; @@ -44,10 +41,14 @@ export const bounds = { }, clear: function () { this.actors = []; + this.boxes = []; this.loops = []; this.messages = []; this.notes = []; }, + addBox: function (boxModel) { + this.boxes.push(boxModel); + }, addActor: function (actorModel) { this.actors.push(actorModel); }, @@ -73,6 +74,7 @@ export const bounds = { return this.notes[this.notes.length - 1]; }, actors: [], + boxes: [], loops: [], messages: [], notes: [], @@ -466,7 +468,8 @@ export const drawActors = function ( actorKeys, verticalPos, configuration, - messages + messages, + isFooter ) { if (configuration.hideUnusedParticipants === true) { const newActors = new Set(); @@ -481,8 +484,28 @@ export const drawActors = function ( let prevWidth = 0; let prevMargin = 0; let maxHeight = 0; + let prevBox = undefined; + for (const actorKey of actorKeys) { 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 actor.width = actor.width || conf.width; @@ -490,18 +513,27 @@ export const drawActors = function ( actor.margin = actor.margin || conf.actorMargin; actor.x = prevWidth + prevMargin; - actor.y = verticalPos; + actor.y = bounds.getVerticalPos(); // 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); bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height); - prevWidth += actor.width; - prevMargin += actor.margin; + prevWidth += actor.width + prevMargin; + if (actor.box) { + actor.box.width = prevWidth + box.margin - actor.box.x; + } + prevMargin = actor.margin; + prevBox = actor.box; 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 bounds.bumpVerticalPos(maxHeight); }; @@ -596,6 +628,9 @@ function adjustLoopHeightForWrap(loopWidths, msg, preMargin, postMargin, addLoop export const draw = function (_text: string, id: string, _version: string, diagObj: Diagram) { const { securityLevel, sequence } = configApi.getConfig(); conf = sequence; + diagObj.db.clear(); + // Parse the graph definition + diagObj.parser.parse(_text); // Handle root and Document for when rendering in sandbox mode let sandboxElement; if (securityLevel === 'sandbox') { @@ -615,18 +650,27 @@ export const draw = function (_text: string, id: string, _version: string, diagO // Fetch data from the parsing const actors = diagObj.db.getActors(); + const boxes = diagObj.db.getBoxes(); const actorKeys = diagObj.db.getActorKeys(); const messages = diagObj.db.getMessages(); const title = diagObj.db.getDiagramTitle(); - + const hasBoxes = diagObj.db.hasAtLeastOneBox(); + const hasBoxTitles = diagObj.db.hasAtLeastOneBoxWithTitle(); const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages, diagObj); - conf.height = calculateActorMargins(actors, maxMessageWidthPerActor); + conf.height = calculateActorMargins(actors, maxMessageWidthPerActor, boxes); svgDraw.insertComputerIcon(diagram); svgDraw.insertDatabaseIcon(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); // The arrow head definition is attached to the svg once @@ -848,11 +892,26 @@ export const draw = function (_text: string, id: string, _version: string, diagO if (conf.mirrorActors) { // Draw actors below diagram 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); 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. const requiredBoxSize = drawActorsPopup(diagram, actors, actorKeys, doc); @@ -904,7 +963,6 @@ export const draw = function (_text: string, id: string, _version: string, diagO (height + extraVertForTitle) ); - addSVGAccessibilityFields(diagObj.db, diagram, id); log.debug(`models:`, bounds.models); }; @@ -1041,10 +1099,12 @@ const getRequiredPopupWidth = function (actor) { * * @param actors - The actors map to calculate margins for * @param actorToMessageWidth - A map of actor key → max message width it holds + * @param boxes - The boxes around the actors if any */ function calculateActorMargins( actors: { [id: string]: any }, - actorToMessageWidth: ReturnType + actorToMessageWidth: ReturnType, + boxes ) { let maxHeight = 0; Object.keys(actors).forEach((prop) => { @@ -1076,6 +1136,9 @@ function calculateActorMargins( // No need to space out an actor that doesn't have a next link if (!nextActor) { + const messageWidth = actorToMessageWidth[actorKey]; + const actorWidth = messageWidth + conf.actorMargin - actor.width / 2; + actor.margin = Math.max(actorWidth, conf.actorMargin); continue; } @@ -1085,6 +1148,29 @@ function calculateActorMargins( 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); } diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index ce1caea69..be34daf4b 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -1,5 +1,6 @@ import common from '../common/common'; import { addFunction } from '../../interactionDb'; +import { parseFontSize } from '../../utils'; import { sanitizeUrl } from '@braintree/sanitize-url'; export const drawRect = function (elem, rectData) { @@ -156,6 +157,8 @@ export const drawText = function (elem, textData) { textHeight = 0; const lines = textData.text.split(common.lineBreakRegex); + const [_textFontSize, _textFontSizePx] = parseFontSize(textData.fontSize); + let textElems = []; let dy = 0; let yfunc = () => textData.y; @@ -218,9 +221,9 @@ export const drawText = function (elem, textData) { if ( textData.textMargin !== undefined && textData.textMargin === 0 && - textData.fontSize !== undefined + _textFontSize !== undefined ) { - dy = i * textData.fontSize; + dy = i * _textFontSize; } const textElem = elem.append('text'); @@ -235,8 +238,8 @@ export const drawText = function (elem, textData) { if (textData.fontFamily !== undefined) { textElem.style('font-family', textData.fontFamily); } - if (textData.fontSize !== undefined) { - textElem.style('font-size', textData.fontSize); + if (_textFontSizePx !== undefined) { + textElem.style('font-size', _textFontSizePx); } if (textData.fontWeight !== undefined) { textElem.style('font-weight', textData.fontWeight); @@ -338,19 +341,21 @@ export const fixLifeLineHeights = (diagram, bounds) => { * @param {any} elem - The diagram we'll draw to. * @param {any} actor - The actor to draw. * @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 centerY = actor.y + 5; const boxpluslineGroup = elem.append('g'); var g = boxpluslineGroup; - if (actor.y === 0) { + if (!isFooter) { actorCnt++; g.append('line') .attr('id', 'actor' + actorCnt) .attr('x1', center) - .attr('y1', 5) + .attr('y1', centerY) .attr('x2', center) .attr('y2', 2000) .attr('class', 'actor-line') @@ -413,16 +418,17 @@ const drawActorTypeParticipant = function (elem, actor, conf) { return height; }; -const drawActorTypeActor = function (elem, actor, conf) { +const drawActorTypeActor = function (elem, actor, conf, isFooter) { const center = actor.x + actor.width / 2; + const centerY = actor.y + 80; - if (actor.y === 0) { + if (!isFooter) { actorCnt++; elem .append('line') .attr('id', 'actor' + actorCnt) .attr('x1', center) - .attr('y1', 80) + .attr('y1', centerY) .attr('x2', center) .attr('y2', 2000) .attr('class', 'actor-line') @@ -495,15 +501,34 @@ const drawActorTypeActor = function (elem, actor, conf) { return actor.height; }; -export const drawActor = function (elem, actor, conf) { +export const drawActor = function (elem, actor, conf, isFooter) { switch (actor.type) { case 'actor': - return drawActorTypeActor(elem, actor, conf); + return drawActorTypeActor(elem, actor, conf, isFooter); 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) { return elem.append('g'); }; @@ -642,6 +667,7 @@ export const drawBackgroundRect = function (elem, bounds) { width: bounds.stopx - bounds.startx, height: bounds.stopy - bounds.starty, fill: bounds.fill, + stroke: bounds.stroke, class: 'rect', }); rectElem.lower(); @@ -840,8 +866,7 @@ const _drawTextCandidateFunc = (function () { function byTspan(content, g, x, y, width, height, textAttrs, conf) { const { actorFontSize, actorFontFamily, actorFontWeight } = conf; - let _actorFontSize = - actorFontSize && actorFontSize.replace ? actorFontSize.replace('px', '') : actorFontSize; + const [_actorFontSize, _actorFontSizePx] = parseFontSize(actorFontSize); const lines = content.split(common.lineBreakRegex); for (let i = 0; i < lines.length; i++) { @@ -851,7 +876,7 @@ const _drawTextCandidateFunc = (function () { .attr('x', x + width / 2) .attr('y', y) .style('text-anchor', 'middle') - .style('font-size', actorFontSize) + .style('font-size', _actorFontSizePx) .style('font-weight', actorFontWeight) .style('font-family', actorFontFamily); text @@ -1035,6 +1060,7 @@ export default { drawText, drawLabel, drawActor, + drawBox, drawPopup, drawImage, drawEmbeddedImage, diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js index 580dafe89..ed60285ed 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.spec.js @@ -1,5 +1,31 @@ +import { vi } from 'vitest'; import svgDraw from './svgDraw'; -import { MockD3 } from 'd3'; + +// This is the only place that uses this mock +export const MockD3 = (name, parent) => { + const children = []; + const elem = { + get __children() { + return children; + }, + get __name() { + return name; + }, + get __parent() { + return parent; + }, + }; + elem.append = (name) => { + const mockElem = MockD3(name, elem); + children.push(mockElem); + return mockElem; + }; + elem.lower = vi.fn(() => elem); + elem.attr = vi.fn(() => elem); + elem.text = vi.fn(() => elem); + elem.style = vi.fn(() => elem); + return elem; +}; describe('svgDraw', function () { describe('drawRect', function () { @@ -99,6 +125,30 @@ describe('svgDraw', function () { expect(text3.attr).toHaveBeenCalledWith('y', 10); expect(text3.text).toHaveBeenCalledWith('fine lines'); }); + it('should work with numeral font sizes', function () { + const svg = MockD3('svg'); + svgDraw.drawText(svg, { + x: 10, + y: 10, + dy: '1em', + text: 'One fine text message', + class: 'noteText', + fontFamily: 'courier', + fontSize: 10, + fontWeight: '500', + }); + expect(svg.__children.length).toBe(1); + const text = svg.__children[0]; + expect(text.__name).toBe('text'); + expect(text.attr).toHaveBeenCalledWith('x', 10); + expect(text.attr).toHaveBeenCalledWith('y', 10); + expect(text.attr).toHaveBeenCalledWith('dy', '1em'); + expect(text.attr).toHaveBeenCalledWith('class', 'noteText'); + expect(text.text).toHaveBeenCalledWith('One fine text message'); + expect(text.style).toHaveBeenCalledWith('font-family', 'courier'); + expect(text.style).toHaveBeenCalledWith('font-size', '10px'); + expect(text.style).toHaveBeenCalledWith('font-weight', '500'); + }); }); describe('drawBackgroundRect', function () { it('should append a rect before the previous element within a given bound', function () { diff --git a/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js new file mode 100644 index 000000000..f8ea694a6 --- /dev/null +++ b/packages/mermaid/src/diagrams/state/parser/state-parser.spec.js @@ -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'); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison index 67073210b..dc050b2ff 100644 --- a/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison +++ b/packages/mermaid/src/diagrams/state/parser/stateDiagram.jison @@ -65,14 +65,14 @@ \%%[^\n]* /* skip comments */ "scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; } \d+ return 'WIDTH'; -\s+"width" {this.popState();} +\s+"width" { this.popState(); } accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } (?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } -accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} -[\}] { this.popState(); } +accDescr\s*"{"\s* { this.begin("acc_descr_multiline"); } +[\}] { this.popState(); } [^\}]* return "acc_descr_multiline_value"; "classDef"\s+ { this.pushState('CLASSDEF'); return 'classDef'; } @@ -81,57 +81,60 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili [^\n]* { this.popState(); return 'CLASSDEF_STYLEOPTS' } "class"\s+ { this.pushState('CLASS'); return 'class'; } -(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' } +(\w+)+((","\s*\w+)*) { this.popState(); this.pushState('CLASS_STYLE'); return 'CLASSENTITY_IDS' } [^\n]* { this.popState(); return 'STYLECLASS' } "scale"\s+ { this.pushState('SCALE'); /* console.log('Got scale', yytext);*/ return 'scale'; } \d+ return 'WIDTH'; \s+"width" {this.popState();} +"state"\s+ { /* console.log('Starting STATE '); */ this.pushState('STATE'); } -"state"\s+ { /*console.log('Starting STATE zxzx'+yy.getDirection());*/this.pushState('STATE'); } .*"<>" {this.popState();yytext=yytext.slice(0,-8).trim(); /*console.warn('Fork Fork: ',yytext);*/return 'FORK';} .*"<>" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';} -.*"<>" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';} +.*"<>" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';} .*"[[fork]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Fork: ',yytext);*/return 'FORK';} .*"[[join]]" {this.popState();yytext=yytext.slice(0,-8).trim();/*console.warn('Fork Join: ',yytext);*/return 'JOIN';} -.*"[[choice]]" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';} +.*"[[choice]]" {this.popState();yytext=yytext.slice(0,-10).trim();/*console.warn('Fork Join: ',yytext);*/return 'CHOICE';} + .*direction\s+TB[^\n]* { return 'direction_tb';} .*direction\s+BT[^\n]* { return 'direction_bt';} .*direction\s+RL[^\n]* { return 'direction_rl';} .*direction\s+LR[^\n]* { return 'direction_lr';} -["] { /*console.log('Starting STATE_STRING zxzx');*/this.begin("STATE_STRING");} -\s*"as"\s+ {this.popState();this.pushState('STATE_ID');return "AS";} -[^\n\{]* {this.popState();/* console.log('STATE_ID', yytext);*/return "ID";} -["] this.popState(); -[^"]* { /*console.log('Long description:', yytext);*/return "STATE_DESCR";} -[^\n\s\{]+ {/*console.log('COMPOSIT_STATE', yytext);*/return 'COMPOSIT_STATE';} -\n {this.popState();} -\{ {this.popState();this.pushState('struct'); /*console.log('begin struct', yytext);*/return 'STRUCT_START';} -\%\%(?!\{)[^\n]* /* skip comments inside state*/ -\} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';}} -[\n] /* nothing */ +["] { /* console.log('Starting STATE_STRING'); */ this.pushState("STATE_STRING"); } +\s*"as"\s+ { this.pushState('STATE_ID'); /* console.log('pushState(STATE_ID)'); */ return "AS"; } +[^\n\{]* { this.popState(); /* console.log('STATE_ID', yytext); */ return "ID"; } +["] { this.popState(); } +[^"]* { /* console.log('Long description:', yytext); */ return "STATE_DESCR"; } +[^\n\s\{]+ { /* console.log('COMPOSIT_STATE', yytext); */ return 'COMPOSIT_STATE'; } +\n { this.popState(); } +\{ { this.popState(); this.pushState('struct'); /* console.log('begin struct', yytext); */ return 'STRUCT_START'; } +\%\%(?!\{)[^\n]* /* skip comments inside state*/ +\} { /*console.log('Ending struct');*/ this.popState(); return 'STRUCT_STOP';} } +[\n] /* nothing */ "note"\s+ { this.begin('NOTE'); return 'note'; } -"left of" { this.popState();this.pushState('NOTE_ID');return 'left_of';} -"right of" { this.popState();this.pushState('NOTE_ID');return 'right_of';} -\" { this.popState();this.pushState('FLOATING_NOTE');} -\s*"as"\s* {this.popState();this.pushState('FLOATING_NOTE_ID');return "AS";} -["] /**/ -[^"]* { /*console.log('Floating note text: ', yytext);*/return "NOTE_TEXT";} -[^\n]* {this.popState();/*console.log('Floating note ID', yytext);*/return "ID";} -\s*[^:\n\s\-]+ { this.popState();this.pushState('NOTE_TEXT');/*console.log('Got ID for note', yytext);*/return 'ID';} -\s*":"[^:\n;]+ { this.popState();/*console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.substr(2).trim();return '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';} +"left of" { this.popState(); this.pushState('NOTE_ID'); return 'left_of'; } +"right of" { this.popState(); this.pushState('NOTE_ID'); return 'right_of'; } +\" { this.popState(); this.pushState('FLOATING_NOTE'); } +\s*"as"\s* { this.popState(); this.pushState('FLOATING_NOTE_ID'); return "AS"; } +["] /**/ +[^"]* { /* console.log('Floating note text: ', yytext); */ return "NOTE_TEXT"; } +[^\n]* { this.popState(); /* console.log('Floating note ID', yytext);*/ return "ID"; } +\s*[^:\n\s\-]+ { this.popState(); this.pushState('NOTE_TEXT'); /*console.log('Got ID for note', yytext);*/ return 'ID'; } +\s*":"[^:\n;]+ { this.popState(); /* console.log('Got NOTE_TEXT for note',yytext);*/yytext = yytext.substr(2).trim(); return '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-v2"\s+ { /*console.log('Got state diagram', yytext,'#');*/return 'SD'; } -"hide empty description" { /*console.log('HIDE_EMPTY', yytext,'#');*/return 'HIDE_EMPTY'; } -"[*]" { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';} -[^:\n\s\-\{]+ { /*console.log('=>ID=',yytext);*/ return 'ID';} -// \s*":"[^\+\->:\n;]+ { yytext = yytext.trim(); /*console.log('Descr = ', yytext);*/ return 'DESCR'; } -\s*":"[^:\n;]+ { yytext = yytext.trim(); /*console.log('Descr = ', yytext);*/ return 'DESCR'; } +"stateDiagram"\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'; } + +"[*]" { /* console.log('EDGE_STATE=',yytext); */ return 'EDGE_STATE'; } +[^:\n\s\-\{]+ { /* console.log('=>ID=',yytext); */ return 'ID'; } +// \s*":"[^\+\->:\n;]+ { yytext = yytext.trim(); /* console.log('Descr = ', yytext); */ return 'DESCR'; } +\s*":"[^:\n;]+ { yytext = yytext.trim(); /* console.log('Descr = ', yytext); */ return 'DESCR'; } "-->" return '-->'; "--" return 'CONCURRENT'; @@ -201,7 +204,7 @@ statement | COMPOSIT_STATE | 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 } } | STATE_DESCR AS ID { @@ -217,7 +220,7 @@ statement } | 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 } } | FORK { diff --git a/packages/mermaid/src/diagrams/state/stateDb.js b/packages/mermaid/src/diagrams/state/stateDb.js index 991aba078..81b8ffb8b 100644 --- a/packages/mermaid/src/diagrams/state/stateDb.js +++ b/packages/mermaid/src/diagrams/state/stateDb.js @@ -94,9 +94,14 @@ const docTranslator = (parent, node, first) => { docTranslator(parent, node.state1, true); docTranslator(parent, node.state2, false); } else { - if (node.stmt === STMT_STATE && node.id === '[*]') { - node.id = first ? parent.id + '_start' : parent.id + '_end'; - node.start = first; + if (node.stmt === STMT_STATE) { + if (node.id === '[*]') { + 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) { @@ -170,7 +175,7 @@ const extract = (_doc) => { switch (item.stmt) { case STMT_STATE: addState( - item.id, + item.id.trim(), item.type, item.doc, item.description, @@ -184,10 +189,10 @@ const extract = (_doc) => { addRelation(item.state1, item.state2, item.description); break; case STMT_CLASSDEF: - addStyleClass(item.id, item.classes); + addStyleClass(item.id.trim(), item.classes); break; case STMT_APPLYCLASS: - setCssClass(item.id, item.styleClass); + setCssClass(item.id.trim(), item.styleClass); break; } }); @@ -215,11 +220,12 @@ export const addState = function ( styles = null, textStyles = null ) { + const trimmedId = id?.trim(); // add the state if needed - if (currentDocument.states[id] === undefined) { - log.info('Adding state ', id, descr); - currentDocument.states[id] = { - id: id, + if (currentDocument.states[trimmedId] === undefined) { + log.info('Adding state ', trimmedId, descr); + currentDocument.states[trimmedId] = { + id: trimmedId, descriptions: [], type, doc, @@ -229,49 +235,49 @@ export const addState = function ( textStyles: [], }; } else { - if (!currentDocument.states[id].doc) { - currentDocument.states[id].doc = doc; + if (!currentDocument.states[trimmedId].doc) { + currentDocument.states[trimmedId].doc = doc; } - if (!currentDocument.states[id].type) { - currentDocument.states[id].type = type; + if (!currentDocument.states[trimmedId].type) { + currentDocument.states[trimmedId].type = type; } } if (descr) { - log.info('Setting state description', id, descr); + log.info('Setting state description', trimmedId, descr); if (typeof descr === 'string') { - addDescription(id, descr.trim()); + addDescription(trimmedId, descr.trim()); } if (typeof descr === 'object') { - descr.forEach((des) => addDescription(id, des.trim())); + descr.forEach((des) => addDescription(trimmedId, des.trim())); } } if (note) { - currentDocument.states[id].note = note; - currentDocument.states[id].note.text = common.sanitizeText( - currentDocument.states[id].note.text, + currentDocument.states[trimmedId].note = note; + currentDocument.states[trimmedId].note.text = common.sanitizeText( + currentDocument.states[trimmedId].note.text, configApi.getConfig() ); } if (classes) { - log.info('Setting state classes', id, classes); + log.info('Setting state classes', trimmedId, classes); const classesList = typeof classes === 'string' ? [classes] : classes; - classesList.forEach((klass) => setCssClass(id, klass.trim())); + classesList.forEach((klass) => setCssClass(trimmedId, klass.trim())); } if (styles) { - log.info('Setting state styles', id, styles); + log.info('Setting state styles', trimmedId, styles); const stylesList = typeof styles === 'string' ? [styles] : styles; - stylesList.forEach((style) => setStyle(id, style.trim())); + stylesList.forEach((style) => setStyle(trimmedId, style.trim())); } if (textStyles) { - log.info('Setting state styles', id, styles); + log.info('Setting state styles', trimmedId, styles); 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 */ export function addRelationObjs(item1, item2, relationTitle) { - let id1 = startIdIfNeeded(item1.id); - let type1 = startTypeIfNeeded(item1.id, item1.type); - let id2 = startIdIfNeeded(item2.id); - let type2 = startTypeIfNeeded(item2.id, item2.type); + let id1 = startIdIfNeeded(item1.id.trim()); + let type1 = startTypeIfNeeded(item1.id.trim(), item1.type); + let id2 = startIdIfNeeded(item2.id.trim()); + let type2 = startTypeIfNeeded(item2.id.trim(), item2.type); addState( id1, @@ -412,9 +418,9 @@ export const addRelation = function (item1, item2, title) { if (typeof item1 === 'object') { addRelationObjs(item1, item2, title); } else { - const id1 = startIdIfNeeded(item1); + const id1 = startIdIfNeeded(item1.trim()); const type1 = startTypeIfNeeded(item1); - const id2 = endIdIfNeeded(item2); + const id2 = endIdIfNeeded(item2.trim()); const type2 = endTypeIfNeeded(item2); addState(id1, type1); diff --git a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js index 78e38726e..8629f74db 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer-v2.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer-v2.js @@ -1,4 +1,4 @@ -import * as graphlib from 'dagre-d3-es/src/graphlib'; +import * as graphlib from 'dagre-d3-es/src/graphlib/index.js'; import { select } from 'd3'; import { getConfig } from '../../config'; import { render } from '../../dagre-wrapper/index.js'; @@ -6,7 +6,7 @@ import { log } from '../../logger'; import { configureSvgSize } from '../../setupGraphViewbox'; import common from '../common/common'; import utils from '../../utils'; -import addSVGAccessibilityFields from '../../accessibility'; + import { DEFAULT_DIAGRAM_DIRECTION, DEFAULT_NESTED_DOC_DIR, @@ -307,8 +307,8 @@ const setupNode = (g, parent, parsedItem, diagramStates, diagramDb, altFlag) => * * @param g * @param parentParsedItem - parsed Item that is the parent of this document (doc) - * @param doc - the document to set up - * @param {object} diagramStates - the list of all known states for the diagram + * @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 diagramDb * @param {boolean} altFlag * @todo This duplicates some of what is done in stateDb.js extract method @@ -470,7 +470,6 @@ export const draw = function (text, id, _version, diag) { label.insertBefore(rect, label.firstChild); // } } - addSVGAccessibilityFields(diag.db, svg, id); }; export default { diff --git a/packages/mermaid/src/diagrams/state/stateRenderer.js b/packages/mermaid/src/diagrams/state/stateRenderer.js index 4eeede12e..8d410fdd9 100644 --- a/packages/mermaid/src/diagrams/state/stateRenderer.js +++ b/packages/mermaid/src/diagrams/state/stateRenderer.js @@ -6,7 +6,6 @@ import common from '../common/common'; import { drawState, addTitleAndBox, drawEdge } from './shapes'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; // TODO Move conf object to main conf in mermaidAPI let conf; @@ -97,7 +96,6 @@ export const draw = function (text, id, _version, diagObj) { 'viewBox', `${bounds.x - conf.padding} ${bounds.y - conf.padding} ` + width + ' ' + height ); - addSVGAccessibilityFields(diagObj.db, diagram, id); }; const getLabelWidth = (text) => { return text ? text.length * conf.fontSizeFactor : 1; diff --git a/packages/mermaid/src/diagrams/timeline/detector.ts b/packages/mermaid/src/diagrams/timeline/detector.ts new file mode 100644 index 000000000..9bd2b5ece --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/detector.ts @@ -0,0 +1,20 @@ +import type { ExternalDiagramDefinition } from '../../diagram-api/types'; + +const id = 'timeline'; + +const detector = (txt: string) => { + return txt.match(/^\s*timeline/) !== null; +}; + +const loader = async () => { + const { diagram } = await import('./timeline-definition.js'); + return { id, diagram }; +}; + +const plugin: ExternalDiagramDefinition = { + id, + detector, + loader, +}; + +export default plugin; diff --git a/packages/mermaid/src/diagrams/timeline/parser/timeline.jison b/packages/mermaid/src/diagrams/timeline/parser/timeline.jison new file mode 100644 index 000000000..59b96516a --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/parser/timeline.jison @@ -0,0 +1,112 @@ +/** mermaid + * https://mermaidjs.github.io/ + * (c) 2023 Knut Sveidqvist + * MIT license. + */ +%lex +%options case-insensitive +%x acc_title +%x acc_descr +%x acc_descr_multiline + +// Directive states +%x open_directive type_directive arg_directive + + +%% + +\%\%\{ { this.begin('open_directive'); return 'open_directive'; } +((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } +":" { this.popState(); this.begin('arg_directive'); return ':'; } +\}\%\% { this.popState(); this.popState(); return 'close_directive'; } +((?:(?!\}\%\%).|\n)*) return 'arg_directive'; +\%%(?!\{)[^\n]* /* skip comments */ +[^\}]\%\%[^\n]* /* skip comments */ +[\n]+ return 'NEWLINE'; +\s+ /* skip whitespace */ +\#[^\n]* /* skip comments */ + +"timeline" return 'timeline'; +"title"\s[^#\n;]+ return 'title'; +accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } +accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } +accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} +[\}] { this.popState(); } +[^\}]* return "acc_descr_multiline_value"; +"section"\s[^#:\n;]+ return 'section'; + +// event starting with "==>" keyword +":"\s[^#:\n;]+ return 'event'; +[^#:\n;]+ return 'period'; + + +<> return 'EOF'; +. return 'INVALID'; + +/lex + +%left '^' + +%start start + +%% /* language grammar */ + +start + : timeline document 'EOF' { return $2; } + | directive start + ; + +document + : /* empty */ { $$ = [] } + | document line {$1.push($2);$$ = $1} + ; + +line + : SPACE statement { $$ = $2 } + | statement { $$ = $1 } + | NEWLINE { $$=[];} + | EOF { $$=[];} + ; + +directive + : openDirective typeDirective closeDirective 'NEWLINE' + | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' + ; + +statement + : title {yy.getCommonDb().setDiagramTitle($1.substr(6));$$=$1.substr(6);} + | acc_title acc_title_value { $$=$2.trim();yy.getCommonDb().setAccTitle($$); } + | acc_descr acc_descr_value { $$=$2.trim();yy.getCommonDb().setAccDescription($$); } + | acc_descr_multiline_value { $$=$1.trim();yy.getCommonDb().setAccDescription($$); } + | section {yy.addSection($1.substr(8));$$=$1.substr(8);} + | period_statement + | event_statement + | directive + ; +period_statement + : period {yy.addTask($1,0,'');$$=$1;} +; +event_statement + : event {yy.addEvent($1.substr(2));$$=$1;} +; + + +openDirective + : open_directive { yy.parseDirective('%%{', 'open_directive'); } + ; + +typeDirective + : type_directive { yy.parseDirective($1, 'type_directive'); } + ; + +argDirective + : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } + ; + +closeDirective + : close_directive { yy.parseDirective('}%%', 'close_directive', 'timeline'); } + ; + +%% diff --git a/packages/mermaid/src/diagrams/timeline/styles.js b/packages/mermaid/src/diagrams/timeline/styles.js new file mode 100644 index 000000000..fd9526e98 --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/styles.js @@ -0,0 +1,81 @@ +import { darken, lighten, isDark } from 'khroma'; + +const genSections = (options) => { + let sections = ''; + + for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) { + options['lineColor' + i] = options['lineColor' + i] || options['cScaleInv' + i]; + if (isDark(options['lineColor' + i])) { + options['lineColor' + i] = lighten(options['lineColor' + i], 20); + } else { + options['lineColor' + i] = darken(options['lineColor' + i], 20); + } + } + + for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) { + const sw = '' + (17 - 3 * i); + sections += ` + .section-${i - 1} rect, .section-${i - 1} path, .section-${i - 1} circle, .section-${ + i - 1 + } path { + fill: ${options['cScale' + i]}; + } + .section-${i - 1} text { + fill: ${options['cScaleLabel' + i]}; + } + .node-icon-${i - 1} { + font-size: 40px; + color: ${options['cScaleLabel' + i]}; + } + .section-edge-${i - 1}{ + stroke: ${options['cScale' + i]}; + } + .edge-depth-${i - 1}{ + stroke-width: ${sw}; + } + .section-${i - 1} line { + stroke: ${options['cScaleInv' + i]} ; + stroke-width: 3; + } + + .lineWrapper line{ + stroke: ${options['cScaleLabel' + i]} ; + } + + .disabled, .disabled circle, .disabled text { + fill: lightgray; + } + .disabled text { + fill: #efefef; + } + `; + } + return sections; +}; + +const getStyles = (options) => + ` + .edge { + stroke-width: 3; + } + ${genSections(options)} + .section-root rect, .section-root path, .section-root circle { + fill: ${options.git0}; + } + .section-root text { + fill: ${options.gitBranchLabel0}; + } + .icon-container { + height:100%; + display: flex; + justify-content: center; + align-items: center; + } + .edge { + fill: none; + } + .eventWrapper { + filter: brightness(120%); + } +`; +export default getStyles; diff --git a/packages/mermaid/src/diagrams/timeline/svgDraw.js b/packages/mermaid/src/diagrams/timeline/svgDraw.js new file mode 100644 index 000000000..874ac62ef --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/svgDraw.js @@ -0,0 +1,602 @@ +import { arc as d3arc, select } from 'd3'; +const MAX_SECTIONS = 12; + +export const drawRect = function (elem, rectData) { + const rectElem = elem.append('rect'); + rectElem.attr('x', rectData.x); + rectElem.attr('y', rectData.y); + rectElem.attr('fill', rectData.fill); + rectElem.attr('stroke', rectData.stroke); + rectElem.attr('width', rectData.width); + rectElem.attr('height', rectData.height); + rectElem.attr('rx', rectData.rx); + rectElem.attr('ry', rectData.ry); + + if (rectData.class !== undefined) { + rectElem.attr('class', rectData.class); + } + + return rectElem; +}; + +export const drawFace = function (element, faceData) { + const radius = 15; + const circleElement = element + .append('circle') + .attr('cx', faceData.cx) + .attr('cy', faceData.cy) + .attr('class', 'face') + .attr('r', radius) + .attr('stroke-width', 2) + .attr('overflow', 'visible'); + + const face = element.append('g'); + + //left eye + face + .append('circle') + .attr('cx', faceData.cx - radius / 3) + .attr('cy', faceData.cy - radius / 3) + .attr('r', 1.5) + .attr('stroke-width', 2) + .attr('fill', '#666') + .attr('stroke', '#666'); + + //right eye + face + .append('circle') + .attr('cx', faceData.cx + radius / 3) + .attr('cy', faceData.cy - radius / 3) + .attr('r', 1.5) + .attr('stroke-width', 2) + .attr('fill', '#666') + .attr('stroke', '#666'); + + /** @param {any} face */ + function smile(face) { + const arc = d3arc() + .startAngle(Math.PI / 2) + .endAngle(3 * (Math.PI / 2)) + .innerRadius(radius / 2) + .outerRadius(radius / 2.2); + //mouth + face + .append('path') + .attr('class', 'mouth') + .attr('d', arc) + .attr('transform', 'translate(' + faceData.cx + ',' + (faceData.cy + 2) + ')'); + } + + /** @param {any} face */ + function sad(face) { + const arc = d3arc() + .startAngle((3 * Math.PI) / 2) + .endAngle(5 * (Math.PI / 2)) + .innerRadius(radius / 2) + .outerRadius(radius / 2.2); + //mouth + face + .append('path') + .attr('class', 'mouth') + .attr('d', arc) + .attr('transform', 'translate(' + faceData.cx + ',' + (faceData.cy + 7) + ')'); + } + + /** @param {any} face */ + function ambivalent(face) { + face + .append('line') + .attr('class', 'mouth') + .attr('stroke', 2) + .attr('x1', faceData.cx - 5) + .attr('y1', faceData.cy + 7) + .attr('x2', faceData.cx + 5) + .attr('y2', faceData.cy + 7) + .attr('class', 'mouth') + .attr('stroke-width', '1px') + .attr('stroke', '#666'); + } + + if (faceData.score > 3) { + smile(face); + } else if (faceData.score < 3) { + sad(face); + } else { + ambivalent(face); + } + + return circleElement; +}; + +export const drawCircle = function (element, circleData) { + const circleElement = element.append('circle'); + circleElement.attr('cx', circleData.cx); + circleElement.attr('cy', circleData.cy); + circleElement.attr('class', 'actor-' + circleData.pos); + circleElement.attr('fill', circleData.fill); + circleElement.attr('stroke', circleData.stroke); + circleElement.attr('r', circleData.r); + + if (circleElement.class !== undefined) { + circleElement.attr('class', circleElement.class); + } + + if (circleData.title !== undefined) { + circleElement.append('title').text(circleData.title); + } + + return circleElement; +}; + +export const drawText = function (elem, textData) { + // Remove and ignore br:s + const nText = textData.text.replace(//gi, ' '); + + const textElem = elem.append('text'); + textElem.attr('x', textData.x); + textElem.attr('y', textData.y); + textElem.attr('class', 'legend'); + + textElem.style('text-anchor', textData.anchor); + + if (textData.class !== undefined) { + textElem.attr('class', textData.class); + } + + const span = textElem.append('tspan'); + span.attr('x', textData.x + textData.textMargin * 2); + span.text(nText); + + return textElem; +}; + +export const drawLabel = function (elem, txtObject) { + /** + * @param {any} x + * @param {any} y + * @param {any} width + * @param {any} height + * @param {any} cut + */ + function genPoints(x, y, width, height, cut) { + return ( + x + + ',' + + y + + ' ' + + (x + width) + + ',' + + y + + ' ' + + (x + width) + + ',' + + (y + height - cut) + + ' ' + + (x + width - cut * 1.2) + + ',' + + (y + height) + + ' ' + + x + + ',' + + (y + height) + ); + } + const polygon = elem.append('polygon'); + polygon.attr('points', genPoints(txtObject.x, txtObject.y, 50, 20, 7)); + polygon.attr('class', 'labelBox'); + + txtObject.y = txtObject.y + txtObject.labelMargin; + txtObject.x = txtObject.x + 0.5 * txtObject.labelMargin; + drawText(elem, txtObject); +}; + +export const drawSection = function (elem, section, conf) { + const g = elem.append('g'); + + const rect = getNoteRect(); + rect.x = section.x; + rect.y = section.y; + rect.fill = section.fill; + rect.width = conf.width; + rect.height = conf.height; + rect.class = 'journey-section section-type-' + section.num; + rect.rx = 3; + rect.ry = 3; + drawRect(g, rect); + + _drawTextCandidateFunc(conf)( + section.text, + g, + rect.x, + rect.y, + rect.width, + rect.height, + { class: 'journey-section section-type-' + section.num }, + conf, + section.colour + ); +}; + +let taskCount = -1; +/** + * Draws an actor in the diagram with the attached line + * + * @param {any} elem The HTML element + * @param {any} task The task to render + * @param {any} conf The global configuration + */ +export const drawTask = function (elem, task, conf) { + const center = task.x + conf.width / 2; + const g = elem.append('g'); + taskCount++; + const maxHeight = 300 + 5 * 30; + g.append('line') + .attr('id', 'task' + taskCount) + .attr('x1', center) + .attr('y1', task.y) + .attr('x2', center) + .attr('y2', maxHeight) + .attr('class', 'task-line') + .attr('stroke-width', '1px') + .attr('stroke-dasharray', '4 2') + .attr('stroke', '#666'); + + drawFace(g, { + cx: center, + cy: 300 + (5 - task.score) * 30, + score: task.score, + }); + + const rect = getNoteRect(); + rect.x = task.x; + rect.y = task.y; + rect.fill = task.fill; + rect.width = conf.width; + rect.height = conf.height; + rect.class = 'task task-type-' + task.num; + rect.rx = 3; + rect.ry = 3; + drawRect(g, rect); + + let xPos = task.x + 14; + // task.people.forEach((person) => { + // const colour = task.actors[person].color; + + // const circle = { + // cx: xPos, + // cy: task.y, + // r: 7, + // fill: colour, + // stroke: '#000', + // title: person, + // pos: task.actors[person].position, + // }; + + // drawCircle(g, circle); + // xPos += 10; + // }); + + _drawTextCandidateFunc(conf)( + task.task, + g, + rect.x, + rect.y, + rect.width, + rect.height, + { class: 'task' }, + conf, + task.colour + ); +}; + +/** + * Draws a background rectangle + * + * @param {any} elem The html element + * @param {any} bounds The bounds of the drawing + */ +export const drawBackgroundRect = function (elem, bounds) { + const rectElem = drawRect(elem, { + x: bounds.startx, + y: bounds.starty, + width: bounds.stopx - bounds.startx, + height: bounds.stopy - bounds.starty, + fill: bounds.fill, + class: 'rect', + }); + rectElem.lower(); +}; + +export const getTextObj = function () { + return { + x: 0, + y: 0, + fill: undefined, + 'text-anchor': 'start', + width: 100, + height: 100, + textMargin: 0, + rx: 0, + ry: 0, + }; +}; + +export const getNoteRect = function () { + return { + x: 0, + y: 0, + width: 100, + anchor: 'start', + height: 100, + rx: 0, + ry: 0, + }; +}; + +const _drawTextCandidateFunc = (function () { + /** + * @param {any} content + * @param {any} g + * @param {any} x + * @param {any} y + * @param {any} width + * @param {any} height + * @param {any} textAttrs + * @param {any} colour + */ + function byText(content, g, x, y, width, height, textAttrs, colour) { + const text = g + .append('text') + .attr('x', x + width / 2) + .attr('y', y + height / 2 + 5) + .style('font-color', colour) + .style('text-anchor', 'middle') + .text(content); + _setTextAttrs(text, textAttrs); + } + + /** + * @param {any} content + * @param {any} g + * @param {any} x + * @param {any} y + * @param {any} width + * @param {any} height + * @param {any} textAttrs + * @param {any} conf + * @param {any} colour + */ + function byTspan(content, g, x, y, width, height, textAttrs, conf, colour) { + const { taskFontSize, taskFontFamily } = conf; + + const lines = content.split(//gi); + for (let i = 0; i < lines.length; i++) { + const dy = i * taskFontSize - (taskFontSize * (lines.length - 1)) / 2; + const text = g + .append('text') + .attr('x', x + width / 2) + .attr('y', y) + .attr('fill', colour) + .style('text-anchor', 'middle') + .style('font-size', taskFontSize) + .style('font-family', taskFontFamily); + text + .append('tspan') + .attr('x', x + width / 2) + .attr('dy', dy) + .text(lines[i]); + + text + .attr('y', y + height / 2.0) + .attr('dominant-baseline', 'central') + .attr('alignment-baseline', 'central'); + + _setTextAttrs(text, textAttrs); + } + } + + /** + * @param {any} content + * @param {any} g + * @param {any} x + * @param {any} y + * @param {any} width + * @param {any} height + * @param {any} textAttrs + * @param {any} conf + */ + function byFo(content, g, x, y, width, height, textAttrs, conf) { + const body = g.append('switch'); + const f = body + .append('foreignObject') + .attr('x', x) + .attr('y', y) + .attr('width', width) + .attr('height', height) + .attr('position', 'fixed'); + + const text = f + .append('xhtml:div') + .style('display', 'table') + .style('height', '100%') + .style('width', '100%'); + + text + .append('div') + .attr('class', 'label') + .style('display', 'table-cell') + .style('text-align', 'center') + .style('vertical-align', 'middle') + .text(content); + + byTspan(content, body, x, y, width, height, textAttrs, conf); + _setTextAttrs(text, textAttrs); + } + + /** + * @param {any} toText + * @param {any} fromTextAttrsDict + */ + function _setTextAttrs(toText, fromTextAttrsDict) { + for (const key in fromTextAttrsDict) { + if (key in fromTextAttrsDict) { + // noinspection JSUnfilteredForInLoop + toText.attr(key, fromTextAttrsDict[key]); + } + } + } + + return function (conf) { + return conf.textPlacement === 'fo' ? byFo : conf.textPlacement === 'old' ? byText : byTspan; + }; +})(); + +const initGraphics = function (graphics) { + graphics + .append('defs') + .append('marker') + .attr('id', 'arrowhead') + .attr('refX', 5) + .attr('refY', 2) + .attr('markerWidth', 6) + .attr('markerHeight', 4) + .attr('orient', 'auto') + .append('path') + .attr('d', 'M 0,0 V 4 L6,2 Z'); // this is actual shape for arrowhead +}; + +/** + * @param {string} text The text to be wrapped + * @param {number} width The max width of the text + */ +function wrap(text, width) { + text.each(function () { + var text = select(this), + words = text + .text() + .split(/(\s+|
)/) + .reverse(), + word, + line = [], + lineHeight = 1.1, // ems + y = text.attr('y'), + dy = parseFloat(text.attr('dy')), + tspan = text + .text(null) + .append('tspan') + .attr('x', 0) + .attr('y', y) + .attr('dy', dy + 'em'); + for (let j = 0; j < words.length; j++) { + word = words[words.length - 1 - j]; + line.push(word); + tspan.text(line.join(' ').trim()); + if (tspan.node().getComputedTextLength() > width || word === '
') { + line.pop(); + tspan.text(line.join(' ').trim()); + if (word === '
') { + line = ['']; + } else { + line = [word]; + } + + tspan = text + .append('tspan') + .attr('x', 0) + .attr('y', y) + .attr('dy', lineHeight + 'em') + .text(word); + } + } + }); +} + +export const drawNode = function (elem, node, fullSection, conf) { + const section = (fullSection % MAX_SECTIONS) - 1; + const nodeElem = elem.append('g'); + node.section = section; + nodeElem.attr( + 'class', + (node.class ? node.class + ' ' : '') + 'timeline-node ' + ('section-' + section) + ); + const bkgElem = nodeElem.append('g'); + + // Create the wrapped text element + const textElem = nodeElem.append('g'); + + const txt = textElem + .append('text') + .text(node.descr) + .attr('dy', '1em') + .attr('alignment-baseline', 'middle') + .attr('dominant-baseline', 'middle') + .attr('text-anchor', 'middle') + .call(wrap, node.width); + const bbox = txt.node().getBBox(); + const fontSize = + conf.fontSize && conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize; + node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding; + node.height = Math.max(node.height, node.maxHeight); + node.width = node.width + 2 * node.padding; + + textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')'); + + // Create the background element + defaultBkg(bkgElem, node, section, conf); + + return node; +}; + +export const getVirtualNodeHeight = function (elem, node, conf) { + const textElem = elem.append('g'); + const txt = textElem + .append('text') + .text(node.descr) + .attr('dy', '1em') + .attr('alignment-baseline', 'middle') + .attr('dominant-baseline', 'middle') + .attr('text-anchor', 'middle') + .call(wrap, node.width); + const bbox = txt.node().getBBox(); + const fontSize = + conf.fontSize && conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize; + textElem.remove(); + return bbox.height + fontSize * 1.1 * 0.5 + node.padding; +}; + +const defaultBkg = function (elem, node, section) { + const rd = 5; + elem + .append('path') + .attr('id', 'node-' + node.id) + .attr('class', 'node-bkg node-' + node.type) + .attr( + 'd', + `M0 ${node.height - rd} v${-node.height + 2 * rd} q0,-5 5,-5 h${ + node.width - 2 * rd + } q5,0 5,5 v${node.height - rd} H0 Z` + ); + + elem + .append('line') + .attr('class', 'node-line-' + section) + .attr('x1', 0) + .attr('y1', node.height) + .attr('x2', node.width) + .attr('y2', node.height); +}; + +export default { + drawRect, + drawCircle, + drawSection, + drawText, + drawLabel, + drawTask, + drawBackgroundRect, + getTextObj, + getNoteRect, + initGraphics, + drawNode, + getVirtualNodeHeight, +}; diff --git a/packages/mermaid/src/diagrams/timeline/timeline-definition.ts b/packages/mermaid/src/diagrams/timeline/timeline-definition.ts new file mode 100644 index 000000000..898af8b78 --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/timeline-definition.ts @@ -0,0 +1,12 @@ +// @ts-ignore: TODO Fix ts errors +import parser from './parser/timeline.jison'; +import * as db from './timelineDb'; +import renderer from './timelineRenderer'; +import styles from './styles'; + +export const diagram = { + db, + renderer, + parser, + styles, +}; diff --git a/packages/mermaid/src/diagrams/timeline/timeline.spec.js b/packages/mermaid/src/diagrams/timeline/timeline.spec.js new file mode 100644 index 000000000..0697b194e --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/timeline.spec.js @@ -0,0 +1,122 @@ +import { parser as timeline } from './parser/timeline'; +import * as timelineDB from './timelineDb'; +// import { injectUtils } from './mermaidUtils'; +import * as _commonDb from '../../commonDb'; +import { parseDirective as _parseDirective } from '../../directiveUtils'; + +import { + log, + setLogLevel, + getConfig, + sanitizeText, + setupGraphViewBox, +} from '../../diagram-api/diagramAPI'; + +// injectUtils( +// log, +// setLogLevel, +// getConfig, +// sanitizeText, +// setupGraphViewBox, +// _commonDb, +// _parseDirective +// ); + +describe('when parsing a timeline ', function () { + beforeEach(function () { + timeline.yy = timelineDB; + timelineDB.clear(); + setLogLevel('trace'); + }); + describe('Timeline', function () { + it('TL-1 should handle a simple section definition abc-123', function () { + let str = `timeline + section abc-123`; + + timeline.parse(str); + expect(timelineDB.getSections()).to.deep.equal(['abc-123']); + }); + + it('TL-2 should handle a simple section and only two tasks', function () { + let str = `timeline + section abc-123 + task1 + task2`; + timeline.parse(str); + timelineDB.getTasks().forEach((task) => { + expect(task.section).to.equal('abc-123'); + expect(task.task).to.be.oneOf(['task1', 'task2']); + }); + }); + + it('TL-3 should handle a two section and two coressponding tasks', function () { + let str = `timeline + section abc-123 + task1 + task2 + section abc-456 + task3 + task4`; + timeline.parse(str); + expect(timelineDB.getSections()).to.deep.equal(['abc-123', 'abc-456']); + timelineDB.getTasks().forEach((task) => { + expect(task.section).to.be.oneOf(['abc-123', 'abc-456']); + expect(task.task).to.be.oneOf(['task1', 'task2', 'task3', 'task4']); + if (task.section === 'abc-123') { + expect(task.task).to.be.oneOf(['task1', 'task2']); + } else { + expect(task.task).to.be.oneOf(['task3', 'task4']); + } + }); + }); + + it('TL-4 should handle a section, and task and its events', function () { + let str = `timeline + section abc-123 + task1: event1 + task2: event2: event3 + `; + timeline.parse(str); + expect(timelineDB.getSections()[0]).to.deep.equal('abc-123'); + timelineDB.getTasks().forEach((t) => { + switch (t.task.trim()) { + case 'task1': + expect(t.events).to.deep.equal(['event1']); + break; + + case 'task2': + expect(t.events).to.deep.equal(['event2', 'event3']); + break; + + default: + break; + } + }); + }); + + it('TL-5 should handle a section, and task and its multi line events', function () { + let str = `timeline + section abc-123 + task1: event1 + task2: event2: event3 + : event4: event5 + `; + timeline.parse(str); + expect(timelineDB.getSections()[0]).to.deep.equal('abc-123'); + timelineDB.getTasks().forEach((t) => { + switch (t.task.trim()) { + case 'task1': + expect(t.events).to.deep.equal(['event1']); + break; + + case 'task2': + expect(t.events).to.deep.equal(['event2', 'event3', 'event4', 'event5']); + break; + + default: + break; + } + }); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/timeline/timelineDb.js b/packages/mermaid/src/diagrams/timeline/timelineDb.js new file mode 100644 index 000000000..7bc5c2692 --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/timelineDb.js @@ -0,0 +1,108 @@ +import { parseDirective as _parseDirective } from '../../directiveUtils'; +import * as commonDb from '../../commonDb'; +let currentSection = ''; +let currentTaskId = 0; + +const sections = []; +const tasks = []; +const rawTasks = []; + +export const getCommonDb = () => commonDb; + +export const parseDirective = (statement, context, type) => { + _parseDirective(this, statement, context, type); +}; + +export const clear = function () { + sections.length = 0; + tasks.length = 0; + currentSection = ''; + rawTasks.length = 0; + commonDb.clear(); +}; + +export const addSection = function (txt) { + currentSection = txt; + sections.push(txt); +}; + +export const getSections = function () { + return sections; +}; + +export const getTasks = function () { + let allItemsProcessed = compileTasks(); + const maxDepth = 100; + let iterationCount = 0; + while (!allItemsProcessed && iterationCount < maxDepth) { + allItemsProcessed = compileTasks(); + iterationCount++; + } + + tasks.push(...rawTasks); + + return tasks; +}; + +export const addTask = function (period, length, event) { + const rawTask = { + id: currentTaskId++, + section: currentSection, + type: currentSection, + task: period, + score: length ? length : 0, + //if event is defined, then add it the events array + events: event ? [event] : [], + }; + rawTasks.push(rawTask); +}; + +export const addEvent = function (event) { + // fetch current task with currnetTaskId + const currentTask = rawTasks.find((task) => task.id === currentTaskId - 1); + //add event to the events array + currentTask.events.push(event); +}; + +export const addTaskOrg = function (descr) { + const newTask = { + section: currentSection, + type: currentSection, + description: descr, + task: descr, + classes: [], + }; + tasks.push(newTask); +}; + +/** + * Compiles the raw tasks into a list of tasks with events + * @returns {boolean} true if all items are processed + * @private + * @memberof timelineDb + */ +const compileTasks = function () { + const compileTask = function (pos) { + return rawTasks[pos].processed; + }; + + let allProcessed = true; + for (const [i, rawTask] of rawTasks.entries()) { + compileTask(i); + + allProcessed = allProcessed && rawTask.processed; + } + return allProcessed; +}; + +export default { + clear, + getCommonDb, + addSection, + getSections, + getTasks, + addTask, + addTaskOrg, + addEvent, + parseDirective, +}; diff --git a/packages/mermaid/src/diagrams/timeline/timelineRenderer.ts b/packages/mermaid/src/diagrams/timeline/timelineRenderer.ts new file mode 100644 index 000000000..02e706bf6 --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/timelineRenderer.ts @@ -0,0 +1,336 @@ +// @ts-nocheck TODO: fix file +import { select } from 'd3'; +import svgDraw from './svgDraw'; +import { log } from '../../logger'; +import { getConfig } from '../../config'; +import { setupGraphViewbox } from '../../setupGraphViewbox'; + +export const setConf = function (cnf) { + const keys = Object.keys(cnf); + + keys.forEach(function (key) { + conf[key] = cnf[key]; + }); +}; + +export const draw = function (text, id, version, diagObj) { + //1. Fetch the configuration + const conf = getConfig(); + const LEFT_MARGIN = conf.leftMargin ? conf.leftMargin : 50; + + //2. Clear the diagram db before parsing + diagObj.db.clear(); + + //3. Parse the diagram text + diagObj.parser.parse(text + '\n'); + + log.debug('timeline', diagObj.db); + + const securityLevel = conf.securityLevel; + // 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 svg = root.select('#' + id); + + svg.append('g'); + + //4. Fetch the diagram data + const tasks = diagObj.db.getTasks(); + const title = diagObj.db.getCommonDb().getDiagramTitle(); + log.debug('task', tasks); + + //5. Initialize the diagram + svgDraw.initGraphics(svg); + + // fetch Sections + const sections = diagObj.db.getSections(); + log.debug('sections', sections); + + let maxSectionHeight = 0; + let maxTaskHeight = 0; + //let sectionBeginX = 0; + let depthY = 0; + let sectionBeginY = 0; + let masterX = 50 + LEFT_MARGIN; + //sectionBeginX = masterX; + let masterY = 50; + sectionBeginY = 50; + //draw sections + let sectionNumber = 0; + let hasSections = true; + + //Calculate the max height of the sections + sections.forEach(function (section) { + const sectionNode = { + number: sectionNumber, + descr: section, + section: sectionNumber, + width: 150, + padding: 20, + maxHeight: maxSectionHeight, + }; + const sectionHeight = svgDraw.getVirtualNodeHeight(svg, sectionNode, conf); + log.debug('sectionHeight before draw', sectionHeight); + maxSectionHeight = Math.max(maxSectionHeight, sectionHeight + 20); + }); + + //tasks length and maxEventCount + let maxEventCount = 0; + let maxEventLineLength = 0; + log.debug('tasks.length', tasks.length); + //calculate max task height + // for loop till tasks.length + for (const [i, task] of tasks.entries()) { + const taskNode = { + number: i, + descr: task, + section: task.section, + width: 150, + padding: 20, + maxHeight: maxTaskHeight, + }; + const taskHeight = svgDraw.getVirtualNodeHeight(svg, taskNode, conf); + log.debug('taskHeight before draw', taskHeight); + maxTaskHeight = Math.max(maxTaskHeight, taskHeight + 20); + + //calculate maxEventCount + maxEventCount = Math.max(maxEventCount, task.events.length); + //calculate maxEventLineLength + let maxEventLineLengthTemp = 0; + for (let j = 0; j < task.events.length; j++) { + const event = task.events[j]; + const eventNode = { + descr: event, + section: task.section, + number: task.section, + width: 150, + padding: 20, + maxHeight: 50, + }; + maxEventLineLengthTemp += svgDraw.getVirtualNodeHeight(svg, eventNode, conf); + } + maxEventLineLength = Math.max(maxEventLineLength, maxEventLineLengthTemp); + } + + log.debug('maxSectionHeight before draw', maxSectionHeight); + log.debug('maxTaskHeight before draw', maxTaskHeight); + + if (sections && sections.length > 0) { + sections.forEach((section) => { + const sectionNode = { + number: sectionNumber, + descr: section, + section: sectionNumber, + width: 150, + padding: 20, + maxHeight: maxSectionHeight, + }; + log.debug('sectionNode', sectionNode); + const sectionNodeWrapper = svg.append('g'); + const node = svgDraw.drawNode(sectionNodeWrapper, sectionNode, sectionNumber, conf); + log.debug('sectionNode output', node); + + sectionNodeWrapper.attr('transform', `translate(${masterX}, ${sectionBeginY})`); + + masterY += maxSectionHeight + 50; + + //draw tasks for this section + //filter task where tasks.section == section + const tasksForSection = tasks.filter((task) => task.section === section); + if (tasksForSection.length > 0) { + drawTasks( + svg, + tasksForSection, + sectionNumber, + masterX, + masterY, + maxTaskHeight, + conf, + maxEventCount, + maxEventLineLength, + maxSectionHeight, + false + ); + } + // todo replace with total width of section and its tasks + masterX += 200 * Math.max(tasksForSection.length, 1); + + masterY = sectionBeginY; + sectionNumber++; + }); + } else { + //draw tasks + hasSections = false; + drawTasks( + svg, + tasks, + sectionNumber, + masterX, + masterY, + maxTaskHeight, + conf, + maxEventCount, + maxEventLineLength, + maxSectionHeight, + true + ); + } + + // Get BBox of the diagram + const box = svg.node().getBBox(); + log.debug('bounds', box); + + if (title) { + svg + .append('text') + .text(title) + .attr('x', box.width / 2 - LEFT_MARGIN) + .attr('font-size', '4ex') + .attr('font-weight', 'bold') + .attr('y', 20); + } + //5. Draw the diagram + depthY = hasSections ? maxSectionHeight + maxTaskHeight + 150 : maxTaskHeight + 100; + + const lineWrapper = svg.append('g').attr('class', 'lineWrapper'); + // Draw activity line + lineWrapper + .append('line') + .attr('x1', LEFT_MARGIN) + .attr('y1', depthY) // One section head + one task + margins + .attr('x2', box.width + 3 * LEFT_MARGIN) // Subtract stroke width so arrow point is retained + .attr('y2', depthY) + .attr('stroke-width', 4) + .attr('stroke', 'black') + .attr('marker-end', 'url(#arrowhead)'); + + // Setup the view box and size of the svg element + setupGraphViewbox( + undefined, + svg, + conf.timeline.padding ? conf.timeline.padding : 50, + conf.timeline.useMaxWidth ? conf.timeline.useMaxWidth : false + ); + + // addSVGAccessibilityFields(diagObj.db, diagram, id); +}; + +export const drawTasks = function ( + diagram, + tasks, + sectionColor, + masterX, + masterY, + maxTaskHeight, + conf, + maxEventCount, + maxEventLineLength, + maxSectionHeight, + isWithoutSections +) { + // Draw the tasks + for (const task of tasks) { + // create node from task + const taskNode = { + descr: task.task, + section: sectionColor, + number: sectionColor, + width: 150, + padding: 20, + maxHeight: maxTaskHeight, + }; + + log.debug('taskNode', taskNode); + // create task wrapper + const taskWrapper = diagram.append('g').attr('class', 'taskWrapper'); + const node = svgDraw.drawNode(taskWrapper, taskNode, sectionColor, conf); + const taskHeight = node.height; + //log task height + log.debug('taskHeight after draw', taskHeight); + taskWrapper.attr('transform', `translate(${masterX}, ${masterY})`); + + // update max task height + maxTaskHeight = Math.max(maxTaskHeight, taskHeight); + + // if task has events, draw them + if (task.events) { + // draw a line between the task and the events + const lineWrapper = diagram.append('g').attr('class', 'lineWrapper'); + let linelength = maxTaskHeight; + //add margin to task + masterY += 100; + linelength = + linelength + drawEvents(diagram, task.events, sectionColor, masterX, masterY, conf); + masterY -= 100; + + lineWrapper + .append('line') + .attr('x1', masterX + 190 / 2) + .attr('y1', masterY + maxTaskHeight) // One section head + one task + margins + .attr('x2', masterX + 190 / 2) // Subtract stroke width so arrow point is retained + .attr( + 'y2', + masterY + + maxTaskHeight + + (isWithoutSections ? maxTaskHeight : maxSectionHeight) + + maxEventLineLength + + 120 + ) + .attr('stroke-width', 2) + .attr('stroke', 'black') + .attr('marker-end', 'url(#arrowhead)') + .attr('stroke-dasharray', '5,5'); + } + + masterX = masterX + 200; + if (isWithoutSections && !getConfig().timeline.disableMulticolor) { + sectionColor++; + } + } + + // reset Y coordinate for next section + masterY = masterY - 10; +}; + +export const drawEvents = function (diagram, events, sectionColor, masterX, masterY, conf) { + let maxEventHeight = 0; + const eventBeginY = masterY; + masterY = masterY + 100; + // Draw the events + for (const event of events) { + // create node from event + const eventNode = { + descr: event, + section: sectionColor, + number: sectionColor, + width: 150, + padding: 20, + maxHeight: 50, + }; + + //log task node + log.debug('eventNode', eventNode); + // create event wrapper + const eventWrapper = diagram.append('g').attr('class', 'eventWrapper'); + const node = svgDraw.drawNode(eventWrapper, eventNode, sectionColor, conf); + const eventHeight = node.height; + maxEventHeight = maxEventHeight + eventHeight; + eventWrapper.attr('transform', `translate(${masterX}, ${masterY})`); + masterY = masterY + 10 + eventHeight; + } + // set masterY back to eventBeginY + masterY = eventBeginY; + return maxEventHeight; +}; + +export default { + setConf, + draw, +}; diff --git a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts index b22192101..df46fc9c6 100644 --- a/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts +++ b/packages/mermaid/src/diagrams/user-journey/journeyRenderer.ts @@ -3,7 +3,6 @@ import { select } from 'd3'; import svgDraw from './svgDraw'; import { getConfig } from '../../config'; import { configureSvgSize } from '../../setupGraphViewbox'; -import addSVGAccessibilityFields from '../../accessibility'; export const setConf = function (cnf) { const keys = Object.keys(cnf); @@ -121,8 +120,6 @@ export const draw = function (text, id, version, diagObj) { diagram.attr('viewBox', `${box.startx} -25 ${width} ${height + extraVertForTitle}`); diagram.attr('preserveAspectRatio', 'xMinYMin meet'); diagram.attr('height', height + extraVertForTitle + 25); - - addSVGAccessibilityFields(diagObj.db, diagram, id); }; export const bounds = { diff --git a/packages/mermaid/src/directiveUtils.ts b/packages/mermaid/src/directiveUtils.ts new file mode 100644 index 000000000..2d2971a85 --- /dev/null +++ b/packages/mermaid/src/directiveUtils.ts @@ -0,0 +1,87 @@ +import * as configApi from './config'; + +import { log } from './logger'; +import { directiveSanitizer } from './utils'; + +let currentDirective: { type?: string; args?: any } | undefined = {}; + +export const parseDirective = function ( + p: any, + statement: string, + context: string, + type: string +): void { + log.debug('parseDirective is being called', statement, context, type); + try { + if (statement !== undefined) { + statement = statement.trim(); + switch (context) { + case 'open_directive': + currentDirective = {}; + break; + case 'type_directive': + if (!currentDirective) { + throw new Error('currentDirective is undefined'); + } + currentDirective.type = statement.toLowerCase(); + break; + case 'arg_directive': + if (!currentDirective) { + throw new Error('currentDirective is undefined'); + } + currentDirective.args = JSON.parse(statement); + break; + case 'close_directive': + handleDirective(p, currentDirective, type); + currentDirective = undefined; + break; + } + } + } catch (error) { + log.error( + `Error while rendering sequenceDiagram directive: ${statement} jison context: ${context}` + ); + // @ts-ignore: TODO Fix ts errors + log.error(error.message); + } +}; + +const handleDirective = function (p: any, directive: any, type: string): void { + log.info(`Directive type=${directive.type} with args:`, directive.args); + switch (directive.type) { + case 'init': + case 'initialize': { + ['config'].forEach((prop) => { + if (directive.args[prop] !== undefined) { + if (type === 'flowchart-v2') { + type = 'flowchart'; + } + directive.args[type] = directive.args[prop]; + delete directive.args[prop]; + } + }); + log.info('sanitize in handleDirective', directive.args); + directiveSanitizer(directive.args); + log.info('sanitize in handleDirective (done)', directive.args); + configApi.addDirective(directive.args); + break; + } + case 'wrap': + case 'nowrap': + if (p && p['setWrap']) { + p.setWrap(directive.type === 'wrap'); + } + break; + case 'themeCss': + log.warn('themeCss encountered'); + break; + default: + log.warn( + `Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify( + directive.args ? directive.args : {} + )}}%%`, + directive + ); + break; + } +}; diff --git a/packages/mermaid/src/docs.mts b/packages/mermaid/src/docs.mts index 846e92212..7f3ab4e8b 100644 --- a/packages/mermaid/src/docs.mts +++ b/packages/mermaid/src/docs.mts @@ -38,6 +38,8 @@ import type { Code, Root } from 'mdast'; import { posix, dirname, relative, join } from 'path'; import prettier from 'prettier'; import { remark } from 'remark'; +import remarkFrontmatter from 'remark-frontmatter'; +import remarkGfm from 'remark-gfm'; import chokidar from 'chokidar'; import mm from 'micromatch'; // @ts-ignore No typescript declaration file @@ -46,16 +48,28 @@ import flatmap from 'unist-util-flatmap'; const MERMAID_MAJOR_VERSION = ( JSON.parse(readFileSync('../mermaid/package.json', 'utf8')).version as string ).split('.')[0]; -const CDN_URL = 'https://unpkg.com'; // https://cdn.jsdelivr.net/npm +const CDN_URL = 'https://cdn.jsdelivr.net/npm'; // 'https://unpkg.com'; +const MERMAID_KEYWORD = 'mermaid'; +const MERMAID_CODE_ONLY_KEYWORD = 'mermaid-example'; + +// These keywords will produce both a mermaid diagram and a code block with the diagram source +const MERMAID_EXAMPLE_KEYWORDS = [MERMAID_KEYWORD, 'mmd', MERMAID_CODE_ONLY_KEYWORD]; // 'mmd' is an old keyword that used to be used + +// This keyword will only produce a mermaid diagram +const MERMAID_DIAGRAM_ONLY = 'mermaid-nocode'; + +// These will be transformed into block quotes +const BLOCK_QUOTE_KEYWORDS = ['note', 'tip', 'warning', 'danger']; + +// options for running the main command const verifyOnly: boolean = process.argv.includes('--verify'); const git: boolean = process.argv.includes('--git'); const watch: boolean = process.argv.includes('--watch'); const vitepress: boolean = process.argv.includes('--vitepress'); const noHeader: boolean = process.argv.includes('--noHeader') || vitepress; -// These paths are from the root of the mono-repo, not from the -// mermaid sub-directory +// These paths are from the root of the mono-repo, not from the mermaid subdirectory const SOURCE_DOCS_DIR = 'src/docs'; const FINAL_DOCS_DIR = vitepress ? 'src/vitepress' : '../../docs'; @@ -74,7 +88,7 @@ const filesTransformed: Set = new Set(); const generateHeader = (file: string): string => { // 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 sourcePathRelativeToGenerated = posix.join(relativePath, filePathFromRoot); return ` @@ -146,9 +160,26 @@ const readSyncedUTF8file = (filename: string): string => { return readFileSync(filename, 'utf8'); }; -const transformToBlockQuote = (content: string, type: string) => { - const title = type === 'warning' ? 'Warning' : 'Note'; - return `> **${title}** \n> ${content.replace(/\n/g, '\n> ')}`; +const blockIcons: Record = { + tip: '💡 ', + danger: '‼️ ', +}; + +const capitalize = (word: string) => word[0].toUpperCase() + word.slice(1); + +export const transformToBlockQuote = ( + content: string, + type: string, + customTitle?: string | null +) => { + if (vitepress) { + const vitepressType = type === 'note' ? 'info' : type; + return `::: ${vitepressType} ${customTitle || ''}\n${content}\n:::`; + } else { + const icon = blockIcons[type] || ''; + const title = `${icon}${customTitle || capitalize(type)}`; + return `> **${title}** \n> ${content.replace(/\n/g, '\n> ')}`; + } }; const injectPlaceholders = (text: string): string => @@ -158,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 return text.replace(includesRE, (m, m1) => { try { - const includePath = join(dirname(file), m1); + const includePath = join(dirname(file), m1).replaceAll('\\', '/'); const content = readSyncedUTF8file(includePath); includedFiles.add(changeToFinalDocDir(includePath)); return content; @@ -167,45 +198,116 @@ 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; +} + +/** + * Remark plugin that transforms mermaid repo markdown to Vitepress/GFM markdown. + * + * 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_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 + * + * If `addAutogeneratedWarning` is `true`, generates a header stating that this file is autogenerated. + * + * @returns plugin function for Remark + */ +export function transformMarkdownAst({ + originalFilename, + addAutogeneratedWarning, + removeYAML, +}: 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 (removeYAML) { + const firstNode = astWithTransformedBlocks.children[0]; + if (firstNode.type == 'yaml') { + // YAML is currently only used for Vitepress metadata, so we should remove it for GFM output + astWithTransformedBlocks.children.shift(); + } + } + + return astWithTransformedBlocks; + }; +} + /** * Transform a markdown file and write the transformed file to the directory for published * documentation * - * 1. Add a `mermaid-example` block before every `mermaid` or `mmd` block On the docsify site (one - * place where the documentation is published), this will show the code used for the mermaid - * diagram - * 2. Add the text that says the file is automatically generated - * 3. Use prettier to format the file Verify that the file has been changed and write out the changes + * 1. include any included files (copy and insert the source) + * 2. Add a `mermaid-example` block before every `mermaid` or `mmd` block On the main documentation site (one + * place where the documentation is published), this will show the code for the mermaid diagram + * 3. Transform blocks to block quotes as needed + * 4. Add the text that says the file is automatically generated + * 5. Use prettier to format the file. + * 6. Verify that the file has been changed and write out the changes * * @param file {string} name of the file that will be verified */ const transformMarkdown = (file: string) => { const doc = injectPlaceholders(transformIncludeStatements(file, readSyncedUTF8file(file))); - const ast: Root = remark.parse(doc); - const out = flatmap(ast, (c: Code) => { - if (c.type !== 'code' || !c.lang) { - return [c]; - } - // Convert mermaid code blocks to mermaid-example blocks - if (['mermaid', 'mmd', 'mermaid-example'].includes(c.lang)) { - c.lang = 'mermaid-example'; - return [c, Object.assign({}, c, { lang: 'mermaid' })]; - } - - // Transform codeblocks into block quotes. - if (['note', 'tip', 'warning'].includes(c.lang)) { - return [remark.parse(transformToBlockQuote(c.value, c.lang))]; - } - - return [c]; - }); - - let transformed = remark.stringify(out); - if (!noHeader) { - // Add the header to the start of the file - transformed = `${generateHeader(file)}\n${transformed}`; - } + let transformed = remark() + .use(remarkGfm) + .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') { // Skip transforming index if vitepress is enabled @@ -260,7 +362,7 @@ const transformHtml = (filename: string) => { }; const getGlobs = (globs: string[]): string[] => { - globs.push('!**/dist', '!**/redirect.spec.ts'); + globs.push('!**/dist', '!**/redirect.spec.ts', '!**/landing'); if (!vitepress) { globs.push('!**/.vitepress', '!**/vite.config.ts', '!src/docs/index.md'); } @@ -272,7 +374,7 @@ const getFilesFromGlobs = async (globs: string[]): Promise => { }; /** Main method (entry point) */ -(async () => { +const main = async () => { if (verifyOnly) { console.log('Verifying that all files are in sync with the source files'); } @@ -341,4 +443,6 @@ const getFilesFromGlobs = async (globs: string[]): Promise => { } }); } -})(); +}; + +void main(); diff --git a/packages/mermaid/src/docs.spec.ts b/packages/mermaid/src/docs.spec.ts new file mode 100644 index 000000000..50feaee6a --- /dev/null +++ b/packages/mermaid/src/docs.spec.ts @@ -0,0 +1,145 @@ +import { transformMarkdownAst, transformToBlockQuote } from './docs.mjs'; + +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'; + +afterEach(() => { + vi.restoreAllMocks(); +}); + +const originalFilename = 'example-input-filename.md'; +const remarkBuilder = remark().use(remarkFrontmatter, ['yaml']); // support YAML front-matter in Markdown + +describe('docs.mts', () => { + describe('transformMarkdownAst', () => { + describe('checks each AST node', () => { + it('does no transformation if there are no code blocks', async () => { + const contents = 'Markdown file contents\n'; + const result = ( + await remarkBuilder().use(transformMarkdownAst, { originalFilename }).process(contents) + ).toString(); + expect(result).toEqual(contents); + }); + + describe('is a code block', () => { + const beforeCodeLine = 'test\n'; + const diagram_text = 'graph\n A --> B\n'; + + describe('language = "mermaid-nocode"', () => { + const lang_keyword = 'mermaid-nocode'; + const contents = beforeCodeLine + '```' + lang_keyword + '\n' + diagram_text + '\n```\n'; + + it('changes the language to "mermaid"', async () => { + const result = ( + await remarkBuilder() + .use(transformMarkdownAst, { originalFilename }) + .process(contents) + ).toString(); + expect(result).toEqual( + beforeCodeLine + '\n' + '```' + 'mermaid' + '\n' + diagram_text + '\n```\n' + ); + }); + }); + + describe('language = "mermaid" | "mmd" | "mermaid-example"', () => { + const mermaid_keywords = ['mermaid', 'mmd', 'mermaid-example']; + + mermaid_keywords.forEach((lang_keyword) => { + const contents = + 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"', async () => { + const result = ( + await remarkBuilder() + .use(transformMarkdownAst, { originalFilename }) + .process(contents) + ).toString(); + expect(result).toEqual( + beforeCodeLine + + '\n' + + '```mermaid-example\n' + + diagram_text + + '\n```\n' + + '\n```mermaid\n' + + diagram_text + + '\n```\n' + ); + }); + }); + }); + + it('calls transformToBlockQuote with the node information', async () => { + const lang_keyword = 'note'; + const contents = + beforeCodeLine + '```' + lang_keyword + '\n' + 'This is the text\n' + '```\n'; + + const result = ( + await remarkBuilder().use(transformMarkdownAst, { originalFilename }).process(contents) + ).toString(); + 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', () => { + // TODO Is there a way to test this with --vitepress given as a process argument? + + describe('vitepress is not given as an argument', () => { + it('everything starts with "> " (= block quote)', () => { + const result = transformToBlockQuote('first line\n\n\nfourth line', 'blorfType'); + expect(result).toMatch(/> (.)*\n> first line(?:\n> ){3}fourth line/); + }); + + it('includes an icon if there is one for the type', () => { + const result = transformToBlockQuote( + 'first line\n\n\nfourth line', + 'danger', + 'Custom Title' + ); + expect(result).toMatch(/> \*\*‼️ Custom Title\*\* /); + }); + + describe('a custom title is given', () => { + it('custom title is surrounded in spaces, in bold', () => { + const result = transformToBlockQuote( + 'first line\n\n\nfourth line', + 'blorfType', + 'Custom Title' + ); + expect(result).toMatch(/> \*\*Custom Title\*\* /); + }); + }); + + describe.skip('no custom title is given', () => { + it('title is the icon and the capitalized type, in bold', () => { + const result = transformToBlockQuote('first line\n\n\nfourth line', 'blorf type'); + expect(result).toMatch(/> \*\*Blorf Type\*\* /); + }); + }); + }); + }); +}); diff --git a/packages/mermaid/src/docs/.vitepress/config.ts b/packages/mermaid/src/docs/.vitepress/config.ts index 216541d52..9b01fdbde 100644 --- a/packages/mermaid/src/docs/.vitepress/config.ts +++ b/packages/mermaid/src/docs/.vitepress/config.ts @@ -14,37 +14,34 @@ export default defineConfig({ lang: 'en-US', title: 'Mermaid', description: 'Create diagrams and visualizations using text and code.', - base: '/mermaid/', + base: '/', markdown: allMarkdownTransformers, - head: [['link', { rel: 'icon', type: 'image/x-icon', href: '/mermaid/favicon.ico' }]], + head: [['link', { rel: 'icon', type: 'image/x-icon', href: '/favicon.ico' }]], themeConfig: { nav: nav(), editLink: { pattern: 'https://github.com/mermaid-js/mermaid/edit/develop/packages/mermaid/src/docs/:path', text: 'Edit this page on GitHub', }, - sidebar: { '/': sidebarAll(), }, + socialLinks: [ + { icon: 'github', link: 'https://github.com/mermaid-js/mermaid' }, + { icon: 'slack', link: 'https://mermaid-talk.slack.com' }, + ], }, }); function nav() { return [ - { text: 'Intro', link: '/intro/', activeMatch: '/intro/' }, + { text: 'Docs', link: '/intro/', activeMatch: '/intro/' }, { - text: 'Configuration', - link: '/config/configuration', + text: 'Tutorials', + link: '/config/Tutorials', activeMatch: '/config/', }, - { text: 'Syntax', link: '/syntax/classDiagram', activeMatch: '/syntax/' }, - { text: 'Misc', link: '/misc/integrations', activeMatch: '/misc/' }, - { - text: 'Community', - link: '/community/n00b-overview', - activeMatch: '/community/', - }, + { text: 'Integrations', link: '/ecosystem/integrations', activeMatch: '/ecosystem/' }, { text: version, items: [ @@ -80,8 +77,8 @@ function sidebarAll() { ], }, ...sidebarSyntax(), + ...sidebarEcosystem(), ...sidebarConfig(), - ...sidebarMisc(), ...sidebarCommunity(), ]; } @@ -107,6 +104,7 @@ function sidebarSyntax() { { text: 'Gitgraph (Git) Diagram 🔥', link: '/syntax/gitgraph' }, { text: 'C4C Diagram (Context) Diagram 🦺⚠️', link: '/syntax/c4c' }, { text: 'Mindmaps 🔥', link: '/syntax/mindmap' }, + { text: 'Timeline 🔥', link: '/syntax/timeline' }, { text: 'Other Examples', link: '/syntax/examples' }, ], }, @@ -128,19 +126,20 @@ function sidebarConfig() { { text: 'Accessibility', link: '/config/accessibility' }, { text: 'Mermaid CLI', link: '/config/mermaidCLI' }, { text: 'Advanced usage', link: '/config/n00b-advanced' }, + { text: 'FAQ', link: '/config/faq' }, ], }, ]; } -function sidebarMisc() { +function sidebarEcosystem() { return [ { - text: '📚 Misc', + text: '📚 Ecosystem', collapsible: true, items: [ - { text: 'Use-Cases and Integrations', link: '/misc/integrations' }, - { text: 'FAQ', link: '/misc/faq' }, + { text: 'Showcases', link: '/ecosystem/showcases' }, + { text: 'Use-Cases and Integrations', link: '/ecosystem/integrations' }, ], }, ]; diff --git a/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue b/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue index 9ae9c9f3b..85c13393c 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue +++ b/packages/mermaid/src/docs/.vitepress/theme/Mermaid.vue @@ -20,11 +20,6 @@ const props = defineProps({ const svg = ref(null); let mut = null; -const mermaidConfig = { - securityLevel: 'loose', - startOnLoad: false, -}; - onMounted(async () => { mut = new MutationObserver(() => renderChart()); mut.observe(document.documentElement, { attributes: true }); @@ -58,9 +53,20 @@ onUnmounted(() => mut.disconnect()); const renderChart = async () => { console.log('rendering chart' + props.id + props.graph); const hasDarkClass = document.documentElement.classList.contains('dark'); - mermaidConfig.theme = hasDarkClass ? 'dark' : 'default'; + const mermaidConfig = { + securityLevel: 'loose', + startOnLoad: false, + theme: hasDarkClass ? 'dark' : 'default', + }; console.log({ mermaidConfig }); - svg.value = await render(props.id, decodeURIComponent(props.graph), mermaidConfig); + let svgCode = await render(props.id, decodeURIComponent(props.graph), mermaidConfig); + // This is a hack to force v-html to re-render, otherwise the diagram disappears + // when **switching themes** or **reloading the page**. + // The cause is that the diagram is deleted during rendering (out of Vue's knowledge). + // Because svgCode does NOT change, v-html does not re-render. + // This is not required for all diagrams, but it is required for c4c, mindmap and zenuml. + const salt = Math.random().toString(36).substring(7); + svg.value = `${svgCode} ${salt}`; }; diff --git a/packages/mermaid/src/docs/.vitepress/theme/custom.css b/packages/mermaid/src/docs/.vitepress/theme/custom.css index e1ef049cd..28ef8d338 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/custom.css +++ b/packages/mermaid/src/docs/.vitepress/theme/custom.css @@ -1,3 +1,6 @@ +@import url('https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css'); +@import url('https://cdn.jsdelivr.net/npm/@mdi/font@6.9.96/css/materialdesignicons.min.css'); + :root { --vp-c-brand: #ff3670; --vp-c-brand-light: #ff5e8c; diff --git a/packages/mermaid/src/docs/.vitepress/theme/index.ts b/packages/mermaid/src/docs/.vitepress/theme/index.ts index efb065fea..273880d91 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/index.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/index.ts @@ -10,15 +10,12 @@ export default { // register global components app.component('Mermaid', Mermaid); router.onBeforeRouteChange = (to) => { - if (router.route.path !== '/') { - return; - } try { const newPath = getRedirect(to); if (newPath) { console.log(`Redirecting to ${newPath} from ${window.location}`); // router.go isn't loading the ID properly. - window.location.href = `/mermaid/${newPath}`; + window.location.href = `/${newPath}`; } } catch (e) {} }; diff --git a/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts b/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts index b287346f9..c5b57f886 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts @@ -1,14 +1,7 @@ import mermaid, { type MermaidConfig } from 'mermaid'; -import mindmap from '@mermaid-js/mermaid-mindmap'; - -try { - await mermaid.registerExternalDiagrams([mindmap]); -} catch (e) { - console.error(e); -} export const render = async (id: string, code: string, config: MermaidConfig): Promise => { mermaid.initialize(config); - const svg = await mermaid.renderAsync(id, code); + const svg = await mermaid.render(id, code); return svg; }; diff --git a/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts b/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts index c26364108..ec0404264 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts @@ -5,33 +5,34 @@ import { expect, test } from 'vitest'; import { getRedirect } from './redirect'; test.each([ + // Old docs, localhost ['http://localhost:1234/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'], - ['https://mermaid-js.github.io/mermaid/#/./flowchart', 'syntax/flowchart.html'], - ['https://mermaid-js.github.io/mermaid/#/flowchart', 'syntax/flowchart.html'], - ['https://mermaid-js.github.io/mermaid/#flowchart', 'syntax/flowchart.html'], - ['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'], - ['https://mermaid-js.github.io/mermaid/#/./flowchart.md?id=my-id', 'syntax/flowchart.html#my-id'], + // Old docs, github pages + ['https://mermaid-js.github.io/mermaid/#/flowchart.md', 'syntax/flowchart.html'], // without dot + ['https://mermaid-js.github.io/mermaid/#/./flowchart', 'syntax/flowchart.html'], // with dot + ['https://mermaid-js.github.io/mermaid/#flowchart', 'syntax/flowchart.html'], // without slash + ['https://mermaid-js.github.io/mermaid/#/flowchart', 'syntax/flowchart.html'], // with slash + ['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'], // with id and dot [ - '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', ], - ['https://mermaid-js.github.io/mermaid/#/n00b-advanced', 'config/n00b-advanced.html'], - ['https://mermaid-js.github.io/mermaid/#/n00b-advanced.md', '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'], // 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', ], + // 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) => { expect(getRedirect(link)).toBe(path); }); - -test('should throw for invalid URL', () => { - // Not mermaid domain - expect(() => getRedirect('https://www.google.com')).toThrowError(); - - // Not `/mermaid/` path - expect(() => getRedirect('http://localhost/#/flowchart.md')).toThrowError(); -}); diff --git a/packages/mermaid/src/docs/.vitepress/theme/redirect.ts b/packages/mermaid/src/docs/.vitepress/theme/redirect.ts index ca4606be0..936d6f7e2 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/redirect.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/redirect.ts @@ -1,4 +1,4 @@ -export interface Redirect { +interface Redirect { path: string; id?: string; } @@ -7,14 +7,7 @@ export interface Redirect { * Extracts the base slug from the old URL. * @param link - The old URL. */ -const getBaseFile = (link: string): Redirect => { - const url = new URL(link); - if ( - (url.hostname !== 'mermaid-js.github.io' && url.hostname !== 'localhost') || - url.pathname !== '/mermaid/' - ) { - throw new Error('Not mermaidjs url'); - } +const getBaseFile = (url: URL): Redirect => { const [path, params, ...rest] = url.hash .toLowerCase() .replace('.md', '') @@ -31,14 +24,14 @@ const getBaseFile = (link: string): Redirect => { return { path, id }; }; -const redirectMap: Record = { +const idRedirectMap: Record = { '8.6.0_docs': '', accessibility: 'config/theming', breakingchanges: '', c4c: 'syntax/c4c', classdiagram: 'syntax/classDiagram', configuration: 'config/configuration', - demos: 'misc/integrations', + demos: 'ecosystem/integrations', development: 'community/development', directives: 'config/directives', entityrelationshipdiagram: 'syntax/entityRelationshipDiagram', @@ -47,7 +40,7 @@ const redirectMap: Record = { flowchart: 'syntax/flowchart', gantt: 'syntax/gantt', gitgraph: 'syntax/gitgraph', - integrations: 'misc/integrations', + integrations: 'ecosystem/integrations', 'language-highlight': '', markdown: '', mermaidapi: 'config/usage', @@ -75,15 +68,25 @@ const redirectMap: Record = { 'user-journey': 'syntax/userJourney', }; +const urlRedirectMap: Record = { + '/misc/faq.html': 'configure/faq.html', +}; + /** * * @param link - The old documentation URL. * @returns The new documentation path. */ export const getRedirect = (link: string): string | undefined => { - const { path, id } = getBaseFile(link); - if (!(path in redirectMap)) { - return; + const url = new URL(link); + // Redirects for deprecated vitepress URLs + 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}` : ''}`; }; diff --git a/packages/mermaid/src/docs/community/newDiagram.md b/packages/mermaid/src/docs/community/newDiagram.md index 74026b3ff..75e17e4c9 100644 --- a/packages/mermaid/src/docs/community/newDiagram.md +++ b/packages/mermaid/src/docs/community/newDiagram.md @@ -8,8 +8,8 @@ This would be to define a jison grammar for the new diagram type. That should st For instance: -- the flowchart starts with the keyword graph. -- the sequence diagram starts with the keyword sequenceDiagram +- the flowchart starts with the keyword _graph_ +- the sequence diagram starts with the keyword _sequenceDiagram_ #### Store data found during parsing @@ -55,7 +55,12 @@ Place the renderer in the diagram folder. ### Step 3: Detection of the new diagram type -The second thing to do is to add the capability to detect the new new diagram to type to the detectType in utils.js. The detection should return a key for the new diagram type. +The second thing to do is to add the capability to detect the new diagram to type to the detectType in utils.js. The detection should return a key for the new diagram type. +[This key will be used to as the aria roledescription](#aria-roledescription), so it should be a word that clearly describes the diagram type. +For example, if your new diagram use a UML deployment diagram, a good key would be "UMLDeploymentDiagram" because assistive technologies such as a screen reader +would voice that as "U-M-L Deployment diagram." Another good key would be "deploymentDiagram" because that would be voiced as "Deployment Diagram." A bad key would be "deployment" because that would not sufficiently describe the diagram. + +Note that the diagram type key does not have to be the same as the diagram keyword chosen for the [grammar](#grammar), but it is helpful if they are the same. ### Step 4: The final piece - triggering the rendering @@ -163,19 +168,23 @@ It is probably a good idea to keep the handling similar to this in your new diag ## Accessibility -The syntax for adding title and description looks like this: +Mermaid automatically adds the following accessibility information for the diagram SVG HTML element: -``` -accTitle: The title -accDescr: The description +- aria-roledescription +- accessible title +- accessible description -accDescr { - Syntax for a description text - written on multiple lines. -} -``` +### aria-roledescription -In a similar way to the directives the jison syntax are quite similar between the diagrams. +The aria-roledescription is automatically set to [the diagram type](#step-3--detection-of-the-new-diagram-type) and inserted into the SVG element. + +See [the definition of aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) in [the Accessible Rich Internet Applications W3 standard.](https://www.w3.org/WAI/standards-guidelines/aria/) + +### accessible title and description + +The syntax for accessible titles and descriptions is described in [the Accessibility documenation section.](../config/accessibility.md) + +In a similar way to the directives, the jison syntax are quite similar between the diagrams. ```jison @@ -213,18 +222,7 @@ import { } from '../../commonDb'; ``` -For rendering the accessibility tags you have again an existing function you can use. - -**In the renderer:** - -```js -import addSVGAccessibilityFields from '../../accessibility'; - -/* ... */ - -// Adds title and description to the flow chart -addSVGAccessibilityFields(parser.yy, svg, id); -``` +The accessibility title and description are inserted into the SVG element in the `render` function in mermaidAPI. ## Theming diff --git a/packages/mermaid/src/docs/config/8.6.0_docs.md b/packages/mermaid/src/docs/config/8.6.0_docs.md index bc19e08d5..7b470eab8 100644 --- a/packages/mermaid/src/docs/config/8.6.0_docs.md +++ b/packages/mermaid/src/docs/config/8.6.0_docs.md @@ -2,7 +2,7 @@ ## [New Mermaid Live-Editor Beta](https://mermaid-js.github.io/docs/mermaid-live-editor-beta/#/edit/eyJjb2RlIjoiJSV7aW5pdDoge1widGhlbWVcIjogXCJmb3Jlc3RcIiwgXCJsb2dMZXZlbFwiOiAxIH19JSVcbmdyYXBoIFREXG4gIEFbQ2hyaXN0bWFzXSAtLT58R2V0IG1vbmV5fCBCKEdvIHNob3BwaW5nKVxuICBCIC0tPiBDe0xldCBtZSB0aGlua31cbiAgQyAtLT58T25lfCBEW0xhcHRvcF1cbiAgQyAtLT58VHdvfCBFW2lQaG9uZV1cbiAgQyAtLT58VGhyZWV8IEZbZmE6ZmEtY2FyIENhcl1cblx0XHQiLCJtZXJtYWlkIjp7InRoZW1lIjoiZGFyayJ9fQ) -## [CDN](https://unpkg.com/mermaid/) +## [CDN](https://www.jsdelivr.com/package/npm/mermaid) With version 8.6.0 comes the release of directives for mermaid, a new system for modifying configurations, with the aim of establishing centralized, sane defaults and simple implementation. diff --git a/packages/mermaid/src/docs/config/accessibility.md b/packages/mermaid/src/docs/config/accessibility.md index ade20a839..67fb090b8 100644 --- a/packages/mermaid/src/docs/config/accessibility.md +++ b/packages/mermaid/src/docs/config/accessibility.md @@ -4,83 +4,152 @@ Now with Mermaid library in much wider use, we have started to work towards more accessible features, based on the feedback from the community. -To begin with, we have added a new feature to Mermaid library, which is to support accessibility options, **Accessibility Title** and **Accessibility Description**. +Adding accessibility means that the rich information communicated by visual diagrams can be made available to those using assistive technologies (and of course to search engines). +[Read more about Accessible Rich Internet Applications and the W3 standards.](https://www.w3.org/WAI/standards-guidelines/aria/) -This support for accessibility options is available for all the diagrams/chart types. Also, we have tired to keep the same format for the accessibility options, so that it is easy to understand and maintain. +Mermaid will automatically insert the [aria-roledescription](#aria-roledescription) and, if provided in the diagram text by the diagram author, the [accessible title and description.](#accessible-title-and-description) -## Defining Accessibility Options +### aria-roledescription -### Single line accessibility values +The [aria-roledescription](https://www.w3.org/TR/wai-aria-1.1/#aria-roledescription) for the SVG HTML element is set to the diagram type key. (Note this may be slightly different than the keyword used for the diagram in the diagram text.) -The diagram authors can now add the accessibility options in the diagram definition, using the `accTitle` and `accDescr` keywords, where each keyword is followed by `:` and the string value for title and description like: - -- `accTitle: "Your Accessibility Title"` or -- `accDescr: "Your Accessibility Description"` - -**When these two options are defined, they will add a corresponding `` and `<desc>` tag in the SVG.** - -Let us take a look at the following example with a flowchart diagram: - -```mermaid-example - graph LR - accTitle: Big decisions - accDescr: Flow chart of the decision making process - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] +For example: The diagram type key for a state diagram is "stateDiagram". Here (a part of) the HTML of the SVG tag that shows the automatically inserted aria-roledscription set to "stateDiagram". _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ +```html +<svg + aria-roledescription="stateDiagram" + class="statediagram" + xmlns="http://www.w3.org/2000/svg" + width="100%" + id="mermaid-1668720491568" +></svg> ``` -See in the code snippet above, the `accTitle` and `accDescr` are defined in the diagram definition. They result in the following tags in SVG code: +### Accessible Title and Description -![Accessibility options rendered inside SVG](img/accessibility-div-example.png) +Support for accessible titles and descriptions is available for all diagrams/chart types. We have tried to keep the same keywords and format for all diagrams so that it is easy to understand and maintain. -### Multi-line Accessibility title/description +The accessible title and description will add `<title>` and `<desc>` elements within the SVG element and the [aria-labelledby](https://www.w3.org/TR/wai-aria/#aria-labelledby) and [aria-describedby](https://www.w3.org/TR/wai-aria/#aria-describedby) attributes in the SVG tag. -You can also define the accessibility options in a multi-line format, where the keyword is followed by opening curly bracket `{` and then multiple lines, followed by a closing `}`. +Here is HTML that is generated, showing that the SVG element is labelled by the accessible title (id = `chart-title-mermaid-1668725057758`) +and described by the accessible description (id = `chart-desc-mermaid-1668725057758` ); +and the accessible title element (text = "This is the accessible title") +and the accessible description element (text = "This is an accessible description"). -`accTitle: My single line title value` (**_single line format_**) +_(Note that some of the SVG attributes and the SVG contents are omitted for clarity.)_ -vs +```html +<svg + aria-labelledby="chart-title-mermaid-1668725057758" + aria-describedby="chart-desc-mermaid-1668725057758" + xmlns="http://www.w3.org/2000/svg" + width="100%" + id="mermaid-1668725057758" +> + <title id="chart-title-mermaid-1668725057758">This is the accessible title + This is an accessible description + +``` -`accDescr: { My multi-line description of the diagram }` (**_multi-line format_**) +Details for the syntax follow. -Let us look at it in the following example, with same flowchart: +#### accessible title + +The **accessible title** is specified with the **accTitle** _keyword_, followed by a colon (`:`), and the string value for the title. +The string value ends at the end of the line. (It can only be a single line.) + +Ex: `accTitle: This is a single line title` + +See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. + +#### accessible description + +An accessible description can be 1 line long (a single line) or many lines long. + +The **single line accessible description** is specified with the **accDescr** _keyword_, followed by a colon (`:`), followed by the string value for the description. + +Ex: `accDescr: This is a single line description.` + +A **multiple line accessible description** _does not have a colon (`:`) after the accDescr keyword_ and is surrounded by curly brackets (`{}`). + +Ex: + +```markdown +accDescr { +This is a multiple line accessible description. +It does not have a colon and is surrounded by curly brackets. +} +``` + +See [the accTitle and accDescr usage examples](#acctitle-and-accdescr-usage-examples) for how this can be used in a diagram and the resulting HTML generated. + +#### accTitle and accDescr Usage Examples + +- Flowchart with the accessible title "Big Decisions" and the single-line accessible description "Bob's Burgers process for making big decisions" ```mermaid-example - graph LR - accTitle: Big decisions + graph LR + accTitle: Big Decisions + accDescr: Bob's Burgers process for making big decisions + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] +``` +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ + +```html + + Big decisions + Bob's Burgers process for making big decisions + +``` + +- Flowchart with the accessible title "Bob's Burger's Making Big Decisions" and the multiple line accessible description "The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision." + +```mermaid-example + graph LR + accTitle: Bob's Burger's Making Big Decisions accDescr { - My multi-line description - of the diagram - } - - A[Hard] -->|Text| B(Round) - B --> C{Decision} - C -->|One| D[Result 1] - + The official Bob's Burgers corporate processes that are used + for making very, very big decisions. + This is actually a very simple flow: identify the big decision and then make the big decision. + } + A[Identify Big Descision] --> B{Make Big Decision} + B --> D[Be done] ``` -See in the code snippet above, the `accTitle` and `accDescr` are defined in the diagram definition. They result in the following tags in SVG code: +Here is the HTML generated for the SVG element: _(Note that some of the SVG attributes and the SVG contents are omitted for clarity.):_ -![Accessibility options rendered inside SVG](img/accessibility-div-example-2.png) - -### Sample Code Snippet for other diagram types - -#### Sequence Diagram - -```mermaid-example - sequenceDiagram - accTitle: My Sequence Diagram - accDescr: My Sequence Diagram Description - - Alice->>John: Hello John, how are you? - John-->>Alice: Great! - Alice-)John: See you later! +```html + + Big decisions + + The official Bob's Burgers corporate processes that are used for making very, very big + decisions. This is actually a very simple flow: identify the big decision and then make the big + decision. + + ``` -#### Class Diagram +#### Sample Code Snippets for other diagram types + +##### Class Diagram ```mermaid-example classDiagram @@ -90,18 +159,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the Vehicle <|-- Car ``` -#### State Diagram - -```mermaid-example - stateDiagram - accTitle: My State Diagram - accDescr: My State Diagram Description - - s1 --> s2 - -``` - -#### Entity Relationship Diagram +##### Entity Relationship Diagram ```mermaid-example erDiagram @@ -114,25 +172,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### User Journey Diagram - -```mermaid-example - journey - accTitle: My User Journey Diagram - accDescr: My User Journey Diagram Description - - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me - -``` - -#### Gantt Chart +##### Gantt Chart ```mermaid-example gantt @@ -150,7 +190,27 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Pie Chart +##### Gitgraph + +```mermaid-example + gitGraph + accTitle: My Gitgraph Accessibility Title + accDescr: My Gitgraph Accessibility Description + + commit + commit + branch develop + checkout develop + commit + commit + checkout main + merge develop + commit + commit + +``` + +##### Pie Chart ```mermaid-example pie @@ -165,7 +225,7 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Requirement Diagram +##### Requirement Diagram ```mermaid-example requirementDiagram @@ -187,22 +247,43 @@ See in the code snippet above, the `accTitle` and `accDescr` are defined in the ``` -#### Gitgraph +##### Sequence Diagram ```mermaid-example - gitGraph - accTitle: My Gitgraph Accessibility Title - accDescr: My Gitgraph Accessibility Description + sequenceDiagram + accTitle: My Sequence Diagram + accDescr: My Sequence Diagram Description - commit - commit - branch develop - checkout develop - commit - commit - checkout main - merge develop - commit - commit + Alice->>John: Hello John, how are you? + John-->>Alice: Great! + Alice-)John: See you later! +``` + +##### State Diagram + +```mermaid-example + stateDiagram + accTitle: My State Diagram + accDescr: My State Diagram Description + + s1 --> s2 + +``` + +##### User Journey Diagram + +```mermaid-example + journey + accTitle: My User Journey Diagram + accDescr: My User Journey Diagram Description + + title My working day + section Go to work + Make tea: 5: Me + Go upstairs: 3: Me + Do work: 1: Me, Cat + section Go home + Go downstairs: 5: Me + Sit down: 5: Me ``` diff --git a/packages/mermaid/src/docs/config/directives.md b/packages/mermaid/src/docs/config/directives.md index bc74ad309..ac57e6d21 100644 --- a/packages/mermaid/src/docs/config/directives.md +++ b/packages/mermaid/src/docs/config/directives.md @@ -26,8 +26,8 @@ Mermaid basically supports two types of configuration options to be overridden b **NOTE:** These options listed here are not all the configuration options. To get hold of all the configuration options, please refer to the [defaultConfig.ts](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/defaultConfig.ts) in the source code. -``` -Soon we plan to publish a complete list of top-level configurations & all the diagram specific configurations, with their possible values in the docs +```note +We plan to publish a complete list of top-level configurations & all the diagram specific configurations, with their possible values in the docs soon. ``` ## Declaring directives diff --git a/packages/mermaid/src/docs/misc/faq.md b/packages/mermaid/src/docs/config/faq.md similarity index 100% rename from packages/mermaid/src/docs/misc/faq.md rename to packages/mermaid/src/docs/config/faq.md diff --git a/packages/mermaid/src/docs/config/n00b-advanced.md b/packages/mermaid/src/docs/config/n00b-advanced.md index 1e6546f5c..2932faa48 100644 --- a/packages/mermaid/src/docs/config/n00b-advanced.md +++ b/packages/mermaid/src/docs/config/n00b-advanced.md @@ -4,8 +4,8 @@ A more condensed html code can be achieved by embedding the mermaid code in its own .js file, which is referenced like so: -``` -stuff stuff +```html +... @@ -13,12 +13,12 @@ stuff stuff The actual mermaid file could for example look like this: +```javascript +mermaid content ... ``` -mermaid content... -``` - ---- ## mermaid configuration options -... +```markdown +(coming soon) +``` diff --git a/packages/mermaid/src/docs/config/theming.md b/packages/mermaid/src/docs/config/theming.md index 78f3546cc..da021f7f8 100644 --- a/packages/mermaid/src/docs/config/theming.md +++ b/packages/mermaid/src/docs/config/theming.md @@ -1,30 +1,26 @@ # Theme Configuration -With Version 8.7.0 Mermaid comes out with a system for dynamic and integrated configuration of themes. The intent is to increase the customizability and ease of styling for mermaid diagrams. +Dynamic and integrated theme configuration was introduced in Mermaid version 8.7.0. -The theme can be altered by changing the root level variable `theme` variable in the configuration. To change it for the whole site you must use the `initialize` call. To do it for just for a single diagram you can use the `%%init%%` directive +Themes can now be customized at the site-wide level, or on individual Mermaid diagrams. For site-wide theme customization, the `initialize` call is used. For diagram specific customization, the `init` directive is used. -Themes follow and build upon the Levels of Configuration, and employ `directives` to modify and create custom configurations, as they were introduced in Version [8.6.0](./8.6.0_docs.md). +## Available Themes -## Deployable Themes +1. [**default**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-default.js) - This is the default theme for all diagrams. -The following are a list of **Deployable themes**, sample `%%init%%` directives and `initialize` calls. +2. [**neutral**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-neutral.js) - This theme is great for black and white documents that will be printed. -1. **base**- Designed to be modified, as the name implies it is supposed to be used as the base for making custom themes. +3. [**dark**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-dark.js) - This theme goes well with dark-colored elements or dark-mode. -2. **forest**- A theme full of light greens that is easy on the eyes. +4. [**forest**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-forest.js) - This theme contains shades of green. -3. **dark**- A theme that would go well with other dark-colored elements. +5. [**base**](https://github.com/mermaid-js/mermaid/blob/develop/packages/mermaid/src/themes/theme-base.js) - This is the only theme that can be modified. Use this theme as the base for customizations. -4. **default**- The default theme for all diagrams. +## Site-wide Theme -5. **neutral**- The theme to be used for black and white printing. +To customize themes site-wide, call the `initialize` method on the `mermaidAPI`. -## Site-wide Themes - -Site-wide themes are declared via `initialize` by site owners. - -Example of `Initialize` call setting `theme` to `base`: +Example of `initialize` call setting `theme` to `base`: ```javascript mermaidAPI.initialize({ @@ -33,22 +29,52 @@ mermaidAPI.initialize({ }); ``` -**Notes**: Only site owners can use the `mermaidAPI.initialize` call, to set values. Site-Users will have to use `%%init%%` to modify or create the theme for their diagrams. +## Diagram-specific Themes -## Themes at the Local or Current Level +To customize the theme of an individual diagram, use the `init` directive. -When Generating a diagram using on a webpage that supports mermaid. It is also possible to override site-wide theme settings locally, for a specific diagram, using directives, as long as it is not prohibited by the `secure` array. +Example of `init` directive setting the `theme` to `forest`: -```mmd -%%{init: {'theme':'base'}}%% +```mermaid-example +%%{init: {'theme':'forest'}}%% graph TD a --> b ``` -Here is an example of how `%%init%%` can set the theme to 'base', this assumes that `themeVariables` are set to default: +```mermaid +%%{init: {'theme':'forest'}}%% + graph TD + a --> b +``` + +> **Reminder**: the only theme that can be customed is the `base` theme. The following section covers how to use `themeVariables` for customizations. + +## Customizing Themes with `themeVariables` + +To make a custom theme, modify `themeVariables` via `init`. + +You will need to use the [base](#available-themes) theme as it is the only modifiable theme. + +| Parameter | Description | Type | Properties | +| -------------- | ------------------------------------ | ------ | --------------------------------------------------------------------------------------------------- | +| themeVariables | Modifiable with the `init` directive | Object | `primaryColor`, `primaryTextColor`, `lineColor` ([see full list](#theme-variables-reference-table)) | + +Example of modifying `themeVariables` using the `init` directive: ```mermaid-example -%%{init: {'theme':'base'}}%% +%%{ + init: { + 'theme': 'base', + 'themeVariables': { + 'primaryColor': '#BB2528', + 'primaryTextColor': '#fff', + 'primaryBorderColor': '#7C0000', + 'lineColor': '#F8B229', + 'secondaryColor': '#006100', + 'tertiaryColor': '#fff' + } + } +}%% graph TD A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me think} @@ -65,20 +91,20 @@ Here is an example of how `%%init%%` can set the theme to 'base', this assumes t end ``` -# List of Themes - -# Customizing Themes with `themeVariables` - -The easiest way to make a custom theme is to start with the base theme, and just modify theme variables through `themeVariables`, via `%%init%%`. - -| Parameter | Description | Type | Required | Objects contained | -| -------------- | ------------------------------------------------------------------ | ----- | -------- | ---------------------------------- | -| themeVariables | Array containing objects, modifiable with the `%%init%%` directive | Array | Required | primaryColor, lineColor, textColor | - -**Here is an example of overriding `primaryColor` through `themeVariables` and giving everything a different look, using `%%init%%`.** - -```mermaid-example -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%% +```mermaid +%%{ + init: { + 'theme': 'base', + 'themeVariables': { + 'primaryColor': '#BB2528', + 'primaryTextColor': '#fff', + 'primaryBorderColor': '#7C0000', + 'lineColor': '#F8B229', + 'secondaryColor': '#006100', + 'tertiaryColor': '#fff' + } + } +}%% graph TD A[Christmas] -->|Get money| B(Go shopping) B --> C{Let me think} @@ -95,372 +121,90 @@ The easiest way to make a custom theme is to start with the base theme, and just end ``` -**Notes:** -Leaving it empty will set all variable values to default. +## Color and Color Calculation -## Color and Color Calculation: +To ensure diagram readability, the default value of certain variables is calculated or derived from other variables. For example, `primaryBorderColor` is derived from the `primaryColor` variable. So if the `primaryColor` variable is customized, Mermaid will adjust `primaryBorderColor` automatically. Adjustments can mean a color inversion, a hue change, a darkening/lightening by 10%, etc. -Color definitions have certain interactions in mermaid, this is in order to ensure visibility for diagrams. Mermaid will adjust some variables automatically, when colors are changed in order to compensate and maintain readability. +The theming engine will only recognize hex colors and not color names. So, the value `#ff0000` will work, but `red` will not. -**The Default Value Column** to the right of the Variable column will denote the Variable paired/associated with the Variable on the left and the nature of this pairing or association. If it for instance says primaryColor it means that it gets primaryColor as default value. If it says "based on primaryColor" it means that it is calculated/ derived from primaryColor. This calculation can be primary color inversion, a change of hue, darkening or lightening by 10%, etc. +## Theme Variables -You can create your own themes, by changing any of the given variables below. If you are using a dark background, set dark mode to true to adjust the colors. It is possible to override the calculations using the variable names below, with `%%init%%` if you wish to style it differently. +| Variable | Default value | Description | +| -------------------- | ---------------------------------- | -------------------------------------------------------------------------------------------------------------------------------- | +| darkMode | false | Affects how derived colors are calculated. Set value to `true` for dark mode. | +| background | #f4f4f4 | Used to calculate color for items that should either be background colored or contrasting to the background | +| fontFamily | trebuchet ms, verdana, arial | | +| fontSize | 16px | Font size in pixels | +| primaryColor | #fff4dd | Color to be used as background in nodes, other colors will be derived from this | +| primaryTextColor | calculated from darkMode #ddd/#333 | Color to be used as text color in nodes using `primaryColor` | +| secondaryColor | calculated from primaryColor | | +| primaryBorderColor | calculated from primaryColor | Color to be used as border in nodes using `primaryColor` | +| secondaryBorderColor | calculated from secondaryColor | Color to be used as border in nodes using `secondaryColor` | +| secondaryTextColor | calculated from secondaryColor | Color to be used as text color in nodes using `secondaryColor` | +| tertiaryColor | calculated from primaryColor | | +| tertiaryBorderColor | calculated from tertiaryColor | Color to be used as border in nodes using `tertiaryColor` | +| tertiaryTextColor | calculated from tertiaryColor | Color to be used as text color in nodes using `tertiaryColor` | +| noteBkgColor | #fff5ad | Color used as background in notes | +| noteTextColor | #333 | Text color in note rectangles | +| noteBorderColor | calculated from noteBkgColor | Border color in note rectangles | +| lineColor | calculated from background | | +| textColor | calculated from primaryTextColor | Text in diagram over the background for instance text on labels and on signals in sequence diagram or the title in Gantt diagram | +| mainBkg | calculated from primaryColor | Background in flowchart objects like rects/circles, class diagram classes, sequence diagram etc | +| errorBkgColor | tertiaryColor | Color for syntax error message | +| errorTextColor | tertiaryTextColor | Color for syntax error message | -## Theme Variables Reference Table +## Flowchart Variables -```note -Variables that are unique to some diagrams can be affected by changes in Theme Variables -``` +| Variable | Default value | Description | +| ------------------- | ------------------------------ | --------------------------- | +| nodeBorder | primaryBorderColor | Node Border Color | +| clusterBkg | tertiaryColor | Background in subgraphs | +| clusterBorder | tertiaryBorderColor | Cluster Border Color | +| defaultLinkColor | lineColor | Link Color | +| titleColor | tertiaryTextColor | Title Color | +| edgeLabelBackground | calculated from secondaryColor | | +| nodeTextColor | primaryTextColor | Color for text inside Nodes | -| Variable | Default/Base/Factor value | Calc | Description | -| -------------------- | ------------------------------ | ---- | -------------------------------------------------------------------------------------------------------------------------------- | -| darkMode | false | | Boolean Value that dictates how to calculate colors. "true" will activate darkmode. | -| background | #f4f4f4 | | Used to calculate color for items that should either be background colored or contrasting to the background. | -| fontFamily | "trebuchet ms", verdana, arial | | | -| fontSize | 16px | | Font Size, in pixels | -| primaryColor | #fff4dd | | Color to be used as background in nodes, other colors will be derived from this | -| primaryBorderColor | based on primaryColor | \* | Color to be used as border in nodes using primaryColor | -| primaryTextColor | based on darkMode #ddd/#333 | \* | Color to be used as text color in nodes using primaryColor | -| secondaryColor | based on primaryColor | \* | | -| secondaryBorderColor | based on secondaryColor | \* | Color to be used as border in nodes using secondaryColor | -| secondaryTextColor | based on secondaryColor | \* | Color to be used as text color in nodes using secondaryColor | -| tertiaryColor | based on primaryColor | \* | | -| tertiaryBorderColor | based on tertiaryColor | \* | Color to be used as border in nodes using tertiaryColor | -| tertiaryTextColor | based on tertiaryColor | \* | Color to be used as text color in nodes using tertiaryColor | -| noteBkgColor | #fff5ad | | Color used as background in notes | -| noteTextColor | #333 | | Text color in note rectangles. | -| noteBorderColor | based on noteBkgColor | \* | Border color in note rectangles. | -| lineColor | based on background | \* | | -| textColor | based on primaryTextColor | \* | Text in diagram over the background for instance text on labels and on signals in sequence diagram or the title in gantt diagram | -| mainBkg | based on primaryColor | \* | Background in flowchart objects like rects/circles, class diagram classes, sequence diagram etc | -| errorBkgColor | tertiaryColor | \* | Color for syntax error message | -| errorTextColor | tertiaryTextColor | \* | Color for syntax error message | +## Sequence Diagram Variables -# What follows are Variables, specific to different diagrams and charts. +| Variable | Default value | Description | +| --------------------- | ------------------------------ | --------------------------- | +| actorBkg | mainBkg | Actor Background Color | +| actorBorder | primaryBorderColor | Actor Border Color | +| actorTextColor | primaryTextColor | Actor Text Color | +| actorLineColor | grey | Actor Line Color | +| signalColor | textColor | Signal Color | +| signalTextColor | textColor | Signal Text Color | +| labelBoxBkgColor | actorBkg | Label Box Background Color | +| labelBoxBorderColor | actorBorder | Label Box Border Color | +| labelTextColor | actorTextColor | Label Text Color | +| loopTextColor | actorTextColor | Loop Text Color | +| activationBorderColor | calculated from secondaryColor | Activation Border Color | +| activationBkgColor | secondaryColor | Activation Background Color | +| sequenceNumberColor | calculated from lineColor | Sequence Number Color | -## Some Theme Variables serve as, or affect the Default Values for Specific Diagram Variables, unless changed using `%%init%%` . +## State Colors -## Flowchart +| Variable | Default value | Description | +| ------------- | ---------------- | -------------------------------------------- | +| labelColor | primaryTextColor | | +| altBackground | tertiaryColor | Used for background in deep composite states | -| Variable | Default/ Associated Value | Calc | Description | -| ------------------- | ------------------------- | ---- | ---------------------------- | -| nodeBorder | primaryBorderColor | \* | Node Border Color | -| clusterBkg | tertiaryColor | \* | Background in subgraphs | -| clusterBorder | tertiaryBorderColor | \* | Cluster Border Color | -| defaultLinkColor | lineColor | \* | Link Color | -| titleColor | tertiaryTextColor | \* | Title Color | -| edgeLabelBackground | based on secondaryColor | \* | | -| nodeTextColor | primaryTextColor | \* | Color for text inside Nodes. | +## Class Colors -# sequence diagram +| Variable | Default value | Description | +| --------- | ------------- | ------------------------------- | +| classText | textColor | Color of Text in class diagrams | -| name | Default value | Calc | Description | -| --------------------- | ----------------------- | ---- | --------------------------- | -| actorBorder | primaryBorderColor | \* | Actor Border Color | -| actorBkg | mainBkg | \* | Actor Background Color | -| actorTextColor | primaryTextColor | \* | Actor Text Color | -| actorLineColor | grey | \* | Actor Line Color | -| signalColor | textColor | \* | Signal Color | -| signalTextColor | textColor | \* | Signal Text Color | -| labelBoxBkgColor | actorBkg | \* | Label Box Background Color | -| labelBoxBorderColor | actorBorder | \* | Label Box Border Color | -| labelTextColor | actorTextColor | \* | Label Text Color | -| loopTextColor | actorTextColor | \* | Loop ext Color | -| activationBorderColor | based on secondaryColor | \* | Activation Border Color | -| activationBkgColor | secondaryColor | \* | Activation Background Color | -| sequenceNumberColor | based on lineColor | \* | Sequence Number Color | +## User Journey Colors -# state colors - -| name | Default value | Calc | Description | -| ------------- | ---------------- | ---- | -------------------------------------------- | -| labelColor | primaryTextColor | \* | | -| altBackground | tertiaryColor | \* | Used for background in deep composite states | - -# class colors - -| name | Default value | Calc | Description | -| --------- | ------------- | ---- | ------------------------------- | -| classText | textColor | \* | Color of Text in class diagrams | - -# User journey colors - -| name | Default value | Calc | Description | -| --------- | ----------------------- | ---- | --------------------------------------- | -| fillType0 | primaryColor | \* | Fill for 1st section in journey diagram | -| fillType1 | secondaryColor | \* | Fill for 2nd section in journey diagram | -| fillType2 | based on primaryColor | \* | Fill for 3rd section in journey diagram | -| fillType3 | based on secondaryColor | \* | Fill for 4th section in journey diagram | -| fillType4 | based on primaryColor | \* | Fill for 5th section in journey diagram | -| fillType5 | based on secondaryColor | \* | Fill for 6th section in journey diagram | -| fillType6 | based on primaryColor | \* | Fill for 7th section in journey diagram | -| fillType7 | based on secondaryColor | \* | Fill for 8th section in journey diagram | - -\*\*Notes: Values are meant to create an alternating look. - -# Here is an example of overriding `primaryColor` and giving everything a different look, using `%%init%%`. - -```mermaid-example -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ff0000'}}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -\*\*This got a bit too dark and bit too colorful. With some easy steps this can be fixed: - -- Make the primary color a little lighter -- set the tertiary color to a reddish shade as well -- make the edge label background differ from the subgraph by setting the edgeLabelBackground - -```mermaid-example -%%{init: {'theme': 'base', 'themeVariables': { 'primaryColor': '#ffcccc', 'edgeLabelBackground':'#ffffee', 'tertiaryColor': '#fff0f0'}}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -The Theming Engine does not admit color codes and will only accept proper color values. Color Names is not supported so for instance, the color value 'red' will not work, but '#ff0000' will work. - -# Common theming activities - -## How to change the color of the arrows - -# Examples: - -When adjusting a theme it might be helpful to look at how your preferred theme goes with the diagrams, to evaluate whether everything is visible and looks good. -In the following examples, the directive `init` is used, with the `theme` being declared as `base`. For more information on using directives, read the documentation for [Version 8.6.0](/8.6.0_docs.md) - -### Flowchart - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - graph TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[/Another/] - C ==>|One| D[Laptop] - C -->|Two| E[iPhone] - C -->|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -### Flowchart (beta) - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - flowchart TD - A[Christmas] -->|Get money| B(Go shopping) - B --> C{Let me think} - B --> G[Another] - C ==>|One| D[Laptop] - C x--x|Two| E[iPhone] - C o--o|Three| F[fa:fa-car Car] - subgraph section - C - D - E - F - G - end -``` - -### Sequence diagram - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - sequenceDiagram - autonumber - par Action 1 - Alice->>John: Hello John, how are you? - and Action 2 - Alice->>Bob: Hello Bob, how are you? - end - Alice->>+John: Hello John, how are you? - Alice->>+John: John, can you hear me? - John-->>-Alice: Hi Alice, I can hear you! - Note right of John: John is perceptive - John-->>-Alice: I feel great! - loop Every minute - John-->Alice: Great! - end -``` - -### Class diagram - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - -classDiagram - Animal "1" <|-- Duck - Animal <|-- Fish - Animal <--o Zebra - Animal : +int age - Animal : +String gender - Animal: +isMammal() - Animal: +mate() - class Duck{ - +String beakColor - +swim() - +quack() - } - class Fish{ - -int sizeInFeet - -canEat() - } - class Zebra{ - +bool is_wild - +run() - } -``` - -### Gantt - -```mermaid-example -gantt - dateFormat YYYY-MM-DD - title Adding GANTT diagram functionality to mermaid - excludes :excludes the named dates/days from being included in a charted task.. - section A section - Completed task :done, des1, 2014-01-06,2014-01-08 - Active task :active, des2, 2014-01-09, 3d - Future task : des3, after des2, 5d - Future task2 : des4, after des3, 5d - - section Critical tasks - Completed task in the critical line :crit, done, 2014-01-06,24h - Implement parser and jison :crit, done, after des1, 2d - Create tests for parser :crit, active, 3d - Future task in critical line :crit, 5d - Create tests for renderer :2d - Add to mermaid :1d - - section Documentation - Describe gantt syntax :active, a1, after des1, 3d - Add gantt diagram to demo page :after a1 , 20h - Add another diagram to demo page :doc1, after a1 , 48h - - section Last section - Describe gantt syntax :after doc1, 3d - Add gantt diagram to demo page :20h - Add another diagram to demo page :48h -``` - -### State diagram - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% - stateDiagram - [*] --> Active - - state Active { - [*] --> NumLockOff - NumLockOff --> NumLockOn : EvNumLockPressed - NumLockOn --> NumLockOff : EvNumLockPressed - -- - [*] --> CapsLockOff - CapsLockOff --> CapsLockOn : EvCapsLockPressed - CapsLockOn --> CapsLockOff : EvCapsLockPressed - -- - [*] --> ScrollLockOff - ScrollLockOff --> ScrollLockOn : EvCapsLockPressed - ScrollLockOn --> ScrollLockOff : EvCapsLockPressed - } - state SomethingElse { - A --> B - B --> A - } - - Active --> SomethingElse - note right of SomethingElse : This is the note to the right. - - SomethingElse --> [*] - -``` - -### State diagram (beta) - -```mermaid-example -%%{init: {'securityLevel': 'loose', 'theme':'base'}}%% -stateDiagram-v2 - [*] --> Active - - state Active { - [*] --> NumLockOff - NumLockOff --> NumLockOn : EvNumLockPressed - NumLockOn --> NumLockOff : EvNumLockPressed - -- - [*] --> CapsLockOff - CapsLockOff --> CapsLockOn : EvCapsLockPressed - CapsLockOn --> CapsLockOff : EvCapsLockPressed - -- - [*] --> ScrollLockOff - ScrollLockOff --> ScrollLockOn : EvCapsLockPressed - ScrollLockOn --> ScrollLockOff : EvCapsLockPressed - } - state SomethingElse { - A --> B - B --> A - } - - Active --> SomethingElse2 - note right of SomethingElse2 : This is the note to the right. - - SomethingElse2 --> [*] -``` - -### Entity Relations diagram - -```mermaid-example - erDiagram - CUSTOMER }|..|{ DELIVERY-ADDRESS : has - CUSTOMER ||--o{ ORDER : places - CUSTOMER ||--o{ INVOICE : "liable for" - DELIVERY-ADDRESS ||--o{ ORDER : receives - INVOICE ||--|{ ORDER : covers - ORDER ||--|{ ORDER-ITEM : includes - PRODUCT-CATEGORY ||--|{ PRODUCT : contains - PRODUCT ||--o{ ORDER-ITEM : "ordered in" -``` - -### User journey diagram - -```mermaid-example -journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 5: Me -``` +| Variable | Default value | Description | +| --------- | ------------------------------ | --------------------------------------- | +| fillType0 | primaryColor | Fill for 1st section in journey diagram | +| fillType1 | secondaryColor | Fill for 2nd section in journey diagram | +| fillType2 | calculated from primaryColor | Fill for 3rd section in journey diagram | +| fillType3 | calculated from secondaryColor | Fill for 4th section in journey diagram | +| fillType4 | calculated from primaryColor | Fill for 5th section in journey diagram | +| fillType5 | calculated from secondaryColor | Fill for 6th section in journey diagram | +| fillType6 | calculated from primaryColor | Fill for 7th section in journey diagram | +| fillType7 | calculated from secondaryColor | Fill for 8th section in journey diagram | diff --git a/packages/mermaid/src/docs/config/usage.md b/packages/mermaid/src/docs/config/usage.md index 3eac4ad6f..dd989069f 100644 --- a/packages/mermaid/src/docs/config/usage.md +++ b/packages/mermaid/src/docs/config/usage.md @@ -6,7 +6,7 @@ Diagrams can be re-rendered/modified by modifying their descriptions. ### CDN -[https://unpkg.com/mermaid/](https://unpkg.com/mermaid/) +[https://www.jsdelivr.com/package/npm/mermaid](https://www.jsdelivr.com/package/npm/mermaid) Please note that you can switch versions through the dropdown box at the top right. @@ -16,26 +16,21 @@ For the majority of users, Using the [Live Editor](https://mermaid.live/) would We have compiled some Video [Tutorials](./Tutorials.md) on how to use the mermaid Live Editor. -**Installing and Hosting Mermaid on a Webpage** +### Installing and Hosting Mermaid on a Webpage -**Using the npm package** +**Using the npm package:** -``` -1. You will need to install node v16, which would have npm. +1. You will need to install `node v16`, which would have npm. -2. download yarn using npm. +2. Download `yarn` using npm. -3. enter the following command: - yarn add mermaid +3. Enter the following command: `yarn add mermaid`. -4. At this point, you can add mermaid as a dev dependency using this command: - yarn add --dev mermaid +4. At this point, you can add mermaid as a dev dependency using this command: `yarn add --dev mermaid`. -5. Alternatively, you can also deploy mermaid using the script tag in an HTML file with mermaid diagram descriptions. - as is shown in the example below -``` +5. Alternatively, you can also deploy mermaid using the script tag in an HTML file with mermaid diagram descriptions as is shown in the example below. -**Hosting mermaid on a web page.** +**Hosting mermaid on a web page:** > Note:This topic explored in greater depth in the [User Guide for Beginners](../intro/n00b-gettingStarted.md) @@ -332,8 +327,8 @@ The future proof way of setting the configuration is by using the initialization on what kind of integration you use. ```html - - diff --git a/docs/misc/integrations.md b/packages/mermaid/src/docs/ecosystem/integrations.md similarity index 96% rename from docs/misc/integrations.md rename to packages/mermaid/src/docs/ecosystem/integrations.md index 007b9e778..cf4ccc9f8 100644 --- a/docs/misc/integrations.md +++ b/packages/mermaid/src/docs/ecosystem/integrations.md @@ -1,9 +1,3 @@ -> **Warning** -> -> ## 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). - # Integrations The following list is a compilation of different integrations and plugins that allow the rendering of mermaid definitions within other applications. @@ -21,6 +15,7 @@ They also serve as proof of concept, for the variety of things that can be built - [Azure Devops](https://docs.microsoft.com/en-us/azure/devops/project/wiki/wiki-markdown-guidance?view=azure-devops#add-mermaid-diagrams-to-a-wiki-page) (**Native support**) - [Tuleap](https://docs.tuleap.org/user-guide/writing-in-tuleap.html#graphs) (**Native support**) - [Joplin](https://joplinapp.org) (**Native support**) +- [Swimm](https://swimm.io) (**Native support**) - [Notion](https://notion.so) (**Native support**) - [Observable](https://observablehq.com/@observablehq/mermaid) (**Native support**) - [Obsidian](https://help.obsidian.md/How+to/Format+your+notes#Diagram) (**Native support**) @@ -94,9 +89,10 @@ They also serve as proof of concept, for the variety of things that can be built ## 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) - [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 Editor](https://marketplace.visualstudio.com/items?itemName=tomoyukim.vscode-mermaid-editor) - [Mermaid Export](https://marketplace.visualstudio.com/items?itemName=Gruntfuggly.mermaid-export) @@ -109,10 +105,10 @@ They also serve as proof of concept, for the variety of things that can be built - [md-it-mermaid](https://github.com/iamcco/md-it-mermaid) - [markdown-it-mermaid-fence-new](https://github.com/Revomatico/markdown-it-mermaid-fence-new) - [markdown-it-mermaid-less](https://github.com/searKing/markdown-it-mermaid-less) -- [Atom](https://atom.io) - - [Markdown Preview Enhanced](https://atom.io/packages/markdown-preview-enhanced) - - [Atom Mermaid](https://atom.io/packages/atom-mermaid) - - [Language Mermaid Syntax Highlighter](https://atom.io/packages/language-mermaid) +- Atom _(Atom has been [archived.](https://github.blog/2022-06-08-sunsetting-atom/))_ + - [Markdown Preview Enhanced](https://github.com/shd101wyy/markdown-preview-enhanced) + - [Atom Mermaid](https://github.com/y-takey/atom-mermaid) + - [Language Mermaid Syntax Highlighter](https://github.com/ytisf/language-mermaid) - [Sublime Text 3](https://sublimetext.com) - [Mermaid Package](https://packagecontrol.io/packages/Mermaid) - [Astah](https://astah.net) diff --git a/packages/mermaid/src/docs/ecosystem/showcases.md b/packages/mermaid/src/docs/ecosystem/showcases.md new file mode 100644 index 000000000..0c756759f --- /dev/null +++ b/packages/mermaid/src/docs/ecosystem/showcases.md @@ -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). diff --git a/packages/mermaid/src/docs/index.md b/packages/mermaid/src/docs/index.md index 6c2763904..b0b38bd79 100644 --- a/packages/mermaid/src/docs/index.md +++ b/packages/mermaid/src/docs/index.md @@ -21,13 +21,17 @@ hero: features: - title: ➕ Easy to use! - details: Mermaid allows even non-programmers to easily create detailed and diagrams through the Mermaid Live Editor. + details: Easily create and render detailed diagrams and charts with the Mermaid Live Editor. + link: https://mermaid.live/ - title: 🎥 Video Tutorials! - details: Has video tutorials for beginners and advanced users. - - title: 🏆 Award winner! - details: Mermaid was nominated and won the JS Open Source Awards (2019) in the category "The most exciting use of technology"!!! + details: Curated list of video tutorials and examples created by the community. + link: ../../config/Tutorials.html - title: 🧩 Integrations available! - details: Use Mermaid with your favorite applications, check out the list of Integrations and Usages of Mermaid. + details: Use Mermaid with your favorite applications, check out the integrations list. + link: ../../ecosystem/integrations.md + - title: 🏆 Award winning! + details: 2019 JavaScript Open Source Award winner for "The Most Exciting Use of Technology". + link: https://osawards.com/javascript/2019 --- + + + + + + +
+
+ +
+
+
+

MermaidPress

+

+ The Official Guide to Mermaid.js +

+

+ Learn to create complex diagrams and beautiful flowcharts easily using text and code + using Mermaid.js. +

+ + + +
+
+
+ +
+ +
+
+
+
+ + + + + + + + + + + + +
+
+
+

+ Get up to speed with using Mermaid diagrams along with real-world examples and expert tips + from the authors to facilitate a seamless development workflow +

+
+
+
+
+
+

+ Flowcharts is a diagram type that visualizes a process or an algorithm by showing the + steps in order, as well as the different paths the execution can take. +

+
+
+ +
+
+
+
+ +
+
+
+

+ Sequence diagrams lets you model and visualize interactions between different actors + or objects in a system, as well as the order of those interactions +

+
+
+
+
+
+

+ A class diagram is a graphical representation that is used to visualize and describe + an object-oriented system. +

+
+
+ +
+
+
+
+ +
+
+
+

+ An entity-relationship diagram is a graphical representation that is used to + visualize the different types of entities that exist within a system. +

+
+
+
+
+
+

+ Use State diagrams to model and document state machines, an abstract way of + representing a system or an algorithm. +

+
+
+ +
+
+
+
+ +
+
+
+

+ A Gantt chart is a graphical representation that is used to visualize and describe + tasks (events or activities) over time. +

+
+
+
+
+

+ These were a few of the diagrams supported by Mermaid. +

+
+ +
+
+

+ Book description +

+
+

+ Mermaid lets you represent diagrams using text and code which simplifies the maintenance + of complex diagrams. This is a great option for developers as they’re more familiar with + code, rather than special tools for generating diagrams. Besides, diagrams in code + simplify maintenance and ensure that the code is supported by version control systems. + In some cases, Mermaid makes refactoring support for name changes possible while also + enabling team collaboration for review distribution and updates. +

+

+ Developers working with any system will be able to put their knowledge to work with this + practical guide to using Mermaid for documentation. The book is also a great reference + for looking up the syntax for specific diagrams when authoring diagrams. +

+

+ You’ll start by getting up to speed with the importance of accurate and visual + documentation. Next, the book introduces Mermaid and establishes how to use it to create + effective documentation. By using different tools, editors, or a custom documentation + platform, you’ll also learn how to use Mermaid syntax for various diagrams. Later + chapters cover advanced configuration settings and theme options to manipulate your + diagram as per your needs. +

+

+ By the end of this Mermaid book, you’ll have become well-versed with the different types + of Mermaid diagrams and how they can be used in your workflows. +

+
+
+
+
+
+

+ What you will learn +

+
+
+
+
+
+
+
    +
  • + Understand good and bad documentation, and the art of effective documentation +
  • +
  • + Become well-versed with maintaining complex diagrams with ease +
  • +
  • + Learn how to set up a custom documentation system +
  • +
  • + Learn how to implement Mermaid diagrams in your workflows +
  • +
  • + Understand how to set up themes for a Mermaid diagram for an entire site +
  • +
  • + Discover how to draw different types of diagrams such as flowcharts, class + diagrams, Gantt charts, and more +
  • +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + +
+

+ Purchase The Official Guide to Mermaid.js +

+
+
+
+

+

Written by Knut Sveidqvist and Ashish Jain.

+

+ Knut is the creator of Mermaid and both authors are active core team members of the + Mermaid open-source project. +

+

+ + + +
+ + + diff --git a/packages/mermaid/src/docs/landing/sequence-diagram.png b/packages/mermaid/src/docs/landing/sequence-diagram.png new file mode 100644 index 000000000..8c51ac1c5 Binary files /dev/null and b/packages/mermaid/src/docs/landing/sequence-diagram.png differ diff --git a/packages/mermaid/src/docs/landing/state.png b/packages/mermaid/src/docs/landing/state.png new file mode 100644 index 000000000..2ef66ea2f Binary files /dev/null and b/packages/mermaid/src/docs/landing/state.png differ diff --git a/packages/mermaid/src/docs/syntax/classDiagram.md b/packages/mermaid/src/docs/syntax/classDiagram.md index e9b918529..10ccc3522 100644 --- a/packages/mermaid/src/docs/syntax/classDiagram.md +++ b/packages/mermaid/src/docs/syntax/classDiagram.md @@ -1,7 +1,8 @@ # Class diagrams > "In software engineering, a class diagram in the Unified Modeling Language (UML) is a type of static structure diagram that describes the structure of a system by showing the system's classes, their attributes, operations (or methods), and the relationships among objects." -> Wikipedia +> +> -Wikipedia The class diagram is the main building block of object-oriented modeling. It is used for general conceptual modeling of the structure of the application, and for detailed modeling to translate the models into programming code. Class diagrams can also be used for data modeling. The classes in a class diagram represent both the main elements, interactions in the application, and the classes to be programmed. @@ -122,7 +123,7 @@ class BankAccount{ #### Generic Types -Members can be defined using generic types, such as `List`, for fields, parameters, and return types by enclosing the type within `~` (**tilde**). Note: **nested** type declarations such as `List>` are not currently supported. +Members can be defined using generic types, such as `List`, for fields, parameters, and return types by enclosing the type within `~` (**tilde**). **Nested** type declarations such as `List>` are supported. Generics can be represented as part of a class definition and also in the parameters or the return value of a method/function: @@ -138,12 +139,9 @@ class Square~Shape~{ Square : -List~string~ messages Square : +setMessages(List~string~ messages) Square : +getMessages() List~string~ +Square : +getDistanceMatrix() List~List~int~~ ``` -#### Return Type - -Optionally you can end the method/function definition with the data type that will be returned. - #### Visibility To describe the visibility (or encapsulation) of an attribute or method/function that is a part of a class (i.e. a class member), optional notation may be placed before that members' name: @@ -175,7 +173,7 @@ There are eight different types of relations defined for classes under UML which | Type | Description | | ------- | ------------- | | `<\|--` | Inheritance | -| `\*--` | Composition | +| `*--` | Composition | | `o--` | Aggregation | | `-->` | Association | | `--` | Link (Solid) | diff --git a/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md b/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md index c666877c5..7067a65d9 100644 --- a/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md +++ b/packages/mermaid/src/docs/syntax/entityRelationshipDiagram.md @@ -124,7 +124,7 @@ erDiagram ### Attributes -Attributes can be defined for entities by specifying the entity name followed by a block containing multiple `type name` pairs, where a block is delimited by an opening `{` and a closing `}`. For example: +Attributes can be defined for entities by specifying the entity name followed by a block containing multiple `type name` pairs, where a block is delimited by an opening `{` and a closing `}`. The attributes are rendered inside the entity boxes. For example: ```mermaid-example erDiagram @@ -142,47 +142,34 @@ erDiagram } ``` -The attributes are rendered inside the entity boxes: - -```mermaid-example -erDiagram - CAR ||--o{ NAMED-DRIVER : allows - CAR { - string registrationNumber - string make - string model - } - PERSON ||--o{ NAMED-DRIVER : is - PERSON { - string firstName - string lastName - int age - } -``` - -The `type` and `name` values must begin with an alphabetic character and may contain digits, hyphens or underscores. Other than that, there are no restrictions, and there is no implicit set of valid data types. +The `type` and `name` values must begin with an alphabetic character and may contain digits, hyphens, underscores, parentheses and square brackets. Other than that, there are no restrictions, and there is no implicit set of valid data types. #### Attribute Keys and Comments -Attributes may also have a `key` or comment defined. Keys can be "PK" or "FK", for Primary Key or Foreign 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 erDiagram CAR ||--o{ NAMED-DRIVER : allows CAR { - string allowedDriver FK "The license of the allowed driver" - string registrationNumber + string registrationNumber PK string make string model + string[] parts } PERSON ||--o{ NAMED-DRIVER : is PERSON { string driversLicense PK "The license #" - string firstName + string(99) firstName "Only 99 characters are allowed" string lastName + string phone UK 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 diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md index 7f8284a2f..5896e0518 100644 --- a/packages/mermaid/src/docs/syntax/flowchart.md +++ b/packages/mermaid/src/docs/syntax/flowchart.md @@ -1,3 +1,8 @@ +--- +title: Flowcharts Syntax +outline: 'deep' # shows all h3 headings in outline in Vitepress +--- + # 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. @@ -16,7 +21,9 @@ flowchart LR id ``` -> **Note** The id is what is displayed in the box. +```note +The id is what is displayed in the box. +``` ### A node with text @@ -547,8 +554,8 @@ linkStyle 3 stroke:#ff3,stroke-width:4px,color:red; ### 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. -Available curve styles include `basis`, `bump`, `linear`, `monotoneX`, `monotoneY`, `natural`, `step`, `stepAfter`, -and `stepBefore`. +Available curve styles include `basis`, `bumpX`, `bumpY`, `cardinal`, `catmullRom`, `linear`, `monotoneX`, `monotoneY`, +`natural`, `step`, `stepAfter`, and `stepBefore`. In this example, a left-to-right graph uses the `stepBefore` curve style: @@ -669,7 +676,24 @@ flowchart LR C -->|Two| E[Result two] ``` -## Configuration... +## Configuration + +### Renderer + +The layout of the diagram is done with the renderer. The default renderer is dagre. + +Starting with Mermaid version 9.4, you can use an alternate renderer named elk. The elk renderer is better for larger and/or more complex diagrams. + +The _elk_ renderer is an experimenal feature. +You can change the renderer to elk by adding this directive: + +``` +%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% +``` + +Note that the site needs to use mermaid version 9.4+ for this to work and have this featured enabled in the lazy-loading configuration. + +### Width It is possible to adjust the width of the rendered flowchart. diff --git a/packages/mermaid/src/docs/syntax/gantt.md b/packages/mermaid/src/docs/syntax/gantt.md index c9301bfee..0cc915ca1 100644 --- a/packages/mermaid/src/docs/syntax/gantt.md +++ b/packages/mermaid/src/docs/syntax/gantt.md @@ -104,34 +104,33 @@ Final milestone : milestone, m2, 18:14, 2min The default input date format is `YYYY-MM-DD`. You can define your custom `dateFormat`. -``` +```markdown dateFormat YYYY-MM-DD ``` The following formatting options are supported: -``` -Input Example Description: -YYYY 2014 4 digit year -YY 14 2 digit year -Q 1..4 Quarter of year. Sets month to first month in quarter. -M MM 1..12 Month number -MMM MMMM January..Dec Month name in locale set by moment.locale() -D DD 1..31 Day of month -Do 1st..31st Day of month with ordinal -DDD DDDD 1..365 Day of year -X 1410715640.579 Unix timestamp -x 1410715640579 Unix ms timestamp -H HH 0..23 24 hour time -h hh 1..12 12 hour time used with a A. -a A am pm Post or ante meridiem -m mm 0..59 Minutes -s ss 0..59 Seconds -S 0..9 Tenths of a second -SS 0..99 Hundreds of a second -SSS 0..999 Thousandths of a second -Z ZZ +12:00 Offset from UTC as +-HH:mm, +-HHmm, or Z -``` +| Input | Example | Description | +| ---------- | -------------- | ------------------------------------------------------ | +| `YYYY` | 2014 | 4 digit year | +| `YY` | 14 | 2 digit year | +| `Q` | 1..4 | Quarter of year. Sets month to first month in quarter. | +| `M MM` | 1..12 | Month number | +| `MMM MMMM` | January..Dec | Month name in locale set by `moment.locale()` | +| `D DD` | 1..31 | Day of month | +| `Do` | 1st..31st | Day of month with ordinal | +| `DDD DDDD` | 1..365 | Day of year | +| `X` | 1410715640.579 | Unix timestamp | +| `x` | 1410715640579 | Unix ms timestamp | +| `H HH` | 0..23 | 24 hour time | +| `h hh` | 1..12 | 12 hour time used with `a A`. | +| `a A` | am pm | Post or ante meridiem | +| `m mm` | 0..59 | Minutes | +| `s ss` | 0..59 | Seconds | +| `S` | 0..9 | Tenths of a second | +| `SS` | 0..99 | Hundreds of a second | +| `SSS` | 0..999 | Thousandths of a second | +| `Z ZZ` | +12:00 | Offset from UTC as +-HH:mm, +-HHmm, or Z | More info in: https://momentjs.com/docs/#/parsing/string-format/ @@ -139,38 +138,38 @@ More info in: https://momentjs.com/docs/#/parsing/string-format/ The default output date format is `YYYY-MM-DD`. You can define your custom `axisFormat`, like `2020-Q1` for the first quarter of the year 2020. -``` -axisFormat %Y-%m-%d +```markdown +axisFormat %Y-%m-%d ``` The following formatting strings are supported: -``` -%a - abbreviated weekday name. -%A - full weekday name. -%b - abbreviated month name. -%B - full month name. -%c - date and time, as "%a %b %e %H:%M:%S %Y". -%d - zero-padded day of the month as a decimal number [01,31]. -%e - space-padded day of the month as a decimal number [ 1,31]; equivalent to %_d. -%H - hour (24-hour clock) as a decimal number [00,23]. -%I - hour (12-hour clock) as a decimal number [01,12]. -%j - day of the year as a decimal number [001,366]. -%m - month as a decimal number [01,12]. -%M - minute as a decimal number [00,59]. -%L - milliseconds as a decimal number [000, 999]. -%p - either AM or PM. -%S - second as a decimal number [00,61]. -%U - week number of the year (Sunday as the first day of the week) as a decimal number [00,53]. -%w - weekday as a decimal number [0(Sunday),6]. -%W - week number of the year (Monday as the first day of the week) as a decimal number [00,53]. -%x - date, as "%m/%d/%Y". -%X - time, as "%H:%M:%S". -%y - year without century as a decimal number [00,99]. -%Y - year with century as a decimal number. -%Z - time zone offset, such as "-0700". -%% - a literal "%" character. -``` +| Format | Definition | +| ------ | ----------------------------------------------------------------------------------------- | +| %a | abbreviated weekday name | +| %A | full weekday name | +| %b | abbreviated month name | +| %B | full month name | +| %c | date and time, as "%a %b %e %H:%M:%S %Y" | +| %d | zero-padded day of the month as a decimal number [01,31] | +| %e | space-padded day of the month as a decimal number [ 1,31]; equivalent to %\_d | +| %H | hour (24-hour clock) as a decimal number [00,23] | +| %I | hour (12-hour clock) as a decimal number [01,12] | +| %j | day of the year as a decimal number [001,366] | +| %m | month as a decimal number [01,12] | +| %M | minute as a decimal number [00,59] | +| %L | milliseconds as a decimal number [000, 999] | +| %p | either AM or PM | +| %S | second as a decimal number [00,61] | +| %U | week number of the year (Sunday as the first day of the week) as a decimal number [00,53] | +| %w | weekday as a decimal number [0(Sunday),6] | +| %W | week number of the year (Monday as the first day of the week) as a decimal number [00,53] | +| %x | date, as "%m/%d/%Y" | +| %X | time, as "%H:%M:%S" | +| %y | year without century as a decimal number [00,99] | +| %Y | year with century as a decimal number | +| %Z | time zone offset, such as "-0700" | +| %% | a literal "%" character | More info in: [https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format](https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format) @@ -178,14 +177,14 @@ More info in: [https://github.com/d3/d3-time-format/tree/v4.0.0#locale_format](h The default output ticks are auto. You can custom your `tickInterval`, like `1day` or `1week`. -``` +```markdown tickInterval 1day ``` The pattern is: -``` -/^([1-9][0-9]*)(minute|hour|day|week|month)$/ +```javascript +/^([1-9][0-9]*)(minute|hour|day|week|month)$/; ``` More info in: [https://github.com/d3/d3-time#interval_every](https://github.com/d3/d3-time#interval_every) diff --git a/packages/mermaid/src/docs/syntax/mindmap.md b/packages/mermaid/src/docs/syntax/mindmap.md index beaae7ad5..c8a252691 100644 --- a/packages/mermaid/src/docs/syntax/mindmap.md +++ b/packages/mermaid/src/docs/syntax/mindmap.md @@ -55,7 +55,7 @@ In this way we can use a text outline to generate a hierarchical mindmap. ## 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: @@ -112,7 +112,7 @@ More shapes will be added, beginning with the shapes available in flowcharts. # Icons and classes -## icons +## Icons As with flowcharts you can add icons to your nodes but with an updated syntax. The styling for the font based icons are added during the integration so that they are available for the web page. _This is not something a diagram author can do but has to be done with the site administrator or the integrator_. Once the icon fonts are in place you add them to the mind map nodes using the `::icon()` syntax. You place the classes for the icon within the parenthesis like in the following example where icons for material design and fontawesome 4 are displayed. The intention is that this approach should be used for all diagrams supporting icons. **Experimental feature:** This wider scope is also the reason Mindmaps are experimental as this syntax and approach could change. @@ -164,14 +164,24 @@ Root ## Integrating with your library/website. -Mindmap uses the experimental lazy loading & async rendering features which could change in the future. +Mindmap uses the experimental lazy loading & async rendering features which could change in the future. From version 9.4.0 this diagram is included in mermaid but use lazy loading in order to keep the size of mermaid down. This is important in order to be able to add additional diagrams going forward. + +You can still use the pre 9.4.0 method to add mermaid with mindmaps to a web page: ```html ``` -You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/fcf53c98c25604c90a218104268c339be53035a6/src/lib/util/mermaid.ts) to see how the async loading is done. +From version 9.4.0 you can simplify this code to: + +```html + +``` + +You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/develop/src/lib/util/mermaid.ts) to see how the async loading is done. diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md index 9c28883c9..3f3818589 100644 --- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md +++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md @@ -58,6 +58,48 @@ sequenceDiagram 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 can be of two displayed either solid or with a dotted line. @@ -68,16 +110,16 @@ Messages can be of two displayed either solid or with a dotted line. There are six types of arrows currently supported: -| Type | Description | -| ---- | ------------------------------------------------ | -| -> | Solid line without arrow | -| --> | Dotted line without arrow | -| ->> | Solid line with arrowhead | -| -->> | Dotted line with arrowhead | -| -x | Solid line with a cross at the end | -| --x | Dotted line with a cross at the end. | -| -) | Solid line with an open arrow at the end (async) | -| --) | Dotted line with a open arrow at the end (async) | +| Type | Description | +| ------ | ------------------------------------------------ | +| `->` | Solid line without arrow | +| `-->` | Dotted line without arrow | +| `->>` | Solid line with arrowhead | +| `-->>` | Dotted line with arrowhead | +| `-x` | Solid line with a cross at the end | +| `--x` | Dotted line with a cross at the end. | +| `-)` | Solid line with an open arrow at the end (async) | +| `--)` | Dotted line with a open arrow at the end (async) | ## Activations @@ -130,6 +172,14 @@ sequenceDiagram Note over Alice,John: A typical interaction ``` +It is also possible to add a line break (applies to text input in general): + +```mermaid-example +sequenceDiagram + Alice->John: Hello John, how are you? + Note over Alice,John: A typical interaction
But now in two lines +``` + ## Loops It is possible to express loops in a sequence diagram. This is done by the notation diff --git a/packages/mermaid/src/docs/syntax/stateDiagram.md b/packages/mermaid/src/docs/syntax/stateDiagram.md index 29e355a72..ddfe61c49 100644 --- a/packages/mermaid/src/docs/syntax/stateDiagram.md +++ b/packages/mermaid/src/docs/syntax/stateDiagram.md @@ -318,19 +318,19 @@ There are two ways to apply a `classDef` style to a state: A `class` statement tells Mermaid to apply the named classDef to one or more classes. The form is: -```text +```txt class [one or more state names, separated by commas] [name of a style defined with classDef] ``` Here is an example applying the `badBadEvent` style to a state named `Crash`: -```text +```txt class Crash badBadEvent ``` Here is an example applying the `movement` style to the two states `Moving` and `Crash`: -```text +```txt class Moving, Crash movement ``` @@ -365,7 +365,7 @@ and `badBadEvent` You can apply a classDef style to a state using the `:::` (three colons) operator. The syntax is -```text +```txt [state]:::[style name] ``` diff --git a/packages/mermaid/src/docs/syntax/timeline.md b/packages/mermaid/src/docs/syntax/timeline.md new file mode 100644 index 000000000..ef48d2b61 --- /dev/null +++ b/packages/mermaid/src/docs/syntax/timeline.md @@ -0,0 +1,306 @@ +# Timeline Diagram + +> Timeline: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part. + +"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life." Wikipedia + +### An example of a timeline. + +```mermaid +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook + : Google + 2005 : Youtube + 2006 : Twitter +``` + +## Syntax + +The syntax for creating Timeline diagram is simple. You always start with the `timeline` keyword to let mermaid know that you want to create a timeline diagram. + +After that there is a possibility to add a title to the timeline. This is done by adding a line with the keyword `title` followed by the title text. + +Then you add the timeline data, where you always start with a time period, followed by a colon and then the text for the event. Optionally you can add a second colon and then the text for the event. So, you can have one or more events per time period. + +```json +{time period} : {event} +``` + +or + +```json +{time period} : {event} : {event} +``` + +or + +```json +{time period} : {event} + : {event} + : {event} +``` + +NOTE: Both time period and event are simple text, and not limited to numbers. + +Let us look at the syntax for the example above. + +```mermaid-example +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter +``` + +In this way we can use a text outline to generate a timeline diagram. +The sequence of time period and events is important, as it will be used to draw the timeline. The first time period will be placed at the left side of the timeline, and the last time period will be placed at the right side of the timeline. + +Similarly, the first event will be placed at the top for that specific time period, and the last event will be placed at the bottom. + +## Grouping of time periods in sections/ages + +You can group time periods in sections/ages. This is done by adding a line with the keyword `section` followed by the section name. + +All subsequent time periods will be placed in this section until a new section is defined. + +If no section is defined, all time periods will be placed in the default section. + +Let us look at an example, where we have grouped the time periods in sections. + +```mermaid-example +timeline + title Timeline of Industrial Revolution + section 17th-20th century + Industry 1.0 : Machinery, Water power, Steam
power + Industry 2.0 : Electricity, Internal combustion engine, Mass production + Industry 3.0 : Electronics, Computers, Automation + section 21st century + Industry 4.0 : Internet, Robotics, Internet of Things + Industry 5.0 : Artificial intelligence, Big data,3D printing +``` + +As you can see, the time periods are placed in the sections, and the sections are placed in the order they are defined. + +All time periods and events under a given section follow a similar color scheme. This is done to make it easier to see the relationship between time periods and events. + +## Wrapping of text for long time-periods or events + +By default, the text for time-periods and events will be wrapped if it is too long. This is done to avoid that the text is drawn outside the diagram. + +You can also use `
` to force a line break. + +Let us look at another example, where we have a long time period, and a long event. + +```mermaid-example +timeline + title England's History Timeline + section Stone Age + 7600 BC : Britain's oldest known house was built in Orkney, Scotland + 6000 BC : Sea levels rise and Britain becomes an island.
The people who live here are hunter-gatherers. + section Broze Age + 2300 BC : People arrive from Europe and settle in Britain.
They bring farming and metalworking. + : New styles of pottery and ways of burying the dead appear. + 2200 BC : The last major building works are completed at Stonehenge.
People now bury their dead in stone circles. + : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive. + +``` + +```mermaid-example +timeline + title MermaidChart 2023 Timeline + section 2023 Q1
Release Personal Tier + Buttet 1 : sub-point 1a : sub-point 1b + : sub-point 1c + Bullet 2 : sub-point 2a : sub-point 2b + section 2023 Q2
Release XYZ Tier + Buttet 3 : sub-point
3a : sub-point 3b + : sub-point 3c + Bullet 4 : sub-point 4a : sub-point 4b +``` + +## Styling of time periods and events + +As explained earlier, each section has a color scheme, and each time period and event under a section follow the similar color scheme. + +However, if there is no section defined, then we have two possibilities: + +1. Style time periods individually, i.e. each time period(and its coressponding events) will have its own color scheme. This is the DEFAULT behavior. + +```mermaid-example + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +Note that this is no, section defined, and each time period and its corresponding events will have its own color scheme. + +2. Disable the multiColor option using the `disableMultiColor` option. This will make all time periods and events follow the same color scheme. + +You will need to add this option either via mermaid.intialize function or directives. + +```javascript +mermaid.initialize({ + theme: 'base', + startOnLoad: true, + logLevel: 0, + timeline: { + disableMulticolor: false, + }, + ... + ... +``` + +let us look at same example, where we have disabled the multiColor option. + +```mermaid-example + %%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +### Customizing Color scheme + +You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on. +In case you have more than 12 sections, the color scheme will start to repeat. + +NOTE: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values. + +Example: + +Now let's override the default values for the `cScale0` to `cScale2` variables: + +```mermaid-example + %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { + 'cScale0': '#ff0000', + 'cScale1': '#00ff00', + 'cScale2': '#0000ff' + } } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + +``` + +See how the colors are changed to the values specified in the theme variables. + +## Themes + +Mermaid supports a bunch of pre-defined themes which you can use to find the right one for you. PS: you can actually override an existing theme's variable to get your own custom theme going. Learn more about theming your diagram [here](../config/theming.md). + +The following are the different pre-defined theme options: + +- `base` +- `forest` +- `dark` +- `default` +- `neutral` + +**NOTE**: To change theme you can either use the `initialize` call or _directives_. Learn more about [directives](../config/directives.md) +Let's put them to use, and see how our sample diagram looks in different themes: + +### Base Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Forest Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Dark Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Default Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Neutral Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +## Integrating with your library/website. + +Timeline uses experimental lazy loading & async rendering features which could change in the future.The lazy loading is important in order to be able to add additional diagrams going forward. + +You can use this method to add mermaid including the timeline diagram to a web page: + +```html + +``` + +You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/develop/src/lib/util/mermaid.ts) to see how the async loading is done. diff --git a/packages/mermaid/src/docs/vite.config.ts b/packages/mermaid/src/docs/vite.config.ts index 15652c21c..179e271fd 100644 --- a/packages/mermaid/src/docs/vite.config.ts +++ b/packages/mermaid/src/docs/vite.config.ts @@ -1,6 +1,5 @@ -import { defineConfig, searchForWorkspaceRoot } from 'vite'; +import { defineConfig, type PluginOption, searchForWorkspaceRoot } from 'vite'; import path from 'path'; -// @ts-ignore: still in alpha import { SearchPlugin } from 'vitepress-plugin-search'; const virtualModuleId = 'virtual:mermaid-config'; @@ -8,17 +7,17 @@ const resolvedVirtualModuleId = '\0' + virtualModuleId; export default defineConfig({ plugins: [ - SearchPlugin(), + SearchPlugin() as PluginOption, { // TODO: will be fixed in the next vitepress release. name: 'fix-virtual', - async resolveId(id) { + async resolveId(id: string) { if (id === virtualModuleId) { return resolvedVirtualModuleId; } }, - async load(this, id) { + async load(this, id: string) { if (id === resolvedVirtualModuleId) { return `export default ${JSON.stringify({ securityLevel: 'loose', @@ -26,15 +25,14 @@ export default defineConfig({ })};`; } }, - }, + } as PluginOption, ], resolve: { alias: { mermaid: path.join(__dirname, '../../dist/mermaid.esm.min.mjs'), // Use this one to build - - '@mermaid-js/mermaid-mindmap': path.join( + '@mermaid-js/mermaid-example-diagram': path.join( __dirname, - '../../../mermaid-mindmap/dist/mermaid-mindmap.esm.min.mjs' + '../../../mermaid-example-diagram/dist/mermaid-example-diagram.esm.min.mjs' ), // Use this one to build }, }, diff --git a/packages/mermaid/src/errors.ts b/packages/mermaid/src/errors.ts new file mode 100644 index 000000000..e3650c5a9 --- /dev/null +++ b/packages/mermaid/src/errors.ts @@ -0,0 +1,6 @@ +export class UnknownDiagramError extends Error { + constructor(message: string) { + super(message); + this.name = 'UnknownDiagramError'; + } +} diff --git a/packages/mermaid/src/mermaid.spec.ts b/packages/mermaid/src/mermaid.spec.ts index aa797af0e..75cafcdf3 100644 --- a/packages/mermaid/src/mermaid.spec.ts +++ b/packages/mermaid/src/mermaid.spec.ts @@ -6,55 +6,58 @@ const spyOn = vi.spyOn; vi.mock('./mermaidAPI'); afterEach(() => { - vi.restoreAllMocks(); + vi.clearAllMocks(); }); -describe('when using mermaid and ', function () { - describe('when detecting chart type ', function () { - it('should not start rendering with mermaid.startOnLoad set to false', function () { +describe('when using mermaid and ', () => { + describe('when detecting chart type ', () => { + it('should not start rendering with mermaid.startOnLoad set to false', async () => { mermaid.startOnLoad = false; document.body.innerHTML = '
graph TD;\na;
'; - spyOn(mermaid, 'init'); + spyOn(mermaid, 'run'); mermaid.contentLoaded(); - expect(mermaid.init).not.toHaveBeenCalled(); + expect(mermaid.run).not.toHaveBeenCalled(); }); - it('should start rendering with both startOnLoad set', function () { + it('should start rendering with both startOnLoad set', async () => { mermaid.startOnLoad = true; document.body.innerHTML = '
graph TD;\na;
'; - spyOn(mermaid, 'init'); + spyOn(mermaid, 'run'); mermaid.contentLoaded(); - expect(mermaid.init).toHaveBeenCalled(); + expect(mermaid.run).toHaveBeenCalled(); }); - it('should start rendering with mermaid.startOnLoad', function () { + it('should start rendering with mermaid.startOnLoad', async () => { mermaid.startOnLoad = true; document.body.innerHTML = '
graph TD;\na;
'; - spyOn(mermaid, 'init'); + spyOn(mermaid, 'run'); mermaid.contentLoaded(); - expect(mermaid.init).toHaveBeenCalled(); + expect(mermaid.run).toHaveBeenCalled(); }); - it('should start rendering as a default with no changes performed', function () { + it('should start rendering as a default with no changes performed', async () => { document.body.innerHTML = '
graph TD;\na;
'; - spyOn(mermaid, 'init'); + spyOn(mermaid, 'run'); mermaid.contentLoaded(); - expect(mermaid.init).toHaveBeenCalled(); + expect(mermaid.run).toHaveBeenCalled(); }); }); - describe('when using #initThrowsErrors', function () { + describe('when using #run', () => { it('should accept single node', async () => { const node = document.createElement('div'); node.appendChild(document.createTextNode('graph TD;\na;')); - mermaid.initThrowsErrors(undefined, node); + await mermaid.run({ + nodes: [node], + }); // mermaidAPI.render function has been mocked, since it doesn't yet work // in Node.JS (only works in browser) expect(mermaidAPI.render).toHaveBeenCalled(); }); }); - describe('when using #registerExternalDiagrams', function () { + + describe('when using #registerExternalDiagrams', () => { it('should throw error (but still render) if registerExternalDiagrams fails', async () => { const node = document.createElement('div'); node.appendChild(document.createTextNode('graph TD;\na;')); @@ -72,9 +75,13 @@ describe('when using mermaid and ', function () { ) ).rejects.toThrow('Failed to load 1 external diagrams'); - expect(() => mermaid.initThrowsErrorsAsync(undefined, node)).not.toThrow(); + await expect( + mermaid.run({ + nodes: [node], + }) + ).resolves.not.toThrow(); // should still render, even if lazyLoadedDiagrams fails - expect(mermaidAPI.renderAsync).toHaveBeenCalled(); + expect(mermaidAPI.render).toHaveBeenCalled(); }); it('should defer diagram load based on parameter', async () => { @@ -137,19 +144,21 @@ describe('when using mermaid and ', function () { }); }); - describe('checking validity of input ', function () { - it('should throw for an invalid definition', function () { - expect(() => mermaid.parse('this is not a mermaid diagram definition')).toThrow(); + describe('checking validity of input ', () => { + it('should throw for an invalid definition', async () => { + await expect(mermaid.parse('this is not a mermaid diagram definition')).rejects.toThrow(); }); - it('should not throw for a valid flow definition', function () { - expect(() => mermaid.parse('graph TD;A--x|text including URL space|B;')).not.toThrow(); + it('should not throw for a valid flow definition', async () => { + await expect( + mermaid.parse('graph TD;A--x|text including URL space|B;') + ).resolves.not.toThrow(); }); - it('should throw for an invalid flow definition', function () { - expect(() => mermaid.parse('graph TQ;A--x|text including URL space|B;')).toThrow(); + it('should throw for an invalid flow definition', async () => { + await expect(mermaid.parse('graph TQ;A--x|text including URL space|B;')).rejects.toThrow(); }); - it('should not throw for a valid sequenceDiagram definition (mmds1)', function () { + it('should not throw for a valid sequenceDiagram definition (mmds1)', async () => { const text = 'sequenceDiagram\n' + 'Alice->Bob: Hello Bob, how are you?\n\n' + @@ -160,10 +169,10 @@ describe('when using mermaid and ', function () { 'else isSick\n' + 'Bob-->Alice: Feel sick...\n' + 'end'; - expect(() => mermaid.parse(text)).not.toThrow(); + await expect(mermaid.parse(text)).resolves.not.toThrow(); }); - it('should throw for an invalid sequenceDiagram definition', function () { + it('should throw for an invalid sequenceDiagram definition', async () => { const text = 'sequenceDiagram\n' + 'Alice:->Bob: Hello Bob, how are you?\n\n' + @@ -174,15 +183,15 @@ describe('when using mermaid and ', function () { 'else isSick\n' + 'Bob-->Alice: Feel sick...\n' + 'end'; - expect(() => mermaid.parse(text)).toThrow(); + await expect(mermaid.parse(text)).rejects.toThrow(); }); - it('should return false for invalid definition WITH a parseError() callback defined', function () { + it('should return false for invalid definition WITH a parseError() callback defined', async () => { let parseErrorWasCalled = false; mermaid.setParseErrorHandler(() => { parseErrorWasCalled = true; }); - expect(mermaid.parse('this is not a mermaid diagram definition')).toEqual(false); + await expect(mermaid.parse('this is not a mermaid diagram definition')).rejects.toThrow(); expect(parseErrorWasCalled).toEqual(true); }); }); diff --git a/packages/mermaid/src/mermaid.ts b/packages/mermaid/src/mermaid.ts index 9d9963741..29d25c016 100644 --- a/packages/mermaid/src/mermaid.ts +++ b/packages/mermaid/src/mermaid.ts @@ -2,65 +2,44 @@ * Web page integration module for the mermaid framework. It uses the mermaidAPI for mermaid * functionality and to render the diagrams to svg code! */ +import dedent from 'ts-dedent'; import { MermaidConfig } from './config.type'; import { log } from './logger'; import utils from './utils'; -import { mermaidAPI } from './mermaidAPI'; +import { mermaidAPI, ParseOptions, RenderResult } from './mermaidAPI'; import { registerLazyLoadedDiagrams } from './diagram-api/detectType'; import type { ParseErrorFunction } from './Diagram'; -import { isDetailedError, type DetailedError } from './utils'; -import { loadExternalDiagrams } from './diagram-api/diagramAPI'; +import { isDetailedError } from './utils'; +import type { DetailedError } from './utils'; +import { registerDiagram } from './diagram-api/diagramAPI'; import { ExternalDiagramDefinition } from './diagram-api/types'; -export type { MermaidConfig, DetailedError, ExternalDiagramDefinition, ParseErrorFunction }; - -/** - * ## init - * - * Function that goes through the document to find the chart definitions in there and render them. - * - * The function tags the processed attributes with the attribute data-processed and ignores found - * elements with the attribute already set. This way the init function can be triggered several - * times. - * - * ```mermaid - * graph LR; - * a(Find elements)-->b{Processed} - * b-->|Yes|c(Leave element) - * b-->|No |d(Transform) - * ``` - * - * Renders the mermaid diagrams - * - * @param config - **Deprecated**, please set configuration in {@link initialize}. - * @param nodes - **Default**: `.mermaid`. One of the following: - * - A DOM Node - * - An array of DOM nodes (as would come from a jQuery selector) - * - A W3C selector, a la `.mermaid` - * @param callback - Called once for each rendered diagram's id. - */ -const init = async function ( - config?: MermaidConfig, - // eslint-disable-next-line no-undef - nodes?: string | HTMLElement | NodeListOf, - // eslint-disable-next-line @typescript-eslint/ban-types - callback?: Function -) { - try { - await initThrowsErrorsAsync(config, nodes, callback); - } catch (e) { - log.warn('Syntax Error rendering'); - if (isDetailedError(e)) { - log.warn(e.str); - } - if (mermaid.parseError) { - mermaid.parseError(e as string); - } - } +export type { + MermaidConfig, + DetailedError, + ExternalDiagramDefinition, + ParseErrorFunction, + RenderResult, + ParseOptions, }; -// eslint-disable-next-line @typescript-eslint/ban-types -const handleError = (error: unknown, errors: DetailedError[], parseError?: Function) => { +/** + * The options used when running mermaid. + * + * @param querySelector - The query selector to use when finding elements to render. Default: .mermaid + * @param nodes - The nodes to render. If this is set, querySelector will be ignored. + * @param postRenderCallback - A callback to call after each diagram is rendered. + * @param suppressErrors - If true, errors will be logged to the console, but not thrown. Default: false + */ +export interface RunOptions { + // Default: .mermaid + querySelector?: string; + nodes?: ArrayLike; + postRenderCallback?: (id: string) => unknown; + suppressErrors?: boolean; +} + +const handleError = (error: unknown, errors: DetailedError[], parseError?: ParseErrorFunction) => { log.warn(error); if (isDetailedError(error)) { // handle case where error string and hash were @@ -85,39 +64,93 @@ const handleError = (error: unknown, errors: DetailedError[], parseError?: Funct } }; -const initThrowsErrors = function ( - config?: MermaidConfig, - // eslint-disable-next-line no-undef - nodes?: string | HTMLElement | NodeListOf, - // eslint-disable-next-line @typescript-eslint/ban-types - callback?: Function +/** + * This is an internal function and should not be made public, as it will likely change. + * @internal + * @param diagrams - Array of {@link ExternalDiagramDefinition}. + */ +const loadExternalDiagrams = async (...diagrams: ExternalDiagramDefinition[]) => { + log.debug(`Loading ${diagrams.length} external diagrams`); + // Load all lazy loaded diagrams in parallel + const results = await Promise.allSettled( + diagrams.map(async ({ id, detector, loader }) => { + const { diagram } = await loader(); + registerDiagram(id, diagram, detector); + }) + ); + const failed = results.filter((result) => result.status === 'rejected'); + if (failed.length > 0) { + log.error(`Failed to load ${failed.length} external diagrams`); + for (const res of failed) { + log.error(res); + } + throw new Error(`Failed to load ${failed.length} external diagrams`); + } +}; + +/** + * ## run + * + * Function that goes through the document to find the chart definitions in there and render them. + * + * The function tags the processed attributes with the attribute data-processed and ignores found + * elements with the attribute already set. This way the init function can be triggered several + * times. + * + * ```mermaid + * graph LR; + * a(Find elements)-->b{Processed} + * b-->|Yes|c(Leave element) + * b-->|No |d(Transform) + * ``` + * + * Renders the mermaid diagrams + * + * @param options - Optional runtime configs + */ +const run = async function ( + options: RunOptions = { + querySelector: '.mermaid', + } +) { + try { + await runThrowsErrors(options); + } catch (e) { + if (isDetailedError(e)) { + log.error(e.str); + } + if (mermaid.parseError) { + mermaid.parseError(e as string); + } + if (!options.suppressErrors) { + log.error('Use the suppressErrors option to suppress these errors'); + throw e; + } + } +}; + +const runThrowsErrors = async function ( + { postRenderCallback, querySelector, nodes }: Omit = { + querySelector: '.mermaid', + } ) { const conf = mermaidAPI.getConfig(); - if (config) { - // This is a legacy way of setting config. It is not documented and should be removed in the future. - // @ts-ignore: TODO Fix ts errors - mermaid.sequenceConfig = config; - } - // if last argument is a function this is the callback function - log.debug(`${!callback ? 'No ' : ''}Callback function found`); + log.debug(`${!postRenderCallback ? 'No ' : ''}Callback function found`); + let nodesToProcess: ArrayLike; - if (nodes === undefined) { - nodesToProcess = document.querySelectorAll('.mermaid'); - } else if (typeof nodes === 'string') { - nodesToProcess = document.querySelectorAll(nodes); - } else if (nodes instanceof HTMLElement) { - nodesToProcess = [nodes]; - } else if (nodes instanceof NodeList) { + if (nodes) { nodesToProcess = nodes; + } else if (querySelector) { + nodesToProcess = document.querySelectorAll(querySelector); } else { - throw new Error('Invalid argument nodes for mermaid.init'); + throw new Error('Nodes and querySelector are both undefined'); } log.debug(`Found ${nodesToProcess.length} diagrams`); - if (config?.startOnLoad !== undefined) { - log.debug('Start On Load: ' + config?.startOnLoad); - mermaidAPI.updateSiteConfig({ startOnLoad: config?.startOnLoad }); + if (conf?.startOnLoad !== undefined) { + log.debug('Start On Load: ' + conf?.startOnLoad); + mermaidAPI.updateSiteConfig({ startOnLoad: conf?.startOnLoad }); } // generate the id of the diagram @@ -142,8 +175,7 @@ const initThrowsErrors = function ( txt = element.innerHTML; // transforms the html to pure text - txt = utils - .entityDecode(txt) + txt = dedent(utils.entityDecode(txt)) // removes indentation, required for YAML parsing .trim() .replace(//gi, '
'); @@ -152,20 +184,14 @@ const initThrowsErrors = function ( log.debug('Detected early reinit: ', init); } try { - mermaidAPI.render( - id, - txt, - (svgCode: string, bindFunctions?: (el: Element) => void) => { - element.innerHTML = svgCode; - if (callback !== undefined) { - callback(id); - } - if (bindFunctions) { - bindFunctions(element); - } - }, - element - ); + const { svg, bindFunctions } = await mermaidAPI.render(id, txt, element); + element.innerHTML = svg; + if (postRenderCallback) { + await postRenderCallback(id); + } + if (bindFunctions) { + bindFunctions(element); + } } catch (error) { handleError(error, errors, mermaid.parseError); } @@ -177,116 +203,51 @@ const initThrowsErrors = function ( }; /** - * Equivalent to {@link init()}, except an error will be thrown on error. - * - * @alpha - * @deprecated This is an internal function and will very likely be modified in v10, or earlier. - * We recommend staying with {@link initThrowsErrors} if you don't need `lazyLoadedDiagrams`. - * - * @param config - **Deprecated** Mermaid sequenceConfig. - * @param nodes - One of: - * - A DOM Node - * - An array of DOM nodes (as would come from a jQuery selector) - * - A W3C selector, a la `.mermaid` (default) - * @param callback - Function that is called with the id of each generated mermaid diagram. - * @returns Resolves on success, otherwise the {@link Promise} will be rejected. + * Used to set configurations for mermaid. + * This function should be called before the run function. + * @param config - Configuration object for mermaid. */ -const initThrowsErrorsAsync = async function ( - config?: MermaidConfig, - // eslint-disable-next-line no-undef - nodes?: string | HTMLElement | NodeListOf, - // eslint-disable-next-line @typescript-eslint/ban-types - callback?: Function -) { - const conf = mermaidAPI.getConfig(); - - if (config) { - // This is a legacy way of setting config. It is not documented and should be removed in the future. - // @ts-ignore: TODO Fix ts errors - mermaid.sequenceConfig = config; - } - - // if last argument is a function this is the callback function - log.debug(`${!callback ? 'No ' : ''}Callback function found`); - let nodesToProcess: ArrayLike; - if (nodes === undefined) { - nodesToProcess = document.querySelectorAll('.mermaid'); - } else if (typeof nodes === 'string') { - nodesToProcess = document.querySelectorAll(nodes); - } else if (nodes instanceof HTMLElement) { - nodesToProcess = [nodes]; - } else if (nodes instanceof NodeList) { - nodesToProcess = nodes; - } else { - throw new Error('Invalid argument nodes for mermaid.init'); - } - - log.debug(`Found ${nodesToProcess.length} diagrams`); - if (config?.startOnLoad !== undefined) { - log.debug('Start On Load: ' + config?.startOnLoad); - mermaidAPI.updateSiteConfig({ startOnLoad: config?.startOnLoad }); - } - - // generate the id of the diagram - const idGenerator = new utils.initIdGenerator(conf.deterministicIds, conf.deterministicIDSeed); - - let txt: string; - const errors: DetailedError[] = []; - - // element is the current div with mermaid class - // eslint-disable-next-line unicorn/prefer-spread - for (const element of Array.from(nodesToProcess)) { - log.info('Rendering diagram: ' + element.id); - /*! Check if previously processed */ - if (element.getAttribute('data-processed')) { - continue; - } - element.setAttribute('data-processed', 'true'); - - const id = `mermaid-${idGenerator.next()}`; - - // Fetch the graph definition including tags - txt = element.innerHTML; - - // transforms the html to pure text - txt = utils - .entityDecode(txt) - .trim() - .replace(//gi, '
'); - - const init = utils.detectInit(txt); - if (init) { - log.debug('Detected early reinit: ', init); - } - try { - await mermaidAPI.renderAsync( - id, - txt, - (svgCode: string, bindFunctions?: (el: Element) => void) => { - element.innerHTML = svgCode; - if (callback !== undefined) { - callback(id); - } - if (bindFunctions) { - bindFunctions(element); - } - }, - element - ); - } catch (error) { - handleError(error, errors, mermaid.parseError); - } - } - if (errors.length > 0) { - // TODO: We should be throwing an error object. - throw errors[0]; - } -}; const initialize = function (config: MermaidConfig) { mermaidAPI.initialize(config); }; +/** + * ## init + * + * @deprecated Use {@link initialize} and {@link run} instead. + * + * Renders the mermaid diagrams + * + * @param config - **Deprecated**, please set configuration in {@link initialize}. + * @param nodes - **Default**: `.mermaid`. One of the following: + * - A DOM Node + * - An array of DOM nodes (as would come from a jQuery selector) + * - A W3C selector, a la `.mermaid` + * @param callback - Called once for each rendered diagram's id. + */ +const init = async function ( + config?: MermaidConfig, + nodes?: string | HTMLElement | NodeListOf, + callback?: (id: string) => unknown +) { + log.warn('mermaid.init is deprecated. Please use run instead.'); + if (config) { + initialize(config); + } + const runOptions: RunOptions = { postRenderCallback: callback, querySelector: '.mermaid' }; + if (typeof nodes === 'string') { + runOptions.querySelector = nodes; + } else if (nodes) { + if (nodes instanceof HTMLElement) { + runOptions.nodes = [nodes]; + } else { + runOptions.nodes = nodes; + } + } + await run(runOptions); +}; + /** * Used to register external diagram types. * @param diagrams - Array of {@link ExternalDiagramDefinition}. @@ -316,7 +277,7 @@ const contentLoaded = function () { if (mermaid.startOnLoad) { const { startOnLoad } = mermaidAPI.getConfig(); if (startOnLoad) { - mermaid.init(); + mermaid.run().catch((err) => log.error('Mermaid failed to initialize', err)); } } }; @@ -340,14 +301,10 @@ if (typeof document !== 'undefined') { * This is provided for environments where the mermaid object can't directly have a new member added * to it (eg. dart interop wrapper). (Initially there is no parseError member of mermaid). * - * @param newParseErrorHandler - New parseError() callback. + * @param parseErrorHandler - New parseError() callback. */ -const setParseErrorHandler = function (newParseErrorHandler: (err: any, hash: any) => void) { - mermaid.parseError = newParseErrorHandler; -}; - -const parse = (txt: string) => { - return mermaidAPI.parse(txt, mermaid.parseError); +const setParseErrorHandler = function (parseErrorHandler: (err: any, hash: any) => void) { + mermaid.parseError = parseErrorHandler; }; const executionQueue: (() => Promise)[] = []; @@ -371,16 +328,19 @@ const executeQueue = async () => { }; /** - * @param txt - The mermaid code to be parsed. - * @deprecated This is an internal function and should not be used. Will be removed in v10. + * Parse the text and validate the syntax. + * @param text - The mermaid diagram definition. + * @param parseOptions - Options for parsing. + * @returns true if the diagram is valid, false otherwise if parseOptions.suppressErrors is true. + * @throws Error if the diagram is invalid and parseOptions.suppressErrors is false. */ -const parseAsync = (txt: string): Promise => { +const parse = async (text: string, parseOptions?: ParseOptions): Promise => { return new Promise((resolve, reject) => { // This promise will resolve when the mermaidAPI.render call is done. // It will be queued first and will be executed when it is first in line const performCall = () => new Promise((res, rej) => { - mermaidAPI.parseAsync(txt, mermaid.parseError).then( + mermaidAPI.parse(text, parseOptions).then( (r) => { // This resolves for the promise for the queue handling res(r); @@ -389,31 +349,24 @@ const parseAsync = (txt: string): Promise => { }, (e) => { log.error('Error parsing', e); + mermaid.parseError?.(e); rej(e); reject(e); } ); }); executionQueue.push(performCall); - executeQueue(); + executeQueue().catch(reject); }); }; -/** - * @deprecated This is an internal function and should not be used. Will be removed in v10. - */ -const renderAsync = ( - id: string, - text: string, - cb?: (svgCode: string, bindFunctions?: (element: Element) => void) => void, - container?: Element -): Promise => { +const render = (id: string, text: string, container?: Element): Promise => { return new Promise((resolve, reject) => { // This promise will resolve when the mermaidAPI.render call is done. // It will be queued first and will be executed when it is first in line const performCall = () => new Promise((res, rej) => { - mermaidAPI.renderAsync(id, text, cb, container).then( + mermaidAPI.render(id, text, container).then( (r) => { // This resolves for the promise for the queue handling res(r); @@ -422,43 +375,36 @@ const renderAsync = ( }, (e) => { log.error('Error parsing', e); + mermaid.parseError?.(e); rej(e); reject(e); } ); }); executionQueue.push(performCall); - executeQueue(); + executeQueue().catch(reject); }); }; const mermaid: { startOnLoad: boolean; - diagrams: any; parseError?: ParseErrorFunction; mermaidAPI: typeof mermaidAPI; parse: typeof parse; - parseAsync: typeof parseAsync; - render: typeof mermaidAPI.render; - renderAsync: typeof renderAsync; + render: typeof render; init: typeof init; - initThrowsErrors: typeof initThrowsErrors; - initThrowsErrorsAsync: typeof initThrowsErrorsAsync; + run: typeof run; registerExternalDiagrams: typeof registerExternalDiagrams; initialize: typeof initialize; contentLoaded: typeof contentLoaded; setParseErrorHandler: typeof setParseErrorHandler; } = { startOnLoad: true, - diagrams: {}, mermaidAPI, parse, - parseAsync, - render: mermaidAPI.render, - renderAsync, + render, init, - initThrowsErrors, - initThrowsErrorsAsync, + run, registerExternalDiagrams, initialize, parseError: undefined, diff --git a/packages/mermaid/src/mermaidAPI.spec.ts b/packages/mermaid/src/mermaidAPI.spec.ts index 55d46ae7c..cf858b58e 100644 --- a/packages/mermaid/src/mermaidAPI.spec.ts +++ b/packages/mermaid/src/mermaidAPI.spec.ts @@ -1,6 +1,38 @@ 'use strict'; import { vi } from 'vitest'; +// ------------------------------------- +// Mocks and mocking + +import { MockedD3 } from './tests/MockedD3'; + +// Note: If running this directly from within an IDE, the mocks directory must be at packages/mermaid/mocks +vi.mock('d3'); +vi.mock('dagre-d3'); + +// mermaidAPI.spec.ts: +import * as accessibility from './accessibility'; // Import it this way so we can use spyOn(accessibility,...) +vi.mock('./accessibility', () => ({ + setA11yDiagramInfo: vi.fn(), + addSVGa11yTitleDescription: vi.fn(), +})); + +// Mock the renderers specifically so we can test render(). Need to mock draw() for each renderer +vi.mock('./diagrams/c4/c4Renderer'); +vi.mock('./diagrams/class/classRenderer'); +vi.mock('./diagrams/class/classRenderer-v2'); +vi.mock('./diagrams/er/erRenderer'); +vi.mock('./diagrams/flowchart/flowRenderer-v2'); +vi.mock('./diagrams/git/gitGraphRenderer'); +vi.mock('./diagrams/gantt/ganttRenderer'); +vi.mock('./diagrams/user-journey/journeyRenderer'); +vi.mock('./diagrams/pie/pieRenderer'); +vi.mock('./diagrams/requirement/requirementRenderer'); +vi.mock('./diagrams/sequence/sequenceRenderer'); +vi.mock('./diagrams/state/stateRenderer-v2'); + +// ------------------------------------- + import mermaid from './mermaid'; import { MermaidConfig } from './config.type'; @@ -37,11 +69,14 @@ vi.mock('stylis', () => { }); import { compile, serialize } from 'stylis'; -import { MockedD3 } from './tests/MockedD3'; +/** + * @see https://vitest.dev/guide/mocking.html Mock part of a module + * To investigate how to mock just some methods from a module - call the actual implementation and then mock others, e.g. so they can be spied on + */ // ------------------------------------------------------------------------------------- -describe('mermaidAPI', function () { +describe('mermaidAPI', () => { describe('encodeEntities', () => { it('removes the ending ; from style [text1]:[optional word]#[text2]; with ', () => { const text = 'style this; is ; everything :something#not-nothing; and this too;'; @@ -335,7 +370,8 @@ describe('mermaidAPI', function () { const htmlElements = ['> *', 'span']; it('creates CSS styles for every style and textStyle in every classDef', () => { - // @todo TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result + // @todo TODO Can't figure out how to spy on the cssImportantStyles method. + // That would be a much better approach than manually checking the result const styles = createCssStyles(mocked_config, graphType, classDefs); htmlElements.forEach((htmlElement) => { @@ -373,7 +409,7 @@ describe('mermaidAPI', function () { const htmlElements = ['rect', 'polygon', 'ellipse', 'circle']; it('creates CSS styles for every style and textStyle in every classDef', () => { - // @todo TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result + // TODO Can't figure out how to spy on the cssImportantStyles method. That would be a much better approach than manually checking the result. const styles = createCssStyles(mocked_config_no_htmlLabels, graphType, classDefs); htmlElements.forEach((htmlElement) => { @@ -434,71 +470,58 @@ describe('mermaidAPI', function () { svgElement.id = svgId; const tempDivElement = givenDocument.createElement('div'); // doesn't matter what the tag is in the test tempDivElement.id = tempDivId; - const tempiFrameElement = givenDocument.createElement('div'); // doesn't matter what the tag is in the test + const tempiFrameElement = givenDocument.createElement('iframe'); // doesn't matter what the tag is in the test tempiFrameElement.id = tempIframeId; it('removes an existing element with given id', () => { rootHtml.appendChild(svgElement); + rootHtml.append(tempDivElement); + rootHtml.append(tempiFrameElement); + expect(givenDocument.getElementById(svgElement.id)).toEqual(svgElement); - removeExistingElements(givenDocument, false, svgId, tempDivId, tempIframeId); + expect(givenDocument.getElementById(tempDivElement.id)).toEqual(tempDivElement); + expect(givenDocument.getElementById(tempiFrameElement.id)).toEqual(tempiFrameElement); + removeExistingElements(givenDocument, svgId, tempDivId, tempIframeId); expect(givenDocument.getElementById(svgElement.id)).toBeNull(); + expect(givenDocument.getElementById(tempDivElement.id)).toBeNull(); + expect(givenDocument.getElementById(tempiFrameElement.id)).toBeNull(); }); - describe('is in sandboxed mode', () => { - const inSandboxedMode = true; + it('removes an existing iframe element even if div element is absent', () => { + tempiFrameElement.append(svgElement); + rootHtml.append(tempiFrameElement); - it('removes an existing element with the given iFrame selector', () => { - tempiFrameElement.append(svgElement); - rootHtml.append(tempiFrameElement); - rootHtml.append(tempDivElement); - - expect(givenDocument.getElementById(tempIframeId)).toEqual(tempiFrameElement); - expect(givenDocument.getElementById(tempDivId)).toEqual(tempDivElement); - expect(givenDocument.getElementById(svgId)).toEqual(svgElement); - removeExistingElements( - givenDocument, - inSandboxedMode, - svgId, - '#' + tempDivId, - '#' + tempIframeId - ); - expect(givenDocument.getElementById(tempDivId)).toEqual(tempDivElement); - expect(givenDocument.getElementById(tempIframeId)).toBeNull(); - expect(givenDocument.getElementById(svgId)).toBeNull(); - }); + expect(givenDocument.getElementById(tempIframeId)).toEqual(tempiFrameElement); + expect(givenDocument.getElementById(tempDivId)).toBeNull(); + expect(givenDocument.getElementById(svgId)).toEqual(svgElement); + removeExistingElements(givenDocument, svgId, tempDivId, tempIframeId); + expect(givenDocument.getElementById(tempDivId)).toBeNull(); + expect(givenDocument.getElementById(tempIframeId)).toBeNull(); + expect(givenDocument.getElementById(svgId)).toBeNull(); }); - describe('not in sandboxed mode', () => { - const inSandboxedMode = false; - it('removes an existing element with the given enclosing div selector', () => { - tempDivElement.append(svgElement); - rootHtml.append(tempDivElement); - rootHtml.append(tempiFrameElement); + it('removes both existing div and iframe elements when both are present', () => { + tempDivElement.append(svgElement); + rootHtml.append(tempDivElement); + rootHtml.append(tempiFrameElement); - expect(givenDocument.getElementById(tempIframeId)).toEqual(tempiFrameElement); - expect(givenDocument.getElementById(tempDivId)).toEqual(tempDivElement); - expect(givenDocument.getElementById(svgId)).toEqual(svgElement); - removeExistingElements( - givenDocument, - inSandboxedMode, - svgId, - '#' + tempDivId, - '#' + tempIframeId - ); - expect(givenDocument.getElementById(tempIframeId)).toEqual(tempiFrameElement); - expect(givenDocument.getElementById(tempDivId)).toBeNull(); - expect(givenDocument.getElementById(svgId)).toBeNull(); - }); + expect(givenDocument.getElementById(tempIframeId)).toEqual(tempiFrameElement); + expect(givenDocument.getElementById(tempDivId)).toEqual(tempDivElement); + expect(givenDocument.getElementById(svgId)).toEqual(svgElement); + removeExistingElements(givenDocument, svgId, tempDivId, tempIframeId); + expect(givenDocument.getElementById(tempIframeId)).toBeNull(); + expect(givenDocument.getElementById(tempDivId)).toBeNull(); + expect(givenDocument.getElementById(svgId)).toBeNull(); }); }); - describe('initialize', function () { - beforeEach(function () { + describe('initialize', () => { + beforeEach(() => { document.body.innerHTML = ''; mermaidAPI.globalReset(); }); - it('copies a literal into the configuration', function () { + it('copies a literal into the configuration', () => { const orgConfig: any = mermaidAPI.getConfig(); expect(orgConfig.testLiteral).toBe(undefined); @@ -510,7 +533,7 @@ describe('mermaidAPI', function () { expect(config.testLiteral).toBe(true); }); - it('copies a an object into the configuration', function () { + it('copies an object into the configuration', () => { const orgConfig: any = mermaidAPI.getConfig(); expect(orgConfig.testObject).toBe(undefined); @@ -536,7 +559,7 @@ describe('mermaidAPI', function () { expect(config.testObject.test3).toBe(true); }); - it('resets mermaid config to global defaults', function () { + it('resets mermaid config to global defaults', () => { const config = { logLevel: 0, securityLevel: 'loose', @@ -553,7 +576,7 @@ describe('mermaidAPI', function () { expect(mermaidAPI.getConfig().securityLevel).toBe('strict'); }); - it('prevents changes to site defaults (sneaky)', function () { + it('prevents changes to site defaults (sneaky)', () => { const config: any = { logLevel: 0, }; @@ -561,7 +584,7 @@ describe('mermaidAPI', function () { const siteConfig = mermaidAPI.getSiteConfig(); expect(mermaidAPI.getConfig().logLevel).toBe(0); config.secure = { - toString: function () { + toString: () => { mermaidAPI.initialize({ securityLevel: 'loose' }); }, }; @@ -572,7 +595,7 @@ describe('mermaidAPI', function () { expect(mermaidAPI.getSiteConfig()).toEqual(siteConfig); expect(mermaidAPI.getConfig()).toEqual(siteConfig); }); - it('prevents clobbering global defaults (direct)', function () { + it('prevents clobbering global defaults (direct)', () => { const config = assignWithDepth({}, mermaidAPI.defaultConfig); assignWithDepth(config, { logLevel: 0 }); @@ -588,7 +611,7 @@ describe('mermaidAPI', function () { ); expect(mermaidAPI.defaultConfig['logLevel']).toBe(5); }); - it('prevents changes to global defaults (direct)', function () { + it('prevents changes to global defaults (direct)', () => { let error: any = { message: '' }; try { mermaidAPI.defaultConfig['logLevel'] = 0; @@ -600,7 +623,7 @@ describe('mermaidAPI', function () { ); expect(mermaidAPI.defaultConfig['logLevel']).toBe(5); }); - it('prevents sneaky changes to global defaults (assignWithDepth)', function () { + it('prevents sneaky changes to global defaults (assignWithDepth)', () => { const config = { logLevel: 0, }; @@ -616,34 +639,164 @@ describe('mermaidAPI', function () { expect(mermaidAPI.defaultConfig['logLevel']).toBe(5); }); }); - describe('dompurify config', function () { - it('allows dompurify config to be set', function () { + + describe('dompurify config', () => { + it('allows dompurify config to be set', () => { mermaidAPI.initialize({ dompurifyConfig: { ADD_ATTR: ['onclick'] } }); expect(mermaidAPI!.getConfig()!.dompurifyConfig!.ADD_ATTR).toEqual(['onclick']); }); }); - describe('parse', function () { + + describe('parse', () => { mermaid.parseError = undefined; // ensure it parseError undefined - it('throws for an invalid definition (with no mermaid.parseError() defined)', function () { + it('throws for an invalid definition (with no mermaid.parseError() defined)', async () => { expect(mermaid.parseError).toEqual(undefined); - expect(() => mermaidAPI.parse('this is not a mermaid diagram definition')).toThrow(); + await expect( + mermaidAPI.parse('this is not a mermaid diagram definition') + ).rejects.toThrowError(); }); - it('does not throw for a valid definition', function () { - expect(() => mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).not.toThrow(); + it('throws for a nicer error for a invalid definition starting with `---`', async () => { + expect(mermaid.parseError).toEqual(undefined); + await expect( + mermaidAPI.parse(` + --- + title: a malformed YAML front-matter + `) + ).rejects.toThrow( + 'Diagrams beginning with --- are not valid. ' + + 'If you were trying to use a YAML front-matter, please ensure that ' + + "you've correctly opened and closed the YAML front-matter with unindented `---` blocks" + ); }); - it('returns false for invalid definition WITH a parseError() callback defined', function () { - let parseErrorWasCalled = false; - // also test setParseErrorHandler() call working to set mermaid.parseError - expect( - mermaidAPI.parse('this is not a mermaid diagram definition', () => { - parseErrorWasCalled = true; - }) - ).toEqual(false); - expect(parseErrorWasCalled).toEqual(true); + it('does not throw for a valid definition', async () => { + await expect( + mermaidAPI.parse('graph TD;A--x|text including URL space|B;') + ).resolves.not.toThrow(); }); - it('returns true for valid definition', function () { - expect(mermaidAPI.parse('graph TD;A--x|text including URL space|B;')).toEqual(true); + it('throws for invalid definition', async () => { + await expect( + mermaidAPI.parse('this is not a mermaid diagram definition') + ).rejects.toThrowErrorMatchingInlineSnapshot( + '"No diagram type detected for text: this is not a mermaid diagram definition"' + ); + }); + it('returns false for invalid definition with silent option', async () => { + await expect( + mermaidAPI.parse('this is not a mermaid diagram definition', { suppressErrors: true }) + ).resolves.toBe(false); + }); + it('resolves for valid definition', async () => { + await expect( + mermaidAPI.parse('graph TD;A--x|text including URL space|B;') + ).resolves.not.toThrow(); + }); + it('returns true for valid definition with silent option', async () => { + await expect( + mermaidAPI.parse('graph TD;A--x|text including URL space|B;', { suppressErrors: true }) + ).resolves.toBe(true); + }); + }); + + describe('render', () => { + // These are more like integration tests right now because nothing is mocked. + // But it is faster that a cypress test and there's no real reason to actually evaluate an image pixel by pixel. + + // render(id, text, cb?, svgContainingElement?) + + // Test all diagram types. Note that old flowchart 'graph' type will invoke the flowRenderer-v2. (See the flowchart v2 detector.) + // We have to have both the specific textDiagramType and the expected type name because the expected type may be slightly different than was is put in the diagram text (ex: in -v2 diagrams) + const diagramTypesAndExpectations = [ + { textDiagramType: 'C4Context', expectedType: 'c4' }, + { textDiagramType: 'classDiagram', expectedType: 'classDiagram' }, + { textDiagramType: 'classDiagram-v2', expectedType: 'classDiagram' }, + { textDiagramType: 'erDiagram', expectedType: 'er' }, + { textDiagramType: 'graph', expectedType: 'flowchart-v2' }, + { textDiagramType: 'flowchart', expectedType: 'flowchart-v2' }, + { textDiagramType: 'gitGraph', expectedType: 'gitGraph' }, + { textDiagramType: 'gantt', expectedType: 'gantt' }, + { textDiagramType: 'journey', expectedType: 'journey' }, + { textDiagramType: 'pie', expectedType: 'pie' }, + { textDiagramType: 'requirementDiagram', expectedType: 'requirement' }, + { textDiagramType: 'sequenceDiagram', expectedType: 'sequence' }, + { textDiagramType: 'stateDiagram-v2', expectedType: 'stateDiagram' }, + ]; + + describe('accessibility', () => { + const id = 'mermaid-fauxId'; + const a11yTitle = 'a11y title'; + const a11yDescr = 'a11y description'; + + diagramTypesAndExpectations.forEach((testedDiagram) => { + describe(`${testedDiagram.textDiagramType}`, () => { + const diagramType = testedDiagram.textDiagramType; + const diagramText = `${diagramType}\n accTitle: ${a11yTitle}\n accDescr: ${a11yDescr}\n`; + const expectedDiagramType = testedDiagram.expectedType; + + it('aria-roledscription is set to the diagram type, addSVGa11yTitleDescription is called', async () => { + const a11yDiagramInfo_spy = vi.spyOn(accessibility, 'setA11yDiagramInfo'); + const a11yTitleDesc_spy = vi.spyOn(accessibility, 'addSVGa11yTitleDescription'); + await mermaidAPI.render(id, diagramText); + expect(a11yDiagramInfo_spy).toHaveBeenCalledWith( + expect.anything(), + expectedDiagramType + ); + expect(a11yTitleDesc_spy).toHaveBeenCalled(); + }); + }); + }); + }); + }); + + describe('render', () => { + // Be sure to add async before each test (anonymous) method + + // These are more like integration tests right now because nothing is mocked. + // But it is faster that a cypress test and there's no real reason to actually evaluate an image pixel by pixel. + + // render(id, text, cb?, svgContainingElement?) + + // Test all diagram types. Note that old flowchart 'graph' type will invoke the flowRenderer-v2. (See the flowchart v2 detector.) + // We have to have both the specific textDiagramType and the expected type name because the expected type may be slightly different than was is put in the diagram text (ex: in -v2 diagrams) + const diagramTypesAndExpectations = [ + { textDiagramType: 'C4Context', expectedType: 'c4' }, + { textDiagramType: 'classDiagram', expectedType: 'classDiagram' }, + { textDiagramType: 'classDiagram-v2', expectedType: 'classDiagram' }, + { textDiagramType: 'erDiagram', expectedType: 'er' }, + { textDiagramType: 'graph', expectedType: 'flowchart-v2' }, + { textDiagramType: 'flowchart', expectedType: 'flowchart-v2' }, + { textDiagramType: 'gitGraph', expectedType: 'gitGraph' }, + { textDiagramType: 'gantt', expectedType: 'gantt' }, + { textDiagramType: 'journey', expectedType: 'journey' }, + { textDiagramType: 'pie', expectedType: 'pie' }, + { textDiagramType: 'requirementDiagram', expectedType: 'requirement' }, + { textDiagramType: 'sequenceDiagram', expectedType: 'sequence' }, + { textDiagramType: 'stateDiagram-v2', expectedType: 'stateDiagram' }, + ]; + + describe('accessibility', () => { + const id = 'mermaid-fauxId'; + const a11yTitle = 'a11y title'; + const a11yDescr = 'a11y description'; + + diagramTypesAndExpectations.forEach((testedDiagram) => { + describe(`${testedDiagram.textDiagramType}`, () => { + const diagramType = testedDiagram.textDiagramType; + const diagramText = `${diagramType}\n accTitle: ${a11yTitle}\n accDescr: ${a11yDescr}\n`; + const expectedDiagramType = testedDiagram.expectedType; + + it('aria-roledscription is set to the diagram type, addSVGa11yTitleDescription is called', async () => { + const a11yDiagramInfo_spy = vi.spyOn(accessibility, 'setA11yDiagramInfo'); + const a11yTitleDesc_spy = vi.spyOn(accessibility, 'addSVGa11yTitleDescription'); + await mermaidAPI.render(id, diagramText); + expect(a11yDiagramInfo_spy).toHaveBeenCalledWith( + expect.anything(), + expectedDiagramType + ); + expect(a11yTitleDesc_spy).toHaveBeenCalled(); + }); + }); + }); }); }); }); diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 309cef115..999db5088 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -10,13 +10,15 @@ * * In addition to the render function, a number of behavioral configuration options are available. */ +// @ts-ignore TODO: Investigate D3 issue import { select } from 'd3'; import { compile, serialize, stringify } from 'stylis'; // @ts-ignore: TODO Fix ts errors -import pkg from '../package.json'; +import { version } from '../package.json'; import * as configApi from './config'; import { addDiagrams } from './diagram-api/diagram-orchestration'; -import Diagram, { getDiagramFromText, type ParseErrorFunction } from './Diagram'; +import { Diagram, getDiagramFromText } from './Diagram'; +import errorRenderer from './diagrams/error/errorRenderer'; import { attachFunctions } from './interactionDb'; import { log, setLogLevel } from './logger'; import getStyles from './styles'; @@ -25,12 +27,13 @@ import utils, { directiveSanitizer } from './utils'; import DOMPurify from 'dompurify'; import { MermaidConfig } from './config.type'; import { evaluate } from './diagrams/common/common'; -import errorRenderer from './diagrams/error/errorRenderer'; -import isEmpty from 'lodash-es/isEmpty'; +import isEmpty from 'lodash-es/isEmpty.js'; +import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility'; +import { parseDirective } from './directiveUtils'; // diagram names that support classDef statements const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram', 'stateDiagram-v2']; - +const MAX_TEXTLENGTH = 50_000; const MAX_TEXTLENGTH_EXCEEDED_MSG = 'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa'; @@ -51,8 +54,8 @@ const IFRAME_SANDBOX_OPTS = 'allow-top-navigation-by-user-activation allow-popup const IFRAME_NOT_SUPPORTED_MSG = 'The "iframe" tag is not supported by your browser.'; // DOMPurify settings for svgCode -const DOMPURE_TAGS = ['foreignobject']; -const DOMPURE_ATTR = ['dominant-baseline']; +const DOMPURIFY_TAGS = ['foreignobject']; +const DOMPURIFY_ATTR = ['dominant-baseline']; // This is what is returned from getClasses(...) methods. // It is slightly renamed to ..StyleClassDef instead of just ClassDef because "class" is a greatly ambiguous and overloaded word. @@ -63,30 +66,37 @@ interface DiagramStyleClassDef { textStyles?: string[]; } -// This makes it clear that we're working with a d3 selected element of some kind, even though it's hard to specify the exact type. -// @ts-ignore Could replicate the type definition in d3. This also makes it possible to use the untyped info from the js diagram files. -type D3Element = any; - -// ---------------------------------------------------------------------------- - -/** - * @param text - The mermaid diagram definition. - * @param parseError - If set, handles errors. - */ -function parse(text: string, parseError?: ParseErrorFunction): boolean { - addDiagrams(); - const diagram = new Diagram(text, parseError); - return diagram.parse(text, parseError); +export interface ParseOptions { + suppressErrors?: boolean; } +// This makes it clear that we're working with a d3 selected element of some kind, even though it's hard to specify the exact type. +// @ts-ignore Could replicate the type definition in d3. This also makes it possible to use the untyped info from the js diagram files. +export type D3Element = any; + /** + * Parse the text and validate the syntax. * @param text - The mermaid diagram definition. - * @param parseError - If set, handles errors. + * @param parseOptions - Options for parsing. + * @returns true if the diagram is valid, false otherwise if parseOptions.suppressErrors is true. + * @throws Error if the diagram is invalid and parseOptions.suppressErrors is false. */ -async function parseAsync(text: string, parseError?: ParseErrorFunction): Promise { + +async function parse(text: string, parseOptions?: ParseOptions): Promise { addDiagrams(); - const diagram = await getDiagramFromText(text, parseError); - return diagram.parse(text, parseError); + let error; + try { + const diagram = await getDiagramFromText(text); + diagram.parse(); + } catch (err) { + error = err; + } + if (parseOptions?.suppressErrors) { + return error === undefined; + } + if (error) { + throw error; + } } /** @@ -324,29 +334,22 @@ function sandboxedIframe(parentNode: D3Element, iFrameId: string): D3Element { * Remove any existing elements from the given document * * @param doc - the document to removed elements from - * @param isSandboxed - whether or not we are in sandboxed mode * @param id - id for any existing SVG element * @param divSelector - selector for any existing enclosing div element * @param iFrameSelector - selector for any existing iFrame element */ export const removeExistingElements = ( doc: Document, - isSandboxed: boolean, id: string, - divSelector: string, - iFrameSelector: string + divId: string, + iFrameId: string ) => { // Remove existing SVG element if it exists - const existingSvg = doc.getElementById(id); - if (existingSvg) { - existingSvg.remove(); - } - + doc.getElementById(id)?.remove(); // Remove previous temporary element if it exists - const element = isSandboxed ? doc.querySelector(iFrameSelector) : doc.querySelector(divSelector); - if (element) { - element.remove(); - } + // Both div and iframe needs to be cleared in case there is a config change happening between renders. + doc.getElementById(divId)?.remove(); + doc.getElementById(iFrameId)?.remove(); }; /** @@ -368,19 +371,24 @@ export const removeExistingElements = ( * @param id - The id for the SVG element (the element to be rendered) * @param text - The text for the graph definition * @param cb - Callback which is called after rendering is finished with the svg code as in param. - * @param container - HTML element where the svg will be inserted. (Is usually element with the .mermaid class) + * @param svgContainingElement - HTML element where the svg will be inserted. (Is usually element with the .mermaid class) * If no svgContainingElement is provided then the SVG element will be appended to the body. * Selector to element in which a div with the graph temporarily will be * inserted. If one is provided a hidden div will be inserted in the body of the page instead. The * element will be removed when rendering is completed. * @returns Returns the rendered element as a string containing the SVG definition. */ -const render = function ( + +export interface RenderResult { + svg: string; + bindFunctions?: (element: Element) => void; +} + +const render = async function ( id: string, text: string, - cb?: (svgCode: string, bindFunctions?: (element: Element) => void) => void, svgContainingElement?: Element -): string { +): Promise { addDiagrams(); configApi.reset(); @@ -396,8 +404,7 @@ const render = function ( log.debug(config); // Check the maximum allowed text size - // TODO: Remove magic number - if (text.length > (config?.maxTextSize ?? 50000)) { + if (text.length > (config?.maxTextSize ?? MAX_TEXTLENGTH)) { text = MAX_TEXTLENGTH_EXCEEDED_MSG; } @@ -439,7 +446,7 @@ const render = function ( // No svgContainingElement was provided // If there is an existing element with the id, we remove it. This likely a previously rendered diagram - removeExistingElements(document, isSandboxed, id, iFrameID_selector, enclosingDivID_selector); + removeExistingElements(document, id, enclosingDivID, iFrameID); // Add the temporary div used for rendering with the enclosingDivID. // This temporary div will contain a svg with the id == id @@ -462,206 +469,10 @@ const render = function ( // Create the diagram // Important that we do not create the diagram until after the directives have been included - let diag; + let diag: Diagram; let parseEncounteredException; try { - // diag = new Diagram(text); - diag = getDiagramFromText(text); - if ('then' in diag) { - throw new Error('Diagram is a promise. Use renderAsync.'); - } - } catch (error) { - diag = new Diagram('error'); - parseEncounteredException = error; - } - - // Get the temporary div element containing the svg - const element = root.select(enclosingDivID_selector).node(); - const graphType = diag.type; - - // ------------------------------------------------------------------------------- - // Create and insert the styles (user styles, theme styles, config styles) - - // Insert an element into svg. This is where we put the styles - const svg = element.firstChild; - const firstChild = svg.firstChild; - const diagramClassDefs = CLASSDEF_DIAGRAMS.includes(graphType) - ? diag.renderer.getClasses(text, diag) - : {}; - - const rules = createUserStyles( - config, - graphType, - // @ts-ignore convert renderer to TS. - diagramClassDefs, - idSelector - ); - - const style1 = document.createElement('style'); - style1.innerHTML = rules; - svg.insertBefore(style1, firstChild); - - // ------------------------------------------------------------------------------- - // Draw the diagram with the renderer - try { - diag.renderer.draw(text, id, pkg.version, diag); - } catch (e) { - errorRenderer.draw(text, id, pkg.version); - throw e; - } - - // ------------------------------------------------------------------------------- - // Clean up SVG code - root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD); - - // Fix for when the base tag is used - let svgCode = root.select(enclosingDivID_selector).node().innerHTML; - - log.debug('config.arrowMarkerAbsolute', config.arrowMarkerAbsolute); - svgCode = cleanUpSvgCode(svgCode, isSandboxed, evaluate(config.arrowMarkerAbsolute)); - - if (isSandboxed) { - const svgEl = root.select(enclosingDivID_selector + ' svg').node(); - svgCode = putIntoIFrame(svgCode, svgEl); - } else if (isLooseSecurityLevel) { - // Sanitize the svgCode using DOMPurify - svgCode = DOMPurify.sanitize(svgCode, { - ADD_TAGS: DOMPURE_TAGS, - ADD_ATTR: DOMPURE_ATTR, - }); - } - - // ------------------------------------------------------------------------------- - // Do any callbacks (cb = callback) - if (cb !== undefined) { - switch (graphType) { - case 'flowchart': - case 'flowchart-v2': - case 'gantt': - case 'class': - case 'classDiagram': - cb(svgCode, diag.db.bindFunctions); - break; - default: - cb(svgCode); - } - } else { - log.debug('CB = undefined!'); - } - attachFunctions(); - - // ------------------------------------------------------------------------------- - // Remove the temporary element if appropriate - const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector; - const node = select(tmpElementSelector).node(); - if (node && 'remove' in node) { - node.remove(); - } - - if (parseEncounteredException) { - throw parseEncounteredException; - } - - return svgCode; -}; - -/** - * @deprecated This is an internal function and should not be used. Will be removed in v10. - */ - -const renderAsync = async function ( - id: string, - text: string, - cb?: (svgCode: string, bindFunctions?: (element: Element) => void) => void, - svgContainingElement?: Element -): Promise { - addDiagrams(); - - configApi.reset(); - - // Add Directives. Must do this before getting the config and before creating the diagram. - const graphInit = utils.detectInit(text); - if (graphInit) { - directiveSanitizer(graphInit); - configApi.addDirective(graphInit); - } - - const config = configApi.getConfig(); - log.debug(config); - - // Check the maximum allowed text size - // TODO: Remove magic number - if (text.length > (config?.maxTextSize ?? 50000)) { - text = MAX_TEXTLENGTH_EXCEEDED_MSG; - } - - // clean up text CRLFs - text = text.replace(/\r\n?/g, '\n'); // parser problems on CRLF ignore all CR and leave LF;; - - const idSelector = '#' + id; - const iFrameID = 'i' + id; - const iFrameID_selector = '#' + iFrameID; - const enclosingDivID = 'd' + id; - const enclosingDivID_selector = '#' + enclosingDivID; - - let root: any = select('body'); - - const isSandboxed = config.securityLevel === SECURITY_LVL_SANDBOX; - const isLooseSecurityLevel = config.securityLevel === SECURITY_LVL_LOOSE; - - const fontFamily = config.fontFamily; - - // ------------------------------------------------------------------------------- - // Define the root d3 node - // In regular execution the svgContainingElement will be the element with a mermaid class - - if (svgContainingElement !== undefined) { - if (svgContainingElement) { - svgContainingElement.innerHTML = ''; - } - - if (isSandboxed) { - // If we are in sandboxed mode, we do everything mermaid related in a (sandboxed )iFrame - const iframe = sandboxedIframe(select(svgContainingElement), iFrameID); - root = select(iframe.nodes()[0]!.contentDocument!.body); - root.node().style.margin = 0; - } else { - root = select(svgContainingElement); - } - appendDivSvgG(root, id, enclosingDivID, `font-family: ${fontFamily}`, XMLNS_XLINK_STD); - } else { - // No svgContainingElement was provided - - // If there is an existing element with the id, we remove it. This likely a previously rendered diagram - removeExistingElements(document, isSandboxed, id, iFrameID_selector, enclosingDivID_selector); - - // Add the temporary div used for rendering with the enclosingDivID. - // This temporary div will contain a svg with the id == id - - if (isSandboxed) { - // If we are in sandboxed mode, we do everything mermaid related in a (sandboxed) iFrame - const iframe = sandboxedIframe(select('body'), iFrameID); - root = select(iframe.nodes()[0]!.contentDocument!.body); - root.node().style.margin = 0; - } else { - root = select('body'); - } - - appendDivSvgG(root, id, enclosingDivID); - } - - text = encodeEntities(text); - - // ------------------------------------------------------------------------------- - // Create the diagram - - // Important that we do not create the diagram until after the directives have been included - let diag; - let parseEncounteredException; - - try { - // diag = new Diagram(text); diag = await getDiagramFromText(text); } catch (error) { diag = new Diagram('error'); @@ -697,18 +508,24 @@ const renderAsync = async function ( // ------------------------------------------------------------------------------- // Draw the diagram with the renderer try { - await diag.renderer.draw(text, id, pkg.version, diag); + await diag.renderer.draw(text, id, version, diag); } catch (e) { - errorRenderer.draw(text, id, pkg.version); + errorRenderer.draw(text, id, version); throw e; } + // This is the d3 node for the svg element + const svgNode = root.select(`${enclosingDivID_selector} svg`); + const a11yTitle = diag.db.getAccTitle?.(); + const a11yDescr = diag.db.getAccDescription?.(); + addA11yInfo(graphType, svgNode, a11yTitle, a11yDescr); + // ------------------------------------------------------------------------------- // Clean up SVG code root.select(`[id="${id}"]`).selectAll('foreignobject > *').attr('xmlns', XMLNS_XHTML_STD); // Fix for when the base tag is used - let svgCode = root.select(enclosingDivID_selector).node().innerHTML; + let svgCode: string = root.select(enclosingDivID_selector).node().innerHTML; log.debug('config.arrowMarkerAbsolute', config.arrowMarkerAbsolute); svgCode = cleanUpSvgCode(svgCode, isSandboxed, evaluate(config.arrowMarkerAbsolute)); @@ -716,35 +533,18 @@ const renderAsync = async function ( if (isSandboxed) { const svgEl = root.select(enclosingDivID_selector + ' svg').node(); svgCode = putIntoIFrame(svgCode, svgEl); - } else if (isLooseSecurityLevel) { + } else if (!isLooseSecurityLevel) { // Sanitize the svgCode using DOMPurify svgCode = DOMPurify.sanitize(svgCode, { - ADD_TAGS: DOMPURE_TAGS, - ADD_ATTR: DOMPURE_ATTR, + ADD_TAGS: DOMPURIFY_TAGS, + ADD_ATTR: DOMPURIFY_ATTR, }); } - // ------------------------------------------------------------------------------- - // Do any callbacks (cb = callback) - if (cb !== undefined) { - switch (graphType) { - case 'flowchart': - case 'flowchart-v2': - case 'gantt': - case 'class': - case 'classDiagram': - cb(svgCode, diag.db.bindFunctions); - break; - default: - cb(svgCode); - } - } else { - log.debug('CB = undefined!'); - } attachFunctions(); // ------------------------------------------------------------------------------- - // Remove the temporary element if appropriate + // Remove the temporary HTML element if appropriate const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector; const node = select(tmpElementSelector).node(); if (node && 'remove' in node) { @@ -755,84 +555,10 @@ const renderAsync = async function ( throw parseEncounteredException; } - return svgCode; -}; - -let currentDirective: { type?: string; args?: any } | undefined = {}; - -const parseDirective = function (p: any, statement: string, context: string, type: string): void { - try { - if (statement !== undefined) { - statement = statement.trim(); - switch (context) { - case 'open_directive': - currentDirective = {}; - break; - case 'type_directive': - if (!currentDirective) { - throw new Error('currentDirective is undefined'); - } - currentDirective.type = statement.toLowerCase(); - break; - case 'arg_directive': - if (!currentDirective) { - throw new Error('currentDirective is undefined'); - } - currentDirective.args = JSON.parse(statement); - break; - case 'close_directive': - handleDirective(p, currentDirective, type); - currentDirective = undefined; - break; - } - } - } catch (error) { - log.error( - `Error while rendering sequenceDiagram directive: ${statement} jison context: ${context}` - ); - // @ts-ignore: TODO Fix ts errors - log.error(error.message); - } -}; - -const handleDirective = function (p: any, directive: any, type: string): void { - log.debug(`Directive type=${directive.type} with args:`, directive.args); - switch (directive.type) { - case 'init': - case 'initialize': { - ['config'].forEach((prop) => { - if (directive.args[prop] !== undefined) { - if (type === 'flowchart-v2') { - type = 'flowchart'; - } - directive.args[type] = directive.args[prop]; - delete directive.args[prop]; - } - }); - log.debug('sanitize in handleDirective', directive.args); - directiveSanitizer(directive.args); - log.debug('sanitize in handleDirective (done)', directive.args); - configApi.addDirective(directive.args); - break; - } - case 'wrap': - case 'nowrap': - if (p && p['setWrap']) { - p.setWrap(directive.type === 'wrap'); - } - break; - case 'themeCss': - log.warn('themeCss encountered'); - break; - default: - log.warn( - `Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify( - directive.args ? directive.args : {} - )}}%%`, - directive - ); - break; - } + return { + svg: svgCode, + bindFunctions: diag.db.bindFunctions, + }; }; /** @@ -863,6 +589,20 @@ function initialize(options: MermaidConfig = {}) { addDiagrams(); } +/** + * Add accessibility (a11y) information to the diagram. + * + */ +function addA11yInfo( + graphType: string, + svgNode: D3Element, + a11yTitle: string | undefined, + a11yDescr: string | undefined +) { + setA11yDiagramInfo(svgNode, graphType); + addSVGa11yTitleDescription(svgNode, a11yTitle, a11yDescr, svgNode.attr('id')); +} + /** * ## mermaidAPI configuration defaults * @@ -927,9 +667,7 @@ function initialize(options: MermaidConfig = {}) { export const mermaidAPI = Object.freeze({ render, - renderAsync, parse, - parseAsync, parseDirective, initialize, getConfig: configApi.getConfig, diff --git a/packages/mermaid/src/tests/MockedD3.ts b/packages/mermaid/src/tests/MockedD3.ts index 9cf01ddad..ccf21a269 100644 --- a/packages/mermaid/src/tests/MockedD3.ts +++ b/packages/mermaid/src/tests/MockedD3.ts @@ -1,12 +1,20 @@ +import type {} from '@vitest/spy/dist/index'; + /** * This is a mocked/stubbed version of the d3 Selection type. Each of the main functions are all * mocked (via vi.fn()) so you can track if they have been called, etc. + * + * Note that node() returns a HTML Element with tag 'svg'. It is an empty element (no innerHTML, no children, etc). + * This potentially allows testing of mermaidAPI render(). */ + export class MockedD3 { public attribs = new Map(); public id: string | undefined = ''; _children: MockedD3[] = []; + _containingHTMLdoc = new Document(); + constructor(givenId = 'mock-id') { this.id = givenId; } @@ -29,6 +37,11 @@ export class MockedD3 { return new MockedD3(cleanId); }); + // This has the same implementation as select(). (It calls it.) + selectAll = vi.fn().mockImplementation(({ select_str = '' }): MockedD3 => { + return this.select(select_str); + }); + append = vi .fn() .mockImplementation(function (this: MockedD3, type: string, id = '' + '-appended'): MockedD3 { @@ -87,9 +100,18 @@ export class MockedD3 { this.attribs.set('text', attrValue); return this; } - // NOTE: Arbitrarily returns an empty object. The return value could be something different with a mockReturnValue() or mockImplementation() - public node = vi.fn().mockReturnValue({}); + // NOTE: Returns a HTML Element with tag 'svg' that has _another_ 'svg' element child. + // This allows different tests to succeed -- some need a top level 'svg' and some need a 'svg' element to be the firstChild + // Real implementation returns an HTML Element + public node = vi.fn().mockImplementation(() => { + const topElem = this._containingHTMLdoc.createElement('svg'); + const elem_svgChild = this._containingHTMLdoc.createElement('svg'); // another svg element + topElem.appendChild(elem_svgChild); + return topElem; + }); + + // TODO Is this correct? shouldn't it return a list of HTML Elements? nodes = vi.fn().mockImplementation(function (this: MockedD3): MockedD3[] { return this._children; }); diff --git a/packages/mermaid/src/themes/theme-base.js b/packages/mermaid/src/themes/theme-base.js index c940a0055..8ff544feb 100644 --- a/packages/mermaid/src/themes/theme-base.js +++ b/packages/mermaid/src/themes/theme-base.js @@ -13,7 +13,6 @@ class Theme { * deducing colors for instance line color. Default value is #f4f4f4. */ this.background = '#f4f4f4'; - this.darkMode = false; this.primaryColor = '#fff4dd'; @@ -169,6 +168,16 @@ class Theme { this['cScaleLabel' + i] = this['cScaleLabel' + i] || this.scaleLabelColor; } + const multiplier = this.darkMode ? -4 : -1; + for (let i = 0; i < 5; i++) { + this['surface' + i] = + this['surface' + i] || + adjust(this.mainBkg, { h: 180, s: -15, l: multiplier * (5 + i * 3) }); + this['surfacePeer' + i] = + this['surfacePeer' + i] || + adjust(this.mainBkg, { h: 180, s: -15, l: multiplier * (8 + i * 3) }); + } + /* class */ this.classText = this.classText || this.textColor; diff --git a/packages/mermaid/src/themes/theme-dark.js b/packages/mermaid/src/themes/theme-dark.js index 5ba63e155..af21b4f13 100644 --- a/packages/mermaid/src/themes/theme-dark.js +++ b/packages/mermaid/src/themes/theme-dark.js @@ -196,6 +196,13 @@ class Theme { this['cScalePeer' + i] = this['cScalePeer' + i] || lighten(this['cScale' + i], 10); } + for (let i = 0; i < 5; i++) { + this['surface' + i] = + this['surface' + i] || adjust(this.mainBkg, { h: 30, s: -30, l: -(-10 + i * 4) }); + this['surfacePeer' + i] = + this['surfacePeer' + i] || adjust(this.mainBkg, { h: 30, s: -30, l: -(-7 + i * 4) }); + } + // Setup teh label color for the set this.scaleLabelColor = this.scaleLabelColor || (this.darkMode ? 'black' : this.labelTextColor); diff --git a/packages/mermaid/src/themes/theme-default.js b/packages/mermaid/src/themes/theme-default.js index 95710629b..391c0298f 100644 --- a/packages/mermaid/src/themes/theme-default.js +++ b/packages/mermaid/src/themes/theme-default.js @@ -122,6 +122,7 @@ class Theme { updateColors() { /* Color Scale */ /* Each color-set will have a background, a foreground and a border color */ + this.cScale0 = this.cScale0 || this.primaryColor; this.cScale1 = this.cScale1 || this.secondaryColor; this.cScale2 = this.cScale2 || this.tertiaryColor; @@ -141,12 +142,16 @@ class Theme { this['cScale' + i] = darken(this['cScale' + i], 10); this['cScalePeer' + i] = this['cScalePeer' + i] || darken(this['cScale' + i], 25); } - // Setup the inverted color for the set for (let i = 0; i < this.THEME_COLOR_LIMIT; i++) { this['cScaleInv' + i] = this['cScaleInv' + i] || adjust(this['cScale' + i], { h: 180 }); } + for (let i = 0; i < 5; i++) { + this['surface' + i] = this['surface' + i] || adjust(this.mainBkg, { h: 30, l: -(5 + i * 5) }); + this['surfacePeer' + i] = + this['surfacePeer' + i] || adjust(this.mainBkg, { h: 30, l: -(7 + i * 5) }); + } // Setup the label color for the set this.scaleLabelColor = this.scaleLabelColor !== 'calculated' && this.scaleLabelColor diff --git a/packages/mermaid/src/themes/theme-forest.js b/packages/mermaid/src/themes/theme-forest.js index 860326dea..59adc9139 100644 --- a/packages/mermaid/src/themes/theme-forest.js +++ b/packages/mermaid/src/themes/theme-forest.js @@ -130,6 +130,13 @@ class Theme { this['cScaleLabel' + i] = this['cScaleLabel' + i] || this.scaleLabelColor; } + for (let i = 0; i < 5; i++) { + this['surface' + i] = + this['surface' + i] || adjust(this.mainBkg, { h: 30, s: -30, l: -(5 + i * 5) }); + this['surfacePeer' + i] = + this['surfacePeer' + i] || adjust(this.mainBkg, { h: 30, s: -30, l: -(8 + i * 5) }); + } + /* Flowchart variables */ this.nodeBkg = this.mainBkg; diff --git a/packages/mermaid/src/themes/theme-neutral.js b/packages/mermaid/src/themes/theme-neutral.js index f22710387..e7a136c6b 100644 --- a/packages/mermaid/src/themes/theme-neutral.js +++ b/packages/mermaid/src/themes/theme-neutral.js @@ -147,6 +147,12 @@ class Theme { this['cScaleLabel' + i] = this['cScaleLabel' + i] || this.scaleLabelColor; } + for (let i = 0; i < 5; i++) { + this['surface' + i] = this['surface' + i] || adjust(this.mainBkg, { l: -(5 + i * 5) }); + this['surfacePeer' + i] = + this['surfacePeer' + i] || adjust(this.mainBkg, { l: -(8 + i * 5) }); + } + /* Flowchart variables */ this.nodeBkg = this.mainBkg; diff --git a/packages/mermaid/src/utils.spec.js b/packages/mermaid/src/utils.spec.js index 54262f10e..0f0bc1e92 100644 --- a/packages/mermaid/src/utils.spec.js +++ b/packages/mermaid/src/utils.spec.js @@ -3,8 +3,9 @@ import utils from './utils'; import assignWithDepth from './assignWithDepth'; import { detectType } from './diagram-api/detectType'; import { addDiagrams } from './diagram-api/diagram-orchestration'; -import memoize from 'lodash-es/memoize'; -import { MockD3 } from 'd3'; +import memoize from 'lodash-es/memoize.js'; +import { MockedD3 } from './tests/MockedD3'; + addDiagrams(); describe('when assignWithDepth: should merge objects within objects', function () { @@ -238,9 +239,9 @@ Alice->Bob: hi`; const type = detectType(str); expect(type).toBe('gitGraph'); }); - it('should not allow frontmatter with leading spaces', function () { + it('should handle malformed frontmatter (with leading spaces) with `---` error graphtype', function () { const str = ' ---\ntitle: foo\n---\n gitGraph TB:\nbfs1:queue'; - expect(() => detectType(str)).toThrow('No diagram type detected for text'); + expect(detectType(str)).toBe('---'); }); }); describe('when finding substring in array ', function () { @@ -352,21 +353,78 @@ describe('when initializing the id generator', function () { }); describe('when inserting titles', function () { - it('should do nothing when title is empty', function () { - const svg = MockD3('svg'); - utils.insertTitle(svg, 'testClass', 0, ''); - expect(svg.__children.length).toBe(0); + const svg = new MockedD3('svg'); + const mockedElement = { + getBBox: vi.fn().mockReturnValue({ x: 10, y: 11, width: 100, height: 200 }), + }; + const fauxTitle = new MockedD3('title'); + + beforeEach(() => { + svg.node = vi.fn().mockReturnValue(mockedElement); }); - it('should insert title centered', function () { - const svg = MockD3('svg'); + it('does nothing if the title is empty', function () { + const svgAppendSpy = vi.spyOn(svg, 'append'); + utils.insertTitle(svg, 'testClass', 0, ''); + expect(svgAppendSpy).not.toHaveBeenCalled(); + }); + + it('appends the title as a text item with the given title text', function () { + const svgAppendSpy = vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const titleTextSpy = vi.spyOn(fauxTitle, 'text'); + utils.insertTitle(svg, 'testClass', 5, 'test title'); - expect(svg.__children.length).toBe(1); - const text = svg.__children[0]; - expect(text.__name).toBe('text'); - expect(text.text).toHaveBeenCalledWith('test title'); - expect(text.attr).toHaveBeenCalledWith('x', 15); - expect(text.attr).toHaveBeenCalledWith('y', -5); - expect(text.attr).toHaveBeenCalledWith('class', 'testClass'); + expect(svgAppendSpy).toHaveBeenCalled(); + expect(titleTextSpy).toHaveBeenCalledWith('test title'); + }); + + it('x value is the bounds x position + half of the bounds width', () => { + vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const titleAttrSpy = vi.spyOn(fauxTitle, 'attr'); + + utils.insertTitle(svg, 'testClass', 5, 'test title'); + expect(titleAttrSpy).toHaveBeenCalledWith('x', 10 + 100 / 2); + }); + + it('y value is the negative of given title top margin', () => { + vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const titleAttrSpy = vi.spyOn(fauxTitle, 'attr'); + + utils.insertTitle(svg, 'testClass', 5, 'test title'); + expect(titleAttrSpy).toHaveBeenCalledWith('y', -5); + }); + + it('class is the given css class', () => { + vi.spyOn(svg, 'append').mockReturnValue(fauxTitle); + const titleAttrSpy = vi.spyOn(fauxTitle, 'attr'); + + utils.insertTitle(svg, 'testClass', 5, 'test title'); + expect(titleAttrSpy).toHaveBeenCalledWith('class', 'testClass'); + }); +}); + +describe('when parsing font sizes', function () { + it('parses number inputs', function () { + expect(utils.parseFontSize(14)).toEqual([14, '14px']); + }); + + it('parses string em inputs', function () { + expect(utils.parseFontSize('14em')).toEqual([14, '14em']); + }); + + it('parses string px inputs', function () { + expect(utils.parseFontSize('14px')).toEqual([14, '14px']); + }); + + it('parses string inputs without units', function () { + expect(utils.parseFontSize('14')).toEqual([14, '14px']); + }); + + it('handles undefined input', function () { + expect(utils.parseFontSize(undefined)).toEqual([undefined, undefined]); + }); + + it('handles unparseable input', function () { + expect(utils.parseFontSize({ fontSize: 14 })).toEqual([undefined, undefined]); }); }); diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 16566c3b1..76fce1999 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -4,6 +4,15 @@ import { curveBasis, curveBasisClosed, curveBasisOpen, + curveBumpX, + curveBumpY, + curveBundle, + curveCardinalClosed, + curveCardinalOpen, + curveCardinal, + curveCatmullRomClosed, + curveCatmullRomOpen, + curveCatmullRom, CurveFactory, curveLinear, curveLinearClosed, @@ -21,13 +30,22 @@ import { log } from './logger'; import { detectType } from './diagram-api/detectType'; import assignWithDepth from './assignWithDepth'; import { MermaidConfig } from './config.type'; -import memoize from 'lodash-es/memoize'; +import memoize from 'lodash-es/memoize.js'; // Effectively an enum of the supported curve types, accessible by name const d3CurveTypes = { curveBasis: curveBasis, curveBasisClosed: curveBasisClosed, curveBasisOpen: curveBasisOpen, + curveBumpX: curveBumpX, + curveBumpY: curveBumpY, + curveBundle: curveBundle, + curveCardinalClosed: curveCardinalClosed, + curveCardinalOpen: curveCardinalOpen, + curveCardinal: curveCardinal, + curveCatmullRomClosed: curveCatmullRomClosed, + curveCatmullRomOpen: curveCatmullRomOpen, + curveCatmullRom: curveCatmullRom, curveLinear: curveLinear, curveLinearClosed: curveLinearClosed, curveMonotoneX: curveMonotoneX, @@ -194,7 +212,10 @@ export const isSubstringInArray = function (str: string, arr: string[]): number * @param defaultCurve - The default curve to return * @returns The curve factory to use */ -export function interpolateToCurve(interpolate?: string, defaultCurve: CurveFactory): CurveFactory { +export function interpolateToCurve( + interpolate: string | undefined, + defaultCurve: CurveFactory +): CurveFactory { if (!interpolate) { return defaultCurve; } @@ -540,12 +561,14 @@ export const drawSimpleText = function ( // Remove and ignore br:s const nText = textData.text.replace(common.lineBreakRegex, ' '); + const [, _fontSizePx] = parseFontSize(textData.fontSize); + const textElem = elem.append('text'); textElem.attr('x', textData.x); textElem.attr('y', textData.y); textElem.style('text-anchor', textData.anchor); textElem.style('font-family', textData.fontFamily); - textElem.style('font-size', textData.fontSize); + textElem.style('font-size', _fontSizePx); textElem.style('font-weight', textData.fontWeight); textElem.attr('fill', textData.fill); if (textData.class !== undefined) { @@ -719,6 +742,8 @@ export const calculateTextDimensions: ( return { width: 0, height: 0 }; } + const [, _fontSizePx] = parseFontSize(fontSize); + // We can't really know if the user supplied font family will render on the user agent; // thus, we'll take the max width between the user supplied font family, and a default // of sans-serif. @@ -742,7 +767,7 @@ export const calculateTextDimensions: ( const textObj = getTextObj(); textObj.text = line; const textElem = drawSimpleText(g, textObj) - .style('font-size', fontSize) + .style('font-size', _fontSizePx) .style('font-weight', fontWeight) .style('font-family', fontFamily); @@ -913,7 +938,7 @@ export function getErrorMessage(error: unknown): string { } /** - * Appends element with the given title, centered. + * Appends element with the given title and css class. * * @param parent - d3 svg object to append title to * @param cssClass - CSS class for the element containing the title @@ -938,6 +963,32 @@ export const insertTitle = ( .attr('class', cssClass); }; +/** + * Parses a raw fontSize configuration value into a number and string value. + * + * @param fontSize - a string or number font size configuration value + * + * @returns parsed number and string style font size values, or nulls if a number value can't + * be parsed from an input string. + */ +export const parseFontSize = (fontSize: string | number | undefined): [number?, string?] => { + // if the font size is a number, assume a px string representation + if (typeof fontSize === 'number') { + return [fontSize, fontSize + 'px']; + } + + const fontSizeNumber = parseInt(fontSize, 10); + if (Number.isNaN(fontSizeNumber)) { + // if a number value can't be parsed, return null for both values + return [undefined, undefined]; + } else if (fontSize === String(fontSizeNumber)) { + // if a string input doesn't contain any units, assume px units + return [fontSizeNumber, fontSize + 'px']; + } else { + return [fontSizeNumber, fontSize]; + } +}; + export default { assignWithDepth, wrapLabel, @@ -961,4 +1012,5 @@ export default { directiveSanitizer, sanitizeCss, insertTitle, + parseFontSize, }; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 55207bde1..d32e5545f 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -16,18 +16,21 @@ importers: '@cspell/eslint-plugin': specifier: ^6.14.2 version: 6.14.2 + '@types/cors': + specifier: ^2.8.13 + version: 2.8.13 '@types/eslint': specifier: ^8.4.10 version: 8.4.10 '@types/express': - specifier: ^4.17.14 - version: 4.17.14 + specifier: ^4.17.17 + version: 4.17.17 '@types/js-yaml': specifier: ^4.0.5 version: 4.0.5 '@types/jsdom': - specifier: ^20.0.1 - version: 20.0.1 + specifier: ^21.0.0 + version: 21.1.0 '@types/lodash': specifier: ^4.14.188 version: 4.14.188 @@ -44,59 +47,65 @@ importers: specifier: ^4.2.1 version: 4.2.1 '@typescript-eslint/eslint-plugin': - specifier: ^5.42.1 - version: 5.42.1_2udltptbznfmezdozpdoa2aemq + specifier: ^5.48.2 + version: 5.48.2_azmbqzqvrlvblbdtiwxwvyvjjy '@typescript-eslint/parser': - specifier: ^5.42.1 - version: 5.42.1_rmayb2veg2btbq6mbmnyivgasy + specifier: ^5.48.2 + version: 5.48.2_et5x32uxl7z5ldub3ye5rhlyqm '@vitest/coverage-c8': - specifier: ^0.25.1 - version: 0.25.1_oullksb5ic6y72oh2wekoaiuii + specifier: ^0.28.4 + version: 0.28.4_vun5xzxu3tkrssf3erdbijyyki + '@vitest/spy': + specifier: ^0.28.4 + version: 0.28.4 '@vitest/ui': - specifier: ^0.25.1 - version: 0.25.1 + specifier: ^0.28.4 + version: 0.28.4 concurrently: specifier: ^7.5.0 version: 7.5.0 + cors: + specifier: ^2.8.5 + version: 2.8.5 coveralls: specifier: ^3.1.1 version: 3.1.1 cypress: - specifier: ^10.11.0 - version: 10.11.0 + specifier: ^12.0.0 + version: 12.5.1 cypress-image-snapshot: specifier: ^4.0.1 - version: 4.0.1_bg25yee4qeg7mpleuvd346a3tq + version: 4.0.1_cypress@12.5.1+jest@29.3.1 esbuild: - specifier: ^0.15.13 - version: 0.15.13 + specifier: ^0.17.0 + version: 0.17.0 eslint: - specifier: ^8.27.0 - version: 8.27.0 + specifier: ^8.32.0 + version: 8.32.0 eslint-config-prettier: - specifier: ^8.5.0 - version: 8.5.0_eslint@8.27.0 + specifier: ^8.6.0 + version: 8.6.0_eslint@8.32.0 eslint-plugin-cypress: specifier: ^2.12.1 - version: 2.12.1_eslint@8.27.0 + version: 2.12.1_eslint@8.32.0 eslint-plugin-html: specifier: ^7.1.0 version: 7.1.0 eslint-plugin-jest: specifier: ^27.1.5 - version: 27.1.5_kdswgjmqcx7mthqz7ow2zlfevy + version: 27.1.5_i5clxtuiaceouxhg5syqkw5wwi eslint-plugin-jsdoc: specifier: ^39.6.2 - version: 39.6.2_eslint@8.27.0 + version: 39.6.2_eslint@8.32.0 eslint-plugin-json: specifier: ^3.1.0 version: 3.1.0 eslint-plugin-lodash: specifier: ^7.4.0 - version: 7.4.0_eslint@8.27.0 + version: 7.4.0_eslint@8.32.0 eslint-plugin-markdown: 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: specifier: ^3.1.0 version: 3.1.0 @@ -105,7 +114,7 @@ importers: version: 0.2.17 eslint-plugin-unicorn: specifier: ^45.0.0 - version: 45.0.0_eslint@8.27.0 + version: 45.0.0_eslint@8.32.0 express: specifier: ^4.18.2 version: 4.18.2 @@ -125,8 +134,8 @@ importers: specifier: ^4.1.0 version: 4.1.0 jsdom: - specifier: ^20.0.2 - version: 20.0.2 + specifier: ^21.0.0 + version: 21.1.0 lint-staged: specifier: ^13.0.3 version: 13.0.3 @@ -143,41 +152,53 @@ importers: specifier: ^0.4.2 version: 0.4.2_prettier@2.7.1 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: ^4.0.0 + version: 4.1.2 rollup-plugin-visualizer: specifier: ^5.8.3 version: 5.8.3 start-server-and-test: - specifier: ^1.14.0 - version: 1.14.0 + specifier: ^1.15.4 + version: 1.15.4 ts-node: specifier: ^10.9.1 - version: 10.9.1_cbe7ovvae6zqfnmtgctpgpys54 + version: 10.9.1_w6ufic3jqylcjznzspnj4wjqfe typescript: specifier: ^4.8.4 - version: 4.8.4 + version: 4.9.5 vite: - specifier: ^3.2.3 - version: 3.2.3_@types+node@18.11.9 + specifier: ^4.1.1 + version: 4.1.1_@types+node@18.11.9 vitest: - specifier: ^0.25.3 - version: 0.25.3_oullksb5ic6y72oh2wekoaiuii + specifier: ^0.28.5 + version: 0.28.5_vun5xzxu3tkrssf3erdbijyyki packages/mermaid: dependencies: '@braintree/sanitize-url': specifier: ^6.0.0 version: 6.0.0 + cytoscape: + specifier: ^3.23.0 + version: 3.23.0 + cytoscape-cose-bilkent: + specifier: ^4.1.0 + version: 4.1.0_cytoscape@3.23.0 + cytoscape-fcose: + specifier: ^2.1.0 + version: 2.1.0_cytoscape@3.23.0 d3: - specifier: ^7.0.0 - version: 7.6.1 + specifier: ^7.4.0 + version: 7.8.2 dagre-d3-es: - specifier: 7.0.4 - version: 7.0.4 + specifier: 7.0.8 + version: 7.0.8 dompurify: - specifier: 2.4.1 - version: 2.4.1 + specifier: 2.4.3 + version: 2.4.3 + elkjs: + specifier: ^0.8.2 + version: 0.8.2 khroma: specifier: ^2.0.0 version: 2.0.0 @@ -185,7 +206,7 @@ importers: specifier: ^4.17.21 version: 4.17.21 moment-mini: - specifier: ^2.24.0 + specifier: ^2.29.4 version: 2.29.4 non-layered-tidy-tree-layout: specifier: ^2.0.2 @@ -193,10 +214,19 @@ importers: stylis: specifier: ^4.1.2 version: 4.1.2 + ts-dedent: + specifier: ^2.2.0 + version: 2.2.0 uuid: specifier: ^9.0.0 version: 9.0.0 + web-worker: + specifier: ^1.2.0 + version: 1.2.0 devDependencies: + '@types/cytoscape': + specifier: ^3.19.9 + version: 3.19.9 '@types/d3': specifier: ^7.4.0 version: 7.4.0 @@ -204,8 +234,8 @@ importers: specifier: ^2.4.0 version: 2.4.0 '@types/jsdom': - specifier: ^20.0.1 - version: 20.0.1 + specifier: ^21.0.0 + version: 21.1.0 '@types/lodash-es': specifier: ^4.17.6 version: 4.17.6 @@ -219,14 +249,14 @@ importers: specifier: ^4.0.2 version: 4.0.2 '@types/uuid': - specifier: ^8.3.4 - version: 8.3.4 + specifier: ^9.0.0 + version: 9.0.0 '@typescript-eslint/eslint-plugin': specifier: ^5.42.1 - version: 5.42.1_2udltptbznfmezdozpdoa2aemq + version: 5.42.1_qxgr6oy2qtsmmpo3f6iejuryuq '@typescript-eslint/parser': specifier: ^5.42.1 - version: 5.42.1_rmayb2veg2btbq6mbmnyivgasy + version: 5.42.1_yygwinqv3a2io74xmwofqb7uka chokidar: specifier: ^3.5.3 version: 3.5.3 @@ -236,6 +266,9 @@ importers: coveralls: specifier: ^3.1.1 version: 3.1.1 + cpy-cli: + specifier: ^4.2.0 + version: 4.2.0 cspell: specifier: ^6.14.3 version: 6.14.3 @@ -249,14 +282,11 @@ importers: specifier: ^3.7.2 version: 3.7.2 jsdom: - specifier: ^20.0.2 - version: 20.0.2 + specifier: ^21.0.0 + version: 21.1.0 micromatch: specifier: ^4.0.5 version: 4.0.5 - moment: - specifier: ^2.29.4 - version: 2.29.4 path-browserify: specifier: ^1.0.1 version: 1.0.1 @@ -266,9 +296,15 @@ importers: remark: specifier: ^14.0.2 version: 14.0.2 + remark-frontmatter: + specifier: ^4.0.1 + version: 4.0.1 + remark-gfm: + specifier: ^3.0.1 + version: 3.0.1 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: ^4.0.0 + version: 4.1.2 start-server-and-test: specifier: ^1.14.0 version: 1.14.0 @@ -285,22 +321,13 @@ importers: specifier: ^1.0.0 version: 1.0.0 vitepress: - specifier: ^1.0.0-alpha.28 - version: 1.0.0-alpha.28_tbpndr44ulefs3hehwpi2mkf2y + specifier: ^1.0.0-alpha.46 + version: 1.0.0-alpha.46_tbpndr44ulefs3hehwpi2mkf2y vitepress-plugin-search: - specifier: ^1.0.4-alpha.15 - version: 1.0.4-alpha.15_s3edpouswd4dgoi2en7bdlrp54 + specifier: ^1.0.4-alpha.19 + version: 1.0.4-alpha.19_g67lr3vgasogkevpbew55lljzq packages/mermaid-example-diagram: - devDependencies: - concurrently: - specifier: ^7.5.0 - version: 7.5.0 - rimraf: - specifier: ^3.0.2 - version: 3.0.2 - - packages/mermaid-mindmap: dependencies: '@braintree/sanitize-url': specifier: ^6.0.0 @@ -316,7 +343,7 @@ importers: version: 2.1.0_cytoscape@3.23.0 d3: specifier: ^7.0.0 - version: 7.6.1 + version: 7.8.2 khroma: specifier: ^2.0.0 version: 2.0.0 @@ -324,6 +351,9 @@ importers: specifier: ^2.0.2 version: 2.0.2 devDependencies: + '@types/cytoscape': + specifier: ^3.19.9 + version: 3.19.9 concurrently: specifier: ^7.5.0 version: 7.5.0 @@ -331,14 +361,14 @@ importers: specifier: workspace:* version: link:../mermaid rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: ^4.0.0 + version: 4.1.2 tests/webpack: dependencies: - '@mermaid-js/mermaid-mindmap': + '@mermaid-js/mermaid-example-diagram': specifier: workspace:* - version: link:../../packages/mermaid-mindmap + version: link:../../packages/mermaid-example-diagram mermaid: specifier: workspace:* version: link:../../packages/mermaid @@ -355,25 +385,25 @@ importers: packages: - /@algolia/autocomplete-core/1.7.2: - resolution: {integrity: sha512-eclwUDC6qfApNnEfu1uWcL/rudQsn59tjEoUYZYE2JSXZrHLRjBUGMxiCoknobU2Pva8ejb0eRxpIYDtVVqdsw==} + /@algolia/autocomplete-core/1.7.4: + resolution: {integrity: sha512-daoLpQ3ps/VTMRZDEBfU8ixXd+amZcNJ4QSP3IERGyzqnL5Ch8uSRFt/4G8pUvW9c3o6GA4vtVv4I4lmnkdXyg==} dependencies: - '@algolia/autocomplete-shared': 1.7.2 + '@algolia/autocomplete-shared': 1.7.4 dev: true - /@algolia/autocomplete-preset-algolia/1.7.2_qs6lk5nhygj2o3hj4sf6xnr724: - resolution: {integrity: sha512-+RYEG6B0QiGGfRb2G3MtPfyrl0dALF3cQNTWBzBX6p5o01vCCGTTinAm2UKG3tfc2CnOMAtnPLkzNZyJUpnVJw==} + /@algolia/autocomplete-preset-algolia/1.7.4_qs6lk5nhygj2o3hj4sf6xnr724: + resolution: {integrity: sha512-s37hrvLEIfcmKY8VU9LsAXgm2yfmkdHT3DnA3SgHaY93yjZ2qL57wzb5QweVkYuEBZkT2PIREvRoLXC2sxTbpQ==} peerDependencies: '@algolia/client-search': '>= 4.9.1 < 6' algoliasearch: '>= 4.9.1 < 6' dependencies: - '@algolia/autocomplete-shared': 1.7.2 + '@algolia/autocomplete-shared': 1.7.4 '@algolia/client-search': 4.14.2 algoliasearch: 4.14.2 dev: true - /@algolia/autocomplete-shared/1.7.2: - resolution: {integrity: sha512-QCckjiC7xXHIUaIL3ektBtjJ0w7tTA3iqKcAE/Hjn1lZ5omp7i3Y4e09rAr9ZybqirL7AbxCLLq0Ra5DDPKeug==} + /@algolia/autocomplete-shared/1.7.4: + resolution: {integrity: sha512-2VGCk7I9tA9Ge73Km99+Qg87w0wzW4tgUruvWAn/gfey1ZXgmxZtyIRBebk35R1O8TbK77wujVtCnpsGpRy1kg==} dev: true /@algolia/cache-browser-local-storage/4.14.2: @@ -708,7 +738,7 @@ packages: engines: {node: '>=12'} dependencies: abab: 2.0.6 - acorn: 8.8.0 + acorn: 8.8.1 acorn-globals: 6.0.0 cssom: 0.5.0 cssstyle: 2.3.0 @@ -1235,11 +1265,11 @@ packages: '@types/node': 14.18.29 chalk: 4.1.2 cosmiconfig: 7.0.1 - cosmiconfig-typescript-loader: 4.1.0_nxlrwu45zhpwmwjzs33dzt3ak4 + cosmiconfig-typescript-loader: 4.1.0_2uclxasecupgvdn72amnhmyg7y lodash: 4.17.21 resolve-from: 5.0.0 - ts-node: 10.9.1_sqjhzn5m3vxyw66a2xhtc43hby - typescript: 4.8.4 + ts-node: 10.9.1_yxpazyh7n5pql7jdaglasgwqki + typescript: 4.9.5 transitivePeerDependencies: - '@swc/core' - '@swc/wasm' @@ -1670,14 +1700,14 @@ packages: engines: {node: '>=10.0.0'} dev: true - /@docsearch/css/3.3.0: - resolution: {integrity: sha512-rODCdDtGyudLj+Va8b6w6Y85KE85bXRsps/R4Yjwt5vueXKXZQKYw0aA9knxLBT6a/bI/GMrAcmCR75KYOM6hg==} + /@docsearch/css/3.3.3: + resolution: {integrity: sha512-6SCwI7P8ao+se1TUsdZ7B4XzL+gqeQZnBc+2EONZlcVa0dVrk0NjETxozFKgMv0eEGH8QzP1fkN+A1rH61l4eg==} dev: true - /@docsearch/js/3.3.0_tbpndr44ulefs3hehwpi2mkf2y: - resolution: {integrity: sha512-oFXWRPNvPxAzBhnFJ9UCFIYZiQNc3Yrv6912nZHw/UIGxsyzKpNRZgHq8HDk1niYmOSoLKtVFcxkccpQmYGFyg==} + /@docsearch/js/3.3.3_tbpndr44ulefs3hehwpi2mkf2y: + resolution: {integrity: sha512-2xAv2GFuHzzmG0SSZgf8wHX0qZX8n9Y1ZirKUk5Wrdc+vH9CL837x2hZIUdwcPZI9caBA+/CzxsS68O4waYjUQ==} dependencies: - '@docsearch/react': 3.3.0_tbpndr44ulefs3hehwpi2mkf2y + '@docsearch/react': 3.3.3_tbpndr44ulefs3hehwpi2mkf2y preact: 10.11.0 transitivePeerDependencies: - '@algolia/client-search' @@ -1686,8 +1716,8 @@ packages: - react-dom dev: true - /@docsearch/react/3.3.0_tbpndr44ulefs3hehwpi2mkf2y: - resolution: {integrity: sha512-fhS5adZkae2SSdMYEMVg6pxI5a/cE+tW16ki1V0/ur4Fdok3hBRkmN/H8VvlXnxzggkQIIRIVvYPn00JPjen3A==} + /@docsearch/react/3.3.3_tbpndr44ulefs3hehwpi2mkf2y: + resolution: {integrity: sha512-pLa0cxnl+G0FuIDuYlW+EBK6Rw2jwLw9B1RHIeS4N4s2VhsfJ/wzeCi3CWcs5yVfxLd5ZK50t//TMA5e79YT7Q==} peerDependencies: '@types/react': '>= 16.8.0 < 19.0.0' react: '>= 16.8.0 < 19.0.0' @@ -1700,9 +1730,9 @@ packages: react-dom: optional: true dependencies: - '@algolia/autocomplete-core': 1.7.2 - '@algolia/autocomplete-preset-algolia': 1.7.2_qs6lk5nhygj2o3hj4sf6xnr724 - '@docsearch/css': 3.3.0 + '@algolia/autocomplete-core': 1.7.4 + '@algolia/autocomplete-preset-algolia': 1.7.4_qs6lk5nhygj2o3hj4sf6xnr724 + '@docsearch/css': 3.3.3 algoliasearch: 4.14.2 transitivePeerDependencies: - '@algolia/client-search' @@ -1717,8 +1747,8 @@ packages: jsdoc-type-pratt-parser: 3.1.0 dev: true - /@esbuild/android-arm/0.15.13: - resolution: {integrity: sha512-RY2fVI8O0iFUNvZirXaQ1vMvK0xhCcl0gqRj74Z6yEiO1zAUa7hbsdwZM1kzqbxHK7LFyMizipfXT3JME+12Hw==} + /@esbuild/android-arm/0.16.17: + resolution: {integrity: sha512-N9x1CMXVhtWEAMS7pNNONyA14f71VPQN9Cnavj1XQh6T7bskqiLLrSca4O0Vr8Wdcga943eThxnVp3JLnBMYtw==} engines: {node: '>=12'} cpu: [arm] os: [android] @@ -1726,8 +1756,179 @@ packages: dev: true optional: true - /@esbuild/linux-loong64/0.15.13: - resolution: {integrity: sha512-+BoyIm4I8uJmH/QDIH0fu7MG0AEx9OXEDXnqptXCwKOlOqZiS4iraH1Nr7/ObLMokW3sOCeBNyD68ATcV9b9Ag==} + /@esbuild/android-arm/0.17.0: + resolution: {integrity: sha512-hlbX5ym1V5kIKvnwFhm6rhar7MNqfJrZyYTNfk6+WS1uQfQmszFgXeyPH2beP3lSCumZyqX0zMBfOqftOpZ7GA==} + engines: {node: '>=12'} + cpu: [arm] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64/0.16.17: + resolution: {integrity: sha512-MIGl6p5sc3RDTLLkYL1MyL8BMRN4tLMRCn+yRJJmEDvYZ2M7tmAf80hx1kbNEUX2KJ50RRtxZ4JHLvCfuB6kBg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-arm64/0.17.0: + resolution: {integrity: sha512-77GVyD7ToESy/7+9eI8z62GGBdS/hsqsrpM+JA4kascky86wHbN29EEFpkVvxajPL7k6mbLJ5VBQABdj7n9FhQ==} + engines: {node: '>=12'} + cpu: [arm64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64/0.16.17: + resolution: {integrity: sha512-a3kTv3m0Ghh4z1DaFEuEDfz3OLONKuFvI4Xqczqx4BqLyuFaFkuaG4j2MtA6fuWEFeC5x9IvqnX7drmRq/fyAQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/android-x64/0.17.0: + resolution: {integrity: sha512-TroxZdZhtAz0JyD0yahtjcbKuIXrBEAoAazaYSeR2e2tUtp9uXrcbpwFJF6oxxOiOOne6y7l4hx4YVnMW/tdFw==} + engines: {node: '>=12'} + cpu: [x64] + os: [android] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64/0.16.17: + resolution: {integrity: sha512-/2agbUEfmxWHi9ARTX6OQ/KgXnOWfsNlTeLcoV7HSuSTv63E4DqtAc+2XqGw1KHxKMHGZgbVCZge7HXWX9Vn+w==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-arm64/0.17.0: + resolution: {integrity: sha512-wP/v4cgdWt1m8TS/WmbaBc3NZON10eCbm6XepdVc3zJuqruHCzCKcC9dTSTEk50zX04REcRcbIbdhTMciQoFIg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64/0.16.17: + resolution: {integrity: sha512-2By45OBHulkd9Svy5IOCZt376Aa2oOkiE9QWUK9fe6Tb+WDr8hXL3dpqi+DeLiMed8tVXspzsTAvd0jUl96wmg==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/darwin-x64/0.17.0: + resolution: {integrity: sha512-R4WB6D6V9KGO/3LVTT8UlwRJO26IBFatOdo/bRXksfJR0vyOi2/lgmAAMBSpgcnnwvts9QsWiyM++mTTlwRseA==} + engines: {node: '>=12'} + cpu: [x64] + os: [darwin] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64/0.16.17: + resolution: {integrity: sha512-mt+cxZe1tVx489VTb4mBAOo2aKSnJ33L9fr25JXpqQqzbUIw/yzIzi+NHwAXK2qYV1lEFp4OoVeThGjUbmWmdw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-arm64/0.17.0: + resolution: {integrity: sha512-FO7+UEZv79gen2df8StFYFHZPI9ADozpFepLZCxY+O8sYLDa1rirvenmLwJiOHmeQRJ5orYedFeLk1PFlZ6t8Q==} + engines: {node: '>=12'} + cpu: [arm64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64/0.16.17: + resolution: {integrity: sha512-8ScTdNJl5idAKjH8zGAsN7RuWcyHG3BAvMNpKOBaqqR7EbUhhVHOqXRdL7oZvz8WNHL2pr5+eIT5c65kA6NHug==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/freebsd-x64/0.17.0: + resolution: {integrity: sha512-qCsNRsVTaC3ekwZcb2sa7l1gwCtJK3EqCWyDgpoQocYf3lRpbAzaCvqZSF2+NOO64cV+JbedXPsFiXU1aaVcIg==} + engines: {node: '>=12'} + cpu: [x64] + os: [freebsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm/0.16.17: + resolution: {integrity: sha512-iihzrWbD4gIT7j3caMzKb/RsFFHCwqqbrbH9SqUSRrdXkXaygSZCZg1FybsZz57Ju7N/SHEgPyaR0LZ8Zbe9gQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm/0.17.0: + resolution: {integrity: sha512-Y2G2NU6155gcfNKvrakVmZV5xUAEhXjsN/uKtbKKRnvee0mHUuaT3OdQJDJKjHVGr6B0898pc3slRpI1PqspoQ==} + engines: {node: '>=12'} + cpu: [arm] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64/0.16.17: + resolution: {integrity: sha512-7S8gJnSlqKGVJunnMCrXHU9Q8Q/tQIxk/xL8BqAP64wchPCTzuM6W3Ra8cIa1HIflAvDnNOt2jaL17vaW+1V0g==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-arm64/0.17.0: + resolution: {integrity: sha512-js4Vlch5XJQYISbDVJd2hsI/MsfVUz6d/FrclCE73WkQmniH37vFpuQI42ntWAeBghDIfaPZ6f9GilhwGzVFUg==} + engines: {node: '>=12'} + cpu: [arm64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32/0.16.17: + resolution: {integrity: sha512-kiX69+wcPAdgl3Lonh1VI7MBr16nktEvOfViszBSxygRQqSpzv7BffMKRPMFwzeJGPxcio0pdD3kYQGpqQ2SSg==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ia32/0.17.0: + resolution: {integrity: sha512-7tl/jSPkF59R3zeFDB2/09zLGhcM7DM+tCoOqjJbQjuL6qbMWomGT2RglCqRFpCSdzBx0hukmPPgUAMlmdj0sQ==} + engines: {node: '>=12'} + cpu: [ia32] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-loong64/0.16.17: + resolution: {integrity: sha512-dTzNnQwembNDhd654cA4QhbS9uDdXC3TKqMJjgOWsC0yNCbpzfWoXdZvp0mY7HU6nzk5E0zpRGGx3qoQg8T2DQ==} engines: {node: '>=12'} cpu: [loong64] os: [linux] @@ -1735,14 +1936,221 @@ packages: dev: true optional: true - /@eslint/eslintrc/1.3.3: - resolution: {integrity: sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==} + /@esbuild/linux-loong64/0.17.0: + resolution: {integrity: sha512-OG356F7dIVVF+EXJx5UfzFr1I5l6ES53GlMNSr3U1MhlaVyrP9um5PnrSJ+7TSDAzUC7YGjxb2GQWqHLd5XFoA==} + engines: {node: '>=12'} + cpu: [loong64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el/0.16.17: + resolution: {integrity: sha512-ezbDkp2nDl0PfIUn0CsQ30kxfcLTlcx4Foz2kYv8qdC6ia2oX5Q3E/8m6lq84Dj/6b0FrkgD582fJMIfHhJfSw==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-mips64el/0.17.0: + resolution: {integrity: sha512-LWQJgGpxrjh2x08UYf6G5R+Km7zhkpCvKXtFQ6SX0fimDvy1C8kslgFHGxLS0wjGV8C4BNnENW/HNy57+RB7iA==} + engines: {node: '>=12'} + cpu: [mips64el] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64/0.16.17: + resolution: {integrity: sha512-dzS678gYD1lJsW73zrFhDApLVdM3cUF2MvAa1D8K8KtcSKdLBPP4zZSLy6LFZ0jYqQdQ29bjAHJDgz0rVbLB3g==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-ppc64/0.17.0: + resolution: {integrity: sha512-f40N8fKiTQslUcUuhof2/syOQ+DC9Mqdnm9d063pew+Ptv9r6dBNLQCz4300MOfCLAbb0SdnrcMSzHbMehXWLw==} + engines: {node: '>=12'} + cpu: [ppc64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64/0.16.17: + resolution: {integrity: sha512-ylNlVsxuFjZK8DQtNUwiMskh6nT0vI7kYl/4fZgV1llP5d6+HIeL/vmmm3jpuoo8+NuXjQVZxmKuhDApK0/cKw==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-riscv64/0.17.0: + resolution: {integrity: sha512-sc/pvLexRvxgEbmeq7LfLGnzUBFi/E2MGbnQj3CG8tnQ90tWPTi+9CbZEgIADhj6CAlCCmqxpUclIV1CRVUOTw==} + engines: {node: '>=12'} + cpu: [riscv64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x/0.16.17: + resolution: {integrity: sha512-gzy7nUTO4UA4oZ2wAMXPNBGTzZFP7mss3aKR2hH+/4UUkCOyqmjXiKpzGrY2TlEUhbbejzXVKKGazYcQTZWA/w==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-s390x/0.17.0: + resolution: {integrity: sha512-7xq9/kY0vunCL2vjHKdHGI+660pCdeEC6K6TWBVvbTGXvT8s/qacfxMgr8PCeQRbNUZLOA13G6/G1+c0lYXO1A==} + engines: {node: '>=12'} + cpu: [s390x] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64/0.16.17: + resolution: {integrity: sha512-mdPjPxfnmoqhgpiEArqi4egmBAMYvaObgn4poorpUaqmvzzbvqbowRllQ+ZgzGVMGKaPkqUmPDOOFQRUFDmeUw==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/linux-x64/0.17.0: + resolution: {integrity: sha512-o7FhBLONk1mLT2ytlj/j/WuJcPdhWcVpysSJn1s9+zRdLwLKveipbPi5SIasJIqMq0T4CkQW76pxJYMqz9HrQA==} + engines: {node: '>=12'} + cpu: [x64] + os: [linux] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64/0.16.17: + resolution: {integrity: sha512-/PzmzD/zyAeTUsduZa32bn0ORug+Jd1EGGAUJvqfeixoEISYpGnAezN6lnJoskauoai0Jrs+XSyvDhppCPoKOA==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/netbsd-x64/0.17.0: + resolution: {integrity: sha512-V6xXsv71b8vwFCW/ky82Rs//SbyA+ORty6A7Mzkg33/4NbYZ/1Vcbk7qAN5oi0i/gS4Q0+7dYT7NqaiVZ7+Xjw==} + engines: {node: '>=12'} + cpu: [x64] + os: [netbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64/0.16.17: + resolution: {integrity: sha512-2yaWJhvxGEz2RiftSk0UObqJa/b+rIAjnODJgv2GbGGpRwAfpgzyrg1WLK8rqA24mfZa9GvpjLcBBg8JHkoodg==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/openbsd-x64/0.17.0: + resolution: {integrity: sha512-StlQor6A0Y9SSDxraytr46Qbz25zsSDmsG3MCaNkBnABKHP3QsngOCfdBikqHVVrXeK0KOTmtX92/ncTGULYgQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [openbsd] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64/0.16.17: + resolution: {integrity: sha512-xtVUiev38tN0R3g8VhRfN7Zl42YCJvyBhRKw1RJjwE1d2emWTVToPLNEQj/5Qxc6lVFATDiy6LjVHYhIPrLxzw==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/sunos-x64/0.17.0: + resolution: {integrity: sha512-K64Wqw57j8KrwjR3QjsuzN/qDGK6Cno6QYtIlWAmGab5iYPBZCWz7HFtF2a86/130LmUsdXqOID7J0SmjjRFIQ==} + engines: {node: '>=12'} + cpu: [x64] + os: [sunos] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64/0.16.17: + resolution: {integrity: sha512-ga8+JqBDHY4b6fQAmOgtJJue36scANy4l/rL97W+0wYmijhxKetzZdKOJI7olaBaMhWt8Pac2McJdZLxXWUEQw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-arm64/0.17.0: + resolution: {integrity: sha512-hly6iSWAf0hf3aHD18/qW7iFQbg9KAQ0RFGG9plcxkhL4uGw43O+lETGcSO/PylNleFowP/UztpF6U4oCYgpPw==} + engines: {node: '>=12'} + cpu: [arm64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32/0.16.17: + resolution: {integrity: sha512-WnsKaf46uSSF/sZhwnqE4L/F89AYNMiD4YtEcYekBt9Q7nj0DiId2XH2Ng2PHM54qi5oPrQ8luuzGszqi/veig==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-ia32/0.17.0: + resolution: {integrity: sha512-aL4EWPh0nyC5uYRfn+CHkTgawd4DjtmwquthNDmGf6Ht6+mUc+bQXyZNH1QIw8x20hSqFc4Tf36aLLWP/TPR3g==} + engines: {node: '>=12'} + cpu: [ia32] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64/0.16.17: + resolution: {integrity: sha512-y+EHuSchhL7FjHgvQL/0fnnFmO4T1bhvWANX6gcnqTjtnKWbTvUMCpGnv2+t+31d7RzyEAYAd4u2fnIhHL6N/Q==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@esbuild/win32-x64/0.17.0: + resolution: {integrity: sha512-W6IIQ9Rt43I/GqfXeBFLk0TvowKBoirs9sw2LPfhHax6ayMlW5PhFzSJ76I1ac9Pk/aRcSMrHWvVyZs8ZPK2wA==} + engines: {node: '>=12'} + cpu: [x64] + os: [win32] + requiresBuild: true + dev: true + optional: true + + /@eslint/eslintrc/1.4.1: + resolution: {integrity: sha512-XXrH9Uarn0stsyldqDYq8r++mROmWRI1xKMXa640Bb//SY1+ECYX6VzT6Lcx5frD0V30XieqJ0oX9I2Xj5aoMA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: ajv: 6.12.6 debug: 4.3.4 espree: 9.4.0 - globals: 13.17.0 + globals: 13.19.0 ignore: 5.2.0 import-fresh: 3.3.0 js-yaml: 4.1.0 @@ -1762,8 +2170,8 @@ packages: '@hapi/hoek': 9.3.0 dev: true - /@humanwhocodes/config-array/0.11.7: - resolution: {integrity: sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==} + /@humanwhocodes/config-array/0.11.8: + resolution: {integrity: sha512-UybHIJzJnR5Qc/MsD9Kr+RpO2h+/P1GhOwdiLPXK5TWk5sgTdu88bTD9UP+CKbPPh5Rni1u0GjAdYQLemG8g+g==} engines: {node: '>=10.10.0'} dependencies: '@humanwhocodes/object-schema': 1.2.1 @@ -2107,6 +2515,10 @@ packages: resolution: {integrity: sha512-vHe7wZ4NOXVfkoRb8T5otiENVlT7a3IAiw7H5M2+GO+9CDgcVUUsX1zalAztCmwyOr2RUTGJdgB+ZvSVqmdHmg==} dev: true + /@sideway/formula/3.0.1: + resolution: {integrity: sha512-/poHZJJVjx3L+zVD6g9KgHfYnb443oi7wLu/XKojDviHy6HOEOA6z1Trk5aR1dGcmPenJEgb2sK2I80LeS3MIg==} + dev: true + /@sideway/pinpoint/2.0.0: resolution: {integrity: sha512-RNiOoTPkptFtSVzQevY/yWtZwf/RxyVnPy/OcA9HBM3MlGDnBEYL5B41H0MTn0Uec8Hi+2qUtTfG2WWZBmMejQ==} dev: true @@ -2223,11 +2635,11 @@ packages: /@types/chai-subset/1.3.3: resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} dependencies: - '@types/chai': 4.3.3 + '@types/chai': 4.3.4 dev: true - /@types/chai/4.3.3: - resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==} + /@types/chai/4.3.4: + resolution: {integrity: sha512-KnRanxnpfpjUTqTCXslZSEdLfXExwgNxYPdiO2WGUj8+HDjFi8R3k5RVKPeSCzLjCcshCAtVO2QBbVuAV4kTnw==} dev: true /@types/connect-history-api-fallback/1.3.5: @@ -2243,6 +2655,16 @@ packages: '@types/node': 18.11.9 dev: true + /@types/cors/2.8.13: + resolution: {integrity: sha512-RG8AStHlUiV5ysZQKq97copd2UmVYw3/pRMLefISZ3S1hK104Cwm7iLQ3fTKx+lsUH2CE8FlLaYeEA2LSeqYUA==} + dependencies: + '@types/node': 18.11.9 + dev: true + + /@types/cytoscape/3.19.9: + resolution: {integrity: sha512-oqCx0ZGiBO0UESbjgq052vjDAy2X53lZpMrWqiweMpvVwKw/2IiYDdzPFK6+f4tMfdv9YKEM9raO5bAZc3UYBg==} + dev: true + /@types/d3-array/3.0.3: resolution: {integrity: sha512-Reoy+pKnvsksN0lQUlcH6dOGjRZ/3WRwXR//m+/8lt1BXeI4xyaUZoqULNjyXXRuh0Mj4LNpkCvhUpQlY3X5xQ==} dev: true @@ -2464,6 +2886,14 @@ packages: '@types/range-parser': 1.2.4 dev: true + /@types/express-serve-static-core/4.17.33: + resolution: {integrity: sha512-TPBqmR/HRYI3eC2E5hmiivIzv+bidAfXofM+sbonAGvyDhySGw9/PQZFt2BLOrjUUR++4eJVpx6KnLQK1Fk9tA==} + dependencies: + '@types/node': 18.11.9 + '@types/qs': 6.9.7 + '@types/range-parser': 1.2.4 + dev: true + /@types/express/4.17.14: resolution: {integrity: sha512-TEbt+vaPFQ+xpxFLFssxUDXj5cWCxZJjIcB7Yg0k0GMHGtgtQgpvx/MUQUeAkNbA9AAGrwkAsoeItdTgS7FMyg==} dependencies: @@ -2473,6 +2903,15 @@ packages: '@types/serve-static': 1.15.0 dev: true + /@types/express/4.17.17: + resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} + dependencies: + '@types/body-parser': 1.19.2 + '@types/express-serve-static-core': 4.17.33 + '@types/qs': 6.9.7 + '@types/serve-static': 1.15.0 + dev: true + /@types/flexsearch/0.7.3: resolution: {integrity: sha512-HXwADeHEP4exXkCIwy2n1+i0f1ilP1ETQOH5KDOugjkTFZPntWo0Gr8stZOaebkxsdx+k0X/K6obU/+it07ocg==} dev: true @@ -2517,8 +2956,8 @@ packages: resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} dev: true - /@types/jsdom/20.0.1: - resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} + /@types/jsdom/21.1.0: + resolution: {integrity: sha512-leWreJOdnuIxq9Y70tBVm/bvTuh31DSlF/r4l7Cfi4uhVQqLHD0Q4v301GMisEMwwbMgF7ZKxuZ+Jbd4NcdmRw==} dependencies: '@types/node': 18.11.9 '@types/tough-cookie': 4.0.2 @@ -2695,8 +3134,8 @@ packages: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true - /@types/uuid/8.3.4: - resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} + /@types/uuid/9.0.0: + resolution: {integrity: sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==} dev: true /@types/web-bluetooth/0.0.16: @@ -2727,7 +3166,7 @@ packages: dev: 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==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -2738,12 +3177,12 @@ packages: typescript: optional: true dependencies: - '@typescript-eslint/parser': 5.42.1_rmayb2veg2btbq6mbmnyivgasy + '@typescript-eslint/parser': 5.42.1_yygwinqv3a2io74xmwofqb7uka '@typescript-eslint/scope-manager': 5.42.1 - '@typescript-eslint/type-utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy - '@typescript-eslint/utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy + '@typescript-eslint/type-utils': 5.42.1_yygwinqv3a2io74xmwofqb7uka + '@typescript-eslint/utils': 5.42.1_yygwinqv3a2io74xmwofqb7uka debug: 4.3.4 - eslint: 8.27.0 + eslint: 8.32.0 ignore: 5.2.0 natural-compare-lite: 1.4.0 regexpp: 3.2.0 @@ -2754,7 +3193,34 @@ packages: - supports-color dev: true - /@typescript-eslint/parser/5.42.1_rmayb2veg2btbq6mbmnyivgasy: + /@typescript-eslint/eslint-plugin/5.48.2_azmbqzqvrlvblbdtiwxwvyvjjy: + 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_et5x32uxl7z5ldub3ye5rhlyqm + '@typescript-eslint/scope-manager': 5.48.2 + '@typescript-eslint/type-utils': 5.48.2_et5x32uxl7z5ldub3ye5rhlyqm + '@typescript-eslint/utils': 5.48.2_et5x32uxl7z5ldub3ye5rhlyqm + 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.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/parser/5.42.1_yygwinqv3a2io74xmwofqb7uka: resolution: {integrity: sha512-kAV+NiNBWVQDY9gDJDToTE/NO8BHi4f6b7zTsVAJoTkmB/zlfOpiEVBzHOKtlgTndCKe8vj9F/PuolemZSh50Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -2768,12 +3234,32 @@ packages: '@typescript-eslint/types': 5.42.1 '@typescript-eslint/typescript-estree': 5.42.1_typescript@4.8.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_et5x32uxl7z5ldub3ye5rhlyqm: + 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.9.5 + debug: 4.3.4 + eslint: 8.32.0 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/scope-manager/5.42.1: resolution: {integrity: sha512-QAZY/CBP1Emx4rzxurgqj3rUinfsh/6mvuKbLNMfJMMKYLRBfweus8brgXF8f64ABkIZ3zdj2/rYYtF8eiuksQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2782,7 +3268,15 @@ packages: '@typescript-eslint/visitor-keys': 5.42.1 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==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -2793,20 +3287,45 @@ packages: optional: true dependencies: '@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 - 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_et5x32uxl7z5ldub3ye5rhlyqm: + 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.9.5 + '@typescript-eslint/utils': 5.48.2_et5x32uxl7z5ldub3ye5rhlyqm + debug: 4.3.4 + eslint: 8.32.0 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + /@typescript-eslint/types/5.42.1: resolution: {integrity: sha512-Qrco9dsFF5lhalz+lLFtxs3ui1/YfC6NdXu+RAGBa8uSfn01cjO7ssCsjIsUs484vny9Xm699FSKwpkCcqwWwA==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} 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: resolution: {integrity: sha512-qElc0bDOuO0B8wDhhW4mYVgi/LZL+igPwXtV87n69/kYC/7NG3MES0jHxJNCr4EP7kY1XVsRy8C/u3DYeTKQmw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} @@ -2828,7 +3347,69 @@ packages: - supports-color dev: true - /@typescript-eslint/utils/5.42.1_rmayb2veg2btbq6mbmnyivgasy: + /@typescript-eslint/typescript-estree/5.42.1_typescript@4.9.5: + resolution: {integrity: sha512-qElc0bDOuO0B8wDhhW4mYVgi/LZL+igPwXtV87n69/kYC/7NG3MES0jHxJNCr4EP7kY1XVsRy8C/u3DYeTKQmw==} + engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + peerDependencies: + typescript: '*' + peerDependenciesMeta: + typescript: + optional: true + dependencies: + '@typescript-eslint/types': 5.42.1 + '@typescript-eslint/visitor-keys': 5.42.1 + debug: 4.3.4 + globby: 11.1.0 + is-glob: 4.0.3 + semver: 7.3.8 + tsutils: 3.21.0_typescript@4.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/typescript-estree/5.48.2_typescript@4.9.5: + 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.9.5 + typescript: 4.9.5 + transitivePeerDependencies: + - supports-color + dev: true + + /@typescript-eslint/utils/5.42.1_et5x32uxl7z5ldub3ye5rhlyqm: + resolution: {integrity: sha512-Gxvf12xSp3iYZd/fLqiQRD4uKZjDNR01bQ+j8zvhPjpsZ4HmvEFL/tC4amGNyxN9Rq+iqvpHLhlqx6KTxz9ZyQ==} + 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.42.1 + '@typescript-eslint/types': 5.42.1 + '@typescript-eslint/typescript-estree': 5.42.1_typescript@4.9.5 + eslint: 8.32.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.32.0 + semver: 7.3.8 + transitivePeerDependencies: + - supports-color + - typescript + dev: true + + /@typescript-eslint/utils/5.42.1_yygwinqv3a2io74xmwofqb7uka: resolution: {integrity: sha512-Gxvf12xSp3iYZd/fLqiQRD4uKZjDNR01bQ+j8zvhPjpsZ4HmvEFL/tC4amGNyxN9Rq+iqvpHLhlqx6KTxz9ZyQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: @@ -2839,9 +3420,29 @@ packages: '@typescript-eslint/scope-manager': 5.42.1 '@typescript-eslint/types': 5.42.1 '@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-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_et5x32uxl7z5ldub3ye5rhlyqm: + 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.9.5 + eslint: 8.32.0 + eslint-scope: 5.1.1 + eslint-utils: 3.0.0_eslint@8.32.0 semver: 7.3.8 transitivePeerDependencies: - supports-color @@ -2856,22 +3457,32 @@ packages: eslint-visitor-keys: 3.3.0 dev: true - /@vitejs/plugin-vue/3.2.0_vite@3.2.3+vue@3.2.41: - resolution: {integrity: sha512-E0tnaL4fr+qkdCNxJ+Xd0yM31UwMkQje76fsDVBBUCoGOUPexu2VDUYHL8P4CwV+zMvWw6nlRw19OnRKmYAJpw==} - engines: {node: ^14.18.0 || >=16.0.0} - peerDependencies: - vite: ^3.0.0 - vue: ^3.2.25 + /@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: - vite: 3.2.3 - vue: 3.2.41 + '@typescript-eslint/types': 5.48.2 + eslint-visitor-keys: 3.3.0 dev: true - /@vitest/coverage-c8/0.25.1_oullksb5ic6y72oh2wekoaiuii: - resolution: {integrity: sha512-gpl5QNaNeIN0mfRiosCqBFoZcizb5GA458TDnOQXkGDc4kklazxn70u9evGfV62wiiAUfGGebgRhxlBkAa6m6g==} + /@vitejs/plugin-vue/4.0.0_vite@4.1.1+vue@3.2.45: + resolution: {integrity: sha512-e0X4jErIxAB5oLtDqbHvHpJe/uWNkdpYV83AOG2xo2tEVSzCzewgJMtREZM30wXnM5ls90hxiOtAuVU6H5JgbA==} + engines: {node: ^14.18.0 || >=16.0.0} + peerDependencies: + vite: ^4.0.0 + vue: ^3.2.25 + dependencies: + vite: 4.1.1 + vue: 3.2.45 + dev: true + + /@vitest/coverage-c8/0.28.4_vun5xzxu3tkrssf3erdbijyyki: + resolution: {integrity: sha512-btelLBxaWhHnywXRQxDlrvPhGdnuIaD3XulsxcZRIcnpLPbFu39dNTT0IYu2QWP2ZZrV0AmNtdLIfD4c77zMAg==} dependencies: c8: 7.12.0 - vitest: 0.25.1_oullksb5ic6y72oh2wekoaiuii + picocolors: 1.0.0 + std-env: 3.3.2 + vitest: 0.28.4_vun5xzxu3tkrssf3erdbijyyki transitivePeerDependencies: - '@edge-runtime/vm' - '@vitest/browser' @@ -2886,120 +3497,187 @@ packages: - terser dev: true - /@vitest/ui/0.25.1: - resolution: {integrity: sha512-VjzyfLjNS5Zc7XCCFJW3cM2iVW305D65NG0PIWefA4A8mwOH/QJJ4nFj/4cwXzwL0/VT3/ppvpv3UDNZoh/YOQ==} + /@vitest/expect/0.28.4: + resolution: {integrity: sha512-JqK0NZ4brjvOSL8hXAnIsfi+jxDF7rH/ZWCGCt0FAqRnVFc1hXsfwXksQvEnKqD84avRt3gmeXoK4tNbmkoVsQ==} dependencies: + '@vitest/spy': 0.28.4 + '@vitest/utils': 0.28.4 + chai: 4.3.7 + dev: true + + /@vitest/expect/0.28.5: + resolution: {integrity: sha512-gqTZwoUTwepwGIatnw4UKpQfnoyV0Z9Czn9+Lo2/jLIt4/AXLTn+oVZxlQ7Ng8bzcNkR+3DqLJ08kNr8jRmdNQ==} + dependencies: + '@vitest/spy': 0.28.5 + '@vitest/utils': 0.28.5 + chai: 4.3.7 + dev: true + + /@vitest/runner/0.28.4: + resolution: {integrity: sha512-Q8UV6GjDvBSTfUoq0QXVCNpNOUrWu4P2qvRq7ssJWzn0+S0ojbVOxEjMt+8a32X6SdkhF8ak+2nkppsqV0JyNQ==} + dependencies: + '@vitest/utils': 0.28.4 + p-limit: 4.0.0 + pathe: 1.1.0 + dev: true + + /@vitest/runner/0.28.5: + resolution: {integrity: sha512-NKkHtLB+FGjpp5KmneQjTcPLWPTDfB7ie+MmF1PnUBf/tGe2OjGxWyB62ySYZ25EYp9krR5Bw0YPLS/VWh1QiA==} + dependencies: + '@vitest/utils': 0.28.5 + p-limit: 4.0.0 + pathe: 1.1.0 + dev: true + + /@vitest/spy/0.28.4: + resolution: {integrity: sha512-8WuhfXLlvCXpNXEGJW6Gc+IKWI32435fQJLh43u70HnZ1otJOa2Cmg2Wy2Aym47ZnNCP4NolF+8cUPwd0MigKQ==} + dependencies: + tinyspy: 1.0.2 + dev: true + + /@vitest/spy/0.28.5: + resolution: {integrity: sha512-7if6rsHQr9zbmvxN7h+gGh2L9eIIErgf8nSKYDlg07HHimCxp4H6I/X/DPXktVPPLQfiZ1Cw2cbDIx9fSqDjGw==} + dependencies: + tinyspy: 1.0.2 + dev: true + + /@vitest/ui/0.28.4: + resolution: {integrity: sha512-LQfCCFc17n49mwtraV9/NAWl2DUqJS/9ZEa3fqJjoYO+HowdseQ5jvWflpzliCyfrIAh6cXVo1bNzHnDXe0cbw==} + dependencies: + fast-glob: 3.2.12 + flatted: 3.2.7 + pathe: 1.1.0 + picocolors: 1.0.0 sirv: 2.0.2 dev: true - /@vue/compiler-core/3.2.41: - resolution: {integrity: sha512-oA4mH6SA78DT+96/nsi4p9DX97PHcNROxs51lYk7gb9Z4BPKQ3Mh+BLn6CQZBw857Iuhu28BfMSRHAlPvD4vlw==} + /@vitest/utils/0.28.4: + resolution: {integrity: sha512-l2QztOLdc2LkR+w/lP52RGh8hW+Ul4KESmCAgVE8q737I7e7bQoAfkARKpkPJ4JQtGpwW4deqlj1732VZD7TFw==} + dependencies: + cli-truncate: 3.1.0 + diff: 5.1.0 + loupe: 2.3.6 + picocolors: 1.0.0 + pretty-format: 27.5.1 + dev: true + + /@vitest/utils/0.28.5: + resolution: {integrity: sha512-UyZdYwdULlOa4LTUSwZ+Paz7nBHGTT72jKwdFSV4IjHF1xsokp+CabMdhjvVhYwkLfO88ylJT46YMilnkSARZA==} + dependencies: + cli-truncate: 3.1.0 + diff: 5.1.0 + loupe: 2.3.6 + picocolors: 1.0.0 + pretty-format: 27.5.1 + dev: true + + /@vue/compiler-core/3.2.45: + resolution: {integrity: sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==} dependencies: '@babel/parser': 7.19.1 - '@vue/shared': 3.2.41 + '@vue/shared': 3.2.45 estree-walker: 2.0.2 source-map: 0.6.1 dev: true - /@vue/compiler-dom/3.2.41: - resolution: {integrity: sha512-xe5TbbIsonjENxJsYRbDJvthzqxLNk+tb3d/c47zgREDa/PCp6/Y4gC/skM4H6PIuX5DAxm7fFJdbjjUH2QTMw==} + /@vue/compiler-dom/3.2.45: + resolution: {integrity: sha512-tyYeUEuKqqZO137WrZkpwfPCdiiIeXYCcJ8L4gWz9vqaxzIQRccTSwSWZ/Axx5YR2z+LvpUbmPNXxuBU45lyRw==} dependencies: - '@vue/compiler-core': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/compiler-core': 3.2.45 + '@vue/shared': 3.2.45 dev: true - /@vue/compiler-sfc/3.2.41: - resolution: {integrity: sha512-+1P2m5kxOeaxVmJNXnBskAn3BenbTmbxBxWOtBq3mQTCokIreuMULFantBUclP0+KnzNCMOvcnKinqQZmiOF8w==} - requiresBuild: true + /@vue/compiler-sfc/3.2.45: + resolution: {integrity: sha512-1jXDuWah1ggsnSAOGsec8cFjT/K6TMZ0sPL3o3d84Ft2AYZi2jWJgRMjw4iaK0rBfA89L5gw427H4n1RZQBu6Q==} dependencies: '@babel/parser': 7.19.1 - '@vue/compiler-core': 3.2.41 - '@vue/compiler-dom': 3.2.41 - '@vue/compiler-ssr': 3.2.41 - '@vue/reactivity-transform': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/compiler-core': 3.2.45 + '@vue/compiler-dom': 3.2.45 + '@vue/compiler-ssr': 3.2.45 + '@vue/reactivity-transform': 3.2.45 + '@vue/shared': 3.2.45 estree-walker: 2.0.2 magic-string: 0.25.9 - postcss: 8.4.18 + postcss: 8.4.20 source-map: 0.6.1 dev: true - /@vue/compiler-ssr/3.2.41: - resolution: {integrity: sha512-Y5wPiNIiaMz/sps8+DmhaKfDm1xgj6GrH99z4gq2LQenfVQcYXmHIOBcs5qPwl7jaW3SUQWjkAPKMfQemEQZwQ==} + /@vue/compiler-ssr/3.2.45: + resolution: {integrity: sha512-6BRaggEGqhWht3lt24CrIbQSRD5O07MTmd+LjAn5fJj568+R9eUD2F7wMQJjX859seSlrYog7sUtrZSd7feqrQ==} dependencies: - '@vue/compiler-dom': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/compiler-dom': 3.2.45 + '@vue/shared': 3.2.45 dev: true - /@vue/devtools-api/6.4.5: - resolution: {integrity: sha512-JD5fcdIuFxU4fQyXUu3w2KpAJHzTVdN+p4iOX2lMWSHMOoQdMAcpFLZzm9Z/2nmsoZ1a96QEhZ26e50xLBsgOQ==} + /@vue/devtools-api/6.5.0: + resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} dev: true - /@vue/reactivity-transform/3.2.41: - resolution: {integrity: sha512-mK5+BNMsL4hHi+IR3Ft/ho6Za+L3FA5j8WvreJ7XzHrqkPq8jtF/SMo7tuc9gHjLDwKZX1nP1JQOKo9IEAn54A==} + /@vue/reactivity-transform/3.2.45: + resolution: {integrity: sha512-BHVmzYAvM7vcU5WmuYqXpwaBHjsS8T63jlKGWVtHxAHIoMIlmaMyurUSEs1Zcg46M4AYT5MtB1U274/2aNzjJQ==} dependencies: '@babel/parser': 7.19.1 - '@vue/compiler-core': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/compiler-core': 3.2.45 + '@vue/shared': 3.2.45 estree-walker: 2.0.2 magic-string: 0.25.9 dev: true - /@vue/reactivity/3.2.41: - resolution: {integrity: sha512-9JvCnlj8uc5xRiQGZ28MKGjuCoPhhTwcoAdv3o31+cfGgonwdPNuvqAXLhlzu4zwqavFEG5tvaoINQEfxz+l6g==} + /@vue/reactivity/3.2.45: + resolution: {integrity: sha512-PRvhCcQcyEVohW0P8iQ7HDcIOXRjZfAsOds3N99X/Dzewy8TVhTCT4uXpAHfoKjVTJRA0O0K+6QNkDIZAxNi3A==} dependencies: - '@vue/shared': 3.2.41 + '@vue/shared': 3.2.45 dev: true - /@vue/runtime-core/3.2.41: - resolution: {integrity: sha512-0LBBRwqnI0p4FgIkO9q2aJBBTKDSjzhnxrxHYengkAF6dMOjeAIZFDADAlcf2h3GDALWnblbeprYYpItiulSVQ==} + /@vue/runtime-core/3.2.45: + resolution: {integrity: sha512-gzJiTA3f74cgARptqzYswmoQx0fIA+gGYBfokYVhF8YSXjWTUA2SngRzZRku2HbGbjzB6LBYSbKGIaK8IW+s0A==} dependencies: - '@vue/reactivity': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/reactivity': 3.2.45 + '@vue/shared': 3.2.45 dev: true - /@vue/runtime-dom/3.2.41: - resolution: {integrity: sha512-U7zYuR1NVIP8BL6jmOqmapRAHovEFp7CSw4pR2FacqewXNGqZaRfHoNLQsqQvVQ8yuZNZtxSZy0FFyC70YXPpA==} + /@vue/runtime-dom/3.2.45: + resolution: {integrity: sha512-cy88YpfP5Ue2bDBbj75Cb4bIEZUMM/mAkDMfqDTpUYVgTf/kuQ2VQ8LebuZ8k6EudgH8pYhsGWHlY0lcxlvTwA==} dependencies: - '@vue/runtime-core': 3.2.41 - '@vue/shared': 3.2.41 + '@vue/runtime-core': 3.2.45 + '@vue/shared': 3.2.45 csstype: 2.6.21 dev: true - /@vue/server-renderer/3.2.41_vue@3.2.41: - resolution: {integrity: sha512-7YHLkfJdTlsZTV0ae5sPwl9Gn/EGr2hrlbcS/8naXm2CDpnKUwC68i1wGlrYAfIgYWL7vUZwk2GkYLQH5CvFig==} + /@vue/server-renderer/3.2.45_vue@3.2.45: + resolution: {integrity: sha512-ebiMq7q24WBU1D6uhPK//2OTR1iRIyxjF5iVq/1a5I1SDMDyDu4Ts6fJaMnjrvD3MqnaiFkKQj+LKAgz5WIK3g==} peerDependencies: - vue: 3.2.41 + vue: 3.2.45 dependencies: - '@vue/compiler-ssr': 3.2.41 - '@vue/shared': 3.2.41 - vue: 3.2.41 + '@vue/compiler-ssr': 3.2.45 + '@vue/shared': 3.2.45 + vue: 3.2.45 dev: true - /@vue/shared/3.2.41: - resolution: {integrity: sha512-W9mfWLHmJhkfAmV+7gDjcHeAWALQtgGT3JErxULl0oz6R6+3ug91I7IErs93eCFhPCZPHBs4QJS7YWEV7A3sxw==} + /@vue/shared/3.2.45: + resolution: {integrity: sha512-Ewzq5Yhimg7pSztDV+RH1UDKBzmtqieXQlpTVm2AwraoRL/Rks96mvd8Vgi7Lj+h+TH8dv7mXD3FRZR3TUvbSg==} dev: true - /@vueuse/core/9.4.0_vue@3.2.41: - resolution: {integrity: sha512-JzgenGj1ZF2BHOen5rsFiAyyI9sXAv7aKhNLlm9b7SwYQeKTcxTWdhudonURCSP3Egl9NQaRBzes2lv/1JUt/Q==} + /@vueuse/core/9.12.0_vue@3.2.45: + resolution: {integrity: sha512-h/Di8Bvf6xRcvS/PvUVheiMYYz3U0tH3X25YxONSaAUBa841ayMwxkuzx/DGUMCW/wHWzD8tRy2zYmOC36r4sg==} dependencies: '@types/web-bluetooth': 0.0.16 - '@vueuse/metadata': 9.4.0 - '@vueuse/shared': 9.4.0_vue@3.2.41 - vue-demi: 0.13.11_vue@3.2.41 + '@vueuse/metadata': 9.12.0 + '@vueuse/shared': 9.12.0_vue@3.2.45 + vue-demi: 0.13.11_vue@3.2.45 transitivePeerDependencies: - '@vue/composition-api' - vue dev: true - /@vueuse/metadata/9.4.0: - resolution: {integrity: sha512-7GKMdGAsJyQJl35MYOz/RDpP0FxuiZBRDSN79QIPbdqYx4Sd0sVTnIC68KJ6Oln0t0SouvSUMvRHuno216Ud2Q==} + /@vueuse/metadata/9.12.0: + resolution: {integrity: sha512-9oJ9MM9lFLlmvxXUqsR1wLt1uF7EVbP5iYaHJYqk+G2PbMjY6EXvZeTjbdO89HgoF5cI6z49o2zT/jD9SVoNpQ==} dev: true - /@vueuse/shared/9.4.0_vue@3.2.41: - resolution: {integrity: sha512-fTuem51KwMCnqUKkI8B57qAIMcFovtGgsCtAeqxIzH3i6nE9VYge+gVfneNHAAy7lj8twbkNfqQSygOPJTm4tQ==} + /@vueuse/shared/9.12.0_vue@3.2.45: + resolution: {integrity: sha512-TWuJLACQ0BVithVTRbex4Wf1a1VaRuSpVeyEd4vMUWl54PzlE0ciFUshKCXnlLuD0lxIaLK4Ypj3NXYzZh4+SQ==} dependencies: - vue-demi: 0.13.11_vue@3.2.41 + vue-demi: 0.13.11_vue@3.2.45 transitivePeerDependencies: - '@vue/composition-api' - vue @@ -3239,7 +3917,7 @@ packages: /acorn-globals/7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} dependencies: - acorn: 8.8.0 + acorn: 8.8.1 acorn-walk: 8.2.0 dev: true @@ -3251,12 +3929,12 @@ packages: acorn: 8.8.0 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==} peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - acorn: 8.8.0 + acorn: 8.8.1 dev: true /acorn-walk/7.2.0: @@ -3281,6 +3959,12 @@ packages: hasBin: true dev: true + /acorn/8.8.1: + resolution: {integrity: sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==} + engines: {node: '>=0.4.0'} + hasBin: true + dev: true + /agent-base/6.0.2: resolution: {integrity: sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==} engines: {node: '>= 6.0.0'} @@ -3298,6 +3982,14 @@ packages: indent-string: 4.0.0 dev: true + /aggregate-error/4.0.1: + resolution: {integrity: sha512-0poP0T7el6Vq3rstR8Mn4V/IQrpBLO6POkUSrN7RhyY+GF/InCFShQzsQ39T25gkHhLgSLByyAz+Kjb+c2L98w==} + engines: {node: '>=12'} + dependencies: + clean-stack: 4.2.0 + indent-string: 5.0.0 + dev: true + /ajv-formats/2.1.1_ajv@8.11.0: resolution: {integrity: sha512-Wx0Kx52hxE7C18hkMEggYlEifqWZtYaRgouJor+WMdPnQyEK13vgEWyVNup7SoeeoLMsr4kf5h6dOW11I15MUA==} peerDependencies: @@ -3402,6 +4094,10 @@ packages: engines: {node: '>=12'} dev: true + /ansi-sequence-parser/1.1.0: + resolution: {integrity: sha512-lEm8mt52to2fT8GhciPCGeCXACSz2UwIN4X2e2LJSnZ5uAbn2/dsYdOmUXq0AtWS5cpAupysIneExOgH0Vd2TQ==} + dev: true + /ansi-styles/2.2.1: resolution: {integrity: sha512-kmCevFghRiWM7HB5zTPULl4r9bVFSWjz62MhqizDGUrq2NWuNMQyuv4tHHoKJHs69M/MF64lEcHdYIocrdWQYA==} engines: {node: '>=0.10.0'} @@ -3454,6 +4150,10 @@ packages: resolution: {integrity: sha512-58S9QDqG0Xx27YwPSt9fJxivjYl432YCwfDMfZ+71RAqUrZef7LrKQZ3LHLOwCS4FLNBplP533Zx895SeOCHvA==} dev: true + /arg/5.0.2: + resolution: {integrity: sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==} + dev: true + /argparse/1.0.10: resolution: {integrity: sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==} dependencies: @@ -3490,6 +4190,11 @@ packages: engines: {node: '>=0.10.0'} dev: true + /arrify/3.0.0: + resolution: {integrity: sha512-tLkvA81vQG/XqE2mjDkGQHoOINtMHtysSnemrmoGe6PydDPMRbVugqyk4A6V/WDWEfm3l+0d8anA9r8cv/5Jaw==} + engines: {node: '>=12'} + dev: true + /asn1/0.2.6: resolution: {integrity: sha512-ix/FxPn0MDjeyJ7i/yoHGFt/EX6LyNbxSEhPPXODPL+KB0VPk86UYfL0lMdy+KCnv+fmvIzySwaK5COwqVbWTQ==} dependencies: @@ -3546,6 +4251,15 @@ packages: - debug dev: true + /axios/0.27.2_debug@4.3.4: + resolution: {integrity: sha512-t+yRIyySRTp/wua5xEr+z1q60QmLq8ABsS5O9Me1AsE5dfKqgnCFzwiCZZ/cGNd1lq4/7akDWMxdhVlucjmnOQ==} + dependencies: + follow-redirects: 1.15.2_debug@4.3.4 + form-data: 4.0.0 + transitivePeerDependencies: + - debug + dev: true + /babel-jest/29.3.1_@babel+core@7.12.3: resolution: {integrity: sha512-aard+xnMoxgjwV70t0L6wkW/3HQQtV+O0PEimxKgzNqCJnbYmroPojdP2tqKSOAt8QAKV/uSZU8851M7B5+fcA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -3780,6 +4494,11 @@ packages: yargs-parser: 20.2.9 dev: true + /cac/6.7.14: + resolution: {integrity: sha512-b6Ilus+c3RrdDk+JhLKUAQfzzgLEPy6wcXqS7f/xe1EETvsDP6GORG7SFuOs6cID5YkqchW/LXZbX5bc8j7ZcQ==} + engines: {node: '>=8'} + dev: true + /cacheable-lookup/5.0.4: resolution: {integrity: sha512-2/kNscPhpcxrOigMZzbiWF7dz8ilhb/nIHU3EyZiXWXpeq/au8qJ8VhdftMkty3n7Gj6HIGalQG8oiBNB3AJgA==} engines: {node: '>=10.6.0'} @@ -3824,6 +4543,16 @@ packages: quick-lru: 4.0.1 dev: true + /camelcase-keys/7.0.2: + resolution: {integrity: sha512-Rjs1H+A9R+Ig+4E/9oyB66UC5Mj9Xq3N//vcLf2WzgdTi/3gUu3Z9KoqmlrEG4VuuLK8wJHofxzdQXz/knhiYg==} + engines: {node: '>=12'} + dependencies: + camelcase: 6.3.0 + map-obj: 4.3.0 + quick-lru: 5.1.1 + type-fest: 1.4.0 + dev: true + /camelcase/5.3.1: resolution: {integrity: sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==} engines: {node: '>=6'} @@ -3842,15 +4571,19 @@ packages: resolution: {integrity: sha512-4tYFyifaFfGacoiObjJegolkwSU4xQNGbVgUiNYVUxbQ2x2lUsFvY4hVgVzGiIe6WLOPqycWXA40l+PWsxthUw==} dev: true - /chai/4.3.6: - resolution: {integrity: sha512-bbcp3YfHCUzMOvKqsztczerVgBKSsEijCySNlHHbX3VG1nskvqjz5Rfso1gGwD6w6oOV3eI60pKuMOV5MV7p3Q==} + /ccount/2.0.1: + resolution: {integrity: sha512-eyrF0jiFpY+3drT6383f1qhkbGsLSifNAjA61IUjZjmLCWjItY6LB9ft9YhoDgwfmclB2zhu51Lc7+95b8NRAg==} + dev: true + + /chai/4.3.7: + resolution: {integrity: sha512-HLnAzZ2iupm25PlN0xFreAlBA5zaBSv3og0DdeGA4Ar6h6rJ3A0rolRUKJhSF2V10GZKDgWF/VmAEsNWjCRB+A==} engines: {node: '>=4'} dependencies: assertion-error: 1.1.0 check-error: 1.0.2 - deep-eql: 3.0.1 + deep-eql: 4.1.3 get-func-name: 2.0.0 - loupe: 2.3.4 + loupe: 2.3.6 pathval: 1.1.1 type-detect: 4.0.8 dev: true @@ -3969,6 +4702,13 @@ packages: engines: {node: '>=6'} dev: true + /clean-stack/4.2.0: + resolution: {integrity: sha512-LYv6XPxoyODi36Dp976riBtSY27VmFo+MKqEU9QCCWyTrdEPDog+RWA7xQWHi6Vbp61j5c4cdzzX1NidnwtUWg==} + engines: {node: '>=12'} + dependencies: + escape-string-regexp: 5.0.0 + dev: true + /clear-module/4.1.2: resolution: {integrity: sha512-LWAxzHqdHsAZlPlEyJ2Poz6AIs384mPeqLVCru2p0BrP9G/kVGuhNyZYClLO6cXlnuJjzC8xtsJIuMjKqLXoAw==} engines: {node: '>=8'} @@ -4152,7 +4892,7 @@ packages: dev: true /concat-map/0.0.1: - resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} + resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} dev: true /concurrently/7.5.0: @@ -4257,6 +4997,14 @@ packages: resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} dev: true + /cors/2.8.5: + resolution: {integrity: sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==} + engines: {node: '>= 0.10'} + dependencies: + object-assign: 4.1.1 + vary: 1.1.2 + dev: true + /cose-base/1.0.3: resolution: {integrity: sha512-s9whTXInMSgAp/NVXVNuVxVKzGH2qck3aQlVHxDCdAEPgtMKwc4Wq6/QKhgdEdgbLSi9rBTAcPoRa6JpiG4ksg==} dependencies: @@ -4269,7 +5017,7 @@ packages: layout-base: 2.0.1 dev: false - /cosmiconfig-typescript-loader/4.1.0_nxlrwu45zhpwmwjzs33dzt3ak4: + /cosmiconfig-typescript-loader/4.1.0_2uclxasecupgvdn72amnhmyg7y: resolution: {integrity: sha512-HbWIuR5O+XO5Oj9SZ5bzgrD4nN+rfhrm2PMb0FVx+t+XIvC45n8F0oTNnztXtspWGw0i2IzHaUWFD5LzV1JB4A==} engines: {node: '>=12', npm: '>=6'} peerDependencies: @@ -4280,8 +5028,8 @@ packages: dependencies: '@types/node': 14.18.29 cosmiconfig: 7.0.1 - ts-node: 10.9.1_sqjhzn5m3vxyw66a2xhtc43hby - typescript: 4.8.4 + ts-node: 10.9.1_yxpazyh7n5pql7jdaglasgwqki + typescript: 4.9.5 dev: true /cosmiconfig/7.0.1: @@ -4318,6 +5066,39 @@ packages: request: 2.88.2 dev: true + /cp-file/9.1.0: + resolution: {integrity: sha512-3scnzFj/94eb7y4wyXRWwvzLFaQp87yyfTnChIjlfYrVqp5lVO3E2hIJMeQIltUT0K2ZAB3An1qXcBmwGyvuwA==} + engines: {node: '>=10'} + dependencies: + graceful-fs: 4.2.10 + make-dir: 3.1.0 + nested-error-stacks: 2.1.1 + p-event: 4.2.0 + dev: true + + /cpy-cli/4.2.0: + resolution: {integrity: sha512-b04b+cbdr29CdpREPKw/itrfjO43Ty0Aj7wRM6M6LoE4GJxZJCk9Xp+Eu1IqztkKh3LxIBt1tDplENsa6KYprg==} + engines: {node: '>=12.20'} + hasBin: true + dependencies: + cpy: 9.0.1 + meow: 10.1.5 + dev: true + + /cpy/9.0.1: + resolution: {integrity: sha512-D9U0DR5FjTCN3oMTcFGktanHnAG5l020yvOCR1zKILmAyPP7I/9pl6NFgRbDcmSENtbK1sQLBz1p9HIOlroiNg==} + engines: {node: ^12.20.0 || ^14.17.0 || >=16.0.0} + dependencies: + arrify: 3.0.0 + cp-file: 9.1.0 + globby: 13.1.2 + junk: 4.0.0 + micromatch: 4.0.5 + nested-error-stacks: 2.1.1 + p-filter: 3.0.0 + p-map: 5.5.0 + dev: true + /create-require/1.1.1: resolution: {integrity: sha512-dcKFX3jn0MpIaXjisoRvexIJVEKzaq7z2rZKxf+MSr9TkdmHmsU4m2lcLojrj/FHl8mk5VxMmYA+ftRkP/3oKQ==} dev: true @@ -4561,14 +5342,14 @@ packages: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} dev: true - /cypress-image-snapshot/4.0.1_bg25yee4qeg7mpleuvd346a3tq: + /cypress-image-snapshot/4.0.1_cypress@12.5.1+jest@29.3.1: resolution: {integrity: sha512-PBpnhX/XItlx3/DAk5ozsXQHUi72exybBNH5Mpqj1DVmjq+S5Jd9WE5CRa4q5q0zuMZb2V2VpXHth6MjFpgj9Q==} engines: {node: '>=8'} peerDependencies: cypress: ^4.5.0 dependencies: chalk: 2.4.2 - cypress: 10.11.0 + cypress: 12.5.1 fs-extra: 7.0.1 glob: 7.2.3 jest-image-snapshot: 4.2.0_jest@29.3.1 @@ -4578,9 +5359,9 @@ packages: - jest dev: true - /cypress/10.11.0: - resolution: {integrity: sha512-lsaE7dprw5DoXM00skni6W5ElVVLGAdRUUdZjX2dYsGjbY/QnpzWZ95Zom1mkGg0hAaO/QVTZoFVS7Jgr/GUPA==} - engines: {node: '>=12.0.0'} + /cypress/12.5.1: + resolution: {integrity: sha512-ZmCmJ3lsyeOpBfh410m5+AO2CO1AxAzFBt7k6/uVbNcrNZje1vdiwYTpj2ksPKg9mjr9lR6V8tmlDNMvr4H/YQ==} + engines: {node: ^14.0.0 || ^16.0.0 || >=18.0.0} hasBin: true requiresBuild: true dependencies: @@ -4836,8 +5617,8 @@ packages: d3-selection: 3.0.0 d3-transition: 3.0.1_d3-selection@3.0.0 - /d3/7.6.1: - resolution: {integrity: sha512-txMTdIHFbcpLx+8a0IFhZsbp+PfBBPt8yfbmukZTQFroKuFqIwqswF0qE5JXWefylaAVpSXFoKm3yP+jpNLFLw==} + /d3/7.8.2: + resolution: {integrity: sha512-WXty7qOGSHb7HR7CfOzwN1Gw04MUOzN8qh9ZUsvwycIMb4DYMpY9xczZ6jUorGtO6bR9BPMPaueIKwiDxu9uiQ==} engines: {node: '>=12'} dependencies: d3-array: 3.2.0 @@ -4871,10 +5652,10 @@ packages: d3-transition: 3.0.1_d3-selection@3.0.0 d3-zoom: 3.0.0 - /dagre-d3-es/7.0.4: - resolution: {integrity: sha512-fQL8ldFR9UYpecz48d1smrXNJ9zGUK38Vl5OzX6Fhn9LR+oQh0GzHRPQylP5kWawmMTKm1QtqcHMVySMJ5CYaQ==} + /dagre-d3-es/7.0.8: + resolution: {integrity: sha512-eykdoYQ4FwCJinEYS0gPL2f2w+BPbSLvnQSJ3Ye1vAoPjdkq6xIMKBv+UkICd3qZE26wBKIn3p+6n0QC7R1LyA==} dependencies: - d3: 7.6.1 + d3: 7.8.2 lodash-es: 4.17.21 dev: false @@ -4986,10 +5767,19 @@ packages: engines: {node: '>=0.10.0'} dev: true + /decamelize/5.0.1: + resolution: {integrity: sha512-VfxadyCECXgQlkoEAjeghAr5gY3Hf+IKjKb+X8tGVDtveCjN+USwprd2q3QXBR9T1+x2DG0XZF5/w+7HAtSaXA==} + engines: {node: '>=10'} + dev: true + /decimal.js/10.4.1: resolution: {integrity: sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw==} dev: true + /decimal.js/10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + dev: true + /decode-named-character-reference/1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} dependencies: @@ -5007,9 +5797,9 @@ packages: resolution: {integrity: sha512-Q6fKUPqnAHAyhiUgFU7BUzLiv0kd8saH9al7tnu5Q/okj6dnupxyTgFIBjVzJATdfIAm9NAsvXNzjaKa+bxVyA==} dev: true - /deep-eql/3.0.1: - resolution: {integrity: sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==} - engines: {node: '>=0.12'} + /deep-eql/4.1.3: + resolution: {integrity: sha512-WaEtAOpRA1MQ0eohqZjpGD8zdI0Ovsm8mmFhaDN8dvDZzyoUMcYDnf5Y6iu7HTXxf8JDS23qWa4a+hKCDyOPzw==} + engines: {node: '>=6'} dependencies: type-detect: 4.0.8 dev: true @@ -5155,12 +5945,8 @@ packages: domelementtype: 2.3.0 dev: true - /dompurify/2.4.0: - resolution: {integrity: sha512-Be9tbQMZds4a3C6xTmz68NlMfeONA//4dOavl/1rNw50E+/QO0KVpbcU0PcaW0nsQxurXls9ZocqFxk8R2mWEA==} - dev: true - - /dompurify/2.4.1: - resolution: {integrity: sha512-ewwFzHzrrneRjxzmK6oVz/rZn9VWspGFRDb4/rRtIsM1n36t9AKma/ye8syCpcw+XJ25kOK/hOG7t1j2I2yBqA==} + /dompurify/2.4.3: + resolution: {integrity: sha512-q6QaLcakcRjebxjg8/+NP+h0rPfatOgOzc46Fst9VAA3jF2ApfKBNKMzdP4DYTqtUMXSCd5pRS/8Po/OmoCHZQ==} dev: false /domutils/3.0.1: @@ -5205,6 +5991,10 @@ packages: resolution: {integrity: sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA==} dev: true + /elkjs/0.8.2: + resolution: {integrity: sha512-L6uRgvZTH+4OF5NE/MBbzQx/WYpru1xCBE9respNj6qznEewGUIfhzmm7horWWxbNO2M0WckQypGctR8lH79xQ==} + dev: false + /emittery/0.13.1: resolution: {integrity: sha512-DeWwawk6r5yR9jFgnDKYt4sLS0LmHJJi3ZOnb5/JdbYwj3nW+FxQnHIjhBKz8YLC7oRNPVM9NQ47I3CVx34eqQ==} engines: {node: '>=12'} @@ -5270,214 +6060,64 @@ packages: resolution: {integrity: sha512-1HQ2M2sPtxwnvOvT1ZClHyQDiggdNjURWpY2we6aMKCQiUVxTmVs2UYPLIrD84sS+kMdUwfBSylbJPwNnBrnHQ==} dev: true - /esbuild-android-64/0.15.13: - resolution: {integrity: sha512-yRorukXBlokwTip+Sy4MYskLhJsO0Kn0/Fj43s1krVblfwP+hMD37a4Wmg139GEsMLl+vh8WXp2mq/cTA9J97g==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-android-arm64/0.15.13: - resolution: {integrity: sha512-TKzyymLD6PiVeyYa4c5wdPw87BeAiTXNtK6amWUcXZxkV51gOk5u5qzmDaYSwiWeecSNHamFsaFjLoi32QR5/w==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-64/0.15.13: - resolution: {integrity: sha512-WAx7c2DaOS6CrRcoYCgXgkXDliLnFv3pQLV6GeW1YcGEZq2Gnl8s9Pg7ahValZkpOa0iE/ojRVQ87sbUhF1Cbg==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-darwin-arm64/0.15.13: - resolution: {integrity: sha512-U6jFsPfSSxC3V1CLiQqwvDuj3GGrtQNB3P3nNC3+q99EKf94UGpsG9l4CQ83zBs1NHrk1rtCSYT0+KfK5LsD8A==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-64/0.15.13: - resolution: {integrity: sha512-whItJgDiOXaDG/idy75qqevIpZjnReZkMGCgQaBWZuKHoElDJC1rh7MpoUgupMcdfOd+PgdEwNQW9DAE6i8wyA==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-freebsd-arm64/0.15.13: - resolution: {integrity: sha512-6pCSWt8mLUbPtygv7cufV0sZLeylaMwS5Fznj6Rsx9G2AJJsAjQ9ifA+0rQEIg7DwJmi9it+WjzNTEAzzdoM3Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-32/0.15.13: - resolution: {integrity: sha512-VbZdWOEdrJiYApm2kkxoTOgsoCO1krBZ3quHdYk3g3ivWaMwNIVPIfEE0f0XQQ0u5pJtBsnk2/7OPiCFIPOe/w==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-64/0.15.13: - resolution: {integrity: sha512-rXmnArVNio6yANSqDQlIO4WiP+Cv7+9EuAHNnag7rByAqFVuRusLbGi2697A5dFPNXoO//IiogVwi3AdcfPC6A==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm/0.15.13: - resolution: {integrity: sha512-Ac6LpfmJO8WhCMQmO253xX2IU2B3wPDbl4IvR0hnqcPrdfCaUa2j/lLMGTjmQ4W5JsJIdHEdW12dG8lFS0MbxQ==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-arm64/0.15.13: - resolution: {integrity: sha512-alEMGU4Z+d17U7KQQw2IV8tQycO6T+rOrgW8OS22Ua25x6kHxoG6Ngry6Aq6uranC+pNWNMB6aHFPh7aTQdORQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-mips64le/0.15.13: - resolution: {integrity: sha512-47PgmyYEu+yN5rD/MbwS6DxP2FSGPo4Uxg5LwIdxTiyGC2XKwHhHyW7YYEDlSuXLQXEdTO7mYe8zQ74czP7W8A==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-ppc64le/0.15.13: - resolution: {integrity: sha512-z6n28h2+PC1Ayle9DjKoBRcx/4cxHoOa2e689e2aDJSaKug3jXcQw7mM+GLg+9ydYoNzj8QxNL8ihOv/OnezhA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-riscv64/0.15.13: - resolution: {integrity: sha512-+Lu4zuuXuQhgLUGyZloWCqTslcCAjMZH1k3Xc9MSEJEpEFdpsSU0sRDXAnk18FKOfEjhu4YMGaykx9xjtpA6ow==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-linux-s390x/0.15.13: - resolution: {integrity: sha512-BMeXRljruf7J0TMxD5CIXS65y7puiZkAh+s4XFV9qy16SxOuMhxhVIXYLnbdfLrsYGFzx7U9mcdpFWkkvy/Uag==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - requiresBuild: true - dev: true - optional: true - - /esbuild-netbsd-64/0.15.13: - resolution: {integrity: sha512-EHj9QZOTel581JPj7UO3xYbltFTYnHy+SIqJVq6yd3KkCrsHRbapiPb0Lx3EOOtybBEE9EyqbmfW1NlSDsSzvQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-openbsd-64/0.15.13: - resolution: {integrity: sha512-nkuDlIjF/sfUhfx8SKq0+U+Fgx5K9JcPq1mUodnxI0x4kBdCv46rOGWbuJ6eof2n3wdoCLccOoJAbg9ba/bT2w==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - requiresBuild: true - dev: true - optional: true - - /esbuild-sunos-64/0.15.13: - resolution: {integrity: sha512-jVeu2GfxZQ++6lRdY43CS0Tm/r4WuQQ0Pdsrxbw+aOrHQPHV0+LNOLnvbN28M7BSUGnJnHkHm2HozGgNGyeIRw==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-32/0.15.13: - resolution: {integrity: sha512-XoF2iBf0wnqo16SDq+aDGi/+QbaLFpkiRarPVssMh9KYbFNCqPLlGAWwDvxEVz+ywX6Si37J2AKm+AXq1kC0JA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-64/0.15.13: - resolution: {integrity: sha512-Et6htEfGycjDrtqb2ng6nT+baesZPYQIW+HUEHK4D1ncggNrDNk3yoboYQ5KtiVrw/JaDMNttz8rrPubV/fvPQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild-windows-arm64/0.15.13: - resolution: {integrity: sha512-3bv7tqntThQC9SWLRouMDmZnlOukBhOCTlkzNqzGCmrkCJI7io5LLjwJBOVY6kOUlIvdxbooNZwjtBvj+7uuVg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - requiresBuild: true - dev: true - optional: true - - /esbuild/0.15.13: - resolution: {integrity: sha512-Cu3SC84oyzzhrK/YyN4iEVy2jZu5t2fz66HEOShHURcjSkOSAVL8C/gfUT+lDJxkVHpg8GZ10DD0rMHRPqMFaQ==} + /esbuild/0.16.17: + resolution: {integrity: sha512-G8LEkV0XzDMNwXKgM0Jwu3nY3lSTwSGY6XbxM9cr9+s0T/qSV1q1JVPBGzm3dcjhCic9+emZDmMffkwgPeOeLg==} engines: {node: '>=12'} hasBin: true requiresBuild: true optionalDependencies: - '@esbuild/android-arm': 0.15.13 - '@esbuild/linux-loong64': 0.15.13 - esbuild-android-64: 0.15.13 - esbuild-android-arm64: 0.15.13 - esbuild-darwin-64: 0.15.13 - esbuild-darwin-arm64: 0.15.13 - esbuild-freebsd-64: 0.15.13 - esbuild-freebsd-arm64: 0.15.13 - esbuild-linux-32: 0.15.13 - esbuild-linux-64: 0.15.13 - esbuild-linux-arm: 0.15.13 - esbuild-linux-arm64: 0.15.13 - esbuild-linux-mips64le: 0.15.13 - esbuild-linux-ppc64le: 0.15.13 - esbuild-linux-riscv64: 0.15.13 - esbuild-linux-s390x: 0.15.13 - esbuild-netbsd-64: 0.15.13 - esbuild-openbsd-64: 0.15.13 - esbuild-sunos-64: 0.15.13 - esbuild-windows-32: 0.15.13 - esbuild-windows-64: 0.15.13 - esbuild-windows-arm64: 0.15.13 + '@esbuild/android-arm': 0.16.17 + '@esbuild/android-arm64': 0.16.17 + '@esbuild/android-x64': 0.16.17 + '@esbuild/darwin-arm64': 0.16.17 + '@esbuild/darwin-x64': 0.16.17 + '@esbuild/freebsd-arm64': 0.16.17 + '@esbuild/freebsd-x64': 0.16.17 + '@esbuild/linux-arm': 0.16.17 + '@esbuild/linux-arm64': 0.16.17 + '@esbuild/linux-ia32': 0.16.17 + '@esbuild/linux-loong64': 0.16.17 + '@esbuild/linux-mips64el': 0.16.17 + '@esbuild/linux-ppc64': 0.16.17 + '@esbuild/linux-riscv64': 0.16.17 + '@esbuild/linux-s390x': 0.16.17 + '@esbuild/linux-x64': 0.16.17 + '@esbuild/netbsd-x64': 0.16.17 + '@esbuild/openbsd-x64': 0.16.17 + '@esbuild/sunos-x64': 0.16.17 + '@esbuild/win32-arm64': 0.16.17 + '@esbuild/win32-ia32': 0.16.17 + '@esbuild/win32-x64': 0.16.17 + dev: true + + /esbuild/0.17.0: + resolution: {integrity: sha512-4yGk3rD95iS/wGzrx0Ji5czZcx1j2wvfF1iAJaX2FIYLB6sU6wYkDeplpZHzfwQw2yXGXsAoxmO6LnMQkl04Kg==} + engines: {node: '>=12'} + hasBin: true + requiresBuild: true + optionalDependencies: + '@esbuild/android-arm': 0.17.0 + '@esbuild/android-arm64': 0.17.0 + '@esbuild/android-x64': 0.17.0 + '@esbuild/darwin-arm64': 0.17.0 + '@esbuild/darwin-x64': 0.17.0 + '@esbuild/freebsd-arm64': 0.17.0 + '@esbuild/freebsd-x64': 0.17.0 + '@esbuild/linux-arm': 0.17.0 + '@esbuild/linux-arm64': 0.17.0 + '@esbuild/linux-ia32': 0.17.0 + '@esbuild/linux-loong64': 0.17.0 + '@esbuild/linux-mips64el': 0.17.0 + '@esbuild/linux-ppc64': 0.17.0 + '@esbuild/linux-riscv64': 0.17.0 + '@esbuild/linux-s390x': 0.17.0 + '@esbuild/linux-x64': 0.17.0 + '@esbuild/netbsd-x64': 0.17.0 + '@esbuild/openbsd-x64': 0.17.0 + '@esbuild/sunos-x64': 0.17.0 + '@esbuild/win32-arm64': 0.17.0 + '@esbuild/win32-ia32': 0.17.0 + '@esbuild/win32-x64': 0.17.0 dev: true /escalade/3.1.1: @@ -5504,6 +6144,11 @@ packages: engines: {node: '>=10'} dev: true + /escape-string-regexp/5.0.0: + resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} + engines: {node: '>=12'} + dev: true + /escodegen/1.14.3: resolution: {integrity: sha512-qFcX0XJkdg+PB3xjZZG/wKSuT1PnQWx57+TVSjIMmILd2yC/6ByYElPwJnslDsuWuSAp4AwJGumarAAmJch5Kw==} engines: {node: '>=4.0'} @@ -5542,21 +6187,21 @@ packages: source-map: 0.6.1 dev: true - /eslint-config-prettier/8.5.0_eslint@8.27.0: - resolution: {integrity: sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==} + /eslint-config-prettier/8.6.0_eslint@8.32.0: + resolution: {integrity: sha512-bAF0eLpLVqP5oEVUFKpMA+NnRFICwn9X8B5jrR9FcqnYBuPbqWEjTEspPWMj5ye6czoSLDweCzSo3Ko7gGrZaA==} hasBin: true peerDependencies: eslint: '>=7.0.0' dependencies: - eslint: 8.27.0 + eslint: 8.32.0 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==} peerDependencies: eslint: '>= 3.2.1' dependencies: - eslint: 8.27.0 + eslint: 8.32.0 globals: 11.12.0 dev: true @@ -5566,7 +6211,7 @@ packages: htmlparser2: 8.0.1 dev: true - /eslint-plugin-jest/27.1.5_kdswgjmqcx7mthqz7ow2zlfevy: + /eslint-plugin-jest/27.1.5_i5clxtuiaceouxhg5syqkw5wwi: resolution: {integrity: sha512-CK2dekZ5VBdzsOSOH5Fc1rwC+cWXjkcyrmf1RV714nDUDKu+o73TTJiDxpbILG8PtPPpAAl3ywzh5QA7Ft0mjA==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} peerDependencies: @@ -5579,16 +6224,16 @@ packages: jest: optional: true dependencies: - '@typescript-eslint/eslint-plugin': 5.42.1_2udltptbznfmezdozpdoa2aemq - '@typescript-eslint/utils': 5.42.1_rmayb2veg2btbq6mbmnyivgasy - eslint: 8.27.0 + '@typescript-eslint/eslint-plugin': 5.48.2_azmbqzqvrlvblbdtiwxwvyvjjy + '@typescript-eslint/utils': 5.42.1_et5x32uxl7z5ldub3ye5rhlyqm + eslint: 8.32.0 jest: 29.3.1_odkjkoia5xunhxkdrka32ib6vi transitivePeerDependencies: - supports-color - typescript 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==} engines: {node: ^14 || ^16 || ^17 || ^18 || ^19} peerDependencies: @@ -5598,7 +6243,7 @@ packages: comment-parser: 1.3.1 debug: 4.3.4 escape-string-regexp: 4.0.0 - eslint: 8.27.0 + eslint: 8.32.0 esquery: 1.4.0 semver: 7.3.8 spdx-expression-parse: 3.0.1 @@ -5614,23 +6259,23 @@ packages: vscode-json-languageservice: 4.2.1 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==} engines: {node: '>=10'} peerDependencies: eslint: '>=2' dependencies: - eslint: 8.27.0 + eslint: 8.32.0 lodash: 4.17.21 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==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 dependencies: - eslint: 8.27.0 + eslint: 8.32.0 mdast-util-from-markdown: 0.8.5 transitivePeerDependencies: - supports-color @@ -5648,7 +6293,7 @@ packages: '@microsoft/tsdoc-config': 0.16.2 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==} engines: {node: '>=14.18'} peerDependencies: @@ -5657,8 +6302,8 @@ packages: '@babel/helper-validator-identifier': 7.19.1 ci-info: 3.6.2 clean-regexp: 1.0.0 - eslint: 8.27.0 - eslint-utils: 3.0.0_eslint@8.27.0 + eslint: 8.32.0 + eslint-utils: 3.0.0_eslint@8.32.0 esquery: 1.4.0 indent-string: 4.0.0 is-builtin-module: 3.2.0 @@ -5689,13 +6334,13 @@ packages: estraverse: 5.3.0 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==} engines: {node: ^10.0.0 || ^12.0.0 || >= 14.0.0} peerDependencies: eslint: '>=5' dependencies: - eslint: 8.27.0 + eslint: 8.32.0 eslint-visitor-keys: 2.1.0 dev: true @@ -5709,13 +6354,13 @@ packages: engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dev: true - /eslint/8.27.0: - resolution: {integrity: sha512-0y1bfG2ho7mty+SiILVf9PfuRA49ek4Nc60Wmmu62QlobNR+CeXa4xXIJgcuwSQgZiWaPH+5BDsctpIW0PR/wQ==} + /eslint/8.32.0: + resolution: {integrity: sha512-nETVXpnthqKPFyuY2FNjz/bEd6nbosRgKbkgS/y1C7LJop96gYHWpiguLecMHQ2XCPxn77DS0P+68WzG6vkZSQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} hasBin: true dependencies: - '@eslint/eslintrc': 1.3.3 - '@humanwhocodes/config-array': 0.11.7 + '@eslint/eslintrc': 1.4.1 + '@humanwhocodes/config-array': 0.11.8 '@humanwhocodes/module-importer': 1.0.1 '@nodelib/fs.walk': 1.2.8 ajv: 6.12.6 @@ -5725,7 +6370,7 @@ packages: doctrine: 3.0.0 escape-string-regexp: 4.0.0 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 espree: 9.4.0 esquery: 1.4.0 @@ -5734,7 +6379,7 @@ packages: file-entry-cache: 6.0.1 find-up: 5.0.0 glob-parent: 6.0.2 - globals: 13.17.0 + globals: 13.19.0 grapheme-splitter: 1.0.4 ignore: 5.2.0 import-fresh: 3.3.0 @@ -5761,8 +6406,8 @@ packages: resolution: {integrity: sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} dependencies: - acorn: 8.8.0 - acorn-jsx: 5.3.2_acorn@8.8.0 + acorn: 8.8.1 + acorn-jsx: 5.3.2_acorn@8.8.1 eslint-visitor-keys: 3.3.0 dev: true @@ -6037,6 +6682,12 @@ packages: reusify: 1.0.4 dev: true + /fault/2.0.1: + resolution: {integrity: sha512-WtySTkS4OKev5JtpHXnib4Gxiurzh5NCGvWrFaZ34m6JehfTUhKZvn9njTfw48t6JumVQOmrKqpmGcdwxnhqBQ==} + dependencies: + format: 0.2.2 + dev: true + /faye-websocket/0.11.4: resolution: {integrity: sha512-CzbClwlXAuiRQAlUyfqPgvPoNKTckTPGfwZV4ZdAhVcP2lh9KUxJg2b5GkE7XbjKQ3YJnQ9z6D9ntLAlB+tP8g==} engines: {node: '>=0.8.0'} @@ -6148,6 +6799,18 @@ packages: debug: 4.3.2 dev: true + /follow-redirects/1.15.2_debug@4.3.4: + resolution: {integrity: sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==} + engines: {node: '>=4.0'} + peerDependencies: + debug: '*' + peerDependenciesMeta: + debug: + optional: true + dependencies: + debug: 4.3.4 + dev: true + /foreground-child/2.0.0: resolution: {integrity: sha512-dCIq9FpEcyQyXKCkyzmlPTFNgrCzPudOe+mhvJU5zAtlBnGVy2yKxtfsxK2tQBThwq225jcvBjpw1Gr40uzZCA==} engines: {node: '>=8.0.0'} @@ -6187,6 +6850,11 @@ packages: mime-types: 2.1.35 dev: true + /format/0.2.2: + resolution: {integrity: sha512-wzsgA6WOq+09wrU1tsJ09udeR/YZRaeArL9e1wPbFg3GG2yDnC2ldKpxs4xunpFF9DgqCqOIra3bc1HWrJ37Ww==} + engines: {node: '>=0.4.x'} + dev: true + /forwarded/0.2.0: resolution: {integrity: sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==} engines: {node: '>= 0.6'} @@ -6424,8 +7092,8 @@ packages: engines: {node: '>=4'} dev: true - /globals/13.17.0: - resolution: {integrity: sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==} + /globals/13.19.0: + resolution: {integrity: sha512-dkQ957uSRWHw7CFXLUtUHQI3g3aWApYhfNR2O6jn/907riyTYKVBmxYVROkBcY614FSSeSJh7Xm7SrUWCxvJMQ==} engines: {node: '>=8'} dependencies: type-fest: 0.20.2 @@ -6792,6 +7460,11 @@ packages: engines: {node: '>=8'} dev: true + /indent-string/5.0.0: + resolution: {integrity: sha512-m6FAo/spmsW2Ab2fU35JTYwtOKa2yAwXSwgjSv1TJzh4Mh7mC3lzAOVLBprb72XsTrgkEIsl7YrFNAiDiRhIGg==} + engines: {node: '>=12'} + dev: true + /inflight/1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} dependencies: @@ -7191,7 +7864,7 @@ packages: pretty-format: 29.3.1 slash: 3.0.0 strip-json-comments: 3.1.1 - ts-node: 10.9.1_cbe7ovvae6zqfnmtgctpgpys54 + ts-node: 10.9.1_w6ufic3jqylcjznzspnj4wjqfe transitivePeerDependencies: - supports-color dev: true @@ -7568,6 +8241,16 @@ packages: '@sideway/pinpoint': 2.0.0 dev: true + /joi/17.7.1: + resolution: {integrity: sha512-teoLhIvWE298R6AeJywcjR4sX2hHjB3/xJX4qPjg+gTg+c0mzUDsziYlqPmLomq9gVsfaMcgPaGc7VxtD/9StA==} + dependencies: + '@hapi/hoek': 9.3.0 + '@hapi/topo': 5.1.0 + '@sideway/address': 4.1.4 + '@sideway/formula': 3.0.1 + '@sideway/pinpoint': 2.0.0 + dev: true + /jpeg-js/0.4.4: resolution: {integrity: sha512-WZzeDOEtTOBK4Mdsar0IqEU5sMr3vSV2RqkAIzUEV2BHnUfKGyswWFPFwK5EeDo93K3FohSHbLAjj0s1Wzd+dg==} dev: true @@ -7608,8 +8291,8 @@ packages: engines: {node: '>=12.0.0'} dev: true - /jsdom/20.0.2: - resolution: {integrity: sha512-AHWa+QO/cgRg4N+DsmHg1Y7xnz+8KU3EflM0LVDTdmrYOc1WWTSkOjtpUveQH+1Bqd5rtcVnb/DuxV/UjDO4rA==} + /jsdom/21.1.0: + resolution: {integrity: sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==} engines: {node: '>=14'} peerDependencies: canvas: ^2.5.0 @@ -7618,12 +8301,12 @@ packages: optional: true dependencies: abab: 2.0.6 - acorn: 8.8.0 + acorn: 8.8.1 acorn-globals: 7.0.1 cssom: 0.5.0 cssstyle: 2.3.0 data-urls: 3.0.2 - decimal.js: 10.4.1 + decimal.js: 10.4.3 domexception: 4.0.0 escodegen: 2.0.0 form-data: 4.0.0 @@ -7636,12 +8319,12 @@ packages: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 4.1.2 - w3c-xmlserializer: 3.0.0 + w3c-xmlserializer: 4.0.0 webidl-conversions: 7.0.0 whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 - ws: 8.9.0 + ws: 8.12.0 xml-name-validator: 4.0.0 transitivePeerDependencies: - bufferutil @@ -7752,6 +8435,11 @@ packages: verror: 1.10.0 dev: true + /junk/4.0.0: + resolution: {integrity: sha512-ojtSU++zLJ3jQG9bAYjg94w+/DOJtRyD7nPaerMFrBhmdVmiV5/exYH5t4uHga4G/95nT6hr1OJoKIFbYbrW5w==} + engines: {node: '>=12.20'} + dev: true + /keyv/4.5.0: resolution: {integrity: sha512-2YvuMsA+jnFGtBareKqgANOEKe1mk3HKiXu2fRmAfyxG0MJAywNhi5ttWA3PMjl4NmpyjZNbFifR2vNjW1znfA==} dependencies: @@ -7984,8 +8672,8 @@ packages: resolution: {integrity: sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==} dev: true - /loupe/2.3.4: - resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==} + /loupe/2.3.6: + resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} dependencies: get-func-name: 2.0.0 dev: true @@ -8060,12 +8748,24 @@ packages: uc.micro: 1.0.6 dev: true + /markdown-table/3.0.3: + resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} + dev: true + /marked/4.1.1: resolution: {integrity: sha512-0cNMnTcUJPxbA6uWmCmjWz4NJRe/0Xfk2NhXCUHjew9qJzFN20krFnsUe7QynwqOwa5m1fZ4UDg0ycKFVC0ccw==} engines: {node: '>= 12'} hasBin: true dev: true + /mdast-util-find-and-replace/2.2.1: + resolution: {integrity: sha512-SobxkQXFAdd4b5WmEakmkVoh18icjQRxGy5OWTCzgsLRm1Fu/KCtwD1HIQSsmq5ZRjVH0Ehwg6/Fn3xIUk+nKw==} + dependencies: + escape-string-regexp: 5.0.0 + unist-util-is: 5.1.1 + unist-util-visit-parents: 5.1.1 + dev: true + /mdast-util-from-markdown/0.8.5: resolution: {integrity: sha512-2hkTXtYYnr+NubD/g6KGBS/0mFmBcifAsI0yIWRiRo0PjVs6SSOSOdtzbp6kSGnShDN6G5aWZpKQ2lWRy27mWQ==} dependencies: @@ -8097,6 +8797,68 @@ packages: - supports-color 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: + resolution: {integrity: sha512-FzopkOd4xTTBeGXhXSBU0OCDDh5lUj2rd+HQqG92Ld+jL4lpUfgX2AT2OHAVP9aEeDKp7G92fuooSZcYJA3cRg==} + dependencies: + '@types/mdast': 3.0.10 + ccount: 2.0.1 + mdast-util-find-and-replace: 2.2.1 + micromark-util-character: 1.1.0 + dev: true + + /mdast-util-gfm-footnote/1.0.1: + resolution: {integrity: sha512-p+PrYlkw9DeCRkTVw1duWqPRHX6Ywh2BNKJQcZbCwAuP/59B0Lk9kakuAd7KbQprVO4GzdW8eS5++A9PUSqIyw==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-to-markdown: 1.3.0 + micromark-util-normalize-identifier: 1.0.0 + dev: true + + /mdast-util-gfm-strikethrough/1.0.2: + resolution: {integrity: sha512-T/4DVHXcujH6jx1yqpcAYYwd+z5lAYMw4Ls6yhTfbMMtCt0PHY4gEfhW9+lKsLBtyhUGKRIzcUA2FATVqnvPDA==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-to-markdown: 1.3.0 + dev: true + + /mdast-util-gfm-table/1.0.6: + resolution: {integrity: sha512-uHR+fqFq3IvB3Rd4+kzXW8dmpxUhvgCQZep6KdjsLK4O6meK5dYZEayLtIxNus1XO3gfjfcIFe8a7L0HZRGgag==} + dependencies: + '@types/mdast': 3.0.10 + markdown-table: 3.0.3 + mdast-util-from-markdown: 1.2.0 + mdast-util-to-markdown: 1.3.0 + transitivePeerDependencies: + - supports-color + dev: true + + /mdast-util-gfm-task-list-item/1.0.1: + resolution: {integrity: sha512-KZ4KLmPdABXOsfnM6JHUIjxEvcx2ulk656Z/4Balw071/5qgnhz+H1uGtf2zIGnrnvDC8xR4Fj9uKbjAFGNIeA==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-to-markdown: 1.3.0 + dev: true + + /mdast-util-gfm/2.0.1: + resolution: {integrity: sha512-42yHBbfWIFisaAfV1eixlabbsa6q7vHeSPY+cg+BBjX51M8xhgMacqH9g6TftB/9+YkcI0ooV4ncfrJslzm/RQ==} + dependencies: + mdast-util-from-markdown: 1.2.0 + mdast-util-gfm-autolink-literal: 1.0.2 + mdast-util-gfm-footnote: 1.0.1 + mdast-util-gfm-strikethrough: 1.0.2 + mdast-util-gfm-table: 1.0.6 + mdast-util-gfm-task-list-item: 1.0.1 + mdast-util-to-markdown: 1.3.0 + transitivePeerDependencies: + - supports-color + dev: true + /mdast-util-to-markdown/1.3.0: resolution: {integrity: sha512-6tUSs4r+KK4JGTTiQ7FfHmVOaDrLQJPmpjD6wPMlHGUVXoG9Vjc3jIeP+uyBWRf8clwB2blM+W7+KrlMYQnftA==} dependencies: @@ -8137,6 +8899,24 @@ packages: fs-monkey: 1.0.3 dev: true + /meow/10.1.5: + resolution: {integrity: sha512-/d+PQ4GKmGvM9Bee/DPa8z3mXs/pkvJE2KEThngVNOqtmljC6K7NMPxtc2JeZYTmpWb9k/TmxjeL18ez3h7vCw==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + '@types/minimist': 1.2.2 + camelcase-keys: 7.0.2 + decamelize: 5.0.1 + decamelize-keys: 1.1.0 + hard-rejection: 2.1.0 + minimist-options: 4.1.0 + normalize-package-data: 3.0.3 + read-pkg-up: 8.0.0 + redent: 4.0.0 + trim-newlines: 4.0.2 + type-fest: 1.4.0 + yargs-parser: 20.2.9 + dev: true + /meow/8.1.2: resolution: {integrity: sha512-r85E3NdZ+mpYk1C6RjPFEMSE+s1iZMuHtsHAqY0DT3jZczl0diWUZ8g6oU7h0M9cD2EL+PzaYghhCLzR0ZNn5Q==} engines: {node: '>=10'} @@ -8167,24 +8947,6 @@ packages: engines: {node: '>= 8'} dev: true - /mermaid/9.2.2: - resolution: {integrity: sha512-6s7eKMqFJGS+0MYjmx8f6ZigqKBJVoSx5ql2gw6a4Aa+WJ49QiEJg7gPwywaBg3DZMs79UP7trESp4+jmaQccw==} - dependencies: - '@braintree/sanitize-url': 6.0.0 - d3: 7.6.1 - dagre: 0.8.5 - dagre-d3: 0.6.4 - dompurify: 2.4.0 - fast-clone: 1.5.13 - graphlib: 2.1.8 - khroma: 2.0.0 - lodash: 4.17.21 - moment-mini: 2.29.4 - non-layered-tidy-tree-layout: 2.0.2 - stylis: 4.1.2 - uuid: 9.0.0 - dev: true - /methods/1.1.2: resolution: {integrity: sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==} engines: {node: '>= 0.6'} @@ -8211,6 +8973,87 @@ packages: uvu: 0.5.6 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: + resolution: {integrity: sha512-i3dmvU0htawfWED8aHMMAzAVp/F0Z+0bPh3YrbTPPL1v4YAlCZpy5rBO5p0LPYiZo0zFVkoYh7vDU7yQSiCMjg==} + dependencies: + micromark-util-character: 1.1.0 + micromark-util-sanitize-uri: 1.0.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.6 + dev: true + + /micromark-extension-gfm-footnote/1.0.4: + resolution: {integrity: sha512-E/fmPmDqLiMUP8mLJ8NbJWJ4bTw6tS+FEQS8CcuDtZpILuOb2kjLqPEeAePF1djXROHXChM/wPJw0iS4kHCcIg==} + dependencies: + micromark-core-commonmark: 1.0.6 + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-normalize-identifier: 1.0.0 + micromark-util-sanitize-uri: 1.0.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.6 + dev: true + + /micromark-extension-gfm-strikethrough/1.0.4: + resolution: {integrity: sha512-/vjHU/lalmjZCT5xt7CcHVJGq8sYRm80z24qAKXzaHzem/xsDYb2yLL+NNVbYvmpLx3O7SYPuGL5pzusL9CLIQ==} + dependencies: + micromark-util-chunked: 1.0.0 + micromark-util-classify-character: 1.0.0 + micromark-util-resolve-all: 1.0.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.6 + dev: true + + /micromark-extension-gfm-table/1.0.5: + resolution: {integrity: sha512-xAZ8J1X9W9K3JTJTUL7G6wSKhp2ZYHrFk5qJgY/4B33scJzE2kpfRL6oiw/veJTbt7jiM/1rngLlOKPWr1G+vg==} + dependencies: + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.6 + dev: true + + /micromark-extension-gfm-tagfilter/1.0.1: + resolution: {integrity: sha512-Ty6psLAcAjboRa/UKUbbUcwjVAv5plxmpUTy2XC/3nJFL37eHej8jrHrRzkqcpipJliuBH30DTs7+3wqNcQUVA==} + dependencies: + micromark-util-types: 1.0.2 + dev: true + + /micromark-extension-gfm-task-list-item/1.0.3: + resolution: {integrity: sha512-PpysK2S1Q/5VXi72IIapbi/jliaiOFzv7THH4amwXeYXLq3l1uo8/2Be0Ac1rEwK20MQEsGH2ltAZLNY2KI/0Q==} + dependencies: + micromark-factory-space: 1.0.0 + micromark-util-character: 1.1.0 + micromark-util-symbol: 1.0.1 + micromark-util-types: 1.0.2 + uvu: 0.5.6 + dev: true + + /micromark-extension-gfm/2.0.1: + resolution: {integrity: sha512-p2sGjajLa0iYiGQdT0oelahRYtMWvLjy8J9LOCxzIQsllMCGLbsLW+Nc+N4vi02jcRJvedVJ68cjelKIO6bpDA==} + dependencies: + micromark-extension-gfm-autolink-literal: 1.0.3 + micromark-extension-gfm-footnote: 1.0.4 + micromark-extension-gfm-strikethrough: 1.0.4 + micromark-extension-gfm-table: 1.0.5 + micromark-extension-gfm-tagfilter: 1.0.1 + micromark-extension-gfm-task-list-item: 1.0.3 + micromark-util-combine-extensions: 1.0.0 + micromark-util-types: 1.0.2 + dev: true + /micromark-factory-destination/1.0.0: resolution: {integrity: sha512-eUBA7Rs1/xtTVun9TmV3gjfPz2wEwgK5R5xcbIM5ZYAtvGF6JkyaDsj0agx8urXnO31tEO6Ug83iVH3tdedLnw==} dependencies: @@ -8456,6 +9299,10 @@ packages: resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} dev: true + /minimist/1.2.8: + resolution: {integrity: sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==} + dev: true + /mkdirp/0.5.6: resolution: {integrity: sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==} hasBin: true @@ -8463,13 +9310,18 @@ packages: minimist: 1.2.6 dev: true + /mlly/1.1.0: + resolution: {integrity: sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==} + dependencies: + acorn: 8.8.1 + pathe: 1.1.0 + pkg-types: 1.0.1 + ufo: 1.0.1 + dev: true + /moment-mini/2.29.4: resolution: {integrity: sha512-uhXpYwHFeiTbY9KSgPPRoo1nt8OxNVdMVoTBYHfSEKeRkIkwGpO+gERmhuhBtzfaeOyTkykSrm2+noJBgqt3Hg==} - /moment/2.29.4: - resolution: {integrity: sha512-5LC9SOxjSc2HF6vO2CyuTDNivEdoz2IvyJJGj6X8DJ0eFyfszE0QiEd+iXmBvUP3WHxSjFH/vIsA0EN00cgr8w==} - dev: true - /mri/1.2.0: resolution: {integrity: sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==} engines: {node: '>=4'} @@ -8523,6 +9375,10 @@ packages: resolution: {integrity: sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==} dev: true + /nested-error-stacks/2.1.1: + resolution: {integrity: sha512-9iN1ka/9zmX1ZvLV9ewJYEk9h7RyRRtqdK0woXcqohu8EWIerfPUjYJPg0ULy0UqP7cslmdGc8xKDJcojlKiaw==} + dev: true + /netmask/2.0.2: resolution: {integrity: sha512-dBpDMdxv9Irdq66304OLfEmQ9tbNRFnFTuZiLo+bD+r332bBmMJ8GBLXklIXXgxd3+v9+KUnZaUR5PJMa75Gsg==} engines: {node: '>= 0.4.0'} @@ -8626,6 +9482,11 @@ packages: resolution: {integrity: sha512-fexhUFFPTGV8ybAtSIGbV6gOkSv8UtRbDBnAyLQw4QPKkgNlsH2ByPGtMUqdWkos6YCRmAqViwgZrJc/mRDzZQ==} dev: true + /object-assign/4.1.1: + resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} + engines: {node: '>=0.10.0'} + dev: true + /object-inspect/1.12.2: resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} dev: true @@ -8708,6 +9569,20 @@ packages: engines: {node: '>=8'} dev: true + /p-event/4.2.0: + resolution: {integrity: sha512-KXatOjCRXXkSePPb1Nbi0p0m+gQAwdlbhi4wQKJPI1HsMQS9g+Sqp2o+QHziPr7eYJyOZet836KoHEVM1mwOrQ==} + engines: {node: '>=8'} + dependencies: + p-timeout: 3.2.0 + dev: true + + /p-filter/3.0.0: + resolution: {integrity: sha512-QtoWLjXAW++uTX67HZQz1dbTpqBfiidsB6VtQUC9iR85S120+s0T5sO6s+B5MLzFcZkrEd/DGMmCjR+f2Qpxwg==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + p-map: 5.5.0 + dev: true + /p-finally/1.0.0: resolution: {integrity: sha512-LICb2p9CB7FS+0eR1oqWnHhp0FljGLZCWBE9aix0Uye9W8LTQPwMTYVGWQWIw9RdQiDg4+epXQODwIYJtSJaow==} engines: {node: '>=4'} @@ -8732,6 +9607,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-limit/4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: true + /p-locate/3.0.0: resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} engines: {node: '>=6'} @@ -8760,6 +9642,13 @@ packages: aggregate-error: 3.1.0 dev: true + /p-map/5.5.0: + resolution: {integrity: sha512-VFqfGDHlx87K66yZrNdI4YGtD70IRyd+zSvgks6mzHPRNkoKy+9EKP4SFC77/vTTQYmRmti7dvqC+m5jBrBAcg==} + engines: {node: '>=12'} + dependencies: + aggregate-error: 4.0.1 + dev: true + /p-retry/4.6.2: resolution: {integrity: sha512-312Id396EbJdvRONlngUx0NydfrIQ5lsYu0znKVUzVvArzEIt08V1qhtyESbGVd1FGX7UKtiFp5uwKZdM8wIuQ==} engines: {node: '>=8'} @@ -8768,6 +9657,13 @@ packages: retry: 0.13.1 dev: true + /p-timeout/3.2.0: + resolution: {integrity: sha512-rhIwUycgwwKcP9yTOOFK/AKsAopjjCakVqLHePO3CC6Mir1Z99xT+R63jZxAT5lFZLa2inS5h+ZS2GvR99/FBg==} + engines: {node: '>=8'} + dependencies: + p-finally: 1.0.0 + dev: true + /p-try/2.2.0: resolution: {integrity: sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==} engines: {node: '>=6'} @@ -8900,6 +9796,10 @@ packages: engines: {node: '>=8'} dev: true + /pathe/1.1.0: + resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} + dev: true + /pathval/1.1.1: resolution: {integrity: sha512-Dp6zGqpTdETdR63lehJYPeIOqpiNBNtc7BpWSLrOje7UaIsE5aY92r/AunQA7rsXvet3lrJ3JnZX29UPTKXyKQ==} dev: true @@ -8964,6 +9864,14 @@ packages: find-up: 4.1.0 dev: true + /pkg-types/1.0.1: + resolution: {integrity: sha512-jHv9HB+Ho7dj6ItwppRDDl0iZRYBD0jsakHXtFgoLr+cHSF6xC+QL54sJmWxyGxOLYSHm0afhXhXcQDQqH9z8g==} + dependencies: + jsonc-parser: 3.2.0 + mlly: 1.1.0 + pathe: 1.1.0 + dev: true + /plist/3.0.6: resolution: {integrity: sha512-WiIVYyrp8TD4w8yCvyeIr+lkmrGRd5u0VbRnU+tP/aRLxP/YadJUYOMZJ/6hIa3oUyVCsycXvtNRgd5XBJIbiA==} engines: {node: '>=6'} @@ -9001,8 +9909,17 @@ packages: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} dev: true - /postcss/8.4.18: - resolution: {integrity: sha512-Wi8mWhncLJm11GATDaQKobXSNEYGUHeQLiQqDFG1qQ5UTDPTEvKw0Xt5NsTpktGTwLps3ByrWsBrG0rB8YQ9oA==} + /postcss/8.4.20: + resolution: {integrity: sha512-6Q04AXR1212bXr5fh03u8aAwbLxAQNGQ/Q1LNa0VfOI06ZAlhPHtQvE4OIdpj4kLThXilalPnmDSOD65DcHt+g==} + engines: {node: ^10 || ^12 || >=14} + dependencies: + nanoid: 3.3.4 + picocolors: 1.0.0 + source-map-js: 1.0.2 + dev: true + + /postcss/8.4.21: + resolution: {integrity: sha512-tP7u/Sn/dVxK2NnruI4H9BG+x+Wxz6oeZ1cJ8P6G/PZY0IKk4k/63TDsQf2kQq3+qoJeLm2kIBUNlZe3zgb4Zg==} engines: {node: ^10 || ^12 || >=14} dependencies: nanoid: 3.3.4 @@ -9049,6 +9966,15 @@ packages: engines: {node: '>=6'} dev: true + /pretty-format/27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + dev: true + /pretty-format/29.3.1: resolution: {integrity: sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -9182,6 +10108,10 @@ packages: unpipe: 1.0.0 dev: true + /react-is/17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + dev: true + /react-is/18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true @@ -9195,6 +10125,15 @@ packages: type-fest: 0.8.1 dev: true + /read-pkg-up/8.0.0: + resolution: {integrity: sha512-snVCqPczksT0HS2EC+SxUndvSzn6LRCwpfSvLrIfR5BKDQQZMaI6jPRC9dYvYFDRAuFEAnkwww8kBBNE/3VvzQ==} + engines: {node: '>=12'} + dependencies: + find-up: 5.0.0 + read-pkg: 6.0.0 + type-fest: 1.4.0 + dev: true + /read-pkg/5.2.0: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} @@ -9205,6 +10144,16 @@ packages: type-fest: 0.6.0 dev: true + /read-pkg/6.0.0: + resolution: {integrity: sha512-X1Fu3dPuk/8ZLsMhEj5f4wFAF0DWoK7qhGJvgaijocXxBmSToKfbFtqbxMO7bVjNA1dmE5huAzjXj/ey86iw9Q==} + engines: {node: '>=12'} + dependencies: + '@types/normalize-package-data': 2.4.1 + normalize-package-data: 3.0.3 + parse-json: 5.2.0 + type-fest: 1.4.0 + dev: true + /readable-stream/1.1.14: resolution: {integrity: sha512-+MeVjFf4L44XUkhM1eYbD8fyEsxcV81pqMSR5gblfcLCHfZvbrqy4/qYHE+/R5HoBUT11WV5O08Cr1n3YXkWVQ==} dependencies: @@ -9257,6 +10206,14 @@ packages: strip-indent: 3.0.0 dev: true + /redent/4.0.0: + resolution: {integrity: sha512-tYkDkVVtYkSVhuQ4zBgfvciymHaeuel+zFKXShfDnFP5SyVEP7qo70Rf1jTOTCx3vGNAbnEi/xFkcfQVMIBWag==} + engines: {node: '>=12'} + dependencies: + indent-string: 5.0.0 + strip-indent: 4.0.0 + dev: true + /regexp-tree/0.1.24: resolution: {integrity: sha512-s2aEVuLhvnVJW6s/iPgEGK6R+/xngd2jNQ+xy4bXNDKxZKJH6jpPHY6kVeVv1IeLCHgswRj+Kl3ELaDjG6V1iw==} hasBin: true @@ -9274,6 +10231,26 @@ packages: jsesc: 0.5.0 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: + resolution: {integrity: sha512-lEFDoi2PICJyNrACFOfDD3JlLkuSbOa5Wd8EPt06HUdptv8Gn0bxYTdbU/XXQ3swAPkEaGxxPN9cbnMHvVu1Ig==} + dependencies: + '@types/mdast': 3.0.10 + mdast-util-gfm: 2.0.1 + micromark-extension-gfm: 2.0.1 + unified: 10.1.2 + transitivePeerDependencies: + - supports-color + dev: true + /remark-parse/10.0.1: resolution: {integrity: sha512-1fUyHr2jLsVOkhbvPRBJ5zTKZZyD6yZzYaWCS6BPBdQ8vEMBCH+9zNCDA6tET/zHCi/jLqjCWtlJZUPk+DbnFw==} dependencies: @@ -9446,6 +10423,12 @@ packages: glob: 7.2.3 dev: true + /rimraf/4.1.2: + resolution: {integrity: sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==} + engines: {node: '>=14'} + hasBin: true + dev: true + /robust-predicates/3.0.1: resolution: {integrity: sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==} @@ -9472,6 +10455,14 @@ packages: fsevents: 2.3.2 dev: true + /rollup/3.15.0: + resolution: {integrity: sha512-F9hrCAhnp5/zx/7HYmftvsNBkMfLfk/dXUh73hPSM2E3CRgap65orDNJbLetoiUFwSAk6iHPLvBrZ5iHYvzqsg==} + engines: {node: '>=14.18.0', npm: '>=8.0.0'} + hasBin: true + optionalDependencies: + fsevents: 2.3.2 + dev: true + /run-parallel/1.2.0: resolution: {integrity: sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==} dependencies: @@ -9487,6 +10478,12 @@ packages: tslib: 2.4.0 dev: true + /rxjs/7.8.0: + resolution: {integrity: sha512-F2+gxDshqmIub1KdvZkaEfGDwLNpPvk9Fs6LD/MyQxNgMds/WH9OdDDXOmxUZpME+iSK3rQCctkL0DYyytUqMg==} + dependencies: + tslib: 2.4.0 + dev: true + /sade/1.8.1: resolution: {integrity: sha512-xal3CZX1Xlo/k4ApwCFrHVACi9fBqJ7V+mwhBsuf/1IOKbBy098Fex+Wa/5QMubw09pSZ/u8EY8PWgevJsXp1A==} engines: {node: '>=6'} @@ -9686,6 +10683,15 @@ packages: vscode-textmate: 6.0.0 dev: true + /shiki/0.14.1: + resolution: {integrity: sha512-+Jz4nBkCBe0mEDqo1eKRcCdjRtrCjozmcbTUjbPTX7OOJfEbTZzlUWlZtGe3Gb5oV1/jnojhG//YZc3rs9zSEw==} + dependencies: + ansi-sequence-parser: 1.1.0 + jsonc-parser: 3.2.0 + vscode-oniguruma: 1.7.0 + vscode-textmate: 8.0.0 + dev: true + /side-channel/1.0.4: resolution: {integrity: sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==} dependencies: @@ -9694,6 +10700,10 @@ packages: object-inspect: 1.12.2 dev: true + /siginfo/2.0.0: + resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==} + dev: true + /signal-exit/3.0.7: resolution: {integrity: sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==} dev: true @@ -9824,6 +10834,7 @@ packages: /sourcemap-codec/1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead dev: true /spawn-command/0.0.2-1: @@ -9921,6 +10932,10 @@ packages: escape-string-regexp: 2.0.0 dev: true + /stackback/0.0.2: + resolution: {integrity: sha512-1XMJE5fQo1jGH6Y/7ebnwPOBEkIEnT4QF32d5R1+VXdXveM0IBMJt8zfaxX1P3QhVwrYe+576+jkANtSS2mBbw==} + dev: true + /start-server-and-test/1.14.0: resolution: {integrity: sha512-on5ELuxO2K0t8EmNj9MtVlFqwBMxfWOhu4U7uZD1xccVpFlOQKR93CSe0u98iQzfNxRyaNTb/CdadbNllplTsw==} engines: {node: '>=6'} @@ -9937,6 +10952,23 @@ packages: - supports-color dev: true + /start-server-and-test/1.15.4: + resolution: {integrity: sha512-ucQtp5+UCr0m4aHlY+aEV2JSYNTiMZKdSKK/bsIr6AlmwAWDYDnV7uGlWWEtWa7T4XvRI5cPYcPcQgeLqpz+Tg==} + engines: {node: '>=6'} + hasBin: true + dependencies: + arg: 5.0.2 + bluebird: 3.7.2 + check-more-types: 2.24.0 + debug: 4.3.4 + execa: 5.1.1 + lazy-ass: 1.6.0 + ps-tree: 1.2.0 + wait-on: 7.0.1_debug@4.3.4 + transitivePeerDependencies: + - supports-color + dev: true + /statuses/1.5.0: resolution: {integrity: sha512-OpZ3zP+jT1PI7I8nemJX4AKmAX070ZkYPVWV/AaKTJl+tXCTGyVdC1a4SL8RUQYEwk/f34ZX8UTykN68FwrqAA==} engines: {node: '>= 0.6'} @@ -9947,6 +10979,10 @@ packages: engines: {node: '>= 0.8'} dev: true + /std-env/3.3.2: + resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==} + dev: true + /stream-combiner/0.0.4: resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} dependencies: @@ -10048,15 +11084,22 @@ packages: min-indent: 1.0.1 dev: true + /strip-indent/4.0.0: + resolution: {integrity: sha512-mnVSV2l+Zv6BLpSD/8V87CW/y9EmmbYzGCIavsnsI6/nwn26DwffM/yztm30Z/I2DY9wdS3vXVCMnHDgZaVNoA==} + engines: {node: '>=12'} + dependencies: + min-indent: 1.0.1 + dev: true + /strip-json-comments/3.1.1: resolution: {integrity: sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==} engines: {node: '>=8'} dev: true - /strip-literal/0.4.2: - resolution: {integrity: sha512-pv48ybn4iE1O9RLgCAN0iU4Xv7RlBTiit6DKmMiErbs9x1wH6vXBs45tWc0H5wUIF6TLTrKweqkmYF/iraQKNw==} + /strip-literal/1.0.0: + resolution: {integrity: sha512-5o4LsH1lzBzO9UFH63AJ2ad2/S2AVx6NtjOcaz+VTT2h1RiRvbipW72z8M/lxEhcPHDBQwpDrnTF7sXy/7OwCQ==} dependencies: - acorn: 8.8.0 + acorn: 8.8.1 dev: true /stylis/4.1.2: @@ -10140,7 +11183,7 @@ packages: hasBin: true dependencies: '@jridgewell/source-map': 0.3.2 - acorn: 8.8.0 + acorn: 8.8.1 commander: 2.20.3 source-map-support: 0.5.21 dev: true @@ -10189,8 +11232,8 @@ packages: resolution: {integrity: sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==} dev: true - /tinypool/0.3.0: - resolution: {integrity: sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ==} + /tinypool/0.3.1: + resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==} engines: {node: '>=14.0.0'} dev: true @@ -10271,11 +11314,21 @@ packages: engines: {node: '>=8'} dev: true + /trim-newlines/4.0.2: + resolution: {integrity: sha512-GJtWyq9InR/2HRiLZgpIKv+ufIKrVrvjQWEj7PxAXNc5dwbNJkqhAUoAGgzRmULAnoOM5EIpveYd3J2VeSAIew==} + engines: {node: '>=12'} + dev: true + /trough/2.1.0: resolution: {integrity: sha512-AqTiAOLcj85xS7vQ8QkAV41hPDIJ71XJB4RCUrzo/1GM2CQwhkJGaf9Hgr7BOugMRpgGUrqRg/DrBDl4H40+8g==} dev: true - /ts-node/10.9.1_cbe7ovvae6zqfnmtgctpgpys54: + /ts-dedent/2.2.0: + resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} + engines: {node: '>=6.10'} + dev: false + + /ts-node/10.9.1_w6ufic3jqylcjznzspnj4wjqfe: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -10301,12 +11354,12 @@ packages: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 4.8.4 + typescript: 4.9.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true - /ts-node/10.9.1_sqjhzn5m3vxyw66a2xhtc43hby: + /ts-node/10.9.1_yxpazyh7n5pql7jdaglasgwqki: resolution: {integrity: sha512-NtVysVPkxxrwFGUUxGYhfux8k78pQB3JqYBXlLRZgdGUqTO5wU/UyHop5p70iEbGhB7q5KmiZiU0Y3KlJrScEw==} hasBin: true peerDependencies: @@ -10332,7 +11385,7 @@ packages: create-require: 1.1.1 diff: 4.0.2 make-error: 1.3.6 - typescript: 4.8.4 + typescript: 4.9.5 v8-compile-cache-lib: 3.0.1 yn: 3.1.1 dev: true @@ -10355,6 +11408,16 @@ packages: typescript: 4.8.4 dev: true + /tsutils/3.21.0_typescript@4.9.5: + resolution: {integrity: sha512-mHKK3iUXL+3UF6xL5k0PEhKRUBKPBCv/+RkEOpjRWxxx27KKRBmmA60A9pgOUvMi8GKhRMPEmjBRPzs2W7O1OA==} + engines: {node: '>= 6'} + peerDependencies: + typescript: '>=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta' + dependencies: + tslib: 1.14.1 + typescript: 4.9.5 + dev: true + /tunnel-agent/0.6.0: resolution: {integrity: sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==} dependencies: @@ -10409,6 +11472,11 @@ packages: engines: {node: '>=8'} dev: true + /type-fest/1.4.0: + resolution: {integrity: sha512-yGSza74xk0UG8k+pLh5oeoYirvIiWo5t0/o3zHHAO2tRDiZcxWP7fywNlXhqb6/r6sWvwi+RsyQMWhVLe4BVuA==} + engines: {node: '>=10'} + dev: true + /type-is/1.6.18: resolution: {integrity: sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==} engines: {node: '>= 0.6'} @@ -10452,10 +11520,20 @@ packages: hasBin: true dev: true + /typescript/4.9.5: + resolution: {integrity: sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==} + engines: {node: '>=4.2.0'} + hasBin: true + dev: true + /uc.micro/1.0.6: resolution: {integrity: sha512-8Y75pvTYkLJW2hWQHXxoqRgV7qb9B+9vFEtidML+7koHUFapnVJAZ6cKs+Qjz5Aw3aZWHMC6u0wJE3At+nSGwA==} dev: true + /ufo/1.0.1: + resolution: {integrity: sha512-boAm74ubXHY7KJQZLlXrtMz52qFvpsbOxDcZOnw/Wf+LS4Mmyu7JxmzD4tDLtUQtmZECypJ0FrCz4QIe6dvKRA==} + dev: true + /uglify-js/3.17.3: resolution: {integrity: sha512-JmMFDME3iufZnBpyKL+uS78LRiC+mK55zWfM5f/pWBJfpOttXAqYfdDGRukYhJuyRinvPVAtUhvy7rlDybNtFg==} engines: {node: '>=0.8.0'} @@ -10656,8 +11734,54 @@ packages: vfile-message: 3.1.2 dev: true - /vite/3.2.3: - resolution: {integrity: sha512-h8jl1TZ76eGs3o2dIBSsvXDLb1m/Ec1iej8ZMdz+PsaFUsftZeWe2CZOI3qogEsMNaywc17gu0q6cQDzh/weCQ==} + /vite-node/0.28.4_@types+node@18.11.9: + resolution: {integrity: sha512-KM0Q0uSG/xHHKOJvVHc5xDBabgt0l70y7/lWTR7Q0pR5/MrYxadT+y32cJOE65FfjGmJgxpVEEY+69btJgcXOQ==} + engines: {node: '>=v14.16.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + mlly: 1.1.0 + pathe: 1.1.0 + picocolors: 1.0.0 + source-map: 0.6.1 + source-map-support: 0.5.21 + vite: 4.1.1_@types+node@18.11.9 + transitivePeerDependencies: + - '@types/node' + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite-node/0.28.5_@types+node@18.11.9: + resolution: {integrity: sha512-LmXb9saMGlrMZbXTvOveJKwMTBTNUH66c8rJnQ0ZPNX+myPEol64+szRzXtV5ORb0Hb/91yq+/D3oERoyAt6LA==} + engines: {node: '>=v14.16.0'} + hasBin: true + dependencies: + cac: 6.7.14 + debug: 4.3.4 + mlly: 1.1.0 + pathe: 1.1.0 + picocolors: 1.0.0 + source-map: 0.6.1 + source-map-support: 0.5.21 + vite: 4.1.1_@types+node@18.11.9 + transitivePeerDependencies: + - '@types/node' + - less + - sass + - stylus + - sugarss + - supports-color + - terser + dev: true + + /vite/4.1.1: + resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -10681,16 +11805,16 @@ packages: terser: optional: true dependencies: - esbuild: 0.15.13 - postcss: 8.4.18 + esbuild: 0.16.17 + postcss: 8.4.21 resolve: 1.22.1 - rollup: 2.79.1 + rollup: 3.15.0 optionalDependencies: fsevents: 2.3.2 dev: true - /vite/3.2.3_@types+node@18.11.9: - resolution: {integrity: sha512-h8jl1TZ76eGs3o2dIBSsvXDLb1m/Ec1iej8ZMdz+PsaFUsftZeWe2CZOI3qogEsMNaywc17gu0q6cQDzh/weCQ==} + /vite/4.1.1_@types+node@18.11.9: + resolution: {integrity: sha512-LM9WWea8vsxhr782r9ntg+bhSFS06FJgCvvB0+8hf8UWtvaiDagKYWXndjfX6kGl74keHJUcpzrQliDXZlF5yg==} engines: {node: ^14.18.0 || >=16.0.0} hasBin: true peerDependencies: @@ -10715,20 +11839,19 @@ packages: optional: true dependencies: '@types/node': 18.11.9 - esbuild: 0.15.13 - postcss: 8.4.18 + esbuild: 0.16.17 + postcss: 8.4.21 resolve: 1.22.1 - rollup: 2.79.1 + rollup: 3.15.0 optionalDependencies: fsevents: 2.3.2 dev: true - /vitepress-plugin-search/1.0.4-alpha.15_s3edpouswd4dgoi2en7bdlrp54: - resolution: {integrity: sha512-Ef/VkhTVYlECVI0H9Ck6745UNPfYFppAqnlxVSMJXdxP2vjOZ5TYNczlTTQ2p9dh16MFw/IurbL1/GrG4nXdNw==} + /vitepress-plugin-search/1.0.4-alpha.19_g67lr3vgasogkevpbew55lljzq: + resolution: {integrity: sha512-WFOPn5dStyMINd+rVjNxbEmGa7U+qGHLxLnda56EG+ATil1i0yOauGhJEh5LPMvuCUVIA9tInJnFXklOBb39dA==} engines: {node: ^14.13.1 || ^16.7.0 || >=18} peerDependencies: flexsearch: ^0.7.31 - vite: 2 || 3 vitepress: ^1.0.0-alpha.13 vue: '3' dependencies: @@ -10736,24 +11859,23 @@ packages: '@types/markdown-it': 12.2.3 flexsearch: 0.7.31 markdown-it: 13.0.1 - vite: 3.2.3 - vitepress: 1.0.0-alpha.28_tbpndr44ulefs3hehwpi2mkf2y - vue: 3.2.41 + vitepress: 1.0.0-alpha.46_tbpndr44ulefs3hehwpi2mkf2y + vue: 3.2.45 dev: true - /vitepress/1.0.0-alpha.28_tbpndr44ulefs3hehwpi2mkf2y: - resolution: {integrity: sha512-pvbLssDMgLUN1terajmPlFBxHSDGO4DqwexKbjFyr7LeELerVuwGrG6F2J1hxmwOlbpLd1kHXEDqGm9JX/kTDQ==} + /vitepress/1.0.0-alpha.46_tbpndr44ulefs3hehwpi2mkf2y: + resolution: {integrity: sha512-HiKiHzC0iTPsRsKs8XcsMeMzCpcCt5LWcX9mpDr288Ju+nQf1G8A2+Wm44ZkBsVv4EHxFK4ChmWyZrL1OJUXpg==} hasBin: true dependencies: - '@docsearch/css': 3.3.0 - '@docsearch/js': 3.3.0_tbpndr44ulefs3hehwpi2mkf2y - '@vitejs/plugin-vue': 3.2.0_vite@3.2.3+vue@3.2.41 - '@vue/devtools-api': 6.4.5 - '@vueuse/core': 9.4.0_vue@3.2.41 + '@docsearch/css': 3.3.3 + '@docsearch/js': 3.3.3_tbpndr44ulefs3hehwpi2mkf2y + '@vitejs/plugin-vue': 4.0.0_vite@4.1.1+vue@3.2.45 + '@vue/devtools-api': 6.5.0 + '@vueuse/core': 9.12.0_vue@3.2.45 body-scroll-lock: 4.0.0-beta.0 - shiki: 0.11.1 - vite: 3.2.3 - vue: 3.2.41 + shiki: 0.14.1 + vite: 4.1.1 + vue: 3.2.45 transitivePeerDependencies: - '@algolia/client-search' - '@types/node' @@ -10768,8 +11890,8 @@ packages: - terser dev: true - /vitest/0.25.1_oullksb5ic6y72oh2wekoaiuii: - resolution: {integrity: sha512-eH74h6MkuEgsqR4mAQZeMK9O0PROiKY+i+1GMz/fBi5A3L2ml5U7JQs7LfPU7+uWUziZyLHagl+rkyfR8SLhlA==} + /vitest/0.28.4_vun5xzxu3tkrssf3erdbijyyki: + resolution: {integrity: sha512-sfWIy0AdlbyGRhunm+TLQEJrFH9XuRPdApfubsyLcDbCRrUX717BRQKInTgzEfyl2Ipi1HWoHB84Nqtcwxogcg==} engines: {node: '>=v14.16.0'} hasBin: true peerDependencies: @@ -10790,22 +11912,32 @@ packages: jsdom: optional: true dependencies: - '@types/chai': 4.3.3 + '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 '@types/node': 18.11.9 - '@vitest/ui': 0.25.1 - acorn: 8.8.0 + '@vitest/expect': 0.28.4 + '@vitest/runner': 0.28.4 + '@vitest/spy': 0.28.4 + '@vitest/ui': 0.28.4 + '@vitest/utils': 0.28.4 + acorn: 8.8.1 acorn-walk: 8.2.0 - chai: 4.3.6 + cac: 6.7.14 + chai: 4.3.7 debug: 4.3.4 - jsdom: 20.0.2 + jsdom: 21.1.0 local-pkg: 0.4.2 + pathe: 1.1.0 + picocolors: 1.0.0 source-map: 0.6.1 - strip-literal: 0.4.2 + std-env: 3.3.2 + strip-literal: 1.0.0 tinybench: 2.3.1 - tinypool: 0.3.0 + tinypool: 0.3.1 tinyspy: 1.0.2 - vite: 3.2.3_@types+node@18.11.9 + vite: 4.1.1_@types+node@18.11.9 + vite-node: 0.28.4_@types+node@18.11.9 + why-is-node-running: 2.2.2 transitivePeerDependencies: - less - sass @@ -10815,8 +11947,8 @@ packages: - terser dev: true - /vitest/0.25.3_oullksb5ic6y72oh2wekoaiuii: - resolution: {integrity: sha512-/UzHfXIKsELZhL7OaM2xFlRF8HRZgAHtPctacvNK8H4vOcbJJAMEgbWNGSAK7Y9b1NBe5SeM7VTuz2RsTHFJJA==} + /vitest/0.28.5_vun5xzxu3tkrssf3erdbijyyki: + resolution: {integrity: sha512-pyCQ+wcAOX7mKMcBNkzDwEHRGqQvHUl0XnoHR+3Pb1hytAHISgSxv9h0gUiSiYtISXUU3rMrKiKzFYDrI6ZIHA==} engines: {node: '>=v14.16.0'} hasBin: true peerDependencies: @@ -10837,22 +11969,32 @@ packages: jsdom: optional: true dependencies: - '@types/chai': 4.3.3 + '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 '@types/node': 18.11.9 - '@vitest/ui': 0.25.1 - acorn: 8.8.0 + '@vitest/expect': 0.28.5 + '@vitest/runner': 0.28.5 + '@vitest/spy': 0.28.5 + '@vitest/ui': 0.28.4 + '@vitest/utils': 0.28.5 + acorn: 8.8.1 acorn-walk: 8.2.0 - chai: 4.3.6 + cac: 6.7.14 + chai: 4.3.7 debug: 4.3.4 - jsdom: 20.0.2 + jsdom: 21.1.0 local-pkg: 0.4.2 + pathe: 1.1.0 + picocolors: 1.0.0 source-map: 0.6.1 - strip-literal: 0.4.2 + std-env: 3.3.2 + strip-literal: 1.0.0 tinybench: 2.3.1 - tinypool: 0.3.0 + tinypool: 0.3.1 tinyspy: 1.0.2 - vite: 3.2.3_@types+node@18.11.9 + vite: 4.1.1_@types+node@18.11.9 + vite-node: 0.28.5_@types+node@18.11.9 + why-is-node-running: 2.2.2 transitivePeerDependencies: - less - sass @@ -10867,7 +12009,7 @@ packages: engines: {node: '>=6.0'} hasBin: true dependencies: - acorn: 8.8.0 + acorn: 8.8.1 acorn-walk: 8.2.0 dev: true @@ -10897,15 +12039,23 @@ packages: resolution: {integrity: sha512-KH8+KKov5eS/9WhofZR8M8dMHWN2gTxjMsG4jd04YhpbPR91fUj7rYQ2/XjeHCJWbg7X++ApRIU9NUwM2vTvLA==} dev: true + /vscode-oniguruma/1.7.0: + resolution: {integrity: sha512-L9WMGRfrjOhgHSdOYgCt/yRMsXzLDJSL7BPrOZt73gU0iWO4mpqzqQzOz5srxqTvMBaR0XZTSrVWo4j55Rc6cA==} + dev: true + /vscode-textmate/6.0.0: resolution: {integrity: sha512-gu73tuZfJgu+mvCSy4UZwd2JXykjK9zAZsfmDeut5dx/1a7FeTk0XwJsSuqQn+cuMCGVbIBfl+s53X4T19DnzQ==} dev: true + /vscode-textmate/8.0.0: + resolution: {integrity: sha512-AFbieoL7a5LMqcnOF04ji+rpXadgOXnZsxQr//r83kLPr7biP7am3g9zbaZIaBGwBRWeSvoMD4mgPdX3e4NWBg==} + dev: true + /vscode-uri/3.0.6: resolution: {integrity: sha512-fmL7V1eiDBFRRnu+gfRWTzyPpNIHJTc4mWnFkwBUmO9U3KPgJAmTx7oxi2bl/Rh6HLdU7+4C9wlj0k2E4AdKFQ==} dev: true - /vue-demi/0.13.11_vue@3.2.41: + /vue-demi/0.13.11_vue@3.2.45: resolution: {integrity: sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==} engines: {node: '>=12'} hasBin: true @@ -10917,17 +12067,17 @@ packages: '@vue/composition-api': optional: true dependencies: - vue: 3.2.41 + vue: 3.2.45 dev: true - /vue/3.2.41: - resolution: {integrity: sha512-uuuvnrDXEeZ9VUPljgHkqB5IaVO8SxhPpqF2eWOukVrBnRBx2THPSGQBnVRt0GrIG1gvCmFXMGbd7FqcT1ixNQ==} + /vue/3.2.45: + resolution: {integrity: sha512-9Nx/Mg2b2xWlXykmCwiTUCWHbWIj53bnkizBxKai1g61f2Xit700A1ljowpTIM11e3uipOeiPcSqnmBg6gyiaA==} dependencies: - '@vue/compiler-dom': 3.2.41 - '@vue/compiler-sfc': 3.2.41 - '@vue/runtime-dom': 3.2.41 - '@vue/server-renderer': 3.2.41_vue@3.2.41 - '@vue/shared': 3.2.41 + '@vue/compiler-dom': 3.2.45 + '@vue/compiler-sfc': 3.2.45 + '@vue/runtime-dom': 3.2.45 + '@vue/server-renderer': 3.2.45_vue@3.2.45 + '@vue/shared': 3.2.45 dev: true /w3c-hr-time/1.0.2: @@ -10944,6 +12094,13 @@ packages: xml-name-validator: 4.0.0 dev: true + /w3c-xmlserializer/4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} + dependencies: + xml-name-validator: 4.0.0 + dev: true + /wait-on/6.0.0_debug@4.3.2: resolution: {integrity: sha512-tnUJr9p5r+bEYXPUdRseolmz5XqJTTj98JgOsfBn7Oz2dxfE2g3zw1jE+Mo8lopM3j3et/Mq1yW7kKX6qw7RVw==} engines: {node: '>=10.0.0'} @@ -10958,6 +12115,20 @@ packages: - debug dev: true + /wait-on/7.0.1_debug@4.3.4: + resolution: {integrity: sha512-9AnJE9qTjRQOlTZIldAaf/da2eW0eSRSgcqq85mXQja/DW3MriHxkpODDSUEg+Gri/rKEcXUZHe+cevvYItaog==} + engines: {node: '>=12.0.0'} + hasBin: true + dependencies: + axios: 0.27.2_debug@4.3.4 + joi: 17.7.1 + lodash: 4.17.21 + minimist: 1.2.8 + rxjs: 7.8.0 + transitivePeerDependencies: + - debug + dev: true + /walker/1.0.8: resolution: {integrity: sha512-ts/8E8l5b7kY0vlWLewOkDXMmPdLcVV4GmOQLyxuSswIJsweeFZtAsMF7k1Nszz+TYBQrlYRmzOnr398y1JemQ==} dependencies: @@ -10978,6 +12149,10 @@ packages: minimalistic-assert: 1.0.1 dev: true + /web-worker/1.2.0: + resolution: {integrity: sha512-PgF341avzqyx60neE9DD+XS26MMNMoUQRz9NOZwW32nPQrF6p77f1htcnjBSEV8BGMKZ16choqUG4hyI0Hx7mA==} + dev: false + /webdriver/7.16.11: resolution: {integrity: sha512-6nBOXae4xuBH4Nqvi/zvtwjnxSLTONBpxOiRJtQ68CYTYv5+w3m8CsaWy3HbK/0XXa++NYl62bDNn70OGEKb+Q==} engines: {node: '>=12.0.0'} @@ -11219,6 +12394,15 @@ packages: isexe: 2.0.0 dev: true + /why-is-node-running/2.2.2: + resolution: {integrity: sha512-6tSwToZxTOcotxHeA+qGCq1mVzKR3CwcJGmVcY+QE8SHy6TnpFnh8PAvPNHYr7EcuVeG0QSMxtYCuO1ta/G/oA==} + engines: {node: '>=8'} + hasBin: true + dependencies: + siginfo: 2.0.0 + stackback: 0.0.2 + dev: true + /wildcard/2.0.0: resolution: {integrity: sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==} dev: true @@ -11284,6 +12468,19 @@ packages: optional: true dev: true + /ws/8.12.0: + resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /ws/8.5.0: resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} engines: {node: '>=10.0.0'} @@ -11422,6 +12619,11 @@ packages: engines: {node: '>=10'} dev: true + /yocto-queue/1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true + /zwitch/2.0.2: resolution: {integrity: sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==} dev: true diff --git a/scripts/jison/lint.mts b/scripts/jison/lint.mts index c410d5999..95edd4fb1 100644 --- a/scripts/jison/lint.mts +++ b/scripts/jison/lint.mts @@ -23,7 +23,7 @@ const lint = async (file: string): Promise => { return result.errorCount === 0; }; -(async () => { +const main = async () => { const jisonFiles = await globby(['./packages/**/*.jison', '!./**/node_modules/**'], { dot: true, }); @@ -31,4 +31,6 @@ const lint = async (file: string): Promise => { if (lintResults.includes(false)) { process.exit(1); } -})(); +}; + +void main(); diff --git a/tests/webpack/package.json b/tests/webpack/package.json index c58f456a6..e51845399 100644 --- a/tests/webpack/package.json +++ b/tests/webpack/package.json @@ -18,6 +18,6 @@ }, "dependencies": { "mermaid": "workspace:*", - "@mermaid-js/mermaid-mindmap": "workspace:*" + "@mermaid-js/mermaid-example-diagram": "workspace:*" } } diff --git a/tests/webpack/src/index.js b/tests/webpack/src/index.js index 899f66596..51738aa62 100644 --- a/tests/webpack/src/index.js +++ b/tests/webpack/src/index.js @@ -4,7 +4,7 @@ const mermaid = require('mermaid'); import mindmap from '@mermaid-js/mermaid-mindmap'; const render = async (graph) => { - const svg = await mermaid.renderAsync('dummy', graph); + const svg = await mermaid.render('dummy', graph); console.log(svg); document.getElementById('graphDiv').innerHTML = svg; }; @@ -13,8 +13,8 @@ const load = async () => { await mermaid.registerExternalDiagrams([mindmap]); await render('info'); - setTimeout(async () => { - await render(`mindmap + setTimeout(() => { + void render(`mindmap root((mindmap)) Origins Long history @@ -35,4 +35,4 @@ const load = async () => { }, 2500); }; -window.addEventListener('load', load, false); +window.addEventListener('load', () => void load(), false); diff --git a/tsconfig.eslint.json b/tsconfig.eslint.json new file mode 100644 index 000000000..5090f49d1 --- /dev/null +++ b/tsconfig.eslint.json @@ -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", "./*"] +} diff --git a/tsconfig.json b/tsconfig.json index fe107f205..c66d62784 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -76,7 +76,7 @@ // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ "esModuleInterop": true /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables `allowSyntheticDefaultImports` for type compatibility. */, - // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "preserveSymlinks": true /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */, "forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */, /* Type Checking */