From 58d4ba0d8f76b689448fbabbd24323ef3bdaf33c Mon Sep 17 00:00:00 2001 From: Alois Klink Date: Thu, 9 Mar 2023 16:45:39 +0000 Subject: [PATCH 01/31] test(e2e): fix gantt `todayMarker` tests The gantt diagram that were supposed to test whether `todayMarker off` works wasn't working properly, because `todayMarker on` wasn't working (i.e. the test never failed). I've fixed this issue, and added a test that checks whether `todayMarker on` works. Fixes: https://github.com/mermaid-js/mermaid/issues/4198 --- cypress/integration/rendering/gantt.spec.js | 24 +++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/cypress/integration/rendering/gantt.spec.js b/cypress/integration/rendering/gantt.spec.js index c0156eee3..b01a15809 100644 --- a/cypress/integration/rendering/gantt.spec.js +++ b/cypress/integration/rendering/gantt.spec.js @@ -133,6 +133,24 @@ describe('Gantt diagram', () => { ); }); + it('should default to showing today marker', () => { + // This test only works if the environment thinks today is 1010-10-10 + imgSnapshotTest( + ` + gantt + title Show today marker (vertical line should be visible) + dateFormat YYYY-MM-DD + axisFormat %d + %% Should default to being on + %% todayMarker on + section Section1 + Yesterday: 1010-10-09, 1d + Today: 1010-10-10, 1d + `, + {} + ); + }); + it('should hide today marker', () => { imgSnapshotTest( ` @@ -142,7 +160,8 @@ describe('Gantt diagram', () => { axisFormat %d todayMarker off section Section1 - Today: 1, -1h + Yesterday: 1010-10-09, 1d + Today: 1010-10-10, 1d `, {} ); @@ -157,7 +176,8 @@ describe('Gantt diagram', () => { axisFormat %d todayMarker stroke-width:5px,stroke:#00f,opacity:0.5 section Section1 - Today: 1, -1h + Yesterday: 1010-10-09, 1d + Today: 1010-10-10, 1d `, {} ); From 853d9b7f981fde279dafc7f32e931579f75b6a48 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Tue, 14 Mar 2023 13:52:20 +0100 Subject: [PATCH 02/31] #4220 Create text utility functions handling new lines and applying them on mindmap --- cypress/platform/knsv2.html | 15 +- .../mermaid/src/dagre-wrapper/createLabel.js | 8 +- .../mermaid/src/diagrams/mindmap/mindmapDb.js | 2 +- .../src/diagrams/mindmap/parser/mindmap.jison | 4 + .../mermaid/src/diagrams/mindmap/svgDraw.js | 51 ++++-- .../mermaid/src/rendering-util/createText.js | 161 ++++++++++++++++++ 6 files changed, 219 insertions(+), 22 deletions(-) create mode 100644 packages/mermaid/src/rendering-util/createText.js diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index fccd65004..36f481a08 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -54,7 +54,7 @@ -
+    
 %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
 graph BT
 a{The cat in the hat} -- 1o --> b
@@ -66,12 +66,13 @@ h --3i -->a
 b --> d(The dog in the hog)
 c --> d
     
