mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-08-15 22:39:26 +02:00
Compare commits
4 Commits
sidv/remov
...
huynhicode
Author | SHA1 | Date | |
---|---|---|---|
![]() |
a5e0630e21 | ||
![]() |
b30e9620f4 | ||
![]() |
be69f494b7 | ||
![]() |
fd6d1941b7 |
29
.github/workflows/build-docs.yml
vendored
29
.github/workflows/build-docs.yml
vendored
@@ -1,29 +0,0 @@
|
||||
name: Build Vitepress docs
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
# Build job
|
||||
build:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v3
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version: 18
|
||||
|
||||
- name: Install Packages
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Run Build
|
||||
run: pnpm --filter mermaid run docs:build:vitepress
|
20
.github/workflows/e2e-applitools.yml
vendored
20
.github/workflows/e2e-applitools.yml
vendored
@@ -38,8 +38,15 @@ jobs:
|
||||
- name: Setup Node.js ${{ matrix.node-version }}
|
||||
uses: actions/setup-node@v3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version: ${{ matrix.node-version }}
|
||||
|
||||
- name: Install Packages
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
env:
|
||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
||||
|
||||
- if: ${{ env.USE_APPLI }}
|
||||
name: Notify applitools of new batch
|
||||
# Copied from docs https://applitools.com/docs/topics/integrations/github-integration-ci-setup.html
|
||||
@@ -47,22 +54,19 @@ jobs:
|
||||
env:
|
||||
# e.g. mermaid-js/mermaid/my-branch
|
||||
APPLITOOLS_BRANCH: ${{ github.repository }}/${{ github.ref_name }}
|
||||
APPLITOOLS_PARENT_BRANCH: ${{ github.event.inputs.parent_branch }}
|
||||
APPLITOOLS_PARENT_BRANCH: ${{ github.inputs.parent_branch }}
|
||||
APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
|
||||
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
|
||||
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v4
|
||||
id: cypress
|
||||
with:
|
||||
start: pnpm run dev
|
||||
wait-on: 'http://localhost:9000'
|
||||
- name: Run E2E Tests
|
||||
run: pnpm run e2e
|
||||
env:
|
||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
||||
# Mermaid applitools.config.js uses this to pick batch name.
|
||||
APPLI_BRANCH: ${{ github.ref_name }}
|
||||
APPLITOOLS_BATCH_ID: ${{ github.sha }}
|
||||
# e.g. mermaid-js/mermaid/my-branch
|
||||
APPLITOOLS_BRANCH: ${{ github.repository }}/${{ github.ref_name }}
|
||||
APPLITOOLS_PARENT_BRANCH: ${{ github.event.inputs.parent_branch }}
|
||||
APPLITOOLS_PARENT_BRANCH: ${{ github.inputs.parent_branch }}
|
||||
APPLITOOLS_API_KEY: ${{ secrets.APPLITOOLS_API_KEY }}
|
||||
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
|
||||
|
2
.github/workflows/link-checker.yml
vendored
2
.github/workflows/link-checker.yml
vendored
@@ -36,7 +36,7 @@ jobs:
|
||||
restore-keys: cache-lychee-
|
||||
|
||||
- name: Link Checker
|
||||
uses: lycheeverse/lychee-action@v1.7.0
|
||||
uses: lycheeverse/lychee-action@v1.6.1
|
||||
with:
|
||||
args: >-
|
||||
--verbose
|
||||
|
7
.github/workflows/publish-docs.yml
vendored
7
.github/workflows/publish-docs.yml
vendored
@@ -5,6 +5,10 @@ on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
pull_request:
|
||||
|
||||
# Allows you to run this workflow manually from the Actions tab
|
||||
workflow_dispatch:
|
||||
|
||||
# Sets permissions of the GITHUB_TOKEN to allow deployment to GitHub Pages
|
||||
permissions:
|
||||
@@ -49,6 +53,7 @@ jobs:
|
||||
|
||||
# Deployment job
|
||||
deploy:
|
||||
if: ${{ github.event_name == 'push' && github.ref == 'refs/heads/master' }}
|
||||
environment:
|
||||
name: github-pages
|
||||
runs-on: ubuntu-latest
|
||||
@@ -56,4 +61,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v2
|
||||
uses: actions/deploy-pages@v1
|
||||
|
2
.github/workflows/release-publish.yml
vendored
2
.github/workflows/release-publish.yml
vendored
@@ -9,7 +9,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v3
|
||||
- uses: fregante/setup-git-user@v2
|
||||
- uses: fregante/setup-git-user@v1
|
||||
|
||||
- uses: pnpm/action-setup@v2
|
||||
# uses version from "packageManager" field in package.json
|
||||
|
@@ -11,7 +11,6 @@ const visualize = process.argv.includes('--visualize');
|
||||
const watch = process.argv.includes('--watch');
|
||||
const mermaidOnly = process.argv.includes('--mermaid');
|
||||
const __dirname = fileURLToPath(new URL('.', import.meta.url));
|
||||
const sourcemap = false;
|
||||
|
||||
type OutputOptions = Exclude<
|
||||
Exclude<InlineConfig['build'], undefined>['rollupOptions'],
|
||||
@@ -61,15 +60,9 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
||||
{
|
||||
name,
|
||||
format: 'esm',
|
||||
sourcemap,
|
||||
sourcemap: true,
|
||||
entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`,
|
||||
},
|
||||
{
|
||||
name,
|
||||
format: 'umd',
|
||||
sourcemap,
|
||||
entryFileNames: `${name}${minify ? '.min' : ''}.js`,
|
||||
},
|
||||
];
|
||||
|
||||
if (core) {
|
||||
@@ -86,7 +79,7 @@ export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions)
|
||||
{
|
||||
name,
|
||||
format: 'esm',
|
||||
sourcemap,
|
||||
sourcemap: true,
|
||||
entryFileNames: `${name}.core.mjs`,
|
||||
},
|
||||
];
|
||||
|
@@ -65,7 +65,6 @@
|
||||
"mdast",
|
||||
"mdbook",
|
||||
"mermerd",
|
||||
"micromark",
|
||||
"mindaugas",
|
||||
"mindmap",
|
||||
"mindmaps",
|
||||
@@ -84,7 +83,6 @@
|
||||
"rect",
|
||||
"rects",
|
||||
"redmine",
|
||||
"rehype",
|
||||
"roledescription",
|
||||
"sandboxed",
|
||||
"setupgraphviewbox",
|
||||
|
@@ -286,7 +286,7 @@ describe('Class diagram', () => {
|
||||
cy.get('svg');
|
||||
});
|
||||
|
||||
it('15: should render a simple class diagram with css classes applied to multiple classes', () => {
|
||||
it('15: should render a simple class diagram with css classes applied two multiple classes', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
classDiagram
|
||||
|
@@ -38,7 +38,7 @@
|
||||
</pre>
|
||||
|
||||
<script type="module">
|
||||
import mermaid from './mermaid.esm.mjs';
|
||||
import mermaid from '../packages/mermaid/src/mermaid';
|
||||
mermaid.initialize({
|
||||
theme: 'forest',
|
||||
// themeCSS: '.node rect { fill: red; }',
|
||||
|
@@ -96,7 +96,7 @@ mermaid.initialize(config);
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:667](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L667)
|
||||
[mermaidAPI.ts:673](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L673)
|
||||
|
||||
## Functions
|
||||
|
||||
@@ -127,7 +127,7 @@ Return the last node appended
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:306](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L306)
|
||||
[mermaidAPI.ts:312](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L312)
|
||||
|
||||
---
|
||||
|
||||
@@ -153,7 +153,7 @@ the cleaned up svgCode
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:257](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L257)
|
||||
[mermaidAPI.ts:263](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L263)
|
||||
|
||||
---
|
||||
|
||||
@@ -179,7 +179,7 @@ the string with all the user styles
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L186)
|
||||
[mermaidAPI.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L192)
|
||||
|
||||
---
|
||||
|
||||
@@ -202,7 +202,7 @@ the string with all the user styles
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:234](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L234)
|
||||
[mermaidAPI.ts:240](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L240)
|
||||
|
||||
---
|
||||
|
||||
@@ -229,7 +229,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:170](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L170)
|
||||
[mermaidAPI.ts:176](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L176)
|
||||
|
||||
---
|
||||
|
||||
@@ -295,7 +295,7 @@ Put the svgCode into an iFrame. Return the iFrame code
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:285](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L285)
|
||||
[mermaidAPI.ts:291](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L291)
|
||||
|
||||
---
|
||||
|
||||
@@ -320,4 +320,4 @@ Remove any existing elements from the given document
|
||||
|
||||
#### Defined in
|
||||
|
||||
[mermaidAPI.ts:356](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L356)
|
||||
[mermaidAPI.ts:362](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L362)
|
||||
|
@@ -128,7 +128,7 @@ classDiagram
|
||||
Vehicle <|-- Car
|
||||
```
|
||||
|
||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), underscores, and dashes (-).
|
||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), and underscores.
|
||||
|
||||
### Class labels
|
||||
|
||||
@@ -283,12 +283,12 @@ To describe the visibility (or encapsulation) of an attribute or method/function
|
||||
- `#` Protected
|
||||
- `~` Package/Internal
|
||||
|
||||
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type:
|
||||
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()`:
|
||||
>
|
||||
> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*`
|
||||
> - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$`
|
||||
> - `*` Abstract e.g.: `someAbstractMethod()*`
|
||||
> - `$` Static e.g.: `someStaticMethod()$`
|
||||
|
||||
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end:
|
||||
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the end of its name:
|
||||
>
|
||||
> - `$` Static e.g.: `String someField$`
|
||||
|
||||
@@ -604,26 +604,10 @@ You would define these actions on a separate line after all classes have been de
|
||||
|
||||
## Notes
|
||||
|
||||
It is possible to add notes on the diagram using `note "line1\nline2"`. A note can be added for a specific class using `note for <CLASS NAME> "line1\nline2"`.
|
||||
It is possible to add notes on diagram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"`
|
||||
|
||||
### Examples
|
||||
|
||||
```mermaid-example
|
||||
classDiagram
|
||||
note "This is a general note"
|
||||
note for MyClass "This is a note for a class"
|
||||
class MyClass{
|
||||
}
|
||||
```
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
note "This is a general note"
|
||||
note for MyClass "This is a note for a class"
|
||||
class MyClass{
|
||||
}
|
||||
```
|
||||
|
||||
_URL Link:_
|
||||
|
||||
```mermaid-example
|
||||
|
@@ -6,11 +6,10 @@
|
||||
|
||||
# Flowcharts - Basic Syntax
|
||||
|
||||
All Flowcharts are composed of **nodes**, the geometric shapes and **edges**, the arrows or lines. The mermaid code defines the way that these **nodes** and **edges** are made and interact.
|
||||
Flowcharts are composed of **nodes** (geometric shapes) and **edges** (arrows or lines). The Mermaid code defines how nodes and edges are made and accommodates different arrow types, multi-directional arrows, and any linking to and from subgraphs.
|
||||
|
||||
It can also accommodate different arrow types, multi directional arrows, and linking to and from subgraphs.
|
||||
|
||||
> **Important note**: Do not type the word "end" as a Flowchart node. Capitalize all or any one the letters to keep the flowchart from breaking, i.e, "End" or "END". Or you can apply this [workaround](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897).
|
||||
> **Warning**
|
||||
> If you are using the word "end" in a Flowchart node, capitalize the entire word or any of the letters (e.g., "End" or "END"), or apply this [workaround](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897). Typing "end" in all lowercase letters will break the Flowchart.
|
||||
|
||||
### A node (default)
|
||||
|
||||
@@ -55,45 +54,7 @@ flowchart LR
|
||||
id1[This is the text in the box]
|
||||
```
|
||||
|
||||
#### Unicode text
|
||||
|
||||
Use `"` to enclose the unicode text.
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
id["This ❤ Unicode"]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
flowchart LR
|
||||
id["This ❤ Unicode"]
|
||||
```
|
||||
|
||||
#### Markdown formatting
|
||||
|
||||
Use double quotes and backticks "\` text \`" to enclose the markdown text.
|
||||
|
||||
```mermaid-example
|
||||
%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
markdown["`This **is** _Markdown_`"]
|
||||
newLines["`Line1
|
||||
Line 2
|
||||
Line 3`"]
|
||||
markdown --> newLines
|
||||
```
|
||||
|
||||
```mermaid
|
||||
%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
markdown["`This **is** _Markdown_`"]
|
||||
newLines["`Line1
|
||||
Line 2
|
||||
Line 3`"]
|
||||
markdown --> newLines
|
||||
```
|
||||
|
||||
### Direction
|
||||
## Graph
|
||||
|
||||
This statement declares the direction of the Flowchart.
|
||||
|
||||
@@ -121,13 +82,15 @@ flowchart LR
|
||||
Start --> Stop
|
||||
```
|
||||
|
||||
## Flowchart Orientation
|
||||
|
||||
Possible FlowChart orientations are:
|
||||
|
||||
- TB - Top to bottom
|
||||
- TD - Top-down/ same as top to bottom
|
||||
- BT - Bottom to top
|
||||
- RL - Right to left
|
||||
- LR - Left to right
|
||||
- TB - top to bottom
|
||||
- TD - top-down/ same as top to bottom
|
||||
- BT - bottom to top
|
||||
- RL - right to left
|
||||
- LR - left to right
|
||||
|
||||
## Node shapes
|
||||
|
||||
|
80
package.json
80
package.json
@@ -4,7 +4,7 @@
|
||||
"version": "10.1.0",
|
||||
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"packageManager": "pnpm@8.3.1",
|
||||
"packageManager": "pnpm@7.30.1",
|
||||
"keywords": [
|
||||
"diagram",
|
||||
"markdown",
|
||||
@@ -54,65 +54,65 @@
|
||||
]
|
||||
},
|
||||
"devDependencies": {
|
||||
"@applitools/eyes-cypress": "^3.32.0",
|
||||
"@commitlint/cli": "^17.6.1",
|
||||
"@commitlint/config-conventional": "^17.6.1",
|
||||
"@cspell/eslint-plugin": "^6.31.1",
|
||||
"@rollup/plugin-typescript": "^11.1.0",
|
||||
"@applitools/eyes-cypress": "^3.27.6",
|
||||
"@commitlint/cli": "^17.2.0",
|
||||
"@commitlint/config-conventional": "^17.2.0",
|
||||
"@cspell/eslint-plugin": "^6.14.2",
|
||||
"@rollup/plugin-typescript": "^11.0.0",
|
||||
"@types/cors": "^2.8.13",
|
||||
"@types/eslint": "^8.37.0",
|
||||
"@types/eslint": "^8.4.10",
|
||||
"@types/express": "^4.17.17",
|
||||
"@types/js-yaml": "^4.0.5",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
"@types/lodash": "^4.14.194",
|
||||
"@types/mdast": "^3.0.11",
|
||||
"@types/node": "^18.16.0",
|
||||
"@types/prettier": "^2.7.2",
|
||||
"@types/jsdom": "^21.0.0",
|
||||
"@types/lodash": "^4.14.188",
|
||||
"@types/mdast": "^3.0.10",
|
||||
"@types/node": "^18.11.9",
|
||||
"@types/prettier": "^2.7.1",
|
||||
"@types/rollup-plugin-visualizer": "^4.2.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@vitest/coverage-c8": "^0.30.1",
|
||||
"@vitest/spy": "^0.30.1",
|
||||
"@vitest/ui": "^0.30.1",
|
||||
"concurrently": "^8.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.48.2",
|
||||
"@typescript-eslint/parser": "^5.48.2",
|
||||
"@vitest/coverage-c8": "^0.29.0",
|
||||
"@vitest/spy": "^0.29.0",
|
||||
"@vitest/ui": "^0.29.0",
|
||||
"concurrently": "^7.5.0",
|
||||
"cors": "^2.8.5",
|
||||
"coveralls": "^3.1.1",
|
||||
"cypress": "^12.10.0",
|
||||
"cypress": "^12.0.0",
|
||||
"cypress-image-snapshot": "^4.0.1",
|
||||
"esbuild": "^0.17.18",
|
||||
"eslint": "^8.39.0",
|
||||
"eslint-config-prettier": "^8.8.0",
|
||||
"eslint-plugin-cypress": "^2.13.2",
|
||||
"esbuild": "^0.17.0",
|
||||
"eslint": "^8.32.0",
|
||||
"eslint-config-prettier": "^8.6.0",
|
||||
"eslint-plugin-cypress": "^2.12.1",
|
||||
"eslint-plugin-html": "^7.1.0",
|
||||
"eslint-plugin-jest": "^27.2.1",
|
||||
"eslint-plugin-jsdoc": "^43.0.7",
|
||||
"eslint-plugin-jest": "^27.1.5",
|
||||
"eslint-plugin-jsdoc": "^39.6.2",
|
||||
"eslint-plugin-json": "^3.1.0",
|
||||
"eslint-plugin-lodash": "^7.4.0",
|
||||
"eslint-plugin-markdown": "^3.0.0",
|
||||
"eslint-plugin-no-only-tests": "^3.1.0",
|
||||
"eslint-plugin-tsdoc": "^0.2.17",
|
||||
"eslint-plugin-unicorn": "^46.0.0",
|
||||
"eslint-plugin-unicorn": "^45.0.0",
|
||||
"express": "^4.18.2",
|
||||
"globby": "^13.1.4",
|
||||
"husky": "^8.0.3",
|
||||
"jest": "^29.5.0",
|
||||
"globby": "^13.1.2",
|
||||
"husky": "^8.0.2",
|
||||
"jest": "^29.3.1",
|
||||
"jison": "^0.4.18",
|
||||
"js-yaml": "^4.1.0",
|
||||
"jsdom": "^21.1.1",
|
||||
"lint-staged": "^13.2.1",
|
||||
"jsdom": "^21.0.0",
|
||||
"lint-staged": "^13.0.3",
|
||||
"path-browserify": "^1.0.1",
|
||||
"pnpm": "^8.3.1",
|
||||
"prettier": "^2.8.8",
|
||||
"pnpm": "^7.15.0",
|
||||
"prettier": "^2.7.1",
|
||||
"prettier-plugin-jsdoc": "^0.4.2",
|
||||
"rimraf": "^5.0.0",
|
||||
"rollup-plugin-visualizer": "^5.9.0",
|
||||
"start-server-and-test": "^2.0.0",
|
||||
"rimraf": "^4.0.0",
|
||||
"rollup-plugin-visualizer": "^5.8.3",
|
||||
"start-server-and-test": "^1.15.4",
|
||||
"ts-node": "^10.9.1",
|
||||
"typescript": "^5.0.4",
|
||||
"vite": "^4.3.1",
|
||||
"vitest": "^0.30.1"
|
||||
"typescript": "^4.8.4",
|
||||
"vite": "^4.1.1",
|
||||
"vitest": "^0.29.0"
|
||||
},
|
||||
"volta": {
|
||||
"node": "18.16.0"
|
||||
"node": "18.15.0"
|
||||
}
|
||||
}
|
||||
|
@@ -48,8 +48,8 @@
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"concurrently": "^8.0.0",
|
||||
"rimraf": "^5.0.0",
|
||||
"concurrently": "^7.5.0",
|
||||
"rimraf": "^4.0.0",
|
||||
"mermaid": "workspace:*"
|
||||
},
|
||||
"resolutions": {
|
||||
|
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "10.2.0-rc.2",
|
||||
"version": "10.1.0",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
@@ -52,72 +52,64 @@
|
||||
]
|
||||
},
|
||||
"dependencies": {
|
||||
"@braintree/sanitize-url": "^6.0.2",
|
||||
"@braintree/sanitize-url": "^6.0.0",
|
||||
"@khanacademy/simple-markdown": "^0.8.6",
|
||||
"cytoscape": "^3.23.0",
|
||||
"cytoscape-cose-bilkent": "^4.1.0",
|
||||
"cytoscape-fcose": "^2.1.0",
|
||||
"d3": "^7.4.0",
|
||||
"dagre-d3-es": "7.0.10",
|
||||
"dayjs": "^1.11.7",
|
||||
"dompurify": "3.0.2",
|
||||
"dompurify": "2.4.5",
|
||||
"elkjs": "^0.8.2",
|
||||
"khroma": "^2.0.0",
|
||||
"lodash-es": "^4.17.21",
|
||||
"non-layered-tidy-tree-layout": "^2.0.2",
|
||||
"rehype-sanitize": "^5.0.1",
|
||||
"rehype-stringify": "^9.0.3",
|
||||
"remark-breaks": "^3.0.2",
|
||||
"remark-parse": "^10.0.1",
|
||||
"remark-rehype": "^10.1.0",
|
||||
"stylis": "^4.1.3",
|
||||
"stylis": "^4.1.2",
|
||||
"ts-dedent": "^2.2.0",
|
||||
"unified": "^10.1.2",
|
||||
"uuid": "^9.0.0",
|
||||
"web-worker": "^1.2.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@types/cytoscape": "^3.19.9",
|
||||
"@types/d3": "^7.4.0",
|
||||
"@types/dompurify": "^3.0.2",
|
||||
"@types/jsdom": "^21.1.1",
|
||||
"@types/dompurify": "^2.4.0",
|
||||
"@types/jsdom": "^21.0.0",
|
||||
"@types/lodash-es": "^4.17.7",
|
||||
"@types/micromatch": "^4.0.2",
|
||||
"@types/prettier": "^2.7.2",
|
||||
"@types/prettier": "^2.7.1",
|
||||
"@types/stylis": "^4.0.2",
|
||||
"@types/uuid": "^9.0.1",
|
||||
"@typescript-eslint/eslint-plugin": "^5.59.0",
|
||||
"@typescript-eslint/parser": "^5.59.0",
|
||||
"@types/uuid": "^9.0.0",
|
||||
"@typescript-eslint/eslint-plugin": "^5.42.1",
|
||||
"@typescript-eslint/parser": "^5.42.1",
|
||||
"chokidar": "^3.5.3",
|
||||
"concurrently": "^8.0.1",
|
||||
"concurrently": "^7.5.0",
|
||||
"coveralls": "^3.1.1",
|
||||
"cpy-cli": "^4.2.0",
|
||||
"cspell": "^6.31.1",
|
||||
"cspell": "^6.14.3",
|
||||
"csstree-validator": "^3.0.0",
|
||||
"globby": "^13.1.4",
|
||||
"globby": "^13.1.2",
|
||||
"jison": "^0.4.18",
|
||||
"js-base64": "^3.7.5",
|
||||
"jsdom": "^21.1.1",
|
||||
"js-base64": "^3.7.2",
|
||||
"jsdom": "^21.0.0",
|
||||
"micromatch": "^4.0.5",
|
||||
"path-browserify": "^1.0.1",
|
||||
"prettier": "^2.8.8",
|
||||
"prettier": "^2.7.1",
|
||||
"remark": "^14.0.2",
|
||||
"remark-frontmatter": "^4.0.1",
|
||||
"remark-gfm": "^3.0.1",
|
||||
"rimraf": "^5.0.0",
|
||||
"start-server-and-test": "^2.0.0",
|
||||
"typedoc": "^0.24.5",
|
||||
"typedoc-plugin-markdown": "^3.15.2",
|
||||
"typescript": "^5.0.4",
|
||||
"rimraf": "^4.0.0",
|
||||
"start-server-and-test": "^1.14.0",
|
||||
"typedoc": "^0.23.18",
|
||||
"typedoc-plugin-markdown": "^3.13.6",
|
||||
"typescript": "^4.8.4",
|
||||
"unist-util-flatmap": "^1.0.0",
|
||||
"vitepress": "^1.0.0-alpha.72",
|
||||
"vitepress-plugin-search": "^1.0.4-alpha.20"
|
||||
"vitepress": "^1.0.0-alpha.46",
|
||||
"vitepress-plugin-search": "^1.0.4-alpha.19"
|
||||
},
|
||||
"files": [
|
||||
"dist/",
|
||||
"dist",
|
||||
"README.md"
|
||||
],
|
||||
"sideEffects": false,
|
||||
"publishConfig": {
|
||||
"access": "public"
|
||||
}
|
||||
"sideEffects": false
|
||||
}
|
||||
|
@@ -106,7 +106,6 @@ export const clear = function () {
|
||||
export const getClass = function (id: string) {
|
||||
return classes[id];
|
||||
};
|
||||
|
||||
export const getClasses = function () {
|
||||
return classes;
|
||||
};
|
||||
@@ -171,10 +170,9 @@ export const addMember = function (className: string, member: string) {
|
||||
const memberString = member.trim();
|
||||
|
||||
if (memberString.startsWith('<<') && memberString.endsWith('>>')) {
|
||||
// its an annotation
|
||||
// Remove leading and trailing brackets
|
||||
theClass.annotations.push(sanitizeText(memberString.substring(2, memberString.length - 2)));
|
||||
} else if (memberString.indexOf(')') > 0) {
|
||||
//its a method
|
||||
theClass.methods.push(sanitizeText(memberString));
|
||||
} else if (memberString) {
|
||||
theClass.members.push(sanitizeText(memberString));
|
||||
@@ -236,7 +234,6 @@ const setTooltip = function (ids: string, tooltip?: string) {
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
export const getTooltip = function (id: string) {
|
||||
return classes[id].tooltip;
|
||||
};
|
||||
|
File diff suppressed because it is too large
Load Diff
@@ -0,0 +1,13 @@
|
||||
// eslint-disable-next-line @typescript-eslint/no-var-requires
|
||||
const fs = require('fs');
|
||||
|
||||
import { LALRGenerator } from 'jison';
|
||||
|
||||
describe('class diagram grammar', function () {
|
||||
it('should introduce no new conflicts', function () {
|
||||
const file = require.resolve('./parser/classDiagram.jison');
|
||||
const grammarSource = fs.readFileSync(file, 'utf8');
|
||||
const grammarParser = new LALRGenerator(grammarSource, {});
|
||||
expect(grammarParser.conflicts < 16).toBe(true);
|
||||
});
|
||||
});
|
@@ -1,16 +0,0 @@
|
||||
import { readFile } from 'node:fs/promises';
|
||||
// @ts-ignore - no types
|
||||
import { LALRGenerator } from 'jison';
|
||||
import path from 'path';
|
||||
|
||||
const getAbsolutePath = (relativePath: string) => {
|
||||
return new URL(path.join(path.dirname(import.meta.url), relativePath)).pathname;
|
||||
};
|
||||
|
||||
describe('class diagram grammar', function () {
|
||||
it('should have no conflicts', async function () {
|
||||
const grammarSource = await readFile(getAbsolutePath('./parser/classDiagram.jison'), 'utf8');
|
||||
const grammarParser = new LALRGenerator(grammarSource, {});
|
||||
expect(grammarParser.conflicts).toBe(0);
|
||||
});
|
||||
});
|
@@ -1,78 +0,0 @@
|
||||
import { setConfig } from '../../config.js';
|
||||
import classDB from './classDb.js';
|
||||
// @ts-ignore - no types in jison
|
||||
import classDiagram from './parser/classDiagram.jison';
|
||||
|
||||
setConfig({
|
||||
securityLevel: 'strict',
|
||||
});
|
||||
|
||||
describe('when parsing class diagram', function () {
|
||||
beforeEach(function () {
|
||||
classDiagram.parser.yy = classDB;
|
||||
classDiagram.parser.yy.clear();
|
||||
});
|
||||
|
||||
it('should parse diagram with direction', () => {
|
||||
classDiagram.parser.parse(`classDiagram
|
||||
direction TB
|
||||
class Student {
|
||||
-idCard : IdCard
|
||||
}
|
||||
class IdCard{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
class Bike{
|
||||
-id : int
|
||||
-name : string
|
||||
}
|
||||
Student "1" --o "1" IdCard : carries
|
||||
Student "1" --o "1" Bike : rides`);
|
||||
|
||||
expect(Object.keys(classDB.getClasses()).length).toBe(3);
|
||||
expect(classDB.getClasses().Student).toMatchInlineSnapshot(`
|
||||
{
|
||||
"annotations": [],
|
||||
"cssClasses": [],
|
||||
"domId": "classId-Student-0",
|
||||
"id": "Student",
|
||||
"label": "Student",
|
||||
"members": [
|
||||
"-idCard : IdCard",
|
||||
],
|
||||
"methods": [],
|
||||
"type": "",
|
||||
}
|
||||
`);
|
||||
expect(classDB.getRelations().length).toBe(2);
|
||||
expect(classDB.getRelations()).toMatchInlineSnapshot(`
|
||||
[
|
||||
{
|
||||
"id1": "Student",
|
||||
"id2": "IdCard",
|
||||
"relation": {
|
||||
"lineType": 0,
|
||||
"type1": "none",
|
||||
"type2": 0,
|
||||
},
|
||||
"relationTitle1": "1",
|
||||
"relationTitle2": "1",
|
||||
"title": "carries",
|
||||
},
|
||||
{
|
||||
"id1": "Student",
|
||||
"id2": "Bike",
|
||||
"relation": {
|
||||
"lineType": 0,
|
||||
"type1": "none",
|
||||
"type2": 0,
|
||||
},
|
||||
"relationTitle1": "1",
|
||||
"relationTitle2": "1",
|
||||
"title": "rides",
|
||||
},
|
||||
]
|
||||
`);
|
||||
});
|
||||
});
|
@@ -200,8 +200,9 @@ Function arguments are optional: 'call <callback_name>()' simply executes 'callb
|
||||
|
||||
start
|
||||
: mermaidDoc
|
||||
| statments
|
||||
| direction
|
||||
| directive start
|
||||
| statements
|
||||
;
|
||||
|
||||
direction
|
||||
@@ -258,8 +259,8 @@ className
|
||||
: alphaNumToken { $$=$1; }
|
||||
| classLiteralName { $$=$1; }
|
||||
| alphaNumToken className { $$=$1+$2; }
|
||||
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2+'~'; }
|
||||
| classLiteralName GENERICTYPE { $$=$1+'~'+$2+'~'; }
|
||||
| alphaNumToken GENERICTYPE { $$=$1+'~'+$2; }
|
||||
| classLiteralName GENERICTYPE { $$=$1+'~'+$2; }
|
||||
;
|
||||
|
||||
statement
|
||||
@@ -271,6 +272,7 @@ statement
|
||||
| clickStatement
|
||||
| cssClassStatement
|
||||
| noteStatement
|
||||
| directive
|
||||
| direction
|
||||
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
|
||||
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
||||
@@ -364,7 +366,7 @@ textToken : textNoTagsToken | TAGSTART | TAGEND | '==' | '--' | PCT | DEFA
|
||||
|
||||
textNoTagsToken: alphaNumToken | SPACE | MINUS | keywords ;
|
||||
|
||||
alphaNumToken : UNICODE_TEXT | NUM | ALPHA | MINUS;
|
||||
alphaNumToken : UNICODE_TEXT | NUM | ALPHA;
|
||||
|
||||
classLiteralName : BQUOTE_STR;
|
||||
|
||||
|
@@ -199,7 +199,11 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
||||
isFirst = false;
|
||||
});
|
||||
|
||||
let classTitleString = getClassTitleString(classDef);
|
||||
let classTitleString = classDef.id;
|
||||
|
||||
if (classDef.type !== undefined && classDef.type !== '') {
|
||||
classTitleString += '<' + classDef.type + '>';
|
||||
}
|
||||
|
||||
const classTitle = title.append('tspan').text(classTitleString).attr('class', 'title');
|
||||
|
||||
@@ -287,16 +291,6 @@ export const drawClass = function (elem, classDef, conf, diagObj) {
|
||||
return classInfo;
|
||||
};
|
||||
|
||||
export const getClassTitleString = function (classDef) {
|
||||
let classTitleString = classDef.id;
|
||||
|
||||
if (classDef.type) {
|
||||
classTitleString += '<' + classDef.type + '>';
|
||||
}
|
||||
|
||||
return classTitleString;
|
||||
};
|
||||
|
||||
/**
|
||||
* Renders a note diagram
|
||||
*
|
||||
@@ -361,9 +355,6 @@ export const drawNote = function (elem, note, conf, diagObj) {
|
||||
};
|
||||
|
||||
export const parseMember = function (text) {
|
||||
// Note: these two regular expressions don't parse the official UML syntax for attributes
|
||||
// and methods. They parse a Java-style syntax of the form
|
||||
// "String name" (for attributes) and "String name(int x)" for methods
|
||||
const fieldRegEx = /^([#+~-])?(\w+)(~\w+~|\[])?\s+(\w+) *([$*])?$/;
|
||||
const methodRegEx = /^([#+|~-])?(\w+) *\( *(.*)\) *([$*])? *(\w*[[\]|~]*\s*\w*~?)$/;
|
||||
|
||||
@@ -430,48 +421,33 @@ const buildLegacyDisplay = function (text) {
|
||||
let displayText = '';
|
||||
let cssStyle = '';
|
||||
let returnType = '';
|
||||
|
||||
let visibility = '';
|
||||
let firstChar = text.substring(0, 1);
|
||||
let lastChar = text.substring(text.length - 1, text.length);
|
||||
|
||||
if (firstChar.match(/[#+~-]/)) {
|
||||
visibility = firstChar;
|
||||
}
|
||||
|
||||
let noClassifierRe = /[\s\w)~]/;
|
||||
if (!lastChar.match(noClassifierRe)) {
|
||||
cssStyle = parseClassifier(lastChar);
|
||||
}
|
||||
|
||||
let startIndex = visibility === '' ? 0 : 1;
|
||||
let endIndex = cssStyle === '' ? text.length : text.length - 1;
|
||||
text = text.substring(startIndex, endIndex);
|
||||
|
||||
let methodStart = text.indexOf('(');
|
||||
let methodEnd = text.indexOf(')');
|
||||
|
||||
if (methodStart > 1 && methodEnd > methodStart && methodEnd <= text.length) {
|
||||
let methodName = text.substring(0, methodStart).trim();
|
||||
let visibility = '';
|
||||
let methodName = '';
|
||||
|
||||
let firstChar = text.substring(0, 1);
|
||||
if (firstChar.match(/\w/)) {
|
||||
methodName = text.substring(0, methodStart).trim();
|
||||
} else {
|
||||
if (firstChar.match(/[#+~-]/)) {
|
||||
visibility = firstChar;
|
||||
}
|
||||
|
||||
methodName = text.substring(1, methodStart).trim();
|
||||
}
|
||||
|
||||
const parameters = text.substring(methodStart + 1, methodEnd);
|
||||
const classifier = text.substring(methodEnd + 1, 1);
|
||||
cssStyle = parseClassifier(text.substring(methodEnd + 1, methodEnd + 2));
|
||||
|
||||
displayText = visibility + methodName + '(' + parseGenericTypes(parameters.trim()) + ')';
|
||||
|
||||
if (methodEnd < text.length) {
|
||||
// special case: classifier after the closing parenthesis
|
||||
let potentialClassifier = text.substring(methodEnd + 1, methodEnd + 2);
|
||||
if (cssStyle === '' && !potentialClassifier.match(noClassifierRe)) {
|
||||
cssStyle = parseClassifier(potentialClassifier);
|
||||
returnType = text.substring(methodEnd + 2).trim();
|
||||
} else {
|
||||
returnType = text.substring(methodEnd + 1).trim();
|
||||
}
|
||||
|
||||
returnType = text.substring(methodEnd + 2).trim();
|
||||
if (returnType !== '') {
|
||||
if (returnType.charAt(0) === ':') {
|
||||
returnType = returnType.substring(1).trim();
|
||||
}
|
||||
returnType = ' : ' + parseGenericTypes(returnType);
|
||||
displayText += returnType;
|
||||
}
|
||||
@@ -526,7 +502,6 @@ const parseClassifier = function (classifier) {
|
||||
};
|
||||
|
||||
export default {
|
||||
getClassTitleString,
|
||||
drawClass,
|
||||
drawEdge,
|
||||
drawNote,
|
||||
|
@@ -1,19 +1,8 @@
|
||||
import svgDraw from './svgDraw.js';
|
||||
|
||||
describe('given a string representing class method, ', function () {
|
||||
it('should handle class names with generics', function () {
|
||||
const classDef = {
|
||||
id: 'Car',
|
||||
type: 'T',
|
||||
label: 'Car',
|
||||
};
|
||||
|
||||
let actual = svgDraw.getClassTitleString(classDef);
|
||||
expect(actual).toBe('Car<T>');
|
||||
});
|
||||
|
||||
describe('when parsing base method declaration', function () {
|
||||
it('should handle simple declaration', function () {
|
||||
describe('class member Renderer, ', function () {
|
||||
describe('when parsing text to build method display string', function () {
|
||||
it('should handle simple method declaration', function () {
|
||||
const str = 'foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -21,105 +10,7 @@ describe('given a string representing class method, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with parameters', function () {
|
||||
const str = 'foo(int id)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(int id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with multiple parameters', function () {
|
||||
const str = 'foo(int id, object thing)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(int id, object thing)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with single item in parameters', function () {
|
||||
const str = 'foo(id)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with single item in parameters with extra spaces', function () {
|
||||
const str = ' foo ( id) ';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with generic parameter', function () {
|
||||
const str = 'foo(List~int~)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(List<int>)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with normal and generic parameter', function () {
|
||||
const str = 'foo(int, List~int~)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(int, List<int>)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with return value', function () {
|
||||
const str = 'foo(id) int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with colon return value', function () {
|
||||
const str = 'foo(id) : int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with generic return value', function () {
|
||||
const str = 'foo(id) List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle declaration with colon generic return value', function () {
|
||||
const str = 'foo(id) : List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with all possible markup', function () {
|
||||
const str = '+foo ( List~int~ ids )* List~Item~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo(List<int> ids) : List<Item>');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle method declaration with nested generics', function () {
|
||||
const str = '+foo ( List~List~int~~ ids )* List~List~Item~~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo(List<List<int>> ids) : List<List<Item>>');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing method visibility', function () {
|
||||
it('should correctly handle public', function () {
|
||||
it('should handle public visibility', function () {
|
||||
const str = '+foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -127,7 +18,7 @@ describe('given a string representing class method, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should correctly handle private', function () {
|
||||
it('should handle private visibility', function () {
|
||||
const str = '-foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -135,7 +26,7 @@ describe('given a string representing class method, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should correctly handle protected', function () {
|
||||
it('should handle protected visibility', function () {
|
||||
const str = '#foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -143,17 +34,23 @@ describe('given a string representing class method, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should correctly handle package/internal', function () {
|
||||
it('should handle package/internal visibility', function () {
|
||||
const str = '~foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('~foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing method classifier', function () {
|
||||
it('should handle abstract method', function () {
|
||||
it('should ignore unknown character for visibility', function () {
|
||||
const str = '!foo()';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle abstract method classifier', function () {
|
||||
const str = 'foo()*';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -161,22 +58,6 @@ describe('given a string representing class method, ', function () {
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle abstract method with return type', function () {
|
||||
const str = 'foo(name: String) int*';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle abstract method classifier after parenthesis with return type', function () {
|
||||
const str = 'foo(name: String)* int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier', function () {
|
||||
const str = 'foo()$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
@@ -185,30 +66,6 @@ describe('given a string representing class method, ', function () {
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier with return type', function () {
|
||||
const str = 'foo(name: String) int$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier with colon and return type', function () {
|
||||
const str = 'foo(name: String): int$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static method classifier after parenthesis with return type', function () {
|
||||
const str = 'foo(name: String)$ int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(name: String) : int');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should ignore unknown character for classifier', function () {
|
||||
const str = 'foo()!';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
@@ -216,36 +73,82 @@ describe('given a string representing class method, ', function () {
|
||||
expect(actual.displayText).toBe('foo()');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with parameters', function () {
|
||||
const str = 'foo(int id)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(int id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with multiple parameters', function () {
|
||||
const str = 'foo(int id, object thing)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(int id, object thing)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with single item in parameters', function () {
|
||||
const str = 'foo(id)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle simple method declaration with single item in parameters with extra spaces', function () {
|
||||
const str = ' foo ( id) ';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with return value', function () {
|
||||
const str = 'foo(id) int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with generic return value', function () {
|
||||
const str = 'foo(id) List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(id) : List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with generic parameter', function () {
|
||||
const str = 'foo(List~int~)';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo(List<int>)');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle method declaration with all possible markup', function () {
|
||||
const str = '+foo ( List~int~ ids )* List~Item~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo(List<int> ids) : List<Item>');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
|
||||
it('should handle method declaration with nested markup', function () {
|
||||
const str = '+foo ( List~List~int~~ ids )* List~List~Item~~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('+foo(List<List<int>> ids) : List<List<Item>>');
|
||||
expect(actual.cssStyle).toBe('font-style:italic;');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
describe('given a string representing class member, ', function () {
|
||||
describe('when parsing member declaration', function () {
|
||||
it('should handle simple field', function () {
|
||||
const str = 'id';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('id');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with type', function () {
|
||||
const str = 'int id';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('int id');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with type (name first)', function () {
|
||||
const str = 'id: int';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('id: int');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle array field', function () {
|
||||
describe('when parsing text to build field display string', function () {
|
||||
it('should handle simple field declaration', function () {
|
||||
const str = 'int[] ids';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -253,15 +156,7 @@ describe('given a string representing class member, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle array field (name first)', function () {
|
||||
const str = 'ids: int[]';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('ids: int[]');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with generic type', function () {
|
||||
it('should handle field declaration with generic type', function () {
|
||||
const str = 'List~int~ ids';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
@@ -269,62 +164,12 @@ describe('given a string representing class member, ', function () {
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with generic type (name first)', function () {
|
||||
const str = 'ids: List~int~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('ids: List<int>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
|
||||
describe('when parsing classifiers', function () {
|
||||
it('should handle static field', function () {
|
||||
it('should handle static field classifier', function () {
|
||||
const str = 'String foo$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('String foo');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static field (name first)', function () {
|
||||
const str = 'foo: String$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo: String');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static field with generic type', function () {
|
||||
const str = 'List~String~ foo$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('List<String> foo');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle static field with generic type (name first)', function () {
|
||||
const str = 'foo: List~String~$';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('foo: List<String>');
|
||||
expect(actual.cssStyle).toBe('text-decoration:underline;');
|
||||
});
|
||||
|
||||
it('should handle field with nested generic type', function () {
|
||||
const str = 'List~List~int~~ idLists';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('List<List<int>> idLists');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
|
||||
it('should handle field with nested generic type (name first)', function () {
|
||||
const str = 'idLists: List~List~int~~';
|
||||
let actual = svgDraw.parseMember(str);
|
||||
|
||||
expect(actual.displayText).toBe('idLists: List<List<int>>');
|
||||
expect(actual.cssStyle).toBe('');
|
||||
});
|
||||
});
|
||||
});
|
||||
|
@@ -1,8 +1,6 @@
|
||||
import DOMPurify from 'dompurify';
|
||||
import { MermaidConfig } from '../../config.type.js';
|
||||
|
||||
export const lineBreakRegex = /<br\s*\/?>/gi;
|
||||
|
||||
/**
|
||||
* Gets the rows of lines in a string
|
||||
*
|
||||
@@ -67,6 +65,8 @@ export const sanitizeTextOrArray = (
|
||||
return a.flat().map((x: string) => sanitizeText(x, config));
|
||||
};
|
||||
|
||||
export const lineBreakRegex = /<br\s*\/?>/gi;
|
||||
|
||||
/**
|
||||
* Whether or not a text has any line breaks
|
||||
*
|
||||
|
@@ -74,7 +74,7 @@ classDiagram
|
||||
Vehicle <|-- Car
|
||||
```
|
||||
|
||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), underscores, and dashes (-).
|
||||
Naming convention: a class name should be composed only of alphanumeric characters (including unicode), and underscores.
|
||||
|
||||
### Class labels
|
||||
|
||||
@@ -171,12 +171,12 @@ To describe the visibility (or encapsulation) of an attribute or method/function
|
||||
- `#` Protected
|
||||
- `~` Package/Internal
|
||||
|
||||
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()` or after the return type:
|
||||
> _note_ you can also include additional _classifiers_ to a method definition by adding the following notation to the _end_ of the method, i.e.: after the `()`:
|
||||
>
|
||||
> - `*` Abstract e.g.: `someAbstractMethod()*` or `someAbstractMethod() int*`
|
||||
> - `$` Static e.g.: `someStaticMethod()$` or `someStaticMethod() String$`
|
||||
> - `*` Abstract e.g.: `someAbstractMethod()*`
|
||||
> - `$` Static e.g.: `someStaticMethod()$`
|
||||
|
||||
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the very end:
|
||||
> _note_ you can also include additional _classifiers_ to a field definition by adding the following notation to the end of its name:
|
||||
>
|
||||
> - `$` Static e.g.: `String someField$`
|
||||
|
||||
@@ -403,18 +403,10 @@ click className href "url" "tooltip"
|
||||
|
||||
## Notes
|
||||
|
||||
It is possible to add notes on the diagram using `note "line1\nline2"`. A note can be added for a specific class using `note for <CLASS NAME> "line1\nline2"`.
|
||||
It is possible to add notes on diagram using `note "line1\nline2"` or note for class using `note for class "line1\nline2"`
|
||||
|
||||
### Examples
|
||||
|
||||
```mermaid
|
||||
classDiagram
|
||||
note "This is a general note"
|
||||
note for MyClass "This is a note for a class"
|
||||
class MyClass{
|
||||
}
|
||||
```
|
||||
|
||||
_URL Link:_
|
||||
|
||||
```mmd
|
||||
|
@@ -5,11 +5,11 @@ outline: 'deep' # shows all h3 headings in outline in Vitepress
|
||||
|
||||
# Flowcharts - Basic Syntax
|
||||
|
||||
All Flowcharts are composed of **nodes**, the geometric shapes and **edges**, the arrows or lines. The mermaid code defines the way that these **nodes** and **edges** are made and interact.
|
||||
Flowcharts are composed of **nodes** (geometric shapes) and **edges** (arrows or lines). The Mermaid code defines how nodes and edges are made and accommodates different arrow types, multi-directional arrows, and any linking to and from subgraphs.
|
||||
|
||||
It can also accommodate different arrow types, multi directional arrows, and linking to and from subgraphs.
|
||||
|
||||
> **Important note**: Do not type the word "end" as a Flowchart node. Capitalize all or any one the letters to keep the flowchart from breaking, i.e, "End" or "END". Or you can apply this [workaround](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897).
|
||||
```warning
|
||||
If you are using the word "end" in a Flowchart node, capitalize the entire word or any of the letters (e.g., "End" or "END"), or apply this [workaround](https://github.com/mermaid-js/mermaid/issues/1444#issuecomment-639528897). Typing "end" in all lowercase letters will break the Flowchart.
|
||||
```
|
||||
|
||||
### A node (default)
|
||||
|
||||
@@ -39,30 +39,7 @@ flowchart LR
|
||||
id1[This is the text in the box]
|
||||
```
|
||||
|
||||
#### Unicode text
|
||||
|
||||
Use `"` to enclose the unicode text.
|
||||
|
||||
```mermaid-example
|
||||
flowchart LR
|
||||
id["This ❤ Unicode"]
|
||||
```
|
||||
|
||||
#### Markdown formatting
|
||||
|
||||
Use double quotes and backticks "\` text \`" to enclose the markdown text.
|
||||
|
||||
```mermaid-example
|
||||
%%{init: {"flowchart": {"htmlLabels": false}} }%%
|
||||
flowchart LR
|
||||
markdown["`This **is** _Markdown_`"]
|
||||
newLines["`Line1
|
||||
Line 2
|
||||
Line 3`"]
|
||||
markdown --> newLines
|
||||
```
|
||||
|
||||
### Direction
|
||||
## Graph
|
||||
|
||||
This statement declares the direction of the Flowchart.
|
||||
|
||||
@@ -80,13 +57,15 @@ flowchart LR
|
||||
Start --> Stop
|
||||
```
|
||||
|
||||
## Flowchart Orientation
|
||||
|
||||
Possible FlowChart orientations are:
|
||||
|
||||
- TB - Top to bottom
|
||||
- TD - Top-down/ same as top to bottom
|
||||
- BT - Bottom to top
|
||||
- RL - Right to left
|
||||
- LR - Left to right
|
||||
- TB - top to bottom
|
||||
- TD - top-down/ same as top to bottom
|
||||
- BT - bottom to top
|
||||
- RL - right to left
|
||||
- LR - left to right
|
||||
|
||||
## Node shapes
|
||||
|
||||
|
@@ -12,24 +12,24 @@ vi.mock('dagre-d3');
|
||||
|
||||
// mermaidAPI.spec.ts:
|
||||
import * as accessibility from './accessibility.js'; // Import it this way so we can use spyOn(accessibility,...)
|
||||
vi.mock('./accessibility.js', () => ({
|
||||
vi.mock('./accessibility', () => ({
|
||||
setA11yDiagramInfo: vi.fn(),
|
||||
addSVGa11yTitleDescription: vi.fn(),
|
||||
}));
|
||||
|
||||
// Mock the renderers specifically so we can test render(). Need to mock draw() for each renderer
|
||||
vi.mock('./diagrams/c4/c4Renderer.js');
|
||||
vi.mock('./diagrams/class/classRenderer.js');
|
||||
vi.mock('./diagrams/class/classRenderer-v2.js');
|
||||
vi.mock('./diagrams/er/erRenderer.js');
|
||||
vi.mock('./diagrams/flowchart/flowRenderer-v2.js');
|
||||
vi.mock('./diagrams/git/gitGraphRenderer.js');
|
||||
vi.mock('./diagrams/gantt/ganttRenderer.js');
|
||||
vi.mock('./diagrams/user-journey/journeyRenderer.js');
|
||||
vi.mock('./diagrams/pie/pieRenderer.js');
|
||||
vi.mock('./diagrams/requirement/requirementRenderer.js');
|
||||
vi.mock('./diagrams/sequence/sequenceRenderer.js');
|
||||
vi.mock('./diagrams/state/stateRenderer-v2.js');
|
||||
vi.mock('./diagrams/c4/c4Renderer');
|
||||
vi.mock('./diagrams/class/classRenderer');
|
||||
vi.mock('./diagrams/class/classRenderer-v2');
|
||||
vi.mock('./diagrams/er/erRenderer');
|
||||
vi.mock('./diagrams/flowchart/flowRenderer-v2');
|
||||
vi.mock('./diagrams/git/gitGraphRenderer');
|
||||
vi.mock('./diagrams/gantt/ganttRenderer');
|
||||
vi.mock('./diagrams/user-journey/journeyRenderer');
|
||||
vi.mock('./diagrams/pie/pieRenderer');
|
||||
vi.mock('./diagrams/requirement/requirementRenderer');
|
||||
vi.mock('./diagrams/sequence/sequenceRenderer');
|
||||
vi.mock('./diagrams/state/stateRenderer-v2');
|
||||
|
||||
// -------------------------------------
|
||||
|
||||
@@ -52,7 +52,7 @@ import assignWithDepth from './assignWithDepth.js';
|
||||
// --------------
|
||||
// Mocks
|
||||
// To mock a module, first define a mock for it, then (if used explicitly in the tests) import it. Be sure the path points to exactly the same file as is imported in mermaidAPI (the module being tested)
|
||||
vi.mock('./styles.js', () => {
|
||||
vi.mock('./styles', () => {
|
||||
return {
|
||||
addStylesForDiagram: vi.fn(),
|
||||
default: vi.fn().mockReturnValue(' .userStyle { font-weight:bold; }'),
|
||||
|
@@ -154,7 +154,13 @@ export const encodeEntities = function (text: string): string {
|
||||
* @returns
|
||||
*/
|
||||
export const decodeEntities = function (text: string): string {
|
||||
return text.replace(/fl°°/g, '&#').replace(/fl°/g, '&').replace(/¶ß/g, ';');
|
||||
let txt = text;
|
||||
|
||||
txt = txt.replace(/fl°°/g, '&#');
|
||||
txt = txt.replace(/fl°/g, '&');
|
||||
txt = txt.replace(/¶ß/g, ';');
|
||||
|
||||
return txt;
|
||||
};
|
||||
|
||||
// append !important; to each cssClass followed by a final !important, all enclosed in { }
|
||||
|
@@ -1,4 +1,7 @@
|
||||
import { select } from 'd3';
|
||||
import { log } from '../logger.js';
|
||||
import { getConfig } from '../config.js';
|
||||
import { evaluate } from '../diagrams/common/common.js';
|
||||
import { decodeEntities } from '../mermaidAPI.js';
|
||||
import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdown-text.js';
|
||||
/**
|
||||
@@ -204,6 +207,17 @@ export const createText = (
|
||||
return vertexNode;
|
||||
} else {
|
||||
const structuredText = markdownToLines(text);
|
||||
const special = ['"', "'", '.', ',', ':', ';', '!', '?', '(', ')', '[', ']', '{', '}'];
|
||||
let lastWord;
|
||||
structuredText.forEach((line) => {
|
||||
line.forEach((word) => {
|
||||
if (special.includes(word.content) && lastWord) {
|
||||
lastWord.content += word.content;
|
||||
word.content = '';
|
||||
}
|
||||
lastWord = word;
|
||||
});
|
||||
});
|
||||
const svgLabel = createFormattedText(width, el, structuredText, addSvgBackground);
|
||||
return svgLabel;
|
||||
}
|
||||
|
94
packages/mermaid/src/rendering-util/handle-markdown-text.js
Normal file
94
packages/mermaid/src/rendering-util/handle-markdown-text.js
Normal file
@@ -0,0 +1,94 @@
|
||||
import SimpleMarkdown from '@khanacademy/simple-markdown';
|
||||
|
||||
/**
|
||||
*
|
||||
* @param markdown
|
||||
*/
|
||||
function preprocessMarkdown(markdown) {
|
||||
// Replace multiple newlines with a single newline
|
||||
const withoutMultipleNewlines = markdown.replace(/\n{2,}/g, '\n');
|
||||
// Remove extra spaces at the beginning of each line
|
||||
const withoutExtraSpaces = withoutMultipleNewlines.replace(/^\s+/gm, '');
|
||||
return withoutExtraSpaces;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param markdown
|
||||
*/
|
||||
export function markdownToLines(markdown) {
|
||||
const preprocessedMarkdown = preprocessMarkdown(markdown);
|
||||
const mdParse = SimpleMarkdown.defaultBlockParse;
|
||||
const syntaxTree = mdParse(preprocessedMarkdown);
|
||||
|
||||
let lines = [[]];
|
||||
let currentLine = 0;
|
||||
|
||||
/**
|
||||
*
|
||||
* @param node
|
||||
* @param parentType
|
||||
*/
|
||||
function processNode(node, parentType) {
|
||||
if (node.type === 'text') {
|
||||
const textLines = node.content.split('\n');
|
||||
|
||||
textLines.forEach((textLine, index) => {
|
||||
if (index !== 0) {
|
||||
currentLine++;
|
||||
lines.push([]);
|
||||
}
|
||||
|
||||
// textLine.split(/ (?=[^!"#$%&'()*+,-./:;<=>?@[\]^_`{|}~]+)/).forEach((word) => {
|
||||
textLine.split(' ').forEach((word) => {
|
||||
if (word) {
|
||||
lines[currentLine].push({ content: word, type: parentType || 'normal' });
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (node.type === 'strong' || node.type === 'em') {
|
||||
node.content.forEach((contentNode) => {
|
||||
processNode(contentNode, node.type);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
syntaxTree.forEach((treeNode) => {
|
||||
if (treeNode.type === 'paragraph') {
|
||||
treeNode.content.forEach((contentNode) => {
|
||||
processNode(contentNode);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param markdown
|
||||
*/
|
||||
export function markdownToHTML(markdown) {
|
||||
const mdParse = SimpleMarkdown.defaultBlockParse;
|
||||
const syntaxTree = mdParse(markdown);
|
||||
|
||||
/**
|
||||
*
|
||||
* @param node
|
||||
*/
|
||||
function output(node) {
|
||||
if (node.type === 'text') {
|
||||
return node.content.replace(/\n/g, '<br/>');
|
||||
} else if (node.type === 'strong') {
|
||||
return `<strong>${node.content.map(output).join('')}</strong>`;
|
||||
} else if (node.type === 'em') {
|
||||
return `<em>${node.content.map(output).join('')}</em>`;
|
||||
} else if (node.type === 'paragraph') {
|
||||
return `<p>${node.content.map(output).join('')}</p>`;
|
||||
} else {
|
||||
return '';
|
||||
}
|
||||
}
|
||||
|
||||
return syntaxTree.map(output).join('');
|
||||
}
|
@@ -1,5 +1,6 @@
|
||||
import { markdownToLines, markdownToHTML } from './handle-markdown-text.js';
|
||||
import { test, expect } from 'vitest';
|
||||
// import { test } from 'vitest';
|
||||
import { markdownToLines, markdownToHTML } from './handle-markdown-text';
|
||||
import { test } from 'vitest';
|
||||
|
||||
test('markdownToLines - Basic test', () => {
|
||||
const input = `This is regular text
|
||||
@@ -36,9 +37,9 @@ Here is a line *with an italic* section`;
|
||||
{ content: 'is', type: 'normal' },
|
||||
{ content: 'a', type: 'normal' },
|
||||
{ content: 'line', type: 'normal' },
|
||||
{ content: 'with', type: 'emphasis' },
|
||||
{ content: 'an', type: 'emphasis' },
|
||||
{ content: 'italic', type: 'emphasis' },
|
||||
{ content: 'with', type: 'em' },
|
||||
{ content: 'an', type: 'em' },
|
||||
{ content: 'italic', type: 'em' },
|
||||
{ content: 'section', type: 'normal' },
|
||||
],
|
||||
];
|
||||
@@ -116,6 +117,7 @@ test('markdownToLines - paragraph 1', () => {
|
||||
|
||||
test('markdownToLines - paragraph', () => {
|
||||
const input = `**Start** with
|
||||
|
||||
a second line`;
|
||||
|
||||
const expectedOutput = [
|
||||
@@ -142,7 +144,7 @@ test('markdownToLines - Only italic formatting', () => {
|
||||
{ content: 'This', type: 'normal' },
|
||||
{ content: 'is', type: 'normal' },
|
||||
{ content: 'an', type: 'normal' },
|
||||
{ content: 'italic', type: 'emphasis' },
|
||||
{ content: 'italic', type: 'em' },
|
||||
{ content: 'test', type: 'normal' },
|
||||
],
|
||||
];
|
||||
@@ -156,7 +158,7 @@ it('markdownToLines - Mixed formatting', () => {
|
||||
|
||||
const expectedOutput = [
|
||||
[
|
||||
{ content: 'Italic', type: 'emphasis' },
|
||||
{ content: 'Italic', type: 'em' },
|
||||
{ content: 'and', type: 'normal' },
|
||||
{ content: 'bold', type: 'strong' },
|
||||
{ content: 'formatting', type: 'normal' },
|
||||
@@ -177,15 +179,21 @@ Word!`;
|
||||
{ content: 'dog', type: 'normal' },
|
||||
{ content: 'in', type: 'normal' },
|
||||
{ content: 'the', type: 'strong' },
|
||||
{ content: 'hog...', type: 'normal' },
|
||||
{ content: 'hog', type: 'normal' },
|
||||
{ content: '.', type: 'normal' },
|
||||
{ content: '.', type: 'normal' },
|
||||
{ content: '.', type: 'normal' },
|
||||
{ content: 'a', type: 'normal' },
|
||||
{ content: 'very', type: 'emphasis' },
|
||||
{ content: 'long', type: 'emphasis' },
|
||||
{ content: 'text', type: 'emphasis' },
|
||||
{ content: 'very', type: 'em' },
|
||||
{ content: 'long', type: 'em' },
|
||||
{ content: 'text', type: 'em' },
|
||||
{ content: 'about', type: 'normal' },
|
||||
{ content: 'it', type: 'normal' },
|
||||
],
|
||||
[{ content: 'Word!', type: 'normal' }],
|
||||
[
|
||||
{ content: 'Word', type: 'normal' },
|
||||
{ content: '!', type: 'normal' },
|
||||
],
|
||||
];
|
||||
|
||||
const output = markdownToLines(input);
|
||||
@@ -195,32 +203,29 @@ Word!`;
|
||||
test('markdownToHTML - Basic test', () => {
|
||||
const input = `This is regular text
|
||||
Here is a new line
|
||||
|
||||
There is some words **with a bold** section
|
||||
Here is a line *with an italic* section`;
|
||||
|
||||
const expectedOutput = `<p>This is regular text<br/>Here is a new line<br/>There is some words <strong>with a bold</strong> section<br/>Here is a line <em>with an italic</em> section</p>`;
|
||||
|
||||
expect(markdownToHTML(input)).toMatchInlineSnapshot(`
|
||||
"<p>This is regular text<br>
|
||||
Here is a new line</p>
|
||||
<p>There is some words <strong>with a bold</strong> section<br>
|
||||
Here is a line <em>with an italic</em> section</p>"
|
||||
`);
|
||||
const output = markdownToHTML(input);
|
||||
expect(output).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
test('markdownToHTML - Empty input', () => {
|
||||
expect(markdownToHTML('')).toEqual('');
|
||||
const input = '';
|
||||
const expectedOutput = '';
|
||||
const output = markdownToHTML(input);
|
||||
expect(output).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
test('markdownToHTML - No formatting', () => {
|
||||
expect(
|
||||
markdownToHTML(`This is a simple test
|
||||
with no formatting`)
|
||||
).toMatchInlineSnapshot(`
|
||||
"<p>This is a simple test<br>
|
||||
with no formatting</p>"
|
||||
`);
|
||||
const input = `This is a simple test
|
||||
with no formatting`;
|
||||
|
||||
const expectedOutput = `<p>This is a simple test<br/>with no formatting</p>`;
|
||||
const output = markdownToHTML(input);
|
||||
expect(output).toEqual(expectedOutput);
|
||||
});
|
||||
|
||||
test('markdownToHTML - Only bold formatting', () => {
|
||||
@@ -240,27 +245,9 @@ test('markdownToHTML - Only italic formatting', () => {
|
||||
});
|
||||
|
||||
test('markdownToHTML - Mixed formatting', () => {
|
||||
expect(markdownToHTML(`*Italic* and **bold** formatting`)).toMatchInlineSnapshot(
|
||||
'"<p><em>Italic</em> and <strong>bold</strong> formatting</p>"'
|
||||
);
|
||||
const input = `*Italic* and **bold** formatting`;
|
||||
|
||||
expect(markdownToHTML('special characters: `<p>hi</p>`')).toMatchInlineSnapshot(
|
||||
'"<p>special characters: <code><p>hi</p></code></p>"'
|
||||
);
|
||||
});
|
||||
|
||||
test('markdownToHTML - List formatting', () => {
|
||||
expect(
|
||||
markdownToHTML(`Hello
|
||||
- l1
|
||||
- l2
|
||||
- l3`)
|
||||
).toMatchInlineSnapshot(`
|
||||
"<p>Hello</p>
|
||||
<ul>
|
||||
<li>l1</li>
|
||||
<li>l2</li>
|
||||
<li>l3</li>
|
||||
</ul>"
|
||||
`);
|
||||
const expectedOutput = `<p><em>Italic</em> and <strong>bold</strong> formatting</p>`;
|
||||
const output = markdownToHTML(input);
|
||||
expect(output).toEqual(expectedOutput);
|
||||
});
|
@@ -1,58 +0,0 @@
|
||||
import { unified } from 'unified';
|
||||
import remarkParse from 'remark-parse';
|
||||
import remarkBreaks from 'remark-breaks';
|
||||
import remarkRehype from 'remark-rehype';
|
||||
import rehypeSanitize from 'rehype-sanitize';
|
||||
import rehypeStringify from 'rehype-stringify';
|
||||
import type { Content } from 'mdast';
|
||||
|
||||
export function markdownToLines(markdown: string) {
|
||||
const { children } = unified().use(remarkParse).use(remarkBreaks).parse(markdown);
|
||||
const lines: { content: string; type: string }[][] = [[]];
|
||||
let currentLine = 0;
|
||||
|
||||
function processNode(node: Content, parentType?: string) {
|
||||
if (node.type === 'text') {
|
||||
const textLines = node.value.split('\n');
|
||||
textLines.forEach((textLine, index) => {
|
||||
if (index !== 0) {
|
||||
currentLine++;
|
||||
lines.push([]);
|
||||
}
|
||||
textLine.split(' ').forEach((word) => {
|
||||
if (word) {
|
||||
lines[currentLine].push({ content: word, type: parentType || 'normal' });
|
||||
}
|
||||
});
|
||||
});
|
||||
} else if (node.type === 'strong' || node.type === 'emphasis') {
|
||||
node.children.forEach((contentNode) => {
|
||||
processNode(contentNode, node.type);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
children.forEach((treeNode) => {
|
||||
if (treeNode.type === 'paragraph') {
|
||||
treeNode.children.forEach((contentNode) => {
|
||||
processNode(contentNode);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
return lines;
|
||||
}
|
||||
|
||||
export function markdownToHTML(markdown: string): string {
|
||||
return (
|
||||
unified()
|
||||
.use(remarkParse)
|
||||
.use(remarkBreaks)
|
||||
.use(remarkRehype)
|
||||
.use(rehypeSanitize)
|
||||
// @ts-ignore - rehype-stringify types are incorrect
|
||||
.use(rehypeStringify)
|
||||
.processSync(markdown)
|
||||
.toString()
|
||||
);
|
||||
}
|
7762
pnpm-lock.yaml
generated
7762
pnpm-lock.yaml
generated
File diff suppressed because it is too large
Load Diff
@@ -2,6 +2,7 @@
|
||||
"extends": [
|
||||
"config:base",
|
||||
":rebaseStalePrs",
|
||||
"group:allNonMajor",
|
||||
"schedule:earlyMondays",
|
||||
":automergeMinor",
|
||||
":automergeTesters",
|
||||
@@ -13,18 +14,6 @@
|
||||
{
|
||||
"matchUpdateTypes": ["minor", "patch", "digest"],
|
||||
"automerge": true
|
||||
},
|
||||
{
|
||||
"groupName": "all patch dependencies",
|
||||
"groupSlug": "all-patch",
|
||||
"matchPackagePatterns": ["*"],
|
||||
"matchUpdateTypes": ["patch"]
|
||||
},
|
||||
{
|
||||
"groupName": "all minor dependencies",
|
||||
"groupSlug": "all-minor",
|
||||
"matchPackagePatterns": ["*"],
|
||||
"matchUpdateTypes": ["minor"]
|
||||
}
|
||||
],
|
||||
"dependencyDashboard": true,
|
||||
|
@@ -14,17 +14,12 @@ const lint = async (file: string): Promise<boolean> => {
|
||||
console.log(`Linting ${file}`);
|
||||
const jisonCode = await readFile(file, 'utf8');
|
||||
// @ts-ignore no typings
|
||||
const generator = new jison.Generator(jisonCode, { moduleType: 'amd' });
|
||||
const jsCode = generator.generate();
|
||||
const jsCode = new jison.Generator(jisonCode, { moduleType: 'amd' }).generate();
|
||||
const [result] = await linter.lintText(jsCode);
|
||||
if (result.errorCount > 0) {
|
||||
console.error(`Linting failed for ${file}`);
|
||||
console.error(result.messages);
|
||||
}
|
||||
if (generator.conflicts > 0) {
|
||||
console.error(`Linting failed for ${file}. Conflicts found in grammar`);
|
||||
return false;
|
||||
}
|
||||
return result.errorCount === 0;
|
||||
};
|
||||
|
||||
|
Reference in New Issue
Block a user