Merge branch 'develop' into origin/3258_Flowchart_nodeSpacing_Subgraph

This commit is contained in:
Sidharth Vinod
2024-03-23 17:09:50 +05:30
committed by GitHub
16 changed files with 134 additions and 45 deletions

View File

@@ -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

View File

@@ -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)', () => {

View File

@@ -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

View File

@@ -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.

View File

@@ -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
*/ */

View File

@@ -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.

View File

@@ -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

View File

@@ -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

View File

@@ -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';

View File

@@ -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('')

View File

@@ -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

View File

@@ -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.

View File

@@ -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;
} }

View File

@@ -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,&nbsp;how&nbsp;do<br/>you&nbsp;do?</p>"');
});

View File

@@ -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, '&nbsp;');
}
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, '&nbsp;');
}
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>`;

View File

@@ -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