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
254 lines
7.5 KiB
JavaScript
254 lines
7.5 KiB
JavaScript
"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;
|
|
});
|
|
}
|
|
};
|