mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-11-04 12:54:08 +01:00 
			
		
		
		
	Merge branch 'develop' of https://github.com/NicolasNewman/mermaid into feature/2776_katex_math
This commit is contained in:
		
							
								
								
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/ISSUE_TEMPLATE/bug_report.yml
									
									
									
									
										vendored
									
									
								
							@@ -50,7 +50,7 @@ body:
 | 
			
		||||
    attributes:
 | 
			
		||||
      label: Setup
 | 
			
		||||
      description: |-
 | 
			
		||||
        Please fill out the below info.
 | 
			
		||||
        Please fill out the info below.
 | 
			
		||||
        Note that you only need to fill out the relevant section
 | 
			
		||||
      value: |-
 | 
			
		||||
        - Mermaid version: 
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										29
									
								
								.github/workflows/build-docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							
							
						
						
									
										29
									
								
								.github/workflows/build-docs.yml
									
									
									
									
										vendored
									
									
										Normal file
									
								
							@@ -0,0 +1,29 @@
 | 
			
		||||
name: Build Vitepress docs
 | 
			
		||||
 | 
			
		||||
on:
 | 
			
		||||
  pull_request:
 | 
			
		||||
 | 
			
		||||
permissions:
 | 
			
		||||
  contents: read
 | 
			
		||||
 | 
			
		||||
jobs:
 | 
			
		||||
  # Build job
 | 
			
		||||
  build:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Checkout
 | 
			
		||||
        uses: actions/checkout@v3
 | 
			
		||||
 | 
			
		||||
      - uses: pnpm/action-setup@v2
 | 
			
		||||
 | 
			
		||||
      - name: Setup Node.js
 | 
			
		||||
        uses: actions/setup-node@v3
 | 
			
		||||
        with:
 | 
			
		||||
          cache: pnpm
 | 
			
		||||
          node-version: 18
 | 
			
		||||
 | 
			
		||||
      - name: Install Packages
 | 
			
		||||
        run: pnpm install --frozen-lockfile
 | 
			
		||||
 | 
			
		||||
      - name: Run Build
 | 
			
		||||
        run: pnpm --filter mermaid run docs:build:vitepress
 | 
			
		||||
							
								
								
									
										20
									
								
								.github/workflows/e2e-applitools.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										20
									
								
								.github/workflows/e2e-applitools.yml
									
									
									
									
										vendored
									
									
								
							@@ -38,15 +38,8 @@ jobs:
 | 
			
		||||
      - name: Setup Node.js ${{ matrix.node-version }}
 | 
			
		||||
        uses: actions/setup-node@v3
 | 
			
		||||
        with:
 | 
			
		||||
          cache: pnpm
 | 
			
		||||
          node-version: ${{ matrix.node-version }}
 | 
			
		||||
 | 
			
		||||
      - name: Install Packages
 | 
			
		||||
        run: |
 | 
			
		||||
          pnpm install --frozen-lockfile
 | 
			
		||||
        env:
 | 
			
		||||
          CYPRESS_CACHE_FOLDER: .cache/Cypress
 | 
			
		||||
 | 
			
		||||
      - if: ${{ env.USE_APPLI }}
 | 
			
		||||
        name: Notify applitools of new batch
 | 
			
		||||
        # Copied from docs https://applitools.com/docs/topics/integrations/github-integration-ci-setup.html
 | 
			
		||||
@@ -54,19 +47,22 @@ jobs:
 | 
			
		||||
        env:
 | 
			
		||||
          # e.g. mermaid-js/mermaid/my-branch
 | 
			
		||||
          APPLITOOLS_BRANCH: ${{ github.repository }}/${{ github.ref_name }}
 | 
			
		||||
          APPLITOOLS_PARENT_BRANCH: ${{ github.inputs.parent_branch }}
 | 
			
		||||
          APPLITOOLS_PARENT_BRANCH: ${{ github.event.inputs.parent_branch }}
 | 
			
		||||
          APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
 | 
			
		||||
          APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
 | 
			
		||||
 | 
			
		||||
      - name: Run E2E Tests
 | 
			
		||||
        run: pnpm run e2e
 | 
			
		||||
      - name: Cypress run
 | 
			
		||||
        uses: cypress-io/github-action@v4
 | 
			
		||||
        id: cypress
 | 
			
		||||
        with:
 | 
			
		||||
          start: pnpm run dev
 | 
			
		||||
          wait-on: 'http://localhost:9000'
 | 
			
		||||
        env:
 | 
			
		||||
          CYPRESS_CACHE_FOLDER: .cache/Cypress
 | 
			
		||||
          # Mermaid applitools.config.js uses this to pick batch name.
 | 
			
		||||
          APPLI_BRANCH: ${{ github.ref_name }}
 | 
			
		||||
          APPLITOOLS_BATCH_ID: ${{ github.sha }}
 | 
			
		||||
          # e.g. mermaid-js/mermaid/my-branch
 | 
			
		||||
          APPLITOOLS_BRANCH: ${{ github.repository }}/${{ github.ref_name }}
 | 
			
		||||
          APPLITOOLS_PARENT_BRANCH: ${{ github.inputs.parent_branch }}
 | 
			
		||||
          APPLITOOLS_PARENT_BRANCH: ${{ github.event.inputs.parent_branch }}
 | 
			
		||||
          APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
 | 
			
		||||
          APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/link-checker.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/link-checker.yml
									
									
									
									
										vendored
									
									
								
							@@ -36,7 +36,7 @@ jobs:
 | 
			
		||||
          restore-keys: cache-lychee-
 | 
			
		||||
 | 
			
		||||
      - name: Link Checker
 | 
			
		||||
        uses: lycheeverse/lychee-action@v1.6.1
 | 
			
		||||
        uses: lycheeverse/lychee-action@v1.7.0
 | 
			
		||||
        with:
 | 
			
		||||
          args: >-
 | 
			
		||||
            --verbose
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7
									
								
								.github/workflows/publish-docs.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										7
									
								
								.github/workflows/publish-docs.yml
									
									
									
									
										vendored
									
									
								
							@@ -5,10 +5,6 @@ on:
 | 
			
		||||
  push:
 | 
			
		||||
    branches:
 | 
			
		||||
      - master
 | 
			
		||||
  pull_request:
 | 
			
		||||
 | 
			
		||||
  # Allows you to run this workflow manually from the Actions tab
 | 
			
		||||
  workflow_dispatch:
 | 
			
		||||
 | 
			
		||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
 | 
			
		||||
permissions:
 | 
			
		||||
@@ -53,7 +49,6 @@ jobs:
 | 
			
		||||
 | 
			
		||||
  # Deployment job
 | 
			
		||||
  deploy:
 | 
			
		||||
    if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
 | 
			
		||||
    environment:
 | 
			
		||||
      name: github-pages
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
@@ -61,4 +56,4 @@ jobs:
 | 
			
		||||
    steps:
 | 
			
		||||
      - name: Deploy to GitHub Pages
 | 
			
		||||
        id: deployment
 | 
			
		||||
        uses: actions/deploy-pages@v1
 | 
			
		||||
        uses: actions/deploy-pages@v2
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										2
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										2
									
								
								.github/workflows/release-publish.yml
									
									
									
									
										vendored
									
									
								
							@@ -9,7 +9,7 @@ jobs:
 | 
			
		||||
    runs-on: ubuntu-latest
 | 
			
		||||
    steps:
 | 
			
		||||
      - uses: actions/checkout@v3
 | 
			
		||||
      - uses: fregante/setup-git-user@v1
 | 
			
		||||
      - uses: fregante/setup-git-user@v2
 | 
			
		||||
 | 
			
		||||
      - uses: pnpm/action-setup@v2
 | 
			
		||||
        # uses version from "packageManager" field in package.json
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ const visualize = process.argv.includes('--visualize');
 | 
			
		||||
const watch = process.argv.includes('--watch');
 | 
			
		||||
const mermaidOnly = process.argv.includes('--mermaid');
 | 
			
		||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
 | 
			
		||||
const sourcemap = false;
 | 
			
		||||
 | 
			
		||||
type OutputOptions = Exclude<
 | 
			
		||||
  Exclude<InlineConfig['build'], undefined>['rollupOptions'],
 | 
			
		||||
