mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-09-02 23:26:44 +02:00
Merge branch 'develop' into origin/3258_Flowchart_nodeSpacing_Subgraph
This commit is contained in:
@@ -1,4 +1,4 @@
|
|||||||
#!/bin/sh
|
#!/bin/sh
|
||||||
. "$(dirname "$0")/_/husky.sh"
|
. "$(dirname "$0")/_/husky.sh"
|
||||||
|
|
||||||
NODE_OPTIONS=--max_old_space_size=8192 pnpm run pre-commit
|
NODE_OPTIONS="--max_old_space_size=8192" pnpm run pre-commit
|
||||||
|
@@ -926,6 +926,18 @@ end
|
|||||||
);
|
);
|
||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
it('should not auto wrap when markdownAutoWrap is false', () => {
|
||||||
|
imgSnapshotTest(
|
||||||
|
`flowchart TD
|
||||||
|
angular_velocity["\`**angular_velocity**
|
||||||
|
*angular_displacement / duration*
|
||||||
|
[rad/s, 1/s]
|
||||||
|
{vector}\`"]
|
||||||
|
frequency["frequency\n(1 / period_duration)\n[Hz, 1/s]"]`,
|
||||||
|
{ markdownAutoWrap: false }
|
||||||
|
);
|
||||||
|
});
|
||||||
});
|
});
|
||||||
describe('Subgraph title margins', () => {
|
describe('Subgraph title margins', () => {
|
||||||
it('Should render subgraphs with title margins set (LR)', () => {
|
it('Should render subgraphs with title margins set (LR)', () => {
|
||||||
|
@@ -50,7 +50,7 @@ Pushes in a directive to the configuration
|
|||||||
| --------- | ------------------------- | ----------- | ------------------------------ |
|
| --------- | ------------------------- | ----------- | ------------------------------ |
|
||||||
| getConfig | Obtains the currentConfig | Get Request | Any Values from current Config |
|
| getConfig | Obtains the currentConfig | Get Request | Any Values from current Config |
|
||||||
|
|
||||||
**Notes**: Returns **any** the currentConfig
|
**Notes**: Avoid calling this function repeatedly. Instead, store the result in a variable and use it, and pass it down to function calls.
|
||||||
|
|
||||||
#### Returns
|
#### Returns
|
||||||
|
|
||||||
|
@@ -852,6 +852,16 @@ Formatting:
|
|||||||
|
|
||||||
This feature is applicable to node labels, edge labels, and subgraph labels.
|
This feature is applicable to node labels, edge labels, and subgraph labels.
|
||||||
|
|
||||||
|
The auto wrapping can be disabled by using
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
markdownAutoWrap: false
|
||||||
|
---
|
||||||
|
graph LR
|
||||||
|
```
|
||||||
|
|
||||||
## Interaction
|
## Interaction
|
||||||
|
|
||||||
It is possible to bind a click event to a node, the click can lead to either a javascript callback or to a link which will be opened in a new browser tab.
|
It is possible to bind a click event to a node, the click can lead to either a javascript callback or to a link which will be opened in a new browser tab.
|
||||||
|
@@ -124,7 +124,7 @@ export const setConfig = (conf: MermaidConfig): MermaidConfig => {
|
|||||||
* | --------- | ------------------------- | ----------- | ------------------------------ |
|
* | --------- | ------------------------- | ----------- | ------------------------------ |
|
||||||
* | getConfig | Obtains the currentConfig | Get Request | Any Values from current Config |
|
* | getConfig | Obtains the currentConfig | Get Request | Any Values from current Config |
|
||||||
*
|
*
|
||||||
* **Notes**: Returns **any** the currentConfig
|
* **Notes**: Avoid calling this function repeatedly. Instead, store the result in a variable and use it, and pass it down to function calls.
|
||||||
*
|
*
|
||||||
* @returns The currentConfig
|
* @returns The currentConfig
|
||||||
*/
|
*/
|
||||||
|
@@ -159,6 +159,7 @@ export interface MermaidConfig {
|
|||||||
dompurifyConfig?: DOMPurifyConfiguration;
|
dompurifyConfig?: DOMPurifyConfiguration;
|
||||||
wrap?: boolean;
|
wrap?: boolean;
|
||||||
fontSize?: number;
|
fontSize?: number;
|
||||||
|
markdownAutoWrap?: boolean;
|
||||||
/**
|
/**
|
||||||
* Suppresses inserting 'Syntax error' diagram in the DOM.
|
* Suppresses inserting 'Syntax error' diagram in the DOM.
|
||||||
* This is useful when you want to control how to handle syntax errors in your application.
|
* This is useful when you want to control how to handle syntax errors in your application.
|
||||||
|
@@ -30,7 +30,7 @@ const rect = (parent, node) => {
|
|||||||
// .appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
|
// .appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
|
||||||
const text =
|
const text =
|
||||||
node.labelType === 'markdown'
|
node.labelType === 'markdown'
|
||||||
? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels })
|
? createText(label, node.labelText, { style: node.labelStyle, useHtmlLabels }, siteConfig)
|
||||||
: label.node().appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
|
: label.node().appendChild(createLabel(node.labelText, node.labelStyle, undefined, true));
|
||||||
|
|
||||||
// Get the size of the label
|
// Get the size of the label
|
||||||
|
@@ -18,15 +18,21 @@ export const clear = () => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const insertEdgeLabel = (elem, edge) => {
|
export const insertEdgeLabel = (elem, edge) => {
|
||||||
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
|
const config = getConfig();
|
||||||
|
const useHtmlLabels = evaluate(config.flowchart.htmlLabels);
|
||||||
// Create the actual text element
|
// Create the actual text element
|
||||||
const labelElement =
|
const labelElement =
|
||||||
edge.labelType === 'markdown'
|
edge.labelType === 'markdown'
|
||||||
? createText(elem, edge.label, {
|
? createText(
|
||||||
style: edge.labelStyle,
|
elem,
|
||||||
useHtmlLabels,
|
edge.label,
|
||||||
addSvgBackground: true,
|
{
|
||||||
})
|
style: edge.labelStyle,
|
||||||
|
useHtmlLabels,
|
||||||
|
addSvgBackground: true,
|
||||||
|
},
|
||||||
|
config
|
||||||
|
)
|
||||||
: createLabel(edge.label, edge.labelStyle);
|
: createLabel(edge.label, edge.labelStyle);
|
||||||
|
|
||||||
// Create outer g, edgeLabel, this will be positioned after graph layout
|
// Create outer g, edgeLabel, this will be positioned after graph layout
|
||||||
|
@@ -6,8 +6,9 @@ import { evaluate, sanitizeText } from '../../diagrams/common/common.js';
|
|||||||
import { decodeEntities } from '../../utils.js';
|
import { decodeEntities } from '../../utils.js';
|
||||||
|
|
||||||
export const labelHelper = async (parent, node, _classes, isNode) => {
|
export const labelHelper = async (parent, node, _classes, isNode) => {
|
||||||
|
const config = getConfig();
|
||||||
let classes;
|
let classes;
|
||||||
const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig().flowchart.htmlLabels);
|
const useHtmlLabels = node.useHtmlLabels || evaluate(config.flowchart.htmlLabels);
|
||||||
if (!_classes) {
|
if (!_classes) {
|
||||||
classes = 'node default';
|
classes = 'node default';
|
||||||
} else {
|
} else {
|
||||||
@@ -35,26 +36,26 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
|
|||||||
let text;
|
let text;
|
||||||
if (node.labelType === 'markdown') {
|
if (node.labelType === 'markdown') {
|
||||||
// text = textNode;
|
// text = textNode;
|
||||||
text = createText(label, sanitizeText(decodeEntities(labelText), getConfig()), {
|
text = createText(
|
||||||
useHtmlLabels,
|
label,
|
||||||
width: node.width || getConfig().flowchart.wrappingWidth,
|
sanitizeText(decodeEntities(labelText), config),
|
||||||
classes: 'markdown-node-label',
|
{
|
||||||
});
|
useHtmlLabels,
|
||||||
|
width: node.width || config.flowchart.wrappingWidth,
|
||||||
|
classes: 'markdown-node-label',
|
||||||
|
},
|
||||||
|
config
|
||||||
|
);
|
||||||
} else {
|
} else {
|
||||||
text = textNode.appendChild(
|
text = textNode.appendChild(
|
||||||
createLabel(
|
createLabel(sanitizeText(decodeEntities(labelText), config), node.labelStyle, false, isNode)
|
||||||
sanitizeText(decodeEntities(labelText), getConfig()),
|
|
||||||
node.labelStyle,
|
|
||||||
false,
|
|
||||||
isNode
|
|
||||||
)
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
// Get the size of the label
|
// Get the size of the label
|
||||||
let bbox = text.getBBox();
|
let bbox = text.getBBox();
|
||||||
const halfPadding = node.padding / 2;
|
const halfPadding = node.padding / 2;
|
||||||
|
|
||||||
if (evaluate(getConfig().flowchart.htmlLabels)) {
|
if (evaluate(config.flowchart.htmlLabels)) {
|
||||||
const div = text.children[0];
|
const div = text.children[0];
|
||||||
const dv = select(text);
|
const dv = select(text);
|
||||||
|
|
||||||
@@ -76,8 +77,8 @@ export const labelHelper = async (parent, node, _classes, isNode) => {
|
|||||||
|
|
||||||
if (noImgText) {
|
if (noImgText) {
|
||||||
// default size if no text
|
// default size if no text
|
||||||
const bodyFontSize = getConfig().fontSize
|
const bodyFontSize = config.fontSize
|
||||||
? getConfig().fontSize
|
? config.fontSize
|
||||||
: window.getComputedStyle(document.body).fontSize;
|
: window.getComputedStyle(document.body).fontSize;
|
||||||
const enlargingFactor = 5;
|
const enlargingFactor = 5;
|
||||||
const width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px';
|
const width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px';
|
||||||
|
@@ -346,11 +346,9 @@ export const renderKatex = async (text: string, config: MermaidConfig): Promise<
|
|||||||
.split(lineBreakRegex)
|
.split(lineBreakRegex)
|
||||||
.map((line) =>
|
.map((line) =>
|
||||||
hasKatex(line)
|
hasKatex(line)
|
||||||
? `
|
? `<div style="display: flex; align-items: center; justify-content: center; white-space: nowrap;">
|
||||||
<div style="display: flex; align-items: center; justify-content: center; white-space: nowrap;">
|
|
||||||
${line}
|
${line}
|
||||||
</div>
|
</div>`
|
||||||
`
|
|
||||||
: `<div>${line}</div>`
|
: `<div>${line}</div>`
|
||||||
)
|
)
|
||||||
.join('')
|
.join('')
|
||||||
|
@@ -196,11 +196,16 @@ export const drawNode = function (
|
|||||||
// Create the wrapped text element
|
// Create the wrapped text element
|
||||||
const textElem = nodeElem.append('g');
|
const textElem = nodeElem.append('g');
|
||||||
const description = node.descr.replace(/(<br\/*>)/g, '\n');
|
const description = node.descr.replace(/(<br\/*>)/g, '\n');
|
||||||
const newEl = createText(textElem, description, {
|
const newEl = createText(
|
||||||
useHtmlLabels: htmlLabels,
|
textElem,
|
||||||
width: node.width,
|
description,
|
||||||
classes: 'mindmap-node-label',
|
{
|
||||||
});
|
useHtmlLabels: htmlLabels,
|
||||||
|
width: node.width,
|
||||||
|
classes: 'mindmap-node-label',
|
||||||
|
},
|
||||||
|
conf
|
||||||
|
);
|
||||||
|
|
||||||
if (!htmlLabels) {
|
if (!htmlLabels) {
|
||||||
textElem
|
textElem
|
||||||
|
@@ -537,6 +537,16 @@ Formatting:
|
|||||||
|
|
||||||
This feature is applicable to node labels, edge labels, and subgraph labels.
|
This feature is applicable to node labels, edge labels, and subgraph labels.
|
||||||
|
|
||||||
|
The auto wrapping can be disabled by using
|
||||||
|
|
||||||
|
```
|
||||||
|
---
|
||||||
|
config:
|
||||||
|
markdownAutoWrap: false
|
||||||
|
---
|
||||||
|
graph LR
|
||||||
|
```
|
||||||
|
|
||||||
## Interaction
|
## Interaction
|
||||||
|
|
||||||
It is possible to bind a click event to a node, the click can lead to either a javascript callback or to a link which will be opened in a new browser tab.
|
It is possible to bind a click event to a node, the click can lead to either a javascript callback or to a link which will be opened in a new browser tab.
|
||||||
|
@@ -1,5 +1,6 @@
|
|||||||
/* eslint-disable @typescript-eslint/no-explicit-any */
|
/* eslint-disable @typescript-eslint/no-explicit-any */
|
||||||
// @ts-nocheck TODO: Fix types
|
// @ts-nocheck TODO: Fix types
|
||||||
|
import type { MermaidConfig } from '../config.type.js';
|
||||||
import type { Group } from '../diagram-api/types.js';
|
import type { Group } from '../diagram-api/types.js';
|
||||||
import type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js';
|
import type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js';
|
||||||
import { log } from '../logger.js';
|
import { log } from '../logger.js';
|
||||||
@@ -21,8 +22,7 @@ function addHtmlSpan(element, node, width, classes, addBackground = false) {
|
|||||||
const label = node.label;
|
const label = node.label;
|
||||||
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
|
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
|
||||||
div.html(
|
div.html(
|
||||||
`
|
`<span class="${labelClass} ${classes}" ` +
|
||||||
<span class="${labelClass} ${classes}" ` +
|
|
||||||
(node.labelStyle ? 'style="' + node.labelStyle + '"' : '') +
|
(node.labelStyle ? 'style="' + node.labelStyle + '"' : '') +
|
||||||
'>' +
|
'>' +
|
||||||
label +
|
label +
|
||||||
@@ -181,14 +181,14 @@ export const createText = (
|
|||||||
isNode = true,
|
isNode = true,
|
||||||
width = 200,
|
width = 200,
|
||||||
addSvgBackground = false,
|
addSvgBackground = false,
|
||||||
} = {}
|
} = {},
|
||||||
|
config: MermaidConfig
|
||||||
) => {
|
) => {
|
||||||
log.info('createText', text, style, isTitle, classes, useHtmlLabels, isNode, addSvgBackground);
|
log.info('createText', text, style, isTitle, classes, useHtmlLabels, isNode, addSvgBackground);
|
||||||
if (useHtmlLabels) {
|
if (useHtmlLabels) {
|
||||||
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
|
||||||
// text = text.replace(/\\n|\n/g, '<br />');
|
|
||||||
const htmlText = markdownToHTML(text);
|
const htmlText = markdownToHTML(text, config);
|
||||||
// log.info('markdownToHTML' + text, markdownToHTML(text));
|
|
||||||
const node = {
|
const node = {
|
||||||
isNode,
|
isNode,
|
||||||
label: decodeEntities(htmlText).replace(
|
label: decodeEntities(htmlText).replace(
|
||||||
@@ -200,7 +200,7 @@ export const createText = (
|
|||||||
const vertexNode = addHtmlSpan(el, node, width, classes, addSvgBackground);
|
const vertexNode = addHtmlSpan(el, node, width, classes, addSvgBackground);
|
||||||
return vertexNode;
|
return vertexNode;
|
||||||
} else {
|
} else {
|
||||||
const structuredText = markdownToLines(text);
|
const structuredText = markdownToLines(text, config);
|
||||||
const svgLabel = createFormattedText(width, el, structuredText, addSvgBackground);
|
const svgLabel = createFormattedText(width, el, structuredText, addSvgBackground);
|
||||||
return svgLabel;
|
return svgLabel;
|
||||||
}
|
}
|
||||||
|
@@ -1,3 +1,4 @@
|
|||||||
|
/* eslint-disable no-irregular-whitespace */
|
||||||
import { markdownToLines, markdownToHTML } from './handle-markdown-text.js';
|
import { markdownToLines, markdownToHTML } from './handle-markdown-text.js';
|
||||||
import { test, expect } from 'vitest';
|
import { test, expect } from 'vitest';
|
||||||
|
|
||||||
@@ -203,6 +204,31 @@ Word!`;
|
|||||||
expect(output).toEqual(expectedOutput);
|
expect(output).toEqual(expectedOutput);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('markdownToLines - No auto wrapping', () => {
|
||||||
|
expect(
|
||||||
|
markdownToLines(
|
||||||
|
`Hello, how do
|
||||||
|
you do?`,
|
||||||
|
{ markdownAutoWrap: false }
|
||||||
|
)
|
||||||
|
).toMatchInlineSnapshot(`
|
||||||
|
[
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"content": "Hello, how do",
|
||||||
|
"type": "normal",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
[
|
||||||
|
{
|
||||||
|
"content": "you do?",
|
||||||
|
"type": "normal",
|
||||||
|
},
|
||||||
|
],
|
||||||
|
]
|
||||||
|
`);
|
||||||
|
});
|
||||||
|
|
||||||
test('markdownToHTML - Basic test', () => {
|
test('markdownToHTML - Basic test', () => {
|
||||||
const input = `This is regular text
|
const input = `This is regular text
|
||||||
Here is a new line
|
Here is a new line
|
||||||
@@ -262,3 +288,13 @@ test('markdownToHTML - Unsupported formatting', () => {
|
|||||||
- l3`)
|
- l3`)
|
||||||
).toMatchInlineSnapshot('"<p>Hello</p>Unsupported markdown: list"');
|
).toMatchInlineSnapshot('"<p>Hello</p>Unsupported markdown: list"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('markdownToHTML - no auto wrapping', () => {
|
||||||
|
expect(
|
||||||
|
markdownToHTML(
|
||||||
|
`Hello, how do
|
||||||
|
you do?`,
|
||||||
|
{ markdownAutoWrap: false }
|
||||||
|
)
|
||||||
|
).toMatchInlineSnapshot('"<p>Hello, how do<br/>you do?</p>"');
|
||||||
|
});
|
||||||
|
@@ -2,24 +2,28 @@ import type { Content } from 'mdast';
|
|||||||
import { fromMarkdown } from 'mdast-util-from-markdown';
|
import { fromMarkdown } from 'mdast-util-from-markdown';
|
||||||
import { dedent } from 'ts-dedent';
|
import { dedent } from 'ts-dedent';
|
||||||
import type { MarkdownLine, MarkdownWordType } from './types.js';
|
import type { MarkdownLine, MarkdownWordType } from './types.js';
|
||||||
|
import type { MermaidConfig } from '../config.type.js';
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param markdown - markdown to process
|
* @param markdown - markdown to process
|
||||||
* @returns processed markdown
|
* @returns processed markdown
|
||||||
*/
|
*/
|
||||||
function preprocessMarkdown(markdown: string): string {
|
function preprocessMarkdown(markdown: string, { markdownAutoWrap }: MermaidConfig): string {
|
||||||
// Replace multiple newlines with a single newline
|
// Replace multiple newlines with a single newline
|
||||||
const withoutMultipleNewlines = markdown.replace(/\n{2,}/g, '\n');
|
const withoutMultipleNewlines = markdown.replace(/\n{2,}/g, '\n');
|
||||||
// Remove extra spaces at the beginning of each line
|
// Remove extra spaces at the beginning of each line
|
||||||
const withoutExtraSpaces = dedent(withoutMultipleNewlines);
|
const withoutExtraSpaces = dedent(withoutMultipleNewlines);
|
||||||
|
if (markdownAutoWrap === false) {
|
||||||
|
return withoutExtraSpaces.replace(/ /g, ' ');
|
||||||
|
}
|
||||||
return withoutExtraSpaces;
|
return withoutExtraSpaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param markdown - markdown to split into lines
|
* @param markdown - markdown to split into lines
|
||||||
*/
|
*/
|
||||||
export function markdownToLines(markdown: string): MarkdownLine[] {
|
export function markdownToLines(markdown: string, config: MermaidConfig = {}): MarkdownLine[] {
|
||||||
const preprocessedMarkdown = preprocessMarkdown(markdown);
|
const preprocessedMarkdown = preprocessMarkdown(markdown, config);
|
||||||
const { children } = fromMarkdown(preprocessedMarkdown);
|
const { children } = fromMarkdown(preprocessedMarkdown);
|
||||||
const lines: MarkdownLine[] = [[]];
|
const lines: MarkdownLine[] = [[]];
|
||||||
let currentLine = 0;
|
let currentLine = 0;
|
||||||
@@ -56,11 +60,14 @@ export function markdownToLines(markdown: string): MarkdownLine[] {
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
export function markdownToHTML(markdown: string) {
|
export function markdownToHTML(markdown: string, { markdownAutoWrap }: MermaidConfig = {}) {
|
||||||
const { children } = fromMarkdown(markdown);
|
const { children } = fromMarkdown(markdown);
|
||||||
|
|
||||||
function output(node: Content): string {
|
function output(node: Content): string {
|
||||||
if (node.type === 'text') {
|
if (node.type === 'text') {
|
||||||
|
if (markdownAutoWrap === false) {
|
||||||
|
return node.value.replace(/\n/g, '<br/>').replace(/ /g, ' ');
|
||||||
|
}
|
||||||
return node.value.replace(/\n/g, '<br/>');
|
return node.value.replace(/\n/g, '<br/>');
|
||||||
} else if (node.type === 'strong') {
|
} else if (node.type === 'strong') {
|
||||||
return `<strong>${node.children.map(output).join('')}</strong>`;
|
return `<strong>${node.children.map(output).join('')}</strong>`;
|
||||||
|
@@ -243,6 +243,9 @@ properties:
|
|||||||
fontSize:
|
fontSize:
|
||||||
type: number
|
type: number
|
||||||
default: 16
|
default: 16
|
||||||
|
markdownAutoWrap:
|
||||||
|
type: boolean
|
||||||
|
default: true
|
||||||
suppressErrorRendering:
|
suppressErrorRendering:
|
||||||
type: boolean
|
type: boolean
|
||||||
default: false
|
default: false
|
||||||
|
Reference in New Issue
Block a user