Currently, at least on Windows, if you try to open a link like:
[%LocalAppData%/nvim](<file:///%LocalAppData%/nvim>)
you will get an error because %LocalAppData% is processed as it is.
I wrote a script that detects when user clicks on links containing a user environment and resolves it before opening the path.
Currently, it handles Windows only. You are welcome if you want to post your solution to handle other OS.
List of user environments that can be resolved:
Prerequisite
It is not a plugin but a JS script. I personally use user plugins to run my scripts. You could use another solution like CodeScript Toolkit but I didn’t test my script with it. It could require some modification to work with it.
Setup
Create a file named like Resolve Env in link.js and put it in your the scripts folder set in user plugins. In my vault, it is MyScripts/UserPlugins. Paste this code in the file:
// check https://forum.obsidian.md/t/resolve-user-environment-in-local-links-before-opening/106566
module.exports = {};
const Plugin = require("obsidian").Plugin;
const { exec } = require("child_process");
const { shell } = require("electron");
const prefix = "Resolve Env in link";
module.exports.default = class MyPlugin extends Plugin {
documentClickHandler = (evt) => {
const target = evt.target;
// if target is not a url, exit
if (!(target instanceof HTMLAnchorElement)) {
return;
}
const href = target.href;
log(`User clicked on the link: "${href}"`);
if (!href.startsWith("file:///")) {
log("Ignored because link is not a local path.");
return;
}
let transformedHref = decodeURIComponent(href);
log(`Decoded link: "${transformedHref}"`);
let path = transformedHref.replace("file:///", "");
log(`Trying to resolve this path: "${path}"`);
log(`path match /%\\w+%/ : ${/%\w+%/.test(path)}`);
// Apply resolveEnv only if %env% is present
if (/%\w+%/.test(path)) {
path = resolveEnv(path);
log(`Resolved path: "${path}"`);
} else {
log("No env var pattern found, keeping original path.");
}
transformedHref = `file:///${encodeURIComponent(path)}`;
log(`Open this new url: "${transformedHref}"`);
evt.preventDefault();
evt.stopPropagation();
evt.stopImmediatePropagation();
try {
// Using exec to run a shell command is required to open the default app in foreground (at least on Windows)
const command = getOpenCommand(path);
log(`Executing command: ${command}`);
new Promise((resolve, reject) => {
exec(command, (error, stdout, stderr) => {
if (error) {
log(`Error opening file: ${error.message}`);
reject(error);
} else {
log("File opened successfully.");
resolve(null);
}
});
});
} catch (error) {
log(`Error opening file: ${error.message}`);
}
};
async onload() {
log("script loaded. Listening click events.");
document.addEventListener("click", this.documentClickHandler, true);
}
async onunload() {
log("script unloaded. Click events unregistered.");
document.removeEventListener("click", this.documentClickHandler, true);
}
};
/**==========================================================================
* ℹ UTILS
===========================================================================*/
function resolveEnv(path) {
log("START resolveEnv");
// Create oEnv object with specified keys
const oEnv = {
AppData: process.env.APPDATA,
LocalAppData: process.env.LOCALAPPDATA,
OneDrive: process.env.ONEDRIVE,
UserProfile: process.env.USERPROFILE,
AllUsersProfile: process.env.ALLUSERSPROFILE,
ProgramData: process.env.PROGRAMDATA,
ProgramFiles: process.env.PROGRAMFILES,
WinDir: process.env.WINDIR,
};
// Debug object
log(`oEnv: ${JSON.stringify(oEnv, null, 2)}`);
// Check if the path begins with one of the oEnv key
for (const [key, value] of Object.entries(oEnv)) {
if (value && path.startsWith(`%${key}%`)) {
// Replace the corresponding part with the environment variable
const newPath = path.replace(`%${key}%`, value);
log(`Path matched with ${key}, newPath: ${newPath}`);
return newPath;
}
}
// If no match, return the original path
log("No environment variable match found, returning original path");
return path;
}
function getOpenCommand(path) {
const platform = process.platform;
if (platform === "win32") {
return `start "" "${path.replace(/"/g, '\\"')}"`;
} else if (platform === "darwin") {
return `open "${path}"`;
} else {
return `xdg-open "${path}"`; // Linux
}
}
function log(msg) {
console.log(`[${prefix}] ${msg}`);
}
In user plugins settings, reload the scripts then enable Resolve Env in link.js
Test
Copy a path containing a user environment. You can use this templater script to auto convert your local path to a string using the right user environment and pretty paste the link in your note.
Open the link. It should open with no error.
Update
Subscribe to this topic to be notified of updates.
Dev
You’re welcome to transform this script into a plugin, improve it, etc. Please just share with us in this topic.
Like if you appreciated this tip.