@@ -60,9 +61,15 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
 | 
			
		||||
    {
 | 
			
		||||
      name,
 | 
			
		||||
      format: 'esm',
 | 
			
		||||
      sourcemap: true,
 | 
			
		||||
      sourcemap,
 | 
			
		||||
      entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`,
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      name,
 | 
			
		||||
      format: 'umd',
 | 
			
		||||
      sourcemap,
 | 
			
		||||
      entryFileNames: `${name}${minify ? '.min' : ''}.js`,
 | 
			
		||||
    },
 | 
			
		||||
  ];
 | 
			
		||||
 | 
			
		||||
  if (core) {
 | 
			
		||||
@@ -79,7 +86,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
 | 
			
		||||
      {
 | 
			
		||||
        name,
 | 
			
		||||
        format: 'esm',
 | 
			
		||||
        sourcemap: true,
 | 
			
		||||
        sourcemap,
 | 
			
		||||
        entryFileNames: `${name}.core.mjs`,
 | 
			
		||||
      },
 | 
			
		||||
    ];
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										14
									
								
								README.md
									
									
									
									
									
								
							
							
						
						
									
										14
									
								
								README.md
									
									
									
									
									
								
							@@ -165,6 +165,13 @@ class Class10 {
 | 
			
		||||
  int id
 | 
			
		||||
  size()
 | 
			
		||||
}
 | 
			
		||||
namespace Namespace01 {
 | 
			
		||||
  class Class11
 | 
			
		||||
  class Class12 {
 | 
			
		||||
    int id
 | 
			
		||||
    size()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```mermaid
 | 
			
		||||
@@ -184,6 +191,13 @@ class Class10 {
 | 
			
		||||
  int id
 | 
			
		||||
  size()
 | 
			
		||||
}
 | 
			
		||||
namespace Namespace01 {
 | 
			
		||||
  class Class11
 | 
			
		||||
  class Class12 {
 | 
			
		||||
    int id
 | 
			
		||||
    size()
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
### State diagram [<a href="https://mermaid-js.github.io/mermaid/#/stateDiagram">docs</a> - <a href="https://mermaid.live/edit#pako:eNpdkEFvgzAMhf8K8nEqpYSNthx22Xbcqcexg0sCiZQQlDhIFeK_L8A6TfXp6fOz9ewJGssFVOAJSbwr7ByadGR1n8T6evpO0vQ1uZDSekOrXGFsPqJPO6q-2-imH8f_0TeHXm50lfelsAMjnEHFY6xpMdRAUhhRQxUlFy0GTTXU_RytYeAx-AdXZB1ULWovdoCB7OXWN1CRC-Ju-r3uz6UtchGHJqDbsPygU57iysb2reoWHpyOWBINvsqypb3vFMlw3TfWZF5xiY7keC6zkpUnZIUojwW-FAVvrvn51LLnvOXHQ84Q5nn-AVtLcwk">live editor</a>]
 | 
			
		||||
 
 | 
			
		||||
@@ -23,6 +23,7 @@
 | 
			
		||||
    "classdef",
 | 
			
		||||
    "codedoc",
 | 
			
		||||
    "colour",
 | 
			
		||||
    "commitlint",
 | 
			
		||||
    "cpettitt",
 | 
			
		||||
    "customizability",
 | 
			
		||||
    "cuzon",
 | 
			
		||||
@@ -62,6 +63,7 @@
 | 
			
		||||
    "lintstagedrc",
 | 
			
		||||
    "logmsg",
 | 
			
		||||
    "lucida",
 | 
			
		||||
    "markdownish",
 | 
			
		||||
    "matthieu",
 | 
			
		||||
    "mdast",
 | 
			
		||||
    "mdbook",
 | 
			
		||||
@@ -91,8 +93,12 @@
 | 
			
		||||
    "sidharth",
 | 
			
		||||
    "sidharthv",
 | 
			
		||||
    "sphinxcontrib",
 | 
			
		||||
    "startx",
 | 
			
		||||
    "starty",
 | 
			
		||||
    "statediagram",
 | 
			
		||||
    "steph",
 | 
			
		||||
    "stopx",
 | 
			
		||||
    "stopy",
 | 
			
		||||
    "stylis",
 | 
			
		||||
    "substate",
 | 
			
		||||
    "sveidqvist",
 | 
			
		||||
@@ -102,9 +108,11 @@
 | 
			
		||||
    "textlength",
 | 
			
		||||
    "treemap",
 | 
			
		||||
    "ts-nocheck",
 | 
			
		||||
    "tsdoc",
 | 
			
		||||
    "tuleap",
 | 
			
		||||
    "ugge",
 | 
			
		||||
    "unist",
 | 
			
		||||
    "valign",
 | 
			
		||||
    "verdana",
 | 
			
		||||
    "viewports",
 | 
			
		||||
    "vinod",
 | 
			
		||||
 
 | 
			
		||||
@@ -548,4 +548,18 @@ class C13["With Città foreign language"]
 | 
			
		||||
`
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
  it('should add classes namespaces', function () {
 | 
			
		||||
    imgSnapshotTest(
 | 
			
		||||
      `
 | 
			
		||||
      classDiagram
 | 
			
		||||
      namespace Namespace1 {
 | 
			
		||||
        class C1
 | 
			
		||||
        class C2
 | 
			
		||||
      }
 | 
			
		||||
      C1 --> C2
 | 
			
		||||
      class C3
 | 
			
		||||
      class C4
 | 
			
		||||
      `
 | 
			
		||||
    );
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -286,7 +286,7 @@ describe('Class diagram', () => {
 | 
			
		||||
    cy.get('svg');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('15: should render a simple class diagram with css classes applied two multiple classes', () => {
 | 
			
		||||
  it('15: should render a simple class diagram with css classes applied to multiple classes', () => {
 | 
			
		||||
    imgSnapshotTest(
 | 
			
		||||
      `
 | 
			
		||||
    classDiagram
 | 
			
		||||
 
 | 
			
		||||
@@ -393,9 +393,9 @@ mindmap
 | 
			
		||||
 | 
			
		||||
    <script type="module">
 | 
			
		||||
      // import mindmap from '../../packages/mermaid-mindmap/src/detector';
 | 
			
		||||
      import example from '../../packages/mermaid-example-diagram/src/mermaid-example-diagram.core.mjs';
 | 
			
		||||
      // import example from '../../packages/mermaid-example-diagram/src/mermaid-example-diagram.core.mjs';
 | 
			
		||||
      import mermaid from './mermaid.esm.mjs';
 | 
			
		||||
      await mermaid.registerExternalDiagrams([example]);
 | 
			
		||||
      // await mermaid.registerExternalDiagrams([example]);
 | 
			
		||||
      mermaid.parseError = function (err, hash) {
 | 
			
		||||
        // console.error('Mermaid error: ', err);
 | 
			
		||||
      };
 | 
			
		||||
 
 | 
			
		||||
@@ -38,7 +38,7 @@
 | 
			
		||||
    </pre>
 | 
			
		||||
 | 
			
		||||
    <script type="module">
 | 
			
		||||
      import mermaid from '../packages/mermaid/src/mermaid';
 | 
			
		||||
      import mermaid from './mermaid.esm.mjs';
 | 
			
		||||
      mermaid.initialize({
 | 
			
		||||
        theme: 'forest',
 | 
			
		||||
        // themeCSS: '.node rect { fill: red; }',
 | 
			
		||||
 
 | 
			
		||||
@@ -27,7 +27,7 @@ They also serve as proof of concept, for the variety of things that can be built
 | 
			
		||||
- [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**)
 | 
			
		||||
- [Obsidian](https://help.obsidian.md/Editing+and+formatting/Advanced+formatting+syntax#Diagram) (**Native support**)
 | 
			
		||||
- [GitBook](https://gitbook.com)
 | 
			
		||||
  - [Mermaid Plugin](https://github.com/JozoVilcek/gitbook-plugin-mermaid)
 | 
			
		||||
  - [Markdown with Mermaid CLI](https://github.com/miao1007/gitbook-plugin-mermaid-cli)
 | 
			
		||||
@@ -161,6 +161,7 @@ They also serve as proof of concept, for the variety of things that can be built
 | 
			
		||||
  - [codedoc-mermaid-plugin](https://www.npmjs.com/package/codedoc-mermaid-plugin)
 | 
			
		||||
- [mdbook](https://rust-lang.github.io/mdBook/index.html)
 | 
			
		||||
  - [mdbook-mermaid](https://github.com/badboy/mdbook-mermaid)
 | 
			
		||||
- [Quarto](https://quarto.org/)
 | 
			
		||||
 | 
			
		||||
## Browser Extensions
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -128,7 +128,7 @@ classDiagram
 | 
			
		||||
    Vehicle <|-- Car
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), and underscores.
 | 
			
		||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), underscores, and dashes (-).
 | 
			
		||||
 | 
			
		||||
### Class labels
 | 
			
		||||
 | 
			
		||||
@@ -283,12 +283,12 @@ To describe the visibility (or encapsulation) of an attribute or method/function
 | 
			
		||||
- `#` Protected
 | 
			
		||||
- `~` Package/Internal
 | 
			
		||||
 | 
			
		||||
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()`:
 | 
			
		||||
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type:
 | 
			
		||||
>
 | 
			
		||||
> - `*` Abstract e.g.: `someAbstractMethod()*`
 | 
			
		||||
> - `$` Static e.g.: `someStaticMethod()$`
 | 
			
		||||
> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*`
 | 
			
		||||
> - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$`
 | 
			
		||||
 | 
			
		||||
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the end of its name:
 | 
			
		||||
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end:
 | 
			
		||||
>
 | 
			
		||||
> - `$` Static e.g.: `String someField$`
 | 
			
		||||
 | 
			
		||||
@@ -421,6 +421,34 @@ And `Link` can be one of:
 | 
			
		||||
| --   | Solid       |
 | 
			
		||||
| ..   | Dashed      |
 | 
			
		||||
 | 
			
		||||
## Define Namespace
 | 
			
		||||
 | 
			
		||||
A namespace groups classes.
 | 
			
		||||
 | 
			
		||||
Code:
 | 
			
		||||
 | 
			
		||||
```mermaid-example
 | 
			
		||||
classDiagram
 | 
			
		||||
namespace BaseShapes {
 | 
			
		||||
    class Triangle
 | 
			
		||||
    class Rectangle {
 | 
			
		||||
      double width
 | 
			
		||||
      double height
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```mermaid
 | 
			
		||||
classDiagram
 | 
			
		||||
namespace BaseShapes {
 | 
			
		||||
    class Triangle
 | 
			
		||||
    class Rectangle {
 | 
			
		||||
      double width
 | 
			
		||||
      double height
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Cardinality / Multiplicity on relations
 | 
			
		||||
 | 
			
		||||
Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies.
 | 
			
		||||
@@ -604,10 +632,26 @@ You would define these actions on a separate line after all classes have been de
 | 
			
		||||
 | 
			
		||||
## Notes
 | 
			
		||||
 | 
			
		||||
It is possible to add notes on diagram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"`
 | 
			
		||||
It is possible to add notes on the diagram using `note "line1\nline2"`. A note can be added for a specific class using `note for <CLASS NAME> "line1\nline2"`.
 | 
			
		||||
 | 
			
		||||
### Examples
 | 
			
		||||
 | 
			
		||||
```mermaid-example
 | 
			
		||||
classDiagram
 | 
			
		||||
    note "This is a general note"
 | 
			
		||||
    note for MyClass "This is a note for a class"
 | 
			
		||||
    class MyClass{
 | 
			
		||||
    }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
```mermaid
 | 
			
		||||
classDiagram
 | 
			
		||||
    note "This is a general note"
 | 
			
		||||
    note for MyClass "This is a note for a class"
 | 
			
		||||
    class MyClass{
 | 
			
		||||
    }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
_URL Link:_
 | 
			
		||||
 | 
			
		||||
```mermaid-example
 | 
			
		||||
 
 | 
			
		||||
@@ -742,9 +742,9 @@ end
 | 
			
		||||
 | 
			
		||||
Formatting:
 | 
			
		||||
 | 
			
		||||
- For bold text, use double asterisks \*\* before and after the text.
 | 
			
		||||
- For italics, use single asterisks \* before and after the text.
 | 
			
		||||
- With traditional strings, you needed to add <br> tags for text to wrap in nodes. However, markdown strings automatically wrap text when it becomes too long and allows you to start a new line by simply using a newline character instead of a <br> tag.
 | 
			
		||||
- For bold text, use double asterisks (`**`) before and after the text.
 | 
			
		||||
- For italics, use single asterisks (`*`) before and after the text.
 | 
			
		||||
- With traditional strings, you needed to add `<br>` tags for text to wrap in nodes. However, markdown strings automatically wrap text when it becomes too long and allows you to start a new line by simply using a newline character instead of a `<br>` tag.
 | 
			
		||||
 | 
			
		||||
This feature is applicable to node labels, edge labels, and subgraph labels.
 | 
			
		||||
 | 
			
		||||
@@ -1003,7 +1003,7 @@ flowchart TD
 | 
			
		||||
    B-->E(A fa:fa-camera-retro perhaps?)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
?> Mermaid is now only compatible with Font Awesome versions 4 and 5. Check that you are using the correct version of Font Awesome.
 | 
			
		||||
Mermaid is compatible with Font Awesome up to verion 5, Free icons only. Check that the icons you use are from the [supported set of icons](https://fontawesome.com/v5/search?o=r&m=free).
 | 
			
		||||
 | 
			
		||||
## Graph declarations with spaces between vertices and link and without semicolon
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -182,7 +182,7 @@ More shapes will be added, beginning with the shapes available in flowcharts.
 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
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 [Font Awesome 5](https://fontawesome.com/v5/search?o=r&m=free) 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.
 | 
			
		||||
 | 
			
		||||
```mermaid-example
 | 
			
		||||
mindmap
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										80
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										80
									
								
								package.json
									
									
									
									
									
								
							@@ -4,7 +4,7 @@
 | 
			
		||||
  "version": "10.1.0",
 | 
			
		||||
  "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "packageManager": "pnpm@7.30.1",
 | 
			
		||||
  "packageManager": "pnpm@8.3.1",
 | 
			
		||||
  "keywords": [
 | 
			
		||||
    "diagram",
 | 
			
		||||
    "markdown",
 | 
			
		||||
@@ -54,65 +54,65 @@
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@applitools/eyes-cypress": "^3.27.6",
 | 
			
		||||
    "@commitlint/cli": "^17.2.0",
 | 
			
		||||
    "@commitlint/config-conventional": "^17.2.0",
 | 
			
		||||
    "@cspell/eslint-plugin": "^6.14.2",
 | 
			
		||||
    "@rollup/plugin-typescript": "^11.0.0",
 | 
			
		||||
    "@applitools/eyes-cypress": "^3.32.0",
 | 
			
		||||
    "@commitlint/cli": "^17.6.1",
 | 
			
		||||
    "@commitlint/config-conventional": "^17.6.1",
 | 
			
		||||
    "@cspell/eslint-plugin": "^6.31.1",
 | 
			
		||||
    "@rollup/plugin-typescript": "^11.1.0",
 | 
			
		||||
    "@types/cors": "^2.8.13",
 | 
			
		||||
    "@types/eslint": "^8.4.10",
 | 
			
		||||
    "@types/eslint": "^8.37.0",
 | 
			
		||||
    "@types/express": "^4.17.17",
 | 
			
		||||
    "@types/js-yaml": "^4.0.5",
 | 
			
		||||
    "@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/jsdom": "^21.1.1",
 | 
			
		||||
    "@types/lodash": "^4.14.194",
 | 
			
		||||
    "@types/mdast": "^3.0.11",
 | 
			
		||||
    "@types/node": "^18.16.0",
 | 
			
		||||
    "@types/prettier": "^2.7.2",
 | 
			
		||||
    "@types/rollup-plugin-visualizer": "^4.2.1",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.48.2",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.48.2",
 | 
			
		||||
    "@vitest/coverage-c8": "^0.29.0",
 | 
			
		||||
    "@vitest/spy": "^0.29.0",
 | 
			
		||||
    "@vitest/ui": "^0.29.0",
 | 
			
		||||
    "concurrently": "^7.5.0",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.59.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.59.0",
 | 
			
		||||
    "@vitest/coverage-c8": "^0.30.1",
 | 
			
		||||
    "@vitest/spy": "^0.30.1",
 | 
			
		||||
    "@vitest/ui": "^0.30.1",
 | 
			
		||||
    "concurrently": "^8.0.1",
 | 
			
		||||
    "cors": "^2.8.5",
 | 
			
		||||
    "coveralls": "^3.1.1",
 | 
			
		||||
    "cypress": "^12.0.0",
 | 
			
		||||
    "cypress": "^12.10.0",
 | 
			
		||||
    "cypress-image-snapshot": "^4.0.1",
 | 
			
		||||
    "esbuild": "^0.17.0",
 | 
			
		||||
    "eslint": "^8.32.0",
 | 
			
		||||
    "eslint-config-prettier": "^8.6.0",
 | 
			
		||||
    "eslint-plugin-cypress": "^2.12.1",
 | 
			
		||||
    "esbuild": "^0.17.18",
 | 
			
		||||
    "eslint": "^8.39.0",
 | 
			
		||||
    "eslint-config-prettier": "^8.8.0",
 | 
			
		||||
    "eslint-plugin-cypress": "^2.13.2",
 | 
			
		||||
    "eslint-plugin-html": "^7.1.0",
 | 
			
		||||
    "eslint-plugin-jest": "^27.1.5",
 | 
			
		||||
    "eslint-plugin-jsdoc": "^39.6.2",
 | 
			
		||||
    "eslint-plugin-jest": "^27.2.1",
 | 
			
		||||
    "eslint-plugin-jsdoc": "^43.0.7",
 | 
			
		||||
    "eslint-plugin-json": "^3.1.0",
 | 
			
		||||
    "eslint-plugin-lodash": "^7.4.0",
 | 
			
		||||
    "eslint-plugin-markdown": "^3.0.0",
 | 
			
		||||
    "eslint-plugin-no-only-tests": "^3.1.0",
 | 
			
		||||
    "eslint-plugin-tsdoc": "^0.2.17",
 | 
			
		||||
    "eslint-plugin-unicorn": "^45.0.0",
 | 
			
		||||
    "eslint-plugin-unicorn": "^46.0.0",
 | 
			
		||||
    "express": "^4.18.2",
 | 
			
		||||
    "globby": "^13.1.2",
 | 
			
		||||
    "husky": "^8.0.2",
 | 
			
		||||
    "jest": "^29.3.1",
 | 
			
		||||
    "globby": "^13.1.4",
 | 
			
		||||
    "husky": "^8.0.3",
 | 
			
		||||
    "jest": "^29.5.0",
 | 
			
		||||
    "jison": "^0.4.18",
 | 
			
		||||
    "js-yaml": "^4.1.0",
 | 
			
		||||
    "jsdom": "^21.0.0",
 | 
			
		||||
    "lint-staged": "^13.0.3",
 | 
			
		||||
    "jsdom": "^21.1.1",
 | 
			
		||||
    "lint-staged": "^13.2.1",
 | 
			
		||||
    "path-browserify": "^1.0.1",
 | 
			
		||||
    "pnpm": "^7.15.0",
 | 
			
		||||
    "prettier": "^2.7.1",
 | 
			
		||||
    "pnpm": "^8.3.1",
 | 
			
		||||
    "prettier": "^2.8.8",
 | 
			
		||||
    "prettier-plugin-jsdoc": "^0.4.2",
 | 
			
		||||
    "rimraf": "^4.0.0",
 | 
			
		||||
    "rollup-plugin-visualizer": "^5.8.3",
 | 
			
		||||
    "start-server-and-test": "^1.15.4",
 | 
			
		||||
    "rimraf": "^5.0.0",
 | 
			
		||||
    "rollup-plugin-visualizer": "^5.9.0",
 | 
			
		||||
    "start-server-and-test": "^2.0.0",
 | 
			
		||||
    "ts-node": "^10.9.1",
 | 
			
		||||
    "typescript": "^4.8.4",
 | 
			
		||||
    "vite": "^4.1.1",
 | 
			
		||||
    "vitest": "^0.29.0"
 | 
			
		||||
    "typescript": "^5.0.4",
 | 
			
		||||
    "vite": "^4.3.1",
 | 
			
		||||
    "vitest": "^0.30.1"
 | 
			
		||||
  },
 | 
			
		||||
  "volta": {
 | 
			
		||||
    "node": "18.15.0"
 | 
			
		||||
    "node": "18.16.0"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -48,8 +48,8 @@
 | 
			
		||||
  },
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/cytoscape": "^3.19.9",
 | 
			
		||||
    "concurrently": "^7.5.0",
 | 
			
		||||
    "rimraf": "^4.0.0",
 | 
			
		||||
    "concurrently": "^8.0.0",
 | 
			
		||||
    "rimraf": "^5.0.0",
 | 
			
		||||
    "mermaid": "workspace:*"
 | 
			
		||||
  },
 | 
			
		||||
  "resolutions": {
 | 
			
		||||
 
 | 
			
		||||
@@ -1,6 +1,6 @@
 | 
			
		||||
{
 | 
			
		||||
  "name": "mermaid",
 | 
			
		||||
  "version": "10.1.0",
 | 
			
		||||
  "version": "10.2.0-rc.2",
 | 
			
		||||
  "description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
 | 
			
		||||
  "type": "module",
 | 
			
		||||
  "module": "./dist/mermaid.core.mjs",
 | 
			
		||||
@@ -52,21 +52,21 @@
 | 
			
		||||
    ]
 | 
			
		||||
  },
 | 
			
		||||
  "dependencies": {
 | 
			
		||||
    "@braintree/sanitize-url": "^6.0.0",
 | 
			
		||||
    "@khanacademy/simple-markdown": "^0.8.6",
 | 
			
		||||
    "@braintree/sanitize-url": "^6.0.2",
 | 
			
		||||
    "@khanacademy/simple-markdown": "^0.9.0",
 | 
			
		||||
    "cytoscape": "^3.23.0",
 | 
			
		||||
    "cytoscape-cose-bilkent": "^4.1.0",
 | 
			
		||||
    "cytoscape-fcose": "^2.1.0",
 | 
			
		||||
    "d3": "^7.4.0",
 | 
			
		||||
    "dagre-d3-es": "7.0.10",
 | 
			
		||||
    "dayjs": "^1.11.7",
 | 
			
		||||
    "dompurify": "2.4.5",
 | 
			
		||||
    "dompurify": "3.0.2",
 | 
			
		||||
    "elkjs": "^0.8.2",
 | 
			
		||||
    "katex": "^0.15.2",
 | 
			
		||||
    "khroma": "^2.0.0",
 | 
			
		||||
    "lodash-es": "^4.17.21",
 | 
			
		||||
    "non-layered-tidy-tree-layout": "^2.0.2",
 | 
			
		||||
    "stylis": "^4.1.2",
 | 
			
		||||
    "stylis": "^4.1.3",
 | 
			
		||||
    "ts-dedent": "^2.2.0",
 | 
			
		||||
    "uuid": "^9.0.0",
 | 
			
		||||
    "web-worker": "^1.2.0"
 | 
			
		||||
@@ -74,43 +74,46 @@
 | 
			
		||||
  "devDependencies": {
 | 
			
		||||
    "@types/cytoscape": "^3.19.9",
 | 
			
		||||
    "@types/d3": "^7.4.0",
 | 
			
		||||
    "@types/dompurify": "^2.4.0",
 | 
			
		||||
    "@types/jsdom": "^21.0.0",
 | 
			
		||||
    "@types/dompurify": "^3.0.2",
 | 
			
		||||
    "@types/jsdom": "^21.1.1",
 | 
			
		||||
    "@types/lodash-es": "^4.17.7",
 | 
			
		||||
    "@types/micromatch": "^4.0.2",
 | 
			
		||||
    "@types/prettier": "^2.7.1",
 | 
			
		||||
    "@types/prettier": "^2.7.2",
 | 
			
		||||
    "@types/stylis": "^4.0.2",
 | 
			
		||||
    "@types/uuid": "^9.0.0",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.42.1",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.42.1",
 | 
			
		||||
    "@types/uuid": "^9.0.1",
 | 
			
		||||
    "@typescript-eslint/eslint-plugin": "^5.59.0",
 | 
			
		||||
    "@typescript-eslint/parser": "^5.59.0",
 | 
			
		||||
    "chokidar": "^3.5.3",
 | 
			
		||||
    "concurrently": "^7.5.0",
 | 
			
		||||
    "concurrently": "^8.0.1",
 | 
			
		||||
    "coveralls": "^3.1.1",
 | 
			
		||||
    "cpy-cli": "^4.2.0",
 | 
			
		||||
    "cspell": "^6.14.3",
 | 
			
		||||
    "cspell": "^6.31.1",
 | 
			
		||||
    "csstree-validator": "^3.0.0",
 | 
			
		||||
    "globby": "^13.1.2",
 | 
			
		||||
    "globby": "^13.1.4",
 | 
			
		||||
    "jison": "^0.4.18",
 | 
			
		||||
    "js-base64": "^3.7.2",
 | 
			
		||||
    "jsdom": "^21.0.0",
 | 
			
		||||
    "js-base64": "^3.7.5",
 | 
			
		||||
    "jsdom": "^21.1.1",
 | 
			
		||||
    "micromatch": "^4.0.5",
 | 
			
		||||
    "path-browserify": "^1.0.1",
 | 
			
		||||
    "prettier": "^2.7.1",
 | 
			
		||||
    "prettier": "^2.8.8",
 | 
			
		||||
    "remark": "^14.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",
 | 
			
		||||
    "rimraf": "^5.0.0",
 | 
			
		||||
    "start-server-and-test": "^2.0.0",
 | 
			
		||||
    "typedoc": "^0.24.5",
 | 
			
		||||
    "typedoc-plugin-markdown": "^3.15.2",
 | 
			
		||||
    "typescript": "^5.0.4",
 | 
			
		||||
    "unist-util-flatmap": "^1.0.0",
 | 
			
		||||
    "vitepress": "^1.0.0-alpha.46",
 | 
			
		||||
    "vitepress-plugin-search": "^1.0.4-alpha.19"
 | 
			
		||||
    "vitepress": "^1.0.0-alpha.72",
 | 
			
		||||
    "vitepress-plugin-search": "^1.0.4-alpha.20"
 | 
			
		||||
  },
 | 
			
		||||
  "files": [
 | 
			
		||||
    "dist",
 | 
			
		||||
    "dist/",
 | 
			
		||||
    "README.md"
 | 
			
		||||
  ],
 | 
			
		||||
  "sideEffects": false
 | 
			
		||||
  "sideEffects": false,
 | 
			
		||||
  "publishConfig": {
 | 
			
		||||
    "access": "public"
 | 
			
		||||
  }
 | 
			
		||||
}
 | 
			
		||||
 
 | 
			
		||||
@@ -1,28 +1,9 @@
 | 
			
		||||
import common from '../common/common.js';
 | 
			
		||||
import * as svgDrawCommon from '../common/svgDrawCommon';
 | 
			
		||||
import { sanitizeUrl } from '@braintree/sanitize-url';
 | 
			
		||||
 | 
			
		||||
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.attrs !== 'undefined' && rectData.attrs !== null) {
 | 
			
		||||
    for (let attrKey in rectData.attrs) {
 | 
			
		||||
      rectElem.attr(attrKey, rectData.attrs[attrKey]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (rectData.class !== 'undefined') {
 | 
			
		||||
    rectElem.attr('class', rectData.class);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return rectElem;
 | 
			
		||||
  return svgDrawCommon.drawRect(elem, rectData);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const drawImage = function (elem, width, height, x, y, link) {
 | 
			
		||||
@@ -236,7 +217,8 @@ export const drawC4Shape = function (elem, c4Shape, conf) {
 | 
			
		||||
 | 
			
		||||
  // <rect fill="#08427B" height="119.2188" rx="2.5" ry="2.5" stroke="#073B6F" stroke-width="0.5" width="110" x="120" y="7"/>
 | 
			
		||||
  // draw rect of c4Shape
 | 
			
		||||
  const rect = getNoteRect();
 | 
			
		||||
  const rect = svgDrawCommon.getNoteRect();
 | 
			
		||||
 | 
			
		||||
  switch (c4Shape.typeC4Shape.text) {
 | 
			
		||||
    case 'person':
 | 
			
		||||
    case 'external_person':
 | 
			
		||||
@@ -479,6 +461,7 @@ export const insertArrowHead = function (elem) {
 | 
			
		||||
    .append('path')
 | 
			
		||||
    .attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const insertArrowEnd = function (elem) {
 | 
			
		||||
  elem
 | 
			
		||||
    .append('defs')
 | 
			
		||||
@@ -493,6 +476,7 @@ export const insertArrowEnd = function (elem) {
 | 
			
		||||
    .append('path')
 | 
			
		||||
    .attr('d', 'M 10 0 L 0 5 L 10 10 z'); // this is actual shape for arrowhead
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Setup arrow head and define the marker. The result is appended to the svg.
 | 
			
		||||
 *
 | 
			
		||||
@@ -511,6 +495,7 @@ export const insertArrowFilledHead = function (elem) {
 | 
			
		||||
    .append('path')
 | 
			
		||||
    .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Setup node number. The result is appended to the svg.
 | 
			
		||||
 *
 | 
			
		||||
@@ -532,6 +517,7 @@ export const insertDynamicNumber = function (elem) {
 | 
			
		||||
    .attr('r', 6);
 | 
			
		||||
  // .style("fill", '#f00');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Setup arrow head and define the marker. The result is appended to the svg.
 | 
			
		||||
 *
 | 
			
		||||
@@ -568,20 +554,6 @@ export const insertArrowCrossHead = function (elem) {
 | 
			
		||||
  // this is actual shape for arrowhead
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getNoteRect = function () {
 | 
			
		||||
  return {
 | 
			
		||||
    x: 0,
 | 
			
		||||
    y: 0,
 | 
			
		||||
    fill: '#EDF2AE',
 | 
			
		||||
    stroke: '#666',
 | 
			
		||||
    width: 100,
 | 
			
		||||
    anchor: 'start',
 | 
			
		||||
    height: 100,
 | 
			
		||||
    rx: 0,
 | 
			
		||||
    ry: 0,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getC4ShapeFont = (cnf, typeC4Shape) => {
 | 
			
		||||
  return {
 | 
			
		||||
    fontFamily: cnf[typeC4Shape + 'FontFamily'],
 | 
			
		||||
@@ -714,6 +686,4 @@ export default {
 | 
			
		||||
  insertDatabaseIcon,
 | 
			
		||||
  insertComputerIcon,
 | 
			
		||||
  insertClockIcon,
 | 
			
		||||
  getNoteRect,
 | 
			
		||||
  sanitizeUrl, // TODO why is this exported?
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -14,7 +14,14 @@ import {
 | 
			
		||||
  setDiagramTitle,
 | 
			
		||||
  getDiagramTitle,
 | 
			
		||||
} from '../../commonDb.js';
 | 
			
		||||
import { ClassRelation, ClassNode, ClassNote, ClassMap } from './classTypes.js';
 | 
			
		||||
import {
 | 
			
		||||
  ClassRelation,
 | 
			
		||||
  ClassNode,
 | 
			
		||||
  ClassNote,
 | 
			
		||||
  ClassMap,
 | 
			
		||||
  NamespaceMap,
 | 
			
		||||
  NamespaceNode,
 | 
			
		||||
} from './classTypes.js';
 | 
			
		||||
 | 
			
		||||
const MERMAID_DOM_ID_PREFIX = 'classId-';
 | 
			
		||||
 | 
			
		||||
@@ -22,6 +29,8 @@ let relations: ClassRelation[] = [];
 | 
			
		||||
let classes: ClassMap = {};
 | 
			
		||||
let notes: ClassNote[] = [];
 | 
			
		||||
let classCounter = 0;
 | 
			
		||||
let namespaces: NamespaceMap = {};
 | 
			
		||||
let namespaceCounter = 0;
 | 
			
		||||
 | 
			
		||||
let functions: any[] = [];
 | 
			
		||||
 | 
			
		||||
@@ -100,12 +109,15 @@ export const clear = function () {
 | 
			
		||||
  notes = [];
 | 
			
		||||
  functions = [];
 | 
			
		||||
  functions.push(setupToolTips);
 | 
			
		||||
  namespaces = {};
 | 
			
		||||
  namespaceCounter = 0;
 | 
			
		||||
  commonClear();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getClass = function (id: string) {
 | 
			
		||||
  return classes[id];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getClasses = function () {
 | 
			
		||||
  return classes;
 | 
			
		||||
};
 | 
			
		||||
@@ -170,9 +182,10 @@ export const addMember = function (className: string, member: string) {
 | 
			
		||||
    const memberString = member.trim();
 | 
			
		||||
 | 
			
		||||
    if (memberString.startsWith('<<') && memberString.endsWith('>>')) {
 | 
			
		||||
      // Remove leading and trailing brackets
 | 
			
		||||
      // its an annotation
 | 
			
		||||
      theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2)));
 | 
			
		||||
    } else if (memberString.indexOf(')') > 0) {
 | 
			
		||||
      //its a method
 | 
			
		||||
      theClass.methods.push(sanitizeText(memberString));
 | 
			
		||||
    } else if (memberString) {
 | 
			
		||||
      theClass.members.push(sanitizeText(memberString));
 | 
			
		||||
@@ -234,7 +247,12 @@ const setTooltip = function (ids: string, tooltip?: string) {
 | 
			
		||||
    }
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
export const getTooltip = function (id: string) {
 | 
			
		||||
 | 
			
		||||
export const getTooltip = function (id: string, namespace?: string) {
 | 
			
		||||
  if (namespace) {
 | 
			
		||||
    return namespaces[namespace].classes[id].tooltip;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return classes[id].tooltip;
 | 
			
		||||
};
 | 
			
		||||
/**
 | 
			
		||||
@@ -392,6 +410,52 @@ const setDirection = (dir: string) => {
 | 
			
		||||
  direction = dir;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Function called by parser when a namespace definition has been found.
 | 
			
		||||
 *
 | 
			
		||||
 * @param id - Id of the namespace to add
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
export const addNamespace = function (id: string) {
 | 
			
		||||
  if (namespaces[id] !== undefined) {
 | 
			
		||||
    return;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  namespaces[id] = {
 | 
			
		||||
    id: id,
 | 
			
		||||
    classes: {},
 | 
			
		||||
    children: {},
 | 
			
		||||
    domId: MERMAID_DOM_ID_PREFIX + id + '-' + namespaceCounter,
 | 
			
		||||
  } as NamespaceNode;
 | 
			
		||||
 | 
			
		||||
  namespaceCounter++;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getNamespace = function (name: string): NamespaceNode {
 | 
			
		||||
  return namespaces[name];
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const getNamespaces = function (): NamespaceMap {
 | 
			
		||||
  return namespaces;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Function called by parser when a namespace definition has been found.
 | 
			
		||||
 *
 | 
			
		||||
 * @param id - Id of the namespace to add
 | 
			
		||||
 * @param classNames - Ids of the class to add
 | 
			
		||||
 * @public
 | 
			
		||||
 */
 | 
			
		||||
export const addClassesToNamespace = function (id: string, classNames: string[]) {
 | 
			
		||||
  if (namespaces[id] !== undefined) {
 | 
			
		||||
    classNames.map((className) => {
 | 
			
		||||
      namespaces[id].classes[className] = classes[className];
 | 
			
		||||
      delete classes[className];
 | 
			
		||||
      classCounter--;
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  parseDirective,
 | 
			
		||||
  setAccTitle,
 | 
			
		||||
@@ -425,4 +489,8 @@ export default {
 | 
			
		||||
  setDiagramTitle,
 | 
			
		||||
  getDiagramTitle,
 | 
			
		||||
  setClassLabel,
 | 
			
		||||
  addNamespace,
 | 
			
		||||
  addClassesToNamespace,
 | 
			
		||||
  getNamespace,
 | 
			
		||||
  getNamespaces,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -1,13 +0,0 @@
 | 
			
		||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
 | 
			
		||||
const fs = require('fs');
 | 
			
		||||
 | 
			
		||||
import { LALRGenerator } from 'jison';
 | 
			
		||||
 | 
			
		||||
describe('class diagram grammar', function () {
 | 
			
		||||
  it('should introduce no new conflicts', function () {
 | 
			
		||||
    const file = require.resolve('./parser/classDiagram.jison');
 | 
			
		||||
    const grammarSource = fs.readFileSync(file, 'utf8');
 | 
			
		||||
    const grammarParser = new LALRGenerator(grammarSource, {});
 | 
			
		||||
    expect(grammarParser.conflicts < 16).toBe(true);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -0,0 +1,16 @@
 | 
			
		||||
import { readFile } from 'node:fs/promises';
 | 
			
		||||
// @ts-ignore - no types
 | 
			
		||||
import { LALRGenerator } from 'jison';
 | 
			
		||||
import path from 'path';
 | 
			
		||||
 | 
			
		||||
const getAbsolutePath = (relativePath: string) => {
 | 
			
		||||
  return new URL(path.join(__dirname, relativePath)).pathname;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
describe('class diagram grammar', function () {
 | 
			
		||||
  it('should have no conflicts', async function () {
 | 
			
		||||
    const grammarSource = await readFile(getAbsolutePath('parser/classDiagram.jison'), 'utf8');
 | 
			
		||||
    const grammarParser = new LALRGenerator(grammarSource, {});
 | 
			
		||||
    expect(grammarParser.conflicts).toBe(0);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
							
								
								
									
										78
									
								
								packages/mermaid/src/diagrams/class/classParser.spec.ts
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										78
									
								
								packages/mermaid/src/diagrams/class/classParser.spec.ts
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,78 @@
 | 
			
		||||
import { setConfig } from '../../config.js';
 | 
			
		||||
import classDB from './classDb.js';
 | 
			
		||||
// @ts-ignore - no types in jison
 | 
			
		||||
import classDiagram from './parser/classDiagram.jison';
 | 
			
		||||
 | 
			
		||||
setConfig({
 | 
			
		||||
  securityLevel: 'strict',
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('when parsing class diagram', function () {
 | 
			
		||||
  beforeEach(function () {
 | 
			
		||||
    classDiagram.parser.yy = classDB;
 | 
			
		||||
    classDiagram.parser.yy.clear();
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should parse diagram with direction', () => {
 | 
			
		||||
    classDiagram.parser.parse(`classDiagram
 | 
			
		||||
        direction TB
 | 
			
		||||
        class Student {
 | 
			
		||||
          -idCard : IdCard
 | 
			
		||||
        }
 | 
			
		||||
        class IdCard{
 | 
			
		||||
          -id : int
 | 
			
		||||
          -name : string
 | 
			
		||||
        }
 | 
			
		||||
        class Bike{
 | 
			
		||||
          -id : int
 | 
			
		||||
          -name : string
 | 
			
		||||
        }
 | 
			
		||||
        Student "1" --o "1" IdCard : carries
 | 
			
		||||
        Student "1" --o "1" Bike : rides`);
 | 
			
		||||
 | 
			
		||||
    expect(Object.keys(classDB.getClasses()).length).toBe(3);
 | 
			
		||||
    expect(classDB.getClasses().Student).toMatchInlineSnapshot(`
 | 
			
		||||
      {
 | 
			
		||||
        "annotations": [],
 | 
			
		||||
        "cssClasses": [],
 | 
			
		||||
        "domId": "classId-Student-0",
 | 
			
		||||
        "id": "Student",
 | 
			
		||||
        "label": "Student",
 | 
			
		||||
        "members": [
 | 
			
		||||
          "-idCard : IdCard",
 | 
			
		||||
        ],
 | 
			
		||||
        "methods": [],
 | 
			
		||||
        "type": "",
 | 
			
		||||
      }
 | 
			
		||||
    `);
 | 
			
		||||
    expect(classDB.getRelations().length).toBe(2);
 | 
			
		||||
    expect(classDB.getRelations()).toMatchInlineSnapshot(`
 | 
			
		||||
      [
 | 
			
		||||
        {
 | 
			
		||||
          "id1": "Student",
 | 
			
		||||
          "id2": "IdCard",
 | 
			
		||||
          "relation": {
 | 
			
		||||
            "lineType": 0,
 | 
			
		||||
            "type1": "none",
 | 
			
		||||
            "type2": 0,
 | 
			
		||||
          },
 | 
			
		||||
          "relationTitle1": "1",
 | 
			
		||||
          "relationTitle2": "1",
 | 
			
		||||
          "title": "carries",
 | 
			
		||||
        },
 | 
			
		||||
        {
 | 
			
		||||
          "id1": "Student",
 | 
			
		||||
          "id2": "Bike",
 | 
			
		||||
          "relation": {
 | 
			
		||||
            "lineType": 0,
 | 
			
		||||
            "type1": "none",
 | 
			
		||||
            "type2": 0,
 | 
			
		||||
          },
 | 
			
		||||
          "relationTitle1": "1",
 | 
			
		||||
          "relationTitle2": "1",
 | 
			
		||||
          "title": "rides",
 | 
			
		||||
        },
 | 
			
		||||
      ]
 | 
			
		||||
    `);
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
@@ -8,7 +8,7 @@ import utils from '../../utils.js';
 | 
			
		||||
import { interpolateToCurve, getStylesFromArray } from '../../utils.js';
 | 
			
		||||
import { setupGraphViewbox } from '../../setupGraphViewbox.js';
 | 
			
		||||
import common from '../common/common.js';
 | 
			
		||||
import { ClassRelation, ClassNote, ClassMap, EdgeData } from './classTypes.js';
 | 
			
		||||
import { ClassRelation, ClassNote, ClassMap, EdgeData, NamespaceMap } from './classTypes.js';
 | 
			
		||||
 | 
			
		||||
const sanitizeText = (txt: string) => common.sanitizeText(txt, getConfig());
 | 
			
		||||
 | 
			
		||||
@@ -19,6 +19,59 @@ let conf = {
 | 
			
		||||
  curve: undefined,
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
interface RectParameters {
 | 
			
		||||
  id: string;
 | 
			
		||||
  shape: 'rect';
 | 
			
		||||
  labelStyle: string;
 | 
			
		||||
  domId: string;
 | 
			
		||||
  labelText: string;
 | 
			
		||||
  padding: number | undefined;
 | 
			
		||||
  style?: string;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Function that adds the vertices found during parsing to the graph to be rendered.
 | 
			
		||||
 *
 | 
			
		||||
 * @param namespaces - Object containing the vertices.
 | 
			
		||||
 * @param g - The graph that is to be drawn.
 | 
			
		||||
 * @param _id - id of the graph
 | 
			
		||||
 * @param diagObj - The diagram object
 | 
			
		||||
 */
 | 
			
		||||
export const addNamespaces = function (
 | 
			
		||||
  namespaces: NamespaceMap,
 | 
			
		||||
  g: graphlib.Graph,
 | 
			
		||||
  _id: string,
 | 
			
		||||
  diagObj: any
 | 
			
		||||
) {
 | 
			
		||||
  const keys = Object.keys(namespaces);
 | 
			
		||||
  log.info('keys:', keys);
 | 
			
		||||
  log.info(namespaces);
 | 
			
		||||
 | 
			
		||||
  // Iterate through each item in the vertex object (containing all the vertices found) in the graph definition
 | 
			
		||||
  keys.forEach(function (id) {
 | 
			
		||||
    const vertex = namespaces[id];
 | 
			
		||||
 | 
			
		||||
    // parent node must be one of [rect, roundedWithTitle, noteGroup, divider]
 | 
			
		||||
    const shape = 'rect';
 | 
			
		||||
 | 
			
		||||
    const node: RectParameters = {
 | 
			
		||||
      shape: shape,
 | 
			
		||||
      id: vertex.id,
 | 
			
		||||
      domId: vertex.domId,
 | 
			
		||||
      labelText: sanitizeText(vertex.id),
 | 
			
		||||
      labelStyle: '',
 | 
			
		||||
      style: 'fill: none; stroke: black',
 | 
			
		||||
      // TODO V10: Flowchart ? Keeping flowchart for backwards compatibility. Remove in next major release
 | 
			
		||||
      padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    g.setNode(vertex.id, node);
 | 
			
		||||
    addClasses(vertex.classes, g, _id, diagObj, vertex.id);
 | 
			
		||||
 | 
			
		||||
    log.info('setNode', node);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Function that adds the vertices found during parsing to the graph to be rendered.
 | 
			
		||||
 *
 | 
			
		||||
@@ -26,12 +79,14 @@ let conf = {
 | 
			
		||||
 * @param g - The graph that is to be drawn.
 | 
			
		||||
 * @param _id - id of the graph
 | 
			
		||||
 * @param diagObj - The diagram object
 | 
			
		||||
 * @param parent - id of the parent namespace, if it exists
 | 
			
		||||
 */
 | 
			
		||||
export const addClasses = function (
 | 
			
		||||
  classes: ClassMap,
 | 
			
		||||
  g: graphlib.Graph,
 | 
			
		||||
  _id: string,
 | 
			
		||||
  diagObj: any
 | 
			
		||||
  diagObj: any,
 | 
			
		||||
  parent?: string
 | 
			
		||||
) {
 | 
			
		||||
  const keys = Object.keys(classes);
 | 
			
		||||
  log.info('keys:', keys);
 | 
			
		||||
@@ -55,6 +110,7 @@ export const addClasses = function (
 | 
			
		||||
    const vertexText = vertex.label ?? vertex.id;
 | 
			
		||||
    const radius = 0;
 | 
			
		||||
    const shape = 'class_box';
 | 
			
		||||
 | 
			
		||||
    // Add the node
 | 
			
		||||
    const node = {
 | 
			
		||||
      labelStyle: styles.labelStyle,
 | 
			
		||||
@@ -67,7 +123,7 @@ export const addClasses = function (
 | 
			
		||||
      style: styles.style,
 | 
			
		||||
      id: vertex.id,
 | 
			
		||||
      domId: vertex.domId,
 | 
			
		||||
      tooltip: diagObj.db.getTooltip(vertex.id) || '',
 | 
			
		||||
      tooltip: diagObj.db.getTooltip(vertex.id, parent) || '',
 | 
			
		||||
      haveCallback: vertex.haveCallback,
 | 
			
		||||
      link: vertex.link,
 | 
			
		||||
      width: vertex.type === 'group' ? 500 : undefined,
 | 
			
		||||
@@ -76,6 +132,11 @@ export const addClasses = function (
 | 
			
		||||
      padding: getConfig().flowchart?.padding ?? getConfig().class?.padding,
 | 
			
		||||
    };
 | 
			
		||||
    g.setNode(vertex.id, node);
 | 
			
		||||
 | 
			
		||||
    if (parent) {
 | 
			
		||||
      g.setParent(vertex.id, parent);
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    log.info('setNode', node);
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
@@ -275,10 +336,12 @@ export const draw = async function (text: string, id: string, _version: string,
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
  // Fetch the vertices/nodes and edges/links from the parsed graph definition
 | 
			
		||||
  const namespaces: NamespaceMap = diagObj.db.getNamespaces();
 | 
			
		||||
  const classes: ClassMap = diagObj.db.getClasses();
 | 
			
		||||
  const relations: ClassRelation[] = diagObj.db.getRelations();
 | 
			
		||||
  const notes: ClassNote[] = diagObj.db.getNotes();
 | 
			
		||||
  log.info(relations);
 | 
			
		||||
  addNamespaces(namespaces, g, id, diagObj);
 | 
			
		||||
  addClasses(classes, g, id, diagObj);
 | 
			
		||||
  addRelations(relations, g);
 | 
			
		||||
  addNotes(notes, g, relations.length + 1, classes);
 | 
			
		||||
 
 | 
			
		||||
@@ -52,4 +52,13 @@ export type ClassRelation = {
 | 
			
		||||
    lineType: number;
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export interface NamespaceNode {
 | 
			
		||||
  id: string;
 | 
			
		||||
  domId: string;
 | 
			
		||||
  classes: ClassMap;
 | 
			
		||||
  children: NamespaceMap;
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
export type ClassMap = Record<string, ClassNode>;
 | 
			
		||||
export type NamespaceMap = Record<string, NamespaceNode>;
 | 
			
		||||
 
 | 
			
		||||
@@ -19,6 +19,10 @@
 | 
			
		||||
%x acc_title
 | 
			
		||||
%x acc_descr
 | 
			
		||||
%x acc_descr_multiline
 | 
			
		||||
%x class
 | 
			
		||||
%x class-body
 | 
			
		||||
%x namespace
 | 
			
		||||
%x namespace-body
 | 
			
		||||
%%
 | 
			
		||||
\%\%\{                                                          { this.begin('open_directive'); return 'open_directive'; }
 | 
			
		||||
.*direction\s+TB[^\n]*                                          return 'direction_tb';
 | 
			
		||||
@@ -41,35 +45,41 @@ accDescr\s*"{"\s*                                { this.begin("acc_descr_multili
 | 
			
		||||
 | 
			
		||||
\s*(\r?\n)+                return 'NEWLINE';
 | 
			
		||||
\s+                     /* skip whitespace */
 | 
			
		||||
 | 
			
		||||
"classDiagram-v2"       return 'CLASS_DIAGRAM';
 | 
			
		||||
"classDiagram"          return 'CLASS_DIAGRAM';
 | 
			
		||||
[{]                     { this.begin("struct"); /*console.log('Starting struct');*/ return 'STRUCT_START';}
 | 
			
		||||
<INITIAL,struct>"[*]"   { /*console.log('EDGE_STATE=',yytext);*/ return 'EDGE_STATE';}
 | 
			
		||||
<struct><<EOF>>         return "EOF_IN_STRUCT";
 | 
			
		||||
<struct>[{]             return "OPEN_IN_STRUCT";
 | 
			
		||||
<struct>[}]             { /*console.log('Ending struct');*/this.popState(); return 'STRUCT_STOP';}}
 | 
			
		||||
<struct>[\n]            /* nothing */
 | 
			
		||||
<struct>[^{}\n]*        { /*console.log('lex-member: ' + yytext);*/  return "MEMBER";}
 | 
			
		||||
"[*]"                   return 'EDGE_STATE';
 | 
			
		||||
 | 
			
		||||
"class"               return 'CLASS';
 | 
			
		||||
"cssClass"            return 'CSSCLASS';
 | 
			
		||||
"callback"            return 'CALLBACK';
 | 
			
		||||
"link"                return 'LINK';
 | 
			
		||||
"click"               return 'CLICK';
 | 
			
		||||
"note for"            return 'NOTE_FOR';
 | 
			
		||||
"note"                return 'NOTE';
 | 
			
		||||
"<<"                  return 'ANNOTATION_START';
 | 
			
		||||
">>"                  return 'ANNOTATION_END';
 | 
			
		||||
[~]                   this.begin("generic");
 | 
			
		||||
<generic>[~]          this.popState();
 | 
			
		||||
<generic>[^~]*        return "GENERICTYPE";
 | 
			
		||||
["]                   this.begin("string");
 | 
			
		||||
<string>["]           this.popState();
 | 
			
		||||
<string>[^"]*         return "STR";
 | 
			
		||||
<INITIAL,namespace>"namespace"  { this.begin('namespace'); return 'NAMESPACE'; }
 | 
			
		||||
<namespace>\s*(\r?\n)+          { this.popState(); return 'NEWLINE'; }
 | 
			
		||||
<namespace>\s+                  /* skip whitespace */
 | 
			
		||||
<namespace>[{]                  { this.begin("namespace-body"); return 'STRUCT_START';}
 | 
			
		||||
<namespace-body>[}]             { this.popState(); return 'STRUCT_STOP'; }
 | 
			
		||||
<namespace-body><<EOF>>         return "EOF_IN_STRUCT";
 | 
			
		||||
<namespace-body>\s*(\r?\n)+     return 'NEWLINE';
 | 
			
		||||
<namespace-body>\s+             /* skip whitespace */
 | 
			
		||||
<namespace-body>"[*]"           return 'EDGE_STATE';
 | 
			
		||||
 | 
			
		||||
[`]                   this.begin("bqstring");
 | 
			
		||||
<bqstring>[`]         this.popState();
 | 
			
		||||
<bqstring>[^`]+       return "BQUOTE_STR";
 | 
			
		||||
<INITIAL,namespace-body>"class"     { this.begin('class'); return 'CLASS';}
 | 
			
		||||
<class>\s*(\r?\n)+          { this.popState(); return 'NEWLINE'; }
 | 
			
		||||
<class>\s+                  /* skip whitespace */
 | 
			
		||||
<class>[}]                  { this.popState(); this.popState(); return 'STRUCT_STOP';}
 | 
			
		||||
<class>[{]                  { this.begin("class-body"); return 'STRUCT_START';}
 | 
			
		||||
<class-body>[}]             { this.popState(); return 'STRUCT_STOP'; }
 | 
			
		||||
<class-body><<EOF>>         return "EOF_IN_STRUCT";
 | 
			
		||||
<class-body>"[*]"           { return 'EDGE_STATE';}
 | 
			
		||||
<class-body>[{]             return "OPEN_IN_STRUCT";
 | 
			
		||||
<class-body>[\n]            /* nothing */
 | 
			
		||||
<class-body>[^{}\n]*        { return "MEMBER";}
 | 
			
		||||
 | 
			
		||||
<*>"cssClass"           return 'CSSCLASS';
 | 
			
		||||
<*>"callback"           return 'CALLBACK';
 | 
			
		||||
<*>"link"               return 'LINK';
 | 
			
		||||
<*>"click"              return 'CLICK';
 | 
			
		||||
<*>"note for"           return 'NOTE_FOR';
 | 
			
		||||
<*>"note"               return 'NOTE';
 | 
			
		||||
<*>"<<"                 return 'ANNOTATION_START';
 | 
			
		||||
<*>">>"                 return 'ANNOTATION_END';
 | 
			
		||||
 | 
			
		||||
/*
 | 
			
		||||
---interactivity command---
 | 
			
		||||
@@ -77,7 +87,7 @@ accDescr\s*"{"\s*                                { this.begin("acc_descr_multili
 | 
			
		||||
line was introduced with 'click'.
 | 
			
		||||
'href "<link>"' attaches the specified link to the node that was specified by 'click'.
 | 
			
		||||
*/
 | 
			
		||||
"href"[\s]+["]          this.begin("href");
 | 
			
		||||
<*>"href"[\s]+["]       this.begin("href");
 | 
			
		||||
<href>["]               this.popState();
 | 
			
		||||
<href>[^"]*             return 'HREF';
 | 
			
		||||
 | 
			
		||||
@@ -89,41 +99,53 @@ the line was introduced with 'click'.
 | 
			
		||||
arguments to the node that was specified by 'click'.
 | 
			
		||||
Function arguments are optional: 'call <callback_name>()' simply executes 'callback_name' without any arguments.
 | 
			
		||||
*/
 | 
			
		||||
"call"[\s]+              this.begin("callback_name");
 | 
			
		||||
<*>"call"[\s]+              this.begin("callback_name");
 | 
			
		||||
<callback_name>\([\s]*\) this.popState();
 | 
			
		||||
<callback_name>\(        this.popState(); this.begin("callback_args");
 | 
			
		||||
<callback_name>[^(]*     return 'CALLBACK_NAME';
 | 
			
		||||
<callback_args>\)        this.popState();
 | 
			
		||||
<callback_args>[^)]*     return 'CALLBACK_ARGS';
 | 
			
		||||
 | 
			
		||||
"_self"               return 'LINK_TARGET';
 | 
			
		||||
"_blank"              return 'LINK_TARGET';
 | 
			
		||||
"_parent"             return 'LINK_TARGET';
 | 
			
		||||
"_top"                return 'LINK_TARGET';
 | 
			
		||||
<generic>[~]            this.popState();
 | 
			
		||||
<generic>[^~]*          return "GENERICTYPE";
 | 
			
		||||
<*>[~]                  this.begin("generic");
 | 
			
		||||
 | 
			
		||||
\s*\<\|               return 'EXTENSION';
 | 
			
		||||
\s*\|\>               return 'EXTENSION';
 | 
			
		||||
\s*\>                 return 'DEPENDENCY';
 | 
			
		||||
\s*\<                 return 'DEPENDENCY';
 | 
			
		||||
\s*\*                 return 'COMPOSITION';
 | 
			
		||||
\s*o                  return 'AGGREGATION';
 | 
			
		||||
\s*\(\)               return 'LOLLIPOP';
 | 
			
		||||
\-\-                  return 'LINE';
 | 
			
		||||
\.\.                  return 'DOTTED_LINE';
 | 
			
		||||
":"{1}[^:\n;]+        return 'LABEL';
 | 
			
		||||
":"{3}                return 'STYLE_SEPARATOR';
 | 
			
		||||
\-                    return 'MINUS';
 | 
			
		||||
"."                   return 'DOT';
 | 
			
		||||
\+                    return 'PLUS';
 | 
			
		||||
\%                    return 'PCT';
 | 
			
		||||
"="                   return 'EQUALS';
 | 
			
		||||
\=                    return 'EQUALS';
 | 
			
		||||
\w+                   return 'ALPHA';
 | 
			
		||||
"["                   return 'SQS';
 | 
			
		||||
"]"                   return 'SQE';
 | 
			
		||||
[!"#$%&'*+,-.`?\\/]   return 'PUNCTUATION';
 | 
			
		||||
[0-9]+                return 'NUM';
 | 
			
		||||
[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
 | 
			
		||||
<string>["]             this.popState();
 | 
			
		||||
<string>[^"]*           return "STR";
 | 
			
		||||
<*>["]                  this.begin("string");
 | 
			
		||||
 | 
			
		||||
<bqstring>[`]           this.popState();
 | 
			
		||||
<bqstring>[^`]+         return "BQUOTE_STR";
 | 
			
		||||
<*>[`]                  this.begin("bqstring");
 | 
			
		||||
 | 
			
		||||
<*>"_self"              return 'LINK_TARGET';
 | 
			
		||||
<*>"_blank"             return 'LINK_TARGET';
 | 
			
		||||
<*>"_parent"            return 'LINK_TARGET';
 | 
			
		||||
<*>"_top"               return 'LINK_TARGET';
 | 
			
		||||
 | 
			
		||||
<*>\s*\<\|              return 'EXTENSION';
 | 
			
		||||
<*>\s*\|\>              return 'EXTENSION';
 | 
			
		||||
<*>\s*\>                return 'DEPENDENCY';
 | 
			
		||||
<*>\s*\<                return 'DEPENDENCY';
 | 
			
		||||
<*>\s*\*                return 'COMPOSITION';
 | 
			
		||||
<*>\s*o                 return 'AGGREGATION';
 | 
			
		||||
<*>\s*\(\)              return 'LOLLIPOP';
 | 
			
		||||
<*>\-\-                 return 'LINE';
 | 
			
		||||
<*>\.\.                 return 'DOTTED_LINE';
 | 
			
		||||
<*>":"{1}[^:\n;]+       return 'LABEL';
 | 
			
		||||
<*>":"{3}               return 'STYLE_SEPARATOR';
 | 
			
		||||
<*>\-                   return 'MINUS';
 | 
			
		||||
<*>"."                  return 'DOT';
 | 
			
		||||
<*>\+                   return 'PLUS';
 | 
			
		||||
<*>\%                   return 'PCT';
 | 
			
		||||
<*>"="                  return 'EQUALS';
 | 
			
		||||
<*>\=                   return 'EQUALS';
 | 
			
		||||
<*>\w+                  return 'ALPHA';
 | 
			
		||||
<*>"["                  return 'SQS';
 | 
			
		||||
<*>"]"                  return 'SQE';
 | 
			
		||||
<*>[!"#$%&'*+,-.`?\\/]  return 'PUNCTUATION';
 | 
			
		||||
<*>[0-9]+               return 'NUM';
 | 
			
		||||
<*>[\u00AA\u00B5\u00BA\u00C0-\u00D6\u00D8-\u00F6]|
 | 
			
		||||
[\u00F8-\u02C1\u02C6-\u02D1\u02E0-\u02E4\u02EC\u02EE\u0370-\u0374\u0376\u0377]|
 | 
			
		||||
[\u037A-\u037D\u0386\u0388-\u038A\u038C\u038E-\u03A1\u03A3-\u03F5]|
 | 
			
		||||
[\u03F7-\u0481\u048A-\u0527\u0531-\u0556\u0559\u0561-\u0587\u05D0-\u05EA]|
 | 
			
		||||
@@ -184,9 +206,9 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
 | 
			
		||||
[\uFD50-\uFD8F\uFD92-\uFDC7\uFDF0-\uFDFB\uFE70-\uFE74\uFE76-\uFEFC]|
 | 
			
		||||
[\uFF21-\uFF3A\uFF41-\uFF5A\uFF66-\uFFBE\uFFC2-\uFFC7\uFFCA-\uFFCF]|
 | 
			
		||||
[\uFFD2-\uFFD7\uFFDA-\uFFDC]
 | 
			
		||||
                      return 'UNICODE_TEXT';
 | 
			
		||||
\s                    return 'SPACE';
 | 
			
		||||
<<EOF>>               return 'EOF';
 | 
			
		||||
                        return 'UNICODE_TEXT';
 | 
			
		||||
<*>\s                   return 'SPACE';
 | 
			
		||||
<*><<EOF>>              return 'EOF';
 | 
			
		||||
 | 
			
		||||
/lex
 | 
			
		||||
 | 
			
		||||
@@ -200,9 +222,8 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
 | 
			
		||||
 | 
			
		||||
start
 | 
			
		||||
    : mermaidDoc
 | 
			
		||||
    | statments
 | 
			
		||||
    | direction
 | 
			
		||||
    | directive start
 | 
			
		||||
    | statements
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
direction
 | 
			
		||||
@@ -255,30 +276,50 @@ classLabel
 | 
			
		||||
    : SQS STR SQE { $$=$2; }
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
namespaceName
 | 
			
		||||
    : alphaNumToken { $$=$1; }
 | 
			
		||||
    | alphaNumToken namespaceName { $$=$1+$2; }
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
className
 | 
			
		||||
    : alphaNumToken { $$=$1; }
 | 
			
		||||
    | classLiteralName { $$=$1; }
 | 
			
		||||
    | alphaNumToken className { $$=$1+$2; }
 | 
			
		||||
    | alphaNumToken GENERICTYPE { $$=$1+'~'+$2; }
 | 
			
		||||
    | classLiteralName GENERICTYPE { $$=$1+'~'+$2; }
 | 
			
		||||
    | alphaNumToken GENERICTYPE { $$=$1+'~'+$2+'~'; }
 | 
			
		||||
    | classLiteralName GENERICTYPE { $$=$1+'~'+$2+'~'; }
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
statement
 | 
			
		||||
    : relationStatement       { yy.addRelation($1); }
 | 
			
		||||
    | relationStatement LABEL { $1.title =  yy.cleanupLabel($2); yy.addRelation($1);        }
 | 
			
		||||
    | namespaceStatement
 | 
			
		||||
    | classStatement
 | 
			
		||||
    | methodStatement
 | 
			
		||||
    | annotationStatement
 | 
			
		||||
    | clickStatement
 | 
			
		||||
    | cssClassStatement
 | 
			
		||||
    | noteStatement
 | 
			
		||||
    | directive
 | 
			
		||||
    | direction
 | 
			
		||||
    | acc_title acc_title_value  { $$=$2.trim();yy.setAccTitle($$); }
 | 
			
		||||
    | acc_descr acc_descr_value  { $$=$2.trim();yy.setAccDescription($$); }
 | 
			
		||||
    | acc_descr_multiline_value  { $$=$1.trim();yy.setAccDescription($$); }
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
namespaceStatement
 | 
			
		||||
    : namespaceIdentifier STRUCT_START classStatements STRUCT_STOP          {yy.addClassesToNamespace($1, $3);}
 | 
			
		||||
    | namespaceIdentifier STRUCT_START NEWLINE classStatements STRUCT_STOP  {yy.addClassesToNamespace($1, $4);}
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
namespaceIdentifier
 | 
			
		||||
    : NAMESPACE namespaceName   {$$=$2; yy.addNamespace($2);}
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
classStatements
 | 
			
		||||
    : classStatement                            {$$=[$1]}
 | 
			
		||||
    | classStatement NEWLINE                    {$$=[$1]}
 | 
			
		||||
    | classStatement NEWLINE classStatements    {$3.unshift($1); $$=$3}
 | 
			
		||||
    ;
 | 
			
		||||
 | 
			
		||||
classStatement
 | 
			
		||||
    : classIdentifier                                    
 | 
			
		||||
    | classIdentifier STYLE_SEPARATOR alphaNumToken      {yy.setCssClass($1, $3);}
 | 
			
		||||
@@ -366,7 +407,7 @@ textToken      : textNoTagsToken | TAGSTART | TAGEND | '=='  | '--' | PCT | DEFA
 | 
			
		||||
 | 
			
		||||
textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ;
 | 
			
		||||
 | 
			
		||||
alphaNumToken  : UNICODE_TEXT | NUM | ALPHA;
 | 
			
		||||
alphaNumToken  : UNICODE_TEXT | NUM | ALPHA | MINUS;
 | 
			
		||||
 | 
			
		||||
classLiteralName : BQUOTE_STR;
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -199,11 +199,7 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
 | 
			
		||||
    isFirst = false;
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  let classTitleString = classDef.id;
 | 
			
		||||
 | 
			
		||||
  if (classDef.type !== undefined && classDef.type !== '') {
 | 
			
		||||
    classTitleString += '<' + classDef.type + '>';
 | 
			
		||||
  }
 | 
			
		||||
  let classTitleString = getClassTitleString(classDef);
 | 
			
		||||
 | 
			
		||||
  const classTitle = title.append('tspan').text(classTitleString).attr('class', 'title');
 | 
			
		||||
 | 
			
		||||
@@ -291,6 +287,16 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
 | 
			
		||||
  return classInfo;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getClassTitleString = function (classDef) {
 | 
			
		||||
  let classTitleString = classDef.id;
 | 
			
		||||
 | 
			
		||||
  if (classDef.type) {
 | 
			
		||||
    classTitleString += '<' + classDef.type + '>';
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return classTitleString;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Renders a note diagram
 | 
			
		||||
 *
 | 
			
		||||
@@ -355,6 +361,9 @@ export const drawNote = function (elem, note, conf, diagObj) {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const parseMember = function (text) {
 | 
			
		||||
  // Note: these two regular expressions don't parse the official UML syntax for attributes
 | 
			
		||||
  // and methods. They parse a Java-style syntax of the form
 | 
			
		||||
  // "String name" (for attributes) and "String name(int x)" for methods
 | 
			
		||||
  const fieldRegEx = /^([#+~-])?(\w+)(~\w+~|\[])?\s+(\w+) *([$*])?$/;
 | 
			
		||||
  const methodRegEx = /^([#+|~-])?(\w+) *\( *(.*)\) *([$*])? *(\w*[[\]|~]*\s*\w*~?)$/;
 | 
			
		||||
 | 
			
		||||
@@ -421,33 +430,48 @@ const buildLegacyDisplay = function (text) {
 | 
			
		||||
  let displayText = '';
 | 
			
		||||
  let cssStyle = '';
 | 
			
		||||
  let returnType = '';
 | 
			
		||||
 | 
			
		||||
  let visibility = '';
 | 
			
		||||
  let firstChar = text.substring(0, 1);
 | 
			
		||||
  let lastChar = text.substring(text.length - 1, text.length);
 | 
			
		||||
 | 
			
		||||
  if (firstChar.match(/[#+~-]/)) {
 | 
			
		||||
    visibility = firstChar;
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let noClassifierRe = /[\s\w)~]/;
 | 
			
		||||
  if (!lastChar.match(noClassifierRe)) {
 | 
			
		||||
    cssStyle = parseClassifier(lastChar);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let startIndex = visibility === '' ? 0 : 1;
 | 
			
		||||
  let endIndex = cssStyle === '' ? text.length : text.length - 1;
 | 
			
		||||
  text = text.substring(startIndex, endIndex);
 | 
			
		||||
 | 
			
		||||
  let methodStart = text.indexOf('(');
 | 
			
		||||
  let methodEnd = text.indexOf(')');
 | 
			
		||||
 | 
			
		||||
  if (methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length) {
 | 
			
		||||
    let visibility = '';
 | 
			
		||||
    let methodName = '';
 | 
			
		||||
 | 
			
		||||
    let firstChar = text.substring(0, 1);
 | 
			
		||||
    if (firstChar.match(/\w/)) {
 | 
			
		||||
      methodName = text.substring(0, methodStart).trim();
 | 
			
		||||
    } else {
 | 
			
		||||
      if (firstChar.match(/[#+~-]/)) {
 | 
			
		||||
        visibility = firstChar;
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      methodName = text.substring(1, methodStart).trim();
 | 
			
		||||
    }
 | 
			
		||||
    let methodName = text.substring(0, methodStart).trim();
 | 
			
		||||
 | 
			
		||||
    const parameters = text.substring(methodStart + 1, methodEnd);
 | 
			
		||||
    const classifier = text.substring(methodEnd + 1, 1);
 | 
			
		||||
    cssStyle = parseClassifier(text.substring(methodEnd + 1, methodEnd + 2));
 | 
			
		||||
 | 
			
		||||
    displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')';
 | 
			
		||||
 | 
			
		||||
    if (methodEnd < text.length) {
 | 
			
		||||
      returnType = text.substring(methodEnd + 2).trim();
 | 
			
		||||
      // special case: classifier after the closing parenthesis
 | 
			
		||||
      let potentialClassifier = text.substring(methodEnd + 1, methodEnd + 2);
 | 
			
		||||
      if (cssStyle === '' && !potentialClassifier.match(noClassifierRe)) {
 | 
			
		||||
        cssStyle = parseClassifier(potentialClassifier);
 | 
			
		||||
        returnType = text.substring(methodEnd + 2).trim();
 | 
			
		||||
      } else {
 | 
			
		||||
        returnType = text.substring(methodEnd + 1).trim();
 | 
			
		||||
      }
 | 
			
		||||
 | 
			
		||||
      if (returnType !== '') {
 | 
			
		||||
        if (returnType.charAt(0) === ':') {
 | 
			
		||||
          returnType = returnType.substring(1).trim();
 | 
			
		||||
        }
 | 
			
		||||
        returnType = ' : ' + parseGenericTypes(returnType);
 | 
			
		||||
        displayText += returnType;
 | 
			
		||||
      }
 | 
			
		||||
@@ -462,6 +486,7 @@ const buildLegacyDisplay = function (text) {
 | 
			
		||||
    cssStyle,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Adds a <tspan> for a member in a diagram
 | 
			
		||||
 *
 | 
			
		||||
@@ -502,6 +527,7 @@ const parseClassifier = function (classifier) {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export default {
 | 
			
		||||
  getClassTitleString,
 | 
			
		||||
  drawClass,
 | 
			
		||||
  drawEdge,
 | 
			
		||||
  drawNote,
 | 
			
		||||
 
 | 
			
		||||
@@ -1,8 +1,19 @@
 | 
			
		||||
import svgDraw from './svgDraw.js';
 | 
			
		||||
 | 
			
		||||
describe('class member Renderer, ', function () {
 | 
			
		||||
  describe('when parsing text to build method display string', function () {
 | 
			
		||||
    it('should handle simple method declaration', function () {
 | 
			
		||||
describe('given a string representing class method, ', function () {
 | 
			
		||||
  it('should handle class names with generics', function () {
 | 
			
		||||
    const classDef = {
 | 
			
		||||
      id: 'Car',
 | 
			
		||||
      type: 'T',
 | 
			
		||||
      label: 'Car',
 | 
			
		||||
    };
 | 
			
		||||
 | 
			
		||||
    let actual = svgDraw.getClassTitleString(classDef);
 | 
			
		||||
    expect(actual).toBe('Car<T>');
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('when parsing base method declaration', function () {
 | 
			
		||||
    it('should handle simple declaration', function () {
 | 
			
		||||
      const str = 'foo()';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
@@ -10,71 +21,7 @@ describe('class member Renderer, ', function () {
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle public visibility', function () {
 | 
			
		||||
      const str = '+foo()';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('+foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle private visibility', function () {
 | 
			
		||||
      const str = '-foo()';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('-foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle protected visibility', function () {
 | 
			
		||||
      const str = '#foo()';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('#foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle package/internal visibility', function () {
 | 
			
		||||
      const str = '~foo()';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('~foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should ignore unknown character for visibility', function () {
 | 
			
		||||
      const str = '!foo()';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle abstract method classifier', function () {
 | 
			
		||||
      const str = 'foo()*';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('font-style:italic;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle static method classifier', function () {
 | 
			
		||||
      const str = 'foo()$';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('text-decoration:underline;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should ignore unknown character for classifier', function () {
 | 
			
		||||
      const str = 'foo()!';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle simple method declaration with parameters', function () {
 | 
			
		||||
    it('should handle declaration with parameters', function () {
 | 
			
		||||
      const str = 'foo(int id)';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
@@ -82,7 +29,7 @@ describe('class member Renderer, ', function () {
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle simple method declaration with multiple parameters', function () {
 | 
			
		||||
    it('should handle declaration with multiple parameters', function () {
 | 
			
		||||
      const str = 'foo(int id, object thing)';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
@@ -90,7 +37,7 @@ describe('class member Renderer, ', function () {
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle simple method declaration with single item in parameters', function () {
 | 
			
		||||
    it('should handle declaration with single item in parameters', function () {
 | 
			
		||||
      const str = 'foo(id)';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
@@ -98,7 +45,7 @@ describe('class member Renderer, ', function () {
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle simple method declaration with single item in parameters with extra spaces', function () {
 | 
			
		||||
    it('should handle declaration with single item in parameters with extra spaces', function () {
 | 
			
		||||
      const str = ' foo ( id) ';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
@@ -106,22 +53,6 @@ describe('class member Renderer, ', function () {
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle method declaration with return value', function () {
 | 
			
		||||
      const str = 'foo(id) int';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(id) : int');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle method declaration with generic return value', function () {
 | 
			
		||||
      const str = 'foo(id) List~int~';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(id) : List<int>');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle method declaration with generic parameter', function () {
 | 
			
		||||
      const str = 'foo(List~int~)';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
@@ -130,6 +61,46 @@ describe('class member Renderer, ', function () {
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle method declaration with normal and generic parameter', function () {
 | 
			
		||||
      const str = 'foo(int, List~int~)';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(int, List<int>)');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle declaration with return value', function () {
 | 
			
		||||
      const str = 'foo(id) int';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(id) : int');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle declaration with colon return value', function () {
 | 
			
		||||
      const str = 'foo(id) : int';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(id) : int');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle declaration with generic return value', function () {
 | 
			
		||||
      const str = 'foo(id) List~int~';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(id) : List<int>');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle declaration with colon generic return value', function () {
 | 
			
		||||
      const str = 'foo(id) : List~int~';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(id) : List<int>');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle method declaration with all possible markup', function () {
 | 
			
		||||
      const str = '+foo (  List~int~ ids  )* List~Item~';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
@@ -138,7 +109,7 @@ describe('class member Renderer, ', function () {
 | 
			
		||||
      expect(actual.cssStyle).toBe('font-style:italic;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle method declaration with nested markup', function () {
 | 
			
		||||
    it('should handle method declaration with nested generics', function () {
 | 
			
		||||
      const str = '+foo (  List~List~int~~ ids  )* List~List~Item~~';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
@@ -147,8 +118,134 @@ describe('class member Renderer, ', function () {
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('when parsing text to build field display string', function () {
 | 
			
		||||
    it('should handle simple field declaration', function () {
 | 
			
		||||
  describe('when parsing method visibility', function () {
 | 
			
		||||
    it('should correctly handle public', function () {
 | 
			
		||||
      const str = '+foo()';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('+foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should correctly handle private', function () {
 | 
			
		||||
      const str = '-foo()';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('-foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should correctly handle protected', function () {
 | 
			
		||||
      const str = '#foo()';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('#foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should correctly handle package/internal', function () {
 | 
			
		||||
      const str = '~foo()';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('~foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('when parsing method classifier', function () {
 | 
			
		||||
    it('should handle abstract method', function () {
 | 
			
		||||
      const str = 'foo()*';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('font-style:italic;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle abstract method with return type', function () {
 | 
			
		||||
      const str = 'foo(name: String) int*';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(name: String) : int');
 | 
			
		||||
      expect(actual.cssStyle).toBe('font-style:italic;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle abstract method classifier after parenthesis with return type', function () {
 | 
			
		||||
      const str = 'foo(name: String)* int';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(name: String) : int');
 | 
			
		||||
      expect(actual.cssStyle).toBe('font-style:italic;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle static method classifier', function () {
 | 
			
		||||
      const str = 'foo()$';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('text-decoration:underline;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle static method classifier with return type', function () {
 | 
			
		||||
      const str = 'foo(name: String) int$';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(name: String) : int');
 | 
			
		||||
      expect(actual.cssStyle).toBe('text-decoration:underline;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle static method classifier with colon and return type', function () {
 | 
			
		||||
      const str = 'foo(name: String): int$';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(name: String) : int');
 | 
			
		||||
      expect(actual.cssStyle).toBe('text-decoration:underline;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle static method classifier after parenthesis with return type', function () {
 | 
			
		||||
      const str = 'foo(name: String)$ int';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo(name: String) : int');
 | 
			
		||||
      expect(actual.cssStyle).toBe('text-decoration:underline;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should ignore unknown character for classifier', function () {
 | 
			
		||||
      const str = 'foo()!';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo()');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 | 
			
		||||
describe('given a string representing class member, ', function () {
 | 
			
		||||
  describe('when parsing member declaration', function () {
 | 
			
		||||
    it('should handle simple field', function () {
 | 
			
		||||
      const str = 'id';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('id');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle field with type', function () {
 | 
			
		||||
      const str = 'int id';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('int id');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle field with type (name first)', function () {
 | 
			
		||||
      const str = 'id: int';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('id: int');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle array field', function () {
 | 
			
		||||
      const str = 'int[] ids';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
@@ -156,7 +253,15 @@ describe('class member Renderer, ', function () {
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle field declaration with generic type', function () {
 | 
			
		||||
    it('should handle array field (name first)', function () {
 | 
			
		||||
      const str = 'ids: int[]';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('ids: int[]');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle field with generic type', function () {
 | 
			
		||||
      const str = 'List~int~ ids';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
@@ -164,12 +269,62 @@ describe('class member Renderer, ', function () {
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle static field classifier', function () {
 | 
			
		||||
    it('should handle field with generic type (name first)', function () {
 | 
			
		||||
      const str = 'ids: List~int~';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('ids: List<int>');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  describe('when parsing classifiers', function () {
 | 
			
		||||
    it('should handle static field', function () {
 | 
			
		||||
      const str = 'String foo$';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('String foo');
 | 
			
		||||
      expect(actual.cssStyle).toBe('text-decoration:underline;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle static field (name first)', function () {
 | 
			
		||||
      const str = 'foo: String$';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo: String');
 | 
			
		||||
      expect(actual.cssStyle).toBe('text-decoration:underline;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle static field with generic type', function () {
 | 
			
		||||
      const str = 'List~String~ foo$';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('List<String> foo');
 | 
			
		||||
      expect(actual.cssStyle).toBe('text-decoration:underline;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle static field with generic type (name first)', function () {
 | 
			
		||||
      const str = 'foo: List~String~$';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('foo: List<String>');
 | 
			
		||||
      expect(actual.cssStyle).toBe('text-decoration:underline;');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle field with nested generic type', function () {
 | 
			
		||||
      const str = 'List~List~int~~ idLists';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('List<List<int>> idLists');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    it('should handle field with nested generic type (name first)', function () {
 | 
			
		||||
      const str = 'idLists: List~List~int~~';
 | 
			
		||||
      let actual = svgDraw.parseMember(str);
 | 
			
		||||
 | 
			
		||||
      expect(actual.displayText).toBe('idLists: List<List<int>>');
 | 
			
		||||
      expect(actual.cssStyle).toBe('');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,8 @@ import DOMPurify from 'dompurify';
 | 
			
		||||
import katex from 'katex';
 | 
			
		||||
import { MermaidConfig } from '../../config.type.js';
 | 
			
		||||
 | 
			
		||||
export const lineBreakRegex = /<br\s*\/?>/gi;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Gets the rows of lines in a string
 | 
			
		||||
 *
 | 
			
		||||
@@ -67,8 +69,6 @@ export const sanitizeTextOrArray = (
 | 
			
		||||
  return a.flat().map((x: string) => sanitizeText(x, config));
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const lineBreakRegex = /<br\s*\/?>/gi;
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Whether or not a text has any line breaks
 | 
			
		||||
 *
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										114
									
								
								packages/mermaid/src/diagrams/common/svgDrawCommon.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										114
									
								
								packages/mermaid/src/diagrams/common/svgDrawCommon.js
									
									
									
									
									
										Normal file
									
								
							@@ -0,0 +1,114 @@
 | 
			
		||||
import { sanitizeUrl } from '@braintree/sanitize-url';
 | 
			
		||||
 | 
			
		||||
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.attrs !== 'undefined' && rectData.attrs !== null) {
 | 
			
		||||
    for (let attrKey in rectData.attrs) {
 | 
			
		||||
      rectElem.attr(attrKey, rectData.attrs[attrKey]);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (rectData.class !== 'undefined') {
 | 
			
		||||
    rectElem.attr('class', rectData.class);
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  return rectElem;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Draws a background rectangle
 | 
			
		||||
 *
 | 
			
		||||
 * @param {any} elem Diagram (reference for bounds)
 | 
			
		||||
 * @param {any} bounds Shape of the rectangle
 | 
			
		||||
 */
 | 
			
		||||
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,
 | 
			
		||||
    stroke: bounds.stroke,
 | 
			
		||||
    class: 'rect',
 | 
			
		||||
  });
 | 
			
		||||
  rectElem.lower();
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const drawText = function (elem, textData) {
 | 
			
		||||
  // Remove and ignore br:s
 | 
			
		||||
  const nText = textData.text.replace(/<br\s*\/?>/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 drawImage = function (elem, x, y, link) {
 | 
			
		||||
  const imageElem = elem.append('image');
 | 
			
		||||
  imageElem.attr('x', x);
 | 
			
		||||
  imageElem.attr('y', y);
 | 
			
		||||
  var sanitizedLink = sanitizeUrl(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);
 | 
			
		||||
  const sanitizedLink = sanitizeUrl(link);
 | 
			
		||||
  imageElem.attr('xlink:href', '#' + sanitizedLink);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getNoteRect = function () {
 | 
			
		||||
  return {
 | 
			
		||||
    x: 0,
 | 
			
		||||
    y: 0,
 | 
			
		||||
    width: 100,
 | 
			
		||||
    height: 100,
 | 
			
		||||
    fill: '#EDF2AE',
 | 
			
		||||
    stroke: '#666',
 | 
			
		||||
    anchor: 'start',
 | 
			
		||||
    rx: 0,
 | 
			
		||||
    ry: 0,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const getTextObj = function () {
 | 
			
		||||
  return {
 | 
			
		||||
    x: 0,
 | 
			
		||||
    y: 0,
 | 
			
		||||
    width: 100,
 | 
			
		||||
    height: 100,
 | 
			
		||||
    fill: undefined,
 | 
			
		||||
    anchor: undefined,
 | 
			
		||||
    'text-anchor': 'start',
 | 
			
		||||
    style: '#666',
 | 
			
		||||
    textMargin: 0,
 | 
			
		||||
    rx: 0,
 | 
			
		||||
    ry: 0,
 | 
			
		||||
    tspan: true,
 | 
			
		||||
    valign: undefined,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
@@ -182,7 +182,7 @@ export const addVertices = async function (vert, svgId, root, doc, diagObj, pare
 | 
			
		||||
 | 
			
		||||
      // Add the element to the DOM
 | 
			
		||||
      if (node.type !== 'group') {
 | 
			
		||||
        nodeEl = insertNode(nodes, node, vertex.dir);
 | 
			
		||||
        nodeEl = await insertNode(nodes, node, vertex.dir);
 | 
			
		||||
        boundingBox = nodeEl.node().getBBox();
 | 
			
		||||
      } else {
 | 
			
		||||
        const svgLabel = doc.createElementNS('http://www.w3.org/2000/svg', 'text');
 | 
			
		||||
@@ -414,7 +414,7 @@ export const addEdges = function (edges, diagObj, graph, svg) {
 | 
			
		||||
 | 
			
		||||
  edges.forEach(function (edge) {
 | 
			
		||||
    // Identify Link
 | 
			
		||||
    var linkIdBase = 'L-' + edge.start + '-' + edge.end;
 | 
			
		||||
    const 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;
 | 
			
		||||
@@ -425,8 +425,8 @@ export const addEdges = function (edges, diagObj, graph, svg) {
 | 
			
		||||
    }
 | 
			
		||||
    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 linkNameStart = 'LS-' + edge.start;
 | 
			
		||||
    const linkNameEnd = 'LE-' + edge.end;
 | 
			
		||||
 | 
			
		||||
    const edgeData = { style: '', labelStyle: '' };
 | 
			
		||||
    edgeData.minlen = edge.length || 1;
 | 
			
		||||
 
 | 
			
		||||
@@ -213,7 +213,7 @@ export const addEdges = function (edges, g, diagObj) {
 | 
			
		||||
    cnt++;
 | 
			
		||||
 | 
			
		||||
    // Identify Link
 | 
			
		||||
    var linkIdBase = 'L-' + edge.start + '-' + edge.end;
 | 
			
		||||
    const 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;
 | 
			
		||||
@@ -224,8 +224,8 @@ export const addEdges = function (edges, g, diagObj) {
 | 
			
		||||
    }
 | 
			
		||||
    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 linkNameStart = 'LS-' + edge.start;
 | 
			
		||||
    const linkNameEnd = 'LE-' + edge.end;
 | 
			
		||||
 | 
			
		||||
    const edgeData = { style: '', labelStyle: '' };
 | 
			
		||||
    edgeData.minlen = edge.length || 1;
 | 
			
		||||
 
 | 
			
		||||
@@ -178,9 +178,9 @@ export const addEdges = function (edges, g, diagObj) {
 | 
			
		||||
    cnt++;
 | 
			
		||||
 | 
			
		||||
    // Identify Link
 | 
			
		||||
    var linkId = 'L-' + edge.start + '-' + edge.end;
 | 
			
		||||
    var linkNameStart = 'LS-' + edge.start;
 | 
			
		||||
    var linkNameEnd = 'LE-' + edge.end;
 | 
			
		||||
    const linkId = 'L-' + edge.start + '-' + edge.end;
 | 
			
		||||
    const linkNameStart = 'LS-' + edge.start;
 | 
			
		||||
    const linkNameEnd = 'LE-' + edge.end;
 | 
			
		||||
 | 
			
		||||
    const edgeData = {};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -287,7 +287,17 @@ const getStartDate = function (prevTime, dateFormat, str) {
 | 
			
		||||
    log.debug('Invalid date:' + str);
 | 
			
		||||
    log.debug('With date format:' + dateFormat.trim());
 | 
			
		||||
    const d = new Date(str);
 | 
			
		||||
    if (d === undefined || isNaN(d.getTime())) {
 | 
			
		||||
    if (
 | 
			
		||||
      d === undefined ||
 | 
			
		||||
      isNaN(d.getTime()) ||
 | 
			
		||||
      // WebKit browsers can mis-parse invalid dates to be ridiculously
 | 
			
		||||
      // huge numbers, e.g. new Date('202304') gets parsed as January 1, 202304.
 | 
			
		||||
      // This can cause virtually infinite loops while rendering, so for the
 | 
			
		||||
      // purposes of Gantt charts we'll just treat any date beyond 10,000 AD/BC as
 | 
			
		||||
      // invalid.
 | 
			
		||||
      d.getFullYear() < -10000 ||
 | 
			
		||||
      d.getFullYear() > 10000
 | 
			
		||||
    ) {
 | 
			
		||||
      throw new Error('Invalid date:' + str);
 | 
			
		||||
    }
 | 
			
		||||
    return d;
 | 
			
		||||
 
 | 
			
		||||
@@ -432,4 +432,10 @@ describe('when using the ganttDb', function () {
 | 
			
		||||
    ganttDb.setTodayMarker(expected);
 | 
			
		||||
    expect(ganttDb.getTodayMarker()).toEqual(expected);
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  it('should reject dates with ridiculous years', function () {
 | 
			
		||||
    ganttDb.setDateFormat('YYYYMMDD');
 | 
			
		||||
    ganttDb.addTask('test1', 'id1,202304,1d');
 | 
			
		||||
    expect(() => ganttDb.getTasks()).toThrowError('Invalid date:202304');
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -56,7 +56,6 @@ const getStyles = (options) =>
 | 
			
		||||
    font-size: 18px;
 | 
			
		||||
    fill: ${options.textColor};
 | 
			
		||||
  }
 | 
			
		||||
  }
 | 
			
		||||
`;
 | 
			
		||||
 | 
			
		||||
export default getStyles;
 | 
			
		||||
 
 | 
			
		||||
@@ -70,6 +70,7 @@ const defaultBkg = function (elem, node, section) {
 | 
			
		||||
    .attr('x2', node.width)
 | 
			
		||||
    .attr('y2', node.height);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const rectBkg = function (elem, node) {
 | 
			
		||||
  elem
 | 
			
		||||
    .append('rect')
 | 
			
		||||
@@ -78,6 +79,7 @@ const rectBkg = function (elem, node) {
 | 
			
		||||
    .attr('height', node.height)
 | 
			
		||||
    .attr('width', node.width);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const cloudBkg = function (elem, node) {
 | 
			
		||||
  const w = node.width;
 | 
			
		||||
  const h = node.height;
 | 
			
		||||
@@ -108,6 +110,7 @@ const cloudBkg = function (elem, node) {
 | 
			
		||||
    H0 V0 Z`
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const bangBkg = function (elem, node) {
 | 
			
		||||
  const w = node.width;
 | 
			
		||||
  const h = node.height;
 | 
			
		||||
@@ -139,6 +142,7 @@ const bangBkg = function (elem, node) {
 | 
			
		||||
    H0 V0 Z`
 | 
			
		||||
    );
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const circleBkg = function (elem, node) {
 | 
			
		||||
  elem
 | 
			
		||||
    .append('circle')
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import { select, selectAll } from 'd3';
 | 
			
		||||
import svgDraw, { drawKatex, drawText, fixLifeLineHeights } from './svgDraw.js';
 | 
			
		||||
import { log } from '../../logger.js';
 | 
			
		||||
import common, { calculateMathMLDimensions, hasKatex } from '../common/common.js';
 | 
			
		||||
import * as svgDrawCommon from '../common/svgDrawCommon';
 | 
			
		||||
import * as configApi from '../../config.js';
 | 
			
		||||
import assignWithDepth from '../../assignWithDepth.js';
 | 
			
		||||
import utils from '../../utils.js';
 | 
			
		||||
@@ -225,7 +226,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
 | 
			
		||||
  bounds.bumpVerticalPos(conf.boxMargin);
 | 
			
		||||
  noteModel.height = conf.boxMargin;
 | 
			
		||||
  noteModel.starty = bounds.getVerticalPos();
 | 
			
		||||
  const rect = svgDraw.getNoteRect();
 | 
			
		||||
  const rect = svgDrawCommon.getNoteRect();
 | 
			
		||||
  rect.x = noteModel.startx;
 | 
			
		||||
  rect.y = noteModel.starty;
 | 
			
		||||
  rect.width = noteModel.width || conf.width;
 | 
			
		||||
@@ -233,7 +234,7 @@ const drawNote = function (elem: any, noteModel: NoteModel) {
 | 
			
		||||
 | 
			
		||||
  const g = elem.append('g');
 | 
			
		||||
  const rectElem = svgDraw.drawRect(g, rect);
 | 
			
		||||
  const textObj = svgDraw.getTextObj();
 | 
			
		||||
  const textObj = svgDrawCommon.getTextObj();
 | 
			
		||||
  textObj.x = noteModel.startx;
 | 
			
		||||
  textObj.y = noteModel.starty;
 | 
			
		||||
  textObj.width = rect.width;
 | 
			
		||||
@@ -352,7 +353,7 @@ function boundMessage(_diagram, msgModel): number {
 | 
			
		||||
const drawMessage = function (diagram, msgModel, lineStartY: number, diagObj: Diagram) {
 | 
			
		||||
  const { startx, stopx, starty, message, type, sequenceIndex, sequenceVisible } = msgModel;
 | 
			
		||||
  const textDims = utils.calculateTextDimensions(message, messageFont(conf));
 | 
			
		||||
  const textObj = svgDraw.getTextObj();
 | 
			
		||||
  const textObj = svgDrawCommon.getTextObj();
 | 
			
		||||
  textObj.x = startx;
 | 
			
		||||
  textObj.y = starty + 10;
 | 
			
		||||
  textObj.width = stopx - startx;
 | 
			
		||||
 
 | 
			
		||||
@@ -1,34 +1,14 @@
 | 
			
		||||
import common, { calculateMathMLDimensions, hasKatex, renderKatex } from '../common/common.js';
 | 
			
		||||
import * as svgDrawCommon from '../common/svgDrawCommon';
 | 
			
		||||
import { addFunction } from '../../interactionDb.js';
 | 
			
		||||
import { parseFontSize } from '../../utils.js';
 | 
			
		||||
import { sanitizeUrl } from '@braintree/sanitize-url';
 | 
			
		||||
import * as configApi from '../../config.js';
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
  return svgDrawCommon.drawRect(elem, rectData);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
// const sanitizeUrl = function (s) {
 | 
			
		||||
//   return s
 | 
			
		||||
//     .replace(/&/g, '&')
 | 
			
		||||
//     .replace(/</g, '<')
 | 
			
		||||
//     .replace(/javascript:/g, '');
 | 
			
		||||
// };
 | 
			
		||||
 | 
			
		||||
const addPopupInteraction = (id, actorCnt) => {
 | 
			
		||||
  addFunction(() => {
 | 
			
		||||
    const arr = document.querySelectorAll(id);
 | 
			
		||||
@@ -44,6 +24,7 @@ const addPopupInteraction = (id, actorCnt) => {
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) {
 | 
			
		||||
  if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) {
 | 
			
		||||
    return { height: 0, width: 0 };
 | 
			
		||||
@@ -108,22 +89,6 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe
 | 
			
		||||
  return { height: rectData.height + linkY, width: menuWidth };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const drawImage = function (elem, x, y, link) {
 | 
			
		||||
  const imageElem = elem.append('image');
 | 
			
		||||
  imageElem.attr('x', x);
 | 
			
		||||
  imageElem.attr('y', y);
 | 
			
		||||
  var sanitizedLink = sanitizeUrl(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 popupMenu = function (popid) {
 | 
			
		||||
  return (
 | 
			
		||||
    "var pu = document.getElementById('" +
 | 
			
		||||
@@ -196,8 +161,8 @@ export const drawKatex = function (elem, textData, msgModel = null) {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const drawText = function (elem, textData) {
 | 
			
		||||
  let prevTextHeight = 0,
 | 
			
		||||
    textHeight = 0;
 | 
			
		||||
  let prevTextHeight = 0;
 | 
			
		||||
  let textHeight = 0;
 | 
			
		||||
  const lines = textData.text.split(common.lineBreakRegex);
 | 
			
		||||
 | 
			
		||||
  const [_textFontSize, _textFontSizePx] = parseFontSize(textData.fontSize);
 | 
			
		||||
@@ -231,6 +196,7 @@ export const drawText = function (elem, textData) {
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (
 | 
			
		||||
    textData.anchor !== undefined &&
 | 
			
		||||
    textData.textMargin !== undefined &&
 | 
			
		||||
@@ -260,6 +226,7 @@ export const drawText = function (elem, textData) {
 | 
			
		||||
        break;
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  for (let [i, line] of lines.entries()) {
 | 
			
		||||
    if (
 | 
			
		||||
      textData.textMargin !== undefined &&
 | 
			
		||||
@@ -414,7 +381,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  const rect = getNoteRect();
 | 
			
		||||
  const rect = svgDrawCommon.getNoteRect();
 | 
			
		||||
  var cssclass = 'actor';
 | 
			
		||||
  if (actor.properties != null && actor.properties['class']) {
 | 
			
		||||
    cssclass = actor.properties['class'];
 | 
			
		||||
@@ -434,9 +401,9 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) {
 | 
			
		||||
  if (actor.properties != null && actor.properties['icon']) {
 | 
			
		||||
    const iconSrc = actor.properties['icon'].trim();
 | 
			
		||||
    if (iconSrc.charAt(0) === '@') {
 | 
			
		||||
      drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
 | 
			
		||||
      svgDrawCommon.drawEmbeddedImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc.substr(1));
 | 
			
		||||
    } else {
 | 
			
		||||
      drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
 | 
			
		||||
      svgDrawCommon.drawImage(g, rect.x + rect.width - 20, rect.y + 10, iconSrc);
 | 
			
		||||
    }
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
@@ -481,7 +448,7 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
 | 
			
		||||
  const actElem = elem.append('g');
 | 
			
		||||
  actElem.attr('class', 'actor-man');
 | 
			
		||||
 | 
			
		||||
  const rect = getNoteRect();
 | 
			
		||||
  const rect = svgDrawCommon.getNoteRect();
 | 
			
		||||
  rect.x = actor.x;
 | 
			
		||||
  rect.y = actor.y;
 | 
			
		||||
  rect.fill = '#eaeaea';
 | 
			
		||||
@@ -490,7 +457,6 @@ const drawActorTypeActor = function (elem, actor, conf, isFooter) {
 | 
			
		||||
  rect.class = 'actor';
 | 
			
		||||
  rect.rx = 3;
 | 
			
		||||
  rect.ry = 3;
 | 
			
		||||
  // drawRect(actElem, rect);
 | 
			
		||||
 | 
			
		||||
  actElem
 | 
			
		||||
    .append('line')
 | 
			
		||||
@@ -575,6 +541,7 @@ export const drawBox = function (elem, box, conf) {
 | 
			
		||||
export const anchorElement = function (elem) {
 | 
			
		||||
  return elem.append('g');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Draws an activation in the diagram
 | 
			
		||||
 *
 | 
			
		||||
@@ -585,7 +552,7 @@ export const anchorElement = function (elem) {
 | 
			
		||||
 * @param {any} actorActivations - Number of activations on the actor.
 | 
			
		||||
 */
 | 
			
		||||
export const drawActivation = function (elem, bounds, verticalPos, conf, actorActivations) {
 | 
			
		||||
  const rect = getNoteRect();
 | 
			
		||||
  const rect = svgDrawCommon.getNoteRect();
 | 
			
		||||
  const g = bounds.anchored;
 | 
			
		||||
  rect.x = bounds.startx;
 | 
			
		||||
  rect.y = bounds.starty;
 | 
			
		||||
@@ -637,7 +604,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
 | 
			
		||||
    });
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let txt = getTextObj();
 | 
			
		||||
  let txt = svgDrawCommon.getTextObj();
 | 
			
		||||
  txt.text = labelText;
 | 
			
		||||
  txt.x = loopModel.startx;
 | 
			
		||||
  txt.y = loopModel.starty;
 | 
			
		||||
@@ -653,7 +620,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
 | 
			
		||||
  txt.class = 'labelText';
 | 
			
		||||
 | 
			
		||||
  drawLabel(g, txt);
 | 
			
		||||
  txt = getTextObj();
 | 
			
		||||
  txt = svgDrawCommon.getTextObj();
 | 
			
		||||
  txt.text = loopModel.title;
 | 
			
		||||
  txt.x = loopModel.startx + labelBoxWidth / 2 + (loopModel.stopx - loopModel.startx) / 2;
 | 
			
		||||
  txt.y = loopModel.starty + boxMargin + boxTextMargin;
 | 
			
		||||
@@ -711,16 +678,7 @@ export const drawLoop = function (elem, loopModel, labelText, conf) {
 | 
			
		||||
 * @param {any} bounds Shape of the rectangle
 | 
			
		||||
 */
 | 
			
		||||
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,
 | 
			
		||||
    stroke: bounds.stroke,
 | 
			
		||||
    class: 'rect',
 | 
			
		||||
  });
 | 
			
		||||
  rectElem.lower();
 | 
			
		||||
  svgDrawCommon.drawBackgroundRect(elem, bounds);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const insertDatabaseIcon = function (elem) {
 | 
			
		||||
@@ -787,6 +745,7 @@ export const insertArrowHead = function (elem) {
 | 
			
		||||
    .append('path')
 | 
			
		||||
    .attr('d', 'M 0 0 L 10 5 L 0 10 z'); // this is actual shape for arrowhead
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Setup arrow head and define the marker. The result is appended to the svg.
 | 
			
		||||
 *
 | 
			
		||||
@@ -805,6 +764,7 @@ export const insertArrowFilledHead = function (elem) {
 | 
			
		||||
    .append('path')
 | 
			
		||||
    .attr('d', 'M 18,7 L9,13 L14,7 L9,1 Z');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Setup node number. The result is appended to the svg.
 | 
			
		||||
 *
 | 
			
		||||
@@ -826,6 +786,7 @@ export const insertSequenceNumber = function (elem) {
 | 
			
		||||
    .attr('r', 6);
 | 
			
		||||
  // .style("fill", '#f00');
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
 * Setup cross head and define the marker. The result is appended to the svg.
 | 
			
		||||
 *
 | 
			
		||||
@@ -852,37 +813,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,
 | 
			
		||||
    y: 0,
 | 
			
		||||
    fill: '#EDF2AE',
 | 
			
		||||
    stroke: '#666',
 | 
			
		||||
    width: 100,
 | 
			
		||||
    anchor: 'start',
 | 
			
		||||
    height: 100,
 | 
			
		||||
    rx: 0,
 | 
			
		||||
    ry: 0,
 | 
			
		||||
  };
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const _drawTextCandidateFunc = (function () {
 | 
			
		||||
  /**
 | 
			
		||||
   * @param {any} content
 | 
			
		||||
@@ -1136,8 +1066,6 @@ export default {
 | 
			
		||||
  drawActor,
 | 
			
		||||
  drawBox,
 | 
			
		||||
  drawPopup,
 | 
			
		||||
  drawImage,
 | 
			
		||||
  drawEmbeddedImage,
 | 
			
		||||
  anchorElement,
 | 
			
		||||
  drawActivation,
 | 
			
		||||
  drawLoop,
 | 
			
		||||
@@ -1149,8 +1077,6 @@ export default {
 | 
			
		||||
  insertDatabaseIcon,
 | 
			
		||||
  insertComputerIcon,
 | 
			
		||||
  insertClockIcon,
 | 
			
		||||
  getTextObj,
 | 
			
		||||
  getNoteRect,
 | 
			
		||||
  popupMenu,
 | 
			
		||||
  popdownMenu,
 | 
			
		||||
  fixLifeLineHeights,
 | 
			
		||||
 
 | 
			
		||||
@@ -174,16 +174,4 @@ describe('svgDraw', function () {
 | 
			
		||||
      expect(rect.lower).toHaveBeenCalled();
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
  describe('sanitizeUrl', function () {
 | 
			
		||||
    it('should sanitize malicious urls', function () {
 | 
			
		||||
      const maliciousStr = 'javascript:script:alert(1)';
 | 
			
		||||
      const result = svgDraw.sanitizeUrl(maliciousStr);
 | 
			
		||||
      expect(result).not.toContain('javascript:alert(1)');
 | 
			
		||||
    });
 | 
			
		||||
    it('should not sanitize non dangerous urls', function () {
 | 
			
		||||
      const maliciousStr = 'javajavascript:script:alert(1)';
 | 
			
		||||
      const result = svgDraw.sanitizeUrl(maliciousStr);
 | 
			
		||||
      expect(result).not.toContain('javascript:alert(1)');
 | 
			
		||||
    });
 | 
			
		||||
  });
 | 
			
		||||
});
 | 
			
		||||
 
 | 
			
		||||
@@ -1,21 +1,8 @@
 | 
			
		||||
import { arc as d3arc } from 'd3';
 | 
			
		||||
import * as svgDrawCommon from '../common/svgDrawCommon';
 | 
			
		||||
 | 
			
		||||
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;
 | 
			
		||||
  return svgDrawCommon.drawRect(elem, rectData);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const drawFace = function (element, faceData) {
 | 
			
		||||
@@ -128,25 +115,7 @@ export const drawCircle = function (element, circleData) {
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const drawText = function (elem, textData) {
 | 
			
		||||
  // Remove and ignore br:s
 | 
			
		||||
  const nText = textData.text.replace(/<br\s*\/?>/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;
 | 
			
		||||
  return svgDrawCommon.drawText(elem, textData);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const drawLabel = function (elem, txtObject) {
 | 
			
		||||
@@ -192,7 +161,7 @@ export const drawLabel = function (elem, txtObject) {
 | 
			
		||||
export const drawSection = function (elem, section, conf) {
 | 
			
		||||
  const g = elem.append('g');
 | 
			
		||||
 | 
			
		||||
  const rect = getNoteRect();
 | 
			
		||||
  const rect = svgDrawCommon.getNoteRect();
 | 
			
		||||
  rect.x = section.x;
 | 
			
		||||
  rect.y = section.y;
 | 
			
		||||
  rect.fill = section.fill;
 | 
			
		||||
@@ -249,7 +218,7 @@ export const drawTask = function (elem, task, conf) {
 | 
			
		||||
    score: task.score,
 | 
			
		||||
  });
 | 
			
		||||
 | 
			
		||||
  const rect = getNoteRect();
 | 
			
		||||
  const rect = svgDrawCommon.getNoteRect();
 | 
			
		||||
  rect.x = task.x;
 | 
			
		||||
  rect.y = task.y;
 | 
			
		||||
  rect.fill = task.fill;
 | 
			
		||||
@@ -298,41 +267,7 @@ export const drawTask = function (elem, task, conf) {
 | 
			
		||||
 * @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,
 | 
			
		||||
  };
 | 
			
		||||
  svgDrawCommon.drawBackgroundRect(elem, bounds);
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
const _drawTextCandidateFunc = (function () {
 | 
			
		||||
@@ -475,7 +410,5 @@ export default {
 | 
			
		||||
  drawLabel,
 | 
			
		||||
  drawTask,
 | 
			
		||||
  drawBackgroundRect,
 | 
			
		||||
  getTextObj,
 | 
			
		||||
  getNoteRect,
 | 
			
		||||
  initGraphics,
 | 
			
		||||
};
 | 
			
		||||
 
 | 
			
		||||
@@ -21,7 +21,7 @@ They also serve as proof of concept, for the variety of things that can be built
 | 
			
		||||
- [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**)
 | 
			
		||||
- [Obsidian](https://help.obsidian.md/Editing+and+formatting/Advanced+formatting+syntax#Diagram) (**Native support**)
 | 
			
		||||
- [GitBook](https://gitbook.com)
 | 
			
		||||
  - [Mermaid Plugin](https://github.com/JozoVilcek/gitbook-plugin-mermaid)
 | 
			
		||||
  - [Markdown with Mermaid CLI](https://github.com/miao1007/gitbook-plugin-mermaid-cli)
 | 
			
		||||
@@ -155,6 +155,7 @@ They also serve as proof of concept, for the variety of things that can be built
 | 
			
		||||
  - [codedoc-mermaid-plugin](https://www.npmjs.com/package/codedoc-mermaid-plugin)
 | 
			
		||||
- [mdbook](https://rust-lang.github.io/mdBook/index.html)
 | 
			
		||||
  - [mdbook-mermaid](https://github.com/badboy/mdbook-mermaid)
 | 
			
		||||
- [Quarto](https://quarto.org/)
 | 
			
		||||
 | 
			
		||||
## Browser Extensions
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -74,7 +74,7 @@ classDiagram
 | 
			
		||||
    Vehicle <|-- Car
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), and underscores.
 | 
			
		||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), underscores, and dashes (-).
 | 
			
		||||
 | 
			
		||||
### Class labels
 | 
			
		||||
 | 
			
		||||
@@ -171,12 +171,12 @@ To describe the visibility (or encapsulation) of an attribute or method/function
 | 
			
		||||
- `#` Protected
 | 
			
		||||
- `~` Package/Internal
 | 
			
		||||
 | 
			
		||||
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()`:
 | 
			
		||||
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type:
 | 
			
		||||
>
 | 
			
		||||
> - `*` Abstract e.g.: `someAbstractMethod()*`
 | 
			
		||||
> - `$` Static e.g.: `someStaticMethod()$`
 | 
			
		||||
> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*`
 | 
			
		||||
> - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$`
 | 
			
		||||
 | 
			
		||||
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the end of its name:
 | 
			
		||||
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end:
 | 
			
		||||
>
 | 
			
		||||
> - `$` Static e.g.: `String someField$`
 | 
			
		||||
 | 
			
		||||
@@ -277,6 +277,23 @@ And `Link` can be one of:
 | 
			
		||||
| --   | Solid       |
 | 
			
		||||
| ..   | Dashed      |
 | 
			
		||||
 | 
			
		||||
## Define Namespace
 | 
			
		||||
 | 
			
		||||
A namespace groups classes.
 | 
			
		||||
 | 
			
		||||
Code:
 | 
			
		||||
 | 
			
		||||
```mermaid-example
 | 
			
		||||
classDiagram
 | 
			
		||||
namespace BaseShapes {
 | 
			
		||||
    class Triangle
 | 
			
		||||
    class Rectangle {
 | 
			
		||||
      double width
 | 
			
		||||
      double height
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
## Cardinality / Multiplicity on relations
 | 
			
		||||
 | 
			
		||||
Multiplicity or cardinality in class diagrams indicates the number of instances of one class that can be linked to an instance of the other class. For example, each company will have one or more employees (not zero), and each employee currently works for zero or one companies.
 | 
			
		||||
@@ -403,10 +420,18 @@ click className href "url" "tooltip"
 | 
			
		||||
 | 
			
		||||
## Notes
 | 
			
		||||
 | 
			
		||||
It is possible to add notes on diagram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"`
 | 
			
		||||
It is possible to add notes on the diagram using `note "line1\nline2"`. A note can be added for a specific class using `note for <CLASS NAME> "line1\nline2"`.
 | 
			
		||||
 | 
			
		||||
### Examples
 | 
			
		||||
 | 
			
		||||
```mermaid
 | 
			
		||||
classDiagram
 | 
			
		||||
    note "This is a general note"
 | 
			
		||||
    note for MyClass "This is a note for a class"
 | 
			
		||||
    class MyClass{
 | 
			
		||||
    }
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
_URL Link:_
 | 
			
		||||
 | 
			
		||||
```mmd
 | 
			
		||||
 
 | 
			
		||||
@@ -465,9 +465,9 @@ end
 | 
			
		||||
 | 
			
		||||
Formatting:
 | 
			
		||||
 | 
			
		||||
- For bold text, use double asterisks \*\* before and after the text.
 | 
			
		||||
- For italics, use single asterisks \* before and after the text.
 | 
			
		||||
- With traditional strings, you needed to add <br> tags for text to wrap in nodes. However, markdown strings automatically wrap text when it becomes too long and allows you to start a new line by simply using a newline character instead of a <br> tag.
 | 
			
		||||
- For bold text, use double asterisks (`**`) before and after the text.
 | 
			
		||||
- For italics, use single asterisks (`*`) before and after the text.
 | 
			
		||||
- With traditional strings, you needed to add `<br>` tags for text to wrap in nodes. However, markdown strings automatically wrap text when it becomes too long and allows you to start a new line by simply using a newline character instead of a `<br>` tag.
 | 
			
		||||
 | 
			
		||||
This feature is applicable to node labels, edge labels, and subgraph labels.
 | 
			
		||||
 | 
			
		||||
@@ -683,7 +683,7 @@ flowchart TD
 | 
			
		||||
    B-->E(A fa:fa-camera-retro perhaps?)
 | 
			
		||||
```
 | 
			
		||||
 | 
			
		||||
?> Mermaid is now only compatible with Font Awesome versions 4 and 5. Check that you are using the correct version of Font Awesome.
 | 
			
		||||
Mermaid is compatible with Font Awesome up to verion 5, Free icons only. Check that the icons you use are from the [supported set of icons](https://fontawesome.com/v5/search?o=r&m=free).
 | 
			
		||||
 | 
			
		||||
## Graph declarations with spaces between vertices and link and without semicolon
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -114,7 +114,7 @@ More shapes will be added, beginning with the shapes available in flowcharts.
 | 
			
		||||
 | 
			
		||||
## 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.
 | 
			
		||||
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 [Font Awesome 5](https://fontawesome.com/v5/search?o=r&m=free) 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.
 | 
			
		||||
 | 
			
		||||
```mermaid-example
 | 
			
		||||
mindmap
 | 
			
		||||
 
 | 
			
		||||
@@ -12,24 +12,24 @@ vi.mock('dagre-d3');
 | 
			
		||||
 | 
			
		||||
// mermaidAPI.spec.ts:
 | 
			
		||||
import * as accessibility from './accessibility.js'; // Import it this way so we can use spyOn(accessibility,...)
 | 
			
		||||
vi.mock('./accessibility', () => ({
 | 
			
		||||
vi.mock('./accessibility.js', () => ({
 | 
			
		||||
  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');
 | 
			
		||||
vi.mock('./diagrams/c4/c4Renderer.js');
 | 
			
		||||
vi.mock('./diagrams/class/classRenderer.js');
 | 
			
		||||
vi.mock('./diagrams/class/classRenderer-v2.js');
 | 
			
		||||
vi.mock('./diagrams/er/erRenderer.js');
 | 
			
		||||
vi.mock('./diagrams/flowchart/flowRenderer-v2.js');
 | 
			
		||||
vi.mock('./diagrams/git/gitGraphRenderer.js');
 | 
			
		||||
vi.mock('./diagrams/gantt/ganttRenderer.js');
 | 
			
		||||
vi.mock('./diagrams/user-journey/journeyRenderer.js');
 | 
			
		||||
vi.mock('./diagrams/pie/pieRenderer.js');
 | 
			
		||||
vi.mock('./diagrams/requirement/requirementRenderer.js');
 | 
			
		||||
vi.mock('./diagrams/sequence/sequenceRenderer.js');
 | 
			
		||||
vi.mock('./diagrams/state/stateRenderer-v2.js');
 | 
			
		||||
 | 
			
		||||
// -------------------------------------
 | 
			
		||||
 | 
			
		||||
@@ -52,7 +52,7 @@ import assignWithDepth from './assignWithDepth.js';
 | 
			
		||||
// --------------
 | 
			
		||||
// Mocks
 | 
			
		||||
//   To mock a module, first define a mock for it, then (if used explicitly in the tests) import it. Be sure the path points to exactly the same file as is imported in mermaidAPI (the module being tested)
 | 
			
		||||
vi.mock('./styles', () => {
 | 
			
		||||
vi.mock('./styles.js', () => {
 | 
			
		||||
  return {
 | 
			
		||||
    addStylesForDiagram: vi.fn(),
 | 
			
		||||
    default: vi.fn().mockReturnValue(' .userStyle { font-weight:bold; }'),
 | 
			
		||||
 
 | 
			
		||||
							
								
								
									
										7439
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										7439
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							@@ -2,7 +2,6 @@
 | 
			
		||||
  "extends": [
 | 
			
		||||
    "config:base",
 | 
			
		||||
    ":rebaseStalePrs",
 | 
			
		||||
    "group:allNonMajor",
 | 
			
		||||
    "schedule:earlyMondays",
 | 
			
		||||
    ":automergeMinor",
 | 
			
		||||
    ":automergeTesters",
 | 
			
		||||
@@ -14,6 +13,18 @@
 | 
			
		||||
    {
 | 
			
		||||
      "matchUpdateTypes": ["minor", "patch", "digest"],
 | 
			
		||||
      "automerge": true
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "groupName": "all patch dependencies",
 | 
			
		||||
      "groupSlug": "all-patch",
 | 
			
		||||
      "matchPackagePatterns": ["*"],
 | 
			
		||||
      "matchUpdateTypes": ["patch"]
 | 
			
		||||
    },
 | 
			
		||||
    {
 | 
			
		||||
      "groupName": "all minor dependencies",
 | 
			
		||||
      "groupSlug": "all-minor",
 | 
			
		||||
      "matchPackagePatterns": ["*"],
 | 
			
		||||
      "matchUpdateTypes": ["minor"]
 | 
			
		||||
    }
 | 
			
		||||
  ],
 | 
			
		||||
  "dependencyDashboard": true,
 | 
			
		||||
 
 | 
			
		||||
@@ -14,12 +14,17 @@ const lint = async (file: string): Promise<boolean> => {
 | 
			
		||||
  console.log(`Linting ${file}`);
 | 
			
		||||
  const jisonCode = await readFile(file, 'utf8');
 | 
			
		||||
  // @ts-ignore no typings
 | 
			
		||||
  const jsCode = new jison.Generator(jisonCode, { moduleType: 'amd' }).generate();
 | 
			
		||||
  const generator = new jison.Generator(jisonCode, { moduleType: 'amd' });
 | 
			
		||||
  const jsCode = generator.generate();
 | 
			
		||||
  const [result] = await linter.lintText(jsCode);
 | 
			
		||||
  if (result.errorCount > 0) {
 | 
			
		||||
    console.error(`Linting failed for ${file}`);
 | 
			
		||||
    console.error(result.messages);
 | 
			
		||||
  }
 | 
			
		||||
  if (generator.conflicts > 0) {
 | 
			
		||||
    console.error(`Linting failed for ${file}. Conflicts found in grammar`);
 | 
			
		||||
    return false;
 | 
			
		||||
  }
 | 
			
		||||
  return result.errorCount === 0;
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user