diff --git a/src/helpers/linkUtils.js b/src/helpers/linkUtils.js
index 6f6943d..27bffa4 100644
--- a/src/helpers/linkUtils.js
+++ b/src/helpers/linkUtils.js
@@ -1,108 +1,92 @@
-const wikilink = /\[\[(.*?\|.*?)\]\]/g
+const wikiLinkRegex = /\[\[(.*?\|.*?)\]\]/g;
const internalLinkRegex = /href="\/(.*?)"/g;
-
function caselessCompare(a, b) {
- return a.toLowerCase() === b.toLowerCase();
+ return a.toLowerCase() === b.toLowerCase();
}
function extractLinks(content) {
- return[...(content.match(wikilink) || []).map(link => (
- link.slice(2, -2)
- .split("|")[0]
- .replace(/.(md|markdown)\s?$/i, "")
- .replace("\\", "")
- .trim()
- )), ...(content.match(internalLinkRegex) || []).map(
- (link) =>
- link
- .slice(6, -1)
- .split("|")[0]
- .replace(/.(md|markdown)\s?$/i, "")
- .replace("\\", "")
- .trim()
- )];
+ return [
+ ...(content.match(wikiLinkRegex) || []).map(
+ (link) =>
+ link
+ .slice(2, -2)
+ .split("|")[0]
+ .replace(/.(md|markdown)\s?$/i, "")
+ .replace("\\", "")
+ .trim()
+ .split("#")[0]
+ ),
+ ...(content.match(internalLinkRegex) || []).map(
+ (link) =>
+ link
+ .slice(6, -1)
+ .split("|")[0]
+ .replace(/.(md|markdown)\s?$/i, "")
+ .replace("\\", "")
+ .trim()
+ .split("#")[0]
+ ),
+ ];
}
-function getBacklinks(data) {
- const notes = data.collections.note;
- if (!notes) {
- return [];
+function getGraph(data) {
+ let nodes = {};
+ let links = [];
+ let stemURLs = {};
+ let homeAlias = "/";
+ data.collections.note.forEach((v, idx) => {
+ let fpath = v.filePathStem.replace("/notes/", "");
+ let parts = fpath.split("/");
+ let group = "none";
+ if (parts.length >= 3) {
+ group = parts[parts.length - 2];
}
- const currentFileSlug = data.page.filePathStem.replace('/notes/', '');
- const currentURL = data.page.url;
-
- let backlinks = [];
- let uniqueLinks = new Set();
- let counter = 1;
-
- for (const otherNote of notes) {
- const noteContent = otherNote.template.frontMatter.content;
- const backLinks = extractLinks(noteContent);
-
- if (!uniqueLinks.has(otherNote.url) && backLinks.some(link => caselessCompare(link, currentFileSlug) ||
- currentURL == link.split("#")[0])) {
- let preview = noteContent.slice(0, 240);
- backlinks.push({
- url: otherNote.url,
- title: otherNote.data.title || otherNote.data.page.fileSlug,
- preview,
- id: counter++
- })
- uniqueLinks.add(otherNote.url);
- }
+ nodes[v.url] = {
+ id: idx,
+ title: v.data.title || v.fileSlug,
+ url: v.url,
+ group,
+ home: v.data["dg-home"] || (v.data.tags && v.data.tags.indexOf("gardenEntry") > -1)|| false,
+ outBound: extractLinks(v.template.frontMatter.content),
+ neighbors: new Set(),
+ backLinks: new Set(),
+ };
+ stemURLs[fpath] = v.url;
+ if (v.data["dg-home"] || (v.data.tags && v.data.tags.indexOf("gardenEntry") > -1)) {
+ homeAlias = v.url;
}
- return backlinks;
+ });
+ Object.values(nodes).forEach((node) => {
+ let outBound = new Set();
+ node.outBound.forEach((olink) => {
+ let link = (stemURLs[olink] || olink).split("#")[0];
+ outBound.add(link);
+ });
+ node.outBound = Array.from(outBound);
+ node.outBound.forEach((link) => {
+ let n = nodes[link];
+ if (n) {
+ n.neighbors.add(node.url);
+ n.backLinks.add(node.url);
+ node.neighbors.add(n.url);
+ links.push({ source: node.id, target: n.id });
+ }
+ });
+ });
+ Object.keys(nodes).map((k) => {
+ nodes[k].neighbors = Array.from(nodes[k].neighbors);
+ nodes[k].backLinks = Array.from(nodes[k].backLinks);
+ nodes[k].size = nodes[k].neighbors.length;
+ });
+ return {
+ homeAlias,
+ nodes,
+ links,
+ };
}
-function getOutboundLinks(data, isHome=false){
- const notes = data.collections.note;
-
-
-
- if (!notes || notes.length == 0) {
- return [];
- }
-
- let currentNote;
- if (isHome) {
- currentNote = data.collections.gardenEntry && data.collections.gardenEntry[0];
- } else {
- const currentFileSlug = data.page.filePathStem.replace('/notes/', '');
- currentNote = notes.find(x => x.data.page.filePathStem && caselessCompare(x.data.page.filePathStem.replace('/notes/', ''), currentFileSlug));
- }
-
- if (!currentNote) {
- return [];
- }
-
- let counter = 1;
- let uniqueLinks = new Set();
-
- const outboundLinks = extractLinks(currentNote.template.frontMatter.content);
- let outbound = outboundLinks.map(fileslug => {
- var outboundNote = notes.find(x => caselessCompare(x.data.page.filePathStem.replace("/notes/", ""), fileslug) || x.data.page.url == fileslug.split("#")[0]);
- if (!outboundNote) {
- return null;
- }
- if (!uniqueLinks.has(outboundNote.url)) {
-
- uniqueLinks.add(outboundNote.url);
- return {
- url: outboundNote.url,
- title: outboundNote.data.title || outboundNote.data.page.fileSlug,
- id: counter++,
- };
- } else {
- return null;
- }
- }).filter(x => x);
- return outbound;
-}
-
-exports.wikilink = wikilink;
+exports.wikiLinkRegex = wikiLinkRegex;
exports.internalLinkRegex = internalLinkRegex;
-exports.getBacklinks = getBacklinks;
-exports.getOutboundLinks = getOutboundLinks;
-exports.caselessCompare = caselessCompare;
-exports.extractLinks = extractLinks;
\ No newline at end of file
+exports.extractLinks = extractLinks;
+exports.getGraph = getGraph;
diff --git a/src/site/_data/eleventyComputed.js b/src/site/_data/eleventyComputed.js
new file mode 100644
index 0000000..d19e5e1
--- /dev/null
+++ b/src/site/_data/eleventyComputed.js
@@ -0,0 +1,5 @@
+const { getGraph } = require("../../helpers/linkUtils");
+
+module.exports = {
+ graph: (data) => getGraph(data),
+}
\ No newline at end of file
diff --git a/src/site/_includes/components/graphScript.njk b/src/site/_includes/components/graphScript.njk
index 831886b..4272bdf 100644
--- a/src/site/_includes/components/graphScript.njk
+++ b/src/site/_includes/components/graphScript.njk
@@ -1,113 +1,86 @@
\ No newline at end of file
+ function fetchGraphData() {
+ fetch('/graph.json').then(res => res.json()).then(data => {
+ window.graphData = data;
+ Graph = renderGraph(filterToDepth(JSON.parse(JSON.stringify(data))), "link-graph", 320, 320, 1);
+ });
+ }
+
+ fetchGraphData();
+ window.document.getElementById('graph-depth').value = window.maxGraphDepth;
+ window.document.getElementById('depth-display').innerText = window.maxGraphDepth;
+ window.document.getElementById('graph-depth').addEventListener('input', (evt) => {
+
+ window.maxGraphDepth = evt.target.value;
+ window.document.getElementById('depth-display').innerText = window.maxGraphDepth;
+ if (Graph != null) {
+ Graph._destructor();
+ Graph = null;
+ }
+ renderGraph(filterToDepth(JSON.parse(JSON.stringify(window.graphData))), "link-graph", 330, 330, 1);
+ })
+
+ window.fullGraph = null;
+ function renderFullGraph() {
+ if (!window.fullGraph) {
+ const graphData = {
+ links: JSON.parse(JSON.stringify(window.graphData.links)),
+ nodes: [...Object.values(window.graphData.nodes)]
+ }
+
+ let g = document.createElement('div');
+ g.id = 'full-graph';
+ g.classList.add('show');
+ document.body.appendChild(g);
+ g.innerHTML = '
';
+ window.fullGraph = renderGraph(graphData, "full-graph-container", g.offsetWidth, g.offsetHeight, 200);
+ document.getElementById('full-graph-close').addEventListener('click', (evt) => {
+ g.classList.remove('show');
+ window.fullGraph._destructor();
+ window.fullGraph = null;
+ document.getElementById('full-graph').remove()
+ });
+ }
+
+ }
+
+ document.getElementById('graph-full-btn').addEventListener('click', (evt) => {
+ if (!fullGraph) {
+ renderFullGraph();
+ }
+ });
+
diff --git a/src/site/_includes/components/pageheader.njk b/src/site/_includes/components/pageheader.njk
index 0d400ba..4e3fe30 100644
--- a/src/site/_includes/components/pageheader.njk
+++ b/src/site/_includes/components/pageheader.njk
@@ -17,7 +17,7 @@
-
+
diff --git a/src/site/_includes/components/sidebar.njk b/src/site/_includes/components/sidebar.njk
index b3cfd48..82da48b 100644
--- a/src/site/_includes/components/sidebar.njk
+++ b/src/site/_includes/components/sidebar.njk
@@ -6,6 +6,21 @@
Connected Pages
+
+
+
+
+
+
+
+
+
+
+
@@ -29,25 +44,41 @@
{%endif%}
{%if settings.dgShowBacklinks === true %}
+ {%if settings.dgShowBacklinks === true %}
Pages mentioning this page
-
- {%- if backlinks.length === 0 -%}
+ {%- if page.url == "/" -%}
+ {%- if graph.nodes[graph.homeAlias].backLinks.length === 0 -%}
+
+ No other pages mentions this page
+
+ {%- endif -%}
+ {%- for backlink in graph.nodes[graph.homeAlias].backLinks -%}
+ {%- if graph.nodes[backlink].url != graph.homeAlias -%}
- No other pages mentions this page
-
- {%- endif -%}
-
- {%- for backlink in backlinks -%}
-
+ {%- endif -%}
{%- endfor -%}
-
+ {%- else -%}
+ {%- if graph.nodes[page.url].backLinks.length === 0 -%}
+
+ No other pages mentions this page
+
+ {%- endif -%}
+ {%- for backlink in graph.nodes[page.url].backLinks -%}
+ {%- if graph.nodes[backlink].url != page.url -%}
+
+ {%- endif -%}
+ {%- endfor -%}
+ {%- endif -%}
-
+
{%endif%}
+ {%endif%}
diff --git a/src/site/graph.njk b/src/site/graph.njk
new file mode 100644
index 0000000..eb7abd0
--- /dev/null
+++ b/src/site/graph.njk
@@ -0,0 +1,5 @@
+---
+permalink: /graph.json
+eleventyExcludeFromCollections: true
+---
+{{ graph | jsonify | safe }}
\ No newline at end of file
diff --git a/src/site/index.11tydata.js b/src/site/index.11tydata.js
index 396fe9f..b38ca19 100644
--- a/src/site/index.11tydata.js
+++ b/src/site/index.11tydata.js
@@ -1,55 +1,59 @@
-
require("dotenv").config();
const settings = require("../helpers/constants");
const markdownIt = require("markdown-it");
-const { getBacklinks, getOutboundLinks } = require("../helpers/linkUtils");
const md = markdownIt({
- html: true,
+ html: true,
}).use(require("../helpers/utils").namedHeadingsFilter);
-
const allSettings = settings.ALL_NOTE_SETTINGS;
module.exports = {
- eleventyComputed: {
- backlinks: (data) => getBacklinks(data),
- outbound: (data) => getOutboundLinks(data, true),
- settings: (data) => {
- const currentnote = data.collections.gardenEntry && data.collections.gardenEntry[0];
- if (currentnote && currentnote.data) {
- const noteSettings = {};
- allSettings.forEach(setting => {
- let noteSetting = currentnote.data[setting];
- let globalSetting = process.env[setting];
+ eleventyComputed: {
+ settings: (data) => {
+ const currentnote =
+ data.collections.gardenEntry && data.collections.gardenEntry[0];
+ if (currentnote && currentnote.data) {
+ const noteSettings = {};
+ allSettings.forEach((setting) => {
+ let noteSetting = currentnote.data[setting];
+ let globalSetting = process.env[setting];
- let settingValue = (noteSetting || (globalSetting === 'true' && noteSetting !== false));
- noteSettings[setting] = settingValue;
- });
- return noteSettings;
-
- }
- return {};
- },
- noteTitle: (data) => {
- const currentnote = data.collections.gardenEntry && data.collections.gardenEntry[0];
- if (currentnote && currentnote.data) {
- return currentnote.data.title || currentnote.data.page.fileSlug;
- }
- return "";
- },
- tags: (data) => {
- const currentnote = data.collections.gardenEntry && data.collections.gardenEntry[0];
- if (currentnote && currentnote.data) {
- return currentnote.data.tags;
- }
- return [];
- },
- content: (data) => {
- const currentnote = data.collections.gardenEntry && data.collections.gardenEntry[0];
- if (currentnote && currentnote.template && currentnote.template.frontMatter && currentnote.template.frontMatter.content) {
- return md.render(currentnote.template.frontMatter.content);
- }
- return "";
- }
- }
-}
\ No newline at end of file
+ let settingValue =
+ noteSetting || (globalSetting === "true" && noteSetting !== false);
+ noteSettings[setting] = settingValue;
+ });
+ return noteSettings;
+ }
+ return {};
+ },
+ noteTitle: (data) => {
+ const currentnote =
+ data.collections.gardenEntry && data.collections.gardenEntry[0];
+ if (currentnote && currentnote.data) {
+ return currentnote.data.title || currentnote.data.page.fileSlug;
+ }
+ return "";
+ },
+ tags: (data) => {
+ const currentnote =
+ data.collections.gardenEntry && data.collections.gardenEntry[0];
+ if (currentnote && currentnote.data) {
+ return currentnote.data.tags;
+ }
+ return [];
+ },
+ content: (data) => {
+ const currentnote =
+ data.collections.gardenEntry && data.collections.gardenEntry[0];
+ if (
+ currentnote &&
+ currentnote.template &&
+ currentnote.template.frontMatter &&
+ currentnote.template.frontMatter.content
+ ) {
+ return md.render(currentnote.template.frontMatter.content);
+ }
+ return "";
+ },
+ },
+};
diff --git a/src/site/notes/notes.11tydata.js b/src/site/notes/notes.11tydata.js
index 004846b..c036a74 100644
--- a/src/site/notes/notes.11tydata.js
+++ b/src/site/notes/notes.11tydata.js
@@ -1,24 +1,21 @@
-
require("dotenv").config();
const settings = require("../../helpers/constants");
-const { getBacklinks, getOutboundLinks } = require("../../helpers/linkUtils");
-const allSettings = settings.ALL_NOTE_SETTINGS;
+const allSettings = settings.ALL_NOTE_SETTINGS;
module.exports = {
- eleventyComputed: {
- backlinks: (data) => getBacklinks(data),
- outbound: (data) => getOutboundLinks(data),
- settings: (data) => {
- const noteSettings = {};
- allSettings.forEach(setting => {
- let noteSetting = data[setting];
- let globalSetting = process.env[setting];
+ eleventyComputed: {
+ settings: (data) => {
+ const noteSettings = {};
+ allSettings.forEach((setting) => {
+ let noteSetting = data[setting];
+ let globalSetting = process.env[setting];
- let settingValue = (noteSetting || (globalSetting === 'true' && noteSetting !== false));
- noteSettings[setting] = settingValue;
- });
- return noteSettings;
- }
- }
-}
\ No newline at end of file
+ let settingValue =
+ noteSetting || (globalSetting === "true" && noteSetting !== false);
+ noteSettings[setting] = settingValue;
+ });
+ return noteSettings;
+ },
+ },
+};
diff --git a/src/site/styles/digital-garden-base.scss b/src/site/styles/digital-garden-base.scss
index 252573c..72a23e9 100644
--- a/src/site/styles/digital-garden-base.scss
+++ b/src/site/styles/digital-garden-base.scss
@@ -520,3 +520,96 @@ ul.task-list {
.callout-fold .lucide {
transition: transform 100ms ease-in-out;
}
+
+
+
+
+// Graph Controls
+.graph-title-container {
+ position: relative;
+}
+
+#full-graph {
+ position: fixed;
+ top: 50%;
+ left: 50%;
+ height: 60vh;
+ width: 60vw;
+ min-height: 400px;
+ min-width: 400px;
+ transform: translate(-50%, -50%);
+ z-index: 9999;
+ display: none;
+ background-color: var(--background-secondary);
+
+ #full-graph-close {
+ position: absolute;
+ top: 10px;
+ right: 10px;
+ cursor: pointer;
+ z-index: 9;
+ }
+}
+
+#graph-full-btn {
+ margin-right: 10px;
+}
+
+#full-graph.show {
+ display: block;
+}
+
+#graph-controls {
+ display: flex;
+ flex-direction: row;
+ align-items: center;
+ justify-content: space-between;
+ gap: 5px;
+ position: absolute;
+ top: 115%;
+ cursor: pointer;
+ right: 0px;
+ left: 10px;
+ color: var(--text-accent);
+ z-index: 9;
+
+ .depth-control {
+ display: flex;
+ flex-direction: row;
+ align-items: flex-start;
+ gap: 7px;
+
+ .slider {
+ datalist {
+ display: flex;
+ flex-direction: row;
+ justify-content: space-between;
+ font-size: 0.6rem;
+ // padding: 2px 0px;
+ // width: 200px;
+ }
+
+ option {
+ padding: 0;
+ }
+ }
+
+ #depth-display {
+ background-color: var(--text-accent);
+ color: white;
+ width: 1rem;
+ height: 1rem;
+ font-size: 0.8rem;
+ display: flex;
+ justify-content: center;
+ align-items: center;
+ margin-top: 0.3rem;
+ border-radius: 50%;
+ }
+ }
+}
+
+input[type=range]::-webkit-slider-thumb {
+ -webkit-appearance: none;
+ margin-top: -10px;
+}
diff --git a/src/site/styles/obsidian-base.scss b/src/site/styles/obsidian-base.scss
index 328387a..563409a 100644
--- a/src/site/styles/obsidian-base.scss
+++ b/src/site/styles/obsidian-base.scss
@@ -7943,6 +7943,8 @@ body {
.graph-control-section.mod-display .setting-item:not(.mod-slider):last-child .setting-item-info {
display: none;
}
+
+
.workspace-leaf-content[data-type='outline'] .view-content {
padding: 0;
}
@@ -10632,4 +10634,3 @@ body {
width: 100%;
}
-
\ No newline at end of file