Add katex support

This commit is contained in:
Ashish Jain 2024-06-20 13:21:48 +02:00
parent 1096b185ee
commit e07fdfedb6
7 changed files with 70 additions and 36 deletions

View File

@ -8,6 +8,8 @@ import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdo
import { decodeEntities } from '../utils.js'; import { decodeEntities } from '../utils.js';
import { splitLineToFitWidth } from './splitText.js'; import { splitLineToFitWidth } from './splitText.js';
import type { MarkdownLine, MarkdownWord } from './types.js'; import type { MarkdownLine, MarkdownWord } from './types.js';
import common, { renderKatex } from '$root/diagrams/common/common.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js';
function applyStyle(dom, styleFn) { function applyStyle(dom, styleFn) {
if (styleFn) { if (styleFn) {
@ -15,11 +17,16 @@ function applyStyle(dom, styleFn) {
} }
} }
function addHtmlSpan(element, node, width, classes, addBackground = false) { async function addHtmlSpan(element, node, width, classes, addBackground = false) {
const fo = element.append('foreignObject'); const fo = element.append('foreignObject');
const div = fo.append('xhtml:div'); const div = fo.append('xhtml:div');
const label = node.label; // const label = node.label;
let label = '';
if (node.label) {
label = await renderKatex(node.label.replace(common.lineBreakRegex, '\n'), getConfig());
}
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel'; const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
div.html( div.html(
`<span class="${labelClass} ${classes}" ` + `<span class="${labelClass} ${classes}" ` +
@ -184,7 +191,7 @@ export function replaceIconSubstring(text: string) {
// 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' // 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' // When not using htmlLabels => to set classes to 'title-row' when isTitle=true otherwise 'title-row'
export const createText = ( export const createText = async (
el, el,
text = '', text = '',
{ {
@ -218,7 +225,7 @@ export const createText = (
label: decodedReplacedText, label: decodedReplacedText,
labelStyle: style.replace('fill:', 'color:'), labelStyle: style.replace('fill:', 'color:'),
}; };
const vertexNode = addHtmlSpan(el, node, width, classes, addSvgBackground); const vertexNode = await addHtmlSpan(el, node, width, classes, addSvgBackground);
return vertexNode; return vertexNode;
} else { } else {
const structuredText = markdownToLines(text, config); const structuredText = markdownToLines(text, config);

View File

@ -120,20 +120,36 @@ const recursiveRender = async (_elem, graph, diagramType, id, parentCluster, sit
}) })
); );
// Insert labels, this will insert them into the dom so that the width can be calculated const processEdges = async () => {
// Also figure out which edges point to/from clusters and adjust them accordingly const edgePromises = graph.edges().map(async function (e) {
// Edges from/to clusters really points to the first child in the cluster.
// TODO: pick optimal child in the cluster to us as link anchor
graph.edges().forEach(function (e) {
const edge = graph.edge(e.v, e.w, e.name); const edge = graph.edge(e.v, e.w, e.name);
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e)); log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e))); log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
// Check if link is either from or to a cluster // Check if link is either from or to a cluster
log.info('Fix', clusterDb, 'ids:', e.v, e.w, 'Translating: ', clusterDb[e.v], clusterDb[e.w]); log.info('Fix', clusterDb, 'ids:', e.v, e.w, 'Translating: ', clusterDb[e.v], clusterDb[e.w]);
insertEdgeLabel(edgeLabels, edge); await insertEdgeLabel(edgeLabels, edge);
}); });
await Promise.all(edgePromises);
};
await processEdges();
// // Insert labels, this will insert them into the dom so that the width can be calculated
// // Also figure out which edges point to/from clusters and adjust them accordingly
// // Edges from/to clusters really points to the first child in the cluster.
// // TODO: pick optimal child in the cluster to us as link anchor
// await graph.edges().forEach(async function (e) {
// const edge = graph.edge(e.v, e.w, e.name);
// log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
// log.info('Edge ' + e.v + ' -> ' + e.w + ': ', e, ' ', JSON.stringify(graph.edge(e)));
// // Check if link is either from or to a cluster
// log.info('Fix', clusterDb, 'ids:', e.v, e.w, 'Translating: ', clusterDb[e.v], clusterDb[e.w]);
// await insertEdgeLabel(edgeLabels, edge);
// });
graph.edges().forEach(function (e) { graph.edges().forEach(function (e) {
log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e)); log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
}); });

View File

