Mermaid Output § //YAML Settings let c = dv.current(); let direction = c.Direction ?? "LR"; let showAsCode = c.ShowCode ?? false; let subGroups = c.SubGroupNames ?? []; let removeOrphans = c.RemoveOrphans ?? false; let keepLinksWithoutSource = c.KeepLinksWithoutSource ?? true; let keepLinksWithoutDest = c.KeepLinksWithoutDes ?? true; var nodeQueries = c.Nodes ?? ['TABLE WITHOUT ID "[[No Nodes]]", "No Nodes", "[", "]", "red" FROM "" LIMIT 1']; var linkQueries = c.Links ?? []; var styles = c.Styles ?? []; //DEFAULTS let def = {open: "[", close: "]", style: "default"}; styles.push("classDef red fill:#f2b5bd88,color:#c94f60,stroke:#c94f60,stroke-width:1px"); styles.push("classDef orange fill:#efc5b388,color:#c76a43,stroke:#c76a43,stroke-width:1px"); styles.push("classDef yellow fill:#f2d9b588,color:#b68035,stroke:#b68035,stroke-width:1px"); styles.push("classDef green fill:#bde0bd88,color:#3c9f3e,stroke:#3c9f3e,stroke-width:1px"); styles.push("classDef mint fill:#c9e4d688,color:#24a864,stroke:#24a864,stroke-width:1px"); styles.push("classDef aqua fill:#c0dede88,color:#339999,stroke:#339999,stroke-width:1px"); styles.push("classDef blue fill:#cbcde188,color:#6d73b0,stroke:#6d73b0,stroke-width:1px"); styles.push("classDef purple fill:#d4c0e388,color:#8c59b1,stroke:#8c59b1,stroke-width:1px"); styles.push("classDef pink fill:#e6c1d788,color:#b54a88,stroke:#b54a88,stroke-width:1px"); styles.push("classDef grey fill:#d3cfcf88,color:#7e7273,stroke:#7e7273,stroke-width:1px"); styles.push("classDef default fill:#88888888,color:#000,stroke:#000,stroke-width:1px"); //-------------- //SHOULDN'T NEED TO CHANGE CODE BELOW HERE //-------------- //FRONTMATTER STRING let mFront = (showAsCode ? "```\n" : "```mermaid\n" ); mFront += "graph " + direction + "\n\n"; //CREATE ARRAY OF ALL NODES var nodesArray = []; for (var q = 0; q < nodeQueries.length; q++) { let DQLResults = await dv.tryQuery(nodeQueries[q]); DQLResults.values.forEach(node => { let ref = getNodeRefName(node[0]); let ID = getLegalCharacters(ref); let display = cleanLabel(node[0], node[1]); if (!node[4]) {node[4] = "default"} let nodeObj = {qID: q, ID:ID, link:ref, name, display:display, open:node[2], close:node[3], style:node[4], linked: false} nodesArray.push(nodeObj); }); }; //CREATE MERMAID STRING FOR LINKS (CHECK FOR ORPHANS) nodesArray = dv.array(nodesArray); var mLinks = "%%---LINKS---\n"; for (var q = 0; q < linkQueries.length; q++) { let DQLResults = await dv.tryQuery(linkQueries[q]); DQLResults.values.forEach(link => { let sRef = getNodeRefName(link[0]); let sID = getLegalCharacters(sRef); let dRef = getNodeRefName(link[1]); let dID = getLegalCharacters(dRef); let arrow = (link[2] == "" ? " --> " : " " + link[2] + " "); //Find nodes if they were imported explicitly let sIndex = nodesArray.ID.indexOf(sID); let dIndex = nodesArray.ID.indexOf(dID); //Mark those nodes as linked if they were found if (sIndex >= 0) {nodesArray[sIndex].linked = true;} if (dIndex >= 0) {nodesArray[dIndex].linked = true;} //if both nodes exist OR we have permission to create them if ((sIndex >= 0 || keepLinksWithoutSource) && (dIndex >= 0 || keepLinksWithoutDest)) { var sString = " "; if (sIndex >= 0) { sString += sID; } else { sString += getNodeDef(sID, def.open, "", cleanLabel(sRef), def.close, def.style); } var dString = ""; if (dIndex >= 0) { dString += dID; } else { dString += getNodeDef(dID, def.open, "", cleanLabel(dRef), def.close, def.style); } mLinks += sString + arrow + dString + "\n"; } }); mLinks = mLinks + "\n"; }; //CREATE MERMAID STRING FOR THE NODES var mNodes = "%%---NODES---\n"; var qID = -1; var subGroup = ""; nodesArray.forEach(n => { //if node is linked, or I don't care about orphans if (n.linked || !removeOrphans) { //new subgroup if (qID != n.qID) { if (subGroup != "") {mNodes += "end\n\n"} //close previous qID = n.qID; subGroup = subGroups[qID] ?? ""; if (subGroup != "") { mNodes += "subgraph SG" + qID + " [<B>" + subGroup + "<B>]\n"; } } mNodes += " " mNodes += getNodeDef(n.ID, n.open, n.link, n.display, n.close, n.style); } }); if (subGroup != "") {mNodes += "end\n\n"} //STYLE CLASSES var styleClasses = "%%---STYLES---\n"; for (var s = 0; s < styles.length; s++) { styleClasses += styles[s] + "\n\n" } //OUTPUT dv.span(mFront + mNodes + mLinks + styleClasses + "```"); //-------------- //HELPER FUNCTIONS //-------------- function getNodeDef(ID, open, href, display, close, style) { var def = ""; def += ID + open + "\"<div style='padding:5px;'>"; def += (href != "" ? "<a class='internal-link' href='" + href + ".md'>" : ""); def += display; def += (href != "" ? "</a>" : ""); def += "</div>\"" + close + ":::" + style + "\n"; return def } function getNodeRefName(node) { // return file name if valid page, otherwise return a string representation if (dv.page(node)) { return dv.page(node).file.name; } else { return cleanLinkyString(node); } } function getLegalCharacters(name) { //Remove characters not allowed in Mermaid IDs return name.replace(/[^a-zA-Z0-9]/g, ''); } function cleanLinkyString(link) { //Remove [[ | ]] from linky strings return String(link).split("|")[0].replace(/[\[\]]/g, ''); } function cleanLabel(node, display) { //Try to clean up the display name, but if empty - infer from node var wrapped = ""; if (!display || display == "") { wrapped = getNodeRefName(node); } else { wrapped = cleanLinkyString(String(display)); } wrapped = wrapped.replace(/(?![^\n]{1,20}$)([^\n]{1,20})\s/g, '$1\<br>'); wrapped = wrapped.replace(/[❝❞]/g,'\\"'); return wrapped }