mirror of
https://github.com/tcsenpai/obsidiangarden_netlify.git
synced 2025-06-06 20:55:21 +00:00
Implement support for backlinks and local graph
This commit is contained in:
parent
825893d52c
commit
ad7872d19f
117
src/site/_includes/components/graphScript.njk
Normal file
117
src/site/_includes/components/graphScript.njk
Normal file
@ -0,0 +1,117 @@
|
||||
<script>
|
||||
|
||||
const getCssVar = (variable) => getComputedStyle(document.documentElement).getPropertyValue(variable);
|
||||
|
||||
function htmlDecode(input) {
|
||||
var doc = new DOMParser().parseFromString(input, "text/html");
|
||||
return doc.documentElement.textContent;
|
||||
}
|
||||
|
||||
const backLinks = [
|
||||
|
||||
{%- for backlink in backlinks -%}
|
||||
{
|
||||
id: {{backlink.id}},
|
||||
title: "{{backlink.title | safe}}",
|
||||
url: "{{backlink.url}}"
|
||||
},
|
||||
{%- endfor -%}
|
||||
];
|
||||
|
||||
const outbound = [
|
||||
|
||||
{%- for out in outbound -%}
|
||||
{
|
||||
id: {{out.id}},
|
||||
title: "{{out.title | safe}}",
|
||||
url: "{{out.url}}"
|
||||
},
|
||||
{%- endfor -%}
|
||||
].map(x => {
|
||||
x.id += backLinks.length;
|
||||
return x;
|
||||
});
|
||||
|
||||
const currentNode = {
|
||||
title: "{{page.fileSlug}}",
|
||||
id: 0,
|
||||
url: "{{page.url}}"
|
||||
};
|
||||
|
||||
const gData = {
|
||||
nodes: [
|
||||
currentNode, ...backLinks,
|
||||
...outbound
|
||||
],
|
||||
links: [
|
||||
...backLinks.map(backlink => ({source: backlink.id, target: 0})),
|
||||
...outbound.map(outlink => ({source: 0, target: outlink.id}))
|
||||
]
|
||||
};
|
||||
|
||||
gData
|
||||
.links
|
||||
.forEach(link => {
|
||||
const a = gData.nodes[link.source];
|
||||
const b = gData.nodes[link.target];
|
||||
!a.neighbors && (a.neighbors = []);
|
||||
!b.neighbors && (b.neighbors = []);
|
||||
a
|
||||
.neighbors
|
||||
.push(b);
|
||||
b
|
||||
.neighbors
|
||||
.push(a);
|
||||
|
||||
!a.links && (a.links = []);
|
||||
!b.links && (b.links = []);
|
||||
a
|
||||
.links
|
||||
.push(link);
|
||||
b
|
||||
.links
|
||||
.push(link);
|
||||
});
|
||||
|
||||
let Graph;
|
||||
function renderGraph(width, height) {
|
||||
|
||||
if (Graph) {
|
||||
Graph
|
||||
.width(width)
|
||||
.height(height);
|
||||
|
||||
Graph.zoomToFit()
|
||||
Graph.zoom(3)
|
||||
return;
|
||||
}
|
||||
|
||||
Graph = ForceGraph()(document.getElementById('link-graph'))
|
||||
.width(width)
|
||||
.height(height)
|
||||
.nodeCanvasObject((node, ctx) => {
|
||||
const nodeR = Math.min(7, Math.max((node.links.length + node.neighbors.length)/2, 2));
|
||||
ctx.beginPath();
|
||||
ctx.arc(node.x, node.y, nodeR, 0, 2 * Math.PI, false);
|
||||
ctx.fillStyle = getCssVar("--text-accent");
|
||||
ctx.fill();
|
||||
|
||||
const label = htmlDecode(node.title)
|
||||
const fontSize = 6;
|
||||
ctx.font = `${fontSize}px Sans-Serif`;
|
||||
|
||||
ctx.textAlign = 'center';
|
||||
ctx.textBaseline = 'top';
|
||||
ctx.fillStyle = getCssVar("--text-accent");
|
||||
ctx.fillText(label, node.x, node.y + nodeR + 2);
|
||||
})
|
||||
.linkColor(() => getCssVar("--text-normal"))
|
||||
.graphData(gData)
|
||||
.onNodeClick(node => {
|
||||
window.location = node.url;
|
||||
});
|
||||
|
||||
Graph.zoomToFit()
|
||||
Graph.zoom(3)
|
||||
}
|
||||
</script>
|
@ -7,7 +7,14 @@
|
||||
</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/prism.min.js" integrity="sha512-hpZ5pDCF2bRCweL5WoA0/N1elet1KYL5mx3LP555Eg/0ZguaHawxNvEjF6O3rufAChs16HVNhEc6blF/rZoowQ==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/plugins/autoloader/prism-autoloader.min.js" integrity="sha512-sv0slik/5O0JIPdLBCR2A3XDg/1U3WuDEheZfI/DI5n8Yqc3h5kjrnr46FGBNiUAJF7rE4LHKwQ/SoSLRKAxEA==" crossorigin="anonymous" referrerpolicy="no-referrer"></script>
|
||||
|
||||
|
||||
<script src="//unpkg.com/force-graph"></script>
|
||||
<script src="//unpkg.com/alpinejs" defer></script>
|
||||
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/prism/1.25.0/themes/prism-okaidia.min.css" integrity="sha512-mIs9kKbaw6JZFfSuo+MovjU+Ntggfoj8RwAmJbVXQ5mkAX5LlgETQEweFPI18humSPHymTb5iikEOKWF7I8ncQ==" crossorigin="anonymous" referrerpolicy="no-referrer"/>
|
||||
<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">
|
||||
|
||||
|
||||
<script src="https://polyfill.io/v3/polyfill.min.js?features=es6"></script>
|
||||
<link href="/styles/digital-garden-base.css" rel="stylesheet">
|
||||
|
68
src/site/_includes/components/sidebar.njk
Normal file
68
src/site/_includes/components/sidebar.njk
Normal file
@ -0,0 +1,68 @@
|
||||
<div x-init="isDesktop = window.innerWidth>=1100; rerenderGraph()"
|
||||
x-on:resize.window="isDesktop = (window.innerWidth>=1100) ? true : false;"
|
||||
x-data="sidebarData">
|
||||
|
||||
<div class="sidebar" :style="open && isDesktop ? 'box-shadow: -5px 0 15px rgba(0,0,0,0.3);' : ''">
|
||||
<div x-show="isDesktop" class="expand-line">
|
||||
|
||||
<button style="font-size: 48px; background: transparent;" @click="toggleOpen()">
|
||||
<i x-show="open" class="fa fa-caret-right"></i>
|
||||
<i x-show="!open" class="fa fa-caret-left"></i>
|
||||
</button>
|
||||
</div>
|
||||
|
||||
<div x-show="open || !isDesktop" class="sidebar-container">
|
||||
|
||||
{%if dgShowLocalGraph === true%}
|
||||
<div x-show="isDesktop">
|
||||
<h3>Graph</h3>
|
||||
<div id="link-graph" style="margin-bottom:20px; background-color: var(--background-primary); border-radius: 10px; width: fit-content;"></div>
|
||||
</div>
|
||||
{%endif%}
|
||||
|
||||
{%if dgShowBacklinks === true %}
|
||||
<h3>Links to this page</h3>
|
||||
{%- if backlinks.length === 0 -%}
|
||||
<div class="backlink-card">
|
||||
No backlinks
|
||||
</div>
|
||||
{%- endif -%}
|
||||
|
||||
{%- for backlink in backlinks -%}
|
||||
<div class="backlink-card">
|
||||
<a href="{{backlink.url}}">{{backlink.title}}</a>
|
||||
</div>
|
||||
{%- endfor -%}
|
||||
{%endif%}
|
||||
|
||||
</div>
|
||||
</div>
|
||||
|
||||
{%if dgShowLocalGraph === true %}
|
||||
{%include "components/graphScript.njk"%}
|
||||
{% endif %}
|
||||
|
||||
<script>
|
||||
document.addEventListener('alpine:init', () => {
|
||||
|
||||
const isDesktop = window.innerWidth >= 1100;
|
||||
{% if dgShowLocalGraph === true %}
|
||||
renderGraph(600, 400);
|
||||
{% endif %}
|
||||
|
||||
Alpine.data('sidebarData', () => ({
|
||||
open: false,
|
||||
isDesktop,
|
||||
toggleOpen() {
|
||||
this.open = !this.open;
|
||||
if (Graph) {
|
||||
setTimeout(() => {
|
||||
Graph.zoomToFit();
|
||||
Graph.zoom(3);
|
||||
}, 10);
|
||||
}
|
||||
}
|
||||
|
||||
}))
|
||||
})
|
||||
</script>
|
@ -16,6 +16,10 @@ permalink: "notes/{{ page.fileSlug | slugify }}/"
|
||||
<a href="/">🏡 Back Home</a>
|
||||
{% endif %}
|
||||
{{ content | link | highlight | safe}}
|
||||
|
||||
{% if dgShowBacklinks === true or dgShowLocalGraph === true%}
|
||||
{%include "components/sidebar.njk"%}
|
||||
{% endif %}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
|
101
src/site/index.11tydata.js
Normal file
101
src/site/index.11tydata.js
Normal file
@ -0,0 +1,101 @@
|
||||
|
||||
const wikilink = /\[\[(.*?\|.*?)\]\]/g
|
||||
|
||||
function caselessCompare(a, b) {
|
||||
return a.toLowerCase() === b.toLowerCase();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
eleventyComputed: {
|
||||
backlinks: (data) => {
|
||||
const notes = data.collections.note;
|
||||
if(!notes){
|
||||
return [];
|
||||
}
|
||||
const currentFileSlug = data.page.filePathStem.replace('/notes/', '');
|
||||
|
||||
let backlinks = [];
|
||||
let counter = 1;
|
||||
|
||||
for (const otherNote of notes) {
|
||||
const noteContent = otherNote.template.frontMatter.content;
|
||||
|
||||
const outboundLinks = (noteContent.match(wikilink) || []).map(link => (
|
||||
link.slice(2, -2)
|
||||
.split("|")[0]
|
||||
.replace(/.(md|markdown)\s?$/i, "")
|
||||
.trim()
|
||||
));
|
||||
|
||||
if (outboundLinks.some(link => caselessCompare(link, currentFileSlug))) {
|
||||
|
||||
let preview = noteContent.slice(0, 240);
|
||||
backlinks.push({
|
||||
url: otherNote.url,
|
||||
title: otherNote.data.page.fileSlug,
|
||||
preview,
|
||||
id: counter++
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return backlinks;
|
||||
|
||||
},
|
||||
outbound: (data) => {
|
||||
const notes = data.collections.note;
|
||||
|
||||
if(!notes || notes.length == 0){
|
||||
return [];
|
||||
}
|
||||
|
||||
const currentNote = data.collections.gardenEntry && data.collections.gardenEntry[0];
|
||||
if(!currentNote){
|
||||
return [];
|
||||
}
|
||||
|
||||
|
||||
let counter = 1;
|
||||
|
||||
const noteContent = currentNote.template.frontMatter.content;
|
||||
|
||||
const outboundLinks = (noteContent.match(wikilink) || []).map(link => (
|
||||
link.slice(2, -2)
|
||||
.split("|")[0]
|
||||
.replace(/.(md|markdown)\s?$/i, "")
|
||||
.trim()
|
||||
));
|
||||
|
||||
let outbound = outboundLinks.map(fileslug => {
|
||||
var outboundNote = notes.find(x => caselessCompare(x.data.page.filePathStem.replace("/notes/", ""), fileslug));
|
||||
if(!outboundNote){
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
url: outboundNote.url,
|
||||
title: outboundNote.data.page.fileSlug,
|
||||
id: counter++
|
||||
}
|
||||
}).filter(x=>x);
|
||||
|
||||
return outbound;
|
||||
|
||||
},
|
||||
dgShowLocalGraph: (data) => {
|
||||
const currentNote = data.collections.gardenEntry && data.collections.gardenEntry[0];
|
||||
if(currentNote && currentNote.data && currentNote.data.dgShowLocalGraph){
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
},
|
||||
dgShowBacklinks: (data) =>{
|
||||
const currentnote = data.collections.gardenEntry && data.collections.gardenEntry[0];
|
||||
if(currentnote && currentnote.data && currentnote.data.dgShowLocalGraph){
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
@ -7,9 +7,14 @@
|
||||
<body class="theme-{{meta.baseTheme}} markdown-preview-view">
|
||||
{%include "components/notegrowthhistory.njk"%}
|
||||
<div class="content">
|
||||
|
||||
{%- for garden in collections.gardenEntry -%}
|
||||
{{garden.templateContent | link | highlight | safe }}
|
||||
{%- endfor -%}
|
||||
|
||||
{%if collections.gardenEntry[0].data.dgShowBacklinks === true or collections.gardenEntry[0].data.dgShowLocalGraph === true%}
|
||||
{%include "components/sidebar.njk" %}
|
||||
{%endif%}
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
</html>
|
86
src/site/notes/notes.11tydata.js
Normal file
86
src/site/notes/notes.11tydata.js
Normal file
@ -0,0 +1,86 @@
|
||||
|
||||
const wikilink = /\[\[(.*?\|.*?)\]\]/g
|
||||
|
||||
function caselessCompare(a, b) {
|
||||
return a.toLowerCase() === b.toLowerCase();
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
eleventyComputed: {
|
||||
backlinks: (data) => {
|
||||
const notes = data.collections.note;
|
||||
if(!notes){
|
||||
return [];
|
||||
}
|
||||
const currentFileSlug = data.page.filePathStem.replace('/notes/', '');
|
||||
|
||||
let backlinks = [];
|
||||
let counter = 1;
|
||||
|
||||
for (const otherNote of notes) {
|
||||
const noteContent = otherNote.template.frontMatter.content;
|
||||
|
||||
const outboundLinks = (noteContent.match(wikilink) || []).map(link => (
|
||||
link.slice(2, -2)
|
||||
.split("|")[0]
|
||||
.replace(/.(md|markdown)\s?$/i, "")
|
||||
.trim()
|
||||
));
|
||||
|
||||
if (outboundLinks.some(link => caselessCompare(link, currentFileSlug))) {
|
||||
|
||||
let preview = noteContent.slice(0, 240);
|
||||
backlinks.push({
|
||||
url: otherNote.url,
|
||||
title: otherNote.data.page.fileSlug,
|
||||
preview,
|
||||
id: counter++
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
return backlinks;
|
||||
|
||||
},
|
||||
outbound: (data) => {
|
||||
const notes = data.collections.note;
|
||||
const currentFileSlug = data.page.filePathStem.replace('/notes/', '');
|
||||
|
||||
if(!notes || notes.length == 0){
|
||||
return [];
|
||||
}
|
||||
|
||||
const currentNote = notes.find(x =>x.data.page.filePathStem && caselessCompare(x.data.page.filePathStem.replace('/notes/', ''), currentFileSlug));
|
||||
if(!currentNote){
|
||||
return [];
|
||||
}
|
||||
|
||||
let counter = 1;
|
||||
|
||||
const noteContent = currentNote.template.frontMatter.content;
|
||||
|
||||
const outboundLinks = (noteContent.match(wikilink) || []).map(link => (
|
||||
link.slice(2, -2)
|
||||
.split("|")[0]
|
||||
.replace(/.(md|markdown)\s?$/i, "")
|
||||
.trim()
|
||||
));
|
||||
|
||||
let outbound = outboundLinks.map(fileslug => {
|
||||
var outboundNote = notes.find(x => caselessCompare(x.data.page.filePathStem.replace("/notes/", ""), fileslug));
|
||||
if(!outboundNote){
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
url: outboundNote.url,
|
||||
title: outboundNote.data.page.fileSlug,
|
||||
id: counter++
|
||||
}
|
||||
}).filter(x=>x);
|
||||
|
||||
return outbound;
|
||||
|
||||
}
|
||||
}
|
||||
}
|
@ -60,6 +60,49 @@ ul.task-list {
|
||||
padding-left: 15px;
|
||||
}
|
||||
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 50%;
|
||||
transform: translateY(-50%);
|
||||
right: 0;
|
||||
height: 100%;
|
||||
background-color: var(--background-secondary);
|
||||
min-width: 25px;
|
||||
display: flex;
|
||||
}
|
||||
|
||||
.expand-line {
|
||||
display: flex;
|
||||
flex-direction: column;
|
||||
justify-content: center;
|
||||
}
|
||||
|
||||
.sidebar-container {
|
||||
padding: 20px;
|
||||
width: 100%;
|
||||
overflow-y: auto;
|
||||
}
|
||||
|
||||
.backlink-card {
|
||||
padding: 20px;
|
||||
border-radius: 4px;
|
||||
margin-bottom: 20px;
|
||||
background-color: var(--background-primary);
|
||||
width: 100%;
|
||||
|
||||
}
|
||||
|
||||
@media(max-width: 1100px) {
|
||||
.sidebar {
|
||||
position: relative;
|
||||
transform: none;
|
||||
border-radius: 4px;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
div[class*="language-ad-"],
|
||||
div[class*="callout-"] {
|
||||
font-family: 'Roboto', sans-serif;
|
||||
|
@ -3,6 +3,14 @@
|
||||
* MODIFY THE custom-style.scss FILE INSTEAD.
|
||||
***/
|
||||
|
||||
|
||||
body{
|
||||
--background-primary: rgb(32, 31, 31);
|
||||
--background-secondary: rgb(57, 56, 56);
|
||||
--text-normal: #dcddde;
|
||||
--text-accent: rgb(97, 186, 245);
|
||||
}
|
||||
|
||||
h1 {
|
||||
color: #FFEF60;
|
||||
}
|
||||
@ -19,6 +27,12 @@ h4 {
|
||||
color: #72DCFF;
|
||||
}
|
||||
|
||||
button {
|
||||
border: none;
|
||||
color: white;
|
||||
padding: 5px 15px;
|
||||
}
|
||||
|
||||
.centered {
|
||||
position: absolute;
|
||||
top: 50%;
|
||||
@ -28,8 +42,8 @@ h4 {
|
||||
}
|
||||
|
||||
.theme-dark {
|
||||
background: rgb(32, 31, 31);
|
||||
color: white;
|
||||
background: var(--background-primary);
|
||||
color: var(--text-normal);
|
||||
font-family: 'Roboto', sans-serif;
|
||||
}
|
||||
|
||||
@ -44,7 +58,7 @@ a.is-unresolved{
|
||||
}
|
||||
a {
|
||||
text-decoration: underline;
|
||||
color: rgb(97, 186, 245);
|
||||
color: var(--text-accent)
|
||||
}
|
||||
|
||||
.font-bg {
|
||||
|
Loading…
x
Reference in New Issue
Block a user