1

Implement a plugin to improve inter-page links

This commit is contained in:
Evan Pratten 2023-10-21 15:18:45 -04:00
parent 9d28b89824
commit d4ed6492ad
12 changed files with 244 additions and 14 deletions

4
.gitignore vendored

@ -19,6 +19,8 @@ Cargo.lock
/content/.obsidian/themes
/content/.obsidian/appearance.json
/content/.obsidian/workspace.json
/content/@
# Python
.venv
.venv

@ -7,6 +7,7 @@ build_search_index = true
generate_feed = true
feed_filename = "rss.xml"
minify_html = false # This breaks mermaid diagrams :(
ignored_content = ["content/@/blog/*.md"]
[markdown]
highlight_code = true

@ -1,3 +1,5 @@
[
"obsidian-front-matter-title-plugin"
"obsidian-front-matter-title-plugin",
"link-interceptor",
"hot-reload"
]

@ -32,6 +32,6 @@
"repelStrength": 14.1666666666667,
"linkStrength": 1,
"linkDistance": 250,
"scale": 0.13187606777463332,
"scale": 0.07178423896278742,
"close": false
}

@ -1 +1,10 @@
{}
{
"app:reload": [
{
"modifiers": [
"Mod"
],
"key": "R"
}
]
}

@ -0,0 +1,110 @@
const {Plugin, Notice, debounce} = require("obsidian");
const fs = require("fs");
const watchNeeded = window.process.platform !== "darwin" && window.process.platform !== "win32";
module.exports = class HotReload extends Plugin {
statCache = new Map(); // path -> Stat
queue = Promise.resolve();
run(val, err) {
return this.queue = this.queue.then(val, err);
}
reindexPlugins = debounce(() => this.run(() => this.getPluginNames()), 500, true);
requestScan = debounce(() => this.run(() => this.checkVersions()), 250, true);
onload() {
app.workspace.onLayoutReady(async ()=> {
this.pluginReloaders = {};
this.inProgress = null;
await this.getPluginNames();
this.registerEvent( this.app.vault.on("raw", this.requestScan));
this.watch(".obsidian/plugins");
this.requestScan();
this.addCommand({
id: "scan-for-changes",
name: "Check plugins for changes and reload them",
callback: () => this.requestScan()
})
});
}
watch(path) {
if (this.app.vault.adapter.watchers.hasOwnProperty(path)) return;
const realPath = [this.app.vault.adapter.basePath, path].join("/");
const lstat = fs.lstatSync(realPath);
if (lstat && (watchNeeded || lstat.isSymbolicLink()) && fs.statSync(realPath).isDirectory()) {
this.app.vault.adapter.startWatchPath(path, false);
}
}
async checkVersions() {
const base = this.app.plugins.getPluginFolder();
for (const dir of Object.keys(this.pluginNames)) {
for (const file of ["manifest.json", "main.js", "styles.css", ".hotreload"]) {
const path = `${base}/${dir}/${file}`;
const stat = await app.vault.adapter.stat(path);
if (stat) {
if (this.statCache.has(path) && stat.mtime !== this.statCache.get(path).mtime) {
this.onFileChange(path);
}
this.statCache.set(path, stat);
}
}
}
}
async getPluginNames() {
const plugins = {}, enabled = new Set();
for (const {id, dir} of Object.values(app.plugins.manifests)) {
this.watch(dir);
plugins[dir.split("/").pop()] = id;
if (
await this.app.vault.exists(dir+"/.git") ||
await this.app.vault.exists(dir+"/.hotreload")
) enabled.add(id);
}
this.pluginNames = plugins;
this.enabledPlugins = enabled;
}
onFileChange(filename) {
if (!filename.startsWith(this.app.plugins.getPluginFolder()+"/")) return;
const path = filename.split("/");
const base = path.pop(), dir = path.pop();
if (path.length === 1 && dir === "plugins") return this.watch(filename);
if (path.length != 2) return;
const plugin = dir && this.pluginNames[dir];
if (base === "manifest.json" || base === ".hotreload" || base === ".git" || !plugin) return this.reindexPlugins();
if (base !== "main.js" && base !== "styles.css") return;
if (!this.enabledPlugins.has(plugin)) return;
const reloader = this.pluginReloaders[plugin] || (
this.pluginReloaders[plugin] = debounce(() => this.run(() => this.reload(plugin), console.error), 750, true)
);
reloader();
}
async reload(plugin) {
const plugins = app.plugins;
// Don't reload disabled plugins
if (!plugins.enabledPlugins.has(plugin)) return;
await plugins.disablePlugin(plugin);
console.debug("disabled", plugin);
// Ensure sourcemaps are loaded (Obsidian 14+)
const oldDebug = localStorage.getItem("debug-plugin");
localStorage.setItem("debug-plugin", "1");
try {
await plugins.enablePlugin(plugin);
} finally {
// Restore previous setting
if (oldDebug === null) localStorage.removeItem("debug-plugin"); else localStorage.setItem("debug-plugin", oldDebug);
}
console.debug("enabled", plugin);
new Notice(`Plugin "${plugin}" has been reloaded`);
}
}

