UNPKG

mermaid

Version:

Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.

573 lines (570 loc) 18.7 kB
import { __name, assignWithDepth_default, common_default, detectType, directiveRegex, log, sanitizeDirective } from "./chunk-BMRFU3JQ.mjs"; // src/utils.ts import { sanitizeUrl } from "@braintree/sanitize-url"; import { curveBasis, curveBasisClosed, curveBasisOpen, curveBumpX, curveBumpY, curveBundle, curveCardinalClosed, curveCardinalOpen, curveCardinal, curveCatmullRomClosed, curveCatmullRomOpen, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveMonotoneY, curveNatural, curveStep, curveStepAfter, curveStepBefore, select } from "d3"; import memoize from "lodash-es/memoize.js"; import merge from "lodash-es/merge.js"; var ZERO_WIDTH_SPACE = "\u200B"; var d3CurveTypes = { curveBasis, curveBasisClosed, curveBasisOpen, curveBumpX, curveBumpY, curveBundle, curveCardinalClosed, curveCardinalOpen, curveCardinal, curveCatmullRomClosed, curveCatmullRomOpen, curveCatmullRom, curveLinear, curveLinearClosed, curveMonotoneX, curveMonotoneY, curveNatural, curveStep, curveStepAfter, curveStepBefore }; var directiveWithoutOpen = /\s*(?:(\w+)(?=:):|(\w+))\s*(?:(\w+)|((?:(?!}%{2}).|\r?\n)*))?\s*(?:}%{2})?/gi; var detectInit = /* @__PURE__ */ __name(function(text, config) { const inits = detectDirective(text, /(?:init\b)|(?:initialize\b)/); let results = {}; if (Array.isArray(inits)) { const args = inits.map((init) => init.args); sanitizeDirective(args); results = assignWithDepth_default(results, [...args]); } else { results = inits.args; } if (!results) { return; } let type = detectType(text, config); const prop = "config"; if (results[prop] !== void 0) { if (type === "flowchart-v2") { type = "flowchart"; } results[type] = results[prop]; delete results[prop]; } return results; }, "detectInit"); var detectDirective = /* @__PURE__ */ __name(function(text, type = null) { try { const commentWithoutDirectives = new RegExp( `[%]{2}(?![{]${directiveWithoutOpen.source})(?=[}][%]{2}).* `, "ig" ); text = text.trim().replace(commentWithoutDirectives, "").replace(/'/gm, '"'); log.debug( `Detecting diagram directive${type !== null ? " type:" + type : ""} based on the text:${text}` ); let match; const result = []; while ((match = directiveRegex.exec(text)) !== null) { if (match.index === directiveRegex.lastIndex) { directiveRegex.lastIndex++; } if (match && !type || type && match[1]?.match(type) || type && match[2]?.match(type)) { const type2 = match[1] ? match[1] : match[2]; const args = match[3] ? match[3].trim() : match[4] ? JSON.parse(match[4].trim()) : null; result.push({ type: type2, args }); } } if (result.length === 0) { return { type: text, args: null }; } return result.length === 1 ? result[0] : result; } catch (error) { log.error( `ERROR: ${error.message} - Unable to parse directive type: '${type}' based on the text: '${text}'` ); return { type: void 0, args: null }; } }, "detectDirective"); var removeDirectives = /* @__PURE__ */ __name(function(text) { return text.replace(directiveRegex, ""); }, "removeDirectives"); var isSubstringInArray = /* @__PURE__ */ __name(function(str, arr) { for (const [i, element] of arr.entries()) { if (element.match(str)) { return i; } } return -1; }, "isSubstringInArray"); function interpolateToCurve(interpolate, defaultCurve) { if (!interpolate) { return defaultCurve; } const curveName = `curve${interpolate.charAt(0).toUpperCase() + interpolate.slice(1)}`; return d3CurveTypes[curveName] ?? defaultCurve; } __name(interpolateToCurve, "interpolateToCurve"); function formatUrl(linkStr, config) { const url = linkStr.trim(); if (!url) { return void 0; } if (config.securityLevel !== "loose") { return sanitizeUrl(url); } return url; } __name(formatUrl, "formatUrl"); var runFunc = /* @__PURE__ */ __name((functionName, ...params) => { const arrPaths = functionName.split("."); const len = arrPaths.length - 1; const fnName = arrPaths[len]; let obj = window; for (let i = 0; i < len; i++) { obj = obj[arrPaths[i]]; if (!obj) { log.error(`Function name: ${functionName} not found in window`); return; } } obj[fnName](...params); }, "runFunc"); function distance(p1, p2) { if (!p1 || !p2) { return 0; } return Math.sqrt(Math.pow(p2.x - p1.x, 2) + Math.pow(p2.y - p1.y, 2)); } __name(distance, "distance"); function traverseEdge(points) { let prevPoint; let totalDistance = 0; points.forEach((point) => { totalDistance += distance(point, prevPoint); prevPoint = point; }); const remainingDistance = totalDistance / 2; return calculatePoint(points, remainingDistance); } __name(traverseEdge, "traverseEdge"); function calcLabelPosition(points) { if (points.length === 1) { return points[0]; } return traverseEdge(points); } __name(calcLabelPosition, "calcLabelPosition"); var roundNumber = /* @__PURE__ */ __name((num, precision = 2) => { const factor = Math.pow(10, precision); return Math.round(num * factor) / factor; }, "roundNumber"); var calculatePoint = /* @__PURE__ */ __name((points, distanceToTraverse) => { let prevPoint = void 0; let remainingDistance = distanceToTraverse; for (const point of points) { if (prevPoint) { const vectorDistance = distance(point, prevPoint); if (vectorDistance === 0) { return prevPoint; } if (vectorDistance < remainingDistance) { remainingDistance -= vectorDistance; } else { const distanceRatio = remainingDistance / vectorDistance; if (distanceRatio <= 0) { return prevPoint; } if (distanceRatio >= 1) { return { x: point.x, y: point.y }; } if (distanceRatio > 0 && distanceRatio < 1) { return { x: roundNumber((1 - distanceRatio) * prevPoint.x + distanceRatio * point.x, 5), y: roundNumber((1 - distanceRatio) * prevPoint.y + distanceRatio * point.y, 5) }; } } } prevPoint = point; } throw new Error("Could not find a suitable point for the given distance"); }, "calculatePoint"); var calcCardinalityPosition = /* @__PURE__ */ __name((isRelationTypePresent, points, initialPosition) => { log.info(`our points ${JSON.stringify(points)}`); if (points[0] !== initialPosition) { points = points.reverse(); } const distanceToCardinalityPoint = 25; const center = calculatePoint(points, distanceToCardinalityPoint); const d = isRelationTypePresent ? 10 : 5; const angle = Math.atan2(points[0].y - center.y, points[0].x - center.x); const cardinalityPosition = { x: 0, y: 0 }; cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2; cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2; return cardinalityPosition; }, "calcCardinalityPosition"); function calcTerminalLabelPosition(terminalMarkerSize, position, _points) { const points = structuredClone(_points); log.info("our points", points); if (position !== "start_left" && position !== "start_right") { points.reverse(); } const distanceToCardinalityPoint = 25 + terminalMarkerSize; const center = calculatePoint(points, distanceToCardinalityPoint); const d = 10 + terminalMarkerSize * 0.5; const angle = Math.atan2(points[0].y - center.y, points[0].x - center.x); const cardinalityPosition = { x: 0, y: 0 }; if (position === "start_left") { cardinalityPosition.x = Math.sin(angle + Math.PI) * d + (points[0].x + center.x) / 2; cardinalityPosition.y = -Math.cos(angle + Math.PI) * d + (points[0].y + center.y) / 2; } else if (position === "end_right") { cardinalityPosition.x = Math.sin(angle - Math.PI) * d + (points[0].x + center.x) / 2 - 5; cardinalityPosition.y = -Math.cos(angle - Math.PI) * d + (points[0].y + center.y) / 2 - 5; } else if (position === "end_left") { cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2 - 5; cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2 - 5; } else { cardinalityPosition.x = Math.sin(angle) * d + (points[0].x + center.x) / 2; cardinalityPosition.y = -Math.cos(angle) * d + (points[0].y + center.y) / 2; } return cardinalityPosition; } __name(calcTerminalLabelPosition, "calcTerminalLabelPosition"); function getStylesFromArray(arr) { let style = ""; let labelStyle = ""; for (const element of arr) { if (element !== void 0) { if (element.startsWith("color:") || element.startsWith("text-align:")) { labelStyle = labelStyle + element + ";"; } else { style = style + element + ";"; } } } return { style, labelStyle }; } __name(getStylesFromArray, "getStylesFromArray"); var cnt = 0; var generateId = /* @__PURE__ */ __name(() => { cnt++; return "id-" + Math.random().toString(36).substr(2, 12) + "-" + cnt; }, "generateId"); function makeRandomHex(length) { let result = ""; const characters = "0123456789abcdef"; const charactersLength = characters.length; for (let i = 0; i < length; i++) { result += characters.charAt(Math.floor(Math.random() * charactersLength)); } return result; } __name(makeRandomHex, "makeRandomHex"); var random = /* @__PURE__ */ __name((options) => { return makeRandomHex(options.length); }, "random"); var getTextObj = /* @__PURE__ */ __name(function() { return { x: 0, y: 0, fill: void 0, anchor: "start", style: "#666", width: 100, height: 100, textMargin: 0, rx: 0, ry: 0, valign: void 0, text: "" }; }, "getTextObj"); var drawSimpleText = /* @__PURE__ */ __name(function(elem, textData) { const nText = textData.text.replace(common_default.lineBreakRegex, " "); const [, _fontSizePx] = parseFontSize(textData.fontSize); const textElem = elem.append("text"); textElem.attr("x", textData.x); textElem.attr("y", textData.y); textElem.style("text-anchor", textData.anchor); textElem.style("font-family", textData.fontFamily); textElem.style("font-size", _fontSizePx); textElem.style("font-weight", textData.fontWeight); textElem.attr("fill", textData.fill); if (textData.class !== void 0) { textElem.attr("class", textData.class); } const span = textElem.append("tspan"); span.attr("x", textData.x + textData.textMargin * 2); span.attr("fill", textData.fill); span.text(nText); return textElem; }, "drawSimpleText"); var wrapLabel = memoize( (label, maxWidth, config) => { if (!label) { return label; } config = Object.assign( { fontSize: 12, fontWeight: 400, fontFamily: "Arial", joinWith: "<br/>" }, config ); if (common_default.lineBreakRegex.test(label)) { return label; } const words = label.split(" ").filter(Boolean); const completedLines = []; let nextLine = ""; words.forEach((word, index) => { const wordLength = calculateTextWidth(`${word} `, config); const nextLineLength = calculateTextWidth(nextLine, config); if (wordLength > maxWidth) { const { hyphenatedStrings, remainingWord } = breakString(word, maxWidth, "-", config); completedLines.push(nextLine, ...hyphenatedStrings); nextLine = remainingWord; } else if (nextLineLength + wordLength >= maxWidth) { completedLines.push(nextLine); nextLine = word; } else { nextLine = [nextLine, word].filter(Boolean).join(" "); } const currentWord = index + 1; const isLastWord = currentWord === words.length; if (isLastWord) { completedLines.push(nextLine); } }); return completedLines.filter((line) => line !== "").join(config.joinWith); }, (label, maxWidth, config) => `${label}${maxWidth}${config.fontSize}${config.fontWeight}${config.fontFamily}${config.joinWith}` ); var breakString = memoize( (word, maxWidth, hyphenCharacter = "-", config) => { config = Object.assign( { fontSize: 12, fontWeight: 400, fontFamily: "Arial", margin: 0 }, config ); const characters = [...word]; const lines = []; let currentLine = ""; characters.forEach((character, index) => { const nextLine = `${currentLine}${character}`; const lineWidth = calculateTextWidth(nextLine, config); if (lineWidth >= maxWidth) { const currentCharacter = index + 1; const isLastLine = characters.length === currentCharacter; const hyphenatedNextLine = `${nextLine}${hyphenCharacter}`; lines.push(isLastLine ? nextLine : hyphenatedNextLine); currentLine = ""; } else { currentLine = nextLine; } }); return { hyphenatedStrings: lines, remainingWord: currentLine }; }, (word, maxWidth, hyphenCharacter = "-", config) => `${word}${maxWidth}${hyphenCharacter}${config.fontSize}${config.fontWeight}${config.fontFamily}` ); function calculateTextHeight(text, config) { return calculateTextDimensions(text, config).height; } __name(calculateTextHeight, "calculateTextHeight"); function calculateTextWidth(text, config) { return calculateTextDimensions(text, config).width; } __name(calculateTextWidth, "calculateTextWidth"); var calculateTextDimensions = memoize( (text, config) => { const { fontSize = 12, fontFamily = "Arial", fontWeight = 400 } = config; if (!text) { return { width: 0, height: 0 }; } const [, _fontSizePx] = parseFontSize(fontSize); const fontFamilies = ["sans-serif", fontFamily]; const lines = text.split(common_default.lineBreakRegex); const dims = []; const body = select("body"); if (!body.remove) { return { width: 0, height: 0, lineHeight: 0 }; } const g = body.append("svg"); for (const fontFamily2 of fontFamilies) { let cHeight = 0; const dim = { width: 0, height: 0, lineHeight: 0 }; for (const line of lines) { const textObj = getTextObj(); textObj.text = line || ZERO_WIDTH_SPACE; const textElem = drawSimpleText(g, textObj).style("font-size", _fontSizePx).style("font-weight", fontWeight).style("font-family", fontFamily2); const bBox = (textElem._groups || textElem)[0][0].getBBox(); if (bBox.width === 0 && bBox.height === 0) { throw new Error("svg element not in render tree"); } dim.width = Math.round(Math.max(dim.width, bBox.width)); cHeight = Math.round(bBox.height); dim.height += cHeight; dim.lineHeight = Math.round(Math.max(dim.lineHeight, cHeight)); } dims.push(dim); } g.remove(); const index = isNaN(dims[1].height) || isNaN(dims[1].width) || isNaN(dims[1].lineHeight) || dims[0].height > dims[1].height && dims[0].width > dims[1].width && dims[0].lineHeight > dims[1].lineHeight ? 0 : 1; return dims[index]; }, (text, config) => `${text}${config.fontSize}${config.fontWeight}${config.fontFamily}` ); var InitIDGenerator = class { constructor(deterministic = false, seed) { this.count = 0; this.count = seed ? seed.length : 0; this.next = deterministic ? () => this.count++ : () => Date.now(); } static { __name(this, "InitIDGenerator"); } }; var decoder; var entityDecode = /* @__PURE__ */ __name(function(html) { decoder = decoder || document.createElement("div"); html = escape(html).replace(/%26/g, "&").replace(/%23/g, "#").replace(/%3B/g, ";"); decoder.innerHTML = html; return unescape(decoder.textContent); }, "entityDecode"); function isDetailedError(error) { return "str" in error; } __name(isDetailedError, "isDetailedError"); var insertTitle = /* @__PURE__ */ __name((parent, cssClass, titleTopMargin, title) => { if (!title) { return; } const bounds = parent.node()?.getBBox(); if (!bounds) { return; } parent.append("text").text(title).attr("text-anchor", "middle").attr("x", bounds.x + bounds.width / 2).attr("y", -titleTopMargin).attr("class", cssClass); }, "insertTitle"); var parseFontSize = /* @__PURE__ */ __name((fontSize) => { if (typeof fontSize === "number") { return [fontSize, fontSize + "px"]; } const fontSizeNumber = parseInt(fontSize ?? "", 10); if (Number.isNaN(fontSizeNumber)) { return [void 0, void 0]; } else if (fontSize === String(fontSizeNumber)) { return [fontSizeNumber, fontSize + "px"]; } else { return [fontSizeNumber, fontSize]; } }, "parseFontSize"); function cleanAndMerge(defaultData, data) { return merge({}, defaultData, data); } __name(cleanAndMerge, "cleanAndMerge"); var utils_default = { assignWithDepth: assignWithDepth_default, wrapLabel, calculateTextHeight, calculateTextWidth, calculateTextDimensions, cleanAndMerge, detectInit, detectDirective, isSubstringInArray, interpolateToCurve, calcLabelPosition, calcCardinalityPosition, calcTerminalLabelPosition, formatUrl, getStylesFromArray, generateId, random, runFunc, entityDecode, insertTitle, parseFontSize, InitIDGenerator }; var encodeEntities = /* @__PURE__ */ __name(function(text) { let txt = text; txt = txt.replace(/style.*:\S*#.*;/g, function(s) { return s.substring(0, s.length - 1); }); txt = txt.replace(/classDef.*:\S*#.*;/g, function(s) { return s.substring(0, s.length - 1); }); txt = txt.replace(/#\w+;/g, function(s) { const innerTxt = s.substring(1, s.length - 1); const isInt = /^\+?\d+$/.test(innerTxt); if (isInt) { return "\uFB02\xB0\xB0" + innerTxt + "\xB6\xDF"; } else { return "\uFB02\xB0" + innerTxt + "\xB6\xDF"; } }); return txt; }, "encodeEntities"); var decodeEntities = /* @__PURE__ */ __name(function(text) { return text.replace(/fl°°/g, "&#").replace(/fl°/g, "&").replace(/¶ß/g, ";"); }, "decodeEntities"); var getEdgeId = /* @__PURE__ */ __name((from, to, { counter = 0, prefix, suffix }, id) => { if (id) { return id; } return `${prefix ? `${prefix}_` : ""}${from}_${to}_${counter}${suffix ? `_${suffix}` : ""}`; }, "getEdgeId"); function handleUndefinedAttr(attrValue) { return attrValue ?? null; } __name(handleUndefinedAttr, "handleUndefinedAttr"); export { ZERO_WIDTH_SPACE, removeDirectives, interpolateToCurve, getStylesFromArray, generateId, random, wrapLabel, calculateTextHeight, calculateTextWidth, isDetailedError, parseFontSize, cleanAndMerge, utils_default, encodeEntities, decodeEntities, getEdgeId, handleUndefinedAttr };