mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-10-30 18:34:09 +01:00 
			
		
		
		
	Compare commits
	
		
			2 Commits
		
	
	
		
			feat/confi
			...
			bug/2234_c
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
|   | 5482d0a603 | ||
|   | a22e4193c5 | 
							
								
								
									
										6
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										6
									
								
								.github/ISSUE_TEMPLATE/config.yml
									
									
									
									
										vendored
									
									
								
							| @@ -3,9 +3,9 @@ contact_links: | |||||||
|   - name: GitHub Discussions |   - name: GitHub Discussions | ||||||
|     url: https://github.com/mermaid-js/mermaid/discussions |     url: https://github.com/mermaid-js/mermaid/discussions | ||||||
|     about: Ask the Community questions or share your own graphs in our discussions. |     about: Ask the Community questions or share your own graphs in our discussions. | ||||||
|   - name: Discord |   - name: Slack | ||||||
|     url: https://discord.gg/wwtabKgp8y |     url: https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE | ||||||
|     about: Join our Community on Discord for Help and a casual chat. |     about: Join our Community on Slack for Help and a casual chat. | ||||||
|   - name: Documentation |   - name: Documentation | ||||||
|     url: https://mermaid.js.org |     url: https://mermaid.js.org | ||||||
|     about: Read our documentation for all that Mermaid.js can offer. |     about: Read our documentation for all that Mermaid.js can offer. | ||||||
|   | |||||||
							
								
								
									
										4
									
								
								.github/lychee.toml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										4
									
								
								.github/lychee.toml
									
									
									
									
										vendored
									
									
								
							| @@ -34,8 +34,8 @@ exclude = [ | |||||||
| # Don't check files that are generated during the build via `pnpm docs:code` | # Don't check files that are generated during the build via `pnpm docs:code` | ||||||
| 'packages/mermaid/src/docs/config/setup/*', | 'packages/mermaid/src/docs/config/setup/*', | ||||||
|  |  | ||||||
| # Ignore Discord invite | # Ignore slack invite | ||||||
| "https://discord.gg" | "https://join.slack.com/" | ||||||
| ] | ] | ||||||
|  |  | ||||||
| # Exclude all private IPs from checking. | # Exclude all private IPs from checking. | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/build.yml
									
									
									
									
										vendored
									
									
								
							| @@ -12,23 +12,23 @@ on: | |||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|  |  | ||||||
| env: |  | ||||||
|   node-version: 18.x |  | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   build-mermaid: |   build-mermaid: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         node-version: [18.x] | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|       - uses: pnpm/action-setup@v2 |       - uses: pnpm/action-setup@v2 | ||||||
|         # uses version from "packageManager" field in package.json |         # uses version from "packageManager" field in package.json | ||||||
|  |  | ||||||
|       - name: Setup Node.js ${{ env.node-version }} |       - name: Setup Node.js ${{ matrix.node-version }} | ||||||
|         uses: actions/setup-node@v4 |         uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           cache: pnpm |           cache: pnpm | ||||||
|           node-version: ${{ env.node-version }} |           node-version: ${{ matrix.node-version }} | ||||||
|  |  | ||||||
|       - name: Install Packages |       - name: Install Packages | ||||||
|         run: | |         run: | | ||||||
|   | |||||||
							
								
								
									
										9
									
								
								.github/workflows/e2e.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										9
									
								
								.github/workflows/e2e.yml
									
									
									
									
										vendored
									
									
								
							| @@ -17,9 +17,8 @@ permissions: | |||||||
|   contents: read |   contents: read | ||||||
|  |  | ||||||
| env: | env: | ||||||
|   node-version: 18.x |  | ||||||
|   # For PRs and MergeQueues, the target commit is used, and for push events, github.event.previous is used. |   # For PRs and MergeQueues, the target commit is used, and for push events, github.event.previous is used. | ||||||
|   targetHash: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || (github.event.before == '0000000000000000000000000000000000000000' && 'develop' || github.event.before)  }} |   targetHash: ${{ github.event.pull_request.base.sha || github.event.merge_group.base_sha || github.event.before }} | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   cache: |   cache: | ||||||
| @@ -31,6 +30,7 @@ jobs: | |||||||
|         uses: actions/setup-node@v4 |         uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: 18.x |           node-version: 18.x | ||||||
|  |  | ||||||
|       - name: Cache snapshots |       - name: Cache snapshots | ||||||
|         id: cache-snapshot |         id: cache-snapshot | ||||||
|         uses: actions/cache@v4 |         uses: actions/cache@v4 | ||||||
| @@ -61,6 +61,7 @@ jobs: | |||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|  |         node-version: [18.x] | ||||||
|         containers: [1, 2, 3, 4] |         containers: [1, 2, 3, 4] | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
| @@ -68,10 +69,10 @@ jobs: | |||||||
|       - uses: pnpm/action-setup@v2 |       - uses: pnpm/action-setup@v2 | ||||||
|         # uses version from "packageManager" field in package.json |         # uses version from "packageManager" field in package.json | ||||||
|  |  | ||||||
|       - name: Setup Node.js ${{ env.node-version }} |       - name: Setup Node.js ${{ matrix.node-version }} | ||||||
|         uses: actions/setup-node@v4 |         uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           node-version: ${{ env.node-version }} |           node-version: ${{ matrix.node-version }} | ||||||
|  |  | ||||||
|       # These cached snapshots are downloaded, providing the reference snapshots. |       # These cached snapshots are downloaded, providing the reference snapshots. | ||||||
|       - name: Cache snapshots |       - name: Cache snapshots | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/lint.yml
									
									
									
									
										vendored
									
									
								
							| @@ -13,23 +13,23 @@ on: | |||||||
| permissions: | permissions: | ||||||
|   contents: write |   contents: write | ||||||
|  |  | ||||||
| env: |  | ||||||
|   node-version: 18.x |  | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   lint: |   lint: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         node-version: [18.x] | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|       - uses: pnpm/action-setup@v2 |       - uses: pnpm/action-setup@v2 | ||||||
|         # uses version from "packageManager" field in package.json |         # uses version from "packageManager" field in package.json | ||||||
|  |  | ||||||
|       - name: Setup Node.js ${{ env.node-version }} |       - name: Setup Node.js ${{ matrix.node-version }} | ||||||
|         uses: actions/setup-node@v4 |         uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           cache: pnpm |           cache: pnpm | ||||||
|           node-version: ${{ env.node-version }} |           node-version: ${{ matrix.node-version }} | ||||||
|  |  | ||||||
|       - name: Install Packages |       - name: Install Packages | ||||||
|         run: | |         run: | | ||||||
|   | |||||||
							
								
								
									
										10
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						
									
										10
									
								
								.github/workflows/test.yml
									
									
									
									
										vendored
									
									
								
							| @@ -5,23 +5,23 @@ on: [push, pull_request, merge_group] | |||||||
