mirror of
https://github.com/mermaid-js/mermaid.git
synced 2025-12-21 11:47:27 +01:00
Compare commits
4 Commits
renovate/t
...
flowchart-
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
383f5b5bec | ||
|
|
efff583bb0 | ||
|
|
d435ac6fe1 | ||
|
|
09c60be450 |
2
.github/workflows/e2e-timings.yml
vendored
2
.github/workflows/e2e-timings.yml
vendored
@@ -58,7 +58,7 @@ jobs:
|
|||||||
echo "EOF" >> $GITHUB_OUTPUT
|
echo "EOF" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
- name: Commit and create pull request
|
- name: Commit and create pull request
|
||||||
uses: peter-evans/create-pull-request@84ae59a2cdc2258d6fa0732dd66352dddae2a412
|
uses: peter-evans/create-pull-request@0979079bc20c05bbbb590a56c21c4e2b1d1f1bbe
|
||||||
with:
|
with:
|
||||||
add-paths: |
|
add-paths: |
|
||||||
cypress/timings.json
|
cypress/timings.json
|
||||||
|
|||||||
2
.github/workflows/lint.yml
vendored
2
.github/workflows/lint.yml
vendored
@@ -89,7 +89,7 @@ jobs:
|
|||||||
continue-on-error: ${{ github.event_name == 'push' }}
|
continue-on-error: ${{ github.event_name == 'push' }}
|
||||||
run: pnpm run docs:verify
|
run: pnpm run docs:verify
|
||||||
|
|
||||||
- uses: testomatio/check-tests@8d7e741fd2c9e46c8e8a3b27207731b0658e0fbe # stable
|
- uses: testomatio/check-tests@0ea638fcec1820cf2e7b9854fdbdd04128a55bd4 # stable
|
||||||
with:
|
with:
|
||||||
framework: cypress
|
framework: cypress
|
||||||
tests: './cypress/e2e/**/**.spec.js'
|
tests: './cypress/e2e/**/**.spec.js'
|
||||||
|
|||||||
@@ -1012,6 +1012,7 @@ You have to call mermaid.initialize.`
|
|||||||
const baseNode = {
|
const baseNode = {
|
||||||
id: vertex.id,
|
id: vertex.id,
|
||||||
label: vertex.text,
|
label: vertex.text,
|
||||||
|
labelType: vertex.labelType,
|
||||||
labelStyle: '',
|
labelStyle: '',
|
||||||
parentId,
|
parentId,
|
||||||
padding: config.flowchart?.padding || 8,
|
padding: config.flowchart?.padding || 8,
|
||||||
@@ -1119,6 +1120,7 @@ You have to call mermaid.initialize.`
|
|||||||
end: rawEdge.end,
|
end: rawEdge.end,
|
||||||
type: rawEdge.type ?? 'normal',
|
type: rawEdge.type ?? 'normal',
|
||||||
label: rawEdge.text,
|
label: rawEdge.text,
|
||||||
|
labelType: rawEdge.labelType,
|
||||||
labelpos: 'c',
|
labelpos: 'c',
|
||||||
thickness: rawEdge.stroke,
|
thickness: rawEdge.stroke,
|
||||||
minlen: rawEdge.length,
|
minlen: rawEdge.length,
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
import { markdownToLines, markdownToHTML } from './handle-markdown-text.js';
|
import { markdownToLines, markdownToHTML, hasMarkdownSyntax } from './handle-markdown-text.js';
|
||||||
import { test, expect } from 'vitest';
|
import { test, expect } from 'vitest';
|
||||||
|
|
||||||
test('markdownToLines - Basic test', () => {
|
test('markdownToLines - Basic test', () => {
|
||||||
@@ -311,3 +311,35 @@ test('markdownToHTML - auto wrapping', () => {
|
|||||||
)
|
)
|
||||||
).toMatchInlineSnapshot('"<p>Hello, how do<br/>you do?</p>"');
|
).toMatchInlineSnapshot('"<p>Hello, how do<br/>you do?</p>"');
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('hasMarkdownSyntax - detects bold text', () => {
|
||||||
|
expect(hasMarkdownSyntax('This is **bold** text')).toBe(true);
|
||||||
|
expect(hasMarkdownSyntax('**Bold**')).toBe(true);
|
||||||
|
expect(hasMarkdownSyntax('Text with **bold** in middle')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasMarkdownSyntax - detects italic text', () => {
|
||||||
|
expect(hasMarkdownSyntax('This is *italic* text')).toBe(true);
|
||||||
|
expect(hasMarkdownSyntax('*Italic*')).toBe(true);
|
||||||
|
expect(hasMarkdownSyntax('Text with *italic* in middle')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasMarkdownSyntax - detects mixed formatting', () => {
|
||||||
|
expect(hasMarkdownSyntax('*Italic* and **bold**')).toBe(true);
|
||||||
|
expect(hasMarkdownSyntax('The dog in **the** hog')).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasMarkdownSyntax - returns false for plain text', () => {
|
||||||
|
expect(hasMarkdownSyntax('This is plain text')).toBe(false);
|
||||||
|
expect(hasMarkdownSyntax('The dog in the hog')).toBe(false);
|
||||||
|
expect(hasMarkdownSyntax('Simple label')).toBe(false);
|
||||||
|
expect(hasMarkdownSyntax('')).toBe(false);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('hasMarkdownSyntax - handles edge cases', () => {
|
||||||
|
expect(hasMarkdownSyntax(null as any)).toBe(false);
|
||||||
|
expect(hasMarkdownSyntax(undefined as any)).toBe(false);
|
||||||
|
expect(hasMarkdownSyntax(' ')).toBe(false);
|
||||||
|
expect(hasMarkdownSyntax('Text with asterisks * but not italic')).toBe(false);
|
||||||
|
expect(hasMarkdownSyntax('Text with ** but not bold')).toBe(false);
|
||||||
|
});
|
||||||
|
|||||||
@@ -70,6 +70,42 @@ export function markdownToLines(markdown: string, config: MermaidConfig = {}): M
|
|||||||
return lines;
|
return lines;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Checks if text contains actual markdown syntax (bold, italic, etc.)
|
||||||
|
* This helps validate that labels marked as 'markdown' actually contain markdown
|
||||||
|
* @param text - text to check
|
||||||
|
* @returns true if text contains markdown syntax, false otherwise
|
||||||
|
*/
|
||||||
|
export function hasMarkdownSyntax(text: string): boolean {
|
||||||
|
if (!text || typeof text !== 'string') {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
try {
|
||||||
|
const nodes = marked.lexer(text);
|
||||||
|
|
||||||
|
// Check if any node contains markdown formatting
|
||||||
|
function hasMarkdownInNode(node: Token): boolean {
|
||||||
|
if (node.type === 'strong' || node.type === 'em') {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (node.type === 'paragraph' && node.tokens) {
|
||||||
|
return node.tokens.some(hasMarkdownInNode);
|
||||||
|
}
|
||||||
|
if ('tokens' in node && node.tokens) {
|
||||||
|
return node.tokens.some(hasMarkdownInNode);
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
return nodes.some(hasMarkdownInNode);
|
||||||
|
} catch (error) {
|
||||||
|
// If parsing fails, assume it's not markdown
|
||||||
|
log.debug('Failed to parse text as markdown:', error);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
export function markdownToHTML(markdown: string, { markdownAutoWrap }: MermaidConfig = {}) {
|
export function markdownToHTML(markdown: string, { markdownAutoWrap }: MermaidConfig = {}) {
|
||||||
const nodes = marked.lexer(markdown);
|
const nodes = marked.lexer(markdown);
|
||||||
|
|
||||||
|
|||||||
@@ -45,16 +45,29 @@ export const getLabelStyles = (styleArray) => {
|
|||||||
};
|
};
|
||||||
|
|
||||||
export const insertEdgeLabel = async (elem, edge) => {
|
export const insertEdgeLabel = async (elem, edge) => {
|
||||||
let useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
|
const config = getConfig();
|
||||||
|
let useHtmlLabels = evaluate(config.flowchart.htmlLabels);
|
||||||
|
|
||||||
|
// Only process as markdown if labelType is explicitly 'markdown'
|
||||||
|
// This ensures only labels properly delimited with ["`...`"] are processed as markdown
|
||||||
|
// This validation is restricted to flowcharts only
|
||||||
|
const isFlowchart = config.flowchart !== undefined;
|
||||||
|
const shouldProcessAsMarkdown = isFlowchart && edge.labelType === 'markdown';
|
||||||
|
|
||||||
const { labelStyles } = styles2String(edge);
|
const { labelStyles } = styles2String(edge);
|
||||||
edge.labelStyle = labelStyles;
|
edge.labelStyle = labelStyles;
|
||||||
const labelElement = await createText(elem, edge.label, {
|
|
||||||
style: edge.labelStyle,
|
let labelElement;
|
||||||
useHtmlLabels,
|
if (shouldProcessAsMarkdown) {
|
||||||
addSvgBackground: true,
|
labelElement = await createText(elem, edge.label, {
|
||||||
isNode: false,
|
style: edge.labelStyle,
|
||||||
});
|
useHtmlLabels,
|
||||||
|
addSvgBackground: true,
|
||||||
|
isNode: false,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
labelElement = await createLabel(edge.label, edge.labelStyle, undefined, false);
|
||||||
|
}
|
||||||
log.info('abc82', edge, edge.labelType);
|
log.info('abc82', edge, edge.labelType);
|
||||||
|
|
||||||
// Create outer g, edgeLabel, this will be positioned after graph layout
|
// Create outer g, edgeLabel, this will be positioned after graph layout
|
||||||
|
|||||||
@@ -6,6 +6,7 @@ import defaultConfig from '../../../defaultConfig.js';
|
|||||||
import { evaluate, sanitizeText } from '../../../diagrams/common/common.js';
|
import { evaluate, sanitizeText } from '../../../diagrams/common/common.js';
|
||||||
import { decodeEntities, handleUndefinedAttr, parseFontSize } from '../../../utils.js';
|
import { decodeEntities, handleUndefinedAttr, parseFontSize } from '../../../utils.js';
|
||||||
import type { D3Selection, Point } from '../../../types.js';
|
import type { D3Selection, Point } from '../../../types.js';
|
||||||
|
import createLabel from '../createLabel.js';
|
||||||
|
|
||||||
export const labelHelper = async <T extends SVGGraphicsElement>(
|
export const labelHelper = async <T extends SVGGraphicsElement>(
|
||||||
parent: D3Selection<T>,
|
parent: D3Selection<T>,
|
||||||
@@ -40,14 +41,31 @@ export const labelHelper = async <T extends SVGGraphicsElement>(
|
|||||||
label = typeof node.label === 'string' ? node.label : node.label[0];
|
label = typeof node.label === 'string' ? node.label : node.label[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
const text = await createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), {
|
// Only process as markdown if labelType is explicitly 'markdown'
|
||||||
useHtmlLabels,
|
// This ensures only labels properly delimited with ["`...`"] are processed as markdown
|
||||||
width: node.width || getConfig().flowchart?.wrappingWidth,
|
// This validation is restricted to flowcharts only
|
||||||
// @ts-expect-error -- This is currently not used. Should this be `classes` instead?
|
const config = getConfig();
|
||||||
cssClasses: 'markdown-node-label',
|
const isFlowchart = config.flowchart !== undefined;
|
||||||
style: node.labelStyle,
|
const shouldProcessAsMarkdown = isFlowchart && node.labelType === 'markdown';
|
||||||
addSvgBackground: !!node.icon || !!node.img,
|
let text;
|
||||||
});
|
if (shouldProcessAsMarkdown) {
|
||||||
|
text = await createText(labelEl, sanitizeText(decodeEntities(label), config), {
|
||||||
|
useHtmlLabels,
|
||||||
|
width: node.width || config.flowchart?.wrappingWidth,
|
||||||
|
// @ts-expect-error -- This is currently not used. Should this be `classes` instead?
|
||||||
|
cssClasses: 'markdown-node-label',
|
||||||
|
style: node.labelStyle,
|
||||||
|
addSvgBackground: !!node.icon || !!node.img,
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
const labelElement = await createLabel(
|
||||||
|
sanitizeText(decodeEntities(label), config),
|
||||||
|
node.labelStyle,
|
||||||
|
false,
|
||||||
|
true
|
||||||
|
);
|
||||||
|
text = labelEl.node()?.appendChild(labelElement);
|
||||||
|
}
|
||||||
// Get the size of the label
|
// Get the size of the label
|
||||||
let bbox = text.getBBox();
|
let bbox = text.getBBox();
|
||||||
const halfPadding = (node?.padding ?? 0) / 2;
|
const halfPadding = (node?.padding ?? 0) / 2;
|
||||||
|
|||||||
Reference in New Issue
Block a user