@ -0,0 +1,10 @@
{
"id": "hot-reload",
"name": "Hot Reload",
"author": "PJ Eby",
"authorUrl": "https://github.com/pjeby",
"version": "0.1.10",
"minAppVersion": "0.15.9",
"description": "Automatically reload in-development plugins when their files are changed",
"isDesktopOnly": true
}

@ -0,0 +1,87 @@
const { MarkdownPostProcessorContext, Plugin } = require('obsidian');
module.exports = class LinkInterceptorPlugin extends Plugin {
async onload() {
console.log('Intercept Links plugin loaded');
// // Register an event to handle link clicks
// this.registerEvent(
// this.app.workspace.on('link-clicked',(args)=>{console.log(args);})
// );
// Register an event to handle opening another file through a markdown link
this.registerEvent(
this.app.workspace.on('file-open', (file) => {
if (file) {
console.log(`File open request for: ${file.path}`);
// If the path starts with an `@` then it's a link to another file
if (file.path.startsWith('@/')) {
// remove the @/
const newLink = file.path.substring(2);
// open the new link
this.app.workspace.openLinkText(newLink, '', false);
// Delete the auto-created file
this.app.vault.delete(file);
}
}
})
);
// Rewrite all resolved links in the metadata cache
this.resolveAtLinks();
this.registerEvent(
this.app.metadataCache.on('resolved-links', () => {
this.resolveAtLinks();
})
);
this.registerEvent(
this.app.metadataCache.on('resolve', () => {
this.resolveAtLinks();
})
);
this.registerEvent(
this.app.metadataCache.on('changed', () => {
this.resolveAtLinks();
})
);
}
resolveAtLinks() {
let unresolved_links = this.app.metadataCache.unresolvedLinks;
let unsreolved_at_links = {};
for (let source in unresolved_links) {
for (let destination in unresolved_links[source]) {
if (destination.startsWith('@')) {
if (!unsreolved_at_links.hasOwnProperty(source)) {
unsreolved_at_links[source] = {};
}
unsreolved_at_links[source][destination] = unresolved_links[source][destination];
}
}
}
console.log(`Found ${Object.keys(unsreolved_at_links).length} unresolved @ links`);
// Resolve the links
for (let source in unsreolved_at_links) {
for (let destination in unsreolved_at_links[source]) {
// remove the @/
const newLink = destination.substring(2);
// Add to resolvedLinks
if (!app.metadataCache.resolvedLinks[source].hasOwnProperty(`${newLink}.md`)) {
app.metadataCache.resolvedLinks[source][`${newLink}.md`] = 0;
}
app.metadataCache.resolvedLinks[source][`${newLink}.md`] += unsreolved_at_links[source][destination];
console.log(`Resolved @ link: ${source} -> ${newLink}`);
console.log(app.metadataCache.resolvedLinks[source]);
// Remove the old link
delete unsreolved_at_links[source][destination];
delete this.app.metadataCache.unresolvedLinks[source][destination];
}
}
}
};

@ -0,0 +1,11 @@
{
"id": "link-interceptor",
"name": "Zola Link Interceptor",
"version": "0.0.1",
"minAppVersion": "0.16.3",
"description": "Handles Zola's @ links",
"author": "Evan Pratten <evan@ewpratten.com>",
"authorUrl": "https://ewpratten.com",
"isDesktopOnly": false,
"fundingUrl": "https://ewp.fyi/sponsor"
}

@ -4,21 +4,19 @@ title: Building a VPN with an AMPRNet BGP allocation
description: A quick guide on using your shiny new AMPRNet allocation
date: 2023-07-07
tags:
- networking
- amprnet
- bgp
- networking
- amprnet
- bgp
draft: false
extra:
auto_center_images: true
excerpt: A guide on setting up a VPS, announcing a prefix over BGP, and using it
as a VPN server
excerpt: A guide on setting up a VPS, announcing a prefix over BGP, and using it as a VPN server
discuss:
reddit: https://www.reddit.com/r/ewpratten/comments/14tdltu/building_a_vpn_with_an_amprnet_bgp_allocation/
hacker_news: https://news.ycombinator.com/item?id=36635146
uses:
- mermaid
aliases:
- /blog/ampr-vpn
- mermaid
aliases: []
---
One of the most common emails I receive from readers of this website generally starts with:

@ -15,10 +15,10 @@ extra:
aliases:
- /blog/obsidian-blogging
---
It recently occurred to me that [Obsidian](https://obsidian.md) is capable of editing *any* type of markdown document store, not just its own note "Vaults". So, as a test I've been using it to interface with the source files that make up this website.
This post largely exists for the sake of figuring out how Obsidian behaves when forced into an environment that doesn't entirely agree with the "obsidian way of doing things".
This post largely exists for the sake of figuring out how Obsidian behaves when forced into an environment that doesn't entirely agree with the "obsidian way of doing things".
[test link](@/blog/2023-07-07-ampr-vpn.md)
---
- Editing extra field is hard