@ -10,7 +10,7 @@ import createLabel from './createLabel.js';
import { createRoundedRectPathD } from './shapes/roundedRectPath.ts'; import { createRoundedRectPathD } from './shapes/roundedRectPath.ts';
import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js'; import { userNodeOverrides } from '$root/rendering-util/rendering-elements/shapes/handdrawnStyles.js';
const rect = (parent, node) => { const rect = async (parent, node) => {
log.info('Creating subgraph rect for ', node.id, node); log.info('Creating subgraph rect for ', node.id, node);
const siteConfig = getConfig(); const siteConfig = getConfig();
const { themeVariables, handdrawnSeed } = siteConfig; const { themeVariables, handdrawnSeed } = siteConfig;
@ -29,8 +29,8 @@ const rect = (parent, node) => {
// .appendChild(createLabel(node.label, node.labelStyle, undefined, true)); // .appendChild(createLabel(node.label, node.labelStyle, undefined, true));
const text = const text =
node.labelType === 'markdown' node.labelType === 'markdown'
? createText(labelEl, node.label, { style: node.labelStyle, useHtmlLabels }) ? await createText(labelEl, node.label, { style: node.labelStyle, useHtmlLabels })
: labelEl.node().appendChild(createLabel(node.label, node.labelStyle, undefined, true)); : labelEl.node().appendChild(await createLabel(node.label, node.labelStyle, undefined, true));
// Get the size of the label // Get the size of the label
let bbox = text.getBBox(); let bbox = text.getBBox();
@ -154,7 +154,7 @@ const noteGroup = (parent, node) => {
return { cluster: shapeSvg, labelBBox: { width: 0, height: 0 } }; return { cluster: shapeSvg, labelBBox: { width: 0, height: 0 } };
}; };
const roundedWithTitle = (parent, node) => { const roundedWithTitle = async (parent, node) => {
const siteConfig = getConfig(); const siteConfig = getConfig();
const { themeVariables, handdrawnSeed } = siteConfig; const { themeVariables, handdrawnSeed } = siteConfig;
@ -177,7 +177,9 @@ const roundedWithTitle = (parent, node) => {
const label = shapeSvg.insert('g').attr('class', 'cluster-label'); const label = shapeSvg.insert('g').attr('class', 'cluster-label');
let innerRect = shapeSvg.append('rect'); let innerRect = shapeSvg.append('rect');
const text = label.node().appendChild(createLabel(node.label, node.labelStyle, undefined, true)); const text = label
.node()
.appendChild(await createLabel(node.label, node.labelStyle, undefined, true));
// Get the size of the label // Get the size of the label
let bbox = text.getBBox(); let bbox = text.getBBox();

View File

@ -1,7 +1,7 @@
import { select } from 'd3'; import { select } from 'd3';
import { log } from '$root/logger.js'; import { log } from '$root/logger.js';
import { getConfig } from '$root/diagram-api/diagramAPI.js'; import { getConfig } from '$root/diagram-api/diagramAPI.js';
import { evaluate } from '$root/diagrams/common/common.js'; import common, { evaluate, renderKatex } from '$root/diagrams/common/common.js';
import { decodeEntities } from '$root/utils.js'; import { decodeEntities } from '$root/utils.js';
/** /**
@ -18,11 +18,14 @@ function applyStyle(dom, styleFn) {
* @param {any} node * @param {any} node
* @returns {SVGForeignObjectElement} Node * @returns {SVGForeignObjectElement} Node
*/ */
function addHtmlLabel(node) { async function addHtmlLabel(node) {
const fo = select(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject')); const fo = select(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'));
const div = fo.append('xhtml:div'); const div = fo.append('xhtml:div');
const label = node.label; let label = node.label;
if (node.label) {
label = await renderKatex(node.label.replace(common.lineBreakRegex, '\n'), getConfig());
}
const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel'; const labelClass = node.isNode ? 'nodeLabel' : 'edgeLabel';
div.html( div.html(
'<span class="' + '<span class="' +
@ -49,11 +52,12 @@ function addHtmlLabel(node) {
* @param isNode * @param isNode
* @deprecated svg-util/createText instead * @deprecated svg-util/createText instead
*/ */
const createLabel = (_vertexText, style, isTitle, isNode) => { const createLabel = async (_vertexText, style, isTitle, isNode) => {
let vertexText = _vertexText || ''; let vertexText = _vertexText || '';
if (typeof vertexText === 'object') { if (typeof vertexText === 'object') {
vertexText = vertexText[0]; vertexText = vertexText[0];
} }
if (evaluate(getConfig().flowchart.htmlLabels)) { if (evaluate(getConfig().flowchart.htmlLabels)) {
// TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that? // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
vertexText = vertexText.replace(/\\n|\n/g, '<br />'); vertexText = vertexText.replace(/\\n|\n/g, '<br />');
@ -66,7 +70,7 @@ const createLabel = (_vertexText, style, isTitle, isNode) => {
), ),
labelStyle: style ? style.replace('fill:', 'color:') : style, labelStyle: style ? style.replace('fill:', 'color:') : style,
}; };
let vertexNode = addHtmlLabel(node); let vertexNode = await addHtmlLabel(node);
// vertexNode.parentNode.removeChild(vertexNode); // vertexNode.parentNode.removeChild(vertexNode);
return vertexNode; return vertexNode;
} else { } else {

View File

@ -19,17 +19,17 @@ export const clear = () => {
terminalLabels = {}; terminalLabels = {};
}; };
export const insertEdgeLabel = (elem, edge) => { export const insertEdgeLabel = async (elem, edge) => {
const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels); const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
// Create the actual text element // Create the actual text element
const labelElement = const labelElement =
edge.labelType === 'markdown' edge.labelType === 'markdown'
? createText(elem, edge.label, { ? await createText(elem, edge.label, {
style: edge.labelStyle, style: edge.labelStyle,
useHtmlLabels, useHtmlLabels,
addSvgBackground: true, addSvgBackground: true,
}) })
: createLabel(edge.label, edge.labelStyle); : await createLabel(edge.label, edge.labelStyle);
log.info('abc82', edge, edge.labelType); log.info('abc82', edge, edge.labelType);
// Create outer g, edgeLabel, this will be positioned after graph layout // Create outer g, edgeLabel, this will be positioned after graph layout
@ -60,7 +60,7 @@ export const insertEdgeLabel = (elem, edge) => {
let fo; let fo;
if (edge.startLabelLeft) { if (edge.startLabelLeft) {
// Create the actual text element // Create the actual text element
const startLabelElement = createLabel(edge.startLabelLeft, edge.labelStyle); const startLabelElement = await createLabel(edge.startLabelLeft, edge.labelStyle);
const startEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals'); const startEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
const inner = startEdgeLabelLeft.insert('g').attr('class', 'inner'); const inner = startEdgeLabelLeft.insert('g').attr('class', 'inner');
fo = inner.node().appendChild(startLabelElement); fo = inner.node().appendChild(startLabelElement);
@ -74,7 +74,7 @@ export const insertEdgeLabel = (elem, edge) => {
} }
if (edge.startLabelRight) { if (edge.startLabelRight) {
// Create the actual text element // Create the actual text element
const startLabelElement = createLabel(edge.startLabelRight, edge.labelStyle); const startLabelElement = await createLabel(edge.startLabelRight, edge.labelStyle);
const startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals'); const startEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = startEdgeLabelRight.insert('g').attr('class', 'inner'); const inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
fo = startEdgeLabelRight.node().appendChild(startLabelElement); fo = startEdgeLabelRight.node().appendChild(startLabelElement);
@ -90,7 +90,7 @@ export const insertEdgeLabel = (elem, edge) => {
} }
if (edge.endLabelLeft) { if (edge.endLabelLeft) {
// Create the actual text element // Create the actual text element
const endLabelElement = createLabel(edge.endLabelLeft, edge.labelStyle); const endLabelElement = await createLabel(edge.endLabelLeft, edge.labelStyle);
const endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals'); const endEdgeLabelLeft = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner'); const inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
fo = inner.node().appendChild(endLabelElement); fo = inner.node().appendChild(endLabelElement);
@ -107,7 +107,7 @@ export const insertEdgeLabel = (elem, edge) => {
} }
if (edge.endLabelRight) { if (edge.endLabelRight) {
// Create the actual text element // Create the actual text element
const endLabelElement = createLabel(edge.endLabelRight, edge.labelStyle); const endLabelElement = await createLabel(edge.endLabelRight, edge.labelStyle);
const endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals'); const endEdgeLabelRight = elem.insert('g').attr('class', 'edgeTerminals');
const inner = endEdgeLabelRight.insert('g').attr('class', 'inner'); const inner = endEdgeLabelRight.insert('g').attr('class', 'inner');

View File

@ -34,7 +34,7 @@ export const rectWithTitle = async (parent: SVGElement, node: Node) => {
const title = node.label; const title = node.label;
const text = label.node().appendChild(createLabel(title, node.labelStyle, true, true)); const text = label.node().appendChild(await createLabel(title, node.labelStyle, true, true));
let bbox = { width: 0, height: 0 }; let bbox = { width: 0, height: 0 };
if (evaluate(getConfig()?.flowchart?.htmlLabels)) { if (evaluate(getConfig()?.flowchart?.htmlLabels)) {
const div = text.children[0]; const div = text.children[0];
@ -49,7 +49,12 @@ export const rectWithTitle = async (parent: SVGElement, node: Node) => {
const descr = label const descr = label
.node() .node()
.appendChild( .appendChild(
createLabel(textRows.join ? textRows.join('<br/>') : textRows, node.labelStyle, true, true) await createLabel(
textRows.join ? textRows.join('<br/>') : textRows,
node.labelStyle,
true,
true
)
); );
if (evaluate(getConfig()?.flowchart?.htmlLabels)) { if (evaluate(getConfig()?.flowchart?.htmlLabels)) {

View File

@ -31,7 +31,7 @@ export const labelHelper = async (parent, node, _classes) => {
} }
let text; let text;
text = createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), { text = await createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), {
useHtmlLabels, useHtmlLabels,
width: node.width || getConfig().flowchart.wrappingWidth, width: node.width || getConfig().flowchart.wrappingWidth,
cssClasses: 'markdown-node-label', cssClasses: 'markdown-node-label',