mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-01-28 07:03:17 +08:00
Merge pull request #4856 from dreathed/bug/4391_make_markdown_auto_wrapping_optional
Bug/4391 make markdown auto wrapping optional
This commit is contained in:
commit
223f339682
@ -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
|
||||
|
@ -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)', () => {
|
||||
|
@ -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
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
*/
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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';
|
||||
|
@ -346,11 +346,9 @@ export const renderKatex = async (text: string, config: MermaidConfig): Promise<
|
||||
.split(lineBreakRegex)
|
||||
.map((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}
|
||||
</div>
|
||||
`
|
||||
</div>`
|
||||
: `<div>${line}</div>`
|
||||
)
|
||||
.join('')
|
||||
|
@ -196,11 +196,16 @@ export const drawNode = function (
|
||||
// Create the wrapped text element
|
||||
const textElem = nodeElem.append('g');
|
||||
const description = node.descr.replace(/(<br\/*>)/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
|
||||
|
@ -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.
|
||||
|
@ -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(
|
||||
`
|
||||
<span class="${labelClass} ${classes}" ` +
|
||||
`<span class="${labelClass} ${classes}" ` +
|
||||
(node.labelStyle ? 'style="' + node.labelStyle + '"' : '') +
|
||||
'>' +
|
||||
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, '<br />');
|
||||
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;
|
||||
}
|
||||
|
@ -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('"<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 { 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, '<br/>').replace(/ /g, ' ');
|
||||
}
|
||||
return node.value.replace(/\n/g, '<br/>');
|
||||
} else if (node.type === 'strong') {
|
||||
return `<strong>${node.children.map(output).join('')}</strong>`;
|
||||
|
@ -243,6 +243,9 @@ properties:
|
||||
fontSize:
|
||||
type: number
|
||||
default: 16
|
||||
markdownAutoWrap:
|
||||
type: boolean
|
||||
default: true
|
||||
suppressErrorRendering:
|
||||
type: boolean
|
||||
default: false
|
||||
|
Loading…
x
Reference in New Issue
Block a user