mermaid
Version:
Markdown-ish syntax for generating flowcharts, mindmaps, sequence diagrams, class diagrams, gantt charts, git graphs and more.
547 lines (539 loc) • 16.9 kB
JavaScript
import {
ImperativeState
} from "./chunk-IALYP65X.mjs";
import {
populateCommonDb
} from "./chunk-6ZKBGPIT.mjs";
import {
parse
} from "./chunk-5EDX277H.mjs";
import "./chunk-WKU43CU3.mjs";
import "./chunk-2HS3KVXU.mjs";
import "./chunk-XOOU5E5Q.mjs";
import "./chunk-SXDK7GA3.mjs";
import "./chunk-EQZMDKUZ.mjs";
import "./chunk-3MNNKFVW.mjs";
import "./chunk-X7XOBEZY.mjs";
import "./chunk-4H32FQV7.mjs";
import "./chunk-BDBCVAR5.mjs";
import "./chunk-ZD6PRSSI.mjs";
import "./chunk-FQ2WY6HO.mjs";
import "./chunk-MGNMQO6E.mjs";
import "./chunk-Z53TCUM2.mjs";
import "./chunk-AQRBQPRI.mjs";
import "./chunk-Z2I5LGMO.mjs";
import "./chunk-W5JXREIF.mjs";
import {
selectSvgElement
} from "./chunk-Y5RK4UJQ.mjs";
import {
getIconSVG,
registerIconPacks
} from "./chunk-AYHZ2ZZ4.mjs";
import {
cleanAndMerge
} from "./chunk-TYR5776D.mjs";
import "./chunk-IPM4HZQ6.mjs";
import {
clear,
configureSvgSize,
defaultConfig_default,
getAccDescription,
getAccTitle,
getConfig,
getDiagramTitle,
sanitizeText,
setAccDescription,
setAccTitle,
setDiagramTitle
} from "./chunk-AHS5MEEA.mjs";
import {
log
} from "./chunk-X5WJYYCX.mjs";
import {
__name
} from "./chunk-PTVI3W5X.mjs";
// src/diagrams/treeView/boxDrawingPreprocessor.ts
var ALL_BOX_CHARS = /[─━│┃└┗├┣]/;
var BRANCH_CHAR = /[└┗├┣]/;
var DASH_CHAR = /[─━]/;
var DECORATION_ONLY = /^[\s│┃]+$/;
var METADATA_LINE = /^\s*(title[\t ]|accTitle[\t ]*:|accDescr[\t ]*[:{])/;
var COMMENT_LINE = /^\s*%%/;
var INDENT_UNIT = " ";
function isBoxDrawingFormat(lines) {
return lines.some((line) => ALL_BOX_CHARS.test(line));
}
__name(isBoxDrawingFormat, "isBoxDrawingFormat");
function inferSegmentWidth(contentLines) {
for (const line of contentLines) {
const match = BRANCH_CHAR.exec(line);
if (match?.index && match.index > 0) {
return match.index;
}
}
return 4;
}
__name(inferSegmentWidth, "inferSegmentWidth");
function remapErrorLines(message, lineMap) {
return message.replace(/\bline\s+(\d+)\b/gi, (match, lineStr) => {
const line = parseInt(lineStr, 10);
const original = lineMap.get(line);
return original ? `line ${original}` : match;
});
}
__name(remapErrorLines, "remapErrorLines");
function preprocessBoxDrawing(input) {
const lines = input.split("\n");
const lineMap = /* @__PURE__ */ new Map();
let keywordIdx = -1;
for (const [i, line] of lines.entries()) {
if (line.trim() === "treeView-beta") {
keywordIdx = i;
break;
}
}
if (keywordIdx === -1) {
return { text: input, lineMap };
}
const contentLineTexts = [];
for (let i = keywordIdx + 1; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();
if (trimmed === "" || COMMENT_LINE.test(line) || METADATA_LINE.test(line)) {
continue;
}
if (DECORATION_ONLY.test(line)) {
continue;
}
contentLineTexts.push(line.replace(/\t/g, " "));
}
if (!isBoxDrawingFormat(contentLineTexts)) {
return { text: input, lineMap };
}
const segmentWidth = inferSegmentWidth(contentLineTexts);
const outputLines = [];
let outLineNo = 0;
for (let i = 0; i <= keywordIdx; i++) {
outputLines.push(lines[i]);
outLineNo++;
lineMap.set(outLineNo, i + 1);
}
for (let i = keywordIdx + 1; i < lines.length; i++) {
const line = lines[i];
const trimmed = line.trim();
const origLineNo = i + 1;
if (trimmed === "") {
outputLines.push(line);
outLineNo++;
lineMap.set(outLineNo, origLineNo);
continue;
}
if (COMMENT_LINE.test(line)) {
outputLines.push(line);
outLineNo++;
lineMap.set(outLineNo, origLineNo);
continue;
}
if (METADATA_LINE.test(line)) {
outputLines.push(line);
outLineNo++;
lineMap.set(outLineNo, origLineNo);
continue;
}
if (DECORATION_ONLY.test(line)) {
continue;
}
const normalized = line.replace(/\t/g, " ");
const branchMatch = BRANCH_CHAR.exec(normalized);
if (branchMatch?.index !== void 0) {
const branchCol = branchMatch.index;
const depth = Math.round(branchCol / segmentWidth) + 1;
let pos = branchCol + 1;
while (pos < normalized.length && DASH_CHAR.test(normalized[pos])) {
pos++;
}
while (pos < normalized.length && normalized[pos] === " ") {
pos++;
}
const content = normalized.slice(pos).trimEnd();
if (!content) {
throw new Error(
`Line ${origLineNo}: Empty node \u2014 expected a filename or directory name after the box-drawing prefix`
);
}
const indent = INDENT_UNIT.repeat(depth);
outputLines.push(indent + content);
outLineNo++;
lineMap.set(outLineNo, origLineNo);
} else if (/^[\s─━│┃└┗├┣]+$/.test(normalized)) {
continue;
} else if (ALL_BOX_CHARS.test(normalized)) {
outputLines.push(line);
outLineNo++;
lineMap.set(outLineNo, origLineNo);
} else if (/^\s+/.test(normalized)) {
throw new Error(
`Line ${origLineNo}: Unexpected indentation without box-drawing characters. In box-drawing format, use \u251C\u2500\u2500 or \u2514\u2500\u2500 prefixes for indented nodes.`
);
} else {
outputLines.push(line);
outLineNo++;
lineMap.set(outLineNo, origLineNo);
}
}
return { text: outputLines.join("\n"), lineMap };
}
__name(preprocessBoxDrawing, "preprocessBoxDrawing");
// src/diagrams/treeView/db.ts
var state = new ImperativeState(() => ({
cnt: 1,
stack: [
{
id: 0,
level: -1,
name: "/",
nodeType: "directory",
children: []
}
]
}));
var clear2 = /* @__PURE__ */ __name(() => {
state.reset();
clear();
}, "clear");
var getRoot = /* @__PURE__ */ __name(() => {
return state.records.stack[0];
}, "getRoot");
var getCount = /* @__PURE__ */ __name(() => state.records.cnt, "getCount");
var defaultConfig = defaultConfig_default.treeView;
var getConfig2 = /* @__PURE__ */ __name(() => {
return cleanAndMerge(defaultConfig, getConfig().treeView);
}, "getConfig");
var addNode = /* @__PURE__ */ __name((level, name, nodeType, cssClass, icon, description) => {
while (level <= state.records.stack[state.records.stack.length - 1].level) {
state.records.stack.pop();
}
const node = {
id: state.records.cnt++,
level,
name,
nodeType,
icon,
cssClass,
description,
children: []
};
state.records.stack[state.records.stack.length - 1].children.push(node);
state.records.stack.push(node);
}, "addNode");
var db = {
clear: clear2,
addNode,
getRoot,
getCount,
getConfig: getConfig2,
getAccTitle,
getAccDescription,
getDiagramTitle,
setAccDescription,
setAccTitle,
setDiagramTitle
};
var db_default = db;
// src/diagrams/treeView/parser.ts
var populate = /* @__PURE__ */ __name((ast) => {
populateCommonDb(ast, db_default);
for (const node of ast.nodes) {
const level = typeof node.indent === "number" ? node.indent : 0;
let name = node.name;
const isDirectory = name.endsWith("/");
if (isDirectory) {
name = name.slice(0, -1);
}
const nodeType = isDirectory ? "directory" : "file";
const cssClass = node.classAnnotation || void 0;
const rawIcon = node.iconAnnotation;
const icon = rawIcon !== void 0 ? rawIcon || "none" : void 0;
const rawDesc = node.descAnnotation || void 0;
const description = rawDesc ? sanitizeText(rawDesc, getConfig()) : void 0;
db_default.addNode(level, name, nodeType, cssClass, icon, description);
}
}, "populate");
var parser = {
parse: /* @__PURE__ */ __name(async (input) => {
const { text, lineMap } = preprocessBoxDrawing(input);
try {
const ast = await parse("treeView", text);
log.debug(ast);
populate(ast);
} catch (error) {
if (lineMap.size > 0 && error instanceof Error) {
error.message = remapErrorLines(error.message, lineMap);
}
throw error;
}
}, "parse")
};
// src/diagrams/treeView/icons.ts
var treeViewIcons = {
prefix: "mermaid-treeview",
height: 24,
width: 24,
icons: {
folder: {
body: '<path fill="currentColor" d="M10.59 4.59A2 2 0 0 0 9.17 4H4a2 2 0 0 0-2 2v12a2 2 0 0 0 2 2h16a2 2 0 0 0 2-2V8a2 2 0 0 0-2-2h-7.17z"/>'
},
file: {
body: '<path fill="currentColor" fill-rule="evenodd" d="M6 2a2 2 0 0 0-2 2v16a2 2 0 0 0 2 2h12a2 2 0 0 0 2-2V8.83a2 2 0 0 0-.59-1.42l-4.82-4.82A2 2 0 0 0 13.17 2H6Zm7.5 1.9l4.6 4.6h-3.6a1 1 0 0 1-1-1V3.9Z" clip-rule="evenodd"/>'
}
}
};
function detectIcon(name, config) {
const filenameIcon = config?.filenameIcons?.[name];
if (filenameIcon) {
return filenameIcon;
}
const dotIdx = name.lastIndexOf(".");
if (dotIdx > 0) {
const ext = name.substring(dotIdx).toLowerCase();
const extensionIcons = config?.extensionIcons;
return extensionIcons?.[ext] ?? extensionIcons?.[ext.slice(1)];
}
return void 0;
}
__name(detectIcon, "detectIcon");
function qualifyIcon(icon, defaultIconPack) {
if (icon.includes(":")) {
return icon;
}
if (icon in treeViewIcons.icons || !defaultIconPack) {
return `${treeViewIcons.prefix}:${icon}`;
}
return `${defaultIconPack}:${icon}`;
}
__name(qualifyIcon, "qualifyIcon");
function getNodeIcon(node, config) {
if (node.icon === "none") {
return void 0;
}
if (node.icon) {
return qualifyIcon(node.icon, config.defaultIconPack);
}
if (!config.showIcons) {
return void 0;
}
if (node.nodeType === "file") {
const detected = detectIcon(node.name, config);
if (detected === "none") {
return void 0;
}
if (detected) {
return qualifyIcon(detected, config.defaultIconPack);
}
}
return `${treeViewIcons.prefix}:${node.nodeType === "directory" ? "folder" : "file"}`;
}
__name(getNodeIcon, "getNodeIcon");
// src/diagrams/treeView/renderer.ts
registerIconPacks([
{
name: treeViewIcons.prefix,
icons: treeViewIcons
}
]);
var ICON_SIZE = 14;
var ICON_GAP = 4;
var DESC_GAP = 16;
var resolveNodeIcons = /* @__PURE__ */ __name(async (root, config) => {
const nodeIcons = [];
const collect = /* @__PURE__ */ __name((node) => {
const icon = getNodeIcon(node, config);
if (icon) {
nodeIcons.push({ icon, node });
}
node.children.forEach(collect);
}, "collect");
collect(root);
const resolvedIcons = await Promise.all(
nodeIcons.map(async ({ icon, node }) => ({
id: node.id,
svg: await getIconSVG(icon, {
height: ICON_SIZE,
width: ICON_SIZE
})
}))
);
return new Map(resolvedIcons.map(({ id, svg }) => [id, svg]));
}, "resolveNodeIcons");
var positionLabel = /* @__PURE__ */ __name((x, y, node, domElem, config, iconSVGs) => {
const nodeGroup = domElem.append("g");
let cssClasses = "treeView-node-label";
if (node.nodeType === "directory") {
cssClasses += " treeView-node-dir";
}
if (node.cssClass) {
cssClasses += ` ${node.cssClass}`;
}
const iconOffset = ICON_SIZE + ICON_GAP;
const icon = getNodeIcon(node, config);
const showIcon = icon !== void 0;
if (icon) {
nodeGroup.append("g").attr("class", "treeView-node-icon").attr("transform", `translate(${x + config.paddingX}, ${y + config.paddingY})`).html(iconSVGs.get(node.id) ?? "");
}
const label = nodeGroup.append("text").text(node.name).attr("dominant-baseline", "middle").attr("class", cssClasses);
const { height: labelHeight, width: labelWidth } = label.node().getBBox();
const height = labelHeight + config.paddingY * 2;
const labelX = x + config.paddingX + (showIcon ? iconOffset : 0);
label.attr("x", labelX);
label.attr("y", y + height / 2);
const labelRightEdge = labelX + labelWidth;
const width = labelWidth + config.paddingX * 2 + (showIcon ? iconOffset : 0);
node.BBox = { x, y, width, height };
if (node.cssClass?.split(/\s+/).includes("highlight")) {
nodeGroup.insert("rect", ":first-child").attr("x", x).attr("y", y + 1).attr("width", 0).attr("height", height - 2).attr("rx", 3).attr("class", "treeView-highlight-bg");
}
return { node, nodeGroup, labelRightEdge, centerY: y + height / 2 };
}, "positionLabel");
var positionLine = /* @__PURE__ */ __name((domElem, x1, y1, x2, y2, lineThickness) => {
return domElem.append("line").attr("x1", x1).attr("y1", y1).attr("x2", x2).attr("y2", y2).attr("stroke-width", lineThickness).attr("class", "treeView-node-line");
}, "positionLine");
var drawTree = /* @__PURE__ */ __name((elem, root, config, iconSVGs) => {
let totalHeight = 0;
let totalWidth = 0;
const renderInfos = [];
const drawNode = /* @__PURE__ */ __name((elem2, node, config2, depth) => {
const indent = depth * (config2.rowIndent + config2.paddingX);
const info = positionLabel(indent, totalHeight, node, elem2, config2, iconSVGs);
renderInfos.push(info);
const { height, width } = node.BBox;
positionLine(
elem2,
indent - config2.rowIndent,
totalHeight + height / 2,
indent,
totalHeight + height / 2,
config2.lineThickness
);
totalWidth = Math.max(totalWidth, indent + width);
totalHeight += height;
}, "drawNode");
const processNode = /* @__PURE__ */ __name((node, depth = 0) => {
drawNode(elem, node, config, depth);
node.children.forEach((child) => {
processNode(child, depth + 1);
});
const { x, y, height } = node.BBox;
if (node.children.length) {
const { y: endY, height: endHeight } = node.children[node.children.length - 1].BBox;
positionLine(
elem,
x + config.paddingX,
y + height,
x + config.paddingX,
endY + endHeight / 2 + config.lineThickness / 2,
config.lineThickness
);
}
}, "processNode");
processNode(root);
const nodesWithDesc = renderInfos.filter((ri) => ri.node.description);
if (nodesWithDesc.length > 0) {
const maxLabelRight = Math.max(...renderInfos.map((ri) => ri.labelRightEdge));
const descX = maxLabelRight + DESC_GAP;
for (const ri of nodesWithDesc) {
const desc = ri.nodeGroup.append("text").text(ri.node.description).attr("dominant-baseline", "middle").attr("class", "treeView-node-description").attr("x", descX).attr("y", ri.centerY);
const descBBox = desc.node().getBBox();
totalWidth = Math.max(totalWidth, descX + descBBox.width + config.paddingX);
}
}
for (const ri of renderInfos) {
if (ri.node.cssClass?.split(/\s+/).includes("highlight")) {
const rect = ri.nodeGroup.select(".treeView-highlight-bg");
if (!rect.empty()) {
const rectWidth = totalWidth - ri.node.BBox.x + 8;
rect.attr("width", rectWidth);
totalWidth = Math.max(totalWidth, ri.node.BBox.x + rectWidth + 2);
}
}
}
return { totalHeight, totalWidth };
}, "drawTree");
var draw = /* @__PURE__ */ __name(async (text, id, _ver, diagObj) => {
log.debug("Rendering treeView diagram\n" + text);
const db2 = diagObj.db;
const root = db2.getRoot();
const config = db2.getConfig();
const svg = selectSvgElement(id);
const treeElem = svg.append("g");
treeElem.attr("class", "tree-view");
const iconSVGs = await resolveNodeIcons(root, config);
const { totalHeight, totalWidth } = drawTree(treeElem, root, config, iconSVGs);
svg.attr("viewBox", `-${config.lineThickness / 2} 0 ${totalWidth} ${totalHeight}`);
configureSvgSize(svg, totalHeight, totalWidth, config.useMaxWidth);
}, "draw");
var renderer = {
draw
};
var renderer_default = renderer;
// src/diagrams/treeView/styles.ts
var defaultTreeViewDiagramStyles = {
labelFontSize: "16px",
labelColor: "black",
lineColor: "black",
iconColor: "#546e7a",
descriptionColor: "#6a9955",
highlightBg: "rgba(255, 193, 7, 0.15)",
highlightStroke: "#ffc107"
};
var styles = /* @__PURE__ */ __name(({
treeView
}) => {
const {
labelFontSize,
labelColor,
lineColor,
iconColor,
descriptionColor,
highlightBg,
highlightStroke
} = cleanAndMerge(defaultTreeViewDiagramStyles, treeView);
return `
.treeView-node-label {
font-size: ${labelFontSize};
fill: ${labelColor};
white-space: pre;
}
.treeView-node-dir {
font-weight: bold;
}
.treeView-node-line {
stroke: ${lineColor};
}
.treeView-node-icon {
color: ${iconColor};
}
.treeView-node-description {
font-size: ${labelFontSize};
fill: ${descriptionColor};
font-style: italic;
white-space: pre;
}
.treeView-highlight-bg {
fill: ${highlightBg};
stroke: ${highlightStroke};
stroke-width: 1;
}
`;
}, "styles");
var styles_default = styles;
// src/diagrams/treeView/diagram.ts
var diagram = {
db: db_default,
renderer: renderer_default,
parser,
styles: styles_default
};
export {
diagram
};