//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
}