// Convert mermaid entity codes to text e.g. "#9829;" to "♥" export const entityCodesToText = (input) => { input = decodeEntities(input); // Append & before the pattern #(\d+); or #([a-z]+); to convert to decimal code // so it can be rendered as html character // eg #9829; => ♥ const inputWithDecimalCode = input .replace(/#(\d+);/g, "&#$1;") .replace(/#([a-z]+);/g, "&$1;"); // Render the decimal code as html character, eg ♥ => ♥ const element = document.createElement("textarea"); element.innerHTML = inputWithDecimalCode; return element.value; }; export const getTransformAttr = (el) => { const transformAttr = el.getAttribute("transform"); const translateMatch = transformAttr?.match(/translate\(([ \d.-]+),\s*([\d.-]+)\)/); let transformX = 0; let transformY = 0; if (translateMatch) { transformX = Number(translateMatch[1]); transformY = Number(translateMatch[2]); } return { transformX, transformY }; }; //TODO Once fixed in mermaid this will be removed export const encodeEntities = (text) => { let txt = text; txt = txt.replace(/style.*:\S*#.*;/g, (s) => { return s.substring(0, s.length - 1); }); txt = txt.replace(/classDef.*:\S*#.*;/g, (s) => { return s.substring(0, s.length - 1); }); txt = txt.replace(/#\w+;/g, (s) => { const innerTxt = s.substring(1, s.length - 1); const isInt = /^\+?\d+$/.test(innerTxt); if (isInt) { return `fl°°${innerTxt}¶ß`; } return `fl°${innerTxt}¶ß`; }); return txt; }; export const decodeEntities = function (text) { return text.replace(/fl°°/g, "#").replace(/fl°/g, "&").replace(/¶ß/g, ";"); }; // Compute edge postion start, end and points (reflection points) export const computeEdgePositions = (pathElement, offset = { x: 0, y: 0 }) => { // Check if the element is a path else throw an error if (pathElement.tagName.toLowerCase() !== "path") { throw new Error(`Invalid input: Expected an HTMLElement of tag "path", got ${pathElement.tagName}`); } // Get the d attribute from the path element else throw an error const dAttr = pathElement.getAttribute("d"); if (!dAttr) { throw new Error('Path element does not contain a "d" attribute'); } // Split the d attribute based on M (Move To) and L (Line To) commands // eg "M29.383,38.5L29.383,63.5L29.383,83.2" => ["M29.383,38.5", "L29.383,63.5", "L29.383,83.2"] const commands = dAttr.split(/(?=[LM])/); // Get the start position from the first commands element => [29.383,38.5] const startPosition = commands[0] .substring(1) .split(",") .map((coord) => parseFloat(coord)); // Get the last position from the last commands element => [29.383,83.2] const endPosition = commands[commands.length - 1] .substring(1) .split(",") .map((coord) => parseFloat(coord)); // compute the reflection points -> [ {x: 29.383, y: 38.5}, {x: 29.383, y: 83.2} ] // These includes the start and end points and also points which are not the same as the previous points const reflectionPoints = commands .map((command) => { const coords = command .substring(1) .split(",") .map((coord) => parseFloat(coord)); return { x: coords[0], y: coords[1] }; }) .filter((point, index, array) => { // Always include the last point if (index === 0 || index === array.length - 1) { return true; } // Exclude the points which are the same as the previous point if (point.x === array[index - 1].x && point.y === array[index - 1].y) { return false; } // The below check is exclusively for second last point if (index === array.length - 2 && (array[index - 1].x === point.x || array[index - 1].y === point.y)) { const lastPoint = array[array.length - 1]; // Get the distance between the last point and second last point using Euclidean distance formula const distance = Math.hypot(lastPoint.x - point.x, lastPoint.y - point.y); // Include the second last point if the distance between the // last point and second last point is > 20. // This is to ensure we have a distance for render the edge. // 20 seems to be a good enough distance to render the edge return distance > 20; } // Always include if the current point is not the same as the previous point return point.x !== array[index - 1].x || point.y !== array[index - 1].y; }) .map((p) => { // Offset the point by the provided offset return { x: p.x + offset.x, y: p.y + offset.y, }; }); // Return the edge positions return { startX: startPosition[0] + offset.x, startY: startPosition[1] + offset.y, endX: endPosition[0] + offset.x, endY: endPosition[1] + offset.y, reflectionPoints, }; };