Initial global Obsidian config
Shared config extracted from obsidian-config; vault-specific files (bookmarks.json, daily-notes.json) excluded. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01ASYabtTgxSxJPjxPmBK9PJ
This commit is contained in:
253
plugins/multi-vault-links-0.1.4/main.js
Normal file
253
plugins/multi-vault-links-0.1.4/main.js
Normal file
@@ -0,0 +1,253 @@
|
||||
"use strict";
|
||||
var __defProp = Object.defineProperty;
|
||||
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
||||
var __getOwnPropNames = Object.getOwnPropertyNames;
|
||||
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
||||
var __export = (target, all) => {
|
||||
for (var name in all)
|
||||
__defProp(target, name, { get: all[name], enumerable: true });
|
||||
};
|
||||
var __copyProps = (to, from, except, desc) => {
|
||||
if (from && typeof from === "object" || typeof from === "function") {
|
||||
for (let key of __getOwnPropNames(from))
|
||||
if (!__hasOwnProp.call(to, key) && key !== except)
|
||||
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
||||
}
|
||||
return to;
|
||||
};
|
||||
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
||||
|
||||
// src/main.ts
|
||||
var main_exports = {};
|
||||
__export(main_exports, {
|
||||
createLinkEl: () => createLinkEl,
|
||||
default: () => MultiVaultLinksPlugin,
|
||||
openVaultUri: () => openVaultUri
|
||||
});
|
||||
module.exports = __toCommonJS(main_exports);
|
||||
var import_obsidian2 = require("obsidian");
|
||||
|
||||
// src/parse.ts
|
||||
var LINK_RE = /\[\[([^\]]+?)\]\]/g;
|
||||
var VAULT_RE = /^\s*([^#/|:]+?)\s*:\s*([\s\S]+)$/;
|
||||
function parseInner(inner) {
|
||||
const m = VAULT_RE.exec(inner);
|
||||
if (!m)
|
||||
return null;
|
||||
const vault = m[1].trim();
|
||||
let rest = m[2];
|
||||
let alias = null;
|
||||
const pipe = rest.indexOf("|");
|
||||
if (pipe !== -1) {
|
||||
alias = rest.slice(pipe + 1).trim();
|
||||
rest = rest.slice(0, pipe);
|
||||
}
|
||||
const hash = rest.indexOf("#");
|
||||
let file;
|
||||
let subpath;
|
||||
if (hash !== -1) {
|
||||
file = rest.slice(0, hash).trim();
|
||||
subpath = rest.slice(hash).trim();
|
||||
} else {
|
||||
file = rest.trim();
|
||||
subpath = "";
|
||||
}
|
||||
if (!vault || !file)
|
||||
return null;
|
||||
const display = `${vault}: ${file}${subpath}`;
|
||||
return { vault, file, subpath, alias, display };
|
||||
}
|
||||
function findVaultLinks(text) {
|
||||
const out = [];
|
||||
LINK_RE.lastIndex = 0;
|
||||
let m;
|
||||
while ((m = LINK_RE.exec(text)) !== null) {
|
||||
const parsed = parseInner(m[1]);
|
||||
if (!parsed)
|
||||
continue;
|
||||
out.push({
|
||||
...parsed,
|
||||
raw: m[0],
|
||||
start: m.index,
|
||||
end: m.index + m[0].length
|
||||
});
|
||||
}
|
||||
return out;
|
||||
}
|
||||
function parseVaultTarget(target) {
|
||||
return parseInner(target);
|
||||
}
|
||||
function buildObsidianUri(link) {
|
||||
const vault = encodeURIComponent(link.vault);
|
||||
const file = encodeURIComponent(`${link.file}${link.subpath}`);
|
||||
return `obsidian://open?vault=${vault}&file=${file}`;
|
||||
}
|
||||
|
||||
// src/livePreview.ts
|
||||
var import_view = require("@codemirror/view");
|
||||
var import_state = require("@codemirror/state");
|
||||
var import_obsidian = require("obsidian");
|
||||
var VaultLinkWidget = class extends import_view.WidgetType {
|
||||
constructor(link) {
|
||||
super();
|
||||
this.link = link;
|
||||
}
|
||||
eq(other) {
|
||||
return other.link.raw === this.link.raw;
|
||||
}
|
||||
toDOM() {
|
||||
const a = document.createElement("a");
|
||||
a.className = "multi-vault-link cm-multi-vault-link";
|
||||
const uri = buildObsidianUri(this.link);
|
||||
a.href = uri;
|
||||
a.setAttribute("aria-label", `${this.link.vault} \u2192 ${this.link.file}${this.link.subpath}`);
|
||||
a.textContent = this.link.display;
|
||||
a.addEventListener("click", (evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
window.open(uri);
|
||||
});
|
||||
return a;
|
||||
}
|
||||
ignoreEvent() {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
function selectionTouches(view, from, to) {
|
||||
return view.state.selection.ranges.some((r) => r.from <= to && r.to >= from);
|
||||
}
|
||||
function buildDecorations(view) {
|
||||
const builder = new import_state.RangeSetBuilder();
|
||||
const livePreview = view.state.field(import_obsidian.editorLivePreviewField);
|
||||
for (const { from, to } of view.visibleRanges) {
|
||||
const text = view.state.doc.sliceString(from, to);
|
||||
for (const link of findVaultLinks(text)) {
|
||||
const start = from + link.start;
|
||||
const end = from + link.end;
|
||||
if (!livePreview || selectionTouches(view, start, end)) {
|
||||
builder.add(start, end, import_view.Decoration.mark({ class: "cm-multi-vault-source" }));
|
||||
continue;
|
||||
}
|
||||
builder.add(
|
||||
start,
|
||||
end,
|
||||
import_view.Decoration.replace({ widget: new VaultLinkWidget(link) })
|
||||
);
|
||||
}
|
||||
}
|
||||
return builder.finish();
|
||||
}
|
||||
var multiVaultLivePreview = import_view.ViewPlugin.fromClass(
|
||||
class {
|
||||
constructor(view) {
|
||||
this.decorations = buildDecorations(view);
|
||||
}
|
||||
update(update) {
|
||||
const modeChanged = update.startState.field(import_obsidian.editorLivePreviewField) !== update.state.field(import_obsidian.editorLivePreviewField);
|
||||
if (update.docChanged || update.viewportChanged || update.selectionSet || modeChanged) {
|
||||
this.decorations = buildDecorations(update.view);
|
||||
}
|
||||
}
|
||||
},
|
||||
{ decorations: (v) => v.decorations }
|
||||
);
|
||||
|
||||
// src/main.ts
|
||||
var SKIP_TAGS = /* @__PURE__ */ new Set(["CODE", "PRE", "A"]);
|
||||
function isInsideSkipped(node) {
|
||||
let el = node.parentElement;
|
||||
while (el) {
|
||||
if (SKIP_TAGS.has(el.tagName))
|
||||
return true;
|
||||
el = el.parentElement;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
function openVaultUri(uri, win = window) {
|
||||
win.open(uri);
|
||||
}
|
||||
function attachOpenHandler(a, uri) {
|
||||
a.addEventListener("click", (evt) => {
|
||||
evt.preventDefault();
|
||||
evt.stopPropagation();
|
||||
openVaultUri(uri, a.ownerDocument.defaultView ?? window);
|
||||
});
|
||||
}
|
||||
function buildAnchor(doc, link, display) {
|
||||
const a = doc.createElement("a");
|
||||
a.addClass("multi-vault-link");
|
||||
const uri = buildObsidianUri(link);
|
||||
a.setAttribute("href", uri);
|
||||
a.setAttribute("aria-label", `${link.vault} \u2192 ${link.file}${link.subpath}`);
|
||||
a.textContent = display;
|
||||
attachOpenHandler(a, uri);
|
||||
return a;
|
||||
}
|
||||
function createLinkEl(doc, link) {
|
||||
return buildAnchor(doc, link, link.display);
|
||||
}
|
||||
function rewriteInternalLinks(el) {
|
||||
el.querySelectorAll("a.internal-link").forEach((a) => {
|
||||
const target = a.getAttribute("data-href") ?? a.getAttribute("href") ?? "";
|
||||
const link = parseVaultTarget(target);
|
||||
if (!link)
|
||||
return;
|
||||
a.replaceWith(buildAnchor(a.ownerDocument, link, link.display));
|
||||
});
|
||||
}
|
||||
function rewriteTextNode(node) {
|
||||
const text = node.nodeValue ?? "";
|
||||
const links = findVaultLinks(text);
|
||||
if (links.length === 0)
|
||||
return;
|
||||
const doc = node.ownerDocument;
|
||||
const frag = doc.createDocumentFragment();
|
||||
let cursor = 0;
|
||||
for (const link of links) {
|
||||
if (link.start > cursor) {
|
||||
frag.appendChild(doc.createTextNode(text.slice(cursor, link.start)));
|
||||
}
|
||||
frag.appendChild(createLinkEl(doc, link));
|
||||
cursor = link.end;
|
||||
}
|
||||
if (cursor < text.length) {
|
||||
frag.appendChild(doc.createTextNode(text.slice(cursor)));
|
||||
}
|
||||
node.replaceWith(frag);
|
||||
}
|
||||
var MultiVaultLinksPlugin = class extends import_obsidian2.Plugin {
|
||||
async onload() {
|
||||
this.registerMarkdownPostProcessor((el) => {
|
||||
rewriteInternalLinks(el);
|
||||
const walker = el.ownerDocument.createTreeWalker(el, NodeFilter.SHOW_TEXT);
|
||||
const targets = [];
|
||||
let n;
|
||||
while (n = walker.nextNode()) {
|
||||
const t = n;
|
||||
if (!t.nodeValue || !t.nodeValue.includes("[["))
|
||||
continue;
|
||||
if (isInsideSkipped(t))
|
||||
continue;
|
||||
targets.push(t);
|
||||
}
|
||||
targets.forEach(rewriteTextNode);
|
||||
});
|
||||
this.registerEditorExtension(multiVaultLivePreview);
|
||||
this.patchOpenLinkText();
|
||||
}
|
||||
patchOpenLinkText() {
|
||||
const ws = this.app.workspace;
|
||||
const original = ws.openLinkText.bind(ws);
|
||||
ws.openLinkText = (linktext, sourcePath, newLeaf, viewState) => {
|
||||
const link = parseVaultTarget(linktext);
|
||||
if (link) {
|
||||
openVaultUri(buildObsidianUri(link));
|
||||
return Promise.resolve();
|
||||
}
|
||||
return original(linktext, sourcePath, newLeaf, viewState);
|
||||
};
|
||||
this.register(() => {
|
||||
ws.openLinkText = original;
|
||||
});
|
||||
}
|
||||
};
|
||||
9
plugins/multi-vault-links-0.1.4/manifest.json
Normal file
9
plugins/multi-vault-links-0.1.4/manifest.json
Normal file
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"id": "multi-vault-links",
|
||||
"name": "Multi-Vault Links",
|
||||
"version": "0.1.4",
|
||||
"minAppVersion": "1.5.0",
|
||||
"description": "Turn [[Vault: file]] links into clickable obsidian:// URIs that open the file in another vault.",
|
||||
"author": "exedev",
|
||||
"isDesktopOnly": false
|
||||
}
|
||||
20
plugins/multi-vault-links-0.1.4/styles.css
Normal file
20
plugins/multi-vault-links-0.1.4/styles.css
Normal file
@@ -0,0 +1,20 @@
|
||||
.multi-vault-link {
|
||||
color: var(--link-color, var(--text-accent));
|
||||
text-decoration: var(--link-decoration, underline);
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
.multi-vault-link:hover {
|
||||
color: var(--link-color-hover, var(--text-accent-hover));
|
||||
text-decoration: var(--link-decoration-hover, underline);
|
||||
}
|
||||
|
||||
/* When the raw [[…]] is shown (source mode, or cursor in the link in live
|
||||
preview) Obsidian marks the cross-vault target "unresolved" and fades it with
|
||||
--link-unresolved-opacity (~0.7). Restore full opacity and the normal link
|
||||
colour so a multi-vault link never looks grayed. */
|
||||
.cm-multi-vault-source .cm-hmd-internal-link,
|
||||
.cm-multi-vault-source .is-unresolved {
|
||||
color: var(--link-color, var(--text-accent)) !important;
|
||||
opacity: 1 !important;
|
||||
}
|
||||
Reference in New Issue
Block a user