#4220 Handling paragraphs and html labels with classes in mindmaps.

This commit is contained in:
Knut Sveidqvist
2023-03-22 18:41:31 +01:00
parent a1c50b8079
commit fd9ad95346
6 changed files with 106 additions and 39 deletions

View File

@@ -66,21 +66,24 @@ h --3i -->a
b --> d(The dog in the hog) b --> d(The dog in the hog)
c --> d c --> d
</pre> </pre>
<pre id="diagram" class="mermaid"> <pre id="diagram" class="mermaid2">
mindmap mindmap
id1["`Start`"] id1["`**Start2**
second line 😎 with long text that is wrapping to the next line`"]
id2["`Child **with bold** text`"] id2["`Child **with bold** text`"]
id3["`Children of which some id3["`Children of which some
is using *italic type of* text`"] is using *italic type of* text`"]
id4[Child] id4[Child]
id5["`Child
Row
and another
`"]
</pre> </pre>
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid">
mindmap mindmap
id1["`Start id1["`**Start** with
second line 😎`"]
id2[Child] a second line 😎`"]
id3[Child]
id4[Child]
</pre> </pre>
<pre id="diagram" class="mermaid2"> <pre id="diagram" class="mermaid2">
%%{init: {"flowchart": {"defaultRenderer": "elk"}} }%% %%{init: {"flowchart": {"defaultRenderer": "elk"}} }%%

View File

@@ -70,5 +70,12 @@ const getStyles = (options) =>
.edge { .edge {
fill: none; fill: none;
} }
.mindmap-node-label {
dy: 1em;
alignment-baseline: middle;
text-anchor: middle;
dominant-baseline: middle;
text-align: center;
}
`; `;
export default getStyles; export default getStyles;

View File

@@ -204,7 +204,7 @@ const roundedRectBkg = function (elem, node) {
* @returns {number} The height nodes dom element * @returns {number} The height nodes dom element
*/ */
export const drawNode = function (elem, node, fullSection, conf) { export const drawNode = function (elem, node, fullSection, conf) {
const htmlLabels = false; const htmlLabels = conf.htmlLabels;
const section = fullSection % (MAX_SECTIONS - 1); const section = fullSection % (MAX_SECTIONS - 1);
const nodeElem = elem.append('g'); const nodeElem = elem.append('g');
node.section = section; node.section = section;
@@ -217,19 +217,12 @@ export const drawNode = function (elem, node, fullSection, conf) {
// Create the wrapped text element // Create the wrapped text element
const textElem = nodeElem.append('g'); const textElem = nodeElem.append('g');
const newEl = createText(textElem, node.descr, { useHtmlLabels: htmlLabels, width: node.width }); const newEl = createText(textElem, node.descr, {
// const txt = textElem.node().appendChild(newEl); useHtmlLabels: htmlLabels,
// const txt = textElem.append(newEl); width: node.width,
// const txt = textElem classes: 'mindmap-node-label',
// .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) { if (!htmlLabels) {
textElem textElem
.attr('dy', '1em') .attr('dy', '1em')

View File

@@ -17,19 +17,22 @@ function applyStyle(dom, styleFn) {
/** /**
* @param element * @param element
* @param {any} node * @param {any} node
* @param width
* @param classes
* @returns {SVGForeignObjectElement} Node * @returns {SVGForeignObjectElement} Node
*/ */
function addHtmlSpan(element, node) { function addHtmlSpan(element, node, width, classes) {
const fo = element.append('foreignObject'); 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 = fo.append('xhtml:div');
// const div = body.append('div');
// const div = fo.append('div');
const label = node.label; const label = node.label;
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel'; const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
div.html( div.html(
'<span class="' + `<span class="${labelClass} ${classes}" ` +
labelClass +
'" ' +
(node.labelStyle ? 'style="' + node.labelStyle + '"' : '') + (node.labelStyle ? 'style="' + node.labelStyle + '"' : '') +
'>' + '>' +
label + label +
@@ -37,16 +40,22 @@ function addHtmlSpan(element, node) {
); );
applyStyle(div, node.labelStyle); applyStyle(div, node.labelStyle);
div.style('display', 'inline-block'); div.style('display', 'table-cell');
const bbox = div.node().getBoundingClientRect(); 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('width', bbox.width);
fo.style('height', bbox.height); 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(); return fo.node();
} }
@@ -158,7 +167,7 @@ export const createText = (
), ),
labelStyle: style.replace('fill:', 'color:'), labelStyle: style.replace('fill:', 'color:'),
}; };
let vertexNode = addHtmlSpan(el, node); let vertexNode = addHtmlSpan(el, node, width, classes);
return vertexNode; return vertexNode;
} else { } else {
const structuredText = markdownToLines(text); const structuredText = markdownToLines(text);

View File

@@ -1,12 +1,25 @@
import SimpleMarkdown from '@khanacademy/simple-markdown'; 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 * @param markdown
*/ */
export function markdownToLines(markdown) { export function markdownToLines(markdown) {
const preprocessedMarkdown = preprocessMarkdown(markdown);
const mdParse = SimpleMarkdown.defaultBlockParse; const mdParse = SimpleMarkdown.defaultBlockParse;
const syntaxTree = mdParse(markdown); const syntaxTree = mdParse(preprocessedMarkdown);
let lines = [[]]; let lines = [[]];
let currentLine = 0; let currentLine = 0;
@@ -19,6 +32,7 @@ export function markdownToLines(markdown) {
function processNode(node, parentType) { function processNode(node, parentType) {
if (node.type === 'text') { if (node.type === 'text') {
const textLines = node.content.split('\n'); const textLines = node.content.split('\n');
textLines.forEach((textLine, index) => { textLines.forEach((textLine, index) => {
if (index !== 0) { if (index !== 0) {
currentLine++; currentLine++;
@@ -62,7 +76,7 @@ export function markdownToHTML(markdown) {
*/ */
function output(node) { function output(node) {
if (node.type === 'text') { if (node.type === 'text') {
return node.content.replace(/\n/g, '<br>'); return node.content.replace(/\n/g, '<br/>');
} else if (node.type === 'strong') { } else if (node.type === 'strong') {
return `<strong>${node.content.map(output).join('')}</strong>`; return `<strong>${node.content.map(output).join('')}</strong>`;
} else if (node.type === 'em') { } else if (node.type === 'em') {

View File

@@ -95,6 +95,47 @@ test('markdownToLines - Only bold formatting', () => {
expect(output).toEqual(expectedOutput); 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', () => { test('markdownToLines - Only italic formatting', () => {
const input = `This is an *italic* test`; const input = `This is an *italic* test`;
@@ -134,7 +175,7 @@ Here is a new line
There is some words **with a bold** section There is some words **with a bold** section
Here is a line *with an italic* section`; Here is a line *with an italic* section`;
const expectedOutput = `<p>This is regular text<br>Here is a new line<br>There is some words <strong>with a bold</strong> section<br>Here is a line <em>with an italic</em> section</p>`; const expectedOutput = `<p>This is regular text<br/>Here is a new line<br/>There is some words <strong>with a bold</strong> section<br/>Here is a line <em>with an italic</em> section</p>`;
const output = markdownToHTML(input); const output = markdownToHTML(input);
expect(output).toEqual(expectedOutput); expect(output).toEqual(expectedOutput);
@@ -151,7 +192,7 @@ test('markdownToHTML - No formatting', () => {
const input = `This is a simple test const input = `This is a simple test
with no formatting`; with no formatting`;
const expectedOutput = `<p>This is a simple test<br>with no formatting</p>`; const expectedOutput = `<p>This is a simple test<br/>with no formatting</p>`;
const output = markdownToHTML(input); const output = markdownToHTML(input);
expect(output).toEqual(expectedOutput); expect(output).toEqual(expectedOutput);
}); });