Merge pull request #3531 from mermaid-js/3061_making_a_monorepo

3061 making a monorepo
2979_arrow_alignment
pbrolin47 2022-09-29 19:52:40 +02:00 committed by GitHub
commit 320e105ffe
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
319 changed files with 14978 additions and 11951 deletions

View File

@ -20,26 +20,32 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
cache: yarn
cache: pnpm
node-version: ${{ matrix.node-version }}
- name: Install Yarn
run: npm i yarn --global
- name: Install Packages
run: |
yarn install --frozen-lockfile
pnpm install --frozen-lockfile
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Run Build
run: yarn build
run: pnpm run build
- name: Upload Build as Artifact
- name: Upload Mermaid Build as Artifact
uses: actions/upload-artifact@v3
with:
name: dist
path: dist
name: mermaid-build
path: packages/mermaid/dist
- name: Upload Mermaid Mindmap Build as Artifact
uses: actions/upload-artifact@v3
with:
name: mermaid-mindmap-build
path: packages/mermaid-mindmap/dist

View File

@ -29,25 +29,24 @@ jobs:
name: Warn if not using Applitools
run: |
echo "::error,title=Not using Applitols::APPLITOOLS_API_KEY is empty, disabling Applitools for this run."
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
cache: yarn
cache: pnpm
node-version: ${{ matrix.node-version }}
- name: Install Yarn
run: npm i yarn --global
- name: Install Packages
run: |
yarn install --frozen-lockfile
pnpm install --frozen-lockfile
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Run Build
run: yarn build
- if: ${{ env.USE_APPLI }}
name: Notify applitools of new batch
# Copied from docs https://applitools.com/docs/topics/integrations/github-integration-ci-setup.html
@ -60,7 +59,7 @@ jobs:
APPLITOOLS_SERVER_URL: 'https://eyesapi.applitools.com'
- name: Run E2E Tests
run: yarn e2e
run: pnpm run e2e
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
# Mermaid applitools.config.js uses this to pick batch name.

View File

@ -16,37 +16,31 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: actions/cache@v3
id: yarn-and-build-cache
with:
path: |
~/.cache/Cypress
build
node_modules
key: ${{ runner.os }}-node_modules-build-${{ hashFiles('**/yarn.lock') }}
restore-keys: |
${{ runner.os }}-node_modules-build-
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
# Need to skip setup if Cypress run is skipped, otherwise an error
# is thrown since the pnpm cache step fails
if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }}
with:
cache: yarn
cache: pnpm
node-version: ${{ matrix.node-version }}
# Install NPM dependencies, cache them correctly
# and run all Cypress tests
- name: Cypress run
uses: cypress-io/github-action@v3
uses: cypress-io/github-action@v4
# 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
if: ${{ ( env.CYPRESS_RECORD_KEY != '' ) || ( matrix.containers == 1 ) }}
with:
start: yarn dev
start: pnpm run dev
wait-on: 'http://localhost:9000'
# Disable recording if we don't have an API key
# e.g. if this action was run from a fork
record: ${{ secrets.CYPRESS_RECORD_KEY != '' }}
parallel: ${{ secrets.CYPRESS_RECORD_KEY != '' }}
headless: true
env:
CYPRESS_RECORD_KEY: ${{ secrets.CYPRESS_RECORD_KEY }}

View File

@ -20,23 +20,23 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
cache: yarn
cache: pnpm
node-version: ${{ matrix.node-version }}
- name: Install Yarn
run: npm i yarn --global
- name: Install Packages
run: |
yarn install --frozen-lockfile
pnpm install --frozen-lockfile
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Run Linting
run: yarn lint
run: pnpm run lint
- name: Verify Docs
run: yarn docs:verify
run: pnpm run docs:verify

View File

@ -14,24 +14,24 @@ jobs:
steps:
- uses: actions/checkout@v3
- uses: pnpm/action-setup@v2
# uses version from "packageManager" field in package.json
- name: Setup Node.js ${{ matrix.node-version }}
uses: actions/setup-node@v3
with:
cache: yarn
cache: pnpm
node-version: ${{ matrix.node-version }}
- name: Install Yarn
run: npm i yarn --global
- name: Install Packages
run: |
yarn install --frozen-lockfile
pnpm install --frozen-lockfile
env:
CYPRESS_CACHE_FOLDER: .cache/Cypress
- name: Run Unit Tests
run: |
yarn ci --coverage
pnpm run ci --coverage
- name: Upload Coverage to Coveralls
# it feels a bit weird to use @master, but that's what the docs use

View File

@ -1,4 +1,6 @@
dist
cypress/platform/xss3.html
.cache
coverage
coverage
# Autogenerated by PNPM
pnpm-lock.yaml

View File

@ -3,35 +3,67 @@ import { resolve } from 'path';
import { fileURLToPath } from 'url';
import jisonPlugin from './jisonPlugin.js';
import pkg from '../package.json' assert { type: 'json' };
type OutputOptions = Exclude<
Exclude<InlineConfig['build'], undefined>['rollupOptions'],
undefined
>['output'];
const { dependencies } = pkg;
const watch = process.argv.includes('--watch');
const __dirname = fileURLToPath(new URL('.', import.meta.url));
type OutputOptions = Exclude<
Exclude<InlineConfig['build'], undefined>['rollupOptions'],
undefined
>['output'];
const packageOptions = {
mermaid: {
name: 'mermaid',
packageName: 'mermaid',
file: 'mermaid.ts',
},
'mermaid-mindmap': {
name: 'mermaid-mindmap',
packageName: 'mermaid-mindmap',
file: 'add-diagram.ts',
},
'mermaid-mindmap-detector': {
name: 'mermaid-mindmap-detector',
packageName: 'mermaid-mindmap',
file: 'registry.ts',
},
'mermaid-example-diagram': {
name: 'mermaid-example-diagram',
packageName: 'mermaid-example-diagram',
file: 'add-diagram.ts',
},
'mermaid-example-diagram-detector': {
name: 'mermaid-example-diagram-detector',
packageName: 'mermaid-example-diagram',
file: 'registry.ts',
},
};
interface BuildOptions {
minify: boolean | 'esbuild';
core?: boolean;
watch?: boolean;
entryName: keyof typeof packageOptions;
}
export const getBuildConfig = ({ minify, core, watch }: BuildOptions): InlineConfig => {
export const getBuildConfig = ({ minify, core, watch, entryName }: BuildOptions): InlineConfig => {
const external = ['require', 'fs', 'path'];
console.log(entryName, packageOptions[entryName]);
const { name, file, packageName } = packageOptions[entryName];
let output: OutputOptions = [
{
name: 'mermaid',
name,
format: 'esm',
sourcemap: true,
entryFileNames: `[name].esm${minify ? '.min' : ''}.mjs`,
entryFileNames: `${name}.esm${minify ? '.min' : ''}.mjs`,
},
{
name: 'mermaid',
name,
format: 'umd',
sourcemap: true,
entryFileNames: `[name]${minify ? '.min' : ''}.js`,
entryFileNames: `${name}${minify ? '.min' : ''}.js`,
},
];
@ -42,9 +74,10 @@ export const getBuildConfig = ({ minify, core, watch }: BuildOptions): InlineCon
// This needs to be an array. Otherwise vite will build esm & umd with same name and overwrite esm with umd.
output = [
{
name,
format: 'esm',
sourcemap: true,
entryFileNames: `[name].core.mjs`,
entryFileNames: `${name}.core.mjs`,
},
];
}
@ -53,11 +86,12 @@ export const getBuildConfig = ({ minify, core, watch }: BuildOptions): InlineCon
configFile: false,
build: {
emptyOutDir: false,
outDir: resolve(__dirname, `../packages/${packageName}/dist`),
lib: {
entry: resolve(__dirname, '../src/mermaid.ts'),
name: 'mermaid',
entry: resolve(__dirname, `../packages/${packageName}/src/${file}`),
name,
// the proper extensions will be added
fileName: 'mermaid',
fileName: name,
},
minify,
rollupOptions: {
@ -73,17 +107,36 @@ export const getBuildConfig = ({ minify, core, watch }: BuildOptions): InlineCon
if (watch && config.build) {
config.build.watch = {
include: 'src/**',
include: [
'packages/mermaid-mindmap/src/**',
'packages/mermaid/src/**',
'packages/mermaid-example-diagram/src/**',
],
};
}
return config;
};
const buildPackage = async (entryName: keyof typeof packageOptions) => {
return Promise.allSettled([
build(getBuildConfig({ minify: false, entryName })),
build(getBuildConfig({ minify: 'esbuild', entryName })),
build(getBuildConfig({ minify: false, core: true, entryName })),
]);
};
const main = async () => {
const packageNames = Object.keys(packageOptions) as (keyof typeof packageOptions)[];
for (const pkg of packageNames) {
await buildPackage(pkg);
}
};
if (watch) {
build(getBuildConfig({ minify: false, watch }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid' }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-mindmap' }));
build(getBuildConfig({ minify: false, watch, entryName: 'mermaid-example-diagram' }));
} else {
build(getBuildConfig({ minify: false }));
build(getBuildConfig({ minify: 'esbuild' }));
build(getBuildConfig({ minify: false, core: true }));
void main();
}

View File

@ -13,7 +13,7 @@ async function createServer() {
});
app.use(vite.middlewares);
app.use(express.static('dist'));
app.use(express.static('./packages/mermaid/dist'));
app.use(express.static('demos'));
app.use(express.static('cypress/platform'));