-
-flowchart-elk TB
-      a --> b
-      a --> c
-      b --> d
-      c --> d
+    
+mindmap
+    id1["`Start
+second line 😎`"]
+      id2[Child]
+      id3[Child]
+      id4[Child]
     
 %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
diff --git a/packages/mermaid/src/dagre-wrapper/createLabel.js b/packages/mermaid/src/dagre-wrapper/createLabel.js
index af5032096..ff7834c4f 100644
--- a/packages/mermaid/src/dagre-wrapper/createLabel.js
+++ b/packages/mermaid/src/dagre-wrapper/createLabel.js
@@ -41,7 +41,13 @@ function addHtmlLabel(node) {
   div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
   return fo.node();
 }
-
+/**
+ * @param _vertexText
+ * @param style
+ * @param isTitle
+ * @param isNode
+ * @deprecated svg-util/createText instead
+ */
 const createLabel = (_vertexText, style, isTitle, isNode) => {
   let vertexText = _vertexText || '';
   if (typeof vertexText === 'object') {
diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapDb.js b/packages/mermaid/src/diagrams/mindmap/mindmapDb.js
index 71aa449d9..7585029cf 100644
--- a/packages/mermaid/src/diagrams/mindmap/mindmapDb.js
+++ b/packages/mermaid/src/diagrams/mindmap/mindmapDb.js
@@ -33,7 +33,7 @@ export const addNode = (level, id, descr, type) => {
     id: cnt++,
     nodeId: sanitizeText(id),
     level,
-    descr: sanitizeText(descr),
+    descr: sanitizeText(descr).replace(/\n/g, '
'), type, children: [], width: getConfig().mindmap.maxNodeWidth, diff --git a/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison index d2f6bbf1a..84a6191cf 100644 --- a/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison +++ b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison @@ -12,6 +12,7 @@ %} %x NODE %x NSTR +%x NSTR2 %x ICON %x CLASS @@ -41,6 +42,9 @@ // !(-\() return 'NODE_ID'; [^\(\[\n\-\)\{\}]+ return 'NODE_ID'; <> return 'EOF'; +["][`] { this.begin("NSTR2");} +[^`"]+ { return "NODE_DESCR";} +[`]["] { this.popState();} ["] { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");} [^"]+ { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";} ["] {this.popState();} diff --git a/packages/mermaid/src/diagrams/mindmap/svgDraw.js b/packages/mermaid/src/diagrams/mindmap/svgDraw.js index 2b1aa021e..2c3dcca56 100644 --- a/packages/mermaid/src/diagrams/mindmap/svgDraw.js +++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.js @@ -1,5 +1,6 @@ import { select } from 'd3'; import * as db from './mindmapDb'; +import { createText, setSize } from '../../rendering-util/createText'; const MAX_SECTIONS = 12; /** @@ -11,7 +12,7 @@ function wrap(text, width) { var text = select(this), words = text .text() - .split(/(\s+|
)/) + .split(/(\s+|)/) .reverse(), word, line = [], @@ -28,10 +29,10 @@ function wrap(text, width) { word = words[words.length - 1 - j]; line.push(word); tspan.text(line.join(' ').trim()); - if (tspan.node().getComputedTextLength() > width || word === '
') { + if (tspan.node().getComputedTextLength() > width || word === '
') { line.pop(); tspan.text(line.join(' ').trim()); - if (word === '
') { + if (word === '
') { line = ['']; } else { line = [word]; @@ -203,6 +204,7 @@ const roundedRectBkg = function (elem, node) { * @returns {number} The height nodes dom element */ export const drawNode = function (elem, node, fullSection, conf) { + const htmlLabels = false; const section = fullSection % (MAX_SECTIONS - 1); const nodeElem = elem.append('g'); node.section = section; @@ -215,15 +217,29 @@ export const drawNode = function (elem, node, fullSection, conf) { // Create the wrapped text element const textElem = nodeElem.append('g'); - const txt = textElem - .append('text') - .text(node.descr) - .attr('dy', '1em') - .attr('alignment-baseline', 'middle') - .attr('dominant-baseline', 'middle') - .attr('text-anchor', 'middle') - .call(wrap, node.width); - const bbox = txt.node().getBBox(); + + const newEl = createText(textElem, node.descr, { useHtmlLabels: htmlLabels }); + const txt = textElem.node().appendChild(newEl); + // const txt = textElem.append(newEl); + // const txt = textElem + // .append('text') + // .text(node.descr) + // .attr('dy', '1em') + // .attr('alignment-baseline', 'middle') + // .attr('dominant-baseline', 'middle') + // .attr('text-anchor', 'middle') + // .call(wrap, node.width); + // const newerEl = textElem.node().appendChild(newEl); + // setSize(textElem); + if (!htmlLabels) { + textElem + .attr('dy', '1em') + .attr('alignment-baseline', 'middle') + .attr('dominant-baseline', 'middle') + .attr('text-anchor', 'middle'); + } + // .call(wrap, node.width); + const bbox = textElem.node().getBBox(); const fontSize = conf.fontSize.replace ? conf.fontSize.replace('px', '') : conf.fontSize; node.height = bbox.height + fontSize * 1.1 * 0.5 + node.padding; node.width = bbox.width + 2 * node.padding; @@ -267,7 +283,16 @@ export const drawNode = function (elem, node, fullSection, conf) { ); } } else { - textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')'); + if (!htmlLabels) { + const dx = node.width / 2; + const dy = node.padding / 2; + textElem.attr('transform', 'translate(' + dx + ', ' + dy + ')'); + // textElem.attr('transform', 'translate(' + node.width / 2 + ', ' + node.padding / 2 + ')'); + } else { + const dx = (node.width - bbox.width) / 2; + const dy = (node.height - bbox.height) / 2; + textElem.attr('transform', 'translate(' + dx + ', ' + dy + ')'); + } } switch (node.type) { diff --git a/packages/mermaid/src/rendering-util/createText.js b/packages/mermaid/src/rendering-util/createText.js new file mode 100644 index 000000000..58e0f54a7 --- /dev/null +++ b/packages/mermaid/src/rendering-util/createText.js @@ -0,0 +1,161 @@ +import { select } from 'd3'; +import { log } from '../logger'; +import { getConfig } from '../config'; +import { evaluate } from '../diagrams/common/common'; +import { decodeEntities } from '../mermaidAPI'; + +/** + * @param dom + * @param styleFn + */ +function applyStyle(dom, styleFn) { + if (styleFn) { + dom.attr('style', styleFn); + } +} + +/** + * @param element + * @param {any} node + * @returns {SVGForeignObjectElement} Node + */ +function addHtmlSpan(element, node) { + const fo = element.append('foreignObject'); + const newEl = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'); + const div = fo.append('xhtml:div'); + + const label = node.label; + const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel'; + div.html( + '' + + label + + '' + ); + + applyStyle(div, node.labelStyle); + div.style('display', 'inline-block'); + const bbox = div.node().getBoundingClientRect(); + fo.style('width', bbox.width); + fo.style('height', bbox.height); + + const divNode = div.node(); + window.divNode = divNode; + // Fix for firefox + div.style('white-space', 'nowrap'); + div.attr('xmlns', 'http://www.w3.org/1999/xhtml'); + return fo.node(); +} + +/** + * @param {string} text The text to be wrapped + * @param {number} width The max width of the text + */ +function wrap(text, width) { + text.each(function () { + var text = select(this), + words = text + .text() + .split(/(\s+|)/) + .reverse(), + word, + line = [], + lineHeight = 1.1, // ems + y = text.attr('y'), + dy = parseFloat(text.attr('dy')), + tspan = text + .text(null) + .append('tspan') + .attr('x', 0) + .attr('y', y) + .attr('dy', dy + 'em'); + for (let j = 0; j < words.length; j++) { + word = words[words.length - 1 - j]; + line.push(word); + tspan.text(line.join(' ').trim()); + if (tspan.node().getComputedTextLength() > width || word === '
') { + line.pop(); + tspan.text(line.join(' ').trim()); + if (word === '
') { + line = ['']; + } else { + line = [word]; + } + + tspan = text + .append('tspan') + .attr('x', 0) + .attr('y', y) + .attr('dy', lineHeight + 'em') + .text(word); + } + } + }); +} + +/** + * + * @param el + * @param {*} text + * @param {*} param1 + * @param root0 + * @param root0.style + * @param root0.isTitle + * @param root0.classes + * @param root0.useHtmlLabels + * @param root0.isNode + * @returns + */ +// Note when using from flowcharts converting the API isNode means classes should be set accordingly. When using htmlLabels => to sett classes to'nodeLabel' when isNode=true otherwise 'edgeLabel' +// When not using htmlLabels => to set classes to 'title-row' when isTitle=true otherwise 'title-row' +export const createText = ( + el, + text = '', + { style = '', isTitle = false, classes = '', useHtmlLabels = true, isNode = true } = {} +) => { + if (useHtmlLabels) { + // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? + text = text.replace(/\\n|\n/g, '
'); + log.info('text' + text); + const node = { + isNode, + label: decodeEntities(text).replace( + /fa[blrs]?:fa-[\w-]+/g, + (s) => `` + ), + labelStyle: style.replace('fill:', 'color:'), + }; + let vertexNode = addHtmlSpan(el, node); + return vertexNode; + } else { + const svgText = document.createElementNS('http://www.w3.org/2000/svg', 'text'); + svgText.setAttribute('style', style.replace('color:', 'fill:')); + // el.attr('style', style.replace('color:', 'fill:')); + let rows = []; + if (typeof text === 'string') { + rows = text.split(/\\n|\n|/gi); + } else if (Array.isArray(text)) { + rows = text; + } else { + rows = []; + } + + for (const row of rows) { + const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan'); + tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); + tspan.setAttribute('dy', '1em'); + tspan.setAttribute('x', '0'); + if (isTitle) { + tspan.setAttribute('class', 'title-row'); + } else { + tspan.setAttribute('class', 'row'); + } + tspan.textContent = row.trim(); + svgText.appendChild(tspan); + } + return svgText; + } +}; From a1c50b8079950e44e2cae19620ce92f63d46b17d Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Mon, 20 Mar 2023 14:15:26 +0100 Subject: [PATCH 03/31] #4220 Parsing the text as markdown and rendering accordingly --- cypress/platform/knsv2.html | 12 +- packages/mermaid/package.json | 1 + .../mermaid/src/diagrams/mindmap/mindmapDb.js | 2 +- .../mermaid/src/diagrams/mindmap/svgDraw.js | 7 +- .../mermaid/src/rendering-util/createText.js | 144 +++++++------- .../rendering-util/handle-markdown-text.js | 78 ++++++++ .../handle-markdown-text.spec.js | 181 ++++++++++++++++++ pnpm-lock.yaml | 93 ++++++++- 8 files changed, 434 insertions(+), 84 deletions(-) create mode 100644 packages/mermaid/src/rendering-util/handle-markdown-text.js create mode 100644 packages/mermaid/src/rendering-util/handle-markdown-text.spec.js diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 36f481a08..923529074 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -67,6 +67,14 @@ b --> d(The dog in the hog) c --> d
+mindmap
+    id1["`Start`"]
+      id2["`Child **with bold** text`"]
+      id3["`Children of which some
+      is using *italic type of* text`"]
+      id4[Child]
+    
+
 mindmap
     id1["`Start
 second line 😎`"]
@@ -273,12 +281,14 @@ mindmap
       mermaid.initialize({
         theme: 'forest',
         startOnLoad: true,
-        logLevel: 5,
+        logLevel: 0,
         flowchart: {
           // defaultRenderer: 'elk',
           useMaxWidth: false,
+          // htmlLabels: false,
           htmlLabels: true,
         },
+        htmlLabels: true,
         gantt: {
           useMaxWidth: false,
         },
diff --git a/packages/mermaid/package.json b/packages/mermaid/package.json
index 3c9ff1ed4..b62000937 100644
--- a/packages/mermaid/package.json
+++ b/packages/mermaid/package.json
@@ -53,6 +53,7 @@
   },
   "dependencies": {
     "@braintree/sanitize-url": "^6.0.0",
+    "@khanacademy/simple-markdown": "^0.8.6",
     "cytoscape": "^3.23.0",
     "cytoscape-cose-bilkent": "^4.1.0",
     "cytoscape-fcose": "^2.1.0",
diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapDb.js b/packages/mermaid/src/diagrams/mindmap/mindmapDb.js
index 7585029cf..71aa449d9 100644
--- a/packages/mermaid/src/diagrams/mindmap/mindmapDb.js
+++ b/packages/mermaid/src/diagrams/mindmap/mindmapDb.js
@@ -33,7 +33,7 @@ export const addNode = (level, id, descr, type) => {
     id: cnt++,
     nodeId: sanitizeText(id),
     level,
-    descr: sanitizeText(descr).replace(/\n/g, '
'), + descr: sanitizeText(descr), type, children: [], width: getConfig().mindmap.maxNodeWidth, diff --git a/packages/mermaid/src/diagrams/mindmap/svgDraw.js b/packages/mermaid/src/diagrams/mindmap/svgDraw.js index 2c3dcca56..5394e004e 100644 --- a/packages/mermaid/src/diagrams/mindmap/svgDraw.js +++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.js @@ -1,6 +1,6 @@ import { select } from 'd3'; import * as db from './mindmapDb'; -import { createText, setSize } from '../../rendering-util/createText'; +import { createText } from '../../rendering-util/createText'; const MAX_SECTIONS = 12; /** @@ -217,9 +217,8 @@ export const drawNode = function (elem, node, fullSection, conf) { // Create the wrapped text element const textElem = nodeElem.append('g'); - - const newEl = createText(textElem, node.descr, { useHtmlLabels: htmlLabels }); - const txt = textElem.node().appendChild(newEl); + const newEl = createText(textElem, node.descr, { useHtmlLabels: htmlLabels, width: node.width }); + // const txt = textElem.node().appendChild(newEl); // const txt = textElem.append(newEl); // const txt = textElem // .append('text') diff --git a/packages/mermaid/src/rendering-util/createText.js b/packages/mermaid/src/rendering-util/createText.js index 58e0f54a7..47e0776e4 100644 --- a/packages/mermaid/src/rendering-util/createText.js +++ b/packages/mermaid/src/rendering-util/createText.js @@ -3,7 +3,7 @@ import { log } from '../logger'; import { getConfig } from '../config'; import { evaluate } from '../diagrams/common/common'; import { decodeEntities } from '../mermaidAPI'; - +import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdown-text'; /** * @param dom * @param styleFn @@ -51,51 +51,79 @@ function addHtmlSpan(element, node) { } /** - * @param {string} text The text to be wrapped - * @param {number} width The max width of the text + * Creates a tspan element with the specified attributes for text positioning. + * + * @param {object} textElement - The parent text element to append the tspan element. + * @param {number} lineIndex - The index of the current line in the structuredText array. + * @param {number} lineHeight - The line height value for the text. + * @returns {object} The created tspan element. */ -function wrap(text, width) { - text.each(function () { - var text = select(this), - words = text - .text() - .split(/(\s+|)/) - .reverse(), - word, - line = [], - lineHeight = 1.1, // ems - y = text.attr('y'), - dy = parseFloat(text.attr('dy')), - tspan = text - .text(null) - .append('tspan') - .attr('x', 0) - .attr('y', y) - .attr('dy', dy + 'em'); - for (let j = 0; j < words.length; j++) { - word = words[words.length - 1 - j]; - line.push(word); - tspan.text(line.join(' ').trim()); - if (tspan.node().getComputedTextLength() > width || word === '
') { - line.pop(); - tspan.text(line.join(' ').trim()); - if (word === '
') { - line = ['']; - } else { - line = [word]; - } +function createTspan(textElement, lineIndex, lineHeight) { + return textElement + .append('tspan') + .attr('x', 0) + .attr('y', lineIndex * lineHeight + 'em') + .attr('dy', lineHeight + 'em'); +} - tspan = text - .append('tspan') - .attr('x', 0) - .attr('y', y) - .attr('dy', lineHeight + 'em') - .text(word); +/** + * Creates a formatted text element by breaking lines and applying styles based on + * the given structuredText. + * + * @param {number} width - The maximum allowed width of the text. + * @param {object} g - The parent group element to append the formatted text. + * @param {Array} structuredText - The structured text data to format. + */ +function createFormattedText(width, g, structuredText) { + const lineHeight = 1.1; + + const textElement = g.append('text'); + + structuredText.forEach((line, lineIndex) => { + let tspan = createTspan(textElement, lineIndex, lineHeight); + + let words = [...line].reverse(); + let currentWord; + let wrappedLine = []; + + while (words.length) { + currentWord = words.pop(); + wrappedLine.push(currentWord); + + updateTextContentAndStyles(tspan, wrappedLine); + + if (tspan.node().getComputedTextLength() > width) { + wrappedLine.pop(); + words.push(currentWord); + + updateTextContentAndStyles(tspan, wrappedLine); + + wrappedLine = []; + tspan = createTspan(textElement, ++lineIndex, lineHeight); } } }); } +/** + * Updates the text content and styles of the given tspan element based on the + * provided wrappedLine data. + * + * @param {object} tspan - The tspan element to update. + * @param {Array} wrappedLine - The line data to apply to the tspan element. + */ +function updateTextContentAndStyles(tspan, wrappedLine) { + tspan.text(''); + + wrappedLine.forEach((word) => { + tspan + .append('tspan') + .attr('font-style', word.type === 'em' ? 'italic' : 'normal') + .attr('font-weight', word.type === 'strong' ? 'bold' : 'normal') + .text(word.content + ' '); + }); +} + /** * * @param el @@ -114,15 +142,17 @@ function wrap(text, width) { export const createText = ( el, text = '', - { style = '', isTitle = false, classes = '', useHtmlLabels = true, isNode = true } = {} + { style = '', isTitle = false, classes = '', useHtmlLabels = true, isNode = true, width } = {} ) => { + log.info('createText', text, style, isTitle, classes, useHtmlLabels, isNode); if (useHtmlLabels) { // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? - text = text.replace(/\\n|\n/g, '
'); - log.info('text' + text); + // text = text.replace(/\\n|\n/g, '
'); + const htmlText = markdownToHTML(text); + // log.info('markdo wnToHTML' + text, markdownToHTML(text)); const node = { isNode, - label: decodeEntities(text).replace( + label: decodeEntities(htmlText).replace( /fa[blrs]?:fa-[\w-]+/g, (s) => `` ), @@ -131,31 +161,7 @@ export const createText = ( let vertexNode = addHtmlSpan(el, node); return vertexNode; } else { - const svgText = document.createElementNS('http://www.w3.org/2000/svg', 'text'); - svgText.setAttribute('style', style.replace('color:', 'fill:')); - // el.attr('style', style.replace('color:', 'fill:')); - let rows = []; - if (typeof text === 'string') { - rows = text.split(/\\n|\n|/gi); - } else if (Array.isArray(text)) { - rows = text; - } else { - rows = []; - } - - for (const row of rows) { - const tspan = document.createElementNS('http://www.w3.org/2000/svg', 'tspan'); - tspan.setAttributeNS('http://www.w3.org/XML/1998/namespace', 'xml:space', 'preserve'); - tspan.setAttribute('dy', '1em'); - tspan.setAttribute('x', '0'); - if (isTitle) { - tspan.setAttribute('class', 'title-row'); - } else { - tspan.setAttribute('class', 'row'); - } - tspan.textContent = row.trim(); - svgText.appendChild(tspan); - } - return svgText; + const structuredText = markdownToLines(text); + return createFormattedText(width, el, structuredText); } }; diff --git a/packages/mermaid/src/rendering-util/handle-markdown-text.js b/packages/mermaid/src/rendering-util/handle-markdown-text.js new file mode 100644 index 000000000..c4d10d9dc --- /dev/null +++ b/packages/mermaid/src/rendering-util/handle-markdown-text.js @@ -0,0 +1,78 @@ +import SimpleMarkdown from '@khanacademy/simple-markdown'; + +/** + * + * @param markdown + */ +export function markdownToLines(markdown) { + const mdParse = SimpleMarkdown.defaultBlockParse; + const syntaxTree = mdParse(markdown); + + let lines = [[]]; + let currentLine = 0; + + /** + * + * @param node + * @param parentType + */ + function processNode(node, parentType) { + if (node.type === 'text') { + const textLines = node.content.split('\n'); + textLines.forEach((textLine, index) => { + if (index !== 0) { + currentLine++; + lines.push([]); + } + textLine.split(' ').forEach((word) => { + if (word) { + lines[currentLine].push({ content: word, type: parentType || 'normal' }); + } + }); + }); + } else if (node.type === 'strong' || node.type === 'em') { + node.content.forEach((contentNode) => { + processNode(contentNode, node.type); + }); + } + } + + syntaxTree.forEach((treeNode) => { + if (treeNode.type === 'paragraph') { + treeNode.content.forEach((contentNode) => { + processNode(contentNode); + }); + } + }); + + return lines; +} + +/** + * + * @param markdown + */ +export function markdownToHTML(markdown) { + const mdParse = SimpleMarkdown.defaultBlockParse; + const syntaxTree = mdParse(markdown); + + /** + * + * @param node + */ + function output(node) { + if (node.type === 'text') { + return node.content.replace(/\n/g, '
'); + } else if (node.type === 'strong') { + return `${node.content.map(output).join('')}`; + } else if (node.type === 'em') { + return `${node.content.map(output).join('')}`; + } else if (node.type === 'paragraph') { + return `

${node.content.map(output).join('')}

`; + } else { + return ''; + } + } + + return syntaxTree.map(output).join(''); +} diff --git a/packages/mermaid/src/rendering-util/handle-markdown-text.spec.js b/packages/mermaid/src/rendering-util/handle-markdown-text.spec.js new file mode 100644 index 000000000..db542543a --- /dev/null +++ b/packages/mermaid/src/rendering-util/handle-markdown-text.spec.js @@ -0,0 +1,181 @@ +// import { test } from 'vitest'; +import { markdownToLines, markdownToHTML } from './handle-markdown-text'; +import { test } from 'vitest'; + +test('markdownToLines - Basic test', () => { + const input = `This is regular text +Here is a new line +There is some words **with a bold** section +Here is a line *with an italic* section`; + + const expectedOutput = [ + [ + { content: 'This', type: 'normal' }, + { content: 'is', type: 'normal' }, + { content: 'regular', type: 'normal' }, + { content: 'text', type: 'normal' }, + ], + [ + { content: 'Here', type: 'normal' }, + { content: 'is', type: 'normal' }, + { content: 'a', type: 'normal' }, + { content: 'new', type: 'normal' }, + { content: 'line', type: 'normal' }, + ], + [ + { content: 'There', type: 'normal' }, + { content: 'is', type: 'normal' }, + { content: 'some', type: 'normal' }, + { content: 'words', type: 'normal' }, + { content: 'with', type: 'strong' }, + { content: 'a', type: 'strong' }, + { content: 'bold', type: 'strong' }, + { content: 'section', type: 'normal' }, + ], + [ + { content: 'Here', type: 'normal' }, + { content: 'is', type: 'normal' }, + { content: 'a', type: 'normal' }, + { content: 'line', type: 'normal' }, + { content: 'with', type: 'em' }, + { content: 'an', type: 'em' }, + { content: 'italic', type: 'em' }, + { content: 'section', type: 'normal' }, + ], + ]; + + const output = markdownToLines(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToLines - Empty input', () => { + const input = ''; + const expectedOutput = [[]]; + const output = markdownToLines(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToLines - No formatting', () => { + const input = `This is a simple test +with no formatting`; + + const expectedOutput = [ + [ + { content: 'This', type: 'normal' }, + { content: 'is', type: 'normal' }, + { content: 'a', type: 'normal' }, + { content: 'simple', type: 'normal' }, + { content: 'test', type: 'normal' }, + ], + [ + { content: 'with', type: 'normal' }, + { content: 'no', type: 'normal' }, + { content: 'formatting', type: 'normal' }, + ], + ]; + + const output = markdownToLines(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToLines - Only bold formatting', () => { + const input = `This is a **bold** test`; + + const expectedOutput = [ + [ + { content: 'This', type: 'normal' }, + { content: 'is', type: 'normal' }, + { content: 'a', type: 'normal' }, + { content: 'bold', type: 'strong' }, + { content: 'test', type: 'normal' }, + ], + ]; + + const output = markdownToLines(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToLines - Only italic formatting', () => { + const input = `This is an *italic* test`; + + const expectedOutput = [ + [ + { content: 'This', type: 'normal' }, + { content: 'is', type: 'normal' }, + { content: 'an', type: 'normal' }, + { content: 'italic', type: 'em' }, + { content: 'test', type: 'normal' }, + ], + ]; + + const output = markdownToLines(input); + expect(output).toEqual(expectedOutput); +}); + +it('markdownToLines - Mixed formatting', () => { + const input = `*Italic* and **bold** formatting`; + + const expectedOutput = [ + [ + { content: 'Italic', type: 'em' }, + { content: 'and', type: 'normal' }, + { content: 'bold', type: 'strong' }, + { content: 'formatting', type: 'normal' }, + ], + ]; + + const output = markdownToLines(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToHTML - Basic test', () => { + const input = `This is regular text +Here is a new line +There is some words **with a bold** section +Here is a line *with an italic* section`; + + const expectedOutput = `

This is regular text
Here is a new line
There is some words with a bold section
Here is a line with an italic section

`; + + const output = markdownToHTML(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToHTML - Empty input', () => { + const input = ''; + const expectedOutput = ''; + const output = markdownToHTML(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToHTML - No formatting', () => { + const input = `This is a simple test +with no formatting`; + + const expectedOutput = `

This is a simple test
with no formatting

`; + const output = markdownToHTML(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToHTML - Only bold formatting', () => { + const input = `This is a **bold** test`; + + const expectedOutput = `

This is a bold test

`; + const output = markdownToHTML(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToHTML - Only italic formatting', () => { + const input = `This is an *italic* test`; + + const expectedOutput = `

This is an italic test

`; + const output = markdownToHTML(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToHTML - Mixed formatting', () => { + const input = `*Italic* and **bold** formatting`; + + const expectedOutput = `

Italic and bold formatting

`; + const output = markdownToHTML(input); + expect(output).toEqual(expectedOutput); +}); diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index ef69a1706..948a6d5ce 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -178,6 +178,9 @@ importers: '@braintree/sanitize-url': specifier: ^6.0.0 version: 6.0.0 + '@khanacademy/simple-markdown': + specifier: ^0.8.6 + version: 0.8.6_wcqkhtmu7mswc6yz4uyexck3ty cytoscape: specifier: ^3.23.0 version: 3.23.0 @@ -322,7 +325,7 @@ importers: version: 1.0.0 vitepress: specifier: ^1.0.0-alpha.46 - version: 1.0.0-alpha.46_tbpndr44ulefs3hehwpi2mkf2y + version: 1.0.0-alpha.46_hoyvfk3ab7nzsjkhptt6ai7rzq vitepress-plugin-search: specifier: ^1.0.4-alpha.19 version: 1.0.4-alpha.19_g67lr3vgasogkevpbew55lljzq @@ -1705,10 +1708,10 @@ packages: resolution: {integrity: sha512-6SCwI7P8ao+se1TUsdZ7B4XzL+gqeQZnBc+2EONZlcVa0dVrk0NjETxozFKgMv0eEGH8QzP1fkN+A1rH61l4eg==} dev: true - /@docsearch/js/3.3.3_tbpndr44ulefs3hehwpi2mkf2y: + /@docsearch/js/3.3.3_hoyvfk3ab7nzsjkhptt6ai7rzq: resolution: {integrity: sha512-2xAv2GFuHzzmG0SSZgf8wHX0qZX8n9Y1ZirKUk5Wrdc+vH9CL837x2hZIUdwcPZI9caBA+/CzxsS68O4waYjUQ==} dependencies: - '@docsearch/react': 3.3.3_tbpndr44ulefs3hehwpi2mkf2y + '@docsearch/react': 3.3.3_hoyvfk3ab7nzsjkhptt6ai7rzq preact: 10.11.0 transitivePeerDependencies: - '@algolia/client-search' @@ -1717,7 +1720,7 @@ packages: - react-dom dev: true - /@docsearch/react/3.3.3_tbpndr44ulefs3hehwpi2mkf2y: + /@docsearch/react/3.3.3_hoyvfk3ab7nzsjkhptt6ai7rzq: resolution: {integrity: sha512-pLa0cxnl+G0FuIDuYlW+EBK6Rw2jwLw9B1RHIeS4N4s2VhsfJ/wzeCi3CWcs5yVfxLd5ZK50t//TMA5e79YT7Q==} peerDependencies: '@types/react': '>= 16.8.0 < 19.0.0' @@ -1735,6 +1738,8 @@ packages: '@algolia/autocomplete-preset-algolia': 1.7.4_qs6lk5nhygj2o3hj4sf6xnr724 '@docsearch/css': 3.3.3 algoliasearch: 4.14.2 + react: 16.14.0 + react-dom: 16.14.0_react@16.14.0 transitivePeerDependencies: - '@algolia/client-search' dev: true @@ -2464,6 +2469,17 @@ packages: '@jridgewell/sourcemap-codec': 1.4.14 dev: true + /@khanacademy/simple-markdown/0.8.6_wcqkhtmu7mswc6yz4uyexck3ty: + resolution: {integrity: sha512-mAUlR9lchzfqunR89pFvNI51jQKsMpJeWYsYWw0DQcUXczn/T/V6510utgvm7X0N3zN87j1SvuKk8cMbl9IAFw==} + peerDependencies: + react: 16.14.0 + react-dom: 16.14.0 + dependencies: + '@types/react': 18.0.28 + react: 16.14.0 + react-dom: 16.14.0_react@16.14.0 + dev: false + /@leichtgewicht/ip-codec/2.0.4: resolution: {integrity: sha512-Hcv+nVC0kZnQ3tD9GVu5xSMR4VVYOteQIr/hwFPVEvPdlXqgGEuRjiheChHgdM+JyqdgNcmzZOX/tnl0JOiI7A==} dev: true @@ -3047,6 +3063,10 @@ packages: resolution: {integrity: sha512-ri0UmynRRvZiiUJdiz38MmIblKK+oH30MztdBVR95dv/Ubw6neWSb8u1XpRb72L4qsZOhz+L+z9JD40SJmfWow==} dev: true + /@types/prop-types/15.7.5: + resolution: {integrity: sha512-JCB8C6SnDoQf0cNycqd/35A7MjcnK+ZTqE7judS6o7utxUCg6imJg3QK2qzHKszlTjcj2cn+NwMB2i96ubpj7w==} + dev: false + /@types/qs/6.9.7: resolution: {integrity: sha512-FGa1F62FT09qcrueBA6qYTrJPVDzah9a+493+o2PCXsesWHIn27G98TsSMs3WPNbZIEj4+VJf6saSFpvD+3Zsw==} dev: true @@ -3055,6 +3075,14 @@ packages: resolution: {integrity: sha512-EEhsLsD6UsDM1yFhAvy0Cjr6VwmpMWqFBCb9w07wVugF7w9nfajxLuVmngTIpgS6svCnm6Vaw+MZhoDCKnOfsw==} dev: true + /@types/react/18.0.28: + resolution: {integrity: sha512-RD0ivG1kEztNBdoAK7lekI9M+azSnitIn85h4iOiaLjaTrMjzslhaqCGaI4IyCJ1RljWiLCEu4jyrLLgqxBTew==} + dependencies: + '@types/prop-types': 15.7.5 + '@types/scheduler': 0.16.2 + csstype: 3.1.1 + dev: false + /@types/responselike/1.0.0: resolution: {integrity: sha512-85Y2BjiufFzaMIlvJDvTTB8Fxl2xfLo4HgmHzVBz08w4wDePCTjYw66PdrolO0kzli3yam/YCgRufyo1DdQVTA==} dependencies: @@ -3072,6 +3100,10 @@ packages: rollup: 2.79.1 dev: true + /@types/scheduler/0.16.2: + resolution: {integrity: sha512-hppQEBDmlwhFAXKJX2KnWLYu5yMfi91yazPb2l+lbJiwW+wdo1gNeRA+3RgNSO39WYX2euey41KEwnqesU2Jew==} + dev: false + /@types/semver/7.3.12: resolution: {integrity: sha512-WwA1MW0++RfXmCr12xeYOOC5baSC9mSb0ZqCquFzKhcoF4TvHu5MKOuXsncgZcpVFhB1pXd5hZmM0ryAoCp12A==} dev: true @@ -5248,6 +5280,10 @@ packages: resolution: {integrity: sha512-Z1PhmomIfypOpoMjRQB70jfvy/wxT50qW08YXO5lMIJkrdq4yOTR+AW7FqutScmB9NkLwxo+jU+kZLbofZZq/w==} dev: true + /csstype/3.1.1: + resolution: {integrity: sha512-DJR/VvkAvSZW9bTouZue2sSxDwdTN92uHjqeKVm+0dAqdfNykRzQ95tay8aXMBAAPpUiq4Qcug2L7neoRh2Egw==} + dev: false + /cypress-image-snapshot/4.0.1_cypress@12.5.1+jest@29.3.1: resolution: {integrity: sha512-PBpnhX/XItlx3/DAk5ozsXQHUi72exybBNH5Mpqj1DVmjq+S5Jd9WE5CRa4q5q0zuMZb2V2VpXHth6MjFpgj9Q==} engines: {node: '>=8'} @@ -8213,7 +8249,6 @@ packages: /js-tokens/4.0.0: resolution: {integrity: sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==} - dev: true /js-yaml/3.14.1: resolution: {integrity: sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==} @@ -8621,6 +8656,12 @@ packages: resolution: {integrity: sha512-cHlYSUpL2s7Fb3394mYxwTYj8niTaNHUCLr0qdiCXQfSjfuA7CKofpX2uSwEfFDQ0EB7JcnMnm+GjbqqoinYYg==} dev: true + /loose-envify/1.4.0: + resolution: {integrity: sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==} + hasBin: true + dependencies: + js-tokens: 4.0.0 + /loupe/2.3.6: resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} dependencies: @@ -9432,7 +9473,6 @@ packages: /object-assign/4.1.1: resolution: {integrity: sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==} engines: {node: '>=0.10.0'} - dev: true /object-inspect/1.12.2: resolution: {integrity: sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==} @@ -9943,6 +9983,13 @@ packages: sisteransi: 1.0.5 dev: true + /prop-types/15.8.1: + resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + react-is: 16.13.1 + /proxy-addr/2.0.7: resolution: {integrity: sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==} engines: {node: '>= 0.10'} @@ -10055,6 +10102,20 @@ packages: unpipe: 1.0.0 dev: true + /react-dom/16.14.0_react@16.14.0: + resolution: {integrity: sha512-1gCeQXDLoIqMgqD3IO2Ah9bnf0w9kzhwN5q4FGnHZ67hBm9yePzB5JJAIQCc8x3pFnNlwFq4RidZggNAAkzWWw==} + peerDependencies: + react: ^16.14.0 + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + prop-types: 15.8.1 + react: 16.14.0 + scheduler: 0.19.1 + + /react-is/16.13.1: + resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} + /react-is/17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} dev: true @@ -10063,6 +10124,14 @@ packages: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} dev: true + /react/16.14.0: + resolution: {integrity: sha512-0X2CImDkJGApiAlcf0ODKIneSwBPhqJawOa5wCtKbu7ZECrmS26NvtSILynQ66cgkT/RJ4LidJOc3bUESwmU8g==} + engines: {node: '>=0.10.0'} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + prop-types: 15.8.1 + /read-pkg-up/7.0.1: resolution: {integrity: sha512-zK0TB7Xd6JpCLmlLmufqykGE+/TlOePD6qKClNW7hHDKFh/J7/7gCWGR7joEQEW1bKq3a3yUZSObOoWLFQ4ohg==} engines: {node: '>=8'} @@ -10471,6 +10540,12 @@ packages: xmlchars: 2.2.0 dev: true + /scheduler/0.19.1: + resolution: {integrity: sha512-n/zwRWRYSUj0/3g/otKDRPMh6qv2SYMWNq85IEa8iZyAv8od9zDYpGSnpBEjNgcMNq6Scbu5KfIPxNF72R/2EA==} + dependencies: + loose-envify: 1.4.0 + object-assign: 4.1.1 + /schema-utils/3.1.1: resolution: {integrity: sha512-Y5PQxS4ITlC+EahLuXaY86TXfR7Dc5lw294alXOq86JAHCihAIZfqv8nNCWvaEJvaC51uN9hbLGeV0cFBdH+Fw==} engines: {node: '>= 10.13.0'} @@ -11769,16 +11844,16 @@ packages: '@types/markdown-it': 12.2.3 flexsearch: 0.7.31 markdown-it: 13.0.1 - vitepress: 1.0.0-alpha.46_tbpndr44ulefs3hehwpi2mkf2y + vitepress: 1.0.0-alpha.46_hoyvfk3ab7nzsjkhptt6ai7rzq vue: 3.2.45 dev: true - /vitepress/1.0.0-alpha.46_tbpndr44ulefs3hehwpi2mkf2y: + /vitepress/1.0.0-alpha.46_hoyvfk3ab7nzsjkhptt6ai7rzq: resolution: {integrity: sha512-HiKiHzC0iTPsRsKs8XcsMeMzCpcCt5LWcX9mpDr288Ju+nQf1G8A2+Wm44ZkBsVv4EHxFK4ChmWyZrL1OJUXpg==} hasBin: true dependencies: '@docsearch/css': 3.3.3 - '@docsearch/js': 3.3.3_tbpndr44ulefs3hehwpi2mkf2y + '@docsearch/js': 3.3.3_hoyvfk3ab7nzsjkhptt6ai7rzq '@vitejs/plugin-vue': 4.0.0_vite@4.1.1+vue@3.2.45 '@vue/devtools-api': 6.5.0 '@vueuse/core': 9.12.0_vue@3.2.45 From fd9ad95346094315b25faae5f7a210282959e027 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Wed, 22 Mar 2023 18:41:31 +0100 Subject: [PATCH 04/31] #4220 Handling paragraphs and html labels with classes in mindmaps. --- cypress/platform/knsv2.html | 19 ++++---- .../mermaid/src/diagrams/mindmap/styles.js | 7 +++ .../mermaid/src/diagrams/mindmap/svgDraw.js | 21 +++------ .../mermaid/src/rendering-util/createText.js | 35 +++++++++------ .../rendering-util/handle-markdown-text.js | 18 +++++++- .../handle-markdown-text.spec.js | 45 ++++++++++++++++++- 6 files changed, 106 insertions(+), 39 deletions(-) diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 923529074..4335beaee 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -66,21 +66,24 @@ h --3i -->a b --> d(The dog in the hog) c --> d
-
+    
 mindmap
-    id1["`Start`"]
+    id1["`**Start2**
+    second line 😎 with long text that is wrapping to the next line`"]
       id2["`Child **with bold** text`"]
       id3["`Children of which some
       is using *italic type of* text`"]
       id4[Child]
+      id5["`Child
+      Row
+      and another
+      `"]
     
-
+    
 mindmap
-    id1["`Start
-second line 😎`"]
-      id2[Child]
-      id3[Child]
-      id4[Child]
+    id1["`**Start** with
+
+    a second line 😎`"]
     
 %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
diff --git a/packages/mermaid/src/diagrams/mindmap/styles.js b/packages/mermaid/src/diagrams/mindmap/styles.js
index 986a04514..863522fdf 100644
--- a/packages/mermaid/src/diagrams/mindmap/styles.js
+++ b/packages/mermaid/src/diagrams/mindmap/styles.js
@@ -70,5 +70,12 @@ const getStyles = (options) =>
   .edge {
     fill: none;
   }
+  .mindmap-node-label {
+    dy: 1em;
+    alignment-baseline: middle;
+    text-anchor: middle;
+    dominant-baseline: middle;
+    text-align: center;
+  }
 `;
 export default getStyles;
diff --git a/packages/mermaid/src/diagrams/mindmap/svgDraw.js b/packages/mermaid/src/diagrams/mindmap/svgDraw.js
index 5394e004e..8b58c11a3 100644
--- a/packages/mermaid/src/diagrams/mindmap/svgDraw.js
+++ b/packages/mermaid/src/diagrams/mindmap/svgDraw.js
@@ -204,7 +204,7 @@ const roundedRectBkg = function (elem, node) {
  * @returns {number} The height nodes dom element
  */
 export const drawNode = function (elem, node, fullSection, conf) {
-  const htmlLabels = false;
+  const htmlLabels = conf.htmlLabels;
   const section = fullSection % (MAX_SECTIONS - 1);
   const nodeElem = elem.append('g');
   node.section = section;
@@ -217,19 +217,12 @@ export const drawNode = function (elem, node, fullSection, conf) {
 
   // Create the wrapped text element
   const textElem = nodeElem.append('g');
-  const newEl = createText(textElem, node.descr, { useHtmlLabels: htmlLabels, width: node.width });
-  // const txt = textElem.node().appendChild(newEl);
-  // const txt = textElem.append(newEl);
-  // const txt = textElem
-  //   .append('text')
-  //   .text(node.descr)
-  //   .attr('dy', '1em')
-  //   .attr('alignment-baseline', 'middle')
-  //   .attr('dominant-baseline', 'middle')
-  //   .attr('text-anchor', 'middle')
-  //   .call(wrap, node.width);
-  // const newerEl = textElem.node().appendChild(newEl);
-  // setSize(textElem);
+  const newEl = createText(textElem, node.descr, {
+    useHtmlLabels: htmlLabels,
+    width: node.width,
+    classes: 'mindmap-node-label',
+  });
+
   if (!htmlLabels) {
     textElem
       .attr('dy', '1em')
diff --git a/packages/mermaid/src/rendering-util/createText.js b/packages/mermaid/src/rendering-util/createText.js
index 47e0776e4..5eea6f4c8 100644
--- a/packages/mermaid/src/rendering-util/createText.js
+++ b/packages/mermaid/src/rendering-util/createText.js
@@ -17,19 +17,22 @@ function applyStyle(dom, styleFn) {
 /**
  * @param element
  * @param {any} node
+ * @param width
+ * @param classes
  * @returns {SVGForeignObjectElement} Node
  */
-function addHtmlSpan(element, node) {
+function addHtmlSpan(element, node, width, classes) {
   const fo = element.append('foreignObject');
-  const newEl = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
+  // const newEl = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
+  // const newEl = document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject');
   const div = fo.append('xhtml:div');
+  // const div = body.append('div');
+  // const div = fo.append('div');
 
   const label = node.label;
   const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
   div.html(
-    '' +
       label +
@@ -37,16 +40,22 @@ function addHtmlSpan(element, node) {
   );
 
   applyStyle(div, node.labelStyle);
-  div.style('display', 'inline-block');
-  const bbox = div.node().getBoundingClientRect();
+  div.style('display', 'table-cell');
+  div.style('white-space', 'nowrap');
+  div.style('max-width', width + 'px');
+  div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
+
+  let bbox = div.node().getBoundingClientRect();
+  if (bbox.width === width) {
+    div.style('display', 'table');
+    div.style('white-space', 'break-spaces');
+    div.style('width', '200px');
+    bbox = div.node().getBoundingClientRect();
+  }
+
   fo.style('width', bbox.width);
   fo.style('height', bbox.height);
 
-  const divNode = div.node();
-  window.divNode = divNode;
-  // Fix for firefox
-  div.style('white-space', 'nowrap');
-  div.attr('xmlns', 'http://www.w3.org/1999/xhtml');
   return fo.node();
 }
 
@@ -158,7 +167,7 @@ export const createText = (
       ),
       labelStyle: style.replace('fill:', 'color:'),
     };
-    let vertexNode = addHtmlSpan(el, node);
+    let vertexNode = addHtmlSpan(el, node, width, classes);
     return vertexNode;
   } else {
     const structuredText = markdownToLines(text);
diff --git a/packages/mermaid/src/rendering-util/handle-markdown-text.js b/packages/mermaid/src/rendering-util/handle-markdown-text.js
index c4d10d9dc..cd79623fe 100644
--- a/packages/mermaid/src/rendering-util/handle-markdown-text.js
+++ b/packages/mermaid/src/rendering-util/handle-markdown-text.js
@@ -1,12 +1,25 @@
 import SimpleMarkdown from '@khanacademy/simple-markdown';
 
+/**
+ *
+ * @param markdown
+ */
+function preprocessMarkdown(markdown) {
+  // 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 = withoutMultipleNewlines.replace(/^\s+/gm, '');
+  return withoutExtraSpaces;
+}
+
 /**
  *
  * @param markdown
  */
 export function markdownToLines(markdown) {
+  const preprocessedMarkdown = preprocessMarkdown(markdown);
   const mdParse = SimpleMarkdown.defaultBlockParse;
-  const syntaxTree = mdParse(markdown);
+  const syntaxTree = mdParse(preprocessedMarkdown);
 
   let lines = [[]];
   let currentLine = 0;
@@ -19,6 +32,7 @@ export function markdownToLines(markdown) {
   function processNode(node, parentType) {
     if (node.type === 'text') {
       const textLines = node.content.split('\n');
+
       textLines.forEach((textLine, index) => {
         if (index !== 0) {
           currentLine++;
@@ -62,7 +76,7 @@ export function markdownToHTML(markdown) {
    */
   function output(node) {
     if (node.type === 'text') {
-      return node.content.replace(/\n/g, '
'); + return node.content.replace(/\n/g, '
'); } else if (node.type === 'strong') { return `${node.content.map(output).join('')}`; } else if (node.type === 'em') { diff --git a/packages/mermaid/src/rendering-util/handle-markdown-text.spec.js b/packages/mermaid/src/rendering-util/handle-markdown-text.spec.js index db542543a..005dd42f6 100644 --- a/packages/mermaid/src/rendering-util/handle-markdown-text.spec.js +++ b/packages/mermaid/src/rendering-util/handle-markdown-text.spec.js @@ -95,6 +95,47 @@ test('markdownToLines - Only bold formatting', () => { expect(output).toEqual(expectedOutput); }); +test('markdownToLines - paragraph 1', () => { + const input = `**Start** with + a second line`; + + const expectedOutput = [ + [ + { content: 'Start', type: 'strong' }, + { content: 'with', type: 'normal' }, + ], + [ + { content: 'a', type: 'normal' }, + { content: 'second', type: 'normal' }, + { content: 'line', type: 'normal' }, + ], + ]; + + const output = markdownToLines(input); + expect(output).toEqual(expectedOutput); +}); + +test('markdownToLines - paragraph', () => { + const input = `**Start** with + + a second line`; + + const expectedOutput = [ + [ + { content: 'Start', type: 'strong' }, + { content: 'with', type: 'normal' }, + ], + [ + { content: 'a', type: 'normal' }, + { content: 'second', type: 'normal' }, + { content: 'line', type: 'normal' }, + ], + ]; + + const output = markdownToLines(input); + expect(output).toEqual(expectedOutput); +}); + test('markdownToLines - Only italic formatting', () => { const input = `This is an *italic* test`; @@ -134,7 +175,7 @@ Here is a new line There is some words **with a bold** section Here is a line *with an italic* section`; - const expectedOutput = `

This is regular text
Here is a new line
There is some words with a bold section
Here is a line with an italic section

`; + const expectedOutput = `

This is regular text
Here is a new line
There is some words with a bold section
Here is a line with an italic section

`; const output = markdownToHTML(input); expect(output).toEqual(expectedOutput); @@ -151,7 +192,7 @@ test('markdownToHTML - No formatting', () => { const input = `This is a simple test with no formatting`; - const expectedOutput = `

This is a simple test
with no formatting

`; + const expectedOutput = `

This is a simple test
with no formatting

`; const output = markdownToHTML(input); expect(output).toEqual(expectedOutput); }); From b3b7108d5903ed879e9300abea20dab116002cf5 Mon Sep 17 00:00:00 2001 From: Jeremy Funk Date: Wed, 22 Mar 2023 23:15:54 +0100 Subject: [PATCH 05/31] Implement basic repeating tasks --- packages/mermaid/src/config.type.ts | 1 + .../src/diagrams/gantt/ganttRenderer.js | 78 +++++++++++++++---- 2 files changed, 62 insertions(+), 17 deletions(-) diff --git a/packages/mermaid/src/config.type.ts b/packages/mermaid/src/config.type.ts index 157304149..c1f94055b 100644 --- a/packages/mermaid/src/config.type.ts +++ b/packages/mermaid/src/config.type.ts @@ -335,6 +335,7 @@ export interface GanttDiagramConfig extends BaseDiagramConfig { axisFormat?: string; tickInterval?: string; topAxis?: boolean; + verticalDisplayMode?: 'default' | 'merged' | 'compact'; } export interface SequenceDiagramConfig extends BaseDiagramConfig { diff --git a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js index 7a012beb5..53266a5f4 100644 --- a/packages/mermaid/src/diagrams/gantt/ganttRenderer.js +++ b/packages/mermaid/src/diagrams/gantt/ganttRenderer.js @@ -24,12 +24,31 @@ export const setConf = function () { log.debug('Something is calling, setConf, remove the call'); }; +const getMaxIntersections = (tasks, orderOffset) => { + let timeline = [...tasks].map(() => 0); + let sorted = [...tasks].sort((a, b) => a.startTime - b.startTime || a.order - b.order); + let maxIntersections = 0; + for (const element of sorted) { + for (let j = 0; j < timeline.length; j++) { + if (element.startTime >= timeline[j]) { + timeline[j] = element.endTime; + element.order = j + orderOffset; + if (j > maxIntersections) { + maxIntersections = j; + } + break; + } + } + } + + return maxIntersections; +}; + let w; export const draw = function (text, id, version, diagObj) { const conf = getConfig().gantt; // diagObj.db.clear(); // parser.parse(text); - const securityLevel = getConfig().securityLevel; // Handle root and Document for when rendering in sandbox mode let sandboxElement; @@ -56,7 +75,41 @@ export const draw = function (text, id, version, diagObj) { const taskArray = diagObj.db.getTasks(); // Set height based on number of tasks - const h = taskArray.length * (conf.barHeight + conf.barGap) + 2 * conf.topPadding; + + conf.verticalDisplayMode = 'compact'; + + let categories = []; + + for (const element of taskArray) { + categories.push(element.type); + } + + const catsUnfiltered = categories; // for vert labels + + categories = checkUnique(categories); + const categoryHeights = {}; + + let h = 2 * conf.topPadding; + if (conf.verticalDisplayMode === undefined || conf.verticalDisplayMode === 'default') { + h = taskArray.length * (conf.barHeight + conf.barGap); + } else if (conf.verticalDisplayMode === 'compact') { + const categoryElements = {}; + for (const element of taskArray) { + if (categoryElements[element.section] === undefined) { + categoryElements[element.section] = [element]; + } else { + categoryElements[element.section].push(element); + } + } + + let intersections = 0; + for (const category of Object.keys(categoryElements)) { + const categoryHeight = getMaxIntersections(categoryElements[category], intersections) + 1; + intersections += categoryHeight; + h += categoryHeight * (conf.barHeight + conf.barGap); + categoryHeights[category] = categoryHeight; + } + } // Set viewBox elem.setAttribute('viewBox', '0 0 ' + w + ' ' + h); @@ -74,16 +127,6 @@ export const draw = function (text, id, version, diagObj) { ]) .rangeRound([0, w - conf.leftPadding - conf.rightPadding]); - let categories = []; - - for (const element of taskArray) { - categories.push(element.type); - } - - const catsUnfiltered = categories; // for vert labels - - categories = checkUnique(categories); - /** * @param a * @param b @@ -158,10 +201,14 @@ export const draw = function (text, id, version, diagObj) { */ function drawRects(theArray, theGap, theTopPad, theSidePad, theBarHeight, theColorScale, w) { // Draw background rects covering the entire width of the graph, these form the section rows. + + const uniqueTaskOrderIds = [...new Set(theArray.map((item) => item.order))]; + const uniqueTasks = uniqueTaskOrderIds.map((id) => theArray.find((item) => item.order === id)); + svg .append('g') .selectAll('rect') - .data(theArray) + .data(uniqueTasks) .enter() .append('rect') .attr('x', 0) @@ -582,12 +629,9 @@ export const draw = function (text, id, version, diagObj) { * @param theTopPad */ function vertLabels(theGap, theTopPad) { - const numOccurances = []; let prevGap = 0; - for (const [i, category] of categories.entries()) { - numOccurances[i] = [category, getCount(category, catsUnfiltered)]; - } + const numOccurances = Object.keys(categoryHeights).map((d) => [d, categoryHeights[d]]); svg .append('g') // without doing this, impossible to put grid lines behind text From a535fe1679944a1cb608b2aa9b909c10fe26af75 Mon Sep 17 00:00:00 2001 From: Jeremy Funk Date: Thu, 23 Mar 2023 22:38:04 +0100 Subject: [PATCH 06/31] Bugfixes, refactor, add compact --- demos/gantt.html | 33 +++++++- docs/config/setup/modules/defaultConfig.md | 2 +- packages/mermaid/src/config.type.ts | 2 +- packages/mermaid/src/defaultConfig.ts | 14 ++++ .../mermaid/src/diagrams/gantt/ganttDb.js | 12 +++ .../src/diagrams/gantt/ganttRenderer.js | 78 ++++++++----------- .../src/diagrams/gantt/parser/gantt.jison | 9 ++- 7 files changed, 96 insertions(+), 54 deletions(-) diff --git a/demos/gantt.html b/demos/gantt.html index 613dc8694..aa855a650 100644 --- a/demos/gantt.html +++ b/demos/gantt.html @@ -78,7 +78,7 @@ axisFormat %d/%m todayMarker off section Section1 - Today: 1, -01:00, 5min + Today: 1, 08-08-09-01:00, 5min

@@ -89,7 +89,7 @@ axisFormat %d/%m todayMarker stroke-width:5px,stroke:#00f,opacity:0.5 section Section1 - Today: 1, -01:00, 5min + Today: 1, 08-08-09-01:00, 5min

@@ -166,6 +166,35 @@

+
+      gantt
+        title GANTT compact
+        dateFormat  HH:mm:ss
+        axisFormat  %Hh%M
+        compact
+
+        section DB Clean
+        Clean: 12:00:00 ,  10m
+        Clean: 12:30:00 ,  12m
+        Clean: 13:00:00 ,  8m
+        Clean: 13:30:00 ,  9m
+        Clean: 14:00:00 ,  13m
+        Clean: 14:30:00 ,  10m
+        Clean: 15:00:00 ,  11m
+
+        section Sessions
+        A: 12:00:00 ,  63m
+        B: 12:30:00 ,  12m
+        C: 13:05:00 ,  12m
+        D: 13:06:00 ,  33m
+        E: 13:15:00 ,  55m
+        F: 13:20:00 ,  12m
+        G: 13:32:00 ,  18m
+        H: 13:50:00 ,  20m
+        I: 14:10:00 ,  10m
+    
+
+ + + diff --git a/packages/mermaid/src/diagram-api/diagram-orchestration.ts b/packages/mermaid/src/diagram-api/diagram-orchestration.ts index 73bfcf084..a88a34f19 100644 --- a/packages/mermaid/src/diagram-api/diagram-orchestration.ts +++ b/packages/mermaid/src/diagram-api/diagram-orchestration.ts @@ -13,7 +13,7 @@ import classDiagramV2 from '../diagrams/class/classDetector-V2'; import state from '../diagrams/state/stateDetector'; import stateV2 from '../diagrams/state/stateDetector-V2'; import journey from '../diagrams/user-journey/journeyDetector'; -import error from '../diagrams/error/errorDetector'; +import errorDiagram from '../diagrams/error/errorDiagram'; import flowchartElk from '../diagrams/flowchart/elk/detector'; import timeline from '../diagrams/timeline/detector'; import mindmap from '../diagrams/mindmap/detector'; @@ -28,6 +28,9 @@ export const addDiagrams = () => { // This is added here to avoid race-conditions. // We could optimize the loading logic somehow. hasLoadedDiagrams = true; + registerDiagram('error', errorDiagram, (text) => { + return text.toLowerCase().trim() === 'error'; + }); registerDiagram( '---', // --- diagram type may appear if YAML front-matter is not parsed correctly @@ -57,7 +60,6 @@ export const addDiagrams = () => { ); // Ordering of detectors is important. The first one to return true will be used. registerLazyLoadedDiagrams( - error, c4, classDiagramV2, classDiagram, diff --git a/packages/mermaid/src/diagrams/error/errorDetector.ts b/packages/mermaid/src/diagrams/error/errorDetector.ts deleted file mode 100644 index 2bdcd7028..000000000 --- a/packages/mermaid/src/diagrams/error/errorDetector.ts +++ /dev/null @@ -1,20 +0,0 @@ -import type { DiagramDetector, ExternalDiagramDefinition } from '../../diagram-api/types'; - -const id = 'error'; - -const detector: DiagramDetector = (text) => { - return text.toLowerCase().trim() === 'error'; -}; - -const loader = async () => { - const { diagram } = await import('./errorDiagram'); - return { id, diagram }; -}; - -const plugin: ExternalDiagramDefinition = { - id, - detector, - loader, -}; - -export default plugin; diff --git a/packages/mermaid/src/diagrams/error/errorDiagram.ts b/packages/mermaid/src/diagrams/error/errorDiagram.ts index d081e1028..7b9f18c3b 100644 --- a/packages/mermaid/src/diagrams/error/errorDiagram.ts +++ b/packages/mermaid/src/diagrams/error/errorDiagram.ts @@ -19,3 +19,5 @@ export const diagram: DiagramDefinition = { // no op }, }; + +export default diagram; diff --git a/packages/mermaid/src/diagrams/error/errorRenderer.ts b/packages/mermaid/src/diagrams/error/errorRenderer.ts index 60877cb8d..046bcfdcf 100644 --- a/packages/mermaid/src/diagrams/error/errorRenderer.ts +++ b/packages/mermaid/src/diagrams/error/errorRenderer.ts @@ -4,15 +4,13 @@ import { select } from 'd3'; import { log } from '../../logger'; import { getErrorMessage } from '../../utils'; -let conf = {}; - /** * Merges the value of `conf` with the passed `cnf` * * @param cnf - Config to merge */ -export const setConf = function (cnf: any) { - conf = { ...conf, ...cnf }; +export const setConf = function () { + // no-op }; /** @@ -78,7 +76,7 @@ export const draw = (_text: string, id: string, mermaidVersion: string) => { .attr('y', 250) .attr('font-size', '150px') .style('text-anchor', 'middle') - .text('Syntax error in graph'); + .text('Syntax error in text'); g.append('text') // text label for the x axis .attr('class', 'error-text') .attr('x', 1250) diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts index dba629477..520bf8b4e 100644 --- a/packages/mermaid/src/mermaidAPI.ts +++ b/packages/mermaid/src/mermaidAPI.ts @@ -534,6 +534,10 @@ const render = async function ( attachFunctions(); + if (parseEncounteredException) { + throw parseEncounteredException; + } + // ------------------------------------------------------------------------------- // Remove the temporary HTML element if appropriate const tmpElementSelector = isSandboxed ? iFrameID_selector : enclosingDivID_selector; @@ -542,10 +546,6 @@ const render = async function ( node.remove(); } - if (parseEncounteredException) { - throw parseEncounteredException; - } - return { svg: svgCode, bindFunctions: diag.db.bindFunctions, From 7739302ee80961f27a30d2c39d5494ca94fad54d Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Thu, 30 Mar 2023 23:28:41 +0530 Subject: [PATCH 24/31] fix uncaughexception in tests --- cypress/integration/rendering/errorDiagram.spec.js | 9 +++++++++ cypress/platform/viewer.js | 6 ++---- 2 files changed, 11 insertions(+), 4 deletions(-) diff --git a/cypress/integration/rendering/errorDiagram.spec.js b/cypress/integration/rendering/errorDiagram.spec.js index 89b8403a4..e837565d3 100644 --- a/cypress/integration/rendering/errorDiagram.spec.js +++ b/cypress/integration/rendering/errorDiagram.spec.js @@ -1,6 +1,15 @@ import { imgSnapshotTest } from '../../helpers/util'; describe('Error Diagrams', () => { + beforeEach(() => { + cy.on('uncaught:exception', (err) => { + expect(err.message).to.include('Parse error'); + // return false to prevent the error from + // failing this test + return false; + }); + }); + it('should render a simple ER diagram', () => { imgSnapshotTest( ` diff --git a/cypress/platform/viewer.js b/cypress/platform/viewer.js index 2e1093519..99533192d 100644 --- a/cypress/platform/viewer.js +++ b/cypress/platform/viewer.js @@ -47,7 +47,6 @@ const contentLoaded = async function () { await mermaid2.registerExternalDiagrams([externalExample]); mermaid2.initialize(graphObj.mermaid); await mermaid2.run(); - markRendered(); } }; @@ -123,7 +122,6 @@ const contentLoadedApi = async function () { bindFunctions(div); } } - markRendered(); }; if (typeof document !== 'undefined') { @@ -135,10 +133,10 @@ if (typeof document !== 'undefined') { function () { if (this.location.href.match('xss.html')) { this.console.log('Using api'); - void contentLoadedApi(); + void contentLoadedApi().finally(markRendered); } else { this.console.log('Not using api'); - void contentLoaded(); + void contentLoaded().finally(markRendered); } }, false From d16894daf492acb50d028a089c50b6381d21fddf Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 31 Mar 2023 00:18:53 +0530 Subject: [PATCH 25/31] test: add space before init --- .../mermaid/src/diagram-api/comments.spec.ts | 18 ++++++++++-------- 1 file changed, 10 insertions(+), 8 deletions(-) diff --git a/packages/mermaid/src/diagram-api/comments.spec.ts b/packages/mermaid/src/diagram-api/comments.spec.ts index 2435db0a0..4357b25c3 100644 --- a/packages/mermaid/src/diagram-api/comments.spec.ts +++ b/packages/mermaid/src/diagram-api/comments.spec.ts @@ -29,6 +29,7 @@ graph TD %% This is another comment %%{init: {'theme': 'forest'}}%% +%%{ init: {'theme': 'space before init'}}%% %%{init: {'theme': 'space after ending'}}%% graph TD A-->B @@ -37,17 +38,18 @@ graph TD %% This is a comment `; expect(cleanupComments(text)).toMatchInlineSnapshot(` - " + " - %%{init: {'theme': 'forest'}}%% - %%{init: {'theme': 'space after ending'}}%% - graph TD - A-->B + %%{init: {'theme': 'forest'}}%% + %%{ init: {'theme': 'space before init'}}%% + %%{init: {'theme': 'space after ending'}}%% + graph TD + A-->B - B-->C + B-->C - " - `); + " + `); }); it('should remove indented comments', () => { From 1945a629900c39877c4de123244c205e23509d3c Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 31 Mar 2023 00:25:33 +0530 Subject: [PATCH 26/31] fix: trimStart to text --- .../mermaid/src/diagram-api/comments.spec.ts | 22 +++++++++---------- packages/mermaid/src/diagram-api/comments.ts | 2 +- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/mermaid/src/diagram-api/comments.spec.ts b/packages/mermaid/src/diagram-api/comments.spec.ts index 4357b25c3..366ba119c 100644 --- a/packages/mermaid/src/diagram-api/comments.spec.ts +++ b/packages/mermaid/src/diagram-api/comments.spec.ts @@ -14,13 +14,13 @@ graph TD %% This is a comment `; expect(cleanupComments(text)).toMatchInlineSnapshot(` - " + " - graph TD - A-->B + graph TD + A-->B - " - `); + " + `); }); it('should keep init statements when removing comments', () => { @@ -61,12 +61,12 @@ graph TD C-->D `; expect(cleanupComments(text)).toMatchInlineSnapshot(` - " - graph TD - A-->B + " + graph TD + A-->B - C-->D - " - `); + C-->D + " + `); }); }); diff --git a/packages/mermaid/src/diagram-api/comments.ts b/packages/mermaid/src/diagram-api/comments.ts index 19fafe15c..2ee6232de 100644 --- a/packages/mermaid/src/diagram-api/comments.ts +++ b/packages/mermaid/src/diagram-api/comments.ts @@ -4,5 +4,5 @@ * @returns cleaned text */ export const cleanupComments = (text: string): string => { - return text.replace(/^\s*%%(?!{)[^\n]+/gm, ''); + return text.trimStart().replace(/^\s*%%(?!{)[^\n]+/gm, ''); }; From 006da82470c59989f7ce93968e9f773d2b54d164 Mon Sep 17 00:00:00 2001 From: Sidharth Vinod Date: Fri, 31 Mar 2023 00:35:56 +0530 Subject: [PATCH 27/31] fix: Remove comment line completely --- .../mermaid/src/diagram-api/comments.spec.ts | 44 ++++++++++++++----- packages/mermaid/src/diagram-api/comments.ts | 2 +- 2 files changed, 34 insertions(+), 12 deletions(-) diff --git a/packages/mermaid/src/diagram-api/comments.spec.ts b/packages/mermaid/src/diagram-api/comments.spec.ts index 366ba119c..a2c896079 100644 --- a/packages/mermaid/src/diagram-api/comments.spec.ts +++ b/packages/mermaid/src/diagram-api/comments.spec.ts @@ -14,11 +14,8 @@ graph TD %% This is a comment `; expect(cleanupComments(text)).toMatchInlineSnapshot(` - " - - graph TD + "graph TD A-->B - " `); }); @@ -38,16 +35,13 @@ graph TD %% This is a comment `; expect(cleanupComments(text)).toMatchInlineSnapshot(` - " - - %%{init: {'theme': 'forest'}}%% + "%%{init: {'theme': 'forest'}}%% %%{ init: {'theme': 'space before init'}}%% %%{init: {'theme': 'space after ending'}}%% graph TD A-->B B-->C - " `); }); @@ -61,11 +55,39 @@ graph TD C-->D `; expect(cleanupComments(text)).toMatchInlineSnapshot(` + "graph TD + A-->B + C-->D " - graph TD - A-->B + `); + }); - C-->D + it('should remove empty newlines from start', () => { + const text = ` + + + + +%% This is a comment +graph TD + A-->B +`; + expect(cleanupComments(text)).toMatchInlineSnapshot(` + "graph TD + A-->B + " + `); + }); + + it('should remove comments at end of text with no newline', () => { + const text = ` +graph TD + A-->B +%% This is a comment`; + + expect(cleanupComments(text)).toMatchInlineSnapshot(` + "graph TD + A-->B " `); }); diff --git a/packages/mermaid/src/diagram-api/comments.ts b/packages/mermaid/src/diagram-api/comments.ts index 2ee6232de..be39b0a0f 100644 --- a/packages/mermaid/src/diagram-api/comments.ts +++ b/packages/mermaid/src/diagram-api/comments.ts @@ -4,5 +4,5 @@ * @returns cleaned text */ export const cleanupComments = (text: string): string => { - return text.trimStart().replace(/^\s*%%(?!{)[^\n]+/gm, ''); + return text.trimStart().replace(/^\s*%%(?!{)[^\n]+\n?/gm, ''); }; From f9c0f1d46f412b18409cc4b136e84efa625f1ab4 Mon Sep 17 00:00:00 2001 From: knsv Date: Fri, 31 Mar 2023 06:46:34 +0000 Subject: [PATCH 28/31] Update docs --- docs/config/setup/modules/mermaidAPI.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 5a5a63786..9bc6d3056 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:659](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L659) +[mermaidAPI.ts:660](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L660) ## Functions From 99f65813a1bcf7c36d5107c9d1b7e05d38096b98 Mon Sep 17 00:00:00 2001 From: Knut Sveidqvist Date: Mon, 3 Apr 2023 10:43:15 +0200 Subject: [PATCH 29/31] Syntax for markdown strings is a single backtick. --- cypress/platform/knsv2.html | 70 ++++++++++++------- .../interfaces/mermaidAPI.ParseOptions.md | 2 +- .../interfaces/mermaidAPI.RenderResult.md | 4 +- docs/config/setup/modules/mermaidAPI.md | 22 +++--- packages/mermaid/src/dagre-wrapper/nodes.js | 1 - .../src/diagrams/flowchart/parser/flow.jison | 6 +- .../src/diagrams/mindmap/parser/mindmap.jison | 6 +- packages/mermaid/src/mermaidAPI.ts | 9 ++- 8 files changed, 73 insertions(+), 47 deletions(-) diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html index 02a5ff5e6..4c160ca1b 100644 --- a/cypress/platform/knsv2.html +++ b/cypress/platform/knsv2.html @@ -58,10 +58,30 @@
+flowchart LR
+    A:::someclass --> B[`The **cat** in the hat`]:::someclass
+    id1(Start)-->id2(Stop)
+    style id1 fill:#f9f,stroke:#333,stroke-width:4px
+    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
+    classDef someclass fill:#f96
+
+
+%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
+%%
+flowchart LR
+    A:::someclass --> B[`The **cat** in the hat`]:::someclass
+    id1(Start)-->id2(Stop)
+    style id1 fill:#f9f,stroke:#333,stroke-width:4px
+    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
+    classDef someclass fill:#f96
+
+
 %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
 %%
 graph LR
-  a{"`The **cat** in the hat`"} -- 1o --> b
+  a{`The **cat** in the hat`} -- 1o --> b
   a -- 2o --> c
   a -- 3o --> d
   g --2i--> a
@@ -74,30 +94,30 @@ graph LR
     
       %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
 flowchart LR
-b("`The dog in **the** hog.(1)
-NL`") --"`1o **bold**`"--> c
+b(`The dog in **the** hog.(1)
+NL`) --`1o **bold**`--> c
 
 flowchart-elk LR
-b("`The dog in **the** hog.(1)
-NL`") --"`1o **bold**`"--> c
+b(`The dog in **the** hog.(1)
+NL`) --`1o **bold**`--> c
 
 flowchart-elk LR
-b("`The dog in **the** hog.(1).. a a a a *very long text* about it
+b(`The dog in **the** hog.(1).. a a a a *very long text* about it
 Word!
 
-Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `") --> c
+Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `) --> c
 
       %%{init: {"flowchart": {"htmlLabels": true}} }%%
 flowchart-elk LR
-b("`The dog in **the** hog(2)... a a a a *very long text* about it
+b(`The dog in **the** hog(2)... a a a a *very long text* about it
 Word!
-Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `")
+Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `)
 
@@ -113,34 +133,34 @@ b("The dog in the hog... a very
long text about it
Word!")
 flowchart-elk LR
 subgraph "One"
-  a("`The **cat**
-  in the hat`") -- "1o" --> b{{"`The **dog** in the hog`"}}
+  a(`The **cat**
+  in the hat`) -- "1o" --> b{{`The **dog** in the hog`}}
 end
-subgraph "`**Two**`"
-  c("`The **cat**
-  in the hat`") -- "`1o **ipa**`" --> d("The dog in the hog")
+subgraph `**Two**`
+  c(`The **cat**
+  in the hat`) -- `1o **ipa**` --> d("The dog in the hog")
 end
 
 mindmap
-    id1["`**Start2**
-    second line 😎 with long text that is wrapping to the next line`"]
-      id2["`Child **with bold** text`"]
-      id3["`Children of which some
-      is using *italic type of* text`"]
+    id1[`**Start2**
+    second line 😎 with long text that is wrapping to the next line`]
+      id2[`Child **with bold** text`]
+      id3[`Children of which some
+      is using *italic type of* text`]
       id4[Child]
-      id5["`Child
+      id5[`Child
       Row
       and another
-      `"]
+      `]
     
 mindmap
-    id1["`**Start** with
-    a second line 😎`"]
-      id2["`The dog in **the** hog... a *very long text* about it
-Word!`"]
+    id1[`**Start** with
+    a second line 😎`]
+      id2[`The dog in **the** hog... a *very long text* about it
+Word!`]
     
 %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
diff --git a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md
index 93708863c..8ab259885 100644
--- a/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md
+++ b/docs/config/setup/interfaces/mermaidAPI.ParseOptions.md
@@ -16,4 +16,4 @@
 
 #### Defined in
 
-[mermaidAPI.ts:70](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L70)
+[mermaidAPI.ts:77](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L77)
diff --git a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md
index ec59f8796..f84a51b87 100644
--- a/docs/config/setup/interfaces/mermaidAPI.RenderResult.md
+++ b/docs/config/setup/interfaces/mermaidAPI.RenderResult.md
@@ -39,7 +39,7 @@ bindFunctions?.(div); // To call bindFunctions only if it's present.
 
 #### Defined in
 
-[mermaidAPI.ts:91](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L91)
+[mermaidAPI.ts:98](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L98)
 
 ---
 
@@ -51,4 +51,4 @@ The svg code for the rendered graph.
 
 #### Defined in
 
-[mermaidAPI.ts:81](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L81)
+[mermaidAPI.ts:88](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L88)
diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md
index b94fc8b94..e2f036785 100644
--- a/docs/config/setup/modules/mermaidAPI.md
+++ b/docs/config/setup/modules/mermaidAPI.md
@@ -25,7 +25,7 @@ Renames and re-exports [mermaidAPI](mermaidAPI.md#mermaidapi)
 
 #### Defined in
 
-[mermaidAPI.ts:75](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L75)
+[mermaidAPI.ts:82](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L82)
 
 ## Variables
 
@@ -95,7 +95,7 @@ mermaid.initialize(config);
 
 #### Defined in
 
-[mermaidAPI.ts:662](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L662)
+[mermaidAPI.ts:669](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L669)
 
 ## Functions
 
@@ -126,7 +126,7 @@ Return the last node appended
 
 #### Defined in
 
-[mermaidAPI.ts:305](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L305)
+[mermaidAPI.ts:312](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L312)
 
 ---
 
@@ -152,7 +152,7 @@ the cleaned up svgCode
 
 #### Defined in
 
-[mermaidAPI.ts:256](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L256)
+[mermaidAPI.ts:263](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L263)
 
 ---
 
@@ -178,7 +178,7 @@ the string with all the user styles
 
 #### Defined in
 
-[mermaidAPI.ts:185](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L185)
+[mermaidAPI.ts:192](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L192)
 
 ---
 
@@ -201,7 +201,7 @@ the string with all the user styles
 
 #### Defined in
 
-[mermaidAPI.ts:233](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L233)
+[mermaidAPI.ts:240](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L240)
 
 ---
 
@@ -228,7 +228,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
 
 #### Defined in
 
-[mermaidAPI.ts:169](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L169)
+[mermaidAPI.ts:176](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L176)
 
 ---
 
@@ -248,7 +248,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
 
 #### Defined in
 
-[mermaidAPI.ts:149](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L149)
+[mermaidAPI.ts:156](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L156)
 
 ---
 
@@ -268,7 +268,7 @@ with an enclosing block that has each of the cssClasses followed by !important;
 
 #### Defined in
 
-[mermaidAPI.ts:120](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L120)
+[mermaidAPI.ts:127](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L127)
 
 ---
 
@@ -294,7 +294,7 @@ Put the svgCode into an iFrame. Return the iFrame code
 
 #### Defined in
 
-[mermaidAPI.ts:284](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L284)
+[mermaidAPI.ts:291](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L291)
 
 ---
 
@@ -319,4 +319,4 @@ Remove any existing elements from the given document
 
 #### Defined in
 
-[mermaidAPI.ts:355](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L355)
+[mermaidAPI.ts:362](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L362)
diff --git a/packages/mermaid/src/dagre-wrapper/nodes.js b/packages/mermaid/src/dagre-wrapper/nodes.js
index dd0225770..3e71f500a 100644
--- a/packages/mermaid/src/dagre-wrapper/nodes.js
+++ b/packages/mermaid/src/dagre-wrapper/nodes.js
@@ -997,7 +997,6 @@ export const insertNode = (elem, node, dir) => {
     el.attr('class', 'node default ' + node.class);
   }
 
-  /* MC: 7e790808-9c49-4f74-93de-15c22872377f */
   nodeElems[node.id] = newEl;
 
   if (node.haveCallback) {
diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
index 19ab80b05..dd82cff4d 100644
--- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
+++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison
@@ -38,9 +38,9 @@ accDescr\s*"{"\s*                                { this.begin("acc_descr_multili
 [\}]                       { this.popState(); }
 [^\}]*                     return "acc_descr_multiline_value";
 // .*[^\n]*                    {  return "acc_descr_line"}
-["][`]          { this.begin("md_string");}
-[^`"]+        { return "MD_STR";}
-[`]["]          { this.popState();}
+[`]          { this.begin("md_string");}
+[^`]+        { return "MD_STR";}
+[`]          { this.popState();}
 ["]                     this.begin("string");
 ["]             this.popState();
 [^"]*           return "STR";
diff --git a/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison
index 84a6191cf..41ee33c91 100644
--- a/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison
+++ b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison
@@ -42,9 +42,9 @@
 // !(-\()            return 'NODE_ID';
 [^\(\[\n\-\)\{\}]+         return 'NODE_ID';
 <>            return 'EOF';
-["][`]          { this.begin("NSTR2");}
-[^`"]+        { return "NODE_DESCR";}
-[`]["]          { this.popState();}
+[`]          { this.begin("NSTR2");}
+[^`]+        { return "NODE_DESCR";}
+[`]          { this.popState();}
 ["]          { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");}
 [^"]+        { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";}
 ["]          {this.popState();}
diff --git a/packages/mermaid/src/mermaidAPI.ts b/packages/mermaid/src/mermaidAPI.ts
index dba629477..039e8c596 100644
--- a/packages/mermaid/src/mermaidAPI.ts
+++ b/packages/mermaid/src/mermaidAPI.ts
@@ -32,7 +32,14 @@ import { setA11yDiagramInfo, addSVGa11yTitleDescription } from './accessibility'
 import { parseDirective } from './directiveUtils';
 
 // diagram names that support classDef statements
-const CLASSDEF_DIAGRAMS = ['graph', 'flowchart', 'flowchart-v2', 'stateDiagram', 'stateDiagram-v2'];
+const CLASSDEF_DIAGRAMS = [
+  'graph',
+  'flowchart',
+  'flowchart-v2',
+  'flowchart-elk',
+  'stateDiagram',
+  'stateDiagram-v2',
+];
 const MAX_TEXTLENGTH = 50_000;
 const MAX_TEXTLENGTH_EXCEEDED_MSG =
   'graph TB;a[Maximum text size in diagram exceeded];style a fill:#faa';

From 471c842a58204d19f1e71492db09474b6025ec2a Mon Sep 17 00:00:00 2001
From: Knut Sveidqvist 
Date: Mon, 3 Apr 2023 12:12:51 +0200
Subject: [PATCH 30/31] Adding rendering tests and unit tests

---
 .../rendering/flowchart-elk.spec.js           | 145 ++++++++++++++++++
 .../rendering/flowchart-v2.spec.js            | 145 ++++++++++++++++++
 cypress/integration/rendering/mindmap.spec.ts |  13 ++
 cypress/platform/knsv2.html                   |  98 ++----------
 .../flowchart/parser/flow-md-string.spec.js   |  64 ++++++++
 .../src/diagrams/flowchart/parser/flow.jison  |   6 +-
 .../src/diagrams/mindmap/mindmapRenderer.js   |   5 +-
 .../src/diagrams/mindmap/parser/mindmap.jison |   6 +-
 8 files changed, 392 insertions(+), 90 deletions(-)
 create mode 100644 packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js

diff --git a/cypress/integration/rendering/flowchart-elk.spec.js b/cypress/integration/rendering/flowchart-elk.spec.js
index 414037651..68d8b3ce5 100644
--- a/cypress/integration/rendering/flowchart-elk.spec.js
+++ b/cypress/integration/rendering/flowchart-elk.spec.js
@@ -684,4 +684,149 @@ A --> B
       { titleTopMargin: 0 }
     );
   });
+  describe('Markdown strings flowchart-elk (#4220)', () => {
+    describe('html labels', () => {
+      it('With styling and classes', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart-elk LR
+    A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
+    id1(Start)-->id2(Stop)
+    style id1 fill:#f9f,stroke:#333,stroke-width:4px
+    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
+    classDef someclass fill:#f96
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('With formatting in a node', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart-elk LR
+  a{"\`The **cat** in the hat\`"} -- 1o --> b
+  a -- 2o --> c
+  a -- 3o --> d
+  g --2i--> a
+  d --1i--> a
+  h --3i -->a
+  b --> d(The dog in the hog)
+  c --> d
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('New line in node and formatted edge label', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart-elk LR
+b("\`The dog in **the** hog.(1)
+NL\`") --"\`1o **bold**\`"--> c
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('Wrapping long text with a new line', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart-elk LR
+b(\`The dog in **the** hog.(1).. a a a a *very long text* about it
+Word!
+
+Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`) --> c
+
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('Sub graphs and markdown strings', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart-elk LR
+subgraph "One"
+  a("\`The **cat**
+  in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
+end
+subgraph "\`**Two**\`"
+  c("\`The **cat**
+  in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
+end
+
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+    });
+
+    describe('svg text labels', () => {
+      it('With styling and classes', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart-elk LR
+    A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
+    id1(Start)-->id2(Stop)
+    style id1 fill:#f9f,stroke:#333,stroke-width:4px
+    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
+    classDef someclass fill:#f96
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('With formatting in a node', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart-elk LR
+  a{"\`The **cat** in the hat\`"} -- 1o --> b
+  a -- 2o --> c
+  a -- 3o --> d
+  g --2i--> a
+  d --1i--> a
+  h --3i -->a
+  b --> d(The dog in the hog)
+  c --> d
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('New line in node and formatted edge label', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart-elk LR
+b("\`The dog in **the** hog.(1)
+NL\`") --"\`1o **bold**\`"--> c
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('Wrapping long text with a new line', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart-elk LR
+b("\`The dog in **the** hog.(1).. a a a a *very long text* about it
+Word!
+
+Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
+
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('Sub graphs and markdown strings', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart-elk LR
+subgraph "One"
+  a("\`The **cat**
+  in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
+end
+subgraph "\`**Two**\`"
+  c("\`The **cat**
+  in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
+end
+
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+    });
+  });
 });
diff --git a/cypress/integration/rendering/flowchart-v2.spec.js b/cypress/integration/rendering/flowchart-v2.spec.js
index abdb22265..4513cc87d 100644
--- a/cypress/integration/rendering/flowchart-v2.spec.js
+++ b/cypress/integration/rendering/flowchart-v2.spec.js
@@ -685,4 +685,149 @@ A ~~~ B
       { titleTopMargin: 0 }
     );
   });
+  describe('Markdown strings flowchart (#4220)', () => {
+    describe('html labels', () => {
+      it('With styling and classes', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart LR
+    A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
+    id1(Start)-->id2(Stop)
+    style id1 fill:#f9f,stroke:#333,stroke-width:4px
+    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
+    classDef someclass fill:#f96
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('With formatting in a node', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart LR
+  a{"\`The **cat** in the hat\`"} -- 1o --> b
+  a -- 2o --> c
+  a -- 3o --> d
+  g --2i--> a
+  d --1i--> a
+  h --3i -->a
+  b --> d(The dog in the hog)
+  c --> d
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('New line in node and formatted edge label', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart LR
+b("\`The dog in **the** hog.(1)
+NL\`") --"\`1o **bold**\`"--> c
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('Wrapping long text with a new line', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart LR
+b("\`The dog in **the** hog.(1).. a a a a *very long text* about it
+Word!
+
+Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
+
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('Sub graphs and markdown strings', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": true}} }%%
+flowchart LR
+subgraph "One"
+  a("\`The **cat**
+  in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
+end
+subgraph "\`**Two**\`"
+  c("\`The **cat**
+  in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
+end
+
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+    });
+
+    describe('svg text labels', () => {
+      it('With styling and classes', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart LR
+    A:::someclass --> B["\`The **cat** in the hat\`"]:::someclass
+    id1(Start)-->id2(Stop)
+    style id1 fill:#f9f,stroke:#333,stroke-width:4px
+    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
+    classDef someclass fill:#f96
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('With formatting in a node', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart LR
+  a{"\`The **cat** in the hat\`"} -- 1o --> b
+  a -- 2o --> c
+  a -- 3o --> d
+  g --2i--> a
+  d --1i--> a
+  h --3i -->a
+  b --> d(The dog in the hog)
+  c --> d
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('New line in node and formatted edge label', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart LR
+b("\`The dog in **the** hog.(1)
+NL\`") --"\`1o **bold**\`"--> c
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('Wrapping long text with a new line', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart LR
+b("\`The dog in **the** hog.(1).. a a a a *very long text* about it
+Word!
+
+Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. \`") --> c
+
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+      it('Sub graphs and markdown strings', () => {
+        imgSnapshotTest(
+          `%%{init: {"flowchart": {"htmlLabels": false}} }%%
+flowchart LR
+subgraph "One"
+  a("\`The **cat**
+  in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}}
+end
+subgraph "\`**Two**\`"
+  c("\`The **cat**
+  in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog")
+end
+
+`,
+          { titleTopMargin: 0 }
+        );
+      });
+    });
+  });
 });
diff --git a/cypress/integration/rendering/mindmap.spec.ts b/cypress/integration/rendering/mindmap.spec.ts
index 4663f6225..94b3f9ca0 100644
--- a/cypress/integration/rendering/mindmap.spec.ts
+++ b/cypress/integration/rendering/mindmap.spec.ts
@@ -223,5 +223,18 @@ mindmap
       shouldHaveRoot
     );
   });
+  describe('Markdown strings mindmaps (#4220)', () => {
+    it('Formatted label with linebreak and a wrapping label and emojis', () => {
+      imgSnapshotTest(
+        `mindmap
+    id1[\`**Start** with
+    a second line 😎\`]
+      id2[\`The dog in **the** hog... a *very long text* about it
+Word!\`]
+`,
+        { titleTopMargin: 0 }
+      );
+    });
+  });
   /* The end */
 });
diff --git a/cypress/platform/knsv2.html b/cypress/platform/knsv2.html
index 4c160ca1b..08a4c0e68 100644
--- a/cypress/platform/knsv2.html
+++ b/cypress/platform/knsv2.html
@@ -58,91 +58,23 @@
   
   
     
-flowchart LR
-    A:::someclass --> B[`The **cat** in the hat`]:::someclass
-    id1(Start)-->id2(Stop)
-    style id1 fill:#f9f,stroke:#333,stroke-width:4px
-    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
-    classDef someclass fill:#f96
-
-
-%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
-%%
-flowchart LR
-    A:::someclass --> B[`The **cat** in the hat`]:::someclass
-    id1(Start)-->id2(Stop)
-    style id1 fill:#f9f,stroke:#333,stroke-width:4px
-    style id2 fill:#bbf,stroke:#f66,stroke-width:2px,color:#fff,stroke-dasharray: 5 5
-    classDef someclass fill:#f96
-
-
-%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
-%%
-graph LR
-  a{`The **cat** in the hat`} -- 1o --> b
-  a -- 2o --> c
-  a -- 3o --> d
-  g --2i--> a
-  d --1i--> a
-  h --3i -->a
-  b --> d(The dog in the hog)
-  c --> d
-
-
-      %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
-flowchart LR
-b(`The dog in **the** hog.(1)
-NL`) --`1o **bold**`--> c
-
-
-flowchart-elk LR
-b(`The dog in **the** hog.(1)
-NL`) --`1o **bold**`--> c
-
-
-flowchart-elk LR
-b(`The dog in **the** hog.(1).. a a a a *very long text* about it
-Word!
-
-Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `) --> c
-
-
-      %%{init: {"flowchart": {"htmlLabels": true}} }%%
+      %%{init: {"flowchart": {"htmlLabels": false}} }%%
 flowchart-elk LR
 b(`The dog in **the** hog(2)... a a a a *very long text* about it
 Word!
 Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. Another line with many, many words. `)
 
-
-      %%{init: {"flowchart": {"htmlLabels": false}} }%%
+    
 flowchart-elk LR
 b("The dog in the hog... a very
long text about it
Word!")
-
-      %%{init: {"flowchart": {"htmlLabels": true}} }%%
+    
 flowchart-elk LR
 b("The dog in the hog... a very
long text about it
Word!")
-
-flowchart-elk LR
-subgraph "One"
-  a(`The **cat**
-  in the hat`) -- "1o" --> b{{`The **dog** in the hog`}}
-end
-subgraph `**Two**`
-  c(`The **cat**
-  in the hat`) -- `1o **ipa**` --> d("The dog in the hog")
-end
-
-
+    
 mindmap
     id1[`**Start2**
     second line 😎 with long text that is wrapping to the next line`]
@@ -157,12 +89,12 @@ mindmap
     
 mindmap
-    id1[`**Start** with
-    a second line 😎`]
-      id2[`The dog in **the** hog... a *very long text* about it
-Word!`]
+    id1["`**Start** with
+    a second line 😎`"]
+      id2["`The dog in **the** hog... a *very long text* about it
+Word!`"]
     
-
+    
 %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%
 flowchart TB
   %% I could not figure out how to use double quotes in labels in Mermaid
@@ -218,7 +150,7 @@ flowchart TB
 

-
+    
 flowchart TB
   %% I could not figure out how to use double quotes in labels in Mermaid
   subgraph ibm[IBM Espresso CPU]
@@ -274,7 +206,7 @@ flowchart TB
     >
     
  -
+    
       flowchart LR
   B1 --be be--x B2
   B1 --bo bo--o B3
@@ -307,7 +239,7 @@ flowchart TB
   B6 --> B5
   
-
+    
 sequenceDiagram
     Customer->>+Stripe: Makes a payment request
     Stripe->>+Bank: Forwards the payment request to the bank
@@ -320,7 +252,7 @@ sequenceDiagram
     Customer->>+Merchant: Receives goods or services
         
-
+    
 mindmap
   root((mindmap))
     Origins
@@ -340,7 +272,7 @@ mindmap
       Mermaid
     

-
+    
   example-diagram
     
@@ -368,7 +300,7 @@ mindmap htmlLabels: false, // htmlLabels: true, }, - htmlLabels: false, + // htmlLabels: true, gantt: { useMaxWidth: false, }, diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js b/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js new file mode 100644 index 000000000..005d257e0 --- /dev/null +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow-md-string.spec.js @@ -0,0 +1,64 @@ +import flowDb from '../flowDb'; +import flow from './flow'; +import { setConfig } from '../../../config'; + +setConfig({ + securityLevel: 'strict', +}); + +describe('parsing a flow chart with markdown strings', function () { + beforeEach(function () { + flow.parser.yy = flowDb; + flow.parser.yy.clear(); + }); + + it('mardown formatting in nodes and labels', function () { + const res = flow.parser.parse(`flowchart +A["\`The cat in **the** hat\`"]-- "\`The *bat* in the chat\`" -->B["The dog in the hog"] -- "The rat in the mat" -->C;`); + + const vert = flow.parser.yy.getVertices(); + const edges = flow.parser.yy.getEdges(); + + expect(vert['A'].id).toBe('A'); + expect(vert['A'].text).toBe('The cat in **the** hat'); + expect(vert['A'].labelType).toBe('markdown'); + expect(vert['B'].id).toBe('B'); + expect(vert['B'].text).toBe('The dog in the hog'); + expect(vert['B'].labelType).toBe('text'); + expect(edges.length).toBe(2); + expect(edges[0].start).toBe('A'); + expect(edges[0].end).toBe('B'); + expect(edges[0].type).toBe('arrow_point'); + expect(edges[0].text).toBe('The *bat* in the chat'); + expect(edges[0].labelType).toBe('markdown'); + expect(edges[1].start).toBe('B'); + expect(edges[1].end).toBe('C'); + expect(edges[1].type).toBe('arrow_point'); + expect(edges[1].text).toBe('The rat in the mat'); + expect(edges[1].labelType).toBe('text'); + }); + it('mardown formatting in subgraphs', function () { + const res = flow.parser.parse(`flowchart LR +subgraph "One" + a("\`The **cat** + in the hat\`") -- "1o" --> b{{"\`The **dog** in the hog\`"}} +end +subgraph "\`**Two**\`" + c("\`The **cat** + in the hat\`") -- "\`1o **ipa**\`" --> d("The dog in the hog") +end`); + + const subgraphs = flow.parser.yy.getSubGraphs(); + expect(subgraphs.length).toBe(2); + const subgraph = subgraphs[0]; + + expect(subgraph.nodes.length).toBe(2); + expect(subgraph.title).toBe('One'); + expect(subgraph.labelType).toBe('text'); + + const subgraph2 = subgraphs[1]; + expect(subgraph2.nodes.length).toBe(2); + expect(subgraph2.title).toBe('**Two**'); + expect(subgraph2.labelType).toBe('markdown'); + }); +}); diff --git a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison index dd82cff4d..19ab80b05 100644 --- a/packages/mermaid/src/diagrams/flowchart/parser/flow.jison +++ b/packages/mermaid/src/diagrams/flowchart/parser/flow.jison @@ -38,9 +38,9 @@ accDescr\s*"{"\s* { this.begin("acc_descr_multili [\}] { this.popState(); } [^\}]* return "acc_descr_multiline_value"; // .*[^\n]* { return "acc_descr_line"} -[`] { this.begin("md_string");} -[^`]+ { return "MD_STR";} -[`] { this.popState();} +["][`] { this.begin("md_string");} +[^`"]+ { return "MD_STR";} +[`]["] { this.popState();} ["] this.begin("string"); ["] this.popState(); [^"]* return "STR"; diff --git a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js index c5b5fede1..a2a4def59 100644 --- a/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js +++ b/packages/mermaid/src/diagrams/mindmap/mindmapRenderer.js @@ -167,12 +167,15 @@ function positionNodes(cy) { export const draw = async (text, id, version, diagObj) => { const conf = getConfig(); + // console.log('Config: ', conf); + conf.htmlLabels = false; + // This is done only for throwing the error if the text is not valid. diagObj.db.clear(); // Parse the graph definition diagObj.parser.parse(text); - log.debug('Renering info diagram\n' + text); + log.debug('Renering mindmap diagram\n' + text, diagObj); const securityLevel = getConfig().securityLevel; // Handle root and Document for when rendering in sandbox mode diff --git a/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison index 41ee33c91..84a6191cf 100644 --- a/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison +++ b/packages/mermaid/src/diagrams/mindmap/parser/mindmap.jison @@ -42,9 +42,9 @@ // !(-\() return 'NODE_ID'; [^\(\[\n\-\)\{\}]+ return 'NODE_ID'; <> return 'EOF'; -[`] { this.begin("NSTR2");} -[^`]+ { return "NODE_DESCR";} -[`] { this.popState();} +["][`] { this.begin("NSTR2");} +[^`"]+ { return "NODE_DESCR";} +[`]["] { this.popState();} ["] { yy.getLogger().trace('Starting NSTR');this.begin("NSTR");} [^"]+ { yy.getLogger().trace('description:', yytext); return "NODE_DESCR";} ["] {this.popState();} From c777f9193d1c488e74a72f9447cb657895638b6d Mon Sep 17 00:00:00 2001 From: knsv Date: Mon, 3 Apr 2023 10:35:19 +0000 Subject: [PATCH 31/31] Update docs --- docs/config/setup/modules/defaultConfig.md | 2 +- docs/config/setup/modules/mermaidAPI.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/config/setup/modules/defaultConfig.md b/docs/config/setup/modules/defaultConfig.md index 6493a93db..ad8f90248 100644 --- a/docs/config/setup/modules/defaultConfig.md +++ b/docs/config/setup/modules/defaultConfig.md @@ -14,7 +14,7 @@ #### Defined in -[defaultConfig.ts:2105](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2105) +[defaultConfig.ts:2115](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/defaultConfig.ts#L2115) --- diff --git a/docs/config/setup/modules/mermaidAPI.md b/docs/config/setup/modules/mermaidAPI.md index 91ee30e2b..c09402dbd 100644 --- a/docs/config/setup/modules/mermaidAPI.md +++ b/docs/config/setup/modules/mermaidAPI.md @@ -96,7 +96,7 @@ mermaid.initialize(config); #### Defined in -[mermaidAPI.ts:669](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L669) +[mermaidAPI.ts:667](https://github.com/mermaid-js/mermaid/blob/master/packages/mermaid/src/mermaidAPI.ts#L667) ## Functions