| permissions: | permissions: | ||||||
|   contents: read |   contents: read | ||||||
|  |  | ||||||
| env: |  | ||||||
|   node-version: 18.x |  | ||||||
|  |  | ||||||
| jobs: | jobs: | ||||||
|   unit-test: |   unit-test: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  |     strategy: | ||||||
|  |       matrix: | ||||||
|  |         node-version: [18.x] | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|       - uses: pnpm/action-setup@v2 |       - uses: pnpm/action-setup@v2 | ||||||
|         # uses version from "packageManager" field in package.json |         # uses version from "packageManager" field in package.json | ||||||
|  |  | ||||||
|       - name: Setup Node.js ${{ env.node-version }} |       - name: Setup Node.js ${{ matrix.node-version }} | ||||||
|         uses: actions/setup-node@v4 |         uses: actions/setup-node@v4 | ||||||
|         with: |         with: | ||||||
|           cache: pnpm |           cache: pnpm | ||||||
|           node-version: ${{ env.node-version }} |           node-version: ${{ matrix.node-version }} | ||||||
|  |  | ||||||
|       - name: Install Packages |       - name: Install Packages | ||||||
|         run: | |         run: | | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ Generate diagrams from markdown-like text. | |||||||
| <a href="https://mermaid.live/"><b>Live Editor!</b></a> | <a href="https://mermaid.live/"><b>Live Editor!</b></a> | ||||||
| </p> | </p> | ||||||
| <p align="center"> | <p align="center"> | ||||||
|  <a href="https://mermaid.js.org">📖 Documentation</a> | <a href="https://mermaid.js.org/intro/">🚀 Getting Started</a> | <a href="https://www.jsdelivr.com/package/npm/mermaid">🌐 CDN</a> | <a href="https://discord.gg/wwtabKgp8y" title="Discord invite">🙌 Join Us</a> |  <a href="https://mermaid.js.org">📖 Documentation</a> | <a href="https://mermaid.js.org/intro/">🚀 Getting Started</a> | <a href="https://www.jsdelivr.com/package/npm/mermaid">🌐 CDN</a> | <a href="https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE" title="Slack invite">🙌 Join Us</a> | ||||||
| </p> | </p> | ||||||
| <p align="center"> | <p align="center"> | ||||||
| <a href="./README.zh-CN.md">简体中文</a> | <a href="./README.zh-CN.md">简体中文</a> | ||||||
| @@ -33,7 +33,7 @@ Try Live Editor previews of future releases: <a href="https://develop.git.mermai | |||||||
| [](https://app.codecov.io/github/mermaid-js/mermaid/tree/develop) | [](https://app.codecov.io/github/mermaid-js/mermaid/tree/develop) | ||||||
| [](https://www.jsdelivr.com/package/npm/mermaid) | [](https://www.jsdelivr.com/package/npm/mermaid) | ||||||
| [](https://www.npmjs.com/package/mermaid) | [](https://www.npmjs.com/package/mermaid) | ||||||
| [](https://discord.gg/wwtabKgp8y) | [](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) | ||||||
| [](https://twitter.com/mermaidjs_) | [](https://twitter.com/mermaidjs_) | ||||||
|  |  | ||||||
| <img src="./img/header.png" alt="" /> | <img src="./img/header.png" alt="" /> | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ Mermaid | |||||||
| <a href="https://mermaid.live/"><b>实时编辑器!</b></a> | <a href="https://mermaid.live/"><b>实时编辑器!</b></a> | ||||||
| </p> | </p> | ||||||
| <p align="center"> | <p align="center"> | ||||||
|  <a href="https://mermaid.js.org">📖 文档</a> | <a href="https://mermaid.js.org/intro/">🚀 入门</a> | <a href="https://www.jsdelivr.com/package/npm/mermaid">🌐 CDN</a> | <a href="https://discord.gg/wwtabKgp8y" title="Discord invite">🙌 加入我们</a> |  <a href="https://mermaid.js.org">📖 文档</a> | <a href="https://mermaid.js.org/intro/">🚀 入门</a> | <a href="https://www.jsdelivr.com/package/npm/mermaid">🌐 CDN</a> | <a href="https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE" title="Slack invite">🙌 加入我们</a> | ||||||
| </p> | </p> | ||||||
| <p align="center"> | <p align="center"> | ||||||
| <a href="./README.md">English</a> | <a href="./README.md">English</a> | ||||||
| @@ -34,7 +34,7 @@ Mermaid | |||||||
| [](https://app.codecov.io/github/mermaid-js/mermaid/tree/develop) | [](https://app.codecov.io/github/mermaid-js/mermaid/tree/develop) | ||||||
| [](https://www.jsdelivr.com/package/npm/mermaid) | [](https://www.jsdelivr.com/package/npm/mermaid) | ||||||
| [](https://www.npmjs.com/package/mermaid) | [](https://www.npmjs.com/package/mermaid) | ||||||
| [](https://discord.gg/wwtabKgp8y) | [](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) | ||||||
| [](https://twitter.com/mermaidjs_) | [](https://twitter.com/mermaidjs_) | ||||||
|  |  | ||||||
| <img src="./img/header.png" alt="" /> | <img src="./img/header.png" alt="" /> | ||||||
|   | |||||||
							
								
								
									
										19
									
								
								applitools.config.js
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								applitools.config.js
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | |||||||
|  | // eslint-disable-next-line @typescript-eslint/no-var-requires | ||||||
|  | const { defineConfig } = require('cypress'); | ||||||
|  |  | ||||||
|  | module.exports = defineConfig({ | ||||||
|  |   testConcurrency: 1, | ||||||
|  |   browser: [ | ||||||
|  |     // Add browsers with different viewports | ||||||
|  |     //   { width: 800, height: 600, name: 'chrome' }, | ||||||
|  |     //   { width: 700, height: 500, name: 'firefox' }, | ||||||
|  |     //   { width: 1600, height: 1200, name: 'ie11' }, | ||||||
|  |     //   { width: 1024, height: 768, name: 'edgechromium' }, | ||||||
|  |     //   { width: 800, height: 600, name: 'safari' }, | ||||||
|  |     //   // Add mobile emulation devices in Portrait mode | ||||||
|  |     //   { deviceName: 'iPhone X', screenOrientation: 'portrait' }, | ||||||
|  |     //   { deviceName: 'Pixel 2', screenOrientation: 'portrait' }, | ||||||
|  |   ], | ||||||
|  |   // set batch name to the configuration | ||||||
|  |   // batchName: `Mermaid ${process.env.APPLI_BRANCH ?? "'no APPLI_BRANCH set'"}`, | ||||||
|  | }); | ||||||
							
								
								
									
										32
									
								
								cypress.config.cjs
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										32
									
								
								cypress.config.cjs
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,32 @@ | |||||||
|  | /* eslint-disable @typescript-eslint/no-var-requires */ | ||||||
|  |  | ||||||
|  | const { defineConfig } = require('cypress'); | ||||||
|  | const { addMatchImageSnapshotPlugin } = require('cypress-image-snapshot/plugin'); | ||||||
|  | const coverage = require('@cypress/code-coverage/task'); | ||||||
|  |  | ||||||
|  | module.exports = defineConfig({ | ||||||
|  |   projectId: 'n2sma2', | ||||||
|  |   viewportWidth: 1440, | ||||||
|  |   viewportHeight: 1024, | ||||||
|  |   e2e: { | ||||||
|  |     specPattern: 'cypress/integration/**/*.{js,ts}', | ||||||
|  |     setupNodeEvents(on, config) { | ||||||
|  |       coverage(on, config); | ||||||
|  |       on('before:browser:launch', (browser = {}, launchOptions) => { | ||||||
|  |         if (browser.name === 'chrome' && browser.isHeadless) { | ||||||
|  |           launchOptions.args.push('--window-size=1440,1024', '--force-device-scale-factor=1'); | ||||||
|  |         } | ||||||
|  |         return launchOptions; | ||||||
|  |       }); | ||||||
|  |       addMatchImageSnapshotPlugin(on, config); | ||||||
|  |       // copy any needed variables from process.env to config.env | ||||||
|  |       config.env.useAppli = process.env.USE_APPLI ? true : false; | ||||||
|  |  | ||||||
|  |       // do not forget to return the changed config object! | ||||||
|  |       return config; | ||||||
|  |     }, | ||||||
|  |   }, | ||||||
|  |   video: false, | ||||||
|  | }); | ||||||
|  |  | ||||||
|  | require('@applitools/eyes-cypress')(module); | ||||||
| @@ -1,30 +0,0 @@ | |||||||
| import { defineConfig } from 'cypress'; |  | ||||||
| import { addMatchImageSnapshotPlugin } from 'cypress-image-snapshot/plugin'; |  | ||||||
| import coverage from '@cypress/code-coverage/task'; |  | ||||||
| import eyesPlugin from '@applitools/eyes-cypress'; |  | ||||||
| export default eyesPlugin( |  | ||||||
|   defineConfig({ |  | ||||||
|     projectId: 'n2sma2', |  | ||||||
|     viewportWidth: 1440, |  | ||||||
|     viewportHeight: 1024, |  | ||||||
|     e2e: { |  | ||||||
|       specPattern: 'cypress/integration/**/*.{js,ts}', |  | ||||||
|       setupNodeEvents(on, config) { |  | ||||||
|         coverage(on, config); |  | ||||||
|         on('before:browser:launch', (browser, launchOptions) => { |  | ||||||
|           if (browser.name === 'chrome' && browser.isHeadless) { |  | ||||||
|             launchOptions.args.push('--window-size=1440,1024', '--force-device-scale-factor=1'); |  | ||||||
|           } |  | ||||||
|           return launchOptions; |  | ||||||
|         }); |  | ||||||
|         addMatchImageSnapshotPlugin(on, config); |  | ||||||
|         // copy any needed variables from process.env to config.env |  | ||||||
|         config.env.useAppli = process.env.USE_APPLI ? true : false; |  | ||||||
|  |  | ||||||
|         // do not forget to return the changed config object! |  | ||||||
|         return config; |  | ||||||
|       }, |  | ||||||
|     }, |  | ||||||
|     video: false, |  | ||||||
|   }) |  | ||||||
| ); |  | ||||||
| @@ -583,106 +583,4 @@ describe('Gantt diagram', () => { | |||||||
|       {} |       {} | ||||||
|     ); |     ); | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|   it("should render when there's a semicolon in the title", () => { |  | ||||||
|     imgSnapshotTest( |  | ||||||
|       ` |  | ||||||
|       gantt |  | ||||||
|       title ;Gantt With a Semicolon in the Title |  | ||||||
|       dateFormat  YYYY-MM-DD |  | ||||||
|       section Section |  | ||||||
|       A task           :a1, 2014-01-01, 30d |  | ||||||
|       Another task     :after a1  , 20d |  | ||||||
|       section Another |  | ||||||
|       Task in sec      :2014-01-12  , 12d |  | ||||||
|       another task      : 24d |  | ||||||
|     `, |  | ||||||
|       {} |  | ||||||
|     ); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   it("should render when there's a semicolon in a section is true", () => { |  | ||||||
|     imgSnapshotTest( |  | ||||||
|       ` |  | ||||||
|       gantt |  | ||||||
|       title Gantt Digram |  | ||||||
|       dateFormat  YYYY-MM-DD |  | ||||||
|       section ;Section With a Semicolon |  | ||||||
|       A task           :a1, 2014-01-01, 30d |  | ||||||
|       Another task     :after a1  , 20d |  | ||||||
|       section Another |  | ||||||
|       Task in sec      :2014-01-12  , 12d |  | ||||||
|       another task      : 24d |  | ||||||
|     `, |  | ||||||
|       {} |  | ||||||
|     ); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   it("should render when there's a semicolon in the task data", () => { |  | ||||||
|     imgSnapshotTest( |  | ||||||
|       ` |  | ||||||
|       gantt |  | ||||||
|       title Gantt Digram |  | ||||||
|       dateFormat  YYYY-MM-DD |  | ||||||
|       section Section |  | ||||||
|       ;A task with a semiclon           :a1, 2014-01-01, 30d |  | ||||||
|       Another task     :after a1  , 20d |  | ||||||
|       section Another |  | ||||||
|       Task in sec      :2014-01-12  , 12d |  | ||||||
|       another task      : 24d |  | ||||||
|     `, |  | ||||||
|       {} |  | ||||||
|     ); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   it("should render when there's a hashtag in the title", () => { |  | ||||||
|     imgSnapshotTest( |  | ||||||
|       ` |  | ||||||
|       gantt |  | ||||||
|       title #Gantt With a Hashtag in the Title |  | ||||||
|       dateFormat  YYYY-MM-DD |  | ||||||
|       section Section |  | ||||||
|       A task           :a1, 2014-01-01, 30d |  | ||||||
|       Another task     :after a1  , 20d |  | ||||||
|       section Another |  | ||||||
|       Task in sec      :2014-01-12  , 12d |  | ||||||
|       another task      : 24d |  | ||||||
|     `, |  | ||||||
|       {} |  | ||||||
|     ); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   it("should render when there's a hashtag in a section is true", () => { |  | ||||||
|     imgSnapshotTest( |  | ||||||
|       ` |  | ||||||
|       gantt |  | ||||||
|       title Gantt Digram |  | ||||||
|       dateFormat  YYYY-MM-DD |  | ||||||
|       section #Section With a Hashtag |  | ||||||
|       A task           :a1, 2014-01-01, 30d |  | ||||||
|       Another task     :after a1  , 20d |  | ||||||
|       section Another |  | ||||||
|       Task in sec      :2014-01-12  , 12d |  | ||||||
|       another task      : 24d |  | ||||||
|     `, |  | ||||||
|       {} |  | ||||||
|     ); |  | ||||||
|   }); |  | ||||||
|  |  | ||||||
|   it("should render when there's a hashtag in the task data", () => { |  | ||||||
|     imgSnapshotTest( |  | ||||||
|       ` |  | ||||||
|       gantt |  | ||||||
|       title Gantt Digram |  | ||||||
|       dateFormat  YYYY-MM-DD |  | ||||||
|       section Section |  | ||||||
|       #A task with a hashtag           :a1, 2014-01-01, 30d |  | ||||||
|       Another task     :after a1  , 20d |  | ||||||
|       section Another |  | ||||||
|       Task in sec      :2014-01-12  , 12d |  | ||||||
|       another task      : 24d |  | ||||||
|     `, |  | ||||||
|       {} |  | ||||||
|     ); |  | ||||||
|   }); |  | ||||||
| }); | }); | ||||||
|   | |||||||
| @@ -792,34 +792,6 @@ context('Sequence diagram', () => { | |||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|   context('links', () => { |   context('links', () => { | ||||||
|     it('should support actor links', () => { |  | ||||||
|       renderGraph( |  | ||||||
|         ` |  | ||||||
|       sequenceDiagram |  | ||||||
|         link Alice: Dashboard @ https://dashboard.contoso.com/alice |  | ||||||
|         link Alice: Wiki @ https://wiki.contoso.com/alice |  | ||||||
|         link John: Dashboard @ https://dashboard.contoso.com/john |  | ||||||
|         link John: Wiki @ https://wiki.contoso.com/john |  | ||||||
|         Alice->>John: Hello John<br/> |  | ||||||
|         John-->>Alice: Great<br/><br/>day! |  | ||||||
|       `, |  | ||||||
|         { securityLevel: 'loose' } |  | ||||||
|       ); |  | ||||||
|       cy.get('#actor0_popup').should((popupMenu) => { |  | ||||||
|         const style = popupMenu.attr('style'); |  | ||||||
|         expect(style).to.undefined; |  | ||||||
|       }); |  | ||||||
|       cy.get('#root-0').click(); |  | ||||||
|       cy.get('#actor0_popup').should((popupMenu) => { |  | ||||||
|         const style = popupMenu.attr('style'); |  | ||||||
|         expect(style).to.match(/^display: block;$/); |  | ||||||
|       }); |  | ||||||
|       cy.get('#root-0').click(); |  | ||||||
|       cy.get('#actor0_popup').should((popupMenu) => { |  | ||||||
|         const style = popupMenu.attr('style'); |  | ||||||
|         expect(style).to.match(/^display: none;$/); |  | ||||||
|       }); |  | ||||||
|     }); |  | ||||||
|     it('should support actor links and properties EXPERIMENTAL: USE WITH CAUTION', () => { |     it('should support actor links and properties EXPERIMENTAL: USE WITH CAUTION', () => { | ||||||
|       //Be aware that the syntax for "properties" is likely to be changed. |       //Be aware that the syntax for "properties" is likely to be changed. | ||||||
|       imgSnapshotTest( |       imgSnapshotTest( | ||||||
|   | |||||||
| @@ -30,21 +30,6 @@ | |||||||
|     </pre> |     </pre> | ||||||
|     <hr /> |     <hr /> | ||||||
|  |  | ||||||
|     <pre class="mermaid"> |  | ||||||
|       gantt |  | ||||||
|         title #; Gantt Diagrams Allow Semicolons and Hashtags #;! |  | ||||||
|         accTitle: A simple sample gantt diagram |  | ||||||
|         accDescr: 2 sections with 2 tasks each, from 2014 |  | ||||||
|         dateFormat  YYYY-MM-DD |  | ||||||
|         section #;Section |  | ||||||
|         #;A task           :a1, 2014-01-01, 30d |  | ||||||
|         #;Another task     :after a1  , 20d |  | ||||||
|         section #;Another |  | ||||||
|         Task in sec      :2014-01-12  , 12d |  | ||||||
|         another task      : 24d |  | ||||||
|     </pre> |  | ||||||
|     <hr /> |  | ||||||
|  |  | ||||||
|     <pre class="mermaid"> |     <pre class="mermaid"> | ||||||
|     gantt |     gantt | ||||||
|       title Airworks roadmap |       title Airworks roadmap | ||||||
|   | |||||||
| @@ -15,7 +15,7 @@ | |||||||
|   <body> |   <body> | ||||||
|     <h1>Pie chart demos</h1> |     <h1>Pie chart demos</h1> | ||||||
|     <pre class="mermaid"> |     <pre class="mermaid"> | ||||||
|       pie title Default text position: Animal adoption |       pie title Pets adopted by volunteers | ||||||
|         accTitle: simple pie char demo |         accTitle: simple pie char demo | ||||||
|         accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs. |         accDescr: pie chart with 3 sections: dogs, cats, rats. Most are dogs. | ||||||
|         "Dogs": 386 |         "Dogs": 386 | ||||||
| @@ -27,7 +27,7 @@ | |||||||
|     <pre class="mermaid"> |     <pre class="mermaid"> | ||||||
|       %%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}}}%% |       %%{init: {"pie": {"textPosition": 0.9}, "themeVariables": {"pieOuterStrokeWidth": "5px"}}}%% | ||||||
|       pie |       pie | ||||||
|         title Offset labels close to border: Product X |         title Key elements in Product X | ||||||
|         accTitle: Key elements in Product X |         accTitle: Key elements in Product X | ||||||
|         accDescr: This is a pie chart showing the key elements in Product X. |         accDescr: This is a pie chart showing the key elements in Product X. | ||||||
|         "Calcium": 42.96 |         "Calcium": 42.96 | ||||||
| @@ -36,19 +36,6 @@ | |||||||
|         "Iron": 5 |         "Iron": 5 | ||||||
|     </pre> |     </pre> | ||||||
|  |  | ||||||
|     <pre class="mermaid"> |  | ||||||
|       %%{init: {"pie": {"textPosition": 0.45}, "themeVariables": {"pieOuterStrokeWidth": "5px"}}}%% |  | ||||||
|       pie |  | ||||||
|         title Centralized labels: Languages |  | ||||||
|         accTitle: Key elements in Product X |  | ||||||
|         accDescr: This is a pie chart showing the key elements in Product X. |  | ||||||
|         "JavaScript": 30 |  | ||||||
|         "Python": 25 |  | ||||||
|         "Java": 20 |  | ||||||
|         "C#": 15 |  | ||||||
|         "Others": 10 |  | ||||||
|     </pre> |  | ||||||
|  |  | ||||||
|     <script type="module"> |     <script type="module"> | ||||||
|       import mermaid from './mermaid.esm.mjs'; |       import mermaid from './mermaid.esm.mjs'; | ||||||
|       mermaid.initialize({ |       mermaid.initialize({ | ||||||
|   | |||||||
| @@ -23,10 +23,6 @@ | |||||||
| 			participant Alice | 			participant Alice | ||||||
| 			participant Bob | 			participant Bob | ||||||
| 			participant John as John<br />Second Line | 			participant John as John<br />Second Line | ||||||
| 			link Alice: Dashboard @ https://dashboard.contoso.com/alice |  | ||||||
| 			link Alice: Wiki @ https://wiki.contoso.com/alice |  | ||||||
| 			link John: Dashboard @ https://dashboard.contoso.com/john |  | ||||||
| 			link John: Wiki @ https://wiki.contoso.com/john |  | ||||||
| 			autonumber 10 10 | 			autonumber 10 10 | ||||||
| 			rect rgb(200, 220, 100) | 			rect rgb(200, 220, 100) | ||||||
| 			rect rgb(200, 255, 200) | 			rect rgb(200, 255, 200) | ||||||
| @@ -66,26 +62,6 @@ | |||||||
| 	</pre> | 	</pre> | ||||||
|     <hr /> |     <hr /> | ||||||
|     <pre class="mermaid"> |     <pre class="mermaid"> | ||||||
|     --- |  | ||||||
|     title: With forced menus |  | ||||||
|     config: |  | ||||||
|       sequence: |  | ||||||
|         forceMenus: true |  | ||||||
|     --- |  | ||||||
|     sequenceDiagram |  | ||||||
|       participant Alice |  | ||||||
|       participant John |  | ||||||
|       link Alice: Dashboard @ https://dashboard.contoso.com/alice |  | ||||||
|       link Alice: Wiki @ https://wiki.contoso.com/alice |  | ||||||
|       link John: Dashboard @ https://dashboard.contoso.com/john |  | ||||||
|       link John: Wiki @ https://wiki.contoso.com/john |  | ||||||
|       Alice->>John: Hello John, how are you? |  | ||||||
|       John-->>Alice: Great! |  | ||||||
|       Alice-)John: See you later! |  | ||||||
|   </pre |  | ||||||
|     > |  | ||||||
|     <hr /> |  | ||||||
|     <pre class="mermaid"> |  | ||||||
|     sequenceDiagram |     sequenceDiagram | ||||||
|       accTitle: Sequence diagram title is here |       accTitle: Sequence diagram title is here | ||||||
|       accDescr: Hello friends |       accDescr: Hello friends | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ We aim to reply within three working days, probably much sooner. | |||||||
|  |  | ||||||
| You should expect a close collaboration as we work to resolve the issue you have reported. Please reach out to <security@mermaid.live> again if you do not receive prompt attention and regular updates. | You should expect a close collaboration as we work to resolve the issue you have reported. Please reach out to <security@mermaid.live> again if you do not receive prompt attention and regular updates. | ||||||
|  |  | ||||||
| You may also reach out to the team via our public Discord chat channels; however, please make sure to e-mail <security@mermaid.live> when reporting an issue, and avoid revealing information about vulnerabilities in public as that could that could put users at risk. | You may also reach out to the team via our public Slack chat channels; however, please make sure to e-mail <security@mermaid.live> when reporting an issue, and avoid revealing information about vulnerabilities in public as that could that could put users at risk. | ||||||
|  |  | ||||||
| ## Best practices | ## Best practices | ||||||
|  |  | ||||||
|   | |||||||
										
											
												File diff suppressed because one or more lines are too long
											
										
									
								
							| @@ -31,7 +31,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) | |||||||
|  |  | ||||||
| ### mermaidAPI | ### mermaidAPI | ||||||
|  |  | ||||||
| • `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `RequiredObjectDeep`<`MermaidConfig`> = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: `Pick`<`DiagramMetadata`, `"title"`>) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> | • `Const` **mermaidAPI**: `Readonly`<{ `defaultConfig`: `MermaidConfig` = configApi.defaultConfig; `getConfig`: () => `MermaidConfig` = configApi.getConfig; `getDiagramFromText`: (`text`: `string`, `metadata`: `Pick`<`DiagramMetadata`, `"title"`>) => `Promise`<`Diagram`> ; `getSiteConfig`: () => `MermaidConfig` = configApi.getSiteConfig; `globalReset`: () => `void` ; `initialize`: (`options`: `MermaidConfig`) => `void` ; `parse`: (`text`: `string`, `parseOptions?`: [`ParseOptions`](../interfaces/mermaidAPI.ParseOptions.md)) => `Promise`<`boolean`> ; `render`: (`id`: `string`, `text`: `string`, `svgContainingElement?`: `Element`) => `Promise`<[`RenderResult`](../interfaces/mermaidAPI.RenderResult.md)> ; `reset`: () => `void` ; `setConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.setConfig; `updateSiteConfig`: (`conf`: `MermaidConfig`) => `MermaidConfig` = configApi.updateSiteConfig }> | ||||||
|  |  | ||||||
| ## mermaidAPI configuration defaults | ## mermaidAPI configuration defaults | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,9 +22,9 @@ Currently pending [IANA](https://www.iana.org/) recognition. | |||||||
|  |  | ||||||
| ## Showcase | ## Showcase | ||||||
|  |  | ||||||
| ### Mermaid Discord workspace | ### Mermaid Slack workspace | ||||||
|  |  | ||||||
| We would love to see what you create with Mermaid. Please share your creations with us in our [Discord](https://discord.gg/wwtabKgp8y) server [#showcase](https://discord.com/channels/1079455296289788015/1079502635054399649) channel. | We would love to see what you create with Mermaid. Please share your creations with us in our [Slack](https://join.slack.com/t/mermaid-talk/shared_invite/zt-22p2r8p9y-qiyP1H38GjFQ6S6jbBkOxQ) workspace [#community-showcase](https://mermaid-talk.slack.com/archives/C05NK37LT40) channel. | ||||||
|  |  | ||||||
| ### Add to Mermaid Ecosystem | ### Add to Mermaid Ecosystem | ||||||
|  |  | ||||||
|   | |||||||
| @@ -22,7 +22,7 @@ It is a JavaScript based diagramming and charting tool that renders Markdown-ins | |||||||
| [](https://coveralls.io/github/mermaid-js/mermaid?branch=master) | [](https://coveralls.io/github/mermaid-js/mermaid?branch=master) | ||||||
| [](https://www.jsdelivr.com/package/npm/mermaid) | [](https://www.jsdelivr.com/package/npm/mermaid) | ||||||
| [](https://www.npmjs.com/package/mermaid) | [](https://www.npmjs.com/package/mermaid) | ||||||
| [](https://discord.gg/wwtabKgp8y) | [](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) | ||||||
| [](https://twitter.com/mermaidjs_) | [](https://twitter.com/mermaidjs_) | ||||||
|  |  | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -1134,19 +1134,7 @@ flowchart TD | |||||||
|     B-->E(A fa:fa-camera-retro perhaps?) |     B-->E(A fa:fa-camera-retro perhaps?) | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Mermaid supports Font Awesome if the CSS is included on the website. | Mermaid is compatible with Font Awesome up to version 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). | ||||||
| Mermaid does not have any restriction on the version of Font Awesome that can be used. |  | ||||||
|  |  | ||||||
| Please refer the [Official Font Awesome Documentation](https://fontawesome.com/start) on how to include it in your website. |  | ||||||
|  |  | ||||||
| Adding this snippet in the `<head>` would add support for Font Awesome v6.5.1 |  | ||||||
|  |  | ||||||
| ```html |  | ||||||
| <link |  | ||||||
|   href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" |  | ||||||
|   rel="stylesheet" |  | ||||||
| /> |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Graph declarations with spaces between vertices and link and without semicolon | ## Graph declarations with spaces between vertices and link and without semicolon | ||||||
|  |  | ||||||
|   | |||||||
| @@ -114,30 +114,7 @@ gantt | |||||||
|     Add another diagram to demo page    :48h |     Add another diagram to demo page    :48h | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Tasks are by default sequential. A task start date defaults to the end date of the preceding task. | It is possible to set multiple dependencies separated by space: | ||||||
|  |  | ||||||
| A colon, `:`, separates the task title from its metadata. |  | ||||||
| Metadata items are separated by a comma, `,`. Valid tags are `active`, `done`, `crit`, and `milestone`. Tags are optional, but if used, they must be specified first. |  | ||||||
| After processing the tags, the remaining metadata items are interpreted as follows: |  | ||||||
|  |  | ||||||
| 1.  If a single item is specified, it determines when the task ends. It can either be a specific date/time or a duration. If a duration is specified, it is added to the start date of the task to determine the end date of the task, taking into account any exclusions. |  | ||||||
| 2.  If two items are specified, the last item is interpreted as in the previous case. The first item can either specify an explicit start date/time (in the format specified by `dateFormat`) or reference another task using `after <otherTaskID> [[otherTaskID2 [otherTaskID3]]...]`. In the latter case, the start date of the task will be set according to the latest end date of any referenced task. |  | ||||||
| 3.  If three items are specified, the last two will be interpreted as in the previous case. The first item will denote the ID of the task, which can be referenced using the `later <taskID>` syntax. |  | ||||||
|  |  | ||||||
| | Metadata syntax                            | Start date                                          | End date                                    | ID       | |  | ||||||
| | ------------------------------------------ | --------------------------------------------------- | ------------------------------------------- | -------- | |  | ||||||
| | `<taskID>, <startDate>, <endDate>`         | `startdate` as interpreted using `dateformat`       | `endDate` as interpreted using `dateformat` | `taskID` | |  | ||||||
| | `<taskID>, <startDate>, <length>`          | `startdate` as interpreted using `dateformat`       | Start date + `length`                       | `taskID` | |  | ||||||
| | `<taskID>, after <otherTaskId>, <endDate>` | End date of previously specified task `otherTaskID` | `endDate` as interpreted using `dateformat` | `taskID` | |  | ||||||
| | `<taskID>, after <otherTaskId>, <length>`  | End date of previously specified task `otherTaskID` | Start date + `length`                       | `taskID` | |  | ||||||
| | `<startDate>, <endDate>`                   | `startdate` as interpreted using `dateformat`       | `enddate` as interpreted using `dateformat` | n/a      | |  | ||||||
| | `<startDate>, <length>`                    | `startdate` as interpreted using `dateformat`       | Start date + `length`                       | n/a      | |  | ||||||
| | `after <otherTaskID>, <endDate>`           | End date of previously specified task `otherTaskID` | `enddate` as interpreted using `dateformat` | n/a      | |  | ||||||
| | `after <otherTaskID>, <length>`            | End date of previously specified task `otherTaskID` | Start date + `length`                       | n/a      | |  | ||||||
| | `<endDate>`                                | End date of preceding task                          | `enddate` as interpreted using `dateformat` | n/a      | |  | ||||||
| | `<length>`                                 | End date of preceding task                          | Start date + `length`                       | n/a      | |  | ||||||
|  |  | ||||||
| For simplicity, the table does not show the use of multiple tasks listed with the `after` keyword. Here is an example of how to use it and how it's interpreted: |  | ||||||
|  |  | ||||||
| ```mermaid-example | ```mermaid-example | ||||||
| gantt | gantt | ||||||
|   | |||||||
| @@ -163,11 +163,11 @@ timeline | |||||||
| timeline | timeline | ||||||
|         title MermaidChart 2023 Timeline |         title MermaidChart 2023 Timeline | ||||||
|         section 2023 Q1 <br> Release Personal Tier |         section 2023 Q1 <br> Release Personal Tier | ||||||
|           Bullet 1 : sub-point 1a : sub-point 1b |           Buttet 1 : sub-point 1a : sub-point 1b | ||||||
|                : sub-point 1c |                : sub-point 1c | ||||||
|           Bullet 2 : sub-point 2a : sub-point 2b |           Bullet 2 : sub-point 2a : sub-point 2b | ||||||
|         section 2023 Q2 <br> Release XYZ Tier |         section 2023 Q2 <br> Release XYZ Tier | ||||||
|           Bullet 3 : sub-point <br> 3a : sub-point 3b |           Buttet 3 : sub-point <br> 3a : sub-point 3b | ||||||
|                : sub-point 3c |                : sub-point 3c | ||||||
|           Bullet 4 : sub-point 4a : sub-point 4b |           Bullet 4 : sub-point 4a : sub-point 4b | ||||||
| ``` | ``` | ||||||
| @@ -176,11 +176,11 @@ timeline | |||||||
| timeline | timeline | ||||||
|         title MermaidChart 2023 Timeline |         title MermaidChart 2023 Timeline | ||||||
|         section 2023 Q1 <br> Release Personal Tier |         section 2023 Q1 <br> Release Personal Tier | ||||||
|           Bullet 1 : sub-point 1a : sub-point 1b |           Buttet 1 : sub-point 1a : sub-point 1b | ||||||
|                : sub-point 1c |                : sub-point 1c | ||||||
|           Bullet 2 : sub-point 2a : sub-point 2b |           Bullet 2 : sub-point 2a : sub-point 2b | ||||||
|         section 2023 Q2 <br> Release XYZ Tier |         section 2023 Q2 <br> Release XYZ Tier | ||||||
|           Bullet 3 : sub-point <br> 3a : sub-point 3b |           Buttet 3 : sub-point <br> 3a : sub-point 3b | ||||||
|                : sub-point 3c |                : sub-point 3c | ||||||
|           Bullet 4 : sub-point 4a : sub-point 4b |           Bullet 4 : sub-point 4a : sub-point 4b | ||||||
| ``` | ``` | ||||||
|   | |||||||
							
								
								
									
										11
									
								
								package.json
									
									
									
									
									
								
							
							
						
						
									
										11
									
								
								package.json
									
									
									
									
									
								
							| @@ -61,11 +61,11 @@ | |||||||
|     ] |     ] | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|     "@applitools/eyes-cypress": "^3.40.6", |     "@applitools/eyes-cypress": "^3.33.1", | ||||||
|     "@commitlint/cli": "^17.6.1", |     "@commitlint/cli": "^17.6.1", | ||||||
|     "@commitlint/config-conventional": "^17.6.1", |     "@commitlint/config-conventional": "^17.6.1", | ||||||
|     "@cspell/eslint-plugin": "^6.31.1", |     "@cspell/eslint-plugin": "^6.31.1", | ||||||
|     "@cypress/code-coverage": "^3.12.18", |     "@cypress/code-coverage": "^3.10.7", | ||||||
|     "@rollup/plugin-typescript": "^11.1.1", |     "@rollup/plugin-typescript": "^11.1.1", | ||||||
|     "@types/cors": "^2.8.13", |     "@types/cors": "^2.8.13", | ||||||
|     "@types/eslint": "^8.37.0", |     "@types/eslint": "^8.37.0", | ||||||
| @@ -85,7 +85,7 @@ | |||||||
|     "ajv": "^8.12.0", |     "ajv": "^8.12.0", | ||||||
|     "concurrently": "^8.0.1", |     "concurrently": "^8.0.1", | ||||||
|     "cors": "^2.8.5", |     "cors": "^2.8.5", | ||||||
|     "cypress": "^12.17.4", |     "cypress": "^12.10.0", | ||||||
|     "cypress-image-snapshot": "^4.0.1", |     "cypress-image-snapshot": "^4.0.1", | ||||||
|     "esbuild": "^0.19.0", |     "esbuild": "^0.19.0", | ||||||
|     "eslint": "^8.47.0", |     "eslint": "^8.47.0", | ||||||
| @@ -127,10 +127,5 @@ | |||||||
|   }, |   }, | ||||||
|   "nyc": { |   "nyc": { | ||||||
|     "report-dir": "coverage/cypress" |     "report-dir": "coverage/cypress" | ||||||
|   }, |  | ||||||
|   "pnpm": { |  | ||||||
|     "patchedDependencies": { |  | ||||||
|       "cytoscape@3.28.1": "patches/cytoscape@3.28.1.patch" |  | ||||||
|     } |  | ||||||
|   } |   } | ||||||
| } | } | ||||||
|   | |||||||
| @@ -39,10 +39,15 @@ | |||||||
|   }, |   }, | ||||||
|   "dependencies": { |   "dependencies": { | ||||||
|     "@braintree/sanitize-url": "^6.0.1", |     "@braintree/sanitize-url": "^6.0.1", | ||||||
|  |     "cytoscape": "^3.23.0", | ||||||
|  |     "cytoscape-cose-bilkent": "^4.1.0", | ||||||
|  |     "cytoscape-fcose": "^2.1.0", | ||||||
|     "d3": "^7.0.0", |     "d3": "^7.0.0", | ||||||
|     "khroma": "^2.0.0" |     "khroma": "^2.0.0", | ||||||
|  |     "non-layered-tidy-tree-layout": "^2.0.2" | ||||||
|   }, |   }, | ||||||
|   "devDependencies": { |   "devDependencies": { | ||||||
|  |     "@types/cytoscape": "^3.19.9", | ||||||
|     "concurrently": "^8.0.0", |     "concurrently": "^8.0.0", | ||||||
|     "rimraf": "^5.0.0", |     "rimraf": "^5.0.0", | ||||||
|     "mermaid": "workspace:*" |     "mermaid": "workspace:*" | ||||||
|   | |||||||
| @@ -62,8 +62,9 @@ | |||||||
|     "@braintree/sanitize-url": "^6.0.1", |     "@braintree/sanitize-url": "^6.0.1", | ||||||
|     "@types/d3-scale": "^4.0.3", |     "@types/d3-scale": "^4.0.3", | ||||||
|     "@types/d3-scale-chromatic": "^3.0.0", |     "@types/d3-scale-chromatic": "^3.0.0", | ||||||
|     "cytoscape": "^3.28.1", |     "cytoscape": "^3.23.0", | ||||||
|     "cytoscape-cose-bilkent": "^4.1.0", |     "cytoscape-cose-bilkent": "^4.1.0", | ||||||
|  |     "cytoscape-fcose": "^2.1.0", | ||||||
|     "d3": "^7.4.0", |     "d3": "^7.4.0", | ||||||
|     "d3-sankey": "^0.12.3", |     "d3-sankey": "^0.12.3", | ||||||
|     "dagre-d3-es": "7.0.10", |     "dagre-d3-es": "7.0.10", | ||||||
| @@ -115,7 +116,7 @@ | |||||||
|     "remark-gfm": "^3.0.1", |     "remark-gfm": "^3.0.1", | ||||||
|     "rimraf": "^5.0.0", |     "rimraf": "^5.0.0", | ||||||
|     "start-server-and-test": "^2.0.0", |     "start-server-and-test": "^2.0.0", | ||||||
|     "type-fest": "^4.10.1", |     "type-fest": "^4.1.0", | ||||||
|     "typedoc": "^0.25.0", |     "typedoc": "^0.25.0", | ||||||
|     "typedoc-plugin-markdown": "^3.15.2", |     "typedoc-plugin-markdown": "^3.15.2", | ||||||
|     "typescript": "^5.0.4", |     "typescript": "^5.0.4", | ||||||
|   | |||||||
| @@ -4,10 +4,8 @@ import theme from './themes/index.js'; | |||||||
| import config from './defaultConfig.js'; | import config from './defaultConfig.js'; | ||||||
| import type { MermaidConfig } from './config.type.js'; | import type { MermaidConfig } from './config.type.js'; | ||||||
| import { sanitizeDirective } from './utils/sanitizeDirective.js'; | import { sanitizeDirective } from './utils/sanitizeDirective.js'; | ||||||
| import lodashGet from 'lodash-es/get.js'; |  | ||||||
| import type { RequiredDeep, Get, Paths } from 'type-fest'; |  | ||||||
|  |  | ||||||
| export const defaultConfig: RequiredDeep<MermaidConfig> = Object.freeze(config); | export const defaultConfig: MermaidConfig = Object.freeze(config); | ||||||
|  |  | ||||||
| let siteConfig: MermaidConfig = assignWithDepth({}, defaultConfig); | let siteConfig: MermaidConfig = assignWithDepth({}, defaultConfig); | ||||||
| let configFromInitialize: MermaidConfig; | let configFromInitialize: MermaidConfig; | ||||||
| @@ -247,20 +245,3 @@ const checkConfig = (config: MermaidConfig) => { | |||||||
|     issueWarning('LAZY_LOAD_DEPRECATED'); |     issueWarning('LAZY_LOAD_DEPRECATED'); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Get a value from the provided config, or the default config if it doesn't exist |  | ||||||
|  * @param config - Mermaid Config |  | ||||||
|  * @param path - Path of the value to get |  | ||||||
|  * @returns Value from provided config if it exists, otherwise from default config |  | ||||||
|  */ |  | ||||||
| export const getConfigValue = <Path extends Paths<RequiredDeep<MermaidConfig>>>( |  | ||||||
|   config: MermaidConfig, |  | ||||||
|   path: Path |  | ||||||
| ): Get<RequiredDeep<MermaidConfig>, Path> => { |  | ||||||
|   let value = lodashGet(config, path) as Get<RequiredDeep<MermaidConfig>, Path>; |  | ||||||
|   if (!value) { |  | ||||||
|     value = lodashGet(defaultConfig, path) as Get<RequiredDeep<MermaidConfig>, Path>; |  | ||||||
|   } |  | ||||||
|   return value; |  | ||||||
| }; |  | ||||||
|   | |||||||
| @@ -116,8 +116,8 @@ export const clear = function () { | |||||||
|   commonClear(); |   commonClear(); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const getClass = function (id: string): ClassNode { | export const getClass = function (className: string): ClassNode { | ||||||
|   return classes[id]; |   return classes[className]; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const getClasses = function (): ClassMap { | export const getClasses = function (): ClassMap { | ||||||
| @@ -156,6 +156,7 @@ export const addRelation = function (relation: ClassRelation) { | |||||||
|  * @public |  * @public | ||||||
|  */ |  */ | ||||||
| export const addAnnotation = function (className: string, annotation: string) { | export const addAnnotation = function (className: string, annotation: string) { | ||||||
|  |   addClass(className); | ||||||
|   const validatedClassName = splitClassNameAndType(className).className; |   const validatedClassName = splitClassNameAndType(className).className; | ||||||
|   classes[validatedClassName].annotations.push(annotation); |   classes[validatedClassName].annotations.push(annotation); | ||||||
| }; | }; | ||||||
| @@ -199,6 +200,8 @@ export const addMembers = function (className: string, members: string[]) { | |||||||
| }; | }; | ||||||
|  |  | ||||||
| export const addNote = function (text: string, className: string) { | export const addNote = function (text: string, className: string) { | ||||||
|  |   addClass(className); | ||||||
|  |  | ||||||
|   const note = { |   const note = { | ||||||
|     id: `note${notes.length}`, |     id: `note${notes.length}`, | ||||||
|     class: className, |     class: className, | ||||||
| @@ -217,17 +220,19 @@ export const cleanupLabel = function (label: string) { | |||||||
| /** | /** | ||||||
|  * Called by parser when assigning cssClass to a class |  * Called by parser when assigning cssClass to a class | ||||||
|  * |  * | ||||||
|  * @param ids - Comma separated list of ids |  * @param classNames - Comma separated list of ids | ||||||
|  * @param className - Class to add |  * @param cssClass - Class to add | ||||||
|  */ |  */ | ||||||
| export const setCssClass = function (ids: string, className: string) { | export const setCssClass = function (classNames: string, cssClass: string) { | ||||||
|   ids.split(',').forEach(function (_id) { |   classNames.split(',').forEach(function (_className) { | ||||||
|     let id = _id; |     let className = _className; | ||||||
|     if (_id[0].match(/\d/)) { |     addClass(className); | ||||||
|       id = MERMAID_DOM_ID_PREFIX + id; |  | ||||||
|  |     if (_className[0].match(/\d/)) { | ||||||
|  |       className = MERMAID_DOM_ID_PREFIX + className; | ||||||
|     } |     } | ||||||
|     if (classes[id] !== undefined) { |     if (classes[className] !== undefined) { | ||||||
|       classes[id].cssClasses.push(className); |       classes[className].cssClasses.push(cssClass); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
| @@ -235,66 +240,73 @@ export const setCssClass = function (ids: string, className: string) { | |||||||
| /** | /** | ||||||
|  * Called by parser when a tooltip is found, e.g. a clickable element. |  * Called by parser when a tooltip is found, e.g. a clickable element. | ||||||
|  * |  * | ||||||
|  * @param ids - Comma separated list of ids |  * @param classNames - Comma separated list of ids | ||||||
|  * @param tooltip - Tooltip to add |  * @param tooltip - Tooltip to add | ||||||
|  */ |  */ | ||||||
| const setTooltip = function (ids: string, tooltip?: string) { | const setTooltip = function (classNames: string, tooltip?: string) { | ||||||
|   ids.split(',').forEach(function (id) { |   classNames.split(',').forEach(function (className) { | ||||||
|     if (tooltip !== undefined) { |     if (tooltip !== undefined) { | ||||||
|       classes[id].tooltip = sanitizeText(tooltip); |       addClass(className); | ||||||
|  |       classes[className].tooltip = sanitizeText(tooltip); | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const getTooltip = function (id: string, namespace?: string) { | export const getTooltip = function (className: string, namespace?: string) { | ||||||
|   if (namespace) { |   if (namespace) { | ||||||
|     return namespaces[namespace].classes[id].tooltip; |     return namespaces[namespace].classes[className].tooltip; | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return classes[id].tooltip; |   return classes[className].tooltip; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Called by parser when a link is found. Adds the URL to the vertex data. |  * Called by parser when a link is found. Adds the URL to the vertex data. | ||||||
|  * |  * | ||||||
|  * @param ids - Comma separated list of ids |  * @param classNames - Comma separated list of class ids | ||||||
|  * @param linkStr - URL to create a link for |  * @param linkStr - URL to create a link for | ||||||
|  * @param target - Target of the link, _blank by default as originally defined in the svgDraw.js file |  * @param target - Target of the link, _blank by default as originally defined in the svgDraw.js file | ||||||
|  */ |  */ | ||||||
| export const setLink = function (ids: string, linkStr: string, target: string) { | export const setLink = function (classNames: string, linkStr: string, target: string) { | ||||||
|   const config = getConfig(); |   const config = getConfig(); | ||||||
|   ids.split(',').forEach(function (_id) { |   classNames.split(',').forEach(function (_className) { | ||||||
|     let id = _id; |     let className = _className; | ||||||
|     if (_id[0].match(/\d/)) { |     if (_className[0].match(/\d/)) { | ||||||
|       id = MERMAID_DOM_ID_PREFIX + id; |       className = MERMAID_DOM_ID_PREFIX + className; | ||||||
|     } |     } | ||||||
|     if (classes[id] !== undefined) { |     addClass(className); | ||||||
|       classes[id].link = utils.formatUrl(linkStr, config); |     if (classes[className] !== undefined) { | ||||||
|  |       classes[className].link = utils.formatUrl(linkStr, config); | ||||||
|       if (config.securityLevel === 'sandbox') { |       if (config.securityLevel === 'sandbox') { | ||||||
|         classes[id].linkTarget = '_top'; |         classes[className].linkTarget = '_top'; | ||||||
|       } else if (typeof target === 'string') { |       } else if (typeof target === 'string') { | ||||||
|         classes[id].linkTarget = sanitizeText(target); |         classes[className].linkTarget = sanitizeText(target); | ||||||
|       } else { |       } else { | ||||||
|         classes[id].linkTarget = '_blank'; |         classes[className].linkTarget = '_blank'; | ||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|   setCssClass(ids, 'clickable'); |   setCssClass(classNames, 'clickable'); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| /** | /** | ||||||
|  * Called by parser when a click definition is found. Registers an event handler. |  * Called by parser when a click definition is found. Registers an event handler. | ||||||
|  * |  * | ||||||
|  * @param ids - Comma separated list of ids |  * @param classNames - Comma separated list of class ids | ||||||
|  * @param functionName - Function to be called on click |  * @param functionName - Function to be called on click | ||||||
|  * @param functionArgs - Function args the function should be called with |  * @param functionArgs - Function args the function should be called with | ||||||
|  */ |  */ | ||||||
| export const setClickEvent = function (ids: string, functionName: string, functionArgs: string) { | export const setClickEvent = function ( | ||||||
|   ids.split(',').forEach(function (id) { |   classNames: string, | ||||||
|     setClickFunc(id, functionName, functionArgs); |   functionName: string, | ||||||
|     classes[id].haveCallback = true; |   functionArgs: string | ||||||
|  | ) { | ||||||
|  |   classNames.split(',').forEach(function (className) { | ||||||
|  |     addClass(className); | ||||||
|  |     setClickFunc(className, functionName, functionArgs); | ||||||
|  |     classes[className].haveCallback = true; | ||||||
|   }); |   }); | ||||||
|   setCssClass(ids, 'clickable'); |   setCssClass(classNames, 'clickable'); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const setClickFunc = function (_domId: string, functionName: string, functionArgs: string) { | const setClickFunc = function (_domId: string, functionName: string, functionArgs: string) { | ||||||
| @@ -308,6 +320,7 @@ const setClickFunc = function (_domId: string, functionName: string, functionArg | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   const id = domId; |   const id = domId; | ||||||
|  |   addClass(id); | ||||||
|   if (classes[id] !== undefined) { |   if (classes[id] !== undefined) { | ||||||
|     const elemId = lookUpDomId(id); |     const elemId = lookUpDomId(id); | ||||||
|     let argList: string[] = []; |     let argList: string[] = []; | ||||||
| @@ -447,9 +460,8 @@ const getNamespaces = function (): NamespaceMap { | |||||||
|  * @public |  * @public | ||||||
|  */ |  */ | ||||||
| export const addClassesToNamespace = function (id: string, classNames: string[]) { | export const addClassesToNamespace = function (id: string, classNames: string[]) { | ||||||
|   if (namespaces[id] === undefined) { |   addNamespace(id); | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   for (const name of classNames) { |   for (const name of classNames) { | ||||||
|     const { className } = splitClassNameAndType(name); |     const { className } = splitClassNameAndType(name); | ||||||
|     classes[className].parent = id; |     classes[className].parent = id; | ||||||
| @@ -458,6 +470,7 @@ export const addClassesToNamespace = function (id: string, classNames: string[]) | |||||||
| }; | }; | ||||||
|  |  | ||||||
| export const setCssStyle = function (id: string, styles: string[]) { | export const setCssStyle = function (id: string, styles: string[]) { | ||||||
|  |   addClass(id); | ||||||
|   const thisClass = classes[id]; |   const thisClass = classes[id]; | ||||||
|   if (!styles || !thisClass) { |   if (!styles || !thisClass) { | ||||||
|     return; |     return; | ||||||
|   | |||||||
| @@ -258,9 +258,30 @@ class C13["With Città foreign language"] | |||||||
|       expect(classDb.getClass('C13').label).toBe('With Città foreign language'); |       expect(classDb.getClass('C13').label).toBe('With Città foreign language'); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     it('should handle link if class not created first', function () { | ||||||
|  |       const str = `classDiagram | ||||||
|  |                       link Class1 "/#anchor"`; | ||||||
|  |       parser.parse(str); | ||||||
|  |  | ||||||
|  |       const actual = parser.yy.getClass('Class1'); | ||||||
|  |       expect(actual.link).toBe('/#anchor'); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|  |     it('should handle "note for" without pre-defining class', function () { | ||||||
|  |       const str = `classDiagram | ||||||
|  |                       note for Class1 "test"`; | ||||||
|  |       parser.parse(str); | ||||||
|  |  | ||||||
|  |       const actual = parser.yy.getClass('Class1'); | ||||||
|  |  | ||||||
|  |       expect(classDb.getNotes()[0].text).toEqual(`test`); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     it('should handle "note for"', function () { |     it('should handle "note for"', function () { | ||||||
|       const str = 'classDiagram\n' + 'Class11 <|.. Class12\n' + 'note for Class11 "test"\n'; |       const str = 'classDiagram\n' + 'Class11 <|.. Class12\n' + 'note for Class11 "test"\n'; | ||||||
|       parser.parse(str); |       parser.parse(str); | ||||||
|  |  | ||||||
|  |       expect(classDb.getNotes()[0].text).toEqual(`test`); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|     it('should handle "note"', function () { |     it('should handle "note"', function () { | ||||||
| @@ -632,6 +653,16 @@ foo() | |||||||
|       classDb.clear(); |       classDb.clear(); | ||||||
|       parser.yy = classDb; |       parser.yy = classDb; | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     it('should handle link if class not created first', function () { | ||||||
|  |       const str = `classDiagram | ||||||
|  |                       link Class1 "/#anchor"`; | ||||||
|  |       parser.parse(str); | ||||||
|  |  | ||||||
|  |       const actual = parser.yy.getClass('Class1'); | ||||||
|  |       expect(actual.link).toBe('/#anchor'); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     it('should handle href link', function () { |     it('should handle href link', function () { | ||||||
|       spyOn(classDb, 'setLink'); |       spyOn(classDb, 'setLink'); | ||||||
|       const str = 'classDiagram\n' + 'class Class1 \n' + 'click Class1 href "google.com" '; |       const str = 'classDiagram\n' + 'class Class1 \n' + 'click Class1 href "google.com" '; | ||||||
| @@ -690,6 +721,15 @@ foo() | |||||||
|       expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); |       expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     it('should handle function call when class not created first', function () { | ||||||
|  |       spyOn(classDb, 'setClickEvent'); | ||||||
|  |       const str = `classDiagram | ||||||
|  |         click Class1 call functionCall()`; | ||||||
|  |       parser.parse(str); | ||||||
|  |  | ||||||
|  |       expect(classDb.setClickEvent).toHaveBeenCalledWith('Class1', 'functionCall'); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     it('should handle function call with tooltip', function () { |     it('should handle function call with tooltip', function () { | ||||||
|       spyOn(classDb, 'setClickEvent'); |       spyOn(classDb, 'setClickEvent'); | ||||||
|       spyOn(classDb, 'setTooltip'); |       spyOn(classDb, 'setTooltip'); | ||||||
| @@ -744,6 +784,17 @@ foo() | |||||||
|       parser.yy = classDb; |       parser.yy = classDb; | ||||||
|     }); |     }); | ||||||
|  |  | ||||||
|  |     it('should handle annotation if class not created first', function () { | ||||||
|  |       const str = 'classDiagram\n' + '<<interface>> Class1'; | ||||||
|  |       parser.parse(str); | ||||||
|  |  | ||||||
|  |       const actual = parser.yy.getClass('Class1'); | ||||||
|  |       expect(actual.annotations.length).toBe(1); | ||||||
|  |       expect(actual.members.length).toBe(0); | ||||||
|  |       expect(actual.methods.length).toBe(0); | ||||||
|  |       expect(actual.annotations[0]).toBe('interface'); | ||||||
|  |     }); | ||||||
|  |  | ||||||
|     it('should handle class annotations', function () { |     it('should handle class annotations', function () { | ||||||
|       const str = 'classDiagram\n' + 'class Class1\n' + '<<interface>> Class1'; |       const str = 'classDiagram\n' + 'class Class1\n' + '<<interface>> Class1'; | ||||||
|       parser.parse(str); |       parser.parse(str); | ||||||
|   | |||||||
| @@ -18,18 +18,13 @@ export const getRows = (s?: string): string[] => { | |||||||
|   return str.split('#br#'); |   return str.split('#br#'); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const setupDompurifyHooksIfNotSetup = (() => { | /** | ||||||
|   let setup = false; |  * Removes script tags from a text | ||||||
|  |  * | ||||||
|   return () => { |  * @param txt - The text to sanitize | ||||||
|     if (!setup) { |  * @returns The safer text | ||||||
|       setupDompurifyHooks(); |  */ | ||||||
|       setup = true; | export const removeScript = (txt: string): string => { | ||||||
|     } |  | ||||||
|   }; |  | ||||||
| })(); |  | ||||||
|  |  | ||||||
| function setupDompurifyHooks() { |  | ||||||
|   const TEMPORARY_ATTRIBUTE = 'data-temp-href-target'; |   const TEMPORARY_ATTRIBUTE = 'data-temp-href-target'; | ||||||
|  |  | ||||||
|   DOMPurify.addHook('beforeSanitizeAttributes', (node: Element) => { |   DOMPurify.addHook('beforeSanitizeAttributes', (node: Element) => { | ||||||
| @@ -38,6 +33,8 @@ function setupDompurifyHooks() { | |||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
|  |  | ||||||
|  |   const sanitizedText = DOMPurify.sanitize(txt); | ||||||
|  |  | ||||||
|   DOMPurify.addHook('afterSanitizeAttributes', (node: Element) => { |   DOMPurify.addHook('afterSanitizeAttributes', (node: Element) => { | ||||||
|     if (node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) { |     if (node.tagName === 'A' && node.hasAttribute(TEMPORARY_ATTRIBUTE)) { | ||||||
|       node.setAttribute('target', node.getAttribute(TEMPORARY_ATTRIBUTE) || ''); |       node.setAttribute('target', node.getAttribute(TEMPORARY_ATTRIBUTE) || ''); | ||||||
| @@ -47,18 +44,6 @@ function setupDompurifyHooks() { | |||||||
|       } |       } | ||||||
|     } |     } | ||||||
|   }); |   }); | ||||||
| } |  | ||||||
|  |  | ||||||
| /** |  | ||||||
|  * Removes script tags from a text |  | ||||||
|  * |  | ||||||
|  * @param txt - The text to sanitize |  | ||||||
|  * @returns The safer text |  | ||||||
|  */ |  | ||||||
| export const removeScript = (txt: string): string => { |  | ||||||
|   setupDompurifyHooksIfNotSetup(); |  | ||||||
|  |  | ||||||
|   const sanitizedText = DOMPurify.sanitize(txt); |  | ||||||
|  |  | ||||||
|   return sanitizedText; |   return sanitizedText; | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -27,10 +27,11 @@ accDescr\s*"{"\s*                                { this.begin("acc_descr_multili | |||||||
|  |  | ||||||
| \%\%(?!\{)*[^\n]*                                               /* skip comments */ | \%\%(?!\{)*[^\n]*                                               /* skip comments */ | ||||||
| [^\}]\%\%*[^\n]*                                                /* skip comments */ | [^\}]\%\%*[^\n]*                                                /* skip comments */ | ||||||
| \%\%*[^\n]*[\n]*                                                /* do nothing */ | \%\%*[^\n]*[\n]*           /* do nothing */ | ||||||
|  |  | ||||||
| [\n]+                   return 'NL'; | [\n]+                   return 'NL'; | ||||||
| \s+                     /* skip whitespace */ | \s+                     /* skip whitespace */ | ||||||
|  | \#[^\n]*                /* skip comments */ | ||||||
| \%%[^\n]*               /* skip comments */ | \%%[^\n]*               /* skip comments */ | ||||||
|  |  | ||||||
| /* | /* | ||||||
| @@ -85,10 +86,10 @@ weekday\s+friday                return 'weekday_friday' | |||||||
| weekday\s+saturday              return 'weekday_saturday' | weekday\s+saturday              return 'weekday_saturday' | ||||||
| weekday\s+sunday                return 'weekday_sunday' | weekday\s+sunday                return 'weekday_sunday' | ||||||
| \d\d\d\d"-"\d\d"-"\d\d          return 'date'; | \d\d\d\d"-"\d\d"-"\d\d          return 'date'; | ||||||
| "title"\s[^\n]+               return 'title'; | "title"\s[^#\n;]+               return 'title'; | ||||||
| "accDescription"\s[^#\n;]+      return 'accDescription' | "accDescription"\s[^#\n;]+      return 'accDescription' | ||||||
| "section"\s[^\n]+            return 'section'; | "section"\s[^#:\n;]+            return 'section'; | ||||||
| [^:\n]+                       return 'taskTxt'; | [^#:\n;]+                       return 'taskTxt'; | ||||||
| ":"[^#\n;]+                     return 'taskData'; | ":"[^#\n;]+                     return 'taskData'; | ||||||
| ":"                             return ':'; | ":"                             return ':'; | ||||||
| <<EOF>>                         return 'EOF'; | <<EOF>>                         return 'EOF'; | ||||||
|   | |||||||
| @@ -28,12 +28,8 @@ describe('when parsing a gantt diagram it', function () { | |||||||
|   }); |   }); | ||||||
|   it('should handle a title definition', function () { |   it('should handle a title definition', function () { | ||||||
|     const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid'; |     const str = 'gantt\ndateFormat yyyy-mm-dd\ntitle Adding gantt diagram functionality to mermaid'; | ||||||
|     const semi = 'gantt\ndateFormat yyyy-mm-dd\ntitle ;Gantt diagram titles support semicolons'; |  | ||||||
|     const hash = 'gantt\ndateFormat yyyy-mm-dd\ntitle #Gantt diagram titles support hashtags'; |  | ||||||
|  |  | ||||||
|     expect(parserFnConstructor(str)).not.toThrow(); |     expect(parserFnConstructor(str)).not.toThrow(); | ||||||
|     expect(parserFnConstructor(semi)).not.toThrow(); |  | ||||||
|     expect(parserFnConstructor(hash)).not.toThrow(); |  | ||||||
|   }); |   }); | ||||||
|   it('should handle an excludes definition', function () { |   it('should handle an excludes definition', function () { | ||||||
|     const str = |     const str = | ||||||
| @@ -57,23 +53,7 @@ describe('when parsing a gantt diagram it', function () { | |||||||
|       'excludes weekdays 2019-02-01\n' + |       'excludes weekdays 2019-02-01\n' + | ||||||
|       'section Documentation'; |       'section Documentation'; | ||||||
|  |  | ||||||
|     const semi = |  | ||||||
|       'gantt\n' + |  | ||||||
|       'dateFormat yyyy-mm-dd\n' + |  | ||||||
|       'title Adding gantt diagram functionality to mermaid\n' + |  | ||||||
|       'excludes weekdays 2019-02-01\n' + |  | ||||||
|       'section ;Documentation'; |  | ||||||
|  |  | ||||||
|     const hash = |  | ||||||
|       'gantt\n' + |  | ||||||
|       'dateFormat yyyy-mm-dd\n' + |  | ||||||
|       'title Adding gantt diagram functionality to mermaid\n' + |  | ||||||
|       'excludes weekdays 2019-02-01\n' + |  | ||||||
|       'section #Documentation'; |  | ||||||
|  |  | ||||||
|     expect(parserFnConstructor(str)).not.toThrow(); |     expect(parserFnConstructor(str)).not.toThrow(); | ||||||
|     expect(parserFnConstructor(semi)).not.toThrow(); |  | ||||||
|     expect(parserFnConstructor(hash)).not.toThrow(); |  | ||||||
|   }); |   }); | ||||||
|   it('should handle multiline section titles with different line breaks', function () { |   it('should handle multiline section titles with different line breaks', function () { | ||||||
|     const str = |     const str = | ||||||
| @@ -93,23 +73,7 @@ describe('when parsing a gantt diagram it', function () { | |||||||
|       'section Documentation\n' + |       'section Documentation\n' + | ||||||
|       'Design jison grammar:des1, 2014-01-01, 2014-01-04'; |       'Design jison grammar:des1, 2014-01-01, 2014-01-04'; | ||||||
|  |  | ||||||
|     const semi = |  | ||||||
|       'gantt\n' + |  | ||||||
|       'dateFormat YYYY-MM-DD\n' + |  | ||||||
|       'title Adding gantt diagram functionality to mermaid\n' + |  | ||||||
|       'section Documentation\n' + |  | ||||||
|       ';Design jison grammar:des1, 2014-01-01, 2014-01-04'; |  | ||||||
|  |  | ||||||
|     const hash = |  | ||||||
|       'gantt\n' + |  | ||||||
|       'dateFormat YYYY-MM-DD\n' + |  | ||||||
|       'title Adding gantt diagram functionality to mermaid\n' + |  | ||||||
|       'section Documentation\n' + |  | ||||||
|       '#Design jison grammar:des1, 2014-01-01, 2014-01-04'; |  | ||||||
|  |  | ||||||
|     expect(parserFnConstructor(str)).not.toThrow(); |     expect(parserFnConstructor(str)).not.toThrow(); | ||||||
|     expect(parserFnConstructor(semi)).not.toThrow(); |  | ||||||
|     expect(parserFnConstructor(hash)).not.toThrow(); |  | ||||||
|  |  | ||||||
|     const tasks = parser.yy.getTasks(); |     const tasks = parser.yy.getTasks(); | ||||||
|  |  | ||||||
|   | |||||||
| @@ -1,13 +1,12 @@ | |||||||
| // @ts-ignore: JISON doesn't support types | // @ts-ignore: JISON doesn't support types | ||||||
| import parser from './parser/mindmap.jison'; | import mindmapParser from './parser/mindmap.jison'; | ||||||
| import db from './mindmapDb.js'; | import * as mindmapDb from './mindmapDb.js'; | ||||||
| import renderer from './mindmapRenderer.js'; | import mindmapRenderer from './mindmapRenderer.js'; | ||||||
| import styles from './styles.js'; | import mindmapStyles from './styles.js'; | ||||||
| import type { DiagramDefinition } from '../../diagram-api/types.js'; |  | ||||||
|  |  | ||||||
| export const diagram: DiagramDefinition = { | export const diagram = { | ||||||
|   db, |   db: mindmapDb, | ||||||
|   renderer, |   renderer: mindmapRenderer, | ||||||
|   parser, |   parser: mindmapParser, | ||||||
|   styles, |   styles: mindmapStyles, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -1,6 +1,5 @@ | |||||||
| // @ts-expect-error No types available for JISON
 |  | ||||||
| import { parser as mindmap } from './parser/mindmap.jison'; | import { parser as mindmap } from './parser/mindmap.jison'; | ||||||
| import mindmapDB from './mindmapDb.js'; | import * as mindmapDB from './mindmapDb.js'; | ||||||
| // Todo fix utils functions for tests
 | // Todo fix utils functions for tests
 | ||||||
| import { setLogLevel } from '../../diagram-api/diagramAPI.js'; | import { setLogLevel } from '../../diagram-api/diagramAPI.js'; | ||||||
| 
 | 
 | ||||||
| @@ -12,7 +11,7 @@ describe('when parsing a mindmap ', function () { | |||||||
|   }); |   }); | ||||||
|   describe('hiearchy', function () { |   describe('hiearchy', function () { | ||||||
|     it('MMP-1 should handle a simple root definition abc122', function () { |     it('MMP-1 should handle a simple root definition abc122', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root`;
 |     root`;
 | ||||||
| 
 | 
 | ||||||
|       mindmap.parse(str); |       mindmap.parse(str); | ||||||
| @@ -20,7 +19,7 @@ describe('when parsing a mindmap ', function () { | |||||||
|       expect(mindmap.yy.getMindmap().descr).toEqual('root'); |       expect(mindmap.yy.getMindmap().descr).toEqual('root'); | ||||||
|     }); |     }); | ||||||
|     it('MMP-2 should handle a hierachial mindmap definition', function () { |     it('MMP-2 should handle a hierachial mindmap definition', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root |     root | ||||||
|       child1 |       child1 | ||||||
|       child2 |       child2 | ||||||
| @@ -35,7 +34,7 @@ describe('when parsing a mindmap ', function () { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('3 should handle a simple root definition with a shape and without an id abc123', function () { |     it('3 should handle a simple root definition with a shape and without an id abc123', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     (root)`;
 |     (root)`;
 | ||||||
| 
 | 
 | ||||||
|       mindmap.parse(str); |       mindmap.parse(str); | ||||||
| @@ -44,7 +43,7 @@ describe('when parsing a mindmap ', function () { | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('MMP-4 should handle a deeper hierachial mindmap definition', function () { |     it('MMP-4 should handle a deeper hierachial mindmap definition', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root |     root | ||||||
|       child1 |       child1 | ||||||
|         leaf1 |         leaf1 | ||||||
| @@ -59,27 +58,40 @@ describe('when parsing a mindmap ', function () { | |||||||
|       expect(mm.children[1].descr).toEqual('child2'); |       expect(mm.children[1].descr).toEqual('child2'); | ||||||
|     }); |     }); | ||||||
|     it('5 Multiple roots are illegal', function () { |     it('5 Multiple roots are illegal', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root |     root | ||||||
|     fakeRoot`;
 |     fakeRoot`;
 | ||||||
| 
 | 
 | ||||||
|       expect(() => mindmap.parse(str)).toThrow( |       try { | ||||||
|         'There can be only one root. No parent could be found for ("fakeRoot")' |         mindmap.parse(str); | ||||||
|       ); |         // Fail test if above expression doesn't throw anything.
 | ||||||
|  |         expect(true).toBe(false); | ||||||
|  |       } catch (e) { | ||||||
|  |         expect(e.message).toBe( | ||||||
|  |           'There can be only one root. No parent could be found for ("fakeRoot")' | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|     }); |     }); | ||||||
|     it('MMP-6 real root in wrong place', function () { |     it('MMP-6 real root in wrong place', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|           root |           root | ||||||
|         fakeRoot |         fakeRoot | ||||||
|     realRootWrongPlace`;
 |     realRootWrongPlace`;
 | ||||||
|       expect(() => mindmap.parse(str)).toThrow( | 
 | ||||||
|         'There can be only one root. No parent could be found for ("fakeRoot")' |       try { | ||||||
|       ); |         mindmap.parse(str); | ||||||
|  |         // Fail test if above expression doesn't throw anything.
 | ||||||
|  |         expect(true).toBe(false); | ||||||
|  |       } catch (e) { | ||||||
|  |         expect(e.message).toBe( | ||||||
|  |           'There can be only one root. No parent could be found for ("fakeRoot")' | ||||||
|  |         ); | ||||||
|  |       } | ||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|   describe('nodes', function () { |   describe('nodes', function () { | ||||||
|     it('MMP-7 should handle an id and type for a node definition', function () { |     it('MMP-7 should handle an id and type for a node definition', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root[The root] |     root[The root] | ||||||
|       `;
 |       `;
 | ||||||
| 
 | 
 | ||||||
| @@ -90,7 +102,7 @@ describe('when parsing a mindmap ', function () { | |||||||
|       expect(mm.type).toEqual(mindmap.yy.nodeType.RECT); |       expect(mm.type).toEqual(mindmap.yy.nodeType.RECT); | ||||||
|     }); |     }); | ||||||
|     it('MMP-8 should handle an id and type for a node definition', function () { |     it('MMP-8 should handle an id and type for a node definition', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root |     root | ||||||
|       theId(child1)`;
 |       theId(child1)`;
 | ||||||
| 
 | 
 | ||||||
| @@ -104,7 +116,7 @@ describe('when parsing a mindmap ', function () { | |||||||
|       expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT); |       expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT); | ||||||
|     }); |     }); | ||||||
|     it('MMP-9 should handle an id and type for a node definition', function () { |     it('MMP-9 should handle an id and type for a node definition', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
| root | root | ||||||
|       theId(child1)`;
 |       theId(child1)`;
 | ||||||
| 
 | 
 | ||||||
| @@ -118,7 +130,7 @@ root | |||||||
|       expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT); |       expect(child.type).toEqual(mindmap.yy.nodeType.ROUNDED_RECT); | ||||||
|     }); |     }); | ||||||
|     it('MMP-10 multiple types (circle)', function () { |     it('MMP-10 multiple types (circle)', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|  root((the root)) |  root((the root)) | ||||||
|  `;
 |  `;
 | ||||||
| 
 | 
 | ||||||
| @@ -130,7 +142,7 @@ root | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('MMP-11 multiple types (cloud)', function () { |     it('MMP-11 multiple types (cloud)', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|  root)the root( |  root)the root( | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
| @@ -141,7 +153,7 @@ root | |||||||
|       expect(mm.type).toEqual(mindmap.yy.nodeType.CLOUD); |       expect(mm.type).toEqual(mindmap.yy.nodeType.CLOUD); | ||||||
|     }); |     }); | ||||||
|     it('MMP-12 multiple types (bang)', function () { |     it('MMP-12 multiple types (bang)', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|  root))the root(( |  root))the root(( | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
| @@ -153,7 +165,7 @@ root | |||||||
|     }); |     }); | ||||||
| 
 | 
 | ||||||
|     it('MMP-12-a multiple types (hexagon)', function () { |     it('MMP-12-a multiple types (hexagon)', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|  root{{the root}} |  root{{the root}} | ||||||
| `;
 | `;
 | ||||||
| 
 | 
 | ||||||
| @@ -166,7 +178,7 @@ root | |||||||
|   }); |   }); | ||||||
|   describe('decorations', function () { |   describe('decorations', function () { | ||||||
|     it('MMP-13 should be possible to set an icon for the node', function () { |     it('MMP-13 should be possible to set an icon for the node', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root[The root] |     root[The root] | ||||||
|     ::icon(bomb) |     ::icon(bomb) | ||||||
|     `;
 |     `;
 | ||||||
| @@ -180,7 +192,7 @@ root | |||||||
|       expect(mm.icon).toEqual('bomb'); |       expect(mm.icon).toEqual('bomb'); | ||||||
|     }); |     }); | ||||||
|     it('MMP-14 should be possible to set classes for the node', function () { |     it('MMP-14 should be possible to set classes for the node', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root[The root] |     root[The root] | ||||||
|     :::m-4 p-8 |     :::m-4 p-8 | ||||||
|     `;
 |     `;
 | ||||||
| @@ -194,7 +206,7 @@ root | |||||||
|       expect(mm.class).toEqual('m-4 p-8'); |       expect(mm.class).toEqual('m-4 p-8'); | ||||||
|     }); |     }); | ||||||
|     it('MMP-15 should be possible to set both classes and icon for the node', function () { |     it('MMP-15 should be possible to set both classes and icon for the node', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root[The root] |     root[The root] | ||||||
|     :::m-4 p-8 |     :::m-4 p-8 | ||||||
|     ::icon(bomb) |     ::icon(bomb) | ||||||
| @@ -210,7 +222,7 @@ root | |||||||
|       expect(mm.icon).toEqual('bomb'); |       expect(mm.icon).toEqual('bomb'); | ||||||
|     }); |     }); | ||||||
|     it('MMP-16 should be possible to set both classes and icon for the node', function () { |     it('MMP-16 should be possible to set both classes and icon for the node', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root[The root] |     root[The root] | ||||||
|     ::icon(bomb) |     ::icon(bomb) | ||||||
|     :::m-4 p-8 |     :::m-4 p-8 | ||||||
| @@ -228,7 +240,7 @@ root | |||||||
|   }); |   }); | ||||||
|   describe('descriptions', function () { |   describe('descriptions', function () { | ||||||
|     it('MMP-17 should be possible to use node syntax in the descriptions', function () { |     it('MMP-17 should be possible to use node syntax in the descriptions', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root["String containing []"] |     root["String containing []"] | ||||||
| `;
 | `;
 | ||||||
|       mindmap.parse(str); |       mindmap.parse(str); | ||||||
| @@ -237,7 +249,7 @@ root | |||||||
|       expect(mm.descr).toEqual('String containing []'); |       expect(mm.descr).toEqual('String containing []'); | ||||||
|     }); |     }); | ||||||
|     it('MMP-18 should be possible to use node syntax in the descriptions in children', function () { |     it('MMP-18 should be possible to use node syntax in the descriptions in children', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|     root["String containing []"] |     root["String containing []"] | ||||||
|       child1["String containing ()"] |       child1["String containing ()"] | ||||||
| `;
 | `;
 | ||||||
| @@ -249,7 +261,7 @@ root | |||||||
|       expect(mm.children[0].descr).toEqual('String containing ()'); |       expect(mm.children[0].descr).toEqual('String containing ()'); | ||||||
|     }); |     }); | ||||||
|     it('MMP-19 should be possible to have a child after a class assignment', function () { |     it('MMP-19 should be possible to have a child after a class assignment', function () { | ||||||
|       const str = `mindmap
 |       let str = `mindmap
 | ||||||
|   root(Root) |   root(Root) | ||||||
|     Child(Child) |     Child(Child) | ||||||
|     :::hot |     :::hot | ||||||
| @@ -269,7 +281,7 @@ root | |||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
|   it('MMP-20 should be possible to have meaningless empty rows in a mindmap abc124', function () { |   it('MMP-20 should be possible to have meaningless empty rows in a mindmap abc124', function () { | ||||||
|     const str = `mindmap
 |     let str = `mindmap
 | ||||||
|   root(Root) |   root(Root) | ||||||
|     Child(Child) |     Child(Child) | ||||||
|       a(a) |       a(a) | ||||||
| @@ -288,7 +300,7 @@ root | |||||||
|     expect(child.children[1].nodeId).toEqual('b'); |     expect(child.children[1].nodeId).toEqual('b'); | ||||||
|   }); |   }); | ||||||
|   it('MMP-21 should be possible to have comments in a mindmap', function () { |   it('MMP-21 should be possible to have comments in a mindmap', function () { | ||||||
|     const str = `mindmap
 |     let str = `mindmap
 | ||||||
|   root(Root) |   root(Root) | ||||||
|     Child(Child) |     Child(Child) | ||||||
|       a(a) |       a(a) | ||||||
| @@ -309,7 +321,7 @@ root | |||||||
|   }); |   }); | ||||||
| 
 | 
 | ||||||
|   it('MMP-22 should be possible to have comments at the end of a line', function () { |   it('MMP-22 should be possible to have comments at the end of a line', function () { | ||||||
|     const str = `mindmap
 |     let str = `mindmap
 | ||||||
|   root(Root) |   root(Root) | ||||||
|     Child(Child) |     Child(Child) | ||||||
|       a(a) %% This is a comment |       a(a) %% This is a comment | ||||||
| @@ -327,7 +339,7 @@ root | |||||||
|     expect(child.children[1].nodeId).toEqual('b'); |     expect(child.children[1].nodeId).toEqual('b'); | ||||||
|   }); |   }); | ||||||
|   it('MMP-23 Rows with only spaces should not interfere', function () { |   it('MMP-23 Rows with only spaces should not interfere', function () { | ||||||
|     const str = 'mindmap\nroot\n A\n \n\n B'; |     let str = 'mindmap\nroot\n A\n \n\n B'; | ||||||
|     mindmap.parse(str); |     mindmap.parse(str); | ||||||
|     const mm = mindmap.yy.getMindmap(); |     const mm = mindmap.yy.getMindmap(); | ||||||
|     expect(mm.nodeId).toEqual('root'); |     expect(mm.nodeId).toEqual('root'); | ||||||
| @@ -339,7 +351,7 @@ root | |||||||
|     expect(child2.nodeId).toEqual('B'); |     expect(child2.nodeId).toEqual('B'); | ||||||
|   }); |   }); | ||||||
|   it('MMP-24 Handle rows above the mindmap declarations', function () { |   it('MMP-24 Handle rows above the mindmap declarations', function () { | ||||||
|     const str = '\n \nmindmap\nroot\n A\n \n\n B'; |     let str = '\n \nmindmap\nroot\n A\n \n\n B'; | ||||||
|     mindmap.parse(str); |     mindmap.parse(str); | ||||||
|     const mm = mindmap.yy.getMindmap(); |     const mm = mindmap.yy.getMindmap(); | ||||||
|     expect(mm.nodeId).toEqual('root'); |     expect(mm.nodeId).toEqual('root'); | ||||||
| @@ -351,7 +363,7 @@ root | |||||||
|     expect(child2.nodeId).toEqual('B'); |     expect(child2.nodeId).toEqual('B'); | ||||||
|   }); |   }); | ||||||
|   it('MMP-25 Handle rows above the mindmap declarations, no space', function () { |   it('MMP-25 Handle rows above the mindmap declarations, no space', function () { | ||||||
|     const str = '\n\n\nmindmap\nroot\n A\n \n\n B'; |     let str = '\n\n\nmindmap\nroot\n A\n \n\n B'; | ||||||
|     mindmap.parse(str); |     mindmap.parse(str); | ||||||
|     const mm = mindmap.yy.getMindmap(); |     const mm = mindmap.yy.getMindmap(); | ||||||
|     expect(mm.nodeId).toEqual('root'); |     expect(mm.nodeId).toEqual('root'); | ||||||
| @@ -1,21 +1,19 @@ | |||||||
| import { getConfig } from '../../diagram-api/diagramAPI.js'; | import { getConfig } from '../../diagram-api/diagramAPI.js'; | ||||||
| import type { D3Element } from '../../mermaidAPI.js'; | import { sanitizeText as _sanitizeText } from '../../diagrams/common/common.js'; | ||||||
| import { sanitizeText } from '../../diagrams/common/common.js'; |  | ||||||
| import { log } from '../../logger.js'; | import { log } from '../../logger.js'; | ||||||
| import type { MindmapNode } from './mindmapTypes.js'; |  | ||||||
| import { getConfigValue } from '../../config.js'; |  | ||||||
| 
 | 
 | ||||||
| let nodes: MindmapNode[] = []; | export const sanitizeText = (text) => _sanitizeText(text, getConfig()); | ||||||
|  | 
 | ||||||
|  | let nodes = []; | ||||||
| let cnt = 0; | let cnt = 0; | ||||||
| let elements: Record<number, D3Element> = {}; | let elements = {}; | ||||||
| 
 | export const clear = () => { | ||||||
| const clear = () => { |  | ||||||
|   nodes = []; |   nodes = []; | ||||||
|   cnt = 0; |   cnt = 0; | ||||||
|   elements = {}; |   elements = {}; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const getParent = function (level: number) { | const getParent = function (level) { | ||||||
|   for (let i = nodes.length - 1; i >= 0; i--) { |   for (let i = nodes.length - 1; i >= 0; i--) { | ||||||
|     if (nodes[i].level < level) { |     if (nodes[i].level < level) { | ||||||
|       return nodes[i]; |       return nodes[i]; | ||||||
| @@ -25,32 +23,34 @@ const getParent = function (level: number) { | |||||||
|   return null; |   return null; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const getMindmap = () => { | export const getMindmap = () => { | ||||||
|   return nodes.length > 0 ? nodes[0] : null; |   return nodes.length > 0 ? nodes[0] : null; | ||||||
| }; | }; | ||||||
| 
 | export const addNode = (level, id, descr, type) => { | ||||||
| const addNode = (level: number, id: string, descr: string, type: number) => { |  | ||||||
|   log.info('addNode', level, id, descr, type); |   log.info('addNode', level, id, descr, type); | ||||||
|   const conf = getConfig(); |   const conf = getConfig(); | ||||||
|   let padding: number = getConfigValue(conf, 'mindmap.padding'); |  | ||||||
|   switch (type) { |  | ||||||
|     case nodeType.ROUNDED_RECT: |  | ||||||
|     case nodeType.RECT: |  | ||||||
|     case nodeType.HEXAGON: |  | ||||||
|       padding *= 2; |  | ||||||
|   } |  | ||||||
| 
 |  | ||||||
|   const node = { |   const node = { | ||||||
|     id: cnt++, |     id: cnt++, | ||||||
|     nodeId: sanitizeText(id, conf), |     nodeId: sanitizeText(id), | ||||||
|     level, |     level, | ||||||
|     descr: sanitizeText(descr, conf), |     descr: sanitizeText(descr), | ||||||
|     type, |     type, | ||||||
|     children: [], |     children: [], | ||||||
|     width: getConfigValue(conf, 'mindmap.maxNodeWidth'), |     width: getConfig().mindmap.maxNodeWidth, | ||||||
|     padding, |   }; | ||||||
|   } satisfies MindmapNode; |   switch (node.type) { | ||||||
| 
 |     case nodeType.ROUNDED_RECT: | ||||||
|  |       node.padding = 2 * conf.mindmap.padding; | ||||||
|  |       break; | ||||||
|  |     case nodeType.RECT: | ||||||
|  |       node.padding = 2 * conf.mindmap.padding; | ||||||
|  |       break; | ||||||
|  |     case nodeType.HEXAGON: | ||||||
|  |       node.padding = 2 * conf.mindmap.padding; | ||||||
|  |       break; | ||||||
|  |     default: | ||||||
|  |       node.padding = conf.mindmap.padding; | ||||||
|  |   } | ||||||
|   const parent = getParent(level); |   const parent = getParent(level); | ||||||
|   if (parent) { |   if (parent) { | ||||||
|     parent.children.push(node); |     parent.children.push(node); | ||||||
| @@ -62,14 +62,22 @@ const addNode = (level: number, id: string, descr: string, type: number) => { | |||||||
|       nodes.push(node); |       nodes.push(node); | ||||||
|     } else { |     } else { | ||||||
|       // Syntax error ... there can only bee one root
 |       // Syntax error ... there can only bee one root
 | ||||||
|       throw new Error( |       let error = new Error( | ||||||
|         'There can be only one root. No parent could be found for ("' + node.descr + '")' |         'There can be only one root. No parent could be found for ("' + node.descr + '")' | ||||||
|       ); |       ); | ||||||
|  |       error.hash = { | ||||||
|  |         text: 'branch ' + name, | ||||||
|  |         token: 'branch ' + name, | ||||||
|  |         line: '1', | ||||||
|  |         loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 }, | ||||||
|  |         expected: ['"checkout ' + name + '"'], | ||||||
|  |       }; | ||||||
|  |       throw error; | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const nodeType = { | export const nodeType = { | ||||||
|   DEFAULT: 0, |   DEFAULT: 0, | ||||||
|   NO_BORDER: 0, |   NO_BORDER: 0, | ||||||
|   ROUNDED_RECT: 1, |   ROUNDED_RECT: 1, | ||||||
| @@ -80,7 +88,7 @@ const nodeType = { | |||||||
|   HEXAGON: 6, |   HEXAGON: 6, | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const getType = (startStr: string, endStr: string): number => { | export const getType = (startStr, endStr) => { | ||||||
|   log.debug('In get type', startStr, endStr); |   log.debug('In get type', startStr, endStr); | ||||||
|   switch (startStr) { |   switch (startStr) { | ||||||
|     case '[': |     case '[': | ||||||
| @@ -100,25 +108,21 @@ const getType = (startStr: string, endStr: string): number => { | |||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const setElementForId = (id: number, element: D3Element) => { | export const setElementForId = (id, element) => { | ||||||
|   elements[id] = element; |   elements[id] = element; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const decorateNode = (decoration?: { class?: string; icon?: string }) => { | export const decorateNode = (decoration) => { | ||||||
|   if (!decoration) { |  | ||||||
|     return; |  | ||||||
|   } |  | ||||||
|   const config = getConfig(); |  | ||||||
|   const node = nodes[nodes.length - 1]; |   const node = nodes[nodes.length - 1]; | ||||||
|   if (decoration.icon) { |   if (decoration && decoration.icon) { | ||||||
|     node.icon = sanitizeText(decoration.icon, config); |     node.icon = sanitizeText(decoration.icon); | ||||||
|   } |   } | ||||||
|   if (decoration.class) { |   if (decoration && decoration.class) { | ||||||
|     node.class = sanitizeText(decoration.class, config); |     node.class = sanitizeText(decoration.class); | ||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const type2Str = (type: number) => { | export const type2Str = (type) => { | ||||||
|   switch (type) { |   switch (type) { | ||||||
|     case nodeType.DEFAULT: |     case nodeType.DEFAULT: | ||||||
|       return 'no-border'; |       return 'no-border'; | ||||||
| @@ -139,21 +143,13 @@ const type2Str = (type: number) => { | |||||||
|   } |   } | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
|  | export let parseError; | ||||||
|  | export const setErrorHandler = (handler) => { | ||||||
|  |   parseError = handler; | ||||||
|  | }; | ||||||
|  | 
 | ||||||
| // Expose logger to grammar
 | // Expose logger to grammar
 | ||||||
| const getLogger = () => log; | export const getLogger = () => log; | ||||||
| const getElementById = (id: number) => elements[id]; |  | ||||||
| 
 | 
 | ||||||
| const db = { | export const getNodeById = (id) => nodes[id]; | ||||||
|   clear, | export const getElementById = (id) => elements[id]; | ||||||
|   addNode, |  | ||||||
|   getMindmap, |  | ||||||
|   nodeType, |  | ||||||
|   getType, |  | ||||||
|   setElementForId, |  | ||||||
|   decorateNode, |  | ||||||
|   type2Str, |  | ||||||
|   getLogger, |  | ||||||
|   getElementById, |  | ||||||
| } as const; |  | ||||||
| 
 |  | ||||||
| export default db; |  | ||||||
| @@ -1,52 +1,36 @@ | |||||||
| import cytoscape from 'cytoscape'; | /** Created by knut on 14-12-11. */ | ||||||
| // @ts-expect-error No types available
 |  | ||||||
| import coseBilkent from 'cytoscape-cose-bilkent'; |  | ||||||
| import { select } from 'd3'; | import { select } from 'd3'; | ||||||
| import type { MermaidConfig } from '../../config.type.js'; |  | ||||||
| import type { DrawDefinition } from '../../diagram-api/types.js'; |  | ||||||
| import { log } from '../../logger.js'; | import { log } from '../../logger.js'; | ||||||
| import type { D3Element } from '../../mermaidAPI.js'; | import { getConfig } from '../../diagram-api/diagramAPI.js'; | ||||||
| import { selectSvgElement } from '../../rendering-util/selectSvgElement.js'; |  | ||||||
| import { setupGraphViewbox } from '../../setupGraphViewbox.js'; | import { setupGraphViewbox } from '../../setupGraphViewbox.js'; | ||||||
| import type { FilledMindMapNode, MindmapDB, MindmapNode } from './mindmapTypes.js'; | import svgDraw from './svgDraw.js'; | ||||||
| import { drawNode, positionNode } from './svgDraw.js'; | import cytoscape from 'cytoscape/dist/cytoscape.umd.js'; | ||||||
| import { getConfig, getConfigValue } from '../../config.js'; | import coseBilkent from 'cytoscape-cose-bilkent'; | ||||||
|  | import * as db from './mindmapDb.js'; | ||||||
| 
 | 
 | ||||||
| // Inject the layout algorithm into cytoscape
 | // Inject the layout algorithm into cytoscape
 | ||||||
| cytoscape.use(coseBilkent); | cytoscape.use(coseBilkent); | ||||||
| 
 | 
 | ||||||
| function drawNodes( | /** | ||||||
|   db: MindmapDB, |  * @param {any} svg The svg element to draw the diagram onto | ||||||
|   svg: D3Element, |  * @param {object} mindmap The mindmap data and hierarchy | ||||||
|   mindmap: FilledMindMapNode, |  * @param section | ||||||
|   section: number, |  * @param {object} conf The configuration object | ||||||
|   conf: MermaidConfig |  */ | ||||||
| ) { | function drawNodes(svg, mindmap, section, conf) { | ||||||
|   drawNode(db, svg, mindmap, section, conf); |   svgDraw.drawNode(svg, mindmap, section, conf); | ||||||
|   if (mindmap.children) { |   if (mindmap.children) { | ||||||
|     mindmap.children.forEach((child, index) => { |     mindmap.children.forEach((child, index) => { | ||||||
|       drawNodes(db, svg, child, section < 0 ? index : section, conf); |       drawNodes(svg, child, section < 0 ? index : section, conf); | ||||||
|     }); |     }); | ||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| declare module 'cytoscape' { | /** | ||||||
|   interface EdgeSingular { |  * @param edgesEl | ||||||
|     _private: { |  * @param cy | ||||||
|       bodyBounds: unknown; |  */ | ||||||
|       rscratch: { | function drawEdges(edgesEl, cy) { | ||||||
|         startX: number; |  | ||||||
|         startY: number; |  | ||||||
|         midX: number; |  | ||||||
|         midY: number; |  | ||||||
|         endX: number; |  | ||||||
|         endY: number; |  | ||||||
|       }; |  | ||||||
|     }; |  | ||||||
|   } |  | ||||||
| } |  | ||||||
| 
 |  | ||||||
| function drawEdges(edgesEl: D3Element, cy: cytoscape.Core) { |  | ||||||
|   cy.edges().map((edge, id) => { |   cy.edges().map((edge, id) => { | ||||||
|     const data = edge.data(); |     const data = edge.data(); | ||||||
|     if (edge[0]._private.bodyBounds) { |     if (edge[0]._private.bodyBounds) { | ||||||
| @@ -63,11 +47,17 @@ function drawEdges(edgesEl: D3Element, cy: cytoscape.Core) { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function addNodes(mindmap: MindmapNode, cy: cytoscape.Core, conf: MermaidConfig, level: number) { | /** | ||||||
|  |  * @param mindmap The mindmap data and hierarchy | ||||||
|  |  * @param cy | ||||||
|  |  * @param conf The configuration object | ||||||
|  |  * @param level | ||||||
|  |  */ | ||||||
|  | function addNodes(mindmap, cy, conf, level) { | ||||||
|   cy.add({ |   cy.add({ | ||||||
|     group: 'nodes', |     group: 'nodes', | ||||||
|     data: { |     data: { | ||||||
|       id: mindmap.id.toString(), |       id: mindmap.id, | ||||||
|       labelText: mindmap.descr, |       labelText: mindmap.descr, | ||||||
|       height: mindmap.height, |       height: mindmap.height, | ||||||
|       width: mindmap.width, |       width: mindmap.width, | ||||||
| @@ -77,8 +67,8 @@ function addNodes(mindmap: MindmapNode, cy: cytoscape.Core, conf: MermaidConfig, | |||||||
|       type: mindmap.type, |       type: mindmap.type, | ||||||
|     }, |     }, | ||||||
|     position: { |     position: { | ||||||
|       x: mindmap.x!, |       x: mindmap.x, | ||||||
|       y: mindmap.y!, |       y: mindmap.y, | ||||||
|     }, |     }, | ||||||
|   }); |   }); | ||||||
|   if (mindmap.children) { |   if (mindmap.children) { | ||||||
| @@ -98,7 +88,12 @@ function addNodes(mindmap: MindmapNode, cy: cytoscape.Core, conf: MermaidConfig, | |||||||
|   } |   } | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| function layoutMindmap(node: MindmapNode, conf: MermaidConfig): Promise<cytoscape.Core> { | /** | ||||||
|  |  * @param node | ||||||
|  |  * @param conf | ||||||
|  |  * @param cy | ||||||
|  |  */ | ||||||
|  | function layoutMindmap(node, conf) { | ||||||
|   return new Promise((resolve) => { |   return new Promise((resolve) => { | ||||||
|     // Add temporary render element
 |     // Add temporary render element
 | ||||||
|     const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none'); |     const renderEl = select('body').append('div').attr('id', 'cy').attr('style', 'display:none'); | ||||||
| @@ -127,8 +122,8 @@ function layoutMindmap(node: MindmapNode, conf: MermaidConfig): Promise<cytoscap | |||||||
| 
 | 
 | ||||||
|     cy.layout({ |     cy.layout({ | ||||||
|       name: 'cose-bilkent', |       name: 'cose-bilkent', | ||||||
|       // @ts-ignore Types for cose-bilkent are not correct?
 |  | ||||||
|       quality: 'proof', |       quality: 'proof', | ||||||
|  |       // headless: true,
 | ||||||
|       styleEnabled: false, |       styleEnabled: false, | ||||||
|       animate: false, |       animate: false, | ||||||
|     }).run(); |     }).run(); | ||||||
| @@ -138,13 +133,18 @@ function layoutMindmap(node: MindmapNode, conf: MermaidConfig): Promise<cytoscap | |||||||
|     }); |     }); | ||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | /** | ||||||
| function positionNodes(db: MindmapDB, cy: cytoscape.Core) { |  * @param node | ||||||
|  |  * @param cy | ||||||
|  |  * @param positionedMindmap | ||||||
|  |  * @param conf | ||||||
|  |  */ | ||||||
|  | function positionNodes(cy) { | ||||||
|   cy.nodes().map((node, id) => { |   cy.nodes().map((node, id) => { | ||||||
|     const data = node.data(); |     const data = node.data(); | ||||||
|     data.x = node.position().x; |     data.x = node.position().x; | ||||||
|     data.y = node.position().y; |     data.y = node.position().y; | ||||||
|     positionNode(db, data); |     svgDraw.positionNode(data); | ||||||
|     const el = db.getElementById(data.nodeId); |     const el = db.getElementById(data.nodeId); | ||||||
|     log.info('Id:', id, 'Position: (', node.position().x, ', ', node.position().y, ')', data); |     log.info('Id:', id, 'Position: (', node.position().x, ', ', node.position().y, ')', data); | ||||||
|     el.attr( |     el.attr( | ||||||
| @@ -155,19 +155,38 @@ function positionNodes(db: MindmapDB, cy: cytoscape.Core) { | |||||||
|   }); |   }); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| export const draw: DrawDefinition = async (text, id, _version, diagObj) => { | /** | ||||||
|   log.debug('Rendering mindmap diagram\n' + text); |  * Draws a an info picture in the tag with id: id based on the graph definition in text. | ||||||
| 
 |  * | ||||||
|   const db = diagObj.db as MindmapDB; |  * @param {any} text | ||||||
|   const mm = db.getMindmap(); |  * @param {any} id | ||||||
|   if (!mm) { |  * @param {any} version | ||||||
|     return; |  * @param diagObj | ||||||
|   } |  */ | ||||||
| 
 | 
 | ||||||
|  | export const draw = async (text, id, version, diagObj) => { | ||||||
|   const conf = getConfig(); |   const conf = getConfig(); | ||||||
|  | 
 | ||||||
|   conf.htmlLabels = false; |   conf.htmlLabels = false; | ||||||
| 
 | 
 | ||||||
|   const svg = selectSvgElement(id); |   log.debug('Rendering mindmap diagram\n' + text, diagObj.parser); | ||||||
|  | 
 | ||||||
|  |   const securityLevel = getConfig().securityLevel; | ||||||
|  |   // Handle root and Document for when rendering in sandbox mode
 | ||||||
|  |   let sandboxElement; | ||||||
|  |   if (securityLevel === 'sandbox') { | ||||||
|  |     sandboxElement = select('#i' + id); | ||||||
|  |   } | ||||||
|  |   const root = | ||||||
|  |     securityLevel === 'sandbox' | ||||||
|  |       ? select(sandboxElement.nodes()[0].contentDocument.body) | ||||||
|  |       : select('body'); | ||||||
|  |   // Parse the graph definition
 | ||||||
|  | 
 | ||||||
|  |   const svg = root.select('#' + id); | ||||||
|  | 
 | ||||||
|  |   svg.append('g'); | ||||||
|  |   const mm = diagObj.db.getMindmap(); | ||||||
| 
 | 
 | ||||||
|   // Draw the graph and start with drawing the nodes without proper position
 |   // Draw the graph and start with drawing the nodes without proper position
 | ||||||
|   // this gives us the size of the nodes and we can set the positions later
 |   // this gives us the size of the nodes and we can set the positions later
 | ||||||
| @@ -176,23 +195,18 @@ export const draw: DrawDefinition = async (text, id, _version, diagObj) => { | |||||||
|   edgesElem.attr('class', 'mindmap-edges'); |   edgesElem.attr('class', 'mindmap-edges'); | ||||||
|   const nodesElem = svg.append('g'); |   const nodesElem = svg.append('g'); | ||||||
|   nodesElem.attr('class', 'mindmap-nodes'); |   nodesElem.attr('class', 'mindmap-nodes'); | ||||||
|   drawNodes(db, nodesElem, mm as FilledMindMapNode, -1, conf); |   drawNodes(nodesElem, mm, -1, conf); | ||||||
| 
 | 
 | ||||||
|   // Next step is to layout the mindmap, giving each node a position
 |   // Next step is to layout the mindmap, giving each node a position
 | ||||||
| 
 | 
 | ||||||
|   const cy = await layoutMindmap(mm, conf); |   const cy = await layoutMindmap(mm, conf); | ||||||
| 
 | 
 | ||||||
|   // After this we can draw, first the edges and the then nodes with the correct position
 |   // // After this we can draw, first the edges and the then nodes with the correct position
 | ||||||
|   drawEdges(edgesElem, cy); |   drawEdges(edgesElem, cy, conf); | ||||||
|   positionNodes(db, cy); |   positionNodes(cy, conf); | ||||||
| 
 | 
 | ||||||
|   // Setup the view box and size of the svg element
 |   // Setup the view box and size of the svg element
 | ||||||
|   setupGraphViewbox( |   setupGraphViewbox(undefined, svg, conf.mindmap.padding, conf.mindmap.useMaxWidth); | ||||||
|     undefined, |  | ||||||
|     svg, |  | ||||||
|     getConfigValue(conf, 'mindmap.padding'), |  | ||||||
|     getConfigValue(conf, 'mindmap.useMaxWidth') |  | ||||||
|   ); |  | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export default { | export default { | ||||||
| @@ -1,22 +0,0 @@ | |||||||
| import type { RequiredDeep } from 'type-fest'; |  | ||||||
| import type mindmapDb from './mindmapDb.js'; |  | ||||||
|  |  | ||||||
| export interface MindmapNode { |  | ||||||
|   id: number; |  | ||||||
|   nodeId: string; |  | ||||||
|   level: number; |  | ||||||
|   descr: string; |  | ||||||
|   type: number; |  | ||||||
|   children: MindmapNode[]; |  | ||||||
|   width: number; |  | ||||||
|   padding: number; |  | ||||||
|   section?: number; |  | ||||||
|   height?: number; |  | ||||||
|   class?: string; |  | ||||||
|   icon?: string; |  | ||||||
|   x?: number; |  | ||||||
|   y?: number; |  | ||||||
| } |  | ||||||
|  |  | ||||||
| export type FilledMindMapNode = RequiredDeep<MindmapNode>; |  | ||||||
| export type MindmapDB = typeof mindmapDb; |  | ||||||
| @@ -1,8 +1,6 @@ | |||||||
| // @ts-expect-error Incorrect khroma types
 |  | ||||||
| import { darken, lighten, isDark } from 'khroma'; | import { darken, lighten, isDark } from 'khroma'; | ||||||
| import type { DiagramStylesProvider } from '../../diagram-api/types.js'; |  | ||||||
| 
 | 
 | ||||||
| const genSections: DiagramStylesProvider = (options) => { | const genSections = (options) => { | ||||||
|   let sections = ''; |   let sections = ''; | ||||||
| 
 | 
 | ||||||
|   for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) { |   for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) { | ||||||
| @@ -51,8 +49,7 @@ const genSections: DiagramStylesProvider = (options) => { | |||||||
|   return sections; |   return sections; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| // TODO: These options seem incorrect.
 | const getStyles = (options) => | ||||||
| const getStyles: DiagramStylesProvider = (options) => |  | ||||||
|   ` |   ` | ||||||
|   .edge { |   .edge { | ||||||
|     stroke-width: 3; |     stroke-width: 3; | ||||||
| @@ -1,20 +1,55 @@ | |||||||
| import type { D3Element } from '../../mermaidAPI.js'; | import { select } from 'd3'; | ||||||
|  | import * as db from './mindmapDb.js'; | ||||||
| import { createText } from '../../rendering-util/createText.js'; | import { createText } from '../../rendering-util/createText.js'; | ||||||
| import type { FilledMindMapNode, MindmapDB } from './mindmapTypes.js'; |  | ||||||
| import type { Point } from '../../types.js'; |  | ||||||
| import { parseFontSize } from '../../utils.js'; |  | ||||||
| import type { MermaidConfig } from '../../config.type.js'; |  | ||||||
| 
 |  | ||||||
| const MAX_SECTIONS = 12; | const MAX_SECTIONS = 12; | ||||||
| 
 | 
 | ||||||
| type ShapeFunction = ( | /** | ||||||
|   db: MindmapDB, |  * @param {string} text The text to be wrapped | ||||||
|   elem: D3Element, |  * @param {number} width The max width of the text | ||||||
|   node: FilledMindMapNode, |  */ | ||||||
|   section?: number | function wrap(text, width) { | ||||||
| ) => void; |   text.each(function () { | ||||||
|  |     var text = select(this), | ||||||
|  |       words = text | ||||||
|  |         .text() | ||||||
|  |         .split(/(\s+|<br\/>)/) | ||||||
|  |         .reverse(), | ||||||
|  |       word, | ||||||
|  |       line = [], | ||||||
|  |       lineHeight = 1.1, // ems
 | ||||||
|  |       y = text.attr('y'), | ||||||
|  |       dy = parseFloat(text.attr('dy')), | ||||||
|  |       tspan = text | ||||||
|  |         .text(null) | ||||||
|  |         .append('tspan') | ||||||
|  |         .attr('x', 0) | ||||||
|  |         .attr('y', y) | ||||||
|  |         .attr('dy', dy + 'em'); | ||||||
|  |     for (let j = 0; j < words.length; j++) { | ||||||
|  |       word = words[words.length - 1 - j]; | ||||||
|  |       line.push(word); | ||||||
|  |       tspan.text(line.join(' ').trim()); | ||||||
|  |       if (tspan.node().getComputedTextLength() > width || word === '<br/>') { | ||||||
|  |         line.pop(); | ||||||
|  |         tspan.text(line.join(' ').trim()); | ||||||
|  |         if (word === '<br/>') { | ||||||
|  |           line = ['']; | ||||||
|  |         } else { | ||||||
|  |           line = [word]; | ||||||
|  |         } | ||||||
| 
 | 
 | ||||||
| const defaultBkg: ShapeFunction = function (db, elem, node, section) { |         tspan = text | ||||||
|  |           .append('tspan') | ||||||
|  |           .attr('x', 0) | ||||||
|  |           .attr('y', y) | ||||||
|  |           .attr('dy', lineHeight + 'em') | ||||||
|  |           .text(word); | ||||||
|  |       } | ||||||
|  |     } | ||||||
|  |   }); | ||||||
|  | } | ||||||
|  | 
 | ||||||
|  | const defaultBkg = function (elem, node, section) { | ||||||
|   const rd = 5; |   const rd = 5; | ||||||
|   elem |   elem | ||||||
|     .append('path') |     .append('path') | ||||||
| @@ -36,7 +71,7 @@ const defaultBkg: ShapeFunction = function (db, elem, node, section) { | |||||||
|     .attr('y2', node.height); |     .attr('y2', node.height); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const rectBkg: ShapeFunction = function (db, elem, node) { | const rectBkg = function (elem, node) { | ||||||
|   elem |   elem | ||||||
|     .append('rect') |     .append('rect') | ||||||
|     .attr('id', 'node-' + node.id) |     .attr('id', 'node-' + node.id) | ||||||
| @@ -45,7 +80,7 @@ const rectBkg: ShapeFunction = function (db, elem, node) { | |||||||
|     .attr('width', node.width); |     .attr('width', node.width); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const cloudBkg: ShapeFunction = function (db, elem, node) { | const cloudBkg = function (elem, node) { | ||||||
|   const w = node.width; |   const w = node.width; | ||||||
|   const h = node.height; |   const h = node.height; | ||||||
|   const r1 = 0.15 * w; |   const r1 = 0.15 * w; | ||||||
| @@ -76,7 +111,7 @@ const cloudBkg: ShapeFunction = function (db, elem, node) { | |||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const bangBkg: ShapeFunction = function (db, elem, node) { | const bangBkg = function (elem, node) { | ||||||
|   const w = node.width; |   const w = node.width; | ||||||
|   const h = node.height; |   const h = node.height; | ||||||
|   const r = 0.15 * w; |   const r = 0.15 * w; | ||||||
| @@ -108,7 +143,7 @@ const bangBkg: ShapeFunction = function (db, elem, node) { | |||||||
|     ); |     ); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const circleBkg: ShapeFunction = function (db, elem, node) { | const circleBkg = function (elem, node) { | ||||||
|   elem |   elem | ||||||
|     .append('circle') |     .append('circle') | ||||||
|     .attr('id', 'node-' + node.id) |     .attr('id', 'node-' + node.id) | ||||||
| @@ -116,13 +151,15 @@ const circleBkg: ShapeFunction = function (db, elem, node) { | |||||||
|     .attr('r', node.width / 2); |     .attr('r', node.width / 2); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| function insertPolygonShape( | /** | ||||||
|   parent: D3Element, |  * | ||||||
|   w: number, |  * @param parent | ||||||
|   h: number, |  * @param w | ||||||
|   points: Point[], |  * @param h | ||||||
|   node: FilledMindMapNode |  * @param points | ||||||
| ) { |  * @param node | ||||||
|  |  */ | ||||||
|  | function insertPolygonShape(parent, w, h, points, node) { | ||||||
|   return parent |   return parent | ||||||
|     .insert('polygon', ':first-child') |     .insert('polygon', ':first-child') | ||||||
|     .attr( |     .attr( | ||||||
| @@ -136,16 +173,12 @@ function insertPolygonShape( | |||||||
|     .attr('transform', 'translate(' + (node.width - w) / 2 + ', ' + h + ')'); |     .attr('transform', 'translate(' + (node.width - w) / 2 + ', ' + h + ')'); | ||||||
| } | } | ||||||
| 
 | 
 | ||||||
| const hexagonBkg: ShapeFunction = function ( | const hexagonBkg = function (elem, node) { | ||||||
|   _db: MindmapDB, |  | ||||||
|   elem: D3Element, |  | ||||||
|   node: FilledMindMapNode |  | ||||||
| ) { |  | ||||||
|   const h = node.height; |   const h = node.height; | ||||||
|   const f = 4; |   const f = 4; | ||||||
|   const m = h / f; |   const m = h / f; | ||||||
|   const w = node.width - node.padding + 2 * m; |   const w = node.width - node.padding + 2 * m; | ||||||
|   const points: Point[] = [ |   const points = [ | ||||||
|     { x: m, y: 0 }, |     { x: m, y: 0 }, | ||||||
|     { x: w - m, y: 0 }, |     { x: w - m, y: 0 }, | ||||||
|     { x: w, y: -h / 2 }, |     { x: w, y: -h / 2 }, | ||||||
| @@ -153,10 +186,10 @@ const hexagonBkg: ShapeFunction = function ( | |||||||
|     { x: m, y: -h }, |     { x: m, y: -h }, | ||||||
|     { x: 0, y: -h / 2 }, |     { x: 0, y: -h / 2 }, | ||||||
|   ]; |   ]; | ||||||
|   insertPolygonShape(elem, w, h, points, node); |   const shapeSvg = insertPolygonShape(elem, w, h, points, node); | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| const roundedRectBkg: ShapeFunction = function (db, elem, node) { | const roundedRectBkg = function (elem, node) { | ||||||
|   elem |   elem | ||||||
|     .append('rect') |     .append('rect') | ||||||
|     .attr('id', 'node-' + node.id) |     .attr('id', 'node-' + node.id) | ||||||
| @@ -168,20 +201,13 @@ const roundedRectBkg: ShapeFunction = function (db, elem, node) { | |||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| /** | /** | ||||||
|  * @param db - The database |  * @param {object} elem The D3 dom element in which the node is to be added | ||||||
|  * @param elem - The D3 dom element in which the node is to be added |  * @param {object} node The node to be added | ||||||
|  * @param node - The node to be added |  * @param fullSection | ||||||
|  * @param fullSection - ? |  * @param {object} conf The configuration object | ||||||
|  * @param conf - The configuration object |  * @returns {number} The height nodes dom element | ||||||
|  * @returns The height nodes dom element |  | ||||||
|  */ |  */ | ||||||
| export const drawNode = function ( | export const drawNode = function (elem, node, fullSection, conf) { | ||||||
|   db: MindmapDB, |  | ||||||
|   elem: D3Element, |  | ||||||
|   node: FilledMindMapNode, |  | ||||||
|   fullSection: number, |  | ||||||
|   conf: MermaidConfig |  | ||||||
| ): number { |  | ||||||
|   const htmlLabels = conf.htmlLabels; |   const htmlLabels = conf.htmlLabels; | ||||||
|   const section = fullSection % (MAX_SECTIONS - 1); |   const section = fullSection % (MAX_SECTIONS - 1); | ||||||
|   const nodeElem = elem.append('g'); |   const nodeElem = elem.append('g'); | ||||||
| @@ -209,9 +235,10 @@ export const drawNode = function ( | |||||||
|       .attr('dominant-baseline', 'middle') |       .attr('dominant-baseline', 'middle') | ||||||
|       .attr('text-anchor', 'middle'); |       .attr('text-anchor', 'middle'); | ||||||
|   } |   } | ||||||
|  |   // .call(wrap, node.width);
 | ||||||
|   const bbox = textElem.node().getBBox(); |   const bbox = textElem.node().getBBox(); | ||||||
|   const [fontSize] = parseFontSize(conf.fontSize); |   const fontSize = conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize; | ||||||
|   node.height = bbox.height + fontSize! * 1.1 * 0.5 + node.padding; |   node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding; | ||||||
|   node.width = bbox.width + 2 * node.padding; |   node.width = bbox.width + 2 * node.padding; | ||||||
|   if (node.icon) { |   if (node.icon) { | ||||||
|     if (node.type === db.nodeType.CIRCLE) { |     if (node.type === db.nodeType.CIRCLE) { | ||||||
| @@ -267,34 +294,60 @@ export const drawNode = function ( | |||||||
| 
 | 
 | ||||||
|   switch (node.type) { |   switch (node.type) { | ||||||
|     case db.nodeType.DEFAULT: |     case db.nodeType.DEFAULT: | ||||||
|       defaultBkg(db, bkgElem, node, section); |       defaultBkg(bkgElem, node, section, conf); | ||||||
|       break; |       break; | ||||||
|     case db.nodeType.ROUNDED_RECT: |     case db.nodeType.ROUNDED_RECT: | ||||||
|       roundedRectBkg(db, bkgElem, node, section); |       roundedRectBkg(bkgElem, node, section, conf); | ||||||
|       break; |       break; | ||||||
|     case db.nodeType.RECT: |     case db.nodeType.RECT: | ||||||
|       rectBkg(db, bkgElem, node, section); |       rectBkg(bkgElem, node, section, conf); | ||||||
|       break; |       break; | ||||||
|     case db.nodeType.CIRCLE: |     case db.nodeType.CIRCLE: | ||||||
|       bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')'); |       bkgElem.attr('transform', 'translate(' + node.width / 2 + ', ' + +node.height / 2 + ')'); | ||||||
|       circleBkg(db, bkgElem, node, section); |       circleBkg(bkgElem, node, section, conf); | ||||||
|       break; |       break; | ||||||
|     case db.nodeType.CLOUD: |     case db.nodeType.CLOUD: | ||||||
|       cloudBkg(db, bkgElem, node, section); |       cloudBkg(bkgElem, node, section, conf); | ||||||
|       break; |       break; | ||||||
|     case db.nodeType.BANG: |     case db.nodeType.BANG: | ||||||
|       bangBkg(db, bkgElem, node, section); |       bangBkg(bkgElem, node, section, conf); | ||||||
|       break; |       break; | ||||||
|     case db.nodeType.HEXAGON: |     case db.nodeType.HEXAGON: | ||||||
|       hexagonBkg(db, bkgElem, node, section); |       hexagonBkg(bkgElem, node, section, conf); | ||||||
|       break; |       break; | ||||||
|   } |   } | ||||||
| 
 | 
 | ||||||
|  |   // Position the node to its coordinate
 | ||||||
|  |   // if (typeof node.x !== 'undefined' && typeof node.y !== 'undefined') {
 | ||||||
|  |   //   nodeElem.attr('transform', 'translate(' + node.x + ',' + node.y + ')');
 | ||||||
|  |   // }
 | ||||||
|   db.setElementForId(node.id, nodeElem); |   db.setElementForId(node.id, nodeElem); | ||||||
|   return node.height; |   return node.height; | ||||||
| }; | }; | ||||||
| 
 | 
 | ||||||
| export const positionNode = function (db: MindmapDB, node: FilledMindMapNode) { | export const drawEdge = function drawEdge(edgesElem, mindmap, parent, depth, fullSection) { | ||||||
|  |   const section = fullSection % (MAX_SECTIONS - 1); | ||||||
|  |   const sx = parent.x + parent.width / 2; | ||||||
|  |   const sy = parent.y + parent.height / 2; | ||||||
|  |   const ex = mindmap.x + mindmap.width / 2; | ||||||
|  |   const ey = mindmap.y + mindmap.height / 2; | ||||||
|  |   const mx = ex > sx ? sx + Math.abs(sx - ex) / 2 : sx - Math.abs(sx - ex) / 2; | ||||||
|  |   const my = ey > sy ? sy + Math.abs(sy - ey) / 2 : sy - Math.abs(sy - ey) / 2; | ||||||
|  |   const qx = ex > sx ? Math.abs(sx - mx) / 2 + sx : -Math.abs(sx - mx) / 2 + sx; | ||||||
|  |   const qy = ey > sy ? Math.abs(sy - my) / 2 + sy : -Math.abs(sy - my) / 2 + sy; | ||||||
|  | 
 | ||||||
|  |   edgesElem | ||||||
|  |     .append('path') | ||||||
|  |     .attr( | ||||||
|  |       'd', | ||||||
|  |       parent.direction === 'TB' || parent.direction === 'BT' | ||||||
|  |         ? `M${sx},${sy} Q${sx},${qy} ${mx},${my} T${ex},${ey}` | ||||||
|  |         : `M${sx},${sy} Q${qx},${sy} ${mx},${my} T${ex},${ey}` | ||||||
|  |     ) | ||||||
|  |     .attr('class', 'edge section-edge-' + section + ' edge-depth-' + depth); | ||||||
|  | }; | ||||||
|  | 
 | ||||||
|  | export const positionNode = function (node) { | ||||||
|   const nodeElem = db.getElementById(node.id); |   const nodeElem = db.getElementById(node.id); | ||||||
| 
 | 
 | ||||||
|   const x = node.x || 0; |   const x = node.x || 0; | ||||||
| @@ -302,3 +355,5 @@ export const positionNode = function (db: MindmapDB, node: FilledMindMapNode) { | |||||||
|   // Position the node to its coordinate
 |   // Position the node to its coordinate
 | ||||||
|   nodeElem.attr('transform', 'translate(' + x + ',' + y + ')'); |   nodeElem.attr('transform', 'translate(' + x + ',' + y + ')'); | ||||||
| }; | }; | ||||||
|  | 
 | ||||||
|  | export default { drawNode, positionNode, drawEdge }; | ||||||
| @@ -1,5 +1,6 @@ | |||||||
| import type { Diagram } from '../../Diagram.js'; | import type { Diagram } from '../../Diagram.js'; | ||||||
| import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js'; | import { getConfig, defaultConfig } from '../../diagram-api/diagramAPI.js'; | ||||||
|  |  | ||||||
| import { | import { | ||||||
|   select as d3select, |   select as d3select, | ||||||
|   scaleOrdinal as d3scaleOrdinal, |   scaleOrdinal as d3scaleOrdinal, | ||||||
|   | |||||||
| @@ -10,6 +10,22 @@ export const drawRect = function (elem, rectData) { | |||||||
|   return svgDrawCommon.drawRect(elem, rectData); |   return svgDrawCommon.drawRect(elem, rectData); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | const addPopupInteraction = (id, actorCnt) => { | ||||||
|  |   addFunction(() => { | ||||||
|  |     const arr = document.querySelectorAll(id); | ||||||
|  |     // This will be the case when running in sandboxed mode | ||||||
|  |     if (arr.length === 0) { | ||||||
|  |       return; | ||||||
|  |     } | ||||||
|  |     arr[0].addEventListener('mouseover', function () { | ||||||
|  |       popupMenuUpFunc('actor' + actorCnt + '_popup'); | ||||||
|  |     }); | ||||||
|  |     arr[0].addEventListener('mouseout', function () { | ||||||
|  |       popupMenuDownFunc('actor' + actorCnt + '_popup'); | ||||||
|  |     }); | ||||||
|  |   }); | ||||||
|  | }; | ||||||
|  |  | ||||||
| export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) { | export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMenus) { | ||||||
|   if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) { |   if (actor.links === undefined || actor.links === null || Object.keys(actor.links).length === 0) { | ||||||
|     return { height: 0, width: 0 }; |     return { height: 0, width: 0 }; | ||||||
| @@ -28,6 +44,7 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe | |||||||
|   g.attr('id', 'actor' + actorCnt + '_popup'); |   g.attr('id', 'actor' + actorCnt + '_popup'); | ||||||
|   g.attr('class', 'actorPopupMenu'); |   g.attr('class', 'actorPopupMenu'); | ||||||
|   g.attr('display', displayValue); |   g.attr('display', displayValue); | ||||||
|  |   addPopupInteraction('#actor' + actorCnt + '_popup', actorCnt); | ||||||
|   var actorClass = ''; |   var actorClass = ''; | ||||||
|   if (rectData.class !== undefined) { |   if (rectData.class !== undefined) { | ||||||
|     actorClass = ' ' + rectData.class; |     actorClass = ' ' + rectData.class; | ||||||
| @@ -73,14 +90,36 @@ export const drawPopup = function (elem, actor, minMenuWidth, textAttrs, forceMe | |||||||
|   return { height: rectData.height + linkY, width: menuWidth }; |   return { height: rectData.height + linkY, width: menuWidth }; | ||||||
| }; | }; | ||||||
|  |  | ||||||
| const popupMenuToggle = function (popid) { | export const popupMenu = function (popid) { | ||||||
|   return ( |   return ( | ||||||
|     "var pu = document.getElementById('" + |     "var pu = document.getElementById('" + | ||||||
|     popid + |     popid + | ||||||
|     "'); if (pu != null) { pu.style.display = pu.style.display == 'block' ? 'none' : 'block'; }" |     "'); if (pu != null) { pu.style.display = 'block'; }" | ||||||
|   ); |   ); | ||||||
| }; | }; | ||||||
|  |  | ||||||
|  | export const popdownMenu = function (popid) { | ||||||
|  |   return ( | ||||||
|  |     "var pu = document.getElementById('" + | ||||||
|  |     popid + | ||||||
|  |     "'); if (pu != null) { pu.style.display = 'none'; }" | ||||||
|  |   ); | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const popupMenuUpFunc = function (popupId) { | ||||||
|  |   var pu = document.getElementById(popupId); | ||||||
|  |   if (pu != null) { | ||||||
|  |     pu.style.display = 'block'; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
|  | const popupMenuDownFunc = function (popupId) { | ||||||
|  |   var pu = document.getElementById(popupId); | ||||||
|  |   if (pu != null) { | ||||||
|  |     pu.style.display = 'none'; | ||||||
|  |   } | ||||||
|  | }; | ||||||
|  |  | ||||||
| export const drawText = function (elem, textData) { | export const drawText = function (elem, textData) { | ||||||
|   let prevTextHeight = 0; |   let prevTextHeight = 0; | ||||||
|   let textHeight = 0; |   let textHeight = 0; | ||||||
| @@ -290,9 +329,6 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { | |||||||
|  |  | ||||||
|   if (!isFooter) { |   if (!isFooter) { | ||||||
|     actorCnt++; |     actorCnt++; | ||||||
|     if (Object.keys(actor.links || {}).length && !conf.forceMenus) { |  | ||||||
|       g.attr('onclick', popupMenuToggle(`actor${actorCnt}_popup`)).attr('cursor', 'pointer'); |  | ||||||
|     } |  | ||||||
|     g.append('line') |     g.append('line') | ||||||
|       .attr('id', 'actor' + actorCnt) |       .attr('id', 'actor' + actorCnt) | ||||||
|       .attr('x1', center) |       .attr('x1', center) | ||||||
| @@ -309,6 +345,7 @@ const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { | |||||||
|  |  | ||||||
|     if (actor.links != null) { |     if (actor.links != null) { | ||||||
|       g.attr('id', 'root-' + actorCnt); |       g.attr('id', 'root-' + actorCnt); | ||||||
|  |       addPopupInteraction('#root-' + actorCnt, actorCnt); | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  |  | ||||||
| @@ -1016,6 +1053,8 @@ export default { | |||||||
|   insertClockIcon, |   insertClockIcon, | ||||||
|   getTextObj, |   getTextObj, | ||||||
|   getNoteRect, |   getNoteRect, | ||||||
|  |   popupMenu, | ||||||
|  |   popdownMenu, | ||||||
|   fixLifeLineHeights, |   fixLifeLineHeights, | ||||||
|   sanitizeUrl, |   sanitizeUrl, | ||||||
| }; | }; | ||||||
|   | |||||||
| @@ -16,7 +16,11 @@ import { teamMembers } from '../contributors'; | |||||||
|           <p text-lg max-w-200 text-center leading-7> |           <p text-lg max-w-200 text-center leading-7> | ||||||
|             <Contributors /> |             <Contributors /> | ||||||
|             <br /> |             <br /> | ||||||
|             <a href="https://discord.gg/wwtabKgp8y" rel="noopener noreferrer">Join the community</a> |             <a | ||||||
|  |               href="https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE" | ||||||
|  |               rel="noopener noreferrer" | ||||||
|  |               >Join the community</a | ||||||
|  |             > | ||||||
|             and get involved! |             and get involved! | ||||||
|           </p> |           </p> | ||||||
|         </div> |         </div> | ||||||
|   | |||||||
| @@ -55,8 +55,8 @@ export default defineConfig({ | |||||||
|     socialLinks: [ |     socialLinks: [ | ||||||
|       { icon: 'github', link: 'https://github.com/mermaid-js/mermaid' }, |       { icon: 'github', link: 'https://github.com/mermaid-js/mermaid' }, | ||||||
|       { |       { | ||||||
|         icon: 'discord', |         icon: 'slack', | ||||||
|         link: 'https://discord.gg/wwtabKgp8y', |         link: 'https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE', | ||||||
|       }, |       }, | ||||||
|       { |       { | ||||||
|         icon: { |         icon: { | ||||||
|   | |||||||
| @@ -10,7 +10,7 @@ We aim to reply within three working days, probably much sooner. | |||||||
|  |  | ||||||
| You should expect a close collaboration as we work to resolve the issue you have reported. Please reach out to <security@mermaid.live> again if you do not receive prompt attention and regular updates. | You should expect a close collaboration as we work to resolve the issue you have reported. Please reach out to <security@mermaid.live> again if you do not receive prompt attention and regular updates. | ||||||
|  |  | ||||||
| You may also reach out to the team via our public Discord chat channels; however, please make sure to e-mail <security@mermaid.live> when reporting an issue, and avoid revealing information about vulnerabilities in public as that could that could put users at risk. | You may also reach out to the team via our public Slack chat channels; however, please make sure to e-mail <security@mermaid.live> when reporting an issue, and avoid revealing information about vulnerabilities in public as that could that could put users at risk. | ||||||
|  |  | ||||||
| ## Best practices | ## Best practices | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,9 +16,9 @@ Currently pending [IANA](https://www.iana.org/) recognition. | |||||||
|  |  | ||||||
| ## Showcase | ## Showcase | ||||||
|  |  | ||||||
| ### Mermaid Discord workspace | ### Mermaid Slack workspace | ||||||
|  |  | ||||||
| We would love to see what you create with Mermaid. Please share your creations with us in our [Discord](https://discord.gg/wwtabKgp8y) server [#showcase](https://discord.com/channels/1079455296289788015/1079502635054399649) channel. | We would love to see what you create with Mermaid. Please share your creations with us in our [Slack](https://join.slack.com/t/mermaid-talk/shared_invite/zt-22p2r8p9y-qiyP1H38GjFQ6S6jbBkOxQ) workspace [#community-showcase](https://mermaid-talk.slack.com/archives/C05NK37LT40) channel. | ||||||
|  |  | ||||||
| ### Add to Mermaid Ecosystem | ### Add to Mermaid Ecosystem | ||||||
|  |  | ||||||
|   | |||||||
| @@ -16,7 +16,7 @@ It is a JavaScript based diagramming and charting tool that renders Markdown-ins | |||||||
| [](https://coveralls.io/github/mermaid-js/mermaid?branch=master) | [](https://coveralls.io/github/mermaid-js/mermaid?branch=master) | ||||||
| [](https://www.jsdelivr.com/package/npm/mermaid) | [](https://www.jsdelivr.com/package/npm/mermaid) | ||||||
| [](https://www.npmjs.com/package/mermaid) | [](https://www.npmjs.com/package/mermaid) | ||||||
| [](https://discord.gg/wwtabKgp8y) | [](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) | ||||||
| [](https://twitter.com/mermaidjs_) | [](https://twitter.com/mermaidjs_) | ||||||
|  |  | ||||||
| </div> | </div> | ||||||
|   | |||||||
| @@ -775,19 +775,7 @@ flowchart TD | |||||||
|     B-->E(A fa:fa-camera-retro perhaps?) |     B-->E(A fa:fa-camera-retro perhaps?) | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Mermaid supports Font Awesome if the CSS is included on the website. | Mermaid is compatible with Font Awesome up to version 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). | ||||||
| Mermaid does not have any restriction on the version of Font Awesome that can be used. |  | ||||||
|  |  | ||||||
| Please refer the [Official Font Awesome Documentation](https://fontawesome.com/start) on how to include it in your website. |  | ||||||
|  |  | ||||||
| Adding this snippet in the `<head>` would add support for Font Awesome v6.5.1 |  | ||||||
|  |  | ||||||
| ```html |  | ||||||
| <link |  | ||||||
|   href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/6.5.1/css/all.min.css" |  | ||||||
|   rel="stylesheet" |  | ||||||
| /> |  | ||||||
| ``` |  | ||||||
|  |  | ||||||
| ## Graph declarations with spaces between vertices and link and without semicolon | ## Graph declarations with spaces between vertices and link and without semicolon | ||||||
|  |  | ||||||
|   | |||||||
| @@ -63,30 +63,7 @@ gantt | |||||||
|     Add another diagram to demo page    :48h |     Add another diagram to demo page    :48h | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
| Tasks are by default sequential. A task start date defaults to the end date of the preceding task. | It is possible to set multiple dependencies separated by space: | ||||||
|  |  | ||||||
| A colon, `:`, separates the task title from its metadata. |  | ||||||
| Metadata items are separated by a comma, `,`. Valid tags are `active`, `done`, `crit`, and `milestone`. Tags are optional, but if used, they must be specified first. |  | ||||||
| After processing the tags, the remaining metadata items are interpreted as follows: |  | ||||||
|  |  | ||||||
| 1. If a single item is specified, it determines when the task ends. It can either be a specific date/time or a duration. If a duration is specified, it is added to the start date of the task to determine the end date of the task, taking into account any exclusions. |  | ||||||
| 2. If two items are specified, the last item is interpreted as in the previous case. The first item can either specify an explicit start date/time (in the format specified by `dateFormat`) or reference another task using `after <otherTaskID> [[otherTaskID2 [otherTaskID3]]...]`. In the latter case, the start date of the task will be set according to the latest end date of any referenced task. |  | ||||||
| 3. If three items are specified, the last two will be interpreted as in the previous case. The first item will denote the ID of the task, which can be referenced using the `later <taskID>` syntax. |  | ||||||
|  |  | ||||||
| | Metadata syntax                            | Start date                                          | End date                                    | ID       | |  | ||||||
| | ------------------------------------------ | --------------------------------------------------- | ------------------------------------------- | -------- | |  | ||||||
| | `<taskID>, <startDate>, <endDate>`         | `startdate` as interpreted using `dateformat`       | `endDate` as interpreted using `dateformat` | `taskID` | |  | ||||||
| | `<taskID>, <startDate>, <length>`          | `startdate` as interpreted using `dateformat`       | Start date + `length`                       | `taskID` | |  | ||||||
| | `<taskID>, after <otherTaskId>, <endDate>` | End date of previously specified task `otherTaskID` | `endDate` as interpreted using `dateformat` | `taskID` | |  | ||||||
| | `<taskID>, after <otherTaskId>, <length>`  | End date of previously specified task `otherTaskID` | Start date + `length`                       | `taskID` | |  | ||||||
| | `<startDate>, <endDate>`                   | `startdate` as interpreted using `dateformat`       | `enddate` as interpreted using `dateformat` | n/a      | |  | ||||||
| | `<startDate>, <length>`                    | `startdate` as interpreted using `dateformat`       | Start date + `length`                       | n/a      | |  | ||||||
| | `after <otherTaskID>, <endDate>`           | End date of previously specified task `otherTaskID` | `enddate` as interpreted using `dateformat` | n/a      | |  | ||||||
| | `after <otherTaskID>, <length>`            | End date of previously specified task `otherTaskID` | Start date + `length`                       | n/a      | |  | ||||||
| | `<endDate>`                                | End date of preceding task                          | `enddate` as interpreted using `dateformat` | n/a      | |  | ||||||
| | `<length>`                                 | End date of preceding task                          | Start date + `length`                       | n/a      | |  | ||||||
|  |  | ||||||
| For simplicity, the table does not show the use of multiple tasks listed with the `after` keyword. Here is an example of how to use it and how it's interpreted: |  | ||||||
|  |  | ||||||
| ```mermaid-example | ```mermaid-example | ||||||
| gantt | gantt | ||||||
|   | |||||||
| @@ -112,11 +112,11 @@ timeline | |||||||
| timeline | timeline | ||||||
|         title MermaidChart 2023 Timeline |         title MermaidChart 2023 Timeline | ||||||
|         section 2023 Q1 <br> Release Personal Tier |         section 2023 Q1 <br> Release Personal Tier | ||||||
|           Bullet 1 : sub-point 1a : sub-point 1b |           Buttet 1 : sub-point 1a : sub-point 1b | ||||||
|                : sub-point 1c |                : sub-point 1c | ||||||
|           Bullet 2 : sub-point 2a : sub-point 2b |           Bullet 2 : sub-point 2a : sub-point 2b | ||||||
|         section 2023 Q2 <br> Release XYZ Tier |         section 2023 Q2 <br> Release XYZ Tier | ||||||
|           Bullet 3 : sub-point <br> 3a : sub-point 3b |           Buttet 3 : sub-point <br> 3a : sub-point 3b | ||||||
|                : sub-point 3c |                : sub-point 3c | ||||||
|           Bullet 4 : sub-point 4a : sub-point 4b |           Bullet 4 : sub-point 4a : sub-point 4b | ||||||
| ``` | ``` | ||||||
|   | |||||||
| @@ -925,7 +925,3 @@ export const encodeEntities = function (text: string): string { | |||||||
| export const decodeEntities = function (text: string): string { | export const decodeEntities = function (text: string): string { | ||||||
|   return text.replace(/fl°°/g, '&#').replace(/fl°/g, '&').replace(/¶ß/g, ';'); |   return text.replace(/fl°°/g, '&#').replace(/fl°/g, '&').replace(/¶ß/g, ';'); | ||||||
| }; | }; | ||||||
|  |  | ||||||
| export const isString = (value: unknown): value is string => { |  | ||||||
|   return typeof value === 'string'; |  | ||||||
| }; |  | ||||||
|   | |||||||
| @@ -1,20 +0,0 @@ | |||||||
| diff --git a/package.json b/package.json |  | ||||||
| index f2f77fa79c99382b079f4051ed51eafe8d2379c8..0bfddf55394e86f3a386eb7ab681369d410bae07 100644 |  | ||||||
| --- a/package.json |  | ||||||
| +++ b/package.json |  | ||||||
| @@ -30,7 +30,15 @@ |  | ||||||
|    "engines": { |  | ||||||
|      "node": ">=0.10" |  | ||||||
|    }, |  | ||||||
| +  "exports": { |  | ||||||
| +    ".": { |  | ||||||
| +      "import": "./dist/cytoscape.umd.js", |  | ||||||
| +      "default": "./dist/cytoscape.cjs.js" |  | ||||||
| +    }, |  | ||||||
| +    "./*": "./*" |  | ||||||
| +  }, |  | ||||||
|    "main": "dist/cytoscape.cjs.js", |  | ||||||
| +  "module": "dist/cytoscape.umd.js", |  | ||||||
|    "unpkg": "dist/cytoscape.min.js", |  | ||||||
|    "jsdelivr": "dist/cytoscape.min.js", |  | ||||||
|    "scripts": { |  | ||||||
							
								
								
									
										988
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
							
						
						
									
										988
									
								
								pnpm-lock.yaml
									
									
									
										generated
									
									
									
								
							
										
											
												File diff suppressed because it is too large
												Load Diff
											
										
									
								
							
		Reference in New Issue
	
	Block a user