View File

@ -1,5 +1,22 @@
# mermaid [![Build Status](https://travis-ci.org/mermaid-js/mermaid.svg?branch=master)](https://travis-ci.org/mermaid-js/mermaid) [![NPM](https://img.shields.io/npm/v/mermaid)](https://www.npmjs.com/package/mermaid) [![Coverage Status](https://coveralls.io/repos/github/mermaid-js/mermaid/badge.svg?branch=master)](https://coveralls.io/github/mermaid-js/mermaid?branch=master) [![Join our Slack!](https://img.shields.io/static/v1?message=join%20chat&color=9cf&logo=slack&label=slack)](https://join.slack.com/t/mermaid-talk/shared_invite/enQtNzc4NDIyNzk4OTAyLWVhYjQxOTI2OTg4YmE1ZmJkY2Y4MTU3ODliYmIwOTY3NDJlYjA0YjIyZTdkMDMyZTUwOGI0NjEzYmEwODcwOTE)
# Whoa, whats going on here?
We are transforming the Mermaid repository to a so called mono-repo. This is a part of the effort to decouple the diagrams from the core of mermaid. This will:
- Make it possible to select which diagrams to include on your site
- Open up for lazy loading
- Make it possible to add diagrams from outside of the Mermaid repository
- Separate the release flow between different diagrams and the Mermaid core
As such be aware of sime changes..
# We use pnpm now
# The source code has moved
It is now localted in the src forunder for respoective package located as subfoders in packages.
English | [简体中文](./README.zh-CN.md)
<img src="./img/header.png" alt="" />

View File

