mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Merge branch 'develop' into 5367/architecture-diagram
This commit is contained in:
commit
eee1be474a
8
.changeset/README.md
Normal file
8
.changeset/README.md
Normal file
@ -0,0 +1,8 @@
|
||||
# Changesets
|
||||
|
||||
Hello and welcome! This folder has been automatically generated by `@changesets/cli`, a build tool that works
|
||||
with multi-package repos, or single-package repos to help you version and publish your code. You can
|
||||
find the full documentation for it [in our repository](https://github.com/changesets/changesets)
|
||||
|
||||
We have a quick list of common questions to get you started engaging with this project in
|
||||
[our documentation](https://github.com/changesets/changesets/blob/main/docs/common-questions.md)
|
12
.changeset/config.json
Normal file
12
.changeset/config.json
Normal file
@ -0,0 +1,12 @@
|
||||
{
|
||||
"$schema": "https://unpkg.com/@changesets/config@3.0.0/schema.json",
|
||||
"changelog": ["@changesets/changelog-github", { "repo": "mermaid-js/mermaid" }],
|
||||
"commit": false,
|
||||
"fixed": [],
|
||||
"linked": [],
|
||||
"access": "public",
|
||||
"baseBranch": "master",
|
||||
"updateInternalDependencies": "patch",
|
||||
"bumpVersionsWithWorkspaceProtocolOnly": true,
|
||||
"ignore": ["@mermaid-js/docs", "@mermaid-js/webpack-test", "@mermaid-js/mermaid-example-diagram"]
|
||||
}
|
5
.changeset/khaki-items-leave.md
Normal file
5
.changeset/khaki-items-leave.md
Normal file
@ -0,0 +1,5 @@
|
||||
---
|
||||
'mermaid': minor
|
||||
---
|
||||
|
||||
feat(er): allow multi-line relationship labels
|
@ -122,6 +122,8 @@ SUBROUTINEEND
|
||||
SUBROUTINESTART
|
||||
Subschemas
|
||||
substr
|
||||
SVGG
|
||||
SVGSVG
|
||||
TAGEND
|
||||
TAGSTART
|
||||
techn
|
||||
@ -138,6 +140,7 @@ tsdoc
|
||||
typeof
|
||||
typestr
|
||||
unshift
|
||||
urlsafe
|
||||
verifymethod
|
||||
VERIFYMTHD
|
||||
WARN_DOCSDIR_DOESNT_MATCH
|
||||
|
@ -59,6 +59,7 @@ rehype
|
||||
roughjs
|
||||
rscratch
|
||||
shiki
|
||||
Slidev
|
||||
sparkline
|
||||
sphinxcontrib
|
||||
ssim
|
||||
|
@ -1,5 +1,6 @@
|
||||
Adamiecki
|
||||
arrowend
|
||||
Bendpoints
|
||||
bmatrix
|
||||
braintree
|
||||
catmull
|
||||
|
@ -4,3 +4,4 @@ handDrawn
|
||||
KOEPF
|
||||
neato
|
||||
newbranch
|
||||
validify
|
||||
|
2
.github/pull_request_template.md
vendored
2
.github/pull_request_template.md
vendored
@ -15,4 +15,4 @@ Make sure you
|
||||
- [ ] :book: have read the [contribution guidelines](https://mermaid.js.org/community/contributing.html)
|
||||
- [ ] :computer: have added necessary unit/e2e tests.
|
||||
- [ ] :notebook: have added documentation. Make sure [`MERMAID_RELEASE_VERSION`](https://mermaid.js.org/community/contributing.html#update-documentation) is used for all new features.
|
||||
- [ ] :bookmark: targeted `develop` branch
|
||||
- [ ] :butterfly: If your PR makes a change that should be noted in one or more packages' changelogs, generate a changeset by running `pnpm changeset` and following the prompts. Changesets that add features should be `minor` and those that fix bugs should be `patch`. Please prefix changeset messages with `feat:`, `fix:`, or `chore:`.
|
||||
|
36
.github/release-drafter.yml
vendored
36
.github/release-drafter.yml
vendored
@ -1,36 +0,0 @@
|
||||
name-template: '$NEXT_PATCH_VERSION'
|
||||
tag-template: '$NEXT_PATCH_VERSION'
|
||||
categories:
|
||||
- title: '🚨 **Breaking Changes**'
|
||||
labels:
|
||||
- 'Breaking Change'
|
||||
- title: '🚀 Features'
|
||||
labels:
|
||||
- 'Type: Enhancement'
|
||||
- 'feature' # deprecated, new PRs shouldn't have this
|
||||
- title: '🐛 Bug Fixes'
|
||||
labels:
|
||||
- 'Type: Bug / Error'
|
||||
- 'fix' # deprecated, new PRs shouldn't have this
|
||||
- title: '🧰 Maintenance'
|
||||
labels:
|
||||
- 'Type: Other'
|
||||
- 'chore' # deprecated, new PRs shouldn't have this
|
||||
- title: '⚡️ Performance'
|
||||
labels:
|
||||
- 'Type: Performance'
|
||||
- title: '📚 Documentation'
|
||||
labels:
|
||||
- 'Area: Documentation'
|
||||
change-template: '- $TITLE (#$NUMBER) @$AUTHOR'
|
||||
sort-by: title
|
||||
sort-direction: ascending
|
||||
exclude-labels:
|
||||
- 'Skip changelog'
|
||||
no-changes-template: 'This release contains minor changes and bugfixes.'
|
||||
template: |
|
||||
# Release Notes
|
||||
|
||||
$CHANGES
|
||||
|
||||
🎉 **Thanks to all contributors helping with this release!** 🎉
|
10
.github/workflows/autofix.yml
vendored
10
.github/workflows/autofix.yml
vendored
@ -7,17 +7,19 @@ on:
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
jobs:
|
||||
autofix:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
# uses version from "packageManager" field in package.json
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version-file: '.node-version'
|
||||
@ -40,4 +42,4 @@ jobs:
|
||||
working-directory: ./packages/mermaid
|
||||
run: pnpm run docs:build
|
||||
|
||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c
|
||||
- uses: autofix-ci/action@ff86a557419858bb967097bfc916833f5647fa8c # main
|
||||
|
8
.github/workflows/build-docs.yml
vendored
8
.github/workflows/build-docs.yml
vendored
@ -8,6 +8,8 @@ on:
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@ -16,12 +18,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version-file: '.node-version'
|
||||
|
49
.github/workflows/build.yml
vendored
49
.github/workflows/build.yml
vendored
@ -1,49 +0,0 @@
|
||||
name: Build
|
||||
|
||||
on:
|
||||
push: {}
|
||||
merge_group:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
build-mermaid:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
# uses version from "packageManager" field in package.json
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version-file: '.node-version'
|
||||
|
||||
- name: Install Packages
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
env:
|
||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
||||
|
||||
- name: Run Build
|
||||
run: pnpm run build
|
||||
|
||||
- name: Upload Mermaid Build as Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mermaid-build
|
||||
path: packages/mermaid/dist
|
||||
|
||||
- name: Upload Mermaid Mindmap Build as Artifact
|
||||
uses: actions/upload-artifact@v4
|
||||
with:
|
||||
name: mermaid-mindmap-build
|
||||
path: packages/mermaid-mindmap/dist
|
2
.github/workflows/check-readme-in-sync.yml
vendored
2
.github/workflows/check-readme-in-sync.yml
vendored
@ -18,7 +18,7 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Check for difference in README.md and docs/README.md
|
||||
run: |
|
||||
|
26
.github/workflows/checks.yml
vendored
26
.github/workflows/checks.yml
vendored
@ -1,26 +0,0 @@
|
||||
on:
|
||||
push:
|
||||
merge_group:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
name: Static analysis on Test files
|
||||
|
||||
jobs:
|
||||
check-tests:
|
||||
runs-on: ubuntu-latest
|
||||
name: check tests
|
||||
if: github.repository_owner == 'mermaid-js'
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
with:
|
||||
fetch-depth: 0
|
||||
- uses: testomatio/check-tests@stable
|
||||
with:
|
||||
framework: cypress
|
||||
tests: './cypress/e2e/**/**.spec.js'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
has-tests-label: true
|
11
.github/workflows/codeql.yml
vendored
11
.github/workflows/codeql.yml
vendored
@ -11,6 +11,9 @@ on:
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
|
||||
permissions: # added using https://github.com/step-security/secure-repo
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
analyze:
|
||||
name: Analyze
|
||||
@ -29,11 +32,11 @@ jobs:
|
||||
|
||||
steps:
|
||||
- name: Checkout repository
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
# Initializes the CodeQL tools for scanning.
|
||||
- name: Initialize CodeQL
|
||||
uses: github/codeql-action/init@v3
|
||||
uses: github/codeql-action/init@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
|
||||
with:
|
||||
config-file: ./.github/codeql/codeql-config.yml
|
||||
languages: ${{ matrix.language }}
|
||||
@ -45,7 +48,7 @@ jobs:
|
||||
# Autobuild attempts to build any compiled languages (C/C++, C#, or Java).
|
||||
# If this step fails, then you should remove it and run the build manually (see below)
|
||||
- name: Autobuild
|
||||
uses: github/codeql-action/autobuild@v3
|
||||
uses: github/codeql-action/autobuild@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
|
||||
|
||||
# ℹ️ Command-line programs to run using the OS shell.
|
||||
# 📚 See https://docs.github.com/en/actions/using-workflows/workflow-syntax-for-github-actions#jobsjob_idstepsrun
|
||||
@ -59,4 +62,4 @@ jobs:
|
||||
# make release
|
||||
|
||||
- name: Perform CodeQL Analysis
|
||||
uses: github/codeql-action/analyze@v3
|
||||
uses: github/codeql-action/analyze@2c779ab0d087cd7fe7b826087247c2c81f27bfa6 # v3.26.5
|
||||
|
4
.github/workflows/dependency-review.yml
vendored
4
.github/workflows/dependency-review.yml
vendored
@ -15,6 +15,6 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: 'Checkout Repository'
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- name: 'Dependency Review'
|
||||
uses: actions/dependency-review-action@v4
|
||||
uses: actions/dependency-review-action@5a2ce3f5b92ee19cbb1541a4984c76d921601d7c # v4.3.4
|
||||
|
10
.github/workflows/e2e-applitools.yml
vendored
10
.github/workflows/e2e-applitools.yml
vendored
@ -11,6 +11,8 @@ on:
|
||||
default: master
|
||||
description: 'Parent branch to use for PRs'
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@ -30,13 +32,13 @@ jobs:
|
||||
run: |
|
||||
echo "::error,title=Not using Applitools::APPLITOOLS_API_KEY is empty, disabling Applitools for this run."
|
||||
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
# uses version from "packageManager" field in package.json
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
||||
@ -52,7 +54,7 @@ jobs:
|
||||
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
|
||||
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v4
|
||||
uses: cypress-io/github-action@d79d2d530a66e641eb4a5f227e13bc985c60b964 # v4.2.2
|
||||
id: cypress
|
||||
with:
|
||||
start: pnpm run dev
|
||||
|
34
.github/workflows/e2e.yml
vendored
34
.github/workflows/e2e.yml
vendored
@ -2,11 +2,15 @@ name: E2E
|
||||
|
||||
on:
|
||||
push:
|
||||
branches-ignore:
|
||||
- 'gh-readonly-queue/**'
|
||||
branches:
|
||||
- develop
|
||||
- master
|
||||
- release/**
|
||||
pull_request:
|
||||
merge_group:
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
@ -32,15 +36,15 @@ jobs:
|
||||
image: cypress/browsers:node-20.11.0-chrome-121.0.6167.85-1-ff-120.0-edge-121.0.2277.83-1
|
||||
options: --user 1001
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
- name: Cache snapshots
|
||||
id: cache-snapshot
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
with:
|
||||
save-always: true
|
||||
path: ./cypress/snapshots
|
||||
@ -49,13 +53,13 @@ jobs:
|
||||
# If a snapshot for a given Hash is not found, we checkout that commit, run the tests and cache the snapshots.
|
||||
- name: Switch to base branch
|
||||
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
ref: ${{ env.targetHash }}
|
||||
|
||||
- name: Install dependencies
|
||||
if: ${{ steps.cache-snapshot.outputs.cache-hit != 'true' }}
|
||||
uses: cypress-io/github-action@v6
|
||||
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
|
||||
with:
|
||||
# just perform install
|
||||
runTests: false
|
||||
@ -78,26 +82,26 @@ jobs:
|
||||
matrix:
|
||||
containers: [1, 2, 3, 4]
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
# uses version from "packageManager" field in package.json
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
node-version-file: '.node-version'
|
||||
|
||||
# These cached snapshots are downloaded, providing the reference snapshots.
|
||||
- name: Cache snapshots
|
||||
id: cache-snapshot
|
||||
uses: actions/cache/restore@v4
|
||||
uses: actions/cache/restore@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
with:
|
||||
path: ./cypress/snapshots
|
||||
key: ${{ runner.os }}-snapshots-${{ env.targetHash }}
|
||||
|
||||
- name: Install dependencies
|
||||
uses: cypress-io/github-action@v6
|
||||
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
|
||||
with:
|
||||
runTests: false
|
||||
|
||||
@ -113,7 +117,7 @@ jobs:
|
||||
# Install NPM dependencies, cache them correctly
|
||||
# and run all Cypress tests
|
||||
- name: Cypress run
|
||||
uses: cypress-io/github-action@v6
|
||||
uses: cypress-io/github-action@df7484c5ba85def7eef30db301afa688187bc378 # v6.7.2
|
||||
id: cypress
|
||||
# If CYPRESS_RECORD_KEY is set, run in parallel on all containers
|
||||
# Otherwise (e.g. if running from fork), we run on a single container only
|
||||
@ -137,7 +141,7 @@ jobs:
|
||||
ARGOS_PARALLEL_INDEX: ${{ matrix.containers }}
|
||||
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
|
||||
# Run step only pushes to develop and pull_requests
|
||||
if: ${{ steps.cypress.conclusion == 'success' && (github.event_name == 'pull_request' || github.ref == 'refs/heads/develop')}}
|
||||
with:
|
||||
|
8
.github/workflows/issue-triage.yml
vendored
8
.github/workflows/issue-triage.yml
vendored
@ -4,11 +4,17 @@ on:
|
||||
issues:
|
||||
types: [opened]
|
||||
|
||||
permissions: # added using https://github.com/step-security/secure-repo
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
triage:
|
||||
permissions:
|
||||
issues: write # for andymckay/labeler to label issues
|
||||
pull-requests: write # for andymckay/labeler to label PRs
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: andymckay/labeler@1.0.4
|
||||
- uses: andymckay/labeler@e6c4322d0397f3240f0e7e30a33b5c5df2d39e90 # 1.0.4
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
add-labels: 'Status: Triage'
|
||||
|
9
.github/workflows/link-checker.yml
vendored
9
.github/workflows/link-checker.yml
vendored
@ -19,6 +19,9 @@ on:
|
||||
# * is a special character in YAML so you have to quote this string
|
||||
- cron: '30 8 * * *'
|
||||
|
||||
permissions: # added using https://github.com/step-security/secure-repo
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
link-checker:
|
||||
runs-on: ubuntu-latest
|
||||
@ -26,17 +29,17 @@ jobs:
|
||||
# lychee only uses the GITHUB_TOKEN to avoid rate-limiting
|
||||
contents: read
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- name: Restore lychee cache
|
||||
uses: actions/cache@v4
|
||||
uses: actions/cache@0c45773b623bea8c8e75f6c82b208c3cf94ea4f9 # v4.0.2
|
||||
with:
|
||||
path: .lycheecache
|
||||
key: cache-lychee-${{ github.sha }}
|
||||
restore-keys: cache-lychee-
|
||||
|
||||
- name: Link Checker
|
||||
uses: lycheeverse/lychee-action@v1.9.3
|
||||
uses: lycheeverse/lychee-action@c053181aa0c3d17606addfe97a9075a32723548a # v1.9.3
|
||||
with:
|
||||
args: >-
|
||||
--config .github/lychee.toml
|
||||
|
27
.github/workflows/lint.yml
vendored
27
.github/workflows/lint.yml
vendored
@ -4,26 +4,32 @@ on:
|
||||
push:
|
||||
merge_group:
|
||||
pull_request:
|
||||
types:
|
||||
- opened
|
||||
- synchronize
|
||||
- ready_for_review
|
||||
workflow_dispatch:
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
permissions:
|
||||
contents: write
|
||||
|
||||
jobs:
|
||||
docker-lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- uses: hadolint/hadolint-action@54c9adbab1582c2ef04b2016b760714a4bfde3cf # v3.1.0
|
||||
with:
|
||||
verbose: true
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
# uses version from "packageManager" field in package.json
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version-file: '.node-version'
|
||||
@ -82,3 +88,10 @@ jobs:
|
||||
working-directory: ./packages/mermaid
|
||||
continue-on-error: ${{ github.event_name == 'push' }}
|
||||
run: pnpm run docs:verify
|
||||
|
||||
- uses: testomatio/check-tests@0ea638fcec1820cf2e7b9854fdbdd04128a55bd4 # stable
|
||||
with:
|
||||
framework: cypress
|
||||
tests: './cypress/e2e/**/**.spec.js'
|
||||
token: ${{ secrets.GITHUB_TOKEN }}
|
||||
has-tests-label: true
|
||||
|
2
.github/workflows/pr-labeler.yml
vendored
2
.github/workflows/pr-labeler.yml
vendored
@ -22,7 +22,7 @@ jobs:
|
||||
pull-requests: write # write permission is required to label PRs
|
||||
steps:
|
||||
- name: Label PR
|
||||
uses: release-drafter/release-drafter@v6
|
||||
uses: release-drafter/release-drafter@3f0f87098bd6b5c5b9a36d49c41d998ea58f9348 # v6.0.0
|
||||
with:
|
||||
config-name: pr-labeler.yml
|
||||
disable-autolabeler: false
|
||||
|
12
.github/workflows/publish-docs.yml
vendored
12
.github/workflows/publish-docs.yml
vendored
@ -23,12 +23,12 @@ jobs:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout
|
||||
uses: actions/checkout@v4
|
||||
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version-file: '.node-version'
|
||||
@ -37,13 +37,13 @@ jobs:
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Setup Pages
|
||||
uses: actions/configure-pages@v4
|
||||
uses: actions/configure-pages@1f0c5cde4bc74cd7e1254d0cb4de8d49e9068c7d # v4.0.0
|
||||
|
||||
- name: Run Build
|
||||
run: pnpm --filter mermaid run docs:build:vitepress
|
||||
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-pages-artifact@v3
|
||||
uses: actions/upload-pages-artifact@56afc609e74202658d3ffba0e8f6dda462b719fa # v3.0.1
|
||||
with:
|
||||
path: packages/mermaid/src/vitepress/.vitepress/dist
|
||||
|
||||
@ -56,4 +56,4 @@ jobs:
|
||||
steps:
|
||||
- name: Deploy to GitHub Pages
|
||||
id: deployment
|
||||
uses: actions/deploy-pages@v4
|
||||
uses: actions/deploy-pages@d6db90164ac5ed86f2b6aed7e0febac5b3c0c03e # v4.0.5
|
||||
|
23
.github/workflows/release-draft.yml
vendored
23
.github/workflows/release-draft.yml
vendored
@ -1,23 +0,0 @@
|
||||
name: Draft Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
permissions:
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
draft-release:
|
||||
runs-on: ubuntu-latest
|
||||
permissions:
|
||||
contents: write # write permission is required to create a GitHub release
|
||||
pull-requests: read # required to read PR titles/labels
|
||||
steps:
|
||||
- name: Draft Release
|
||||
uses: release-drafter/release-drafter@v6
|
||||
with:
|
||||
disable-autolabeler: true
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
@ -9,14 +9,14 @@ jobs:
|
||||
publish-preview:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
with:
|
||||
fetch-depth: 0
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version-file: '.node-version'
|
||||
@ -28,7 +28,7 @@ jobs:
|
||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
||||
|
||||
- name: Install Json
|
||||
run: npm i json --global
|
||||
run: npm i json@11.0.0 --global
|
||||
|
||||
- name: Publish
|
||||
working-directory: ./packages/mermaid
|
||||
|
47
.github/workflows/release-publish.yml
vendored
47
.github/workflows/release-publish.yml
vendored
@ -1,47 +0,0 @@
|
||||
name: Publish release
|
||||
|
||||
on:
|
||||
release:
|
||||
types: [published]
|
||||
|
||||
jobs:
|
||||
publish:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: fregante/setup-git-user@v2
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
# uses version from "packageManager" field in package.json
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version-file: '.node-version'
|
||||
|
||||
- name: Install Packages
|
||||
run: |
|
||||
pnpm install --frozen-lockfile
|
||||
npm i json --global
|
||||
env:
|
||||
CYPRESS_CACHE_FOLDER: .cache/Cypress
|
||||
|
||||
- name: Prepare release
|
||||
run: |
|
||||
VERSION=${GITHUB_REF:10}
|
||||
echo "Preparing release $VERSION"
|
||||
git checkout -t origin/release/$VERSION
|
||||
npm version --no-git-tag-version --allow-same-version $VERSION
|
||||
git add package.json
|
||||
git commit -nm "Bump version $VERSION"
|
||||
git checkout -t origin/master
|
||||
git merge -m "Release $VERSION" --no-ff release/$VERSION
|
||||
git push --no-verify
|
||||
|
||||
- name: Publish
|
||||
run: |
|
||||
npm set //registry.npmjs.org/:_authToken $NPM_TOKEN
|
||||
npm publish
|
||||
env:
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
44
.github/workflows/release.yml
vendored
Normal file
44
.github/workflows/release.yml
vendored
Normal file
@ -0,0 +1,44 @@
|
||||
name: Release
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- master
|
||||
|
||||
concurrency: ${{ github.workflow }}-${{ github.ref }}
|
||||
|
||||
permissions: # added using https://github.com/step-security/secure-repo
|
||||
contents: read
|
||||
|
||||
jobs:
|
||||
release:
|
||||
permissions:
|
||||
contents: write # for changesets/action to push to the repo
|
||||
pull-requests: write # for changesets/action to create PRs
|
||||
name: Release
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout Repo
|
||||
uses: actions/checkout@f43a0e5ff2bd294095638e18286ca9a3d1956744 # v3.6.0
|
||||
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version-file: '.node-version'
|
||||
|
||||
- name: Install Packages
|
||||
run: pnpm install --frozen-lockfile
|
||||
|
||||
- name: Create Release Pull Request or Publish to npm
|
||||
id: changesets
|
||||
uses: changesets/action@aba318e9165b45b7948c60273e0b72fce0a64eb9 # v1.4.7
|
||||
with:
|
||||
version: pnpm changeset:version
|
||||
publish: pnpm changeset:publish
|
||||
env:
|
||||
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||
NPM_TOKEN: ${{ secrets.NPM_TOKEN }}
|
||||
NPM_CONFIG_PROVENANCE: true
|
37
.github/workflows/scorecard.yml
vendored
Normal file
37
.github/workflows/scorecard.yml
vendored
Normal file
@ -0,0 +1,37 @@
|
||||
name: Scorecard supply-chain security
|
||||
on:
|
||||
branch_protection_rule:
|
||||
push:
|
||||
branches:
|
||||
- develop
|
||||
schedule:
|
||||
- cron: 29 15 * * 0
|
||||
permissions: read-all
|
||||
jobs:
|
||||
analysis:
|
||||
name: Scorecard analysis
|
||||
permissions:
|
||||
id-token: write
|
||||
security-events: write
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- name: Checkout code
|
||||
uses: actions/checkout@b4ffde65f46336ab88eb53be808477a3936bae11 # v4.1.1
|
||||
with:
|
||||
persist-credentials: false
|
||||
- name: Run analysis
|
||||
uses: ossf/scorecard-action@0864cf19026789058feabb7e87baa5f140aac736 # v2.3.1
|
||||
with:
|
||||
results_file: results.sarif
|
||||
results_format: sarif
|
||||
publish_results: true
|
||||
- name: Upload artifact
|
||||
uses: actions/upload-artifact@97a0fba1372883ab732affbe8f94b823f91727db # v3.pre.node20
|
||||
with:
|
||||
name: SARIF file
|
||||
path: results.sarif
|
||||
retention-days: 5
|
||||
- name: Upload to code-scanning
|
||||
uses: github/codeql-action/upload-sarif@f0f3afee809481da311ca3a6ff1ff51d81dbeb24 # v3.26.4
|
||||
with:
|
||||
sarif_file: results.sarif
|
8
.github/workflows/test.yml
vendored
8
.github/workflows/test.yml
vendored
@ -9,13 +9,13 @@ jobs:
|
||||
unit-test:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
# uses version from "packageManager" field in package.json
|
||||
|
||||
- name: Setup Node.js
|
||||
uses: actions/setup-node@v4
|
||||
uses: actions/setup-node@1e60f620b9541d16bece96c5465dc8ee9832be0b # v4.0.3
|
||||
with:
|
||||
cache: pnpm
|
||||
node-version-file: '.node-version'
|
||||
@ -39,7 +39,7 @@ jobs:
|
||||
pnpm exec vitest run ./packages/mermaid/src/diagrams/gantt/ganttDb.spec.ts --coverage
|
||||
|
||||
- name: Upload Coverage to Codecov
|
||||
uses: codecov/codecov-action@v4
|
||||
uses: codecov/codecov-action@e28ff129e5465c2c0dcc6f003fc735cb6ae0c673 # v4.5.0
|
||||
# Run step only pushes to develop and pull_requests
|
||||
if: ${{ github.event_name == 'pull_request' || github.ref == 'refs/heads/develop' }}
|
||||
with:
|
||||
|
2
.github/workflows/unlock-reopened-issues.yml
vendored
2
.github/workflows/unlock-reopened-issues.yml
vendored
@ -8,6 +8,6 @@ jobs:
|
||||
triage:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: Dunning-Kruger/unlock-issues@v1
|
||||
- uses: Dunning-Kruger/unlock-issues@b06b7f7e5c3f2eaa1c6d5d89f40930e4d6d9699e # v1
|
||||
with:
|
||||
repo-token: '${{ secrets.GITHUB_TOKEN }}'
|
||||
|
8
.github/workflows/update-browserlist.yml
vendored
8
.github/workflows/update-browserlist.yml
vendored
@ -8,18 +8,18 @@ jobs:
|
||||
update-browser-list:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v4
|
||||
- uses: pnpm/action-setup@v4
|
||||
- uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
|
||||
- uses: pnpm/action-setup@fe02b34f77f8bc703788d5817da081398fad5dd2 # v4.0.0
|
||||
- run: npx update-browserslist-db@latest
|
||||
- name: Commit changes
|
||||
uses: EndBug/add-and-commit@v9
|
||||
uses: EndBug/add-and-commit@a94899bca583c204427a224a7af87c02f9b325d5 # v9.1.4
|
||||
with:
|
||||
author_name: ${{ github.actor }}
|
||||
author_email: ${{ github.actor }}@users.noreply.github.com
|
||||
message: 'chore: update browsers list'
|
||||
push: false
|
||||
- name: Create Pull Request
|
||||
uses: peter-evans/create-pull-request@v6
|
||||
uses: peter-evans/create-pull-request@c5a7806660adbe173f04e3e038b0ccdcd758773c # v6.1.0
|
||||
with:
|
||||
branch: update-browserslist
|
||||
title: Update Browserslist
|
||||
|
2
.hadolint.yaml
Normal file
2
.hadolint.yaml
Normal file
@ -0,0 +1,2 @@
|
||||
ignored:
|
||||
- DL3002 # TODO: Last USER should not be root
|
@ -1,4 +1,2 @@
|
||||
#!/bin/sh
|
||||
. "$(dirname "$0")/_/husky.sh"
|
||||
|
||||
#!/usr/bin/env sh
|
||||
NODE_OPTIONS="--max_old_space_size=8192" pnpm run pre-commit
|
||||
|
12
Dockerfile
12
Dockerfile
@ -1,2 +1,10 @@
|
||||
FROM node:20.12.2-alpine3.19 AS base
|
||||
RUN wget -qO- https://get.pnpm.io/install.sh | ENV="$HOME/.shrc" SHELL="$(which sh)" sh -
|
||||
FROM node:20.12.2-alpine3.19@sha256:7a91aa397f2e2dfbfcdad2e2d72599f374e0b0172be1d86eeb73f1d33f36a4b2
|
||||
|
||||
USER 0:0
|
||||
|
||||
RUN corepack enable \
|
||||
&& corepack enable pnpm
|
||||
|
||||
ENV NODE_OPTIONS="--max_old_space_size=8192"
|
||||
|
||||
EXPOSE 9000 3333
|
||||
|
@ -36,6 +36,7 @@ Try Live Editor previews of future releases: <a href="https://develop.git.mermai
|
||||
[![Join our Discord!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=discord&label=discord)](https://discord.gg/AgrbSrBer3)
|
||||
[![Twitter Follow](https://img.shields.io/badge/Social-mermaidjs__-blue?style=social&logo=X)](https://twitter.com/mermaidjs_)
|
||||
[![Covered by Argos Visual Testing](https://argos-ci.com/badge.svg)](https://argos-ci.com)
|
||||
[![OpenSSF Scorecard](https://api.securityscorecards.dev/projects/github.com/mermaid-js/mermaid/badge)](https://securityscorecards.dev/viewer/?uri=github.com/mermaid-js/mermaid)
|
||||
|
||||
<img src="./img/header.png" alt="" />
|
||||
|
||||
|
@ -321,4 +321,37 @@ ORDER ||--|{ LINE-ITEM : contains
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
it('should render relationship labels with line breaks', () => {
|
||||
imgSnapshotTest(
|
||||
`
|
||||
erDiagram
|
||||
p[Person] {
|
||||
string firstName
|
||||
string lastName
|
||||
}
|
||||
a["Customer Account"] {
|
||||
string email
|
||||
}
|
||||
|
||||
b["Customer Account Secondary"] {
|
||||
string email
|
||||
}
|
||||
|
||||
c["Customer Account Tertiary"] {
|
||||
string email
|
||||
}
|
||||
|
||||
d["Customer Account Nth"] {
|
||||
string email
|
||||
}
|
||||
|
||||
p ||--o| a : "has<br />one"
|
||||
p ||--o| b : "has<br />one<br />two"
|
||||
p ||--o| c : "has<br />one<br/>two<br />three"
|
||||
p ||--o| d : "has<br />one<br />two<br/>three<br />...<br/>Nth"
|
||||
`,
|
||||
{ logLevel: 1 }
|
||||
);
|
||||
});
|
||||
});
|
||||
|
@ -12,7 +12,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -30,7 +30,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: true },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -47,7 +47,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[Car]
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -62,7 +62,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
C -->|Two| E[\\iPhone\\]
|
||||
C -->|Three| F[Car]
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -78,7 +78,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
classDef processHead fill:#888888,color:white,font-weight:bold,stroke-width:3px,stroke:#001f3f
|
||||
class 1A,1B,D,E processHead
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -107,7 +107,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
35(SAM.CommonFA.PopulationFME)-->39(SAM.CommonFA.ChargeDetails)
|
||||
36(SAM.CommonFA.PremetricCost)-->39(SAM.CommonFA.ChargeDetails)
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -178,7 +178,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
9a072290_1ec3_e711_8c5a_005056ad0002-->d6072290_1ec3_e711_8c5a_005056ad0002
|
||||
9a072290_1ec3_e711_8c5a_005056ad0002-->71082290_1ec3_e711_8c5a_005056ad0002
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -187,7 +187,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`
|
||||
graph TB;subgraph "number as labels";1;end;
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -199,7 +199,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
a1-->a2
|
||||
end
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -211,7 +211,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
a1-->a2
|
||||
end
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -246,7 +246,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
style foo fill:#F99,stroke-width:2px,stroke:#F0F,color:darkred
|
||||
style bar fill:#999,stroke-width:10px,stroke:#0F0,color:blue
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -348,7 +348,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4DA958A0-26D9-4D47-93A7-70F39FD7D51A;
|
||||
sid-7CE72B24-E0C1-46D3-8132-8BA66BE05AA7-->sid-4FC27B48-A6F9-460A-A675-021F5854FE22;
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -364,7 +364,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
listUrl: false,
|
||||
listId: 'color styling',
|
||||
fontFamily: 'courier',
|
||||
@ -390,7 +390,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
listUrl: false,
|
||||
listId: 'color styling',
|
||||
fontFamily: 'courier',
|
||||
@ -411,7 +411,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -435,7 +435,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -457,7 +457,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -471,7 +471,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -485,7 +485,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -500,7 +500,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -527,7 +527,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
class A someclass;`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -544,7 +544,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, flowchart: { nodeSpacing: 50 }, fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { nodeSpacing: 50 }, fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -560,7 +560,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { rankSpacing: '100' },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -578,7 +578,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -603,7 +603,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
click E "notes://do-your-thing/id" "other protocol test"
|
||||
click F "javascript:alert('test')" "script test"
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, securityLevel: 'loose', fontFamily: 'courier' }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, securityLevel: 'loose', fontFamily: 'courier' }
|
||||
);
|
||||
});
|
||||
|
||||
@ -623,7 +623,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
click B "index.html#link-clicked" "link test"
|
||||
click D testClick "click test"
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, flowchart: { htmlLabels: true } }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { htmlLabels: true } }
|
||||
);
|
||||
});
|
||||
|
||||
@ -645,7 +645,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -664,7 +664,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
class A myClass1
|
||||
class D myClass2
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, flowchart: { htmlLabels: true } }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { htmlLabels: true } }
|
||||
);
|
||||
});
|
||||
|
||||
@ -682,7 +682,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -711,7 +711,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -728,7 +728,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -752,7 +752,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: false },
|
||||
fontFamily: 'courier',
|
||||
}
|
||||
@ -769,7 +769,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, flowchart: { diagramPadding: 0 } }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { diagramPadding: 0 } }
|
||||
);
|
||||
});
|
||||
|
||||
@ -778,7 +778,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`graph TD
|
||||
A[Christmas]
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0 }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1 }
|
||||
);
|
||||
});
|
||||
|
||||
@ -796,7 +796,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
C -----> E4
|
||||
C ======> E5
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0 }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1 }
|
||||
);
|
||||
});
|
||||
it('FDH36: should render escaped without html labels', () => {
|
||||
@ -804,7 +804,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`graph TD
|
||||
a["<strong>Haiya</strong>"]-->b
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, htmlLabels: false, flowchart: { htmlLabels: false } }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, htmlLabels: false, flowchart: { htmlLabels: false } }
|
||||
);
|
||||
});
|
||||
it('FDH37: should render non-escaped with html labels', () => {
|
||||
@ -814,7 +814,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
htmlLabels: true,
|
||||
flowchart: { htmlLabels: true },
|
||||
securityLevel: 'loose',
|
||||
@ -830,7 +830,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, flowchart: { useMaxWidth: true } }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { useMaxWidth: true } }
|
||||
);
|
||||
cy.get('svg').should((svg) => {
|
||||
expect(svg).to.have.attr('width', '100%');
|
||||
@ -853,7 +853,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
C -->|Two| E[iPhone]
|
||||
C -->|Three| F[fa:fa-car Car]
|
||||
`,
|
||||
{ look: 'handDrawn', handDrawnSeed: 0, flowchart: { useMaxWidth: false } }
|
||||
{ look: 'handDrawn', handDrawnSeed: 1, flowchart: { useMaxWidth: false } }
|
||||
);
|
||||
cy.get('svg').should((svg) => {
|
||||
// const height = parseFloat(svg.attr('height'));
|
||||
@ -874,7 +874,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
htmlLabels: true,
|
||||
flowchart: { htmlLabels: true },
|
||||
securityLevel: 'loose',
|
||||
@ -904,7 +904,7 @@ describe('Flowchart HandDrawn', () => {
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
htmlLabels: true,
|
||||
flowchart: { htmlLabels: true },
|
||||
securityLevel: 'loose',
|
||||
@ -919,7 +919,7 @@ graph TD
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
htmlLabels: true,
|
||||
flowchart: { htmlLabels: true },
|
||||
securityLevel: 'loose',
|
||||
@ -937,7 +937,7 @@ graph TD
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
htmlLabels: true,
|
||||
flowchart: { htmlLabels: true },
|
||||
securityLevel: 'loose',
|
||||
@ -977,7 +977,7 @@ graph TD
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
htmlLabels: true,
|
||||
flowchart: { htmlLabels: true },
|
||||
securityLevel: 'loose',
|
||||
@ -999,7 +999,7 @@ graph TD
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
htmlLabels: true,
|
||||
flowchart: { htmlLabels: true },
|
||||
securityLevel: 'loose',
|
||||
@ -1016,7 +1016,7 @@ graph TD
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
htmlLabels: true,
|
||||
flowchart: { htmlLabels: true },
|
||||
securityLevel: 'loose',
|
||||
@ -1027,12 +1027,12 @@ graph TD
|
||||
imgSnapshotTest(
|
||||
`
|
||||
graph TD
|
||||
classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
|
||||
classDef default fill:#a34,stroke:#000,stroke-width:4px,color:#fff
|
||||
hello --> default
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
htmlLabels: true,
|
||||
flowchart: { htmlLabels: true },
|
||||
securityLevel: 'loose',
|
||||
@ -1051,7 +1051,7 @@ graph TD
|
||||
`,
|
||||
{
|
||||
look: 'handDrawn',
|
||||
handDrawnSeed: 0,
|
||||
handDrawnSeed: 1,
|
||||
flowchart: { htmlLabels: true },
|
||||
securityLevel: 'loose',
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ describe('Flowchart v2', () => {
|
||||
const style = svg.attr('style');
|
||||
expect(style).to.match(/^max-width: [\d.]+px;$/);
|
||||
const maxWidthValue = parseFloat(style.match(/[\d.]+/g).join(''));
|
||||
expect(maxWidthValue).to.be.within(446 * 0.95 - 1, 446 * 1.05);
|
||||
expect(maxWidthValue).to.be.within(417 * 0.95, 417 * 1.05);
|
||||
});
|
||||
});
|
||||
it('8: should render a flowchart when useMaxWidth is false', () => {
|
||||
@ -118,7 +118,7 @@ describe('Flowchart v2', () => {
|
||||
const width = parseFloat(svg.attr('width'));
|
||||
// use within because the absolute value can be slightly different depending on the environment ±5%
|
||||
// expect(height).to.be.within(446 * 0.95, 446 * 1.05);
|
||||
expect(width).to.be.within(446 * 0.95 - 1, 446 * 1.05);
|
||||
expect(width).to.be.within(417 * 0.95, 417 * 1.05);
|
||||
expect(svg).to.not.have.attr('style');
|
||||
});
|
||||
});
|
||||
|
@ -73,7 +73,9 @@
|
||||
font-family: monospace;
|
||||
font-size: 72px;
|
||||
}
|
||||
|
||||
pre {
|
||||
width: 100%;
|
||||
}
|
||||
/* tspan {
|
||||
font-size: 6px !important;
|
||||
} */
|
||||
@ -84,17 +86,205 @@
|
||||
<div class="flex">
|
||||
<pre id="diagram" class="mermaid">
|
||||
---
|
||||
config:
|
||||
look: handDrawn
|
||||
layout: elk
|
||||
elk:
|
||||
title: hello2
|
||||
config:
|
||||
look: handDrawn
|
||||
layout: elk
|
||||
elk:
|
||||
<!-- nodePlacementStrategy: INTERACTIVE -->
|
||||
<!-- mergeEdges: true -->
|
||||
---
|
||||
stateDiagram-v2
|
||||
direction LR
|
||||
accTitle: An idealized Open Source supply-chain graph
|
||||
|
||||
%%
|
||||
state "🟦 Importer" as author_importer
|
||||
state "🟥 Supplier, Owner" as author_owner
|
||||
state "🟨🟥 Maintainer, Author\n🟨 Custodian" as author
|
||||
state "🟩 Distributor" as repository_distributor
|
||||
state "🟦 Importer" as language_importer
|
||||
state "🟦🟨 Packager" as language_packager
|
||||
state "🟦🟨 OSS Steward" as language_steward
|
||||
state "🟨 Curator" as language_curator
|
||||
state "🟩 Distributor" as language_distributor
|
||||
state "🟦 Contributor" as contributor
|
||||
state "🟦 Importer" as package_importer
|
||||
state "🟨 Patcher" as package_patcher
|
||||
state "🟨🟦 Builder\n🟨🟦 Packager\n🟨🟦 Containerizer" as package_packager
|
||||
state "🟨 Curator" as package_curator
|
||||
state "🟩 Distributor" as package_distributor
|
||||
state "🟦 Importer" as integrator_importer
|
||||
state "🟥 Supplier, Manufacturer, Owner" as integrator_owner
|
||||
state "🟦🟨🟥 Integrator, Developer" as integrator_developer
|
||||
state "🟩🟨 SBOM Redactor\n🟩 Publisher" as integrator_publisher
|
||||
state "🟦🟨 Builder" as integrator_builder
|
||||
state "🟨 Deployer" as deployer
|
||||
state "🟦 Vuln. Checker" as integrator_checker
|
||||
state "🟩🟨 SBOM Redactor" as redactor
|
||||
state "🟦 Consumer\n🟦 User" as consumer
|
||||
state "🟦 Auditor" as auditor_internal
|
||||
state "🟦 Auditor" as auditor_external
|
||||
|
||||
%%
|
||||
classDef createsSBOM stroke:red,stroke-width:3px;
|
||||
classDef updatesSBOM stroke:yellow,stroke-width:3px;
|
||||
classDef assemblesSBOM stroke:yellow,stroke-width:3px;
|
||||
classDef distributesSBOM stroke:green,stroke-width:3px;
|
||||
classDef verifiesSBOM stroke:#07f,stroke-width:3px;
|
||||
|
||||
%%
|
||||
class author_importer verifiesSBOM
|
||||
class author_owner createsSBOM
|
||||
class manufacturer_owner createsSBOM
|
||||
class author assemblesSBOM
|
||||
class package_importer verifiesSBOM
|
||||
class package_patcher updatesSBOM
|
||||
class package_packager assemblesSBOM
|
||||
class package_curator distributesSBOM
|
||||
class package_distributor distributesSBOM
|
||||
class language_importer verifiesSBOM
|
||||
class language_packager assemblesSBOM
|
||||
class language_steward updatesSBOM
|
||||
class language_curator distributesSBOM
|
||||
class language_distributor distributesSBOM
|
||||
class repository_distributor distributesSBOM
|
||||
class integrator_importer verifiesSBOM
|
||||
class integrator_owner createsSBOM
|
||||
class integrator_developer assemblesSBOM
|
||||
class integrator_publisher distributesSBOM
|
||||
class integrator_builder assemblesSBOM
|
||||
class integrator_checker verifiesSBOM
|
||||
class deployer assemblesSBOM
|
||||
class redactor distributesSBOM
|
||||
class auditor_internal verifiesSBOM
|
||||
class auditor_external verifiesSBOM
|
||||
|
||||
state "Maintainer Environment" as environment_maintainer {
|
||||
[*] --> author_importer
|
||||
[*] --> author
|
||||
author_importer --> author
|
||||
author_owner --> author
|
||||
author --> language_packager
|
||||
}
|
||||
|
||||
[*] --> environment_maintainer
|
||||
|
||||
state "Language Ecosystem" as ecosystem_lang {
|
||||
[*] --> language_importer
|
||||
[*] --> language_steward
|
||||
[*] --> language_curator
|
||||
[*] --> language_distributor
|
||||
language_importer --> language_distributor
|
||||
language_importer --> language_curator
|
||||
language_steward --> language_curator
|
||||
language_curator --> language_distributor
|
||||
}
|
||||
|
||||
language_packager --> ecosystem_lang
|
||||
ecosystem_lang --> ecosystem_lang
|
||||
|
||||
state "Public Collaboration Ecosystem" as ecosystem_repo {
|
||||
[*] --> repository_distributor
|
||||
}
|
||||
|
||||
author --> ecosystem_repo
|
||||
ecosystem_repo --> author
|
||||
|
||||
repository_distributor --> contributor
|
||||
contributor --> repository_distributor
|
||||
|
||||
state "Package Ecosystem" as ecosystem_package {
|
||||
[*] --> package_importer
|
||||
[*] --> package_packager
|
||||
[*] --> package_patcher
|
||||
package_importer --> package_patcher
|
||||
package_importer --> package_packager
|
||||
package_patcher --> package_packager
|
||||
package_packager --> package_curator
|
||||
package_packager --> package_distributor
|
||||
package_curator --> package_distributor
|
||||
}
|
||||
|
||||
repository_distributor --> ecosystem_package
|
||||
language_distributor --> ecosystem_package
|
||||
ecosystem_package --> ecosystem_package
|
||||
|
||||
state "Integrator Environment" as environment_integrator {
|
||||
[*] --> integrator_developer
|
||||
[*] --> integrator_importer
|
||||
integrator_importer --> integrator_developer
|
||||
integrator_owner --> integrator_developer
|
||||
integrator_builder --> integrator_publisher
|
||||
integrator_developer --> integrator_checker
|
||||
integrator_checker --> integrator_developer
|
||||
auditor_internal --> integrator_developer
|
||||
integrator_developer --> integrator_builder
|
||||
integrator_developer --> auditor_internal
|
||||
}
|
||||
|
||||
repository_distributor --> environment_integrator
|
||||
language_distributor --> environment_integrator
|
||||
package_distributor --> environment_integrator
|
||||
|
||||
state "Production Environment" as environment_prod {
|
||||
[*] --> deployer
|
||||
deployer --> redactor
|
||||
}
|
||||
|
||||
integrator_publisher --> [*]
|
||||
integrator_developer --> environment_prod
|
||||
integrator_builder --> environment_prod
|
||||
integrator_publisher --> environment_prod
|
||||
|
||||
deployer --> auditor_external
|
||||
deployer --> consumer
|
||||
redactor --> consumer
|
||||
|
||||
|
||||
|
||||
</pre>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
---
|
||||
title: hello2
|
||||
config:
|
||||
look: handDrawn
|
||||
layout: dagre
|
||||
elk:
|
||||
nodePlacementStrategy: BRANDES_KOEPF
|
||||
---
|
||||
flowchart TB
|
||||
%% A --> C
|
||||
B -- sdf sdf --> C{"Evaluate"}
|
||||
C --> n4["salkdfjh akljd lkasdjf lkashj lksjadf klasdh"]
|
||||
C --> F
|
||||
stateDiagram-v2
|
||||
A --> A
|
||||
state A {
|
||||
B --> D
|
||||
state B {
|
||||
C
|
||||
}
|
||||
state D {
|
||||
E
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
</pre
|
||||
>
|
||||
<pre id="diagram" class="mermaid2">
|
||||
---
|
||||
title: hello2
|
||||
config:
|
||||
look: handDrawn
|
||||
layout: dagre
|
||||
elk:
|
||||
nodePlacementStrategy: BRANDES_KOEPF
|
||||
---
|
||||
flowchart
|
||||
A --> A
|
||||
subgraph A
|
||||
B --> B
|
||||
subgraph B
|
||||
C
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
</pre
|
||||
@ -192,7 +382,7 @@ flowchart LR
|
||||
messageFontFamily: 'courier',
|
||||
},
|
||||
fontSize: 12,
|
||||
logLevel: 0,
|
||||
logLevel: 3,
|
||||
securityLevel: 'loose',
|
||||
});
|
||||
function callback() {
|
||||
|
@ -125,6 +125,35 @@
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<pre class="mermaid">
|
||||
erDiagram
|
||||
p[Person] {
|
||||
string firstName
|
||||
string lastName
|
||||
}
|
||||
a["Customer Account"] {
|
||||
string email
|
||||
}
|
||||
|
||||
b["Customer Account Secondary"] {
|
||||
string email
|
||||
}
|
||||
|
||||
c["Customer Account Tertiary"] {
|
||||
string email
|
||||
}
|
||||
|
||||
d["Customer Account Nth"] {
|
||||
string email
|
||||
}
|
||||
|
||||
p ||--o| a : "has<br />one"
|
||||
p ||--o| b : "has<br />one<br />two"
|
||||
p ||--o| c : "has<br />one<br />two<br />three"
|
||||
p ||--o| d : "has<br />one<br />two<br />three<br />...<br />Nth"
|
||||
</pre>
|
||||
<hr />
|
||||
|
||||
<pre class="mermaid">
|
||||
erDiagram
|
||||
_customer_order {
|
||||
|
@ -7,9 +7,6 @@ services:
|
||||
tty: true
|
||||
working_dir: /mermaid
|
||||
mem_limit: '8G'
|
||||
entrypoint: ./docker-entrypoint.sh
|
||||
environment:
|
||||
- NODE_OPTIONS=--max_old_space_size=8192
|
||||
volumes:
|
||||
- ./:/mermaid
|
||||
- root_cache:/root/.cache
|
||||
|
@ -1,3 +0,0 @@
|
||||
#!/bin/sh
|
||||
source /root/.shrc
|
||||
exec "$@"
|
@ -370,9 +370,9 @@ If the users have no way to know that things have changed, then you haven't real
|
||||
Likewise, if users don't know that there is a new feature that you've implemented, it will forever remain unknown and unused.
|
||||
|
||||
The documentation has to be updated for users to know that things have been changed and added!
|
||||
If you are adding a new feature, add `(v10.8.0+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
|
||||
If you are adding a new feature, add `(v<MERMAID_RELEASE_VERSION>+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
|
||||
|
||||
eg: `# Feature Name (v10.8.0+)`
|
||||
eg: `# Feature Name (v<MERMAID_RELEASE_VERSION>+)`
|
||||
|
||||
We know it can sometimes be hard to code _and_ write user documentation.
|
||||
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/rendering-util/render.ts:25](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L25)
|
||||
[packages/mermaid/src/rendering-util/render.ts:24](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L24)
|
||||
|
||||
---
|
||||
|
||||
@ -26,7 +26,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/rendering-util/render.ts:24](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L24)
|
||||
[packages/mermaid/src/rendering-util/render.ts:23](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L23)
|
||||
|
||||
---
|
||||
|
||||
@ -36,4 +36,4 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/rendering-util/render.ts:23](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L23)
|
||||
[packages/mermaid/src/rendering-util/render.ts:22](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/rendering-util/render.ts#L22)
|
||||
|
@ -16,7 +16,7 @@
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:112](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L112)
|
||||
[packages/mermaid/src/config.type.ts:122](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L122)
|
||||
|
||||
---
|
||||
|
||||
@ -39,7 +39,7 @@ This matters if you are using base tag settings.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L131)
|
||||
[packages/mermaid/src/config.type.ts:141](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L141)
|
||||
|
||||
---
|
||||
|
||||
@ -69,7 +69,8 @@ This matters if you are using base tag settings.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L186)
|
||||
|
||||
[packages/mermaid/src/config.type.ts:187](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L187)
|
||||
|
||||
---
|
||||
|
||||
@ -79,7 +80,7 @@ This matters if you are using base tag settings.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:103](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L103)
|
||||
[packages/mermaid/src/config.type.ts:113](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L113)
|
||||
|
||||
---
|
||||
|
||||
@ -93,7 +94,7 @@ You can set this attribute to base the seed on a static string.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:180](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L180)
|
||||
[packages/mermaid/src/config.type.ts:181](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L181)
|
||||
|
||||
---
|
||||
|
||||
@ -111,7 +112,7 @@ should not change unless content is changed.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:173](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L173)
|
||||
[packages/mermaid/src/config.type.ts:174](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L174)
|
||||
|
||||
---
|
||||
|
||||
@ -131,10 +132,11 @@ should not change unless content is changed.
|
||||
|
||||
#### Type declaration
|
||||
|
||||
| Name | Type | Description |
|
||||
| :----------------------- | :---------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `mergeEdges?` | `boolean` | Elk specific option that allows edges to share path where it convenient. It can make for pretty diagrams but can also make it harder to read the diagram. |
|
||||
| `nodePlacementStrategy?` | `"SIMPLE"` \| `"NETWORK_SIMPLEX"` \| `"LINEAR_SEGMENTS"` \| `"BRANDES_KOEPF"` | Elk specific option affecting how nodes are placed. |
|
||||
| Name | Type | Description |
|
||||
| :----------------------- | :-------------------------------------------------------------------------------------------- | :-------------------------------------------------------------------------------------------------------------------------------------------------------- |
|
||||
| `cycleBreakingStrategy?` | `"GREEDY"` \| `"DEPTH_FIRST"` \| `"INTERACTIVE"` \| `"MODEL_ORDER"` \| `"GREEDY_MODEL_ORDER"` | This strategy decides how to find cycles in the graph and deciding which edges need adjustment to break loops. |
|
||||
| `mergeEdges?` | `boolean` | Elk specific option that allows edges to share path where it convenient. It can make for pretty diagrams but can also make it harder to read the diagram. |
|
||||
| `nodePlacementStrategy?` | `"SIMPLE"` \| `"NETWORK_SIMPLEX"` \| `"LINEAR_SEGMENTS"` \| `"BRANDES_KOEPF"` | Elk specific option affecting how nodes are placed. |
|
||||
|
||||
#### Defined in
|
||||
|
||||
@ -148,7 +150,7 @@ should not change unless content is changed.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L188)
|
||||
[packages/mermaid/src/config.type.ts:189](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L189)
|
||||
|
||||
---
|
||||
|
||||
@ -158,7 +160,7 @@ should not change unless content is changed.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:181](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L181)
|
||||
[packages/mermaid/src/config.type.ts:182](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L182)
|
||||
|
||||
---
|
||||
|
||||
@ -172,7 +174,7 @@ See <https://developer.mozilla.org/en-US/docs/Web/CSS/font-family>
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:111](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L111)
|
||||
[packages/mermaid/src/config.type.ts:121](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L121)
|
||||
|
||||
---
|
||||
|
||||
@ -196,7 +198,7 @@ If set to true, ignores legacyMathML.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:162](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L162)
|
||||
[packages/mermaid/src/config.type.ts:163](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L163)
|
||||
|
||||
---
|
||||
|
||||
@ -206,7 +208,7 @@ If set to true, ignores legacyMathML.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:183](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L183)
|
||||
[packages/mermaid/src/config.type.ts:184](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L184)
|
||||
|
||||
---
|
||||
|
||||
@ -238,7 +240,7 @@ Defines the seed to be used when using handDrawn look. This is important for the
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:104](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L104)
|
||||
[packages/mermaid/src/config.type.ts:114](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L114)
|
||||
|
||||
---
|
||||
|
||||
@ -261,7 +263,7 @@ For supported diagrams (i.e., Architecture), their syntax allows refering to key
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:184](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L184)
|
||||
[packages/mermaid/src/config.type.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L185)
|
||||
|
||||
---
|
||||
|
||||
@ -288,7 +290,7 @@ fall back to legacy rendering for KaTeX.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:146](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L146)
|
||||
[packages/mermaid/src/config.type.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L156)
|
||||
|
||||
---
|
||||
|
||||
@ -300,7 +302,7 @@ This option decides the amount of logging to be used by mermaid.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:117](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L117)
|
||||
[packages/mermaid/src/config.type.ts:127](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L127)
|
||||
|
||||
---
|
||||
|
||||
@ -376,7 +378,7 @@ The maximum allowed size of the users text diagram
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:189](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L189)
|
||||
[packages/mermaid/src/config.type.ts:190](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L190)
|
||||
|
||||
---
|
||||
|
||||
@ -386,7 +388,7 @@ The maximum allowed size of the users text diagram
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:190](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L190)
|
||||
[packages/mermaid/src/config.type.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L191)
|
||||
|
||||
---
|
||||
|
||||
@ -396,7 +398,7 @@ The maximum allowed size of the users text diagram
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L192)
|
||||
[packages/mermaid/src/config.type.ts:193](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L193)
|
||||
|
||||
---
|
||||
|
||||
@ -420,7 +422,7 @@ This prevents malicious graph directives from overriding a site's default securi
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:138](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L138)
|
||||
[packages/mermaid/src/config.type.ts:148](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L148)
|
||||
|
||||
---
|
||||
|
||||
@ -432,7 +434,7 @@ Level of trust for parsed diagram
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:121](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L121)
|
||||
[packages/mermaid/src/config.type.ts:131](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L131)
|
||||
|
||||
---
|
||||
|
||||
@ -442,7 +444,7 @@ Level of trust for parsed diagram
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:182](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L182)
|
||||
[packages/mermaid/src/config.type.ts:183](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L183)
|
||||
|
||||
---
|
||||
|
||||
@ -454,7 +456,7 @@ Dictates whether mermaid starts on Page load
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:125](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L125)
|
||||
[packages/mermaid/src/config.type.ts:135](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L135)
|
||||
|
||||
---
|
||||
|
||||
@ -464,7 +466,7 @@ Dictates whether mermaid starts on Page load
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:187](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L187)
|
||||
[packages/mermaid/src/config.type.ts:188](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L188)
|
||||
|
||||
---
|
||||
|
||||
@ -520,7 +522,7 @@ You may also use `themeCSS` to override this value.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L185)
|
||||
[packages/mermaid/src/config.type.ts:186](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L186)
|
||||
|
||||
---
|
||||
|
||||
@ -540,4 +542,4 @@ You may also use `themeCSS` to override this value.
|
||||
|
||||
#### Defined in
|
||||
|
||||
[packages/mermaid/src/config.type.ts:191](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L191)
|
||||
[packages/mermaid/src/config.type.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/config.type.ts#L192)
|
||||
|
@ -59,6 +59,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
|
||||
- [MonsterWriter](https://www.monsterwriter.com/) ✅
|
||||
- [Joplin](https://joplinapp.org) ✅
|
||||
- [LiveBook](https://livebook.dev) ✅
|
||||
- [Slidev](https://sli.dev) ✅
|
||||
- [Tuleap](https://docs.tuleap.org/user-guide/writing-in-tuleap.html#graphs) ✅
|
||||
- [Mermaid Flow Visual Editor](https://www.mermaidflow.app) ✅
|
||||
- [Mermerd](https://github.com/KarnerTh/mermerd)
|
||||
@ -134,7 +135,7 @@ Communication tools and platforms
|
||||
### Wikis
|
||||
|
||||
- [DokuWiki](https://dokuwiki.org)
|
||||
- [ComboStrap](https://combostrap.com/mermaid)
|
||||
- [ComboStrap](https://combostrap.com/utility/create-diagram-with-mermaid-vh3ab9yj)
|
||||
- [Mermaid Plugin](https://www.dokuwiki.org/plugin:mermaid)
|
||||
- [Foswiki](https://foswiki.org)
|
||||
- [Mermaid Plugin](https://foswiki.org/Extensions/MermaidPlugin)
|
||||
|
@ -12,7 +12,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
|
||||
|
||||
<br />
|
||||
|
||||
<a href="https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=416671&theme=light" alt="Mermaid Chart - A smarter way to create diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
<a href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid-chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light" alt="Mermaid Chart - A smarter way to create diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## About
|
||||
|
||||
|
@ -63,7 +63,7 @@ import matplotlib.pyplot as plt
|
||||
|
||||
def mm(graph):
|
||||
graphbytes = graph.encode("utf8")
|
||||
base64_bytes = base64.b64encode(graphbytes)
|
||||
base64_bytes = base64.urlsafe_b64encode(graphbytes)
|
||||
base64_string = base64_bytes.decode("ascii")
|
||||
display(Image(url="https://mermaid.ink/img/" + base64_string))
|
||||
|
||||
|
@ -83,3 +83,139 @@ Allows for the limited reconfiguration of a diagram just before it is rendered.
|
||||
### [Theme Manipulation](../config/theming.md)
|
||||
|
||||
An application of using Directives to change [Themes](../config/theming.md). `Theme` is a value within Mermaid's configuration that dictates the color scheme for diagrams.
|
||||
|
||||
### Layout and look
|
||||
|
||||
We've restructured how Mermaid renders diagrams, enabling new features like selecting layout and look. **Currently, this is supported for flowcharts and state diagrams**, with plans to extend support to all diagram types.
|
||||
|
||||
### Selecting Diagram Looks
|
||||
|
||||
Mermaid offers a variety of styles or “looks” for your diagrams, allowing you to tailor the visual appearance to match your specific needs or preferences. Whether you prefer a hand-drawn or classic style, you can easily customize your diagrams.
|
||||
|
||||
**Available Looks:**
|
||||
|
||||
```
|
||||
• Hand-Drawn Look: For a more personal, creative touch, the hand-drawn look brings a sketch-like quality to your diagrams. This style is perfect for informal settings or when you want to add a bit of personality to your diagrams.
|
||||
• Classic Look: If you prefer the traditional Mermaid style, the classic look maintains the original appearance that many users are familiar with. It’s great for consistency across projects or when you want to keep the familiar aesthetic.
|
||||
```
|
||||
|
||||
**How to Select a Look:**
|
||||
|
||||
You can select a look by adding the look parameter in the metadata section of your Mermaid diagram code. Here’s an example:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
look: handDrawn
|
||||
theme: neutral
|
||||
---
|
||||
flowchart LR
|
||||
A[Start] --> B{Decision}
|
||||
B -->|Yes| C[Continue]
|
||||
B -->|No| D[Stop]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
look: handDrawn
|
||||
theme: neutral
|
||||
---
|
||||
flowchart LR
|
||||
A[Start] --> B{Decision}
|
||||
B -->|Yes| C[Continue]
|
||||
B -->|No| D[Stop]
|
||||
```
|
||||
|
||||
#### Selecting Layout Algorithms
|
||||
|
||||
In addition to customizing the look of your diagrams, Mermaid Chart now allows you to choose different layout algorithms to better organize and present your diagrams, especially when dealing with more complex structures. The layout algorithm dictates how nodes and edges are arranged on the page.
|
||||
|
||||
#### Supported Layout Algorithms:
|
||||
|
||||
```
|
||||
• Dagre (default): This is the classic layout algorithm that has been used in Mermaid for a long time. It provides a good balance of simplicity and visual clarity, making it ideal for most diagrams.
|
||||
• ELK: For those who need more sophisticated layout capabilities, especially when working with large or intricate diagrams, the ELK (Eclipse Layout Kernel) layout offers advanced options. It provides a more optimized arrangement, potentially reducing overlapping and improving readability. This is not included out the box but needs to be added when integrating mermaid for sites/applications that want to have elk support.
|
||||
```
|
||||
|
||||
#### How to Select a Layout Algorithm:
|
||||
|
||||
You can specify the layout algorithm directly in the metadata section of your Mermaid diagram code. Here’s an example:
|
||||
|
||||
```mermaid-example
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
look: handDrawn
|
||||
theme: dark
|
||||
---
|
||||
flowchart TB
|
||||
A[Start] --> B{Decision}
|
||||
B -->|Yes| C[Continue]
|
||||
B -->|No| D[Stop]
|
||||
```
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
look: handDrawn
|
||||
theme: dark
|
||||
---
|
||||
flowchart TB
|
||||
A[Start] --> B{Decision}
|
||||
B -->|Yes| C[Continue]
|
||||
B -->|No| D[Stop]
|
||||
```
|
||||
|
||||
In this example, the `layout: elk` line configures the diagram to use the ELK layout algorithm, along with the hand drawn look and forest theme.
|
||||
|
||||
#### Customizing ELK Layout:
|
||||
|
||||
When using the ELK layout, you can further refine the diagram’s configuration, such as how nodes are placed and whether parallel edges should be combined:
|
||||
|
||||
- To combine parallel edges, use mergeEdges: true | false.
|
||||
- To configure node placement, use nodePlacementStrategy with the following options:
|
||||
- SIMPLE
|
||||
- NETWORK_SIMPLEX
|
||||
- LINEAR_SEGMENTS
|
||||
- BRANDES_KOEPF (default)
|
||||
|
||||
**Example configuration:**
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
elk:
|
||||
mergeEdges: true
|
||||
nodePlacementStrategy: LINEAR_SEGMENTS
|
||||
---
|
||||
flowchart LR
|
||||
A[Start] --> B{Choose Path}
|
||||
B -->|Option 1| C[Path 1]
|
||||
B -->|Option 2| D[Path 2]
|
||||
|
||||
#### Using Dagre Layout with Classic Look:
|
||||
```
|
||||
|
||||
Another example:
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: dagre
|
||||
look: classic
|
||||
theme: default
|
||||
---
|
||||
|
||||
flowchart LR
|
||||
A[Start] --> B{Choose Path}
|
||||
B -->|Option 1| C[Path 1]
|
||||
B -->|Option 2| D[Path 2]
|
||||
|
||||
```
|
||||
|
||||
These options give you the flexibility to create diagrams that not only look great but are also arranged to best suit your data’s structure and flow.
|
||||
|
||||
When integrating Mermaid, you can include look and layout configuration with the initialize call. This is also where you add the loading of elk.
|
||||
|
@ -6,6 +6,48 @@
|
||||
|
||||
# Blog
|
||||
|
||||
## [Mermaid v11 is out!](https://www.mermaidchart.com/blog/posts/mermaid-v11/)
|
||||
|
||||
23 August 2024 · 2 mins
|
||||
|
||||
Mermaid v11 introduces advanced layout options, new diagram types, and enhanced customization features, thanks to the incredible contributions from our community.
|
||||
|
||||
## [Mermaid Innovation - Introducing New Looks for Mermaid Diagrams](https://www.mermaidchart.com/blog/posts/mermaid-innovation-introducing-new-looks-for-mermaid-diagrams/)
|
||||
|
||||
6 August 2024 ·3 mins
|
||||
|
||||
Discover the fresh new and unique Neo and Hand-Drawn looks for Mermaid Diagrams, while still offering the classic look you love.
|
||||
|
||||
## [The Mermaid Chart Plugin for Jira: A How-To User Guide](https://www.mermaidchart.com/blog/posts/the-mermaid-chart-plugin-for-jira-a-how-to-user-guide/)
|
||||
|
||||
31 July 2024 · 5 mins
|
||||
|
||||
The Mermaid Chart plugin for Jira has arrived!
|
||||
|
||||
## [Mermaid AI Is Here to Change the Game For Diagram Creation](https://www.mermaidchart.com/blog/posts/mermaid-ai-is-here-to-change-the-game-for-diagram-creation/)
|
||||
|
||||
22 July 2024 · 5 mins
|
||||
|
||||
The Mermaid AI chat interface
|
||||
|
||||
## [How to Make a Sequence Diagram with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-make-a-sequence-diagram-in-mermaid-chart-step-by-step-guide/)
|
||||
|
||||
8 July 2024 · 6 mins
|
||||
|
||||
Sequence diagrams are important for communicating complex systems in a clear and concise manner.
|
||||
|
||||
## [How to Use the New “Comments” Feature in Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-use-the-new-comments-feature-in-mermaid-chart/)
|
||||
|
||||
2 July 2024 · 3 mins
|
||||
|
||||
How to Use the New Comments Feature in Mermaid Chart
|
||||
|
||||
## [How to Use the official Mermaid Chart for Confluence app](https://www.mermaidchart.com/blog/posts/how-to-use-the-official-mermaid-chart-for-confluence-app/)
|
||||
|
||||
21 May 2024 · 4 mins
|
||||
|
||||
It doesn’t matter if you’re a data enthusiast, software engineer, or visual storyteller; our Confluence app can allow you to embed Mermaid Chart diagrams — and dynamically edit them — within your Confluence pages.
|
||||
|
||||
## [How to Choose the Right Documentation Software](https://www.mermaidchart.com/blog/posts/how-to-choose-the-right-documentation-software/)
|
||||
|
||||
7 May 2024 · 5 mins
|
||||
|
@ -286,6 +286,7 @@ erDiagram
|
||||
|
||||
- If you want the relationship label to be more than one word, you must use double quotes around the phrase
|
||||
- If you don't want a label at all on a relationship, you must use an empty double-quoted string
|
||||
- (v\<MERMAID_RELEASE_VERSION>+) If you want a multi-line label on a relationship, use `<br />` between the two lines (`"first line<br />second line"`)
|
||||
|
||||
## Styling
|
||||
|
||||
|
@ -172,7 +172,7 @@ The `title` is an _optional_ string to be displayed at the top of the Gantt char
|
||||
The `excludes` is an _optional_ attribute that accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".
|
||||
These date will be marked on the graph, and be excluded from the duration calculation of tasks. Meaning that if there are excluded dates during a task interval, the number of 'skipped' days will be added to the end of the task to ensure the duration is as specified in the code.
|
||||
|
||||
#### Weekend (v\<MERMAID_RELEASE_VERSION>+)
|
||||
#### Weekend (v\11.0.0+)
|
||||
|
||||
When excluding weekends, it is possible to configure the weekends to be either Friday and Saturday or Saturday and Sunday. By default weekends are Saturday and Sunday.
|
||||
To define the weekend start day, there is an _optional_ attribute `weekend` that can be added in a new line followed by either `friday` or `saturday`.
|
||||
|
@ -918,7 +918,7 @@ Usage example:
|
||||
commit
|
||||
```
|
||||
|
||||
### Bottom to Top (`BT:`) (v\<MERMAID_RELEASE_VERSION>+)
|
||||
### Bottom to Top (`BT:`) (v11.0.0+)
|
||||
|
||||
In `BT` (**Bottom-to-Top**) orientation, the commits run from bottom to top of the graph and branches are arranged side-by-side.
|
||||
|
||||
|
@ -4,7 +4,7 @@
|
||||
>
|
||||
> ## Please edit the corresponding file in [/packages/mermaid/src/docs/syntax/packet.md](../../packages/mermaid/src/docs/syntax/packet.md).
|
||||
|
||||
# Packet Diagram (v\<MERMAID_RELEASE_VERSION>+)
|
||||
# Packet Diagram (v11.0.0+)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
@ -208,18 +208,18 @@ Messages can be of two displayed either solid or with a dotted line.
|
||||
|
||||
There are ten types of arrows currently supported:
|
||||
|
||||
| Type | Description |
|
||||
| -------- | ------------------------------------------------------------------------ |
|
||||
| `->` | Solid line without arrow |
|
||||
| `-->` | Dotted line without arrow |
|
||||
| `->>` | Solid line with arrowhead |
|
||||
| `-->>` | Dotted line with arrowhead |
|
||||
| `<<->>` | Solid line with bidirectional arrowheads (v\<MERMAID_RELEASE_VERSION>+) |
|
||||
| `<<-->>` | Dotted line with bidirectional arrowheads (v\<MERMAID_RELEASE_VERSION>+) |
|
||||
| `-x` | Solid line with a cross at the end |
|
||||
| `--x` | Dotted line with a cross at the end. |
|
||||
| `-)` | Solid line with an open arrow at the end (async) |
|
||||
| `--)` | Dotted line with a open arrow at the end (async) |
|
||||
| Type | Description |
|
||||
| -------- | ---------------------------------------------------- |
|
||||
| `->` | Solid line without arrow |
|
||||
| `-->` | Dotted line without arrow |
|
||||
| `->>` | Solid line with arrowhead |
|
||||
| `-->>` | Dotted line with arrowhead |
|
||||
| `<<->>` | Solid line with bidirectional arrowheads (v11.0.0+) |
|
||||
| `<<-->>` | Dotted line with bidirectional arrowheads (v11.0.0+) |
|
||||
| `-x` | Solid line with a cross at the end |
|
||||
| `--x` | Dotted line with a cross at the end. |
|
||||
| `-)` | Solid line with an open arrow at the end (async) |
|
||||
| `--)` | Dotted line with a open arrow at the end (async) |
|
||||
|
||||
## Activations
|
||||
|
||||
|
10
package.json
10
package.json
@ -24,7 +24,9 @@
|
||||
"dev": "tsx .esbuild/server.ts",
|
||||
"dev:vite": "tsx .vite/server.ts",
|
||||
"dev:coverage": "pnpm coverage:cypress:clean && VITE_COVERAGE=true pnpm dev:vite",
|
||||
"release": "pnpm build",
|
||||
"copy-readme": "cpy './README.*' ./packages/mermaid/ --cwd=.",
|
||||
"changeset:version": "changeset version && pnpm build && pnpm --filter mermaid run docs:release-version && pnpm --filter mermaid run docs:build && git add --all",
|
||||
"changeset:publish": "pnpm copy-readme && changeset publish",
|
||||
"lint": "eslint --quiet --stats --cache --cache-strategy content . && pnpm lint:jison && prettier --cache --check .",
|
||||
"lint:fix": "eslint --cache --cache-strategy content --fix . && prettier --write . && tsx scripts/fixCSpell.ts",
|
||||
"lint:jison": "tsx ./scripts/jison/lint.mts",
|
||||
@ -40,8 +42,7 @@
|
||||
"test": "pnpm lint && vitest run",
|
||||
"test:watch": "vitest --watch",
|
||||
"test:coverage": "vitest --coverage",
|
||||
"prepublishOnly": "pnpm build && pnpm test",
|
||||
"prepare": "husky install && pnpm build",
|
||||
"prepare": "husky && pnpm build",
|
||||
"pre-commit": "lint-staged"
|
||||
},
|
||||
"repository": {
|
||||
@ -63,6 +64,8 @@
|
||||
"devDependencies": {
|
||||
"@applitools/eyes-cypress": "^3.44.4",
|
||||
"@argos-ci/cypress": "^2.1.0",
|
||||
"@changesets/changelog-github": "^0.5.0",
|
||||
"@changesets/cli": "^2.27.7",
|
||||
"@cspell/eslint-plugin": "^8.8.4",
|
||||
"@cypress/code-coverage": "^3.12.30",
|
||||
"@eslint/js": "^9.4.0",
|
||||
@ -82,6 +85,7 @@
|
||||
"chokidar": "^3.6.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"cors": "^2.8.5",
|
||||
"cpy-cli": "^5.0.0",
|
||||
"cross-env": "^7.0.3",
|
||||
"cspell": "^8.6.0",
|
||||
"cypress": "^13.11.0",
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "@mermaid-js/mermaid-example-diagram",
|
||||
"version": "9.3.0",
|
||||
"private": true,
|
||||
"description": "Example of external diagram module for MermaidJS.",
|
||||
"module": "dist/mermaid-example-diagram.core.mjs",
|
||||
"types": "dist/detector.d.ts",
|
||||
@ -18,9 +19,7 @@
|
||||
"example",
|
||||
"mermaid"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
},
|
||||
"scripts": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mermaid-js/mermaid"
|
||||
|
16
packages/mermaid-layout-elk/CHANGELOG.md
Normal file
16
packages/mermaid-layout-elk/CHANGELOG.md
Normal file
@ -0,0 +1,16 @@
|
||||
# @mermaid-js/layout-elk
|
||||
|
||||
## 0.1.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5761](https://github.com/mermaid-js/mermaid/pull/5761) [`b34dfe8`](https://github.com/mermaid-js/mermaid/commit/b34dfe8f45eded31da10965ced7ea40fde1ca76c) Thanks [@sidharthv96](https://github.com/sidharthv96)! - Fix type file path
|
||||
|
||||
## 0.1.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5758](https://github.com/mermaid-js/mermaid/pull/5758) [`501a55d`](https://github.com/mermaid-js/mermaid/commit/501a55d8f225901ba345c498dec4298490a0196e) Thanks [@sidharthv96](https://github.com/sidharthv96)! - fix: Types path
|
||||
|
||||
- Updated dependencies [[`5deaef4`](https://github.com/mermaid-js/mermaid/commit/5deaef456e74d796866431c26f69360e4e74dbff)]:
|
||||
- mermaid@11.0.2
|
@ -1,14 +1,14 @@
|
||||
{
|
||||
"name": "@mermaid-js/layout-elk",
|
||||
"version": "0.0.1",
|
||||
"version": "0.1.2",
|
||||
"description": "ELK layout engine for mermaid",
|
||||
"module": "dist/mermaid-layout-elk.core.mjs",
|
||||
"types": "dist/packages/mermaid-layout-elk/src/index.d.ts",
|
||||
"types": "dist/layouts.d.ts",
|
||||
"type": "module",
|
||||
"exports": {
|
||||
".": {
|
||||
"import": "./dist/mermaid-layout-elk.core.mjs",
|
||||
"types": "./dist/packages/mermaid-layout-elk/src/index.d.ts"
|
||||
"types": "./dist/layouts.d.ts"
|
||||
},
|
||||
"./*": "./*"
|
||||
},
|
||||
@ -18,9 +18,7 @@
|
||||
"elk",
|
||||
"mermaid"
|
||||
],
|
||||
"scripts": {
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
},
|
||||
"scripts": {},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
"url": "https://github.com/mermaid-js/mermaid"
|
||||
@ -34,13 +32,14 @@
|
||||
"d3": "^7.9.0",
|
||||
"elkjs": "^0.9.3"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"devDependencies": {
|
||||
"@types/d3": "^7.4.3",
|
||||
"mermaid": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mermaid": "^11.0.0"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
],
|
||||
"devDependencies": {
|
||||
"@types/d3": "^7.4.3"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -6,7 +6,6 @@ import { type TreeData, findCommonAncestor } from './find-common-ancestor.js';
|
||||
export const render = async (
|
||||
data4Layout: LayoutData,
|
||||
svg: SVG,
|
||||
element: any,
|
||||
{
|
||||
common,
|
||||
getConfig,
|
||||
@ -377,6 +376,7 @@ export const render = async (
|
||||
|
||||
// calculate start and end points of the edge, note that the source and target
|
||||
// can be modified for shapes that have ports
|
||||
// @ts-ignore TODO: fix this
|
||||
const { source, target, sourceId, targetId } = getEdgeStartEndPoint(edge, dir);
|
||||
log.debug('abc78 source and target', source, target);
|
||||
// Add the edge to the graph
|
||||
@ -739,7 +739,7 @@ export const render = async (
|
||||
|
||||
// @ts-ignore - ELK is not typed
|
||||
const elk = new ELK();
|
||||
|
||||
const element = svg.select('g');
|
||||
// Add the arrowheads to the svg
|
||||
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
|
||||
|
||||
@ -752,14 +752,34 @@ export const render = async (
|
||||
'nodePlacement.strategy': data4Layout.config.elk.nodePlacementStrategy,
|
||||
'elk.layered.mergeEdges': data4Layout.config.elk.mergeEdges,
|
||||
'elk.direction': 'DOWN',
|
||||
'spacing.baseValue': 30,
|
||||
// 'spacing.nodeNode': 40,
|
||||
// 'spacing.nodeNodeBetweenLayers': 45,
|
||||
// 'spacing.edgeNode': 40,
|
||||
// 'spacing.edgeNodeBetweenLayers': 30,
|
||||
// 'spacing.edgeEdge': 30,
|
||||
// 'spacing.edgeEdgeBetweenLayers': 40,
|
||||
// 'spacing.nodeSelfLoop': 50,
|
||||
'spacing.baseValue': 35,
|
||||
'elk.layered.unnecessaryBendpoints': true,
|
||||
'elk.layered.cycleBreaking.strategy': data4Layout.config.elk.cycleBreakingStrategy,
|
||||
// 'spacing.nodeNode': 20,
|
||||
// 'spacing.nodeNodeBetweenLayers': 25,
|
||||
// 'spacing.edgeNode': 20,
|
||||
// 'spacing.edgeNodeBetweenLayers': 10,
|
||||
// 'spacing.edgeEdge': 10,
|
||||
// 'spacing.edgeEdgeBetweenLayers': 20,
|
||||
// 'spacing.nodeSelfLoop': 20,
|
||||
|
||||
// Tweaking options
|
||||
// 'elk.layered.nodePlacement.favorStraightEdges': true,
|
||||
// 'nodePlacement.feedbackEdges': true,
|
||||
// 'elk.layered.wrapping.multiEdge.improveCuts': true,
|
||||
// 'elk.layered.wrapping.multiEdge.improveWrappedEdges': true,
|
||||
// 'elk.layered.wrapping.strategy': 'MULTI_EDGE',
|
||||
// 'elk.layered.edgeRouting.selfLoopDistribution': 'EQUALLY',
|
||||
// 'elk.layered.mergeHierarchyEdges': true,
|
||||
// 'elk.layered.feedbackEdges': true,
|
||||
// 'elk.layered.crossingMinimization.semiInteractive': true,
|
||||
// 'elk.layered.edgeRouting.splines.sloppy.layerSpacingFactor': 1,
|
||||
// 'elk.layered.edgeRouting.polyline.slopedEdgeZoneWidth': 4.0,
|
||||
// 'elk.layered.wrapping.validify.strategy': 'LOOK_BACK',
|
||||
// 'elk.insideSelfLoops.activate': true,
|
||||
// 'elk.alg.layered.options.EdgeStraighteningStrategy': 'NONE',
|
||||
// 'elk.layered.considerModelOrder.strategy': 'NODES_AND_EDGES', // NODES_AND_EDGES
|
||||
// 'elk.layered.wrapping.cutting.strategy': 'ARD', // NODES_AND_EDGES
|
||||
},
|
||||
children: [],
|
||||
edges: [],
|
||||
|
@ -19,8 +19,7 @@
|
||||
"mermaid"
|
||||
],
|
||||
"scripts": {
|
||||
"clean": "rimraf dist",
|
||||
"prepublishOnly": "pnpm -w run build"
|
||||
"clean": "rimraf dist"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -40,7 +39,7 @@
|
||||
"mermaid": "workspace:^"
|
||||
},
|
||||
"peerDependencies": {
|
||||
"mermaid": "workspace:>=10.0.0"
|
||||
"mermaid": "^10 || ^11"
|
||||
},
|
||||
"files": [
|
||||
"dist"
|
||||
|
32
packages/mermaid/CHANGELOG.md
Normal file
32
packages/mermaid/CHANGELOG.md
Normal file
@ -0,0 +1,32 @@
|
||||
# mermaid
|
||||
|
||||
## 11.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5664](https://github.com/mermaid-js/mermaid/pull/5664) [`5deaef4`](https://github.com/mermaid-js/mermaid/commit/5deaef456e74d796866431c26f69360e4e74dbff) Thanks [@Austin-Fulbright](https://github.com/Austin-Fulbright)! - chore: Migrate git graph to langium, use typescript for internals
|
||||
|
||||
- Updated dependencies [[`5deaef4`](https://github.com/mermaid-js/mermaid/commit/5deaef456e74d796866431c26f69360e4e74dbff)]:
|
||||
- @mermaid-js/parser@0.2.0
|
||||
|
||||
## 11.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#2](https://github.com/calvinvette/mermaid/pull/2) [`bf05d87`](https://github.com/mermaid-js/mermaid/commit/bf05d8781edacb580fdb053da167e968b7570117) Thanks [@calvinvette](https://github.com/calvinvette)! - test changeset
|
||||
|
||||
## 11.0.2
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- Updated dependencies [[`83926c9`](https://github.com/mermaid-js/mermaid/commit/83926c9707b09c34e300888186250191ee8ae30a)]:
|
||||
- @mermaid-js/parser@0.1.1
|
||||
|
||||
## 11.0.1
|
||||
|
||||
### Patch Changes
|
||||
|
||||
- [#5744](https://github.com/mermaid-js/mermaid/pull/5744) [`5013484`](https://github.com/mermaid-js/mermaid/commit/50134849246141ec400e33e08c12c10539b84de9) Thanks [@sidharthv96](https://github.com/sidharthv96)! - Release parser, test changesets
|
||||
|
||||
- Updated dependencies [[`5013484`](https://github.com/mermaid-js/mermaid/commit/50134849246141ec400e33e08c12c10539b84de9)]:
|
||||
- @mermaid-js/parser@0.1.0
|
@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "mermaid",
|
||||
"version": "11.0.0-alpha.7",
|
||||
"version": "11.0.2",
|
||||
"description": "Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.",
|
||||
"type": "module",
|
||||
"module": "./dist/mermaid.core.mjs",
|
||||
@ -48,8 +48,7 @@
|
||||
"types:build-config": "tsx scripts/create-types-from-json-schema.mts",
|
||||
"types:verify-config": "tsx scripts/create-types-from-json-schema.mts --verify",
|
||||
"checkCircle": "npx madge --circular ./src",
|
||||
"release": "pnpm build",
|
||||
"prepublishOnly": "cpy '../../README.*' ./ --cwd=. && pnpm docs:release-version && pnpm -w run build"
|
||||
"prepublishOnly": "pnpm docs:verify-version"
|
||||
},
|
||||
"repository": {
|
||||
"type": "git",
|
||||
@ -108,7 +107,6 @@
|
||||
"ajv": "^8.12.0",
|
||||
"chokidar": "^3.6.0",
|
||||
"concurrently": "^8.2.2",
|
||||
"cpy-cli": "^5.0.0",
|
||||
"csstree-validator": "^3.0.0",
|
||||
"globby": "^14.0.1",
|
||||
"jison": "^0.4.18",
|
||||
|
@ -5,23 +5,34 @@
|
||||
* So contributors adding new features will only have to add the placeholder and not worry about updating the version number.
|
||||
*
|
||||
*/
|
||||
import { readFile, writeFile } from 'fs/promises';
|
||||
import { posix } from 'path';
|
||||
import {
|
||||
getGlobs,
|
||||
getFilesFromGlobs,
|
||||
SOURCE_DOCS_DIR,
|
||||
readSyncedUTF8file,
|
||||
getGlobs,
|
||||
MERMAID_RELEASE_VERSION,
|
||||
readSyncedUTF8file,
|
||||
SOURCE_DOCS_DIR,
|
||||
} from './docs.mjs';
|
||||
import { writeFile } from 'fs/promises';
|
||||
|
||||
const verifyOnly: boolean = process.argv.includes('--verify');
|
||||
const versionPlaceholder = '<MERMAID_RELEASE_VERSION>';
|
||||
|
||||
const verifyDocumentation = async () => {
|
||||
const fileContent = await readFile('./src/docs/community/contributing.md', 'utf-8');
|
||||
if (!fileContent.includes(versionPlaceholder)) {
|
||||
console.error(
|
||||
`The placeholder ${versionPlaceholder} is not present in the contributing.md file.`
|
||||
);
|
||||
process.exit(1);
|
||||
}
|
||||
};
|
||||
|
||||
const main = async () => {
|
||||
await verifyDocumentation();
|
||||
const sourceDirGlob = posix.join('.', SOURCE_DOCS_DIR, '**');
|
||||
const mdFileGlobs = getGlobs([posix.join(sourceDirGlob, '*.md')]);
|
||||
mdFileGlobs.push('!**/community/development.md', '!**/community/code.md');
|
||||
mdFileGlobs.push('!**/community/contributing.md');
|
||||
const mdFiles = await getFilesFromGlobs(mdFileGlobs);
|
||||
mdFiles.sort();
|
||||
const mdFilesWithPlaceholder: string[] = [];
|
||||
|
@ -99,6 +99,16 @@ export interface MermaidConfig {
|
||||
*
|
||||
*/
|
||||
nodePlacementStrategy?: 'SIMPLE' | 'NETWORK_SIMPLEX' | 'LINEAR_SEGMENTS' | 'BRANDES_KOEPF';
|
||||
/**
|
||||
* This strategy decides how to find cycles in the graph and deciding which edges need adjustment to break loops.
|
||||
*
|
||||
*/
|
||||
cycleBreakingStrategy?:
|
||||
| 'GREEDY'
|
||||
| 'DEPTH_FIRST'
|
||||
| 'INTERACTIVE'
|
||||
| 'MODEL_ORDER'
|
||||
| 'GREEDY_MODEL_ORDER';
|
||||
};
|
||||
darkMode?: boolean;
|
||||
htmlLabels?: boolean;
|
||||
|
@ -519,6 +519,8 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
|
||||
// Append a text node containing the label
|
||||
const labelId = 'rel' + relCnt;
|
||||
|
||||
const labelText = rel.roleA.split(/<br ?\/>/g);
|
||||
|
||||
const labelNode = svg
|
||||
.append('text')
|
||||
.classed('er relationshipLabel', true)
|
||||
@ -528,8 +530,20 @@ const drawRelationshipFromLayout = function (svg, rel, g, insert, diagObj) {
|
||||
.style('text-anchor', 'middle')
|
||||
.style('dominant-baseline', 'middle')
|
||||
.style('font-family', getConfig().fontFamily)
|
||||
.style('font-size', conf.fontSize + 'px')
|
||||
.text(rel.roleA);
|
||||
.style('font-size', conf.fontSize + 'px');
|
||||
|
||||
if (labelText.length == 1) {
|
||||
labelNode.text(rel.roleA);
|
||||
} else {
|
||||
const firstShift = -(labelText.length - 1) * 0.5;
|
||||
labelText.forEach((txt, i) => {
|
||||
labelNode
|
||||
.append('tspan')
|
||||
.attr('x', labelPoint.x)
|
||||
.attr('dy', `${i === 0 ? firstShift : 1}em`)
|
||||
.text(txt);
|
||||
});
|
||||
}
|
||||
|
||||
// Figure out how big the opaque 'container' rectangle needs to be
|
||||
const labelBBox = labelNode.node().getBBox();
|
||||
|
@ -7,13 +7,14 @@ import type {
|
||||
const id = 'flowchart-v2';
|
||||
|
||||
const detector: DiagramDetector = (txt, config) => {
|
||||
if (
|
||||
config?.flowchart?.defaultRenderer === 'dagre-d3' ||
|
||||
config?.flowchart?.defaultRenderer === 'elk'
|
||||
) {
|
||||
if (config?.flowchart?.defaultRenderer === 'dagre-d3') {
|
||||
return false;
|
||||
}
|
||||
|
||||
if (config?.flowchart?.defaultRenderer === 'elk') {
|
||||
config.layout = 'elk';
|
||||
}
|
||||
|
||||
// If we have configured to use dagre-wrapper then we should return true in this function for graph code thus making it use the new flowchart diagram
|
||||
if (/^\s*graph/.test(txt) && config?.flowchart?.defaultRenderer === 'dagre-wrapper') {
|
||||
return true;
|
||||
|
@ -2,7 +2,7 @@ import { select } from 'd3';
|
||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
|
||||
import { log } from '../../logger.js';
|
||||
import { getDiagramElements } from '../../rendering-util/insertElementsForSize.js';
|
||||
import { getDiagramElement } from '../../rendering-util/insertElementsForSize.js';
|
||||
import { getRegisteredLayoutAlgorithm, render } from '../../rendering-util/render.js';
|
||||
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
|
||||
import type { LayoutData } from '../../rendering-util/types.js';
|
||||
@ -35,8 +35,8 @@ export const draw = async function (text: string, id: string, _version: string,
|
||||
log.debug('Before getData: ');
|
||||
const data4Layout = diag.db.getData() as LayoutData;
|
||||
log.debug('Data: ', data4Layout);
|
||||
// Create the root SVG - the element is the div containing the SVG element
|
||||
const { element, svg } = getDiagramElements(id, securityLevel);
|
||||
// Create the root SVG
|
||||
const svg = getDiagramElement(id, securityLevel);
|
||||
const direction = getDirection();
|
||||
|
||||
data4Layout.type = diag.type;
|
||||
@ -53,8 +53,8 @@ export const draw = async function (text: string, id: string, _version: string,
|
||||
|
||||
data4Layout.diagramId = id;
|
||||
log.debug('REF1:', data4Layout);
|
||||
await render(data4Layout, svg, element);
|
||||
const padding = data4Layout.config.flowchart?.padding ?? 8;
|
||||
await render(data4Layout, svg);
|
||||
const padding = data4Layout.config.flowchart?.diagramPadding ?? 8;
|
||||
utils.insertTitle(
|
||||
svg,
|
||||
'flowchartTitleText',
|
||||
|
1322
packages/mermaid/src/diagrams/git/gitGraph.spec.ts
Normal file
1322
packages/mermaid/src/diagrams/git/gitGraph.spec.ts
Normal file
File diff suppressed because it is too large
Load Diff
@ -1,535 +0,0 @@
|
||||
import { log } from '../../logger.js';
|
||||
import { random } from '../../utils.js';
|
||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import common from '../common/common.js';
|
||||
import {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
clear as commonClear,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
} from '../common/commonDb.js';
|
||||
|
||||
let { mainBranchName, mainBranchOrder } = getConfig().gitGraph;
|
||||
let commits = new Map();
|
||||
let head = null;
|
||||
let branchesConfig = new Map();
|
||||
branchesConfig.set(mainBranchName, { name: mainBranchName, order: mainBranchOrder });
|
||||
let branches = new Map();
|
||||
branches.set(mainBranchName, head);
|
||||
let curBranch = mainBranchName;
|
||||
let direction = 'LR';
|
||||
let seq = 0;
|
||||
|
||||
/**
|
||||
*
|
||||
*/
|
||||
function getId() {
|
||||
return random({ length: 7 });
|
||||
}
|
||||
|
||||
// /**
|
||||
// * @param currentCommit
|
||||
// * @param otherCommit
|
||||
// */
|
||||
|
||||
// function isFastForwardable(currentCommit, otherCommit) {
|
||||
// log.debug('Entering isFastForwardable:', currentCommit.id, otherCommit.id);
|
||||
// let cnt = 0;
|
||||
// while (currentCommit.seq <= otherCommit.seq && currentCommit !== otherCommit && cnt < 1000) {
|
||||
// cnt++;
|
||||
// // only if other branch has more commits
|
||||
// if (otherCommit.parent == null) break;
|
||||
// if (Array.isArray(otherCommit.parent)) {
|
||||
// log.debug('In merge commit:', otherCommit.parent);
|
||||
// return (
|
||||
// isFastForwardable(currentCommit, commits.get(otherCommit.parent[0])) ||
|
||||
// isFastForwardable(currentCommit, commits.get(otherCommit.parent[1]))
|
||||
// );
|
||||
// } else {
|
||||
// otherCommit = commits.get(otherCommit.parent);
|
||||
// }
|
||||
// }
|
||||
// log.debug(currentCommit.id, otherCommit.id);
|
||||
// return currentCommit.id === otherCommit.id;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @param currentCommit
|
||||
* @param otherCommit
|
||||
*/
|
||||
// function isReachableFrom(currentCommit, otherCommit) {
|
||||
// const currentSeq = currentCommit.seq;
|
||||
// const otherSeq = otherCommit.seq;
|
||||
// if (currentSeq > otherSeq) return isFastForwardable(otherCommit, currentCommit);
|
||||
// return false;
|
||||
// }
|
||||
|
||||
/**
|
||||
* @param list
|
||||
* @param fn
|
||||
*/
|
||||
function uniqBy(list, fn) {
|
||||
const recordMap = Object.create(null);
|
||||
return list.reduce((out, item) => {
|
||||
const key = fn(item);
|
||||
if (!recordMap[key]) {
|
||||
recordMap[key] = true;
|
||||
out.push(item);
|
||||
}
|
||||
return out;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export const setDirection = function (dir) {
|
||||
direction = dir;
|
||||
};
|
||||
let options = {};
|
||||
export const setOptions = function (rawOptString) {
|
||||
log.debug('options str', rawOptString);
|
||||
rawOptString = rawOptString?.trim();
|
||||
rawOptString = rawOptString || '{}';
|
||||
try {
|
||||
options = JSON.parse(rawOptString);
|
||||
} catch (e) {
|
||||
log.error('error while parsing gitGraph options', e.message);
|
||||
}
|
||||
};
|
||||
|
||||
export const getOptions = function () {
|
||||
return options;
|
||||
};
|
||||
|
||||
export const commit = function (msg, id, type, tags) {
|
||||
log.debug('Entering commit:', msg, id, type, tags);
|
||||
const config = getConfig();
|
||||
id = common.sanitizeText(id, config);
|
||||
msg = common.sanitizeText(msg, config);
|
||||
tags = tags?.map((tag) => common.sanitizeText(tag, config));
|
||||
const commit = {
|
||||
id: id ? id : seq + '-' + getId(),
|
||||
message: msg,
|
||||
seq: seq++,
|
||||
type: type ? type : commitType.NORMAL,
|
||||
tags: tags ?? [],
|
||||
parents: head == null ? [] : [head.id],
|
||||
branch: curBranch,
|
||||
};
|
||||
head = commit;
|
||||
commits.set(commit.id, commit);
|
||||
branches.set(curBranch, commit.id);
|
||||
log.debug('in pushCommit ' + commit.id);
|
||||
};
|
||||
|
||||
export const branch = function (name, order) {
|
||||
name = common.sanitizeText(name, getConfig());
|
||||
if (!branches.has(name)) {
|
||||
branches.set(name, head != null ? head.id : null);
|
||||
branchesConfig.set(name, { name, order: order ? parseInt(order, 10) : null });
|
||||
checkout(name);
|
||||
log.debug('in createBranch');
|
||||
} else {
|
||||
let error = new Error(
|
||||
'Trying to create an existing branch. (Help: Either use a new name if you want create a new branch or try using "checkout ' +
|
||||
name +
|
||||
'")'
|
||||
);
|
||||
error.hash = {
|
||||
text: 'branch ' + name,
|
||||
token: 'branch ' + name,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['"checkout ' + name + '"'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
};
|
||||
|
||||
export const merge = function (otherBranch, custom_id, override_type, custom_tags) {
|
||||
const config = getConfig();
|
||||
otherBranch = common.sanitizeText(otherBranch, config);
|
||||
custom_id = common.sanitizeText(custom_id, config);
|
||||
|
||||
const currentCommit = commits.get(branches.get(curBranch));
|
||||
const otherCommit = commits.get(branches.get(otherBranch));
|
||||
if (curBranch === otherBranch) {
|
||||
let error = new Error('Incorrect usage of "merge". Cannot merge a branch to itself');
|
||||
error.hash = {
|
||||
text: 'merge ' + otherBranch,
|
||||
token: 'merge ' + otherBranch,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['branch abc'],
|
||||
};
|
||||
throw error;
|
||||
} else if (currentCommit === undefined || !currentCommit) {
|
||||
let error = new Error(
|
||||
'Incorrect usage of "merge". Current branch (' + curBranch + ')has no commits'
|
||||
);
|
||||
error.hash = {
|
||||
text: 'merge ' + otherBranch,
|
||||
token: 'merge ' + otherBranch,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['commit'],
|
||||
};
|
||||
throw error;
|
||||
} else if (!branches.has(otherBranch)) {
|
||||
let error = new Error(
|
||||
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') does not exist'
|
||||
);
|
||||
error.hash = {
|
||||
text: 'merge ' + otherBranch,
|
||||
token: 'merge ' + otherBranch,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['branch ' + otherBranch],
|
||||
};
|
||||
throw error;
|
||||
} else if (otherCommit === undefined || !otherCommit) {
|
||||
let error = new Error(
|
||||
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') has no commits'
|
||||
);
|
||||
error.hash = {
|
||||
text: 'merge ' + otherBranch,
|
||||
token: 'merge ' + otherBranch,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['"commit"'],
|
||||
};
|
||||
throw error;
|
||||
} else if (currentCommit === otherCommit) {
|
||||
let error = new Error('Incorrect usage of "merge". Both branches have same head');
|
||||
error.hash = {
|
||||
text: 'merge ' + otherBranch,
|
||||
token: 'merge ' + otherBranch,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['branch abc'],
|
||||
};
|
||||
throw error;
|
||||
} else if (custom_id && commits.has(custom_id)) {
|
||||
let error = new Error(
|
||||
'Incorrect usage of "merge". Commit with id:' +
|
||||
custom_id +
|
||||
' already exists, use different custom Id'
|
||||
);
|
||||
error.hash = {
|
||||
text: 'merge ' + otherBranch + custom_id + override_type + custom_tags?.join(','),
|
||||
token: 'merge ' + otherBranch + custom_id + override_type + custom_tags?.join(','),
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: [
|
||||
`merge ${otherBranch} ${custom_id}_UNIQUE ${override_type} ${custom_tags?.join(',')}`,
|
||||
],
|
||||
};
|
||||
|
||||
throw error;
|
||||
}
|
||||
// if (isReachableFrom(currentCommit, otherCommit)) {
|
||||
// log.debug('Already merged');
|
||||
// return;
|
||||
// }
|
||||
// if (isFastForwardable(currentCommit, otherCommit)) {
|
||||
// branches.set(curBranch, branches.get(otherBranch));
|
||||
// head = commits.get(branches.get(curBranch));
|
||||
// } else {
|
||||
// create merge commit
|
||||
const commit = {
|
||||
id: custom_id ? custom_id : seq + '-' + getId(),
|
||||
message: 'merged branch ' + otherBranch + ' into ' + curBranch,
|
||||
seq: seq++,
|
||||
parents: [head == null ? null : head.id, branches.get(otherBranch)],
|
||||
branch: curBranch,
|
||||
type: commitType.MERGE,
|
||||
customType: override_type,
|
||||
customId: custom_id ? true : false,
|
||||
tags: custom_tags ? custom_tags : [],
|
||||
};
|
||||
head = commit;
|
||||
commits.set(commit.id, commit);
|
||||
branches.set(curBranch, commit.id);
|
||||
// }
|
||||
log.debug(branches);
|
||||
log.debug('in mergeBranch');
|
||||
};
|
||||
|
||||
export const cherryPick = function (sourceId, targetId, tags, parentCommitId) {
|
||||
log.debug('Entering cherryPick:', sourceId, targetId, tags);
|
||||
const config = getConfig();
|
||||
sourceId = common.sanitizeText(sourceId, config);
|
||||
targetId = common.sanitizeText(targetId, config);
|
||||
tags = tags?.map((tag) => common.sanitizeText(tag, config));
|
||||
parentCommitId = common.sanitizeText(parentCommitId, config);
|
||||
|
||||
if (!sourceId || !commits.has(sourceId)) {
|
||||
let error = new Error(
|
||||
'Incorrect usage of "cherryPick". Source commit id should exist and provided'
|
||||
);
|
||||
error.hash = {
|
||||
text: 'cherryPick ' + sourceId + ' ' + targetId,
|
||||
token: 'cherryPick ' + sourceId + ' ' + targetId,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['cherry-pick abc'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
let sourceCommit = commits.get(sourceId);
|
||||
let sourceCommitBranch = sourceCommit.branch;
|
||||
if (
|
||||
parentCommitId &&
|
||||
!(Array.isArray(sourceCommit.parents) && sourceCommit.parents.includes(parentCommitId))
|
||||
) {
|
||||
let error = new Error(
|
||||
'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
if (sourceCommit.type === commitType.MERGE && !parentCommitId) {
|
||||
let error = new Error(
|
||||
'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.'
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
if (!targetId || !commits.has(targetId)) {
|
||||
// cherry-pick source commit to current branch
|
||||
|
||||
if (sourceCommitBranch === curBranch) {
|
||||
let error = new Error(
|
||||
'Incorrect usage of "cherryPick". Source commit is already on current branch'
|
||||
);
|
||||
error.hash = {
|
||||
text: 'cherryPick ' + sourceId + ' ' + targetId,
|
||||
token: 'cherryPick ' + sourceId + ' ' + targetId,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['cherry-pick abc'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
const currentCommit = commits.get(branches.get(curBranch));
|
||||
if (currentCommit === undefined || !currentCommit) {
|
||||
let error = new Error(
|
||||
'Incorrect usage of "cherry-pick". Current branch (' + curBranch + ')has no commits'
|
||||
);
|
||||
error.hash = {
|
||||
text: 'cherryPick ' + sourceId + ' ' + targetId,
|
||||
token: 'cherryPick ' + sourceId + ' ' + targetId,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['cherry-pick abc'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
const commit = {
|
||||
id: seq + '-' + getId(),
|
||||
message: 'cherry-picked ' + sourceCommit + ' into ' + curBranch,
|
||||
seq: seq++,
|
||||
parents: [head == null ? null : head.id, sourceCommit.id],
|
||||
branch: curBranch,
|
||||
type: commitType.CHERRY_PICK,
|
||||
tags: tags
|
||||
? tags.filter(Boolean)
|
||||
: [
|
||||
`cherry-pick:${sourceCommit.id}${
|
||||
sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : ''
|
||||
}`,
|
||||
],
|
||||
};
|
||||
head = commit;
|
||||
commits.set(commit.id, commit);
|
||||
branches.set(curBranch, commit.id);
|
||||
log.debug(branches);
|
||||
log.debug('in cherryPick');
|
||||
}
|
||||
};
|
||||
export const checkout = function (branch) {
|
||||
branch = common.sanitizeText(branch, getConfig());
|
||||
if (!branches.has(branch)) {
|
||||
let error = new Error(
|
||||
'Trying to checkout branch which is not yet created. (Help try using "branch ' + branch + '")'
|
||||
);
|
||||
error.hash = {
|
||||
text: 'checkout ' + branch,
|
||||
token: 'checkout ' + branch,
|
||||
line: '1',
|
||||
loc: { first_line: 1, last_line: 1, first_column: 1, last_column: 1 },
|
||||
expected: ['"branch ' + branch + '"'],
|
||||
};
|
||||
throw error;
|
||||
} else {
|
||||
curBranch = branch;
|
||||
const id = branches.get(curBranch);
|
||||
head = commits.get(id);
|
||||
}
|
||||
};
|
||||
|
||||
// export const reset = function (commitRef) {
|
||||
// log.debug('in reset', commitRef);
|
||||
// const ref = commitRef.split(':')[0];
|
||||
// let parentCount = parseInt(commitRef.split(':')[1]);
|
||||
// let commit = ref === 'HEAD' ? head : commits.get(branches.get(ref));
|
||||
// log.debug(commit, parentCount);
|
||||
// while (parentCount > 0) {
|
||||
// commit = commits.get(commit.parent);
|
||||
// parentCount--;
|
||||
// if (!commit) {
|
||||
// const err = 'Critical error - unique parent commit not found during reset';
|
||||
// log.error(err);
|
||||
// throw err;
|
||||
// }
|
||||
// }
|
||||
// head = commit;
|
||||
// branches[curBranch] = commit.id;
|
||||
// };
|
||||
|
||||
/**
|
||||
* @param arr
|
||||
* @param key
|
||||
* @param newVal
|
||||
*/
|
||||
function upsert(arr, key, newVal) {
|
||||
const index = arr.indexOf(key);
|
||||
if (index === -1) {
|
||||
arr.push(newVal);
|
||||
} else {
|
||||
arr.splice(index, 1, newVal);
|
||||
}
|
||||
}
|
||||
|
||||
/** @param commitArr */
|
||||
function prettyPrintCommitHistory(commitArr) {
|
||||
const commit = commitArr.reduce((out, commit) => {
|
||||
if (out.seq > commit.seq) {
|
||||
return out;
|
||||
}
|
||||
return commit;
|
||||
}, commitArr[0]);
|
||||
let line = '';
|
||||
commitArr.forEach(function (c) {
|
||||
if (c === commit) {
|
||||
line += '\t*';
|
||||
} else {
|
||||
line += '\t|';
|
||||
}
|
||||
});
|
||||
const label = [line, commit.id, commit.seq];
|
||||
for (let branch in branches) {
|
||||
if (branches.get(branch) === commit.id) {
|
||||
label.push(branch);
|
||||
}
|
||||
}
|
||||
log.debug(label.join(' '));
|
||||
if (commit.parents && commit.parents.length == 2) {
|
||||
const newCommit = commits.get(commit.parents[0]);
|
||||
upsert(commitArr, commit, newCommit);
|
||||
commitArr.push(commits.get(commit.parents[1]));
|
||||
} else if (commit.parents.length == 0) {
|
||||
return;
|
||||
} else {
|
||||
const nextCommit = commits.get(commit.parents);
|
||||
upsert(commitArr, commit, nextCommit);
|
||||
}
|
||||
commitArr = uniqBy(commitArr, (c) => c.id);
|
||||
prettyPrintCommitHistory(commitArr);
|
||||
}
|
||||
|
||||
export const prettyPrint = function () {
|
||||
log.debug(commits);
|
||||
const node = getCommitsArray()[0];
|
||||
prettyPrintCommitHistory([node]);
|
||||
};
|
||||
|
||||
export const clear = function () {
|
||||
commits = new Map();
|
||||
head = null;
|
||||
const { mainBranchName, mainBranchOrder } = getConfig().gitGraph;
|
||||
branches = new Map();
|
||||
branches.set(mainBranchName, null);
|
||||
branchesConfig = new Map();
|
||||
branchesConfig.set(mainBranchName, { name: mainBranchName, order: mainBranchOrder });
|
||||
curBranch = mainBranchName;
|
||||
seq = 0;
|
||||
commonClear();
|
||||
};
|
||||
|
||||
export const getBranchesAsObjArray = function () {
|
||||
const branchesArray = [...branchesConfig.values()]
|
||||
.map((branchConfig, i) => {
|
||||
if (branchConfig.order !== null) {
|
||||
return branchConfig;
|
||||
}
|
||||
return {
|
||||
...branchConfig,
|
||||
order: parseFloat(`0.${i}`, 10),
|
||||
};
|
||||
})
|
||||
.sort((a, b) => a.order - b.order)
|
||||
.map(({ name }) => ({ name }));
|
||||
|
||||
return branchesArray;
|
||||
};
|
||||
|
||||
export const getBranches = function () {
|
||||
return branches;
|
||||
};
|
||||
export const getCommits = function () {
|
||||
return commits;
|
||||
};
|
||||
export const getCommitsArray = function () {
|
||||
const commitArr = [...commits.values()];
|
||||
commitArr.forEach(function (o) {
|
||||
log.debug(o.id);
|
||||
});
|
||||
commitArr.sort((a, b) => a.seq - b.seq);
|
||||
return commitArr;
|
||||
};
|
||||
export const getCurrentBranch = function () {
|
||||
return curBranch;
|
||||
};
|
||||
export const getDirection = function () {
|
||||
return direction;
|
||||
};
|
||||
export const getHead = function () {
|
||||
return head;
|
||||
};
|
||||
|
||||
export const commitType = {
|
||||
NORMAL: 0,
|
||||
REVERSE: 1,
|
||||
HIGHLIGHT: 2,
|
||||
MERGE: 3,
|
||||
CHERRY_PICK: 4,
|
||||
};
|
||||
|
||||
export default {
|
||||
getConfig: () => getConfig().gitGraph,
|
||||
setDirection,
|
||||
setOptions,
|
||||
getOptions,
|
||||
commit,
|
||||
branch,
|
||||
merge,
|
||||
cherryPick,
|
||||
checkout,
|
||||
//reset,
|
||||
prettyPrint,
|
||||
clear,
|
||||
getBranchesAsObjArray,
|
||||
getBranches,
|
||||
getCommits,
|
||||
getCommitsArray,
|
||||
getCurrentBranch,
|
||||
getDirection,
|
||||
getHead,
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
commitType,
|
||||
};
|
522
packages/mermaid/src/diagrams/git/gitGraphAst.ts
Normal file
522
packages/mermaid/src/diagrams/git/gitGraphAst.ts
Normal file
@ -0,0 +1,522 @@
|
||||
import { log } from '../../logger.js';
|
||||
import { cleanAndMerge, random } from '../../utils.js';
|
||||
import { getConfig as commonGetConfig } from '../../config.js';
|
||||
import common from '../common/common.js';
|
||||
import {
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
clear as commonClear,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
} from '../common/commonDb.js';
|
||||
import type {
|
||||
DiagramOrientation,
|
||||
Commit,
|
||||
GitGraphDB,
|
||||
CommitDB,
|
||||
MergeDB,
|
||||
BranchDB,
|
||||
CherryPickDB,
|
||||
} from './gitGraphTypes.js';
|
||||
import { commitType } from './gitGraphTypes.js';
|
||||
import { ImperativeState } from '../../utils/imperativeState.js';
|
||||
|
||||
import DEFAULT_CONFIG from '../../defaultConfig.js';
|
||||
|
||||
import type { GitGraphDiagramConfig } from '../../config.type.js';
|
||||
interface GitGraphState {
|
||||
commits: Map<string, Commit>;
|
||||
head: Commit | null;
|
||||
branchConfig: Map<string, { name: string; order: number | undefined }>;
|
||||
branches: Map<string, string | null>;
|
||||
currBranch: string;
|
||||
direction: DiagramOrientation;
|
||||
seq: number;
|
||||
options: any;
|
||||
}
|
||||
|
||||
const DEFAULT_GITGRAPH_CONFIG: Required<GitGraphDiagramConfig> = DEFAULT_CONFIG.gitGraph;
|
||||
const getConfig = (): Required<GitGraphDiagramConfig> => {
|
||||
const config = cleanAndMerge({
|
||||
...DEFAULT_GITGRAPH_CONFIG,
|
||||
...commonGetConfig().gitGraph,
|
||||
});
|
||||
return config;
|
||||
};
|
||||
|
||||
const state = new ImperativeState<GitGraphState>(() => {
|
||||
const config = getConfig();
|
||||
const mainBranchName = config.mainBranchName;
|
||||
const mainBranchOrder = config.mainBranchOrder;
|
||||
return {
|
||||
mainBranchName,
|
||||
commits: new Map(),
|
||||
head: null,
|
||||
branchConfig: new Map([[mainBranchName, { name: mainBranchName, order: mainBranchOrder }]]),
|
||||
branches: new Map([[mainBranchName, null]]),
|
||||
currBranch: mainBranchName,
|
||||
direction: 'LR',
|
||||
seq: 0,
|
||||
options: {},
|
||||
};
|
||||
});
|
||||
|
||||
function getID() {
|
||||
return random({ length: 7 });
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list - list of items
|
||||
* @param fn - function to get the key
|
||||
*/
|
||||
function uniqBy(list: any[], fn: (item: any) => any) {
|
||||
const recordMap = Object.create(null);
|
||||
return list.reduce((out, item) => {
|
||||
const key = fn(item);
|
||||
if (!recordMap[key]) {
|
||||
recordMap[key] = true;
|
||||
out.push(item);
|
||||
}
|
||||
return out;
|
||||
}, []);
|
||||
}
|
||||
|
||||
export const setDirection = function (dir: DiagramOrientation) {
|
||||
state.records.direction = dir;
|
||||
};
|
||||
|
||||
export const setOptions = function (rawOptString: string) {
|
||||
log.debug('options str', rawOptString);
|
||||
rawOptString = rawOptString?.trim();
|
||||
rawOptString = rawOptString || '{}';
|
||||
try {
|
||||
state.records.options = JSON.parse(rawOptString);
|
||||
} catch (e: any) {
|
||||
log.error('error while parsing gitGraph options', e.message);
|
||||
}
|
||||
};
|
||||
|
||||
export const getOptions = function () {
|
||||
return state.records.options;
|
||||
};
|
||||
|
||||
export const commit = function (commitDB: CommitDB) {
|
||||
let msg = commitDB.msg;
|
||||
let id = commitDB.id;
|
||||
const type = commitDB.type;
|
||||
let tags = commitDB.tags;
|
||||
|
||||
log.info('commit', msg, id, type, tags);
|
||||
log.debug('Entering commit:', msg, id, type, tags);
|
||||
const config = getConfig();
|
||||
id = common.sanitizeText(id, config);
|
||||
msg = common.sanitizeText(msg, config);
|
||||
tags = tags?.map((tag) => common.sanitizeText(tag, config));
|
||||
const newCommit: Commit = {
|
||||
id: id ? id : state.records.seq + '-' + getID(),
|
||||
message: msg,
|
||||
seq: state.records.seq++,
|
||||
type: type ?? commitType.NORMAL,
|
||||
tags: tags ?? [],
|
||||
parents: state.records.head == null ? [] : [state.records.head.id],
|
||||
branch: state.records.currBranch,
|
||||
};
|
||||
state.records.head = newCommit;
|
||||
log.info('main branch', config.mainBranchName);
|
||||
state.records.commits.set(newCommit.id, newCommit);
|
||||
state.records.branches.set(state.records.currBranch, newCommit.id);
|
||||
log.debug('in pushCommit ' + newCommit.id);
|
||||
};
|
||||
|
||||
export const branch = function (branchDB: BranchDB) {
|
||||
let name = branchDB.name;
|
||||
const order = branchDB.order;
|
||||
name = common.sanitizeText(name, getConfig());
|
||||
if (state.records.branches.has(name)) {
|
||||
throw new Error(
|
||||
`Trying to create an existing branch. (Help: Either use a new name if you want create a new branch or try using "checkout ${name}")`
|
||||
);
|
||||
}
|
||||
|
||||
state.records.branches.set(name, state.records.head != null ? state.records.head.id : null);
|
||||
state.records.branchConfig.set(name, { name, order });
|
||||
checkout(name);
|
||||
log.debug('in createBranch');
|
||||
};
|
||||
|
||||
export const merge = (mergeDB: MergeDB): void => {
|
||||
let otherBranch = mergeDB.branch;
|
||||
let customId = mergeDB.id;
|
||||
const overrideType = mergeDB.type;
|
||||
const customTags = mergeDB.tags;
|
||||
const config = getConfig();
|
||||
otherBranch = common.sanitizeText(otherBranch, config);
|
||||
if (customId) {
|
||||
customId = common.sanitizeText(customId, config);
|
||||
}
|
||||
const currentBranchCheck = state.records.branches.get(state.records.currBranch);
|
||||
const otherBranchCheck = state.records.branches.get(otherBranch);
|
||||
const currentCommit = currentBranchCheck
|
||||
? state.records.commits.get(currentBranchCheck)
|
||||
: undefined;
|
||||
const otherCommit: Commit | undefined = otherBranchCheck
|
||||
? state.records.commits.get(otherBranchCheck)
|
||||
: undefined;
|
||||
if (currentCommit && otherCommit && currentCommit.branch === otherBranch) {
|
||||
throw new Error(`Cannot merge branch '${otherBranch}' into itself.`);
|
||||
}
|
||||
if (state.records.currBranch === otherBranch) {
|
||||
const error: any = new Error('Incorrect usage of "merge". Cannot merge a branch to itself');
|
||||
error.hash = {
|
||||
text: `merge ${otherBranch}`,
|
||||
token: `merge ${otherBranch}`,
|
||||
expected: ['branch abc'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
if (currentCommit === undefined || !currentCommit) {
|
||||
const error: any = new Error(
|
||||
`Incorrect usage of "merge". Current branch (${state.records.currBranch})has no commits`
|
||||
);
|
||||
error.hash = {
|
||||
text: `merge ${otherBranch}`,
|
||||
token: `merge ${otherBranch}`,
|
||||
expected: ['commit'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
if (!state.records.branches.has(otherBranch)) {
|
||||
const error: any = new Error(
|
||||
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') does not exist'
|
||||
);
|
||||
error.hash = {
|
||||
text: `merge ${otherBranch}`,
|
||||
token: `merge ${otherBranch}`,
|
||||
expected: [`branch ${otherBranch}`],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
if (otherCommit === undefined || !otherCommit) {
|
||||
const error: any = new Error(
|
||||
'Incorrect usage of "merge". Branch to be merged (' + otherBranch + ') has no commits'
|
||||
);
|
||||
error.hash = {
|
||||
text: `merge ${otherBranch}`,
|
||||
token: `merge ${otherBranch}`,
|
||||
expected: ['"commit"'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
if (currentCommit === otherCommit) {
|
||||
const error: any = new Error('Incorrect usage of "merge". Both branches have same head');
|
||||
error.hash = {
|
||||
text: `merge ${otherBranch}`,
|
||||
token: `merge ${otherBranch}`,
|
||||
expected: ['branch abc'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
if (customId && state.records.commits.has(customId)) {
|
||||
const error: any = new Error(
|
||||
'Incorrect usage of "merge". Commit with id:' +
|
||||
customId +
|
||||
' already exists, use different custom Id'
|
||||
);
|
||||
error.hash = {
|
||||
text: `merge ${otherBranch} ${customId} ${overrideType} ${customTags?.join(' ')}`,
|
||||
token: `merge ${otherBranch} ${customId} ${overrideType} ${customTags?.join(' ')}`,
|
||||
expected: [
|
||||
`merge ${otherBranch} ${customId}_UNIQUE ${overrideType} ${customTags?.join(' ')}`,
|
||||
],
|
||||
};
|
||||
|
||||
throw error;
|
||||
}
|
||||
|
||||
const verifiedBranch: string = otherBranchCheck ? otherBranchCheck : ''; //figure out a cleaner way to do this
|
||||
|
||||
const commit = {
|
||||
id: customId || `${state.records.seq}-${getID()}`,
|
||||
message: `merged branch ${otherBranch} into ${state.records.currBranch}`,
|
||||
seq: state.records.seq++,
|
||||
parents: state.records.head == null ? [] : [state.records.head.id, verifiedBranch],
|
||||
branch: state.records.currBranch,
|
||||
type: commitType.MERGE,
|
||||
customType: overrideType,
|
||||
customId: customId ? true : false,
|
||||
tags: customTags ?? [],
|
||||
} satisfies Commit;
|
||||
state.records.head = commit;
|
||||
state.records.commits.set(commit.id, commit);
|
||||
state.records.branches.set(state.records.currBranch, commit.id);
|
||||
log.debug(state.records.branches);
|
||||
log.debug('in mergeBranch');
|
||||
};
|
||||
|
||||
export const cherryPick = function (cherryPickDB: CherryPickDB) {
|
||||
let sourceId = cherryPickDB.id;
|
||||
let targetId = cherryPickDB.targetId;
|
||||
let tags = cherryPickDB.tags;
|
||||
let parentCommitId = cherryPickDB.parent;
|
||||
log.debug('Entering cherryPick:', sourceId, targetId, tags);
|
||||
const config = getConfig();
|
||||
sourceId = common.sanitizeText(sourceId, config);
|
||||
targetId = common.sanitizeText(targetId, config);
|
||||
|
||||
tags = tags?.map((tag) => common.sanitizeText(tag, config));
|
||||
|
||||
parentCommitId = common.sanitizeText(parentCommitId, config);
|
||||
|
||||
if (!sourceId || !state.records.commits.has(sourceId)) {
|
||||
const error: any = new Error(
|
||||
'Incorrect usage of "cherryPick". Source commit id should exist and provided'
|
||||
);
|
||||
error.hash = {
|
||||
text: `cherryPick ${sourceId} ${targetId}`,
|
||||
token: `cherryPick ${sourceId} ${targetId}`,
|
||||
expected: ['cherry-pick abc'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
|
||||
const sourceCommit = state.records.commits.get(sourceId);
|
||||
if (sourceCommit === undefined || !sourceCommit) {
|
||||
throw new Error('Incorrect usage of "cherryPick". Source commit id should exist and provided');
|
||||
}
|
||||
if (
|
||||
parentCommitId &&
|
||||
!(Array.isArray(sourceCommit.parents) && sourceCommit.parents.includes(parentCommitId))
|
||||
) {
|
||||
const error = new Error(
|
||||
'Invalid operation: The specified parent commit is not an immediate parent of the cherry-picked commit.'
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
const sourceCommitBranch = sourceCommit.branch;
|
||||
if (sourceCommit.type === commitType.MERGE && !parentCommitId) {
|
||||
const error = new Error(
|
||||
'Incorrect usage of cherry-pick: If the source commit is a merge commit, an immediate parent commit must be specified.'
|
||||
);
|
||||
throw error;
|
||||
}
|
||||
if (!targetId || !state.records.commits.has(targetId)) {
|
||||
// cherry-pick source commit to current branch
|
||||
|
||||
if (sourceCommitBranch === state.records.currBranch) {
|
||||
const error: any = new Error(
|
||||
'Incorrect usage of "cherryPick". Source commit is already on current branch'
|
||||
);
|
||||
error.hash = {
|
||||
text: `cherryPick ${sourceId} ${targetId}`,
|
||||
token: `cherryPick ${sourceId} ${targetId}`,
|
||||
expected: ['cherry-pick abc'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
const currentCommitId = state.records.branches.get(state.records.currBranch);
|
||||
if (currentCommitId === undefined || !currentCommitId) {
|
||||
const error: any = new Error(
|
||||
`Incorrect usage of "cherry-pick". Current branch (${state.records.currBranch})has no commits`
|
||||
);
|
||||
error.hash = {
|
||||
text: `cherryPick ${sourceId} ${targetId}`,
|
||||
token: `cherryPick ${sourceId} ${targetId}`,
|
||||
expected: ['cherry-pick abc'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
|
||||
const currentCommit = state.records.commits.get(currentCommitId);
|
||||
if (currentCommit === undefined || !currentCommit) {
|
||||
const error: any = new Error(
|
||||
`Incorrect usage of "cherry-pick". Current branch (${state.records.currBranch})has no commits`
|
||||
);
|
||||
error.hash = {
|
||||
text: `cherryPick ${sourceId} ${targetId}`,
|
||||
token: `cherryPick ${sourceId} ${targetId}`,
|
||||
expected: ['cherry-pick abc'],
|
||||
};
|
||||
throw error;
|
||||
}
|
||||
const commit = {
|
||||
id: state.records.seq + '-' + getID(),
|
||||
message: `cherry-picked ${sourceCommit?.message} into ${state.records.currBranch}`,
|
||||
seq: state.records.seq++,
|
||||
parents: state.records.head == null ? [] : [state.records.head.id, sourceCommit.id],
|
||||
branch: state.records.currBranch,
|
||||
type: commitType.CHERRY_PICK,
|
||||
tags: tags
|
||||
? tags.filter(Boolean)
|
||||
: [
|
||||
`cherry-pick:${sourceCommit.id}${
|
||||
sourceCommit.type === commitType.MERGE ? `|parent:${parentCommitId}` : ''
|
||||
}`,
|
||||
],
|
||||
};
|
||||
|
||||
state.records.head = commit;
|
||||
state.records.commits.set(commit.id, commit);
|
||||
state.records.branches.set(state.records.currBranch, commit.id);
|
||||
log.debug(state.records.branches);
|
||||
log.debug('in cherryPick');
|
||||
}
|
||||
};
|
||||
export const checkout = function (branch: string) {
|
||||
branch = common.sanitizeText(branch, getConfig());
|
||||
if (!state.records.branches.has(branch)) {
|
||||
const error: any = new Error(
|
||||
`Trying to checkout branch which is not yet created. (Help try using "branch ${branch}")`
|
||||
);
|
||||
error.hash = {
|
||||
text: `checkout ${branch}`,
|
||||
token: `checkout ${branch}`,
|
||||
expected: [`branch ${branch}`],
|
||||
};
|
||||
throw error;
|
||||
} else {
|
||||
state.records.currBranch = branch;
|
||||
const id = state.records.branches.get(state.records.currBranch);
|
||||
if (id === undefined || !id) {
|
||||
state.records.head = null;
|
||||
} else {
|
||||
state.records.head = state.records.commits.get(id) ?? null;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* @param arr - array
|
||||
* @param key - key
|
||||
* @param newVal - new value
|
||||
*/
|
||||
function upsert(arr: any[], key: any, newVal: any) {
|
||||
const index = arr.indexOf(key);
|
||||
if (index === -1) {
|
||||
arr.push(newVal);
|
||||
} else {
|
||||
arr.splice(index, 1, newVal);
|
||||
}
|
||||
}
|
||||
|
||||
function prettyPrintCommitHistory(commitArr: Commit[]) {
|
||||
const commit = commitArr.reduce((out, commit) => {
|
||||
if (out.seq > commit.seq) {
|
||||
return out;
|
||||
}
|
||||
return commit;
|
||||
}, commitArr[0]);
|
||||
let line = '';
|
||||
commitArr.forEach(function (c) {
|
||||
if (c === commit) {
|
||||
line += '\t*';
|
||||
} else {
|
||||
line += '\t|';
|
||||
}
|
||||
});
|
||||
const label = [line, commit.id, commit.seq];
|
||||
for (const branch in state.records.branches) {
|
||||
if (state.records.branches.get(branch) === commit.id) {
|
||||
label.push(branch);
|
||||
}
|
||||
}
|
||||
log.debug(label.join(' '));
|
||||
if (commit.parents && commit.parents.length == 2 && commit.parents[0] && commit.parents[1]) {
|
||||
const newCommit = state.records.commits.get(commit.parents[0]);
|
||||
upsert(commitArr, commit, newCommit);
|
||||
if (commit.parents[1]) {
|
||||
commitArr.push(state.records.commits.get(commit.parents[1])!);
|
||||
}
|
||||
} else if (commit.parents.length == 0) {
|
||||
return;
|
||||
} else {
|
||||
if (commit.parents[0]) {
|
||||
const newCommit = state.records.commits.get(commit.parents[0]);
|
||||
upsert(commitArr, commit, newCommit);
|
||||
}
|
||||
}
|
||||
commitArr = uniqBy(commitArr, (c) => c.id);
|
||||
prettyPrintCommitHistory(commitArr);
|
||||
}
|
||||
|
||||
export const prettyPrint = function () {
|
||||
log.debug(state.records.commits);
|
||||
const node = getCommitsArray()[0];
|
||||
prettyPrintCommitHistory([node]);
|
||||
};
|
||||
|
||||
export const clear = function () {
|
||||
state.reset();
|
||||
commonClear();
|
||||
};
|
||||
|
||||
export const getBranchesAsObjArray = function () {
|
||||
const branchesArray = [...state.records.branchConfig.values()]
|
||||
.map((branchConfig, i) => {
|
||||
if (branchConfig.order !== null && branchConfig.order !== undefined) {
|
||||
return branchConfig;
|
||||
}
|
||||
return {
|
||||
...branchConfig,
|
||||
order: parseFloat(`0.${i}`),
|
||||
};
|
||||
})
|
||||
.sort((a, b) => (a.order ?? 0) - (b.order ?? 0))
|
||||
.map(({ name }) => ({ name }));
|
||||
|
||||
return branchesArray;
|
||||
};
|
||||
|
||||
export const getBranches = function () {
|
||||
return state.records.branches;
|
||||
};
|
||||
export const getCommits = function () {
|
||||
return state.records.commits;
|
||||
};
|
||||
export const getCommitsArray = function () {
|
||||
const commitArr = [...state.records.commits.values()];
|
||||
commitArr.forEach(function (o) {
|
||||
log.debug(o.id);
|
||||
});
|
||||
commitArr.sort((a, b) => a.seq - b.seq);
|
||||
return commitArr;
|
||||
};
|
||||
export const getCurrentBranch = function () {
|
||||
return state.records.currBranch;
|
||||
};
|
||||
export const getDirection = function () {
|
||||
return state.records.direction;
|
||||
};
|
||||
export const getHead = function () {
|
||||
return state.records.head;
|
||||
};
|
||||
|
||||
export const db: GitGraphDB = {
|
||||
commitType,
|
||||
getConfig,
|
||||
setDirection,
|
||||
setOptions,
|
||||
getOptions,
|
||||
commit,
|
||||
branch,
|
||||
merge,
|
||||
cherryPick,
|
||||
checkout,
|
||||
//reset,
|
||||
prettyPrint,
|
||||
clear,
|
||||
getBranchesAsObjArray,
|
||||
getBranches,
|
||||
getCommits,
|
||||
getCommitsArray,
|
||||
getCurrentBranch,
|
||||
getDirection,
|
||||
getHead,
|
||||
setAccTitle,
|
||||
getAccTitle,
|
||||
getAccDescription,
|
||||
setAccDescription,
|
||||
setDiagramTitle,
|
||||
getDiagramTitle,
|
||||
};
|
@ -1,13 +1,13 @@
|
||||
// @ts-ignore: JISON doesn't support types
|
||||
import gitGraphParser from './parser/gitGraph.jison';
|
||||
import gitGraphDb from './gitGraphAst.js';
|
||||
import { parser } from './gitGraphParser.js';
|
||||
import { db } from './gitGraphAst.js';
|
||||
import gitGraphRenderer from './gitGraphRenderer.js';
|
||||
import gitGraphStyles from './styles.js';
|
||||
import type { DiagramDefinition } from '../../diagram-api/types.js';
|
||||
|
||||
export const diagram: DiagramDefinition = {
|
||||
parser: gitGraphParser,
|
||||
db: gitGraphDb,
|
||||
parser,
|
||||
db,
|
||||
renderer: gitGraphRenderer,
|
||||
styles: gitGraphStyles,
|
||||
};
|
||||
|
@ -1,272 +0,0 @@
|
||||
import gitGraphAst from './gitGraphAst.js';
|
||||
import { parser } from './parser/gitGraph.jison';
|
||||
|
||||
describe('when parsing a gitGraph', function () {
|
||||
beforeEach(function () {
|
||||
parser.yy = gitGraphAst;
|
||||
parser.yy.clear();
|
||||
});
|
||||
it('should handle a gitGraph definition', function () {
|
||||
const str = 'gitGraph:\n' + 'commit\n';
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(commits.size).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('main');
|
||||
expect(parser.yy.getDirection()).toBe('LR');
|
||||
expect(parser.yy.getBranches().size).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle a gitGraph definition with empty options', function () {
|
||||
const str = 'gitGraph:\n' + 'options\n' + ' end\n' + 'commit\n';
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(parser.yy.getOptions()).toEqual({});
|
||||
expect(commits.size).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('main');
|
||||
expect(parser.yy.getDirection()).toBe('LR');
|
||||
expect(parser.yy.getBranches().size).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle a gitGraph definition with valid options', function () {
|
||||
const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"}\n' + 'end\n' + 'commit\n';
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(parser.yy.getOptions().key).toBe('value');
|
||||
expect(commits.size).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('main');
|
||||
expect(parser.yy.getDirection()).toBe('LR');
|
||||
expect(parser.yy.getBranches().size).toBe(1);
|
||||
});
|
||||
|
||||
it('should not fail on a gitGraph with malformed json', function () {
|
||||
const str = 'gitGraph:\n' + 'options\n' + '{"key": "value"\n' + 'end\n' + 'commit\n';
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(commits.size).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('main');
|
||||
expect(parser.yy.getDirection()).toBe('LR');
|
||||
expect(parser.yy.getBranches().size).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle set direction top to bottom', function () {
|
||||
const str = 'gitGraph TB:\n' + 'commit\n';
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(commits.size).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('main');
|
||||
expect(parser.yy.getDirection()).toBe('TB');
|
||||
expect(parser.yy.getBranches().size).toBe(1);
|
||||
});
|
||||
|
||||
it('should handle set direction bottom to top', function () {
|
||||
const str = 'gitGraph BT:\n' + 'commit\n';
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(commits.size).toBe(1);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('main');
|
||||
expect(parser.yy.getDirection()).toBe('BT');
|
||||
expect(parser.yy.getBranches().size).toBe(1);
|
||||
});
|
||||
|
||||
it('should checkout a branch', function () {
|
||||
const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n';
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(commits.size).toBe(0);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('new');
|
||||
});
|
||||
|
||||
it('should switch a branch', function () {
|
||||
const str = 'gitGraph:\n' + 'branch new\n' + 'switch new\n';
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(commits.size).toBe(0);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('new');
|
||||
});
|
||||
|
||||
it('should add commits to checked out branch', function () {
|
||||
const str = 'gitGraph:\n' + 'branch new\n' + 'checkout new\n' + 'commit\n' + 'commit\n';
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(commits.size).toBe(2);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('new');
|
||||
const branchCommit = parser.yy.getBranches().get('new');
|
||||
expect(branchCommit).not.toBeNull();
|
||||
expect(commits.get(branchCommit).parent).not.toBeNull();
|
||||
});
|
||||
it('should handle commit with args', function () {
|
||||
const str = 'gitGraph:\n' + 'commit "a commit"\n';
|
||||
|
||||
parser.parse(str);
|
||||
const commits = parser.yy.getCommits();
|
||||
|
||||
expect(commits.size).toBe(1);
|
||||
const key = commits.keys().next().value;
|
||||
expect(commits.get(key).message).toBe('a commit');
|
||||
expect(parser.yy.getCurrentBranch()).toBe('main');
|
||||
});
|
||||
|
||||
// Reset has been commented out in JISON
|
||||
it.skip('should reset a branch', function () {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
'commit\n' +
|
||||
'reset main\n';
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(commits.size).toBe(3);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
|
||||
expect(parser.yy.getBranches().get('newbranch')).toEqual(parser.yy.getBranches().get('main'));
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches().get('newbranch'));
|
||||
});
|
||||
|
||||
it.skip('reset can take an argument', function () {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
'commit\n' +
|
||||
'reset main^\n';
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(commits.size).toBe(3);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
|
||||
const main = commits.get(parser.yy.getBranches().get('main'));
|
||||
expect(parser.yy.getHead().id).toEqual(main.parent);
|
||||
});
|
||||
|
||||
it.skip('should handle fast forwardable merges', function () {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'checkout main\n' +
|
||||
'merge newbranch\n';
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(commits.size).toBe(4);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('main');
|
||||
expect(parser.yy.getBranches().get('newbranch')).toEqual(parser.yy.getBranches().get('main'));
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches().get('newbranch'));
|
||||
});
|
||||
|
||||
it('should handle cases when merge is a noop', function () {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'merge main\n';
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(commits.size).toBe(4);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
|
||||
expect(parser.yy.getBranches().get('newbranch')).not.toEqual(
|
||||
parser.yy.getBranches().get('main')
|
||||
);
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches().get('newbranch'));
|
||||
});
|
||||
|
||||
it('should handle merge with 2 parents', function () {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'checkout main\n' +
|
||||
'commit\n' +
|
||||
'merge newbranch\n';
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(commits.size).toBe(5);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('main');
|
||||
expect(parser.yy.getBranches().get('newbranch')).not.toEqual(
|
||||
parser.yy.getBranches().get('main')
|
||||
);
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches().get('main'));
|
||||
});
|
||||
|
||||
it.skip('should handle ff merge when history walk has two parents (merge commit)', function () {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'branch newbranch\n' +
|
||||
'checkout newbranch\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'checkout main\n' +
|
||||
'commit\n' +
|
||||
'merge newbranch\n' +
|
||||
'commit\n' +
|
||||
'checkout newbranch\n' +
|
||||
'merge main\n';
|
||||
|
||||
parser.parse(str);
|
||||
|
||||
const commits = parser.yy.getCommits();
|
||||
expect(commits.size).toBe(7);
|
||||
expect(parser.yy.getCurrentBranch()).toBe('newbranch');
|
||||
expect(parser.yy.getBranches().get('newbranch')).toEqual(parser.yy.getBranches().get('main'));
|
||||
expect(parser.yy.getHead().id).toEqual(parser.yy.getBranches().get('main'));
|
||||
|
||||
parser.yy.prettyPrint();
|
||||
});
|
||||
|
||||
it('should generate an array of known branches', function () {
|
||||
const str =
|
||||
'gitGraph:\n' +
|
||||
'commit\n' +
|
||||
'branch b1\n' +
|
||||
'checkout b1\n' +
|
||||
'commit\n' +
|
||||
'commit\n' +
|
||||
'branch b2\n';
|
||||
|
||||
parser.parse(str);
|
||||
const branches = gitGraphAst.getBranchesAsObjArray();
|
||||
|
||||
expect(branches).toHaveLength(3);
|
||||
expect(branches[0]).toHaveProperty('name', 'main');
|
||||
expect(branches[1]).toHaveProperty('name', 'b1');
|
||||
expect(branches[2]).toHaveProperty('name', 'b2');
|
||||
});
|
||||
});
|
243
packages/mermaid/src/diagrams/git/gitGraphParser.ts
Normal file
243
packages/mermaid/src/diagrams/git/gitGraphParser.ts
Normal file
@ -0,0 +1,243 @@
|
||||
import type { GitGraph } from '@mermaid-js/parser';
|
||||
import { parse } from '@mermaid-js/parser';
|
||||
import type { ParserDefinition } from '../../diagram-api/types.js';
|
||||
import { log } from '../../logger.js';
|
||||
import { populateCommonDb } from '../common/populateCommonDb.js';
|
||||
import { db } from './gitGraphAst.js';
|
||||
import { commitType } from './gitGraphTypes.js';
|
||||
import type {
|
||||
CheckoutAst,
|
||||
CherryPickingAst,
|
||||
MergeAst,
|
||||
CommitAst,
|
||||
BranchAst,
|
||||
GitGraphDBParseProvider,
|
||||
CommitDB,
|
||||
BranchDB,
|
||||
MergeDB,
|
||||
CherryPickDB,
|
||||
} from './gitGraphTypes.js';
|
||||
|
||||
const populate = (ast: GitGraph, db: GitGraphDBParseProvider) => {
|
||||
populateCommonDb(ast, db);
|
||||
// @ts-ignore: this wont exist if the direction is not specified
|
||||
if (ast.dir) {
|
||||
// @ts-ignore: this wont exist if the direction is not specified
|
||||
db.setDirection(ast.dir);
|
||||
}
|
||||
for (const statement of ast.statements) {
|
||||
parseStatement(statement, db);
|
||||
}
|
||||
};
|
||||
|
||||
const parseStatement = (statement: any, db: GitGraphDBParseProvider) => {
|
||||
const parsers: Record<string, (stmt: any) => void> = {
|
||||
Commit: (stmt) => db.commit(parseCommit(stmt)),
|
||||
Branch: (stmt) => db.branch(parseBranch(stmt)),
|
||||
Merge: (stmt) => db.merge(parseMerge(stmt)),
|
||||
Checkout: (stmt) => db.checkout(parseCheckout(stmt)),
|
||||
CherryPicking: (stmt) => db.cherryPick(parseCherryPicking(stmt)),
|
||||
};
|
||||
|
||||
const parser = parsers[statement.$type];
|
||||
if (parser) {
|
||||
parser(statement);
|
||||
} else {
|
||||
log.error(`Unknown statement type: ${statement.$type}`);
|
||||
}
|
||||
};
|
||||
|
||||
const parseCommit = (commit: CommitAst): CommitDB => {
|
||||
const commitDB: CommitDB = {
|
||||
id: commit.id,
|
||||
msg: commit.message ?? '',
|
||||
type: commit.type !== undefined ? commitType[commit.type] : commitType.NORMAL,
|
||||
tags: commit.tags ?? undefined,
|
||||
};
|
||||
return commitDB;
|
||||
};
|
||||
|
||||
const parseBranch = (branch: BranchAst): BranchDB => {
|
||||
const branchDB: BranchDB = {
|
||||
name: branch.name,
|
||||
order: branch.order ?? 0,
|
||||
};
|
||||
return branchDB;
|
||||
};
|
||||
|
||||
const parseMerge = (merge: MergeAst): MergeDB => {
|
||||
const mergeDB: MergeDB = {
|
||||
branch: merge.branch,
|
||||
id: merge.id ?? '',
|
||||
type: merge.type !== undefined ? commitType[merge.type] : undefined,
|
||||
tags: merge.tags ?? undefined,
|
||||
};
|
||||
return mergeDB;
|
||||
};
|
||||
|
||||
const parseCheckout = (checkout: CheckoutAst): string => {
|
||||
const branch = checkout.branch;
|
||||
return branch;
|
||||
};
|
||||
|
||||
const parseCherryPicking = (cherryPicking: CherryPickingAst): CherryPickDB => {
|
||||
const cherryPickDB: CherryPickDB = {
|
||||
id: cherryPicking.id,
|
||||
targetId: '',
|
||||
tags: cherryPicking.tags?.length === 0 ? undefined : cherryPicking.tags,
|
||||
parent: cherryPicking.parent,
|
||||
};
|
||||
return cherryPickDB;
|
||||
};
|
||||
|
||||
export const parser: ParserDefinition = {
|
||||
parse: async (input: string): Promise<void> => {
|
||||
const ast: GitGraph = await parse('gitGraph', input);
|
||||
log.debug(ast);
|
||||
populate(ast, db);
|
||||
},
|
||||
};
|
||||
|
||||
if (import.meta.vitest) {
|
||||
const { it, expect, describe } = import.meta.vitest;
|
||||
|
||||
const mockDB: GitGraphDBParseProvider = {
|
||||
commitType: commitType,
|
||||
setDirection: vi.fn(),
|
||||
commit: vi.fn(),
|
||||
branch: vi.fn(),
|
||||
merge: vi.fn(),
|
||||
cherryPick: vi.fn(),
|
||||
checkout: vi.fn(),
|
||||
};
|
||||
|
||||
describe('GitGraph Parser', () => {
|
||||
it('should parse a commit statement', () => {
|
||||
const commit = {
|
||||
$type: 'Commit',
|
||||
id: '1',
|
||||
message: 'test',
|
||||
tags: ['tag1', 'tag2'],
|
||||
type: 'NORMAL',
|
||||
};
|
||||
parseStatement(commit, mockDB);
|
||||
expect(mockDB.commit).toHaveBeenCalledWith({
|
||||
id: '1',
|
||||
msg: 'test',
|
||||
tags: ['tag1', 'tag2'],
|
||||
type: 0,
|
||||
});
|
||||
});
|
||||
it('should parse a branch statement', () => {
|
||||
const branch = {
|
||||
$type: 'Branch',
|
||||
name: 'newBranch',
|
||||
order: 1,
|
||||
};
|
||||
parseStatement(branch, mockDB);
|
||||
expect(mockDB.branch).toHaveBeenCalledWith({ name: 'newBranch', order: 1 });
|
||||
});
|
||||
it('should parse a checkout statement', () => {
|
||||
const checkout = {
|
||||
$type: 'Checkout',
|
||||
branch: 'newBranch',
|
||||
};
|
||||
parseStatement(checkout, mockDB);
|
||||
expect(mockDB.checkout).toHaveBeenCalledWith('newBranch');
|
||||
});
|
||||
it('should parse a merge statement', () => {
|
||||
const merge = {
|
||||
$type: 'Merge',
|
||||
branch: 'newBranch',
|
||||
id: '1',
|
||||
tags: ['tag1', 'tag2'],
|
||||
type: 'NORMAL',
|
||||
};
|
||||
parseStatement(merge, mockDB);
|
||||
expect(mockDB.merge).toHaveBeenCalledWith({
|
||||
branch: 'newBranch',
|
||||
id: '1',
|
||||
tags: ['tag1', 'tag2'],
|
||||
type: 0,
|
||||
});
|
||||
});
|
||||
it('should parse a cherry picking statement', () => {
|
||||
const cherryPick = {
|
||||
$type: 'CherryPicking',
|
||||
id: '1',
|
||||
tags: ['tag1', 'tag2'],
|
||||
parent: '2',
|
||||
};
|
||||
parseStatement(cherryPick, mockDB);
|
||||
expect(mockDB.cherryPick).toHaveBeenCalledWith({
|
||||
id: '1',
|
||||
targetId: '',
|
||||
parent: '2',
|
||||
tags: ['tag1', 'tag2'],
|
||||
});
|
||||
});
|
||||
|
||||
it('should parse a langium generated gitGraph ast', () => {
|
||||
const dummy: GitGraph = {
|
||||
$type: 'GitGraph',
|
||||
statements: [],
|
||||
};
|
||||
const gitGraphAst: GitGraph = {
|
||||
$type: 'GitGraph',
|
||||
statements: [
|
||||
{
|
||||
$container: dummy,
|
||||
$type: 'Commit',
|
||||
id: '1',
|
||||
message: 'test',
|
||||
tags: ['tag1', 'tag2'],
|
||||
type: 'NORMAL',
|
||||
},
|
||||
{
|
||||
$container: dummy,
|
||||
$type: 'Branch',
|
||||
name: 'newBranch',
|
||||
order: 1,
|
||||
},
|
||||
{
|
||||
$container: dummy,
|
||||
$type: 'Merge',
|
||||
branch: 'newBranch',
|
||||
id: '1',
|
||||
tags: ['tag1', 'tag2'],
|
||||
type: 'NORMAL',
|
||||
},
|
||||
{
|
||||
$container: dummy,
|
||||
$type: 'Checkout',
|
||||
branch: 'newBranch',
|
||||
},
|
||||
{
|
||||
$container: dummy,
|
||||
$type: 'CherryPicking',
|
||||
id: '1',
|
||||
tags: ['tag1', 'tag2'],
|
||||
parent: '2',
|
||||
},
|
||||
],
|
||||
};
|
||||
|
||||
populate(gitGraphAst, mockDB);
|
||||
|
||||
expect(mockDB.commit).toHaveBeenCalledWith({
|
||||
id: '1',
|
||||
msg: 'test',
|
||||
tags: ['tag1', 'tag2'],
|
||||
type: 0,
|
||||
});
|
||||
expect(mockDB.branch).toHaveBeenCalledWith({ name: 'newBranch', order: 1 });
|
||||
expect(mockDB.merge).toHaveBeenCalledWith({
|
||||
branch: 'newBranch',
|
||||
id: '1',
|
||||
tags: ['tag1', 'tag2'],
|
||||
type: 0,
|
||||
});
|
||||
expect(mockDB.checkout).toHaveBeenCalledWith('newBranch');
|
||||
});
|
||||
});
|
||||
}
|
File diff suppressed because it is too large
Load Diff
@ -1,893 +0,0 @@
|
||||
import { select } from 'd3';
|
||||
import { getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI.js';
|
||||
import { log } from '../../logger.js';
|
||||
import utils from '../../utils.js';
|
||||
|
||||
/**
|
||||
* @typedef {Map<string, { id: string, message: string, seq: number, type: number, tag: string, parents: string[], branch: string }>} CommitMap
|
||||
*/
|
||||
|
||||
/** @type {CommitMap} */
|
||||
let allCommitsDict = new Map();
|
||||
|
||||
const commitType = {
|
||||
NORMAL: 0,
|
||||
REVERSE: 1,
|
||||
HIGHLIGHT: 2,
|
||||
MERGE: 3,
|
||||
CHERRY_PICK: 4,
|
||||
};
|
||||
|
||||
const THEME_COLOR_LIMIT = 8;
|
||||
|
||||
let branchPos = {};
|
||||
let commitPos = {};
|
||||
let lanes = [];
|
||||
let maxPos = 0;
|
||||
let dir = 'LR';
|
||||
let defaultPos = 30;
|
||||
const clear = () => {
|
||||
branchPos = new Map();
|
||||
commitPos = new Map();
|
||||
allCommitsDict = new Map();
|
||||
maxPos = 0;
|
||||
lanes = [];
|
||||
dir = 'LR';
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws a text, used for labels of the branches
|
||||
*
|
||||
* @param {string} txt The text
|
||||
* @returns {SVGElement}
|
||||
*/
|
||||
const drawText = (txt) => {
|
||||
const svgLabel = document.createElementNS('http://www.w3.org/2000/svg', 'text');
|
||||
let rows = [];
|
||||
|
||||
// Handling of new lines in the label
|
||||
if (typeof txt === 'string') {
|
||||
rows = txt.split(/\\n|\n|<br\s*\/?>/gi);
|
||||
} else if (Array.isArray(txt)) {
|
||||
rows = txt;
|
||||
} else {
|
||||
rows = [];
|
||||
}
|
||||
|
||||
for (const row of rows) {
|
||||
const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan');
|
||||
tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve');
|
||||
tspan.setAttribute('dy', '1em');
|
||||
tspan.setAttribute('x', '0');
|
||||
tspan.setAttribute('class', 'row');
|
||||
tspan.textContent = row.trim();
|
||||
svgLabel.appendChild(tspan);
|
||||
}
|
||||
/**
|
||||
* @param svg
|
||||
* @param selector
|
||||
*/
|
||||
return svgLabel;
|
||||
};
|
||||
|
||||
/**
|
||||
* Searches for the closest parent from the parents list passed as argument.
|
||||
* The parents list comes from an individual commit. The closest parent is actually
|
||||
* the one farther down the graph, since that means it is closer to its child.
|
||||
*
|
||||
* @param {string[]} parents
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
const findClosestParent = (parents) => {
|
||||
let closestParent = '';
|
||||
let maxPosition = 0;
|
||||
|
||||
parents.forEach((parent) => {
|
||||
const parentPosition =
|
||||
dir === 'TB' || dir === 'BT' ? commitPos.get(parent).y : commitPos.get(parent).x;
|
||||
if (parentPosition >= maxPosition) {
|
||||
closestParent = parent;
|
||||
maxPosition = parentPosition;
|
||||
}
|
||||
});
|
||||
|
||||
return closestParent || undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Searches for the closest parent from the parents list passed as argument for Bottom-to-Top orientation.
|
||||
* The parents list comes from an individual commit. The closest parent is actually
|
||||
* the one farther down the graph, since that means it is closer to its child.
|
||||
*
|
||||
* @param {string[]} parents
|
||||
* @returns {string | undefined}
|
||||
*/
|
||||
const findClosestParentBT = (parents) => {
|
||||
let closestParent = '';
|
||||
let maxPosition = Infinity;
|
||||
|
||||
parents.forEach((parent) => {
|
||||
const parentPosition = commitPos.get(parent).y;
|
||||
if (parentPosition <= maxPosition) {
|
||||
closestParent = parent;
|
||||
maxPosition = parentPosition;
|
||||
}
|
||||
});
|
||||
|
||||
return closestParent || undefined;
|
||||
};
|
||||
|
||||
/**
|
||||
* Sets the position of the commit elements when the orientation is set to BT-Parallel.
|
||||
* This is needed to render the chart in Bottom-to-Top mode while keeping the parallel
|
||||
* commits in the correct position. First, it finds the correct position of the root commit
|
||||
* using the findClosestParent method. Then, it uses the findClosestParentBT to set the position
|
||||
* of the remaining commits.
|
||||
*
|
||||
* @param {any} sortedKeys
|
||||
* @param {CommitMap} commits
|
||||
* @param {any} defaultPos
|
||||
* @param {any} commitStep
|
||||
* @param {any} layoutOffset
|
||||
*/
|
||||
const setParallelBTPos = (sortedKeys, commits, defaultPos, commitStep, layoutOffset) => {
|
||||
let curPos = defaultPos;
|
||||
let maxPosition = defaultPos;
|
||||
let roots = [];
|
||||
sortedKeys.forEach((key) => {
|
||||
const commit = commits.get(key);
|
||||
if (commit.parents.length) {
|
||||
const closestParent = findClosestParent(commit.parents);
|
||||
curPos = commitPos.get(closestParent).y + commitStep;
|
||||
if (curPos >= maxPosition) {
|
||||
maxPosition = curPos;
|
||||
}
|
||||
} else {
|
||||
roots.push(commit);
|
||||
}
|
||||
const x = branchPos.get(commit.branch).pos;
|
||||
const y = curPos + layoutOffset;
|
||||
commitPos.set(commit.id, { x: x, y: y });
|
||||
});
|
||||
curPos = maxPosition;
|
||||
roots.forEach((commit) => {
|
||||
const posWithOffset = curPos + defaultPos;
|
||||
const y = posWithOffset;
|
||||
const x = branchPos.get(commit.branch).pos;
|
||||
commitPos.set(commit.id, { x: x, y: y });
|
||||
});
|
||||
sortedKeys.forEach((key) => {
|
||||
const commit = commits.get(key);
|
||||
if (commit.parents.length) {
|
||||
const closestParent = findClosestParentBT(commit.parents);
|
||||
curPos = commitPos.get(closestParent).y - commitStep;
|
||||
if (curPos <= maxPosition) {
|
||||
maxPosition = curPos;
|
||||
}
|
||||
const x = branchPos.get(commit.branch).pos;
|
||||
const y = curPos - layoutOffset;
|
||||
commitPos.set(commit.id, { x: x, y: y });
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Draws the commits with its symbol and labels. The function has two modes, one which only
|
||||
* calculates the positions and one that does the actual drawing. This for a simple way getting the
|
||||
* vertical layering correct in the graph.
|
||||
*
|
||||
* @param {any} svg
|
||||
* @param {CommitMap} commits
|
||||
* @param {any} modifyGraph
|
||||
*/
|
||||
const drawCommits = (svg, commits, modifyGraph) => {
|
||||
const gitGraphConfig = getConfig().gitGraph;
|
||||
const gBullets = svg.append('g').attr('class', 'commit-bullets');
|
||||
const gLabels = svg.append('g').attr('class', 'commit-labels');
|
||||
let pos = 0;
|
||||
|
||||
if (dir === 'TB' || dir === 'BT') {
|
||||
pos = defaultPos;
|
||||
}
|
||||
const keys = [...commits.keys()];
|
||||
const isParallelCommits = gitGraphConfig.parallelCommits;
|
||||
const layoutOffset = 10;
|
||||
const commitStep = 40;
|
||||
let sortedKeys =
|
||||
dir !== 'BT' || (dir === 'BT' && isParallelCommits)
|
||||
? keys.sort((a, b) => {
|
||||
return commits.get(a).seq - commits.get(b).seq;
|
||||
})
|
||||
: keys
|
||||
.sort((a, b) => {
|
||||
return commits.get(a).seq - commits.get(b).seq;
|
||||
})
|
||||
.reverse();
|
||||
|
||||
if (dir === 'BT' && isParallelCommits) {
|
||||
setParallelBTPos(sortedKeys, commits, pos, commitStep, layoutOffset);
|
||||
sortedKeys = sortedKeys.reverse();
|
||||
}
|
||||
sortedKeys.forEach((key) => {
|
||||
const commit = commits.get(key);
|
||||
if (isParallelCommits) {
|
||||
if (commit.parents.length) {
|
||||
const closestParent =
|
||||
dir === 'BT' ? findClosestParentBT(commit.parents) : findClosestParent(commit.parents);
|
||||
if (dir === 'TB') {
|
||||
pos = commitPos.get(closestParent).y + commitStep;
|
||||
} else if (dir === 'BT') {
|
||||
pos = commitPos.get(key).y - commitStep;
|
||||
} else {
|
||||
pos = commitPos.get(closestParent).x + commitStep;
|
||||
}
|
||||
} else {
|
||||
if (dir === 'TB') {
|
||||
pos = defaultPos;
|
||||
} else if (dir === 'BT') {
|
||||
pos = commitPos.get(key).y - commitStep;
|
||||
} else {
|
||||
pos = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
const posWithOffset = dir === 'BT' && isParallelCommits ? pos : pos + layoutOffset;
|
||||
const y = dir === 'TB' || dir === 'BT' ? posWithOffset : branchPos.get(commit.branch).pos;
|
||||
const x = dir === 'TB' || dir === 'BT' ? branchPos.get(commit.branch).pos : posWithOffset;
|
||||
|
||||
// Don't draw the commits now but calculate the positioning which is used by the branch lines etc.
|
||||
if (modifyGraph) {
|
||||
let typeClass;
|
||||
let commitSymbolType =
|
||||
commit.customType !== undefined && commit.customType !== ''
|
||||
? commit.customType
|
||||
: commit.type;
|
||||
switch (commitSymbolType) {
|
||||
case commitType.NORMAL:
|
||||
typeClass = 'commit-normal';
|
||||
break;
|
||||
case commitType.REVERSE:
|
||||
typeClass = 'commit-reverse';
|
||||
break;
|
||||
case commitType.HIGHLIGHT:
|
||||
typeClass = 'commit-highlight';
|
||||
break;
|
||||
case commitType.MERGE:
|
||||
typeClass = 'commit-merge';
|
||||
break;
|
||||
case commitType.CHERRY_PICK:
|
||||
typeClass = 'commit-cherry-pick';
|
||||
break;
|
||||
default:
|
||||
typeClass = 'commit-normal';
|
||||
}
|
||||
|
||||
if (commitSymbolType === commitType.HIGHLIGHT) {
|
||||
const circle = gBullets.append('rect');
|
||||
circle.attr('x', x - 10);
|
||||
circle.attr('y', y - 10);
|
||||
circle.attr('height', 20);
|
||||
circle.attr('width', 20);
|
||||
circle.attr(
|
||||
'class',
|
||||
`commit ${commit.id} commit-highlight${
|
||||
branchPos.get(commit.branch).index % THEME_COLOR_LIMIT
|
||||
} ${typeClass}-outer`
|
||||
);
|
||||
gBullets
|
||||
.append('rect')
|
||||
.attr('x', x - 6)
|
||||
.attr('y', y - 6)
|
||||
.attr('height', 12)
|
||||
.attr('width', 12)
|
||||
.attr(
|
||||
'class',
|
||||
`commit ${commit.id} commit${
|
||||
branchPos.get(commit.branch).index % THEME_COLOR_LIMIT
|
||||
} ${typeClass}-inner`
|
||||
);
|
||||
} else if (commitSymbolType === commitType.CHERRY_PICK) {
|
||||
gBullets
|
||||
.append('circle')
|
||||
.attr('cx', x)
|
||||
.attr('cy', y)
|
||||
.attr('r', 10)
|
||||
.attr('class', `commit ${commit.id} ${typeClass}`);
|
||||
gBullets
|
||||
.append('circle')
|
||||
.attr('cx', x - 3)
|
||||
.attr('cy', y + 2)
|
||||
.attr('r', 2.75)
|
||||
.attr('fill', '#fff')
|
||||
.attr('class', `commit ${commit.id} ${typeClass}`);
|
||||
gBullets
|
||||
.append('circle')
|
||||
.attr('cx', x + 3)
|
||||
.attr('cy', y + 2)
|
||||
.attr('r', 2.75)
|
||||
.attr('fill', '#fff')
|
||||
.attr('class', `commit ${commit.id} ${typeClass}`);
|
||||
gBullets
|
||||
.append('line')
|
||||
.attr('x1', x + 3)
|
||||
.attr('y1', y + 1)
|
||||
.attr('x2', x)
|
||||
.attr('y2', y - 5)
|
||||
.attr('stroke', '#fff')
|
||||
.attr('class', `commit ${commit.id} ${typeClass}`);
|
||||
gBullets
|
||||
.append('line')
|
||||
.attr('x1', x - 3)
|
||||
.attr('y1', y + 1)
|
||||
.attr('x2', x)
|
||||
.attr('y2', y - 5)
|
||||
.attr('stroke', '#fff')
|
||||
.attr('class', `commit ${commit.id} ${typeClass}`);
|
||||
} else {
|
||||
const circle = gBullets.append('circle');
|
||||
circle.attr('cx', x);
|
||||
circle.attr('cy', y);
|
||||
circle.attr('r', commit.type === commitType.MERGE ? 9 : 10);
|
||||
circle.attr(
|
||||
'class',
|
||||
`commit ${commit.id} commit${branchPos.get(commit.branch).index % THEME_COLOR_LIMIT}`
|
||||
);
|
||||
if (commitSymbolType === commitType.MERGE) {
|
||||
const circle2 = gBullets.append('circle');
|
||||
circle2.attr('cx', x);
|
||||
circle2.attr('cy', y);
|
||||
circle2.attr('r', 6);
|
||||
circle2.attr(
|
||||
'class',
|
||||
`commit ${typeClass} ${commit.id} commit${
|
||||
branchPos.get(commit.branch).index % THEME_COLOR_LIMIT
|
||||
}`
|
||||
);
|
||||
}
|
||||
if (commitSymbolType === commitType.REVERSE) {
|
||||
const cross = gBullets.append('path');
|
||||
cross
|
||||
.attr('d', `M ${x - 5},${y - 5}L${x + 5},${y + 5}M${x - 5},${y + 5}L${x + 5},${y - 5}`)
|
||||
.attr(
|
||||
'class',
|
||||
`commit ${typeClass} ${commit.id} commit${
|
||||
branchPos.get(commit.branch).index % THEME_COLOR_LIMIT
|
||||
}`
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (dir === 'TB' || dir === 'BT') {
|
||||
commitPos.set(commit.id, { x: x, y: posWithOffset });
|
||||
} else {
|
||||
commitPos.set(commit.id, { x: posWithOffset, y: y });
|
||||
}
|
||||
|
||||
// The first iteration over the commits are for positioning purposes, this
|
||||
// is required for drawing the lines. The circles and labels is drawn after the labels
|
||||
// placing them on top of the lines.
|
||||
if (modifyGraph) {
|
||||
const px = 4;
|
||||
const py = 2;
|
||||
// Draw the commit label
|
||||
if (
|
||||
commit.type !== commitType.CHERRY_PICK &&
|
||||
((commit.customId && commit.type === commitType.MERGE) ||
|
||||
commit.type !== commitType.MERGE) &&
|
||||
gitGraphConfig.showCommitLabel
|
||||
) {
|
||||
const wrapper = gLabels.append('g');
|
||||
const labelBkg = wrapper.insert('rect').attr('class', 'commit-label-bkg');
|
||||
|
||||
const text = wrapper
|
||||
.append('text')
|
||||
.attr('x', pos)
|
||||
.attr('y', y + 25)
|
||||
.attr('class', 'commit-label')
|
||||
.text(commit.id);
|
||||
let bbox = text.node().getBBox();
|
||||
|
||||
// Now we have the label, lets position the background
|
||||
labelBkg
|
||||
.attr('x', posWithOffset - bbox.width / 2 - py)
|
||||
.attr('y', y + 13.5)
|
||||
.attr('width', bbox.width + 2 * py)
|
||||
.attr('height', bbox.height + 2 * py);
|
||||
|
||||
if (dir === 'TB' || dir === 'BT') {
|
||||
labelBkg.attr('x', x - (bbox.width + 4 * px + 5)).attr('y', y - 12);
|
||||
text.attr('x', x - (bbox.width + 4 * px)).attr('y', y + bbox.height - 12);
|
||||
} else {
|
||||
text.attr('x', posWithOffset - bbox.width / 2);
|
||||
}
|
||||
if (gitGraphConfig.rotateCommitLabel) {
|
||||
if (dir === 'TB' || dir === 'BT') {
|
||||
text.attr('transform', 'rotate(' + -45 + ', ' + x + ', ' + y + ')');
|
||||
labelBkg.attr('transform', 'rotate(' + -45 + ', ' + x + ', ' + y + ')');
|
||||
} else {
|
||||
let r_x = -7.5 - ((bbox.width + 10) / 25) * 9.5;
|
||||
let r_y = 10 + (bbox.width / 25) * 8.5;
|
||||
wrapper.attr(
|
||||
'transform',
|
||||
'translate(' + r_x + ', ' + r_y + ') rotate(' + -45 + ', ' + pos + ', ' + y + ')'
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
if (commit.tags.length > 0) {
|
||||
let yOffset = 0;
|
||||
let maxTagBboxWidth = 0;
|
||||
let maxTagBboxHeight = 0;
|
||||
const tagElements = [];
|
||||
|
||||
for (const tagValue of commit.tags.reverse()) {
|
||||
const rect = gLabels.insert('polygon');
|
||||
const hole = gLabels.append('circle');
|
||||
const tag = gLabels
|
||||
.append('text')
|
||||
// Note that we are delaying setting the x position until we know the width of the text
|
||||
.attr('y', y - 16 - yOffset)
|
||||
.attr('class', 'tag-label')
|
||||
.text(tagValue);
|
||||
let tagBbox = tag.node().getBBox();
|
||||
maxTagBboxWidth = Math.max(maxTagBboxWidth, tagBbox.width);
|
||||
maxTagBboxHeight = Math.max(maxTagBboxHeight, tagBbox.height);
|
||||
|
||||
// We don't use the max over here to center the text within the tags
|
||||
tag.attr('x', posWithOffset - tagBbox.width / 2);
|
||||
|
||||
tagElements.push({
|
||||
tag,
|
||||
hole,
|
||||
rect,
|
||||
yOffset,
|
||||
});
|
||||
|
||||
yOffset += 20;
|
||||
}
|
||||
|
||||
for (const { tag, hole, rect, yOffset } of tagElements) {
|
||||
const h2 = maxTagBboxHeight / 2;
|
||||
const ly = y - 19.2 - yOffset;
|
||||
rect.attr('class', 'tag-label-bkg').attr(
|
||||
'points',
|
||||
`
|
||||
${pos - maxTagBboxWidth / 2 - px / 2},${ly + py}
|
||||
${pos - maxTagBboxWidth / 2 - px / 2},${ly - py}
|
||||
${posWithOffset - maxTagBboxWidth / 2 - px},${ly - h2 - py}
|
||||
${posWithOffset + maxTagBboxWidth / 2 + px},${ly - h2 - py}
|
||||
${posWithOffset + maxTagBboxWidth / 2 + px},${ly + h2 + py}
|
||||
${posWithOffset - maxTagBboxWidth / 2 - px},${ly + h2 + py}`
|
||||
);
|
||||
|
||||
hole
|
||||
.attr('cy', ly)
|
||||
.attr('cx', pos - maxTagBboxWidth / 2 + px / 2)
|
||||
.attr('r', 1.5)
|
||||
.attr('class', 'tag-hole');
|
||||
|
||||
if (dir === 'TB' || dir === 'BT') {
|
||||
const yOrigin = pos + yOffset;
|
||||
|
||||
rect
|
||||
.attr('class', 'tag-label-bkg')
|
||||
.attr(
|
||||
'points',
|
||||
`
|
||||
${x},${yOrigin + py}
|
||||
${x},${yOrigin - py}
|
||||
${x + layoutOffset},${yOrigin - h2 - py}
|
||||
${x + layoutOffset + maxTagBboxWidth + px},${yOrigin - h2 - py}
|
||||
${x + layoutOffset + maxTagBboxWidth + px},${yOrigin + h2 + py}
|
||||
${x + layoutOffset},${yOrigin + h2 + py}`
|
||||
)
|
||||
.attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')');
|
||||
hole
|
||||
.attr('cx', x + px / 2)
|
||||
.attr('cy', yOrigin)
|
||||
.attr('transform', 'translate(12,12) rotate(45, ' + x + ',' + pos + ')');
|
||||
tag
|
||||
.attr('x', x + 5)
|
||||
.attr('y', yOrigin + 3)
|
||||
.attr('transform', 'translate(14,14) rotate(45, ' + x + ',' + pos + ')');
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
pos = dir === 'BT' && isParallelCommits ? pos + commitStep : pos + commitStep + layoutOffset;
|
||||
if (pos > maxPos) {
|
||||
maxPos = pos;
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Detect if there are commits
|
||||
* between commitA's x-position
|
||||
* and commitB's x-position on the
|
||||
* same branch as commitA, where
|
||||
* commitA isn't main
|
||||
*
|
||||
* @param {any} commitA
|
||||
* @param {any} commitB
|
||||
* @param p1
|
||||
* @param p2
|
||||
* @param {CommitMap} allCommits
|
||||
* @returns {boolean}
|
||||
* If there are commits between
|
||||
* commitA's x-position
|
||||
* and commitB's x-position
|
||||
* on the source branch, where
|
||||
* source branch is not main
|
||||
* return true
|
||||
*/
|
||||
const shouldRerouteArrow = (commitA, commitB, p1, p2, allCommits) => {
|
||||
const commitBIsFurthest = dir === 'TB' || dir === 'BT' ? p1.x < p2.x : p1.y < p2.y;
|
||||
const branchToGetCurve = commitBIsFurthest ? commitB.branch : commitA.branch;
|
||||
const isOnBranchToGetCurve = (x) => x.branch === branchToGetCurve;
|
||||
const isBetweenCommits = (x) => x.seq > commitA.seq && x.seq < commitB.seq;
|
||||
return [...allCommits.values()].some((commitX) => {
|
||||
return isBetweenCommits(commitX) && isOnBranchToGetCurve(commitX);
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* This function find a lane in the y-axis that is not overlapping with any other lanes. This is
|
||||
* used for drawing the lines between commits.
|
||||
*
|
||||
* @param {any} y1
|
||||
* @param {any} y2
|
||||
* @param {any} depth
|
||||
* @returns {number} Y value between y1 and y2
|
||||
*/
|
||||
const findLane = (y1, y2, depth = 0) => {
|
||||
const candidate = y1 + Math.abs(y1 - y2) / 2;
|
||||
if (depth > 5) {
|
||||
return candidate;
|
||||
}
|
||||
|
||||
let ok = lanes.every((lane) => Math.abs(lane - candidate) >= 10);
|
||||
if (ok) {
|
||||
lanes.push(candidate);
|
||||
return candidate;
|
||||
}
|
||||
const diff = Math.abs(y1 - y2);
|
||||
return findLane(y1, y2 - diff / 5, depth + 1);
|
||||
};
|
||||
|
||||
/**
|
||||
* Draw the lines between the commits. They were arrows initially.
|
||||
*
|
||||
* @param {any} svg
|
||||
* @param {any} commitA
|
||||
* @param {any} commitB
|
||||
* @param {CommitMap} allCommits
|
||||
*/
|
||||
const drawArrow = (svg, commitA, commitB, allCommits) => {
|
||||
const p1 = commitPos.get(commitA.id); // arrowStart
|
||||
const p2 = commitPos.get(commitB.id); // arrowEnd
|
||||
const arrowNeedsRerouting = shouldRerouteArrow(commitA, commitB, p1, p2, allCommits);
|
||||
// log.debug('drawArrow', p1, p2, arrowNeedsRerouting, commitA.id, commitB.id);
|
||||
|
||||
// Lower-right quadrant logic; top-left is 0,0
|
||||
|
||||
let arc = '';
|
||||
let arc2 = '';
|
||||
let radius = 0;
|
||||
let offset = 0;
|
||||
let colorClassNum = branchPos.get(commitB.branch).index;
|
||||
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
|
||||
colorClassNum = branchPos.get(commitA.branch).index;
|
||||
}
|
||||
|
||||
let lineDef;
|
||||
if (arrowNeedsRerouting) {
|
||||
arc = 'A 10 10, 0, 0, 0,';
|
||||
arc2 = 'A 10 10, 0, 0, 1,';
|
||||
radius = 10;
|
||||
offset = 10;
|
||||
|
||||
const lineY = p1.y < p2.y ? findLane(p1.y, p2.y) : findLane(p2.y, p1.y);
|
||||
const lineX = p1.x < p2.x ? findLane(p1.x, p2.x) : findLane(p2.x, p1.x);
|
||||
|
||||
if (dir === 'TB') {
|
||||
if (p1.x < p2.x) {
|
||||
// Source commit is on branch position left of destination commit
|
||||
// so render arrow rightward with colour of destination branch
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${lineX - radius} ${p1.y} ${arc2} ${lineX} ${
|
||||
p1.y + offset
|
||||
} L ${lineX} ${p2.y - radius} ${arc} ${lineX + offset} ${p2.y} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
// Source commit is on branch position right of destination commit
|
||||
// so render arrow leftward with colour of source branch
|
||||
colorClassNum = branchPos.get(commitA.branch).index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${lineX + radius} ${p1.y} ${arc} ${lineX} ${
|
||||
p1.y + offset
|
||||
} L ${lineX} ${p2.y - radius} ${arc2} ${lineX - offset} ${p2.y} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
} else if (dir === 'BT') {
|
||||
if (p1.x < p2.x) {
|
||||
// Source commit is on branch position left of destination commit
|
||||
// so render arrow rightward with colour of destination branch
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${lineX - radius} ${p1.y} ${arc} ${lineX} ${
|
||||
p1.y - offset
|
||||
} L ${lineX} ${p2.y + radius} ${arc2} ${lineX + offset} ${p2.y} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
// Source commit is on branch position right of destination commit
|
||||
// so render arrow leftward with colour of source branch
|
||||
colorClassNum = branchPos.get(commitA.branch).index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${lineX + radius} ${p1.y} ${arc2} ${lineX} ${
|
||||
p1.y - offset
|
||||
} L ${lineX} ${p2.y + radius} ${arc} ${lineX - offset} ${p2.y} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
} else {
|
||||
if (p1.y < p2.y) {
|
||||
// Source commit is on branch positioned above destination commit
|
||||
// so render arrow downward with colour of destination branch
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY - radius} ${arc} ${
|
||||
p1.x + offset
|
||||
} ${lineY} L ${p2.x - radius} ${lineY} ${arc2} ${p2.x} ${lineY + offset} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
// Source commit is on branch positioned below destination commit
|
||||
// so render arrow upward with colour of source branch
|
||||
colorClassNum = branchPos.get(commitA.branch).index;
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${lineY + radius} ${arc2} ${
|
||||
p1.x + offset
|
||||
} ${lineY} L ${p2.x - radius} ${lineY} ${arc} ${p2.x} ${lineY - offset} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
arc = 'A 20 20, 0, 0, 0,';
|
||||
arc2 = 'A 20 20, 0, 0, 1,';
|
||||
radius = 20;
|
||||
offset = 20;
|
||||
|
||||
if (dir === 'TB') {
|
||||
if (p1.x < p2.x) {
|
||||
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${
|
||||
p2.y
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc2} ${p2.x} ${
|
||||
p1.y + offset
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
}
|
||||
if (p1.x > p2.x) {
|
||||
arc = 'A 20 20, 0, 0, 0,';
|
||||
arc2 = 'A 20 20, 0, 0, 1,';
|
||||
radius = 20;
|
||||
offset = 20;
|
||||
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc2} ${p1.x - offset} ${
|
||||
p2.y
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x + radius} ${p1.y} ${arc} ${p2.x} ${
|
||||
p1.y + offset
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (p1.x === p2.x) {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
} else if (dir === 'BT') {
|
||||
if (p1.x < p2.x) {
|
||||
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y + radius} ${arc2} ${p1.x + offset} ${
|
||||
p2.y
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${
|
||||
p1.y - offset
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
}
|
||||
if (p1.x > p2.x) {
|
||||
arc = 'A 20 20, 0, 0, 0,';
|
||||
arc2 = 'A 20 20, 0, 0, 1,';
|
||||
radius = 20;
|
||||
offset = 20;
|
||||
|
||||
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y + radius} ${arc} ${p1.x - offset} ${
|
||||
p2.y
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${
|
||||
p1.y - offset
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (p1.x === p2.x) {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
} else {
|
||||
if (p1.y < p2.y) {
|
||||
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc2} ${p2.x} ${
|
||||
p1.y + offset
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y - radius} ${arc} ${p1.x + offset} ${
|
||||
p2.y
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
}
|
||||
if (p1.y > p2.y) {
|
||||
if (commitB.type === commitType.MERGE && commitA.id !== commitB.parents[0]) {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x - radius} ${p1.y} ${arc} ${p2.x} ${
|
||||
p1.y - offset
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
} else {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p1.x} ${p2.y + radius} ${arc2} ${p1.x + offset} ${
|
||||
p2.y
|
||||
} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
}
|
||||
|
||||
if (p1.y === p2.y) {
|
||||
lineDef = `M ${p1.x} ${p1.y} L ${p2.x} ${p2.y}`;
|
||||
}
|
||||
}
|
||||
}
|
||||
svg
|
||||
.append('path')
|
||||
.attr('d', lineDef)
|
||||
.attr('class', 'arrow arrow' + (colorClassNum % THEME_COLOR_LIMIT));
|
||||
};
|
||||
|
||||
/**
|
||||
* @param {*} svg
|
||||
* @param {CommitMap} commits
|
||||
*/
|
||||
const drawArrows = (svg, commits) => {
|
||||
const gArrows = svg.append('g').attr('class', 'commit-arrows');
|
||||
[...commits.keys()].forEach((key) => {
|
||||
const commit = commits.get(key);
|
||||
if (commit.parents && commit.parents.length > 0) {
|
||||
commit.parents.forEach((parent) => {
|
||||
drawArrow(gArrows, commits.get(parent), commit, commits);
|
||||
});
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* Adds the branches and the branches' labels to the svg.
|
||||
*
|
||||
* @param svg
|
||||
* @param branches
|
||||
*/
|
||||
const drawBranches = (svg, branches) => {
|
||||
const gitGraphConfig = getConfig().gitGraph;
|
||||
const g = svg.append('g');
|
||||
branches.forEach((branch, index) => {
|
||||
const adjustIndexForTheme = index % THEME_COLOR_LIMIT;
|
||||
|
||||
const pos = branchPos.get(branch.name).pos;
|
||||
const line = g.append('line');
|
||||
line.attr('x1', 0);
|
||||
line.attr('y1', pos);
|
||||
line.attr('x2', maxPos);
|
||||
line.attr('y2', pos);
|
||||
line.attr('class', 'branch branch' + adjustIndexForTheme);
|
||||
|
||||
if (dir === 'TB') {
|
||||
line.attr('y1', defaultPos);
|
||||
line.attr('x1', pos);
|
||||
line.attr('y2', maxPos);
|
||||
line.attr('x2', pos);
|
||||
} else if (dir === 'BT') {
|
||||
line.attr('y1', maxPos);
|
||||
line.attr('x1', pos);
|
||||
line.attr('y2', defaultPos);
|
||||
line.attr('x2', pos);
|
||||
}
|
||||
lanes.push(pos);
|
||||
|
||||
let name = branch.name;
|
||||
|
||||
// Create the actual text element
|
||||
const labelElement = drawText(name);
|
||||
// Create outer g, edgeLabel, this will be positioned after graph layout
|
||||
const bkg = g.insert('rect');
|
||||
const branchLabel = g.insert('g').attr('class', 'branchLabel');
|
||||
|
||||
// Create inner g, label, this will be positioned now for centering the text
|
||||
const label = branchLabel.insert('g').attr('class', 'label branch-label' + adjustIndexForTheme);
|
||||
label.node().appendChild(labelElement);
|
||||
let bbox = labelElement.getBBox();
|
||||
bkg
|
||||
.attr('class', 'branchLabelBkg label' + adjustIndexForTheme)
|
||||
.attr('rx', 4)
|
||||
.attr('ry', 4)
|
||||
.attr('x', -bbox.width - 4 - (gitGraphConfig.rotateCommitLabel === true ? 30 : 0))
|
||||
.attr('y', -bbox.height / 2 + 8)
|
||||
.attr('width', bbox.width + 18)
|
||||
.attr('height', bbox.height + 4);
|
||||
label.attr(
|
||||
'transform',
|
||||
'translate(' +
|
||||
(-bbox.width - 14 - (gitGraphConfig.rotateCommitLabel === true ? 30 : 0)) +
|
||||
', ' +
|
||||
(pos - bbox.height / 2 - 1) +
|
||||
')'
|
||||
);
|
||||
if (dir === 'TB') {
|
||||
bkg.attr('x', pos - bbox.width / 2 - 10).attr('y', 0);
|
||||
label.attr('transform', 'translate(' + (pos - bbox.width / 2 - 5) + ', ' + 0 + ')');
|
||||
} else if (dir === 'BT') {
|
||||
bkg.attr('x', pos - bbox.width / 2 - 10).attr('y', maxPos);
|
||||
label.attr('transform', 'translate(' + (pos - bbox.width / 2 - 5) + ', ' + maxPos + ')');
|
||||
} else {
|
||||
bkg.attr('transform', 'translate(' + -19 + ', ' + (pos - bbox.height / 2) + ')');
|
||||
}
|
||||
});
|
||||
};
|
||||
|
||||
/**
|
||||
* @param txt
|
||||
* @param id
|
||||
* @param ver
|
||||
* @param diagObj
|
||||
*/
|
||||
export const draw = function (txt, id, ver, diagObj) {
|
||||
clear();
|
||||
const conf = getConfig();
|
||||
const gitGraphConfig = conf.gitGraph;
|
||||
// try {
|
||||
log.debug('in gitgraph renderer', txt + '\n', 'id:', id, ver);
|
||||
|
||||
allCommitsDict = diagObj.db.getCommits();
|
||||
const branches = diagObj.db.getBranchesAsObjArray();
|
||||
dir = diagObj.db.getDirection();
|
||||
const diagram = select(`[id="${id}"]`);
|
||||
// Position branches
|
||||
let pos = 0;
|
||||
branches.forEach((branch, index) => {
|
||||
const labelElement = drawText(branch.name);
|
||||
const g = diagram.append('g');
|
||||
const branchLabel = g.insert('g').attr('class', 'branchLabel');
|
||||
const label = branchLabel.insert('g').attr('class', 'label branch-label');
|
||||
label.node().appendChild(labelElement);
|
||||
let bbox = labelElement.getBBox();
|
||||
|
||||
branchPos.set(branch.name, { pos, index });
|
||||
pos +=
|
||||
50 +
|
||||
(gitGraphConfig.rotateCommitLabel ? 40 : 0) +
|
||||
(dir === 'TB' || dir === 'BT' ? bbox.width / 2 : 0);
|
||||
label.remove();
|
||||
branchLabel.remove();
|
||||
g.remove();
|
||||
});
|
||||
|
||||
drawCommits(diagram, allCommitsDict, false);
|
||||
if (gitGraphConfig.showBranches) {
|
||||
drawBranches(diagram, branches);
|
||||
}
|
||||
drawArrows(diagram, allCommitsDict);
|
||||
drawCommits(diagram, allCommitsDict, true);
|
||||
utils.insertTitle(
|
||||
diagram,
|
||||
'gitTitleText',
|
||||
gitGraphConfig.titleTopMargin,
|
||||
diagObj.db.getDiagramTitle()
|
||||
);
|
||||
|
||||
// Setup the view box and size of the svg element
|
||||
setupGraphViewbox(
|
||||
undefined,
|
||||
diagram,
|
||||
gitGraphConfig.diagramPadding,
|
||||
gitGraphConfig.useMaxWidth ?? conf.useMaxWidth
|
||||
);
|
||||
};
|
||||
|
||||
export default {
|
||||
draw,
|
||||
};
|
1350
packages/mermaid/src/diagrams/git/gitGraphRenderer.ts
Normal file
1350
packages/mermaid/src/diagrams/git/gitGraphRenderer.ts
Normal file
File diff suppressed because it is too large
Load Diff
134
packages/mermaid/src/diagrams/git/gitGraphTypes.ts
Normal file
134
packages/mermaid/src/diagrams/git/gitGraphTypes.ts
Normal file
@ -0,0 +1,134 @@
|
||||
import type { GitGraphDiagramConfig } from '../../config.type.js';
|
||||
import type { DiagramDBBase } from '../../diagram-api/types.js';
|
||||
|
||||
export const commitType = {
|
||||
NORMAL: 0,
|
||||
REVERSE: 1,
|
||||
HIGHLIGHT: 2,
|
||||
MERGE: 3,
|
||||
CHERRY_PICK: 4,
|
||||
} as const;
|
||||
|
||||
export interface CommitDB {
|
||||
msg: string;
|
||||
id: string;
|
||||
type: number;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export interface BranchDB {
|
||||
name: string;
|
||||
order: number;
|
||||
}
|
||||
|
||||
export interface MergeDB {
|
||||
branch: string;
|
||||
id: string;
|
||||
type?: number;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export interface CherryPickDB {
|
||||
id: string;
|
||||
targetId: string;
|
||||
parent: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export interface Commit {
|
||||
id: string;
|
||||
message: string;
|
||||
seq: number;
|
||||
type: number;
|
||||
tags: string[];
|
||||
parents: string[];
|
||||
branch: string;
|
||||
customType?: number;
|
||||
customId?: boolean;
|
||||
}
|
||||
|
||||
export interface GitGraph {
|
||||
statements: Statement[];
|
||||
}
|
||||
|
||||
export type Statement = CommitAst | BranchAst | MergeAst | CheckoutAst | CherryPickingAst;
|
||||
|
||||
export interface CommitAst {
|
||||
$type: 'Commit';
|
||||
id: string;
|
||||
message?: string;
|
||||
tags?: string[];
|
||||
type?: 'NORMAL' | 'REVERSE' | 'HIGHLIGHT';
|
||||
}
|
||||
|
||||
export interface BranchAst {
|
||||
$type: 'Branch';
|
||||
name: string;
|
||||
order?: number;
|
||||
}
|
||||
|
||||
export interface MergeAst {
|
||||
$type: 'Merge';
|
||||
branch: string;
|
||||
id?: string;
|
||||
tags?: string[];
|
||||
type?: 'NORMAL' | 'REVERSE' | 'HIGHLIGHT';
|
||||
}
|
||||
|
||||
export interface CheckoutAst {
|
||||
$type: 'Checkout';
|
||||
branch: string;
|
||||
}
|
||||
|
||||
export interface CherryPickingAst {
|
||||
$type: 'CherryPicking';
|
||||
id: string;
|
||||
parent: string;
|
||||
tags?: string[];
|
||||
}
|
||||
|
||||
export interface GitGraphDB extends DiagramDBBase<GitGraphDiagramConfig> {
|
||||
commitType: typeof commitType;
|
||||
setDirection: (dir: DiagramOrientation) => void;
|
||||
setOptions: (rawOptString: string) => void;
|
||||
getOptions: () => any;
|
||||
commit: (commitDB: CommitDB) => void;
|
||||
branch: (branchDB: BranchDB) => void;
|
||||
merge: (mergeDB: MergeDB) => void;
|
||||
cherryPick: (cherryPickDB: CherryPickDB) => void;
|
||||
checkout: (branch: string) => void;
|
||||
prettyPrint: () => void;
|
||||
clear: () => void;
|
||||
getBranchesAsObjArray: () => { name: string }[];
|
||||
getBranches: () => Map<string, string | null>;
|
||||
getCommits: () => Map<string, Commit>;
|
||||
getCommitsArray: () => Commit[];
|
||||
getCurrentBranch: () => string;
|
||||
getDirection: () => DiagramOrientation;
|
||||
getHead: () => Commit | null;
|
||||
}
|
||||
|
||||
export interface GitGraphDBParseProvider extends Partial<GitGraphDB> {
|
||||
commitType: typeof commitType;
|
||||
setDirection: (dir: DiagramOrientation) => void;
|
||||
commit: (commitDB: CommitDB) => void;
|
||||
branch: (branchDB: BranchDB) => void;
|
||||
merge: (mergeDB: MergeDB) => void;
|
||||
cherryPick: (cherryPickDB: CherryPickDB) => void;
|
||||
checkout: (branch: string) => void;
|
||||
}
|
||||
|
||||
export interface GitGraphDBRenderProvider extends Partial<GitGraphDB> {
|
||||
prettyPrint: () => void;
|
||||
clear: () => void;
|
||||
getBranchesAsObjArray: () => { name: string }[];
|
||||
getBranches: () => Map<string, string | null>;
|
||||
getCommits: () => Map<string, Commit>;
|
||||
getCommitsArray: () => Commit[];
|
||||
getCurrentBranch: () => string;
|
||||
getDirection: () => DiagramOrientation;
|
||||
getHead: () => Commit | null;
|
||||
getDiagramTitle: () => string;
|
||||
}
|
||||
|
||||
export type DiagramOrientation = 'LR' | 'TB' | 'BT';
|
@ -1,248 +0,0 @@
|
||||
/*
|
||||
* Parse following
|
||||
* gitGraph:
|
||||
* commit
|
||||
* commit
|
||||
* branch
|
||||
*/
|
||||
%lex
|
||||
|
||||
%x string
|
||||
%x options
|
||||
%x acc_title
|
||||
%x acc_descr
|
||||
%x acc_descr_multiline
|
||||
%options case-insensitive
|
||||
|
||||
|
||||
%%
|
||||
accTitle\s*":"\s* { this.begin("acc_title");return 'acc_title'; }
|
||||
<acc_title>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_title_value"; }
|
||||
accDescr\s*":"\s* { this.begin("acc_descr");return 'acc_descr'; }
|
||||
<acc_descr>(?!\n|;|#)*[^\n]* { this.popState(); return "acc_descr_value"; }
|
||||
accDescr\s*"{"\s* { this.begin("acc_descr_multiline");}
|
||||
<acc_descr_multiline>[\}] { this.popState(); }
|
||||
<acc_descr_multiline>[^\}]* return "acc_descr_multiline_value";
|
||||
(\r?\n)+ /*{console.log('New line');return 'NL';}*/ return 'NL';
|
||||
\#[^\n]* /* skip comments */
|
||||
\%%[^\n]* /* skip comments */
|
||||
"gitGraph" return 'GG';
|
||||
commit(?=\s|$) return 'COMMIT';
|
||||
"id:" return 'COMMIT_ID';
|
||||
"type:" return 'COMMIT_TYPE';
|
||||
"msg:" return 'COMMIT_MSG';
|
||||
"NORMAL" return 'NORMAL';
|
||||
"REVERSE" return 'REVERSE';
|
||||
"HIGHLIGHT" return 'HIGHLIGHT';
|
||||
"tag:" return 'COMMIT_TAG';
|
||||
branch(?=\s|$) return 'BRANCH';
|
||||
"order:" return 'ORDER';
|
||||
merge(?=\s|$) return 'MERGE';
|
||||
cherry\-pick(?=\s|$) return 'CHERRY_PICK';
|
||||
"parent:" return 'PARENT_COMMIT'
|
||||
// "reset" return 'RESET';
|
||||
\b(checkout|switch)(?=\s|$) return 'CHECKOUT';
|
||||
"LR" return 'DIR';
|
||||
"TB" return 'DIR';
|
||||
"BT" return 'DIR';
|
||||
":" return ':';
|
||||
"^" return 'CARET'
|
||||
"options"\r?\n this.begin("options"); //
|
||||
<options>[ \r\n\t]+"end" this.popState(); // not used anymore in the renderer, fixed for backward compatibility
|
||||
<options>[\s\S]+(?=[ \r\n\t]+"end") return 'OPT'; //
|
||||
["]["] return 'EMPTYSTR';
|
||||
["] this.begin("string");
|
||||
<string>["] this.popState();
|
||||
<string>[^"]* return 'STR';
|
||||
[0-9]+(?=\s|$) return 'NUM';
|
||||
\w([-\./\w]*[-\w])? return 'ID'; // only a subset of https://git-scm.com/docs/git-check-ref-format
|
||||
<<EOF>> return 'EOF';
|
||||
\s+ /* skip all whitespace */ // lowest priority so we can use lookaheads in earlier regex
|
||||
|
||||
/lex
|
||||
|
||||
%left '^'
|
||||
|
||||
%start start
|
||||
|
||||
%% /* language grammar */
|
||||
|
||||
start
|
||||
: eol start
|
||||
| GG document EOF{ return $3; }
|
||||
| GG ':' document EOF{ return $3; }
|
||||
| GG DIR ':' document EOF {yy.setDirection($2); return $4;}
|
||||
;
|
||||
|
||||
|
||||
document
|
||||
: /*empty*/
|
||||
| options body { yy.setOptions($1); $$ = $2}
|
||||
;
|
||||
|
||||
options
|
||||
: options OPT {$1 +=$2; $$=$1}
|
||||
| NL
|
||||
;
|
||||
body
|
||||
: /*empty*/ {$$ = []}
|
||||
| body line {$1.push($2); $$=$1;}
|
||||
;
|
||||
line
|
||||
: statement eol {$$ =$1}
|
||||
| NL
|
||||
;
|
||||
|
||||
statement
|
||||
: commitStatement
|
||||
| mergeStatement
|
||||
| cherryPickStatement
|
||||
| acc_title acc_title_value { $$=$2.trim();yy.setAccTitle($$); }
|
||||
| acc_descr acc_descr_value { $$=$2.trim();yy.setAccDescription($$); }
|
||||
| acc_descr_multiline_value { $$=$1.trim();yy.setAccDescription($$); } | section {yy.addSection($1.substr(8));$$=$1.substr(8);}
|
||||
| branchStatement
|
||||
| CHECKOUT ref {yy.checkout($2)}
|
||||
// | RESET reset_arg {yy.reset($2)}
|
||||
;
|
||||
branchStatement
|
||||
: BRANCH ref {yy.branch($2)}
|
||||
| BRANCH ref ORDER NUM {yy.branch($2, $4)}
|
||||
;
|
||||
|
||||
cherryPickStatement
|
||||
: CHERRY_PICK COMMIT_ID STR {yy.cherryPick($3, '', undefined)}
|
||||
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($3, '', undefined,$5)}
|
||||
| CHERRY_PICK COMMIT_ID STR commitTags {yy.cherryPick($3, '', $4)}
|
||||
| CHERRY_PICK COMMIT_ID STR PARENT_COMMIT STR commitTags {yy.cherryPick($3, '', $6,$5)}
|
||||
| CHERRY_PICK COMMIT_ID STR commitTags PARENT_COMMIT STR {yy.cherryPick($3, '', $4,$6)}
|
||||
| CHERRY_PICK commitTags COMMIT_ID STR {yy.cherryPick($4, '', $2)}
|
||||
| CHERRY_PICK commitTags COMMIT_ID STR PARENT_COMMIT STR {yy.cherryPick($4, '', $2,$6)}
|
||||
;
|
||||
|
||||
mergeStatement
|
||||
: MERGE ref {yy.merge($2,'','', undefined)}
|
||||
| MERGE ref COMMIT_ID STR {yy.merge($2, $4,'', undefined)}
|
||||
| MERGE ref COMMIT_TYPE commitType {yy.merge($2,'', $4, undefined)}
|
||||
| MERGE ref commitTags {yy.merge($2, '','',$3)}
|
||||
| MERGE ref commitTags COMMIT_ID STR {yy.merge($2, $5,'', $3)}
|
||||
| MERGE ref commitTags COMMIT_TYPE commitType {yy.merge($2, '',$5, $3)}
|
||||
| MERGE ref COMMIT_TYPE commitType commitTags {yy.merge($2, '',$4, $5)}
|
||||
| MERGE ref COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $4, $6, undefined)}
|
||||
| MERGE ref COMMIT_ID STR commitTags {yy.merge($2, $4, '', $5)}
|
||||
| MERGE ref COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $6,$4, undefined)}
|
||||
| MERGE ref COMMIT_ID STR COMMIT_TYPE commitType commitTags {yy.merge($2, $4, $6, $7)}
|
||||
| MERGE ref COMMIT_TYPE commitType commitTags COMMIT_ID STR {yy.merge($2, $7, $4, $5)}
|
||||
| MERGE ref COMMIT_ID STR commitTags COMMIT_TYPE commitType {yy.merge($2, $4, $7, $5)}
|
||||
| MERGE ref COMMIT_TYPE commitType COMMIT_ID STR commitTags {yy.merge($2, $6, $4, $7)}
|
||||
| MERGE ref commitTags COMMIT_TYPE commitType COMMIT_ID STR {yy.merge($2, $7, $5, $3)}
|
||||
| MERGE ref commitTags COMMIT_ID STR COMMIT_TYPE commitType {yy.merge($2, $5, $7, $3)}
|
||||
;
|
||||
|
||||
commitStatement
|
||||
: COMMIT commit_arg {yy.commit($2)}
|
||||
| COMMIT commitTags {yy.commit('','',yy.commitType.NORMAL,$2)}
|
||||
| COMMIT COMMIT_TYPE commitType {yy.commit('','',$3, undefined)}
|
||||
| COMMIT commitTags COMMIT_TYPE commitType {yy.commit('','',$4,$2)}
|
||||
| COMMIT COMMIT_TYPE commitType commitTags {yy.commit('','',$3,$4)}
|
||||
| COMMIT COMMIT_ID STR {yy.commit('',$3,yy.commitType.NORMAL, undefined)}
|
||||
| COMMIT COMMIT_ID STR commitTags {yy.commit('',$3,yy.commitType.NORMAL,$4)}
|
||||
| COMMIT commitTags COMMIT_ID STR {yy.commit('',$4,yy.commitType.NORMAL,$2)}
|
||||
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType {yy.commit('',$3,$5, undefined)}
|
||||
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR {yy.commit('',$5,$3, undefined)}
|
||||
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType commitTags {yy.commit('',$3,$5,$6)}
|
||||
| COMMIT COMMIT_ID STR commitTags COMMIT_TYPE commitType {yy.commit('',$3,$6,$4)}
|
||||
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR commitTags {yy.commit('',$5,$3,$6)}
|
||||
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_ID STR {yy.commit('',$6,$3,$4)}
|
||||
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_ID STR {yy.commit('',$6,$4,$2)}
|
||||
| COMMIT commitTags COMMIT_ID STR COMMIT_TYPE commitType {yy.commit('',$4,$6,$2)}
|
||||
| COMMIT COMMIT_MSG STR {yy.commit($3,'',yy.commitType.NORMAL, undefined)}
|
||||
| COMMIT commitTags COMMIT_MSG STR {yy.commit($4,'',yy.commitType.NORMAL,$2)}
|
||||
| COMMIT COMMIT_MSG STR commitTags {yy.commit($3,'',yy.commitType.NORMAL,$4)}
|
||||
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($3,'',$5, undefined)}
|
||||
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($5,'',$3, undefined)}
|
||||
| COMMIT COMMIT_ID STR COMMIT_MSG STR {yy.commit($5,$3,yy.commitType.NORMAL, undefined)}
|
||||
| COMMIT COMMIT_MSG STR COMMIT_ID STR {yy.commit($3,$5,yy.commitType.NORMAL, undefined)}
|
||||
|
||||
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType commitTags {yy.commit($3,'',$5,$6)}
|
||||
| COMMIT COMMIT_MSG STR commitTags COMMIT_TYPE commitType {yy.commit($3,'',$6,$4)}
|
||||
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR commitTags {yy.commit($5,'',$3,$6)}
|
||||
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_MSG STR {yy.commit($6,'',$3,$4)}
|
||||
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($6,'',$4,$2)}
|
||||
| COMMIT commitTags COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($4,'',$6,$2)}
|
||||
|
||||
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($3,$7,$5, undefined)}
|
||||
| COMMIT COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($3,$5,$7, undefined)}
|
||||
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR {yy.commit($5,$7,$3, undefined)}
|
||||
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR {yy.commit($7,$5,$3, undefined)}
|
||||
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($7,$3,$5, undefined)}
|
||||
| COMMIT COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($5,$3,$7, undefined)}
|
||||
|
||||
| COMMIT COMMIT_MSG STR commitTags COMMIT_ID STR {yy.commit($3,$6,yy.commitType.NORMAL,$4)}
|
||||
| COMMIT COMMIT_MSG STR COMMIT_ID STR commitTags {yy.commit($3,$5,yy.commitType.NORMAL,$6)}
|
||||
| COMMIT commitTags COMMIT_MSG STR COMMIT_ID STR {yy.commit($4,$6,yy.commitType.NORMAL,$2)}
|
||||
| COMMIT commitTags COMMIT_ID STR COMMIT_MSG STR {yy.commit($6,$4,yy.commitType.NORMAL,$2)}
|
||||
| COMMIT COMMIT_ID STR commitTags COMMIT_MSG STR {yy.commit($6,$3,yy.commitType.NORMAL,$4)}
|
||||
| COMMIT COMMIT_ID STR COMMIT_MSG STR commitTags {yy.commit($5,$3,yy.commitType.NORMAL,$6)}
|
||||
|
||||
| COMMIT COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType commitTags {yy.commit($3,$5,$7,$8)}
|
||||
| COMMIT COMMIT_MSG STR COMMIT_ID STR commitTags COMMIT_TYPE commitType {yy.commit($3,$5,$8,$6)}
|
||||
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR commitTags {yy.commit($3,$7,$5,$8)}
|
||||
| COMMIT COMMIT_MSG STR COMMIT_TYPE commitType commitTags COMMIT_ID STR {yy.commit($3,$8,$5,$6)}
|
||||
| COMMIT COMMIT_MSG STR commitTags COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($3,$6,$8,$4)}
|
||||
| COMMIT COMMIT_MSG STR commitTags COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($3,$8,$6,$4)}
|
||||
|
||||
| COMMIT COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType commitTags {yy.commit($5,$3,$7,$8)}
|
||||
| COMMIT COMMIT_ID STR COMMIT_MSG STR commitTags COMMIT_TYPE commitType {yy.commit($5,$3,$8,$6)}
|
||||
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR commitTags {yy.commit($7,$3,$5,$8)}
|
||||
| COMMIT COMMIT_ID STR COMMIT_TYPE commitType commitTags COMMIT_MSG STR {yy.commit($8,$3,$5,$6)}
|
||||
| COMMIT COMMIT_ID STR commitTags COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($6,$3,$8,$4)}
|
||||
| COMMIT COMMIT_ID STR commitTags COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($8,$3,$6,$4)}
|
||||
|
||||
| COMMIT commitTags COMMIT_ID STR COMMIT_TYPE commitType COMMIT_MSG STR {yy.commit($8,$4,$6,$2)}
|
||||
| COMMIT commitTags COMMIT_ID STR COMMIT_MSG STR COMMIT_TYPE commitType {yy.commit($6,$4,$8,$2)}
|
||||
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR {yy.commit($8,$6,$4,$2)}
|
||||
| COMMIT commitTags COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR {yy.commit($6,$8,$4,$2)}
|
||||
| COMMIT commitTags COMMIT_MSG STR COMMIT_ID STR COMMIT_TYPE commitType {yy.commit($4,$6,$8,$2)}
|
||||
| COMMIT commitTags COMMIT_MSG STR COMMIT_TYPE commitType COMMIT_ID STR {yy.commit($4,$8,$6,$2)}
|
||||
|
||||
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR COMMIT_MSG STR commitTags {yy.commit($7,$5,$3,$8)}
|
||||
| COMMIT COMMIT_TYPE commitType COMMIT_ID STR commitTags COMMIT_MSG STR {yy.commit($8,$5,$3,$6)}
|
||||
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_MSG STR COMMIT_ID STR {yy.commit($6,$8,$3,$4)}
|
||||
| COMMIT COMMIT_TYPE commitType commitTags COMMIT_ID STR COMMIT_MSG STR {yy.commit($8,$6,$3,$4)}
|
||||
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR COMMIT_ID STR commitTags {yy.commit($5,$7,$3,$8)}
|
||||
| COMMIT COMMIT_TYPE commitType COMMIT_MSG STR commitTags COMMIT_ID STR {yy.commit($5,$8,$3,$6)}
|
||||
;
|
||||
commit_arg
|
||||
: /* empty */ {$$ = ""}
|
||||
| STR {$$=$1}
|
||||
;
|
||||
commitType
|
||||
: NORMAL { $$=yy.commitType.NORMAL;}
|
||||
| REVERSE { $$=yy.commitType.REVERSE;}
|
||||
| HIGHLIGHT { $$=yy.commitType.HIGHLIGHT;}
|
||||
;
|
||||
commitTags
|
||||
: COMMIT_TAG STR {$$=[$2]}
|
||||
| COMMIT_TAG EMPTYSTR {$$=['']}
|
||||
| commitTags COMMIT_TAG STR {$commitTags.push($3); $$=$commitTags;}
|
||||
| commitTags COMMIT_TAG EMPTYSTR {$commitTags.push(''); $$=$commitTags;}
|
||||
;
|
||||
|
||||
ref
|
||||
: ID
|
||||
| STR
|
||||
;
|
||||
|
||||
eol
|
||||
: NL
|
||||
| ';'
|
||||
| EOF
|
||||
;
|
||||
// reset_arg
|
||||
// : 'HEAD' reset_parents{$$ = $1+ ":" + $2 }
|
||||
// | ID reset_parents{$$ = $1+ ":" + yy.count; yy.count = 0}
|
||||
// ;
|
||||
// reset_parents
|
||||
// : /* empty */ {yy.count = 0}
|
||||
// | CARET reset_parents { yy.count += 1 }
|
||||
// ;
|
@ -1,7 +1,7 @@
|
||||
import { getConfig } from '../../diagram-api/diagramAPI.js';
|
||||
import type { DiagramStyleClassDef } from '../../diagram-api/types.js';
|
||||
import { log } from '../../logger.js';
|
||||
import { getDiagramElements } from '../../rendering-util/insertElementsForSize.js';
|
||||
import { getDiagramElement } from '../../rendering-util/insertElementsForSize.js';
|
||||
import { render } from '../../rendering-util/render.js';
|
||||
import { setupViewPortForSVG } from '../../rendering-util/setupViewPortForSVG.js';
|
||||
import type { LayoutData } from '../../rendering-util/types.js';
|
||||
@ -55,7 +55,7 @@ export const draw = async function (text: string, id: string, _version: string,
|
||||
const data4Layout = diag.db.getData() as LayoutData;
|
||||
|
||||
// Create the root SVG - the element is the div containing the SVG element
|
||||
const { element, svg } = getDiagramElements(id, securityLevel);
|
||||
const svg = getDiagramElement(id, securityLevel);
|
||||
|
||||
data4Layout.type = diag.type;
|
||||
data4Layout.layoutAlgorithm = layout;
|
||||
@ -67,10 +67,10 @@ export const draw = async function (text: string, id: string, _version: string,
|
||||
data4Layout.markers = ['barb'];
|
||||
data4Layout.diagramId = id;
|
||||
// console.log('REF1:', data4Layout);
|
||||
await render(data4Layout, svg, element);
|
||||
await render(data4Layout, svg);
|
||||
const padding = 8;
|
||||
utils.insertTitle(
|
||||
element,
|
||||
svg,
|
||||
'statediagramTitleText',
|
||||
conf?.titleTopMargin ?? 25,
|
||||
diag.db.getDiagramTitle()
|
||||
|
@ -0,0 +1,14 @@
|
||||
<template>
|
||||
<div class="-mt-6 mb-8">
|
||||
<a
|
||||
href="https://www.producthunt.com/posts/mermaid-ai-2?embed=true&utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-ai-2"
|
||||
target="_blank"
|
||||
><img
|
||||
src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=465568&theme=light"
|
||||
alt="Mermaid AI - Maximize your diagramming efficiency with Mermaid AI | Product Hunt"
|
||||
style="width: 250px; height: 54px"
|
||||
width="250"
|
||||
height="54"
|
||||
/></a>
|
||||
</div>
|
||||
</template>
|
@ -1,26 +1,65 @@
|
||||
<script setup lang="ts">
|
||||
import { ref, onMounted } from 'vue';
|
||||
|
||||
interface Taglines {
|
||||
label: string;
|
||||
url: string;
|
||||
}
|
||||
|
||||
const taglines: Taglines[] = [
|
||||
{
|
||||
label: 'Use the Visual Editor in Mermaid Chart to design and build diagrams',
|
||||
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=visual_editor',
|
||||
},
|
||||
{
|
||||
label: 'Diagram live with teammates in Mermaid Chart',
|
||||
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=teams',
|
||||
},
|
||||
{
|
||||
label: 'Skip the rough draft with Mermaid AI in Mermaid Chart',
|
||||
url: 'https://www.mermaidchart.com/play?utm_source=mermaid_live_editor&utm_medium=banner_ad&utm_campaign=mermaid_ai',
|
||||
},
|
||||
];
|
||||
|
||||
let index = ref(Math.floor(Math.random() * taglines.length));
|
||||
onMounted(() => {
|
||||
setInterval(() => {
|
||||
index.value = (index.value + 1) % taglines.length;
|
||||
}, 60_000);
|
||||
});
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div
|
||||
class="w-full top-bar bg-gradient-to-r from-[#bd34fe] to-[#ff3670] flex items-center text-center justify-center p-1 text-white"
|
||||
>
|
||||
<p class="flex-grow text-center tracking-wide text-text">
|
||||
<a
|
||||
href="https://www.mermaidchart.com/landing"
|
||||
target="_blank"
|
||||
class="unstyled flex-grow tracking-wide plausible-event-name=bannerClick"
|
||||
>
|
||||
<span class="text-primary-50 font-semibold">{{
|
||||
[
|
||||
'Try diagramming with ChatGPT at Mermaid Chart',
|
||||
'Try Mermaid’s Visual Editor at Mermaid Chart',
|
||||
'Enjoy live collaboration with teammates at Mermaid Chart',
|
||||
][Math.floor(Math.random() * 3)]
|
||||
}}</span>
|
||||
<button
|
||||
class="ml-4 rounded bg-[#111113] p-1 px-2 text-sm font-semibold tracking-wide text-white"
|
||||
<div class="mb-4 w-full top-bar bg-gradient-to-r from-[#bd34fe] to-[#ff3670] flex p-1">
|
||||
<p class="w-full tracking-wide fade-text">
|
||||
<transition name="fade" mode="out-in">
|
||||
<a
|
||||
:key="index"
|
||||
:href="taglines[index].url"
|
||||
target="_blank"
|
||||
class="unstyled flex justify-center items-center gap-4 text-white tracking-wide plausible-event-name=bannerClick"
|
||||
>
|
||||
Try it now
|
||||
</button>
|
||||
</a>
|
||||
<span class="font-semibold">{{ taglines[index].label }}</span>
|
||||
<button class="rounded bg-[#111113] p-1 px-2 text-sm font-semibold tracking-wide">
|
||||
Try it now
|
||||
</button>
|
||||
</a>
|
||||
</transition>
|
||||
</p>
|
||||
</div>
|
||||
</template>
|
||||
|
||||
<style>
|
||||
.fade-enter-active,
|
||||
.fade-leave-active {
|
||||
transition: opacity 1s;
|
||||
}
|
||||
.fade-enter-from,
|
||||
.fade-leave-to {
|
||||
opacity: 0;
|
||||
}
|
||||
.fade-enter-to,
|
||||
.fade-leave-from {
|
||||
opacity: 1;
|
||||
}
|
||||
</style>
|
||||
|
@ -11,11 +11,11 @@ import HomePage from '../components/HomePage.vue';
|
||||
import TopBar from '../components/TopBar.vue';
|
||||
import { getRedirect } from './redirect.js';
|
||||
// @ts-ignore Type not available
|
||||
import { h } from 'vue';
|
||||
import Theme from 'vitepress/theme';
|
||||
import '../style/main.css';
|
||||
import 'uno.css';
|
||||
import type { EnhanceAppContext } from 'vitepress';
|
||||
import Theme from 'vitepress/theme';
|
||||
import { h } from 'vue';
|
||||
import '../style/main.css';
|
||||
|
||||
export default {
|
||||
...DefaultTheme,
|
||||
@ -24,6 +24,7 @@ export default {
|
||||
// Keeping this as comment as it took a lot of time to figure out how to add a component to the top bar.
|
||||
'home-hero-before': () => h(TopBar),
|
||||
'home-features-after': () => h(HomePage),
|
||||
'doc-before': () => h(TopBar),
|
||||
});
|
||||
},
|
||||
enhanceApp({ app, router }: EnhanceAppContext) {
|
||||
|
@ -371,9 +371,9 @@ If the users have no way to know that things have changed, then you haven't real
|
||||
Likewise, if users don't know that there is a new feature that you've implemented, it will forever remain unknown and unused.
|
||||
|
||||
The documentation has to be updated for users to know that things have been changed and added!
|
||||
If you are adding a new feature, add `(v10.8.0+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
|
||||
If you are adding a new feature, add `(v<MERMAID_RELEASE_VERSION>+)` in the title or description. It will be replaced automatically with the current version number when the release happens.
|
||||
|
||||
eg: `# Feature Name (v10.8.0+)`
|
||||
eg: `# Feature Name (v<MERMAID_RELEASE_VERSION>+)`
|
||||
|
||||
We know it can sometimes be hard to code _and_ write user documentation.
|
||||
|
||||
|
@ -54,6 +54,7 @@ To add an integration to this list, see the [Integrations - create page](./integ
|
||||
- [MonsterWriter](https://www.monsterwriter.com/) ✅
|
||||
- [Joplin](https://joplinapp.org) ✅
|
||||
- [LiveBook](https://livebook.dev) ✅
|
||||
- [Slidev](https://sli.dev) ✅
|
||||
- [Tuleap](https://docs.tuleap.org/user-guide/writing-in-tuleap.html#graphs) ✅
|
||||
- [Mermaid Flow Visual Editor](https://www.mermaidflow.app) ✅
|
||||
- [Mermerd](https://github.com/KarnerTh/mermerd)
|
||||
@ -129,7 +130,7 @@ Communication tools and platforms
|
||||
### Wikis
|
||||
|
||||
- [DokuWiki](https://dokuwiki.org)
|
||||
- [ComboStrap](https://combostrap.com/mermaid)
|
||||
- [ComboStrap](https://combostrap.com/utility/create-diagram-with-mermaid-vh3ab9yj)
|
||||
- [Mermaid Plugin](https://www.dokuwiki.org/plugin:mermaid)
|
||||
- [Foswiki](https://foswiki.org)
|
||||
- [Mermaid Plugin](https://foswiki.org/Extensions/MermaidPlugin)
|
||||
|
@ -6,7 +6,7 @@ Try the Ultimate AI, Mermaid, and Visual Diagramming Suite by creating an accoun
|
||||
|
||||
<br />
|
||||
|
||||
<a href="https://www.producthunt.com/posts/mermaid-chart?utm_source=badge-featured&utm_medium=badge&utm_souce=badge-mermaid-chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/featured.svg?post_id=416671&theme=light" alt="Mermaid Chart - A smarter way to create diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
<a href="https://www.producthunt.com/products/mermaid-chart?utm_source=badge-follow&utm_medium=badge&utm_souce=badge-mermaid-chart" target="_blank"><img src="https://api.producthunt.com/widgets/embed-image/v1/follow.svg?product_id=552855&theme=light" alt="Mermaid Chart - A smarter way to create diagrams | Product Hunt" style="width: 250px; height: 54px;" width="250" height="54" /></a>
|
||||
|
||||
## About
|
||||
|
||||
|
@ -57,7 +57,7 @@ import matplotlib.pyplot as plt
|
||||
|
||||
def mm(graph):
|
||||
graphbytes = graph.encode("utf8")
|
||||
base64_bytes = base64.b64encode(graphbytes)
|
||||
base64_bytes = base64.urlsafe_b64encode(graphbytes)
|
||||
base64_string = base64_bytes.decode("ascii")
|
||||
display(Image(url="https://mermaid.ink/img/" + base64_string))
|
||||
|
||||
|
@ -65,3 +65,110 @@ Allows for the limited reconfiguration of a diagram just before it is rendered.
|
||||
### [Theme Manipulation](../config/theming.md)
|
||||
|
||||
An application of using Directives to change [Themes](../config/theming.md). `Theme` is a value within Mermaid's configuration that dictates the color scheme for diagrams.
|
||||
|
||||
### Layout and look
|
||||
|
||||
We've restructured how Mermaid renders diagrams, enabling new features like selecting layout and look. **Currently, this is supported for flowcharts and state diagrams**, with plans to extend support to all diagram types.
|
||||
|
||||
### Selecting Diagram Looks
|
||||
|
||||
Mermaid offers a variety of styles or “looks” for your diagrams, allowing you to tailor the visual appearance to match your specific needs or preferences. Whether you prefer a hand-drawn or classic style, you can easily customize your diagrams.
|
||||
|
||||
**Available Looks:**
|
||||
|
||||
• Hand-Drawn Look: For a more personal, creative touch, the hand-drawn look brings a sketch-like quality to your diagrams. This style is perfect for informal settings or when you want to add a bit of personality to your diagrams.
|
||||
• Classic Look: If you prefer the traditional Mermaid style, the classic look maintains the original appearance that many users are familiar with. It’s great for consistency across projects or when you want to keep the familiar aesthetic.
|
||||
|
||||
**How to Select a Look:**
|
||||
|
||||
You can select a look by adding the look parameter in the metadata section of your Mermaid diagram code. Here’s an example:
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
look: handDrawn
|
||||
theme: neutral
|
||||
---
|
||||
flowchart LR
|
||||
A[Start] --> B{Decision}
|
||||
B -->|Yes| C[Continue]
|
||||
B -->|No| D[Stop]
|
||||
```
|
||||
|
||||
#### Selecting Layout Algorithms
|
||||
|
||||
In addition to customizing the look of your diagrams, Mermaid Chart now allows you to choose different layout algorithms to better organize and present your diagrams, especially when dealing with more complex structures. The layout algorithm dictates how nodes and edges are arranged on the page.
|
||||
|
||||
#### Supported Layout Algorithms:
|
||||
|
||||
• Dagre (default): This is the classic layout algorithm that has been used in Mermaid for a long time. It provides a good balance of simplicity and visual clarity, making it ideal for most diagrams.
|
||||
• ELK: For those who need more sophisticated layout capabilities, especially when working with large or intricate diagrams, the ELK (Eclipse Layout Kernel) layout offers advanced options. It provides a more optimized arrangement, potentially reducing overlapping and improving readability. This is not included out the box but needs to be added when integrating mermaid for sites/applications that want to have elk support.
|
||||
|
||||
#### How to Select a Layout Algorithm:
|
||||
|
||||
You can specify the layout algorithm directly in the metadata section of your Mermaid diagram code. Here’s an example:
|
||||
|
||||
```mermaid
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
look: handDrawn
|
||||
theme: dark
|
||||
---
|
||||
flowchart TB
|
||||
A[Start] --> B{Decision}
|
||||
B -->|Yes| C[Continue]
|
||||
B -->|No| D[Stop]
|
||||
```
|
||||
|
||||
In this example, the `layout: elk` line configures the diagram to use the ELK layout algorithm, along with the hand drawn look and forest theme.
|
||||
|
||||
#### Customizing ELK Layout:
|
||||
|
||||
When using the ELK layout, you can further refine the diagram’s configuration, such as how nodes are placed and whether parallel edges should be combined:
|
||||
|
||||
- To combine parallel edges, use mergeEdges: true | false.
|
||||
- To configure node placement, use nodePlacementStrategy with the following options:
|
||||
- SIMPLE
|
||||
- NETWORK_SIMPLEX
|
||||
- LINEAR_SEGMENTS
|
||||
- BRANDES_KOEPF (default)
|
||||
|
||||
**Example configuration:**
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: elk
|
||||
elk:
|
||||
mergeEdges: true
|
||||
nodePlacementStrategy: LINEAR_SEGMENTS
|
||||
---
|
||||
flowchart LR
|
||||
A[Start] --> B{Choose Path}
|
||||
B -->|Option 1| C[Path 1]
|
||||
B -->|Option 2| D[Path 2]
|
||||
|
||||
#### Using Dagre Layout with Classic Look:
|
||||
```
|
||||
|
||||
Another example:
|
||||
|
||||
```
|
||||
---
|
||||
config:
|
||||
layout: dagre
|
||||
look: classic
|
||||
theme: default
|
||||
---
|
||||
|
||||
flowchart LR
|
||||
A[Start] --> B{Choose Path}
|
||||
B -->|Option 1| C[Path 1]
|
||||
B -->|Option 2| D[Path 2]
|
||||
|
||||
```
|
||||
|
||||
These options give you the flexibility to create diagrams that not only look great but are also arranged to best suit your data’s structure and flow.
|
||||
|
||||
When integrating Mermaid, you can include look and layout configuration with the initialize call. This is also where you add the loading of elk.
|
||||
|
@ -1,5 +1,47 @@
|
||||
# Blog
|
||||
|
||||
## [Mermaid v11 is out!](https://www.mermaidchart.com/blog/posts/mermaid-v11/)
|
||||
|
||||
23 August 2024 · 2 mins
|
||||
|
||||
Mermaid v11 introduces advanced layout options, new diagram types, and enhanced customization features, thanks to the incredible contributions from our community.
|
||||
|
||||
## [Mermaid Innovation - Introducing New Looks for Mermaid Diagrams](https://www.mermaidchart.com/blog/posts/mermaid-innovation-introducing-new-looks-for-mermaid-diagrams/)
|
||||
|
||||
6 August 2024 ·3 mins
|
||||
|
||||
Discover the fresh new and unique Neo and Hand-Drawn looks for Mermaid Diagrams, while still offering the classic look you love.
|
||||
|
||||
## [The Mermaid Chart Plugin for Jira: A How-To User Guide](https://www.mermaidchart.com/blog/posts/the-mermaid-chart-plugin-for-jira-a-how-to-user-guide/)
|
||||
|
||||
31 July 2024 · 5 mins
|
||||
|
||||
The Mermaid Chart plugin for Jira has arrived!
|
||||
|
||||
## [Mermaid AI Is Here to Change the Game For Diagram Creation](https://www.mermaidchart.com/blog/posts/mermaid-ai-is-here-to-change-the-game-for-diagram-creation/)
|
||||
|
||||
22 July 2024 · 5 mins
|
||||
|
||||
The Mermaid AI chat interface
|
||||
|
||||
## [How to Make a Sequence Diagram with Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-make-a-sequence-diagram-in-mermaid-chart-step-by-step-guide/)
|
||||
|
||||
8 July 2024 · 6 mins
|
||||
|
||||
Sequence diagrams are important for communicating complex systems in a clear and concise manner.
|
||||
|
||||
## [How to Use the New “Comments” Feature in Mermaid Chart](https://www.mermaidchart.com/blog/posts/how-to-use-the-new-comments-feature-in-mermaid-chart/)
|
||||
|
||||
2 July 2024 · 3 mins
|
||||
|
||||
How to Use the New Comments Feature in Mermaid Chart
|
||||
|
||||
## [How to Use the official Mermaid Chart for Confluence app](https://www.mermaidchart.com/blog/posts/how-to-use-the-official-mermaid-chart-for-confluence-app/)
|
||||
|
||||
21 May 2024 · 4 mins
|
||||
|
||||
It doesn’t matter if you’re a data enthusiast, software engineer, or visual storyteller; our Confluence app can allow you to embed Mermaid Chart diagrams — and dynamically edit them — within your Confluence pages.
|
||||
|
||||
## [How to Choose the Right Documentation Software](https://www.mermaidchart.com/blog/posts/how-to-choose-the-right-documentation-software/)
|
||||
|
||||
7 May 2024 · 5 mins
|
||||
|
@ -1,6 +1,7 @@
|
||||
{
|
||||
"name": "docs",
|
||||
"name": "@mermaid-js/docs",
|
||||
"private": true,
|
||||
"version": "0.0.1",
|
||||
"type": "module",
|
||||
"scripts": {
|
||||
"dev": "vitepress --port 3333 --open",
|
||||
|
@ -192,6 +192,7 @@ erDiagram
|
||||
|
||||
- If you want the relationship label to be more than one word, you must use double quotes around the phrase
|
||||
- If you don't want a label at all on a relationship, you must use an empty double-quoted string
|
||||
- (v<MERMAID_RELEASE_VERSION>+) If you want a multi-line label on a relationship, use `<br />` between the two lines (`"first line<br />second line"`)
|
||||
|
||||
## Styling
|
||||
|
||||
|
@ -114,7 +114,7 @@ The `title` is an _optional_ string to be displayed at the top of the Gantt char
|
||||
The `excludes` is an _optional_ attribute that accepts specific dates in YYYY-MM-DD format, days of the week ("sunday") or "weekends", but not the word "weekdays".
|
||||
These date will be marked on the graph, and be excluded from the duration calculation of tasks. Meaning that if there are excluded dates during a task interval, the number of 'skipped' days will be added to the end of the task to ensure the duration is as specified in the code.
|
||||
|
||||
#### Weekend (v\<MERMAID_RELEASE_VERSION>+)
|
||||
#### Weekend (v\11.0.0+)
|
||||
|
||||
When excluding weekends, it is possible to configure the weekends to be either Friday and Saturday or Saturday and Sunday. By default weekends are Saturday and Sunday.
|
||||
To define the weekend start day, there is an _optional_ attribute `weekend` that can be added in a new line followed by either `friday` or `saturday`.
|
||||
|
@ -571,7 +571,7 @@ Usage example:
|
||||
commit
|
||||
```
|
||||
|
||||
### Bottom to Top (`BT:`) (v<MERMAID_RELEASE_VERSION>+)
|
||||
### Bottom to Top (`BT:`) (v11.0.0+)
|
||||
|
||||
In `BT` (**Bottom-to-Top**) orientation, the commits run from bottom to top of the graph and branches are arranged side-by-side.
|
||||
|
||||
|
@ -1,4 +1,4 @@
|
||||
# Packet Diagram (v<MERMAID_RELEASE_VERSION>+)
|
||||
# Packet Diagram (v11.0.0+)
|
||||
|
||||
## Introduction
|
||||
|
||||
|
@ -143,18 +143,18 @@ Messages can be of two displayed either solid or with a dotted line.
|
||||
|
||||
There are ten types of arrows currently supported:
|
||||
|
||||
| Type | Description |
|
||||
| -------- | ----------------------------------------------------------------------- |
|
||||
| `->` | Solid line without arrow |
|
||||
| `-->` | Dotted line without arrow |
|
||||
| `->>` | Solid line with arrowhead |
|
||||
| `-->>` | Dotted line with arrowhead |
|
||||
| `<<->>` | Solid line with bidirectional arrowheads (v<MERMAID_RELEASE_VERSION>+) |
|
||||
| `<<-->>` | Dotted line with bidirectional arrowheads (v<MERMAID_RELEASE_VERSION>+) |
|
||||
| `-x` | Solid line with a cross at the end |
|
||||
| `--x` | Dotted line with a cross at the end. |
|
||||
| `-)` | Solid line with an open arrow at the end (async) |
|
||||
| `--)` | Dotted line with a open arrow at the end (async) |
|
||||
| Type | Description |
|
||||
| -------- | ---------------------------------------------------- |
|
||||
| `->` | Solid line without arrow |
|
||||
| `-->` | Dotted line without arrow |
|
||||
| `->>` | Solid line with arrowhead |
|
||||
| `-->>` | Dotted line with arrowhead |
|
||||
| `<<->>` | Solid line with bidirectional arrowheads (v11.0.0+) |
|
||||
| `<<-->>` | Dotted line with bidirectional arrowheads (v11.0.0+) |
|
||||
| `-x` | Solid line with a cross at the end |
|
||||
| `--x` | Dotted line with a cross at the end. |
|
||||
| `-)` | Solid line with an open arrow at the end (async) |
|
||||
| `--)` | Dotted line with a open arrow at the end (async) |
|
||||
|
||||
## Activations
|
||||
|
||||
|
@ -36,6 +36,7 @@ async function addHtmlSpan(element, node, width, classes, addBackground = false)
|
||||
div.style('white-space', 'nowrap');
|
||||
div.style('line-height', '1.5');
|
||||
div.style('max-width', width + 'px');
|
||||
div.style('text-align', 'center');
|
||||
div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
|
||||
if (addBackground) {
|
||||
div.attr('class', 'labelBkg');
|
||||
|
@ -1,7 +1,7 @@
|
||||
import { select } from 'd3';
|
||||
import { insertNode } from '../dagre-wrapper/nodes.js';
|
||||
|
||||
export const getDiagramElements = (id, securityLevel) => {
|
||||
export const getDiagramElement = (id, securityLevel) => {
|
||||
let sandboxElement;
|
||||
if (securityLevel === 'sandbox') {
|
||||
sandboxElement = select('#i' + id);
|
||||
@ -15,9 +15,7 @@ export const getDiagramElements = (id, securityLevel) => {
|
||||
|
||||
// Run the renderer. This is what draws the final graph.
|
||||
|
||||
// @ts-ignore todo: fix this
|
||||
const element = root.select('#' + id + ' g');
|
||||
return { svg, element };
|
||||
return svg;
|
||||
};
|
||||
|
||||
export function insertElementsForSize(el, data) {
|
||||
|
@ -28,7 +28,7 @@ import { getSubGraphTitleMargins } from '../../../utils/subGraphTitleMargins.js'
|
||||
import { getConfig } from '../../../diagram-api/diagramAPI.js';
|
||||
|
||||
const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, siteConfig) => {
|
||||
log.info('Graph in recursive render: XXX', graphlibJson.write(graph), parentCluster);
|
||||
log.warn('Graph in recursive render:XAX', graphlibJson.write(graph), parentCluster);
|
||||
const dir = graph.graph().rankdir;
|
||||
log.trace('Dir in recursive render - dir:', dir);
|
||||
|
||||
@ -111,7 +111,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
||||
if (graph.children(v).length > 0) {
|
||||
// This is a cluster but not to be rendered recursively
|
||||
// Render as before
|
||||
log.info(
|
||||
log.trace(
|
||||
'Cluster - the non recursive path XBX',
|
||||
v,
|
||||
node.id,
|
||||
@ -120,11 +120,11 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
||||
'Graph:',
|
||||
graph
|
||||
);
|
||||
log.info(findNonClusterChild(node.id, graph));
|
||||
log.trace(findNonClusterChild(node.id, graph));
|
||||
clusterDb.set(node.id, { id: findNonClusterChild(node.id, graph), node });
|
||||
// insertCluster(clusters, graph.node(v));
|
||||
} else {
|
||||
log.trace('Node - the non recursive path XAX', v, node.id, node);
|
||||
log.trace('Node - the non recursive path XAX', v, nodes, graph.node(v), dir);
|
||||
await insertNode(nodes, graph.node(v), dir);
|
||||
}
|
||||
}
|
||||
@ -268,7 +268,7 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
|
||||
return { elem, diff };
|
||||
};
|
||||
|
||||
export const render = async (data4Layout, svg, element) => {
|
||||
export const render = async (data4Layout, svg) => {
|
||||
const graph = new graphlib.Graph({
|
||||
multigraph: true,
|
||||
compound: true,
|
||||
@ -289,7 +289,7 @@ export const render = async (data4Layout, svg, element) => {
|
||||
.setDefaultEdgeLabel(function () {
|
||||
return {};
|
||||
});
|
||||
|
||||
const element = svg.select('g');
|
||||
insertMarkers(element, data4Layout.markers, data4Layout.type, data4Layout.diagramId);
|
||||
clearNodes();
|
||||
clearEdges();
|
||||
@ -305,12 +305,64 @@ export const render = async (data4Layout, svg, element) => {
|
||||
|
||||
log.debug('Edges:', data4Layout.edges);
|
||||
data4Layout.edges.forEach((edge) => {
|
||||
graph.setEdge(edge.start, edge.end, { ...edge }, edge.id);
|
||||
// Handle self-loops
|
||||
if (edge.start === edge.end) {
|
||||
const nodeId = edge.start;
|
||||
const specialId1 = nodeId + '---' + nodeId + '---1';
|
||||
const specialId2 = nodeId + '---' + nodeId + '---2';
|
||||
const node = graph.node(nodeId);
|
||||
graph.setNode(specialId1, {
|
||||
domId: specialId1,
|
||||
id: specialId1,
|
||||
parentId: node.parentId,
|
||||
labelStyle: '',
|
||||
label: '',
|
||||
padding: 0,
|
||||
shape: 'labelRect',
|
||||
// shape: 'rect',
|
||||
style: '',
|
||||
width: 10,
|
||||
height: 10,
|
||||
});
|
||||
graph.setParent(specialId1, node.parentId);
|
||||
graph.setNode(specialId2, {
|
||||
domId: specialId2,
|
||||
id: specialId2,
|
||||
parentId: node.parentId,
|
||||
labelStyle: '',
|
||||
padding: 0,
|
||||
// shape: 'rect',
|
||||
shape: 'labelRect',
|
||||
label: '',
|
||||
style: '',
|
||||
width: 10,
|
||||
height: 10,
|
||||
});
|
||||
graph.setParent(specialId2, node.parentId);
|
||||
|
||||
const edge1 = structuredClone(edge);
|
||||
const edgeMid = structuredClone(edge);
|
||||
const edge2 = structuredClone(edge);
|
||||
edge1.label = '';
|
||||
edge1.arrowTypeEnd = 'none';
|
||||
edge1.id = nodeId + '-cyclic-special-1';
|
||||
edgeMid.arrowTypeEnd = 'none';
|
||||
edgeMid.id = nodeId + '-cyclic-special-mid';
|
||||
edge2.label = '';
|
||||
edge1.fromCluster = nodeId;
|
||||
edge2.toCluster = nodeId;
|
||||
edge2.id = nodeId + '-cyclic-special-2';
|
||||
graph.setEdge(nodeId, specialId1, edge1, nodeId + '-cyclic-special-0');
|
||||
graph.setEdge(specialId1, specialId2, edgeMid, nodeId + '-cyclic-special-1');
|
||||
graph.setEdge(specialId2, nodeId, edge2, nodeId + '-cyc<lic-special-2');
|
||||
} else {
|
||||
graph.setEdge(edge.start, edge.end, { ...edge }, edge.id);
|
||||
}
|
||||
});
|
||||
|
||||
log.warn('Graph at first:', JSON.stringify(graphlibJson.write(graph)));
|
||||
adjustClustersAndEdges(graph);
|
||||
log.warn('Graph after:', JSON.stringify(graphlibJson.write(graph)));
|
||||
log.warn('Graph after XAX:', JSON.stringify(graphlibJson.write(graph)));
|
||||
const siteConfig = getConfig();
|
||||
await recursiveRender(
|
||||
element,
|
||||
|
@ -267,51 +267,7 @@ export const adjustClustersAndEdges = (graph, depth) => {
|
||||
' --- ',
|
||||
clusterDb.get(e.w)
|
||||
);
|
||||
if (clusterDb.get(e.v) && clusterDb.get(e.w) && clusterDb.get(e.v) === clusterDb.get(e.w)) {
|
||||
log.warn('Fixing and trying link to self - removing XXX', e.v, e.w, e.name);
|
||||
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
|
||||
v = getAnchorId(e.v);
|
||||
w = getAnchorId(e.w);
|
||||
graph.removeEdge(e.v, e.w, e.name);
|
||||
const specialId1 = e.w + '---' + e.v + '---1';
|
||||
const specialId2 = e.w + '---' + e.v + '---2';
|
||||
graph.setNode(specialId1, {
|
||||
domId: specialId1,
|
||||
id: specialId1,
|
||||
labelStyle: '',
|
||||
label: '',
|
||||
padding: 0,
|
||||
shape: 'labelRect',
|
||||
style: '',
|
||||
width: 10,
|
||||
height: 10,
|
||||
});
|
||||
graph.setNode(specialId2, {
|
||||
domId: specialId2,
|
||||
id: specialId2,
|
||||
labelStyle: '',
|
||||
padding: 0,
|
||||
shape: 'labelRect',
|
||||
style: '',
|
||||
width: 10,
|
||||
height: 10,
|
||||
});
|
||||
const edge1 = structuredClone(edge);
|
||||
const edgeMid = structuredClone(edge);
|
||||
const edge2 = structuredClone(edge);
|
||||
edge1.label = '';
|
||||
edge1.arrowTypeEnd = 'none';
|
||||
edge1.id = e.name + '-cyclic-special-1';
|
||||
edgeMid.arrowTypeEnd = 'none';
|
||||
edgeMid.id = e.name + '-cyclic-special-mid';
|
||||
edge2.label = '';
|
||||
edge1.fromCluster = e.v;
|
||||
edge2.toCluster = e.v;
|
||||
edge2.id = e.name + '-cyclic-special-2';
|
||||
graph.setEdge(v, specialId1, edge1, e.name + '-cyclic-special-0');
|
||||
graph.setEdge(specialId1, specialId2, edgeMid, e.name + '-cyclic-special-1');
|
||||
graph.setEdge(specialId2, w, edge2, e.name + '-cyclic-special-2');
|
||||
} else if (clusterDb.get(e.v) || clusterDb.get(e.w)) {
|
||||
if (clusterDb.get(e.v) || clusterDb.get(e.w)) {
|
||||
log.warn('Fixing and trying - removing XXX', e.v, e.w, e.name);
|
||||
v = getAnchorId(e.v);
|
||||
w = getAnchorId(e.w);
|
||||
@ -334,13 +290,6 @@ export const adjustClustersAndEdges = (graph, depth) => {
|
||||
extractor(graph, 0);
|
||||
|
||||
log.trace(clusterDb);
|
||||
|
||||
// Remove references to extracted cluster
|
||||
// graph.edges().forEach((edge) => {
|
||||
// if (isDescendant(edge.v, clusterId) || isDescendant(edge.w, clusterId)) {
|
||||
// graph.removeEdge(edge);
|
||||
// }
|
||||
// });
|
||||
};
|
||||
|
||||
export const extractor = (graph, depth) => {
|
||||
@ -441,7 +390,7 @@ export const extractor = (graph, depth) => {
|
||||
for (const node of nodes) {
|
||||
const data = graph.node(node);
|
||||
log.warn(' Now next level', node, data);
|
||||
if (data.clusterNode) {
|
||||
if (data?.clusterNode) {
|
||||
extractor(data.graph, depth + 1);
|
||||
}
|
||||
}
|
||||
|
@ -12,7 +12,6 @@ export interface LayoutAlgorithm {
|
||||
render(
|
||||
layoutData: LayoutData,
|
||||
svg: SVG,
|
||||
element: any,
|
||||
helpers: InternalHelpers,
|
||||
options?: RenderOptions
|
||||
): Promise<void>;
|
||||
@ -45,14 +44,14 @@ const registerDefaultLayoutLoaders = () => {
|
||||
|
||||
registerDefaultLayoutLoaders();
|
||||
|
||||
export const render = async (data4Layout: LayoutData, svg: SVG, element: any) => {
|
||||
export const render = async (data4Layout: LayoutData, svg: SVG) => {
|
||||
if (!(data4Layout.layoutAlgorithm in layoutAlgorithms)) {
|
||||
throw new Error(`Unknown layout algorithm: ${data4Layout.layoutAlgorithm}`);
|
||||
}
|
||||
|
||||
const layoutDefinition = layoutAlgorithms[data4Layout.layoutAlgorithm];
|
||||
const layoutRenderer = await layoutDefinition.loader();
|
||||
return layoutRenderer.render(data4Layout, svg, element, internalHelpers, {
|
||||
return layoutRenderer.render(data4Layout, svg, internalHelpers, {
|
||||
algorithm: layoutDefinition.algorithm,
|
||||
});
|
||||
};
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user