max-fluff_obsidian-referenc.../main.js

5395 lines
240 KiB
JavaScript
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

/* Reference Linker 1.0.1 — bundled from src/ by esbuild. Do not edit directly; edit src/ and run "npm run build". */
"use strict";
var __getOwnPropNames = Object.getOwnPropertyNames;
var __commonJS = (cb, mod) => function __require() {
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
};
// src/shared/markdown.js
var require_markdown = __commonJS({
"src/shared/markdown.js"(exports2, module2) {
"use strict";
var splitLines2 = (s) => (s || "").split("\n").map((x) => x.trim()).filter(Boolean);
var LINK_PATTERN = "\\[([^\\]]*)\\]\\(([^)]+)\\)";
var linkRegex2 = () => new RegExp(LINK_PATTERN, "g");
var LINK_TITLE = /^([\s\S]*?)\s+(?:"([^"]*)"|'([^']*)')$/;
function splitTarget2(raw) {
const s = String(raw == null ? "" : raw).trim();
const m = LINK_TITLE.exec(s);
if (!m)
return { url: s, title: "" };
return { url: m[1].trim(), title: m[2] != null ? m[2] : m[3] };
}
var withTitle2 = (url, title) => title ? url + ' "' + title + '"' : url;
var isFenceLine = (line) => {
const s = line.trimStart();
return s.startsWith("```") || s.startsWith("~~~");
};
var INLINE_CODE = /`[^`\n]+`/g;
function inMatch(line, col, re) {
re.lastIndex = 0;
let m;
while ((m = re.exec(line)) !== null) {
if (col > m.index && col < m.index + m[0].length)
return true;
}
return false;
}
var inInlineCode = (line, col) => inMatch(line, col, INLINE_CODE);
function locate(lines, pos) {
let start = 0, i = 0;
for (; i < lines.length; i++) {
if (pos <= start + lines[i].length)
break;
start += lines[i].length + 1;
}
return { i, col: pos - start, line: lines[i] || "" };
}
function inCode2(text, pos) {
if (/^---\r?\n/.test(text)) {
const end = text.indexOf("\n---", 3);
if (end !== -1 && pos <= end + 4)
return true;
}
const lines = text.split("\n");
const { i, col, line } = locate(lines, pos);
let fenced = false;
for (let k = 0; k < i; k++)
if (isFenceLine(lines[k]))
fenced = !fenced;
if (fenced)
return true;
return inMatch(line, col, INLINE_CODE);
}
function inLink2(text, pos) {
const { col, line } = locate(text.split("\n"), pos);
return inMatch(line, col, linkRegex2());
}
function isProtected(text, pos) {
return inCode2(text, pos) || inLink2(text, pos);
}
function inTableCell2(text, pos) {
const lines = text.split("\n");
const lineIdx = (text.slice(0, pos).match(/\n/g) || []).length;
if (!lines[lineIdx] || !lines[lineIdx].includes("|"))
return false;
const isDelimiter = (l) => l.includes("|") && l.includes("-") && /^[\s|:-]+$/.test(l);
let top = lineIdx, bot = lineIdx;
while (top > 0 && lines[top - 1].trim() !== "")
top--;
while (bot < lines.length - 1 && lines[bot + 1].trim() !== "")
bot++;
for (let i = top; i <= bot; i++)
if (isDelimiter(lines[i]))
return true;
return false;
}
function rewriteLinks(text, fn) {
const lines = text.split("\n");
let fenced = false, count = 0;
for (let i = 0; i < lines.length; i++) {
if (isFenceLine(lines[i])) {
fenced = !fenced;
continue;
}
if (fenced)
continue;
lines[i] = lines[i].replace(linkRegex2(), (whole, name, target, offset) => {
if (inInlineCode(lines[i], offset))
return whole;
const out = fn(name, target);
if (out == null)
return whole;
count++;
return out;
});
}
return { text: lines.join("\n"), count };
}
function rewriteFences(text, lang, fn) {
const lines = text.split("\n");
let count = 0;
for (let i = 0; i < lines.length; i++) {
const open = new RegExp("^\\s*(`{3,}|~{3,})\\s*" + lang + "\\s*$").exec(lines[i]);
if (!open)
continue;
const close = new RegExp("^\\s*" + open[1][0] + "{" + open[1].length + ",}\\s*$");
let j = i + 1;
while (j < lines.length && !close.test(lines[j]))
j++;
const body = lines.slice(i + 1, j);
const out = fn(body);
if (out) {
lines.splice(i + 1, body.length, ...out);
count++;
j = i + 1 + out.length;
}
i = j;
}
return { text: lines.join("\n"), count };
}
function wordAt(line, ch) {
const s = String(line == null ? "" : line);
if (!s)
return "";
const isWord = (c) => /[\p{L}\p{Nd}]/u.test(c || "");
const at = Math.max(0, Math.min(ch, s.length));
if (!isWord(s[at]) && !isWord(s[at - 1]))
return "";
let start = at;
while (start > 0 && isWord(s[start - 1]))
start--;
let end = at;
while (end < s.length && isWord(s[end]))
end++;
return s.slice(start, end);
}
module2.exports = { splitLines: splitLines2, linkRegex: linkRegex2, splitTarget: splitTarget2, withTitle: withTitle2, rewriteLinks, rewriteFences, isFenceLine, inInlineCode, locate, inCode: inCode2, inLink: inLink2, isProtected, inTableCell: inTableCell2, wordAt };
}
});
// src/constants.js
var require_constants = __commonJS({
"src/constants.js"(exports2, module2) {
"use strict";
var { splitLines: splitLines2 } = require_markdown();
var PRESETS2 = {
// {ref-root} keeps the note portable: the file holds a relative path, the absolute
// reference root is filled in on render/click. Opens in the OS default app. Namespaced,
// so a link says which linker owns it — the bare {root} it replaces was filled by the
// code linker too.
file: "file:///{ref-root}/{path}"
};
var DEFAULT_SETTINGS2 = {
// @@ is Code Linker's default; @! avoids a clash when both are installed.
trigger: "@!",
uriTemplate: PRESETS2.file,
codeRoot: "",
// empty => parent folder of the vault
scanRoots: "",
// one path per line, relative to codeRoot
extensions: "",
// e.g. ".pdf .docx .png"; empty => nothing indexed
skipDirs: ".git\nnode_modules\n.obsidian",
// one folder name per line
editors: [],
// user-defined viewer presets, each { name, template }
askOnInsert: true,
// ask which viewer format to use on every insert (vs. the default)
autoRefresh: true,
// watch scan folders and rebuild the index when files change
hoverPreview: true,
// show the preview popover when hovering a reference link
markStaleLinks: true,
// underline links whose target document moved or is gone
minChars: 1,
maxResults: 12,
contextMenu: true,
// the "Convert"/"Find and open" items in the editor right-click menu
// Breaks a tie when a link lands in both our index and the code linker's and carries no
// binding to say whose it is. A binding always decides on its own, so this only ever
// settles the genuinely ambiguous case.
linkPrecedence: 10
};
function parseExtensions2(raw) {
const out = /* @__PURE__ */ new Set();
for (const tok of String(raw || "").split(/[\s,]+/)) {
const t2 = tok.trim().toLowerCase();
if (!t2)
continue;
out.add(t2[0] === "." ? t2 : "." + t2);
}
return out;
}
function parseSkip2(skipDirs) {
const names = /* @__PURE__ */ new Set();
const paths = /* @__PURE__ */ new Set();
for (const raw of splitLines2(skipDirs)) {
const s = raw.split("\\").join("/").replace(/^\.?\//, "").replace(/\/+$/, "");
if (!s)
continue;
if (s.includes("/"))
paths.add(s);
else
names.add(s);
}
return { names, paths };
}
function underSkip2(rel, skip) {
const segs = rel.split("/").filter(Boolean);
for (const s of segs)
if (skip.names.has(s))
return true;
if (skip.paths.size) {
let acc = "";
for (const seg of segs) {
acc = acc ? acc + "/" + seg : seg;
if (skip.paths.has(acc))
return true;
}
}
return false;
}
module2.exports = { PRESETS: PRESETS2, DEFAULT_SETTINGS: DEFAULT_SETTINGS2, parseExtensions: parseExtensions2, parseSkip: parseSkip2, underSkip: underSkip2 };
}
});
// src/shared/binding.js
var require_binding = __commonJS({
"src/shared/binding.js"(exports2, module2) {
"use strict";
var ANCHORS = { sym: "sym", kind: "kind", sec: "sec", line: "hash" };
var TOKEN = /^(sym|kind|sec|line):(.+)$/;
var OWNERS = { code: ["sym", "kind", "hash"], reference: ["sec"] };
function ownerOf(binding) {
if (!binding)
return null;
const claimed = Object.keys(OWNERS).filter((owner) => OWNERS[owner].some((anchor) => binding[anchor]));
return claimed.length === 1 ? claimed[0] : null;
}
var bindingOwner2 = (title) => ownerOf(parseBinding2(title));
var ownsBinding2 = (title, owner) => bindingOwner2(title) === owner;
var LINE_RE = /:(\d+)(?=\D*$)/;
var PAGE_RE = /#page=(\d+)/i;
var encodeValue = (v) => String(v).replace(/[%"()\s]/g, (c) => "%" + c.charCodeAt(0).toString(16).toUpperCase().padStart(2, "0"));
var decodeValue = (v) => v.replace(/%([0-9A-Fa-f]{2})/g, (_, h) => String.fromCharCode(parseInt(h, 16)));
function hashLine(text) {
let h = 2166136261;
const s = String(text || "").trim();
for (let i = 0; i < s.length; i++) {
h ^= s.charCodeAt(i);
h = Math.imul(h, 16777619) >>> 0;
}
return h.toString(36);
}
function parseBinding2(title) {
const s = String(title || "").trim();
if (!s)
return null;
const b = { sym: "", kind: "", sec: "", hash: "" };
for (const word of s.split(/\s+/)) {
const m = TOKEN.exec(word);
if (!m)
return null;
b[ANCHORS[m[1]]] = decodeValue(m[2]);
}
return b.sym || b.kind || b.sec || b.hash ? b : null;
}
function formatBinding2(b) {
const parts = [];
if (b.sym)
parts.push("sym:" + encodeValue(b.sym));
if (b.kind)
parts.push("kind:" + encodeValue(b.kind));
if (b.sec)
parts.push("sec:" + encodeValue(b.sec));
if (b.hash)
parts.push("line:" + b.hash);
return parts.join(" ");
}
function bindStateFrom2(hits, stored) {
if (hits.includes(stored))
return null;
if (!hits.length)
return { state: "broken" };
const line = hits.reduce((a, n) => Math.abs(n - stored) < Math.abs(a - stored) ? n : a);
return { state: "stale", line };
}
module2.exports = { LINE_RE, PAGE_RE, OWNERS, hashLine, parseBinding: parseBinding2, formatBinding: formatBinding2, bindStateFrom: bindStateFrom2, ownerOf, bindingOwner: bindingOwner2, ownsBinding: ownsBinding2 };
}
});
// src/shared/root-token.js
var require_root_token = __commonJS({
"src/shared/root-token.js"(exports2, module2) {
"use strict";
var OWNER_TOKENS = { code: "code-root", reference: "ref-root" };
var LEGACY_TOKEN = "root";
var tokenRe = (name) => new RegExp("\\{" + name + "\\}|%7B" + name + "%7D", "gi");
function rootTokenIn(url) {
const s = String(url == null ? "" : url);
for (const owner of Object.keys(OWNER_TOKENS)) {
if (tokenRe(OWNER_TOKENS[owner]).test(s))
return owner;
}
return tokenRe(LEGACY_TOKEN).test(s) ? "legacy" : null;
}
function ownsRootToken2(url, owner, claimLegacy) {
const found = rootTokenIn(url);
if (found === owner)
return true;
return found === "legacy" && !!claimLegacy;
}
function fillRoot(url, { owner, root, claimLegacy = false } = {}) {
const s = String(url == null ? "" : url);
if (!owner || !OWNER_TOKENS[owner])
return s;
let out = s.replace(tokenRe(OWNER_TOKENS[owner]), root);
if (claimLegacy)
out = out.replace(tokenRe(LEGACY_TOKEN), root);
return out;
}
function namespaceRoot2(url, owner) {
const s = String(url == null ? "" : url);
if (!owner || !OWNER_TOKENS[owner])
return s;
if (rootTokenIn(s) !== "legacy")
return s;
return s.replace(tokenRe(LEGACY_TOKEN), "{" + OWNER_TOKENS[owner] + "}");
}
module2.exports = { OWNER_TOKENS, LEGACY_TOKEN, rootTokenIn, ownsRootToken: ownsRootToken2, fillRoot, namespaceRoot: namespaceRoot2 };
}
});
// src/shared/menu.js
var require_menu = __commonJS({
"src/shared/menu.js"(exports2, module2) {
"use strict";
var obsidian = require("obsidian");
var submenuSupport = null;
function supportsSubmenu() {
if (submenuSupport !== null)
return submenuSupport;
submenuSupport = false;
try {
const probe = new obsidian.Menu();
probe.addItem((item) => {
submenuSupport = typeof item.setSubmenu === "function";
});
} catch (e) {
submenuSupport = false;
}
return submenuSupport;
}
function menuSection(menu, label, grouped, icon) {
if (!grouped)
return menu;
if (!supportsSubmenu()) {
return {
addItem(cb) {
return menu.addItem((item) => {
const setTitle = item.setTitle.bind(item);
item.setTitle = (title) => setTitle(`${label}: ${title}`);
cb(item);
});
},
addSeparator() {
return menu.addSeparator();
}
};
}
let sub = null;
const ensure = () => {
if (!sub) {
menu.addItem((item) => {
item.setTitle(label);
if (icon)
item.setIcon(icon);
sub = item.setSubmenu();
});
}
return sub;
};
return {
addItem(cb) {
return ensure().addItem(cb);
},
addSeparator() {
return sub ? sub.addSeparator() : null;
}
};
}
var STORE = "__linkerMenuSections";
function sharedSection(menu, key, label, icon) {
if (!supportsSubmenu())
return menuSection(menu, label, true);
let store = menu[STORE];
if (!store) {
store = {};
try {
Object.defineProperty(menu, STORE, { value: store, enumerable: false, configurable: true });
} catch (e) {
return menuSection(menu, label, true, icon);
}
}
if (!store[key]) {
menu.addItem((item) => {
item.setTitle(label);
if (icon)
item.setIcon(icon);
store[key] = item.setSubmenu();
});
}
return store[key];
}
module2.exports = { menuSection, sharedSection, supportsSubmenu };
}
});
// src/shared/discover.js
var require_discover = __commonJS({
"src/shared/discover.js"(exports2, module2) {
"use strict";
var LINKER_API = 1;
function discoverLinkers(app, opts) {
const minVersion = opts && opts.minVersion || LINKER_API;
const found = [];
const plugins = app && app.plugins && app.plugins.plugins;
if (!plugins)
return found;
for (const id of Object.keys(plugins)) {
const plugin = plugins[id];
const provider = plugin && plugin.api && plugin.api.linker;
if (!provider || typeof provider.id !== "string")
continue;
if (!(provider.apiVersion >= minVersion))
continue;
found.push(provider);
}
return found;
}
function outranks(a, b) {
if (a.precedence !== b.precedence)
return (a.precedence || 0) > (b.precedence || 0);
return String(a.id) < String(b.id);
}
function drawsHere(peer, where) {
if (typeof peer.drawsIn !== "function")
return true;
const w = where || {};
try {
return peer.drawsIn(w.path, w.surface) !== false;
} catch (e) {
return true;
}
}
function foreignRanges(app, self, text, where) {
const ranges = [];
for (const peer of discoverLinkers(app)) {
if (peer.id === self.id || !outranks(peer, self))
continue;
if (typeof peer.matches !== "function" || !drawsHere(peer, where))
continue;
let matches;
try {
matches = peer.matches(text) || [];
} catch (e) {
matches = [];
}
for (const m of matches) {
if (m && typeof m.start === "number" && typeof m.end === "number")
ranges.push([m.start, m.end]);
}
}
return ranges.sort((a, b) => a[0] - b[0]);
}
function overlaps(ranges, s, e) {
for (const [rs, re] of ranges) {
if (rs >= e)
break;
if (re > s)
return true;
}
return false;
}
function ownedMatches(app, self, text, matches, where) {
if (!matches.length)
return matches;
const foreign = foreignRanges(app, self, text, where);
if (!foreign.length)
return matches;
return matches.filter((m) => !overlaps(foreign, m.start, m.end));
}
function yieldedCandidates(app, self, text, where) {
const out = [];
for (const peer of discoverLinkers(app)) {
if (peer.id === self.id || outranks(peer, self))
continue;
if (typeof peer.matches !== "function" || !drawsHere(peer, where))
continue;
let matches;
try {
matches = peer.matches(text) || [];
} catch (e) {
matches = [];
}
for (const m of matches) {
if (!m || typeof m.start !== "number" || typeof m.end !== "number")
continue;
out.push({
start: m.start,
end: m.end,
label: m.label || m.target || "",
target: m.target,
// The id survives a round trip through a DOM attribute; the opener is looked up
// again at click time.
id: peer.id,
source: peer.displayName || peer.id,
// How this row reads in an ambiguity list, asked of its owner and only when a list is
// actually drawn — every span on screen produces candidates, few are ever looked at.
describe: (display) => {
if (typeof peer.describe !== "function")
return null;
try {
return peer.describe(m.target, display);
} catch (e) {
return null;
}
},
open: (sourcePath, newTab) => {
if (typeof peer.open === "function")
peer.open(m.target, sourcePath, newTab);
},
hover: (event, targetEl, sourcePath, hoverParent) => {
if (typeof peer.hover === "function")
peer.hover(m.target, event, targetEl, sourcePath, hoverParent);
}
});
}
}
return out;
}
function candidatesFor(candidates, s, e) {
return candidates.filter((c) => c.start < e && c.end > s);
}
function peerSuggestions(app, self, query, sourcePath) {
const out = [];
for (const peer of discoverLinkers(app)) {
if (peer.id === self.id || typeof peer.suggest !== "function")
continue;
let items;
try {
items = peer.suggest(String(query || ""), sourcePath) || [];
} catch (e) {
items = [];
}
for (const it of items) {
if (!it || typeof it.label !== "string")
continue;
out.push({
label: it.label,
note: it.note || "",
target: it.target,
// null means "keep what the reader typed"; only the peer knows whether its
// candidate matched an inflection or completed a prefix.
display: it.display == null ? null : it.display,
id: peer.id,
source: peer.displayName || peer.id,
precedence: peer.precedence || 0,
// Answered by the row's owner, including whether to compose a link at all. A peer
// that predates `insertFor` has only `linkFor`, which always links — the right
// reading for a plugin with no plain-text mode to consult.
insert: (display, inTable) => {
if (typeof peer.insertFor === "function")
return peer.insertFor(it.target, display, inTable);
return typeof peer.linkFor === "function" ? peer.linkFor(it.target, display, inTable) : null;
}
});
}
}
return out;
}
function peersOffering(app, self, kind, text) {
const out = [];
for (const peer of discoverLinkers(app)) {
if (peer.id === self.id || typeof peer.offers !== "function")
continue;
let yes;
try {
yes = peer.offers(kind, text);
} catch (e) {
yes = false;
}
if (yes)
out.push(peer);
}
return out;
}
function siblingLinkers(app, self) {
return discoverLinkers(app).filter((p) => p.id !== self.id);
}
module2.exports = { LINKER_API, discoverLinkers, outranks, drawsHere, foreignRanges, overlaps, ownedMatches, yieldedCandidates, candidatesFor, peerSuggestions, peersOffering, siblingLinkers };
}
});
// src/shared/locales/common.js
var require_common = __commonJS({
"src/shared/locales/common.js"(exports2, module2) {
"use strict";
var en = {
"modal.andMore": "\u2026and {n} more",
"btn.apply": "Apply",
"btn.cancel": "Cancel",
"set.heading.maintenance": "Maintenance",
"set.rebuild.button": "Rebuild",
"set.precedence.name": "Priority among linker plugins",
"set.precedence.desc": "A word or link several linkers claim goes to the one highest in this list. You can only move this plugin \u2014 move the others from their own settings.",
"set.precedence.other": "Moved from its own settings",
"set.precedence.up": "Move up",
"set.precedence.down": "Move down"
};
var ru = {
"modal.andMore": "\u2026\u0438 \u0435\u0449\u0451 {n}",
"btn.apply": "\u041F\u0440\u0438\u043C\u0435\u043D\u0438\u0442\u044C",
"btn.cancel": "\u041E\u0442\u043C\u0435\u043D\u0430",
"set.heading.maintenance": "\u041E\u0431\u0441\u043B\u0443\u0436\u0438\u0432\u0430\u043D\u0438\u0435",
"set.rebuild.button": "\u041F\u0435\u0440\u0435\u0441\u0442\u0440\u043E\u0438\u0442\u044C",
"set.precedence.name": "\u041F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442 \u0441\u0440\u0435\u0434\u0438 \u043F\u043B\u0430\u0433\u0438\u043D\u043E\u0432-\u043B\u0438\u043D\u043A\u0435\u0440\u043E\u0432",
"set.precedence.desc": "\u0421\u043B\u043E\u0432\u043E \u0438\u043B\u0438 \u0441\u0441\u044B\u043B\u043A\u0443, \u043D\u0430 \u043A\u043E\u0442\u043E\u0440\u044B\u0435 \u043F\u0440\u0435\u0442\u0435\u043D\u0434\u0443\u044E\u0442 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u043B\u0438\u043D\u043A\u0435\u0440\u043E\u0432, \u0437\u0430\u0431\u0438\u0440\u0430\u0435\u0442 \u0442\u043E\u0442, \u043A\u0442\u043E \u0432\u044B\u0448\u0435 \u0432 \u0441\u043F\u0438\u0441\u043A\u0435. \u041E\u0442\u0441\u044E\u0434\u0430 \u0434\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u044D\u0442\u043E\u0442 \u043F\u043B\u0430\u0433\u0438\u043D \u2014 \u043E\u0441\u0442\u0430\u043B\u044C\u043D\u044B\u0435 \u0438\u0437 \u0441\u0432\u043E\u0438\u0445 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043A.",
"set.precedence.other": "\u0414\u0432\u0438\u0433\u0430\u0435\u0442\u0441\u044F \u0438\u0437 \u0441\u0432\u043E\u0438\u0445 \u043D\u0430\u0441\u0442\u0440\u043E\u0435\u043A",
"set.precedence.up": "\u0412\u044B\u0448\u0435",
"set.precedence.down": "\u041D\u0438\u0436\u0435"
};
var de = {
"modal.andMore": "\u2026und {n} weitere",
"btn.apply": "Anwenden",
"btn.cancel": "Abbrechen",
"set.heading.maintenance": "Wartung",
"set.rebuild.button": "Neu aufbauen"
};
var es = {
"modal.andMore": "\u2026y {n} m\xE1s",
"btn.apply": "Aplicar",
"btn.cancel": "Cancelar",
"set.heading.maintenance": "Mantenimiento",
"set.rebuild.button": "Reconstruir"
};
var fr = {
"modal.andMore": "\u2026et {n} de plus",
"btn.apply": "Appliquer",
"btn.cancel": "Annuler",
"set.heading.maintenance": "Maintenance",
"set.rebuild.button": "Reconstruire"
};
var uk = {
"modal.andMore": "\u2026\u0442\u0430 \u0449\u0435 {n}",
"btn.apply": "\u0417\u0430\u0441\u0442\u043E\u0441\u0443\u0432\u0430\u0442\u0438",
"btn.cancel": "\u0421\u043A\u0430\u0441\u0443\u0432\u0430\u0442\u0438",
"set.heading.maintenance": "\u041E\u0431\u0441\u043B\u0443\u0433\u043E\u0432\u0443\u0432\u0430\u043D\u043D\u044F",
"set.rebuild.button": "\u041F\u0435\u0440\u0435\u0431\u0443\u0434\u0443\u0432\u0430\u0442\u0438"
};
module2.exports = { en, ru, de, es, fr, uk };
}
});
// src/shared/locales/prose.js
var require_prose = __commonJS({
"src/shared/locales/prose.js"(exports2, module2) {
"use strict";
var en = {
"noun.file": "file",
"noun.folder": "folder",
"scope.first": "first",
"scope.all": "all",
"menu.linkThisWord": "Link \u201C{display}\u201D",
"menu.linkHere": "Link \u201C{display}\u201D here",
"menu.linkDisplayTo": 'Link "{display}" to\u2026',
"menu.linkScopeTo": 'Link {scope} "{display}" to\u2026',
"menu.openThisWord": "Open \u201C{display}\u201D",
"modal.choose.title": "Which one?",
"set.heading.scope": "Scope",
"set.heading.matching": "Matching",
"set.languages.name": "Languages",
"set.languages.show": "Show languages",
"set.languages.hide": "Hide languages",
"set.lang.higher": "Higher priority",
"set.lang.lower": "Lower priority",
"set.linkFirstOnly.name": "Link first occurrence only",
"set.heading.highlighting": "Highlighting",
"set.highlightInReading.name": "Highlight in Reading view",
"set.editingHighlight.onSave": "On save",
"set.skipHeadings.name": "Skip headings",
"set.statusBar.name": "Status bar count",
"set.heading.autocomplete": "Autocomplete",
"set.linkSuggest.name": "Suggest links while typing",
"set.suggestMinChars.desc": "How many characters to type before suggestions appear.",
"set.suggestSkipAfter.name": "Skip after characters",
"set.suggestPlainText.name": "Insert plain text",
"set.suggestPlainText.desc": "Suggestions complete the word without turning it into a link.",
"set.heading.contextMenu": "Context menu",
// The shared submenu the exclusion items collect into, and their wording inside it, where
// the parent already names the word.
"exclude.group": "Exclude \u201C{value}\u201D",
"exclude.addShort": "Add to {noun}",
"exclude.removeShort": "Remove from {noun}",
"label.selection": "Selection",
"modal.leftAsText": "(left as text)",
"modal.skipOption": "skip",
"modal.materialize.summary": "Reviewing {files} file(s), {replacements} replacement(s).",
"modal.unlink.summary": "Reviewing {files} file(s), {links} link(s).",
"modal.choose.body": "This word has more than one match.",
"notice.noActiveNote": "No active note.",
"notice.noSelection": "Nothing selected.",
"notice.scopeSkipped": " Skipped {n} note(s) changed since the preview.",
"set.editingHighlight.live": "Live",
"set.editingHighlight.name": "Highlight in the editor",
"set.lang.invalid": "Invalid: {error}",
"set.languages.desc": "{enabled} of {total} enabled",
"set.matchMode.name": "Match mode",
"set.matchMode.exact": "Exact (case-insensitive)",
"set.matchMode.endingStrip": "Light ending strip",
"set.matchMode.stemmer": "Stemmer (best across forms)",
"kind.heading": "Heading",
"kind.term": "Term",
"kind.viaAlias": "via alias \u201C{form}\u201D",
"set.smartCase.name": "Smart case for acronyms",
"set.smartCase.desc": "Match mostly-uppercase terms (like \u201CIT\u201D or \u201CNASA\u201D) case-sensitively, so they don\u2019t link ordinary words.",
"set.scopeMode.name": "Where to link",
"set.scopeMode.vault": "The whole vault",
"set.scopeMode.folders": "Only chosen folders",
"set.suggestMinChars.name": "Minimum typed length",
"set.statusBarIncludeLinks.name": "Count existing links too",
"set.folderList.add": "Add path\u2026",
"set.folderList.addAria": "Add",
"plural.alias": { one: "{n} alias", other: "{n} aliases" }
};
var ru = {
"noun.file": "\u0444\u0430\u0439\u043B",
"noun.folder": "\u043F\u0430\u043F\u043A\u0443",
"scope.first": "\u043F\u0435\u0440\u0432\u043E\u0435",
"scope.all": "\u0432\u0441\u0435",
"menu.linkThisWord": "\u0421\u0432\u044F\u0437\u0430\u0442\u044C \xAB{display}\xBB",
"menu.linkHere": "\u0421\u0432\u044F\u0437\u0430\u0442\u044C \xAB{display}\xBB \u0437\u0434\u0435\u0441\u044C",
"menu.linkDisplayTo": "\u0421\u0432\u044F\u0437\u0430\u0442\u044C \xAB{display}\xBB \u0441\u2026",
"menu.linkScopeTo": "\u0421\u0432\u044F\u0437\u0430\u0442\u044C {scope} \xAB{display}\xBB \u0441\u2026",
"menu.openThisWord": "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \xAB{display}\xBB",
"modal.choose.title": "\u041A\u0430\u043A\u043E\u0435 \u0438\u0437 \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0439?",
"set.heading.scope": "\u041E\u0431\u043B\u0430\u0441\u0442\u044C",
"set.heading.matching": "\u0421\u043E\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u0438\u0435",
"set.languages.name": "\u042F\u0437\u044B\u043A\u0438",
"set.languages.show": "\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u044C \u044F\u0437\u044B\u043A\u0438",
"set.languages.hide": "\u0421\u043A\u0440\u044B\u0442\u044C \u044F\u0437\u044B\u043A\u0438",
"set.lang.higher": "\u0412\u044B\u0448\u0435 \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442",
"set.lang.lower": "\u041D\u0438\u0436\u0435 \u043F\u0440\u0438\u043E\u0440\u0438\u0442\u0435\u0442",
"set.linkFirstOnly.name": "\u0421\u0432\u044F\u0437\u044B\u0432\u0430\u0442\u044C \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u0435\u0440\u0432\u043E\u0435 \u0432\u0445\u043E\u0436\u0434\u0435\u043D\u0438\u0435",
"set.heading.highlighting": "\u041F\u043E\u0434\u0441\u0432\u0435\u0442\u043A\u0430",
"set.highlightInReading.name": "\u041F\u043E\u0434\u0441\u0432\u0435\u0442\u043A\u0430 \u0432 \u0440\u0435\u0436\u0438\u043C\u0435 \u0447\u0442\u0435\u043D\u0438\u044F",
"set.editingHighlight.onSave": "\u041F\u0440\u0438 \u0441\u043E\u0445\u0440\u0430\u043D\u0435\u043D\u0438\u0438",
"set.skipHeadings.name": "\u041F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0442\u044C \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0438",
"set.statusBar.name": "\u0421\u0447\u0451\u0442\u0447\u0438\u043A \u0432 \u0441\u0442\u0440\u043E\u043A\u0435 \u0441\u043E\u0441\u0442\u043E\u044F\u043D\u0438\u044F",
"set.heading.autocomplete": "\u0410\u0432\u0442\u043E\u0434\u043E\u043F\u043E\u043B\u043D\u0435\u043D\u0438\u0435",
"set.linkSuggest.name": "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0438 \u043F\u0440\u0438 \u043D\u0430\u0431\u043E\u0440\u0435",
"set.suggestMinChars.desc": "\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432 \u043D\u0430\u0431\u0440\u0430\u0442\u044C, \u043F\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043C \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0438.",
"set.suggestSkipAfter.name": "\u041F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0442\u044C \u043F\u043E\u0441\u043B\u0435 \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
"set.suggestPlainText.name": "\u0412\u0441\u0442\u0430\u0432\u043B\u044F\u0442\u044C \u043F\u0440\u043E\u0441\u0442\u043E\u0439 \u0442\u0435\u043A\u0441\u0442",
"set.suggestPlainText.desc": "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0430 \u0434\u043E\u043F\u0438\u0441\u044B\u0432\u0430\u0435\u0442 \u0441\u043B\u043E\u0432\u043E, \u043D\u0435 \u043F\u0440\u0435\u0432\u0440\u0430\u0449\u0430\u044F \u0435\u0433\u043E \u0432 \u0441\u0441\u044B\u043B\u043A\u0443.",
"set.heading.contextMenu": "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043D\u043E\u0435 \u043C\u0435\u043D\u044E",
"exclude.group": "\u0418\u0441\u043A\u043B\u044E\u0447\u0438\u0442\u044C \xAB{value}\xBB",
"exclude.addShort": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0432 {noun}",
"exclude.removeShort": "\u0423\u0431\u0440\u0430\u0442\u044C \u0438\u0437 {noun}",
"label.selection": "\u0412\u044B\u0434\u0435\u043B\u0435\u043D\u0438\u0435",
"modal.leftAsText": "(\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043E \u0442\u0435\u043A\u0441\u0442\u043E\u043C)",
"modal.skipOption": "\u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C",
"modal.materialize.summary": "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430: \u0444\u0430\u0439\u043B\u043E\u0432 \u2014 {files}, \u0437\u0430\u043C\u0435\u043D \u2014 {replacements}.",
"modal.unlink.summary": "\u041F\u0440\u043E\u0432\u0435\u0440\u043A\u0430: \u0444\u0430\u0439\u043B\u043E\u0432 \u2014 {files}, \u0441\u0441\u044B\u043B\u043E\u043A \u2014 {links}.",
"modal.choose.body": "\u0423 \u044D\u0442\u043E\u0433\u043E \u0441\u043B\u043E\u0432\u0430 \u043D\u0435\u0441\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u043E\u0432\u043F\u0430\u0434\u0435\u043D\u0438\u0439.",
"notice.noActiveNote": "\u041D\u0435\u0442 \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0439 \u0437\u0430\u043C\u0435\u0442\u043A\u0438.",
"notice.noSelection": "\u041D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u043E.",
"notice.scopeSkipped": " \u041F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u043E \u0437\u0430\u043C\u0435\u0442\u043E\u043A, \u0438\u0437\u043C\u0435\u043D\u0451\u043D\u043D\u044B\u0445 \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u0435\u0432\u044C\u044E: {n}.",
"set.editingHighlight.live": "\u041D\u0430 \u043B\u0435\u0442\u0443",
"set.editingHighlight.name": "\u041F\u043E\u0434\u0441\u0432\u0435\u0442\u043A\u0430 \u0432 \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0435",
"set.lang.invalid": "\u041E\u0448\u0438\u0431\u043A\u0430: {error}",
"set.languages.desc": "\u0412\u043A\u043B\u044E\u0447\u0435\u043D\u043E {enabled} \u0438\u0437 {total}",
"set.matchMode.name": "\u0420\u0435\u0436\u0438\u043C \u0441\u043E\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u0438\u044F",
"set.matchMode.exact": "\u0422\u043E\u0447\u043D\u043E\u0435 (\u0431\u0435\u0437 \u0443\u0447\u0451\u0442\u0430 \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430)",
"set.matchMode.endingStrip": "\u041B\u0451\u0433\u043A\u043E\u0435 \u043E\u0442\u0441\u0435\u0447\u0435\u043D\u0438\u0435 \u043E\u043A\u043E\u043D\u0447\u0430\u043D\u0438\u0439",
"set.matchMode.stemmer": "\u0421\u0442\u0435\u043C\u043C\u0435\u0440 (\u043B\u0443\u0447\u0448\u0435 \u0434\u043B\u044F \u0432\u0441\u0435\u0445 \u0444\u043E\u0440\u043C)",
"kind.heading": "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A",
"kind.term": "\u0422\u0435\u0440\u043C\u0438\u043D",
"kind.viaAlias": "\u043F\u043E \u0430\u043B\u0438\u0430\u0441\u0443 \xAB{form}\xBB",
"set.smartCase.name": "\u0423\u043C\u043D\u044B\u0439 \u0440\u0435\u0433\u0438\u0441\u0442\u0440 \u0434\u043B\u044F \u0430\u0431\u0431\u0440\u0435\u0432\u0438\u0430\u0442\u0443\u0440",
"set.smartCase.desc": "\u0422\u0435\u0440\u043C\u0438\u043D\u044B \u0438\u0437 \u0437\u0430\u0433\u043B\u0430\u0432\u043D\u044B\u0445 \u0431\u0443\u043A\u0432 (\u043D\u0430\u043F\u0440\u0438\u043C\u0435\u0440 \xABIT\xBB \u0438\u043B\u0438 \xABNASA\xBB) \u0441\u043E\u043F\u043E\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442\u0441\u044F \u0441 \u0443\u0447\u0451\u0442\u043E\u043C \u0440\u0435\u0433\u0438\u0441\u0442\u0440\u0430, \u0447\u0442\u043E\u0431\u044B \u043D\u0435 \u0446\u0435\u043F\u043B\u044F\u0442\u044C \u043E\u0431\u044B\u0447\u043D\u044B\u0435 \u0441\u043B\u043E\u0432\u0430.",
"set.scopeMode.name": "\u0413\u0434\u0435 \u0441\u0432\u044F\u0437\u044B\u0432\u0430\u0442\u044C",
"set.scopeMode.vault": "\u0412\u0441\u0451 \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435",
"set.scopeMode.folders": "\u0422\u043E\u043B\u044C\u043A\u043E \u0432\u044B\u0431\u0440\u0430\u043D\u043D\u044B\u0435 \u043F\u0430\u043F\u043A\u0438",
"set.suggestMinChars.name": "\u041C\u0438\u043D\u0438\u043C\u0443\u043C \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
"set.statusBarIncludeLinks.name": "\u0421\u0447\u0438\u0442\u0430\u0442\u044C \u0438 \u0443\u0436\u0435 \u0441\u0432\u044F\u0437\u0430\u043D\u043D\u044B\u0435",
"set.folderList.add": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u0443\u0442\u044C\u2026",
"set.folderList.addAria": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C",
"plural.alias": { one: "{n} \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C", few: "{n} \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u0430", many: "{n} \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u043E\u0432", other: "{n} \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0438\u043C\u043E\u0432" }
};
var de = {
"noun.file": "Datei",
"noun.folder": "Ordner",
"scope.first": "erstes",
"scope.all": "alle",
"menu.linkDisplayTo": "\u201E{display}\u201C verlinken mit\u2026",
"menu.linkScopeTo": "{scope} \u201E{display}\u201C verlinken mit\u2026",
"modal.choose.title": "Begriff w\xE4hlen",
"set.heading.scope": "Bereich",
"set.heading.matching": "Abgleich",
"set.languages.name": "Sprachen",
"set.languages.show": "Sprachen anzeigen",
"set.languages.hide": "Sprachen ausblenden",
"set.lang.higher": "H\xF6here Priorit\xE4t",
"set.lang.lower": "Niedrigere Priorit\xE4t",
"set.linkFirstOnly.name": "Nur erstes Vorkommen verlinken",
"set.heading.highlighting": "Hervorhebung",
"set.highlightInReading.name": "In der Leseansicht hervorheben",
"set.editingHighlight.onSave": "Beim Speichern",
"set.skipHeadings.name": "\xDCberschriften \xFCberspringen",
"set.statusBar.name": "Z\xE4hler in der Statusleiste",
"set.heading.autocomplete": "Autovervollst\xE4ndigung",
"set.linkSuggest.name": "Links w\xE4hrend der Eingabe vorschlagen",
"set.suggestMinChars.desc": "Wie viele Zeichen einzugeben sind, bevor Vorschl\xE4ge erscheinen.",
"set.suggestSkipAfter.name": "Nach Zeichen \xFCberspringen",
"set.suggestPlainText.name": "Reinen Text einf\xFCgen",
"set.suggestPlainText.desc": "Vorschl\xE4ge vervollst\xE4ndigen das Wort, ohne daraus einen Link zu machen.",
"set.heading.contextMenu": "Kontextmen\xFC",
"label.selection": "Auswahl",
"modal.leftAsText": "\u2014 als Text belassen \u2014",
"modal.skipOption": "(\xFCberspringen \u2014 als Text belassen)",
"modal.materialize.summary": "Dateien: {files}, Ersetzungen: {replacements}",
"modal.unlink.summary": "Dateien: {files}, zu entfernende Links: {links}",
"modal.choose.body": "Dieses Wort passt zu mehr als einem Begriff \u2014 eines w\xE4hlen:",
"notice.noActiveNote": "Keine aktive Notiz",
"notice.noSelection": "Keine Auswahl",
"notice.scopeSkipped": ", {n} \xFCbersprungen (seit der Vorschau ge\xE4ndert)",
"set.editingHighlight.live": "Live (w\xE4hrend der Eingabe)",
"set.editingHighlight.name": "Beim Bearbeiten hervorheben",
"set.lang.invalid": "Ung\xFCltiges Modul: {error}",
"set.languages.desc": "Mitgelieferte Morphologie-Module \u2014 {enabled} von {total} aktiviert",
"set.matchMode.name": "Morphologie",
"set.matchMode.exact": "Exakter Treffer",
"set.matchMode.endingStrip": "Endungen abschneiden",
"set.matchMode.stemmer": "Stemmer (empfohlen)",
"kind.heading": "\xDCberschrift",
"kind.term": "Begriff",
"kind.viaAlias": "\xFCber Alias \u201E{form}\u201C",
"set.smartCase.name": "Schreibweise von Abk\xFCrzungen beachten",
"set.smartCase.desc": "\xDCberwiegend gro\xDFgeschriebene Begriffe (etwa \u201EIT\u201C oder \u201ENASA\u201C) werden nur bei gleicher Schreibweise verkn\xFCpft, damit sie keine gew\xF6hnlichen W\xF6rter erfassen.",
"set.scopeMode.name": "Verlinkungsbereich",
"set.scopeMode.vault": "\xDCberall",
"set.scopeMode.folders": "Nur aufgef\xFChrte Pfade",
"set.suggestMinChars.name": "Mindestanzahl Zeichen",
"set.statusBarIncludeLinks.name": "Direkte Links z\xE4hlen",
"plural.alias": { one: "{n} Alias", other: "{n} Aliasse" }
};
var es = {
"noun.file": "archivo",
"noun.folder": "carpeta",
"scope.first": "la primera",
"scope.all": "todas",
"menu.linkDisplayTo": "Enlazar \xAB{display}\xBB con\u2026",
"menu.linkScopeTo": "Enlazar {scope} \xAB{display}\xBB con\u2026",
"modal.choose.title": "Elegir un t\xE9rmino",
"set.heading.scope": "\xC1mbito",
"set.heading.matching": "Coincidencia",
"set.languages.name": "Idiomas",
"set.languages.show": "Mostrar idiomas",
"set.languages.hide": "Ocultar idiomas",
"set.lang.higher": "Mayor prioridad",
"set.lang.lower": "Menor prioridad",
"set.linkFirstOnly.name": "Enlazar solo la primera aparici\xF3n",
"set.heading.highlighting": "Resaltado",
"set.highlightInReading.name": "Resaltar en vista de lectura",
"set.editingHighlight.onSave": "Al guardar",
"set.skipHeadings.name": "Omitir encabezados",
"set.statusBar.name": "Contador en la barra de estado",
"set.heading.autocomplete": "Autocompletado",
"set.linkSuggest.name": "Sugerir enlaces al escribir",
"set.suggestMinChars.desc": "Cu\xE1ntos caracteres escribir antes de que aparezcan las sugerencias.",
"set.suggestSkipAfter.name": "Omitir tras caracteres",
"set.suggestPlainText.name": "Insertar texto sin enlace",
"set.suggestPlainText.desc": "Las sugerencias completan la palabra sin convertirla en un enlace.",
"set.heading.contextMenu": "Men\xFA contextual",
"label.selection": "selecci\xF3n",
"modal.leftAsText": "\u2014 dejado como texto \u2014",
"modal.skipOption": "(omitir \u2014 dejar como texto)",
"modal.materialize.summary": "Archivos: {files}, reemplazos: {replacements}",
"modal.unlink.summary": "Archivos: {files}, enlaces a eliminar: {links}",
"modal.choose.body": "Esta palabra coincide con m\xE1s de un t\xE9rmino \u2014 elige uno:",
"notice.noActiveNote": "No hay nota activa",
"notice.noSelection": "No hay selecci\xF3n",
"notice.scopeSkipped": ", {n} omitido(s) (cambiado desde la vista previa)",
"set.editingHighlight.live": "En vivo (mientras escribes)",
"set.editingHighlight.name": "Resaltar al editar",
"set.lang.invalid": "M\xF3dulo no v\xE1lido: {error}",
"set.languages.desc": "M\xF3dulos de morfolog\xEDa incluidos \u2014 {enabled} de {total} activados",
"set.matchMode.name": "Morfolog\xEDa",
"set.matchMode.exact": "Coincidencia exacta",
"set.matchMode.endingStrip": "Quitar terminaciones",
"set.matchMode.stemmer": "Lematizador (recomendado)",
"kind.heading": "Encabezado",
"kind.term": "T\xE9rmino",
"kind.viaAlias": "por el alias \xAB{form}\xBB",
"set.smartCase.name": "Distinguir may\xFAsculas en siglas",
"set.smartCase.desc": "Los t\xE9rminos escritos casi todo en may\xFAsculas (como \xABIT\xBB o \xABNASA\xBB) solo coinciden con esa misma graf\xEDa, para que no enlacen palabras corrientes.",
"set.scopeMode.name": "\xC1mbito de enlazado",
"set.scopeMode.vault": "En todas partes",
"set.scopeMode.folders": "Solo rutas indicadas",
"set.suggestMinChars.name": "Caracteres m\xEDnimos",
"set.statusBarIncludeLinks.name": "Contar enlaces directos",
"plural.alias": { one: "{n} alias", other: "{n} alias" }
};
var fr = {
"noun.file": "fichier",
"noun.folder": "dossier",
"scope.first": "la premi\xE8re",
"scope.all": "toutes",
"menu.linkDisplayTo": "Lier \xAB {display} \xBB \xE0\u2026",
"menu.linkScopeTo": "Lier {scope} \xAB {display} \xBB \xE0\u2026",
"modal.choose.title": "Choisir un terme",
"set.heading.scope": "Port\xE9e",
"set.heading.matching": "Correspondance",
"set.languages.name": "Langues",
"set.languages.show": "Afficher les langues",
"set.languages.hide": "Masquer les langues",
"set.lang.higher": "Priorit\xE9 plus haute",
"set.lang.lower": "Priorit\xE9 plus basse",
"set.linkFirstOnly.name": "Lier seulement la premi\xE8re occurrence",
"set.heading.highlighting": "Surlignage",
"set.highlightInReading.name": "Surligner en mode lecture",
"set.editingHighlight.onSave": "\xC0 l\u2019enregistrement",
"set.skipHeadings.name": "Ignorer les titres",
"set.statusBar.name": "Compteur dans la barre d\u2019\xE9tat",
"set.heading.autocomplete": "Autocompl\xE9tion",
"set.linkSuggest.name": "Sugg\xE9rer des liens pendant la saisie",
"set.suggestMinChars.desc": "Combien de caract\xE8res saisir avant que les suggestions apparaissent.",
"set.suggestSkipAfter.name": "Ignorer apr\xE8s caract\xE8res",
"set.suggestPlainText.name": "Ins\xE9rer du texte simple",
"set.suggestPlainText.desc": "Les suggestions compl\xE8tent le mot sans en faire un lien.",
"set.heading.contextMenu": "Menu contextuel",
"label.selection": "s\xE9lection",
"modal.leftAsText": "\u2014 laiss\xE9 en texte \u2014",
"modal.skipOption": "(ignorer \u2014 laisser en texte)",
"modal.materialize.summary": "Fichiers : {files}, remplacements : {replacements}",
"modal.unlink.summary": "Fichiers : {files}, liens \xE0 supprimer : {links}",
"modal.choose.body": "Ce mot correspond \xE0 plus d\u2019un terme \u2014 choisissez-en un :",
"notice.noActiveNote": "Aucune note active",
"notice.noSelection": "Aucune s\xE9lection",
"notice.scopeSkipped": ", {n} ignor\xE9(s) (modifi\xE9 depuis l\u2019aper\xE7u)",
"set.editingHighlight.live": "En direct (pendant la saisie)",
"set.editingHighlight.name": "Surligner pendant l\u2019\xE9dition",
"set.lang.invalid": "Module non valide : {error}",
"set.languages.desc": "Modules de morphologie inclus \u2014 {enabled} sur {total} activ\xE9s",
"set.matchMode.name": "Morphologie",
"set.matchMode.exact": "Correspondance exacte",
"set.matchMode.endingStrip": "Suppression des terminaisons",
"set.matchMode.stemmer": "Racinisation (recommand\xE9)",
"kind.heading": "Titre",
"kind.term": "Terme",
"kind.viaAlias": "via l\u2019alias \xAB {form} \xBB",
"set.smartCase.name": "Respecter la casse des sigles",
"set.smartCase.desc": "Les termes \xE9crits en majuscules (comme \xAB IT \xBB ou \xAB NASA \xBB) ne correspondent qu\u2019\xE0 la m\xEAme graphie, afin de ne pas lier des mots ordinaires.",
"set.scopeMode.name": "Port\xE9e du liage",
"set.scopeMode.vault": "Partout",
"set.scopeMode.folders": "Chemins list\xE9s seulement",
"set.suggestMinChars.name": "Caract\xE8res minimum",
"set.statusBarIncludeLinks.name": "Compter les liens directs",
"plural.alias": { one: "{n} alias", other: "{n} alias" }
};
var uk = {
"noun.file": "\u0444\u0430\u0439\u043B",
"noun.folder": "\u0442\u0435\u043A\u0443",
"scope.first": "\u043F\u0435\u0440\u0448\u0435",
"scope.all": "\u0443\u0441\u0456",
"menu.linkDisplayTo": "\u0417\u0432\u2019\u044F\u0437\u0430\u0442\u0438 \xAB{display}\xBB \u0437\u2026",
"menu.linkScopeTo": "\u0417\u0432\u2019\u044F\u0437\u0430\u0442\u0438 {scope} \xAB{display}\xBB \u0437\u2026",
"modal.choose.title": "\u0412\u0438\u0431\u0435\u0440\u0456\u0442\u044C \u0442\u0435\u0440\u043C\u0456\u043D",
"set.heading.scope": "\u041E\u0431\u043B\u0430\u0441\u0442\u044C",
"set.heading.matching": "\u0417\u0456\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u044F",
"set.languages.name": "\u041C\u043E\u0432\u0438",
"set.languages.show": "\u041F\u043E\u043A\u0430\u0437\u0430\u0442\u0438 \u043C\u043E\u0432\u0438",
"set.languages.hide": "\u0421\u0445\u043E\u0432\u0430\u0442\u0438 \u043C\u043E\u0432\u0438",
"set.lang.higher": "\u0412\u0438\u0449\u0438\u0439 \u043F\u0440\u0456\u043E\u0440\u0438\u0442\u0435\u0442",
"set.lang.lower": "\u041D\u0438\u0436\u0447\u0438\u0439 \u043F\u0440\u0456\u043E\u0440\u0438\u0442\u0435\u0442",
"set.linkFirstOnly.name": "\u0417\u0432\u2019\u044F\u0437\u0443\u0432\u0430\u0442\u0438 \u043B\u0438\u0448\u0435 \u043F\u0435\u0440\u0448\u0435 \u0432\u0445\u043E\u0434\u0436\u0435\u043D\u043D\u044F",
"set.heading.highlighting": "\u041F\u0456\u0434\u0441\u0432\u0456\u0447\u0443\u0432\u0430\u043D\u043D\u044F",
"set.highlightInReading.name": "\u041F\u0456\u0434\u0441\u0432\u0456\u0447\u0443\u0432\u0430\u0442\u0438 \u0432 \u0440\u0435\u0436\u0438\u043C\u0456 \u0447\u0438\u0442\u0430\u043D\u043D\u044F",
"set.editingHighlight.onSave": "\u041F\u0456\u0434 \u0447\u0430\u0441 \u0437\u0431\u0435\u0440\u0435\u0436\u0435\u043D\u043D\u044F",
"set.skipHeadings.name": "\u041F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0442\u0438 \u0437\u0430\u0433\u043E\u043B\u043E\u0432\u043A\u0438",
"set.statusBar.name": "\u041B\u0456\u0447\u0438\u043B\u044C\u043D\u0438\u043A \u0443 \u0440\u044F\u0434\u043A\u0443 \u0441\u0442\u0430\u043D\u0443",
"set.heading.autocomplete": "\u0410\u0432\u0442\u043E\u0434\u043E\u043F\u043E\u0432\u043D\u0435\u043D\u043D\u044F",
"set.linkSuggest.name": "\u041F\u0440\u043E\u043F\u043E\u043D\u0443\u0432\u0430\u0442\u0438 \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F \u043F\u0456\u0434 \u0447\u0430\u0441 \u043D\u0430\u0431\u043E\u0440\u0443",
"set.suggestMinChars.desc": "\u0421\u043A\u0456\u043B\u044C\u043A\u0438 \u0441\u0438\u043C\u0432\u043E\u043B\u0456\u0432 \u043D\u0430\u0431\u0440\u0430\u0442\u0438, \u043F\u0435\u0440\u0448 \u043D\u0456\u0436 \u0437\u2019\u044F\u0432\u043B\u044F\u0442\u044C\u0441\u044F \u043F\u0456\u0434\u043A\u0430\u0437\u043A\u0438.",
"set.suggestSkipAfter.name": "\u041F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0442\u0438 \u043F\u0456\u0441\u043B\u044F \u0441\u0438\u043C\u0432\u043E\u043B\u0456\u0432",
"set.suggestPlainText.name": "\u0412\u0441\u0442\u0430\u0432\u043B\u044F\u0442\u0438 \u043F\u0440\u043E\u0441\u0442\u0438\u0439 \u0442\u0435\u043A\u0441\u0442",
"set.suggestPlainText.desc": "\u041F\u0456\u0434\u043A\u0430\u0437\u043A\u0430 \u0434\u043E\u043F\u0438\u0441\u0443\u0454 \u0441\u043B\u043E\u0432\u043E, \u043D\u0435 \u043F\u0435\u0440\u0435\u0442\u0432\u043E\u0440\u044E\u044E\u0447\u0438 \u0439\u043E\u0433\u043E \u043D\u0430 \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F.",
"set.heading.contextMenu": "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043D\u0435 \u043C\u0435\u043D\u044E",
"label.selection": "\u0432\u0438\u0434\u0456\u043B\u0435\u043D\u043D\u044F",
"modal.leftAsText": "\u2014 \u0437\u0430\u043B\u0438\u0448\u0435\u043D\u043E \u0442\u0435\u043A\u0441\u0442\u043E\u043C \u2014",
"modal.skipOption": "(\u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u0438 \u2014 \u0437\u0430\u043B\u0438\u0448\u0438\u0442\u0438 \u0442\u0435\u043A\u0441\u0442\u043E\u043C)",
"modal.materialize.summary": "\u0424\u0430\u0439\u043B\u0456\u0432: {files}, \u0437\u0430\u043C\u0456\u043D: {replacements}",
"modal.unlink.summary": "\u0424\u0430\u0439\u043B\u0456\u0432: {files}, \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u044C \u0434\u043E \u0432\u0438\u0434\u0430\u043B\u0435\u043D\u043D\u044F: {links}",
"modal.choose.body": "\u0426\u0435 \u0441\u043B\u043E\u0432\u043E \u0437\u0431\u0456\u0433\u0430\u0454\u0442\u044C\u0441\u044F \u0437 \u043A\u0456\u043B\u044C\u043A\u043E\u043C\u0430 \u0442\u0435\u0440\u043C\u0456\u043D\u0430\u043C\u0438 \u2014 \u0432\u0438\u0431\u0435\u0440\u0456\u0442\u044C \u043E\u0434\u0438\u043D:",
"notice.noActiveNote": "\u041D\u0435\u043C\u0430\u0454 \u0430\u043A\u0442\u0438\u0432\u043D\u043E\u0457 \u043D\u043E\u0442\u0430\u0442\u043A\u0438",
"notice.noSelection": "\u041D\u0435\u043C\u0430\u0454 \u0432\u0438\u0434\u0456\u043B\u0435\u043D\u043D\u044F",
"notice.scopeSkipped": ", \u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u043E: {n} (\u0437\u043C\u0456\u043D\u0435\u043D\u043E \u043F\u0456\u0441\u043B\u044F \u043F\u043E\u043F\u0435\u0440\u0435\u0434\u043D\u044C\u043E\u0433\u043E \u043F\u0435\u0440\u0435\u0433\u043B\u044F\u0434\u0443)",
"set.editingHighlight.live": "\u041D\u0430 \u043B\u044C\u043E\u0442\u0443 (\u043F\u0456\u0434 \u0447\u0430\u0441 \u043D\u0430\u0431\u043E\u0440\u0443)",
"set.editingHighlight.name": "\u041F\u0456\u0434\u0441\u0432\u0456\u0447\u0443\u0432\u0430\u0442\u0438 \u043F\u0456\u0434 \u0447\u0430\u0441 \u0440\u0435\u0434\u0430\u0433\u0443\u0432\u0430\u043D\u043D\u044F",
"set.lang.invalid": "\u041D\u0435\u0434\u043E\u043F\u0443\u0441\u0442\u0438\u043C\u0438\u0439 \u043C\u043E\u0434\u0443\u043B\u044C: {error}",
"set.languages.desc": "\u0412\u0431\u0443\u0434\u043E\u0432\u0430\u043D\u0456 \u043C\u043E\u0434\u0443\u043B\u0456 \u043C\u043E\u0440\u0444\u043E\u043B\u043E\u0433\u0456\u0457 \u2014 \u0443\u0432\u0456\u043C\u043A\u043D\u0435\u043D\u043E {enabled} \u0437 {total}",
"set.matchMode.name": "\u041C\u043E\u0440\u0444\u043E\u043B\u043E\u0433\u0456\u044F",
"set.matchMode.exact": "\u0422\u043E\u0447\u043D\u0438\u0439 \u0437\u0431\u0456\u0433",
"set.matchMode.endingStrip": "\u0412\u0456\u0434\u0441\u0456\u043A\u0430\u043D\u043D\u044F \u0437\u0430\u043A\u0456\u043D\u0447\u0435\u043D\u044C",
"set.matchMode.stemmer": "\u0421\u0442\u0435\u043C\u0435\u0440 (\u0440\u0435\u043A\u043E\u043C\u0435\u043D\u0434\u043E\u0432\u0430\u043D\u043E)",
"kind.heading": "\u0417\u0430\u0433\u043E\u043B\u043E\u0432\u043E\u043A",
"kind.term": "\u0422\u0435\u0440\u043C\u0456\u043D",
"kind.viaAlias": "\u0437\u0430 \u0430\u043B\u0456\u0430\u0441\u043E\u043C \xAB{form}\xBB",
"set.smartCase.name": "\u0420\u043E\u0437\u0443\u043C\u043D\u0438\u0439 \u0440\u0435\u0433\u0456\u0441\u0442\u0440 \u0434\u043B\u044F \u0430\u0431\u0440\u0435\u0432\u0456\u0430\u0442\u0443\u0440",
"set.smartCase.desc": "\u0422\u0435\u0440\u043C\u0456\u043D\u0438 \u0437 \u0432\u0435\u043B\u0438\u043A\u0438\u0445 \u043B\u0456\u0442\u0435\u0440 (\u043D\u0430\u043F\u0440\u0438\u043A\u043B\u0430\u0434 \xABIT\xBB \u0430\u0431\u043E \xABNASA\xBB) \u0437\u0456\u0441\u0442\u0430\u0432\u043B\u044F\u044E\u0442\u044C\u0441\u044F \u0437 \u0443\u0440\u0430\u0445\u0443\u0432\u0430\u043D\u043D\u044F\u043C \u0440\u0435\u0433\u0456\u0441\u0442\u0440\u0443, \u0449\u043E\u0431 \u043D\u0435 \u0447\u0456\u043F\u043B\u044F\u0442\u0438 \u0437\u0432\u0438\u0447\u0430\u0439\u043D\u0456 \u0441\u043B\u043E\u0432\u0430.",
"set.scopeMode.name": "\u041E\u0431\u043B\u0430\u0441\u0442\u044C \u0437\u0432\u2019\u044F\u0437\u0443\u0432\u0430\u043D\u043D\u044F",
"set.scopeMode.vault": "\u0423\u0441\u044E\u0434\u0438",
"set.scopeMode.folders": "\u041B\u0438\u0448\u0435 \u0432\u043A\u0430\u0437\u0430\u043D\u0456 \u0448\u043B\u044F\u0445\u0438",
"set.suggestMinChars.name": "\u041C\u0456\u043D\u0456\u043C\u0443\u043C \u0441\u0438\u043C\u0432\u043E\u043B\u0456\u0432",
"set.statusBarIncludeLinks.name": "\u0420\u0430\u0445\u0443\u0432\u0430\u0442\u0438 \u043F\u0440\u044F\u043C\u0456 \u043F\u043E\u0441\u0438\u043B\u0430\u043D\u043D\u044F",
"plural.alias": { one: "{n} \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0456\u043C", few: "{n} \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0456\u043C\u0438", many: "{n} \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0456\u043C\u0456\u0432", other: "{n} \u043F\u0441\u0435\u0432\u0434\u043E\u043D\u0456\u043C\u0456\u0432" }
};
module2.exports = { en, ru, de, es, fr, uk };
}
});
// src/shared/locales/sigil.js
var require_sigil = __commonJS({
"src/shared/locales/sigil.js"(exports2, module2) {
"use strict";
var en = {
"menu.convert": "Find and convert to link",
"menu.convert.group": "Find and convert to link",
"menu.open.group": "Find and open",
"notice.updateSkipped": "({n} note(s) skipped \u2014 changed since the preview)",
"embed.menu.refresh": "Refresh embed",
"modal.embedPlaceholder": "Choose an embed format\u2026",
"modal.update.summary": "{links} change(s) across {files} note(s). Uncheck any change to skip it, or a note to skip all of its changes.",
"modal.update.upToDate": "Everything is up to date \u2014 nothing to update.",
"btn.close": "Close",
"label.thisNote": "This note",
"set.heading.suggestions": "Suggestions & links",
"set.heading.hover": "Hover preview",
"set.heading.links": "Links",
"set.codeRoot.desc": "Base folder the scan paths are relative to. Empty = the folder containing this vault.",
"set.scanFolders.name": "Scan folders",
"set.folderList.add": "Add folder\u2026",
"set.folderList.remove": "Remove",
"set.folderList.addAria": "Add",
"set.skipFolders.name": "Skip folders",
"set.trigger.name": "Trigger",
"set.preset.file": "file://",
"set.preset.ask": "Always ask",
"set.editors.count": "{n} added",
"set.editors.collapse": "Collapse",
"set.editors.expand": "Expand",
"set.editors.namePlaceholder": "Name",
"set.editors.remove": "Remove",
"set.minChars.name": "Min characters",
"set.minChars.desc": "How many characters to type before suggestions appear.",
"set.maxResults.name": "Max results",
"set.maxResults.desc": "Most suggestions to show at once.",
"set.autoRefresh.name": "Auto-refresh index",
"set.autoRefresh.unsupported": "Recursive folder watching isn\u2019t supported on this platform (Linux); rebuild manually instead.",
"set.contextMenu.name": "Editor context menu",
"set.markStaleLinks.name": "Mark stale links",
"set.info.unknownRoot": "(unknown)",
"plural.entry": { one: "{n} entry", other: "{n} entries" }
};
var ru = {
"menu.convert": "\u041D\u0430\u0439\u0442\u0438 \u0438 \u043F\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044C \u0432 \u0441\u0441\u044B\u043B\u043A\u0443",
"menu.convert.group": "\u041D\u0430\u0439\u0442\u0438 \u0438 \u043F\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044C \u0432 \u0441\u0441\u044B\u043B\u043A\u0443",
"menu.open.group": "\u041D\u0430\u0439\u0442\u0438 \u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u044C",
"notice.updateSkipped": "(\u043F\u0440\u043E\u043F\u0443\u0449\u0435\u043D\u043E \u0437\u0430\u043C\u0435\u0442\u043E\u043A \u2014 {n}: \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u0438\u0441\u044C \u043F\u043E\u0441\u043B\u0435 \u043F\u0440\u0435\u0434\u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0430)",
"embed.menu.refresh": "\u041E\u0431\u043D\u043E\u0432\u0438\u0442\u044C embed",
"modal.embedPlaceholder": "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0444\u043E\u0440\u043C\u0430\u0442 embed\u2026",
"modal.update.summary": "\u041F\u0440\u0430\u0432\u043E\u043A \u2014 {links} \u0432 \u0437\u0430\u043C\u0435\u0442\u043A\u0430\u0445: {files}. \u0421\u043D\u0438\u043C\u0438\u0442\u0435 \u0433\u0430\u043B\u043E\u0447\u043A\u0443 \u0441 \u043F\u0440\u0430\u0432\u043A\u0438, \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0435\u0451, \u0438\u043B\u0438 \u0441 \u0437\u0430\u043C\u0435\u0442\u043A\u0438 \u2014 \u0447\u0442\u043E\u0431\u044B \u043F\u0440\u043E\u043F\u0443\u0441\u0442\u0438\u0442\u044C \u0432\u0441\u0435 \u0435\u0451 \u043F\u0440\u0430\u0432\u043A\u0438.",
"modal.update.upToDate": "\u0412\u0441\u0451 \u0430\u043A\u0442\u0443\u0430\u043B\u044C\u043D\u043E \u2014 \u043E\u0431\u043D\u043E\u0432\u043B\u044F\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.",
"btn.close": "\u0417\u0430\u043A\u0440\u044B\u0442\u044C",
"label.thisNote": "\u042D\u0442\u0430 \u0437\u0430\u043C\u0435\u0442\u043A\u0430",
"set.heading.suggestions": "\u041F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0438 \u0438 \u0441\u0441\u044B\u043B\u043A\u0438",
"set.heading.hover": "\u041F\u0440\u0435\u0432\u044C\u044E \u043F\u0440\u0438 \u043D\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u0438",
"set.heading.links": "\u0421\u0441\u044B\u043B\u043A\u0438",
"set.codeRoot.desc": "\u0411\u0430\u0437\u043E\u0432\u0430\u044F \u043F\u0430\u043F\u043A\u0430, \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u043A\u043E\u0442\u043E\u0440\u043E\u0439 \u0437\u0430\u0434\u0430\u044E\u0442\u0441\u044F \u043F\u0443\u0442\u0438 \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F. \u041F\u0443\u0441\u0442\u043E = \u043F\u0430\u043F\u043A\u0430, \u0441\u043E\u0434\u0435\u0440\u0436\u0430\u0449\u0430\u044F \u044D\u0442\u043E \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435.",
"set.scanFolders.name": "\u041F\u0430\u043F\u043A\u0438 \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F",
"set.folderList.add": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u0430\u043F\u043A\u0443\u2026",
"set.folderList.remove": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C",
"set.folderList.addAria": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C",
"set.skipFolders.name": "\u041F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u043C\u044B\u0435 \u043F\u0430\u043F\u043A\u0438",
"set.trigger.name": "\u0422\u0440\u0438\u0433\u0433\u0435\u0440",
"set.preset.file": "file://",
"set.preset.ask": "\u0412\u0441\u0435\u0433\u0434\u0430 \u0441\u043F\u0440\u0430\u0448\u0438\u0432\u0430\u0442\u044C",
"set.editors.count": "\u0434\u043E\u0431\u0430\u0432\u043B\u0435\u043D\u043E: {n}",
"set.editors.collapse": "\u0421\u0432\u0435\u0440\u043D\u0443\u0442\u044C",
"set.editors.expand": "\u0420\u0430\u0437\u0432\u0435\u0440\u043D\u0443\u0442\u044C",
"set.editors.namePlaceholder": "\u041D\u0430\u0437\u0432\u0430\u043D\u0438\u0435",
"set.editors.remove": "\u0423\u0434\u0430\u043B\u0438\u0442\u044C",
"set.minChars.name": "\u041C\u0438\u043D\u0438\u043C\u0443\u043C \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432",
"set.minChars.desc": "\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u0441\u0438\u043C\u0432\u043E\u043B\u043E\u0432 \u0432\u0432\u0435\u0441\u0442\u0438, \u043F\u0440\u0435\u0436\u0434\u0435 \u0447\u0435\u043C \u043F\u043E\u044F\u0432\u044F\u0442\u0441\u044F \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0438.",
"set.maxResults.name": "\u041C\u0430\u043A\u0441\u0438\u043C\u0443\u043C \u0440\u0435\u0437\u0443\u043B\u044C\u0442\u0430\u0442\u043E\u0432",
"set.maxResults.desc": "\u0421\u043A\u043E\u043B\u044C\u043A\u043E \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043E\u043A \u043F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u043E\u0434\u043D\u043E\u0432\u0440\u0435\u043C\u0435\u043D\u043D\u043E.",
"set.autoRefresh.name": "\u0410\u0432\u0442\u043E\u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u0438\u043D\u0434\u0435\u043A\u0441\u0430",
"set.autoRefresh.unsupported": "\u0420\u0435\u043A\u0443\u0440\u0441\u0438\u0432\u043D\u043E\u0435 \u0441\u043B\u0435\u0436\u0435\u043D\u0438\u0435 \u0437\u0430 \u043F\u0430\u043F\u043A\u0430\u043C\u0438 \u043D\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u044D\u0442\u043E\u0439 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435 (Linux); \u043F\u0435\u0440\u0435\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0439\u0442\u0435 \u0432\u0440\u0443\u0447\u043D\u0443\u044E.",
"set.contextMenu.name": "\u041A\u043E\u043D\u0442\u0435\u043A\u0441\u0442\u043D\u043E\u0435 \u043C\u0435\u043D\u044E \u0440\u0435\u0434\u0430\u043A\u0442\u043E\u0440\u0430",
"set.markStaleLinks.name": "\u041E\u0442\u043C\u0435\u0447\u0430\u0442\u044C \u0443\u0441\u0442\u0430\u0440\u0435\u0432\u0448\u0438\u0435 \u0441\u0441\u044B\u043B\u043A\u0438",
"set.info.unknownRoot": "(\u043D\u0435\u0438\u0437\u0432\u0435\u0441\u0442\u043D\u043E)",
"plural.entry": { one: "{n} \u0437\u0430\u043F\u0438\u0441\u044C", few: "{n} \u0437\u0430\u043F\u0438\u0441\u0438", many: "{n} \u0437\u0430\u043F\u0438\u0441\u0435\u0439", other: "{n} \u0437\u0430\u043F\u0438\u0441\u0435\u0439" }
};
module2.exports = { en, ru };
}
});
// src/shared/i18n.js
var require_i18n = __commonJS({
"src/shared/i18n.js"(exports2, module2) {
"use strict";
var LOCALES = { en: {} };
var dict = LOCALES.en;
var pluralRules = new Intl.PluralRules("en");
function initI18n2(locales) {
LOCALES = locales;
const sys = (window.localStorage.getItem("language") || "").split("-")[0].toLowerCase();
const locale = LOCALES[sys] ? sys : "en";
dict = LOCALES[locale];
try {
pluralRules = new Intl.PluralRules(locale);
} catch (e) {
pluralRules = new Intl.PluralRules("en");
}
}
function interpolate(str, vars) {
if (!vars)
return str;
return str.replace(/\{(\w+)\}/g, (m, k) => k in vars ? String(vars[k]) : m);
}
function t2(key, vars) {
let entry = dict[key];
if (entry === void 0)
entry = LOCALES.en[key];
if (entry === void 0)
return key;
return interpolate(entry, vars);
}
function plural2(noun, n) {
const forms = dict["plural." + noun] || LOCALES.en["plural." + noun];
if (!forms)
return n + " " + noun;
let cat;
try {
cat = pluralRules.select(n);
} catch (e) {
cat = "other";
}
const tpl = forms[cat] != null ? forms[cat] : forms.other != null ? forms.other : Object.values(forms)[0];
return interpolate(tpl, { n });
}
var FAMILY = {
common: require_common(),
prose: require_prose(),
sigil: require_sigil()
};
function withFamily2(kind, pluginLocales) {
const common = FAMILY.common;
const pair = FAMILY[kind] || {};
const out = {};
for (const lang of Object.keys(pluginLocales)) {
out[lang] = Object.assign({}, common[lang], pair[lang], pluginLocales[lang]);
}
return out;
}
module2.exports = { initI18n: initI18n2, t: t2, plural: plural2, withFamily: withFamily2 };
}
});
// src/shared/menu-verbs.js
var require_menu_verbs = __commonJS({
"src/shared/menu-verbs.js"(exports2, module2) {
"use strict";
var { sharedSection, menuSection } = require_menu();
var { peersOffering } = require_discover();
var { t: t2 } = require_i18n();
var VERBS = {
convert: { label: "menu.convert.group", icon: "link" },
open: { label: "menu.open.group", icon: "file-search" },
exclude: { label: "exclude.group", icon: "ban" }
};
var MenuBuilder = class {
constructor(plugin, menu) {
this.plugin = plugin;
this.menu = menu;
this.entries = [];
}
// Untagged: written where it stands, exactly as Obsidian's own Menu would.
addItem(cb) {
this.entries.push({ cb });
return this;
}
addSeparator() {
this.entries.push({ separator: true });
return this;
}
// Tagged. `cb(item, grouped)` is told whether it ended up in a submenu, since the wording
// differs: inside one, the parent already names the object.
tagged(verb, opts, cb) {
if (!VERBS[verb])
throw new Error("unknown menu verb: " + verb);
this.entries.push({ cb, verb, value: opts && opts.value });
return this;
}
// A submenu of this plugin's own — the several ways to link one word, say. Unlike a verb it
// is never shared, and it is built even for a single item because the items only read as a
// set. Takes items the way a menu does.
section(label, icon) {
const entry = { section: { label, icon }, children: [] };
this.entries.push(entry);
const child = {
addItem(cb) {
entry.children.push({ cb });
return child;
},
addSeparator() {
entry.children.push({ separator: true });
return child;
}
};
return child;
}
// Verb -> the object it acts on, for those that earned a submenu. All items of one verb in
// one menu act on the same object, so the first one's value names the group.
groupedVerbs() {
const counts = /* @__PURE__ */ new Map();
for (const e of this.entries) {
if (!e.verb)
continue;
const seen = counts.get(e.verb) || { count: 0, value: e.value };
seen.count++;
counts.set(e.verb, seen);
}
const provider = this.plugin.api && this.plugin.api.linker;
const grouped = /* @__PURE__ */ new Map();
for (const [verb, { count, value }] of counts) {
const peers = provider ? peersOffering(this.plugin.app, provider, verb, value).length : 0;
if (count + peers > 1)
grouped.set(verb, value);
}
return grouped;
}
// menuSection builds the group on its first item, so an empty one leaves no trace, and it
// falls back to prefixed titles where the app has no submenus.
writeSection(entry) {
if (!entry.children.length)
return;
const sub = menuSection(this.menu, entry.section.label, true, entry.section.icon);
for (const child of entry.children) {
if (child.separator)
sub.addSeparator();
else
sub.addItem((item) => child.cb(item, true));
}
}
sectionFor(verb, value) {
const spec = VERBS[verb];
const label = t2(spec.label, value == null ? void 0 : { value });
return sharedSection(this.menu, "linker:" + verb, label, spec.icon);
}
// Replayed in declaration order, so a verb's submenu appears where its first item would
// have. Anything else keeps its place.
flush() {
const grouped = this.groupedVerbs();
const sections = /* @__PURE__ */ new Map();
for (const e of this.entries) {
if (e.separator) {
this.menu.addSeparator();
continue;
}
if (e.section) {
this.writeSection(e);
continue;
}
if (!e.verb || !grouped.has(e.verb)) {
this.menu.addItem((item) => e.cb(item, false));
continue;
}
if (!sections.has(e.verb))
sections.set(e.verb, this.sectionFor(e.verb, grouped.get(e.verb)));
sections.get(e.verb).addItem((item) => e.cb(item, true));
}
}
};
function buildMenu2(plugin, menu, fn) {
const builder = new MenuBuilder(plugin, menu);
fn(builder);
builder.flush();
}
module2.exports = { VERBS, MenuBuilder, buildMenu: buildMenu2 };
}
});
// src/shared/link-owner.js
var require_link_owner = __commonJS({
"src/shared/link-owner.js"(exports2, module2) {
"use strict";
var { outranks, discoverLinkers } = require_discover();
var RANK = { binding: 2, index: 1 };
function linkOwner(app, target, title) {
let best = null;
let bestRank = 0;
for (const peer of discoverLinkers(app)) {
if (typeof peer.claim !== "function")
continue;
let claim;
try {
claim = peer.claim(target, title);
} catch (e) {
claim = null;
}
const rank = RANK[claim] || 0;
if (!rank)
continue;
if (rank > bestRank || rank === bestRank && best && outranks(peer, best)) {
best = peer;
bestRank = rank;
}
}
return best;
}
function ownsLink2(app, self, target, title) {
const owner = linkOwner(app, target, title);
return !!owner && owner.id === self.id;
}
module2.exports = { linkOwner, ownsLink: ownsLink2, RANK };
}
});
// src/shared/deeplink/suggest.js
var require_suggest = __commonJS({
"src/shared/deeplink/suggest.js"(exports2, module2) {
"use strict";
var { EditorSuggest, prepareFuzzySearch } = require("obsidian");
var { isProtected, inTableCell: inTableCell2 } = require_markdown();
function createSigilSuggest(config) {
const { cls, kindText } = config;
const prepare = config.prepare || (() => () => true);
return class SigilSuggest extends EditorSuggest {
constructor(app, plugin) {
super(app);
this.plugin = plugin;
}
onTrigger(cursor, editor) {
const s = this.plugin.settings;
const before = editor.getLine(cursor.line).slice(0, cursor.ch);
const i = before.lastIndexOf(s.trigger);
if (i === -1)
return null;
const query = before.slice(i + s.trigger.length);
if (!/^[\w.:]*$/.test(query))
return null;
if (query.length < Math.max(0, s.minChars))
return null;
const off = editor.posToOffset(cursor);
if (isProtected(editor.getValue(), off))
return null;
return { start: { line: cursor.line, ch: i }, end: cursor, query };
}
getSuggestions(ctx) {
const idx = this.plugin.index;
if (!idx || !idx.length)
return [];
const max = this.plugin.settings.maxResults;
const f = this.plugin.parseQuery(ctx.query);
const allowed = prepare(this.plugin);
const pass = (e) => allowed(e) && this.plugin.entryPassesFilter(e, f);
if (!f.name) {
const out = [];
for (const e of idx) {
if (!pass(e))
continue;
out.push(e);
if (out.length >= max)
break;
}
return out;
}
const match = prepareFuzzySearch(f.name);
const scored = [];
for (const e of idx) {
if (!pass(e))
continue;
const r = match(e.name);
if (r)
scored.push({ e, score: r.score });
}
scored.sort((a, b) => b.score - a.score || a.e.name.localeCompare(b.e.name));
return scored.slice(0, max).map((s) => s.e);
}
renderSuggestion(e, el) {
el.addClass(`${cls}-suggestion`);
el.createSpan({ cls: `${cls}-name`, text: e.name });
el.createSpan({ cls: `${cls}-kind`, text: kindText(e) });
el.createSpan({ cls: `${cls}-path`, text: e.path });
}
selectSuggestion(e) {
const ctx = this.context;
if (!ctx)
return;
const inTable = inTableCell2(ctx.editor.getValue(), ctx.editor.posToOffset(ctx.start));
const insert = (template) => {
const link = this.plugin.buildLink(e, inTable, template);
ctx.editor.replaceRange(link, ctx.start, ctx.end);
const pos = ctx.editor.posToOffset(ctx.start) + link.length;
ctx.editor.setCursor(ctx.editor.offsetToPos(pos));
};
this.plugin.withFormat(this.plugin.settings.askOnInsert, insert);
}
};
}
module2.exports = { createSigilSuggest };
}
});
// src/pdf.js
var require_pdf = __commonJS({
"src/pdf.js"(exports2, module2) {
"use strict";
var obsidian = require("obsidian");
var fs2 = require("fs");
var libPromise = null;
function pdfjsLib() {
if (!libPromise) {
libPromise = typeof obsidian.loadPdfJs === "function" ? obsidian.loadPdfJs().catch(() => null) : Promise.resolve(null);
}
return libPromise;
}
async function openDocument(absPath) {
const lib = await pdfjsLib();
if (!lib || typeof lib.getDocument !== "function")
return null;
try {
const data = new Uint8Array(fs2.readFileSync(absPath));
return await lib.getDocument({ data, isEvalSupported: false }).promise;
} catch (e) {
return null;
}
}
async function pageOf(doc, dest) {
try {
let d = dest;
if (typeof d === "string")
d = await doc.getDestination(d);
if (!Array.isArray(d) || !d[0])
return null;
return await doc.getPageIndex(d[0]) + 1;
} catch (e) {
return null;
}
}
async function readOutline(absPath) {
const doc = await openDocument(absPath);
if (!doc)
return [];
try {
const outline = await doc.getOutline();
if (!outline || !outline.length)
return [];
const out = [];
const walk = async (items) => {
for (const it of items) {
const page = await pageOf(doc, it.dest);
const title = it.title && it.title.trim();
if (title && page)
out.push({ title, page });
if (it.items && it.items.length)
await walk(it.items);
}
};
await walk(outline);
return out;
} catch (e) {
return [];
} finally {
try {
await doc.destroy();
} catch (e) {
}
}
}
async function renderPageToCanvas(doc, pageNum, canvas, cssWidth) {
try {
const n = Math.min(Math.max(1, pageNum | 0), doc.numPages);
const page = await doc.getPage(n);
const unit = page.getViewport({ scale: 1 });
const dpr = window.devicePixelRatio || 1;
const viewport = page.getViewport({ scale: cssWidth / unit.width * dpr });
canvas.width = Math.floor(viewport.width);
canvas.height = Math.floor(viewport.height);
canvas.style.width = viewport.width / dpr + "px";
canvas.style.height = viewport.height / dpr + "px";
await page.render({ canvasContext: canvas.getContext("2d"), viewport }).promise;
page.cleanup();
return true;
} catch (e) {
return false;
}
}
module2.exports = { openDocument, readOutline, renderPageToCanvas };
}
});
// src/formats/pdf.js
var require_pdf2 = __commonJS({
"src/formats/pdf.js"(exports2, module2) {
"use strict";
var { openDocument, readOutline, renderPageToCanvas } = require_pdf();
var openPath = "";
var openDoc = null;
async function dispose() {
if (openDoc) {
try {
await openDoc.destroy();
} catch (e) {
}
}
openPath = "";
openDoc = null;
}
async function getDoc(absPath) {
if (openPath === absPath && openDoc)
return openDoc;
await dispose();
const doc = await openDocument(absPath);
if (doc) {
openPath = absPath;
openDoc = doc;
}
return doc;
}
async function render(el, req) {
const doc = await getDoc(req.abs);
if (!req.isCurrent() || !doc)
return false;
const canvas = el.createEl("canvas");
const ok = await renderPageToCanvas(doc, req.page, canvas, req.width);
if (!req.isCurrent() || !ok)
return false;
return null;
}
module2.exports = {
exts: ["pdf"],
anchorKind: "page",
anchorFor: (e) => e.kind === "section" && e.page ? "page=" + e.page : null,
positionLabel: (n, to) => "p." + n + (to && to > n ? "\u2013" + to : ""),
outline: readOutline,
render,
dispose
};
}
});
// src/formats/image.js
var require_image = __commonJS({
"src/formats/image.js"(exports2, module2) {
"use strict";
var fs2 = require("fs");
var MIME = {
png: "image/png",
jpg: "image/jpeg",
jpeg: "image/jpeg",
gif: "image/gif",
webp: "image/webp",
bmp: "image/bmp",
svg: "image/svg+xml",
avif: "image/avif"
};
async function render(el, req) {
let buf;
try {
buf = fs2.readFileSync(req.abs);
} catch (e) {
return false;
}
if (!req.isCurrent())
return false;
const url = URL.createObjectURL(new Blob([buf], { type: MIME[req.ext] || "application/octet-stream" }));
const img = el.createEl("img");
img.src = url;
img.style.maxWidth = req.width + "px";
return () => {
try {
URL.revokeObjectURL(url);
} catch (e) {
}
};
}
module2.exports = {
exts: Object.keys(MIME),
anchorKind: null,
render
};
}
});
// src/zip.js
var require_zip = __commonJS({
"src/zip.js"(exports2, module2) {
"use strict";
var fs2 = require("fs");
var zlib = require("zlib");
var EOCD_SIG = 101010256;
var CEN_SIG = 33639248;
var LOC_SIG = 67324752;
var EOCD_MIN = 22;
var MAX_COMMENT = 65535;
function findEocd(buf) {
const start = Math.max(0, buf.length - EOCD_MIN - MAX_COMMENT);
for (let i = buf.length - EOCD_MIN; i >= start; i--) {
if (buf.readUInt32LE(i) === EOCD_SIG && buf.readUInt16LE(i + 20) === buf.length - i - EOCD_MIN)
return i;
}
return -1;
}
function readCentral(buf) {
const eocd = findEocd(buf);
if (eocd < 0)
return null;
const count = buf.readUInt16LE(eocd + 10);
let p = buf.readUInt32LE(eocd + 16);
if (p === 4294967295)
return null;
const out = /* @__PURE__ */ new Map();
for (let i = 0; i < count; i++) {
if (p + 46 > buf.length || buf.readUInt32LE(p) !== CEN_SIG)
break;
const method = buf.readUInt16LE(p + 10);
const compressed = buf.readUInt32LE(p + 20);
const uncompressed = buf.readUInt32LE(p + 24);
const nameLen = buf.readUInt16LE(p + 28);
const extraLen = buf.readUInt16LE(p + 30);
const commentLen = buf.readUInt16LE(p + 32);
const offset = buf.readUInt32LE(p + 42);
const name = buf.slice(p + 46, p + 46 + nameLen).toString("utf8");
out.set(name, { method, compressed, uncompressed, offset });
p += 46 + nameLen + extraLen + commentLen;
}
return out;
}
function inflateMember(buf, ent) {
if (ent.offset + 30 > buf.length || buf.readUInt32LE(ent.offset) !== LOC_SIG)
return null;
const nameLen = buf.readUInt16LE(ent.offset + 26);
const extraLen = buf.readUInt16LE(ent.offset + 28);
const start = ent.offset + 30 + nameLen + extraLen;
const raw = buf.slice(start, start + ent.compressed);
if (ent.method === 0)
return raw;
if (ent.method !== 8)
return null;
try {
return zlib.inflateRawSync(raw);
} catch (e) {
return null;
}
}
function openZip(absPath) {
let buf;
try {
buf = fs2.readFileSync(absPath);
} catch (e) {
return null;
}
const central = readCentral(buf);
if (!central)
return null;
const cache = /* @__PURE__ */ new Map();
const read = (name) => {
if (cache.has(name))
return cache.get(name);
const ent = central.get(name);
const out = ent ? inflateMember(buf, ent) : null;
cache.set(name, out);
return out;
};
return {
names: () => [...central.keys()],
has: (name) => central.has(name),
read,
text: (name) => {
const b = read(name);
return b ? b.toString("utf8") : null;
}
};
}
module2.exports = { openZip };
}
});
// src/xml.js
var require_xml = __commonJS({
"src/xml.js"(exports2, module2) {
"use strict";
var ENTITIES = {
amp: "&",
lt: "<",
gt: ">",
quot: '"',
apos: "'",
nbsp: " ",
shy: "\xAD",
ensp: " ",
emsp: " ",
thinsp: " ",
ndash: "\u2013",
mdash: "\u2014",
hellip: "\u2026",
bull: "\u2022",
middot: "\xB7",
dagger: "\u2020",
lsquo: "\u2018",
rsquo: "\u2019",
ldquo: "\u201C",
rdquo: "\u201D",
laquo: "\xAB",
raquo: "\xBB",
copy: "\xA9",
reg: "\xAE",
trade: "\u2122",
sect: "\xA7",
para: "\xB6",
deg: "\xB0",
larr: "\u2190",
rarr: "\u2192",
harr: "\u2194",
times: "\xD7",
minus: "\u2212",
plusmn: "\xB1",
ne: "\u2260",
le: "\u2264",
ge: "\u2265",
euro: "\u20AC",
pound: "\xA3",
yen: "\xA5",
cent: "\xA2"
};
function decodeEntities(s) {
return String(s).replace(/&(#x?[0-9a-f]+|[a-z][a-z0-9]*);/gi, (m, body) => {
if (body[0] === "#") {
const code = body[1] === "x" || body[1] === "X" ? parseInt(body.slice(2), 16) : parseInt(body.slice(1), 10);
return code >= 0 && code <= 1114111 ? String.fromCodePoint(code) : m;
}
const hit = ENTITIES[body.toLowerCase()];
return hit === void 0 ? m : hit;
});
}
function elements(xml, tag) {
const out = [];
const open = new RegExp("<" + tag + "(?=[\\s/>])", "g");
let m;
while (m = open.exec(xml)) {
const end = scanElement(xml, m.index, tag);
if (end < 0)
break;
out.push(xml.slice(m.index, end));
open.lastIndex = end;
}
return out;
}
function scanElement(xml, start, tag) {
const open = "<" + tag;
const close = "</" + tag + ">";
let depth = 0;
let i = start;
while (i >= 0 && i < xml.length) {
if (xml.startsWith(close, i)) {
const gt = xml.indexOf(">", i);
if (gt < 0)
return -1;
if (--depth === 0)
return gt + 1;
i = gt + 1;
} else if (xml.startsWith(open, i)) {
if (!/[\s/>]/.test(xml[i + open.length] || "")) {
i += open.length;
continue;
}
const gt = xml.indexOf(">", i);
if (gt < 0)
return -1;
if (xml[gt - 1] === "/") {
if (depth === 0)
return gt + 1;
} else
depth++;
i = gt + 1;
} else {
const a = xml.indexOf(open, i);
const b = xml.indexOf(close, i);
if (a < 0 && b < 0)
return -1;
i = a < 0 ? b : b < 0 ? a : Math.min(a, b);
}
}
return -1;
}
function attr(source, name) {
const m = new RegExp("\\s" + name + `\\s*=\\s*(?:"([^"]*)"|'([^']*)')`).exec(source);
if (!m)
return null;
return decodeEntities(m[1] !== void 0 ? m[1] : m[2]);
}
function textIn(source, tag) {
const re = new RegExp("<" + tag + "(?:\\s[^>]*)?>([\\s\\S]*?)</" + tag + ">", "g");
let out = "";
let m;
while (m = re.exec(source))
out += decodeEntities(m[1]);
return out;
}
module2.exports = { decodeEntities, elements, attr, textIn };
}
});
// src/formats/preview.js
var require_preview = __commonJS({
"src/formats/preview.js"(exports2, module2) {
"use strict";
var obsidian = require("obsidian");
var { t: t2 } = require_i18n();
var IMAGE_BUDGET = 24 * 1024 * 1024;
var REMOTE = /^(?:[a-z][a-z0-9+.-]*:|\/\/)/i;
var IMAGE_MIME = {
png: "image/png",
jpg: "image/jpeg",
jpeg: "image/jpeg",
gif: "image/gif",
webp: "image/webp",
bmp: "image/bmp",
svg: "image/svg+xml",
avif: "image/avif"
};
var mimeForImage = (name) => IMAGE_MIME[(name.split(".").pop() || "").toLowerCase()] || "application/octet-stream";
function inlineImages(box, load) {
const urls = [];
if (!box || !box.querySelectorAll)
return urls;
let spent = 0;
for (const img of box.querySelectorAll("img")) {
const src = img.getAttribute && img.getAttribute("src");
if (!src || REMOTE.test(src))
continue;
let buf = null;
try {
buf = load(src);
} catch (e) {
buf = null;
}
if (!buf || spent + buf.length > IMAGE_BUDGET) {
if (img.removeAttribute)
img.removeAttribute("src");
continue;
}
spent += buf.length;
const url = URL.createObjectURL(new Blob([buf], { type: mimeForImage(src) }));
img.src = url;
urls.push(url);
}
return urls;
}
var revoker = (urls) => urls.length ? () => {
for (const u of urls) {
try {
URL.revokeObjectURL(u);
} catch (e) {
}
}
} : null;
async function renderMarkdown(el, { markdown, width, app, component, loadImage }) {
const R = obsidian.MarkdownRenderer;
const render = R && (R.render || R.renderMarkdown);
if (!render || !component)
return false;
const box = el.createDiv({ cls: "reference-linker-rendered markdown-rendered" });
box.style.maxWidth = width + "px";
try {
if (R.render)
await R.render(app, markdown, box, "", component);
else
await R.renderMarkdown(markdown, box, "", component);
} catch (e) {
box.remove();
return false;
}
return revoker(loadImage ? inlineImages(box, loadImage) : []);
}
function renderHtml(el, { html, width, loadImage }) {
if (typeof obsidian.sanitizeHTMLToDom !== "function")
return false;
const box = el.createDiv({ cls: "reference-linker-rendered markdown-rendered" });
box.style.maxWidth = width + "px";
try {
box.appendChild(obsidian.sanitizeHTMLToDom(html));
} catch (e) {
box.remove();
return false;
}
return revoker(loadImage ? inlineImages(box, loadImage) : []);
}
function renderLines(el, { title, body, width }) {
const box = el.createDiv({ cls: "reference-linker-doc" });
box.style.maxWidth = width + "px";
if (title)
box.createDiv({ cls: "reference-linker-doc-title", text: title });
for (const line of body || [])
box.createDiv({ cls: "reference-linker-doc-line", text: line });
if (!title && !(body || []).length) {
box.createDiv({ cls: "reference-linker-doc-empty", text: t2("preview.empty") });
}
return null;
}
module2.exports = { renderLines, renderMarkdown, renderHtml, inlineImages };
}
});
// src/formats/util.js
var require_util = __commonJS({
"src/formats/util.js"(exports2, module2) {
"use strict";
var clampPage = (page, total) => Math.min(Math.max(1, page | 0), total);
function normPath(pathStr) {
const out = [];
for (const seg of String(pathStr).split("/")) {
if (!seg || seg === ".")
continue;
if (seg === "..")
out.pop();
else
out.push(seg);
}
return out.join("/");
}
var assetSrc = (src) => decodeURIComponent(String(src).split(/[?#]/)[0]);
module2.exports = { clampPage, normPath, assetSrc };
}
});
// src/formats/pptx.js
var require_pptx = __commonJS({
"src/formats/pptx.js"(exports2, module2) {
"use strict";
var { openZip } = require_zip();
var { elements, attr, textIn } = require_xml();
var { renderLines } = require_preview();
var { clampPage, normPath } = require_util();
var SLIDE_RE = /^ppt\/slides\/slide(\d+)\.xml$/;
var TITLE_PH = /* @__PURE__ */ new Set(["title", "ctrTitle"]);
var resolveTarget = (target) => normPath("ppt/" + String(target).replace(/^\/+/, ""));
function slideParts(zip) {
const pres = zip.text("ppt/presentation.xml");
const rels = zip.text("ppt/_rels/presentation.xml.rels");
if (pres && rels) {
const targets = /* @__PURE__ */ new Map();
for (const r of elements(rels, "Relationship")) {
const id = attr(r, "Id");
const tgt = attr(r, "Target");
if (id && tgt)
targets.set(id, resolveTarget(tgt));
}
const lst = elements(pres, "p:sldIdLst")[0];
if (lst) {
const out = [];
for (const s of elements(lst, "p:sldId")) {
const tgt = targets.get(attr(s, "r:id"));
if (tgt && zip.has(tgt))
out.push(tgt);
}
if (out.length)
return out;
}
}
return zip.names().filter((n) => SLIDE_RE.test(n)).sort((a, b) => Number(SLIDE_RE.exec(a)[1]) - Number(SLIDE_RE.exec(b)[1]));
}
var BR = /<a:br(?:\s[^>]*)?\/>|<a:br(?:\s[^>]*)?>[\s\S]*?<\/a:br>/;
var paragraphs = (source) => elements(source, "a:p").flatMap((p) => p.split(BR)).map((chunk) => textIn(chunk, "a:t").replace(/\s+/g, " ").trim()).filter(Boolean);
function shapeIsTitle(sp) {
const ph = elements(elements(sp, "p:nvSpPr")[0] || "", "p:ph")[0];
return !!ph && TITLE_PH.has(attr(ph, "type") || "");
}
function slideTitle(xml) {
for (const sp of elements(xml, "p:sp")) {
if (!shapeIsTitle(sp))
continue;
const text = paragraphs(sp).join(" ");
if (text)
return text;
}
return paragraphs(xml)[0] || "";
}
function slideText(xml) {
const title = slideTitle(xml);
const body = [];
for (const sp of elements(xml, "p:sp")) {
if (shapeIsTitle(sp))
continue;
for (const line of paragraphs(sp))
if (line !== title)
body.push(line);
}
return { title, body };
}
function readSlides(absPath) {
const zip = openZip(absPath);
if (!zip)
return null;
const parts = slideParts(zip);
if (!parts.length)
return null;
return { zip, parts };
}
async function readOutline(absPath) {
const doc = readSlides(absPath);
if (!doc)
return [];
const out = [];
doc.parts.forEach((part, i) => {
const xml = doc.zip.text(part);
if (!xml)
return;
const title = slideTitle(xml);
if (title)
out.push({ title, page: i + 1 });
});
return out;
}
async function readSlide(absPath, page) {
const doc = readSlides(absPath);
if (!doc)
return null;
const n = clampPage(page, doc.parts.length);
const xml = doc.zip.text(doc.parts[n - 1]);
if (!xml)
return null;
return { ...slideText(xml), page: n, total: doc.parts.length };
}
async function render(el, req) {
const slide = await readSlide(req.abs, req.page);
if (!req.isCurrent() || !slide)
return false;
return renderLines(el, { title: slide.title, body: slide.body, width: req.width });
}
module2.exports = {
exts: ["pptx"],
anchorKind: null,
// PowerPoint takes a fragment as part of the file name and finds nothing
outline: readOutline,
render,
readOutline,
readSlide
};
}
});
// src/formats/html.js
var require_html = __commonJS({
"src/formats/html.js"(exports2, module2) {
"use strict";
var fs2 = require("fs");
var nodePath2 = require("path");
var { decodeEntities } = require_xml();
var { renderLines, renderHtml } = require_preview();
var { clampPage, assetSrc } = require_util();
function assetLoader(htmlAbs) {
const dir = nodePath2.dirname(htmlAbs);
return (src) => {
const abs = nodePath2.resolve(dir, assetSrc(src));
if (abs !== dir && !abs.startsWith(dir + nodePath2.sep))
return null;
return fs2.readFileSync(abs);
};
}
var HEADING = /<h([1-6])\b([^>]*)>([\s\S]*?)<\/h\1\s*>/gi;
var ID_ATTR = /\b(?:id|name)\s*=\s*(?:"([^"]*)"|'([^']*)'|([^\s">]+))/i;
var DROP = /<(script|style|template)\b[\s\S]*?<\/\1\s*>/gi;
var BLOCK_END = /<\/(?:p|div|li|tr|dt|dd|h[1-6]|pre|blockquote|section|article|table)\s*>/gi;
var MAX_LINES = 40;
var read = (absPath) => {
try {
return fs2.readFileSync(absPath, "utf8");
} catch (e) {
return null;
}
};
var inlineText = (fragment) => decodeEntities(fragment.replace(/<[^>]*>/g, "")).replace(/\s+/g, " ").trim();
function blockLines(fragment) {
const flat = fragment.replace(DROP, "").replace(/<br\s*\/?>/gi, "\n").replace(BLOCK_END, "\n").replace(/<[^>]*>/g, "");
return decodeEntities(flat).split("\n").map((l) => l.replace(/[ \t ]+/g, " ").trim()).filter(Boolean);
}
function idOf(attrs, inner) {
const own = ID_ATTR.exec(attrs);
if (own)
return own[1] || own[2] || own[3] || null;
const child = /<a\b([^>]*)>/i.exec(inner);
if (!child)
return null;
const m = ID_ATTR.exec(child[1]);
return m ? m[1] || m[2] || m[3] || null : null;
}
function headings(html) {
const out = [];
let m;
HEADING.lastIndex = 0;
while (m = HEADING.exec(html)) {
const title = inlineText(m[3]);
if (title)
out.push({ title, anchor: idOf(m[2], m[3]), from: m.index, to: HEADING.lastIndex });
}
return out;
}
async function readOutline(absPath) {
const html = read(absPath);
if (!html)
return [];
return headings(html).map((h, i) => ({ title: h.title, page: i + 1, anchor: h.anchor || void 0 }));
}
async function readSection(absPath, page) {
const html = read(absPath);
if (!html)
return null;
const hs = headings(html);
if (!hs.length)
return { title: "", body: blockLines(html).slice(0, MAX_LINES), page: 1, total: 1 };
const n = clampPage(page, hs.length);
const here = hs[n - 1];
const next = hs[n];
const end = next ? next.from : html.length;
const body = blockLines(html.slice(here.to, end));
return { title: here.title, body: body.slice(0, MAX_LINES), raw: html.slice(here.from, end), page: n, total: hs.length };
}
async function render(el, req) {
const sec = await readSection(req.abs, req.page);
if (!req.isCurrent() || !sec)
return false;
if (sec.raw) {
const done = renderHtml(el, { html: sec.raw.replace(DROP, ""), width: req.width, loadImage: assetLoader(req.abs) });
if (done !== false)
return done;
}
return renderLines(el, { title: sec.title, body: sec.body, width: req.width });
}
module2.exports = {
// EPUB content is XHTML, so it reads its chapters with these rather than its own copy.
blockLines,
inlineText,
decodeEntities,
assetLoader,
exts: ["html", "htm", "xhtml"],
anchorKind: "id",
anchorFor: (e) => e.kind === "section" && e.anchor ? e.anchor : null,
outline: readOutline,
render,
readOutline,
readSection
};
}
});
// src/formats/text.js
var require_text = __commonJS({
"src/formats/text.js"(exports2, module2) {
"use strict";
var fs2 = require("fs");
var { isFenceLine } = require_markdown();
var { renderLines, renderMarkdown } = require_preview();
var { assetLoader } = require_html();
var { clampPage } = require_util();
var ATX = /^(#{1,6})\s+(.+?)\s*#*\s*$/;
var SETEXT = /^(=+|-{2,})\s*$/;
var MAX_LINES = 60;
function readLines(absPath) {
try {
return fs2.readFileSync(absPath, "utf8").replace(/^/, "").split(/\r?\n/);
} catch (e) {
return null;
}
}
function headings(lines) {
const out = [];
let fenced = false;
for (let i = 0; i < lines.length; i++) {
const line = lines[i];
if (isFenceLine(line)) {
fenced = !fenced;
continue;
}
if (fenced)
continue;
const atx = ATX.exec(line);
if (atx) {
out.push({ title: atx[2].trim(), page: i + 1 });
continue;
}
if (SETEXT.test(line) && i > 0) {
const above = lines[i - 1].trim();
if (above && !ATX.test(above) && !isFenceLine(above))
out.push({ title: above, page: i });
}
}
return out;
}
async function readOutline(absPath) {
const lines = readLines(absPath);
return lines ? headings(lines) : [];
}
async function readSection(absPath, page) {
const lines = readLines(absPath);
if (!lines)
return null;
const hs = headings(lines);
if (!hs.length) {
return { title: "", body: lines.map((l) => l.trimEnd()).filter(Boolean).slice(0, MAX_LINES), page: 1, total: 1 };
}
const n = clampPage(page, lines.length);
let at = hs.findIndex((h) => h.page === n);
if (at < 0)
at = 0;
const here = hs[at];
const next = hs[at + 1];
const slice = lines.slice(here.page, next ? next.page - 1 : lines.length).map((l) => l.trimEnd());
const body = slice.filter(Boolean);
return {
title: here.title,
body: body.slice(0, MAX_LINES),
raw: slice.slice(0, MAX_LINES).join("\n"),
page: here.page,
total: hs.length
};
}
var MARKDOWN = /* @__PURE__ */ new Set(["md", "markdown"]);
async function render(el, req) {
const sec = await readSection(req.abs, req.page);
if (!req.isCurrent() || !sec)
return false;
if (MARKDOWN.has(req.ext) && sec.raw !== void 0) {
const md = (sec.title ? "## " + sec.title + "\n\n" : "") + sec.raw;
const done = await renderMarkdown(el, {
markdown: md,
width: req.width,
app: req.app,
component: req.component,
loadImage: assetLoader(req.abs)
});
if (done !== false)
return done;
}
return renderLines(el, { title: sec.title, body: sec.body, width: req.width });
}
module2.exports = {
exts: ["md", "markdown", "txt", "text", "log"],
anchorKind: null,
// no viewer honours a position in a text file
outline: readOutline,
render,
readOutline,
readSection
};
}
});
// src/formats/epub.js
var require_epub = __commonJS({
"src/formats/epub.js"(exports2, module2) {
"use strict";
var { openZip } = require_zip();
var { elements, attr, textIn } = require_xml();
var { blockLines, inlineText } = require_html();
var { renderLines, renderHtml } = require_preview();
var { clampPage, normPath, assetSrc } = require_util();
var MAX_LINES = 60;
var resolve = (base, href) => normPath((base ? base.split("/").slice(0, -1).join("/") + "/" : "") + href);
var dropFragment = (href) => String(href).split("#")[0];
function opfPath(zip) {
const container = zip.text("META-INF/container.xml");
if (!container)
return null;
for (const r of elements(container, "rootfile")) {
const p = attr(r, "full-path");
if (p && zip.has(p))
return p;
}
return null;
}
function readSpine(zip, opf, xml) {
const items = /* @__PURE__ */ new Map();
const manifest = elements(xml, "manifest")[0] || "";
for (const it of elements(manifest, "item")) {
const id = attr(it, "id");
const href = attr(it, "href");
if (id && href)
items.set(id, { path: resolve(opf, dropFragment(href)), props: attr(it, "properties") || "" });
}
const spine = elements(xml, "spine")[0] || "";
const order = [];
for (const ref of elements(spine, "itemref")) {
const hit = items.get(attr(ref, "idref"));
if (hit && zip.has(hit.path))
order.push(hit.path);
}
return { items, order, tocId: attr(spine, "toc") };
}
function readToc(zip, spine) {
const nav = [...spine.items.values()].find((i) => /\bnav\b/.test(i.props));
if (nav) {
const doc = zip.text(nav.path);
const out = doc ? tocFromNav(doc, nav.path) : [];
if (out.length)
return out;
}
const ncx = spine.tocId && spine.items.get(spine.tocId);
if (ncx) {
const doc = zip.text(ncx.path);
if (doc)
return tocFromNcx(doc, ncx.path);
}
return [];
}
function tocFromNav(doc, base) {
const toc = elements(doc, "nav").find((n) => /toc/i.test(attr(n, "epub:type") || attr(n, "type") || "")) || elements(doc, "nav")[0];
if (!toc)
return [];
const out = [];
for (const a of elements(toc, "a")) {
const href = attr(a, "href");
const title = inlineText(a);
if (href && title)
out.push({ title, path: resolve(base, dropFragment(href)) });
}
return out;
}
function tocFromNcx(doc, base) {
const out = [];
for (const p of elements(doc, "navPoint")) {
const title = textIn(elements(p, "navLabel")[0] || "", "text").replace(/\s+/g, " ").trim();
const content = elements(p, "content")[0];
const src = content && attr(content, "src");
if (title && src)
out.push({ title, path: resolve(base, dropFragment(src)) });
}
return out;
}
function open(absPath) {
const zip = openZip(absPath);
if (!zip)
return null;
const opf = opfPath(zip);
const xml = opf && zip.text(opf);
if (!xml)
return null;
const spine = readSpine(zip, opf, xml);
if (!spine.order.length)
return null;
return { zip, opf, spine };
}
async function readOutline(absPath) {
const doc = open(absPath);
if (!doc)
return [];
const at = new Map(doc.spine.order.map((p, i) => [p, i + 1]));
const out = [];
const seen = /* @__PURE__ */ new Set();
for (const entry of readToc(doc.zip, doc.spine)) {
const page = at.get(entry.path);
if (!page || seen.has(entry.title + "|" + page))
continue;
seen.add(entry.title + "|" + page);
out.push({ title: entry.title, page });
}
return out;
}
function chapterAt(doc, page) {
const n = clampPage(page, doc.spine.order.length);
const path = doc.spine.order[n - 1];
const xhtml = doc.zip.text(path);
if (!xhtml)
return null;
const body = elements(xhtml, "body")[0] || xhtml;
const lines = blockLines(body);
return { title: lines[0] || "", body: lines.slice(1, MAX_LINES), raw: body, path, page: n, total: doc.spine.order.length };
}
async function readChapter(absPath, page) {
const doc = open(absPath);
return doc ? chapterAt(doc, page) : null;
}
async function render(el, req) {
const doc = open(req.abs);
const ch = doc && chapterAt(doc, req.page);
if (!req.isCurrent() || !ch)
return false;
if (ch.raw) {
const done = renderHtml(el, { html: ch.raw, width: req.width, loadImage: imageLoader(doc, ch.path) });
if (done !== false)
return done;
}
return renderLines(el, { title: ch.title, body: ch.body, width: req.width });
}
var imageLoader = (doc, chapterPath) => (src) => doc.zip.read(resolve(chapterPath, assetSrc(src)));
module2.exports = {
exts: ["epub"],
anchorKind: null,
// an e-reader takes the file and ignores the fragment
outline: readOutline,
render,
readOutline,
readChapter,
open,
imageLoader
};
}
});
// src/formats/media.js
var require_media = __commonJS({
"src/formats/media.js"(exports2, module2) {
"use strict";
var fs2 = require("fs");
var nodePath2 = require("path");
var VIDEO = { mp4: "video/mp4", m4v: "video/mp4", webm: "video/webm", mkv: "video/x-matroska", mov: "video/quicktime", ogv: "video/ogg" };
var AUDIO = { mp3: "audio/mpeg", m4a: "audio/mp4", wav: "audio/wav", flac: "audio/flac", ogg: "audio/ogg", opus: "audio/ogg", aac: "audio/aac" };
var BLOB_LIMIT = 96 * 1024 * 1024;
var fileUrl = (abs) => "file:///" + encodeURI(abs.split(nodePath2.sep).join("/").replace(/^\/+/, "")).replace(/#/g, "%23").replace(/\?/g, "%3F");
function blobFallback(el, abs, ext) {
let size = 0;
try {
size = fs2.statSync(abs).size;
} catch (e) {
return "";
}
if (size > BLOB_LIMIT)
return "";
let buf;
try {
buf = fs2.readFileSync(abs);
} catch (e) {
return "";
}
const url = URL.createObjectURL(new Blob([buf], { type: VIDEO[ext] || AUDIO[ext] || "application/octet-stream" }));
el.src = url;
return url;
}
async function render(el, req) {
if (!fs2.existsSync(req.abs))
return false;
const isVideo = !!VIDEO[req.ext];
const media = el.createEl(isVideo ? "video" : "audio");
media.controls = true;
media.preload = "metadata";
media.style.width = req.width + "px";
media.style.maxWidth = "100%";
const at = Math.max(0, (req.page | 0) - (req.page > 1 ? 0 : 1));
const seek = () => {
try {
if (at > 0)
media.currentTime = at;
} catch (e) {
}
};
media.addEventListener("loadedmetadata", seek, { once: true });
let blobUrl = "";
let disposed = false;
const dispose = () => {
disposed = true;
if (blobUrl) {
try {
URL.revokeObjectURL(blobUrl);
} catch (e) {
}
blobUrl = "";
}
try {
media.removeAttribute("src");
} catch (e) {
}
};
media.addEventListener("error", () => {
if (disposed || blobUrl)
return;
blobUrl = blobFallback(media, req.abs, req.ext);
if (blobUrl)
media.addEventListener("loadedmetadata", seek, { once: true });
}, { once: true });
media.src = fileUrl(req.abs);
if (!req.isCurrent()) {
dispose();
return false;
}
return dispose;
}
function positionLabel(n) {
const s = Math.max(0, n | 0);
const mm = Math.floor(s / 60);
const ss = String(s % 60).padStart(2, "0");
return mm >= 60 ? Math.floor(mm / 60) + ":" + String(mm % 60).padStart(2, "0") + ":" + ss : mm + ":" + ss;
}
module2.exports = {
exts: [...Object.keys(VIDEO), ...Object.keys(AUDIO)],
anchorKind: null,
// no outline, so nothing writes an anchor; a hand-written #t= still previews
positionLabel,
render
};
}
});
// src/formats/odf.js
var require_odf = __commonJS({
"src/formats/odf.js"(exports2, module2) {
"use strict";
var { openZip } = require_zip();
var { elements, attr, decodeEntities } = require_xml();
var { renderLines } = require_preview();
var { clampPage } = require_util();
var MAX_LINES = 60;
function contentOf(absPath) {
const zip = openZip(absPath);
return zip ? zip.text("content.xml") : null;
}
var TEXT_BLOCK = /<text:(?:h|p)\b[^>]*>([\s\S]*?)<\/text:(?:h|p)>/g;
function textLines(xml) {
const out = [];
let m;
while (m = TEXT_BLOCK.exec(xml)) {
const line = decodeEntities(m[1].replace(/<text:tab\b[^>]*\/?>/g, " ").replace(/<text:s\b[^>]*\/?>/g, " ").replace(/<text:line-break\b[^>]*\/?>/g, " ").replace(/<[^>]*>/g, "")).replace(/\s+/g, " ").trim();
if (line)
out.push(line);
}
return out;
}
function odtOutline(xml) {
const out = [];
let m;
const re = /<text:h\b[^>]*>([\s\S]*?)<\/text:h>/g;
while (m = re.exec(xml)) {
const title = decodeEntities(m[1].replace(/<[^>]*>/g, "")).replace(/\s+/g, " ").trim();
if (title)
out.push({ title, page: out.length + 1 });
}
return out;
}
function slideText(page) {
let title = "";
const body = [];
for (const frame of elements(page, "draw:frame")) {
const lines = textLines(frame);
if (!title && (attr(frame, "presentation:class") || "") === "title")
title = lines.join(" ");
else
body.push(...lines);
}
if (!title)
title = body.shift() || "";
return { title, body };
}
function odpOutline(xml) {
const out = [];
for (const page of elements(xml, "draw:page")) {
const title = slideText(page).title;
if (title)
out.push({ title, page: out.length + 1 });
}
return out;
}
function odsOutline(xml) {
const out = [];
for (const table of elements(xml, "table:table")) {
const name = attr(table, "table:name");
if (name)
out.push({ title: name, page: out.length + 1 });
}
return out;
}
function outlineFor(ext, xml) {
if (ext === "odp")
return odpOutline(xml);
if (ext === "ods")
return odsOutline(xml);
return odtOutline(xml);
}
async function readOutline(absPath, ext) {
const xml = contentOf(absPath);
return xml ? outlineFor(ext, xml) : [];
}
async function readSection(absPath, ext, page) {
const xml = contentOf(absPath);
if (!xml)
return null;
if (ext === "odp") {
const pages = elements(xml, "draw:page");
if (!pages.length)
return null;
const n2 = clampPage(page, pages.length);
const { title, body: body2 } = slideText(pages[n2 - 1]);
return { title, body: body2.slice(0, MAX_LINES), page: n2, total: pages.length };
}
if (ext === "ods") {
const tables = elements(xml, "table:table");
if (!tables.length)
return null;
const n2 = clampPage(page, tables.length);
return { title: attr(tables[n2 - 1], "table:name") || "", body: textLines(tables[n2 - 1]).slice(0, MAX_LINES), page: n2, total: tables.length };
}
const hs = odtOutline(xml);
const lines = textLines(xml);
if (!hs.length)
return { title: "", body: lines.slice(0, MAX_LINES), page: 1, total: 1 };
const n = clampPage(page, hs.length);
const here = lines.indexOf(hs[n - 1].title);
const next = hs[n] ? lines.indexOf(hs[n].title, here + 1) : lines.length;
const body = lines.slice(here + 1, next < 0 ? lines.length : next);
return { title: hs[n - 1].title, body: body.slice(0, MAX_LINES), page: n, total: hs.length };
}
async function render(el, req) {
const sec = await readSection(req.abs, req.ext, req.page);
if (!req.isCurrent() || !sec)
return false;
return renderLines(el, { title: sec.title, body: sec.body, width: req.width });
}
module2.exports = {
exts: ["odt", "ods", "odp"],
anchorKind: null,
outline: (abs, ext) => readOutline(abs, ext),
render,
readOutline,
readSection
};
}
});
// src/formats/index.js
var require_formats = __commonJS({
"src/formats/index.js"(exports2, module2) {
"use strict";
var pdf = require_pdf2();
var image = require_image();
var pptx = require_pptx();
var html = require_html();
var text = require_text();
var epub = require_epub();
var media = require_media();
var odf = require_odf();
var HANDLERS = [pdf, image, pptx, html, text, epub, media, odf];
var byExt = /* @__PURE__ */ new Map();
for (const h of HANDLERS)
for (const e of h.exts)
byExt.set(e, h);
var handlerFor = (ext) => byExt.get(String(ext || "").toLowerCase()) || null;
var knownExtensions = () => [...byExt.keys()].map((e) => "." + e).sort();
var canOutline = (ext) => {
const h = handlerFor(ext);
return !!(h && h.outline);
};
var canPreview = (ext) => {
const h = handlerFor(ext);
return !!(h && h.render);
};
var anchorKind = (ext) => {
const h = handlerFor(ext);
return h && h.anchorKind || null;
};
var anchorFor = (entry) => {
const h = handlerFor(entry && entry.lang);
if (!h || !h.anchorFor)
return null;
return h.anchorFor(entry) || null;
};
var hasOsAnchor = (ext) => anchorKind(ext) !== null;
var positionLabel = (ext, n, to) => {
if (!(n > 1) && !(to > 1))
return null;
const h = handlerFor(ext);
return h && h.positionLabel ? h.positionLabel(n, to) : null;
};
async function outline(ext, absPath) {
const h = handlerFor(ext);
if (!h || !h.outline)
return [];
try {
return await h.outline(absPath, ext);
} catch (e) {
return [];
}
}
async function render(el, req) {
const h = handlerFor(req.ext);
if (!h || !h.render)
return false;
try {
return await h.render(el, req);
} catch (e) {
return false;
}
}
async function dispose() {
for (const h of HANDLERS) {
if (!h.dispose)
continue;
try {
await h.dispose();
} catch (e) {
}
}
}
module2.exports = {
handlerFor,
knownExtensions,
canOutline,
canPreview,
anchorKind,
anchorFor,
hasOsAnchor,
positionLabel,
outline,
render,
dispose
};
}
});
// src/suggest.js
var require_suggest2 = __commonJS({
"src/suggest.js"(exports2, module2) {
"use strict";
var nodePath2 = require("path");
var { createSigilSuggest } = require_suggest();
var formats2 = require_formats();
var baseName = (p) => nodePath2.basename(p).replace(/\.[^.]+$/, "");
var ReferenceSuggest2 = createSigilSuggest({
cls: "reference-linker",
kindText: (e) => {
if (e.kind !== "section")
return e.lang;
return formats2.anchorKind(e.lang) === "page" ? "p." + e.page : baseName(e.path);
}
});
module2.exports = { ReferenceSuggest: ReferenceSuggest2 };
}
});
// src/filter.js
var require_filter = __commonJS({
"src/filter.js"(exports2, module2) {
"use strict";
function parseQuery(raw, kinds, exts) {
const f = { kind: null, ext: null, name: "" };
const parts = String(raw == null ? "" : raw).split(":");
let i = 0;
for (; i < parts.length - 1; i++) {
const p = parts[i];
if (kinds && kinds.has(p))
f.kind = p;
else if (exts && exts.has(p))
f.ext = p;
else
break;
}
f.name = parts.slice(i).join(":");
return f;
}
module2.exports = { parseQuery };
}
});
// src/shared/popover.js
var require_popover = __commonJS({
"src/shared/popover.js"(exports2, module2) {
"use strict";
var SHOW_DELAY = 200;
var HIDE_GRACE = 250;
var EDGE_PAD = 12;
var Popover = class {
constructor(opts) {
this.cls = opts.cls;
this.hiddenCls = opts.hiddenCls;
this.showDelay = opts.showDelay == null ? SHOW_DELAY : opts.showDelay;
this.hideGrace = opts.hideGrace == null ? HIDE_GRACE : opts.hideGrace;
this.onHide = opts.onHide || null;
this.onDestroy = opts.onDestroy || null;
this.keepAlive = opts.keepAlive || null;
this.el = null;
this.timer = null;
this.hideTimer = null;
this.key = "";
this.pendingKey = "";
this.token = 0;
}
ensureEl() {
if (!this.el) {
this.el = document.body.createDiv({ cls: `${this.cls} ${this.hiddenCls}` });
this.el.addEventListener("mouseenter", () => this.cancelHide());
this.el.addEventListener("mouseleave", () => this.leave());
}
return this.el;
}
isVisible() {
return !!this.el && !this.el.classList.contains(this.hiddenCls);
}
contains(node) {
return !!this.el && !!node && this.el.contains(node);
}
cancelHide() {
clearTimeout(this.hideTimer);
this.hideTimer = null;
}
// Re-asking for what is already up, or already on its way, changes nothing — otherwise
// every mouse move would restart the timer.
schedule(key, x, y, build) {
this.cancelHide();
if (key === this.key && this.isVisible())
return;
if (key === this.pendingKey)
return;
this.pendingKey = key;
clearTimeout(this.timer);
this.timer = setTimeout(() => {
this.pendingKey = "";
this.show(key, x, y, build);
}, this.showDelay);
}
leave() {
if (this.hideTimer)
return;
this.hideTimer = setTimeout(() => {
this.hideTimer = null;
if (this.keepAlive && this.keepAlive()) {
this.leave();
return;
}
this.hide();
}, this.hideGrace);
}
async show(key, x, y, build) {
const token = ++this.token;
const ctx = { isCurrent: () => token === this.token };
const el = this.ensureEl();
el.empty();
const after = await build(el, ctx);
if (after === false || !ctx.isCurrent())
return;
this.key = key;
el.style.visibility = "hidden";
el.style.left = "-9999px";
el.style.top = "0px";
el.removeClass(this.hiddenCls);
if (typeof after === "function")
after();
const r = el.getBoundingClientRect();
let left = x + EDGE_PAD;
let top = y + EDGE_PAD;
if (left + r.width > window.innerWidth - EDGE_PAD)
left = Math.max(EDGE_PAD, x - EDGE_PAD - r.width);
if (top + r.height > window.innerHeight - EDGE_PAD)
top = Math.max(EDGE_PAD, y - EDGE_PAD - r.height);
el.style.left = left + "px";
el.style.top = top + "px";
el.style.visibility = "visible";
}
hide() {
clearTimeout(this.timer);
clearTimeout(this.hideTimer);
this.hideTimer = null;
this.pendingKey = "";
this.key = "";
this.token++;
if (this.onHide)
this.onHide();
if (this.el) {
this.el.addClass(this.hiddenCls);
this.el.empty();
}
}
destroy() {
clearTimeout(this.timer);
clearTimeout(this.hideTimer);
this.token++;
if (this.onDestroy)
this.onDestroy();
if (this.el) {
this.el.remove();
this.el = null;
}
}
};
module2.exports = { Popover, SHOW_DELAY, HIDE_GRACE };
}
});
// src/hover.js
var require_hover = __commonJS({
"src/hover.js"(exports2, module2) {
"use strict";
var nodePath2 = require("path");
var formats2 = require_formats();
var { Popover } = require_popover();
var PREVIEW_WIDTH = 420;
var keyOf = (e) => e.path + ":" + (e.page || e.line || 1);
var HoverPreview2 = class {
constructor(plugin) {
this.plugin = plugin;
this.cleanup = null;
this.pop = new Popover({
cls: "reference-linker-hover",
hiddenCls: "reference-linker-hidden",
onHide: () => this.release(),
onDestroy: () => this.release()
});
}
// Read from onHoverMove to tell "nothing scheduled" from "waiting to show".
get pendingKey() {
return this.pop.pendingKey;
}
isVisible() {
return this.pop.isVisible();
}
contains(node) {
return this.pop.contains(node);
}
cancelHide() {
this.pop.cancelHide();
}
leave() {
this.pop.leave();
}
hide() {
this.pop.hide();
}
destroy() {
this.pop.destroy();
}
// Skip types no handler can draw, so nothing schedules for them.
previewable(entry) {
return formats2.canPreview(entry.lang);
}
schedule(entry, x, y) {
this.pop.cancelHide();
if (!this.previewable(entry))
return;
this.pop.schedule(keyOf(entry), x, y, (el, ctx) => this.build(entry, el, ctx));
}
async build(entry, el, ctx) {
const root = this.plugin.codeRoot();
const abs = root ? nodePath2.join(root, entry.path) : entry.path;
const ext = (entry.lang || "").toLowerCase();
const page = entry.page || 1;
const label = entry.title || entry.name;
const pos = formats2.positionLabel(ext, page);
el.createDiv({ cls: "reference-linker-hover-header", text: pos ? label + " \xB7 " + pos : label });
const body = el.createDiv({ cls: "reference-linker-hover-body" });
this.release();
const cleanup = await formats2.render(body, {
abs,
ext,
page,
width: PREVIEW_WIDTH,
app: this.plugin.app,
component: this.plugin,
isCurrent: () => ctx.isCurrent()
});
if (cleanup === false)
return false;
this.cleanup = cleanup || null;
return void 0;
}
release() {
if (this.cleanup) {
try {
this.cleanup();
} catch (e) {
}
this.cleanup = null;
}
}
};
module2.exports = { HoverPreview: HoverPreview2 };
}
});
// src/embed.js
var require_embed = __commonJS({
"src/embed.js"(exports2, module2) {
"use strict";
var { MarkdownRenderChild, Menu } = require("obsidian");
var nodePath2 = require("path");
var formats2 = require_formats();
var { t: t2 } = require_i18n();
var EMBED_LANG = "reference-link";
var DEFAULT_WIDTH = 600;
var MAX_RANGE = 20;
var baseName = (p) => nodePath2.basename(p).replace(/\.[^.]+$/, "");
var looksLikePath = (s) => s.includes("/") || s.includes("\\") || /\.[a-z0-9]+$/i.test(s);
function parseSpan(s) {
const m = /^(\d+)\s*[-]\s*(\d+)$/.exec(s) || /^(\d+)$/.exec(s);
if (!m)
return null;
const from = parseInt(m[1], 10);
return { from, to: Math.max(from, m[2] ? parseInt(m[2], 10) : from) };
}
function parseSpec(source) {
const spec = { target: "", page: "", width: "", title: "" };
for (const raw of source.split("\n")) {
const line = raw.trim();
if (!line)
continue;
const m = /^(page|width|title)\s*:\s*(.*)$/i.exec(line);
if (m)
spec[m[1].toLowerCase()] = m[2].trim();
else if (!spec.target)
spec.target = line;
}
return spec;
}
function splitTarget2(target) {
const h = target.indexOf("#");
if (h >= 0)
return { path: target.slice(0, h), frag: target.slice(h + 1).trim() };
const m = /^(.+?):(\d+(?:-\d+)?)\s*$/.exec(target);
if (m)
return { path: m[1], frag: "page=" + m[2] };
return { path: target, frag: "" };
}
function resolve(plugin, spec) {
const target = spec.target;
if (!target)
return { error: t2("embed.empty") };
const { path: rawPath, frag } = splitTarget2(target);
let relPath, name = null, page = null, to = null, anchor = null;
const byPath = looksLikePath(rawPath);
if (byPath) {
const norm = rawPath.split("\\").join("/").replace(/^\.?\//, "");
const hit = plugin.lookup(norm)[0];
relPath = hit ? hit.path : norm;
const pm = /^(?:page|t)=(\d+(?:[-]\d+)?)$/i.exec(frag);
if (pm) {
const sp = parseSpan(pm[1]);
page = sp.from;
to = sp.to;
} else if (frag)
anchor = frag;
} else {
const f = plugin.parseQuery(target);
const matches = plugin.entriesByName(f.name).filter((m) => plugin.entryPassesFilter(m, f));
if (!matches.length)
return { error: t2("embed.notFound", { query: target }) };
const paths = new Set(matches.map((m) => m.path));
if (paths.size > 1)
return { error: t2("embed.ambiguous", { n: paths.size, query: target }) };
const e = matches.find((m) => m.kind === "section") || matches[0];
relPath = e.path;
name = e.name;
page = e.page;
}
const ext = nodePath2.extname(relPath).slice(1).toLowerCase();
if (anchor) {
const sec = plugin.entriesIn(relPath).find((x) => x.kind === "section" && x.anchor === anchor);
if (!sec)
return { error: t2("embed.notFound", { query: target }) };
page = sec.page;
name = sec.name;
}
const span = parseSpan(spec.page);
if (span) {
page = span.from;
to = span.to;
}
page = page || 1;
to = to && to >= page ? to : page;
if (!formats2.canOutline(ext))
to = page;
to = Math.min(to, page + MAX_RANGE - 1);
if (name === null) {
const sec = page === to && plugin.entriesIn(relPath).find((x) => x.kind === "section" && x.page === page);
name = sec ? sec.name : baseName(relPath);
}
const root = plugin.codeRoot();
const absPath = root ? nodePath2.join(root, relPath) : relPath;
const kind = page > 1 || to > page ? "section" : "file";
return { absPath, relPath, ext, page, to, name, entry: { name, kind, path: relPath, line: page, page } };
}
var ReferenceEmbed = class extends MarkdownRenderChild {
constructor(containerEl, plugin, spec) {
super(containerEl);
this.plugin = plugin;
this.spec = spec;
this.renderId = 0;
this.cleanup = null;
}
onload() {
this.containerEl.addEventListener("contextmenu", (evt) => this.onContextMenu(evt));
this.render();
this.unsub = this.plugin.onIndexChange(() => this.render());
}
onunload() {
if (this.unsub)
this.unsub();
this.release();
}
// Open the embedded document at its page — the same path the open/insert commands use.
open() {
const e = this.res && this.res.entry;
if (!e)
return;
this.plugin.withFormat(this.plugin.settings.askOnInsert, (tpl) => this.plugin.openEntry(e, tpl));
}
onContextMenu(evt) {
if (!this.res)
return;
evt.preventDefault();
evt.stopPropagation();
const menu = new Menu();
if (this.res.entry)
menu.addItem((i) => i.setTitle(t2("embed.menu.open")).setIcon("go-to-file").onClick(() => this.open()));
menu.addItem((i) => i.setTitle(t2("embed.menu.refresh")).setIcon("refresh-cw").onClick(() => this.render(true)));
menu.showAtMouseEvent(evt);
}
notice(cls, text) {
this.containerEl.empty();
this.containerEl.createDiv({ cls, text });
}
release() {
if (this.cleanup) {
try {
this.cleanup();
} catch (e) {
}
this.cleanup = null;
}
}
width() {
const n = parseInt(this.spec.width, 10);
return Number.isFinite(n) && n > 0 ? n : DEFAULT_WIDTH;
}
async render(force) {
const token = ++this.renderId;
const res = resolve(this.plugin, this.spec);
this.res = res;
const cached = res.relPath && this.plugin.fileCache.get(res.relPath);
const mtime = cached ? cached.mtimeMs : null;
const sig = res.error ? "err:" + res.error : res.absPath + "|" + res.page + "-" + res.to + "|" + mtime + "|" + this.width();
if (!force && sig === this.lastSig && (res.error || mtime != null))
return;
this.lastSig = sig;
if (res.error) {
this.notice("reference-linker-embed-error", res.error);
return;
}
const el = this.containerEl;
el.empty();
el.addClass("reference-linker-embed");
const header = el.createDiv({ cls: "reference-linker-embed-header mod-clickable" });
const pos = formats2.positionLabel(res.ext, res.page, res.to);
header.createSpan({ text: this.spec.title || res.name + (pos ? " \xB7 " + pos : "") });
header.addEventListener("click", () => this.open());
const body = el.createDiv({ cls: "reference-linker-embed-body" });
if (!formats2.canPreview(res.ext)) {
this.notice("reference-linker-embed-error", t2("embed.unsupported", { path: res.relPath }));
this.lastSig = null;
return;
}
this.release();
const cleanups = [];
let drew = false;
for (let p = res.page; p <= res.to; p++) {
const slot = res.to > res.page ? body.createDiv({ cls: "reference-linker-embed-slot" }) : body;
const cleanup = await formats2.render(slot, {
abs: res.absPath,
ext: res.ext,
page: p,
width: this.width(),
app: this.plugin.app,
component: this,
isCurrent: () => token === this.renderId
});
if (token !== this.renderId) {
if (typeof cleanup === "function") {
try {
cleanup();
} catch (e) {
}
}
cleanups.forEach((c) => {
try {
c();
} catch (e) {
}
});
return;
}
if (cleanup !== false)
drew = true;
if (typeof cleanup === "function")
cleanups.push(cleanup);
}
if (!drew) {
this.fail(res);
return;
}
this.cleanup = cleanups.length ? () => cleanups.forEach((c) => {
try {
c();
} catch (e) {
}
}) : null;
}
fail(res) {
this.notice("reference-linker-embed-error", t2("embed.unreadable", { path: res.relPath }));
this.lastSig = null;
}
};
function registerEmbed2(plugin) {
plugin.registerMarkdownCodeBlockProcessor(EMBED_LANG, (source, el, ctx) => {
ctx.addChild(new ReferenceEmbed(el, plugin, parseSpec(source)));
});
}
module2.exports = { registerEmbed: registerEmbed2, resolve, splitTarget: splitTarget2, parseSpan, parseSpec };
}
});
// src/shared/actualize.js
var require_actualize = __commonJS({
"src/shared/actualize.js"(exports2, module2) {
"use strict";
var { Notice: Notice2, MarkdownView: MarkdownView2 } = require("obsidian");
var { ViewPlugin, Decoration } = require("@codemirror/view");
var { RangeSetBuilder, StateEffect } = require("@codemirror/state");
var { syntaxTree } = require("@codemirror/language");
var { linkRegex: linkRegex2 } = require_markdown();
var { t: t2 } = require_i18n();
var SKIP_NODE = /code|comment|frontmatter/i;
var refreshEffect = StateEffect.define();
function refreshStaleLinks(app) {
app.workspace.iterateAllLeaves((leaf) => {
const cm = leaf.view && leaf.view.editor && leaf.view.editor.cm;
if (cm)
cm.dispatch({ effects: refreshEffect.of(null) });
});
}
function staleLinksExtension(plugin, classes) {
const marks = {
stale: Decoration.mark({ class: classes.stale }),
broken: Decoration.mark({ class: classes.broken })
};
const build = (view) => {
const builder = new RangeSetBuilder();
if (plugin.settings.markStaleLinks) {
const tree = syntaxTree(view.state);
for (const { from, to } of view.visibleRanges) {
const text = view.state.doc.sliceString(from, to);
const re = linkRegex2();
let m;
while (m = re.exec(text)) {
const start = from + m.index;
const end = start + m[0].length;
let inCodeNode = false;
tree.iterate({ from: start, to: end, enter: (n) => {
if (SKIP_NODE.test(n.type.name))
inCodeNode = true;
} });
const state = inCodeNode ? null : plugin.linkState(m[2]);
if (state)
builder.add(start, end, marks[state]);
}
}
}
return builder.finish();
};
return ViewPlugin.fromClass(
class {
constructor(view) {
this.decorations = build(view);
}
update(u) {
const refresh = u.transactions.some((tr) => tr.effects.some((e) => e.is(refreshEffect)));
if (u.docChanged || u.viewportChanged || refresh)
this.decorations = build(u.view);
}
},
{ decorations: (v) => v.decorations }
);
}
async function rewriteActiveNote(plugin, transform, noticeKey) {
const view = plugin.app.workspace.getActiveViewOfType(MarkdownView2);
const editor = view && view.editor;
if (editor) {
const { text: text2, count: count2 } = transform(plugin, editor.getValue());
if (count2) {
const cur = editor.getCursor();
editor.setValue(text2);
editor.setCursor(cur);
}
new Notice2(t2(noticeKey, { n: count2 }));
return;
}
const file = plugin.app.workspace.getActiveFile();
if (!file) {
new Notice2(t2(noticeKey, { n: 0 }));
return;
}
const { text, count } = transform(plugin, await plugin.app.vault.read(file));
if (count)
await plugin.app.vault.modify(file, text);
new Notice2(t2(noticeKey, { n: count }));
}
async function rewriteVault(plugin, transform, noticeKey) {
let files = 0, total = 0;
for (const f of plugin.app.vault.getMarkdownFiles()) {
const { text, count } = transform(plugin, await plugin.app.vault.read(f));
if (count) {
await plugin.app.vault.modify(f, text);
files++;
total += count;
}
}
new Notice2(t2(noticeKey, { n: total, files }));
}
module2.exports = { SKIP_NODE, refreshEffect, refreshStaleLinks, staleLinksExtension, rewriteActiveNote, rewriteVault };
}
});
// src/shared/update-preview.js
var require_update_preview = __commonJS({
"src/shared/update-preview.js"(exports2, module2) {
"use strict";
var { Notice: Notice2, Modal, MarkdownView: MarkdownView2 } = require("obsidian");
var { t: t2 } = require_i18n();
var MAX_ROWS = 50;
var UpdatePreviewModal = class extends Modal {
constructor(app, entries, onApply, prefix) {
super(app);
this.entries = entries;
this.onApply = onApply;
this.prefix = prefix;
for (const e of entries)
for (const c of e.changes)
c.selected = true;
}
cls(suffix) {
return suffix ? this.prefix + "-" + suffix : this.prefix;
}
onOpen() {
const { contentEl } = this;
contentEl.addClass(this.cls());
contentEl.createEl("h3", { text: t2("modal.update.title") });
const changed = this.entries.filter((e) => e.changes.length);
const total = changed.reduce((n, e) => n + e.changes.length, 0);
const brokenTotal = this.entries.reduce((n, e) => n + e.broken.length, 0);
if (!total && !brokenTotal) {
contentEl.createEl("p", { cls: this.cls("empty"), text: t2("modal.update.upToDate") });
} else {
if (total)
contentEl.createEl("p", { text: t2("modal.update.summary", { links: total, files: changed.length }) });
if (brokenTotal)
contentEl.createEl("p", { cls: this.cls("attention"), text: t2("modal.update.attention", { n: brokenTotal }) });
this.entries.forEach((e) => this.renderEntry(contentEl, e));
}
const bar = contentEl.createDiv({ cls: this.cls("buttons") });
if (total) {
bar.createEl("button", { text: t2("btn.apply"), cls: "mod-cta" }).onclick = async () => {
this.close();
await this.onApply(this.entries);
};
bar.createEl("button", { text: t2("btn.cancel") }).onclick = () => this.close();
} else {
bar.createEl("button", { text: t2("btn.close"), cls: "mod-cta" }).onclick = () => this.close();
}
}
renderEntry(contentEl, e) {
if (!e.changes.length && !e.broken.length)
return;
const head = contentEl.createDiv({ cls: this.cls("file") });
if (e.changes.length) {
const rowBoxes = [];
const label = head.createEl("label", { cls: this.cls("check") });
const master = label.createEl("input", { type: "checkbox" });
master.checked = true;
master.onchange = () => {
e.changes.forEach((c, i) => {
c.selected = master.checked;
if (rowBoxes[i])
rowBoxes[i].checked = master.checked;
});
master.indeterminate = false;
};
label.createSpan({ text: e.label });
const syncMaster = () => {
const on = e.changes.filter((c) => c.selected).length;
master.checked = on > 0;
master.indeterminate = on > 0 && on < e.changes.length;
};
const table = contentEl.createEl("table", { cls: this.cls("table") });
e.changes.slice(0, MAX_ROWS).forEach((c) => {
const tr = table.createEl("tr");
const cb = tr.createEl("td", { cls: this.cls("pick") }).createEl("input", { type: "checkbox" });
cb.checked = c.selected;
cb.onchange = () => {
c.selected = cb.checked;
syncMaster();
};
rowBoxes.push(cb);
tr.createEl("td", { text: c.label });
if (c.toPath) {
tr.addClass(this.cls("moved"));
tr.createEl("td", { cls: this.cls("move"), text: c.fromPath + ":" + c.from + " \u2192 " + c.toPath + ":" + c.to });
} else {
tr.createEl("td", { cls: this.cls("move"), text: c.from + " \u2192 " + c.to });
}
});
if (e.changes.length > MAX_ROWS)
contentEl.createEl("div", { cls: this.cls("more"), text: t2("modal.andMore", { n: e.changes.length - MAX_ROWS }) });
} else {
head.setText(e.label);
}
e.broken.forEach((label) => contentEl.createDiv({ cls: this.cls("broken"), text: t2("modal.update.brokenRow", { label }) }));
}
onClose() {
this.contentEl.empty();
}
};
async function applyUpdates(plugin, entries, rewrite) {
let files = 0, total = 0, skipped = 0;
for (const e of entries) {
const keys = new Set(e.changes.filter((c) => c.selected).map((c) => c.key));
if (!keys.size)
continue;
if (e.editor) {
if (e.editor.getValue() !== e.original) {
skipped++;
continue;
}
const { newText, count } = rewrite(plugin, e.original, keys);
const cur = e.editor.getCursor();
e.editor.setValue(newText);
e.editor.setCursor(cur);
files++;
total += count;
} else {
let count = 0;
await plugin.app.vault.process(e.file, (data) => {
if (data !== e.original)
return data;
const out = rewrite(plugin, data, keys);
count = out.count;
return out.newText;
});
if (count) {
files++;
total += count;
} else
skipped++;
}
}
let msg = t2("notice.linksUpdatedVault", { n: total, files });
if (skipped)
msg += " " + t2("notice.updateSkipped", { n: skipped });
new Notice2(msg);
}
function openUpdatePreview(plugin, entries, rewrite, prefix) {
new UpdatePreviewModal(plugin.app, entries, (chosen) => applyUpdates(plugin, chosen, rewrite), prefix).open();
}
async function updateInActiveNote(plugin, rewrite, prefix) {
const view = plugin.app.workspace.getActiveViewOfType(MarkdownView2);
const editor = view && view.editor;
const file = plugin.app.workspace.getActiveFile();
if (editor) {
const original2 = editor.getValue();
const c2 = rewrite(plugin, original2, null);
openUpdatePreview(plugin, [{ editor, label: file && file.path || t2("label.thisNote"), original: original2, changes: c2.changes, broken: c2.broken }], rewrite, prefix);
return;
}
if (!file) {
new Notice2(t2("notice.linksUpdated", { n: 0 }));
return;
}
const original = await plugin.app.vault.read(file);
const c = rewrite(plugin, original, null);
openUpdatePreview(plugin, [{ file, label: file.path, original, changes: c.changes, broken: c.broken }], rewrite, prefix);
}
async function updateInVault(plugin, rewrite, prefix) {
const entries = [];
for (const f of plugin.app.vault.getMarkdownFiles()) {
const original = await plugin.app.vault.read(f);
const c = rewrite(plugin, original, null);
if (c.changes.length || c.broken.length)
entries.push({ file: f, label: f.path, original, changes: c.changes, broken: c.broken });
}
openUpdatePreview(plugin, entries, rewrite, prefix);
}
module2.exports = { UpdatePreviewModal, applyUpdates, openUpdatePreview, updateInActiveNote, updateInVault };
}
});
// src/actualize.js
var require_actualize2 = __commonJS({
"src/actualize.js"(exports2, module2) {
"use strict";
var { splitTarget: splitTarget2, withTitle: withTitle2, rewriteLinks } = require_markdown();
var { parseBinding: parseBinding2, formatBinding: formatBinding2, ownsBinding: ownsBinding2 } = require_binding();
var shared = require_actualize();
var preview = require_update_preview();
var OWNER2 = "reference";
var PREVIEW_CLASS = "reference-linker-preview";
var POS_RE = /([#?&])(page|t)=\d+/i;
var withAnchor = (url, r) => {
if (r.anchor != null)
return url.replace(/#.*$/, "") + "#" + r.anchor;
return POS_RE.test(url) ? url.replace(POS_RE, (_, sep, key) => sep + key + "=" + r.line) : url + "#page=" + r.line;
};
var movedFrom = (plugin, url, r) => r.anchor != null ? plugin.targetAnchor(url) || "\u2014" : String(plugin.targetPage(url));
var movedTo = (r) => r.anchor != null ? r.anchor : String(r.line);
var rewriteUpdates = (plugin, text, selected) => {
const collect = selected == null;
const changes = [];
const broken = [];
let key = 0;
const links = rewriteLinks(text, (name, target) => {
const r = bindStateOf(plugin, target);
if (r && r.state === "stale") {
const k = key++;
const { url, title } = splitTarget2(target);
if (collect)
changes.push({ key: k, label: name, from: movedFrom(plugin, url, r), to: movedTo(r) });
if (!collect && !selected.has(k))
return null;
return "[" + name + "](" + withTitle2(withAnchor(url, r), title) + ")";
}
if (collect && r && r.state === "broken")
broken.push(name);
return null;
});
return { newText: links.text, count: links.count, changes, broken };
};
var pinLinksInText = (plugin, text) => rewriteLinks(text, (name, target) => {
const { url, title } = splitTarget2(target);
if (title)
return null;
const sec = plugin.sectionAtLinkPage(url);
return sec ? "[" + name + "](" + withTitle2(url, formatBinding2({ sec: sec.name })) + ")" : null;
});
var { refreshStaleLinks } = shared;
var staleLinksExtension = (plugin) => shared.staleLinksExtension(plugin, { stale: "reference-linker-stale", broken: "reference-linker-broken" });
function bindStateOf(plugin, target) {
const { url, title } = splitTarget2(target);
if (!url || !/^file:\/\//i.test(url))
return null;
const b = ownsBinding2(title, OWNER2) ? parseBinding2(title) : null;
return b ? plugin.urlBindState(url, b, plugin.targetPage(url)) : null;
}
var methods = {
linkState(target) {
const r = bindStateOf(this, target);
return r ? r.state : null;
},
isLinkStale(target) {
return this.linkState(target) === "stale";
},
// The link with its position corrected, or null when there's nothing to fix. The binding
// rides along.
actualizedTarget(target) {
const r = bindStateOf(this, target);
if (!r || r.state !== "stale")
return null;
const { url, title } = splitTarget2(target);
return withTitle2(withAnchor(url, r), title);
},
rewriteActiveNote(transform, noticeKey) {
return shared.rewriteActiveNote(this, transform, noticeKey);
},
rewriteVault(transform, noticeKey) {
return shared.rewriteVault(this, transform, noticeKey);
},
updateLinksInActiveNote() {
return preview.updateInActiveNote(this, rewriteUpdates, PREVIEW_CLASS);
},
updateLinksInVault() {
return preview.updateInVault(this, rewriteUpdates, PREVIEW_CLASS);
},
pinLinksInActiveNote() {
return this.rewriteActiveNote(pinLinksInText, "notice.linksPinned");
},
pinLinksInVault() {
return this.rewriteVault(pinLinksInText, "notice.linksPinnedVault");
}
};
module2.exports = { methods, staleLinksExtension, refreshStaleLinks };
}
});
// src/modal.js
var require_modal = __commonJS({
"src/modal.js"(exports2, module2) {
"use strict";
var { FuzzySuggestModal } = require("obsidian");
var { t: t2 } = require_i18n();
var ReferenceLinkModal2 = class extends FuzzySuggestModal {
constructor(app, plugin, opts) {
super(app);
this.plugin = plugin;
this.onChoose = opts && opts.onChoose || (() => {
});
this.initialQuery = opts && opts.query || "";
this.setPlaceholder(t2("modal.searchPlaceholder"));
}
onOpen() {
super.onOpen();
if (this.initialQuery) {
this.inputEl.value = this.initialQuery;
this.inputEl.dispatchEvent(new Event("input"));
}
}
getItems() {
return this.plugin.index;
}
// Path keeps same-named entries distinct in the modal's own fuzzy search.
getItemText(e) {
return `${e.name} ${e.lang} ${e.path}`;
}
onChooseItem(e) {
this.onChoose(e);
}
};
var PresetPickerModal2 = class extends FuzzySuggestModal {
constructor(app, items, onChoose, placeholder) {
super(app);
this.items = items;
this.onChoose = onChoose;
if (placeholder)
this.setPlaceholder(placeholder);
}
getItems() {
return this.items;
}
getItemText(p) {
return p.label;
}
onChooseItem(p) {
this.onChoose(p);
}
};
module2.exports = { ReferenceLinkModal: ReferenceLinkModal2, PresetPickerModal: PresetPickerModal2 };
}
});
// src/shared/deeplink/folder-suggest.js
var require_folder_suggest = __commonJS({
"src/shared/deeplink/folder-suggest.js"(exports2, module2) {
"use strict";
var obsidian = require("obsidian");
var fs2 = require("fs");
var nodePath2 = require("path");
var { AbstractInputSuggest } = obsidian;
var FolderSuggest = class extends AbstractInputSuggest {
constructor(app, inputEl, getRoot, onSelect, getSeed) {
super(app, inputEl);
this.inputEl = inputEl;
this.getRoot = getRoot;
this.onSelect = onSelect;
this.getSeed = getSeed;
}
// Immediate subdirectory names of an absolute dir, or [] if it can't be read.
subdirs(dir) {
try {
return fs2.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name);
} catch (e) {
return [];
}
}
getSuggestions(query) {
const base = this.getRoot ? this.getRoot() : "";
const q = query.replace(/\\/g, "/");
const slash = q.lastIndexOf("/");
const partial = (slash === -1 ? q : q.slice(slash + 1)).toLowerCase();
const head = slash === -1 ? "" : q.slice(0, slash);
let scanDir, prefix;
if (base) {
scanDir = nodePath2.join(base, head);
prefix = head;
} else if (slash === -1) {
scanDir = this.getSeed ? this.getSeed() : "";
prefix = scanDir;
} else {
scanDir = head.endsWith(":") ? head + "/" : head;
prefix = head;
}
if (!scanDir)
return [];
const stem = prefix.replace(/\/+$/, "");
return this.subdirs(scanDir).filter((name) => name.toLowerCase().includes(partial)).map((name) => stem ? stem + "/" + name : name).sort().slice(0, 50);
}
renderSuggestion(path, el) {
el.setText(path);
}
selectSuggestion(path) {
if (this.onSelect) {
this.onSelect(path);
this.setValue("");
this.close();
return;
}
this.setValue(path);
this.inputEl.trigger("input");
this.close();
}
};
var folderSuggestAvailable = () => typeof AbstractInputSuggest === "function";
module2.exports = { FolderSuggest, folderSuggestAvailable };
}
});
// src/shared/folder-list.js
var require_folder_list = __commonJS({
"src/shared/folder-list.js"(exports2, module2) {
"use strict";
var { Setting, setIcon } = require("obsidian");
function renderFolderList(containerEl, opts) {
const cls = opts.cls;
const norm = opts.normalize || ((x) => x.trim());
const read = () => (opts.get() || "").split("\n").map((x) => x.trim()).filter(Boolean);
new Setting(containerEl).setName(opts.name).setDesc(opts.desc);
const rowsEl = containerEl.createDiv({ cls: `${cls}-folder-rows` });
const addEl = containerEl.createDiv({ cls: `${cls}-folder-add` });
const commit = async (next) => {
const seen = /* @__PURE__ */ new Set();
const clean = [];
for (const p of next) {
const n = norm(p);
if (n && !seen.has(n)) {
seen.add(n);
clean.push(n);
}
}
await opts.set(clean.join("\n"));
draw();
};
const draw = () => {
rowsEl.empty();
read().forEach((path, i) => {
const row = new Setting(rowsEl).setName(path);
row.settingEl.addClass(`${cls}-folder-row`);
row.addExtraButton((b) => b.setIcon("x").setTooltip(opts.removeLabel || "").onClick(() => {
const next = read();
next.splice(i, 1);
commit(next);
}));
});
};
const input = addEl.createEl("input", { type: "text", cls: `${cls}-folder-input`, attr: { placeholder: opts.placeholder || "" } });
const addBtn = addEl.createEl("button", { cls: `${cls}-folder-addbtn`, attr: { "aria-label": opts.addLabel || "" } });
setIcon(addBtn, "plus");
const add = (raw) => {
if (norm(raw))
commit([...read(), raw]);
input.value = "";
input.focus();
};
if (opts.attachSuggest)
opts.attachSuggest(input, add);
addBtn.addEventListener("click", () => add(input.value));
input.addEventListener("keydown", (e) => {
if (e.key === "Enter") {
e.preventDefault();
add(input.value);
}
});
draw();
}
module2.exports = { renderFolderList };
}
});
// src/shared/precedence.js
var require_precedence = __commonJS({
"src/shared/precedence.js"(exports2, module2) {
"use strict";
var { discoverLinkers, outranks, siblingLinkers } = require_discover();
var { t: t2 } = require_i18n();
var STEP = 10;
function rankedLinkers(app) {
return discoverLinkers(app).slice().sort((a, b) => {
if (outranks(a, b))
return -1;
if (outranks(b, a))
return 1;
return 0;
});
}
function indexForPrecedence(others, self, value) {
const hypothetical = { precedence: value, id: self.id };
return others.filter((o) => outranks(o, hypothetical)).length;
}
function precedenceForIndex(app, self, index) {
const others = rankedLinkers(app).filter((p) => p.id !== self.id);
if (!others.length)
return self.precedence || 0;
const at = Math.max(0, Math.min(index, others.length));
const values = others.map((p) => p.precedence || 0);
const candidates = [values[0] + STEP, values[values.length - 1] - STEP];
for (let i = 1; i < values.length; i++) {
if (values[i - 1] !== values[i])
candidates.push((values[i - 1] + values[i]) / 2);
}
for (const v of values)
candidates.push(v);
const from = currentIndex(app, self);
const wanted = Math.sign(at - from);
let best = null;
let bestLanded = null;
for (const v of candidates) {
const landed = indexForPrecedence(others, self, v);
if (landed === at)
return v;
if (Math.sign(landed - from) !== wanted)
continue;
if (best === null || Math.abs(landed - at) < Math.abs(bestLanded - at)) {
best = v;
bestLanded = landed;
}
}
return best === null ? self.precedence || 0 : best;
}
function currentIndex(app, self) {
return rankedLinkers(app).findIndex((p) => p.id === self.id);
}
function renderPrecedence(containerEl, opts) {
const { app, provider, Setting, name, desc, save } = opts;
if (!provider || !siblingLinkers(app, provider).length)
return;
new Setting(containerEl).setName(name).setDesc(desc);
const cls = opts.cls || "linker";
const list = containerEl.createDiv({ cls: `${cls}-precedence-list` });
const draw = () => {
list.empty();
const ranked = rankedLinkers(app);
ranked.forEach((p, i) => {
const mine = p.id === provider.id;
const row = new Setting(list).setName(`${i + 1}. ${p.displayName || p.id}`);
if (!mine) {
row.setDesc(opts.otherDesc || "");
return;
}
row.settingEl.addClass(`${cls}-precedence-self`);
row.addExtraButton((b) => b.setIcon("arrow-up").setTooltip(opts.upTooltip || "").setDisabled(i === 0).onClick(async () => {
await save(precedenceForIndex(app, provider, i - 1));
refresh();
}));
row.addExtraButton((b) => b.setIcon("arrow-down").setTooltip(opts.downTooltip || "").setDisabled(i === ranked.length - 1).onClick(async () => {
await save(precedenceForIndex(app, provider, i + 1));
refresh();
}));
});
};
const refresh = () => {
for (const p of siblingLinkers(app, provider)) {
if (typeof p.refresh === "function") {
try {
p.refresh();
} catch (e) {
}
}
}
draw();
};
draw();
}
function renderPrecedenceSetting(containerEl, opts) {
renderPrecedence(containerEl, {
app: opts.app,
provider: opts.provider,
Setting: opts.Setting,
cls: opts.cls,
name: t2("set.precedence.name"),
desc: t2("set.precedence.desc"),
otherDesc: t2("set.precedence.other"),
upTooltip: t2("set.precedence.up"),
downTooltip: t2("set.precedence.down"),
save: opts.save
});
}
module2.exports = { STEP, rankedLinkers, precedenceForIndex, currentIndex, renderPrecedence, renderPrecedenceSetting };
}
});
// src/settings-tab.js
var require_settings_tab = __commonJS({
"src/settings-tab.js"(exports2, module2) {
"use strict";
var { PluginSettingTab, Setting } = require("obsidian");
var { PRESETS: PRESETS2 } = require_constants();
var { knownExtensions } = require_formats();
var { FolderSuggest, folderSuggestAvailable } = require_folder_suggest();
var { renderFolderList } = require_folder_list();
var { t: t2, plural: plural2 } = require_i18n();
var { renderPrecedenceSetting: precedenceSetting } = require_precedence();
var normFolder = (p) => p.replace(/\\/g, "/").replace(/\/+$/, "").trim();
var ReferenceLinkerSettingTab2 = class extends PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
// The dropdown key for the active preset: 'ask' in always-ask mode, else a preset
// ('u:<i>' for a user viewer). Migration guarantees a template match.
selectedEditor() {
if (this.plugin.settings.askOnInsert)
return "ask";
const p = this.plugin.editorPresets().find((x) => x.template === this.plugin.settings.uriTemplate);
return p ? p.key : "file";
}
// Chevron toggle shared by the foldable sections.
foldButton(setting, open, onToggle) {
setting.addExtraButton((b) => b.setIcon(open ? "chevron-up" : "chevron-down").setTooltip(open ? t2("set.editors.collapse") : t2("set.editors.expand")).onClick(onToggle));
}
// Update one viewer's dropdown label as its name is typed, sparing a full re-render.
refreshPresetOption(dropdown, i, name) {
if (!dropdown)
return;
const opt = Array.from(dropdown.selectEl.options).find((o) => o.value === "u:" + i);
if (opt)
opt.text = name || `Viewer ${i + 1}`;
}
display() {
const { containerEl } = this;
containerEl.empty();
const s = this.plugin.settings;
const save = async (rebuild) => {
await this.plugin.saveSettings();
if (rebuild)
await this.plugin.rebuildIndex(false);
};
const wide = (c) => {
c.inputEl.addClass("reference-linker-input");
return c;
};
new Setting(containerEl).setName(t2("set.heading.index")).setHeading();
new Setting(containerEl).setName(t2("set.codeRoot.name")).setDesc(t2("set.codeRoot.desc")).addText((c) => {
wide(c).setPlaceholder(this.plugin.codeRoot()).setValue(s.codeRoot).onChange(async (v) => {
s.codeRoot = v.trim();
await save(false);
});
if (folderSuggestAvailable())
new FolderSuggest(this.app, c.inputEl, () => "", null, () => this.plugin.codeRoot());
});
const folderList = (name, desc, key) => renderFolderList(containerEl, {
cls: "reference-linker",
name,
desc,
get: () => s[key],
set: async (v) => {
s[key] = v;
await save(false);
},
normalize: normFolder,
attachSuggest: folderSuggestAvailable() ? (inputEl, onPick) => new FolderSuggest(this.app, inputEl, () => this.plugin.codeRoot(), onPick) : null,
placeholder: t2("set.folderList.add"),
removeLabel: t2("set.folderList.remove"),
addLabel: t2("set.folderList.addAria")
});
folderList(t2("set.scanFolders.name"), t2("set.scanFolders.desc"), "scanRoots");
const missing = this.plugin.scanRootStatus().filter((x) => !x.exists).map((x) => x.rel);
if (missing.length) {
containerEl.createEl("div", { cls: "reference-linker-note is-error", text: t2("set.scanFolders.notFound", { folders: missing.join(", ") }) });
}
new Setting(containerEl).setName(t2("set.extensions.name")).setDesc(t2("set.extensions.desc")).addText((c) => {
this.extInput = c;
wide(c).setPlaceholder(".pdf .pptx .png").setValue(s.extensions).onChange(async (v) => {
s.extensions = v;
await save(false);
});
c.inputEl.addEventListener("blur", () => this.plugin.rebuildIndex(false));
}).addExtraButton((c) => c.setIcon("list-plus").setTooltip(t2("set.extensions.addAll")).onClick(async () => {
s.extensions = knownExtensions().join(" ");
if (this.extInput)
this.extInput.setValue(s.extensions);
await save(false);
this.plugin.rebuildIndex(true);
}));
containerEl.createEl("div", { cls: "reference-linker-note", text: t2("set.extensions.known", { exts: knownExtensions().join(" ") }) });
folderList(t2("set.skipFolders.name"), t2("set.skipFolders.desc"), "skipDirs");
new Setting(containerEl).setName(t2("set.autoRefresh.name")).setDesc(t2("set.autoRefresh.desc")).addToggle((c) => c.setValue(s.autoRefresh).onChange(async (v) => {
s.autoRefresh = v;
await save(false);
if (v)
this.plugin.startWatchers();
else
this.plugin.stopWatchers();
}));
if (s.autoRefresh && this.plugin.watchUnsupported) {
const warn = new Setting(containerEl).setDesc(t2("set.autoRefresh.unsupported"));
warn.settingEl.addClass("mod-warning");
}
const root = this.plugin.codeRoot() || t2("set.info.unknownRoot");
containerEl.createEl("div", { cls: "reference-linker-note", text: t2("set.info", { root, entries: plural2("entry", this.plugin.index.length) }) });
new Setting(containerEl).setName(t2("set.heading.suggestions")).setHeading();
new Setting(containerEl).setName(t2("set.trigger.name")).setDesc(t2("set.trigger.desc")).addText((c) => c.setValue(s.trigger).onChange(async (v) => {
s.trigger = v || "@!";
await save(false);
}));
new Setting(containerEl).setName(t2("set.minChars.name")).setDesc(t2("set.minChars.desc")).addText((c) => {
c.inputEl.type = "number";
c.inputEl.min = "0";
c.setValue(String(s.minChars)).onChange(async (v) => {
const n = parseInt(v, 10);
s.minChars = Number.isFinite(n) && n >= 0 ? n : 1;
await save(false);
});
});
new Setting(containerEl).setName(t2("set.maxResults.name")).setDesc(t2("set.maxResults.desc")).addText((c) => {
c.inputEl.type = "number";
c.inputEl.min = "1";
c.setValue(String(s.maxResults)).onChange(async (v) => {
const n = parseInt(v, 10);
s.maxResults = Number.isFinite(n) && n > 0 ? n : 12;
await save(false);
});
});
let presetDropdown;
new Setting(containerEl).setName(t2("set.editorPreset.name")).setDesc(t2("set.editorPreset.desc")).addDropdown((d) => {
presetDropdown = d;
for (const p of this.plugin.editorPresets())
d.addOption(p.key, p.label);
d.addOption("ask", t2("set.preset.ask"));
d.setValue(this.selectedEditor()).onChange(async (v) => {
s.askOnInsert = v === "ask";
if (!s.askOnInsert) {
const p = this.plugin.editorPresets().find((x) => x.key === v);
if (p)
s.uriTemplate = p.template;
}
await save(false);
});
});
if (this.showEditors === void 0)
this.showEditors = false;
const editors = s.editors || [];
const editorsHeading = new Setting(containerEl).setName(t2("set.editors.name")).setDesc(t2("set.editors.count", { n: editors.length }));
this.foldButton(editorsHeading, this.showEditors, () => {
this.showEditors = !this.showEditors;
this.display();
});
if (this.showEditors) {
editors.forEach((ed, i) => {
const row = new Setting(containerEl).addText((c) => {
c.inputEl.addClass("reference-linker-editor-name");
c.setPlaceholder(t2("set.editors.namePlaceholder")).setValue(ed.name).onChange(async (v) => {
ed.name = v;
this.refreshPresetOption(presetDropdown, i, v);
await save(false);
});
}).addText((c) => {
c.inputEl.addClass("reference-linker-editor-tpl");
c.setPlaceholder("sioyek --page {page} {abs}").setValue(ed.template).onChange(async (v) => {
if (s.uriTemplate === ed.template)
s.uriTemplate = v;
ed.template = v;
await save(false);
});
}).addExtraButton((b) => b.setIcon("trash").setTooltip(t2("set.editors.remove")).onClick(async () => {
if (s.uriTemplate === ed.template)
s.uriTemplate = PRESETS2.file;
editors.splice(i, 1);
await save(false);
this.display();
}));
row.settingEl.addClass("reference-linker-editor-row");
});
new Setting(containerEl).setDesc(t2("set.editors.desc")).addButton((b) => b.setButtonText(t2("set.editors.add")).setCta().onClick(async () => {
editors.push({ name: "", template: "" });
s.editors = editors;
await save(false);
this.display();
}));
}
new Setting(containerEl).setName(t2("set.contextMenu.name")).setDesc(t2("set.contextMenu.desc")).addToggle((c) => c.setValue(s.contextMenu).onChange(async (v) => {
s.contextMenu = v;
await save(false);
}));
new Setting(containerEl).setName(t2("set.heading.hover")).setHeading();
new Setting(containerEl).setName(t2("set.hoverPreview.name")).setDesc(t2("set.hoverPreview.desc")).addToggle((c) => c.setValue(s.hoverPreview).onChange(async (v) => {
s.hoverPreview = v;
await save(false);
}));
new Setting(containerEl).setName(t2("set.heading.links")).setHeading();
new Setting(containerEl).setName(t2("set.markStaleLinks.name")).setDesc(t2("set.markStaleLinks.desc")).addToggle((c) => c.setValue(s.markStaleLinks).onChange(async (v) => {
s.markStaleLinks = v;
await save(false);
}));
new Setting(containerEl).setName(t2("set.heading.maintenance")).setHeading();
precedenceSetting(containerEl, {
app: this.app,
provider: this.plugin.api && this.plugin.api.linker,
Setting,
cls: "reference-linker",
save: async (value) => {
s.linkPrecedence = value;
await save(false);
}
});
new Setting(containerEl).setName(t2("set.rebuild.name")).setDesc(t2("set.rebuild.desc")).addButton((b) => b.setButtonText(t2("set.rebuild.button")).onClick(() => this.plugin.rebuildIndex(true).then(() => this.display())));
}
};
module2.exports = { ReferenceLinkerSettingTab: ReferenceLinkerSettingTab2 };
}
});
// src/api.js
var require_api = __commonJS({
"src/api.js"(exports2, module2) {
"use strict";
var { LINKER_API } = require_discover();
var { splitTarget: splitTarget2 } = require_markdown();
var { bindingOwner: bindingOwner2, ownsBinding: ownsBinding2 } = require_binding();
var OWNER2 = "reference";
var pick = (e) => ({ name: e.name, kind: e.kind, ext: e.lang, path: e.path, page: e.page || 1 });
module2.exports = {
buildApi() {
const plugin = this;
return {
version: this.manifest.version,
// The absolute reference root the scan paths resolve against.
root: () => this.codeRoot(),
// Every indexed entry: { name, kind, ext, path, page } (kind is 'file' or 'section').
getEntries: () => this.index.map(pick),
// One row per indexed file: { name, path, ext, entries }.
getFiles: () => this.apiFiles(),
// Totals: { files, entries, byExt, byKind }.
getStats: () => this.apiStats(),
// Entries matching a name or path tail (the same lookup the commands use).
find: (text) => this.lookup(String(text || "")).map(pick),
// Render helpers: a portable markdown link, or a ready-to-open absolute URI.
linkFor: (entry) => this.buildLink(entry),
uriFor: (entry) => this.fillRoot(this.buildUri(entry)),
// Subscribe to index rebuilds; returns an unsubscribe function.
onChange: (cb) => this.onIndexChange(cb),
// The provider contract the sibling linkers read (consumed in shared/discover.js and
// shared/link-owner.js).
linker: {
apiVersion: LINKER_API,
id: "reference-linker",
displayName: "Reference Linker",
kind: "sigil",
get precedence() {
return plugin.settings.linkPrecedence;
},
claim: (target, title) => {
const split = splitTarget2(String(target || ""));
const ttl = title ? String(title) : split.title;
if (ownsBinding2(ttl, OWNER2))
return "binding";
if (bindingOwner2(ttl))
return null;
return split.url && plugin.refForTarget(split.url) ? "index" : null;
},
// Both selection actions search on click, so the answer doesn't depend on the text —
// only on whether our context menu is switched on at all.
offers: (kind) => (kind === "convert" || kind === "open") && !!plugin.settings.contextMenu
}
};
},
apiFiles() {
const out = [];
for (const v of this.fileCache.values()) {
const f = v.entries[0];
if (f)
out.push({ name: f.name, path: f.path, ext: f.lang, entries: v.entries.length });
}
out.sort((a, b) => a.path.localeCompare(b.path));
return out;
},
apiStats() {
const byExt = {}, byKind = {};
for (const e of this.index) {
byExt[e.lang] = (byExt[e.lang] || 0) + 1;
byKind[e.kind] = (byKind[e.kind] || 0) + 1;
}
return { files: this.fileCache.size, entries: this.index.length, byExt, byKind };
}
};
}
});
// src/shared/index-events.js
var require_index_events = __commonJS({
"src/shared/index-events.js"(exports2, module2) {
"use strict";
module2.exports = {
// Returns an unsubscribe function.
onIndexChange(cb) {
if (typeof cb !== "function")
return () => {
};
if (!this._indexListeners)
this._indexListeners = /* @__PURE__ */ new Set();
this._indexListeners.add(cb);
return () => this._indexListeners.delete(cb);
},
notifyIndexChange() {
for (const cb of this._indexListeners || []) {
try {
cb();
} catch (e) {
console.error(`${this.manifest.id}: index listener failed`, e);
}
}
}
};
}
});
// src/locales/en.js
var require_en = __commonJS({
"src/locales/en.js"(exports2, module2) {
"use strict";
module2.exports = {
// Commands
"cmd.rebuildIndex": "Rebuild reference index",
"cmd.insertLink": "Insert reference link",
"cmd.insertLinkAs": "Insert reference link as\u2026",
"cmd.openFile": "Open referenced document",
"cmd.copyLink": "Copy reference link",
"cmd.convertSelection": "Convert selection to reference link",
"cmd.openSelection": "Find and open document",
"cmd.insertEmbed": "Insert reference embed",
"cmd.updateLinksNote": "Update reference links in this note",
"cmd.updateLinksVault": "Update reference links in the whole vault",
"cmd.pinLinksNote": "Pin unpinned reference links in this note",
"cmd.pinLinksVault": "Pin unpinned reference links in the whole vault",
// Editor context menu
// Selection actions. `.solo` is the flat wording used when no sibling linker offers the
// same verb; `.group` labels the shared submenu when one does, and `.item` names our
// destination inside it. The `.group` wording must match the sibling's word for word —
// whichever plugin is called first creates the group and its label is the one shown.
"menu.convert.solo": "Find and convert to reference link",
"menu.convert.item": "Document",
"menu.open.solo": "Find and open document",
"menu.open.item": "Document",
"menu.copyLink": "Copy reference link",
"menu.fixLink": "Update this reference link",
"menu.pin": "Pin to section \u201C{sec}\u201D",
"menu.unpin": "Unpin this reference link",
// Notices
"notice.noCodeRoot": "Reference Linker: could not determine the reference root",
"notice.noExtensions": "Reference Linker: no file extensions configured",
"notice.scanFailed": "Reference Linker: scan failed \u2014 {error}",
"notice.indexed": "Reference Linker: {entries} indexed",
"notice.missingFolders": "Reference Linker: scan folder not found \u2014 {folders}",
"notice.copied": "Reference Linker: link copied",
"notice.anchorCopied": "Opens at the start \u2014 \u201C{section}\u201D copied, search for it",
"notice.noSelection": "Reference Linker: select a name or path first",
"notice.noMatch": "Reference Linker: no document matches \u201C{query}\u201D",
"notice.watchUnsupported": "Reference Linker: auto-refresh is unavailable on this platform \u2014 rebuild manually",
"notice.linksUpdated": "Reference Linker: {n} link(s) updated",
"notice.linksUpdatedVault": "Reference Linker: {n} link(s) updated across {files} note(s)",
"modal.update.title": "Update reference links",
"modal.update.attention": "{n} link(s) need attention: their section is gone (renamed, or the outline changed), so there\u2019s no page to fix.",
"modal.update.brokenRow": "{label} \u2014 no fix (section renamed or removed)",
"notice.linksPinned": "Reference Linker: {n} link(s) pinned",
"notice.linksPinnedVault": "Reference Linker: {n} link(s) pinned across {files} note(s)",
"notice.pinned": "Reference Linker: link pinned to section \u201C{sec}\u201D",
"notice.unpinned": "Reference Linker: link unpinned \u2014 it is no longer tracked",
"notice.cantPin": "Reference Linker: can't pin \u2014 no section begins on that page",
// Inline embeds
"embed.empty": "Reference Linker: empty embed \u2014 give a document path",
"embed.fmt.file": "Document (first page)",
"embed.fmt.section": "Section \u201C{name}\u201D",
"embed.unsupported": "Reference Linker: no inline preview for {path}",
"preview.empty": "Nothing to show here",
"embed.menu.open": "Open document",
"embed.notFound": "Reference Linker: no document matches \u201C{query}\u201D",
"embed.ambiguous": "Reference Linker: {n} documents match \u201C{query}\u201D \u2014 add a path to pick one",
"embed.unreadable": "Reference Linker: can\u2019t read {path}",
"embed.truncated": "Reference Linker: showing the first {max} lines",
// Status bar
"status.indexing": "Reference Linker: indexing\u2026 {n}",
// Command-palette modal
"modal.searchPlaceholder": "Search documents\u2026",
"modal.formatPlaceholder": "Choose a viewer format for this link\u2026",
// Settings — headings
"set.heading.index": "Reference index",
// Settings — reference index
"set.codeRoot.name": "Reference root",
"set.scanFolders.desc": "Folders scanned for documents, relative to the reference root. Leave empty to scan the whole root.",
"set.scanFolders.notFound": "\u26A0 Not found under the reference root \u2014 {folders}",
"set.extensions.name": "File extensions",
"set.extensions.desc": "Which file types to index, space- or comma-separated (e.g. .pdf .pptx .png). Empty = nothing is indexed.",
"set.extensions.known": "Previews and section indexing: {exts}. Any other extension is indexed by file name only.",
"set.extensions.addAll": "Add every supported extension",
"set.skipFolders.desc": "A bare name (node_modules) is skipped at any depth; a path with a slash (archive/raw) skips only that folder, relative to the reference root.",
"set.autoRefresh.desc": "Watch the scan folders and rebuild the index when documents change.",
"set.info": "Reference root: {root} \xB7 {entries} indexed",
"set.rebuild.name": "Rebuild reference index",
"set.rebuild.desc": "Re-scan the document folders now.",
// Settings — suggestions & links
"set.trigger.desc": "Type this to start a reference suggestion. Default @! (Code Linker uses @@).",
"set.editorPreset.name": "Viewer link preset",
"set.editorPreset.desc": "How inserted links open. file:// uses the OS default app. Add your own under \u201CYour viewers\u201D.",
"set.editors.name": "Your viewers",
"set.editors.desc": "Named URL/command templates for the dropdown above. Placeholders: {abs} {path} {page} {name} {root}.",
"set.editors.add": "+ Add viewer",
"set.contextMenu.desc": "Add \u201CFind and convert to link\u201D and \u201CFind and open document\u201D to the editor right-click menu \u2014 plus \u201CCopy reference link\u201D when you right-click a reference link.",
// Settings — hover preview
"set.hoverPreview.name": "Preview on hover",
"set.hoverPreview.desc": "Preview the referenced document when you hover a link. In live preview, hold Ctrl/Cmd; in reading view a plain hover is enough.",
// Settings — links
"set.markStaleLinks.desc": "Underline a reference link when its document moved (warning colour, fixable with \u201CUpdate reference links\u201D) or is gone from disk (error colour). A link you edited by hand is left alone: the page you typed and the text you wrote are yours."
};
}
});
// src/locales/ru.js
var require_ru = __commonJS({
"src/locales/ru.js"(exports2, module2) {
"use strict";
module2.exports = {
// Commands
"cmd.rebuildIndex": "\u041F\u0435\u0440\u0435\u0441\u0442\u0440\u043E\u0438\u0442\u044C \u0438\u043D\u0434\u0435\u043A\u0441 \u0441\u0441\u044B\u043B\u043E\u043A",
"cmd.insertLink": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"cmd.insertLinkAs": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u043A\u0430\u043A\u2026",
"cmd.openFile": "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"cmd.copyLink": "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"cmd.convertSelection": "\u041F\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044C \u0432\u044B\u0434\u0435\u043B\u0435\u043D\u0438\u0435 \u0432 \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"cmd.openSelection": "\u041D\u0430\u0439\u0442\u0438 \u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"cmd.insertEmbed": "\u0412\u0441\u0442\u0430\u0432\u0438\u0442\u044C embed \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430",
"cmd.updateLinksNote": "\u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0438 \u0432 \u044D\u0442\u043E\u0439 \u0437\u0430\u043C\u0435\u0442\u043A\u0435",
"cmd.updateLinksVault": "\u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0438 \u0432\u043E \u0432\u0441\u0451\u043C \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435",
"cmd.pinLinksNote": "\u0417\u0430\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u043D\u0435\u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0451\u043D\u043D\u044B\u0435 \u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u0432 \u044D\u0442\u043E\u0439 \u0437\u0430\u043C\u0435\u0442\u043A\u0435",
"cmd.pinLinksVault": "\u0417\u0430\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u043D\u0435\u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0451\u043D\u043D\u044B\u0435 \u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B \u0432\u043E \u0432\u0441\u0451\u043C \u0445\u0440\u0430\u043D\u0438\u043B\u0438\u0449\u0435",
// Editor context menu
"menu.convert.solo": "\u041D\u0430\u0439\u0442\u0438 \u0438 \u043F\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044C \u0432 \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"menu.convert.item": "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"menu.open.solo": "\u041D\u0430\u0439\u0442\u0438 \u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"menu.open.item": "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"menu.copyLink": "\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"menu.fixLink": "\u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u044D\u0442\u0443 \u0441\u0441\u044B\u043B\u043A\u0443",
"menu.pin": "\u0417\u0430\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u0437\u0430 \u0440\u0430\u0437\u0434\u0435\u043B\u043E\u043C \xAB{sec}\xBB",
"menu.unpin": "\u041E\u0442\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u044D\u0442\u0443 \u0441\u0441\u044B\u043B\u043A\u0443",
// Notices
"notice.noCodeRoot": "Reference Linker: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043E\u043F\u0440\u0435\u0434\u0435\u043B\u0438\u0442\u044C \u043A\u043E\u0440\u0435\u043D\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432",
"notice.noExtensions": "Reference Linker: \u043D\u0435 \u0437\u0430\u0434\u0430\u043D\u043E \u043D\u0438 \u043E\u0434\u043D\u043E\u0433\u043E \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F",
"notice.scanFailed": "Reference Linker: \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435 \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u2014 {error}",
"notice.indexed": "Reference Linker: \u043F\u0440\u043E\u0438\u043D\u0434\u0435\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u043E {entries}",
"notice.missingFolders": "Reference Linker: \u043F\u0430\u043F\u043A\u0430 \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u043D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u0430 \u2014 {folders}",
"notice.copied": "Reference Linker: \u0441\u0441\u044B\u043B\u043A\u0430 \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u0430",
"notice.anchorCopied": "\u041E\u0442\u043A\u0440\u043E\u0435\u0442\u0441\u044F \u0441 \u043D\u0430\u0447\u0430\u043B\u0430 \u2014 \xAB{section}\xBB \u0441\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u043D\u043E, \u043D\u0430\u0439\u0434\u0438\u0442\u0435 \u043F\u043E\u0438\u0441\u043A\u043E\u043C",
"notice.noSelection": "Reference Linker: \u0441\u043D\u0430\u0447\u0430\u043B\u0430 \u0432\u044B\u0434\u0435\u043B\u0438\u0442\u0435 \u0438\u043C\u044F \u0438\u043B\u0438 \u043F\u0443\u0442\u044C",
"notice.noMatch": "Reference Linker: \u043D\u0435\u0442 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u0434\u043B\u044F \xAB{query}\xBB",
"notice.watchUnsupported": "Reference Linker: \u0430\u0432\u0442\u043E\u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u0438\u0435 \u043D\u0435\u0434\u043E\u0441\u0442\u0443\u043F\u043D\u043E \u043D\u0430 \u044D\u0442\u043E\u0439 \u043F\u043B\u0430\u0442\u0444\u043E\u0440\u043C\u0435 \u2014 \u043F\u0435\u0440\u0435\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0439\u0442\u0435 \u0432\u0440\u0443\u0447\u043D\u0443\u044E",
"notice.linksUpdated": "Reference Linker: \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u0441\u0441\u044B\u043B\u043E\u043A \u2014 {n}",
"notice.linksUpdatedVault": "Reference Linker: \u043E\u0431\u043D\u043E\u0432\u043B\u0435\u043D\u043E \u0441\u0441\u044B\u043B\u043E\u043A \u2014 {n} \u0432 \u0437\u0430\u043C\u0435\u0442\u043A\u0430\u0445: {files}",
"modal.update.title": "\u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0438 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B",
"modal.update.attention": "\u0422\u0440\u0435\u0431\u0443\u044E\u0442 \u0432\u043D\u0438\u043C\u0430\u043D\u0438\u044F \u2014 {n}: \u0438\u0445 \u0440\u0430\u0437\u0434\u0435\u043B \u043F\u0440\u043E\u043F\u0430\u043B (\u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D, \u0438\u043B\u0438 \u0438\u0437\u043C\u0435\u043D\u0438\u043B\u043E\u0441\u044C \u043E\u0433\u043B\u0430\u0432\u043B\u0435\u043D\u0438\u0435), \u043F\u043E\u044D\u0442\u043E\u043C\u0443 \u0447\u0438\u043D\u0438\u0442\u044C \u043D\u0435\u0447\u0435\u0433\u043E.",
"modal.update.brokenRow": "{label} \u2014 \u043D\u0435 \u043F\u043E\u0447\u0438\u043D\u0438\u0442\u044C (\u0440\u0430\u0437\u0434\u0435\u043B \u043F\u0435\u0440\u0435\u0438\u043C\u0435\u043D\u043E\u0432\u0430\u043D \u0438\u043B\u0438 \u0443\u0434\u0430\u043B\u0451\u043D)",
"notice.linksPinned": "Reference Linker: \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u043E \u0441\u0441\u044B\u043B\u043E\u043A \u2014 {n}",
"notice.linksPinnedVault": "Reference Linker: \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u043E \u0441\u0441\u044B\u043B\u043E\u043A \u2014 {n} \u0432 \u0437\u0430\u043C\u0435\u0442\u043A\u0430\u0445: {files}",
"notice.pinned": "Reference Linker: \u0441\u0441\u044B\u043B\u043A\u0430 \u0437\u0430\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u0430 \u0437\u0430 \u0440\u0430\u0437\u0434\u0435\u043B\u043E\u043C \xAB{sec}\xBB",
"notice.unpinned": "Reference Linker: \u0441\u0441\u044B\u043B\u043A\u0430 \u043E\u0442\u043A\u0440\u0435\u043F\u043B\u0435\u043D\u0430 \u2014 \u0431\u043E\u043B\u044C\u0448\u0435 \u043D\u0435 \u043E\u0442\u0441\u043B\u0435\u0436\u0438\u0432\u0430\u0435\u0442\u0441\u044F",
"notice.cantPin": "Reference Linker: \u043D\u0435 \u0437\u0430 \u0447\u0442\u043E \u0437\u0430\u043A\u0440\u0435\u043F\u0438\u0442\u044C \u2014 \u043D\u0430 \u044D\u0442\u043E\u0439 \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0435 \u043D\u0435 \u043D\u0430\u0447\u0438\u043D\u0430\u0435\u0442\u0441\u044F \u0440\u0430\u0437\u0434\u0435\u043B",
// Inline embeds
"embed.empty": "Reference Linker: \u043F\u0443\u0441\u0442\u043E\u0439 embed \u2014 \u0443\u043A\u0430\u0436\u0438\u0442\u0435 \u043F\u0443\u0442\u044C \u043A \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0443",
"embed.fmt.file": "\u0414\u043E\u043A\u0443\u043C\u0435\u043D\u0442 (\u043F\u0435\u0440\u0432\u0430\u044F \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430)",
"embed.fmt.section": "\u0420\u0430\u0437\u0434\u0435\u043B \xAB{name}\xBB",
"embed.unsupported": "Reference Linker: \u043D\u0435\u0442 \u0438\u043D\u043B\u0430\u0439\u043D-\u043F\u0440\u0435\u0432\u044C\u044E \u0434\u043B\u044F {path}",
"preview.empty": "\u0417\u0434\u0435\u0441\u044C \u043D\u0435\u0447\u0435\u0433\u043E \u043F\u043E\u043A\u0430\u0437\u0430\u0442\u044C",
"embed.menu.open": "\u041E\u0442\u043A\u0440\u044B\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442",
"embed.notFound": "Reference Linker: \u043D\u0435\u0442 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430 \u0434\u043B\u044F \xAB{query}\xBB",
"embed.ambiguous": "Reference Linker: \u043F\u043E\u0434 \xAB{query}\xBB \u043F\u043E\u0434\u0445\u043E\u0434\u0438\u0442 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432: {n} \u2014 \u0443\u0442\u043E\u0447\u043D\u0438\u0442\u0435 \u043F\u0443\u0442\u0451\u043C",
"embed.unreadable": "Reference Linker: \u043D\u0435 \u0443\u0434\u0430\u043B\u043E\u0441\u044C \u043F\u0440\u043E\u0447\u0438\u0442\u0430\u0442\u044C {path}",
"embed.truncated": "Reference Linker: \u043F\u043E\u043A\u0430\u0437\u0430\u043D\u044B \u043F\u0435\u0440\u0432\u044B\u0435 {max} \u0441\u0442\u0440\u043E\u043A",
// Status bar
"status.indexing": "Reference Linker: \u0438\u043D\u0434\u0435\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u0435\u2026 {n}",
// Command-palette modal
"modal.searchPlaceholder": "\u041F\u043E\u0438\u0441\u043A \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432\u2026",
"modal.formatPlaceholder": "\u0412\u044B\u0431\u0435\u0440\u0438\u0442\u0435 \u0444\u043E\u0440\u043C\u0430\u0442 \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0449\u0438\u043A\u0430 \u0434\u043B\u044F \u044D\u0442\u043E\u0439 \u0441\u0441\u044B\u043B\u043A\u0438\u2026",
// Settings — headings
"set.heading.index": "\u0418\u043D\u0434\u0435\u043A\u0441 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432",
// Settings — reference index
"set.codeRoot.name": "\u041A\u043E\u0440\u0435\u043D\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432",
"set.scanFolders.desc": "\u041F\u0430\u043F\u043A\u0438, \u0441\u043A\u0430\u043D\u0438\u0440\u0443\u0435\u043C\u044B\u0435 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u044B, \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u043A\u043E\u0440\u043D\u044F. \u041E\u0441\u0442\u0430\u0432\u044C\u0442\u0435 \u043F\u0443\u0441\u0442\u044B\u043C, \u0447\u0442\u043E\u0431\u044B \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0432\u0435\u0441\u044C \u043A\u043E\u0440\u0435\u043D\u044C.",
"set.scanFolders.notFound": "\u26A0 \u041D\u0435 \u043D\u0430\u0439\u0434\u0435\u043D\u043E \u0432 \u043A\u043E\u0440\u043D\u0435 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432 \u2014 {folders}",
"set.extensions.name": "\u0420\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F \u0444\u0430\u0439\u043B\u043E\u0432",
"set.extensions.desc": "\u041A\u0430\u043A\u0438\u0435 \u0442\u0438\u043F\u044B \u0444\u0430\u0439\u043B\u043E\u0432 \u0438\u043D\u0434\u0435\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u0442\u044C, \u0447\u0435\u0440\u0435\u0437 \u043F\u0440\u043E\u0431\u0435\u043B \u0438\u043B\u0438 \u0437\u0430\u043F\u044F\u0442\u0443\u044E (\u043D\u0430\u043F\u0440. .pdf .pptx .png). \u041F\u0443\u0441\u0442\u043E = \u043D\u0438\u0447\u0435\u0433\u043E \u043D\u0435 \u0438\u043D\u0434\u0435\u043A\u0441\u0438\u0440\u0443\u0435\u0442\u0441\u044F.",
"set.extensions.known": "\u041F\u0440\u0435\u0432\u044C\u044E \u0438 \u0438\u043D\u0434\u0435\u043A\u0441\u0430\u0446\u0438\u044F \u0440\u0430\u0437\u0434\u0435\u043B\u043E\u0432: {exts}. \u041E\u0441\u0442\u0430\u043B\u044C\u043D\u044B\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F \u0438\u043D\u0434\u0435\u043A\u0441\u0438\u0440\u0443\u044E\u0442\u0441\u044F \u0442\u043E\u043B\u044C\u043A\u043E \u043F\u043E \u0438\u043C\u0435\u043D\u0438 \u0444\u0430\u0439\u043B\u0430.",
"set.extensions.addAll": "\u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u0432\u0441\u0435 \u043F\u043E\u0434\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0435\u043C\u044B\u0435 \u0440\u0430\u0441\u0448\u0438\u0440\u0435\u043D\u0438\u044F",
"set.skipFolders.desc": "\u041F\u0440\u043E\u0441\u0442\u043E \u0438\u043C\u044F (node_modules) \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442\u0441\u044F \u043D\u0430 \u043B\u044E\u0431\u043E\u0439 \u0433\u043B\u0443\u0431\u0438\u043D\u0435; \u043F\u0443\u0442\u044C \u0441\u043E \u0441\u043B\u044D\u0448\u0435\u043C (archive/raw) \u043F\u0440\u043E\u043F\u0443\u0441\u043A\u0430\u0435\u0442 \u0442\u043E\u043B\u044C\u043A\u043E \u044D\u0442\u0443 \u043F\u0430\u043F\u043A\u0443 \u043E\u0442\u043D\u043E\u0441\u0438\u0442\u0435\u043B\u044C\u043D\u043E \u043A\u043E\u0440\u043D\u044F.",
"set.autoRefresh.desc": "\u0421\u043B\u0435\u0434\u0438\u0442\u044C \u0437\u0430 \u043F\u0430\u043F\u043A\u0430\u043C\u0438 \u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u043D\u0438\u044F \u0438 \u043F\u0435\u0440\u0435\u0441\u0442\u0440\u0430\u0438\u0432\u0430\u0442\u044C \u0438\u043D\u0434\u0435\u043A\u0441 \u043F\u0440\u0438 \u0438\u0437\u043C\u0435\u043D\u0435\u043D\u0438\u0438 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432.",
"set.info": "\u041A\u043E\u0440\u0435\u043D\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432: {root} \xB7 \u043F\u0440\u043E\u0438\u043D\u0434\u0435\u043A\u0441\u0438\u0440\u043E\u0432\u0430\u043D\u043E {entries}",
"set.rebuild.name": "\u041F\u0435\u0440\u0435\u0441\u0442\u0440\u043E\u0438\u0442\u044C \u0438\u043D\u0434\u0435\u043A\u0441 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u043E\u0432",
"set.rebuild.desc": "\u041F\u0435\u0440\u0435\u0441\u043A\u0430\u043D\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u043F\u0430\u043F\u043A\u0438 \u0441 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\u0430\u043C\u0438 \u0441\u0435\u0439\u0447\u0430\u0441.",
// Settings — suggestions & links
"set.trigger.desc": "\u0412\u0432\u0435\u0434\u0438\u0442\u0435 \u044D\u0442\u043E, \u0447\u0442\u043E\u0431\u044B \u043D\u0430\u0447\u0430\u0442\u044C \u043F\u043E\u0434\u0441\u043A\u0430\u0437\u043A\u0443. \u041F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E @! (Code Linker \u0437\u0430\u043D\u0438\u043C\u0430\u0435\u0442 @@).",
"set.editorPreset.name": "\u041F\u0440\u0435\u0441\u0435\u0442 \u043E\u0442\u043A\u0440\u044B\u0442\u0438\u044F",
"set.editorPreset.desc": "\u041A\u0430\u043A \u043E\u0442\u043A\u0440\u044B\u0432\u0430\u044E\u0442\u0441\u044F \u0432\u0441\u0442\u0430\u0432\u043B\u0435\u043D\u043D\u044B\u0435 \u0441\u0441\u044B\u043B\u043A\u0438. file:// \u2014 \u043F\u0440\u0438\u043B\u043E\u0436\u0435\u043D\u0438\u0435 \u041E\u0421 \u043F\u043E \u0443\u043C\u043E\u043B\u0447\u0430\u043D\u0438\u044E. \u0421\u0432\u043E\u0438 \u2014 \u0432 \xAB\u0412\u0430\u0448\u0438 \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0449\u0438\u043A\u0438\xBB.",
"set.editors.name": "\u0412\u0430\u0448\u0438 \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0449\u0438\u043A\u0438",
"set.editors.desc": "\u0418\u043C\u0435\u043D\u043E\u0432\u0430\u043D\u043D\u044B\u0435 \u0448\u0430\u0431\u043B\u043E\u043D\u044B URL/\u043A\u043E\u043C\u0430\u043D\u0434 \u0434\u043B\u044F \u0441\u043F\u0438\u0441\u043A\u0430 \u0432\u044B\u0448\u0435. \u041F\u043B\u0435\u0439\u0441\u0445\u043E\u043B\u0434\u0435\u0440\u044B: {abs} {path} {page} {name} {root}.",
"set.editors.add": "+ \u0414\u043E\u0431\u0430\u0432\u0438\u0442\u044C \u043F\u0440\u043E\u0441\u043C\u043E\u0442\u0440\u0449\u0438\u043A",
"set.contextMenu.desc": "\u0414\u043E\u0431\u0430\u0432\u043B\u044F\u0442\u044C \xAB\u041D\u0430\u0439\u0442\u0438 \u0438 \u043F\u0440\u0435\u0432\u0440\u0430\u0442\u0438\u0442\u044C \u0432 \u0441\u0441\u044B\u043B\u043A\u0443\xBB \u0438 \xAB\u041D\u0430\u0439\u0442\u0438 \u0438 \u043E\u0442\u043A\u0440\u044B\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\xBB \u0432 \u043C\u0435\u043D\u044E \u043F\u043E \u043F\u0440\u0430\u0432\u043E\u043C\u0443 \u043A\u043B\u0438\u043A\u0443 \u2014 \u043F\u043B\u044E\u0441 \xAB\u0421\u043A\u043E\u043F\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443 \u043D\u0430 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442\xBB \u043F\u0440\u0438 \u043A\u043B\u0438\u043A\u0435 \u043F\u043E \u0441\u0441\u044B\u043B\u043A\u0435.",
// Settings — hover preview
"set.hoverPreview.name": "\u041F\u0440\u0435\u0432\u044C\u044E \u043F\u0440\u0438 \u043D\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u0438",
"set.hoverPreview.desc": "\u041F\u043E\u043A\u0430\u0437\u044B\u0432\u0430\u0442\u044C \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u043F\u0440\u0438 \u043D\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u0438 \u043D\u0430 \u0441\u0441\u044B\u043B\u043A\u0443. \u0412 \u0440\u0435\u0436\u0438\u043C\u0435 live preview \u0443\u0434\u0435\u0440\u0436\u0438\u0432\u0430\u0439\u0442\u0435 Ctrl/Cmd; \u0432 \u0440\u0435\u0436\u0438\u043C\u0435 \u0447\u0442\u0435\u043D\u0438\u044F \u0434\u043E\u0441\u0442\u0430\u0442\u043E\u0447\u043D\u043E \u043F\u0440\u043E\u0441\u0442\u043E\u0433\u043E \u043D\u0430\u0432\u0435\u0434\u0435\u043D\u0438\u044F.",
// Settings — links
"set.markStaleLinks.desc": "\u041F\u043E\u0434\u0447\u0451\u0440\u043A\u0438\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0443, \u0435\u0441\u043B\u0438 \u0435\u0451 \u0434\u043E\u043A\u0443\u043C\u0435\u043D\u0442 \u043F\u0435\u0440\u0435\u0435\u0445\u0430\u043B (\u0446\u0432\u0435\u0442 \u043F\u0440\u0435\u0434\u0443\u043F\u0440\u0435\u0436\u0434\u0435\u043D\u0438\u044F, \u0447\u0438\u043D\u0438\u0442\u0441\u044F \u043A\u043E\u043C\u0430\u043D\u0434\u043E\u0439 \xAB\u0410\u043A\u0442\u0443\u0430\u043B\u0438\u0437\u0438\u0440\u043E\u0432\u0430\u0442\u044C \u0441\u0441\u044B\u043B\u043A\u0438\xBB) \u0438\u043B\u0438 \u043F\u0440\u043E\u043F\u0430\u043B \u0441 \u0434\u0438\u0441\u043A\u0430 (\u0446\u0432\u0435\u0442 \u043E\u0448\u0438\u0431\u043A\u0438). \u0421\u0441\u044B\u043B\u043A\u0443, \u043F\u043E\u043F\u0440\u0430\u0432\u043B\u0435\u043D\u043D\u0443\u044E \u0440\u0443\u043A\u0430\u043C\u0438, \u043F\u043B\u0430\u0433\u0438\u043D \u043D\u0435 \u0442\u0440\u043E\u0433\u0430\u0435\u0442: \u0441\u0442\u0440\u0430\u043D\u0438\u0446\u0430 \u0438 \u0442\u0435\u043A\u0441\u0442 \u2014 \u0442\u0432\u043E\u0438."
};
}
});
// src/main.js
var { Plugin, Notice, normalizePath, MarkdownView } = require("obsidian");
var { EditorView } = require("@codemirror/view");
var { Prec } = require("@codemirror/state");
var fs = require("fs");
var fsp = fs.promises;
var nodePath = require("path");
var { PRESETS, DEFAULT_SETTINGS, parseExtensions, parseSkip, underSkip } = require_constants();
var { splitLines, inTableCell, inCode, inLink, linkRegex, splitTarget, withTitle } = require_markdown();
var { parseBinding, formatBinding, bindStateFrom, bindingOwner, ownsBinding } = require_binding();
var { fillRoot: fillRootToken, ownsRootToken, namespaceRoot } = require_root_token();
var { buildMenu } = require_menu_verbs();
var { ownsLink } = require_link_owner();
var { ReferenceSuggest } = require_suggest2();
var filter = require_filter();
var { HoverPreview } = require_hover();
var { registerEmbed } = require_embed();
var actualize = require_actualize2();
var { ReferenceLinkModal, PresetPickerModal } = require_modal();
var { ReferenceLinkerSettingTab } = require_settings_tab();
var formats = require_formats();
var { initI18n, withFamily, t, plural } = require_i18n();
var api = require_api();
var indexEvents = require_index_events();
function openExternal(uri) {
try {
require("electron").shell.openExternal(uri);
} catch (e) {
window.open(uri);
}
}
var PAGE_LINK = /^file:\/\/\/.+#page=\d+/i;
var ROOT_ATTR = "data-reference-root";
var OWNER = "reference";
var SIBLING_ID = "code-linker";
var TITLE_ATTR = "data-reference-title";
var anchorTitle = (a) => a.getAttribute(TITLE_ATTR) || a.getAttribute("title") || "";
var pathPart = (dec) => dec.split("#")[0].split("?")[0];
var extOf = (rel) => nodePath.extname(rel).slice(1).toLowerCase();
var normCase = (s) => process.platform === "win32" ? s.toLowerCase() : s;
function namesPath(p, full) {
const a = normCase(p), b = normCase(full);
if (!b || !a.endsWith(b))
return false;
const i = a.length - b.length;
return i === 0 || a[i - 1] === "/";
}
var previewEntry = (plugin, ref, title, url) => {
const b = parseBinding(title);
const at = plugin.sectionAtLinkPage(url);
if (b && b.sec) {
const named = at && at.name === b.sec ? at : plugin.sectionNamed(ref.entry.path, b.sec);
return Object.assign({}, ref.entry, { page: named && named.page || ref.page, title: b.sec });
}
return Object.assign({}, ref.entry, { page: at && at.page || ref.page, title: at ? at.name : "" });
};
var ReferenceLinkerPlugin = class extends Plugin {
async onload() {
initI18n(withFamily("sigil", { en: require_en(), ru: require_ru() }));
this.settings = Object.assign({}, DEFAULT_SETTINGS, await this.loadData());
this.setIndex([]);
this.watchers = [];
this.fileCache = /* @__PURE__ */ new Map();
this.cacheSignature = "";
this._indexListeners = /* @__PURE__ */ new Set();
this.migrateSettings();
await this.loadCache();
this.hover = new HoverPreview(this);
this.registerEditorSuggest(new ReferenceSuggest(this.app, this));
this.registerMarkdownPostProcessor((el) => this.resolveRootLinks(el));
this.registerEditorExtension(
Prec.highest(
EditorView.domEventHandlers({
mousedown: (evt, view) => this.onEditorLink(evt, view, false),
click: (evt, view) => this.onEditorLink(evt, view, true),
auxclick: (evt, view) => this.onEditorLink(evt, view, true)
})
)
);
registerEmbed(this);
this.registerEditorExtension(actualize.staleLinksExtension(this));
this.register(this.onIndexChange(() => this.refreshStale()));
this.lastX = 0;
this.lastY = 0;
this.registerDomEvent(document, "mousemove", (evt) => this.onHoverMove(evt));
this.registerDomEvent(document, "keydown", (evt) => {
if (evt.key === "Control" || evt.key === "Meta")
this.onHoverKey();
});
this.registerDomEvent(document, "scroll", (evt) => {
if (!this.hover.contains(evt.target))
this.hover.hide();
}, { capture: true });
this.registerDomEvent(window, "blur", () => this.hover.hide());
this.registerDomEvent(document, "keyup", (evt) => {
if (evt.key === "Escape")
this.hover.hide();
});
this.registerDomEvent(document, "click", (evt) => this.onAnchorClick(evt), { capture: true });
this.registerDomEvent(document, "auxclick", (evt) => this.onAnchorClick(evt), { capture: true });
this.addSettingTab(new ReferenceLinkerSettingTab(this.app, this));
this.statusEl = this.addStatusBarItem();
this.addCommand({ id: "rebuild-reference-index", name: t("cmd.rebuildIndex"), callback: () => this.rebuildIndex(true) });
this.addCommand({ id: "insert-reference-link", name: t("cmd.insertLink"), editorCallback: (editor) => this.pickEntry((e) => this.withFormat(this.settings.askOnInsert, (tpl) => this.insertLink(editor, e, tpl))) });
this.addCommand({ id: "insert-reference-link-as", name: t("cmd.insertLinkAs"), editorCallback: (editor) => this.pickEntry((e) => this.withFormat(true, (tpl) => this.insertLink(editor, e, tpl))) });
this.addCommand({ id: "open-reference-file", name: t("cmd.openFile"), callback: () => this.pickEntry((e) => this.withFormat(this.settings.askOnInsert, (tpl) => this.openEntry(e, tpl))) });
this.addCommand({ id: "copy-reference-link", name: t("cmd.copyLink"), callback: () => this.pickEntry((e) => this.withFormat(this.settings.askOnInsert, (tpl) => this.copyLink(e, tpl))) });
this.addCommand({ id: "convert-selection-to-link", name: t("cmd.convertSelection"), editorCallback: (editor) => this.convertSelection(editor) });
this.addCommand({ id: "open-selected-reference", name: t("cmd.openSelection"), editorCallback: (editor) => this.openSelection(editor) });
this.addCommand({ id: "insert-reference-embed", name: t("cmd.insertEmbed"), editorCallback: (editor) => this.pickEntry((e) => this.insertEmbed(editor, e)) });
this.addCommand({ id: "update-links-note", name: t("cmd.updateLinksNote"), callback: () => this.updateLinksInActiveNote() });
this.addCommand({ id: "update-links-vault", name: t("cmd.updateLinksVault"), callback: () => this.updateLinksInVault() });
this.addCommand({ id: "pin-links-note", name: t("cmd.pinLinksNote"), callback: () => this.pinLinksInActiveNote() });
this.addCommand({ id: "pin-links-vault", name: t("cmd.pinLinksVault"), callback: () => this.pinLinksInVault() });
this.registerEvent(
this.app.workspace.on("editor-menu", (nativeMenu, editor) => buildMenu(this, nativeMenu, (menu) => {
if (!this.settings.contextMenu)
return;
if (this.selectionTarget(editor, true)) {
this.selectionItem(menu, "convert", "link", () => this.convertSelection(editor));
}
if (this.selectionTarget(editor, false)) {
this.selectionItem(menu, "open", "file-search", () => this.openSelection(editor));
}
const link = this.linkAtCursor(editor);
if (link && this.ownsLinkAtCursor(link)) {
menu.addItem((item) => item.setTitle(t("menu.copyLink")).setIcon("copy").onClick(() => this.copyLinkAtCursor(link)));
if (this.isLinkStale(withTitle(link.target, link.title))) {
menu.addItem((item) => item.setTitle(t("menu.fixLink")).setIcon("wrench").onClick(() => this.fixLinkAtCursor(editor, link)));
}
const bound = !!parseBinding(link.title);
const pin = bound ? null : this.linkPinOption(link);
if (bound) {
menu.addItem((item) => item.setTitle(t("menu.unpin")).setIcon("pin-off").onClick(() => this.unpinLinkAtCursor(editor, link)));
} else if (pin) {
menu.addItem((item) => item.setTitle(t("menu.pin", { sec: pin.value })).setIcon("pin").onClick(() => this.pinLinkAtCursor(editor, link)));
}
}
}))
);
this.app.workspace.onLayoutReady(() => this.rebuildIndex(false));
this.api = this.buildApi();
}
onunload() {
this.stopWatchers();
clearTimeout(this.watchTimer);
if (this.hover)
this.hover.destroy();
formats.dispose();
}
migrateSettings() {
this.settings.skipDirs = (this.settings.skipDirs || "").split(/[\n,]+/).map((s) => s.trim()).filter(Boolean).join("\n");
this.settings.uriTemplate = namespaceRoot(this.settings.uriTemplate, OWNER);
for (const e of this.settings.editors || [])
e.template = namespaceRoot(e.template, OWNER);
const tpl = this.settings.uriTemplate;
const editors = this.settings.editors || (this.settings.editors = []);
const known = Object.values(PRESETS).includes(tpl) || editors.some((e) => e.template === tpl);
if (!known)
editors.push({ name: "Custom", template: tpl });
}
// Our own {ref-root} is always ours to fill. A bare {root} predates the namespacing and
// Code Linker used to fill it too, so it takes a verdict — see legacyRootIsOurs. The
// default claims it, which is what every call about our own links wants; only the render
// path, where another plugin's links go past, asks first.
fillRoot(v, claimLegacy = true) {
const root = encodeURI(this.codeRoot().split(nodePath.sep).join("/"));
return fillRootToken(v, { owner: OWNER, root, claimLegacy });
}
siblingLinkerInstalled() {
const plugins = this.app.plugins && this.app.plugins.plugins;
return !!(plugins && plugins[SIBLING_ID]);
}
// Whether a bare {root} in a rendered link is ours to resolve. The binding settles it
// when there is one. Failing that, being the only linker installed makes every legacy
// link ours, which keeps a solo vault behaving exactly as it always did. Otherwise the
// link has to point at something inside our root to count as ours.
legacyRootIsOurs(url, title) {
const owner = bindingOwner(title);
if (owner)
return owner === OWNER;
if (!this.siblingLinkerInstalled())
return true;
return !!this.targetIndexedFile(this.decodeTarget(url));
}
resolveRootLinks(el) {
const links = el.querySelectorAll ? el.querySelectorAll("a") : [];
for (const a of links) {
const title = a.getAttribute("title") || "";
let ours = false;
for (const attr of ["href", "data-href"]) {
const v = a.getAttribute(attr);
if (!v)
continue;
const out = this.fillRoot(v, this.legacyRootIsOurs(v, title));
if (out !== v) {
a.setAttribute(attr, out);
ours = true;
}
}
if (ours)
a.setAttribute(ROOT_ATTR, "");
this.stashTitle(a);
}
this.markStaleAnchors(el);
}
// Park a binding title on a data attribute and drop the real one, so the binding string
// doesn't show as a native tooltip. A plain tooltip the reader wrote is left as-is, and
// so is Code Linker's binding: taking its title away left it unable to read its own
// pin and marking its links wrongly.
stashTitle(a) {
const title = a.getAttribute("title");
if (!title || a.hasAttribute(TITLE_ATTR) || !ownsBinding(title, OWNER))
return;
a.setAttribute(TITLE_ATTR, title);
a.removeAttribute("title");
}
// Toggle the drifted/broken-link underline on every rendered anchor in `el`. toggle (not
// add) so re-running after an index rebuild also clears links that are now current.
markStaleAnchors(el) {
const links = el.querySelectorAll ? el.querySelectorAll("a") : [];
for (const a of links) {
const href = a.getAttribute("href") || a.getAttribute("data-href") || "";
const state = this.settings.markStaleLinks ? this.linkState(withTitle(href, anchorTitle(a))) : null;
a.classList.toggle("reference-linker-stale", state === "stale");
a.classList.toggle("reference-linker-broken", state === "broken");
}
}
// After an index rebuild, refresh stale marks in both render modes: the CM6 effect for
// Live Preview, and a re-scan of rendered anchors for Reading view (its post-processor
// doesn't re-run on its own).
refreshStale() {
actualize.refreshStaleLinks(this.app);
this.app.workspace.iterateAllLeaves((leaf) => {
const view = leaf.view;
if (view && view.getViewType && view.getViewType() === "markdown" && view.containerEl) {
this.markStaleAnchors(view.containerEl);
}
});
}
hoverEnabled() {
return this.settings.hoverPreview;
}
// Pointer tracking that mirrors a real page preview. Rendered (Reading view) links
// preview on plain hover; the editor (Live Preview) needs the modifier — same split
// as native page preview. Idle in the editor (nothing shown, no modifier, not over a
// rendered link) does no work beyond storing the position. While a preview is up it
// follows the pointer so it stays until you leave the link (entering it keeps it).
onHoverMove(evt) {
this.lastX = evt.clientX;
this.lastY = evt.clientY;
if (!this.hoverEnabled())
return;
if (evt.buttons)
return;
const el = evt.target;
if (this.hover.contains(el)) {
this.hover.cancelHide();
return;
}
const mod = evt.ctrlKey || evt.metaKey;
const overAnchor = !!(el && el.closest && el.closest("a"));
if (!this.hover.isVisible() && !this.hover.pendingKey && !mod && !overAnchor)
return;
const hit = this.entryAtPoint(el, evt.clientX, evt.clientY);
if (hit && (!hit.requireMod || mod)) {
this.hover.cancelHide();
this.hover.schedule(hit.entry, evt.clientX, evt.clientY);
} else if (this.hover.isVisible() || this.hover.pendingKey) {
this.hover.leave();
}
}
// Pressing the modifier while already hovering a link shows it — the other order
// (modifier first, then move onto the link) is handled by onHoverMove.
onHoverKey() {
if (!this.hoverEnabled())
return;
const el = document.elementFromPoint(this.lastX, this.lastY);
if (this.hover.contains(el))
return;
const hit = this.entryAtPoint(el, this.lastX, this.lastY);
if (hit)
this.hover.schedule(hit.entry, this.lastX, this.lastY);
}
// The document under a screen point as { entry, requireMod }, across both render
// modes, or null. Reading view carries the URL on a rendered anchor and previews on
// plain hover; Live Preview's CM6 link span has no href (recovered from the editor at
// those coordinates) and requires the modifier, like a link in the editor natively.
entryAtPoint(el, x, y) {
if (!el || !el.closest)
return null;
const a = el.closest("a");
if (a && !(a.classList && a.classList.contains("internal-link"))) {
const href = a.getAttribute("href") || a.getAttribute("data-href") || "";
const ref = this.refForTarget(href);
if (ref)
return { entry: previewEntry(this, ref, anchorTitle(a), href), requireMod: false };
}
if (el.closest(".cm-link")) {
const view = typeof EditorView.findFromDOM === "function" ? EditorView.findFromDOM(el) : this.activeCm();
const at = view && this.codeRefAt(view, x, y);
const ref = at && this.refForTarget(at.target);
if (ref)
return { entry: previewEntry(this, ref, at.title, at.target), requireMod: true };
}
return null;
}
// The CM6 EditorView of the active Markdown editor, used as a fallback when
// EditorView.findFromDOM isn't available to map a point to its editor.
activeCm() {
const mv = this.app.workspace.getActiveViewOfType(MarkdownView);
return mv && mv.editor && mv.editor.cm;
}
// {root} filled in, %-escapes undone, backslashes normalised — the form links are matched on.
decodeTarget(target) {
let dec = this.fillRoot(target);
try {
dec = decodeURIComponent(dec);
} catch (e) {
}
return dec.split("\\").join("/");
}
// The position a link asks for — only ever read, never overridden. A #page fragment or a
// {page} query both count, and #t= is the same question asked of a recording.
targetPage(dec) {
const m = /[#?&](?:page|t)=(\d+)/i.exec(dec);
return m ? parseInt(m[1], 10) : 1;
}
// The document a link points at, from its target alone: { entry, page }, or null for a
// link into no indexed document. The label is never consulted.
refForTarget(target) {
if (!target)
return null;
const dec = this.decodeTarget(target);
const cached = this.fileCache.get(this.targetIndexedFile(dec));
const entry = cached && cached.entries[0];
return entry ? { entry, page: this.targetPage(dec) } : null;
}
entriesIn(rel) {
return rel ? (this.fileCache.get(rel) || { entries: [] }).entries : [];
}
// A document's section by name — how a link that stores no position finds where it points.
sectionNamed(rel, name) {
return this.entriesIn(rel).find((e) => e.kind === "section" && e.name === name) || null;
}
// What a section binding says about the page a link stores: null when the section still
// sits there, stale with the page it moved to, or broken when the document is indexed but
// no such section resolves any more (renamed, or the outline changed).
//
// Broken is reserved for a document the index *has*, never for one it doesn't know — a
// reference root pointed at the wrong folder, or a document not scanned yet, would
// otherwise turn every link red at once. An unknown document gets no verdict rather than
// a guess. Code Linker already worked this way; this is the two brought into line.
urlBindState(url, b, storedPage) {
if (!b.sec)
return null;
const rel = this.targetIndexedFile(this.decodeTarget(url));
if (!rel)
return null;
const hits = this.entriesIn(rel).filter((e) => e.kind === "section" && e.name === b.sec);
if (!hits.length)
return { state: "broken" };
const kind = formats.anchorKind(extOf(rel));
if (!kind)
return null;
if (kind === "id")
return this.idBindState(url, hits);
return bindStateFrom(hits.map((e) => e.page), storedPage);
}
// An id-anchored link drifts when its heading is still there under a different id, which
// is what regenerating a doc site does. A heading with no id anchors as the empty fragment,
// so a link pinned to it must match by that — else a same-named heading that does have an id
// would drag the link onto the wrong one.
idBindState(url, hits) {
const stored = this.targetAnchor(this.decodeTarget(url));
const anchors = hits.map((e) => e.anchor || "");
if (anchors.includes(stored))
return null;
const withId = anchors.filter(Boolean);
return withId.length ? { state: "stale", anchor: withId[0] } : null;
}
// The fragment a link carries, without the '#'.
targetAnchor(dec) {
const i = dec.indexOf("#");
return i < 0 ? "" : dec.slice(i + 1);
}
// The outline section beginning on a link's page — what it can be pinned to. Null when the
// page is mid-section or the document has no outline.
sectionAtLinkPage(url) {
const rel = url && this.targetIndexedFile(this.decodeTarget(url));
if (!rel)
return null;
const kind = formats.anchorKind(extOf(rel));
if (!kind)
return null;
const entries = this.entriesIn(rel);
if (kind === "id") {
const frag = this.targetAnchor(this.decodeTarget(url));
return frag && entries.find((e) => e.kind === "section" && e.anchor === frag) || null;
}
const page = this.targetPage(url);
return entries.find((e) => e.kind === "section" && e.page === page) || null;
}
// The title pinning would produce and the section it pins to, or null when there's nothing
// to pin or it would change nothing.
linkPinOption(link) {
const sec = this.sectionAtLinkPage(link.target);
if (!sec)
return null;
const title = formatBinding({ sec: sec.name });
return title === (link.title || "") ? null : { title, value: sec.name };
}
// CM6 link handler for Live Preview. Suppresses Obsidian's open of the literal
// {root} URL; opens the resolved one on click/auxclick. Returns true when handled.
onEditorLink(evt, view, open) {
if (evt.button !== 0 && evt.button !== 1)
return false;
const uri = this.rootUriAt(evt, view);
if (!uri)
return false;
evt.preventDefault();
evt.stopPropagation();
if (open)
openExternal(uri);
return true;
}
// Reading view renders our links as real <a>; Obsidian's opener corrupts a #page=
// fragment, so we intercept and open through the shell — for a link resolveRootLinks
// marked ours, and any file:// link with a page. Everything else is left to Obsidian.
//
// This runs in the capture phase, ahead of every other handler, so it has to be sure the
// link is ours before swallowing the click: claiming a Code Linker link here sent it to
// the OS viewer instead of the editor.
onAnchorClick(evt) {
if (evt.button !== 0 && evt.button !== 1)
return;
const a = evt.target && evt.target.closest && evt.target.closest("a");
if (!a)
return;
const href = a.getAttribute("href") || a.getAttribute("data-href") || "";
const filled = this.fillRoot(href, this.legacyRootIsOurs(href, anchorTitle(a)));
if (!a.hasAttribute(ROOT_ATTR) && !PAGE_LINK.test(filled))
return;
evt.preventDefault();
evt.stopPropagation();
openExternal(filled);
}
// The markdown link at screen coords in Live Preview, as { name, target }. The
// rendered span has no href, so map the coords to a document position and read it.
codeRefAt(view, x, y) {
if (typeof view.posAtCoords !== "function")
return null;
const offset = view.posAtCoords({ x, y });
if (offset == null)
return null;
const line = view.state.doc.lineAt(offset);
const ch = offset - line.from;
const re = linkRegex();
let m;
while (m = re.exec(line.text)) {
if (ch < m.index || ch > m.index + m[0].length)
continue;
const { url, title } = splitTarget(m[2]);
return { name: m[1], target: url, title };
}
return null;
}
// The link under the click resolved, if the token it carries is ours — else null, so a
// plain link falls through to Obsidian's own opener and the other linker's link falls
// through to that plugin. Both register a highest-precedence handler, so each has to
// claim only its own; otherwise the winner comes down to which plugin loaded first.
// codeRefAt has already split the title off the target.
rootUriAt(evt, view) {
const el = evt.target;
if (!el || !el.closest || !el.closest(".cm-link"))
return null;
const ref = this.codeRefAt(view, evt.clientX, evt.clientY);
if (!ref)
return null;
const claimLegacy = this.legacyRootIsOurs(ref.target, ref.title);
return ownsRootToken(ref.target, OWNER, claimLegacy) ? this.fillRoot(ref.target, claimLegacy) : null;
}
// Absolute base folder the scan paths are resolved against.
codeRoot() {
if (this.settings.codeRoot)
return this.settings.codeRoot;
const adapter = this.app.vault.adapter;
const base = adapter && typeof adapter.getBasePath === "function" ? adapter.getBasePath() : "";
return base ? nodePath.dirname(base) : "";
}
cacheFilePath() {
return normalizePath(`${this.manifest.dir}/index-cache.json`);
}
// A fingerprint of what the scan would produce: the indexed extensions plus a format
// version (bumped when indexing logic changes, e.g. PDF sections were added). When it
// changes, the per-file cache is stale even if mtimes haven't moved, so we drop it.
indexSignature() {
return JSON.stringify({ v: 4, exts: [...parseExtensions(this.settings.extensions)].sort() });
}
async loadCache() {
try {
const p = this.cacheFilePath();
if (!await this.app.vault.adapter.exists(p))
return;
const data = JSON.parse(await this.app.vault.adapter.read(p));
if (!data || data.version !== 1 || !data.files)
return;
this.cacheSignature = data.signature || "";
this.fileCache = new Map(Object.entries(data.files));
this.setIndex(this.flattenCache());
} catch (e) {
}
}
async saveCache() {
try {
const files = {};
for (const [rel, v] of this.fileCache.entries())
files[rel] = v;
const data = { version: 1, signature: this.cacheSignature, files };
await this.app.vault.adapter.write(this.cacheFilePath(), JSON.stringify(data));
} catch (e) {
}
}
flattenCache() {
const out = [];
for (const v of this.fileCache.values())
for (const e of v.entries)
out.push(e);
out.sort((a, b) => a.name.localeCompare(b.name) || a.path.localeCompare(b.path));
return out;
}
// Set the index and its name lookup together. byName groups entries by lowercased
// name so resolving a link/symbol scans only the same-named entries, not the whole
// index (the hot paths — hover, stale marks, embeds — call this per event).
setIndex(entries) {
this.index = entries;
this.byName = /* @__PURE__ */ new Map();
this.kinds = /* @__PURE__ */ new Set();
this.exts = /* @__PURE__ */ new Set();
for (const e of entries) {
const k = e.name.toLowerCase();
const a = this.byName.get(k);
if (a)
a.push(e);
else
this.byName.set(k, [e]);
this.kinds.add(e.kind);
this.exts.add(e.lang);
}
}
// Index entries whose (lowercased) name equals `name` — the candidate set a bare
// symbol resolves against.
entriesByName(name) {
return this.byName.get(String(name).toLowerCase()) || [];
}
// An inline prefix filters by extension ("pdf:") or kind ("sec:", a shorthand for
// "section"); the rest is the name to match.
parseQuery(raw) {
const kinds = this.kinds && this.kinds.has("section") ? /* @__PURE__ */ new Set([...this.kinds, "sec"]) : this.kinds;
const f = filter.parseQuery(raw, kinds, this.exts);
if (f.kind === "sec")
f.kind = "section";
return f;
}
entryPassesFilter(e, f) {
return (!f.kind || e.kind === f.kind) && (!f.ext || e.lang === f.ext);
}
// The indexed document a link target names, or null: the entry whose root-joined path the
// target ends with. Works whatever scheme the link was built with.
targetIndexedFile(dec) {
const p = pathPart(dec);
const root = this.codeRoot().split(nodePath.sep).join("/").replace(/\/+$/, "");
for (const rel of this.fileCache.keys()) {
if (namesPath(p, root ? root + "/" + rel : rel))
return rel;
}
return null;
}
// The set of indexed extensions (".pdf" etc.), used for the scan and watch filtering.
watchedExts() {
return parseExtensions(this.settings.extensions);
}
startWatchers() {
this.stopWatchers();
this.watchUnsupported = false;
if (!this.settings.autoRefresh)
return;
const root = this.codeRoot();
if (!root)
return;
for (const r of this.scanFolders()) {
const dir = nodePath.join(root, r);
if (!fs.existsSync(dir))
continue;
try {
const w = fs.watch(dir, { recursive: true }, (_evt, filename) => this.onWatchEvent(r, filename));
this.watchers.push(w);
} catch (e) {
if (e && e.code === "ERR_FEATURE_UNAVAILABLE_ON_PLATFORM")
this.watchUnsupported = true;
}
}
if (this.watchUnsupported && !this.watchUnsupportedNotified) {
this.watchUnsupportedNotified = true;
new Notice(t("notice.watchUnsupported"));
}
}
stopWatchers() {
for (const w of this.watchers) {
try {
w.close();
} catch (e) {
}
}
this.watchers = [];
}
// Debounce a background rebuild on file changes. Skip-dir noise (node_modules)
// and files we don't index are dropped cheaply before scheduling. `r` is the scan
// root the event came from, so the path can be resolved relative to the reference root.
onWatchEvent(r, filename) {
if (filename) {
const base = (r || "").split("\\").join("/").replace(/\/+$/, "");
const rel = (base ? base + "/" : "") + String(filename).split("\\").join("/");
if (underSkip(rel, parseSkip(this.settings.skipDirs)))
return;
const ext = nodePath.extname(rel).toLowerCase();
if (ext && !this.watchedExts().has(ext))
return;
}
clearTimeout(this.watchTimer);
this.watchTimer = setTimeout(() => this.rebuildIndex(false), 1500);
}
// Empty the index (nothing to scan) and persist, telling whoever's listening.
async resetIndex(noticeKey, notify) {
this.setIndex([]);
this.fileCache = /* @__PURE__ */ new Map();
await this.saveCache();
this.notifyIndexChange();
if (notify)
new Notice(t(noticeKey));
}
async rebuildIndex(notify) {
this.stopWatchers();
const root = this.codeRoot();
if (!root) {
if (notify)
new Notice(t("notice.noCodeRoot"));
return;
}
const roots = this.scanFolders();
const exts = this.watchedExts();
if (!exts.size) {
await this.resetIndex("notice.noExtensions", notify);
return;
}
const signature = this.indexSignature();
const old = signature === this.cacheSignature ? this.fileCache : /* @__PURE__ */ new Map();
let seen = 0;
const onFile = () => {
if (++seen % 200 === 0)
this.statusEl.setText(t("status.indexing", { n: seen }));
};
const scan = { root, exts, skip: parseSkip(this.settings.skipDirs), old, next: /* @__PURE__ */ new Map(), onFile };
try {
for (const r of roots) {
await this.walk(nodePath.join(root, r), scan);
}
} catch (err) {
this.statusEl.setText("");
if (notify)
new Notice(t("notice.scanFailed", { error: err && err.message }));
return;
}
this.statusEl.setText("");
this.fileCache = scan.next;
this.cacheSignature = signature;
this.setIndex(this.flattenCache());
await this.saveCache();
this.notifyIndexChange();
this.startWatchers();
if (notify) {
const missing = this.scanRootStatus().filter((st) => !st.exists).map((st) => st.rel);
if (missing.length)
new Notice(t("notice.missingFolders", { folders: missing.join(", ") }));
else
new Notice(t("notice.indexed", { entries: plural("entry", this.index.length) }));
}
}
async walk(absDir, scan) {
let items;
try {
items = await fsp.readdir(absDir, { withFileTypes: true });
} catch (e) {
return;
}
for (const it of items) {
const abs = nodePath.join(absDir, it.name);
if (it.isDirectory()) {
const rel = nodePath.relative(scan.root, abs).split(nodePath.sep).join("/");
if (!underSkip(rel, scan.skip))
await this.walk(abs, scan);
} else if (it.isFile()) {
if (scan.exts.has(nodePath.extname(it.name).toLowerCase()))
await this.indexFile(abs, scan);
}
}
}
async indexFile(abs, scan) {
const rel = nodePath.relative(scan.root, abs).split(nodePath.sep).join("/");
let stat;
try {
stat = await fsp.stat(abs);
} catch (e) {
return;
}
if (scan.onFile)
scan.onFile();
const cached = scan.old.get(rel);
if (cached && cached.mtimeMs === stat.mtimeMs) {
scan.next.set(rel, cached);
return;
}
const base = nodePath.basename(abs).replace(/\.[^.]+$/, "");
const ext = nodePath.extname(abs).slice(1).toLowerCase();
const entries = [{ name: base, kind: "file", lang: ext, path: rel, line: 1, page: 1 }];
for (const s of await formats.outline(ext, abs)) {
const entry = { name: s.title, kind: "section", lang: ext, path: rel, line: s.page, page: s.page };
if (s.anchor)
entry.anchor = s.anchor;
entries.push(entry);
}
scan.next.set(rel, { mtimeMs: stat.mtimeMs, entries });
}
// An entry's absolute path on disk: the reference root joined with its stored relative path.
entryPath(e) {
const root = this.codeRoot();
return root ? nodePath.join(root, e.path) : e.path;
}
// {root} stays in the link for portability (resolved on render/click); call fillRoot()
// when opening the URI directly. `template` overrides the default preset.
buildUri(e, template) {
const tpl = template || this.settings.uriTemplate;
const absFwd = this.entryPath(e).split(nodePath.sep).join("/");
const page = String(e.page || 1);
const encPath = (p) => p.split("/").map(encodeURIComponent).join("/");
let uri = tpl.replace(/{abs}/g, encodeURI(absFwd)).replace(/{path}/g, encPath(e.path)).replace(/{page}/g, page).replace(/{name}/g, encodeURIComponent(e.name));
const anchor = formats.anchorFor(e);
if (anchor && /^file:/i.test(uri) && !uri.includes("#"))
uri += "#" + anchor;
return uri;
}
// The markdown link to insert. A section link is pinned to its section by a title binding
// (see shared/binding), so it tracks without the label being read. A pipe would split a
// table row.
buildLink(e, inTable, template) {
const url = this.buildUri(e, template);
const link = `[${e.name}](${e.kind === "section" ? withTitle(url, formatBinding({ sec: e.name })) : url})`;
return inTable ? link.replace(/\|/g, "\\|") : link;
}
pickEntry(onChoose, query) {
new ReferenceLinkModal(this.app, this, { onChoose, query }).open();
}
insertLink(editor, e, template) {
const inTable = inTableCell(editor.getValue(), editor.posToOffset(editor.getCursor("from")));
editor.replaceSelection(this.buildLink(e, inTable, template));
}
// The ```reference-link block body offered for an entry: a section embeds by its own anchor
// (a #id for HTML, #page= for a PDF, the ordinal page otherwise), any document by its path.
embedFormats(e) {
const out = [];
if (e.kind === "section" && e.page) {
const frag = formats.anchorFor(e) || "page=" + e.page;
out.push({ label: t("embed.fmt.section", { name: e.name }), body: e.path + "#" + frag });
}
out.push({ label: t("embed.fmt.file"), body: e.path });
return out;
}
insertEmbed(editor, e) {
const formats2 = this.embedFormats(e);
new PresetPickerModal(this.app, formats2, (f) => {
editor.replaceSelection("```reference-link\n" + f.body + "\n```\n");
}, t("modal.embedPlaceholder")).open();
}
// The selectable viewer presets — the built-in file:// then the user's own. 'u:<i>' is a
// user viewer's key in the settings dropdown.
editorPresets() {
const out = [{ key: "file", label: t("set.preset.file"), template: PRESETS.file }];
(this.settings.editors || []).forEach((e, i) => out.push({ key: "u:" + i, label: e.name || `Viewer ${i + 1}`, template: e.template }));
return out;
}
// Ask-on-insert picks the viewer format per insert; otherwise the default preset is used.
withFormat(ask, run) {
if (ask)
new PresetPickerModal(this.app, this.editorPresets(), (p) => run(p.template), t("modal.formatPlaceholder")).open();
else
run(void 0);
}
// Resolve {root} to the absolute reference root: a copied link is usually pasted outside
// the vault (a browser, a terminal), where the portable {root} token wouldn't resolve.
// Inserted links keep {root} for note portability.
copyLink(e, template) {
navigator.clipboard.writeText(this.fillRoot(this.buildLink(e, false, template)));
new Notice(t("notice.copied"));
}
// fillRoot resolves the portable {root} token, since there's no note to render it.
openEntry(e, template) {
if (e.kind === "section" && e.name && !formats.hasOsAnchor(e.lang)) {
navigator.clipboard.writeText(e.name);
new Notice(t("notice.anchorCopied", { section: e.name }));
}
openExternal(this.fillRoot(this.buildUri(e, template)));
}
// Entries matched by name, or by path tail so a selected "Foo/Bar.cs" resolves too.
lookup(text) {
const q = text.trim();
if (!q)
return [];
const lc = q.toLowerCase();
const norm = lc.split("\\").join("/");
const out = [];
for (const e of this.index) {
const p = e.path.toLowerCase();
if (e.name.toLowerCase() === lc || p === norm || p.endsWith("/" + norm))
out.push(e);
}
return out;
}
selectionOrWord(editor) {
const sel = editor.getSelection();
if (sel)
return { text: sel, from: editor.getCursor("from"), to: editor.getCursor("to") };
const cur = editor.getCursor();
const line = editor.getLine(cur.line);
const isWord = (ch) => ch && /[\w./\\-]/.test(ch);
let s = cur.ch, en = cur.ch;
while (s > 0 && isWord(line[s - 1]))
s--;
while (en < line.length && isWord(line[en]))
en++;
const text = line.slice(s, en);
return text ? { text, from: { line: cur.line, ch: s }, to: { line: cur.line, ch: en } } : null;
}
// The selection/word to act on, or null when it makes no sense there. Never inside an
// existing link (both actions). For `write` (convert-to-link) also never inside code or
// frontmatter, where inserting a link would corrupt the sample; opening code from there
// is harmless, so read-only actions are allowed.
selectionTarget(editor, write) {
const target = this.selectionOrWord(editor);
if (!target)
return null;
const text = editor.getValue();
const off = editor.posToOffset(target.from);
if (inLink(text, off))
return null;
if (write && inCode(text, off))
return null;
return target;
}
// The markdown link spanning the editor cursor, as { name, target, line, from, to }
// (character offsets within the line), or null. Right-click puts the cursor on the
// click, so this reads the link that was clicked.
linkAtCursor(editor) {
const cur = editor.getCursor();
const line = editor.getLine(cur.line);
const re = linkRegex();
let m;
while (m = re.exec(line)) {
if (cur.ch >= m.index && cur.ch <= m.index + m[0].length) {
const { url, title } = splitTarget(m[2]);
return { name: m[1], target: url, title, line: cur.line, from: m.index, to: m.index + m[0].length };
}
}
return null;
}
fixLinkAtCursor(editor, link) {
const fixed = this.actualizedTarget(withTitle(link.target, link.title));
if (fixed == null) {
new Notice(t("notice.linksUpdated", { n: 0 }));
return;
}
editor.replaceRange("[" + link.name + "](" + fixed + ")", { line: link.line, ch: link.from }, { line: link.line, ch: link.to });
new Notice(t("notice.linksUpdated", { n: 1 }));
}
pinLinkAtCursor(editor, link) {
const opt = this.linkPinOption(link);
if (!opt) {
new Notice(t("notice.cantPin"));
return;
}
const pinned = withTitle(link.target, opt.title);
editor.replaceRange("[" + link.name + "](" + pinned + ")", { line: link.line, ch: link.from }, { line: link.line, ch: link.to });
new Notice(t("notice.pinned", { sec: opt.value }));
}
unpinLinkAtCursor(editor, link) {
if (!parseBinding(link.title))
return;
editor.replaceRange("[" + link.name + "](" + link.target + ")", { line: link.line, ch: link.from }, { line: link.line, ch: link.to });
new Notice(t("notice.unpinned"));
}
// One of the two selection verbs. The builder decides whether it ends up under the verb
// or on its own; the wording follows, since inside the submenu the verb is already named.
selectionItem(menu, kind, icon, run) {
menu.tagged(kind, {}, (item, grouped) => item.setTitle(t("menu." + kind + (grouped ? ".item" : ".solo"))).setIcon(icon).onClick(run));
}
// Whether the link under the cursor is ours to act on. Recognising it isn't enough: the
// code linker recognises a file both indexes cover just as readily, and two Copy and two
// Unpin items on one link tell the reader nothing about which is which.
ownsLinkAtCursor(link) {
if (!this.isReferenceLink(link.name, link.target, link.title))
return false;
const provider = this.api && this.api.linker;
if (!provider)
return true;
return ownsLink(this.app, provider, link.target, link.title);
}
// One of ours — a link into an indexed document — so the copy/pin/fix items show only on
// our links.
isReferenceLink(name, target, title) {
return !!this.refForTarget(target) || !!this.linkState(withTitle(target, title));
}
// Copy the clicked link's own target ({root} filled in), keeping the scheme it was
// saved with — unlike copyLink, which builds a fresh link from the default preset.
copyLinkAtCursor(link) {
navigator.clipboard.writeText(this.fillRoot(link.target));
new Notice(t("notice.copied"));
}
// Run the selected (or under-cursor) token through the index: a single match runs
// `action`, several open the picker, none notifies. `write` gates the protected-range
// check (convert may not run in code; open may).
resolveSelection(editor, action, write) {
const target = this.selectionTarget(editor, write);
if (!target) {
new Notice(t("notice.noSelection"));
return;
}
const matches = this.lookup(target.text);
if (!matches.length) {
new Notice(t("notice.noMatch", { query: target.text }));
return;
}
const run = (e) => action(e, target);
if (matches.length === 1)
run(matches[0]);
else
this.pickEntry(run, target.text);
}
convertSelection(editor) {
this.resolveSelection(editor, (e, target) => this.withFormat(this.settings.askOnInsert, (template) => {
const inTable = inTableCell(editor.getValue(), editor.posToOffset(target.from));
editor.replaceRange(this.buildLink(e, inTable, template), target.from, target.to);
}), true);
}
openSelection(editor) {
this.resolveSelection(editor, (e) => this.withFormat(this.settings.askOnInsert, (template) => this.openEntry(e, template)), false);
}
// Folders to scan, relative to the reference root; empty means the whole reference root.
scanFolders() {
const roots = splitLines(this.settings.scanRoots);
return roots.length ? roots : ["."];
}
scanRootStatus() {
const root = this.codeRoot();
return this.scanFolders().map((rel) => ({
rel,
exists: !!root && fs.existsSync(nodePath.join(root, rel))
}));
}
async saveSettings() {
await this.saveData(this.settings);
}
};
Object.assign(ReferenceLinkerPlugin.prototype, api, indexEvents);
Object.assign(ReferenceLinkerPlugin.prototype, actualize.methods);
module.exports = ReferenceLinkerPlugin;