/* THIS IS A GENERATED/BUNDLED FILE BY ESBUILD if you want to view the source, please visit the github repository of this plugin */ var __defProp = Object.defineProperty; var __getOwnPropDesc = Object.getOwnPropertyDescriptor; var __getOwnPropNames = Object.getOwnPropertyNames; var __hasOwnProp = Object.prototype.hasOwnProperty; var __export = (target, all) => { for (var name in all) __defProp(target, name, { get: all[name], enumerable: true }); }; var __copyProps = (to, from, except, desc) => { if (from && typeof from === "object" || typeof from === "function") { for (let key of __getOwnPropNames(from)) if (!__hasOwnProp.call(to, key) && key !== except) __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable }); } return to; }; var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); // src/main.js var main_exports = {}; __export(main_exports, { default: () => main_default }); module.exports = __toCommonJS(main_exports); var import_obsidian4 = require("obsidian"); // src/modals.js var import_obsidian = require("obsidian"); var ReleaseNotesModal = class extends import_obsidian.Modal { constructor(app, plugin, version, releaseNotes2) { super(app); this.plugin = plugin; this.version = version; this.releaseNotes = releaseNotes2; } async onOpen() { const { contentEl } = this; contentEl.empty(); contentEl.createEl("h2", { text: `Welcome to \u{1F9B6} Rich Foot v${this.version}` }); contentEl.createEl("p", { text: "After each update you'll be prompted with the release notes. You can disable this in the plugin settings.", cls: "release-notes-instructions" }); const promotionalLinks = contentEl.createEl("div", { cls: "release-notes-promotional-links" }); const equilllabsLink = promotionalLinks.createEl("a", { href: "https://www.equilllabs.com", target: "_blank" }); equilllabsLink.createEl("img", { attr: { height: "36", style: "border:0px;height:36px;", src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/equilllabs.png?raw=true", border: "0", alt: "eQuill-Labs" } }); const discordLink = promotionalLinks.createEl("a", { href: "https://discord.gg/sp8AQQhMJ7", target: "_blank" }); discordLink.createEl("img", { attr: { height: "36", style: "border:0px;height:36px;", src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/discord.png?raw=true", border: "0", alt: "Discord" } }); const kofiLink = promotionalLinks.createEl("a", { href: "https://ko-fi.com/Z8Z212UMBI", target: "_blank" }); kofiLink.createEl("img", { attr: { height: "36", style: "border:0px;height:36px;", src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/support.png?raw=true", border: "0", alt: "Buy Me a Coffee at ko-fi.com" } }); const notesContainer = contentEl.createDiv("release-notes-container"); await import_obsidian.MarkdownRenderer.render( this.app, this.releaseNotes, notesContainer, "", this ); contentEl.createEl("div", { cls: "release-notes-spacer" }); new import_obsidian.Setting(contentEl).addButton((btn) => btn.setButtonText("Close").onClick(() => this.close())); } onClose() { const { contentEl } = this; contentEl.empty(); } }; // virtual-module:virtual:release-notes var releaseNotes = "
rich foot element at the bottom of notesThis release represents a complete architectural overhaul of the Rich Foot plugin, implementing modern best practices and significant performance improvements.
\nrequestAnimationFrame for all visual updates to eliminate page jittercontain and will-change properties for better rendering performanceregisterEvent exclusivelyonunload (automatic via registration)will-change hintsThis update maintains 100% backwards compatibility with all existing settings and configurations while providing a more robust, performant foundation for future enhancements.
\n"; // src/settings.js var import_obsidian2 = require("obsidian"); // src/utils.js function rgbToHex(color) { if (color.startsWith("hsl")) { const temp = document.createElement("div"); temp.style.color = color; document.body.appendChild(temp); color = getComputedStyle(temp).color; document.body.removeChild(temp); } const rgb = color.match(/\d+/g); if (!rgb || rgb.length < 3) return "#000000"; const [r, g, b] = rgb.slice(0, 3).map((x) => { const val = Math.min(255, Math.max(0, Math.round(parseFloat(x)))); return val.toString(16).padStart(2, "0"); }); return `#${r}${g}${b}`; } function resolveCssVar(cssValue, property = "color") { const temp = document.createElement("div"); temp.style[property] = cssValue; document.body.appendChild(temp); const computed = getComputedStyle(temp)[property]; document.body.removeChild(temp); return rgbToHex(computed); } // src/settings.js var DEFAULT_SETTINGS = { borderWidth: 1, borderStyle: "dashed", borderOpacity: 1, borderRadius: 15, datesOpacity: 1, linksOpacity: 1, showReleaseNotes: true, excludedFolders: [], dateColor: "var(--text-accent)", borderColor: "var(--text-accent)", linkColor: "var(--link-color)", linkBackgroundColor: "var(--tag-background)", linkBorderColor: "rgba(255, 255, 255, 0.204)", customCreatedDateProp: "", customModifiedDateProp: "", dateDisplayFormat: "mmmm dd, yyyy", showBacklinks: true, showOutlinks: true, showDates: true, combineLinks: false, limitLinks: false, linksLimit: 10, footerWidth: "default", footerMaxWidth: 700, updateDelay: 3e3, excludedParentSelectors: [], frontmatterExclusionField: "" }; var RichFootSettingTab = class extends import_obsidian2.PluginSettingTab { constructor(app, plugin) { super(app, plugin); this.plugin = plugin; this.excludedParentSelectors = []; this.frontmatterExclusionField = ""; } display() { var _a, _b; const { containerEl } = this; containerEl.empty(); containerEl.addClass("rich-foot-settings"); containerEl.createEl("div", { cls: "rich-foot-info", text: "\u{1F9B6} Rich Foot adds a footer to your notes with useful information such as backlinks, creation date, and last modified date. Use the settings below to customize the appearance." }); containerEl.createEl("h3", { text: "Visibility Settings" }); new import_obsidian2.Setting(containerEl).setName("Show Backlinks").setDesc("Show backlinks in the footer").addToggle((toggle) => toggle.setValue(this.plugin.settings.showBacklinks).onChange(async (value) => { this.plugin.settings.showBacklinks = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); })); new import_obsidian2.Setting(containerEl).setName("Show Outlinks").setDesc("Show outgoing links in the footer").addToggle((toggle) => toggle.setValue(this.plugin.settings.showOutlinks).onChange(async (value) => { this.plugin.settings.showOutlinks = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); })); new import_obsidian2.Setting(containerEl).setName("Show Combine Links").setDesc("Show backlinks and outlinks in a single combined section (overrides show backlinks and show outlinks settings)").addToggle((toggle) => toggle.setValue(this.plugin.settings.combineLinks).onChange(async (value) => { this.plugin.settings.combineLinks = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); })); new import_obsidian2.Setting(containerEl).setName("Limit Links Shown").setDesc('Limit the number of backlinks/outlinks shown in the footer. Surplus links are hidden behind a "Show More" button that expands them on click.').addToggle((toggle) => toggle.setValue(this.plugin.settings.limitLinks).onChange(async (value) => { this.plugin.settings.limitLinks = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); })); let linksLimitInput; new import_obsidian2.Setting(containerEl).setName("Links Limit").setDesc('Maximum number of links to show before the "Show More" button (only applies when "Limit Links Shown" is enabled)').addText((text) => { linksLimitInput = text; text.setPlaceholder("10").setValue(String(this.plugin.settings.linksLimit)).onChange(async (value) => { const numValue = Math.floor(Number(value)); if (!isNaN(numValue) && numValue > 0) { this.plugin.settings.linksLimit = numValue; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); } }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.linksLimit = DEFAULT_SETTINGS.linksLimit; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); if (linksLimitInput) { linksLimitInput.setValue(String(DEFAULT_SETTINGS.linksLimit)); } })); let updateDelayInput; new import_obsidian2.Setting(containerEl).setName("Rich-foot update delay").setDesc("Delay in milliseconds before updating the rich-foot in edit mode (lower values may impact performance)").addText((text) => { updateDelayInput = text; text.setPlaceholder("3000").setValue(String(this.plugin.settings.updateDelay)).onChange(async (value) => { const numValue = Math.floor(Number(value)); if (!isNaN(numValue) && numValue > 0) { this.plugin.settings.updateDelay = numValue; await this.plugin.saveSettings(); } }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.updateDelay = DEFAULT_SETTINGS.updateDelay; await this.plugin.saveSettings(); if (updateDelayInput) { updateDelayInput.setValue(String(DEFAULT_SETTINGS.updateDelay)); } })); containerEl.createEl("hr"); containerEl.createEl("h3", { text: "Date Settings" }); new import_obsidian2.Setting(containerEl).setName("Show Dates").setDesc("Show creation and modification dates in the footer").addToggle((toggle) => toggle.setValue(this.plugin.settings.showDates).onChange(async (value) => { this.plugin.settings.showDates = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); })); new import_obsidian2.Setting(containerEl).setName("Date Display Format").setDesc("Choose how dates should be displayed in the footer").addDropdown((dropdown) => { const today = /* @__PURE__ */ new Date(); const formats = [ "mm/dd/yyyy", "dd/mm/yyyy", "yyyy-mm-dd", "mmm dd, yyyy", "dd mmm yyyy", "mmmm dd, yyyy", "ddd, mmm dd, yyyy", "dddd, mmmm dd, yyyy", "mm/dd/yy", "dd/mm/yy", "yy-mm-dd", "m/d/yy" ]; formats.forEach((format) => { const example = this.plugin.dataManager.formatDate(today, format); dropdown.addOption(format, `${format} (${example})`); }); dropdown.setValue(this.plugin.settings.dateDisplayFormat).onChange(async (value) => { this.plugin.settings.dateDisplayFormat = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }); new import_obsidian2.Setting(containerEl).setName("Custom Created Date Property").setDesc("Specify a frontmatter property to use for creation date (leave empty to use file creation date)").addText((text) => { text.setValue(this.plugin.settings.customCreatedDateProp).onChange(async (value) => { this.plugin.settings.customCreatedDateProp = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); this.createdDateInput = text; return text; }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.customCreatedDateProp = ""; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); this.createdDateInput.setValue(""); })); new import_obsidian2.Setting(containerEl).setName("Custom Modified Date Property").setDesc("Specify a frontmatter property to use for modification date (leave empty to use file modification date)").addText((text) => { text.setValue(this.plugin.settings.customModifiedDateProp).onChange(async (value) => { this.plugin.settings.customModifiedDateProp = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); this.modifiedDateInput = text; return text; }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.customModifiedDateProp = ""; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); this.modifiedDateInput.setValue(""); })); containerEl.createEl("hr"); containerEl.createEl("h3", { text: "Style Settings" }); let customWidthSetting; let footerMaxWidthSlider; const toggleCustomWidthVisibility = (value) => { if (customWidthSetting) { customWidthSetting.settingEl.style.display = value === "custom" ? "" : "none"; } }; new import_obsidian2.Setting(containerEl).setName("Footer Width").setDesc(`Control how wide the footer is. "Readable line length" locks the footer to Obsidian's readable line width (recommended when your notes use Readable Line Length), and "Custom" lets you set a maximum width in pixels. The footer never grows wider than the note, so it won't cause horizontal scrolling on narrow screens.`).addDropdown((dropdown) => { dropdown.addOptions({ "default": "Default (match content width)", "readable": "Readable line length", "custom": "Custom width (px)" }).setValue(this.plugin.settings.footerWidth).onChange(async (value) => { this.plugin.settings.footerWidth = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); toggleCustomWidthVisibility(value); }); }); let footerMaxWidthDebounce; customWidthSetting = new import_obsidian2.Setting(containerEl).setName("Custom Footer Width").setDesc("Maximum width of the footer in pixels (200-1200px)").addSlider((slider) => { footerMaxWidthSlider = slider; slider.setLimits(200, 1200, 10).setValue(this.plugin.settings.footerMaxWidth).setDynamicTooltip().onChange((value) => { this.plugin.settings.footerMaxWidth = value; document.documentElement.style.setProperty("--rich-foot-content-max-width", value + "px"); if (footerMaxWidthDebounce) clearTimeout(footerMaxWidthDebounce); footerMaxWidthDebounce = setTimeout(async () => { await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }, 300); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.footerMaxWidth = DEFAULT_SETTINGS.footerMaxWidth; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); footerMaxWidthSlider.setValue(DEFAULT_SETTINGS.footerMaxWidth); })); toggleCustomWidthVisibility(this.plugin.settings.footerWidth); let borderWidthSlider; new import_obsidian2.Setting(containerEl).setName("Border Width").setDesc("Adjust the width of the footer border (1-10px)").addSlider((slider) => { borderWidthSlider = slider; slider.setLimits(1, 10, 1).setValue(this.plugin.settings.borderWidth).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.borderWidth = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.borderWidth = DEFAULT_SETTINGS.borderWidth; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); borderWidthSlider.setValue(DEFAULT_SETTINGS.borderWidth); })); let borderStyleDropdown; new import_obsidian2.Setting(containerEl).setName("Border Style").setDesc("Choose the style of the footer border").addDropdown((dropdown) => { borderStyleDropdown = dropdown; dropdown.addOptions({ "solid": "Solid", "dashed": "Dashed", "dotted": "Dotted", "double": "Double", "groove": "Groove", "ridge": "Ridge", "inset": "Inset", "outset": "Outset" }).setValue(this.plugin.settings.borderStyle).onChange(async (value) => { this.plugin.settings.borderStyle = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.borderStyle = DEFAULT_SETTINGS.borderStyle; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); borderStyleDropdown.setValue(DEFAULT_SETTINGS.borderStyle); })); let borderOpacitySlider; new import_obsidian2.Setting(containerEl).setName("Border Opacity").setDesc("Adjust the opacity of the footer border (0-1)").addSlider((slider) => { borderOpacitySlider = slider; slider.setLimits(0, 1, 0.1).setValue(this.plugin.settings.borderOpacity).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.borderOpacity = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.borderOpacity = DEFAULT_SETTINGS.borderOpacity; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); borderOpacitySlider.setValue(DEFAULT_SETTINGS.borderOpacity); })); let borderColorPicker; new import_obsidian2.Setting(containerEl).setName("Border Color").setDesc("Choose the color for the footer border").addColorPicker((color) => { borderColorPicker = color; color.setValue(this.plugin.settings.borderColor.startsWith("var(--") ? resolveCssVar("var(--text-accent)", "borderColor") : this.plugin.settings.borderColor).onChange(async (value) => { this.plugin.settings.borderColor = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.borderColor = DEFAULT_SETTINGS.borderColor; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); borderColorPicker.setValue(resolveCssVar("var(--text-accent)", "borderColor")); })); let borderRadiusSlider; new import_obsidian2.Setting(containerEl).setName("Link Border Radius").setDesc("Adjust the border radius of Backlinks and Outlinks (0-15px)").addSlider((slider) => { borderRadiusSlider = slider; slider.setLimits(0, 15, 1).setValue(this.plugin.settings.borderRadius).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.borderRadius = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.borderRadius = DEFAULT_SETTINGS.borderRadius; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); borderRadiusSlider.setValue(DEFAULT_SETTINGS.borderRadius); })); let linksOpacitySlider; new import_obsidian2.Setting(containerEl).setName("Links Opacity").setDesc("Adjust the opacity of Backlinks and Outlinks (0-1)").addSlider((slider) => { linksOpacitySlider = slider; slider.setLimits(0, 1, 0.1).setValue(this.plugin.settings.linksOpacity).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.linksOpacity = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.linksOpacity = DEFAULT_SETTINGS.linksOpacity; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); linksOpacitySlider.setValue(DEFAULT_SETTINGS.linksOpacity); })); let linkColorPicker; new import_obsidian2.Setting(containerEl).setName("Link Text Color").setDesc("Choose the color for link text").addColorPicker((color) => { linkColorPicker = color; color.setValue(this.plugin.settings.linkColor.startsWith("var(--") ? resolveCssVar("var(--link-color)", "color") : this.plugin.settings.linkColor).onChange(async (value) => { this.plugin.settings.linkColor = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.linkColor = DEFAULT_SETTINGS.linkColor; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); linkColorPicker.setValue(resolveCssVar("var(--link-color)", "color")); })); let linkBackgroundColorPicker; new import_obsidian2.Setting(containerEl).setName("Link Background Color").setDesc("Choose the background color for links").addColorPicker((color) => { linkBackgroundColorPicker = color; color.setValue(this.plugin.settings.linkBackgroundColor.startsWith("var(--") ? resolveCssVar("var(--tag-background)", "backgroundColor") : this.plugin.settings.linkBackgroundColor).onChange(async (value) => { this.plugin.settings.linkBackgroundColor = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.linkBackgroundColor = DEFAULT_SETTINGS.linkBackgroundColor; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); linkBackgroundColorPicker.setValue(resolveCssVar("var(--tag-background)", "backgroundColor")); })); let linkBorderColorPicker; new import_obsidian2.Setting(containerEl).setName("Link Border Color").setDesc("Choose the border color for links").addColorPicker((color) => { linkBorderColorPicker = color; color.setValue(this.plugin.settings.linkBorderColor.startsWith("rgba(") ? resolveCssVar(this.plugin.settings.linkBorderColor, "borderColor") : this.plugin.settings.linkBorderColor).onChange(async (value) => { this.plugin.settings.linkBorderColor = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.linkBorderColor = DEFAULT_SETTINGS.linkBorderColor; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); linkBorderColorPicker.setValue(resolveCssVar(DEFAULT_SETTINGS.linkBorderColor, "borderColor")); })); let datesOpacitySlider; new import_obsidian2.Setting(containerEl).setName("Dates Opacity").setDesc("Adjust the opacity of the Created / Modified Dates (0-1)").addSlider((slider) => { datesOpacitySlider = slider; slider.setLimits(0, 1, 0.1).setValue(this.plugin.settings.datesOpacity).setDynamicTooltip().onChange(async (value) => { this.plugin.settings.datesOpacity = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.datesOpacity = DEFAULT_SETTINGS.datesOpacity; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); datesOpacitySlider.setValue(DEFAULT_SETTINGS.datesOpacity); })); let dateColorPicker; new import_obsidian2.Setting(containerEl).setName("Date Color").setDesc("Choose the color for Created / Modified Dates").addColorPicker((color) => { dateColorPicker = color; color.setValue(this.plugin.settings.dateColor.startsWith("var(--") ? resolveCssVar("var(--text-accent)", "color") : this.plugin.settings.dateColor).onChange(async (value) => { this.plugin.settings.dateColor = value; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.dateColor = DEFAULT_SETTINGS.dateColor; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); dateColorPicker.setValue(resolveCssVar("var(--text-accent)", "color")); })); containerEl.createEl("hr"); containerEl.createEl("h3", { text: "Exclusion Rules" }); containerEl.createEl("h3", { text: "Excluded Folders" }); containerEl.createEl("p", { text: "Notes in excluded folders (and their subfolders) will not display the Rich Foot footer. This is useful for system folders or areas where you don't want footer information to appear.", cls: "setting-item-description" }); const excludedFoldersContainer = containerEl.createDiv("excluded-folders-container"); if ((_a = this.plugin.settings) == null ? void 0 : _a.excludedFolders) { this.plugin.settings.excludedFolders.forEach((folder, index) => { const folderDiv = excludedFoldersContainer.createDiv("excluded-folder-item"); folderDiv.createSpan({ text: folder }); const deleteButton = folderDiv.createEl("button", { text: "Delete", cls: "excluded-folder-delete" }); deleteButton.addEventListener("click", async () => { this.plugin.settings.excludedFolders.splice(index, 1); await this.plugin.saveSettings(); this.display(); }); }); } const newFolderSetting = new import_obsidian2.Setting(containerEl).setName("Add excluded folder").setDesc("Enter a folder path or browse to select").addText((text) => text.setPlaceholder("folder/subfolder")).addButton((button) => button.setButtonText("Browse").onClick(async () => { const folder = await this.browseForFolder(); if (folder) { const textComponent = newFolderSetting.components[0]; textComponent.setValue(folder); } })).addButton((button) => button.setButtonText("Add").onClick(async () => { const textComponent = newFolderSetting.components[0]; const newFolder = textComponent.getValue().trim(); if (newFolder && !this.plugin.settings.excludedFolders.includes(newFolder)) { this.plugin.settings.excludedFolders.push(newFolder); await this.plugin.saveSettings(); textComponent.setValue(""); this.display(); } })); containerEl.createEl("h4", { text: "Exclude Rich Foot via Frontmatter" }); let frontmatterExclusionInput; new import_obsidian2.Setting(containerEl).setName("Frontmatter Exclusion Field").setDesc("If this frontmatter field exists and has a truthy value (true, yes, 1, on), Rich Foot will not be shown on that note").addText((text) => { frontmatterExclusionInput = text; text.setPlaceholder("e.g., exclude-rich-foot").setValue(this.plugin.settings.frontmatterExclusionField).onChange(async (value) => { this.plugin.settings.frontmatterExclusionField = value.trim(); await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); }); }).addButton((button) => button.setButtonText("Reset").onClick(async () => { this.plugin.settings.frontmatterExclusionField = ""; await this.plugin.saveSettings(); await this.plugin.updateRichFoot(); if (frontmatterExclusionInput) { frontmatterExclusionInput.setValue(""); } })); containerEl.createEl("h4", { text: "Excluded Parent Selectors" }); containerEl.createEl("p", { text: "Rich Foot will not be added to notes that have any of these parent selectors in their DOM hierarchy. Useful for compatibility with other plugins.", cls: "setting-item-description" }); const excludedSelectorsContainer = containerEl.createDiv("excluded-selectors-container"); if ((_b = this.plugin.settings) == null ? void 0 : _b.excludedParentSelectors) { this.plugin.settings.excludedParentSelectors.forEach((selector, index) => { const selectorDiv = excludedSelectorsContainer.createDiv("excluded-selector-item"); selectorDiv.createSpan({ text: selector }); const deleteButton = selectorDiv.createEl("button", { text: "Delete", cls: "excluded-selector-delete" }); deleteButton.addEventListener("click", async () => { this.plugin.settings.excludedParentSelectors.splice(index, 1); await this.plugin.saveSettings(); this.display(); }); }); } const newSelectorSetting = new import_obsidian2.Setting(containerEl).setName("Add excluded parent selector").setDesc('Enter a CSS selector (e.g., .some-class, #some-id, [data-type="special"])').addText((text) => text.setPlaceholder("Enter selector").onChange(() => { try { document.querySelector(text.getValue()); text.inputEl.removeClass("rich-foot-input-error"); } catch (e) { text.inputEl.addClass("rich-foot-input-error"); } })).addButton((button) => button.setButtonText("Add").onClick(async () => { const textComponent = newSelectorSetting.components[0]; const newSelector = textComponent.getValue().trim(); if (!newSelector) return; try { document.querySelector(newSelector); } catch (e) { new import_obsidian2.Notice("Invalid CSS selector"); return; } if (!this.plugin.settings.excludedParentSelectors.includes(newSelector)) { this.plugin.settings.excludedParentSelectors.push(newSelector); await this.plugin.saveSettings(); textComponent.setValue(""); this.display(); } })); containerEl.createEl("h3", { text: "Example Screenshot", cls: "rich-foot-example-title" }); const exampleDiv = containerEl.createDiv({ cls: "rich-foot-example" }); exampleDiv.createEl("img", { attr: { src: "https://raw.githubusercontent.com/jparkerweb/rich-foot/refs/heads/main/rich-foot.jpg", alt: "Rich Foot Example" } }); new import_obsidian2.Setting(containerEl).setName("Show Release Notes").setDesc("Show release notes after plugin updates").addToggle((toggle) => toggle.setValue(this.plugin.settings.showReleaseNotes).onChange(async (value) => { this.plugin.settings.showReleaseNotes = value; await this.plugin.saveSettings(); })); new import_obsidian2.Setting(containerEl).setName("View Release Notes").setDesc("View release notes for the current version").addButton((button) => button.setButtonText("View Release Notes").onClick(async () => { const notes = await this.plugin.getReleaseNotes(this.plugin.manifest.version); new ReleaseNotesModal(this.app, this.plugin, this.plugin.manifest.version, notes).open(); })); } async browseForFolder() { const folders = this.app.vault.getAllLoadedFiles().filter((file) => file.children).map((folder) => folder.path); return new Promise((resolve) => { const modal = new FolderSuggestModal(this.app, folders, (result) => { resolve(result); }); modal.open(); }); } }; var FolderSuggestModal = class extends import_obsidian2.FuzzySuggestModal { constructor(app, folders, onChoose) { super(app); this.folders = folders; this.onChoose = onChoose; } getItems() { return this.folders; } getItemText(item) { return item; } onChooseItem(item, evt) { this.onChoose(item); } }; // src/data-manager.js var RichFootDataManager = class { constructor(app) { this.app = app; } /** * Get backlinks for a file * @param {TFile} file - The file to get backlinks for * @returns {Map} Map of backlink paths to their data */ getBacklinks(file) { const resolvedLinks = this.app.metadataCache.resolvedLinks; const backlinks = /* @__PURE__ */ new Map(); for (const sourcePath in resolvedLinks) { const targets = resolvedLinks[sourcePath]; if (targets[file.path]) { backlinks.set(sourcePath, targets[file.path]); } } return backlinks; } /** * Get all outlinks from a file (links, embeds, frontmatter, footnotes) * @param {TFile} file - The file to get outlinks from * @returns {Promise