mirror of
				https://github.com/mermaid-js/mermaid.git
				synced 2025-11-04 12:54:08 +01:00 
			
		
		
		
	Add katex support
This commit is contained in:
		@@ -8,6 +8,8 @@ import { markdownToHTML, markdownToLines } from '../rendering-util/handle-markdo
 | 
			
		||||
import { decodeEntities } from '../utils.js';
 | 
			
		||||
import { splitLineToFitWidth } from './splitText.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) {
 | 
			
		||||
  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 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';
 | 
			
		||||
  div.html(
 | 
			
		||||
    `<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'
 | 
			
		||||
// When not using htmlLabels => to set classes to 'title-row' when isTitle=true otherwise 'title-row'
 | 
			
		||||
export const createText = (
 | 
			
		||||
export const createText = async (
 | 
			
		||||
  el,
 | 
			
		||||
  text = '',
 | 
			
		||||
  {
 | 
			
		||||
@@ -218,7 +225,7 @@ export const createText = (
 | 
			
		||||
      label: decodedReplacedText,
 | 
			
		||||
      labelStyle: style.replace('fill:', 'color:'),
 | 
			
		||||
    };
 | 
			
		||||
    const vertexNode = addHtmlSpan(el, node, width, classes, addSvgBackground);
 | 
			
		||||
    const vertexNode = await addHtmlSpan(el, node, width, classes, addSvgBackground);
 | 
			
		||||
    return vertexNode;
 | 
			
		||||
  } else {
 | 
			
		||||
    const structuredText = markdownToLines(text, config);
 | 
			
		||||
 
 | 
			
		||||
@@ -120,19 +120,35 @@ 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
 | 
			
		||||
  // 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
 | 
			
		||||
  graph.edges().forEach(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)));
 | 
			
		||||
  const processEdges = async () => {
 | 
			
		||||
    const edgePromises = graph.edges().map(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]);
 | 
			
		||||
    insertEdgeLabel(edgeLabels, edge);
 | 
			
		||||
  });
 | 
			
		||||
      // 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);
 | 
			
		||||
    });
 | 
			
		||||
 | 
			
		||||
    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) {
 | 
			
		||||
    log.info('Edge ' + e.v + ' -> ' + e.w + ': ' + JSON.stringify(e));
 | 
			
		||||
 
 | 
			
		||||
@@ -10,7 +10,7 @@ import createLabel from './createLabel.js';
 | 
			
		||||
import { createRoundedRectPathD } from './shapes/roundedRectPath.ts';
 | 
			
		||||
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);
 | 
			
		||||
  const siteConfig = getConfig();
 | 
			
		||||
  const { themeVariables, handdrawnSeed } = siteConfig;
 | 
			
		||||
@@ -29,8 +29,8 @@ const rect = (parent, node) => {
 | 
			
		||||
  //   .appendChild(createLabel(node.label, node.labelStyle, undefined, true));
 | 
			
		||||
  const text =
 | 
			
		||||
    node.labelType === 'markdown'
 | 
			
		||||
      ? createText(labelEl, node.label, { style: node.labelStyle, useHtmlLabels })
 | 
			
		||||
      : labelEl.node().appendChild(createLabel(node.label, node.labelStyle, undefined, true));
 | 
			
		||||
      ? await createText(labelEl, node.label, { style: node.labelStyle, useHtmlLabels })
 | 
			
		||||
      : labelEl.node().appendChild(await createLabel(node.label, node.labelStyle, undefined, true));
 | 
			
		||||
 | 
			
		||||
  // Get the size of the label
 | 
			
		||||
  let bbox = text.getBBox();
 | 
			
		||||
@@ -154,7 +154,7 @@ const noteGroup = (parent, node) => {
 | 
			
		||||
 | 
			
		||||
  return { cluster: shapeSvg, labelBBox: { width: 0, height: 0 } };
 | 
			
		||||
};
 | 
			
		||||
const roundedWithTitle = (parent, node) => {
 | 
			
		||||
const roundedWithTitle = async (parent, node) => {
 | 
			
		||||
  const siteConfig = getConfig();
 | 
			
		||||
 | 
			
		||||
  const { themeVariables, handdrawnSeed } = siteConfig;
 | 
			
		||||
@@ -177,7 +177,9 @@ const roundedWithTitle = (parent, node) => {
 | 
			
		||||
  const label = shapeSvg.insert('g').attr('class', 'cluster-label');
 | 
			
		||||
  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
 | 
			
		||||
  let bbox = text.getBBox();
 | 
			
		||||
 
 | 
			
		||||
@@ -1,7 +1,7 @@
 | 
			
		||||
import { select } from 'd3';
 | 
			
		||||
import { log } from '$root/logger.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';
 | 
			
		||||
 | 
			
		||||
/**
 | 
			
		||||
@@ -18,11 +18,14 @@ function applyStyle(dom, styleFn) {
 | 
			
		||||
 * @param {any} node
 | 
			
		||||
 * @returns {SVGForeignObjectElement} Node
 | 
			
		||||
 */
 | 
			
		||||
function addHtmlLabel(node) {
 | 
			
		||||
async function addHtmlLabel(node) {
 | 
			
		||||
  const fo = select(document.createElementNS('http://www.w3.org/2000/svg', 'foreignObject'));
 | 
			
		||||
  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';
 | 
			
		||||
  div.html(
 | 
			
		||||
    '<span class="' +
 | 
			
		||||
@@ -49,11 +52,12 @@ function addHtmlLabel(node) {
 | 
			
		||||
 * @param isNode
 | 
			
		||||
 * @deprecated svg-util/createText instead
 | 
			
		||||
 */
 | 
			
		||||
const createLabel = (_vertexText, style, isTitle, isNode) => {
 | 
			
		||||
const createLabel = async (_vertexText, style, isTitle, isNode) => {
 | 
			
		||||
  let vertexText = _vertexText || '';
 | 
			
		||||
  if (typeof vertexText === 'object') {
 | 
			
		||||
    vertexText = vertexText[0];
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  if (evaluate(getConfig().flowchart.htmlLabels)) {
 | 
			
		||||
    // TODO: addHtmlLabel accepts a labelStyle. Do we possibly have that?
 | 
			
		||||
    vertexText = vertexText.replace(/\\n|\n/g, '<br />');
 | 
			
		||||
@@ -66,7 +70,7 @@ const createLabel = (_vertexText, style, isTitle, isNode) => {
 | 
			
		||||
      ),
 | 
			
		||||
      labelStyle: style ? style.replace('fill:', 'color:') : style,
 | 
			
		||||
    };
 | 
			
		||||
    let vertexNode = addHtmlLabel(node);
 | 
			
		||||
    let vertexNode = await addHtmlLabel(node);
 | 
			
		||||
    // vertexNode.parentNode.removeChild(vertexNode);
 | 
			
		||||
    return vertexNode;
 | 
			
		||||
  } else {
 | 
			
		||||
 
 | 
			
		||||
@@ -19,17 +19,17 @@ export const clear = () => {
 | 
			
		||||
  terminalLabels = {};
 | 
			
		||||
};
 | 
			
		||||
 | 
			
		||||
export const insertEdgeLabel = (elem, edge) => {
 | 
			
		||||
export const insertEdgeLabel = async (elem, edge) => {
 | 
			
		||||
  const useHtmlLabels = evaluate(getConfig().flowchart.htmlLabels);
 | 
			
		||||
  // Create the actual text element
 | 
			
		||||
  const labelElement =
 | 
			
		||||
    edge.labelType === 'markdown'
 | 
			
		||||
      ? createText(elem, edge.label, {
 | 
			
		||||
      ? await createText(elem, edge.label, {
 | 
			
		||||
          style: edge.labelStyle,
 | 
			
		||||
          useHtmlLabels,
 | 
			
		||||
          addSvgBackground: true,
 | 
			
		||||
        })
 | 
			
		||||
      : createLabel(edge.label, edge.labelStyle);
 | 
			
		||||
      : await createLabel(edge.label, edge.labelStyle);
 | 
			
		||||
  log.info('abc82', edge, edge.labelType);
 | 
			
		||||
 | 
			
		||||
  // Create outer g, edgeLabel, this will be positioned after graph layout
 | 
			
		||||
@@ -60,7 +60,7 @@ export const insertEdgeLabel = (elem, edge) => {
 | 
			
		||||
  let fo;
 | 
			
		||||
  if (edge.startLabelLeft) {
 | 
			
		||||
    // 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 inner = startEdgeLabelLeft.insert('g').attr('class', 'inner');
 | 
			
		||||
    fo = inner.node().appendChild(startLabelElement);
 | 
			
		||||
@@ -74,7 +74,7 @@ export const insertEdgeLabel = (elem, edge) => {
 | 
			
		||||
  }
 | 
			
		||||
  if (edge.startLabelRight) {
 | 
			
		||||
    // 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 inner = startEdgeLabelRight.insert('g').attr('class', 'inner');
 | 
			
		||||
    fo = startEdgeLabelRight.node().appendChild(startLabelElement);
 | 
			
		||||
@@ -90,7 +90,7 @@ export const insertEdgeLabel = (elem, edge) => {
 | 
			
		||||
  }
 | 
			
		||||
  if (edge.endLabelLeft) {
 | 
			
		||||
    // 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 inner = endEdgeLabelLeft.insert('g').attr('class', 'inner');
 | 
			
		||||
    fo = inner.node().appendChild(endLabelElement);
 | 
			
		||||
@@ -107,7 +107,7 @@ export const insertEdgeLabel = (elem, edge) => {
 | 
			
		||||
  }
 | 
			
		||||
  if (edge.endLabelRight) {
 | 
			
		||||
    // 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 inner = endEdgeLabelRight.insert('g').attr('class', 'inner');
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -34,7 +34,7 @@ export const rectWithTitle = async (parent: SVGElement, node: Node) => {
 | 
			
		||||
 | 
			
		||||
  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 };
 | 
			
		||||
  if (evaluate(getConfig()?.flowchart?.htmlLabels)) {
 | 
			
		||||
    const div = text.children[0];
 | 
			
		||||
@@ -49,7 +49,12 @@ export const rectWithTitle = async (parent: SVGElement, node: Node) => {
 | 
			
		||||
  const descr = label
 | 
			
		||||
    .node()
 | 
			
		||||
    .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)) {
 | 
			
		||||
 
 | 
			
		||||
@@ -31,7 +31,7 @@ export const labelHelper = async (parent, node, _classes) => {
 | 
			
		||||
  }
 | 
			
		||||
 | 
			
		||||
  let text;
 | 
			
		||||
  text = createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), {
 | 
			
		||||
  text = await createText(labelEl, sanitizeText(decodeEntities(label), getConfig()), {
 | 
			
		||||
    useHtmlLabels,
 | 
			
		||||
    width: node.width || getConfig().flowchart.wrappingWidth,
 | 
			
		||||
    cssClasses: 'markdown-node-label',
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user