diff --git a/.husky/pre-commit b/.husky/pre-commit index e8191928d4..ad85fc42c2 100755 --- a/.husky/pre-commit +++ b/.husky/pre-commit @@ -1,4 +1,4 @@ #!/bin/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 diff --git a/cypress/integration/rendering/flowchart-v2.spec.js b/cypress/integration/rendering/flowchart-v2.spec.js index 857d395be7..5b358e051f 100644 --- a/cypress/integration/rendering/flowchart-v2.spec.js +++ b/cypress/integration/rendering/flowchart-v2.spec.js @@ -904,6 +904,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', () => { it('Should render subgraphs with title margins set (LR)', () => { diff --git a/docs/config/setup/modules/config.md b/docs/config/setup/modules/config.md index f1de64e2df..48e6875779 100644 --- a/docs/config/setup/modules/config.md +++ b/docs/config/setup/modules/config.md @@ -50,7 +50,7 @@ Pushes in a directive to the configuration | --------- | ------------------------- | ----------- | ------------------------------ | | 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 diff --git a/docs/syntax/flowchart.md b/docs/syntax/flowchart.md index 3f4e9b04af..83676b0e41 100644 --- a/docs/syntax/flowchart.md +++ b/docs/syntax/flowchart.md @@ -852,6 +852,16 @@ Formatting: 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 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. diff --git a/packages/mermaid/src/config.ts b/packages/mermaid/src/config.ts index ede3a568df..4168c24c4f 100644 --- a/packages/mermaid/src/config.ts +++ b/packages/mermaid/src/config.ts @@ -124,7 +124,7 @@ export const setConfig = (conf: MermaidConfig): MermaidConfig => { * | --------- | ------------------------- | ----------- | ------------------------------ | * | 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 */ diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 2ff19c2d6e..79f4243151 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -159,6 +159,7 @@ export interface MermaidConfig { dompurifyConfig?: DOMPurifyConfiguration; wrap?: boolean; fontSize?: number; + markdownAutoWrap?: boolean; /** * Suppresses inserting 'Syntax error' diagram in the DOM. * This is useful when you want to control how to handle syntax errors in your application. diff --git a/packages/mermaid/src/dagre-wrapper/clusters.js b/packages/mermaid/src/dagre-wrapper/clusters.js index 9dde401878..2c77468769 100644 --- a/packages/mermaid/src/dagre-wrapper/clusters.js +++ b/packages/mermaid/src/dagre-wrapper/clusters.js @@ -30,7 +30,7 @@ const rect = (parent, node) => { // .appendChild(createLabel(node.labelText, node.labelStyle, undefined, true)); const text = 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)); // Get the size of the label diff --git a/packages/mermaid/src/dagre-wrapper/edges.js b/packages/mermaid/src/dagre-wrapper/edges.js index 6f7e4695d4..1a72328e81 100644 --- a/packages/mermaid/src/dagre-wrapper/edges.js +++ b/packages/mermaid/src/dagre-wrapper/edges.js @@ -18,15 +18,21 @@ export const clear = () => { }; 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 const labelElement = edge.labelType === 'markdown' - ? createText(elem, edge.label, { - style: edge.labelStyle, - useHtmlLabels, - addSvgBackground: true, - }) + ? createText( + elem, + edge.label, + { + style: edge.labelStyle, + useHtmlLabels, + addSvgBackground: true, + }, + config + ) : createLabel(edge.label, edge.labelStyle); // Create outer g, edgeLabel, this will be positioned after graph layout diff --git a/packages/mermaid/src/dagre-wrapper/shapes/util.js b/packages/mermaid/src/dagre-wrapper/shapes/util.js index df2c27bd5a..1d0d2d77e6 100644 --- a/packages/mermaid/src/dagre-wrapper/shapes/util.js +++ b/packages/mermaid/src/dagre-wrapper/shapes/util.js @@ -6,8 +6,9 @@ import { evaluate, sanitizeText } from '../../diagrams/common/common.js'; import { decodeEntities } from '../../utils.js'; export const labelHelper = async (parent, node, _classes, isNode) => { + const config = getConfig(); let classes; - const useHtmlLabels = node.useHtmlLabels || evaluate(getConfig().flowchart.htmlLabels); + const useHtmlLabels = node.useHtmlLabels || evaluate(config.flowchart.htmlLabels); if (!_classes) { classes = 'node default'; } else { @@ -35,26 +36,26 @@ export const labelHelper = async (parent, node, _classes, isNode) => { let text; if (node.labelType === 'markdown') { // text = textNode; - text = createText(label, sanitizeText(decodeEntities(labelText), getConfig()), { - useHtmlLabels, - width: node.width || getConfig().flowchart.wrappingWidth, - classes: 'markdown-node-label', - }); + text = createText( + label, + sanitizeText(decodeEntities(labelText), config), + { + useHtmlLabels, + width: node.width || config.flowchart.wrappingWidth, + classes: 'markdown-node-label', + }, + config + ); } else { text = textNode.appendChild( - createLabel( - sanitizeText(decodeEntities(labelText), getConfig()), - node.labelStyle, - false, - isNode - ) + createLabel(sanitizeText(decodeEntities(labelText), config), node.labelStyle, false, isNode) ); } // Get the size of the label let bbox = text.getBBox(); const halfPadding = node.padding / 2; - if (evaluate(getConfig().flowchart.htmlLabels)) { + if (evaluate(config.flowchart.htmlLabels)) { const div = text.children[0]; const dv = select(text); @@ -76,8 +77,8 @@ export const labelHelper = async (parent, node, _classes, isNode) => { if (noImgText) { // default size if no text - const bodyFontSize = getConfig().fontSize - ? getConfig().fontSize + const bodyFontSize = config.fontSize + ? config.fontSize : window.getComputedStyle(document.body).fontSize; const enlargingFactor = 5; const width = parseInt(bodyFontSize, 10) * enlargingFactor + 'px'; diff --git a/packages/mermaid/src/diagrams/common/common.ts b/packages/mermaid/src/diagrams/common/common.ts index 04be2a5f46..017b2b0911 100644 --- a/packages/mermaid/src/diagrams/common/common.ts +++ b/packages/mermaid/src/diagrams/common/common.ts @@ -346,11 +346,9 @@ export const renderKatex = async (text: string, config: MermaidConfig): Promise< .split(lineBreakRegex) .map((line) => hasKatex(line) - ? ` -
+ ? `
${line} -
- ` +
` : `
${line}
` ) .join('') diff --git a/packages/mermaid/src/diagrams/mindmap/svgDraw.ts b/packages/mermaid/src/diagrams/mindmap/svgDraw.ts index 3c7da86156..c84a7b16c0 100644 --- a/packages/mermaid/src/diagrams/mindmap/svgDraw.ts +++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.ts @@ -196,11 +196,16 @@ export const drawNode = function ( // Create the wrapped text element const textElem = nodeElem.append('g'); const description = node.descr.replace(/()/g, '\n'); - const newEl = createText(textElem, description, { - useHtmlLabels: htmlLabels, - width: node.width, - classes: 'mindmap-node-label', - }); + const newEl = createText( + textElem, + description, + { + useHtmlLabels: htmlLabels, + width: node.width, + classes: 'mindmap-node-label', + }, + conf + ); if (!htmlLabels) { textElem diff --git a/packages/mermaid/src/docs/syntax/flowchart.md b/packages/mermaid/src/docs/syntax/flowchart.md index a097e9739a..462a9aecc9 100644 --- a/packages/mermaid/src/docs/syntax/flowchart.md +++ b/packages/mermaid/src/docs/syntax/flowchart.md @@ -537,6 +537,16 @@ Formatting: 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 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. diff --git a/packages/mermaid/src/rendering-util/createText.ts b/packages/mermaid/src/rendering-util/createText.ts index 20efc2f744..725d65da66 100644 --- a/packages/mermaid/src/rendering-util/createText.ts +++ b/packages/mermaid/src/rendering-util/createText.ts @@ -1,5 +1,6 @@ /* eslint-disable @typescript-eslint/no-explicit-any */ // @ts-nocheck TODO: Fix types +import type { MermaidConfig } from '../config.type.js'; import type { Group } from '../diagram-api/types.js'; import type { D3TSpanElement, D3TextElement } from '../diagrams/common/commonTypes.js'; import { log } from '../logger.js'; @@ -21,8 +22,7 @@ function addHtmlSpan(element, node, width, classes, addBackground = false) { const label = node.label; const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel'; div.html( - ` - ' + label + @@ -181,14 +181,14 @@ export const createText = ( isNode = true, width = 200, addSvgBackground = false, - } = {} + } = {}, + config: MermaidConfig ) => { log.info('createText', text, style, isTitle, classes, useHtmlLabels, isNode, addSvgBackground); if (useHtmlLabels) { // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? - // text = text.replace(/\\n|\n/g, '
'); - const htmlText = markdownToHTML(text); - // log.info('markdownToHTML' + text, markdownToHTML(text)); + + const htmlText = markdownToHTML(text, config); const node = { isNode, label: decodeEntities(htmlText).replace( @@ -200,7 +200,7 @@ export const createText = ( const vertexNode = addHtmlSpan(el, node, width, classes, addSvgBackground); return vertexNode; } else { - const structuredText = markdownToLines(text); + const structuredText = markdownToLines(text, config); const svgLabel = createFormattedText(width, el, structuredText, addSvgBackground); return svgLabel; } diff --git a/packages/mermaid/src/rendering-util/handle-markdown-text.spec.ts b/packages/mermaid/src/rendering-util/handle-markdown-text.spec.ts index 3ca7a3d7a6..7362e6f70f 100644 --- a/packages/mermaid/src/rendering-util/handle-markdown-text.spec.ts +++ b/packages/mermaid/src/rendering-util/handle-markdown-text.spec.ts @@ -1,3 +1,4 @@ +/* eslint-disable no-irregular-whitespace */ import { markdownToLines, markdownToHTML } from './handle-markdown-text.js'; import { test, expect } from 'vitest'; @@ -203,6 +204,31 @@ Word!`; 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', () => { const input = `This is regular text Here is a new line @@ -262,3 +288,13 @@ test('markdownToHTML - Unsupported formatting', () => { - l3`) ).toMatchInlineSnapshot('"

Hello

Unsupported markdown: list"'); }); + +test('markdownToHTML - no auto wrapping', () => { + expect( + markdownToHTML( + `Hello, how do + you do?`, + { markdownAutoWrap: false } + ) + ).toMatchInlineSnapshot('"

Hello, how do
you do?

"'); +}); diff --git a/packages/mermaid/src/rendering-util/handle-markdown-text.ts b/packages/mermaid/src/rendering-util/handle-markdown-text.ts index ce694edcda..c539f72684 100644 --- a/packages/mermaid/src/rendering-util/handle-markdown-text.ts +++ b/packages/mermaid/src/rendering-util/handle-markdown-text.ts @@ -2,24 +2,28 @@ import type { Content } from 'mdast'; import { fromMarkdown } from 'mdast-util-from-markdown'; import { dedent } from 'ts-dedent'; import type { MarkdownLine, MarkdownWordType } from './types.js'; +import type { MermaidConfig } from '../config.type.js'; /** * @param markdown - markdown to process * @returns processed markdown */ -function preprocessMarkdown(markdown: string): string { +function preprocessMarkdown(markdown: string, { markdownAutoWrap }: MermaidConfig): string { // Replace multiple newlines with a single newline const withoutMultipleNewlines = markdown.replace(/\n{2,}/g, '\n'); // Remove extra spaces at the beginning of each line const withoutExtraSpaces = dedent(withoutMultipleNewlines); + if (markdownAutoWrap === false) { + return withoutExtraSpaces.replace(/ /g, ' '); + } return withoutExtraSpaces; } /** * @param markdown - markdown to split into lines */ -export function markdownToLines(markdown: string): MarkdownLine[] { - const preprocessedMarkdown = preprocessMarkdown(markdown); +export function markdownToLines(markdown: string, config: MermaidConfig = {}): MarkdownLine[] { + const preprocessedMarkdown = preprocessMarkdown(markdown, config); const { children } = fromMarkdown(preprocessedMarkdown); const lines: MarkdownLine[] = [[]]; let currentLine = 0; @@ -56,11 +60,14 @@ export function markdownToLines(markdown: string): MarkdownLine[] { return lines; } -export function markdownToHTML(markdown: string) { +export function markdownToHTML(markdown: string, { markdownAutoWrap }: MermaidConfig = {}) { const { children } = fromMarkdown(markdown); function output(node: Content): string { if (node.type === 'text') { + if (markdownAutoWrap === false) { + return node.value.replace(/\n/g, '
').replace(/ /g, ' '); + } return node.value.replace(/\n/g, '
'); } else if (node.type === 'strong') { return `${node.children.map(output).join('')}`; diff --git a/packages/mermaid/src/schemas/config.schema.yaml b/packages/mermaid/src/schemas/config.schema.yaml index d580300744..f10dc30b2d 100644 --- a/packages/mermaid/src/schemas/config.schema.yaml +++ b/packages/mermaid/src/schemas/config.schema.yaml @@ -243,6 +243,9 @@ properties: fontSize: type: number default: 16 + markdownAutoWrap: + type: boolean + default: true suppressErrorRendering: type: boolean default: false