From d4ed6492adc553f0b31a4546636f430a212742e3 Mon Sep 17 00:00:00 2001 From: Evan Pratten Date: Sat, 21 Oct 2023 15:18:45 -0400 Subject: [PATCH] Implement a plugin to improve inter-page links --- .gitignore | 4 +- config.toml | 1 + content/.obsidian/community-plugins.json | 4 +- content/.obsidian/graph.json | 2 +- content/.obsidian/hotkeys.json | 11 +- content/.obsidian/plugins/hot-reload/main.js | 110 ++++++++++++++++++ .../plugins/hot-reload/manifest.json | 10 ++ .../plugins/link-interceptor/.hotreload | 0 .../plugins/link-interceptor/main.js | 87 ++++++++++++++ .../plugins/link-interceptor/manifest.json | 11 ++ content/blog/2023-07-07-ampr-vpn.md | 14 +-- content/blog/2023-10-20-obsidian-blogging.md | 4 +- 12 files changed, 244 insertions(+), 14 deletions(-) create mode 100644 content/.obsidian/plugins/hot-reload/main.js create mode 100644 content/.obsidian/plugins/hot-reload/manifest.json create mode 100644 content/.obsidian/plugins/link-interceptor/.hotreload create mode 100644 content/.obsidian/plugins/link-interceptor/main.js create mode 100644 content/.obsidian/plugins/link-interceptor/manifest.json diff --git a/.gitignore b/.gitignore index 1997864..935aac1 100644 --- a/.gitignore +++ b/.gitignore @@ -19,6 +19,8 @@ Cargo.lock /content/.obsidian/themes /content/.obsidian/appearance.json /content/.obsidian/workspace.json +/content/@ # Python -.venv \ No newline at end of file +.venv + diff --git a/config.toml b/config.toml index e9c8a8a..8d73d12 100644 --- a/config.toml +++ b/config.toml @@ -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 diff --git a/content/.obsidian/community-plugins.json b/content/.obsidian/community-plugins.json index 6acaa19..00df5bc 100644 --- a/content/.obsidian/community-plugins.json +++ b/content/.obsidian/community-plugins.json @@ -1,3 +1,5 @@ [ - "obsidian-front-matter-title-plugin" + "obsidian-front-matter-title-plugin", + "link-interceptor", + "hot-reload" ] \ No newline at end of file diff --git a/content/.obsidian/graph.json b/content/.obsidian/graph.json index 8659094..af6df99 100644 --- a/content/.obsidian/graph.json +++ b/content/.obsidian/graph.json @@ -32,6 +32,6 @@ "repelStrength": 14.1666666666667, "linkStrength": 1, "linkDistance": 250, - "scale": 0.13187606777463332, + "scale": 0.07178423896278742, "close": false } \ No newline at end of file diff --git a/content/.obsidian/hotkeys.json b/content/.obsidian/hotkeys.json index 9e26dfe..7f5636a 100644 --- a/content/.obsidian/hotkeys.json +++ b/content/.obsidian/hotkeys.json @@ -1 +1,10 @@ -{} \ No newline at end of file +{ + "app:reload": [ + { + "modifiers": [ + "Mod" + ], + "key": "R" + } + ] +} \ No newline at end of file diff --git a/content/.obsidian/plugins/hot-reload/main.js b/content/.obsidian/plugins/hot-reload/main.js new file mode 100644 index 0000000..5b2d537 --- /dev/null +++ b/content/.obsidian/plugins/hot-reload/main.js @@ -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`); + } +} \ No newline at end of file diff --git a/content/.obsidian/plugins/hot-reload/manifest.json b/content/.obsidian/plugins/hot-reload/manifest.json new file mode 100644 index 0000000..9090f44 --- /dev/null +++ b/content/.obsidian/plugins/hot-reload/manifest.json @@ -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 +} \ No newline at end of file diff --git a/content/.obsidian/plugins/link-interceptor/.hotreload b/content/.obsidian/plugins/link-interceptor/.hotreload new file mode 100644 index 0000000..e69de29 diff --git a/content/.obsidian/plugins/link-interceptor/main.js b/content/.obsidian/plugins/link-interceptor/main.js new file mode 100644 index 0000000..e0945bd --- /dev/null +++ b/content/.obsidian/plugins/link-interceptor/main.js @@ -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]; + } + } + } + +}; \ No newline at end of file diff --git a/content/.obsidian/plugins/link-interceptor/manifest.json b/content/.obsidian/plugins/link-interceptor/manifest.json new file mode 100644 index 0000000..34a248f --- /dev/null +++ b/content/.obsidian/plugins/link-interceptor/manifest.json @@ -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 ", + "authorUrl": "https://ewpratten.com", + "isDesktopOnly": false, + "fundingUrl": "https://ewp.fyi/sponsor" +} diff --git a/content/blog/2023-07-07-ampr-vpn.md b/content/blog/2023-07-07-ampr-vpn.md index 67186e3..7e25553 100644 --- a/content/blog/2023-07-07-ampr-vpn.md +++ b/content/blog/2023-07-07-ampr-vpn.md @@ -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: diff --git a/content/blog/2023-10-20-obsidian-blogging.md b/content/blog/2023-10-20-obsidian-blogging.md index 5531514..d00cc52 100644 --- a/content/blog/2023-10-20-obsidian-blogging.md +++ b/content/blog/2023-10-20-obsidian-blogging.md @@ -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