@ -1,4 +1,4 @@
import mermaid from '../../src/mermaid';
import mermaid from '../../packages/mermaid/src/mermaid';
let code = `flowchart LR
Power_Supply --> Transmitter_A

View File

@ -2,7 +2,7 @@
<head>
<meta charset="utf-8" />
<!-- <meta charset="iso-8859-15"/> -->
<script src="./viewer.js" type="module" />
<script src="./viewer.js" type="module"></script>
<!-- <link href="https://fonts.googleapis.com/css?family=Mansalva&display=swap" rel="stylesheet" /> -->
<link
href="https://fonts.googleapis.com/css?family=Noto+Sans+SC&display=swap"

View File

@ -363,7 +363,9 @@ flowchart TD
}
</pre>
<script src="./mermaid.js"></script>
<!-- <script src="./mermaid.js"></script> -->
<script src="./packages/mermaid-mindmap/dist/mermaid-mindmap.js"></script>
<script src="./packages/mermaid/dist/mermaid.js"></script>
<script>
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);

View File

@ -45,41 +45,37 @@
</head>
<body>
<div>Security check</div>
<div class="flex">
<pre id="diagram" class="mermaid">
flowchart TD
A[myClass1] --> B[default] & C[default]
B[default] & C[default] --> D[myClass2]
classDef default stroke-width:2px,fill:none,stroke:silver
classDef node color:red
classDef myClass1 color:#0000ff
classDef myClass2 stroke:#0000ff,fill:#ccccff
class A myClass1
class D myClass2
</pre
>
<div id="res" class=""></div>
</div>
<script src="./mermaid.js"></script>
<pre id="diagram" class="mermaid">
info
</pre
>
<pre id="diagram" class="mermaid2">
mindmap
root
ch1
ch2
</pre
>
<script src="./packages/mermaid-mindmap/dist/mermaid-mindmap-detector.js"></script>
<script src="./packages/mermaid-mindmap/dist/mermaid-example-diagram-detector.js"></script>
<script src="./packages/mermaid/dist/mermaid.js"></script>
<script>
mermaid.parseError = function (err, hash) {
// console.error('Mermaid error: ', err);
};
mermaid.initialize({
startOnLoad: false,
startOnLoad: true,
logLevel: 0,
basePath: './packages/',
// themeVariables: {relationLabelColor: 'red'}
});
function callback() {
alert('It worked');
}
// document.querySelector('#diagram').innerHTML = diagram;
try {
mermaid.initThrowsErrors(undefined, '#diagram');
} catch (err) {
console.log('Caught error:', err);
}
mermaid.parseError = function (err, hash) {
console.error('In parse error:');
console.error(err);
};
</script>
</body>
</html>

View File

@ -1,4 +1,4 @@
import mermaid2 from '../../src/mermaid';
import mermaid2 from '../../packages/mermaid/src/mermaid';
function b64ToUtf8(str) {
return decodeURIComponent(escape(window.atob(str)));

View File

@ -1,11 +1,13 @@
{
"name": "mermaid",
"name": "mermaid-monorepo",
"private": true,
"version": "9.2.0-rc2",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid.core.mjs",
"module": "dist/mermaid.core.mjs",
"types": "dist/mermaid.d.ts",
"type": "module",
"packageManager": "pnpm@7.12.2",
"exports": {
".": {
"require": "./dist/mermaid.min.js",
@ -26,25 +28,25 @@
"scripts": {
"clean": "rimraf dist",
"build:vite": "ts-node-esm --transpileOnly --project=.vite/tsconfig.json .vite/build.ts",
"build:types": "tsc -p ./tsconfig.json --emitDeclarationOnly",
"build:watch": "yarn build:code --watch",
"build": "yarn clean; concurrently \"yarn build:vite\" \"yarn build:types\"",
"dev": "concurrently \"yarn build:vite --watch\" \"ts-node-esm .vite/server\"",
"docs:build": "ts-node-esm --transpileOnly src/docs.mts",
"docs:verify": "yarn docs:build --verify",
"postbuild": "documentation build src/mermaidAPI.ts src/config.ts src/defaultConfig.ts --shallow -f md --markdown-toc false > src/docs/Setup.md && prettier --write src/docs/Setup.md",
"release": "yarn build",
"lint": "eslint --cache --ignore-path .gitignore . && yarn lint:jison && prettier --check .",
"build:types": "concurrently \"tsc -p ./packages/mermaid/tsconfig.json --emitDeclarationOnly\" \"tsc -p ./packages/mermaid-mindmap/tsconfig.json --emitDeclarationOnly\"",
"build:watch": "pnpm build:vite --watch",
"build": "pnpm clean; concurrently \"pnpm build:vite\" \"pnpm build:types\"",
"dev": "concurrently \"pnpm build:vite --watch\" \"ts-node-esm .vite/server\"",
"docs:build": "ts-node-esm --transpileOnly packages/mermaid/src/docs.mts",
"docs:verify": "pnpm docs:build --verify",
"todo-postbuild": "documentation build src/mermaidAPI.ts src/config.ts src/defaultConfig.ts --shallow -f md --markdown-toc false > src/docs/Setup.md && prettier --write src/docs/Setup.md",
"release": "pnpm build",
"lint": "eslint --cache --ignore-path .gitignore . && pnpm lint:jison && prettier --check .",
"lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .",
"lint:jison": "ts-node-esm --transpileOnly src/jison/lint.mts",
"lint:jison": "ts-node-esm --transpileOnly packages/mermaid/src/jison/lint.mts",
"cypress": "cypress run",
"cypress:open": "cypress open",
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
"ci": "vitest run",
"test": "yarn lint && vitest run",
"test": "pnpm lint && vitest run",
"test:watch": "vitest --coverage --watch",
"prepublishOnly": "yarn build && yarn test",
"prepare": "concurrently \"husky install\" \"yarn build\"",
"prepublishOnly": "pnpm build && pnpm test",
"todo-prepare": "concurrently \"husky install\" \"pnpm build\"",
"pre-commit": "lint-staged"
},
"repository": {
@ -66,6 +68,7 @@
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"@types/uuid": "^8.3.4",
"@types/node": "^18.7.21",
"d3": "^7.0.0",
"dagre": "^0.8.5",
"dagre-d3": "^0.6.4",
@ -76,6 +79,7 @@
"lodash": "^4.17.21",
"moment-mini": "^2.24.0",
"non-layered-tidy-tree-layout": "^2.0.2",
"rollup": "^2.79.1",
"stylis": "^4.1.2",
"uuid": "^9.0.0"
},
@ -112,6 +116,7 @@
"globby": "^13.1.2",
"husky": "^8.0.0",
"identity-obj-proxy": "^3.0.0",
"jest": "29.x",
"jison": "^0.4.18",
"jsdom": "^20.0.0",
"lint-staged": "^13.0.0",
@ -121,7 +126,6 @@
"prettier-plugin-jsdoc": "^0.4.2",
"remark": "^14.0.2",
"rimraf": "^3.0.2",
"rollup": "^2.79.1",
"start-server-and-test": "^1.12.6",
"ts-node": "^10.9.1",
"typescript": "^4.8.3",

View File

@ -0,0 +1,70 @@
{
"name": "@mermaid-js/mermaid-example-diagram",
"version": "9.2.0-rc2",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid-mindmap.core.mjs",
"module": "dist/mermaid-mindmap.core.mjs",
"type": "module",
"exports": {
".": {
"require": "./dist/mermaid-example-diagram.min.js",
"import": "./dist/mermaid-example-diagram.core.mjs"
},
"./*": "./*"
},
"keywords": [
"diagram",
"markdown",
"mindmap",
"mermaid"
],
"scripts": {
"clean": "rimraf dist",
"build:types": "tsc -p ./tsconfig.json --emitDeclarationOnly",
"build:watch": "yarn build:code --watch",
"build:esbuild": "concurrently \"yarn build:code\" \"yarn build:types\"",
"build": "yarn clean; yarn build:esbuild",
"dev": "node .esbuild/serve.cjs",
"release": "yarn build",
"lint": "eslint --cache --ignore-path .gitignore . && yarn lint:jison && prettier --check .",
"lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .",
"lint:jison": "ts-node-esm src/jison/lint.mts",
"ci": "vitest run",
"test": "yarn lint && vitest run",
"test:watch": "vitest --coverage --watch",
"todo-prepublishOnly": "yarn build && yarn test",
"todo-prepare": "concurrently \"husky install ../../.husky\" \"yarn build\"",
"todo-pre-commit": "lint-staged"
},
"repository": {
"type": "git",
"url": "https://github.com/mermaid-js/mermaid"
},
"author": "Knut Sveidqvist",
"license": "MIT",
"standard": {
"ignore": [
"**/parser/*.js",
"dist/**/*.js",
"cypress/**/*.js"
],
"globals": [
"page"
]
},
"dependencies": {},
"devDependencies": {
"concurrently": "^7.4.0",
"rimraf": "^3.0.2"
},
"resolutions": {
"d3": "^7.0.0"
},
"files": [
"dist"
],
"sideEffects": [
"**/*.css",
"**/*.scss"
]
}

View File

@ -0,0 +1,17 @@
// @ts-ignore: TODO Fix ts errors
import parser from './parser/exampleDiagram';
import * as db from './exampleDiagramDb';
import renderer from './exampleDiagramRenderer';
import styles from './styles';
import { injectUtils } from './mermaidUtils';
window.mermaid.connectDiagram(
'example-diagram',
{
db,
renderer,
parser,
styles,
},
injectUtils
);

View File

@ -0,0 +1,3 @@
export const detector = (txt: string) => {
return txt.match(/^\s*example-diagram/) !== null;
};

View File

@ -0,0 +1,16 @@
import { parser } from './parser/exampleDiagram';
import db from './exampleDiagramDb';
describe('when parsing an info graph it', function () {
let ex;
beforeEach(function () {
ex = parser;
ex.yy = db;
});
it('should handle an info definition', function () {
let str = `info
showInfo`;
ex.parse(str);
});
});

View File

@ -0,0 +1,33 @@
/** Created by knut on 15-01-14. */
import { log } from './mermaidUtils';
var message = '';
var info = false;
export const setMessage = (txt) => {
log.debug('Setting message to: ' + txt);
message = txt;
};
export const getMessage = () => {
return message;
};
export const setInfo = (inf) => {
info = inf;
};
export const getInfo = () => {
return info;
};
export default {
setMessage,
getMessage,
setInfo,
getInfo,
clear: () => {
message = '';
info = false;
},
};

View File

@ -0,0 +1,55 @@
/** Created by knut on 14-12-11. */
import { select } from 'd3';
import { log, getConfig } from './mermaidUtils';
/**
* Draws a an info picture in the tag with id: id based on the graph definition in text.
*
* @param {any} text
* @param {any} id
* @param {any} version
* @param diagObj
*/
export const draw = (text, id, version, diagObj) => {
try {
log.debug('Rendering info diagram\n' + text);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sanbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
// Parse the graph definition
// parser.parse(text);
// log.debug('Parsed info diagram');
// Fetch the default direction, use TD if none was found
const svg = root.select('#' + id);
const g = svg.append('g');
g.append('text') // text label for the x axis
.attr('x', 100)
.attr('y', 40)
.attr('class', 'version')
.attr('font-size', '32px')
.style('text-anchor', 'middle')
.text('v ' + version);
svg.attr('height', 100);
svg.attr('width', 400);
// svg.attr('viewBox', '0 0 300 150');
} catch (e) {
log.error('Error while rendering info diagram');
log.error(e.message);
}
};
export default {
draw,
};

View File

@ -0,0 +1,63 @@
const warning = () => null;
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
export const LEVELS: Record<LogLevel, number> = {
trace: 0,
debug: 1,
info: 2,
warn: 3,
error: 4,
fatal: 5,
};
export const log: Record<keyof typeof LEVELS, typeof console.log> = {
trace: warning,
debug: warning,
info: warning,
warn: warning,
error: warning,
fatal: warning,
};
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
export let getConfig: () => object;
export let sanitizeText: (str: string) => string;
/**
* Placeholder for the real function that will be injected by mermaid.
*/
// eslint-disable @typescript-eslint/no-explicit-any
export let setupGraphViewbox: (
graph: any,
svgElem: any,
padding: any,
useMaxWidth: boolean
) => void;
/**
* Function called by mermaid that injects utility functions that help the diagram to be a good citizen.
* @param _log
* @param _setLogLevel
* @param _getConfig
* @param _sanitizeText
* @param _setupGraphViewbox
*/
export const injectUtils = (
_log: Record<keyof typeof LEVELS, typeof console.log>,
_setLogLevel: any,
_getConfig: any,
_sanitizeText: any,
_setupGraphViewbox: any
) => {
_log.debug('Mermaid utils injected into example-diagram');
log.trace = _log.trace;
log.debug = _log.debug;
log.info = _log.info;
log.warn = _log.warn;
log.error = _log.error;
log.fatal = _log.fatal;
setLogLevel = _setLogLevel;
getConfig = _getConfig;
sanitizeText = _sanitizeText;
setupGraphViewbox = _setupGraphViewbox;
};

View File

@ -0,0 +1,43 @@
%lex
%options case-insensitive
%{
// Pre-lexer code can go here
%}
%%
"info" return 'info' ;
[\s\n\r]+ return 'NL' ;
[\s]+ return 'space';
"showInfo" return 'showInfo';
<<EOF>> return 'EOF' ;
. return 'TXT' ;
/lex
%start start
%% /* language grammar */
start
// %{ : info document 'EOF' { return yy; } }
: info document 'EOF' { return yy; }
;
document
: /* empty */
| document line
;
line
: statement { }
| 'NL'
;
statement
: showInfo { yy.setInfo(true); }
;
%%

View File

@ -0,0 +1,33 @@
// @ts-ignore: TODO Fix ts errors
import { detector } from './exampleDetector';
const scriptElement = document.currentScript as HTMLScriptElement;
const path = scriptElement.src;
const lastSlash = path.lastIndexOf('/');
const baseFolder = lastSlash < 0 ? path : path.substring(0, lastSlash + 1);
if (typeof document !== 'undefined') {
if (window.mermaid && typeof window.mermaid.detectors === 'object') {
window.mermaid.detectors.push({ id: 'example-diagram', detector });
} else {
window.mermaid = {};
window.mermaid.detectors = [{ id: 'example-diagram', detector }];
}
/*
* Wait for document loaded before starting the execution.
*/
window.addEventListener(
'load',
() => {
if (window.mermaid && typeof window.mermaid.detectors === 'object') {
window.mermaid.detectors.push({
id: 'example-diagram',
detector,
path: baseFolder,
});
}
},
false
);
}

View File

@ -0,0 +1,7 @@
export {};
declare global {
interface Window {
mermaid: any; // 👈️ turn off type checking
}
}

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["./src/**/*.ts"],
"typeRoots": ["./src/types"]
}

View File

@ -0,0 +1,75 @@
{
"name": "@mermaid-js/mermaid-mindmap",
"version": "9.2.0-rc2",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "dist/mermaid-mindmap.core.mjs",
"module": "dist/mermaid-mindmap.core.mjs",
"type": "module",
"exports": {
".": {
"require": "./dist/mermaid-mindmap.min.js",
"import": "./dist/mermaid-mindmap.core.mjs"
},
"./*": "./*"
},
"keywords": [
"diagram",
"markdown",
"mindmap",
"mermaid"
],
"scripts": {
"clean": "rimraf dist",
"build:types": "tsc -p ./tsconfig.json --emitDeclarationOnly",
"build:watch": "yarn build:code --watch",
"build:esbuild": "concurrently \"yarn build:code\" \"yarn build:types\"",
"build": "yarn clean; yarn build:esbuild",
"dev": "node .esbuild/serve.cjs",
"release": "yarn build",
"lint": "eslint --cache --ignore-path .gitignore . && yarn lint:jison && prettier --check .",
"lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .",
"lint:jison": "ts-node-esm src/jison/lint.mts",
"ci": "vitest run",
"test": "yarn lint && vitest run",
"test:watch": "vitest --coverage --watch",
"todo-prepublishOnly": "yarn build && yarn test",
"todo-prepare": "concurrently \"husky install ../../.husky\" \"yarn build\"",
"todo-pre-commit": "lint-staged"
},
"repository": {
"type": "git",
"url": "https://github.com/mermaid-js/mermaid"
},
"author": "Knut Sveidqvist",
"license": "MIT",
"standard": {
"ignore": [
"**/parser/*.js",
"dist/**/*.js",
"cypress/**/*.js"
],
"globals": [
"page"
]
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"d3": "^7.0.0",
"mermaid": "workspace:*",
"non-layered-tidy-tree-layout": "^2.0.2"
},
"devDependencies": {
"concurrently": "^7.4.0",
"rimraf": "^3.0.2"
},
"resolutions": {
"d3": "^7.0.0"
},
"files": [
"dist"
],
"sideEffects": [
"**/*.css",
"**/*.scss"
]
}

View File

@ -0,0 +1,23 @@
// @ts-ignore: TODO Fix ts errors
import mindmapParser from './parser/mindmap';
import * as mindmapDb from './mindmapDb';
import mindmapRenderer from './mindmapRenderer';
import mindmapStyles from './styles';
import { injectUtils } from './mermaidUtils';
// const getBaseFolder = (path: string) => {
// const parts = path.split('/');
// parts.pop();
// return parts.join('/');
// };
window.mermaid.connectDiagram(
'mindmap',
{
db: mindmapDb,
renderer: mindmapRenderer,
parser: mindmapParser,
styles: mindmapStyles,
},
injectUtils
);

View File

@ -0,0 +1,81 @@
const warning = (s: string) => {
// Todo remove debug code
console.error('Log function was called before initialization', s); // eslint-disable-line
};
export type LogLevel = 'trace' | 'debug' | 'info' | 'warn' | 'error' | 'fatal';
export const LEVELS: Record<LogLevel, number> = {
trace: 0,
debug: 1,
info: 2,
warn: 3,
error: 4,
fatal: 5,
};
export const log: Record<keyof typeof LEVELS, typeof console.log> = {
trace: warning,
debug: warning,
info: warning,
warn: warning,
error: warning,
fatal: warning,
};
export let setLogLevel: (level: keyof typeof LEVELS | number | string) => void;
export let getConfig: () => object;
export let sanitizeText: (str: string) => string;
// eslint-disable @typescript-eslint/no-explicit-any
export let setupGraphViewbox: (
graph: any,
svgElem: any,
padding: any,
useMaxWidth: boolean
) => void;
export const injectUtils = (
_log: Record<keyof typeof LEVELS, typeof console.log>,
_setLogLevel: any,
_getConfig: any,
_sanitizeText: any,
_setupGraphViewbox: any
) => {
_log.info('Mermaid utils injected');
log.trace = _log.trace;
log.debug = _log.debug;
log.info = _log.info;
log.warn = _log.warn;
log.error = _log.error;
log.fatal = _log.fatal;
setLogLevel = _setLogLevel;
getConfig = _getConfig;
sanitizeText = _sanitizeText;
setupGraphViewbox = _setupGraphViewbox;
};
/*
const warning = (..._args: any[]) => {
console.error('Log function was called before initialization');
};
export let log = {
trace: warning,
debug: warning,
info: warning,
warn: warning,
error: warning,
fatal: warning,
};
export let setLogLevel;
export let getConfig;
export let sanitizeText;
export let setupGraphViewbox;
export const injectUtils = (_log, _setLogLevel, _getConfig, _sanitizeText, _setupGraphViewbox) => {
log = _log;
setLogLevel = _setLogLevel;
getConfig = _getConfig;
sanitizeText = _sanitizeText;
setupGraphViewbox = _setupGraphViewbox;
};
*/

View File

@ -1,6 +1,16 @@
import { parser as mindmap } from './parser/mindmap';
import * as mindmapDB from './mindmapDb';
import { setLogLevel } from '../../diagram-api/diagramAPI';
import { injectUtils } from './mermaidUtils';
// Todo fix utils functions for tests
import {
log,
setLogLevel,
getConfig,
sanitizeText,
setupGraphViewBox,
} from '../../mermaid/src/diagram-api/diagramAPI';
injectUtils(log, setLogLevel, getConfig, sanitizeText, setupGraphViewBox);
describe('when parsing a mindmap ', function () {
beforeEach(function () {

View File

@ -1,5 +1,5 @@
/** Created by knut on 15-01-14. */
import { sanitizeText, getConfig, log as _log } from '../../diagram-api/diagramAPI';
import { sanitizeText, getConfig, log } from './mermaidUtils';
let nodes = [];
let cnt = 0;
@ -131,8 +131,19 @@ export const type2Str = (type) => {
return 'no-border';
}
};
export let parseError; // = (str, hash)
// => {
// const error = { str, hash };
// throw error;
// };
export const setErrorHandler = (handler) => {
parseError = handler;
};
// Expose logger to grammar
export const log = _log;
export const getLogger = () => log;
export const getNodeById = (id) => nodes[id];
export const getElementById = (id) => elements[id];
// export default {

View File

@ -0,0 +1,3 @@
export const mindmapDetector = (txt: string) => {
return txt.match(/^\s*mindmap/) !== null;
};

View File

@ -1,6 +1,6 @@
/** Created by knut on 14-12-11. */
import { select } from 'd3';
import { log, getConfig, setupGraphViewbox } from '../../diagram-api/diagramAPI';
import { log, getConfig, setupGraphViewbox } from './mermaidUtils';
import svgDraw from './svgDraw';
import { BoundingBox, Layout } from 'non-layered-tidy-tree-layout';
import clone from 'fast-clone';
@ -215,51 +215,53 @@ function positionNodes(node, conf) {
* @param {any} version
* @param diagObj
*/
export const draw = (text, id, version, diagObj) => {
const conf = getConfig();
try {
log.debug('Renering info diagram\n' + text);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sanbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
// Parse the graph definition
// This is done only for throwing the error if the text is not valid.
diagObj.db.clear();
// Parse the graph definition
diagObj.parser.parse(text);
const svg = root.select('#' + id);
log.debug('Renering info diagram\n' + text);
svg.append('g');
const mm = diagObj.db.getMindmap();
// Draw the graph and start with drawing the nodes without proper position
// this gives us the size of the nodes and we can set the positions later
const edgesElem = svg.append('g');
edgesElem.attr('class', 'mindmap-edges');
const nodesElem = svg.append('g');
nodesElem.attr('class', 'mindmap-nodes');
drawNodes(nodesElem, mm, -1, conf);
// Next step is to layout the mindmap, giving each node a position
const positionedMindmap = layoutMindmap(mm, conf);
// After this we can draw, first the edges and the then nodes with the correct position
drawEdges(edgesElem, positionedMindmap, null, 0, -1, conf);
positionNodes(positionedMindmap, conf);
// Setup the view box and size of the svg element
setupGraphViewbox(undefined, svg, conf.mindmap.padding, conf.mindmap.useMaxWidth);
} catch (e) {
log.error('Error while rendering info diagram');
log.error(e.message);
const securityLevel = getConfig().securityLevel;
// Handle root and Document for when rendering in sanbox mode
let sandboxElement;
if (securityLevel === 'sandbox') {
sandboxElement = select('#i' + id);
}
const root =
securityLevel === 'sandbox'
? select(sandboxElement.nodes()[0].contentDocument.body)
: select('body');
// Parse the graph definition
const svg = root.select('#' + id);
svg.append('g');
const mm = diagObj.db.getMindmap();
// Draw the graph and start with drawing the nodes without proper position
// this gives us the size of the nodes and we can set the positions later
const edgesElem = svg.append('g');
edgesElem.attr('class', 'mindmap-edges');
const nodesElem = svg.append('g');
nodesElem.attr('class', 'mindmap-nodes');
drawNodes(nodesElem, mm, -1, conf);
// Next step is to layout the mindmap, giving each node a position
const positionedMindmap = layoutMindmap(mm, conf);
// After this we can draw, first the edges and the then nodes with the correct position
drawEdges(edgesElem, positionedMindmap, null, 0, -1, conf);
positionNodes(positionedMindmap, conf);
// Setup the view box and size of the svg element
setupGraphViewbox(undefined, svg, conf.mindmap.padding, conf.mindmap.useMaxWidth);
};
export default {

View File

@ -0,0 +1,108 @@
/** mermaid
* https://knsv.github.io/mermaid
* (c) 2015 Knut Sveidqvist
* MIT license.
*/
%lex
%options case-insensitive
%{
// Pre-lexer code can go here
%}
%x NODE
%x NSTR
%x ICON
%x CLASS
%%
\s*\%\%.* {yy.getLogger().trace('Found comment',yytext);}
// \%\%[^\n]*\n /* skip comments */
"mindmap" return 'MINDMAP';
":::" { this.begin('CLASS'); }
<CLASS>.+ { this.popState();return 'CLASS'; }
<CLASS>\n { this.popState();}
// [\s]*"::icon(" { this.begin('ICON'); }
"::icon(" { yy.getLogger().trace('Begin icon');this.begin('ICON'); }
[\n]+ return 'NL';
<ICON>[^\)]+ { return 'ICON'; }
<ICON>\) {yy.getLogger().trace('end icon');this.popState();}
"-)" { yy.getLogger().trace('Exploding node'); this.begin('NODE');return 'NODE_DSTART'; }
"(-" { yy.getLogger().trace('Cloud'); this.begin('NODE');return 'NODE_DSTART'; }
"))" { yy.getLogger().trace('Explosion Bang'); this.begin('NODE');return 'NODE_DSTART'; }
")" { yy.getLogger().trace('Cloud Bang'); this.begin('NODE');return 'NODE_DSTART'; }
"((" { this.begin('NODE');return 'NODE_DSTART'; }
"(" { this.begin('NODE');return 'NODE_DSTART'; }
"[" { this.begin('NODE');return 'NODE_DSTART'; }
[\s]+ return 'SPACELIST' /* skip all whitespace */ ;
// !(-\() return 'NODE_ID';
[^\(\[\n\-\)]+ return 'NODE_ID';
<<EOF>> return 'EOF';
<NODE>["] { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");}
<NSTR>[^"]+ { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";}
<NSTR>["] {this.popState();}
<NODE>[\)]\) {this.popState();yy.getLogger().trace('node end ))');return "NODE_DEND";}
<NODE>[\)] {this.popState();yy.getLogger().trace('node end )');return "NODE_DEND";}
<NODE>[\]] {this.popState();yy.getLogger().trace('node end ...',yytext);return "NODE_DEND";}
<NODE>"(-" {this.popState();yy.getLogger().trace('node end (-');return "NODE_DEND";}
<NODE>"-)" {this.popState();yy.getLogger().trace('node end (-');return "NODE_DEND";}
<NODE>"((" {this.popState();yy.getLogger().trace('node end ((');return "NODE_DEND";}
<NODE>"(" {this.popState();yy.getLogger().trace('node end ((');return "NODE_DEND";}
<NODE>[^\)\]\(]+ { yy.getLogger().trace('Long description:', yytext); return 'NODE_DESCR';}
<NODE>.+(?!\(\() { yy.getLogger().trace('Long description:', yytext); return 'NODE_DESCR';}
// [\[] return 'NODE_START';
// .+ return 'TXT' ;
/lex
%start start
%% /* language grammar */
start
// %{ : info document 'EOF' { return yy; } }
: MINDMAP document { return yy; }
| MINDMAP NL document { return yy; }
| SPACELIST MINDMAP document { return yy; }
;
stop
: NL {yy.getLogger().trace('Stop NL ');}
| EOF {yy.getLogger().trace('Stop EOF ');}
| stop NL {yy.getLogger().trace('Stop NL2 ');}
| stop EOF {yy.getLogger().trace('Stop EOF2 ');}
;
document
: document statement stop
| statement stop
;
statement
: SPACELIST node { yy.getLogger().trace('Node: ',$2.id);yy.addNode($1.length, $2.id, $2.descr, $2.type); }
| SPACELIST ICON { yy.getLogger().trace('Icon: ',$2);yy.decorateNode({icon: $2}); }
| SPACELIST CLASS { yy.decorateNode({class: $2}); }
| node { yy.getLogger().trace('Node: ',$1.id);yy.addNode(0, $1.id, $1.descr, $1.type); }
| ICON { yy.decorateNode({icon: $1}); }
| CLASS { yy.decorateNode({class: $1}); }
| SPACELIST
;
node
:nodeWithId
|nodeWithoutId
;
nodeWithoutId
: NODE_DSTART NODE_DESCR NODE_DEND
{ yy.getLogger().trace("node found ..", $1); $$ = { id: $2, descr: $2, type: yy.getType($1, $3) }; }
;
nodeWithId
: NODE_ID { $$ = { id: $1, descr: $1, type: yy.nodeType.DEFAULT }; }
| NODE_ID NODE_DSTART NODE_DESCR NODE_DEND
{ yy.getLogger().trace("node found ..", $1); $$ = { id: $1, descr: $3, type: yy.getType($2, $4) }; }
;
%%

View File

@ -0,0 +1,41 @@
// @ts-ignore: TODO Fix ts errors
import { mindmapDetector } from './mindmapDetector';
// const getBaseFolder = () => {
const scriptElement = document.currentScript as HTMLScriptElement;
const path = scriptElement.src;
const lastSlash = path.lastIndexOf('/');
const baseFolder = lastSlash < 0 ? path : path.substring(0, lastSlash + 1);
// };
// console.log('Current script', getBaseFolder(scriptElement !== null ? scriptElement.src : '')); // eslint-disable-line no-console
if (typeof document !== 'undefined') {
if (window.mermaid && typeof window.mermaid.detectors === 'object') {
window.mermaid.detectors.push({ id: 'mindmap', detector: mindmapDetector });
} else {
// console.error('window.mermaid.detectors was not found!'); // eslint-disable-line no-console
window.mermaid = {};
window.mermaid.detectors = [{ id: 'mindmap', detector: mindmapDetector }];
// console.error('Detectors now:', window.mermaid.detectors); // eslint-disable-line no-console
}
/*!
* Wait for document loaded before starting the execution.
*/
window.addEventListener(
'load',
() => {
if (window.mermaid && typeof window.mermaid.detectors === 'object') {
window.mermaid.detectors.push({
id: 'mindmap',
detector: mindmapDetector,
path: baseFolder,
});
// console.error(window.mermaid.detectors); // eslint-disable-line no-console
}
},
false
);
}

View File

@ -0,0 +1,7 @@
export {};
declare global {
interface Window {
mermaid: any; // 👈️ turn off type checking
}
}

View File

@ -0,0 +1,9 @@
{
"extends": "../../tsconfig.json",
"compilerOptions": {
"rootDir": "./src",
"outDir": "./dist"
},
"include": ["./src/**/*.ts"],
"typeRoots": ["./src/types"]
}

View File

@ -0,0 +1,141 @@
{
"name": "mermaid",
"version": "9.2.0-rc2",
"description": "Markdownish syntax for generating flowcharts, sequence diagrams, class diagrams, gantt charts and git graphs.",
"main": "./dist/mermaid.core.mjs",
"module": "./dist/mermaid.core.mjs",
"types": "./dist/mermaid.d.ts",
"type": "module",
"exports": {
".": {
"require": "./dist/mermaid.min.js",
"import": "./dist/mermaid.core.mjs",
"types": "./dist/mermaid.d.ts"
},
"./*": "./*"
},
"keywords": [
"diagram",
"markdown",
"flowchart",
"sequence diagram",
"gantt",
"class diagram",
"git graph"
],
"scripts": {
"clean": "rimraf dist",
"build:code": "node .esbuild/esbuild.cjs",
"build:types": "tsc -p ./tsconfig.json --emitDeclarationOnly",
"build:watch": "yarn build:code --watch",
"build:esbuild": "concurrently \"yarn build:code\" \"yarn build:types\"",
"build": "yarn clean; yarn build:esbuild",
"dev": "node .esbuild/serve.cjs",
"docs:build": "ts-node-esm src/docs.mts",
"docs:verify": "yarn docs:build --verify",
"todo-postbuild": "documentation build src/mermaidAPI.ts src/config.ts src/defaultConfig.ts --shallow -f md --markdown-toc false > src/docs/Setup.md && prettier --write src/docs/Setup.md",
"release": "yarn build",
"lint": "eslint --cache --ignore-path .gitignore . && yarn lint:jison && prettier --check .",
"lint:fix": "eslint --fix --ignore-path .gitignore . && prettier --write .",
"lint:jison": "ts-node-esm src/jison/lint.mts",
"cypress": "cypress run",
"cypress:open": "cypress open",
"e2e": "start-server-and-test dev http://localhost:9000/ cypress",
"ci": "vitest run",
"test": "yarn lint && vitest run",
"test:watch": "vitest --coverage --watch",
"prepublishOnly": "yarn build && yarn test",
"todo-prepare": "concurrently \"husky install\" \"yarn build\"",
"pre-commit": "lint-staged"
},
"repository": {
"type": "git",
"url": "https://github.com/mermaid-js/mermaid"
},
"author": "Knut Sveidqvist",
"license": "MIT",
"standard": {
"ignore": [
"**/parser/*.js",
"dist/**/*.js",
"cypress/**/*.js"
],
"globals": [
"page"
]
},
"dependencies": {
"@braintree/sanitize-url": "^6.0.0",
"d3": "^7.0.0",
"dagre": "^0.8.5",
"dagre-d3": "^0.6.4",
"dompurify": "2.4.0",
"fast-clone": "^1.5.13",
"graphlib": "^2.1.8",
"khroma": "^2.0.0",
"lodash": "^4.17.21",
"moment-mini": "^2.24.0",
"non-layered-tidy-tree-layout": "^2.0.2",
"stylis": "^4.1.2"
},
"devDependencies": {
"@applitools/eyes-cypress": "^3.25.7",
"@commitlint/cli": "^17.1.2",
"@commitlint/config-conventional": "^17.0.0",
"@types/d3": "^7.4.0",
"@types/dompurify": "^2.3.4",
"@types/eslint": "^8.4.6",
"@types/express": "^4.17.13",
"@types/jsdom": "^20.0.0",
"@types/lodash": "^4.14.185",
"@types/prettier": "^2.7.0",
"@types/stylis": "^4.0.2",
"@typescript-eslint/eslint-plugin": "^5.37.0",
"@typescript-eslint/parser": "^5.37.0",
"@vitest/coverage-c8": "^0.23.2",
"@vitest/ui": "^0.23.2",
"concurrently": "^7.4.0",
"coveralls": "^3.1.1",
"cypress": "^10.0.0",
"cypress-image-snapshot": "^4.0.1",
"documentation": "13.2.0",
"esbuild": "^0.15.8",
"eslint": "^8.23.1",
"eslint-config-prettier": "^8.5.0",
"eslint-plugin-cypress": "^2.12.1",
"eslint-plugin-html": "^7.1.0",
"eslint-plugin-jest": "^27.0.4",
"eslint-plugin-jsdoc": "^39.3.6",
"eslint-plugin-json": "^3.1.0",
"eslint-plugin-markdown": "^3.0.0",
"express": "^4.18.1",
"globby": "^13.1.2",
"husky": "^8.0.0",
"identity-obj-proxy": "^3.0.0",
"jison": "^0.4.18",
"js-base64": "3.7.2",
"jsdom": "^20.0.0",
"lint-staged": "^13.0.0",
"moment": "^2.23.0",
"path-browserify": "^1.0.1",
"prettier": "^2.7.1",
"prettier-plugin-jsdoc": "^0.4.2",
"remark": "^14.0.2",
"rimraf": "^3.0.2",
"start-server-and-test": "^1.12.6",
"ts-node": "^10.9.1",
"typescript": "^4.8.3",
"unist-util-flatmap": "^1.0.0",
"vitest": "^0.23.1"
},
"resolutions": {
"d3": "^7.0.0"
},
"files": [
"dist"
],
"sideEffects": [
"**/*.css",
"**/*.scss"
]
}

View File

@ -1,7 +1,7 @@
import * as configApi from './config';
import { log } from './logger';
import { getDiagram } from './diagram-api/diagramAPI';
import { detectType } from './diagram-api/detectType';
import { getDiagram, loadDiagram } from './diagram-api/diagramAPI';
import { detectType, getPathForDiagram } from './diagram-api/detectType';
import { isDetailedError } from './utils';
export class Diagram {
type = 'graph';
@ -26,11 +26,7 @@ export class Diagram {
log.debug('Initialized diagram ' + this.type, cnf);
}
this.txt += '\n';
this.parser.parser.yy.graphType = this.type;
this.parser.parser.yy.parseError = (str: string, hash: string) => {
const error = { str, hash };
throw error;
};
this.parse(this.txt, parseError);
}
@ -71,3 +67,21 @@ export class Diagram {
}
export default Diagram;
// eslint-disable-next-line @typescript-eslint/ban-types
export const getDiagramFromText = async (txt: string, parseError?: Function) => {
const type = detectType(txt, configApi.getConfig());
try {
// Trying to find the diagram
getDiagram(type);
} catch (error) {
// Diagram not avaiable, loading it
const path = getPathForDiagram(type);
// await loadDiagram('./packages/mermaid-mindmap/dist/mermaid-mindmap.js');
await loadDiagram(path + 'mermaid-' + type + '.js');
// new diagram will try getDiagram again and if fails then it is a valid throw
}
// If either of the above worked, we have the diagram
// logic and can continue
return new Diagram(txt, parseError);
};

View File

@ -1,140 +1,140 @@
# Cluster handling
Dagre does not support edges between nodes and clusters or between clusters to other clusters. In order to remedy this shortcoming the dagre wrapper implements a few work-arounds.
In the diagram below there are two clusters and there are no edges to nodes outside the own cluster.
```mermaid
flowchart
subgraph C1
a --> b
end
subgraph C2
c
end
C1 --> C2
```
In this case the dagre-wrapper will transform the graph to the graph below.
```mermaid
flowchart
C1 --> C2
```
The new nodes C1 and C2 are a special type of nodes, clusterNodes. ClusterNodes have have the nodes in the cluster including the cluster attached in a graph object.
When rendering this diagram it it beeing rendered recursively. The diagram is rendered by the dagre-mermaid:render function which in turn will be used to render the node C1 and the node C2. The result of those renderings will be inserted as nodes in the "root" diagram. With this recursive approach it would be possible to have different layout direction for each cluster.
```
{ clusterNode: true, graph }
```
_Data for a clusterNode_
When a cluster has edges to or from some of its nodes leading outside the cluster the approach of recursive rendering can not be used as the layout of the graph needs to take responsibility for nodes outside of the cluster.
```mermaid
flowchart
subgraph C1
a
end
subgraph C2
b
end
a --> C2
```
To handle this case a special type of edge is inserted. The edge to/from the cluster is replaced with an edge to/from a node in the cluster which is tagged with toCluster/fromCluster. When rendering this edge the intersection between the edge and the border of the cluster is calculated making the edge start/stop there. In practice this renders like an an edge to/from the cluster.
In the diagram above the root diagram would be rendered with C1 whereas C2 would be rendered recursively.
Of these two approaches the top one renders better and is used when possible. When this is not possible, ie an edge is added crossing the border the non recursive approach is used.
# Graph objects and their properties
Explains the representation of various objects used to render the flow charts and what the properties mean. This ofc from the perspective of the dagre-wrapper.
## node
Sample object:
```json
{
"shape": "rect",
"labelText": "Test",
"rx": 0,
"ry": 0,
"class": "default",
"style": "",
"id": "Test",
"type": "group",
"padding": 15
}
```
This is set by the renderer of the diagram and insert the data that the wrapper neds for rendering.
| property | description |
| ---------- | ------------------------------------------------------------------------------------------------ |
| labelStyle | Css styles for the label. User for instance for styling the labels for clusters |
| shape | The shape of the node. |
| labelText | The text on the label |
| rx | The corner radius - maybe part of the shape instead? Used for rects. |
| ry | The corner radius - maybe part of the shape instead? Used for rects. |
| classes | Classes to be set for the shape. Not used |
| style | Css styles for the actual shape |
| id | id of the shape |
| type | if set to group then this node indicates _a cluster_. |
| padding | Padding. Passed from the render as this might differ between different diagrams. Maybe obsolete. |
| data | Non-generic data specific to the shape. |
# edge
arrowType sets the type of arrows to use. The following arrow types are currently supported:
arrow_cross
double_arrow_cross
arrow_point
double_arrow_point
arrow_circle
double_arrow_circle
Lets try to make these types semantic free so that diagram type semantics does not find its way in to this more generic layer.
Required edgeData for proper rendering:
| property | description |
| ---------- | -------------------------------------------------------------------- |
| id | Id of the edge |
| arrowHead | overlap between arrowHead and arrowType? |
| arrowType | overlap between arrowHead and arrowType? |
| style | |
| labelStyle | |
| label | overlap between label and labelText? |
| labelPos | |
| labelType | overlap between label and labelText? |
| thickness | Sets the thinkess of the edge. Can be \['normal', 'thick'\] |
| pattern | Sets the pattern of the edge. Can be \['solid', 'dotted', 'dashed'\] |
# Markers
Define what markers that should be included in the diagram with the insert markers function. The function takes two arguments, first the element in which the markers should be included and a list of the markers that should be added.
Ex:
insertMarkers(el, \['point', 'circle'\])
The example above adds the markers point and cross. This means that edges with the arrowTypes arrow_cross, double_arrow_cross, arrow_point and double_arrow_cross will get the corresponding markers but arrowType arrow_cross will have no impact.
Current markers:
- point - the standard arrow from flowcharts
- circle - Arrows ending with circle
- cross - arrows starting and ending with a cross
// Todo - in case of common renderer
# Common functions used by the renderer to be implemented by the Db
getDirection
getClasses
# Cluster handling
Dagre does not support edges between nodes and clusters or between clusters to other clusters. In order to remedy this shortcoming the dagre wrapper implements a few work-arounds.
In the diagram below there are two clusters and there are no edges to nodes outside the own cluster.
```mermaid
flowchart
subgraph C1
a --> b
end
subgraph C2
c
end
C1 --> C2
```
In this case the dagre-wrapper will transform the graph to the graph below.
```mermaid
flowchart
C1 --> C2
```
The new nodes C1 and C2 are a special type of nodes, clusterNodes. ClusterNodes have have the nodes in the cluster including the cluster attached in a graph object.
When rendering this diagram it it beeing rendered recursively. The diagram is rendered by the dagre-mermaid:render function which in turn will be used to render the node C1 and the node C2. The result of those renderings will be inserted as nodes in the "root" diagram. With this recursive approach it would be possible to have different layout direction for each cluster.
```
{ clusterNode: true, graph }
```
_Data for a clusterNode_
When a cluster has edges to or from some of its nodes leading outside the cluster the approach of recursive rendering can not be used as the layout of the graph needs to take responsibility for nodes outside of the cluster.
```mermaid
flowchart
subgraph C1
a
end
subgraph C2
b
end
a --> C2
```
To handle this case a special type of edge is inserted. The edge to/from the cluster is replaced with an edge to/from a node in the cluster which is tagged with toCluster/fromCluster. When rendering this edge the intersection between the edge and the border of the cluster is calculated making the edge start/stop there. In practice this renders like an an edge to/from the cluster.
In the diagram above the root diagram would be rendered with C1 whereas C2 would be rendered recursively.
Of these two approaches the top one renders better and is used when possible. When this is not possible, ie an edge is added crossing the border the non recursive approach is used.
# Graph objects and their properties
Explains the representation of various objects used to render the flow charts and what the properties mean. This ofc from the perspective of the dagre-wrapper.
## node
Sample object:
```json
{
"shape": "rect",
"labelText": "Test",
"rx": 0,
"ry": 0,
"class": "default",
"style": "",
"id": "Test",
"type": "group",
"padding": 15
}
```
This is set by the renderer of the diagram and insert the data that the wrapper neds for rendering.
| property | description |
| ---------- | ------------------------------------------------------------------------------------------------ |
| labelStyle | Css styles for the label. User for instance for styling the labels for clusters |
| shape | The shape of the node. |
| labelText | The text on the label |
| rx | The corner radius - maybe part of the shape instead? Used for rects. |
| ry | The corner radius - maybe part of the shape instead? Used for rects. |
| classes | Classes to be set for the shape. Not used |
| style | Css styles for the actual shape |
| id | id of the shape |
| type | if set to group then this node indicates _a cluster_. |
| padding | Padding. Passed from the render as this might differ between different diagrams. Maybe obsolete. |
| data | Non-generic data specific to the shape. |
# edge
arrowType sets the type of arrows to use. The following arrow types are currently supported:
arrow_cross
double_arrow_cross
arrow_point
double_arrow_point
arrow_circle
double_arrow_circle
Lets try to make these types semantic free so that diagram type semantics does not find its way in to this more generic layer.
Required edgeData for proper rendering:
| property | description |
| ---------- | -------------------------------------------------------------------- |
| id | Id of the edge |
| arrowHead | overlap between arrowHead and arrowType? |
| arrowType | overlap between arrowHead and arrowType? |
| style | |
| labelStyle | |
| label | overlap between label and labelText? |
| labelPos | |
| labelType | overlap between label and labelText? |
| thickness | Sets the thinkess of the edge. Can be \['normal', 'thick'\] |
| pattern | Sets the pattern of the edge. Can be \['solid', 'dotted', 'dashed'\] |
# Markers
Define what markers that should be included in the diagram with the insert markers function. The function takes two arguments, first the element in which the markers should be included and a list of the markers that should be added.
Ex:
insertMarkers(el, \['point', 'circle'\])
The example above adds the markers point and cross. This means that edges with the arrowTypes arrow_cross, double_arrow_cross, arrow_point and double_arrow_cross will get the corresponding markers but arrowType arrow_cross will have no impact.
Current markers:
- point - the standard arrow from flowcharts
- circle - Arrows ending with circle
- cross - arrows starting and ending with a cross
// Todo - in case of common renderer
# Common functions used by the renderer to be implemented by the Db
getDirection
getClasses

View File

@ -1,32 +1,32 @@
const intersectRect = (node, point) => {
var x = node.x;
var y = node.y;
// Rectangle intersection algorithm from:
// https://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
var dx = point.x - x;
var dy = point.y - y;
var w = node.width / 2;
var h = node.height / 2;
var sx, sy;
if (Math.abs(dy) * w > Math.abs(dx) * h) {
// Intersection is top or bottom of rect.
if (dy < 0) {
h = -h;
}
sx = dy === 0 ? 0 : (h * dx) / dy;
sy = h;
} else {
// Intersection is left or right of rect.
if (dx < 0) {
w = -w;
}
sx = w;
sy = dx === 0 ? 0 : (w * dy) / dx;
}
return { x: x + sx, y: y + sy };
};
export default intersectRect;
const intersectRect = (node, point) => {
var x = node.x;
var y = node.y;
// Rectangle intersection algorithm from:
// https://math.stackexchange.com/questions/108113/find-edge-between-two-boxes
var dx = point.x - x;
var dy = point.y - y;
var w = node.width / 2;
var h = node.height / 2;
var sx, sy;
if (Math.abs(dy) * w > Math.abs(dx) * h) {
// Intersection is top or bottom of rect.
if (dy < 0) {
h = -h;
}
sx = dy === 0 ? 0 : (h * dx) / dy;
sy = h;
} else {
// Intersection is left or right of rect.
if (dx < 0) {
w = -w;
}
sx = w;
sy = dx === 0 ? 0 : (w * dy) / dx;
}
return { x: x + sx, y: y + sy };
};
export default intersectRect;

View File

@ -39,8 +39,6 @@ const edgeInCluster = (edge, clusterId) => {
log.debug('Tilt, ', clusterId, ',not in decendants');
return false;
}
log.info('Here ');
if (decendants[clusterId].indexOf(edge.v) >= 0) return true;
if (isDecendant(edge.v, clusterId)) return true;
if (isDecendant(edge.w, clusterId)) return true;

View File

@ -1,12 +1,13 @@
import { MermaidConfig } from '../config.type';
export type DiagramDetector = (text: string, config?: MermaidConfig) => boolean;
export type DetectorRecord = { detector: DiagramDetector; path: string };
const directive =
/[%]{2}[{]\s*(?:(?:(\w+)\s*:|(\w+))\s*(?:(?:(\w+))|((?:(?![}][%]{2}).|\r?\n)*))?\s*)(?:[}][%]{2})?/gi;
const anyComment = /\s*%%.*\n/gm;
const detectors: Record<string, DiagramDetector> = {};
const detectors: Record<string, DetectorRecord> = {};
/**
* @function detectType Detects the type of the graph text. Takes into consideration the possible
@ -34,8 +35,10 @@ const detectors: Record<string, DiagramDetector> = {};
export const detectType = function (text: string, config?: MermaidConfig): string {
text = text.replace(directive, '').replace(anyComment, '\n');
for (const [key, detector] of Object.entries(detectors)) {
if (detector(text, config)) {
// console.log(detectors);
for (const [key, detectorRecord] of Object.entries(detectors)) {
if (detectorRecord.detector(text, config)) {
return key;
}
}
@ -44,6 +47,13 @@ export const detectType = function (text: string, config?: MermaidConfig): strin
return 'flowchart';
};
export const addDetector = (key: string, detector: DiagramDetector) => {
detectors[key] = detector;
export const addDetector = (key: string, detector: DiagramDetector, path: string) => {
detectors[key] = { detector, path };
};
export const getPathForDiagram = (id: string) => {
const detectorRecord = detectors[id];
if (detectorRecord) {
return detectorRecord.path;
}
};

View File

@ -1,11 +1,16 @@
import { registerDiagram } from './diagramAPI';
import {
registerDiagram,
registerDetector,
DiagramDefinition,
DiagramDetector,
} from './diagramAPI';
// @ts-ignore: TODO Fix ts errors
import mindmapParser from '../diagrams/mindmap/parser/mindmap';
import * as mindmapDb from '../diagrams/mindmap/mindmapDb';
import { mindmapDetector } from '../diagrams/mindmap/mindmapDetector';
import mindmapRenderer from '../diagrams/mindmap/mindmapRenderer';
import mindmapStyles from '../diagrams/mindmap/styles';
// // @ts-ignore: TODO Fix ts errors
// import mindmapParser from '../diagrams/mindmap/parser/mindmap';
// import * as mindmapDb from '../diagrams/mindmap/mindmapDb';
// import { mindmapDetector } from '../diagrams/mindmap/mindmapDetector';
// import mindmapRenderer from '../diagrams/mindmap/mindmapRenderer';
// import mindmapStyles from '../diagrams/mindmap/styles';
// @ts-ignore: TODO Fix ts errors
import gitGraphParser from '../diagrams/git/parser/gitGraph';
@ -96,13 +101,22 @@ import { journeyDetector } from '../diagrams/user-journey/journeyDetector';
import journeyDb from '../diagrams/user-journey/journeyDb';
import journeyRenderer from '../diagrams/user-journey/journeyRenderer';
import journeyStyles from '../diagrams/user-journey/styles';
import { getConfig, setConfig } from '../config';
import { setConfig } from '../config';
import errorRenderer from '../diagrams/error/errorRenderer';
import errorStyles from '../diagrams/error/styles';
const registerDiagramAndDetector = (
id: string,
diagram: DiagramDefinition,
detector: DiagramDetector
) => {
registerDiagram(id, diagram);
registerDetector(id, detector, '');
};
export const addDiagrams = () => {
registerDiagram(
registerDiagramAndDetector(
'error',
// Special diagram with error messages but setup as a regular diagram
{
@ -125,7 +139,8 @@ export const addDiagrams = () => {
},
(text) => text.toLowerCase().trim() === 'error'
);
registerDiagram(
registerDiagramAndDetector(
'c4',
{
parser: c4Parser,
@ -138,7 +153,7 @@ export const addDiagrams = () => {
},
c4Detector
);
registerDiagram(
registerDiagramAndDetector(
'class',
{
parser: classParser,
@ -155,7 +170,7 @@ export const addDiagrams = () => {
},
classDetector
);
registerDiagram(
registerDiagramAndDetector(
'classDiagram',
{
parser: classParser,
@ -172,7 +187,7 @@ export const addDiagrams = () => {
},
classDetectorV2
);
registerDiagram(
registerDiagramAndDetector(
'er',
{
parser: erParser,
@ -182,7 +197,7 @@ export const addDiagrams = () => {
},
erDetector
);
registerDiagram(
registerDiagramAndDetector(
'gantt',
{
parser: ganttParser,
@ -192,7 +207,7 @@ export const addDiagrams = () => {
},
ganttDetector
);
registerDiagram(
registerDiagramAndDetector(
'info',
{
parser: infoParser,
@ -202,7 +217,7 @@ export const addDiagrams = () => {
},
infoDetector
);
registerDiagram(
registerDiagramAndDetector(
'pie',
{
parser: pieParser,
@ -212,7 +227,7 @@ export const addDiagrams = () => {
},
pieDetector
);
registerDiagram(
registerDiagramAndDetector(
'requirement',
{
parser: requirementParser,
@ -222,7 +237,7 @@ export const addDiagrams = () => {
},
requirementDetector
);
registerDiagram(
registerDiagramAndDetector(
'sequence',
{
parser: sequenceParser,
@ -245,7 +260,7 @@ export const addDiagrams = () => {
},
sequenceDetector
);
registerDiagram(
registerDiagramAndDetector(
'state',
{
parser: stateParser,
@ -262,7 +277,7 @@ export const addDiagrams = () => {
},
stateDetector
);
registerDiagram(
registerDiagramAndDetector(
'stateDiagram',
{
parser: stateParser,
@ -279,7 +294,7 @@ export const addDiagrams = () => {
},
stateDetectorV2
);
registerDiagram(
registerDiagramAndDetector(
'journey',
{
parser: journeyParser,
@ -294,7 +309,7 @@ export const addDiagrams = () => {
journeyDetector
);
registerDiagram(
registerDiagramAndDetector(
'flowchart',
{
parser: flowParser,
@ -314,7 +329,7 @@ export const addDiagrams = () => {
},
flowDetector
);
registerDiagram(
registerDiagramAndDetector(
'flowchart-v2',
{
parser: flowParser,
@ -335,14 +350,14 @@ export const addDiagrams = () => {
},
flowDetectorV2
);
registerDiagram(
registerDiagramAndDetector(
'gitGraph',
{ parser: gitGraphParser, db: gitGraphDb, renderer: gitGraphRenderer, styles: gitGraphStyles },
gitGraphDetector
);
registerDiagram(
'mindmap',
{ parser: mindmapParser, db: mindmapDb, renderer: mindmapRenderer, styles: mindmapStyles },
mindmapDetector
);
// registerDiagram(
// 'mindmap',
// { parser: mindmapParser, db: mindmapDb, renderer: mindmapRenderer, styles: mindmapStyles },
// mindmapDetector
// );
};

View File

@ -1,5 +1,5 @@
import { detectType } from './detectType';
import { getDiagram, registerDiagram } from './diagramAPI';
import { detectType, DiagramDetector } from './detectType';
import { getDiagram, registerDiagram, registerDetector } from './diagramAPI';
import { addDiagrams } from './diagram-orchestration';
addDiagrams();
@ -16,6 +16,10 @@ describe('DiagramAPI', () => {
it('should handle diagram registrations', () => {
expect(() => getDiagram('loki')).toThrow();
expect(() => detectType('loki diagram')).not.toThrow(); // TODO: #3391
const detector: DiagramDetector = (str: string) => {
return str.match('loki') !== null;
};
registerDetector('loki', detector, '');
registerDiagram(
'loki',
{

View File

@ -27,18 +27,32 @@ export interface DiagramDefinition {
}
const diagrams: Record<string, DiagramDefinition> = {};
const connectCallbacks: Record<string, any> = {}; // TODO fix, eslint-disable-line @typescript-eslint/no-explicit-any
export interface Detectors {
[key: string]: DiagramDetector;
}
export const registerDetector = (id: string, detector: DiagramDetector, path: string) => {
addDetector(id, detector, path);
};
export const registerDiagram = (
id: string,
diagram: DiagramDefinition,
detector: DiagramDetector
callback?: (
_log: any,
_setLogLevel: any,
_getConfig: any,
_sanitizeText: any,
_setupGraphViewbox: any
) => void
) => {
if (diagrams[id]) {
log.warn(`Diagram ${id} already registered.`);
}
diagrams[id] = diagram;
addDetector(id, detector);
addStylesForDiagram(id, diagram.styles);
connectCallbacks[id] = callback;
};
export const getDiagram = (name: string): DiagramDefinition => {
@ -47,3 +61,19 @@ export const getDiagram = (name: string): DiagramDefinition => {
}
throw new Error(`Diagram ${name} not found.`);
};
/**
*
* @param sScriptSrc
*/
export const loadDiagram = (sScriptSrc: string) =>
new Promise((resolve) => {
const oHead = document.getElementsByTagName('HEAD')[0];
const oScript = document.createElement('script');
oScript.type = 'text/javascript';
oScript.src = sScriptSrc;
oHead.appendChild(oScript);
oScript.onload = () => {
resolve(true);
};
});

Some files were not shown because too many files have changed in this diff Show More