diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml index 845c763e8..34b14c395 100644 --- a/.github/workflows/dependency-review.yml +++ b/.github/workflows/dependency-review.yml @@ -17,4 +17,4 @@ jobs: - name: 'Checkout Repository' uses: actions/checkout@v3 - name: 'Dependency Review' - uses: actions/dependency-review-action@v2 + uses: actions/dependency-review-action@v3 diff --git a/.github/workflows/pr-labeler.yml b/.github/workflows/pr-labeler.yml index 107743c6a..0a53c6e42 100644 --- a/.github/workflows/pr-labeler.yml +++ b/.github/workflows/pr-labeler.yml @@ -8,6 +8,6 @@ jobs: runs-on: ubuntu-latest steps: - name: Label PR - uses: TimonVS/pr-labeler-action@v3 + uses: TimonVS/pr-labeler-action@v4 env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/publish-docs.yml b/.github/workflows/publish-docs.yml index 85bc79f6e..2153e11be 100644 --- a/.github/workflows/publish-docs.yml +++ b/.github/workflows/publish-docs.yml @@ -40,7 +40,7 @@ jobs: run: pnpm install --frozen-lockfile - name: Setup Pages - uses: actions/configure-pages@v2 + uses: actions/configure-pages@v3 - name: Run Build run: pnpm --filter mermaid run docs:build:vitepress diff --git a/.lintstagedrc.mjs b/.lintstagedrc.mjs index ff1d8c107..ac2623093 100644 --- a/.lintstagedrc.mjs +++ b/.lintstagedrc.mjs @@ -1,5 +1,11 @@ export default { - '!(docs/**/*)*.{ts,js,json,html,md,mts}': ['eslint --fix', 'prettier --write'], + '!(docs/**/*)*.{ts,js,json,html,md,mts}': [ + 'eslint --cache --cache-strategy content --fix', + // don't cache prettier yet, since we use `prettier-plugin-jsdoc`, + // and prettier doesn't invalidate cache on plugin updates" + // https://prettier.io/docs/en/cli.html#--cache + 'prettier --write', + ], 'cSpell.json': ['ts-node-esm scripts/fixCSpell.ts'], '**/*.jison': ['pnpm -w run lint:jison'], }; diff --git a/.vite/build.ts b/.vite/build.ts index 1261e375b..019461c49 100644 --- a/.vite/build.ts +++ b/.vite/build.ts @@ -41,11 +41,6 @@ const packageOptions = { packageName: 'mermaid-mindmap', file: 'detector.ts', }, - // 'mermaid-example-diagram-detector': { - // name: 'mermaid-example-diagram-detector', - // packageName: 'mermaid-example-diagram', - // file: 'detector.ts', - // }, }; interface BuildOptions { @@ -119,11 +114,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions) if (watch && config.build) { config.build.watch = { - include: [ - 'packages/mermaid-mindmap/src/**', - 'packages/mermaid/src/**', - // 'packages/mermaid-example-diagram/src/**', - ], + include: ['packages/mermaid-mindmap/src/**', 'packages/mermaid/src/**'], }; } @@ -149,7 +140,6 @@ if (watch) { build(getBuildConfig({ minify: false, watch, core: false, entryName: 'mermaid' })); if (!mermaidOnly) { build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' })); - // build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' })); } } else if (visualize) { await build(getBuildConfig({ minify: false, core: true, entryName: 'mermaid' })); diff --git a/.vite/server.ts b/.vite/server.ts index 334398dd8..aced396ec 100644 --- a/.vite/server.ts +++ b/.vite/server.ts @@ -1,6 +1,5 @@ import express, { NextFunction, Request, Response } from 'express'; import { createServer as createViteServer } from 'vite'; -// import { getBuildConfig } from './build'; const cors = (req: Request, res: Response, next: NextFunction) => { res.header('Access-Control-Allow-Origin', '*'); @@ -22,7 +21,7 @@ async function createServer() { app.use(cors); app.use(express.static('./packages/mermaid/dist')); - app.use(express.static('./packages/mermaid-example-diagram/dist')); + // app.use(express.static('./packages/mermaid-example-diagram/dist')); app.use(express.static('./packages/mermaid-mindmap/dist')); app.use(vite.middlewares); app.use(express.static('demos')); @@ -33,5 +32,4 @@ async function createServer() { }); } -// build(getBuildConfig({ minify: false, watch: true })); createServer(); diff --git a/README.md b/README.md index 9fed47a69..f4cf8e105 100644 --- a/README.md +++ b/README.md @@ -1,8 +1,37 @@ -# mermaid +

+ +

+

+Mermaid +

+

+Generate diagrams from markdown-like text. +

+

+ +

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

+Live Editor! +

+

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

+

+简体中文 +

-English | [简体中文](./README.zh-CN.md) +
+
+ +[![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) +[![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) +[![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) +[![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) +[![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) +[![NPM Downloads](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) +[![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) +[![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=twitter)](https://twitter.com/mermaidjs_) @@ -27,14 +56,12 @@ Mermaid addresses this problem by enabling users to create easily modifiable dia Mermaid allows even non-programmers to easily create detailed diagrams through the [Mermaid Live Editor](https://mermaid.live/).
[Tutorials](./docs/config/Tutorials.md) has video tutorials. -Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](./docs/misc/integrations.md). +Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations.md). -You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/misc/integrations.md). +You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/ecosystem/integrations.md). For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/community/n00b-overview.md), [Usage](./docs/config/usage.md) and [Tutorials](./docs/config/Tutorials.md). -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) | 📜 [Changelog](./docs/CHANGELOG.md) - In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests. diff --git a/README.zh-CN.md b/README.zh-CN.md index 0f0cef7a8..65cf38645 100644 --- a/README.zh-CN.md +++ b/README.zh-CN.md @@ -1,8 +1,37 @@ -# mermaid +

+ +

+

+Mermaid +

+

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

+

+ +

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

+Live Editor! +

+

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

+

+English +

-[English](./README.md) | 简体中文 +
+
+ +[![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) +[![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) +[![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) +[![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) +[![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) +[![NPM Downloads](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) +[![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) +[![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=twitter)](https://twitter.com/mermaidjs_) @@ -24,12 +53,10 @@ Mermaid 是一个基于 Javascript 的图表绘制工具,通过解析类 Markd Mermaid 通过允许用户创建便于修改的图表来解决这一难题,它也可以作为生产脚本(或其他代码)的一部分。

Mermaid 甚至能让非程序员也能通过 [Mermaid Live Editor](https://mermaid.live/) 轻松创建详细的图表。
-你可以访问 [教程](./docs/config/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/misc/integrations.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。 +你可以访问 [教程](./docs/config/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/ecosystem/integrations.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。 如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/community/n00b-overview.md), [用法](./docs/config/usage.md) 和 [教程](./docs/config/Tutorials.md). -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [文档](https://mermaidjs.github.io) | 🙌 [贡献](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) | 📜 [更新日志](./docs/CHANGELOG.md) - ## 示例 @@ -325,7 +352,7 @@ _很不幸的是,鱼与熊掌不可兼得,在这个场景下它意味着在 来自 Knut Sveidqvist: -> _特别感谢 [d3](https://d3js.org/) 和 [dagre-d3](https://github.com/cpettitt/dagre-d3) 这两个优秀的项目,它们提供了图形布局和绘图工具库! _ >_同样感谢 [js-sequence-diagram](https://bramp.github.io/js-sequence-diagrams) 提供了时序图语法的使用。 感谢 Jessica Peter 提供了甘特图渲染的灵感。_ >_感谢 [Tyler Long](https://github.com/tylerlong) 从 2017 年四月开始成为了项目的合作者。_ +> _特别感谢 [d3](https://d3js.org/) 和 [dagre-d3](https://github.com/cpettitt/dagre-d3) 这两个优秀的项目,它们提供了图形布局和绘图工具库!_ > _同样感谢 [js-sequence-diagram](https://bramp.github.io/js-sequence-diagrams) 提供了时序图语法的使用。 感谢 Jessica Peter 提供了甘特图渲染的灵感。_ > _感谢 [Tyler Long](https://github.com/tylerlong) 从 2017 年四月开始成为了项目的合作者。_ > > _感谢越来越多的 [贡献者们](https://github.com/knsv/mermaid/graphs/contributors),没有你们,就没有这个项目的今天!_ diff --git a/cypress/integration/rendering/sequencediagram.spec.js b/cypress/integration/rendering/sequencediagram.spec.js index b5ff92c8c..1f063c13e 100644 --- a/cypress/integration/rendering/sequencediagram.spec.js +++ b/cypress/integration/rendering/sequencediagram.spec.js @@ -3,6 +3,42 @@ import { imgSnapshotTest, renderGraph } from '../../helpers/util'; context('Sequence diagram', () => { + it('should render a sequence diagram with boxes', () => { + renderGraph( + ` + sequenceDiagram + box LightGrey Alice and Bob + participant Alice + participant Bob + end + participant John as John
Second Line + Alice ->> Bob: Hello Bob, how are you? + Bob-->>John: How about you John? + Bob--x Alice: I am good thanks! + Bob-x John: I am good thanks! + Note right of John: Bob thinks a long
long time, so long
that the text does
not fit on a row. + Bob-->Alice: Checking with John... + alt either this + Alice->>John: Yes + else or this + Alice->>John: No + else or this will happen + Alice->John: Maybe + end + par this happens in parallel + Alice -->> Bob: Parallel message 1 + and + Alice -->> John: Parallel message 2 + end + `, + { sequence: { useMaxWidth: false } } + ); + cy.get('svg').should((svg) => { + const width = parseFloat(svg.attr('width')); + expect(width).to.be.within(830 * 0.95, 830 * 1.05); + expect(svg).to.not.have.attr('style'); + }); + }); it('should render a simple sequence diagram', () => { imgSnapshotTest( ` diff --git a/cypress/integration/rendering/timeline.spec.ts b/cypress/integration/rendering/timeline.spec.ts new file mode 100644 index 000000000..6fae82fb4 --- /dev/null +++ b/cypress/integration/rendering/timeline.spec.ts @@ -0,0 +1,164 @@ +import { imgSnapshotTest } from '../../helpers/util.js'; + +describe('Timeline diagram', () => { + it('1: should render a simple timeline with no specific sections', () => { + imgSnapshotTest( + `timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + `, + {} + ); + }); + it('2: should render a timeline diagram with sections', () => { + imgSnapshotTest( + `timeline + title Timeline of Industrial Revolution + section 17th-20th century + Industry 1.0 : Machinery, Water power, Steam
power + Industry 2.0 : Electricity, Internal combustion engine, Mass production + Industry 3.0 : Electronics, Computers, Automation + section 21st century + Industry 4.0 : Internet, Robotics, Internet of Things + Industry 5.0 : Artificial intelligence, Big data,3D printing + `, + {} + ); + }); + it('3: should render a complex timeline with sections, and long events text with
', () => { + imgSnapshotTest( + `timeline + title England's History Timeline + section Stone Age + 7600 BC : Britain's oldest known house was built in Orkney, Scotland + 6000 BC : Sea levels rise and Britain becomes an island.
The people who live here are hunter-gatherers. + section Broze Age + 2300 BC : People arrive from Europe and settle in Britain.
They bring farming and metalworking. + : New styles of pottery and ways of burying the dead appear. + 2200 BC : The last major building works are completed at Stonehenge.
People now bury their dead in stone circles. + : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive. + `, + {} + ); + }); + it('4: should render a simple timeline with directives and disableMultiColor:true ', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + `, + {} + ); + }); + it('5: should render a simple timeline with directive overriden colors', () => { + imgSnapshotTest( + ` %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { + 'cScale0': '#ff0000', + 'cScale1': '#00ff00', + 'cScale2': '#0000ff' + } } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); + it('6: should render a simple timeline in base theme', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); + + it('7: should render a simple timeline in default theme', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); + + it('8: should render a simple timeline in dark theme', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); + + it('9: should render a simple timeline in neutral theme', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); + + it('10: should render a simple timeline in forest theme', () => { + imgSnapshotTest( + `%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + `, + {} + ); + }); +}); diff --git a/cypress/platform/ashish2.html b/cypress/platform/ashish2.html new file mode 100644 index 000000000..bcea4f4cc --- /dev/null +++ b/cypress/platform/ashish2.html @@ -0,0 +1,231 @@ + + + + + + + + + + +
Security check
+
+ timeline
+        title My day
+        section section with no tasks
+        section Go to work at the dog office
+          1930 : first step : second step is a long step
+               : third step
+          1940 : fourth step : fifth step
+        section Go home
+          1950 : India got independent and already won war against Pakistan
+          1960 : India fights poverty, looses war to China and gets nuclear weapons from USA and USSR
+          1970 : Green Revolution comes to india
+        section Another section with no tasks
+          I am a big big big tasks
+          I am not so big tasks
+    
+
+ timeline
+        title MermaidChart 2023 Timeline
+        section 2023 Q1 
Release Personal Tier + Buttet 1 : sub-point 1a : sub-point 1b + : sub-point 1c + Bullet 2 : sub-point 2a : sub-point 2b + section 2023 Q2
Release XYZ Tier + Buttet 3 : sub-point
3a : sub-point 3b + : sub-point 3c + Bullet 4 : sub-point 4a : sub-point 4b + +
+ +
+ timeline
+        title England's History Timeline
+        section Stone Age
+          7600 BC : Britain's oldest known house was built in Orkney, Scotland
+          6000 BC : Sea levels rise and Britain becomes an island. The people who live here are hunter-gatherers.
+        section Broze Age
+          2300 BC : People arrive from Europe and settle in Britain. They bring farming and metalworking.
+               : New styles of pottery and ways of burying the dead appear.
+          2200 BC : The last major building works are completed at Stonehenge. People now bury their dead in stone circles.
+                  : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive.
+
+    
+
+      %%{'init': { 'logLevel': 'debug', 'theme': 'default', 'timeline': {'disableMulticolor':false} } }%%
+ timeline
+        title History of Social Media Platform
+          2002 : LinkedIn
+          2004 : Facebook : Google : Pixar
+          2005 : Youtube
+          2006 : Twitter
+          2007 : Tumblr
+          2008s : Instagram
+          2010 : Pinterest
+    
+
+      %%{init: { 'logLevel': 'debug', 'theme': 'base', 'themeVariables': {
+              'cScale0': '#ff0000',
+              'cScale1': '#00ff00',
+              'cScale2': '#ff0000'
+              } } }%%
+ timeline
+        title History of Social Media Platform
+          2002 : LinkedIn
+          2004 : Facebook : Google : Pixar
+          2005 : Youtube
+          2006 : Twitter
+          2007 : Tumblr
+          2008s : Instagram
+          2010 : Pinterest
+    
+ +
+          %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': {
+              'cScale0': '#ff0000',
+              'cScale1': '#00ff00',
+              'cScale2': '#0000ff'
+       } } }%%
+       timeline
+        title History of Social Media Platform
+          2002 : LinkedIn
+          2004 : Facebook : Google
+          2005 : Youtube
+          2006 : Twitter
+          2007 : Tumblr
+          2008 : Instagram
+          2010 : Pinterest
+
+    
+ +
+ timeline
+        title History of Social Media Platform
+          2002 : LinkedIn
+          2004 : Facebook : Google
+          2005 : Youtube
+          2006 : Twitter
+          2007 : Tumblr
+          2008s : Instagram
+          2010 : Pinterest
+    
+
+mindmap
+  root
+    child1((Circle))
+        grandchild 1
+        grandchild 2
+    child2(Round rectangle)
+        grandchild 3
+        grandchild 4
+    child3[Square]
+        grandchild 5
+        ::icon(mdi mdi-fire)
+        gc6((grand
child 6)) + ::icon(mdi mdi-fire) + gc7((grand
grand
child 8)) +
+
+      flowchart-elk TB
+      a --> b
+      a --> c
+      b --> d
+      c --> d
+    
+ + + + + + + + diff --git a/cypress/platform/class.html b/cypress/platform/class.html index 85fae2a77..1d72c34a5 100644 --- a/cypress/platform/class.html +++ b/cypress/platform/class.html @@ -46,13 +46,9 @@
       %%{init: {'theme': 'base',  'fontFamily': 'courier', 'themeVariables': {  'primaryColor': '#fff000'}}}%%
       classDiagram-v2
-       class BankAccount{
-        +String owner
-        +BigDecimal balance
-        +deposit(amount) bool
-        +withdrawl(amount) int
-       }
-       cssClass "BankAccount" customCss
+classA <|-- classB : implements
+classC *-- classD : composition
+classE o-- classF : aggregation
     
         %%{init: {'theme': 'base',  'fontFamily': 'courier', 'themeVariables': {  'primaryColor': '#fff000'}}}%%
diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html
index afc12dbe9..e28184627 100644
--- a/cypress/platform/knsv2.html
+++ b/cypress/platform/knsv2.html
@@ -54,7 +54,7 @@
     
   
   
-    
+    
 %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
 graph TB
       a --> b
@@ -62,14 +62,14 @@ graph TB
       b --> d
       c --> d
     
-
+    
 flowchart-elk TB
       a --> b
       a --> c
       b --> d
       c --> d
     
-
+    
 %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
 flowchart TB
   %% I could not figure out how to use double quotes in labels in Mermaid
@@ -125,7 +125,7 @@ flowchart TB
 

-
+    
 flowchart TB
   %% I could not figure out how to use double quotes in labels in Mermaid
   subgraph ibm[IBM Espresso CPU]
@@ -227,14 +227,24 @@ sequenceDiagram
     Customer->>+Merchant: Receives goods or services
         
-
-      gantt
-        title Style today marker (vertical line should be 5px wide and half-transparent blue)
-        dateFormat YYYY-MM-DD
-        axisFormat %d
-        todayMarker stroke-width:5px,stroke:#00f,opacity:0.5
-        section Section1
-        Today: 1, -1h
+    
+mindmap
+  root((mindmap))
+    Origins
+      Long history
+      ::icon(fa fa-book)
+      Popularisation
+        British popular psychology author Tony Buzan
+    Research
+      On effectiveness
and features + On Automatic creation + Uses + Creative techniques + Strategic planning + Argument mapping + Tools + Pen and paper + Mermaid
@@ -252,7 +262,7 @@ sequenceDiagram // console.error('Mermaid error: ', err); }; mermaid.initialize({ - // theme: 'forest', + theme: 'dark', startOnLoad: true, logLevel: 0, flowchart: { diff --git a/demos/sequence.html b/demos/sequence.html index 2000d489a..891dc90ba 100644 --- a/demos/sequence.html +++ b/demos/sequence.html @@ -128,6 +128,22 @@

+
+      sequenceDiagram
+      box lightgreen Alice & John
+      participant A
+      participant J
+      end
+      box Another Group very very long description not wrapped
+      participant B
+      end
+      A->>J: Hello John, how are you?
+      J->>A: Great!
+      A->>B: Hello Bob, how are you ?
+      
+
+ + + + diff --git a/docs/misc/faq.md b/docs/config/faq.md similarity index 94% rename from docs/misc/faq.md rename to docs/config/faq.md index c7155a5b0..76132762a 100644 --- a/docs/misc/faq.md +++ b/docs/config/faq.md @@ -2,7 +2,7 @@ > > ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. > -> ## Please edit the corresponding file in [/packages/mermaid/src/docs/misc/faq.md](../../packages/mermaid/src/docs/misc/faq.md). +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/config/faq.md](../../packages/mermaid/src/docs/config/faq.md). # Frequently Asked Questions diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index 4089ef00d..302bd51e1 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[defaultConfig.ts:1934](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L1934) +[defaultConfig.ts:2084](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2084) --- diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 7f9093a7b..02d0fcf49 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -20,7 +20,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi) #### Defined in -[mermaidAPI.ts:73](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L73) +[mermaidAPI.ts:74](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L74) ## Variables @@ -90,7 +90,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:962](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L962) +[mermaidAPI.ts:886](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L886) ## Functions @@ -121,7 +121,7 @@ Return the last node appended #### Defined in -[mermaidAPI.ts:286](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L286) +[mermaidAPI.ts:287](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L287) --- @@ -147,7 +147,7 @@ the cleaned up svgCode #### Defined in -[mermaidAPI.ts:237](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L237) +[mermaidAPI.ts:238](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L238) --- @@ -173,7 +173,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:166](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L166) +[mermaidAPI.ts:167](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L167) --- @@ -196,7 +196,7 @@ the string with all the user styles #### Defined in -[mermaidAPI.ts:214](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L214) +[mermaidAPI.ts:215](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L215) --- @@ -223,7 +223,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:150](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L150) +[mermaidAPI.ts:151](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L151) --- @@ -243,7 +243,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:130](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L130) +[mermaidAPI.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L131) --- @@ -263,7 +263,7 @@ with an enclosing block that has each of the cssClasses followed by !important; #### Defined in -[mermaidAPI.ts:101](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L101) +[mermaidAPI.ts:102](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L102) --- @@ -289,7 +289,7 @@ Put the svgCode into an iFrame. Return the iFrame code #### Defined in -[mermaidAPI.ts:265](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L265) +[mermaidAPI.ts:266](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L266) --- @@ -314,4 +314,4 @@ Remove any existing elements from the given document #### Defined in -[mermaidAPI.ts:336](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L336) +[mermaidAPI.ts:337](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L337) diff --git a/docs/misc/integrations.md b/docs/ecosystem/integrations.md similarity index 99% rename from docs/misc/integrations.md rename to docs/ecosystem/integrations.md index c14e4febb..33b6b966e 100644 --- a/docs/misc/integrations.md +++ b/docs/ecosystem/integrations.md @@ -2,7 +2,7 @@ > > ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. > -> ## Please edit the corresponding file in [/packages/mermaid/src/docs/misc/integrations.md](../../packages/mermaid/src/docs/misc/integrations.md). +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/integrations.md](../../packages/mermaid/src/docs/ecosystem/integrations.md). # Integrations diff --git a/docs/ecosystem/showcases.md b/docs/ecosystem/showcases.md new file mode 100644 index 000000000..9f18103b9 --- /dev/null +++ b/docs/ecosystem/showcases.md @@ -0,0 +1,9 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/ecosystem/showcases.md](../../packages/mermaid/src/docs/ecosystem/showcases.md). + +# Showcases + +- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts). diff --git a/docs/intro/index.md b/docs/intro/index.md index e7db076e0..9fbfd3baa 100644 --- a/docs/intro/index.md +++ b/docs/intro/index.md @@ -31,11 +31,11 @@ But not having diagrams or docs ruins productivity and hurts organizational lear Mermaid addresses this problem by enabling users to create easily modifiable diagrams, it can also be made part of production scripts (and other pieces of code).

Mermaid allows even non-programmers to easily create detailed and diagrams through the [Mermaid Live Editor](https://mermaid.live/).
[Tutorials](../config/Tutorials.md) has video tutorials. -Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](../misc/integrations.md). +Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](../ecosystem/integrations.md). For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](../community/n00b-overview.md) and [Usage](../config/usage.md). -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](../community/development.md) | 🔌 [Plug-Ins](../misc/integrations.md) +🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](../community/development.md) | 🔌 [Plug-Ins](../ecosystem/integrations.md) > 🖖 Keep a steady pulse: mermaid needs more Collaborators, [Read More](https://github.com/knsv/mermaid/issues/866). @@ -243,7 +243,7 @@ journey ### CDN - https://unpkg.com/mermaid@/dist/ + https://cdn.jsdelivr.net/npm/mermaid@/dist/ To select a version: diff --git a/docs/intro/n00b-gettingStarted.md b/docs/intro/n00b-gettingStarted.md index 94ccb8982..0826c32c0 100644 --- a/docs/intro/n00b-gettingStarted.md +++ b/docs/intro/n00b-gettingStarted.md @@ -17,7 +17,7 @@ This section talks about the different ways to deploy Mermaid. Learning the [Syn ## Four ways of using mermaid: 1. Using the Mermaid Live Editor at [mermaid.live](https://mermaid.live). -2. Using [mermaid plugins](../misc/integrations.md) with programs you are familiar with. +2. Using [mermaid plugins](../ecosystem/integrations.md) with programs you are familiar with. 3. Calling the Mermaid JavaScript API. 4. Deploying Mermaid as a dependency. @@ -85,7 +85,7 @@ and to View, >A: Great! ``` +### Grouping / Box + +The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation: + + box Aqua Group Description + ... actors ... + end + box Group without description + ... actors ... + end + box rgb(33,66,99) + ... actors ... + end + +> **Note** +> If your group name is a color you can force the color to be transparent: + + box transparent Aqua + ... actors ... + end + +```mermaid-example + sequenceDiagram + box Purple Alice & John + participant A + participant J + end + box Another Group + participant B + participant C + end + A->>J: Hello John, how are you? + J->>A: Great! + A->>B: Hello Bob, how is Charly ? + B->>C: Hello Charly, how are you? +``` + +```mermaid + sequenceDiagram + box Purple Alice & John + participant A + participant J + end + box Another Group + participant B + participant C + end + A->>J: Hello John, how are you? + J->>A: Great! + A->>B: Hello Bob, how is Charly ? + B->>C: Hello Charly, how are you? +``` + ## Messages Messages can be of two displayed either solid or with a dotted line. diff --git a/docs/syntax/timeline.md b/docs/syntax/timeline.md new file mode 100644 index 000000000..4e917bc10 --- /dev/null +++ b/docs/syntax/timeline.md @@ -0,0 +1,462 @@ +> **Warning** +> +> ## THIS IS AN AUTOGENERATED FILE. DO NOT EDIT. +> +> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/timeline.md](../../packages/mermaid/src/docs/syntax/timeline.md). + +# Timeline Diagram + +> Timeline: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part. + +"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life." Wikipedia + +### An example of a timeline. + +```mermaid-example +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook + : Google + 2005 : Youtube + 2006 : Twitter +``` + +```mermaid +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook + : Google + 2005 : Youtube + 2006 : Twitter +``` + +## Syntax + +The syntax for creating Timeline diagram is simple. You always start with the `timeline` keyword to let mermaid know that you want to create a timeline diagram. + +After that there is a possibility to add a title to the timeline. This is done by adding a line with the keyword `title` followed by the title text. + +Then you add the timeline data, where you always start with a time period, followed by a colon and then the text for the event. Optionally you can add a second colon and then the text for the event. So, you can have one or more events per time period. + +```json +{time period} : {event} +``` + +or + +```json +{time period} : {event} : {event} +``` + +or + +```json +{time period} : {event} + : {event} + : {event} +``` + +NOTE: Both time period and event are simple text, and not limited to numbers. + +Let us look at the syntax for the example above. + +```mermaid-example +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter +``` + +```mermaid +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter +``` + +In this way we can use a text outline to generate a timeline diagram. +The sequence of time period and events is important, as it will be used to draw the timeline. The first time period will be placed at the left side of the timeline, and the last time period will be placed at the right side of the timeline. + +Similarly, the first event will be placed at the top for that specific time period, and the last event will be placed at the bottom. + +## Grouping of time periods in sections/ages + +You can group time periods in sections/ages. This is done by adding a line with the keyword `section` followed by the section name. + +All subsequent time periods will be placed in this section until a new section is defined. + +If no section is defined, all time periods will be placed in the default section. + +Let us look at an example, where we have grouped the time periods in sections. + +```mermaid-example +timeline + title Timeline of Industrial Revolution + section 17th-20th century + Industry 1.0 : Machinery, Water power, Steam
power + Industry 2.0 : Electricity, Internal combustion engine, Mass production + Industry 3.0 : Electronics, Computers, Automation + section 21st century + Industry 4.0 : Internet, Robotics, Internet of Things + Industry 5.0 : Artificial intelligence, Big data,3D printing +``` + +```mermaid +timeline + title Timeline of Industrial Revolution + section 17th-20th century + Industry 1.0 : Machinery, Water power, Steam
power + Industry 2.0 : Electricity, Internal combustion engine, Mass production + Industry 3.0 : Electronics, Computers, Automation + section 21st century + Industry 4.0 : Internet, Robotics, Internet of Things + Industry 5.0 : Artificial intelligence, Big data,3D printing +``` + +As you can see, the time periods are placed in the sections, and the sections are placed in the order they are defined. + +All time periods and events under a given section follow a similar color scheme. This is done to make it easier to see the relationship between time periods and events. + +## Wrapping of text for long time-periods or events + +By default, the text for time-periods and events will be wrapped if it is too long. This is done to avoid that the text is drawn outside the diagram. + +You can also use `
` to force a line break. + +Let us look at another example, where we have a long time period, and a long event. + +```mermaid-example +timeline + title England's History Timeline + section Stone Age + 7600 BC : Britain's oldest known house was built in Orkney, Scotland + 6000 BC : Sea levels rise and Britain becomes an island.
The people who live here are hunter-gatherers. + section Broze Age + 2300 BC : People arrive from Europe and settle in Britain.
They bring farming and metalworking. + : New styles of pottery and ways of burying the dead appear. + 2200 BC : The last major building works are completed at Stonehenge.
People now bury their dead in stone circles. + : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive. + +``` + +```mermaid +timeline + title England's History Timeline + section Stone Age + 7600 BC : Britain's oldest known house was built in Orkney, Scotland + 6000 BC : Sea levels rise and Britain becomes an island.
The people who live here are hunter-gatherers. + section Broze Age + 2300 BC : People arrive from Europe and settle in Britain.
They bring farming and metalworking. + : New styles of pottery and ways of burying the dead appear. + 2200 BC : The last major building works are completed at Stonehenge.
People now bury their dead in stone circles. + : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive. + +``` + +```mermaid-example +timeline + title MermaidChart 2023 Timeline + section 2023 Q1
Release Personal Tier + Buttet 1 : sub-point 1a : sub-point 1b + : sub-point 1c + Bullet 2 : sub-point 2a : sub-point 2b + section 2023 Q2
Release XYZ Tier + Buttet 3 : sub-point
3a : sub-point 3b + : sub-point 3c + Bullet 4 : sub-point 4a : sub-point 4b +``` + +```mermaid +timeline + title MermaidChart 2023 Timeline + section 2023 Q1
Release Personal Tier + Buttet 1 : sub-point 1a : sub-point 1b + : sub-point 1c + Bullet 2 : sub-point 2a : sub-point 2b + section 2023 Q2
Release XYZ Tier + Buttet 3 : sub-point
3a : sub-point 3b + : sub-point 3c + Bullet 4 : sub-point 4a : sub-point 4b +``` + +## Styling of time periods and events + +As explained earlier, each section has a color scheme, and each time period and event under a section follow the similar color scheme. + +However, if there is no section defined, then we have two possibilities: + +1. Style time periods individually, i.e. each time period(and its coressponding events) will have its own color scheme. This is the DEFAULT behavior. + +```mermaid-example + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +```mermaid + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +Note that this is no, section defined, and each time period and its corresponding events will have its own color scheme. + +2. Disable the multiColor option using the `disableMultiColor` option. This will make all time periods and events follow the same color scheme. + +You will need to add this option either via mermaid.intialize function or directives. + +```javascript +mermaid.initialize({ + theme: 'base', + startOnLoad: true, + logLevel: 0, + timeline: { + disableMulticolor: false, + }, + ... + ... +``` + +let us look at same example, where we have disabled the multiColor option. + +```mermaid-example + %%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +```mermaid + %%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +### Customizing Color scheme + +You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on. +In case you have more than 12 sections, the color scheme will start to repeat. + +NOTE: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values. + +Example: + +Now let's override the default values for the `cScale0` to `cScale2` variables: + +```mermaid-example + %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { + 'cScale0': '#ff0000', + 'cScale1': '#00ff00', + 'cScale2': '#0000ff' + } } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + +``` + +```mermaid + %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { + 'cScale0': '#ff0000', + 'cScale1': '#00ff00', + 'cScale2': '#0000ff' + } } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + +``` + +See how the colors are changed to the values specified in the theme variables. + +## Themes + +Mermaid supports a bunch of pre-defined themes which you can use to find the right one for you. PS: you can actually override an existing theme's variable to get your own custom theme going. Learn more about theming your diagram [here](../config/theming.md). + +The following are the different pre-defined theme options: + +- `base` +- `forest` +- `dark` +- `default` +- `neutral` + +**NOTE**: To change theme you can either use the `initialize` call or _directives_. Learn more about [directives](../config/directives.md) +Let's put them to use, and see how our sample diagram looks in different themes: + +### Base Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Forest Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Dark Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Default Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Neutral Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +```mermaid +%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/fcf53c98c25604c90a218104268c339be53035a6/src/lib/util/mermaid.ts) to see how the async loading is done. diff --git a/package.json b/package.json index f6130fa47..91d9316b3 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,7 @@ "version": "9.3.0-rc1", "description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.", "type": "module", - "packageManager": "pnpm@7.25.1", + "packageManager": "pnpm@7.27.0", "keywords": [ "diagram", "markdown", @@ -23,8 +23,8 @@ "build": "pnpm run -r clean && concurrently \"pnpm build:vite\" \"pnpm build:types\"", "dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server.ts\"", "release": "pnpm build", - "lint": "eslint --cache --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .", - "lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts", + "lint": "eslint --cache --cache-strategy content --ignore-path .gitignore . && pnpm lint:jison && prettier --cache --check .", + "lint:fix": "eslint --cache --cache-strategy content --fix --ignore-path .gitignore . && prettier --write . && ts-node-esm scripts/fixCSpell.ts", "lint:jison": "ts-node-esm ./scripts/jison/lint.mts", "cypress": "cypress run", "cypress:open": "cypress open", @@ -61,7 +61,7 @@ "@types/eslint": "^8.4.10", "@types/express": "^4.17.14", "@types/js-yaml": "^4.0.5", - "@types/jsdom": "^20.0.1", + "@types/jsdom": "^21.0.0", "@types/lodash": "^4.14.188", "@types/mdast": "^3.0.10", "@types/node": "^18.11.9", @@ -69,8 +69,9 @@ "@types/rollup-plugin-visualizer": "^4.2.1", "@typescript-eslint/eslint-plugin": "^5.48.2", "@typescript-eslint/parser": "^5.48.2", - "@vitest/coverage-c8": "^0.27.0", - "@vitest/ui": "^0.27.0", + "@vitest/coverage-c8": "^0.28.4", + "@vitest/spy": "^0.28.4", + "@vitest/ui": "^0.28.4", "concurrently": "^7.5.0", "coveralls": "^3.1.1", "cypress": "^10.11.0", @@ -94,21 +95,21 @@ "jest": "^29.3.1", "jison": "^0.4.18", "js-yaml": "^4.1.0", - "jsdom": "^20.0.2", + "jsdom": "^21.0.0", "lint-staged": "^13.0.3", "path-browserify": "^1.0.1", "pnpm": "^7.15.0", "prettier": "^2.7.1", "prettier-plugin-jsdoc": "^0.4.2", - "rimraf": "^3.0.2", + "rimraf": "^4.0.0", "rollup-plugin-visualizer": "^5.8.3", "start-server-and-test": "^1.14.0", "ts-node": "^10.9.1", "typescript": "^4.8.4", "vite": "^3.2.3", - "vitest": "^0.27.1" + "vitest": "^0.28.4" }, "volta": { - "node": "18.13.0" + "node": "18.14.0" } } diff --git a/packages/mermaid-example-diagram/package.json b/packages/mermaid-example-diagram/package.json index 8e958806a..13ab587cc 100644 --- a/packages/mermaid-example-diagram/package.json +++ b/packages/mermaid-example-diagram/package.json @@ -51,7 +51,7 @@ "dependencies": {}, "devDependencies": { "concurrently": "^7.5.0", - "rimraf": "^3.0.2" + "rimraf": "^4.0.0" }, "resolutions": { "d3": "^7.0.0" diff --git a/packages/mermaid-example-diagram/src/mermaidUtils.ts b/packages/mermaid-example-diagram/src/mermaidUtils.ts index 8894abdff..9ba66be5a 100644 --- a/packages/mermaid-example-diagram/src/mermaidUtils.ts +++ b/packages/mermaid-example-diagram/src/mermaidUtils.ts @@ -22,6 +22,7 @@ export const log: Record = { export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void; export let getConfig: () => object; export let sanitizeText: (str: string) => string; +export let commonDb: any; /** * Placeholder for the real function that will be injected by mermaid. */ @@ -41,15 +42,17 @@ export let setupGraphViewbox: ( * @param _getConfig - getConfig from mermaid/src/diagramAPI.ts * @param _sanitizeText - sanitizeText from mermaid/src/diagramAPI.ts * @param _setupGraphViewbox - setupGraphViewbox from mermaid/src/diagramAPI.ts + * @param _commonDb -`commonDb` from mermaid/src/diagramAPI.ts */ export const injectUtils = ( _log: Record, _setLogLevel: typeof setLogLevel, _getConfig: typeof getConfig, _sanitizeText: typeof sanitizeText, - _setupGraphViewbox: typeof setupGraphViewbox + _setupGraphViewbox: typeof setupGraphViewbox, + _commonDb: any ) => { - _log.debug('Mermaid utils injected into example-diagram'); + _log.info('Mermaid utils injected into timeline-diagram'); log.trace = _log.trace; log.debug = _log.debug; log.info = _log.info; @@ -60,4 +63,5 @@ export const injectUtils = ( getConfig = _getConfig; sanitizeText = _sanitizeText; setupGraphViewbox = _setupGraphViewbox; + commonDb = _commonDb; }; diff --git a/packages/mermaid-mindmap/package.json b/packages/mermaid-mindmap/package.json index e79e2df4f..8c861097b 100644 --- a/packages/mermaid-mindmap/package.json +++ b/packages/mermaid-mindmap/package.json @@ -50,7 +50,7 @@ "@types/cytoscape": "^3.19.9", "concurrently": "^7.5.0", "mermaid": "workspace:*", - "rimraf": "^3.0.2" + "rimraf": "^4.0.0" }, "resolutions": { "d3": "^7.0.0" diff --git a/packages/mermaid-mindmap/src/mermaidUtils.ts b/packages/mermaid-mindmap/src/mermaidUtils.ts index b575c201b..44cc85f73 100644 --- a/packages/mermaid-mindmap/src/mermaidUtils.ts +++ b/packages/mermaid-mindmap/src/mermaidUtils.ts @@ -27,6 +27,7 @@ export const log: Record = { export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void; export let getConfig: () => object; export let sanitizeText: (str: string) => string; +export let commonDb: () => object; // eslint-disable @typescript-eslint/no-explicit-any export let setupGraphViewbox: ( graph: any, @@ -40,7 +41,8 @@ export const injectUtils = ( _setLogLevel: any, _getConfig: any, _sanitizeText: any, - _setupGraphViewbox: any + _setupGraphViewbox: any, + _commonDb: any ) => { _log.info('Mermaid utils injected'); log.trace = _log.trace; @@ -53,4 +55,5 @@ export const injectUtils = ( getConfig = _getConfig; sanitizeText = _sanitizeText; setupGraphViewbox = _setupGraphViewbox; + commonDb = _commonDb; }; diff --git a/packages/mermaid-mindmap/src/mindmap.spec.js b/packages/mermaid-mindmap/src/mindmap.spec.js index e3f018350..753804a5d 100644 --- a/packages/mermaid-mindmap/src/mindmap.spec.js +++ b/packages/mermaid-mindmap/src/mindmap.spec.js @@ -347,4 +347,40 @@ root expect(child.children.length).toEqual(2); expect(child.children[1].nodeId).toEqual('b'); }); + it('MMP-23 Rows with only spaces should not interfere', function () { + let str = 'mindmap\nroot\n A\n \n\n B'; + mindmap.parse(str); + const mm = mindmap.yy.getMindmap(); + expect(mm.nodeId).toEqual('root'); + expect(mm.children.length).toEqual(2); + + const child = mm.children[0]; + expect(child.nodeId).toEqual('A'); + const child2 = mm.children[1]; + expect(child2.nodeId).toEqual('B'); + }); + it('MMP-24 Handle rows above the mindmap declarations', function () { + let str = '\n \nmindmap\nroot\n A\n \n\n B'; + mindmap.parse(str); + const mm = mindmap.yy.getMindmap(); + expect(mm.nodeId).toEqual('root'); + expect(mm.children.length).toEqual(2); + + const child = mm.children[0]; + expect(child.nodeId).toEqual('A'); + const child2 = mm.children[1]; + expect(child2.nodeId).toEqual('B'); + }); + it('MMP-25 Handle rows above the mindmap declarations, no space', function () { + let str = '\n\n\nmindmap\nroot\n A\n \n\n B'; + mindmap.parse(str); + const mm = mindmap.yy.getMindmap(); + expect(mm.nodeId).toEqual('root'); + expect(mm.children.length).toEqual(2); + + const child = mm.children[0]; + expect(child.nodeId).toEqual('A'); + const child2 = mm.children[1]; + expect(child2.nodeId).toEqual('B'); + }); }); diff --git a/packages/mermaid-mindmap/src/mindmapRenderer.js b/packages/mermaid-mindmap/src/mindmapRenderer.js index 9fd557e51..6ffe80f5e 100644 --- a/packages/mermaid-mindmap/src/mindmapRenderer.js +++ b/packages/mermaid-mindmap/src/mindmapRenderer.js @@ -89,6 +89,7 @@ function addNodes(mindmap, cy, conf, level) { /** * @param node * @param conf + * @param cy */ function layoutMindmap(node, conf) { return new Promise((resolve) => { @@ -131,7 +132,10 @@ function layoutMindmap(node, conf) { }); } /** + * @param node * @param cy + * @param positionedMindmap + * @param conf */ function positionNodes(cy) { cy.nodes().map((node, id) => { diff --git a/packages/mermaid-mindmap/src/parser/mindmap.jison b/packages/mermaid-mindmap/src/parser/mindmap.jison index a96ee6261..d2f6bbf1a 100644 --- a/packages/mermaid-mindmap/src/parser/mindmap.jison +++ b/packages/mermaid-mindmap/src/parser/mindmap.jison @@ -25,6 +25,7 @@ \n { this.popState();} // [\s]*"::icon(" { this.begin('ICON'); } "::icon(" { yy.getLogger().trace('Begin icon');this.begin('ICON'); } +[\s]+[\n] {yy.getLogger().trace('SPACELINE');return 'SPACELINE' /* skip all whitespace */ ;} [\n]+ return 'NL'; [^\)]+ { return 'ICON'; } \) {yy.getLogger().trace('end icon');this.popState();} @@ -64,14 +65,25 @@ start // %{ : info document 'EOF' { return yy; } } - : MINDMAP document { return yy; } - | MINDMAP NL document { return yy; } - | SPACELIST MINDMAP document { return yy; } - ; + : mindMap + | spaceLines mindMap + ; + +spaceLines + : SPACELINE + | spaceLines SPACELINE + | spaceLines NL + ; + +mindMap + : MINDMAP document { return yy; } + | MINDMAP NL document { return yy; } + ; stop : NL {yy.getLogger().trace('Stop NL ');} | EOF {yy.getLogger().trace('Stop EOF ');} + | SPACELINE | stop NL {yy.getLogger().trace('Stop NL2 ');} | stop EOF {yy.getLogger().trace('Stop EOF2 ');} ; @@ -81,9 +93,10 @@ document ; statement - : SPACELIST node { yy.getLogger().trace('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); } + : SPACELIST node { yy.getLogger().info('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); } | SPACELIST ICON { yy.getLogger().trace('Icon: ',$2);yy.decorateNode({icon: $2}); } | SPACELIST CLASS { yy.decorateNode({class: $2}); } + | SPACELINE { yy.getLogger().trace('SPACELIST');} | node { yy.getLogger().trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type); } | ICON { yy.decorateNode({icon: $1}); } | CLASS { yy.decorateNode({class: $1}); } diff --git a/packages/mermaid-mindmap/src/svgDraw.js b/packages/mermaid-mindmap/src/svgDraw.js index d4f57f1f1..2b1aa021e 100644 --- a/packages/mermaid-mindmap/src/svgDraw.js +++ b/packages/mermaid-mindmap/src/svgDraw.js @@ -203,15 +203,14 @@ const roundedRectBkg = function (elem, node) { * @returns {number} The height nodes dom element */ export const drawNode = function (elem, node, fullSection, conf) { - const section = (fullSection % MAX_SECTIONS) - 1; + const section = fullSection % (MAX_SECTIONS - 1); const nodeElem = elem.append('g'); node.section = section; - nodeElem.attr( - 'class', - (node.class ? node.class + ' ' : '') + - 'mindmap-node ' + - (section < 0 ? 'section-root' : 'section-' + section) - ); + let sectionClass = 'section-' + section; + if (section < 0) { + sectionClass += ' section-root'; + } + nodeElem.attr('class', (node.class ? node.class + ' ' : '') + 'mindmap-node ' + sectionClass); const bkgElem = nodeElem.append('g'); // Create the wrapped text element @@ -305,7 +304,7 @@ export const drawNode = function (elem, node, fullSection, conf) { }; export const drawEdge = function drawEdge(edgesElem, mindmap, parent, depth, fullSection) { - const section = (fullSection % MAX_SECTIONS) - 1; + const section = fullSection % (MAX_SECTIONS - 1); const sx = parent.x + parent.width / 2; const sy = parent.y + parent.height / 2; const ex = mindmap.x + mindmap.width / 2; diff --git a/packages/mermaid/.gitignore b/packages/mermaid/.gitignore index 1a961ffe0..6ed32bcf7 100644 --- a/packages/mermaid/.gitignore +++ b/packages/mermaid/.gitignore @@ -1,2 +1,3 @@ src/vitepress -src/docs/config/setup \ No newline at end of file +src/docs/config/setup +README.* \ No newline at end of file diff --git a/packages/mermaid/README.md b/packages/mermaid/README.md deleted file mode 100644 index e6c7db608..000000000 --- a/packages/mermaid/README.md +++ /dev/null @@ -1,346 +0,0 @@ -# mermaid - -[![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) [![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![Twitter Follow](https://img.shields.io/twitter/follow/mermaidjs_?style=social)](https://twitter.com/mermaidjs_) - -English | [简体中文](./README.zh-CN.md) - - - -:trophy: **Mermaid was nominated and won the [JS Open Source Awards (2019)](https://osawards.com/javascript/2019) in the category "The most exciting use of technology"!!!** - -**Thanks to all involved, people committing pull requests, people answering questions! 🙏** - -Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! - -## About - - - -Mermaid is a JavaScript-based diagramming and charting tool that uses Markdown-inspired text definitions and a renderer to create and modify complex diagrams. The main purpose of Mermaid is to help documentation catch up with development. - -> Doc-Rot is a Catch-22 that Mermaid helps to solve. - -Diagramming and documentation costs precious developer time and gets outdated quickly. -But not having diagrams or docs ruins productivity and hurts organizational learning.
-Mermaid addresses this problem by enabling users to create easily modifiable diagrams. It can also be made part of production scripts (and other pieces of code).
-
- -Mermaid allows even non-programmers to easily create detailed diagrams through the [Mermaid Live Editor](https://mermaid.live/).
-[Tutorials](./docs/Tutorials.md) has video tutorials. -Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](./docs/integrations.md). - -You can also use Mermaid within [GitHub](https://github.blog/2022-02-14-include-diagrams-markdown-files-mermaid/) as well many of your other favorite applications—check out the list of [Integrations and Usages of Mermaid](./docs/integrations.md). - -For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](./docs/n00b-overview.md), [Usage](./docs/usage.md) and [Tutorials](./docs/Tutorials.md). - -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) | 📜 [Changelog](./docs/CHANGELOG.md) - -In our release process we rely heavily on visual regression tests using [applitools](https://applitools.com/). Applitools is a great service which has been easy to use and integrate with our tests. - - - - - - - -## Examples - -**The following are some examples of the diagrams, charts and graphs that can be made using Mermaid. Click here to jump into the [text syntax](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference).** - - - -### Flowchart [docs - live editor] - -``` -flowchart LR - -A[Hard] -->|Text| B(Round) -B --> C{Decision} -C -->|One| D[Result 1] -C -->|Two| E[Result 2] -``` - -```mermaid -flowchart LR - -A[Hard] -->|Text| B(Round) -B --> C{Decision} -C -->|One| D[Result 1] -C -->|Two| E[Result 2] -``` - -### Sequence diagram [docs - live editor] - -``` -sequenceDiagram -Alice->>John: Hello John, how are you? -loop Healthcheck - John->>John: Fight against hypochondria -end -Note right of John: Rational thoughts! -John-->>Alice: Great! -John->>Bob: How about you? -Bob-->>John: Jolly good! -``` - -```mermaid -sequenceDiagram -Alice->>John: Hello John, how are you? -loop Healthcheck - John->>John: Fight against hypochondria -end -Note right of John: Rational thoughts! -John-->>Alice: Great! -John->>Bob: How about you? -Bob-->>John: Jolly good! -``` - -### Gantt chart [docs - live editor] - -``` -gantt - section Section - Completed :done, des1, 2014-01-06,2014-01-08 - Active :active, des2, 2014-01-07, 3d - Parallel 1 : des3, after des1, 1d - Parallel 2 : des4, after des1, 1d - Parallel 3 : des5, after des3, 1d - Parallel 4 : des6, after des4, 1d -``` - -```mermaid -gantt - section Section - Completed :done, des1, 2014-01-06,2014-01-08 - Active :active, des2, 2014-01-07, 3d - Parallel 1 : des3, after des1, 1d - Parallel 2 : des4, after des1, 1d - Parallel 3 : des5, after des3, 1d - Parallel 4 : des6, after des4, 1d -``` - -### Class diagram [docs - live editor] - -``` -classDiagram -Class01 <|-- AveryLongClass : Cool -<> Class01 -Class09 --> C2 : Where am I? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -class Class10 { - <> - int id - size() -} -``` - -```mermaid -classDiagram -Class01 <|-- AveryLongClass : Cool -<> Class01 -Class09 --> C2 : Where am I? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -class Class10 { - <> - int id - size() -} -``` - -### State diagram [docs - live editor] - -``` -stateDiagram-v2 -[*] --> Still -Still --> [*] -Still --> Moving -Moving --> Still -Moving --> Crash -Crash --> [*] -``` - -```mermaid -stateDiagram-v2 -[*] --> Still -Still --> [*] -Still --> Moving -Moving --> Still -Moving --> Crash -Crash --> [*] -``` - -### Pie chart [docs - live editor] - -``` -pie -"Dogs" : 386 -"Cats" : 85.9 -"Rats" : 15 -``` - -```mermaid -pie -"Dogs" : 386 -"Cats" : 85.9 -"Rats" : 15 -``` - -### Git graph [experimental - live editor] - -### User Journey diagram [docs - live editor] - -``` - journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 3: Me -``` - -```mermaid - journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 3: Me -``` - -### C4 diagram [docs] - -``` -C4Context -title System Context diagram for Internet Banking System - -Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.") -Person(customerB, "Banking Customer B") -Person_Ext(customerC, "Banking Customer C") -System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.") - -Person(customerD, "Banking Customer D", "A customer of the bank,
with personal bank accounts.") - -Enterprise_Boundary(b1, "BankBoundary") { - - SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") - - System_Boundary(b2, "BankBoundary2") { - System(SystemA, "Banking System A") - System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") - } - - System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") - SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") - - Boundary(b3, "BankBoundary3", "boundary") { - SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") - SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") - } -} - -BiRel(customerA, SystemAA, "Uses") -BiRel(SystemAA, SystemE, "Uses") -Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") -Rel(SystemC, customerA, "Sends e-mails to") -``` - -```mermaid -C4Context -title System Context diagram for Internet Banking System - -Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.") -Person(customerB, "Banking Customer B") -Person_Ext(customerC, "Banking Customer C") -System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.") - -Person(customerD, "Banking Customer D", "A customer of the bank,
with personal bank accounts.") - -Enterprise_Boundary(b1, "BankBoundary") { - - SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") - - System_Boundary(b2, "BankBoundary2") { - System(SystemA, "Banking System A") - System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") - } - - System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") - SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") - - Boundary(b3, "BankBoundary3", "boundary") { - SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") - SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") - } -} - -BiRel(customerA, SystemAA, "Uses") -BiRel(SystemAA, SystemE, "Uses") -Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") -Rel(SystemC, customerA, "Sends e-mails to") -``` - -## Release - -For those who have the permission to do so: - -Update version number in `package.json`. - -```sh -npm publish -``` - -The above command generates files into the `dist` folder and publishes them to npmjs.org. - -## Related projects - -- [Command Line Interface](https://github.com/mermaid-js/mermaid-cli) -- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor) -- [HTTP Server](https://github.com/TomWright/mermaid-server) - -## Contributors [![Good first issue](https://img.shields.io/github/labels/mermaid-js/mermaid/Good%20first%20issue%21)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) - -Mermaid is a growing community and is always accepting new contributors. There's a lot of different ways to help out and we're always looking for extra hands! Look at [this issue](https://github.com/mermaid-js/mermaid/issues/866) if you want to know where to start helping out. - -Detailed information about how to contribute can be found in the [contribution guide](CONTRIBUTING.md) - -## Security and safe diagrams - -For public sites, it can be precarious to retrieve text from users on the internet, storing that content for presentation in a browser at a later stage. The reason is that the user content can contain embedded malicious scripts that will run when the data is presented. For Mermaid this is a risk, specially as mermaid diagrams contain many characters that are used in html which makes the standard sanitation unusable as it also breaks the diagrams. We still make an effort to sanitise the incoming code and keep refining the process but it is hard to guarantee that there are no loop holes. - -As an extra level of security for sites with external users we are happy to introduce a new security level in which the diagram is rendered in a sandboxed iframe preventing javascript in the code from being executed. This is a great step forward for better security. - -_Unfortunately you can not have a cake and eat it at the same time which in this case means that some of the interactive functionality gets blocked along with the possible malicious code._ - -## Reporting vulnerabilities - -To report a vulnerability, please e-mail security@mermaid.live with a description of the issue, the steps you took to create the issue, affected versions, and if known, mitigations for the issue. - -## Appreciation - -A quick note from Knut Sveidqvist: - -> _Many thanks to the [d3](https://d3js.org/) and [dagre-d3](https://github.com/cpettitt/dagre-d3) projects for providing the graphical layout and drawing libraries!_ >_Thanks also to the [js-sequence-diagram](https://bramp.github.io/js-sequence-diagrams) project for usage of the grammar for the sequence diagrams. Thanks to Jessica Peter for inspiration and starting point for gantt rendering._ >_Thank you to [Tyler Long](https://github.com/tylerlong) who has been a collaborator since April 2017._ -> -> _Thank you to the ever-growing list of [contributors](https://github.com/knsv/mermaid/graphs/contributors) that brought the project this far!_ - ---- - -_Mermaid was created by Knut Sveidqvist for easier documentation._ diff --git a/packages/mermaid/README.zh-CN.md b/packages/mermaid/README.zh-CN.md deleted file mode 100644 index f34c7a647..000000000 --- a/packages/mermaid/README.zh-CN.md +++ /dev/null @@ -1,334 +0,0 @@ -# mermaid - -[![Build CI Status](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml/badge.svg)](https://github.com/mermaid-js/mermaid/actions/workflows/build.yml) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![npm minified gzipped bundle size](https://img.shields.io/bundlephobia/minzip/mermaid)](https://bundlephobia.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![CDN Status](https://img.shields.io/jsdelivr/npm/hm/mermaid)](https://www.jsdelivr.com/package/npm/mermaid) [![NPM](https://img.shields.io/npm/dm/mermaid)](https://www.npmjs.com/package/mermaid) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE) [![Twitter Follow](https://img.shields.io/twitter/follow/mermaidjs_?style=social)](https://twitter.com/mermaidjs_) - -[English](./README.md) | 简体中文 - - - -:trophy: **Mermaid 被提名并获得了 [JS Open Source Awards (2019)](https://osawards.com/javascript/2019) 的 "The most exciting use of technology" 奖项!!!** - -**感谢所有参与进来提交 PR,解答疑问的人们! 🙏** - -Explore Mermaid.js in depth, with real-world examples, tips & tricks from the creator... The first official book on Mermaid is available for purchase. Check it out! - -## 关于 Mermaid - - - -Mermaid 是一个基于 Javascript 的图表绘制工具,通过解析类 Markdown 的文本语法来实现图表的创建和动态修改。Mermaid 诞生的主要目的是让文档的更新能够及时跟上开发进度。 - -> Doc-Rot 是 Mermaid 致力于解决的一个难题。 - -绘图和编写文档花费了开发者宝贵的开发时间,而且随着业务的变更,它很快就会过期。 但是如果缺少了图表或文档,对于生产力和团队新人的业务学习都会产生巨大的阻碍。
-Mermaid 通过允许用户创建便于修改的图表来解决这一难题,它也可以作为生产脚本(或其他代码)的一部分。
-
-Mermaid 甚至能让非程序员也能通过 [Mermaid Live Editor](https://mermaid.live/) 轻松创建详细的图表。
-你可以访问 [教程](./docs/Tutorials.md) 来查看 Live Editor 的视频教程,也可以查看 [Mermaid 的集成和使用](./docs/integrations.md) 这个清单来检查你的文档工具是否已经集成了 Mermaid 支持。 - -如果想要查看关于 Mermaid 更详细的介绍及基础使用方式,可以查看 [入门指引](./docs/n00b-overview.md), [用法](./docs/usage.md) 和 [教程](./docs/Tutorials.md). - -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [文档](https://mermaidjs.github.io) | 🙌 [贡献](https://github.com/mermaid-js/mermaid/blob/develop/CONTRIBUTING.md) | 📜 [更新日志](./docs/CHANGELOG.md) - - - -## 示例 - -**下面是一些可以使用 Mermaid 创建的图表示例。点击 [语法](https://mermaid-js.github.io/mermaid/#/n00b-syntaxReference) 查看详情。** - - - - -### 流程图 [文档 - live editor] - -``` -flowchart LR -A[Hard] -->|Text| B(Round) -B --> C{Decision} -C -->|One| D[Result 1] -C -->|Two| E[Result 2] -``` - -```mermaid -flowchart LR -A[Hard] -->|Text| B(Round) -B --> C{Decision} -C -->|One| D[Result 1] -C -->|Two| E[Result 2] -``` - -### 时序图 [文档 - live editor] - -``` -sequenceDiagram -Alice->>John: Hello John, how are you? -loop Healthcheck - John->>John: Fight against hypochondria -end -Note right of John: Rational thoughts! -John-->>Alice: Great! -John->>Bob: How about you? -Bob-->>John: Jolly good! -``` - -```mermaid -sequenceDiagram -Alice->>John: Hello John, how are you? -loop Healthcheck - John->>John: Fight against hypochondria -end -Note right of John: Rational thoughts! -John-->>Alice: Great! -John->>Bob: How about you? -Bob-->>John: Jolly good! -``` - -### 甘特图 [文档 - live editor] - -``` -gantt - section Section - Completed :done, des1, 2014-01-06,2014-01-08 - Active :active, des2, 2014-01-07, 3d - Parallel 1 : des3, after des1, 1d - Parallel 2 : des4, after des1, 1d - Parallel 3 : des5, after des3, 1d - Parallel 4 : des6, after des4, 1d -``` - -```mermaid -gantt - section Section - Completed :done, des1, 2014-01-06,2014-01-08 - Active :active, des2, 2014-01-07, 3d - Parallel 1 : des3, after des1, 1d - Parallel 2 : des4, after des1, 1d - Parallel 3 : des5, after des3, 1d - Parallel 4 : des6, after des4, 1d -``` - -### 类图 [文档 - live editor] - -``` -classDiagram -Class01 <|-- AveryLongClass : Cool -<> Class01 -Class09 --> C2 : Where am I? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -class Class10 { - <> - int id - size() -} -``` - -```mermaid -classDiagram -Class01 <|-- AveryLongClass : Cool -<> Class01 -Class09 --> C2 : Where am I? -Class09 --* C3 -Class09 --|> Class07 -Class07 : equals() -Class07 : Object[] elementData -Class01 : size() -Class01 : int chimp -Class01 : int gorilla -class Class10 { - <> - int id - size() -} -``` - -### 状态图 [[docs - live editor] - -``` -stateDiagram-v2 -[*] --> Still -Still --> [*] -Still --> Moving -Moving --> Still -Moving --> Crash -Crash --> [*] -``` - -```mermaid -stateDiagram-v2 -[*] --> Still -Still --> [*] -Still --> Moving -Moving --> Still -Moving --> Crash -Crash --> [*] -``` - -### 饼图 [文档 - live editor] - -``` -pie -"Dogs" : 386 -"Cats" : 85 -"Rats" : 15 -``` - -```mermaid -pie -"Dogs" : 386 -"Cats" : 85 -"Rats" : 15 -``` - -### Git 图 [实验特性 - live editor] - -### 用户体验旅程图 [文档 - live editor] - -``` - journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 3: Me -``` - -```mermaid - journey - title My working day - section Go to work - Make tea: 5: Me - Go upstairs: 3: Me - Do work: 1: Me, Cat - section Go home - Go downstairs: 5: Me - Sit down: 3: Me -``` - -### C4 图 [文档] - -``` -C4Context -title System Context diagram for Internet Banking System - -Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.") -Person(customerB, "Banking Customer B") -Person_Ext(customerC, "Banking Customer C") -System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.") - -Person(customerD, "Banking Customer D", "A customer of the bank,
with personal bank accounts.") - -Enterprise_Boundary(b1, "BankBoundary") { - - SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") - - System_Boundary(b2, "BankBoundary2") { - System(SystemA, "Banking System A") - System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") - } - - System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") - SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") - - Boundary(b3, "BankBoundary3", "boundary") { - SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") - SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") - } -} - -BiRel(customerA, SystemAA, "Uses") -BiRel(SystemAA, SystemE, "Uses") -Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") -Rel(SystemC, customerA, "Sends e-mails to") -``` - -```mermaid -C4Context -title System Context diagram for Internet Banking System - -Person(customerA, "Banking Customer A", "A customer of the bank, with personal bank accounts.") -Person(customerB, "Banking Customer B") -Person_Ext(customerC, "Banking Customer C") -System(SystemAA, "Internet Banking System", "Allows customers to view information about their bank accounts, and make payments.") - -Person(customerD, "Banking Customer D", "A customer of the bank,
with personal bank accounts.") - -Enterprise_Boundary(b1, "BankBoundary") { - - SystemDb_Ext(SystemE, "Mainframe Banking System", "Stores all of the core banking information about customers, accounts, transactions, etc.") - - System_Boundary(b2, "BankBoundary2") { - System(SystemA, "Banking System A") - System(SystemB, "Banking System B", "A system of the bank, with personal bank accounts.") - } - - System_Ext(SystemC, "E-mail system", "The internal Microsoft Exchange e-mail system.") - SystemDb(SystemD, "Banking System D Database", "A system of the bank, with personal bank accounts.") - - Boundary(b3, "BankBoundary3", "boundary") { - SystemQueue(SystemF, "Banking System F Queue", "A system of the bank, with personal bank accounts.") - SystemQueue_Ext(SystemG, "Banking System G Queue", "A system of the bank, with personal bank accounts.") - } -} - -BiRel(customerA, SystemAA, "Uses") -BiRel(SystemAA, SystemE, "Uses") -Rel(SystemAA, SystemC, "Sends e-mails", "SMTP") -Rel(SystemC, customerA, "Sends e-mails to") -``` - -## 发布 - -对于有权限的同学来说,你可以通过以下步骤来完成发布操作: - -更新 `package.json` 中的版本号,然后执行如下命令: - -```sh -npm publish -``` - -以上的命令会将文件打包到 `dist` 目录并发布至 npmjs.org. - -## 相关项目 - -- [Command Line Interface](https://github.com/mermaid-js/mermaid-cli) -- [Live Editor](https://github.com/mermaid-js/mermaid-live-editor) -- [HTTP Server](https://github.com/TomWright/mermaid-server) - -## 贡献者 [![Good first issue](https://img.shields.io/github/labels/mermaid-js/mermaid/Good%20first%20issue%21)](https://github.com/mermaid-js/mermaid/issues?q=is%3Aissue+is%3Aopen+label%3A%22Good+first+issue%21%22) [![Contributors](https://img.shields.io/github/contributors/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) [![Commits](https://img.shields.io/github/commit-activity/m/mermaid-js/mermaid)](https://github.com/mermaid-js/mermaid/graphs/contributors) - -Mermaid 是一个不断发展中的社区,并且还在接收新的贡献者。有很多不同的方式可以参与进来,而且我们还在寻找额外的帮助。如果你想知道如何开始贡献,请查看 [这个 issue](https://github.com/mermaid-js/mermaid/issues/866)。 - -关于如何贡献的详细信息可以在 [贡献指南](CONTRIBUTING.md) 中找到。 - -## 安全 - -对于公开网站来说,从互联网上的用户处检索文本、存储供后续在浏览器中展示的内容可能是不安全的,理由是用户的内容可能嵌入一些数据加载完成之后就会运行的恶意脚本,这些对于 Mermaid 来说毫无疑问是一个风险,尤其是 mermaid 图表还包含了许多在 html 中使用的字符,这意味着我们难以使用常规的手段来过滤不安全代码,因为这些常规手段会造成图表损坏。我们仍然在努力对获取到的代码进行安全过滤并不断完善我们的程序,但很难保证没有漏洞。 - -作为拥有外部用户的网站的额外安全级别,我们很高兴推出一个新的安全级别,其中的图表在沙盒 iframe 中渲染,防止代码中的 javascript 被执行,这是在安全性方面迈出的一大步。 - -_很不幸的是,鱼与熊掌不可兼得,在这个场景下它意味着在可能的恶意代码被阻止时,也会损失部分交互能力_。 - -## 报告漏洞 - -如果想要报告漏洞,请发送邮件到 security@mermaid.live, 并附上问题的描述、复现问题的步骤、受影响的版本,以及解决问题的方案(如果有的话)。 - -## 鸣谢 - -来自 Knut Sveidqvist: - -> _特别感谢 [d3](https://d3js.org/) 和 [dagre-d3](https://github.com/cpettitt/dagre-d3) 这两个优秀的项目,它们提供了图形布局和绘图工具库! _ >_同样感谢 [js-sequence-diagram](https://bramp.github.io/js-sequence-diagrams) 提供了时序图语法的使用。 感谢 Jessica Peter 提供了甘特图渲染的灵感。_ >_感谢 [Tyler Long](https://github.com/tylerlong) 从 2017 年四月开始成为了项目的合作者。_ -> -> _感谢越来越多的 [贡献者们](https://github.com/knsv/mermaid/graphs/contributors),没有你们,就没有这个项目的今天!_ - ---- - -_Mermaid 是由 Knut Sveidqvist 创建,它为了更简单的文档编写而生。_ diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json index 81b34dafc..a6f85e903 100644 --- a/packages/mermaid/package.json +++ b/packages/mermaid/package.json @@ -33,7 +33,7 @@ "docs:serve": "pnpm docs:build:vitepress && vitepress serve src/vitepress", "docs:spellcheck": "cspell --config ../../cSpell.json \"src/docs/**/*.md\"", "release": "pnpm build", - "prepublishOnly": "pnpm -w run build" + "prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm -w run build" }, "repository": { "type": "git", @@ -69,12 +69,12 @@ "devDependencies": { "@types/d3": "^7.4.0", "@types/dompurify": "^2.4.0", - "@types/jsdom": "^20.0.1", + "@types/jsdom": "^21.0.0", "@types/lodash-es": "^4.17.6", "@types/micromatch": "^4.0.2", "@types/prettier": "^2.7.1", "@types/stylis": "^4.0.2", - "@types/uuid": "^8.3.4", + "@types/uuid": "^9.0.0", "@typescript-eslint/eslint-plugin": "^5.42.1", "@typescript-eslint/parser": "^5.42.1", "chokidar": "^3.5.3", @@ -85,14 +85,14 @@ "globby": "^13.1.2", "jison": "^0.4.18", "js-base64": "^3.7.2", - "jsdom": "^20.0.2", + "jsdom": "^21.0.0", "micromatch": "^4.0.5", "path-browserify": "^1.0.1", "prettier": "^2.7.1", "remark": "^14.0.2", "remark-frontmatter": "^4.0.1", "remark-gfm": "^3.0.1", - "rimraf": "^3.0.2", + "rimraf": "^4.0.0", "start-server-and-test": "^1.14.0", "typedoc": "^0.23.18", "typedoc-plugin-markdown": "^3.13.6", diff --git a/packages/mermaid/src/Diagram.ts b/packages/mermaid/src/Diagram.ts index ed0762ece..1f68e52ba 100644 --- a/packages/mermaid/src/Diagram.ts +++ b/packages/mermaid/src/Diagram.ts @@ -44,7 +44,7 @@ export class Diagram { this.parser.parser.yy = this.db; if (diagram.init) { diagram.init(cnf); - log.debug('Initialized diagram ' + this.type, cnf); + log.info('Initialized diagram ' + this.type, cnf); } this.txt += '\n'; diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index ff199ca8b..474075824 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -26,6 +26,7 @@ export interface MermaidConfig { sequence?: SequenceDiagramConfig; gantt?: GanttDiagramConfig; journey?: JourneyDiagramConfig; + timeline?: TimelineDiagramConfig; class?: ClassDiagramConfig; state?: StateDiagramConfig; er?: ErDiagramConfig; @@ -292,6 +293,30 @@ export interface JourneyDiagramConfig extends BaseDiagramConfig { sectionColours?: string[]; } +export interface TimelineDiagramConfig extends BaseDiagramConfig { + diagramMarginX?: number; + diagramMarginY?: number; + leftMargin?: number; + width?: number; + height?: number; + boxMargin?: number; + boxTextMargin?: number; + noteMargin?: number; + messageMargin?: number; + messageAlign?: string; + bottomMarginAdj?: number; + rightAngles?: boolean; + taskFontSize?: string | number; + taskFontFamily?: string; + taskMargin?: number; + activationWidth?: number; + textPlacement?: string; + actorColours?: string[]; + sectionFills?: string[]; + sectionColours?: string[]; + disableMulticolor?: boolean; +} + export interface GanttDiagramConfig extends BaseDiagramConfig { titleTopMargin?: number; barHeight?: number; diff --git a/packages/mermaid/src/defaultConfig.ts b/packages/mermaid/src/defaultConfig.ts index fb6d88d99..ec741e908 100644 --- a/packages/mermaid/src/defaultConfig.ts +++ b/packages/mermaid/src/defaultConfig.ts @@ -862,6 +862,156 @@ const config: Partial = { sectionFills: ['#191970', '#8B008B', '#4B0082', '#2F4F4F', '#800000', '#8B4513', '#00008B'], sectionColours: ['#fff'], }, + /** The object containing configurations specific for timeline diagrams */ + timeline: { + /** + * | Parameter | Description | Type | Required | Values | + * | -------------- | ---------------------------------------------------- | ------- | -------- | ------------------ | + * | diagramMarginX | Margin to the right and left of the sequence diagram | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 50 + */ + diagramMarginX: 50, + + /** + * | Parameter | Description | Type | Required | Values | + * | -------------- | -------------------------------------------------- | ------- | -------- | ------------------ | + * | diagramMarginY | Margin to the over and under the sequence diagram. | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 10 + */ + diagramMarginY: 10, + + /** + * | Parameter | Description | Type | Required | Values | + * | ----------- | --------------------- | ------- | -------- | ------------------ | + * | actorMargin | Margin between actors | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 50 + */ + leftMargin: 150, + + /** + * | Parameter | Description | Type | Required | Values | + * | --------- | -------------------- | ------- | -------- | ------------------ | + * | width | Width of actor boxes | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 150 + */ + width: 150, + + /** + * | Parameter | Description | Type | Required | Values | + * | --------- | --------------------- | ------- | -------- | ------------------ | + * | height | Height of actor boxes | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 65 + */ + height: 50, + + /** + * | Parameter | Description | Type | Required | Values | + * | --------- | ------------------------ | ------- | -------- | ------------------ | + * | boxMargin | Margin around loop boxes | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 10 + */ + boxMargin: 10, + + /** + * | Parameter | Description | Type | Required | Values | + * | ------------- | -------------------------------------------- | ------- | -------- | ------------------ | + * | boxTextMargin | Margin around the text in loop/alt/opt boxes | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 5 + */ + boxTextMargin: 5, + + /** + * | Parameter | Description | Type | Required | Values | + * | ---------- | ------------------- | ------- | -------- | ------------------ | + * | noteMargin | Margin around notes | Integer | Required | Any Positive Value | + * + * **Notes:** Default value: 10 + */ + noteMargin: 10, + + /** + * | Parameter | Description | Type | Required | Values | + * | ------------- | ----------------------- | ------- | -------- | ------------------ | + * | messageMargin | Space between messages. | Integer | Required | Any Positive Value | + * + * **Notes:** + * + * Space between messages. + * + * Default value: 35 + */ + messageMargin: 35, + + /** + * | Parameter | Description | Type | Required | Values | + * | ------------ | --------------------------- | ---- | -------- | ------------------------- | + * | messageAlign | Multiline message alignment | 3 | 4 | 'left', 'center', 'right' | + * + * **Notes:** Default value: 'center' + */ + messageAlign: 'center', + + /** + * | Parameter | Description | Type | Required | Values | + * | --------------- | ------------------------------------------ | ------- | -------- | ------------------ | + * | bottomMarginAdj | Prolongs the edge of the diagram downwards | Integer | 4 | Any Positive Value | + * + * **Notes:** + * + * Depending on css styling this might need adjustment. + * + * Default value: 1 + */ + bottomMarginAdj: 1, + + /** + * | Parameter | Description | Type | Required | Values | + * | ----------- | ----------- | ------- | -------- | ----------- | + * | useMaxWidth | See notes | boolean | 4 | true, false | + * + * **Notes:** + * + * When this flag is set the height and width is set to 100% and is then scaling with the + * available space if not the absolute space required is used. + * + * Default value: true + */ + useMaxWidth: true, + + /** + * | Parameter | Description | Type | Required | Values | + * | ----------- | --------------------------------- | ---- | -------- | ----------- | + * | rightAngles | Curved Arrows become Right Angles | 3 | 4 | true, false | + * + * **Notes:** + * + * This will display arrows that start and begin at the same node as right angles, rather than a + * curves + * + * Default value: false + */ + rightAngles: false, + taskFontSize: 14, + taskFontFamily: '"Open Sans", sans-serif', + taskMargin: 50, + // width of activation box + activationWidth: 10, + + // text placement as: tspan | fo | old only text as before + textPlacement: 'fo', + actorColours: ['#8FBC8F', '#7CFC00', '#00FFFF', '#20B2AA', '#B0E0E6', '#FFFFE0'], + + sectionFills: ['#191970', '#8B008B', '#4B0082', '#2F4F4F', '#800000', '#8B4513', '#00008B'], + sectionColours: ['#fff'], + disableMulticolor: false, + }, class: { /** * ### titleTopMargin diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index ce54f6f18..81ddb6163 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -1,5 +1,4 @@ import { registerDiagram } from './diagramAPI'; - // @ts-ignore: TODO Fix ts errors import gitGraphParser from '../diagrams/git/parser/gitGraph'; import { gitGraphDetector } from '../diagrams/git/gitGraphDetector'; @@ -97,6 +96,7 @@ import errorStyles from '../diagrams/error/styles'; import flowchartElk from '../diagrams/flowchart/elk/detector'; import { registerLazyLoadedDiagrams } from './detectType'; +import timelineDetector from '../diagrams/timeline/detector'; let hasLoadedDiagrams = false; export const addDiagrams = () => { if (hasLoadedDiagrams) { @@ -105,7 +105,7 @@ export const addDiagrams = () => { // This is added here to avoid race-conditions. // We could optimize the loading logic somehow. hasLoadedDiagrams = true; - registerLazyLoadedDiagrams(flowchartElk); + registerLazyLoadedDiagrams(flowchartElk, timelineDetector); registerDiagram( 'error', diff --git a/packages/mermaid/src/diagram-api/diagramAPI.ts b/packages/mermaid/src/diagram-api/diagramAPI.ts index 748cc5f96..936e43efd 100644 --- a/packages/mermaid/src/diagram-api/diagramAPI.ts +++ b/packages/mermaid/src/diagram-api/diagramAPI.ts @@ -5,6 +5,8 @@ import { sanitizeText as _sanitizeText } from '../diagrams/common/common'; import { setupGraphViewbox as _setupGraphViewbox } from '../setupGraphViewbox'; import { addStylesForDiagram } from '../styles'; import { DiagramDefinition, DiagramDetector } from './types'; +import * as _commonDb from '../commonDb'; +import { parseDirective as _parseDirective } from '../directiveUtils'; /* Packaging and exposing resources for external diagrams so that they can import @@ -16,6 +18,11 @@ export const setLogLevel = _setLogLevel; export const getConfig = _getConfig; export const sanitizeText = (text: string) => _sanitizeText(text, getConfig()); export const setupGraphViewbox = _setupGraphViewbox; +export const getCommonDb = () => { + return _commonDb; +}; +export const parseDirective = (p: any, statement: string, context: string, type: string) => + _parseDirective(p, statement, context, type); const diagrams: Record = {}; export interface Detectors { @@ -46,7 +53,15 @@ export const registerDiagram = ( addStylesForDiagram(id, diagram.styles); if (diagram.injectUtils) { - diagram.injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewbox); + diagram.injectUtils( + log, + setLogLevel, + getConfig, + sanitizeText, + setupGraphViewbox, + getCommonDb(), + parseDirective + ); } }; diff --git a/packages/mermaid/src/diagram-api/types.ts b/packages/mermaid/src/diagram-api/types.ts index 3449782e2..d60019577 100644 --- a/packages/mermaid/src/diagram-api/types.ts +++ b/packages/mermaid/src/diagram-api/types.ts @@ -6,6 +6,8 @@ export interface InjectUtils { _getConfig: any; _sanitizeText: any; _setupGraphViewbox: any; + _commonDb: any; + _parseDirective: any; } /** @@ -29,7 +31,9 @@ export interface DiagramDefinition { _setLogLevel: InjectUtils['_setLogLevel'], _getConfig: InjectUtils['_getConfig'], _sanitizeText: InjectUtils['_sanitizeText'], - _setupGraphViewbox: InjectUtils['_setupGraphViewbox'] + _setupGraphViewbox: InjectUtils['_setupGraphViewbox'], + _commonDb: InjectUtils['_commonDb'], + _parseDirective: InjectUtils['_parseDirective'] ) => void; } diff --git a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison index d5ba18b63..bbc72adcb 100644 --- a/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison +++ b/packages/mermaid/src/diagrams/sequence/parser/sequenceDiagram.jison @@ -35,8 +35,9 @@ \%%(?!\{)[^\n]* /* skip comments */ [^\}]\%\%[^\n]* /* skip comments */ [0-9]+(?=[ \n]+) return 'NUM'; +"box" { this.begin('LINE'); return 'box'; } "participant" { this.begin('ID'); return 'participant'; } -"actor" { this.begin('ID'); return 'participant_actor'; } +"actor" { this.begin('ID'); return 'participant_actor'; } [^\->:\n,;]+?([\-]*[^\->:\n,;]+?)*?(?=((?!\n)\s)+"as"(?!\n)\s|[#\n;]|$) { yytext = yytext.trim(); this.begin('ALIAS'); return 'ACTOR'; } "as" { this.popState(); this.popState(); this.begin('LINE'); return 'AS'; } (?:) { this.popState(); this.popState(); return 'NEWLINE'; } @@ -117,16 +118,30 @@ line | NEWLINE { $$=[]; } ; +box_section + : /* empty */ { $$ = [] } + | box_section box_line {$1.push($2);$$ = $1} + ; + +box_line + : SPACE participant_statement { $$ = $2 } + | participant_statement { $$ = $1 } + | NEWLINE { $$=[]; } + ; + + directive : openDirective typeDirective closeDirective 'NEWLINE' | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' ; statement - : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;} - | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;} - | 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;} + : participant_statement + | 'box' restOfLine box_section end + { + $3.unshift({type: 'boxStart', boxData:yy.parseBoxData($2) }); + $3.push({type: 'boxEnd', boxText:$2}); + $$=$3;} | signal 'NEWLINE' | autonumber NUM NUM 'NEWLINE' { $$= {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:Number($3), sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};} | autonumber NUM 'NEWLINE' { $$ = {type:'sequenceIndex',sequenceIndex: Number($2), sequenceIndexStep:1, sequenceVisible:true, signalType:yy.LINETYPE.AUTONUMBER};} @@ -209,6 +224,13 @@ else_sections { $$ = $1.concat([{type: 'else', altText:yy.parseMessage($3), signalType: yy.LINETYPE.ALT_ELSE}, $4]); } ; +participant_statement + : 'participant' actor 'AS' restOfLine 'NEWLINE' {$2.type='addParticipant';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant' actor 'NEWLINE' {$2.type='addParticipant';$$=$2;} + | 'participant_actor' actor 'AS' restOfLine 'NEWLINE' {$2.type='addActor';$2.description=yy.parseMessage($4); $$=$2;} + | 'participant_actor' actor 'NEWLINE' {$2.type='addActor'; $$=$2;} + ; + note_statement : 'note' placement actor text2 { diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDb.js b/packages/mermaid/src/diagrams/sequence/sequenceDb.js index fadd9f391..5c5554c72 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDb.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDb.js @@ -14,20 +14,52 @@ import { let prevActor = undefined; let actors = {}; +let boxes = []; let messages = []; const notes = []; let sequenceNumbersEnabled = false; let wrapEnabled; +let currentBox = undefined; export const parseDirective = function (statement, context, type) { mermaidAPI.parseDirective(this, statement, context, type); }; +export const addBox = function (data) { + boxes.push({ + name: data.text, + wrap: (data.wrap === undefined && autoWrap()) || !!data.wrap, + fill: data.color, + actorKeys: [], + }); + currentBox = boxes.slice(-1)[0]; +}; + export const addActor = function (id, name, description, type) { - // Don't allow description nulling + let assignedBox = currentBox; const old = actors[id]; - if (old && name === old.name && description == null) { - return; + if (old) { + // If already set and trying to set to a new one throw error + if (currentBox && old.box && currentBox !== old.box) { + throw new Error( + 'A same participant should only be defined in one Box: ' + + old.name + + " can't be in '" + + old.box.name + + "' and in '" + + currentBox.name + + "' at the same time." + ); + } + + // Don't change the box if already + assignedBox = old.box ? old.box : currentBox; + old.box = assignedBox; + + // Don't allow description nulling + if (old && name === old.name && description == null) { + return; + } } // Don't allow null descriptions, either @@ -39,6 +71,7 @@ export const addActor = function (id, name, description, type) { } actors[id] = { + box: assignedBox, name: name, description: description.text, wrap: (description.wrap === undefined && autoWrap()) || !!description.wrap, @@ -53,6 +86,9 @@ export const addActor = function (id, name, description, type) { actors[prevActor].nextActor = id; } + if (currentBox) { + currentBox.actorKeys.push(id); + } prevActor = id; }; @@ -111,10 +147,21 @@ export const addSignal = function ( return true; }; +export const hasAtLeastOneBox = function () { + return boxes.length > 0; +}; + +export const hasAtLeastOneBoxWithTitle = function () { + return boxes.some((b) => b.name); +}; + export const getMessages = function () { return messages; }; +export const getBoxes = function () { + return boxes; +}; export const getActors = function () { return actors; }; @@ -147,6 +194,7 @@ export const autoWrap = () => { export const clear = function () { actors = {}; + boxes = []; messages = []; sequenceNumbersEnabled = false; commonClear(); @@ -167,6 +215,47 @@ export const parseMessage = function (str) { return message; }; +// We expect the box statement to be color first then description +// The color can be rgb,rgba,hsl,hsla, or css code names #hex codes are not supported for now because of the way the char # is handled +// We extract first segment as color, the rest of the line is considered as text +export const parseBoxData = function (str) { + const match = str.match(/^((?:rgba?|hsla?)\s*\(.*\)|\w*)(.*)$/); + let color = match != null && match[1] ? match[1].trim() : 'transparent'; + let title = match != null && match[2] ? match[2].trim() : undefined; + + // check that the string is a color + if (window && window.CSS) { + if (!window.CSS.supports('color', color)) { + color = 'transparent'; + title = str.trim(); + } + } else { + const style = new Option().style; + style.color = color; + if (style.color !== color) { + color = 'transparent'; + title = str.trim(); + } + } + + const boxData = { + color: color, + text: + title !== undefined + ? sanitizeText(title.replace(/^:?(?:no)?wrap:/, ''), configApi.getConfig()) + : undefined, + wrap: + title !== undefined + ? title.match(/^:?wrap:/) !== null + ? true + : title.match(/^:?nowrap:/) !== null + ? false + : undefined + : undefined, + }; + return boxData; +}; + export const LINETYPE = { SOLID: 0, DOTTED: 1, @@ -311,6 +400,13 @@ function insertProperties(actor, properties) { } } +/** + * + */ +function boxEnd() { + currentBox = undefined; +} + export const addDetails = function (actorId, text) { // find the actor const actor = getActor(actorId); @@ -391,6 +487,12 @@ export const apply = function (param) { case 'addMessage': addSignal(param.from, param.to, param.msg, param.signalType); break; + case 'boxStart': + addBox(param.boxData); + break; + case 'boxEnd': + boxEnd(); + break; case 'loopStart': addSignal(undefined, undefined, param.loopText, param.signalType); break; @@ -467,12 +569,14 @@ export default { getActorKeys, getActorProperty, getAccTitle, + getBoxes, getDiagramTitle, setDiagramTitle, parseDirective, getConfig: () => configApi.getConfig().sequence, clear, parseMessage, + parseBoxData, LINETYPE, ARROWTYPE, PLACEMENT, @@ -481,4 +585,6 @@ export default { apply, setAccDescription, getAccDescription, + hasAtLeastOneBox, + hasAtLeastOneBoxWithTitle, }; diff --git a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js index c509fdae5..80850c2f2 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js +++ b/packages/mermaid/src/diagrams/sequence/sequenceDiagram.spec.js @@ -52,8 +52,16 @@ vi.mock('d3', () => { curveBasis: 'basis', curveBasisClosed: 'basisClosed', curveBasisOpen: 'basisOpen', - curveLinear: 'linear', + curveBumpX: 'bumpX', + curveBumpY: 'bumpY', + curveBundle: 'bundle', + curveCardinalClosed: 'cardinalClosed', + curveCardinalOpen: 'cardinalOpen', curveCardinal: 'cardinal', + curveCatmullRomClosed: 'catmullRomClosed', + curveCatmullRomOpen: 'catmullRomOpen', + curveCatmullRom: 'catmullRom', + curveLinear: 'linear', curveLinearClosed: 'linearClosed', curveMonotoneX: 'monotoneX', curveMonotoneY: 'monotoneY', @@ -1299,8 +1307,76 @@ properties b: {"class": "external-service-actor", "icon": "@computer"} expect(actors.b.properties['icon']).toBe('@computer'); expect(actors.c.properties['class']).toBe(undefined); }); -}); + it('should handle box', function () { + const str = ` +sequenceDiagram +box green Group 1 +participant a as Alice +participant b as Bob +end +participant c as Charlie +links a: { "Repo": "https://repo.contoso.com/", "Dashboard": "https://dashboard.contoso.com/" } +links b: { "Dashboard": "https://dashboard.contoso.com/" } +links a: { "On-Call": "https://oncall.contoso.com/?svc=alice" } +link a: Endpoint @ https://alice.contoso.com +link a: Swagger @ https://swagger.contoso.com +link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com +`; + + mermaidAPI.parse(str); + const boxes = diagram.db.getBoxes(); + expect(boxes[0].name).toEqual('Group 1'); + expect(boxes[0].actorKeys).toEqual(['a', 'b']); + expect(boxes[0].fill).toEqual('green'); + }); + + it('should handle box without color', function () { + const str = ` + sequenceDiagram + box Group 1 + participant a as Alice + participant b as Bob + end + participant c as Charlie + links a: { "Repo": "https://repo.contoso.com/", "Dashboard": "https://dashboard.contoso.com/" } + links b: { "Dashboard": "https://dashboard.contoso.com/" } + links a: { "On-Call": "https://oncall.contoso.com/?svc=alice" } + link a: Endpoint @ https://alice.contoso.com + link a: Swagger @ https://swagger.contoso.com + link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com + `; + + mermaidAPI.parse(str); + const boxes = diagram.db.getBoxes(); + expect(boxes[0].name).toEqual('Group 1'); + expect(boxes[0].actorKeys).toEqual(['a', 'b']); + expect(boxes[0].fill).toEqual('transparent'); + }); + + it('should handle box without description', function () { + const str = ` + sequenceDiagram + box Aqua + participant a as Alice + participant b as Bob + end + participant c as Charlie + links a: { "Repo": "https://repo.contoso.com/", "Dashboard": "https://dashboard.contoso.com/" } + links b: { "Dashboard": "https://dashboard.contoso.com/" } + links a: { "On-Call": "https://oncall.contoso.com/?svc=alice" } + link a: Endpoint @ https://alice.contoso.com + link a: Swagger @ https://swagger.contoso.com + link a: Tests @ https://tests.contoso.com/?svc=alice@contoso.com + `; + + mermaidAPI.parse(str); + const boxes = diagram.db.getBoxes(); + expect(boxes[0].name).toBeFalsy(); + expect(boxes[0].actorKeys).toEqual(['a', 'b']); + expect(boxes[0].fill).toEqual('Aqua'); + }); +}); describe('when checking the bounds in a sequenceDiagram', function () { beforeAll(() => { let conf = { @@ -1573,6 +1649,24 @@ Alice->Bob: Hello Bob, how are you?`; expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin); expect(bounds.stopy).toBe(models.lastMessage().stopy + 10); }); + it('should handle two actors in a box', function () { + const str = ` +sequenceDiagram +box rgb(34, 56, 0) Group1 +participant Alice +participant Bob +end +Alice->Bob: Hello Bob, how are you?`; + + mermaidAPI.parse(str); + diagram.renderer.draw(str, 'tst', '1.2.3', diagram); + + const { bounds, models } = diagram.renderer.bounds.getBounds(); + expect(bounds.startx).toBe(0); + expect(bounds.starty).toBe(0); + expect(bounds.stopx).toBe(conf.width * 2 + conf.actorMargin + conf.boxTextMargin * 2); + expect(bounds.stopy).toBe(models.lastMessage().stopy + 20); + }); it('should handle two actors with init directive', function () { const str = ` %%{init: {'logLevel': 0}}%% diff --git a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts index 1f6164b92..ce850c9f1 100644 --- a/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts +++ b/packages/mermaid/src/diagrams/sequence/sequenceRenderer.ts @@ -10,6 +10,7 @@ import assignWithDepth from '../../assignWithDepth'; import utils from '../../utils'; import { configureSvgSize } from '../../setupGraphViewbox'; import Diagram from '../../Diagram'; +import { convert } from '../../tests/util'; let conf = {}; @@ -43,10 +44,14 @@ export const bounds = { }, clear: function () { this.actors = []; + this.boxes = []; this.loops = []; this.messages = []; this.notes = []; }, + addBox: function (boxModel) { + this.boxes.push(boxModel); + }, addActor: function (actorModel) { this.actors.push(actorModel); }, @@ -72,6 +77,7 @@ export const bounds = { return this.notes[this.notes.length - 1]; }, actors: [], + boxes: [], loops: [], messages: [], notes: [], @@ -465,7 +471,8 @@ export const drawActors = function ( actorKeys, verticalPos, configuration, - messages + messages, + isFooter ) { if (configuration.hideUnusedParticipants === true) { const newActors = new Set(); @@ -480,8 +487,28 @@ export const drawActors = function ( let prevWidth = 0; let prevMargin = 0; let maxHeight = 0; + let prevBox = undefined; + for (const actorKey of actorKeys) { const actor = actors[actorKey]; + const box = actor.box; + + // end of box + if (prevBox && prevBox != box) { + if (!isFooter) { + bounds.models.addBox(prevBox); + } + prevMargin += conf.boxMargin + prevBox.margin; + } + + // new box + if (box && box != prevBox) { + if (!isFooter) { + box.x = prevWidth + prevMargin; + box.y = verticalPos; + } + prevMargin += box.margin; + } // Add some rendering data to the object actor.width = actor.width || conf.width; @@ -489,18 +516,27 @@ export const drawActors = function ( actor.margin = actor.margin || conf.actorMargin; actor.x = prevWidth + prevMargin; - actor.y = verticalPos; + actor.y = bounds.getVerticalPos(); // Draw the box with the attached line - const height = svgDraw.drawActor(diagram, actor, conf); + const height = svgDraw.drawActor(diagram, actor, conf, isFooter); maxHeight = Math.max(maxHeight, height); bounds.insert(actor.x, verticalPos, actor.x + actor.width, actor.height); - prevWidth += actor.width; - prevMargin += actor.margin; + prevWidth += actor.width + prevMargin; + if (actor.box) { + actor.box.width = prevWidth + box.margin - actor.box.x; + } + prevMargin = actor.margin; + prevBox = actor.box; bounds.models.addActor(actor); } + // end of box + if (prevBox && !isFooter) { + bounds.models.addBox(prevBox); + } + // Add a margin between the actor boxes and the first arrow bounds.bumpVerticalPos(maxHeight); }; @@ -614,18 +650,27 @@ export const draw = function (_text: string, id: string, _version: string, diagO // Fetch data from the parsing const actors = diagObj.db.getActors(); + const boxes = diagObj.db.getBoxes(); const actorKeys = diagObj.db.getActorKeys(); const messages = diagObj.db.getMessages(); const title = diagObj.db.getDiagramTitle(); - + const hasBoxes = diagObj.db.hasAtLeastOneBox(); + const hasBoxTitles = diagObj.db.hasAtLeastOneBoxWithTitle(); const maxMessageWidthPerActor = getMaxMessageWidthPerActor(actors, messages, diagObj); - conf.height = calculateActorMargins(actors, maxMessageWidthPerActor); + conf.height = calculateActorMargins(actors, maxMessageWidthPerActor, boxes); svgDraw.insertComputerIcon(diagram); svgDraw.insertDatabaseIcon(diagram); svgDraw.insertClockIcon(diagram); - drawActors(diagram, actors, actorKeys, 0, conf, messages); + if (hasBoxes) { + bounds.bumpVerticalPos(conf.boxMargin); + if (hasBoxTitles) { + bounds.bumpVerticalPos(boxes[0].textMaxHeight); + } + } + + drawActors(diagram, actors, actorKeys, 0, conf, messages, false); const loopWidths = calculateLoopBounds(messages, actors, maxMessageWidthPerActor, diagObj); // The arrow head definition is attached to the svg once @@ -847,11 +892,26 @@ export const draw = function (_text: string, id: string, _version: string, diagO if (conf.mirrorActors) { // Draw actors below diagram bounds.bumpVerticalPos(conf.boxMargin * 2); - drawActors(diagram, actors, actorKeys, bounds.getVerticalPos(), conf, messages); + drawActors(diagram, actors, actorKeys, bounds.getVerticalPos(), conf, messages, true); bounds.bumpVerticalPos(conf.boxMargin); fixLifeLineHeights(diagram, bounds.getVerticalPos()); } + bounds.models.boxes.forEach(function (box) { + box.height = bounds.getVerticalPos() - box.y; + bounds.insert(box.x, box.y, box.x + box.width, box.height); + box.startx = box.x; + box.starty = box.y; + box.stopx = box.startx + box.width; + box.stopy = box.starty + box.height; + box.stroke = 'rgb(0,0,0, 0.5)'; + svgDraw.drawBox(diagram, box, conf); + }); + + if (hasBoxes) { + bounds.bumpVerticalPos(conf.boxMargin); + } + // only draw popups for the top row of actors. const requiredBoxSize = drawActorsPopup(diagram, actors, actorKeys, doc); @@ -1039,10 +1099,12 @@ const getRequiredPopupWidth = function (actor) { * * @param actors - The actors map to calculate margins for * @param actorToMessageWidth - A map of actor key → max message width it holds + * @param boxes - The boxes around the actors if any */ function calculateActorMargins( actors: { [id: string]: any }, - actorToMessageWidth: ReturnType + actorToMessageWidth: ReturnType, + boxes ) { let maxHeight = 0; Object.keys(actors).forEach((prop) => { @@ -1074,6 +1136,9 @@ function calculateActorMargins( // No need to space out an actor that doesn't have a next link if (!nextActor) { + const messageWidth = actorToMessageWidth[actorKey]; + const actorWidth = messageWidth + conf.actorMargin - actor.width / 2; + actor.margin = Math.max(actorWidth, conf.actorMargin); continue; } @@ -1083,6 +1148,29 @@ function calculateActorMargins( actor.margin = Math.max(actorWidth, conf.actorMargin); } + let maxBoxHeight = 0; + boxes.forEach((box) => { + const textFont = messageFont(conf); + let totalWidth = box.actorKeys.reduce((total, aKey) => { + return (total += actors[aKey].width + (actors[aKey].margin || 0)); + }, 0); + + totalWidth -= 2 * conf.boxTextMargin; + if (box.wrap) { + box.name = utils.wrapLabel(box.name, totalWidth - 2 * conf.wrapPadding, textFont); + } + + const boxMsgDimensions = utils.calculateTextDimensions(box.name, textFont); + maxBoxHeight = Math.max(boxMsgDimensions.height, maxBoxHeight); + const minWidth = Math.max(totalWidth, boxMsgDimensions.width + 2 * conf.wrapPadding); + box.margin = conf.boxTextMargin; + if (totalWidth < minWidth) { + const missing = (minWidth - totalWidth) / 2; + box.margin += missing; + } + }); + boxes.forEach((box) => (box.textMaxHeight = maxBoxHeight)); + return Math.max(maxHeight, conf.height); } diff --git a/packages/mermaid/src/diagrams/sequence/svgDraw.js b/packages/mermaid/src/diagrams/sequence/svgDraw.js index 220db2209..be34daf4b 100644 --- a/packages/mermaid/src/diagrams/sequence/svgDraw.js +++ b/packages/mermaid/src/diagrams/sequence/svgDraw.js @@ -341,19 +341,21 @@ export const fixLifeLineHeights = (diagram, bounds) => { * @param {any} elem - The diagram we'll draw to. * @param {any} actor - The actor to draw. * @param {any} conf - DrawText implementation discriminator object + * @param {boolean} isFooter - If the actor is the footer one */ -const drawActorTypeParticipant = function (elem, actor, conf) { +const drawActorTypeParticipant = function (elem, actor, conf, isFooter) { const center = actor.x + actor.width / 2; + const centerY = actor.y + 5; const boxpluslineGroup = elem.append('g'); var g = boxpluslineGroup; - if (actor.y === 0) { + if (!isFooter) { actorCnt++; g.append('line') .attr('id', 'actor' + actorCnt) .attr('x1', center) - .attr('y1', 5) + .attr('y1', centerY) .attr('x2', center) .attr('y2', 2000) .attr('class', 'actor-line') @@ -416,16 +418,17 @@ const drawActorTypeParticipant = function (elem, actor, conf) { return height; }; -const drawActorTypeActor = function (elem, actor, conf) { +const drawActorTypeActor = function (elem, actor, conf, isFooter) { const center = actor.x + actor.width / 2; + const centerY = actor.y + 80; - if (actor.y === 0) { + if (!isFooter) { actorCnt++; elem .append('line') .attr('id', 'actor' + actorCnt) .attr('x1', center) - .attr('y1', 80) + .attr('y1', centerY) .attr('x2', center) .attr('y2', 2000) .attr('class', 'actor-line') @@ -498,15 +501,34 @@ const drawActorTypeActor = function (elem, actor, conf) { return actor.height; }; -export const drawActor = function (elem, actor, conf) { +export const drawActor = function (elem, actor, conf, isFooter) { switch (actor.type) { case 'actor': - return drawActorTypeActor(elem, actor, conf); + return drawActorTypeActor(elem, actor, conf, isFooter); case 'participant': - return drawActorTypeParticipant(elem, actor, conf); + return drawActorTypeParticipant(elem, actor, conf, isFooter); } }; +export const drawBox = function (elem, box, conf) { + const boxplustextGroup = elem.append('g'); + const g = boxplustextGroup; + drawBackgroundRect(g, box); + if (box.name) { + _drawTextCandidateFunc(conf)( + box.name, + g, + box.x, + box.y + (box.textMaxHeight || 0) / 2, + box.width, + 0, + { class: 'text' }, + conf + ); + } + g.lower(); +}; + export const anchorElement = function (elem) { return elem.append('g'); }; @@ -645,6 +667,7 @@ export const drawBackgroundRect = function (elem, bounds) { width: bounds.stopx - bounds.startx, height: bounds.stopy - bounds.starty, fill: bounds.fill, + stroke: bounds.stroke, class: 'rect', }); rectElem.lower(); @@ -1037,6 +1060,7 @@ export default { drawText, drawLabel, drawActor, + drawBox, drawPopup, drawImage, drawEmbeddedImage, diff --git a/packages/mermaid/src/diagrams/timeline/detector.ts b/packages/mermaid/src/diagrams/timeline/detector.ts new file mode 100644 index 000000000..9d06d6438 --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/detector.ts @@ -0,0 +1,20 @@ +import type { ExternalDiagramDefinition } from '../../diagram-api/types'; + +const id = 'timeline'; + +const detector = (txt: string) => { + return txt.match(/^\s*timeline/) !== null; +}; + +const loader = async () => { + const { diagram } = await import('./diagram-definition'); + return { id, diagram }; +}; + +const plugin: ExternalDiagramDefinition = { + id, + detector, + loader, +}; + +export default plugin; diff --git a/packages/mermaid/src/diagrams/timeline/diagram-definition.ts b/packages/mermaid/src/diagrams/timeline/diagram-definition.ts new file mode 100644 index 000000000..898af8b78 --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/diagram-definition.ts @@ -0,0 +1,12 @@ +// @ts-ignore: TODO Fix ts errors +import parser from './parser/timeline.jison'; +import * as db from './timelineDb'; +import renderer from './timelineRenderer'; +import styles from './styles'; + +export const diagram = { + db, + renderer, + parser, + styles, +}; diff --git a/packages/mermaid/src/diagrams/timeline/parser/timeline.jison b/packages/mermaid/src/diagrams/timeline/parser/timeline.jison new file mode 100644 index 000000000..59b96516a --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/parser/timeline.jison @@ -0,0 +1,112 @@ +/** mermaid + * https://mermaidjs.github.io/ + * (c) 2023 Knut Sveidqvist + * MIT license. + */ +%lex +%options case-insensitive +%x acc_title +%x acc_descr +%x acc_descr_multiline + +// Directive states +%x open_directive type_directive arg_directive + + +%% + +\%\%\{ { this.begin('open_directive'); return 'open_directive'; } +((?:(?!\}\%\%)[^:.])*) { this.begin('type_directive'); return 'type_directive'; } +":" { this.popState(); this.begin('arg_directive'); return ':'; } +\}\%\% { this.popState(); this.popState(); return 'close_directive'; } +((?:(?!\}\%\%).|\n)*) return 'arg_directive'; +\%%(?!\{)[^\n]* /* skip comments */ +[^\}]\%\%[^\n]* /* skip comments */ +[\n]+ return 'NEWLINE'; +\s+ /* skip whitespace */ +\#[^\n]* /* skip comments */ + +"timeline" return 'timeline'; +"title"\s[^#\n;]+ return 'title'; +accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; } +accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; } +(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; } +accDescr\s*"{"\s* { this.begin("acc_descr_multiline");} +[\}] { this.popState(); } +[^\}]* return "acc_descr_multiline_value"; +"section"\s[^#:\n;]+ return 'section'; + +// event starting with "==>" keyword +":"\s[^#:\n;]+ return 'event'; +[^#:\n;]+ return 'period'; + + +<> return 'EOF'; +. return 'INVALID'; + +/lex + +%left '^' + +%start start + +%% /* language grammar */ + +start + : timeline document 'EOF' { return $2; } + | directive start + ; + +document + : /* empty */ { $$ = [] } + | document line {$1.push($2);$$ = $1} + ; + +line + : SPACE statement { $$ = $2 } + | statement { $$ = $1 } + | NEWLINE { $$=[];} + | EOF { $$=[];} + ; + +directive + : openDirective typeDirective closeDirective 'NEWLINE' + | openDirective typeDirective ':' argDirective closeDirective 'NEWLINE' + ; + +statement + : title {yy.getCommonDb().setDiagramTitle($1.substr(6));$$=$1.substr(6);} + | acc_title acc_title_value { $$=$2.trim();yy.getCommonDb().setAccTitle($$); } + | acc_descr acc_descr_value { $$=$2.trim();yy.getCommonDb().setAccDescription($$); } + | acc_descr_multiline_value { $$=$1.trim();yy.getCommonDb().setAccDescription($$); } + | section {yy.addSection($1.substr(8));$$=$1.substr(8);} + | period_statement + | event_statement + | directive + ; +period_statement + : period {yy.addTask($1,0,'');$$=$1;} +; +event_statement + : event {yy.addEvent($1.substr(2));$$=$1;} +; + + +openDirective + : open_directive { yy.parseDirective('%%{', 'open_directive'); } + ; + +typeDirective + : type_directive { yy.parseDirective($1, 'type_directive'); } + ; + +argDirective + : arg_directive { $1 = $1.trim().replace(/'/g, '"'); yy.parseDirective($1, 'arg_directive'); } + ; + +closeDirective + : close_directive { yy.parseDirective('}%%', 'close_directive', 'timeline'); } + ; + +%% diff --git a/packages/mermaid/src/diagrams/timeline/styles.js b/packages/mermaid/src/diagrams/timeline/styles.js new file mode 100644 index 000000000..fd9526e98 --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/styles.js @@ -0,0 +1,81 @@ +import { darken, lighten, isDark } from 'khroma'; + +const genSections = (options) => { + let sections = ''; + + for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) { + options['lineColor' + i] = options['lineColor' + i] || options['cScaleInv' + i]; + if (isDark(options['lineColor' + i])) { + options['lineColor' + i] = lighten(options['lineColor' + i], 20); + } else { + options['lineColor' + i] = darken(options['lineColor' + i], 20); + } + } + + for (let i = 0; i < options.THEME_COLOR_LIMIT; i++) { + const sw = '' + (17 - 3 * i); + sections += ` + .section-${i - 1} rect, .section-${i - 1} path, .section-${i - 1} circle, .section-${ + i - 1 + } path { + fill: ${options['cScale' + i]}; + } + .section-${i - 1} text { + fill: ${options['cScaleLabel' + i]}; + } + .node-icon-${i - 1} { + font-size: 40px; + color: ${options['cScaleLabel' + i]}; + } + .section-edge-${i - 1}{ + stroke: ${options['cScale' + i]}; + } + .edge-depth-${i - 1}{ + stroke-width: ${sw}; + } + .section-${i - 1} line { + stroke: ${options['cScaleInv' + i]} ; + stroke-width: 3; + } + + .lineWrapper line{ + stroke: ${options['cScaleLabel' + i]} ; + } + + .disabled, .disabled circle, .disabled text { + fill: lightgray; + } + .disabled text { + fill: #efefef; + } + `; + } + return sections; +}; + +const getStyles = (options) => + ` + .edge { + stroke-width: 3; + } + ${genSections(options)} + .section-root rect, .section-root path, .section-root circle { + fill: ${options.git0}; + } + .section-root text { + fill: ${options.gitBranchLabel0}; + } + .icon-container { + height:100%; + display: flex; + justify-content: center; + align-items: center; + } + .edge { + fill: none; + } + .eventWrapper { + filter: brightness(120%); + } +`; +export default getStyles; diff --git a/packages/mermaid/src/diagrams/timeline/svgDraw.js b/packages/mermaid/src/diagrams/timeline/svgDraw.js new file mode 100644 index 000000000..874ac62ef --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/svgDraw.js @@ -0,0 +1,602 @@ +import { arc as d3arc, select } from 'd3'; +const MAX_SECTIONS = 12; + +export const drawRect = function (elem, rectData) { + const rectElem = elem.append('rect'); + rectElem.attr('x', rectData.x); + rectElem.attr('y', rectData.y); + rectElem.attr('fill', rectData.fill); + rectElem.attr('stroke', rectData.stroke); + rectElem.attr('width', rectData.width); + rectElem.attr('height', rectData.height); + rectElem.attr('rx', rectData.rx); + rectElem.attr('ry', rectData.ry); + + if (rectData.class !== undefined) { + rectElem.attr('class', rectData.class); + } + + return rectElem; +}; + +export const drawFace = function (element, faceData) { + const radius = 15; + const circleElement = element + .append('circle') + .attr('cx', faceData.cx) + .attr('cy', faceData.cy) + .attr('class', 'face') + .attr('r', radius) + .attr('stroke-width', 2) + .attr('overflow', 'visible'); + + const face = element.append('g'); + + //left eye + face + .append('circle') + .attr('cx', faceData.cx - radius / 3) + .attr('cy', faceData.cy - radius / 3) + .attr('r', 1.5) + .attr('stroke-width', 2) + .attr('fill', '#666') + .attr('stroke', '#666'); + + //right eye + face + .append('circle') + .attr('cx', faceData.cx + radius / 3) + .attr('cy', faceData.cy - radius / 3) + .attr('r', 1.5) + .attr('stroke-width', 2) + .attr('fill', '#666') + .attr('stroke', '#666'); + + /** @param {any} face */ + function smile(face) { + const arc = d3arc() + .startAngle(Math.PI / 2) + .endAngle(3 * (Math.PI / 2)) + .innerRadius(radius / 2) + .outerRadius(radius / 2.2); + //mouth + face + .append('path') + .attr('class', 'mouth') + .attr('d', arc) + .attr('transform', 'translate(' + faceData.cx + ',' + (faceData.cy + 2) + ')'); + } + + /** @param {any} face */ + function sad(face) { + const arc = d3arc() + .startAngle((3 * Math.PI) / 2) + .endAngle(5 * (Math.PI / 2)) + .innerRadius(radius / 2) + .outerRadius(radius / 2.2); + //mouth + face + .append('path') + .attr('class', 'mouth') + .attr('d', arc) + .attr('transform', 'translate(' + faceData.cx + ',' + (faceData.cy + 7) + ')'); + } + + /** @param {any} face */ + function ambivalent(face) { + face + .append('line') + .attr('class', 'mouth') + .attr('stroke', 2) + .attr('x1', faceData.cx - 5) + .attr('y1', faceData.cy + 7) + .attr('x2', faceData.cx + 5) + .attr('y2', faceData.cy + 7) + .attr('class', 'mouth') + .attr('stroke-width', '1px') + .attr('stroke', '#666'); + } + + if (faceData.score > 3) { + smile(face); + } else if (faceData.score < 3) { + sad(face); + } else { + ambivalent(face); + } + + return circleElement; +}; + +export const drawCircle = function (element, circleData) { + const circleElement = element.append('circle'); + circleElement.attr('cx', circleData.cx); + circleElement.attr('cy', circleData.cy); + circleElement.attr('class', 'actor-' + circleData.pos); + circleElement.attr('fill', circleData.fill); + circleElement.attr('stroke', circleData.stroke); + circleElement.attr('r', circleData.r); + + if (circleElement.class !== undefined) { + circleElement.attr('class', circleElement.class); + } + + if (circleData.title !== undefined) { + circleElement.append('title').text(circleData.title); + } + + return circleElement; +}; + +export const drawText = function (elem, textData) { + // Remove and ignore br:s + const nText = textData.text.replace(//gi, ' '); + + const textElem = elem.append('text'); + textElem.attr('x', textData.x); + textElem.attr('y', textData.y); + textElem.attr('class', 'legend'); + + textElem.style('text-anchor', textData.anchor); + + if (textData.class !== undefined) { + textElem.attr('class', textData.class); + } + + const span = textElem.append('tspan'); + span.attr('x', textData.x + textData.textMargin * 2); + span.text(nText); + + return textElem; +}; + +export const drawLabel = function (elem, txtObject) { + /** + * @param {any} x + * @param {any} y + * @param {any} width + * @param {any} height + * @param {any} cut + */ + function genPoints(x, y, width, height, cut) { + return ( + x + + ',' + + y + + ' ' + + (x + width) + + ',' + + y + + ' ' + + (x + width) + + ',' + + (y + height - cut) + + ' ' + + (x + width - cut * 1.2) + + ',' + + (y + height) + + ' ' + + x + + ',' + + (y + height) + ); + } + const polygon = elem.append('polygon'); + polygon.attr('points', genPoints(txtObject.x, txtObject.y, 50, 20, 7)); + polygon.attr('class', 'labelBox'); + + txtObject.y = txtObject.y + txtObject.labelMargin; + txtObject.x = txtObject.x + 0.5 * txtObject.labelMargin; + drawText(elem, txtObject); +}; + +export const drawSection = function (elem, section, conf) { + const g = elem.append('g'); + + const rect = getNoteRect(); + rect.x = section.x; + rect.y = section.y; + rect.fill = section.fill; + rect.width = conf.width; + rect.height = conf.height; + rect.class = 'journey-section section-type-' + section.num; + rect.rx = 3; + rect.ry = 3; + drawRect(g, rect); + + _drawTextCandidateFunc(conf)( + section.text, + g, + rect.x, + rect.y, + rect.width, + rect.height, + { class: 'journey-section section-type-' + section.num }, + conf, + section.colour + ); +}; + +let taskCount = -1; +/** + * Draws an actor in the diagram with the attached line + * + * @param {any} elem The HTML element + * @param {any} task The task to render + * @param {any} conf The global configuration + */ +export const drawTask = function (elem, task, conf) { + const center = task.x + conf.width / 2; + const g = elem.append('g'); + taskCount++; + const maxHeight = 300 + 5 * 30; + g.append('line') + .attr('id', 'task' + taskCount) + .attr('x1', center) + .attr('y1', task.y) + .attr('x2', center) + .attr('y2', maxHeight) + .attr('class', 'task-line') + .attr('stroke-width', '1px') + .attr('stroke-dasharray', '4 2') + .attr('stroke', '#666'); + + drawFace(g, { + cx: center, + cy: 300 + (5 - task.score) * 30, + score: task.score, + }); + + const rect = getNoteRect(); + rect.x = task.x; + rect.y = task.y; + rect.fill = task.fill; + rect.width = conf.width; + rect.height = conf.height; + rect.class = 'task task-type-' + task.num; + rect.rx = 3; + rect.ry = 3; + drawRect(g, rect); + + let xPos = task.x + 14; + // task.people.forEach((person) => { + // const colour = task.actors[person].color; + + // const circle = { + // cx: xPos, + // cy: task.y, + // r: 7, + // fill: colour, + // stroke: '#000', + // title: person, + // pos: task.actors[person].position, + // }; + + // drawCircle(g, circle); + // xPos += 10; + // }); + + _drawTextCandidateFunc(conf)( + task.task, + g, + rect.x, + rect.y, + rect.width, + rect.height, + { class: 'task' }, + conf, + task.colour + ); +}; + +/** + * Draws a background rectangle + * + * @param {any} elem The html element + * @param {any} bounds The bounds of the drawing + */ +export const drawBackgroundRect = function (elem, bounds) { + const rectElem = drawRect(elem, { + x: bounds.startx, + y: bounds.starty, + width: bounds.stopx - bounds.startx, + height: bounds.stopy - bounds.starty, + fill: bounds.fill, + class: 'rect', + }); + rectElem.lower(); +}; + +export const getTextObj = function () { + return { + x: 0, + y: 0, + fill: undefined, + 'text-anchor': 'start', + width: 100, + height: 100, + textMargin: 0, + rx: 0, + ry: 0, + }; +}; + +export const getNoteRect = function () { + return { + x: 0, + y: 0, + width: 100, + anchor: 'start', + height: 100, + rx: 0, + ry: 0, + }; +}; + +const _drawTextCandidateFunc = (function () { + /** + * @param {any} content + * @param {any} g + * @param {any} x + * @param {any} y + * @param {any} width + * @param {any} height + * @param {any} textAttrs + * @param {any} colour + */ + function byText(content, g, x, y, width, height, textAttrs, colour) { + const text = g + .append('text') + .attr('x', x + width / 2) + .attr('y', y + height / 2 + 5) + .style('font-color', colour) + .style('text-anchor', 'middle') + .text(content); + _setTextAttrs(text, textAttrs); + } + + /** + * @param {any} content + * @param {any} g + * @param {any} x + * @param {any} y + * @param {any} width + * @param {any} height + * @param {any} textAttrs + * @param {any} conf + * @param {any} colour + */ + function byTspan(content, g, x, y, width, height, textAttrs, conf, colour) { + const { taskFontSize, taskFontFamily } = conf; + + const lines = content.split(//gi); + for (let i = 0; i < lines.length; i++) { + const dy = i * taskFontSize - (taskFontSize * (lines.length - 1)) / 2; + const text = g + .append('text') + .attr('x', x + width / 2) + .attr('y', y) + .attr('fill', colour) + .style('text-anchor', 'middle') + .style('font-size', taskFontSize) + .style('font-family', taskFontFamily); + text + .append('tspan') + .attr('x', x + width / 2) + .attr('dy', dy) + .text(lines[i]); + + text + .attr('y', y + height / 2.0) + .attr('dominant-baseline', 'central') + .attr('alignment-baseline', 'central'); + + _setTextAttrs(text, textAttrs); + } + } + + /** + * @param {any} content + * @param {any} g + * @param {any} x + * @param {any} y + * @param {any} width + * @param {any} height + * @param {any} textAttrs + * @param {any} conf + */ + function byFo(content, g, x, y, width, height, textAttrs, conf) { + const body = g.append('switch'); + const f = body + .append('foreignObject') + .attr('x', x) + .attr('y', y) + .attr('width', width) + .attr('height', height) + .attr('position', 'fixed'); + + const text = f + .append('xhtml:div') + .style('display', 'table') + .style('height', '100%') + .style('width', '100%'); + + text + .append('div') + .attr('class', 'label') + .style('display', 'table-cell') + .style('text-align', 'center') + .style('vertical-align', 'middle') + .text(content); + + byTspan(content, body, x, y, width, height, textAttrs, conf); + _setTextAttrs(text, textAttrs); + } + + /** + * @param {any} toText + * @param {any} fromTextAttrsDict + */ + function _setTextAttrs(toText, fromTextAttrsDict) { + for (const key in fromTextAttrsDict) { + if (key in fromTextAttrsDict) { + // noinspection JSUnfilteredForInLoop + toText.attr(key, fromTextAttrsDict[key]); + } + } + } + + return function (conf) { + return conf.textPlacement === 'fo' ? byFo : conf.textPlacement === 'old' ? byText : byTspan; + }; +})(); + +const initGraphics = function (graphics) { + graphics + .append('defs') + .append('marker') + .attr('id', 'arrowhead') + .attr('refX', 5) + .attr('refY', 2) + .attr('markerWidth', 6) + .attr('markerHeight', 4) + .attr('orient', 'auto') + .append('path') + .attr('d', 'M 0,0 V 4 L6,2 Z'); // this is actual shape for arrowhead +}; + +/** + * @param {string} text The text to be wrapped + * @param {number} width The max width of the text + */ +function wrap(text, width) { + text.each(function () { + var text = select(this), + words = text + .text() + .split(/(\s+|
)/) + .reverse(), + word, + line = [], + lineHeight = 1.1, // ems + y = text.attr('y'), + dy = parseFloat(text.attr('dy')), + tspan = text + .text(null) + .append('tspan') + .attr('x', 0) + .attr('y', y) + .attr('dy', dy + 'em'); + for (let j = 0; j < words.length; j++) { + word = words[words.length - 1 - j]; + line.push(word); + tspan.text(line.join(' ').trim()); + if (tspan.node().getComputedTextLength() > width || word === '
') { + line.pop(); + tspan.text(line.join(' ').trim()); + if (word === '
') { + line = ['']; + } else { + line = [word]; + } + + tspan = text + .append('tspan') + .attr('x', 0) + .attr('y', y) + .attr('dy', lineHeight + 'em') + .text(word); + } + } + }); +} + +export const drawNode = function (elem, node, fullSection, conf) { + const section = (fullSection % MAX_SECTIONS) - 1; + const nodeElem = elem.append('g'); + node.section = section; + nodeElem.attr( + 'class', + (node.class ? node.class + ' ' : '') + 'timeline-node ' + ('section-' + section) + ); + const bkgElem = nodeElem.append('g'); + + // Create the wrapped text element + const textElem = nodeElem.append('g'); + + const txt = textElem + .append('text') + .text(node.descr) + .attr('dy', '1em') + .attr('alignment-baseline', 'middle') + .attr('dominant-baseline', 'middle') + .attr('text-anchor', 'middle') + .call(wrap, node.width); + const bbox = txt.node().getBBox(); + const fontSize = + conf.fontSize && conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize; + node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding; + node.height = Math.max(node.height, node.maxHeight); + node.width = node.width + 2 * node.padding; + + textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')'); + + // Create the background element + defaultBkg(bkgElem, node, section, conf); + + return node; +}; + +export const getVirtualNodeHeight = function (elem, node, conf) { + const textElem = elem.append('g'); + const txt = textElem + .append('text') + .text(node.descr) + .attr('dy', '1em') + .attr('alignment-baseline', 'middle') + .attr('dominant-baseline', 'middle') + .attr('text-anchor', 'middle') + .call(wrap, node.width); + const bbox = txt.node().getBBox(); + const fontSize = + conf.fontSize && conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize; + textElem.remove(); + return bbox.height + fontSize * 1.1 * 0.5 + node.padding; +}; + +const defaultBkg = function (elem, node, section) { + const rd = 5; + elem + .append('path') + .attr('id', 'node-' + node.id) + .attr('class', 'node-bkg node-' + node.type) + .attr( + 'd', + `M0 ${node.height - rd} v${-node.height + 2 * rd} q0,-5 5,-5 h${ + node.width - 2 * rd + } q5,0 5,5 v${node.height - rd} H0 Z` + ); + + elem + .append('line') + .attr('class', 'node-line-' + section) + .attr('x1', 0) + .attr('y1', node.height) + .attr('x2', node.width) + .attr('y2', node.height); +}; + +export default { + drawRect, + drawCircle, + drawSection, + drawText, + drawLabel, + drawTask, + drawBackgroundRect, + getTextObj, + getNoteRect, + initGraphics, + drawNode, + getVirtualNodeHeight, +}; diff --git a/packages/mermaid/src/diagrams/timeline/timeline.spec.js b/packages/mermaid/src/diagrams/timeline/timeline.spec.js new file mode 100644 index 000000000..0697b194e --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/timeline.spec.js @@ -0,0 +1,122 @@ +import { parser as timeline } from './parser/timeline'; +import * as timelineDB from './timelineDb'; +// import { injectUtils } from './mermaidUtils'; +import * as _commonDb from '../../commonDb'; +import { parseDirective as _parseDirective } from '../../directiveUtils'; + +import { + log, + setLogLevel, + getConfig, + sanitizeText, + setupGraphViewBox, +} from '../../diagram-api/diagramAPI'; + +// injectUtils( +// log, +// setLogLevel, +// getConfig, +// sanitizeText, +// setupGraphViewBox, +// _commonDb, +// _parseDirective +// ); + +describe('when parsing a timeline ', function () { + beforeEach(function () { + timeline.yy = timelineDB; + timelineDB.clear(); + setLogLevel('trace'); + }); + describe('Timeline', function () { + it('TL-1 should handle a simple section definition abc-123', function () { + let str = `timeline + section abc-123`; + + timeline.parse(str); + expect(timelineDB.getSections()).to.deep.equal(['abc-123']); + }); + + it('TL-2 should handle a simple section and only two tasks', function () { + let str = `timeline + section abc-123 + task1 + task2`; + timeline.parse(str); + timelineDB.getTasks().forEach((task) => { + expect(task.section).to.equal('abc-123'); + expect(task.task).to.be.oneOf(['task1', 'task2']); + }); + }); + + it('TL-3 should handle a two section and two coressponding tasks', function () { + let str = `timeline + section abc-123 + task1 + task2 + section abc-456 + task3 + task4`; + timeline.parse(str); + expect(timelineDB.getSections()).to.deep.equal(['abc-123', 'abc-456']); + timelineDB.getTasks().forEach((task) => { + expect(task.section).to.be.oneOf(['abc-123', 'abc-456']); + expect(task.task).to.be.oneOf(['task1', 'task2', 'task3', 'task4']); + if (task.section === 'abc-123') { + expect(task.task).to.be.oneOf(['task1', 'task2']); + } else { + expect(task.task).to.be.oneOf(['task3', 'task4']); + } + }); + }); + + it('TL-4 should handle a section, and task and its events', function () { + let str = `timeline + section abc-123 + task1: event1 + task2: event2: event3 + `; + timeline.parse(str); + expect(timelineDB.getSections()[0]).to.deep.equal('abc-123'); + timelineDB.getTasks().forEach((t) => { + switch (t.task.trim()) { + case 'task1': + expect(t.events).to.deep.equal(['event1']); + break; + + case 'task2': + expect(t.events).to.deep.equal(['event2', 'event3']); + break; + + default: + break; + } + }); + }); + + it('TL-5 should handle a section, and task and its multi line events', function () { + let str = `timeline + section abc-123 + task1: event1 + task2: event2: event3 + : event4: event5 + `; + timeline.parse(str); + expect(timelineDB.getSections()[0]).to.deep.equal('abc-123'); + timelineDB.getTasks().forEach((t) => { + switch (t.task.trim()) { + case 'task1': + expect(t.events).to.deep.equal(['event1']); + break; + + case 'task2': + expect(t.events).to.deep.equal(['event2', 'event3', 'event4', 'event5']); + break; + + default: + break; + } + }); + }); + }); +}); diff --git a/packages/mermaid/src/diagrams/timeline/timelineDb.js b/packages/mermaid/src/diagrams/timeline/timelineDb.js new file mode 100644 index 000000000..7bc5c2692 --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/timelineDb.js @@ -0,0 +1,108 @@ +import { parseDirective as _parseDirective } from '../../directiveUtils'; +import * as commonDb from '../../commonDb'; +let currentSection = ''; +let currentTaskId = 0; + +const sections = []; +const tasks = []; +const rawTasks = []; + +export const getCommonDb = () => commonDb; + +export const parseDirective = (statement, context, type) => { + _parseDirective(this, statement, context, type); +}; + +export const clear = function () { + sections.length = 0; + tasks.length = 0; + currentSection = ''; + rawTasks.length = 0; + commonDb.clear(); +}; + +export const addSection = function (txt) { + currentSection = txt; + sections.push(txt); +}; + +export const getSections = function () { + return sections; +}; + +export const getTasks = function () { + let allItemsProcessed = compileTasks(); + const maxDepth = 100; + let iterationCount = 0; + while (!allItemsProcessed && iterationCount < maxDepth) { + allItemsProcessed = compileTasks(); + iterationCount++; + } + + tasks.push(...rawTasks); + + return tasks; +}; + +export const addTask = function (period, length, event) { + const rawTask = { + id: currentTaskId++, + section: currentSection, + type: currentSection, + task: period, + score: length ? length : 0, + //if event is defined, then add it the events array + events: event ? [event] : [], + }; + rawTasks.push(rawTask); +}; + +export const addEvent = function (event) { + // fetch current task with currnetTaskId + const currentTask = rawTasks.find((task) => task.id === currentTaskId - 1); + //add event to the events array + currentTask.events.push(event); +}; + +export const addTaskOrg = function (descr) { + const newTask = { + section: currentSection, + type: currentSection, + description: descr, + task: descr, + classes: [], + }; + tasks.push(newTask); +}; + +/** + * Compiles the raw tasks into a list of tasks with events + * @returns {boolean} true if all items are processed + * @private + * @memberof timelineDb + */ +const compileTasks = function () { + const compileTask = function (pos) { + return rawTasks[pos].processed; + }; + + let allProcessed = true; + for (const [i, rawTask] of rawTasks.entries()) { + compileTask(i); + + allProcessed = allProcessed && rawTask.processed; + } + return allProcessed; +}; + +export default { + clear, + getCommonDb, + addSection, + getSections, + getTasks, + addTask, + addTaskOrg, + addEvent, + parseDirective, +}; diff --git a/packages/mermaid/src/diagrams/timeline/timelineRenderer.ts b/packages/mermaid/src/diagrams/timeline/timelineRenderer.ts new file mode 100644 index 000000000..02e706bf6 --- /dev/null +++ b/packages/mermaid/src/diagrams/timeline/timelineRenderer.ts @@ -0,0 +1,336 @@ +// @ts-nocheck TODO: fix file +import { select } from 'd3'; +import svgDraw from './svgDraw'; +import { log } from '../../logger'; +import { getConfig } from '../../config'; +import { setupGraphViewbox } from '../../setupGraphViewbox'; + +export const setConf = function (cnf) { + const keys = Object.keys(cnf); + + keys.forEach(function (key) { + conf[key] = cnf[key]; + }); +}; + +export const draw = function (text, id, version, diagObj) { + //1. Fetch the configuration + const conf = getConfig(); + const LEFT_MARGIN = conf.leftMargin ? conf.leftMargin : 50; + + //2. Clear the diagram db before parsing + diagObj.db.clear(); + + //3. Parse the diagram text + diagObj.parser.parse(text + '\n'); + + log.debug('timeline', diagObj.db); + + const securityLevel = conf.securityLevel; + // Handle root and Document for when rendering in sandbox mode + let sandboxElement; + if (securityLevel === 'sandbox') { + sandboxElement = select('#i' + id); + } + const root = + securityLevel === 'sandbox' + ? select(sandboxElement.nodes()[0].contentDocument.body) + : select('body'); + + const svg = root.select('#' + id); + + svg.append('g'); + + //4. Fetch the diagram data + const tasks = diagObj.db.getTasks(); + const title = diagObj.db.getCommonDb().getDiagramTitle(); + log.debug('task', tasks); + + //5. Initialize the diagram + svgDraw.initGraphics(svg); + + // fetch Sections + const sections = diagObj.db.getSections(); + log.debug('sections', sections); + + let maxSectionHeight = 0; + let maxTaskHeight = 0; + //let sectionBeginX = 0; + let depthY = 0; + let sectionBeginY = 0; + let masterX = 50 + LEFT_MARGIN; + //sectionBeginX = masterX; + let masterY = 50; + sectionBeginY = 50; + //draw sections + let sectionNumber = 0; + let hasSections = true; + + //Calculate the max height of the sections + sections.forEach(function (section) { + const sectionNode = { + number: sectionNumber, + descr: section, + section: sectionNumber, + width: 150, + padding: 20, + maxHeight: maxSectionHeight, + }; + const sectionHeight = svgDraw.getVirtualNodeHeight(svg, sectionNode, conf); + log.debug('sectionHeight before draw', sectionHeight); + maxSectionHeight = Math.max(maxSectionHeight, sectionHeight + 20); + }); + + //tasks length and maxEventCount + let maxEventCount = 0; + let maxEventLineLength = 0; + log.debug('tasks.length', tasks.length); + //calculate max task height + // for loop till tasks.length + for (const [i, task] of tasks.entries()) { + const taskNode = { + number: i, + descr: task, + section: task.section, + width: 150, + padding: 20, + maxHeight: maxTaskHeight, + }; + const taskHeight = svgDraw.getVirtualNodeHeight(svg, taskNode, conf); + log.debug('taskHeight before draw', taskHeight); + maxTaskHeight = Math.max(maxTaskHeight, taskHeight + 20); + + //calculate maxEventCount + maxEventCount = Math.max(maxEventCount, task.events.length); + //calculate maxEventLineLength + let maxEventLineLengthTemp = 0; + for (let j = 0; j < task.events.length; j++) { + const event = task.events[j]; + const eventNode = { + descr: event, + section: task.section, + number: task.section, + width: 150, + padding: 20, + maxHeight: 50, + }; + maxEventLineLengthTemp += svgDraw.getVirtualNodeHeight(svg, eventNode, conf); + } + maxEventLineLength = Math.max(maxEventLineLength, maxEventLineLengthTemp); + } + + log.debug('maxSectionHeight before draw', maxSectionHeight); + log.debug('maxTaskHeight before draw', maxTaskHeight); + + if (sections && sections.length > 0) { + sections.forEach((section) => { + const sectionNode = { + number: sectionNumber, + descr: section, + section: sectionNumber, + width: 150, + padding: 20, + maxHeight: maxSectionHeight, + }; + log.debug('sectionNode', sectionNode); + const sectionNodeWrapper = svg.append('g'); + const node = svgDraw.drawNode(sectionNodeWrapper, sectionNode, sectionNumber, conf); + log.debug('sectionNode output', node); + + sectionNodeWrapper.attr('transform', `translate(${masterX}, ${sectionBeginY})`); + + masterY += maxSectionHeight + 50; + + //draw tasks for this section + //filter task where tasks.section == section + const tasksForSection = tasks.filter((task) => task.section === section); + if (tasksForSection.length > 0) { + drawTasks( + svg, + tasksForSection, + sectionNumber, + masterX, + masterY, + maxTaskHeight, + conf, + maxEventCount, + maxEventLineLength, + maxSectionHeight, + false + ); + } + // todo replace with total width of section and its tasks + masterX += 200 * Math.max(tasksForSection.length, 1); + + masterY = sectionBeginY; + sectionNumber++; + }); + } else { + //draw tasks + hasSections = false; + drawTasks( + svg, + tasks, + sectionNumber, + masterX, + masterY, + maxTaskHeight, + conf, + maxEventCount, + maxEventLineLength, + maxSectionHeight, + true + ); + } + + // Get BBox of the diagram + const box = svg.node().getBBox(); + log.debug('bounds', box); + + if (title) { + svg + .append('text') + .text(title) + .attr('x', box.width / 2 - LEFT_MARGIN) + .attr('font-size', '4ex') + .attr('font-weight', 'bold') + .attr('y', 20); + } + //5. Draw the diagram + depthY = hasSections ? maxSectionHeight + maxTaskHeight + 150 : maxTaskHeight + 100; + + const lineWrapper = svg.append('g').attr('class', 'lineWrapper'); + // Draw activity line + lineWrapper + .append('line') + .attr('x1', LEFT_MARGIN) + .attr('y1', depthY) // One section head + one task + margins + .attr('x2', box.width + 3 * LEFT_MARGIN) // Subtract stroke width so arrow point is retained + .attr('y2', depthY) + .attr('stroke-width', 4) + .attr('stroke', 'black') + .attr('marker-end', 'url(#arrowhead)'); + + // Setup the view box and size of the svg element + setupGraphViewbox( + undefined, + svg, + conf.timeline.padding ? conf.timeline.padding : 50, + conf.timeline.useMaxWidth ? conf.timeline.useMaxWidth : false + ); + + // addSVGAccessibilityFields(diagObj.db, diagram, id); +}; + +export const drawTasks = function ( + diagram, + tasks, + sectionColor, + masterX, + masterY, + maxTaskHeight, + conf, + maxEventCount, + maxEventLineLength, + maxSectionHeight, + isWithoutSections +) { + // Draw the tasks + for (const task of tasks) { + // create node from task + const taskNode = { + descr: task.task, + section: sectionColor, + number: sectionColor, + width: 150, + padding: 20, + maxHeight: maxTaskHeight, + }; + + log.debug('taskNode', taskNode); + // create task wrapper + const taskWrapper = diagram.append('g').attr('class', 'taskWrapper'); + const node = svgDraw.drawNode(taskWrapper, taskNode, sectionColor, conf); + const taskHeight = node.height; + //log task height + log.debug('taskHeight after draw', taskHeight); + taskWrapper.attr('transform', `translate(${masterX}, ${masterY})`); + + // update max task height + maxTaskHeight = Math.max(maxTaskHeight, taskHeight); + + // if task has events, draw them + if (task.events) { + // draw a line between the task and the events + const lineWrapper = diagram.append('g').attr('class', 'lineWrapper'); + let linelength = maxTaskHeight; + //add margin to task + masterY += 100; + linelength = + linelength + drawEvents(diagram, task.events, sectionColor, masterX, masterY, conf); + masterY -= 100; + + lineWrapper + .append('line') + .attr('x1', masterX + 190 / 2) + .attr('y1', masterY + maxTaskHeight) // One section head + one task + margins + .attr('x2', masterX + 190 / 2) // Subtract stroke width so arrow point is retained + .attr( + 'y2', + masterY + + maxTaskHeight + + (isWithoutSections ? maxTaskHeight : maxSectionHeight) + + maxEventLineLength + + 120 + ) + .attr('stroke-width', 2) + .attr('stroke', 'black') + .attr('marker-end', 'url(#arrowhead)') + .attr('stroke-dasharray', '5,5'); + } + + masterX = masterX + 200; + if (isWithoutSections && !getConfig().timeline.disableMulticolor) { + sectionColor++; + } + } + + // reset Y coordinate for next section + masterY = masterY - 10; +}; + +export const drawEvents = function (diagram, events, sectionColor, masterX, masterY, conf) { + let maxEventHeight = 0; + const eventBeginY = masterY; + masterY = masterY + 100; + // Draw the events + for (const event of events) { + // create node from event + const eventNode = { + descr: event, + section: sectionColor, + number: sectionColor, + width: 150, + padding: 20, + maxHeight: 50, + }; + + //log task node + log.debug('eventNode', eventNode); + // create event wrapper + const eventWrapper = diagram.append('g').attr('class', 'eventWrapper'); + const node = svgDraw.drawNode(eventWrapper, eventNode, sectionColor, conf); + const eventHeight = node.height; + maxEventHeight = maxEventHeight + eventHeight; + eventWrapper.attr('transform', `translate(${masterX}, ${masterY})`); + masterY = masterY + 10 + eventHeight; + } + // set masterY back to eventBeginY + masterY = eventBeginY; + return maxEventHeight; +}; + +export default { + setConf, + draw, +}; diff --git a/packages/mermaid/src/directiveUtils.ts b/packages/mermaid/src/directiveUtils.ts new file mode 100644 index 000000000..2d2971a85 --- /dev/null +++ b/packages/mermaid/src/directiveUtils.ts @@ -0,0 +1,87 @@ +import * as configApi from './config'; + +import { log } from './logger'; +import { directiveSanitizer } from './utils'; + +let currentDirective: { type?: string; args?: any } | undefined = {}; + +export const parseDirective = function ( + p: any, + statement: string, + context: string, + type: string +): void { + log.debug('parseDirective is being called', statement, context, type); + try { + if (statement !== undefined) { + statement = statement.trim(); + switch (context) { + case 'open_directive': + currentDirective = {}; + break; + case 'type_directive': + if (!currentDirective) { + throw new Error('currentDirective is undefined'); + } + currentDirective.type = statement.toLowerCase(); + break; + case 'arg_directive': + if (!currentDirective) { + throw new Error('currentDirective is undefined'); + } + currentDirective.args = JSON.parse(statement); + break; + case 'close_directive': + handleDirective(p, currentDirective, type); + currentDirective = undefined; + break; + } + } + } catch (error) { + log.error( + `Error while rendering sequenceDiagram directive: ${statement} jison context: ${context}` + ); + // @ts-ignore: TODO Fix ts errors + log.error(error.message); + } +}; + +const handleDirective = function (p: any, directive: any, type: string): void { + log.info(`Directive type=${directive.type} with args:`, directive.args); + switch (directive.type) { + case 'init': + case 'initialize': { + ['config'].forEach((prop) => { + if (directive.args[prop] !== undefined) { + if (type === 'flowchart-v2') { + type = 'flowchart'; + } + directive.args[type] = directive.args[prop]; + delete directive.args[prop]; + } + }); + log.info('sanitize in handleDirective', directive.args); + directiveSanitizer(directive.args); + log.info('sanitize in handleDirective (done)', directive.args); + configApi.addDirective(directive.args); + break; + } + case 'wrap': + case 'nowrap': + if (p && p['setWrap']) { + p.setWrap(directive.type === 'wrap'); + } + break; + case 'themeCss': + log.warn('themeCss encountered'); + break; + default: + log.warn( + `Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify( + directive.args ? directive.args : {} + )}}%%`, + directive + ); + break; + } +}; diff --git a/packages/mermaid/src/docs.mts b/packages/mermaid/src/docs.mts index d7cf5bac8..7f3ab4e8b 100644 --- a/packages/mermaid/src/docs.mts +++ b/packages/mermaid/src/docs.mts @@ -88,7 +88,7 @@ const filesTransformed: Set = new Set(); const generateHeader = (file: string): string => { // path from file in docs/* to repo root, e.g ../ or ../../ */ - const relativePath = relative(file, SOURCE_DOCS_DIR); + const relativePath = relative(file, SOURCE_DOCS_DIR).replaceAll('\\', '/'); const filePathFromRoot = posix.join('/packages/mermaid', file); const sourcePathRelativeToGenerated = posix.join(relativePath, filePathFromRoot); return ` @@ -189,7 +189,7 @@ const transformIncludeStatements = (file: string, text: string): string => { // resolve includes - src https://github.com/vuejs/vitepress/blob/428eec3750d6b5648a77ac52d88128df0554d4d1/src/node/markdownToVue.ts#L65-L76 return text.replace(includesRE, (m, m1) => { try { - const includePath = join(dirname(file), m1); + const includePath = join(dirname(file), m1).replaceAll('\\', '/'); const content = readSyncedUTF8file(includePath); includedFiles.add(changeToFinalDocDir(includePath)); return content; diff --git a/packages/mermaid/src/docs/.vitepress/config.ts b/packages/mermaid/src/docs/.vitepress/config.ts index 9b5f1547e..396029a96 100644 --- a/packages/mermaid/src/docs/.vitepress/config.ts +++ b/packages/mermaid/src/docs/.vitepress/config.ts @@ -77,8 +77,8 @@ function sidebarAll() { ], }, ...sidebarSyntax(), + ...sidebarEcosystem(), ...sidebarConfig(), - ...sidebarMisc(), ...sidebarCommunity(), ]; } @@ -104,6 +104,7 @@ function sidebarSyntax() { { text: 'Gitgraph (Git) Diagram 🔥', link: '/syntax/gitgraph' }, { text: 'C4C Diagram (Context) Diagram 🦺⚠️', link: '/syntax/c4c' }, { text: 'Mindmaps 🔥', link: '/syntax/mindmap' }, + { text: 'Timeline 🔥', link: '/syntax/timeline' }, { text: 'Other Examples', link: '/syntax/examples' }, ], }, @@ -125,19 +126,20 @@ function sidebarConfig() { { text: 'Accessibility', link: '/config/accessibility' }, { text: 'Mermaid CLI', link: '/config/mermaidCLI' }, { text: 'Advanced usage', link: '/config/n00b-advanced' }, + { text: 'FAQ', link: '/config/faq' }, ], }, ]; } -function sidebarMisc() { +function sidebarEcosystem() { return [ { - text: '📚 Misc', + text: '📚 Ecosystem', collapsible: true, items: [ - { text: 'Use-Cases and Integrations', link: '/misc/integrations' }, - { text: 'FAQ', link: '/misc/faq' }, + { text: 'Showcases', link: '/ecosystem/showcases' }, + { text: 'Use-Cases and Integrations', link: '/ecosystem/integrations' }, ], }, ]; diff --git a/packages/mermaid/src/docs/.vitepress/theme/index.ts b/packages/mermaid/src/docs/.vitepress/theme/index.ts index ef929aa5d..273880d91 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/index.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/index.ts @@ -10,9 +10,6 @@ export default { // register global components app.component('Mermaid', Mermaid); router.onBeforeRouteChange = (to) => { - if (router.route.path !== '/') { - return; - } try { const newPath = getRedirect(to); if (newPath) { diff --git a/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts b/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts index e9a038ec4..dd55d6782 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/mermaid.ts @@ -1,9 +1,10 @@ import mermaid, { type MermaidConfig } from 'mermaid'; import mindmap from '@mermaid-js/mermaid-mindmap'; +// import timeline from '@mermaid-js/mermaid-timeline'; const init = (async () => { try { - await mermaid.registerExternalDiagrams([mindmap]); + await mermaid.registerExternalDiagrams([mindmap, timeline]); } catch (e) { console.error(e); } diff --git a/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts b/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts index 6070abee4..ec0404264 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/redirect.spec.ts @@ -5,31 +5,34 @@ import { expect, test } from 'vitest'; import { getRedirect } from './redirect'; test.each([ + // Old docs, localhost ['http://localhost:1234/mermaid/#/flowchart.md', 'syntax/flowchart.html'], ['http://localhost/mermaid/#/flowchart.md', 'syntax/flowchart.html'], - ['https://mermaid-js.github.io/mermaid/#/flowchart.md', 'syntax/flowchart.html'], - ['https://mermaid.js.org/#/flowchart.md', 'syntax/flowchart.html'], - ['https://mermaid-js.github.io/mermaid/#/./flowchart', 'syntax/flowchart.html'], - ['https://mermaid-js.github.io/mermaid/#/flowchart', 'syntax/flowchart.html'], - ['https://mermaid-js.github.io/mermaid/#flowchart', 'syntax/flowchart.html'], - ['https://mermaid-js.github.io/mermaid/#/flowchart', 'syntax/flowchart.html'], - ['https://mermaid-js.github.io/mermaid/#/flowchart.md?id=my-id', 'syntax/flowchart.html#my-id'], - ['https://mermaid-js.github.io/mermaid/#/./flowchart.md?id=my-id', 'syntax/flowchart.html#my-id'], + // Old docs, github pages + ['https://mermaid-js.github.io/mermaid/#/flowchart.md', 'syntax/flowchart.html'], // without dot + ['https://mermaid-js.github.io/mermaid/#/./flowchart', 'syntax/flowchart.html'], // with dot + ['https://mermaid-js.github.io/mermaid/#flowchart', 'syntax/flowchart.html'], // without slash + ['https://mermaid-js.github.io/mermaid/#/flowchart', 'syntax/flowchart.html'], // with slash + ['https://mermaid-js.github.io/mermaid/#/flowchart.md?id=my-id', 'syntax/flowchart.html#my-id'], // with id + ['https://mermaid-js.github.io/mermaid/#/./flowchart.md?id=my-id', 'syntax/flowchart.html#my-id'], // with id and dot [ - 'https://mermaid-js.github.io/mermaid/#/flowchart?another=test&id=my-id&one=more', + 'https://mermaid-js.github.io/mermaid/#/flowchart?another=test&id=my-id&one=more', // with multiple params 'syntax/flowchart.html#my-id', ], - ['https://mermaid-js.github.io/mermaid/#/n00b-advanced', 'config/n00b-advanced.html'], - ['https://mermaid-js.github.io/mermaid/#/n00b-advanced.md', 'config/n00b-advanced.html'], + ['https://mermaid-js.github.io/mermaid/#/n00b-advanced', 'config/n00b-advanced.html'], // without .md + ['https://mermaid-js.github.io/mermaid/#/n00b-advanced.md', 'config/n00b-advanced.html'], // with .md [ - 'https://mermaid-js.github.io/mermaid/#/flowchart?id=a-node-in-the-form-of-a-circle', + 'https://mermaid-js.github.io/mermaid/#/flowchart?id=a-node-in-the-form-of-a-circle', // with id, without .md 'syntax/flowchart.html#a-node-in-the-form-of-a-circle', ], + // Old docs, without base path, new domain + ['https://mermaid.js.org/#/flowchart.md', 'syntax/flowchart.html'], + // New docs, without base path, new domain + ['https://mermaid.js.org/misc/faq.html', 'configure/faq.html'], + [ + 'https://mermaid.js.org/misc/faq.html#frequently-asked-questions', + 'configure/faq.html#frequently-asked-questions', + ], // with hash ])('should process url %s to %s', (link: string, path: string) => { expect(getRedirect(link)).toBe(path); }); - -test('should throw for invalid URL', () => { - // Not mermaid domain - expect(() => getRedirect('https://www.google.com')).toThrowError(); -}); diff --git a/packages/mermaid/src/docs/.vitepress/theme/redirect.ts b/packages/mermaid/src/docs/.vitepress/theme/redirect.ts index 58537b0ef..0109aea10 100644 --- a/packages/mermaid/src/docs/.vitepress/theme/redirect.ts +++ b/packages/mermaid/src/docs/.vitepress/theme/redirect.ts @@ -1,4 +1,4 @@ -export interface Redirect { +interface Redirect { path: string; id?: string; } @@ -7,15 +7,7 @@ export interface Redirect { * Extracts the base slug from the old URL. * @param link - The old URL. */ -const getBaseFile = (link: string): Redirect => { - const url = new URL(link); - if ( - url.hostname !== 'mermaid-js.github.io' && - url.hostname !== 'mermaid.js.org' && - url.hostname !== 'localhost' - ) { - throw new Error('Not mermaidjs url'); - } +const getBaseFile = (url: URL): Redirect => { const [path, params, ...rest] = url.hash .toLowerCase() .replace('.md', '') @@ -32,7 +24,7 @@ const getBaseFile = (link: string): Redirect => { return { path, id }; }; -const redirectMap: Record = { +const idRedirectMap: Record = { '8.6.0_docs': '', accessibility: 'config/theming', breakingchanges: '', @@ -76,15 +68,25 @@ const redirectMap: Record = { 'user-journey': 'syntax/userJourney', }; +const urlRedirectMap: Record = { + '/misc/faq.html': 'configure/faq.html', +}; + /** * * @param link - The old documentation URL. * @returns The new documentation path. */ export const getRedirect = (link: string): string | undefined => { - const { path, id } = getBaseFile(link); - if (!(path in redirectMap)) { - return; + const url = new URL(link); + // Redirects for deprecated vitepress URLs + if (url.pathname in urlRedirectMap) { + return `${urlRedirectMap[url.pathname]}${url.hash}`; + } + + // Redirects for old docs URLs + const { path, id } = getBaseFile(url); + if (path in idRedirectMap) { + return `${idRedirectMap[path]}.html${id ? `#${id}` : ''}`; } - return `${redirectMap[path]}.html${id ? `#${id}` : ''}`; }; diff --git a/packages/mermaid/src/docs/misc/faq.md b/packages/mermaid/src/docs/config/faq.md similarity index 100% rename from packages/mermaid/src/docs/misc/faq.md rename to packages/mermaid/src/docs/config/faq.md diff --git a/packages/mermaid/src/docs/misc/integrations.md b/packages/mermaid/src/docs/ecosystem/integrations.md similarity index 100% rename from packages/mermaid/src/docs/misc/integrations.md rename to packages/mermaid/src/docs/ecosystem/integrations.md diff --git a/packages/mermaid/src/docs/ecosystem/showcases.md b/packages/mermaid/src/docs/ecosystem/showcases.md new file mode 100644 index 000000000..0c756759f --- /dev/null +++ b/packages/mermaid/src/docs/ecosystem/showcases.md @@ -0,0 +1,3 @@ +# Showcases + +- [Swimm - Up-to-date diagrams with Swimm, the knowledge management tool for code](https://docs.swimm.io/Features/diagrams-and-charts). diff --git a/packages/mermaid/src/docs/index.md b/packages/mermaid/src/docs/index.md index 59a4c03f9..b0b38bd79 100644 --- a/packages/mermaid/src/docs/index.md +++ b/packages/mermaid/src/docs/index.md @@ -28,7 +28,7 @@ features: link: ../../config/Tutorials.html - title: 🧩 Integrations available! details: Use Mermaid with your favorite applications, check out the integrations list. - link: ../../misc/integrations.md + link: ../../ecosystem/integrations.md - title: 🏆 Award winning! details: 2019 JavaScript Open Source Award winner for "The Most Exciting Use of Technology". link: https://osawards.com/javascript/2019 diff --git a/packages/mermaid/src/docs/intro/index.md b/packages/mermaid/src/docs/intro/index.md index df1aa3b62..4c4978cae 100644 --- a/packages/mermaid/src/docs/intro/index.md +++ b/packages/mermaid/src/docs/intro/index.md @@ -26,11 +26,11 @@ Mermaid addresses this problem by enabling users to create easily modifiable dia
Mermaid allows even non-programmers to easily create detailed and diagrams through the [Mermaid Live Editor](https://mermaid.live/).
[Tutorials](../config/Tutorials.md) has video tutorials. -Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](../misc/integrations.md). +Use Mermaid with your favorite applications, check out the list of [Integrations and Usages of Mermaid](../ecosystem/integrations.md). For a more detailed introduction to Mermaid and some of its more basic uses, look to the [Beginner's Guide](../community/n00b-overview.md) and [Usage](../config/usage.md). -🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](../community/development.md) | 🔌 [Plug-Ins](../misc/integrations.md) +🌐 [CDN](https://unpkg.com/mermaid/) | 📖 [Documentation](https://mermaidjs.github.io) | 🙌 [Contribution](../community/development.md) | 🔌 [Plug-Ins](../ecosystem/integrations.md) > 🖖 Keep a steady pulse: mermaid needs more Collaborators, [Read More](https://github.com/knsv/mermaid/issues/866). @@ -55,7 +55,7 @@ In our release process we rely heavily on visual regression tests using [applito ### CDN ``` -https://unpkg.com/mermaid@/dist/ +/mermaid@/dist/ ``` To select a version: diff --git a/packages/mermaid/src/docs/intro/n00b-gettingStarted.md b/packages/mermaid/src/docs/intro/n00b-gettingStarted.md index a4dd7662f..c347f2ef3 100644 --- a/packages/mermaid/src/docs/intro/n00b-gettingStarted.md +++ b/packages/mermaid/src/docs/intro/n00b-gettingStarted.md @@ -11,7 +11,7 @@ This section talks about the different ways to deploy Mermaid. Learning the [Syn ## Four ways of using mermaid: 1. Using the Mermaid Live Editor at [mermaid.live](https://mermaid.live). -2. Using [mermaid plugins](../misc/integrations.md) with programs you are familiar with. +2. Using [mermaid plugins](../ecosystem/integrations.md) with programs you are familiar with. 3. Calling the Mermaid JavaScript API. 4. Deploying Mermaid as a dependency. @@ -68,7 +68,7 @@ and to View, https://mermaid.live/view?gist=https://gist.github.com/sidharthv96/ ## 2. Using Mermaid Plugins: -You can generate mermaid diagrams from within popular applications using plug-ins. It can be done in the same way, you would use the Live Editor. Here's a list of [Mermaid Plugins](../misc/integrations.md). +You can generate mermaid diagrams from within popular applications using plug-ins. It can be done in the same way, you would use the Live Editor. Here's a list of [Mermaid Plugins](../ecosystem/integrations.md). **This is covered in greater detail in the [Usage section](../config/usage.md)** diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md index 4ca3c5466..587b3b1fc 100644 --- a/packages/mermaid/src/docs/syntax/flowchart.md +++ b/packages/mermaid/src/docs/syntax/flowchart.md @@ -552,8 +552,8 @@ linkStyle 3 stroke:#ff3,stroke-width:4px,color:red; ### Styling line curves It is possible to style the type of curve used for lines between items, if the default method does not meet your needs. -Available curve styles include `basis`, `bump`, `linear`, `monotoneX`, `monotoneY`, `natural`, `step`, `stepAfter`, -and `stepBefore`. +Available curve styles include `basis`, `bumpX`, `bumpY`, `cardinal`, `catmullRom`, `linear`, `monotoneX`, `monotoneY`, +`natural`, `step`, `stepAfter`, and `stepBefore`. In this example, a left-to-right graph uses the `stepBefore` curve style: diff --git a/packages/mermaid/src/docs/syntax/sequenceDiagram.md b/packages/mermaid/src/docs/syntax/sequenceDiagram.md index 8d2912e30..3f3818589 100644 --- a/packages/mermaid/src/docs/syntax/sequenceDiagram.md +++ b/packages/mermaid/src/docs/syntax/sequenceDiagram.md @@ -58,6 +58,48 @@ sequenceDiagram J->>A: Great! ``` +### Grouping / Box + +The actor(s) can be grouped in vertical boxes. You can define a color (if not, it will be transparent) and/or a descriptive label using the following notation: + +``` +box Aqua Group Description +... actors ... +end +box Group without description +... actors ... +end +box rgb(33,66,99) +... actors ... +end +``` + +```note +If your group name is a color you can force the color to be transparent: +``` + +``` +box transparent Aqua +... actors ... +end +``` + +```mermaid-example + sequenceDiagram + box Purple Alice & John + participant A + participant J + end + box Another Group + participant B + participant C + end + A->>J: Hello John, how are you? + J->>A: Great! + A->>B: Hello Bob, how is Charly ? + B->>C: Hello Charly, how are you? +``` + ## Messages Messages can be of two displayed either solid or with a dotted line. diff --git a/packages/mermaid/src/docs/syntax/timeline.md b/packages/mermaid/src/docs/syntax/timeline.md new file mode 100644 index 000000000..76fdb5e60 --- /dev/null +++ b/packages/mermaid/src/docs/syntax/timeline.md @@ -0,0 +1,294 @@ +# Timeline Diagram + +> Timeline: This is an experimental diagram for now. The syntax and properties can change in future releases. The syntax is stable except for the icon integration which is the experimental part. + +"A timeline is a type of diagram used to illustrate a chronology of events, dates, or periods of time. It is usually presented graphically to indicate the passing of time, and it is usually organized chronologically. A basic timeline presents a list of events in chronological order, usually using dates as markers. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life. A timeline can also be used to show the relationship between events, such as the relationship between the events of a person's life." Wikipedia + +### An example of a timeline. + +```mermaid +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook + : Google + 2005 : Youtube + 2006 : Twitter +``` + +## Syntax + +The syntax for creating Timeline diagram is simple. You always start with the `timeline` keyword to let mermaid know that you want to create a timeline diagram. + +After that there is a possibility to add a title to the timeline. This is done by adding a line with the keyword `title` followed by the title text. + +Then you add the timeline data, where you always start with a time period, followed by a colon and then the text for the event. Optionally you can add a second colon and then the text for the event. So, you can have one or more events per time period. + +```json +{time period} : {event} +``` + +or + +```json +{time period} : {event} : {event} +``` + +or + +```json +{time period} : {event} + : {event} + : {event} +``` + +NOTE: Both time period and event are simple text, and not limited to numbers. + +Let us look at the syntax for the example above. + +```mermaid-example +timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter +``` + +In this way we can use a text outline to generate a timeline diagram. +The sequence of time period and events is important, as it will be used to draw the timeline. The first time period will be placed at the left side of the timeline, and the last time period will be placed at the right side of the timeline. + +Similarly, the first event will be placed at the top for that specific time period, and the last event will be placed at the bottom. + +## Grouping of time periods in sections/ages + +You can group time periods in sections/ages. This is done by adding a line with the keyword `section` followed by the section name. + +All subsequent time periods will be placed in this section until a new section is defined. + +If no section is defined, all time periods will be placed in the default section. + +Let us look at an example, where we have grouped the time periods in sections. + +```mermaid-example +timeline + title Timeline of Industrial Revolution + section 17th-20th century + Industry 1.0 : Machinery, Water power, Steam
power + Industry 2.0 : Electricity, Internal combustion engine, Mass production + Industry 3.0 : Electronics, Computers, Automation + section 21st century + Industry 4.0 : Internet, Robotics, Internet of Things + Industry 5.0 : Artificial intelligence, Big data,3D printing +``` + +As you can see, the time periods are placed in the sections, and the sections are placed in the order they are defined. + +All time periods and events under a given section follow a similar color scheme. This is done to make it easier to see the relationship between time periods and events. + +## Wrapping of text for long time-periods or events + +By default, the text for time-periods and events will be wrapped if it is too long. This is done to avoid that the text is drawn outside the diagram. + +You can also use `
` to force a line break. + +Let us look at another example, where we have a long time period, and a long event. + +```mermaid-example +timeline + title England's History Timeline + section Stone Age + 7600 BC : Britain's oldest known house was built in Orkney, Scotland + 6000 BC : Sea levels rise and Britain becomes an island.
The people who live here are hunter-gatherers. + section Broze Age + 2300 BC : People arrive from Europe and settle in Britain.
They bring farming and metalworking. + : New styles of pottery and ways of burying the dead appear. + 2200 BC : The last major building works are completed at Stonehenge.
People now bury their dead in stone circles. + : The first metal objects are made in Britain.Some other nice things happen. it is a good time to be alive. + +``` + +```mermaid-example +timeline + title MermaidChart 2023 Timeline + section 2023 Q1
Release Personal Tier + Buttet 1 : sub-point 1a : sub-point 1b + : sub-point 1c + Bullet 2 : sub-point 2a : sub-point 2b + section 2023 Q2
Release XYZ Tier + Buttet 3 : sub-point
3a : sub-point 3b + : sub-point 3c + Bullet 4 : sub-point 4a : sub-point 4b +``` + +## Styling of time periods and events + +As explained earlier, each section has a color scheme, and each time period and event under a section follow the similar color scheme. + +However, if there is no section defined, then we have two possibilities: + +1. Style time periods individually, i.e. each time period(and its coressponding events) will have its own color scheme. This is the DEFAULT behavior. + +```mermaid-example + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +Note that this is no, section defined, and each time period and its corresponding events will have its own color scheme. + +2. Disable the multiColor option using the `disableMultiColor` option. This will make all time periods and events follow the same color scheme. + +You will need to add this option either via mermaid.intialize function or directives. + +```javascript +mermaid.initialize({ + theme: 'base', + startOnLoad: true, + logLevel: 0, + timeline: { + disableMulticolor: false, + }, + ... + ... +``` + +let us look at same example, where we have disabled the multiColor option. + +```mermaid-example + %%{init: { 'logLevel': 'debug', 'theme': 'base', 'timeline': {'disableMulticolor': true}}}%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + +``` + +### Customizing Color scheme + +You can customize the color scheme using the `cScale0` to `cScale11` theme variables. Mermaid allows you to set unique colors for up-to 12, where `cScale0` variable will drive the value of the first section or time-period, `cScale1` will drive the value of the second section and so on. +In case you have more than 12 sections, the color scheme will start to repeat. + +NOTE: Default values for these theme variables are picked from the selected theme. If you want to override the default values, you can use the `initialize` call to add your custom theme variable values. + +Example: + +Now let's override the default values for the `cScale0` to `cScale2` variables: + +```mermaid-example + %%{init: { 'logLevel': 'debug', 'theme': 'default' , 'themeVariables': { + 'cScale0': '#ff0000', + 'cScale1': '#00ff00', + 'cScale2': '#0000ff' + } } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest + +``` + +See how the colors are changed to the values specified in the theme variables. + +## Themes + +Mermaid supports a bunch of pre-defined themes which you can use to find the right one for you. PS: you can actually override an existing theme's variable to get your own custom theme going. Learn more about theming your diagram [here](../config/theming.md). + +The following are the different pre-defined theme options: + +- `base` +- `forest` +- `dark` +- `default` +- `neutral` + +**NOTE**: To change theme you can either use the `initialize` call or _directives_. Learn more about [directives](../config/directives.md) +Let's put them to use, and see how our sample diagram looks in different themes: + +### Base Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'base' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Forest Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'forest' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Dark Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'dark' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Default Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'default' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +### Neutral Theme + +```mermaid-example +%%{init: { 'logLevel': 'debug', 'theme': 'neutral' } }%% + timeline + title History of Social Media Platform + 2002 : LinkedIn + 2004 : Facebook : Google + 2005 : Youtube + 2006 : Twitter + 2007 : Tumblr + 2008 : Instagram + 2010 : Pinterest +``` + +You can also refer the implementation in the live editor [here](https://github.com/mermaid-js/mermaid-live-editor/blob/fcf53c98c25604c90a218104268c339be53035a6/src/lib/util/mermaid.ts) to see how the async loading is done. diff --git a/packages/mermaid/src/docs/vite.config.ts b/packages/mermaid/src/docs/vite.config.ts index 15652c21c..356e9398c 100644 --- a/packages/mermaid/src/docs/vite.config.ts +++ b/packages/mermaid/src/docs/vite.config.ts @@ -36,6 +36,10 @@ export default defineConfig({ __dirname, '../../../mermaid-mindmap/dist/mermaid-mindmap.esm.min.mjs' ), // Use this one to build + // '@mermaid-js/mermaid-timeline': path.join( + // __dirname, + // '../../../mermaid-timeline/dist/mermaid-timeline.esm.min.mjs' + // ), }, }, server: { diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index 7f94a0ff0..99e9f9887 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -32,6 +32,7 @@ import { MermaidConfig } from './config.type'; import { evaluate } from './diagrams/common/common'; import isEmpty from 'lodash-es/isEmpty.js'; import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility'; +import { parseDirective } from './directiveUtils'; // diagram names that support classDef statements const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram', 'stateDiagram-v2']; @@ -778,83 +779,6 @@ const renderAsync = async function ( return svgCode; }; -let currentDirective: { type?: string; args?: any } | undefined = {}; - -const parseDirective = function (p: any, statement: string, context: string, type: string): void { - try { - if (statement !== undefined) { - statement = statement.trim(); - switch (context) { - case 'open_directive': - currentDirective = {}; - break; - case 'type_directive': - if (!currentDirective) { - throw new Error('currentDirective is undefined'); - } - currentDirective.type = statement.toLowerCase(); - break; - case 'arg_directive': - if (!currentDirective) { - throw new Error('currentDirective is undefined'); - } - currentDirective.args = JSON.parse(statement); - break; - case 'close_directive': - handleDirective(p, currentDirective, type); - currentDirective = undefined; - break; - } - } - } catch (error) { - log.error( - `Error while rendering sequenceDiagram directive: ${statement} jison context: ${context}` - ); - // @ts-ignore: TODO Fix ts errors - log.error(error.message); - } -}; - -const handleDirective = function (p: any, directive: any, type: string): void { - log.debug(`Directive type=${directive.type} with args:`, directive.args); - switch (directive.type) { - case 'init': - case 'initialize': { - ['config'].forEach((prop) => { - if (directive.args[prop] !== undefined) { - if (type === 'flowchart-v2') { - type = 'flowchart'; - } - directive.args[type] = directive.args[prop]; - delete directive.args[prop]; - } - }); - log.debug('sanitize in handleDirective', directive.args); - directiveSanitizer(directive.args); - log.debug('sanitize in handleDirective (done)', directive.args); - configApi.addDirective(directive.args); - break; - } - case 'wrap': - case 'nowrap': - if (p && p['setWrap']) { - p.setWrap(directive.type === 'wrap'); - } - break; - case 'themeCss': - log.warn('themeCss encountered'); - break; - default: - log.warn( - `Unhandled directive: source: '%%{${directive.type}: ${JSON.stringify( - directive.args ? directive.args : {} - )}}%%`, - directive - ); - break; - } -}; - /** * @param options - Initial Mermaid options */ diff --git a/packages/mermaid/src/tests/MockedD3.ts b/packages/mermaid/src/tests/MockedD3.ts index 284b21b08..4600e2667 100644 --- a/packages/mermaid/src/tests/MockedD3.ts +++ b/packages/mermaid/src/tests/MockedD3.ts @@ -1,3 +1,5 @@ +import type {} from '@vitest/spy'; + /** * This is a mocked/stubbed version of the d3 Selection type. Each of the main functions are all * mocked (via vi.fn()) so you can track if they have been called, etc. diff --git a/packages/mermaid/src/themes/theme-default.js b/packages/mermaid/src/themes/theme-default.js index 969551ee6..391c0298f 100644 --- a/packages/mermaid/src/themes/theme-default.js +++ b/packages/mermaid/src/themes/theme-default.js @@ -122,6 +122,7 @@ class Theme { updateColors() { /* Color Scale */ /* Each color-set will have a background, a foreground and a border color */ + this.cScale0 = this.cScale0 || this.primaryColor; this.cScale1 = this.cScale1 || this.secondaryColor; this.cScale2 = this.cScale2 || this.tertiaryColor; @@ -141,7 +142,6 @@ class Theme { this['cScale' + i] = darken(this['cScale' + i], 10); this['cScalePeer' + i] = this['cScalePeer' + i] || darken(this['cScale' + i], 25); } - // Setup the inverted color for the set for (let i = 0; i < this.THEME_COLOR_LIMIT; i++) { this['cScaleInv' + i] = this['cScaleInv' + i] || adjust(this['cScale' + i], { h: 180 }); diff --git a/packages/mermaid/src/utils.ts b/packages/mermaid/src/utils.ts index 876c81543..76fce1999 100644 --- a/packages/mermaid/src/utils.ts +++ b/packages/mermaid/src/utils.ts @@ -4,6 +4,15 @@ import { curveBasis, curveBasisClosed, curveBasisOpen, + curveBumpX, + curveBumpY, + curveBundle, + curveCardinalClosed, + curveCardinalOpen, + curveCardinal, + curveCatmullRomClosed, + curveCatmullRomOpen, + curveCatmullRom, CurveFactory, curveLinear, curveLinearClosed, @@ -28,6 +37,15 @@ const d3CurveTypes = { curveBasis: curveBasis, curveBasisClosed: curveBasisClosed, curveBasisOpen: curveBasisOpen, + curveBumpX: curveBumpX, + curveBumpY: curveBumpY, + curveBundle: curveBundle, + curveCardinalClosed: curveCardinalClosed, + curveCardinalOpen: curveCardinalOpen, + curveCardinal: curveCardinal, + curveCatmullRomClosed: curveCatmullRomClosed, + curveCatmullRomOpen: curveCatmullRomOpen, + curveCatmullRom: curveCatmullRom, curveLinear: curveLinear, curveLinearClosed: curveLinearClosed, curveMonotoneX: curveMonotoneX, diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 00278bba5..110464d87 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -26,8 +26,8 @@ importers: specifier: ^4.0.5 version: 4.0.5 '@types/jsdom': - specifier: ^20.0.1 - version: 20.0.1 + specifier: ^21.0.0 + version: 21.1.0 '@types/lodash': specifier: ^4.14.188 version: 4.14.188 @@ -50,11 +50,14 @@ importers: specifier: ^5.48.2 version: 5.48.2_yygwinqv3a2io74xmwofqb7uka '@vitest/coverage-c8': - specifier: ^0.27.0 - version: 0.27.1_6vhkb7zox2ro6wmx3rlvm5i5ce + specifier: ^0.28.4 + version: 0.28.4_vun5xzxu3tkrssf3erdbijyyki + '@vitest/spy': + specifier: ^0.28.4 + version: 0.28.4 '@vitest/ui': - specifier: ^0.27.0 - version: 0.27.1 + specifier: ^0.28.4 + version: 0.28.4 concurrently: specifier: ^7.5.0 version: 7.5.0 @@ -125,8 +128,8 @@ importers: specifier: ^4.1.0 version: 4.1.0 jsdom: - specifier: ^20.0.2 - version: 20.0.2 + specifier: ^21.0.0 + version: 21.1.0 lint-staged: specifier: ^13.0.3 version: 13.0.3 @@ -143,8 +146,8 @@ importers: specifier: ^0.4.2 version: 0.4.2_prettier@2.7.1 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: ^4.0.0 + version: 4.1.2 rollup-plugin-visualizer: specifier: ^5.8.3 version: 5.8.3 @@ -161,8 +164,8 @@ importers: specifier: ^3.2.3 version: 3.2.3_@types+node@18.11.9 vitest: - specifier: ^0.27.1 - version: 0.27.1_6vhkb7zox2ro6wmx3rlvm5i5ce + specifier: ^0.28.4 + version: 0.28.4_vun5xzxu3tkrssf3erdbijyyki packages/mermaid: dependencies: @@ -213,8 +216,8 @@ importers: specifier: ^2.4.0 version: 2.4.0 '@types/jsdom': - specifier: ^20.0.1 - version: 20.0.1 + specifier: ^21.0.0 + version: 21.1.0 '@types/lodash-es': specifier: ^4.17.6 version: 4.17.6 @@ -228,8 +231,8 @@ importers: specifier: ^4.0.2 version: 4.0.2 '@types/uuid': - specifier: ^8.3.4 - version: 8.3.4 + specifier: ^9.0.0 + version: 9.0.0 '@typescript-eslint/eslint-plugin': specifier: ^5.42.1 version: 5.42.1_qxgr6oy2qtsmmpo3f6iejuryuq @@ -261,8 +264,8 @@ importers: specifier: ^3.7.2 version: 3.7.2 jsdom: - specifier: ^20.0.2 - version: 20.0.2 + specifier: ^21.0.0 + version: 21.1.0 micromatch: specifier: ^4.0.5 version: 4.0.5 @@ -282,8 +285,8 @@ importers: specifier: ^3.0.1 version: 3.0.1 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: ^4.0.0 + version: 4.1.2 start-server-and-test: specifier: ^1.14.0 version: 1.14.0 @@ -312,8 +315,8 @@ importers: specifier: ^7.5.0 version: 7.5.0 rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: ^4.0.0 + version: 4.1.2 packages/mermaid-mindmap: dependencies: @@ -349,8 +352,8 @@ importers: specifier: workspace:* version: link:../mermaid rimraf: - specifier: ^3.0.2 - version: 3.0.2 + specifier: ^4.0.0 + version: 4.1.2 tests/webpack: dependencies: @@ -2638,11 +2641,7 @@ packages: /@types/chai-subset/1.3.3: resolution: {integrity: sha512-frBecisrNGz+F4T6bcc+NLeolfiojh5FxW2klu669+8BARtyQv2C/GkNW6FUodVe4BroGMP/wER/YDGc7rEllw==} dependencies: - '@types/chai': 4.3.3 - dev: true - - /@types/chai/4.3.3: - resolution: {integrity: sha512-hC7OMnszpxhZPduX+m+nrx+uFoLkWOMiR4oa/AZF3MuSETYTZmFfJAHqZEM8MVlvfG7BEUcgvtwoCTxBp6hm3g==} + '@types/chai': 4.3.4 dev: true /@types/chai/4.3.4: @@ -2940,8 +2939,8 @@ packages: resolution: {integrity: sha512-FhpRzf927MNQdRZP0J5DLIdTXhjLYzeUTmLAu69mnVksLH9CJY3IuSeEgbKUki7GQZm0WqDkGzyxju2EZGD2wA==} dev: true - /@types/jsdom/20.0.1: - resolution: {integrity: sha512-d0r18sZPmMQr1eG35u12FZfhIXNrnsPU/g5wvRKCUf/tOGilKKwYMYGqh33BNR6ba+2gkHw1EUiHoN3mn7E5IQ==} + /@types/jsdom/21.1.0: + resolution: {integrity: sha512-leWreJOdnuIxq9Y70tBVm/bvTuh31DSlF/r4l7Cfi4uhVQqLHD0Q4v301GMisEMwwbMgF7ZKxuZ+Jbd4NcdmRw==} dependencies: '@types/node': 18.11.9 '@types/tough-cookie': 4.0.2 @@ -3114,8 +3113,8 @@ packages: resolution: {integrity: sha512-PBjIUxZHOuj0R15/xuwJYjFi+KZdNFrehocChv4g5hu6aFroHue8m0lBP0POdK2nKzbw0cgV1mws8+V/JAcEkQ==} dev: true - /@types/uuid/8.3.4: - resolution: {integrity: sha512-c/I8ZRb51j+pYGAu5CrFMRxqZ2ke4y2grEBO5AUjgSkSk+qT2Ea+OdWElz/OiMf5MNpn2b17kuVBwZLQJXzihw==} + /@types/uuid/9.0.0: + resolution: {integrity: sha512-kr90f+ERiQtKWMz5rP32ltJ/BtULDI5RVO0uavn1HQUOwjx0R1h0rnDYNL0CepF1zL5bSY6FISAfd9tOdDhU5Q==} dev: true /@types/web-bluetooth/0.0.16: @@ -3415,11 +3414,13 @@ packages: vue: 3.2.45 dev: true - /@vitest/coverage-c8/0.27.1_6vhkb7zox2ro6wmx3rlvm5i5ce: - resolution: {integrity: sha512-/9VTGDIAp4hv8PBawfyijxhkiyucfOxFRRP+7kzy3Dj0wONy1Mc2MBoPmiH4aZVc0LViQqecrQLs8JVGt42keA==} + /@vitest/coverage-c8/0.28.4_vun5xzxu3tkrssf3erdbijyyki: + resolution: {integrity: sha512-btelLBxaWhHnywXRQxDlrvPhGdnuIaD3XulsxcZRIcnpLPbFu39dNTT0IYu2QWP2ZZrV0AmNtdLIfD4c77zMAg==} dependencies: c8: 7.12.0 - vitest: 0.27.1_6vhkb7zox2ro6wmx3rlvm5i5ce + picocolors: 1.0.0 + std-env: 3.3.2 + vitest: 0.28.4_vun5xzxu3tkrssf3erdbijyyki transitivePeerDependencies: - '@edge-runtime/vm' - '@vitest/browser' @@ -3434,14 +3435,48 @@ packages: - terser dev: true - /@vitest/ui/0.27.1: - resolution: {integrity: sha512-o9LmWY/XyWt5Pqb+bLLK+N3Vk+PP2jqrdDDMEAY43dB6vtZ+gYuOt5IutUfB7gXeJ6xeIlzUZ/OabrR3leldCA==} + /@vitest/expect/0.28.4: + resolution: {integrity: sha512-JqK0NZ4brjvOSL8hXAnIsfi+jxDF7rH/ZWCGCt0FAqRnVFc1hXsfwXksQvEnKqD84avRt3gmeXoK4tNbmkoVsQ==} + dependencies: + '@vitest/spy': 0.28.4 + '@vitest/utils': 0.28.4 + chai: 4.3.7 + dev: true + + /@vitest/runner/0.28.4: + resolution: {integrity: sha512-Q8UV6GjDvBSTfUoq0QXVCNpNOUrWu4P2qvRq7ssJWzn0+S0ojbVOxEjMt+8a32X6SdkhF8ak+2nkppsqV0JyNQ==} + dependencies: + '@vitest/utils': 0.28.4 + p-limit: 4.0.0 + pathe: 1.1.0 + dev: true + + /@vitest/spy/0.28.4: + resolution: {integrity: sha512-8WuhfXLlvCXpNXEGJW6Gc+IKWI32435fQJLh43u70HnZ1otJOa2Cmg2Wy2Aym47ZnNCP4NolF+8cUPwd0MigKQ==} + dependencies: + tinyspy: 1.0.2 + dev: true + + /@vitest/ui/0.28.4: + resolution: {integrity: sha512-LQfCCFc17n49mwtraV9/NAWl2DUqJS/9ZEa3fqJjoYO+HowdseQ5jvWflpzliCyfrIAh6cXVo1bNzHnDXe0cbw==} dependencies: fast-glob: 3.2.12 flatted: 3.2.7 + pathe: 1.1.0 + picocolors: 1.0.0 sirv: 2.0.2 dev: true + /@vitest/utils/0.28.4: + resolution: {integrity: sha512-l2QztOLdc2LkR+w/lP52RGh8hW+Ul4KESmCAgVE8q737I7e7bQoAfkARKpkPJ4JQtGpwW4deqlj1732VZD7TFw==} + dependencies: + cli-truncate: 3.1.0 + diff: 5.1.0 + loupe: 2.3.6 + picocolors: 1.0.0 + pretty-format: 27.5.1 + dev: true + /@vue/compiler-core/3.2.45: resolution: {integrity: sha512-rcMj7H+PYe5wBV3iYeUgbCglC+pbpN8hBLTJvRiK2eKQiWqu+fG9F+8sW99JdL4LQi7Re178UOxn09puSXvn4A==} dependencies: @@ -3788,7 +3823,7 @@ packages: /acorn-globals/7.0.1: resolution: {integrity: sha512-umOSDSDrfHbTNPuNpC2NSnnA3LUrqpevPb4T9jRx4MagXNS0rs+gwiTcAvqCRmsD6utzsrzNt+ebm00SNWiC3Q==} dependencies: - acorn: 8.8.0 + acorn: 8.8.1 acorn-walk: 8.2.0 dev: true @@ -4437,7 +4472,7 @@ packages: check-error: 1.0.2 deep-eql: 4.1.3 get-func-name: 2.0.0 - loupe: 2.3.4 + loupe: 2.3.6 pathval: 1.1.1 type-detect: 4.0.8 dev: true @@ -5694,6 +5729,10 @@ packages: resolution: {integrity: sha512-F29o+vci4DodHYT9UrR5IEbfBw9pE5eSapIJdTqXK5+6hq+t8VRxwQyKlW2i+KDKFkkJQRvFyI/QXD83h8LyQw==} dev: true + /decimal.js/10.4.3: + resolution: {integrity: sha512-VBBaLc1MgL5XpzgIP7ny5Z6Nx3UrRkIViUkPUdtl9aya5amy3De1gsUUSB1g3+3sExYNjCAsAznmukyxCb1GRA==} + dev: true + /decode-named-character-reference/1.0.2: resolution: {integrity: sha512-O8x12RzrUF8xyVcY0KJowWsmaJxQbmy0/EtnNtHRpsOcT7dFk5W598coHqBVpmWo1oQQfsCqfCmkZN5DJrZVdg==} dependencies: @@ -8393,8 +8432,8 @@ packages: engines: {node: '>=12.0.0'} dev: true - /jsdom/20.0.2: - resolution: {integrity: sha512-AHWa+QO/cgRg4N+DsmHg1Y7xnz+8KU3EflM0LVDTdmrYOc1WWTSkOjtpUveQH+1Bqd5rtcVnb/DuxV/UjDO4rA==} + /jsdom/21.1.0: + resolution: {integrity: sha512-m0lzlP7qOtthD918nenK3hdItSd2I+V3W9IrBcB36sqDwG+KnUs66IF5GY7laGWUnlM9vTsD0W1QwSEBYWWcJg==} engines: {node: '>=14'} peerDependencies: canvas: ^2.5.0 @@ -8403,12 +8442,12 @@ packages: optional: true dependencies: abab: 2.0.6 - acorn: 8.8.0 + acorn: 8.8.1 acorn-globals: 7.0.1 cssom: 0.5.0 cssstyle: 2.3.0 data-urls: 3.0.2 - decimal.js: 10.4.1 + decimal.js: 10.4.3 domexception: 4.0.0 escodegen: 2.0.0 form-data: 4.0.0 @@ -8421,12 +8460,12 @@ packages: saxes: 6.0.0 symbol-tree: 3.2.4 tough-cookie: 4.1.2 - w3c-xmlserializer: 3.0.0 + w3c-xmlserializer: 4.0.0 webidl-conversions: 7.0.0 whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 whatwg-url: 11.0.0 - ws: 8.9.0 + ws: 8.12.0 xml-name-validator: 4.0.0 transitivePeerDependencies: - bufferutil @@ -8782,8 +8821,8 @@ packages: resolution: {integrity: sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==} dev: true - /loupe/2.3.4: - resolution: {integrity: sha512-OvKfgCC2Ndby6aSTREl5aCCPTNIzlDfQZvZxNUrBrihDhL3xcrYegTblhmEiCrg2kKQz4XsFIaemE5BF4ybSaQ==} + /loupe/2.3.6: + resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} dependencies: get-func-name: 2.0.0 dev: true @@ -9420,7 +9459,7 @@ packages: resolution: {integrity: sha512-cwzBrBfwGC1gYJyfcy8TcZU1f+dbH/T+TuOhtYP2wLv/Fb51/uV7HJQfBPtEupZ2ORLRU1EKFS/QfS3eo9+kBQ==} dependencies: acorn: 8.8.1 - pathe: 1.0.0 + pathe: 1.1.0 pkg-types: 1.0.1 ufo: 1.0.1 dev: true @@ -9710,6 +9749,13 @@ packages: yocto-queue: 0.1.0 dev: true + /p-limit/4.0.0: + resolution: {integrity: sha512-5b0R4txpzjPWVw/cXXUResoD4hb6U/x9BH08L7nw+GN1sezDzPdxeRvpc9c433fZhBan/wusjbCsqwqm4EIBIQ==} + engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} + dependencies: + yocto-queue: 1.0.0 + dev: true + /p-locate/3.0.0: resolution: {integrity: sha512-x+12w/To+4GFfgJhBEpiDcLozRJGegY+Ei7/z0tSLkMmxGZNybVMSfWj9aJn8Z5Fc7dBUNJOOVgPv2H7IwulSQ==} engines: {node: '>=6'} @@ -9892,12 +9938,8 @@ packages: engines: {node: '>=8'} dev: true - /pathe/0.2.0: - resolution: {integrity: sha512-sTitTPYnn23esFR3RlqYBWn4c45WGeLcsKzQiUpXJAyfcWkolvlYpV8FLo7JishK946oQwMFUCHXQ9AjGPKExw==} - dev: true - - /pathe/1.0.0: - resolution: {integrity: sha512-nPdMG0Pd09HuSsr7QOKUXO2Jr9eqaDiZvDwdyIhNG5SHYujkQHYKDfGQkulBxvbDHz8oHLsTgKN86LSwYzSHAg==} + /pathe/1.1.0: + resolution: {integrity: sha512-ODbEPR0KKHqECXW1GoxdDb+AZvULmXjVPy4rt+pGo2+TnjJTIPJQSVS6N63n8T2Ip+syHhbn52OewKicV0373w==} dev: true /pathval/1.1.1: @@ -9969,7 +10011,7 @@ packages: dependencies: jsonc-parser: 3.2.0 mlly: 1.1.0 - pathe: 1.0.0 + pathe: 1.1.0 dev: true /plist/3.0.6: @@ -10066,6 +10108,15 @@ packages: engines: {node: '>=6'} dev: true + /pretty-format/27.5.1: + resolution: {integrity: sha512-Qb1gy5OrP5+zDf2Bvnzdl3jsTf1qXVMazbvCoKhtKqVs4/YK4ozX4gKQJJVyNe+cajNPn0KoC0MC3FUmaHWEmQ==} + engines: {node: ^10.13.0 || ^12.13.0 || ^14.15.0 || >=15.0.0} + dependencies: + ansi-regex: 5.0.1 + ansi-styles: 5.2.0 + react-is: 17.0.2 + dev: true + /pretty-format/29.3.1: resolution: {integrity: sha512-FyLnmb1cYJV8biEIiRyzRFvs2lry7PPIvOqKVe1GCUEYg4YGmlx1qG9EJNMxArYm7piII4qb8UV1Pncq5dxmcg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} @@ -10199,6 +10250,10 @@ packages: unpipe: 1.0.0 dev: true + /react-is/17.0.2: + resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} + dev: true + /react-is/18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true @@ -10510,6 +10565,12 @@ packages: glob: 7.2.3 dev: true + /rimraf/4.1.2: + resolution: {integrity: sha512-BlIbgFryTbw3Dz6hyoWFhKk+unCcHMSkZGrTFVAx2WmttdBSonsdtRlwiuTbDqTKr+UlXIUqJVS4QT5tUzGENQ==} + engines: {node: '>=14'} + hasBin: true + dev: true + /robust-predicates/3.0.1: resolution: {integrity: sha512-ndEIpszUHiG4HtDsQLeIuMvRsDnn8c8rYStabochtUeCvfuvNptb5TUbVD68LRAILPX7p9nqQGh4xJgn3EHS/g==} dev: false @@ -11030,6 +11091,10 @@ packages: engines: {node: '>= 0.8'} dev: true + /std-env/3.3.2: + resolution: {integrity: sha512-uUZI65yrV2Qva5gqE0+A7uVAvO40iPo6jGhs7s8keRfHCmtg+uB2X6EiLGCI9IgL1J17xGhvoOqSz79lzICPTA==} + dev: true + /stream-combiner/0.0.4: resolution: {integrity: sha512-rT00SPnTVyRsaSz5zgSPma/aHSOic5U1prhYdRy5HS2kTZviFpmDgzilbtsJsxiroqACmayynDN/9VzIbX5DOw==} dependencies: @@ -11231,7 +11296,7 @@ packages: hasBin: true dependencies: '@jridgewell/source-map': 0.3.2 - acorn: 8.8.0 + acorn: 8.8.1 commander: 2.20.3 source-map-support: 0.5.21 dev: true @@ -11280,8 +11345,8 @@ packages: resolution: {integrity: sha512-hGYWYBMPr7p4g5IarQE7XhlyWveh1EKhy4wUBS1LrHXCKYgvz+4/jCqgmJqZxxldesn05vccrtME2RLLZNW7iA==} dev: true - /tinypool/0.3.0: - resolution: {integrity: sha512-NX5KeqHOBZU6Bc0xj9Vr5Szbb1j8tUHIeD18s41aDJaPeC5QTdEhK0SpdpUrZlj2nv5cctNcSjaKNanXlfcVEQ==} + /tinypool/0.3.1: + resolution: {integrity: sha512-zLA1ZXlstbU2rlpA4CIeVaqvWq41MTWqLY3FfsAXgC8+f7Pk7zroaJQxDgxn1xNudKW6Kmj4808rPFShUlIRmQ==} engines: {node: '>=14.0.0'} dev: true @@ -11767,15 +11832,15 @@ packages: vfile-message: 3.1.2 dev: true - /vite-node/0.27.1_@types+node@18.11.9: - resolution: {integrity: sha512-d6+ue/3NzsfndWaPbYh/bFkHbmAWfDXI4B874zRx+WREnG6CUHUbBC8lKaRYZjeR6gCPN5m1aVNNRXBYICA9XA==} + /vite-node/0.28.4_@types+node@18.11.9: + resolution: {integrity: sha512-KM0Q0uSG/xHHKOJvVHc5xDBabgt0l70y7/lWTR7Q0pR5/MrYxadT+y32cJOE65FfjGmJgxpVEEY+69btJgcXOQ==} engines: {node: '>=v14.16.0'} hasBin: true dependencies: cac: 6.7.14 debug: 4.3.4 mlly: 1.1.0 - pathe: 0.2.0 + pathe: 1.1.0 picocolors: 1.0.0 source-map: 0.6.1 source-map-support: 0.5.21 @@ -11969,8 +12034,8 @@ packages: - terser dev: true - /vitest/0.27.1_6vhkb7zox2ro6wmx3rlvm5i5ce: - resolution: {integrity: sha512-1sIpQ1DVFTEn7c1ici1XHcVfdU4nKiBmPtPAtGKJJJLuJjojTv/OHGgcf69P57alM4ty8V4NMv+7Yoi5Cxqx9g==} + /vitest/0.28.4_vun5xzxu3tkrssf3erdbijyyki: + resolution: {integrity: sha512-sfWIy0AdlbyGRhunm+TLQEJrFH9XuRPdApfubsyLcDbCRrUX717BRQKInTgzEfyl2Ipi1HWoHB84Nqtcwxogcg==} engines: {node: '>=v14.16.0'} hasBin: true peerDependencies: @@ -11994,22 +12059,28 @@ packages: '@types/chai': 4.3.4 '@types/chai-subset': 1.3.3 '@types/node': 18.11.9 - '@vitest/ui': 0.27.1 + '@vitest/expect': 0.28.4 + '@vitest/runner': 0.28.4 + '@vitest/spy': 0.28.4 + '@vitest/ui': 0.28.4 + '@vitest/utils': 0.28.4 acorn: 8.8.1 acorn-walk: 8.2.0 cac: 6.7.14 chai: 4.3.7 debug: 4.3.4 - jsdom: 20.0.2 + jsdom: 21.1.0 local-pkg: 0.4.2 + pathe: 1.1.0 picocolors: 1.0.0 source-map: 0.6.1 + std-env: 3.3.2 strip-literal: 1.0.0 tinybench: 2.3.1 - tinypool: 0.3.0 + tinypool: 0.3.1 tinyspy: 1.0.2 vite: 4.0.1_@types+node@18.11.9 - vite-node: 0.27.1_@types+node@18.11.9 + vite-node: 0.28.4_@types+node@18.11.9 why-is-node-running: 2.2.2 transitivePeerDependencies: - less @@ -12102,6 +12173,13 @@ packages: xml-name-validator: 4.0.0 dev: true + /w3c-xmlserializer/4.0.0: + resolution: {integrity: sha512-d+BFHzbiCx6zGfz0HyQ6Rg69w9k19nviJspaj4yNscGjrHu94sVP+aRm75yEbCh+r2/yR+7q6hux9LVtbuTGBw==} + engines: {node: '>=14'} + dependencies: + xml-name-validator: 4.0.0 + dev: true + /wait-on/6.0.0_debug@4.3.2: resolution: {integrity: sha512-tnUJr9p5r+bEYXPUdRseolmz5XqJTTj98JgOsfBn7Oz2dxfE2g3zw1jE+Mo8lopM3j3et/Mq1yW7kKX6qw7RVw==} engines: {node: '>=10.0.0'} @@ -12451,6 +12529,19 @@ packages: optional: true dev: true + /ws/8.12.0: + resolution: {integrity: sha512-kU62emKIdKVeEIOIKVegvqpXMSTAMLJozpHZaJNDYqBjzlSYXQGviYwN1osDLJ9av68qHd4a2oSjd7yD4pacig==} + engines: {node: '>=10.0.0'} + peerDependencies: + bufferutil: ^4.0.1 + utf-8-validate: '>=5.0.2' + peerDependenciesMeta: + bufferutil: + optional: true + utf-8-validate: + optional: true + dev: true + /ws/8.5.0: resolution: {integrity: sha512-BWX0SWVgLPzYwF8lTzEy1egjhS4S4OEAHfsO8o65WOVsrnSRGaSiUaa9e0ggGlkMTtBlmOpEXiie9RUcBO86qg==} engines: {node: '>=10.0.0'} @@ -12589,6 +12680,11 @@ packages: engines: {node: '>=10'} dev: true + /yocto-queue/1.0.0: + resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} + engines: {node: '>=12.20'} + dev: true + /zwitch/2.0.2: resolution: {integrity: sha512-JZxotl7SxAJH0j7dN4pxsTV6ZLXoLdGME+PsjkL/DaBrVryK9kTGq06GfKrwcSOqypP+fdXGoCHE36b99fWVoA==} dev: true