/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin https://github.com/joethei/obisidian-link-favicon */ var __create = Object.create; var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __getProtoOf = Object.getPrototypeOf; var __hasOwnProp = Object.prototype.hasOwnProperty; var __markAsModule = (target) => __defProp(target, "__esModule", { value: true }); var __commonJS = (cb, mod) => function __require() { return mod || (0, cb[Object.keys(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports; }; var __export = (target, all) => { __markAsModule(target); for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __reExport = (target, module2, desc) => { if (module2 && typeof module2 === "object" || typeof module2 === "function") { for (let key of __getOwnPropNames(module2)) if (!__hasOwnProp.call(target, key) && key !== "default") __defProp(target, key, { get: () => module2[key], enumerable: !(desc = __getOwnPropDesc(module2, key)) || desc.enumerable }); } return target; }; var __toModule = (module2) => { return __reExport(__markAsModule(__defProp(module2 != null ? __create(__getProtoOf(module2)) : {}, "default", module2 && module2.__esModule && "default" in module2 ? { get: () => module2.default, enumerable: true } : { value: module2, enumerable: true })), module2); }; var __async = (__this, __arguments, generator) => { return new Promise((resolve, reject) => { var fulfilled = (value) => { try { step(generator.next(value)); } catch (e) { reject(e); } }; var rejected = (value) => { try { step(generator.throw(value)); } catch (e) { reject(e); } }; var step = (x) => x.done ? resolve(x.value) : Promise.resolve(x.value).then(fulfilled, rejected); step((generator = generator.apply(__this, __arguments)).next()); }); }; // node_modules/@aidenlx/obsidian-icon-shortcodes/lib/index.js var require_lib = __commonJS({ "node_modules/@aidenlx/obsidian-icon-shortcodes/lib/index.js"(exports) { var a = Object.create; var t = Object.defineProperty; var d = Object.getOwnPropertyDescriptor; var p = Object.getOwnPropertyNames; var f = Object.getPrototypeOf; var g = Object.prototype.hasOwnProperty; var o = (n) => t(n, "__esModule", { value: true }); var l = (n, e) => { o(n); for (var i in e) t(n, i, { get: e[i], enumerable: true }); }; var c = (n, e, i) => { if (e && typeof e == "object" || typeof e == "function") for (let r of p(e)) !g.call(n, r) && r !== "default" && t(n, r, { get: () => e[r], enumerable: !(i = d(e, r)) || i.enumerable }); return n; }; var u = (n) => c(o(t(n != null ? a(f(n)) : {}, "default", n && n.__esModule && "default" in n ? { get: () => n.default, enumerable: true } : { value: n, enumerable: true })), n); l(exports, { getApi: () => s, isPluginEnabled: () => P }); var v = u(require("obsidian")); var s = (n) => { var e; return n ? (e = n.app.plugins.plugins["obsidian-icon-shortcodes"]) == null ? void 0 : e.api : window.IconSCAPIv0; }; var P = (n) => s(n) !== void 0; } }); // src/main.ts __export(exports, { default: () => FaviconPlugin }); var import_obsidian5 = __toModule(require("obsidian")); // src/settings.ts var import_obsidian3 = __toModule(require("obsidian")); // src/provider.ts var import_obsidian = __toModule(require("obsidian")); var providers = { "google": { name: "Google", url: (domain) => Promise.resolve("https://www.google.com/s2/favicons?domain=" + domain) }, "duckduckgo": { name: "DuckDuckGo", url: (domain) => Promise.resolve("https://icons.duckduckgo.com/ip3/" + domain + ".ico") }, "iconhorse": { name: "Icon Horse", url: (domain) => Promise.resolve("https://icon.horse/icon/" + domain) }, "splitbee": { name: "Splitbee", url: (domain) => Promise.resolve("https://favicon.splitbee.io/?url=" + domain) }, "besticon": { name: "The Favicon Finder", url: (domain, settings) => { const host = settings.provider === "besticon" ? settings.providerDomain : settings.fallbackProviderDomain; return Promise.resolve(host + "/icon?url=" + domain + "&size=32..64..256"); } }, "favicongrabber": { name: "Favicon Grabber", url: (domain) => __async(void 0, null, function* () { const icons = JSON.parse(yield (0, import_obsidian.request)({ method: "GET", url: "https://favicongrabber.com/api/grab/" + domain })); if (icons.length === 0) return Promise.resolve("http://invalid.stuff"); return Promise.resolve(icons.icons[0].src); }) } }; // src/OverwrittenIconModal.ts var import_obsidian2 = __toModule(require("obsidian")); var import_obsidian_icon_shortcodes = __toModule(require_lib()); var OverwrittenIconModal = class extends import_obsidian2.Modal { constructor(plugin, map, name) { super(plugin.app); this.name = "Domain"; this.plugin = plugin; if (name) { this.name = name; } if (map) { this.domain = map.domain; this.icon = map.icon; } } displayPreview(contentEl) { return __async(this, null, function* () { if ((0, import_obsidian_icon_shortcodes.isPluginEnabled)(this.plugin) && this.icon) { contentEl.empty(); const iconPreview = contentEl.createDiv("preview"); iconPreview.addClass("link-favicon-preview"); const iconApi = (0, import_obsidian_icon_shortcodes.getApi)(this.plugin); const icon = iconApi.getIcon(this.icon, false); if (icon !== null) iconPreview.append(icon); } }); } display() { return __async(this, null, function* () { const { contentEl } = this; contentEl.empty(); let previewEL; new import_obsidian2.Setting(contentEl).setName(this.name).addText((text) => { text.setValue(this.domain).onChange((value) => { this.domain = value; }); }); const api = (0, import_obsidian_icon_shortcodes.getApi)(this.plugin); if (api) { if (api.version.compare(">=", "0.6.1")) { new import_obsidian2.Setting(contentEl).setName("Icon").addButton((button) => { button.setButtonText("Choose").onClick(() => __async(this, null, function* () { const icon = yield api.getIconFromUser(); console.log(icon); if (icon) { this.icon = icon.id; if (previewEL) { yield this.displayPreview(previewEL); } } })); }); } else { new import_obsidian2.Setting(contentEl).setName("Icon").addText((text) => { text.setValue(this.icon).onChange((value) => __async(this, null, function* () { this.icon = value; if (previewEL) { yield this.displayPreview(previewEL); } })); }); } } previewEL = contentEl.createDiv("preview"); yield this.displayPreview(previewEL); const footerEl = contentEl.createDiv(); const footerButtons = new import_obsidian2.Setting(footerEl); footerButtons.addButton((b) => { b.setTooltip("Save").setIcon("checkmark").onClick(() => __async(this, null, function* () { this.saved = true; this.close(); })); return b; }); footerButtons.addExtraButton((b) => { b.setIcon("cross").setTooltip("Cancel").onClick(() => { this.saved = false; this.close(); }); return b; }); }); } onOpen() { return __async(this, null, function* () { yield this.display(); }); } }; // src/settings.ts var import_obsidian_icon_shortcodes2 = __toModule(require_lib()); var DEFAULT_SETTINGS = { provider: "duckduckgo", fallbackProvider: "google", providerDomain: "", fallbackProviderDomain: "", ignored: "", overwritten: [], protocol: [] }; var FaviconSettings = class extends import_obsidian3.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; } display() { const { containerEl } = this; containerEl.empty(); containerEl.createEl("h2", { text: "Link Favicons" }); new import_obsidian3.Setting(containerEl).setName("Icon Provider").addDropdown((dropdown) => { for (const id in providers) { if (providers.hasOwnProperty(id)) { dropdown.addOption(id, providers[id].name); } } dropdown.setValue(this.plugin.settings.provider).onChange((value) => __async(this, null, function* () { this.plugin.settings.provider = value; yield this.plugin.saveSettings(); this.display(); })); }); if (Array.of("besticon").includes(this.plugin.settings.provider)) { new import_obsidian3.Setting(containerEl).setName("Provider Domain").setDesc("This Provider is selfhosted, please specify your deployment url. Refer to the readme of the provider for deployment instructions.").addText((text) => text.setValue(this.plugin.settings.providerDomain).onChange((value) => __async(this, null, function* () { this.plugin.settings.providerDomain = value; yield this.plugin.saveSettings(); }))); } new import_obsidian3.Setting(containerEl).setName("Fallback Icon Provider").addDropdown((dropdown) => { for (const id in providers) { if (providers.hasOwnProperty(id)) { dropdown.addOption(id, providers[id].name); } } dropdown.setValue(this.plugin.settings.fallbackProvider).onChange((value) => __async(this, null, function* () { this.plugin.settings.fallbackProvider = value; yield this.plugin.saveSettings(); this.display(); })); }); if (Array.of("besticon").includes(this.plugin.settings.fallbackProvider)) { new import_obsidian3.Setting(containerEl).setName("Fallback Provider Domain").setDesc("This Provider is be selfhosted, please specify your deployment url. Refer to the readme of the provider for deployment instructions.").addText((text) => text.setValue(this.plugin.settings.fallbackProviderDomain).onChange((value) => __async(this, null, function* () { this.plugin.settings.fallbackProviderDomain = value; yield this.plugin.saveSettings(); }))); } new import_obsidian3.Setting(containerEl).setName("Ignored Domains").setDesc("Don't show an favicon for these domains(one per line)").addTextArea((text) => { text.setValue(this.plugin.settings.ignored).onChange((value) => __async(this, null, function* () { this.plugin.settings.ignored = value; yield this.plugin.saveSettings(); })); text.inputEl.setAttr("rows", 8); }); if ((0, import_obsidian_icon_shortcodes2.isPluginEnabled)(this.plugin)) { containerEl.createEl("h2", { text: "Custom icons" }); containerEl.createEl("h3", { text: "for Domains" }); new import_obsidian3.Setting(containerEl).setName("Add New").setDesc("Add custom icon").addButton((button) => { return button.setTooltip("add custom icon").setIcon("plus-with-circle").onClick(() => __async(this, null, function* () { const modal = new OverwrittenIconModal(this.plugin); modal.onClose = () => __async(this, null, function* () { if (modal.saved) { this.plugin.settings.overwritten.push({ domain: modal.domain, icon: modal.icon }); yield this.plugin.saveSettings(); this.display(); } }); modal.open(); })); }); const overwrittenContainer = containerEl.createDiv("overwritten"); const overwrittenDiv = overwrittenContainer.createDiv("overwritten"); for (const overwritten of this.plugin.settings.overwritten) { const setting = new import_obsidian3.Setting(overwrittenDiv); const iconAPI = (0, import_obsidian_icon_shortcodes2.getApi)(this.plugin); const desc = new DocumentFragment(); desc.createEl("p", { text: " " + overwritten.icon }).prepend(iconAPI.getIcon(overwritten.icon)); setting.setName(overwritten.domain).setDesc(desc).addExtraButton((b) => { b.setIcon("pencil").setTooltip("Edit").onClick(() => { const modal = new OverwrittenIconModal(this.plugin, overwritten); modal.onClose = () => __async(this, null, function* () { if (modal.saved) { const setting2 = this.plugin.settings.overwritten.filter((overwritten2) => { return overwritten2.domain !== modal.domain; }); setting2.push({ domain: modal.domain, icon: modal.icon }); this.plugin.settings.overwritten = setting2; yield this.plugin.saveSettings(); this.display(); } }); modal.open(); }); }).addExtraButton((b) => { b.setIcon("trash").setTooltip("Delete").onClick(() => __async(this, null, function* () { this.plugin.settings.overwritten = this.plugin.settings.overwritten.filter((tmp) => { return overwritten.domain !== tmp.domain; }); yield this.plugin.saveSettings(); this.display(); })); }); } containerEl.createEl("h3", { text: "for URI Schemas" }); new import_obsidian3.Setting(containerEl).setName("Add New").setDesc("Add custom icon").addButton((button) => { return button.setTooltip("add custom icon").setIcon("plus-with-circle").onClick(() => __async(this, null, function* () { const modal = new OverwrittenIconModal(this.plugin, null, "URI Schema"); modal.onClose = () => __async(this, null, function* () { if (modal.saved) { this.plugin.settings.protocol.push({ domain: modal.domain, icon: modal.icon }); yield this.plugin.saveSettings(); this.display(); } }); modal.open(); })); }); const protocolContainer = containerEl.createDiv("overwritten"); const voicesDiv = protocolContainer.createDiv("overwritten"); for (const protocol of this.plugin.settings.protocol) { const setting = new import_obsidian3.Setting(voicesDiv); const iconAPI = (0, import_obsidian_icon_shortcodes2.getApi)(this.plugin); const desc = new DocumentFragment(); desc.createEl("p", { text: " " + protocol.icon }).prepend(iconAPI.getIcon(protocol.icon)); setting.setName(protocol.domain).setDesc(desc).addExtraButton((b) => { b.setIcon("pencil").setTooltip("Edit").onClick(() => { const modal = new OverwrittenIconModal(this.plugin, protocol, "URI Schema"); modal.onClose = () => __async(this, null, function* () { if (modal.saved) { const setting2 = this.plugin.settings.protocol.filter((overwritten) => { return overwritten.domain !== modal.domain; }); setting2.push({ domain: modal.domain, icon: modal.icon }); this.plugin.settings.protocol = setting2; yield this.plugin.saveSettings(); this.display(); } }); modal.open(); }); }).addExtraButton((b) => { b.setIcon("trash").setTooltip("Delete").onClick(() => __async(this, null, function* () { this.plugin.settings.protocol = this.plugin.settings.protocol.filter((overwritten) => { return overwritten.domain !== protocol.domain; }); yield this.plugin.saveSettings(); this.display(); })); }); } } } }; // src/main.ts var import_obsidian_icon_shortcodes3 = __toModule(require_lib()); var import_state2 = __toModule(require("@codemirror/state")); // src/Decorations.ts var import_obsidian4 = __toModule(require("obsidian")); var import_view = __toModule(require("@codemirror/view")); var import_state = __toModule(require("@codemirror/state")); var import_language = __toModule(require("@codemirror/language")); var import_stream_parser = __toModule(require("@codemirror/stream-parser")); var statefulDecorations = defineStatefulDecoration(); var StatefulDecorationSet = class { constructor(editor, plugin) { this.decoCache = Object.create(null); this.debouncedUpdate = (0, import_obsidian4.debounce)(this.updateAsyncDecorations, 100, true); this.editor = editor; this.plugin = plugin; } computeAsyncDecorations(tokens) { return __async(this, null, function* () { const decorations = []; for (let token of tokens) { let deco = this.decoCache[token.value]; if (!deco) { const provider = providers[this.plugin.settings.provider]; const fallbackProvider = providers[this.plugin.settings.fallbackProvider]; let url; try { url = new URL(token.value); } catch (e) { console.error("Invalid url: " + token.value); console.error(e); } const icon = yield this.plugin.getIcon(url, provider); const fallbackIcon = yield this.plugin.getIcon(url, fallbackProvider); const domain = url.protocol.contains("http") ? url.hostname : url.protocol; deco = this.decoCache[token.value] = import_view.Decoration.widget({ widget: new IconWidget(this.plugin, icon, fallbackIcon, domain) }); } decorations.push(deco.range(token.from, token.from)); } return import_view.Decoration.set(decorations, true); }); } updateAsyncDecorations(tokens) { return __async(this, null, function* () { const decorations = yield this.computeAsyncDecorations(tokens); if (decorations || this.editor.state.field(statefulDecorations.field).size) { this.editor.dispatch({ effects: statefulDecorations.update.of(decorations || import_view.Decoration.none) }); } }); } }; function buildViewPlugin(plugin) { return import_view.ViewPlugin.fromClass(class { constructor(view) { this.decoManager = new StatefulDecorationSet(view, plugin); this.buildAsyncDecorations(view); } update(update) { if (update.docChanged || update.viewportChanged) { this.buildAsyncDecorations(update.view); } } destroy() { } buildAsyncDecorations(view) { const targetElements = []; for (let { from, to } of view.visibleRanges) { const tree = (0, import_language.syntaxTree)(view.state); tree.iterate({ from, to, enter: (type, from2, to2) => { const tokenProps = type.prop(import_stream_parser.tokenClassNodeProp); if (tokenProps) { const props = new Set(tokenProps.split(" ")); const isExternalLink = props.has("url"); const linkText = view.state.doc.sliceString(from2, to2); if (isExternalLink && linkText.contains("://")) { const line = view.state.doc.lineAt(from2); const toLine = line.to - to2; const toLineT = line.length - toLine; const fromIndex = line.text.lastIndexOf("[", toLineT); if (fromIndex === -1) { return; } const fromTarget = line.from + fromIndex; targetElements.push({ from: fromTarget, to: to2, value: linkText }); } } } }); } this.decoManager.debouncedUpdate(targetElements); } }); } function asyncDecoBuilderExt(plugin) { return [statefulDecorations.field, buildViewPlugin(plugin)]; } function defineStatefulDecoration() { const update = import_state.StateEffect.define(); const field = import_state.StateField.define({ create() { return import_view.Decoration.none; }, update(deco, tr) { return tr.effects.reduce((deco2, effect) => effect.is(update) ? effect.value : deco2, deco.map(tr.changes)); }, provide: (field2) => import_view.EditorView.decorations.from(field2) }); return { update, field }; } var IconWidget = class extends import_view.WidgetType { constructor(plugin, icon, fallbackIcon, domain) { super(); this.plugin = plugin; this.icon = icon; this.fallbackIcon = fallbackIcon; this.domain = domain; } eq(other) { return other == this; } toDOM() { if (!this.icon || this.icon === "") { return document.createElement("span"); } if (typeof this.icon === "string") { if (!this.icon.startsWith("http")) { const span = document.createElement("span"); span.textContent = this.icon; span.className = "link-favicon"; return span; } const el = document.createElement("object"); el.addClass("link-favicon"); el.dataset.host = this.domain; el.data = this.icon; el.data.contains(".ico") ? el.type = "image/x-icon" : el.type = "image/png"; el.style.height = "0.8em"; el.style.display = "inline-block"; if (typeof this.fallbackIcon === "string") { const img = el.createEl("img"); img.src = this.fallbackIcon; img.addClass("link-favicon"); img.style.height = "0.8em"; img.style.display = "block"; el.append(img); } return el; } else { return this.icon; } } ignoreEvent() { return false; } }; // src/main.ts var FaviconPlugin = class extends import_obsidian5.Plugin { isDisabled(el) { if (el.getAttribute("data-no-favicon")) return true; if (el.getAttribute("data-favicon")) return true; } getCustomDomainIcon(domain) { if ((0, import_obsidian_icon_shortcodes3.isPluginEnabled)(this)) { const icons = this.settings.overwritten.filter((value) => value.domain === domain); if (icons.length > 0) { const iconApi = (0, import_obsidian_icon_shortcodes3.getApi)(this); const icon = icons[0].icon; return iconApi.getIcon(icon, false); } } } getCustomSchemeIcon(scheme) { if ((0, import_obsidian_icon_shortcodes3.isPluginEnabled)(this)) { const icons = this.settings.protocol.filter((value) => value.domain === scheme.substr(0, scheme.length - 1)); if (icons.length > 0) { const iconApi = (0, import_obsidian_icon_shortcodes3.getApi)(this); const icon = icons[0].icon; return iconApi.getIcon(icon, false); } } } getIcon(domain, provider) { return __async(this, null, function* () { if (!domain.protocol.startsWith("http")) { const customSchemeIcon = this.getCustomSchemeIcon(domain.protocol); if (customSchemeIcon) { if (typeof customSchemeIcon !== "string") { customSchemeIcon.addClass("link-favicon"); customSchemeIcon.dataset.host = domain.hostname; } return customSchemeIcon; } } if (this.settings.ignored.split("\n").contains(domain.hostname)) { return ""; } const customDomainIcon = this.getCustomDomainIcon(domain.hostname); if (customDomainIcon) { if (typeof customDomainIcon !== "string") { customDomainIcon.addClass("link-favicon"); customDomainIcon.dataset.host = domain.hostname; } return customDomainIcon; } return provider.url(domain.hostname, this.settings); }); } onload() { return __async(this, null, function* () { console.log("enabling plugin: link favicons"); yield this.loadSettings(); this.addSettingTab(new FaviconSettings(this.app, this)); this.registerEditorExtension(import_state2.Prec.lowest(asyncDecoBuilderExt(this))); this.registerMarkdownPostProcessor((element, ctx) => __async(this, null, function* () { if (ctx.sourcePath.contains("no-favicon")) { return; } const provider = providers[this.settings.provider]; const fallbackProvider = providers[this.settings.fallbackProvider]; if (!provider || !fallbackProvider) { console.error("Link Favicons: misconfigured providers"); return; } const links = element.querySelectorAll("a.external-link:not([data-favicon])"); for (let index = 0; index < links.length; index++) { const link = links.item(index); if (!this.isDisabled(link)) { link.dataset.favicon = "true"; let domain; try { domain = new URL(link.href); } catch (e) { console.error("Invalid url: " + link.href); console.error(e); } if (!domain) continue; const icon = yield this.getIcon(domain, provider); const fallbackIcon = yield this.getIcon(domain, fallbackProvider); let el; if (!icon || icon === "") { continue; } if (typeof icon === "string") { if (!icon.startsWith("http")) { el = icon; } else { el = document.createElement("object"); el.addClass("link-favicon"); el.dataset.host = domain.hostname; el.data = icon; el.data.contains(".ico") ? el.type = "image/x-icon" : el.type = "image/png"; el.style.height = "0.8em"; el.style.display = "inline-block"; } } else { el = icon; } if (!el) continue; if (typeof el !== "string" && typeof fallbackIcon === "string") { const img = el.createEl("img"); img.src = fallbackIcon; img.addClass("link-favicon"); img.style.height = "0.8em"; img.style.display = "block"; el.append(img); } if (el) { link.prepend(el); } } } })); }); } onunload() { console.log("disabling plugin: link favicons"); } loadSettings() { return __async(this, null, function* () { this.settings = Object.assign({}, DEFAULT_SETTINGS, yield this.loadData()); }); } saveSettings() { return __async(this, null, function* () { yield this.saveData(this.settings); }); } };