Templaterjs and Scripting Internal Links

What I’m trying to do

So what my main goal is to write a script that will go through all the notes in my vault and find and draw a marmaid tree diagram using the internal links that it finds.
I’ve already figured out how to go about doing it and Ik a trivial solution would be to just parse the entire note and maybe do a regex search to find the [[ ]] internal links traverse those and possibly use a Depth First search to create the code for the mermaid diagram. Is there an easier way to find and possibly get a list of the internal links in the current note?

Things I have tried

I’ve gone through the Obsidian Api documentation as well as the Templater plugin documentation but I wasn’t able to find a function that would do something like that.

I have no idea how to do what you want to do :innocent:, but on Obsidian’s API side, there’s the MetadataCache class which has the properties resolvedLinks and unresolvedLinks

Depending on the desired result, this could potentially give you a lead :woman_shrugging:

Some have shared examples on how to get all unresolved links in a vault on Obsidian’s Discord server not using templater though… but using JS Engine > here, CustomJS > there or DataviewJS > there

Okay so to Help you get a clearer Idea of what I wanted to do I’ll use the example of lets say a Diagram of Web App Vulnerabilities. So I have a Note named vulnerabilities that has links to other notes on specific types of vulnerabilites lets say SSRF, SQLi etc. etc. and then those Notes have links to specific websites that might have those vulnerabilities. What I want to do is go through each note and make a mermaid diagram of them. But to do so I’ll have to know which links are in the note.


like this image. So I was wondering if there was a function in the Obsidian API or templater that would help me extract those links.

Related thread:

I don’t think that is what I’m looking for. I’m just looking for a way to get a list of all the links in a note in an array or a different variable either one works…

Here is the soultion :slightly_smiling_face:

<%*
const vault = app.vault;
const files = vault.getMarkdownFiles();
const notesWithDiagrams = new Set();
let uniqueIdCounter = 0;
const noteIdMap = new Map();

// Function to generate a unique ID
function generateUniqueId() {
    return `id${uniqueIdCounter++}`;
}

// Function to get or create a unique ID for a note
function getNoteId(noteName) {
    if (!noteIdMap.has(noteName)) {
        noteIdMap.set(noteName, generateUniqueId());
    }
    return noteIdMap.get(noteName);
}

// Function to read a note and extract internal and website links
function extractLinks(noteContent) {
    const internalLinkRegex = /\[\[([^\]]+)\]\]/g;
    const websiteLinkRegex = /\[([^\]]+)\]\((https?:\/\/[^\s]+)\)/g;
    let internalLinks = [];
    let websiteLinks = [];
    let match;

    while ((match = internalLinkRegex.exec(noteContent)) !== null) {
        internalLinks.push({ link: match[1], id: getNoteId(match[1]) });
    }

    while ((match = websiteLinkRegex.exec(noteContent)) !== null) {
        websiteLinks.push({ text: match[1], url: match[2], id: generateUniqueId() });
    }

    return { internalLinks, websiteLinks };
}

// Function to traverse notes and build the mermaid diagram
async function buildMermaidDiagram(notePath, visited = new Set(), parentNote = null) {
    if (visited.has(notePath) || notesWithDiagrams.has(notePath)) return '';
    visited.add(notePath);

    const noteContent = await vault.adapter.read(notePath);
    const { internalLinks, websiteLinks } = extractLinks(noteContent);
    const noteName = notePath.split('/').pop().replace('.md', '');
    const noteId = getNoteId(noteName);
    let diagram = `${noteId}[${noteName}]\n`;

    for (const { link, id } of internalLinks) {
        const linkPath = vault.getAbstractFileByPath(link + '.md');
        if (linkPath) {
            if (parentNote && linkPath.path === parentNote) continue; // Avoid backward links
            diagram += `${noteId} --> ${id}[${link}]\n`;
            diagram += await buildMermaidDiagram(linkPath.path, visited, notePath);
        }
    }

    for (const { text, url, id } of websiteLinks) {
        diagram += `${noteId} --> ${id}[${text}]\n`;
    }

    return diagram;
}

// Function to insert the mermaid diagram into the note
async function insertDiagramIntoNote(notePath, diagram) {
    let noteContent = await vault.adapter.read(notePath);
    const mermaidRegex = /```mermaid[\s\S]*?```$/;
    const allIds = Array.from(noteIdMap.values()).concat(diagram.match(/\bid\d+\b/g) || []).join(',');
    const newDiagramContent = `\n\n\`\`\`mermaid\ngraph TD\n${diagram}\nclass ${allIds} internal-link;\n\`\`\``;

    if (mermaidRegex.test(noteContent)) {
        noteContent = noteContent.replace(mermaidRegex, newDiagramContent);
    } else if (diagram.trim() !== '' && diagram.includes('-->')) {
        noteContent += newDiagramContent;
    }

    await vault.adapter.write(notePath, noteContent);
    notesWithDiagrams.add(notePath);
}

// Function to scan the entire vault and generate diagrams for each note
async function scanVault() {
    for (const file of files) {
        if (!notesWithDiagrams.has(file.path)) {
            const diagram = await buildMermaidDiagram(file.path);
            if (diagram.trim() !== '' && diagram.includes('-->')) {
                await insertDiagramIntoNote(file.path, diagram);
            }
        }
    }
}

// Main function to generate and insert mermaid diagrams
await scanVault();
%>

The script automatically scans the entire vault to create a mermaid tree diagram for notes with internal links. You can run this script multiple times at intervals, and it will update the diagram with the latest changes, replacing the old one without duplicating it.

And this is the result from several notes from different perspectives



Important notes

  1. You must run the script on an empty note.

  2. When using links, you must use them in this format
    [Google](https://www.google.com/) .

  3. When you want to update the Mermaid, you must first close Obsidian and open it again after making your edits (the script will also not work accurately if you run reload the app without saving , so you must reopen it again) and then run the script because if you don’t, the script will not produce accurate results in all notes.

  4. The script does not affect notes that do not contain internal links.

  5. The Mermaid created by the script should not be rendered and left at the end of the page so that it is replaced and not duplicated when the script is run again to update the links.

  6. If the Mermaid doesn’t contain all the results due to long note names, you can use the Mehrmaid plugin and it will automatically resize it.

  7. The script has other uses and also works well if there are no links to websites.