Upload and choose Video files as banners from your vault
\n
Downloadable \u{1F3AC} Video Banners from the Pixel Banner Plus Collection
\n
\n
\n
Added paging controls to the Pixel Banner Plus Collection
\n
New global Banner Max Width setting to control the default max width for all banners
\n
\n
\u{1F4E6} Updated
\n
\n
Moved Default Saved Banners Folder setting to the General tab
\n
Renamed Pixel Banner Plus Store to Pixel Banner Plus Collection as many items are free
\n
\n
v3.6.1
\n
\u{1F41B} Fixed
\n
\n
Resolved issue with Icon Image selection modal not setting the selected icon image
\n
\n
v3.6.2
\n
\u{1F4E6} Updated
\n
\n
Improved debounce logic to prevent multiple banner reloads when opening a note
\n
\n
v3.6.3
\n
\u2728 Added
\n
\n
Added filesize display to the store modal
\n
\n
v3.6.4
\n
\u2728 Added
\n
\n
Banner images now support local file protocol for images outside of your vault (e.g. file:///C:\\path\\banner.jpg)
\n
\n
\u{1F4E6} Updated
\n
\n
Allow commas in banner filenames
\n
\n
\u{1F41B} Fixed
\n
\n
Ensure pinned banner is the currently displayed image when saving API banners
\n
Ensure banner icons are only rendered when a main banner image is present
\n
Banner Icon Image not always rendered until the note was clicked/focused
\n
\n
v3.6.5
\n
\u{1F41B} Fixed
\n
\n
Fix refresh button to use original comma-separated keywords from frontmatter instead of the cached single keyword
\n
Resolved issue with the default x/y frontmatter fields not being hidden when the "Hide Pixel Banner Fields" option is enabled
\n
Updated API call for Pexels to conform to spec changes on their side
\n
\n
v3.6.6
\n
\u{1F41B} Fixed
\n
\n
New folder group entries now inherit the user's default Content Start Position setting instead of being hardcoded to 150px
\n
\n
v3.6.8
\n
\u2728 Added
\n
\n
Pin Choice Modal: When pinning API images, users can now choose between saving locally or pinning the URL directly to frontmatter
\n
New choice modal presents "Save Image Locally" vs "Pin Image URL" options
\n
URL pinning saves no storage space in vault but requires internet connection
\n
Local saving remains available for offline access and permanence
\n
Choice only appears for user-initiated pin actions (pin icon, command palette)
\n
AI generation and Pixel Banner Plus continue to save locally automatically
\n
\n
\n
Auto-Focus Enhancement: Folder selection modal now automatically focuses and selects the text input for improved workflow
\n
\n
v3.6.7
\n
\u{1F41B} Fixed
\n
\n
Fixed ImageViewModal to properly display banner images and videos when clicking the "Show View Image Icon"
\n
Added support for MP4 and MOV video files in the ImageViewModal with proper video player controls
\n
Correctly display actual image URLs instead of keywords for 3rd party API banners in the ImageViewModal
\n
Local images, videos, and file:/// paths maintain original display behavior
\n
\n
\n
\n
v3.6.8
\n
\u2728 Added
\n
\n
Pin Choice Modal: When pinning API images, users can now choose between saving locally or pinning the URL directly to frontmatter
\n
New choice modal presents "Save Image Locally" vs "Pin Image URL" options
\n
URL pinning saves no storage space in vault but requires internet connection
\n
Local saving remains available for offline access and permanence
\n
Choice only appears for user-initiated pin actions (pin icon, command palette)
\n
AI generation and Pixel Banner Plus continue to save locally automatically
\n
\n
\n
Auto-Focus Enhancement: Folder selection modal now automatically focuses and selects the text input for improved workflow
\n
Enter button support for submitting the save image form in the Save Image Modal
\n
New Pin Image URL option to save API images directly as URL references in frontmatter without downloading to vault
\n
\n
\u{1F4E6} Updated
\n
\n
Replaced manual frontmatter string manipulation with Obsidian's native processFrontMatter API for more reliable metadata updates
\n
\n
v3.6.9
\n
\u2728 Added
\n
\n
New Icon Image Size Multiplier setting to the General settings tab to control the global size of banner icon images
\n
Check for version updates when opening General settings and show update button if available
\n
\n
\u{1F4E6} Updated
\n
\n
Moved AI Model selection from radio buttons to a dropdown for better organization
\n
Changed default banner fade value from -70 to -40
\n
\n
v3.6.10
\n
\u{1F41B} Fixed
\n
\n
Icon images and emojis not being displayed properly
\n
\n
v3.6.11
\n
\u2728 Added
\n
\n
New toggle to turn on/off Pixel Banner Plus in the main Pixel Banner select modal
\n
Plain image format support: Added image option to Image Property Format setting (without brackets), improving compatibility with Make.md and other plugins
\n
\n
\u{1F4E6} Updated
\n
\n
Added unquoted wiki-link support for Image Icons paths (e.g. [[path/icon.png]])
\n
Misc code cleanup
\n
\n
v3.6.12
\n
\u{1F41B} Fixed
\n
\n
Resolved incorrect Pixel Banner Plus Server URL
\n
\n
v3.6.13
\n
\u2728 Added
\n
\n
Support for Multiple Image Reference for new AI Image Generation models
\n
Nano Banana
\n
Seedream 4
\n
\n
\n
\n
v3.6.14
\n
\u{1F41B} Fixed
\n
\n
Resolved issue with .webp images not being displayed
\n
\n
v3.6.15
\n
\u{1F41B} Fixed
\n
\n
Resolved issue with plain paths not working for video files (.mp4, .mov)
\n
\n
v3.6.16
\n
\u{1F41B} Fixed
\n
\n
Fixed an occasional error that could prevent banners from displaying correctly
\n
\n
v3.6.18 - 2026-03-08
\n
\u{1F41B} Fixed
\n
\n
Fixed banner flickering on every keystroke when a note contains frontmatter and uses folder group banners (issue #318)
\n
\n
v3.6.17 - 2026-03-08
\n
\u{1F41B} Fixed
\n
\n
Fixed content start position not applying correctly when changed in settings (issue #297)
\n
\n
v3.6.18 - 2026-03-08
\n
\u{1F41B} Fixed
\n
\n
Fixed banner flickering on every keystroke when a note contains frontmatter and uses folder group banners (issue #318)
\n
\n\n \n\n';
// src/settings/settings.js
var import_obsidian6 = require("obsidian");
// src/settings/tabs/settingsTabAPISettings.js
var import_obsidian = require("obsidian");
async function testPexelsApi(apiKey) {
try {
const response = await fetch(`https://api.pexels.com/v1/search?query=${random20characters()}&per_page=3`, {
headers: {
"Authorization": apiKey
}
});
if (!response.ok) {
throw new Error("\u274C Invalid Pexels API key");
}
const data = await response.json();
return data.photos;
} catch (error) {
return false;
}
}
async function testPixabayApi(apiKey) {
try {
const response = await fetch(`https://pixabay.com/api/?key=${apiKey}&q=test&per_page=3`);
const data = await response.json();
if (data.error) {
throw new Error(data.error);
}
return true;
} catch (error) {
return false;
}
}
async function testFlickrApi(apiKey) {
try {
const response = await fetch(`https://www.flickr.com/services/rest/?method=flickr.test.echo&api_key=${apiKey}&format=json&nojsoncallback=1`);
const data = await response.json();
return data.stat === "ok";
} catch (error) {
return false;
}
}
async function testUnsplashApi(apiKey) {
try {
const response = await fetch("https://api.unsplash.com/photos/random", {
headers: {
"Authorization": `Client-ID ${apiKey}`
}
});
return response.ok;
} catch (error) {
return false;
}
}
function random20characters() {
const characters = "ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789";
let result = "";
for (let i = 0; i < 20; i++) {
result += characters.charAt(Math.floor(Math.random() * characters.length));
}
return result;
}
function createAPISettings(containerEl, plugin) {
const calloutEl = containerEl.createEl("div", { cls: "tab-callout" });
calloutEl.createEl("div", { text: "\u{1F310} Optionally select which 3rd party API provider to use for displaying random images." });
new import_obsidian.Setting(containerEl).setName("API Provider").setDesc("Select the API provider for fetching images").addDropdown((dropdown) => dropdown.addOption("all", "All (Random)").addOption("pexels", "Pexels").addOption("pixabay", "Pixabay").addOption("flickr", "Flickr").addOption("unsplash", "Unsplash").setValue(plugin.settings.apiProvider).onChange(async (value) => {
plugin.settings.apiProvider = value;
await plugin.saveSettings();
plugin.settingTab.display();
}));
new import_obsidian.Setting(containerEl).setName("Pexels API Key");
containerEl.createEl("span", { text: "Enter your Pexels API key. Get your API key from ", cls: "setting-item-description" }).createEl("a", { href: "https://www.pexels.com/api/", text: "Pexels API" });
const pexelsApiKeySetting = new import_obsidian.Setting(containerEl).setClass("full-width-control").addText((text) => {
text.setPlaceholder("Pexels API key").setValue(plugin.settings.pexelsApiKey).onChange(async (value) => {
plugin.settings.pexelsApiKey = value;
await plugin.saveSettings();
});
text.inputEl.style.width = "calc(100% - 100px)";
}).addButton((button) => button.setButtonText("Test API").onClick(async () => {
const apiKey = plugin.settings.pexelsApiKey;
if (!apiKey) {
new import_obsidian.Notice("Please enter an API key first");
return;
}
button.setButtonText("Testing...");
button.setDisabled(true);
const isValid = await testPexelsApi(apiKey);
button.setButtonText("Test API");
button.setDisabled(false);
new import_obsidian.Notice(isValid ? "\u2705 Pexels API key is valid!" : "\u274C Invalid Pexels API key");
}));
pexelsApiKeySetting.settingEl.style.width = "100%";
new import_obsidian.Setting(containerEl).setName("Pixabay API Key");
containerEl.createEl("span", { text: "Enter your Pixabay API key. Get your API key from ", cls: "setting-item-description" }).createEl("a", { href: "https://pixabay.com/api/docs/", text: "Pixabay API" });
const pixabayApiKeySetting = new import_obsidian.Setting(containerEl).setClass("full-width-control").addText((text) => {
text.setPlaceholder("Pixabay API key").setValue(plugin.settings.pixabayApiKey).onChange(async (value) => {
plugin.settings.pixabayApiKey = value;
await plugin.saveSettings();
});
text.inputEl.style.width = "calc(100% - 100px)";
}).addButton((button) => button.setButtonText("Test API").onClick(async () => {
const apiKey = plugin.settings.pixabayApiKey;
if (!apiKey) {
new import_obsidian.Notice("Please enter an API key first");
return;
}
button.setButtonText("Testing...");
button.setDisabled(true);
const isValid = await testPixabayApi(apiKey);
button.setButtonText("Test API");
button.setDisabled(false);
new import_obsidian.Notice(isValid ? "\u2705 Pixabay API key is valid!" : "\u274C Invalid Pixabay API key");
}));
pixabayApiKeySetting.settingEl.style.width = "100%";
new import_obsidian.Setting(containerEl).setName("Flickr API Key");
containerEl.createEl("span", { text: "Enter your Flickr API key. Get your API key from ", cls: "setting-item-description" }).createEl("a", { href: "https://www.flickr.com/services/api/", text: "Flickr API" });
const flickrApiKeySetting = new import_obsidian.Setting(containerEl).setClass("full-width-control").addText((text) => {
text.setPlaceholder("Flickr API key").setValue(plugin.settings.flickrApiKey).onChange(async (value) => {
plugin.settings.flickrApiKey = value;
await plugin.saveSettings();
});
text.inputEl.style.width = "calc(100% - 100px)";
}).addButton((button) => button.setButtonText("Test API").onClick(async () => {
const apiKey = plugin.settings.flickrApiKey;
if (!apiKey) {
new import_obsidian.Notice("Please enter an API key first");
return;
}
button.setButtonText("Testing...");
button.setDisabled(true);
const isValid = await testFlickrApi(apiKey);
button.setButtonText("Test API");
button.setDisabled(false);
new import_obsidian.Notice(isValid ? "\u2705 Flickr API key is valid!" : "\u274C Invalid Flickr API key");
}));
new import_obsidian.Setting(containerEl).setName("Unsplash API Key");
containerEl.createEl("span", { text: "Enter your Unsplash API key (Access Key). Get your API key from ", cls: "setting-item-description" }).createEl("a", { href: "https://unsplash.com/oauth/applications", text: "Unsplash API" });
const unsplashApiKeySetting = new import_obsidian.Setting(containerEl).setClass("full-width-control").addText((text) => {
text.setPlaceholder("Unsplash API key").setValue(plugin.settings.unsplashApiKey).onChange(async (value) => {
plugin.settings.unsplashApiKey = value;
await plugin.saveSettings();
});
text.inputEl.style.width = "calc(100% - 100px)";
}).addButton((button) => button.setButtonText("Test API").onClick(async () => {
const apiKey = plugin.settings.unsplashApiKey;
if (!apiKey) {
new import_obsidian.Notice("Please enter an API key first");
return;
}
button.setButtonText("Testing...");
button.setDisabled(true);
const isValid = await testUnsplashApi(apiKey);
button.setButtonText("Test API");
button.setDisabled(false);
new import_obsidian.Notice(isValid ? "\u2705 Unsplash API key is valid!" : "\u274C Invalid Unsplash API key");
}));
const exampleContainer = containerEl.createEl("div", {
attr: {
style: `
margin: 20px 0;
padding: 20px;
border: 1px solid var(--modal-border-color);
border-radius: 7px;
`
}
});
exampleContainer.createEl("div", {
text: "Usage Example",
attr: {
style: `
font-weight: bold;
font-size: 1.1rem;
color: var(--color-accent);
`
}
});
exampleContainer.createEl("div", {
text: "Add keyword(s) to the banner frontmatter to display random API images matching the keywords.",
attr: {
style: `
margin: 10px 0;
`
}
});
exampleContainer.createEl("img", {
attr: {
src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/3rd-party-apis-example.jpg",
style: `
width: auto;
max-width: 100%;
`
}
});
new import_obsidian.Setting(containerEl).setName("Images").setDesc("Configure settings for images fetched from API. These settings apply when using keywords to fetch random images.").setHeading();
new import_obsidian.Setting(containerEl).setName("Show Pin Icon").setDesc("Show a pin icon on random banner images that allows saving them to your vault. Once pinned, your frontmatter will be updated to use the local image instead of the API image.").addToggle((toggle) => toggle.setValue(plugin.settings.showPinIcon).onChange(async (value) => {
plugin.settings.showPinIcon = value;
refreshIconSetting.settingEl.style.display = value ? "flex" : "none";
await plugin.saveSettings();
}));
new import_obsidian.Setting(containerEl).setName("Pinned Image Filename").setDesc("Set the default filename for pinned images.").addText((text) => text.setPlaceholder("pixel-banner-image").setValue(plugin.settings.pinnedImageFilename).onChange(async (value) => {
plugin.settings.pinnedImageFilename = value;
await plugin.saveSettings();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
plugin.settings.pinnedImageFilename = DEFAULT_SETTINGS.pinnedImageFilename;
await plugin.saveSettings();
const textInput = button.extraSettingsEl.parentElement.querySelector("input");
textInput.value = DEFAULT_SETTINGS.pinnedImageFilename;
}));
const refreshIconSetting = new import_obsidian.Setting(containerEl).setName("Show Refresh Icon").setDesc("Show a refresh icon on random banner images that allows fetching a new random image.").addToggle((toggle) => toggle.setValue(plugin.settings.showRefreshIcon).onChange(async (value) => {
plugin.settings.showRefreshIcon = value;
await plugin.saveSettings();
}));
refreshIconSetting.settingEl.style.display = plugin.settings.showPinIcon ? "flex" : "none";
new import_obsidian.Setting(containerEl).setName("Size").setDesc("Select the size of the image - (API only)").addDropdown((dropdown) => dropdown.addOption("small", "Small").addOption("medium", "Medium").addOption("large", "Large").setValue(plugin.settings.imageSize).onChange(async (value) => {
plugin.settings.imageSize = value;
await plugin.saveSettings();
}));
new import_obsidian.Setting(containerEl).setName("Orientation").setDesc("Select the orientation of the image - (API only)").addDropdown((dropdown) => dropdown.addOption("landscape", "Landscape").addOption("portrait", "Portrait").addOption("square", "Square").setValue(plugin.settings.imageOrientation).onChange(async (value) => {
plugin.settings.imageOrientation = value;
await plugin.saveSettings();
}));
new import_obsidian.Setting(containerEl).setName("Number of images").setDesc("Enter the number of random images to fetch (3-50) - (API only)").addText((text) => text.setPlaceholder("10").setValue(String(plugin.settings.numberOfImages || 10)).onChange(async (value) => {
let numValue = Number(value);
if (!isNaN(numValue)) {
numValue = Math.max(3, Math.min(numValue, 50));
plugin.settings.numberOfImages = numValue;
await plugin.saveSettings();
}
})).then((setting) => {
const inputEl = setting.controlEl.querySelector("input");
inputEl.type = "number";
inputEl.min = "3";
inputEl.max = "50";
inputEl.style.width = "50px";
});
const defaultKeywordsSetting = new import_obsidian.Setting(containerEl).setName("Default keywords").setDesc("Enter a comma-separated list of default keywords to be used when no keyword is provided in the frontmatter, or when the provided keyword does not return any results. - (API only)").addTextArea((text) => {
text.setPlaceholder("Enter keywords, separated by commas").setValue(plugin.settings.defaultKeywords).onChange(async (value) => {
plugin.settings.defaultKeywords = value;
await plugin.saveSettings();
});
text.inputEl.style.width = "100%";
text.inputEl.style.marginTop = "15px";
text.inputEl.style.height = "90px";
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
plugin.settings.defaultKeywords = DEFAULT_SETTINGS.defaultKeywords;
await plugin.saveSettings();
plugin.settingTab.display();
}));
defaultKeywordsSetting.settingEl.dataset.id = "defaultKeywords";
defaultKeywordsSetting.settingEl.style.display = "flex";
defaultKeywordsSetting.settingEl.style.flexDirection = "column";
}
// src/settings/tabs/settingsTabFolderImages.js
var import_obsidian2 = require("obsidian");
function createFolderSettings(containerEl, plugin) {
const calloutEl = containerEl.createEl("div", { cls: "tab-callout" });
calloutEl.createEl("div", { text: "\u{1F5C3}\uFE0F Configure banner settings for specific folders. These settings will override the default settings for all notes in the specified folder." });
const folderImagesContainer = containerEl.createEl("div", { cls: "folder-images-container" });
plugin.settings.folderImages.forEach((folderImage, index) => {
new FolderImageSetting(
folderImagesContainer,
plugin,
folderImage,
index,
() => updateFolderSettings(containerEl, plugin)
);
});
const addFolderImageSetting = new import_obsidian2.Setting(containerEl).setClass("add-folder-image-setting").addButton((button) => button.setButtonText("Add Folder Image").onClick(async () => {
const newFolderImage = {
folder: "",
image: "",
imageDisplay: plugin.settings.imageDisplay,
imageRepeat: plugin.settings.imageRepeat,
yPosition: plugin.settings.yPosition,
xPosition: plugin.settings.xPosition,
contentStartPosition: plugin.settings.contentStartPosition,
bannerHeight: plugin.settings.bannerHeight,
fade: plugin.settings.fade,
borderRadius: plugin.settings.borderRadius,
titleColor: "var(--inline-title-color)",
directChildrenOnly: false,
enableImageShuffle: false,
shuffleFolder: ""
};
plugin.settings.folderImages.push(newFolderImage);
await plugin.saveSettings();
updateFolderSettings(containerEl, plugin);
}));
}
function updateFolderSettings(containerEl, plugin) {
containerEl.empty();
createFolderSettings(containerEl, plugin);
}
var FolderImageSetting = class extends import_obsidian2.Setting {
constructor(containerEl, plugin, folderImage, index, onDelete) {
super(containerEl);
this.plugin = plugin;
this.folderImage = folderImage;
this.index = index;
this.onDelete = onDelete;
this.setClass("folder-image-setting");
this.settingEl.empty();
const folderImageDeleteContainer = this.settingEl.createDiv("folder-image-delete-container");
this.addDeleteButton(folderImageDeleteContainer);
const infoEl = this.settingEl.createDiv("setting-item-info");
infoEl.createDiv("setting-item-name");
infoEl.createDiv("setting-item-description");
this.addFolderInput();
this.addImageInput();
this.addImageDisplaySettings();
this.addYPostionAndContentStart();
this.addFadeAndBannerHeight();
const controlEl = this.settingEl.createDiv("setting-item-control full-width-control");
this.addContentStartInput(controlEl);
this.addBorderRadiusInput(controlEl);
const controlEl2 = this.settingEl.createDiv("setting-item-control full-width-control");
this.addColorSettings(controlEl2);
this.addBannerIconSettings();
this.addDirectChildrenOnlyToggle();
}
addDeleteButton(containerEl) {
const deleteButton = containerEl.createEl("button", { cls: "pixel-banner-setting--delete-button" });
deleteButton.style.marginLeft = "20px";
deleteButton.style.width = "30px";
deleteButton.style.height = "30px";
deleteButton.style.padding = "0";
deleteButton.innerHTML = ``;
deleteButton.addEventListener("click", async () => {
this.plugin.settings.folderImages.splice(this.index, 1);
await this.plugin.saveSettings();
this.settingEl.remove();
if (this.onDelete) {
this.onDelete();
}
});
}
addFolderInput() {
const folderInputContainer = this.settingEl.createDiv("folder-input-container");
const folderInput = new import_obsidian2.Setting(folderInputContainer).setName("Folder Path").addText((text) => {
text.setValue(this.folderImage.folder || "").onChange(async (value) => {
this.folderImage.folder = value;
await this.plugin.saveSettings();
});
this.folderInputEl = text.inputEl;
this.folderInputEl.style.width = "300px";
});
folderInput.addButton((button) => button.setButtonText("Browse").onClick(() => {
new FolderSuggestModal(this.plugin.app, (chosenPath) => {
this.folderImage.folder = chosenPath;
this.folderInputEl.value = chosenPath;
this.plugin.saveSettings();
}).open();
}));
const shuffleContainer = this.settingEl.createDiv("shuffle-container");
const shuffleToggle = new import_obsidian2.Setting(shuffleContainer).setName("Enable Image Shuffle").setDesc("Randomly select an image from a specified folder each time the note loads").addToggle((toggle) => {
toggle.setValue(this.folderImage.enableImageShuffle || false).onChange(async (value) => {
this.folderImage.enableImageShuffle = value;
if (value) {
shuffleFolderInput.settingEl.style.display = "flex";
this.imageInputContainer.style.display = "none";
} else {
shuffleFolderInput.settingEl.style.display = "none";
this.imageInputContainer.style.display = "block";
}
await this.plugin.saveSettings();
});
});
const shuffleFolderInput = new import_obsidian2.Setting(shuffleContainer).setName("Image Shuffle Folder").setDesc("Folder containing images to randomly select from").addText((text) => {
text.setValue(this.folderImage.shuffleFolder || "").onChange(async (value) => {
this.folderImage.shuffleFolder = value;
await this.plugin.saveSettings();
});
text.inputEl.style.width = "300px";
});
shuffleFolderInput.addButton((button) => button.setButtonText("Browse").onClick(() => {
new FolderSuggestModal(this.plugin.app, (chosenPath) => {
this.folderImage.shuffleFolder = chosenPath;
shuffleFolderInput.controlEl.querySelector("input").value = chosenPath;
this.plugin.saveSettings();
}).open();
}));
if (!this.folderImage.enableImageShuffle) {
shuffleFolderInput.settingEl.style.display = "none";
}
}
addImageInput() {
this.imageInputContainer = this.settingEl.createDiv("folder-input-container");
if (this.folderImage.enableImageShuffle) {
this.imageInputContainer.style.display = "none";
}
const imageInput = new import_obsidian2.Setting(this.imageInputContainer).setName("Image URL or Keyword").addText((text) => {
text.setValue(this.folderImage.image || "").onChange(async (value) => {
this.folderImage.image = value;
await this.plugin.saveSettings();
});
this.imageInputEl = text.inputEl;
this.imageInputEl.style.width = "306px";
});
}
addImageDisplaySettings(containerEl) {
const displayContainer = this.settingEl.createDiv("display-and-repeat-container");
const displaySetting = new import_obsidian2.Setting(displayContainer).setName("Image Display").addDropdown((dropdown) => {
dropdown.addOption("auto", "Auto").addOption("cover", "Cover").addOption("contain", "Contain").setValue(this.folderImage.imageDisplay || "cover").onChange(async (value) => {
this.folderImage.imageDisplay = value;
await this.plugin.saveSettings();
});
dropdown.selectEl.style.marginRight = "20px";
});
const repeatSetting = new import_obsidian2.Setting(displayContainer).setName("repeat").addToggle((toggle) => {
toggle.setValue(this.folderImage.imageRepeat || false).onChange(async (value) => {
this.folderImage.imageRepeat = value;
await this.plugin.saveSettings();
});
});
const toggleEl = repeatSetting.controlEl.querySelector(".checkbox-container");
if (toggleEl) toggleEl.style.justifyContent = "flex-start";
}
addYPostionAndContentStart() {
const controlEl = this.settingEl.createDiv("setting-item-control full-width-control");
this.addYPositionInput(controlEl);
this.addXPositionInput(controlEl);
}
addFadeAndBannerHeight() {
const controlEl = this.settingEl.createDiv("setting-item-control full-width-control");
this.addFadeInput(controlEl);
this.addBannerHeightInput(controlEl);
}
addYPositionInput(containerEl) {
const label = containerEl.createEl("label", { text: "Y-Position", cls: "setting-item-name__label" });
const sliderContainer = containerEl.createEl("div", { cls: "slider-container" });
const slider = sliderContainer.createEl("input", {
type: "range",
cls: "slider",
attr: {
min: "0",
max: "100",
step: "1"
}
});
slider.value = this.folderImage.yPosition || "50";
slider.style.width = "100px";
slider.style.marginLeft = "10px";
const valueDisplay = sliderContainer.createEl("div", { cls: "slider-value" });
valueDisplay.style.marginLeft = "10px";
const updateValueDisplay = (value) => {
valueDisplay.textContent = value;
};
updateValueDisplay(slider.value);
slider.addEventListener("input", (event) => {
updateValueDisplay(event.target.value);
});
slider.addEventListener("change", async () => {
this.folderImage.yPosition = parseInt(slider.value);
await this.plugin.saveSettings();
});
label.appendChild(sliderContainer);
containerEl.appendChild(label);
}
addXPositionInput(containerEl) {
const label = containerEl.createEl("label", { text: "X-Position", cls: "setting-item-name__label" });
label.style.marginLeft = "20px";
const sliderContainer = containerEl.createEl("div", { cls: "slider-container" });
const slider = sliderContainer.createEl("input", {
type: "range",
cls: "slider",
attr: {
min: "0",
max: "100",
step: "1"
}
});
slider.value = this.folderImage.xPosition || "50";
slider.style.width = "100px";
slider.style.marginLeft = "10px";
const valueDisplay = sliderContainer.createEl("div", { cls: "slider-value" });
valueDisplay.style.marginLeft = "10px";
const updateValueDisplay = (value) => {
valueDisplay.textContent = value;
};
updateValueDisplay(slider.value);
slider.addEventListener("input", (event) => {
updateValueDisplay(event.target.value);
});
slider.addEventListener("change", async () => {
this.folderImage.xPosition = parseInt(slider.value);
await this.plugin.saveSettings();
});
label.appendChild(sliderContainer);
containerEl.appendChild(label);
}
addBannerHeightInput(containerEl) {
const label = containerEl.createEl("label", { text: "Banner Height", cls: "setting-item-name__label" });
label.style.marginLeft = "20px";
const heightInput = containerEl.createEl("input", {
type: "number",
attr: {
min: "0",
max: "1280"
}
});
heightInput.style.width = "50px";
heightInput.style.marginLeft = "10px";
heightInput.value = this.folderImage.bannerHeight || "";
heightInput.placeholder = String(this.plugin.settings.bannerHeight || 350);
heightInput.addEventListener("change", async () => {
let value = heightInput.value ? parseInt(heightInput.value) : null;
if (value !== null) {
value = Math.max(0, Math.min(1280, value));
this.folderImage.bannerHeight = value;
heightInput.value = value;
} else {
delete this.folderImage.bannerHeight;
heightInput.value = "";
}
await this.plugin.saveSettings();
});
label.appendChild(heightInput);
containerEl.appendChild(label);
}
addFadeInput(containerEl) {
const label = containerEl.createEl("label", { text: "Fade", cls: "setting-item-name__label" });
const sliderContainer = containerEl.createEl("div", { cls: "slider-container" });
const slider = sliderContainer.createEl("input", {
type: "range",
cls: "slider",
attr: {
min: "-1500",
max: "100",
step: "5"
}
});
slider.value = this.folderImage.fade !== void 0 ? this.folderImage.fade : "-75";
slider.style.width = "100px";
slider.style.marginLeft = "10px";
const valueDisplay = sliderContainer.createEl("div", { cls: "slider-value" });
valueDisplay.style.marginLeft = "10px";
const updateValueDisplay = (value) => {
valueDisplay.textContent = value;
};
updateValueDisplay(slider.value);
slider.addEventListener("input", (event) => {
updateValueDisplay(event.target.value);
});
slider.addEventListener("change", async () => {
this.folderImage.fade = parseInt(slider.value);
await this.plugin.saveSettings();
});
label.appendChild(sliderContainer);
containerEl.appendChild(label);
}
addColorSettings(containerEl) {
const colorContainer = containerEl.createDiv("color-settings-container");
new import_obsidian2.Setting(colorContainer).setName("Inline Title Color").addColorPicker((color) => color.setValue((() => {
const currentColor = this.folderImage.titleColor || this.plugin.settings.titleColor;
if (currentColor.startsWith("var(--")) {
const temp = document.createElement("div");
temp.style.color = currentColor;
document.body.appendChild(temp);
const computedColor = getComputedStyle(temp).color;
document.body.removeChild(temp);
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
const [_, r, g, b] = rgbMatch;
const hexColor = "#" + parseInt(r).toString(16).padStart(2, "0") + parseInt(g).toString(16).padStart(2, "0") + parseInt(b).toString(16).padStart(2, "0");
return hexColor;
}
return "#000000";
}
return currentColor;
})()).onChange(async (value) => {
this.folderImage.titleColor = value;
await this.plugin.saveSettings();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
this.folderImage.titleColor = this.plugin.settings.titleColor;
await this.plugin.saveSettings();
const colorPickerEl = button.extraSettingsEl.parentElement.querySelector('input[type="color"]');
if (colorPickerEl) {
const currentColor = this.plugin.settings.titleColor;
if (currentColor.startsWith("var(--")) {
const temp = document.createElement("div");
temp.style.color = currentColor;
document.body.appendChild(temp);
const computedColor = getComputedStyle(temp).color;
document.body.removeChild(temp);
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
const [_, r, g, b] = rgbMatch;
const hexColor = "#" + parseInt(r).toString(16).padStart(2, "0") + parseInt(g).toString(16).padStart(2, "0") + parseInt(b).toString(16).padStart(2, "0");
colorPickerEl.value = hexColor;
}
} else {
colorPickerEl.value = currentColor;
}
}
}));
}
addDirectChildrenOnlyToggle() {
new import_obsidian2.Setting(this.settingEl).setName("Direct Children Only").setDesc("Apply banner only to direct children of the folder").addToggle((toggle) => {
toggle.setValue(this.folderImage.directChildrenOnly || false).onChange(async (value) => {
this.folderImage.directChildrenOnly = value;
await this.plugin.saveSettings();
});
});
}
addContentStartInput(containerEl) {
var _a;
const label = containerEl.createEl("label", { text: "Content Start", cls: "setting-item-name__label" });
label.style.marginRight = "20px";
const contentStartInput = containerEl.createEl("input", {
type: "number",
attr: {
min: "0"
}
});
contentStartInput.style.width = "50px";
contentStartInput.style.marginLeft = "10px";
contentStartInput.value = (_a = this.folderImage.contentStartPosition) != null ? _a : "355";
contentStartInput.addEventListener("change", async () => {
this.folderImage.contentStartPosition = parseInt(contentStartInput.value);
await this.plugin.saveSettings();
});
label.appendChild(contentStartInput);
containerEl.appendChild(label);
}
addBorderRadiusInput(containerEl) {
var _a;
const label = containerEl.createEl("label", { text: "Border Radius", cls: "setting-item-name__label" });
const radiusInput = containerEl.createEl("input", {
type: "number",
attr: {
min: "0",
max: "50"
}
});
radiusInput.style.width = "50px";
radiusInput.style.marginLeft = "10px";
radiusInput.value = (_a = this.folderImage.borderRadius) != null ? _a : "";
radiusInput.placeholder = String(this.plugin.settings.borderRadius || 17);
radiusInput.addEventListener("change", async () => {
let value = radiusInput.value ? parseInt(radiusInput.value) : null;
if (value !== null) {
value = Math.max(0, Math.min(50, value));
this.folderImage.borderRadius = value;
radiusInput.value = String(value);
} else {
delete this.folderImage.borderRadius;
radiusInput.value = "";
}
await this.plugin.saveSettings();
});
label.appendChild(radiusInput);
containerEl.appendChild(label);
}
addBannerIconSettings() {
const controlEl1 = this.settingEl.createDiv("setting-item-control full-width-control");
new import_obsidian2.Setting(controlEl1).setName("Icon Size").addSlider((slider) => slider.setLimits(10, 200, 1).setValue(this.folderImage.bannerIconSize || this.plugin.settings.bannerIconSize).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconSize = value;
await this.plugin.saveSettings();
}));
new import_obsidian2.Setting(controlEl1).setName("Icon X Position").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.folderImage.bannerIconXPosition || this.plugin.settings.bannerIconXPosition).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconXPosition = value;
await this.plugin.saveSettings();
}));
const controlEl2 = this.settingEl.createDiv("setting-item-control full-width-control");
new import_obsidian2.Setting(controlEl2).setName("Icon Opacity").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.folderImage.bannerIconOpacity || this.plugin.settings.bannerIconOpacity).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconOpacity = value;
await this.plugin.saveSettings();
}));
new import_obsidian2.Setting(controlEl2).setName("Icon Color").addText((text) => {
text.setPlaceholder("(e.g., #ffffff or white)").setValue(this.folderImage.bannerIconColor || this.plugin.settings.bannerIconColor).onChange(async (value) => {
this.folderImage.bannerIconColor = value;
await this.plugin.saveSettings();
});
text.inputEl.style.width = "160px";
});
const controlEl3 = this.settingEl.createDiv("setting-item-control full-width-control");
new import_obsidian2.Setting(controlEl3).setName("Icon Font Weight").addDropdown((dropdown) => {
dropdown.addOption("lighter", "Lighter").addOption("normal", "Normal").addOption("bold", "Bold").setValue(this.folderImage.bannerIconFontWeight || this.plugin.settings.bannerIconFontWeight).onChange(async (value) => {
this.folderImage.bannerIconFontWeight = value;
await this.plugin.saveSettings();
});
});
new import_obsidian2.Setting(controlEl3).setName("Icon BG Color").addText((text) => {
text.setPlaceholder("(e.g., #ffffff or transparent)").setValue(this.folderImage.bannerIconBackgroundColor || this.plugin.settings.bannerIconBackgroundColor).onChange(async (value) => {
this.folderImage.bannerIconBackgroundColor = value;
await this.plugin.saveSettings();
});
text.inputEl.style.width = "160px";
});
const controlEl4 = this.settingEl.createDiv("setting-item-control full-width-control");
new import_obsidian2.Setting(controlEl4).setName("Icon Padding X").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.folderImage.bannerIconPaddingX || this.plugin.settings.bannerIconPaddingX).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconPaddingX = value;
await this.plugin.saveSettings();
}));
new import_obsidian2.Setting(controlEl4).setName("Icon Padding Y").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(this.folderImage.bannerIconPaddingY || this.plugin.settings.bannerIconPaddingY).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconPaddingY = value;
await this.plugin.saveSettings();
}));
const controlEl5 = this.settingEl.createDiv("setting-item-control full-width-control");
new import_obsidian2.Setting(controlEl5).setName("Icon Border Radius").addSlider((slider) => slider.setLimits(0, 50, 1).setValue(this.folderImage.bannerIconBorderRadius || this.plugin.settings.bannerIconBorderRadius).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconBorderRadius = value;
await this.plugin.saveSettings();
}));
new import_obsidian2.Setting(controlEl5).setName("Icon Vertical Offset").addSlider((slider) => slider.setLimits(-100, 100, 1).setValue(this.folderImage.bannerIconVerticalOffset || this.plugin.settings.bannerIconVerticalOffset).setDynamicTooltip().onChange(async (value) => {
this.folderImage.bannerIconVerticalOffset = value;
await this.plugin.saveSettings();
}));
}
};
// src/settings/tabs/settingsTabCustomFieldNames.js
var import_obsidian3 = require("obsidian");
function arrayToString(arr) {
return Array.isArray(arr) ? arr.join(", ") : arr;
}
function stringToArray(str) {
return str.split(",").map((s) => s.trim()).filter((s) => s.length > 0);
}
function validateFieldNames(settings, allFields, currentField, newNames) {
const validNamePattern = /^[a-zA-Z0-9_-]+$/;
const invalidNames = newNames.filter((name) => !validNamePattern.test(name));
if (invalidNames.length > 0) {
return {
isValid: false,
message: `Invalid characters in field names (only letters, numbers, dashes, and underscores allowed): ${invalidNames.join(", ")}`
};
}
const otherFields = allFields.filter((f) => f !== currentField);
const otherFieldNames = otherFields.flatMap((f) => settings[f]);
const duplicates = newNames.filter((name) => otherFieldNames.includes(name));
if (duplicates.length > 0) {
return {
isValid: false,
message: `Duplicate field names found: ${duplicates.join(", ")}`
};
}
return { isValid: true };
}
function createCustomFieldsSettings(containerEl, plugin) {
const calloutEl = containerEl.createEl("div", { cls: "tab-callout" });
calloutEl.createEl("div", { text: '\u{1F5FA}\uFE0F Customize the frontmatter field names used for Pixel Banner. You can define multiple names for each field, separated by commas. Field names can only contain letters, numbers, dashes, and underscores. Example: "banner, pixel-banner, header_image" could all be used as the banner field name.' });
const customFields = [
{
setting: "customBannerField",
name: "Banner Field Names",
desc: "Set custom field names for the banner in frontmatter",
values: '[[image.png]], "images/image.jpg"',
placeholder: "banner, pixel-banner, header-image"
},
{
setting: "customYPositionField",
name: "Y-Position Field Names",
desc: "Set custom field names for the Y-position in frontmatter",
values: "5, 70, 100",
placeholder: "banner-y, y-position, banner-offset"
},
{
setting: "customXPositionField",
name: "X-Position Field Names",
desc: "Set custom field names for the X-position in frontmatter",
values: "0, 30, 90",
placeholder: "banner-x, x-position, banner-offset-x"
},
{
setting: "customContentStartField",
name: "Content Start Position Field Names",
desc: "Set custom field names for the content start position in frontmatter",
values: "75, 150, 450",
placeholder: "content-start, start-position, content-offset"
},
{
setting: "customImageDisplayField",
name: "Image Display Field Names",
desc: "Set custom field names for the image display in frontmatter",
values: "cover, contain, auto, 200%, 70%",
placeholder: "banner-display, image-display, display-mode"
},
{
setting: "customImageRepeatField",
name: "Image Repeat Field Names",
desc: "Set custom field names for the image repeat in frontmatter",
values: "true, false",
placeholder: "banner-repeat, image-repeat, repeat"
},
{
setting: "customBannerMaxWidthField",
name: "Banner Max Width Field Names",
desc: "Set custom field names for the banner maximum width in frontmatter",
values: "800px, 100%, 1200px",
placeholder: "banner-max-width, max-width, banner-width"
},
{
setting: "customBannerAlignmentField",
name: "Banner Alignment Field Names",
desc: "Set custom field names for the banner alignment in frontmatter",
values: "left, center, right",
placeholder: "banner-alignment"
},
{
setting: "customBannerHeightField",
name: "Banner Height Field Names",
desc: "Set custom field names for the banner height in frontmatter",
values: "150, 350, 500",
placeholder: "banner-height, height, banner-size"
},
{
setting: "customFadeField",
name: "Fade Field Names",
desc: "Set custom field names for the fade in frontmatter",
values: "-300, -50, 0, 100",
placeholder: "banner-fade, fade, fade-amount"
},
{
setting: "customBorderRadiusField",
name: "Border Radius Field Names",
desc: "Set custom field names for the border radius in frontmatter",
values: "0, 17, 25",
placeholder: "banner-radius, radius, border-radius"
},
{
setting: "customTitleColorField",
name: "Title Color Field Names",
desc: "Set custom field names for the title color in frontmatter",
values: "#ffffff, white, var(--text-normal)",
placeholder: "banner-title-color, title-color, inline-title-color"
},
{
setting: "customBannerShuffleField",
name: "Banner Shuffle Field Names",
desc: "Set custom field names for the banner shuffle in frontmatter",
values: "true, false",
placeholder: "banner-shuffle, shuffle, random-banner"
},
{
setting: "customFlagColorField",
name: "Pixel Banner Flag Color Field Names",
desc: "Set custom field names for the pixel banner flag color in frontmatter",
values: "red, blue, green, orange, purple, yellow",
placeholder: "pixel-banner-flag-color, banner-flag-color, flag-color"
},
{
setting: "customBannerIconField",
name: "Banner Icon Field Names",
desc: "Set custom field names for the banner icon in frontmatter",
values: "\u{1F31F}, \u{1F3A8}, \u{1F4DD}",
placeholder: "banner-icon, icon, header-icon"
},
{
setting: "customBannerIconImageField",
name: "Banner Icon Image Field Names",
desc: "Set custom field names for the banner icon image in frontmatter",
values: '[[image.png]], "images/icon.jpg"',
placeholder: "banner-icon-image, icon-image"
},
{
setting: "customBannerIconSizeField",
name: "Banner Icon Size Field Names",
desc: "Set custom field names for the banner icon size in frontmatter",
values: "50, 70, 100",
placeholder: "banner-icon-size, icon-size"
},
{
setting: "customBannerIconImageSizeMultiplierField",
name: "Banner Icon Image Size Multiplier Field Names",
desc: "Set custom field names for the banner icon image size multiplier in frontmatter",
values: ".5, 1.5, 2",
placeholder: "banner-icon-image-size-multiplier, icon-image-size-multiplier"
},
{
setting: "customBannerIconTextVerticalOffsetField",
name: "Banner Icon Text Vertical Offset Field Names",
desc: "Set custom field names for the banner icon text vertical offset in frontmatter",
values: "-10, 0, 10",
placeholder: "banner-icon-text-vertical-offset, icon-text-vertical-offset"
},
{
setting: "customBannerIconRotateField",
name: "Banner Icon Rotate Field Names",
desc: "Set custom field names for the banner icon rotate in frontmatter",
values: "50, 70, 100",
placeholder: "banner-icon-rotate, icon-rotate"
},
{
setting: "customBannerIconXPositionField",
name: "Banner Icon X Position Field Names",
desc: "Set custom field names for the banner icon X position in frontmatter",
values: "25, 50, 75",
placeholder: "banner-icon-x, icon-x"
},
{
setting: "customBannerIconOpacityField",
name: "Banner Icon Opacity Field Names",
desc: "Set custom field names for the banner icon opacity in frontmatter",
values: "50, 75, 100",
placeholder: "banner-icon-opacity, icon-opacity"
},
{
setting: "customBannerIconColorField",
name: "Banner Icon Color Field Names",
desc: "Set custom field names for the banner icon color in frontmatter",
values: "#ffffff, white, var(--text-normal)",
placeholder: "banner-icon-color, icon-color"
},
{
setting: "customBannerIconFontWeightField",
name: "Banner Icon Font Weight Field Names",
desc: "Set custom field names for the banner icon font weight in frontmatter",
values: "lighter, normal, bold",
placeholder: "banner-icon-font-weight, icon-font-weight"
},
{
setting: "customBannerIconBackgroundColorField",
name: "Banner Icon Background Color Field Names",
desc: "Set custom field names for the banner icon background color in frontmatter",
values: "#000000, black, transparent",
placeholder: "banner-icon-bg-color, icon-bg-color"
},
{
setting: "customBannerIconPaddingXField",
name: "Banner Icon Padding X Field Names",
desc: "Set custom field names for the banner icon padding X in frontmatter",
values: "0, 10, 20",
placeholder: "banner-icon-padding-x, icon-padding-x"
},
{
setting: "customBannerIconPaddingYField",
name: "Banner Icon Padding Y Field Names",
desc: "Set custom field names for the banner icon padding Y in frontmatter",
values: "0, 10, 20",
placeholder: "banner-icon-padding-y, icon-padding-y"
},
{
setting: "customBannerIconBorderRadiusField",
name: "Banner Icon Border Radius Field Names",
desc: "Set custom field names for the banner icon border radius in frontmatter",
values: "0, 17, 25",
placeholder: "banner-icon-border-radius, icon-border-radius"
},
{
setting: "customBannerIconVerticalOffsetField",
name: "Banner Icon Vertical Offset Field Names",
desc: "Set custom field names for the banner icon vertical offset in frontmatter",
values: "-50, 0, 50",
placeholder: "banner-icon-y, icon-y"
},
{
setting: "customBannerIconImageAlignmentField",
name: "Banner Icon Image Alignment Field Names",
desc: "Set custom field names for the banner icon image alignment in frontmatter",
values: "left, right",
placeholder: "banner-icon-image-alignment, icon-image-alignment"
}
];
customFields.forEach((field) => {
new import_obsidian3.Setting(containerEl).setName(field.name).setDesc(createFragment((el) => {
el.createEl("div", { text: field.desc });
el.createEl("div", { cls: "setting-item-example" }).innerHTML = `Example frontmatter values: ${field.values}`;
})).addText((text) => {
text.setPlaceholder(field.placeholder).setValue(arrayToString(plugin.settings[field.setting])).onChange(async (value) => {
const newNames = stringToArray(value);
const allFields = customFields.map((f) => f.setting);
const validation = validateFieldNames(plugin.settings, allFields, field.setting, newNames);
if (!validation.isValid) {
text.inputEl.addClass("is-invalid");
text.inputEl.title = validation.message;
return;
}
text.inputEl.removeClass("is-invalid");
text.inputEl.title = "";
plugin.settings[field.setting] = newNames;
await plugin.saveSettings();
});
text.inputEl.style.width = "300px";
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
plugin.settings[field.setting] = DEFAULT_SETTINGS[field.setting];
await plugin.saveSettings();
const settingEl = button.extraSettingsEl.parentElement;
const textInput = settingEl.querySelector('input[type="text"]');
textInput.value = arrayToString(DEFAULT_SETTINGS[field.setting]);
const event = new Event("input", { bubbles: true, cancelable: true });
textInput.dispatchEvent(event);
}));
});
}
// src/settings/tabs/settingsTabGeneral.js
var import_obsidian4 = require("obsidian");
init_flags();
init_semver();
function addUpdateButtonIfNeeded(containerEl, plugin, insertAfterCallout = false) {
if (!plugin.pixelBannerVersion) {
console.log("[Pixel Banner] No cloud version available, skipping update button");
return;
}
const cloudVersion = plugin.pixelBannerVersion;
const currentVersion = plugin.settings.lastVersion;
const isCloudVersionGreater = semver.gt(cloudVersion, currentVersion);
if (isCloudVersionGreater) {
if (containerEl.querySelector(".pixel-banner-update-button")) {
return;
}
const updateContainer = document.createElement("div");
updateContainer.style.cssText = `
display: flex;
justify-content: flex-end;
margin-top: 15px;
`;
const updateButton = document.createElement("button");
updateButton.textContent = "\u{1F504} Update Available!";
updateButton.className = "pixel-banner-scale-up-down-animation pixel-banner-update-button";
updateButton.style.cssText = `
padding: 6px 12px;
background-color: var(--interactive-accent);
color: var(--text-on-accent);
border: none;
border-radius: 4px;
cursor: pointer;
font-weight: 500;
`;
updateContainer.appendChild(updateButton);
const calloutEl = containerEl.querySelector(".tab-callout");
if (calloutEl && calloutEl.nextSibling) {
calloutEl.parentNode.insertBefore(updateContainer, calloutEl.nextSibling);
} else {
containerEl.appendChild(updateContainer);
}
updateButton.addEventListener("click", async () => {
await plugin.app.setting.open();
await new Promise((resolve) => setTimeout(resolve, 300));
const settingsTabs = document.querySelectorAll(".vertical-tab-header-group .vertical-tab-nav-item");
for (const tab of settingsTabs) {
if (tab.textContent.includes("Community plugins")) {
tab.click();
break;
}
}
await new Promise((resolve) => setTimeout(resolve, 500));
const allTheButtons = document.querySelectorAll("button.mod-cta");
for (const button of allTheButtons) {
if (button.textContent.includes("Check for updates")) {
button.click();
break;
}
}
});
}
}
function createGeneralSettings(containerEl, plugin) {
const calloutEl = containerEl.createEl("div", { cls: "tab-callout margin-bottom-0" });
calloutEl.createEl("div", { text: `v${plugin.settings.lastVersion} \u22C5 Configure default settings for all notes.` });
if (!plugin.pixelBannerVersion) {
plugin.getPixelBannerInfo().then(() => {
addUpdateButtonIfNeeded(containerEl, plugin);
}).catch((error) => {
console.log("[Pixel Banner] Failed to fetch version info:", error.message);
});
} else if (!plugin.pixelBannerVersion) {
console.log("[Pixel Banner] Version not available, skipping version check");
} else {
addUpdateButtonIfNeeded(containerEl, plugin);
}
const SelectImageSettingsGroup = containerEl.createDiv({ cls: "setting-group" });
const showSelectImageIconSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Show Pixel Banner Flag").setDesc("Show the banner selector icon in the top-left corner of notes").addToggle((toggle) => toggle.setValue(plugin.settings.showSelectImageIcon).onChange(async (value) => {
try {
plugin.settings.showSelectImageIcon = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.showSelectImageIcon = DEFAULT_SETTINGS.showSelectImageIcon;
await plugin.saveSettings();
const toggleComponent = showSelectImageIconSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.showSelectImageIcon);
}
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const selectImageIconOpacitySetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Pixel Banner Flag Opacity").setDesc("Set the opacity of the banner selector icon in the top-left corner (0-100)").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.selectImageIconOpacity).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.selectImageIconOpacity = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.selectImageIconOpacity = DEFAULT_SETTINGS.selectImageIconOpacity;
await plugin.saveSettings();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.selectImageIconOpacity;
sliderEl.dispatchEvent(new Event("input"));
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const selectImageIconFlagSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Select Pixel Banner Flag Color").setDesc("Choose which flag color to use for the banner selector");
const flagRadioContainer = selectImageIconFlagSetting.controlEl.createDiv({
cls: "pixel-banner-flag-radio-container",
attr: {
style: `
max-width: 600px;
`
}
});
flagRadioContainer.style.display = "flex";
flagRadioContainer.style.flexWrap = "wrap";
flagRadioContainer.style.gap = "10px";
Object.keys(flags).forEach((color) => {
const radioContainer = flagRadioContainer.createDiv({
cls: "pixel-banner-flag-radio"
});
const radio = radioContainer.createEl("input", {
type: "radio",
attr: {
id: `flag-${color}`,
name: "pixel-banner-flag",
value: color
}
});
radio.checked = plugin.settings.selectImageIconFlag === color;
radio.addEventListener("change", async () => {
if (radio.checked) {
try {
plugin.settings.selectImageIconFlag = color;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}
});
const label = radioContainer.createEl("label", {
attr: {
for: `flag-${color}`
}
});
const img = label.createEl("img", {
attr: {
src: flags[color],
alt: `${color} flag`
}
});
img.style.width = "20px";
img.style.height = "25px";
img.style.verticalAlign = "middle";
img.style.marginLeft = "5px";
});
selectImageIconFlagSetting.addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.selectImageIconFlag = DEFAULT_SETTINGS.selectImageIconFlag;
await plugin.saveSettings();
const radios = selectImageIconFlagSetting.controlEl.querySelectorAll('input[type="radio"]');
radios.forEach((radio) => {
radio.checked = radio.value === DEFAULT_SETTINGS.selectImageIconFlag;
});
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const defaultSavedBannersFolderSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Default Saved Banners Folder").setDesc("Default folder where Banners will be saved").addText((text) => {
text.setPlaceholder("pixel-banner-images").setValue(plugin.settings.pinnedImageFolder).onChange(async (value) => {
try {
plugin.settings.pinnedImageFolder = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
text.inputEl.addEventListener("blur", async (event) => {
let value = text.inputEl.value.trim();
if (!value) {
value = "pixel-banner-images";
}
text.setValue(value);
plugin.settings.pinnedImageFolder = value;
try {
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
return text;
}).addButton((button) => button.setButtonText("Browse").onClick(() => {
new FolderSuggestModal(plugin.app, (chosenPath) => {
plugin.settings.pinnedImageFolder = chosenPath;
const textInput = defaultSavedBannersFolderSetting.components[0];
if (textInput) {
textInput.setValue(chosenPath);
}
plugin.saveSettings();
}).open();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.pinnedImageFolder = DEFAULT_SETTINGS.pinnedImageFolder;
await plugin.saveSettings();
const textComponent = defaultSavedBannersFolderSetting.components[0];
if (textComponent) {
textComponent.setValue(DEFAULT_SETTINGS.pinnedImageFolder);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const defaultSelectImagePathSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Default Select Image Path").setDesc("Set a default folder path to filter images when opening the Select Image modal").addText((text) => {
text.setPlaceholder("Example: Images/Banners").setValue(plugin.settings.defaultSelectImagePath).onChange(async (value) => {
try {
plugin.settings.defaultSelectImagePath = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
text.inputEl.style.width = "200px";
return text;
}).addButton((button) => button.setButtonText("Browse").onClick(() => {
new FolderSuggestModal(plugin.app, (chosenPath) => {
plugin.settings.defaultSelectImagePath = chosenPath;
const textInput = defaultSelectImagePathSetting.components[0];
if (textInput) {
textInput.setValue(chosenPath);
}
plugin.saveSettings();
}).open();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.defaultSelectImagePath = DEFAULT_SETTINGS.defaultSelectImagePath;
await plugin.saveSettings();
const textComponent = defaultSelectImagePathSetting.components[0];
if (textComponent) {
textComponent.setValue(DEFAULT_SETTINGS.defaultSelectImagePath);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const defaultSelectIconPathSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Default Select Icon Path").setDesc("Set a default folder path to filter images when selecting a banner icon image").addText((text) => {
text.setPlaceholder("Example: Images/Icons").setValue(plugin.settings.defaultSelectIconPath).onChange(async (value) => {
try {
plugin.settings.defaultSelectIconPath = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
text.inputEl.style.width = "200px";
return text;
}).addButton((button) => button.setButtonText("Browse").onClick(() => {
new FolderSuggestModal(plugin.app, (chosenPath) => {
plugin.settings.defaultSelectIconPath = chosenPath;
const textInput = defaultSelectIconPathSetting.components[0];
if (textInput) {
textInput.setValue(chosenPath);
}
plugin.saveSettings();
}).open();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.defaultSelectIconPath = DEFAULT_SETTINGS.defaultSelectIconPath;
await plugin.saveSettings();
const textComponent = defaultSelectIconPathSetting.components[0];
if (textComponent) {
textComponent.setValue(DEFAULT_SETTINGS.defaultSelectIconPath);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const openTargetingModalSetting = new import_obsidian4.Setting(SelectImageSettingsGroup).setName("Open Targeting Modal after selecting a Banner or Icon").setDesc("Automatically open the Targeting Modal after selecting a banner image or icon").addToggle((toggle) => toggle.setValue(plugin.settings.openTargetingModalAfterSelectingBannerOrIcon).onChange(async (value) => {
try {
plugin.settings.openTargetingModalAfterSelectingBannerOrIcon = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.openTargetingModalAfterSelectingBannerOrIcon = DEFAULT_SETTINGS.openTargetingModalAfterSelectingBannerOrIcon;
await plugin.saveSettings();
const toggleComponent = openTargetingModalSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.openTargetingModalAfterSelectingBannerOrIcon);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Banner Max Width").setDesc("Set the maximum width for banner images (100-2560 pixels)").addSlider((slider) => slider.setLimits(100, 2560, 10).setValue(plugin.settings.bannerMaxWidth).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerMaxWidth = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerMaxWidth = DEFAULT_SETTINGS.bannerMaxWidth;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerMaxWidth;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Image Horizontal Position").setDesc("Set the horizontal position of the image (0-100)").addSlider(
(slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.xPosition).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.xPosition = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})
).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.xPosition = DEFAULT_SETTINGS.xPosition;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.xPosition;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Image Vertical Position").setDesc("Set the vertical position of the image (0-100)").addSlider(
(slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.yPosition).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.yPosition = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})
).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.yPosition = DEFAULT_SETTINGS.yPosition;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.yPosition;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Content Start Position").setDesc("Set the default vertical position where the content starts (in pixels)").addText((text) => text.setPlaceholder("150").setValue(String(plugin.settings.contentStartPosition)).onChange(async (value) => {
try {
const numValue = Number(value);
if (!isNaN(numValue) && numValue >= 0) {
plugin.settings.contentStartPosition = numValue;
await plugin.saveSettings();
plugin.updateAllBanners();
}
} catch (error) {
console.error("Failed to save settings:", error);
}
})).then((setting) => {
const inputEl = setting.controlEl.querySelector("input");
inputEl.type = "number";
inputEl.min = "0";
inputEl.style.width = "60px";
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.contentStartPosition = DEFAULT_SETTINGS.contentStartPosition;
await plugin.saveSettings();
plugin.updateAllBanners();
const inputEl = button.extraSettingsEl.parentElement.querySelector("input");
inputEl.value = DEFAULT_SETTINGS.contentStartPosition;
inputEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Image Display").setDesc("Set how the banner image should be displayed").addDropdown((dropdown) => {
dropdown.addOption("auto", "Auto").addOption("cover", "Cover").addOption("contain", "Contain").setValue(plugin.settings.imageDisplay || "cover").onChange(async (value) => {
try {
plugin.settings.imageDisplay = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
return dropdown;
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.imageDisplay = DEFAULT_SETTINGS.imageDisplay;
await plugin.saveSettings();
const dropdownEl = button.extraSettingsEl.parentElement.querySelector("select");
dropdownEl.value = DEFAULT_SETTINGS.imageDisplay;
dropdownEl.dispatchEvent(new Event("change"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Image Repeat").setDesc('Enable image repetition when "Contain" is selected').addToggle((toggle) => {
toggle.setValue(plugin.settings.imageRepeat).onChange(async (value) => {
try {
plugin.settings.imageRepeat = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
return toggle;
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.imageRepeat = DEFAULT_SETTINGS.imageRepeat;
await plugin.saveSettings();
plugin.updateAllBanners();
const checkboxContainer = button.extraSettingsEl.parentElement.querySelector(".checkbox-container");
const toggleEl = checkboxContainer.querySelector("input");
if (toggleEl) {
toggleEl.checked = DEFAULT_SETTINGS.imageRepeat;
checkboxContainer.classList.toggle("is-enabled", DEFAULT_SETTINGS.imageRepeat);
const event = new Event("change", { bubbles: true });
toggleEl.dispatchEvent(event);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Banner Height").setDesc("Set the default height of the banner image (0-1280 pixels)").addText((text) => {
text.setPlaceholder("350").setValue(String(plugin.settings.bannerHeight)).onChange(async (value) => {
try {
if (value === "" || !isNaN(Number(value))) {
await plugin.saveSettings();
}
} catch (error) {
console.error("Failed to save settings:", error);
}
});
text.inputEl.addEventListener("blur", async (event) => {
try {
let numValue = Number(event.target.value);
if (isNaN(numValue) || event.target.value === "") {
numValue = 350;
} else {
numValue = Math.max(0, Math.min(1280, numValue));
}
plugin.settings.bannerHeight = numValue;
text.setValue(String(numValue));
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
text.inputEl.type = "number";
text.inputEl.min = "0";
text.inputEl.max = "1280";
text.inputEl.style.width = "50px";
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerHeight = DEFAULT_SETTINGS.bannerHeight;
await plugin.saveSettings();
plugin.updateAllBanners();
const inputEl = button.extraSettingsEl.parentElement.querySelector("input");
inputEl.value = DEFAULT_SETTINGS.bannerHeight;
inputEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Banner Fade").setDesc("Set the default fade effect for the banner image (-300 to 100)").addSlider(
(slider) => slider.setLimits(-300, 100, 5).setValue(plugin.settings.fade).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.fade = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})
).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.fade = DEFAULT_SETTINGS.fade;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.fade;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Banner Fade In Animation Duration").setDesc("Set the default fade in animation duration for the banner image (0-1000 milliseconds)").addSlider(
(slider) => slider.setLimits(0, 1e3, 1).setValue(plugin.settings.bannerFadeInAnimationDuration).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerFadeInAnimationDuration = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})
).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerFadeInAnimationDuration = DEFAULT_SETTINGS.bannerFadeInAnimationDuration;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.bannerFadeInAnimationDuration;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Border Radius").setDesc("Set the default border radius of the banner image (0-50 pixels)").addSlider((slider) => slider.setLimits(0, 50, 1).setValue(plugin.settings.borderRadius).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.borderRadius = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.borderRadius = DEFAULT_SETTINGS.borderRadius;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.borderRadius;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Banner Gap").setDesc("Set the gap between the banner and the window edges (0-50 pixels)").addSlider(
(slider) => slider.setLimits(0, 50, 1).setValue(plugin.settings.bannerGap).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerGap = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})
).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerGap = DEFAULT_SETTINGS.bannerGap;
await plugin.saveSettings();
plugin.updateAllBanners();
const sliderEl = button.extraSettingsEl.parentElement.querySelector(".slider");
sliderEl.value = DEFAULT_SETTINGS.bannerGap;
sliderEl.dispatchEvent(new Event("input"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Inline Title Color").setDesc("Set the default inline title color for all banners").addColorPicker((color) => color.setValue((() => {
const currentColor = plugin.settings.titleColor;
if (currentColor && currentColor.startsWith("var(--")) {
const temp = document.createElement("div");
temp.style.color = currentColor;
document.body.appendChild(temp);
const computedColor = getComputedStyle(temp).color;
document.body.removeChild(temp);
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
const [_, r, g, b] = rgbMatch;
const hexColor = "#" + parseInt(r).toString(16).padStart(2, "0") + parseInt(g).toString(16).padStart(2, "0") + parseInt(b).toString(16).padStart(2, "0");
return hexColor;
}
return "#000000";
}
return currentColor || "#000000";
})()).onChange(async (value) => {
try {
plugin.settings.titleColor = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.titleColor = DEFAULT_SETTINGS.titleColor;
await plugin.saveSettings();
const colorPickerEl = button.extraSettingsEl.parentElement.querySelector('input[type="color"]');
if (colorPickerEl) {
const temp = document.createElement("div");
temp.style.color = DEFAULT_SETTINGS.titleColor;
document.body.appendChild(temp);
const computedColor = getComputedStyle(temp).color;
document.body.removeChild(temp);
const rgbMatch = computedColor.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
if (rgbMatch) {
const [_, r, g, b] = rgbMatch;
const hexColor = "#" + parseInt(r).toString(16).padStart(2, "0") + parseInt(g).toString(16).padStart(2, "0") + parseInt(b).toString(16).padStart(2, "0");
colorPickerEl.value = hexColor;
}
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const hideEmbeddedNoteTitlesSetting = new import_obsidian4.Setting(containerEl).setName("Hide Embedded Note Titles").setDesc(`Hide titles of embedded notes if Obsidian's "Show inline title" setting is enabled`).addToggle((toggle) => toggle.setValue(plugin.settings.hideEmbeddedNoteTitles).onChange(async (value) => {
try {
plugin.settings.hideEmbeddedNoteTitles = value;
await plugin.saveSettings();
plugin.updateEmbeddedTitlesVisibility();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.hideEmbeddedNoteTitles = DEFAULT_SETTINGS.hideEmbeddedNoteTitles;
await plugin.saveSettings();
const toggleComponent = hideEmbeddedNoteTitlesSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.hideEmbeddedNoteTitles);
}
plugin.updateEmbeddedTitlesVisibility();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const hideEmbeddedNoteBannersSetting = new import_obsidian4.Setting(containerEl).setName("Hide Embedded Note Banners").setDesc("Hide banners of embedded notes").addToggle((toggle) => toggle.setValue(plugin.settings.hideEmbeddedNoteBanners).onChange(async (value) => {
try {
plugin.settings.hideEmbeddedNoteBanners = value;
await plugin.saveSettings();
plugin.updateEmbeddedBannersVisibility();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.hideEmbeddedNoteBanners = DEFAULT_SETTINGS.hideEmbeddedNoteBanners;
await plugin.saveSettings();
const toggleComponent = hideEmbeddedNoteBannersSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.hideEmbeddedNoteBanners);
}
plugin.updateEmbeddedBannersVisibility();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Show Banner in Popover Previews").setDesc("Show banners in popover note previews").addToggle((toggle) => toggle.setValue(plugin.settings.showBannerInPopoverPreviews).onChange(async (value) => {
try {
plugin.settings.showBannerInPopoverPreviews = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.showBannerInPopoverPreviews = DEFAULT_SETTINGS.showBannerInPopoverPreviews;
await plugin.saveSettings();
plugin.updateAllBanners();
const checkboxContainer = button.extraSettingsEl.parentElement.querySelector(".checkbox-container");
const toggleEl = checkboxContainer.querySelector("input");
if (toggleEl) {
toggleEl.checked = DEFAULT_SETTINGS.showBannerInPopoverPreviews;
checkboxContainer.classList.toggle("is-enabled", DEFAULT_SETTINGS.showBannerInPopoverPreviews);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const showViewImageIconSetting = new import_obsidian4.Setting(containerEl).setName("Show View Image Icon").setDesc("Show an icon to view the banner image in full screen").addToggle((toggle) => toggle.setValue(plugin.settings.showViewImageIcon).onChange(async (value) => {
try {
plugin.settings.showViewImageIcon = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.showViewImageIcon = DEFAULT_SETTINGS.showViewImageIcon;
await plugin.saveSettings();
const toggleComponent = showViewImageIconSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.showViewImageIcon);
}
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Image Property Format").setDesc("Set the format for the banner property value.").addDropdown((dropdown) => dropdown.addOption("image", "image").addOption("[[image]]", "[[image]]").addOption("![[image]]", "![[image]]").setValue(plugin.settings.imagePropertyFormat).onChange(async (value) => {
try {
plugin.settings.imagePropertyFormat = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.imagePropertyFormat = DEFAULT_SETTINGS.imagePropertyFormat;
await plugin.saveSettings();
const dropdown = button.extraSettingsEl.parentElement.querySelector("select");
dropdown.value = DEFAULT_SETTINGS.imagePropertyFormat;
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const hideSettingsGroup = containerEl.createDiv({ cls: "setting-group" });
const hidePixelBannerFieldsSetting = new import_obsidian4.Setting(hideSettingsGroup).setName("Hide Pixel Banner Fields").setDesc("Hide banner-related frontmatter fields in Reading mode").addToggle((toggle) => toggle.setValue(plugin.settings.hidePixelBannerFields).onChange(async (value) => {
try {
plugin.settings.hidePixelBannerFields = value;
if (!value) {
plugin.settings.hidePropertiesSectionIfOnlyBanner = false;
const dependentToggle = hidePropertiesSection.components[0];
if (dependentToggle) {
dependentToggle.setValue(false);
dependentToggle.setDisabled(true);
}
hidePropertiesSection.settingEl.addClass("is-disabled");
plugin.app.workspace.iterateAllLeaves((leaf) => {
if (leaf.view instanceof import_obsidian4.MarkdownView && leaf.view.contentEl) {
const propertiesContainer = leaf.view.contentEl.querySelector(".metadata-container");
if (propertiesContainer) {
propertiesContainer.classList.remove("pixel-banner-hidden-section");
const hiddenFields = propertiesContainer.querySelectorAll(".pixel-banner-hidden-field");
hiddenFields.forEach((field) => {
field.classList.remove("pixel-banner-hidden-field");
});
}
}
});
} else {
const dependentToggle = hidePropertiesSection.components[0];
if (dependentToggle) {
dependentToggle.setDisabled(false);
}
hidePropertiesSection.settingEl.removeClass("is-disabled");
}
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.hidePixelBannerFields = DEFAULT_SETTINGS.hidePixelBannerFields;
plugin.settings.hidePropertiesSectionIfOnlyBanner = DEFAULT_SETTINGS.hidePropertiesSectionIfOnlyBanner;
await plugin.saveSettings();
const mainToggle = hidePixelBannerFieldsSetting.components[0];
if (mainToggle) {
mainToggle.setValue(DEFAULT_SETTINGS.hidePixelBannerFields);
}
const dependentToggle = hidePropertiesSection.components[0];
if (dependentToggle) {
dependentToggle.setValue(DEFAULT_SETTINGS.hidePropertiesSectionIfOnlyBanner);
dependentToggle.setDisabled(!DEFAULT_SETTINGS.hidePixelBannerFields);
}
hidePropertiesSection.settingEl.toggleClass("is-disabled", !DEFAULT_SETTINGS.hidePixelBannerFields);
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const hidePropertiesSection = new import_obsidian4.Setting(hideSettingsGroup).setName("Hide Properties Section").setDesc("Hide the entire Properties section in Reading mode if it only contains Pixel Banner fields").addToggle((toggle) => toggle.setValue(plugin.settings.hidePropertiesSectionIfOnlyBanner).setDisabled(!plugin.settings.hidePixelBannerFields).onChange(async (value) => {
try {
plugin.settings.hidePropertiesSectionIfOnlyBanner = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.hidePropertiesSectionIfOnlyBanner = DEFAULT_SETTINGS.hidePropertiesSectionIfOnlyBanner;
await plugin.saveSettings();
const toggle = hidePropertiesSection.components[0];
if (toggle) {
toggle.setValue(DEFAULT_SETTINGS.hidePropertiesSectionIfOnlyBanner);
}
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
hidePropertiesSection.settingEl.addClass("setting-dependent");
if (!plugin.settings.hidePixelBannerFields) {
hidePropertiesSection.settingEl.addClass("is-disabled");
}
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Size").setDesc("Set the default size for the banner icon").addSlider((slider) => slider.setLimits(10, 200, 1).setValue(plugin.settings.bannerIconSize).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconSize = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconSize = DEFAULT_SETTINGS.bannerIconSize;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconSize;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Image Size Multiplier").setDesc("Set the default size multiplier for banner icon images (0.1-5)").addSlider((slider) => slider.setLimits(0.1, 5, 0.1).setValue(plugin.settings.bannerIconImageSizeMultiplier).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconImageSizeMultiplier = value;
await plugin.saveSettings();
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconImageSizeMultiplier = DEFAULT_SETTINGS.bannerIconImageSizeMultiplier;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconImageSizeMultiplier;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
plugin.updateAllBanners();
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon X Position").setDesc("Set the default X position for the banner icon (0-100)").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.bannerIconXPosition).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconXPosition = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconXPosition = DEFAULT_SETTINGS.bannerIconXPosition;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconXPosition;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Opacity").setDesc("Set the default opacity for the banner icon (0-100)").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.bannerIconOpacity).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconOpacity = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconOpacity = DEFAULT_SETTINGS.bannerIconOpacity;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconOpacity;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Text Color").setDesc("Set the default text color for the banner icon").addText((text) => text.setPlaceholder("Enter color (e.g., #ffffff or white)").setValue(plugin.settings.bannerIconColor).onChange(async (value) => {
try {
plugin.settings.bannerIconColor = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconColor = DEFAULT_SETTINGS.bannerIconColor;
await plugin.saveSettings();
const textInput = button.extraSettingsEl.parentElement.querySelector('input[type="text"]');
textInput.value = DEFAULT_SETTINGS.bannerIconColor;
const event = new Event("input", { bubbles: true, cancelable: true });
textInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Font Weight").setDesc("Set the default font weight for the banner icon").addDropdown((dropdown) => {
dropdown.addOption("lighter", "Lighter").addOption("normal", "Normal").addOption("bold", "Bold").setValue(plugin.settings.bannerIconFontWeight || "normal").onChange(async (value) => {
try {
plugin.settings.bannerIconFontWeight = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
});
return dropdown;
}).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconFontWeight = DEFAULT_SETTINGS.bannerIconFontWeight;
await plugin.saveSettings();
const dropdownEl = button.extraSettingsEl.parentElement.querySelector("select");
dropdownEl.value = DEFAULT_SETTINGS.bannerIconFontWeight;
dropdownEl.dispatchEvent(new Event("change"));
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Background Color").setDesc("Set the default background color for the banner icon").addText((text) => text.setPlaceholder("Enter color (e.g., #ffffff or transparent)").setValue(plugin.settings.bannerIconBackgroundColor).onChange(async (value) => {
try {
plugin.settings.bannerIconBackgroundColor = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconBackgroundColor = DEFAULT_SETTINGS.bannerIconBackgroundColor;
await plugin.saveSettings();
const textInput = button.extraSettingsEl.parentElement.querySelector('input[type="text"]');
textInput.value = DEFAULT_SETTINGS.bannerIconBackgroundColor;
const event = new Event("input", { bubbles: true, cancelable: true });
textInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Padding X").setDesc("Set the default padding X for the banner icon").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.bannerIconPaddingX).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconPaddingX = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconPaddingX = DEFAULT_SETTINGS.bannerIconPaddingX;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconPaddingX;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Padding Y").setDesc("Set the default padding Y for the banner icon").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.bannerIconPaddingY).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconPaddingY = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconPaddingY = DEFAULT_SETTINGS.bannerIconPaddingY;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconPaddingY;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Border Radius").setDesc("Set the default border radius for the banner icon").addSlider((slider) => slider.setLimits(0, 100, 1).setValue(plugin.settings.bannerIconBorderRadius).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconBorderRadius = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconBorderRadius = DEFAULT_SETTINGS.bannerIconBorderRadius;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconBorderRadius;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
new import_obsidian4.Setting(containerEl).setName("Default Banner Icon Vertical Offset").setDesc("Set the default vertical offset for the banner icon").addSlider((slider) => slider.setLimits(-100, 100, 1).setValue(plugin.settings.bannerIconVerticalOffset).setDynamicTooltip().onChange(async (value) => {
try {
plugin.settings.bannerIconVerticalOffset = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.bannerIconVerticalOffset = DEFAULT_SETTINGS.bannerIconVerticalOffset;
await plugin.saveSettings();
const sliderInput = button.extraSettingsEl.parentElement.querySelector('input[type="range"]');
sliderInput.value = DEFAULT_SETTINGS.bannerIconVerticalOffset;
const event = new Event("input", { bubbles: true, cancelable: true });
sliderInput.dispatchEvent(event);
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const showReleaseNotesSetting = new import_obsidian4.Setting(containerEl).setName("Show Release Notes").setDesc("Show release notes after plugin updates").addToggle((toggle) => toggle.setValue(plugin.settings.showReleaseNotes).onChange(async (value) => {
try {
plugin.settings.showReleaseNotes = value;
await plugin.saveSettings();
} catch (error) {
console.error("Failed to save settings:", error);
}
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
try {
plugin.settings.showReleaseNotes = DEFAULT_SETTINGS.showReleaseNotes;
await plugin.saveSettings();
const toggleComponent = showReleaseNotesSetting.components[0];
if (toggleComponent) {
toggleComponent.setValue(DEFAULT_SETTINGS.showReleaseNotes);
}
} catch (error) {
console.error("Failed to save settings:", error);
}
}));
const promotionalLinks = containerEl.createDiv({
cls: "pixel-banner-promotional-links",
attr: {
style: `
display: flex;
gap: 10px;
justify-content: center;
margin-top: 30px;
padding-top: 20px;
border-top: 1px solid var(--background-modifier-border);
`
}
});
const discordLink = promotionalLinks.createEl("a", {
href: "https://discord.gg/sp8AQQhMJ7",
target: "discord"
});
discordLink.createEl("img", {
attr: {
height: "36",
src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/discord.png?raw=true",
alt: "Discord"
}
});
const kofiLink = promotionalLinks.createEl("a", {
href: "https://ko-fi.com/Z8Z212UMBI",
target: "kofi"
});
kofiLink.createEl("img", {
attr: {
height: "36",
src: "https://raw.githubusercontent.com/jparkerweb/pixel-banner/refs/heads/main/img/support.png?raw=true",
alt: "Buy Me a Coffee at ko-fi.com"
}
});
}
// src/settings/tabs/settingsTabPixelBannerPlus.js
var import_obsidian5 = require("obsidian");
init_constants();
function createPixelBannerPlusSettings(containerEl, plugin) {
plugin.pixelBannerPlusEnabled = plugin.settings.pixelBannerPlusEnabled !== false;
const calloutElPixelBannerPlus = containerEl.createEl("div", { cls: "tab-callout margin-bottom-0" });
calloutElPixelBannerPlus.createEl("h4", {
text: "\u2728 Pixel Banner Plus \u2728",
attr: {
style: "margin-top: 5px;"
}
});
calloutElPixelBannerPlus.createEl("div", { text: "Pixel Banner Plus enhances your notes with AI-generated, high-quality banners. Using a token-based system, you can instantly create stunning, customized visuals\u2014no design skills needed. Sign up for free to access the banner store, which includes a selection of zero-token banners at no cost. No subscription required\u2014simply purchase tokens whenever you need AI-generated designs. Transform your Obsidian workspace with professional banners, starting for free and only adding tokens as needed." });
const pixelBannerPlusSettingsGroup = containerEl.createDiv({ cls: "setting-group" });
const plusDependentSettings = containerEl.createDiv({ cls: "pixel-banner-plus-dependent-settings" });
function updatePlusDependentSettingsVisibility(isEnabled) {
if (isEnabled) {
plusDependentSettings.style.display = "block";
} else {
plusDependentSettings.style.display = "none";
}
}
new import_obsidian5.Setting(pixelBannerPlusSettingsGroup).setName("Pixel Banner Plus Enabled").setDesc("Enable or disable Pixel Banner Plus features").addToggle((toggle) => toggle.setValue(plugin.settings.pixelBannerPlusEnabled !== false).onChange(async (value) => {
plugin.pixelBannerPlusEnabled = value;
plugin.settings.pixelBannerPlusEnabled = value;
await plugin.saveSettings();
updatePlusDependentSettingsVisibility(value);
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
plugin.settings.pixelBannerPlusEnabled = DEFAULT_SETTINGS.pixelBannerPlusEnabled;
plugin.pixelBannerPlusEnabled = plugin.settings.pixelBannerPlusEnabled;
await plugin.saveSettings();
const toggleComponent = button.extraSettingsEl.parentElement.querySelector(".checkbox-container input");
if (toggleComponent) {
toggleComponent.checked = plugin.settings.pixelBannerPlusEnabled;
toggleComponent.parentElement.classList.toggle("is-enabled", plugin.settings.pixelBannerPlusEnabled);
toggleComponent.dispatchEvent(new Event("change"));
}
updatePlusDependentSettingsVisibility(plugin.settings.pixelBannerPlusEnabled);
})).then((setting) => {
const nameEl = setting.nameEl;
if (nameEl) {
nameEl.innerHTML = '\u2728 Pixel Banner Plus Enabled';
}
});
updatePlusDependentSettingsVisibility(plugin.settings.pixelBannerPlusEnabled !== false);
const accountSettingsGroup = plusDependentSettings.createDiv({ cls: "setting-group margin-top-0" });
new import_obsidian5.Setting(accountSettingsGroup).setName("Pixel Banner Plus Email Address").setDesc("Your email address for Pixel Banner Plus authentication").addText(
(text) => text.setPlaceholder("Enter your email address").setValue(plugin.settings.pixelBannerPlusEmail).onChange(async (value) => {
plugin.settings.pixelBannerPlusEmail = value;
await plugin.saveSettings();
if (!value) {
plugin.pixelBannerPlusEnabled = false;
plugin.settings.pixelBannerPlusEnabled = false;
}
}).inputEl.style = "width: 100%; max-width: 275px; padding: 5px 10px;"
);
new import_obsidian5.Setting(accountSettingsGroup).setName("Pixel Banner Plus API Key").setDesc("Your API key for Pixel Banner Plus authentication").addText(
(text) => text.setPlaceholder("Enter your API key").setValue(plugin.settings.pixelBannerPlusApiKey).onChange(async (value) => {
plugin.settings.pixelBannerPlusApiKey = value;
await plugin.saveSettings();
if (!value) {
plugin.pixelBannerPlusEnabled = false;
plugin.settings.pixelBannerPlusEnabled = false;
}
}).inputEl.style = "width: 100%; max-width: 275px; padding: 5px 10px;"
);
new import_obsidian5.Setting(accountSettingsGroup).setName("Establish Connection").setDesc("Establish a connection to your Pixel Banner Plus account").addButton((button) => {
const buttonEl = button.buttonEl;
buttonEl.style.textTransform = "uppercase";
buttonEl.style.letterSpacing = "1px";
buttonEl.style.fontWeight = "bold";
buttonEl.style.borderRadius = "5px";
buttonEl.style.padding = "5px 10px";
buttonEl.style.fontSize = ".9em";
buttonEl.style.cursor = "pointer";
button.buttonEl.innerHTML = "\u26A1 Refresh / Connect Account";
button.setCta();
button.onClick(async () => {
const email = plugin.settings.pixelBannerPlusEmail;
const apiKey = plugin.settings.pixelBannerPlusApiKey;
if (!email || !apiKey) {
new import_obsidian5.Notice("Please enter both email and API key");
return;
}
button.buttonEl.innerHTML = "Verifying Account...";
button.setDisabled(true);
try {
const data = await plugin.verifyPixelBannerPlusCredentials();
if (data) {
if (data.verified) {
new import_obsidian5.Notice(`\u2705 Pixel Banner Plus connection successful
\u{1FA99} Banner Tokens Remaining: ${data.bannerTokens}`);
plugin.pixelBannerPlusServerOnline = data.serverOnline;
plugin.pixelBannerPlusEnabled = true;
plugin.pixelBannerPlusBannerTokens = data.bannerTokens;
updateAccountStatusSection(accountStatusGroup, plugin);
updateSignupSection(pixelBannerPlusSettingsGroup, plugin);
} else {
if (data.serverOnline) {
new import_obsidian5.Notice("\u274C Invalid credentials");
} else {
new import_obsidian5.Notice("\u274C Cannot connect to Pixel Banner Plus servers. Please try again later.");
}
plugin.pixelBannerPlusServerOnline = data.serverOnline;
plugin.pixelBannerPlusEnabled = false;
plugin.pixelBannerPlusBannerTokens = 0;
updateAccountStatusSection(accountStatusGroup, plugin);
updateSignupSection(pixelBannerPlusSettingsGroup, plugin);
}
} else {
new import_obsidian5.Notice("\u274C Cannot connect to Pixel Banner Plus servers. Please try again later.");
plugin.pixelBannerPlusServerOnline = false;
plugin.pixelBannerPlusEnabled = false;
plugin.pixelBannerPlusBannerTokens = 0;
updateAccountStatusSection(accountStatusGroup, plugin);
updateSignupSection(pixelBannerPlusSettingsGroup, plugin);
}
} catch (error) {
new import_obsidian5.Notice("\u274C Connection failed.");
plugin.pixelBannerPlusEnabled = false;
updateAccountStatusSection(accountStatusGroup, plugin);
updateSignupSection(pixelBannerPlusSettingsGroup, plugin);
}
button.buttonEl.innerHTML = "\u26A1 Refresh / Authorize Account";
button.setDisabled(false);
});
});
new import_obsidian5.Setting(accountSettingsGroup).setName("Show Daily Game").setDesc("Enable the daily game feature in the banner selection modal for a chance to win the daily jackpot's Tokens").addToggle((toggle) => toggle.setValue(plugin.settings.enableDailyGame).onChange(async (value) => {
plugin.settings.enableDailyGame = value;
await plugin.saveSettings();
})).addExtraButton((button) => button.setIcon("reset").setTooltip("Reset to default").onClick(async () => {
plugin.settings.enableDailyGame = DEFAULT_SETTINGS.enableDailyGame;
await plugin.saveSettings();
const toggleComponent = button.extraSettingsEl.parentElement.querySelector(".checkbox-container input");
if (toggleComponent) {
toggleComponent.checked = DEFAULT_SETTINGS.enableDailyGame;
toggleComponent.parentElement.classList.toggle("is-enabled", DEFAULT_SETTINGS.enableDailyGame);
toggleComponent.dispatchEvent(new Event("change"));
}
})).then((setting) => {
const nameEl = setting.nameEl;
if (nameEl) {
nameEl.innerHTML = '\u{1F579}\uFE0F Show Daily Game';
}
});
updateSignupSection(plusDependentSettings, plugin);
const accountStatusGroup = plusDependentSettings.createDiv({ cls: "setting-group" });
accountStatusGroup.createEl("h3", { text: "Account Status" });
updateAccountStatusSection(accountStatusGroup, plugin);
}
function updateSignupSection(containerEl, plugin) {
containerEl.querySelectorAll(".setting-item").forEach((el) => {
var _a;
if (((_a = el.querySelector(".setting-item-name")) == null ? void 0 : _a.textContent) === "Signup") {
el.remove();
}
});
if (!plugin.pixelBannerPlusEnabled && plugin.pixelBannerPlusServerOnline) {
new import_obsidian5.Setting(containerEl).setName("Signup").setDesc("Create a FREE Pixel Banner Plus account to get started. You will receive \u{1FA99} 5 FREE banner tokens at signup (no payment info required). This form can also be use to recover your account if you forget your API Key.").addButton((button) => {
const buttonEl = button.buttonEl;
buttonEl.style.textTransform = "uppercase";
buttonEl.style.letterSpacing = "1px";
buttonEl.style.fontWeight = "bold";
buttonEl.style.borderRadius = "5px";
buttonEl.style.padding = "5px 10px";
buttonEl.style.fontSize = ".9em";
buttonEl.style.cursor = "pointer";
buttonEl.classList.add("scale-up-down-animation");
button.buttonEl.innerHTML = "\u{1F6A9} Signup for Free!";
button.setCta();
button.onClick(() => {
const signupUrl = PIXEL_BANNER_PLUS.API_URL + PIXEL_BANNER_PLUS.ENDPOINTS.SIGNUP;
window.open(signupUrl, "_blank");
});
});
}
}
function updateAccountStatusSection(containerEl, plugin) {
containerEl.empty();
containerEl.createEl("h3", {
text: "Account Status",
attr: {
style: `
margin-top: 0;
margin-bottom: 0;
`
}
});
new import_obsidian5.Setting(containerEl).setName("Connection Status").setDesc("Current status of your Pixel Banner Plus account").addText((text) => {
const statusText = plugin.pixelBannerPlusServerOnline ? plugin.pixelBannerPlusEnabled ? "\u2705 Authorized" : "\u274C Not Authorized" : "\u{1F6A8} Servers Offline \u{1F6A8}";
const isLightMode = document.body.classList.contains("theme-light");
const statusColor = isLightMode ? "black" : "white";
const statusBGColor = isLightMode ? "white" : "black";
const statusBorderColor = plugin.pixelBannerPlusEnabled ? "#20bf6b" : "#FF0000";
const span = text.inputEl.parentElement.createSpan({
text: statusText,
attr: {
style: `
color: ${statusColor};
background-color: ${statusBGColor};
border: 1px dotted ${statusBorderColor};
padding: 5px 10px;
border-radius: 0px;
text-transform: uppercase;
font-size: .9em;
letter-spacing: 1.5px;
`
}
});
text.inputEl.style.display = "none";
});
new import_obsidian5.Setting(containerEl).setName("Available Tokens").setDesc("Number of banner tokens available in your account").addText((text) => {
const tokenCount = plugin.pixelBannerPlusBannerTokens !== void 0 ? `\u{1FA99} ${plugin.pixelBannerPlusBannerTokens.toString()}` : "\u2753 Unknown";
const isLightMode = document.body.classList.contains("theme-light");
const tokenColor = isLightMode ? "black" : "white";
const tokenBGColor = isLightMode ? "white" : "black";
const span = text.inputEl.parentElement.createSpan({
text: tokenCount,
attr: {
style: `
font-weight: bold;
color: ${tokenColor};
background-color: ${tokenBGColor};
border: 1px dotted #F3B93B;
padding: 5px 10px;
border-radius: 0px;
text-transform: uppercase;
font-size: .9em;
letter-spacing: 1.5px;
`
}
});
text.inputEl.style.display = "none";
});
if (plugin.pixelBannerPlusServerOnline && plugin.pixelBannerPlusEnabled) {
new import_obsidian5.Setting(containerEl).setName("Buy Tokens").setDesc(createFragment((el) => {
el.createEl("div", { text: "Purchase tokens to download Banners from the Store or Generate them with AI \u2728" });
el.createEl("div").innerHTML = 'This greatly helps support the development of this plugin and keep the AI Servers running \u{1F917}. In addition to buying tokens directly, any donation on our Ko-fi page will also add tokens to your account \u{1F496}';
})).addButton((button) => {
const buttonEl = button.buttonEl;
buttonEl.style.textTransform = "uppercase";
buttonEl.style.letterSpacing = "1px";
buttonEl.style.borderRadius = "5px";
buttonEl.style.border = "1px solid papayawhip";
buttonEl.style.padding = "5px 10px";
buttonEl.style.fontSize = ".9em";
buttonEl.style.lineHeight = "1";
buttonEl.style.backgroundColor = "darkgreen";
buttonEl.style.color = "papayawhip";
buttonEl.style.cursor = "pointer";
buttonEl.classList.add("scale-up-down-animation");
button.buttonEl.innerHTML = "\u{1F4B5} Buy More Tokens";
button.onClick(() => {
window.open(PIXEL_BANNER_PLUS.SHOP_URL, "_blank");
});
});
}
}
// src/settings/settings.js
var DEFAULT_SETTINGS = {
pixelBannerPlusEmail: "",
pixelBannerPlusApiKey: "",
pixelBannerPlusEnabled: true,
apiProvider: "all",
pexelsApiKey: "",
pixabayApiKey: "",
flickrApiKey: "",
unsplashApiKey: "",
imageSize: "medium",
imageOrientation: "landscape",
numberOfImages: 10,
defaultKeywords: "nature, abstract, landscape, technology, art, cityscape, wildlife, ocean, mountains, forest, space, architecture, food, travel, science, music, sports, fashion, business, education, health, culture, history, weather, transportation, industry, people, animals, plants, patterns",
xPosition: 50,
yPosition: 60,
customBannerField: ["banner"],
customXPositionField: ["banner-x", "x"],
customYPositionField: ["banner-y", "y"],
customContentStartField: ["content-start"],
customImageDisplayField: ["banner-display"],
customImageRepeatField: ["banner-repeat"],
customBannerMaxWidthField: ["banner-max-width"],
customBannerAlignmentField: ["banner-align"],
customBannerHeightField: ["banner-height"],
customFadeField: ["banner-fade"],
customBorderRadiusField: ["banner-radius"],
customTitleColorField: ["banner-inline-title-color"],
customBannerShuffleField: ["banner-shuffle"],
customBannerIconField: ["icon"],
customBannerIconImageField: ["icon-image"],
customBannerIconSizeField: ["icon-size"],
customBannerIconImageSizeMultiplierField: ["icon-image-size-multiplier"],
customBannerIconTextVerticalOffsetField: ["icon-text-vertical-offset"],
customBannerIconRotateField: ["icon-rotate"],
customBannerIconXPositionField: ["icon-x"],
customBannerIconOpacityField: ["icon-opacity"],
customBannerIconColorField: ["icon-color"],
customBannerIconFontWeightField: ["icon-font-weight"],
customBannerIconBackgroundColorField: ["icon-bg-color"],
customBannerIconPaddingXField: ["icon-padding-x"],
customBannerIconPaddingYField: ["icon-padding-y"],
customBannerIconBorderRadiusField: ["icon-border-radius"],
customBannerIconVerticalOffsetField: ["icon-y"],
customBannerIconImageAlignmentField: ["banner-icon-image-alignment"],
customFlagColorField: ["pixel-banner-flag-color"],
folderImages: [],
contentStartPosition: 355,
imageDisplay: "cover",
imageRepeat: false,
bannerHeight: 350,
bannerMaxWidth: 2560,
fade: -40,
bannerFadeInAnimationDuration: 300,
borderRadius: 17,
showPinIcon: false,
pinnedImageFolder: "pixel-banner-images",
pinnedImageFilename: "pixel-banner-image",
imagePropertyFormat: "image",
showReleaseNotes: true,
lastVersion: null,
showRefreshIcon: false,
showViewImageIcon: false,
hidePixelBannerFields: true,
hidePropertiesSectionIfOnlyBanner: true,
titleColor: "var(--inline-title-color)",
enableImageShuffle: false,
hideEmbeddedNoteTitles: false,
hideEmbeddedNoteBanners: false,
showBannerInPopoverPreviews: true,
showSelectImageIcon: true,
selectImageIconOpacity: 40,
selectImageIconFlag: "red",
defaultSelectImagePath: "",
defaultSelectIconPath: "",
useShortPath: true,
bannerGap: 12,
bannerIconSize: 70,
bannerIconImageSizeMultiplier: 1,
bannerIconTextVerticalOffset: 0,
bannerIconXPosition: 75,
bannerIconOpacity: 100,
bannerIconColor: "",
bannerIconFontWeight: "normal",
bannerIconBackgroundColor: "",
bannerIconPaddingX: "10",
bannerIconPaddingY: "10",
bannerIconBorderRadius: "17",
bannerIconVerticalOffset: "0",
bannerIconImageAlignment: "left",
openTargetingModalAfterSelectingBannerOrIcon: true,
enableDailyGame: false
};
var FolderSuggestModal = class extends import_obsidian6.FuzzySuggestModal {
constructor(app, onChoose) {
super(app);
this.onChoose = onChoose;
}
getItems() {
return this.app.vault.getAllLoadedFiles().filter((file) => file.children).map((folder) => folder.path);
}
getItemText(item) {
return item;
}
onChooseItem(item) {
this.onChoose(item);
}
};
var PixelBannerSettingTab = class extends import_obsidian6.PluginSettingTab {
constructor(app, plugin) {
super(app, plugin);
this.plugin = plugin;
}
display() {
const { containerEl } = this;
containerEl.empty();
containerEl.addClass("pixel-banner-settings");
const mainContent = containerEl.createEl("div", { cls: "pixel-banner-main-content" });
const { tabsEl, tabContentContainer } = this.createTabs(mainContent, [
"\u2699\uFE0F General",
"\u2728 Plus",
"\u{1F5FA}\uFE0F Custom Fields",
"\u{1F5C3}\uFE0F Folder Groups",
"\u{1F310} 3rd Party APIs"
]);
const generalTab = tabContentContainer.createEl("div", { cls: "tab-content", attr: { "data-tab": "\u2699\uFE0F General" } });
createGeneralSettings(generalTab, this.plugin);
const pixelBannerPlusTab = tabContentContainer.createEl("div", { cls: "tab-content", attr: { "data-tab": "\u2728 Plus" } });
createPixelBannerPlusSettings(pixelBannerPlusTab, this.plugin);
const customFieldsTab = tabContentContainer.createEl("div", { cls: "tab-content", attr: { "data-tab": "\u{1F5FA}\uFE0F Custom Fields" } });
createCustomFieldsSettings(customFieldsTab, this.plugin);
const apiTab = tabContentContainer.createEl("div", { cls: "tab-content", attr: { "data-tab": "\u{1F310} 3rd Party APIs" } });
createAPISettings(apiTab, this.plugin);
const foldersTab = tabContentContainer.createEl("div", { cls: "tab-content", attr: { "data-tab": "\u{1F5C3}\uFE0F Folder Groups" } });
createFolderSettings(foldersTab, this.plugin);
tabsEl.firstChild.click();
}
createTabs(containerEl, tabNames) {
const tabsEl = containerEl.createEl("div", { cls: "pixel-banner-settings-tabs" });
const tabContentContainer = containerEl.createEl("div", { cls: "pixel-banner-settings-tab-content-container" });
tabNames.forEach((tabName) => {
const tabEl = tabsEl.createEl("button", { cls: "pixel-banner-settings-tab", text: tabName });
tabEl.addEventListener("click", () => {
tabsEl.querySelectorAll(".pixel-banner-settings-tab").forEach((tab) => tab.removeClass("active"));
tabContentContainer.querySelectorAll(".tab-content").forEach((content) => content.style.display = "none");
tabEl.addClass("active");
tabContentContainer.querySelector(`.tab-content[data-tab="${tabName}"]`).style.display = "flex";
});
});
return { tabsEl, tabContentContainer };
}
};
function debounce(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
// src/core/pixelBannerPlugin.js
init_modals();
init_handlePinIconClick();
// src/core/settings.js
var import_obsidian23 = require("obsidian");
async function loadSettings(plugin) {
plugin.settings = Object.assign({}, DEFAULT_SETTINGS, await plugin.loadData());
if (!Array.isArray(plugin.settings.folderImages)) {
plugin.settings.folderImages = [];
}
if (!Array.isArray(plugin.settings.customBannerField)) {
plugin.settings.customBannerField = DEFAULT_SETTINGS.customBannerField;
}
if (typeof plugin.settings.xPosition !== "number") {
plugin.settings.xPosition = parseInt(plugin.settings.xPosition) || DEFAULT_SETTINGS.xPosition;
}
if (typeof plugin.settings.yPosition !== "number") {
plugin.settings.yPosition = parseInt(plugin.settings.yPosition) || DEFAULT_SETTINGS.yPosition;
}
if (plugin.settings.folderImages) {
plugin.settings.folderImages.forEach((folderImage) => {
folderImage.imageDisplay = folderImage.imageDisplay || "cover";
folderImage.imageRepeat = folderImage.imageRepeat || false;
folderImage.directChildrenOnly = folderImage.directChildrenOnly || false;
});
}
}
async function saveSettings(plugin) {
await plugin.saveData(plugin.settings);
plugin.loadedImages.clear();
plugin.lastKeywords.clear();
plugin.imageCache.clear();
plugin.lastFrontmatter.clear();
plugin.app.workspace.iterateAllLeaves((leaf) => {
if (leaf.view instanceof import_obsidian23.MarkdownView) {
plugin.updateBanner(leaf.view, true);
if (plugin.settings.hidePixelBannerFields) {
plugin.updateFieldVisibility(leaf.view);
}
}
});
}
// src/core/bannerIconHelpers.js
var import_obsidian24 = require("obsidian");
init_modals();
function normalizeColor(color) {
if (!color || color === "transparent" || color === "none") return "transparent";
return color.toLowerCase().replace(/\s+/g, "");
}
function getIconOverlay(plugin) {
if (plugin.iconOverlayPool.length > 0) {
return plugin.iconOverlayPool.pop();
}
const overlay = document.createElement("div");
overlay.className = "banner-icon-overlay";
return overlay;
}
function returnIconOverlay(plugin, overlay) {
if (!overlay) return;
if (plugin.iconOverlayPool.length < plugin.MAX_POOL_SIZE) {
overlay.style.cssText = "";
overlay.className = "banner-icon-overlay";
overlay.textContent = "";
overlay.remove();
plugin.iconOverlayPool.push(overlay);
}
}
function shouldUpdateIconOverlay(plugin, existingOverlay, newIconState, viewType) {
if (!existingOverlay || !newIconState) return true;
if (!existingOverlay._isPersistentBannerIcon || existingOverlay.dataset.viewType !== viewType || existingOverlay.textContent !== newIconState.icon) {
return true;
}
const computedStyle = window.getComputedStyle(existingOverlay);
const styleChecks = {
fontSize: `${newIconState.size}px`,
left: `${newIconState.xPosition}%`,
opacity: `${newIconState.opacity}%`,
color: newIconState.color,
fontWeight: newIconState.fontWeight,
backgroundColor: newIconState.backgroundColor,
borderRadius: `${newIconState.borderRadius}px`,
marginTop: `${newIconState.verticalOffset}px`
};
const currentPadding = computedStyle.padding.split(" ");
const expectedPadding = `${newIconState.paddingY}px ${newIconState.paddingX}px`;
if (currentPadding.join(" ") !== expectedPadding) {
return true;
}
return Object.entries(styleChecks).some(([prop, value]) => {
const current = computedStyle[prop];
return current !== value && // Handle special cases for colors
!(prop.includes("color") && normalizeColor(current) === normalizeColor(value));
});
}
async function handleSetBannerIcon(plugin) {
const activeFile = plugin.app.workspace.getActiveFile();
if (!activeFile) {
new import_obsidian24.Notice("No active file");
return;
}
new EmojiSelectionModal(
plugin.app,
plugin,
async (selectedEmoji) => {
if (!selectedEmoji) {
await plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
const bannerIconField2 = Array.isArray(plugin.settings.customBannerIconField) && plugin.settings.customBannerIconField.length > 0 ? plugin.settings.customBannerIconField[0] : "banner-icon";
delete frontmatter[bannerIconField2];
});
await new Promise((resolve) => setTimeout(resolve, 300));
const view = plugin.app.workspace.getActiveViewOfType(import_obsidian24.MarkdownView);
if (view) {
await plugin.updateBanner(view, true);
}
new import_obsidian24.Notice("Banner icon removed");
return;
}
const bannerIconField = Array.isArray(plugin.settings.customBannerIconField) && plugin.settings.customBannerIconField.length > 0 ? plugin.settings.customBannerIconField[0] : "banner-icon";
await plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
plugin.settings.customBannerIconField.forEach((field) => {
if (field in frontmatter) {
delete frontmatter[field];
}
});
frontmatter[bannerIconField] = selectedEmoji;
});
const metadataUpdated = new Promise((resolve) => {
let eventRef = null;
let resolved = false;
const cleanup = () => {
if (eventRef) {
plugin.app.metadataCache.off("changed", eventRef);
eventRef = null;
}
};
const timeoutId = setTimeout(() => {
if (!resolved) {
resolved = true;
cleanup();
resolve();
}
}, 2e3);
eventRef = plugin.app.metadataCache.on("changed", (file) => {
if (file.path === activeFile.path && !resolved) {
resolved = true;
clearTimeout(timeoutId);
cleanup();
setTimeout(resolve, 50);
}
});
});
await metadataUpdated;
const maxRetries = 3;
const retryDelay = 150;
let success = false;
for (let i = 0; i < maxRetries && !success; i++) {
const view = plugin.app.workspace.getActiveViewOfType(import_obsidian24.MarkdownView);
if (view) {
try {
const cache = plugin.app.metadataCache.getFileCache(activeFile);
if (!cache || !cache.frontmatter || cache.frontmatter[bannerIconField] !== selectedEmoji) {
await new Promise((resolve) => setTimeout(resolve, 100));
continue;
}
await plugin.updateBanner(view, true);
success = true;
} catch (error) {
if (i < maxRetries - 1) {
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
}
}
}
if (!success) {
await new Promise((resolve) => setTimeout(resolve, 500));
const view = plugin.app.workspace.getActiveViewOfType(import_obsidian24.MarkdownView);
if (view) {
await plugin.updateBanner(view, true);
}
}
new import_obsidian24.Notice("Banner icon updated");
if (plugin.settings.openTargetingModalAfterSelectingBannerOrIcon) {
await new Promise((resolve) => setTimeout(resolve, 200));
const { TargetPositionModal: TargetPositionModal2 } = (init_modals(), __toCommonJS(modals_exports));
new TargetPositionModal2(plugin.app, plugin).open();
}
},
true
// Skip the targeting modal in EmojiSelectionModal since we handle it in the callback
).open();
}
function cleanupIconOverlay(plugin, view) {
if (!view || !view.contentEl) return;
const bannerIconOverlays = view.contentEl.querySelectorAll(".banner-icon-overlay");
bannerIconOverlays.forEach((overlay) => {
if (overlay) {
returnIconOverlay(plugin, overlay);
}
});
}
async function handleSetBannerIconImage(plugin) {
const activeFile = plugin.app.workspace.getActiveFile();
if (!activeFile) {
new import_obsidian24.Notice("No active file");
return;
}
new IconImageSelectionModal(
plugin.app,
plugin,
async (selectedImage) => {
if (!selectedImage) {
return;
}
const imagePath = selectedImage.path ? selectedImage.path : selectedImage;
const bannerIconImageField = Array.isArray(plugin.settings.customBannerIconImageField) && plugin.settings.customBannerIconImageField.length > 0 ? plugin.settings.customBannerIconImageField[0].split(",")[0].trim() : "icon-image";
await plugin.app.fileManager.processFrontMatter(activeFile, (frontmatter) => {
if (Array.isArray(plugin.settings.customBannerIconImageField)) {
plugin.settings.customBannerIconImageField.forEach((field) => {
const fieldNames = field.split(",").map((f) => f.trim());
fieldNames.forEach((fieldName) => {
if (fieldName in frontmatter) {
delete frontmatter[fieldName];
}
});
});
}
const format = plugin.settings.imagePropertyFormat;
let iconValue;
if (format === "image") {
iconValue = imagePath;
} else if (format === "[[image]]") {
iconValue = `[[${imagePath}]]`;
} else {
iconValue = `![[${imagePath}]]`;
}
frontmatter[bannerIconImageField] = iconValue;
});
const metadataUpdated = new Promise((resolve) => {
let eventRef = null;
let resolved = false;
const cleanup = () => {
if (eventRef) {
plugin.app.metadataCache.off("changed", eventRef);
eventRef = null;
}
};
const timeoutId = setTimeout(() => {
if (!resolved) {
resolved = true;
cleanup();
resolve();
}
}, 2e3);
eventRef = plugin.app.metadataCache.on("changed", (file) => {
if (file.path === activeFile.path && !resolved) {
resolved = true;
clearTimeout(timeoutId);
cleanup();
setTimeout(resolve, 50);
}
});
});
await metadataUpdated;
const maxRetries = 3;
const retryDelay = 150;
let success = false;
for (let i = 0; i < maxRetries && !success; i++) {
const view = plugin.app.workspace.getActiveViewOfType(import_obsidian24.MarkdownView);
if (view) {
try {
const cache = plugin.app.metadataCache.getFileCache(activeFile);
if (!cache || !cache.frontmatter || cache.frontmatter[bannerIconImageField] !== imagePath) {
await new Promise((resolve) => setTimeout(resolve, 100));
continue;
}
await plugin.updateBanner(view, true);
success = true;
new import_obsidian24.Notice(`Banner icon image updated: ${imagePath.split("/").pop()}`);
} catch (error) {
if (i < maxRetries - 1) {
await new Promise((resolve) => setTimeout(resolve, retryDelay));
}
}
}
}
if (!success) {
new import_obsidian24.Notice("Banner icon image set, but banner update failed. Try reopening the note.");
}
}
).open();
}
// src/core/cacheHelpers.js
var import_obsidian25 = require("obsidian");
function generateCacheKey(filePath, leafId, isShuffled = false) {
const encodedPath = encodeURIComponent(filePath);
return `${encodedPath}-${leafId}${isShuffled ? "-shuffle" : ""}`;
}
function getCacheEntriesForFile(filePath) {
const encodedPath = encodeURIComponent(filePath);
return Array.from(this.bannerStateCache.entries()).filter(([key]) => key.startsWith(`${encodedPath}-`));
}
function cleanupCache(force = false) {
var _a, _b;
const now = Date.now();
for (const [key, entry] of this.bannerStateCache) {
const maxAge = entry.isShuffled ? this.SHUFFLE_CACHE_AGE : this.MAX_CACHE_AGE;
const isOrphaned = entry.leafId && !this.app.workspace.getLeafById(entry.leafId);
if (force || now - entry.timestamp > maxAge || isOrphaned) {
if (entry.leafId) {
const leaf = this.app.workspace.getLeafById(entry.leafId);
if ((leaf == null ? void 0 : leaf.view) instanceof import_obsidian25.MarkdownView) {
const contentEl = leaf.view.contentEl;
["cm-sizer", "markdown-preview-sizer"].forEach((selector) => {
const container = contentEl.querySelector(`.${selector}`);
if (container) {
const iconOverlays = container.querySelectorAll('.banner-icon-overlay[data-persistent="true"]');
iconOverlays.forEach((overlay) => overlay.remove());
}
});
}
}
if (((_a = entry.state) == null ? void 0 : _a.imageUrl) && typeof entry.state.imageUrl === "string" && entry.state.imageUrl.startsWith("blob:")) {
URL.revokeObjectURL(entry.state.imageUrl);
}
this.bannerStateCache.delete(key);
}
}
if (!force && this.bannerStateCache.size > this.MAX_CACHE_ENTRIES) {
const entries = Array.from(this.bannerStateCache.entries()).sort(([, a], [, b]) => a.timestamp - b.timestamp);
while (entries.length > this.MAX_CACHE_ENTRIES) {
const [key, entry] = entries.shift();
if (entry.leafId) {
const leaf = this.app.workspace.getLeafById(entry.leafId);
if ((leaf == null ? void 0 : leaf.view) instanceof import_obsidian25.MarkdownView) {
const contentEl = leaf.view.contentEl;
["cm-sizer", "markdown-preview-sizer"].forEach((selector) => {
const container = contentEl.querySelector(`.${selector}`);
if (container) {
const iconOverlays = container.querySelectorAll('.banner-icon-overlay[data-persistent="true"]');
iconOverlays.forEach((overlay) => overlay.remove());
}
});
}
}
if (((_b = entry.state) == null ? void 0 : _b.imageUrl) && typeof entry.state.imageUrl === "string" && entry.state.imageUrl.startsWith("blob:")) {
URL.revokeObjectURL(entry.state.imageUrl);
}
this.bannerStateCache.delete(key);
}
}
}
function invalidateLeafCache(leafId) {
var _a;
for (const [key, entry] of this.bannerStateCache) {
if (key && typeof key === "string" && key.includes(`-${leafId}`)) {
const leaf = this.app.workspace.getLeafById(leafId);
if ((leaf == null ? void 0 : leaf.view) instanceof import_obsidian25.MarkdownView) {
const contentEl = leaf.view.contentEl;
["cm-sizer", "markdown-preview-sizer"].forEach((selector) => {
const container = contentEl.querySelector(`.${selector}`);
if (container) {
const iconOverlays = container.querySelectorAll('.banner-icon-overlay[data-persistent="true"]');
iconOverlays.forEach((overlay) => overlay.remove());
}
});
}
if (((_a = entry.state) == null ? void 0 : _a.imageUrl) && typeof entry.state.imageUrl === "string" && entry.state.imageUrl.startsWith("blob:")) {
URL.revokeObjectURL(entry.state.imageUrl);
}
this.bannerStateCache.delete(key);
}
}
}
// src/services/apiService.js
var import_obsidian26 = require("obsidian");
var rateLimiter = {
lastRequestTime: 0,
// Use shorter interval in test environment to speed up tests
minInterval: typeof globalThis.vitest !== "undefined" ? 10 : 1e3
// 10ms in tests, 1s in production
};
async function makeRequest(url, options = {}) {
const now = Date.now();
if (now - rateLimiter.lastRequestTime < rateLimiter.minInterval) {
await new Promise((resolve) => setTimeout(resolve, rateLimiter.minInterval));
}
rateLimiter.lastRequestTime = Date.now();
try {
const response = await (0, import_obsidian26.requestUrl)({
url,
headers: options.headers || {},
...options
});
return response;
} catch (error) {
console.error("Request failed:", error);
throw new Error(`Request failed: ${error.message}`);
}
}
async function fetchPexelsImage(plugin, keyword, disableInternalFallback = false) {
const apiKey = plugin.settings.pexelsApiKey;
if (!apiKey) return null;
const useInternalFallback = !disableInternalFallback;
const defaultKeywords = useInternalFallback && plugin.settings.defaultKeywords ? plugin.settings.defaultKeywords.split(",").map((k) => k.trim()) : [];
const fallbackKeyword = defaultKeywords.length > 0 ? defaultKeywords[Math.floor(Math.random() * defaultKeywords.length)] : null;
const keywords = useInternalFallback && fallbackKeyword ? [keyword, fallbackKeyword] : [keyword];
for (const currentKeyword of keywords) {
try {
const response = await makeRequest(
`https://api.pexels.com/v1/search?query=${encodeURIComponent(currentKeyword)}&per_page=${plugin.settings.numberOfImages}`,
{
headers: {
"Authorization": apiKey
}
}
);
if (response.status !== 200) {
console.error("Failed to fetch images:", response.status, response.text);
continue;
}
const data = response.json;
if (data.photos && data.photos.length > 0) {
const randomIndex = Math.floor(Math.random() * data.photos.length);
const imageUrl = data.photos[randomIndex].src[plugin.settings.imageSize];
return imageUrl;
}
} catch (error) {
console.error(`Error fetching image from Pexels for keyword "${currentKeyword}":`, error);
}
}
return null;
}
async function fetchPixabayImage(plugin, keyword) {
var _a;
const apiKey = plugin.settings.pixabayApiKey;
if (!apiKey) return null;
const apiUrl = "https://pixabay.com/api/";
const params = new URLSearchParams({
key: apiKey,
q: encodeURIComponent(keyword),
image_type: "photo",
per_page: plugin.settings.numberOfImages,
safesearch: true
});
try {
const response = await makeRequest(`${apiUrl}?${params}`);
if (response.status !== 200) {
return null;
}
let data;
if (response.json) {
data = response.json;
} else if (response.arrayBuffer) {
data = JSON.parse(new TextDecoder().decode(response.arrayBuffer));
} else {
return null;
}
if (((_a = data.hits) == null ? void 0 : _a.length) > 0) {
const imageUrls = data.hits.map((hit) => hit.largeImageURL);
return imageUrls[Math.floor(Math.random() * imageUrls.length)];
}
return null;
} catch (error) {
console.error(`Error fetching image from Pixabay for keyword "${keyword}":`, error);
return null;
}
}
async function fetchFlickrImage(plugin, keyword) {
var _a, _b;
const apiKey = plugin.settings.flickrApiKey;
if (!apiKey) return null;
try {
const searchUrl = `https://www.flickr.com/services/rest/?method=flickr.photos.search&api_key=${apiKey}&text=${encodeURIComponent(keyword)}&per_page=${plugin.settings.numberOfImages}&format=json&nojsoncallback=1&sort=relevance&content_type=1&media=photos&safe_search=1`;
const response = await makeRequest(searchUrl);
if (response.status !== 200) {
return null;
}
let data;
if (response.text && response.text.includes("jsonFlickrApi(")) {
const jsonpMatch = response.text.match(/jsonFlickrApi\((.*)\)/);
if (jsonpMatch && jsonpMatch[1]) {
try {
data = JSON.parse(jsonpMatch[1]);
} catch (e) {
console.error("Error parsing JSONP response:", e);
return null;
}
} else {
console.error("Invalid JSONP format");
return null;
}
} else {
try {
if (response.json) {
data = response.json;
} else if (response.arrayBuffer) {
data = JSON.parse(new TextDecoder().decode(response.arrayBuffer));
} else {
return null;
}
} catch (e) {
console.error("Error parsing JSON response:", e);
return null;
}
}
if (data.stat && data.stat !== "ok" || !((_b = (_a = data.photos) == null ? void 0 : _a.photo) == null ? void 0 : _b.length)) {
return null;
}
const photos = data.photos.photo;
const photo = photos[Math.floor(Math.random() * photos.length)];
let size = "z";
if (plugin.settings && plugin.settings.imageSize) {
switch (plugin.settings.imageSize) {
case "small":
size = "n";
break;
case "medium":
size = "z";
break;
case "large":
size = "b";
break;
default:
size = "z";
break;
}
}
return `https://live.staticflickr.com/${photo.server}/${photo.id}_${photo.secret}_${size}.jpg`;
} catch (error) {
console.error(`Error fetching image from Flickr for keyword "${keyword}":`, error);
return null;
}
}
async function fetchUnsplashImage(plugin, keyword) {
var _a;
const apiKey = plugin.settings.unsplashApiKey;
if (!apiKey) return null;
try {
const apiUrl = "https://api.unsplash.com/search/photos";
const params = new URLSearchParams({
query: keyword,
per_page: plugin.settings.numberOfImages,
orientation: plugin.settings.imageOrientation
});
const response = await makeRequest(`${apiUrl}?${params}`, {
headers: {
"Authorization": `Client-ID ${apiKey}`,
"Accept-Version": "v1"
}
});
if (response.status !== 200) {
return null;
}
let data;
if (response.json) {
data = response.json;
} else if (response.arrayBuffer) {
data = JSON.parse(new TextDecoder().decode(response.arrayBuffer));
} else {
return null;
}
if (!((_a = data.results) == null ? void 0 : _a.length)) {
return null;
}
const photo = data.results[Math.floor(Math.random() * data.results.length)];
return photo.urls[plugin.settings.imageSize === "small" ? "small" : plugin.settings.imageSize === "medium" ? "regular" : "full"];
} catch (error) {
console.error(`Error fetching image from Unsplash for keyword "${keyword}":`, error);
return null;
}
}
// src/services/apiPIxelBannerPlus.js
init_constants();
async function verifyPixelBannerPlusCredentials(plugin) {
if (!plugin.settings.pixelBannerPlusEmail || !plugin.settings.pixelBannerPlusApiKey) {
return { serverOnline: true, verified: false, bannerTokens: 0 };
}
try {
const response = await makeRequest(
`${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.VERIFY}`,
{
method: "GET",
headers: {
"X-User-Email": plugin.settings.pixelBannerPlusEmail,
"X-API-Key": plugin.settings.pixelBannerPlusApiKey,
"X-Pixel-Banner-Version": plugin.settings.lastVersion,
"Accept": "application/json"
}
}
);
if (response.status === 200) {
const data = response.json;
return {
serverOnline: true,
verified: data.success,
bannerTokens: data.banner_tokens,
jackpot: data.jackpot,
dailyGameName: data.daily_game,
highScore: data.high_score,
topUser: data.top_user,
timeLeft: data.time_left
};
}
return { serverOnline: true, verified: false, bannerTokens: 0, jackpot: 0, dailyGameName: "", highScore: 0, topUser: "", timeLeft: "0" };
} catch (error) {
console.error("Failed to verify Pixel Banner Plus credentials:", error);
const errorMessage = error.message.toLowerCase();
const errorName = error.name.toLowerCase();
const isConnectionError = errorName === "typeerror" || errorName === "error" || errorMessage.includes("network error") || errorMessage.includes("failed to fetch") || errorMessage.includes("network") || errorMessage.startsWith("err_") || !navigator.onLine;
const isUnauthorized = errorMessage.includes("401") || errorMessage.includes("unauthorized");
console.log(`pixel banner plus error.message: ${error.message}`);
console.log(`pixel banner plus isConnectionError: ${isConnectionError}`);
console.log(`pixel banner plus isUnauthorized: ${isUnauthorized}`);
return {
serverOnline: !isConnectionError || isUnauthorized,
verified: false,
bannerTokens: 0,
jackpot: 0,
dailyGameName: "",
highScore: 0,
topUser: "",
timeLeft: "0"
};
}
}
async function getPixelBannerInfo() {
try {
const response = await makeRequest(
`${PIXEL_BANNER_PLUS.API_URL}${PIXEL_BANNER_PLUS.ENDPOINTS.INFO}`,
{
method: "GET"
}
);
if (response.status === 200) {
const data = response.json;
return {
version: data.version
};
}
} catch (error) {
console.error("Failed to get Pixel Banner Plus info:", error);
}
return { version: "0.0.0" };
}
// src/core/bannerManager.js
var import_obsidian27 = require("obsidian");
init_modals();
init_frontmatterUtils();
init_handlePinIconClick();
init_flags();
// src/utils/debounce.js
function debounceFunction(func, wait) {
let timeout;
return function executedFunction(...args) {
const later = () => {
clearTimeout(timeout);
func(...args);
};
clearTimeout(timeout);
timeout = setTimeout(later, wait);
};
}
function debounceAndSwallow(func, wait) {
let lastCallTime = 0;
return function executedFunction(...args) {
const now = Date.now();
if (now - lastCallTime >= wait) {
lastCallTime = now;
return func(...args);
}
};
}
// src/core/bannerManager.js
var markdownPostProcessorDebounceMap = /* @__PURE__ */ new Map();
var MARKDOWN_PROCESSOR_DEBOUNCE_DELAY = 200;
var updateBannerDebounceMap = /* @__PURE__ */ new Map();
var UPDATE_BANNER_DEBOUNCE_DELAY = 250;
var SUPPORTED_IMAGE_EXTENSIONS = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "avif"];
var SUPPORTED_MOVIE_EXTENSIONS = ["mp4", "mov"];
var SUPPORTED_EXTENSIONS = [...SUPPORTED_IMAGE_EXTENSIONS, ...SUPPORTED_MOVIE_EXTENSIONS];
var debouncedAddPixelBanner = debounceAndSwallow(addPixelBanner, 350);
async function addPixelBanner(plugin, el, ctx) {
var _a, _b, _c, _d, _e;
const { frontmatter, file, isContentChange, yPosition, xPosition, contentStartPosition, bannerImage, isReadingView, updateMode } = ctx;
const viewContent = el;
const isEmbedded = viewContent.classList.contains("internal-embed") && viewContent.classList.contains("markdown-embed");
const isHoverPopover = viewContent.closest(".hover-popover") !== null;
const hideEmbeddedNoteBanners = getFrontmatterValue(frontmatter, plugin.settings.customHideEmbeddedNoteBannersField) || ((_a = plugin.getFolderSpecificImage(file.path)) == null ? void 0 : _a.hideEmbeddedNoteBanners) || plugin.settings.hideEmbeddedNoteBanners || false;
if (!isEmbedded && !isHoverPopover && viewContent.classList.contains("view-content")) {
const frontmatterContentStartRaw = getFrontmatterValue(frontmatter, plugin.settings.customContentStartField);
const initialContentStart = (_c = (_b = frontmatterContentStartRaw !== null ? Number(frontmatterContentStartRaw) : null) != null ? _b : contentStartPosition) != null ? _c : plugin.settings.contentStartPosition;
applyContentStartPosition(plugin, viewContent, initialContentStart);
const sourceEl = viewContent.querySelector(":scope > .markdown-source-view .cm-sizer");
if (sourceEl) {
sourceEl.style.paddingTop = "var(--pixel-banner-content-start, 355px)";
sourceEl.style.paddingBottom = "0px !important";
}
const previewEl = viewContent.querySelector(":scope > .markdown-reading-view .markdown-preview-sizer");
if (previewEl) {
previewEl.style.paddingTop = "var(--pixel-banner-content-start, 355px)";
previewEl.style.paddingBottom = "0px !important";
}
viewContent.classList.add("pixel-banner");
plugin.setupResizeObserver(viewContent);
plugin.applyBannerWidth(viewContent);
} else if (isEmbedded) {
viewContent.classList.add("pixel-banner");
plugin.applyContentStartPosition(viewContent, contentStartPosition);
const embedContentDiv = viewContent.querySelector(":scope > .markdown-embed-content");
if (embedContentDiv && !hideEmbeddedNoteBanners) {
const previewViewEl = embedContentDiv.querySelector(":scope > .markdown-preview-view");
if (previewViewEl) {
const folderSpecific = plugin.getFolderSpecificImage(file.path);
const maxWidth = getFrontmatterValue(frontmatter, plugin.settings.customBannerMaxWidthField) || (folderSpecific == null ? void 0 : folderSpecific.bannerMaxWidth) || plugin.settings.bannerMaxWidth || "unset";
const maxWidthValue = maxWidth === "unset" ? "unset" : `${maxWidth}px`;
const bannerYPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customYPositionField),
folderSpecific == null ? void 0 : folderSpecific.bannerYPosition,
plugin.settings.bannerYPosition,
0
]);
const bannerXPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customXPositionField),
folderSpecific == null ? void 0 : folderSpecific.bannerXPosition,
plugin.settings.bannerXPosition,
0
]);
const bannerTitleColor = getFrontmatterValue(frontmatter, plugin.settings.customTitleColorField) || (folderSpecific == null ? void 0 : folderSpecific.titleColor) || plugin.settings.titleColor || "var(--inline-title-color)";
previewViewEl.style.setProperty("--pixel-banner-y-position", `${bannerYPosition}%`);
previewViewEl.style.setProperty("--pixel-banner-x-position", `${bannerXPosition}%`);
previewViewEl.style.setProperty("--pixel-banner-max-width", maxWidthValue);
previewViewEl.style.setProperty("--pixel-banner-title-color", bannerTitleColor);
const bannerIconSize = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconSizeField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconSize) || plugin.settings.bannerIconSize || 70;
const bannerIconXPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconXPositionField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconXPosition,
plugin.settings.bannerIconXPosition,
25
]);
const bannerIconOpacity = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconOpacityField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconOpacity,
plugin.settings.bannerIconOpacity,
100
]);
const bannerIconColor = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconColorField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconColor) || plugin.settings.bannerIconColor || "var(--text-normal)";
const bannerIconFontWeight = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconFontWeightField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconFontWeight) || plugin.settings.bannerIconFontWeight || "normal";
const bannerIconBackgroundColor = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBackgroundColorField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconBackgroundColor) || plugin.settings.bannerIconBackgroundColor || "transparent";
const bannerIconPaddingX = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingXField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconPaddingX,
plugin.settings.bannerIconPaddingX,
10
]);
const bannerIconPaddingY = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingYField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconPaddingY,
plugin.settings.bannerIconPaddingY,
10
]);
const bannerIconBorderRadius = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBorderRadiusField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconBorderRadius,
plugin.settings.bannerIconBorderRadius,
17
]);
const bannerIconVerticalOffset = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconVerticalOffsetField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconVerticalOffset,
plugin.settings.bannerIconVerticalOffset,
0
]);
const bannerIconRotate = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconRotateField),
0
]);
const bannerIconImageSizeMultiplier = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconImageSizeMultiplierField),
plugin.settings.bannerIconImageSizeMultiplier,
1
]);
const bannerIconTextVerticalOffset = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconTextVerticalOffsetField),
plugin.settings.bannerIconTextVerticalOffset,
0
]);
previewViewEl.style.setProperty("--pixel-banner-icon-size", `${bannerIconSize}px`);
previewViewEl.style.setProperty("--pixel-banner-icon-x", `${bannerIconXPosition}%`);
previewViewEl.style.setProperty("--pixel-banner-icon-opacity", `${bannerIconOpacity}%`);
previewViewEl.style.setProperty("--pixel-banner-icon-color", bannerIconColor);
previewViewEl.style.setProperty("--pixel-banner-icon-font-weight", bannerIconFontWeight);
previewViewEl.style.setProperty("--pixel-banner-icon-background-color", bannerIconBackgroundColor);
previewViewEl.style.setProperty("--pixel-banner-icon-padding-x", `${bannerIconPaddingX}px`);
previewViewEl.style.setProperty("--pixel-banner-icon-padding-y", `${bannerIconPaddingY}px`);
previewViewEl.style.setProperty("--pixel-banner-icon-border-radius", `${bannerIconBorderRadius}px`);
previewViewEl.style.setProperty("--pixel-banner-icon-vertical-offset", `${bannerIconVerticalOffset}px`);
previewViewEl.style.setProperty("--pixel-banner-icon-rotate", `${bannerIconRotate}deg`);
previewViewEl.style.setProperty("--pixel-banner-icon-image-size-multiplier", `${bannerIconImageSizeMultiplier}em`);
previewViewEl.style.setProperty("--pixel-banner-icon-text-vertical-offset", `${bannerIconTextVerticalOffset}px`);
const bannerHeight = getFrontmatterValue(frontmatter, plugin.settings.customBannerHeightField) || (folderSpecific == null ? void 0 : folderSpecific.bannerHeight) || plugin.settings.bannerHeight || 150;
const embedMinHeight = `${parseInt(bannerHeight) + parseInt(bannerIconSize) / 2}px`;
previewViewEl.style.setProperty("--pixel-banner-embed-min-height", embedMinHeight);
previewViewEl.style.setProperty("--pixel-banner-height", `${bannerHeight}px`);
const bannerFade = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerFadeField),
folderSpecific == null ? void 0 : folderSpecific.bannerFade,
plugin.settings.fade,
-75
]);
previewViewEl.style.setProperty("--pixel-banner-fade", `${bannerFade}%`);
const bannerRadius = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBorderRadiusField),
folderSpecific == null ? void 0 : folderSpecific.borderRadius,
plugin.settings.borderRadius,
17
]);
previewViewEl.style.setProperty("--pixel-banner-radius", `${bannerRadius}px`);
const bannerAlignment = getFrontmatterValue(frontmatter, plugin.settings.customBannerAlignmentField) || "center";
let alignmentValue = "0 auto";
if (bannerAlignment === "left") {
alignmentValue = "0 auto 0 0";
} else if (bannerAlignment === "right") {
alignmentValue = "0 0 0 auto";
}
previewViewEl.style.setProperty("--pixel-banner-alignment", alignmentValue);
previewViewEl.style.setProperty("--pixel-banner-icon-start", `${bannerHeight - bannerIconSize / 2}px`);
const contentStart = !hideEmbeddedNoteBanners ? `${parseInt(bannerHeight) + parseInt(bannerIconSize) / 2 + parseInt(bannerIconVerticalOffset) + parseInt(bannerIconPaddingY)}px` : "0px";
previewViewEl.style.setProperty("--pixel-banner-content-start", contentStart);
}
}
} else if (isHoverPopover && plugin.settings.showBannerInPopoverPreviews) {
const previewEl = viewContent.querySelector(".markdown-preview-view");
if (previewEl) {
previewEl.classList.add("pixel-banner");
plugin.setupResizeObserver(previewEl);
plugin.applyBannerWidth(previewEl);
}
}
let container;
if (isEmbedded) {
container = viewContent.querySelector(".markdown-preview-sizer");
if (!container) {
container = viewContent.querySelector(".markdown-embed-content");
}
if (!container) {
container = viewContent;
}
} else if (isHoverPopover && plugin.settings.showBannerInPopoverPreviews) {
container = viewContent.querySelector(".markdown-preview-sizer") || viewContent.querySelector(".markdown-preview-view");
if (!container) {
if (viewContent.classList.contains("markdown-preview-view")) {
container = viewContent;
} else if (viewContent.parentElement && viewContent.parentElement.classList.contains("markdown-preview-view")) {
container = viewContent.parentElement;
}
}
} else {
container = isReadingView ? viewContent.querySelector(".markdown-preview-sizer:not(.internal-embed .markdown-preview-sizer)") || viewContent.querySelector(".markdown-preview-view") : viewContent.querySelector(".cm-sizer") || viewContent.querySelector(".markdown-source-view");
if (!container && viewContent.classList.contains("markdown-preview-view")) {
container = viewContent;
}
}
if (!container) {
return;
}
let bannerDiv = container.querySelector(":scope > .pixel-banner-image");
if (!bannerDiv) {
bannerDiv = createDiv({ cls: "pixel-banner-image" });
container.insertBefore(bannerDiv, container.firstChild);
bannerDiv._isPersistentBanner = true;
}
const oldViewIcons = container.querySelectorAll(".view-image-icon");
const oldPinIcons = container.querySelectorAll(".pin-icon");
const oldRefreshIcons = container.querySelectorAll(".refresh-icon");
const oldSelectIcons = container.querySelectorAll(".select-image-icon");
[...oldViewIcons, ...oldPinIcons, ...oldRefreshIcons, ...oldSelectIcons].forEach((el2) => el2.remove());
if (isEmbedded || isHoverPopover) {
plugin.updateEmbeddedBannersVisibility();
} else {
let leftOffset = plugin.settings.bannerGap + 15;
if (plugin.settings.showSelectImageIcon) {
const opacity = plugin.settings.selectImageIconOpacity / 100;
const selectImageIcon = createDiv({
cls: "select-image-icon",
attr: {
style: `
position: absolute;
top: 10px;
left: ${leftOffset}px;
font-size: 1.8em;
cursor: pointer;
opacity: ${opacity};
`
}
});
const flagColor = getFrontmatterValue(frontmatter, plugin.settings.customFlagColorField) || plugin.settings.selectImageIconFlag;
selectImageIcon.innerHTML = ``;
selectImageIcon._isPersistentSelectImage = true;
selectImageIcon.onclick = () => plugin.handleBannerIconClick();
container.appendChild(selectImageIcon);
leftOffset += 35;
}
if (bannerImage && plugin.settings.showViewImageIcon && !isEmbedded) {
const viewImageIcon = createDiv({
cls: "view-image-icon",
attr: {
style: `
display: none;
position: absolute;
top: 10px;
left: ${leftOffset}px;
font-size: 1.5em;
cursor: pointer;
`
}
});
viewImageIcon.innerHTML = "\u{1F5BC}\uFE0F";
viewImageIcon._isPersistentViewImage = true;
viewImageIcon.innerHTML = "\u{1F5BC}\uFE0F";
viewImageIcon._updateVisibility = (newUrl, originalPath) => {
viewImageIcon.style.display = newUrl ? "block" : "none";
if (newUrl) {
viewImageIcon.onclick = () => {
new ImageViewModal(plugin.app, newUrl, originalPath).open();
};
}
};
container.appendChild(viewImageIcon);
leftOffset += 35;
}
const activeFile = plugin.app.workspace.getActiveFile();
const hasBanner = activeFile && plugin.hasBannerFrontmatter(activeFile);
}
if (!container._hasOverriddenSetChildrenInPlace) {
const originalSetChildrenInPlace = container.setChildrenInPlace;
container.setChildrenInPlace = function(children) {
const bannerElement = this.querySelector(":scope > .pixel-banner-image");
const viewImageElement = this.querySelector(":scope > .view-image-icon");
const pinElement = this.querySelector(":scope > .pin-icon");
const refreshElement = this.querySelector(":scope > .refresh-icon");
const selectImageElement = this.querySelector(":scope > .select-image-icon");
const bannerIconOverlay = this.querySelector(":scope > .banner-icon-overlay");
children = Array.from(children).filter(
(child) => {
var _a2, _b2, _c2, _d2, _e2, _f;
return !((_a2 = child.classList) == null ? void 0 : _a2.contains("pixel-banner-image")) && !((_b2 = child.classList) == null ? void 0 : _b2.contains("view-image-icon")) && !((_c2 = child.classList) == null ? void 0 : _c2.contains("pin-icon")) && !((_d2 = child.classList) == null ? void 0 : _d2.contains("refresh-icon")) && !((_e2 = child.classList) == null ? void 0 : _e2.contains("select-image-icon")) && !((_f = child.classList) == null ? void 0 : _f.contains("banner-icon-overlay"));
}
);
if (bannerElement == null ? void 0 : bannerElement._isPersistentBanner) {
children.unshift(bannerElement);
}
if (bannerIconOverlay == null ? void 0 : bannerIconOverlay._isPersistentBannerIcon) {
children.push(bannerIconOverlay);
}
if (selectImageElement == null ? void 0 : selectImageElement._isPersistentSelectImage) {
children.push(selectImageElement);
}
if (viewImageElement == null ? void 0 : viewImageElement._isPersistentViewImage) {
children.push(viewImageElement);
}
if (pinElement == null ? void 0 : pinElement._isPersistentPin) {
children.push(pinElement);
}
if (refreshElement == null ? void 0 : refreshElement._isPersistentRefresh) {
children.push(refreshElement);
}
return originalSetChildrenInPlace.call(this, children);
};
container._hasOverriddenSetChildrenInPlace = true;
}
if (bannerImage) {
let imageUrl = plugin.loadedImages.get(file.path);
const lastInput = plugin.lastKeywords.get(file.path);
const inputType = plugin.getInputType(bannerImage, file.path);
const folderSpecific = plugin.getFolderSpecificImage(file.path);
const hasShufflePath = getFrontmatterValue(frontmatter, plugin.settings.customBannerShuffleField);
const isShuffled = hasShufflePath || (folderSpecific == null ? void 0 : folderSpecific.enableImageShuffle);
const effectiveUpdateMode = updateMode || plugin.UPDATE_MODE.FULL_UPDATE;
let shouldFetchNewImage = false;
if (effectiveUpdateMode === plugin.UPDATE_MODE.ENSURE_VISIBILITY) {
shouldFetchNewImage = !imageUrl;
} else {
shouldFetchNewImage = effectiveUpdateMode === plugin.UPDATE_MODE.FULL_UPDATE || !imageUrl || isShuffled || isContentChange && bannerImage !== lastInput;
}
if (shouldFetchNewImage) {
imageUrl = await plugin.getImageUrl(inputType, bannerImage, file.path);
if (imageUrl) {
plugin.loadedImages.set(file.path, imageUrl);
plugin.lastKeywords.set(file.path, bannerImage);
}
}
if (imageUrl) {
const imageDisplay = getFrontmatterValue(frontmatter, plugin.settings.customImageDisplayField) || (folderSpecific == null ? void 0 : folderSpecific.imageDisplay) || plugin.settings.imageDisplay;
const imageRepeat = getFrontmatterValue(frontmatter, plugin.settings.customImageRepeatField) || (folderSpecific == null ? void 0 : folderSpecific.imageRepeat) || plugin.settings.imageRepeat;
let isVideoFile = false;
let fileUrl = "";
if (typeof imageUrl === "object" && imageUrl !== null) {
isVideoFile = imageUrl.isVideo === true;
fileUrl = imageUrl.url;
} else {
fileUrl = imageUrl;
if (typeof fileUrl === "string" && fileUrl.includes("/media-attachment/") && (fileUrl.toLowerCase().endsWith(".mp4") || fileUrl.toLowerCase().endsWith(".mov"))) {
isVideoFile = true;
}
}
const isSvg = fileUrl.includes("image/svg+xml") || file.path && file.path.toLowerCase().endsWith(".svg");
if (fileUrl.startsWith("blob:")) {
try {
const response = await fetch(fileUrl);
if (!response.ok) {
throw new Error("Blob URL validation failed");
}
} catch (error) {
console.log("Blob URL invalid, refreshing file:", error);
plugin.loadedImages.delete(file.path);
URL.revokeObjectURL(fileUrl);
const inputType2 = plugin.getInputType(bannerImage, file.path);
const freshResult = await plugin.getImageUrl(inputType2, bannerImage, file.path);
if (freshResult) {
if (typeof freshResult === "object" && freshResult !== null) {
isVideoFile = freshResult.isVideo === true;
fileUrl = freshResult.url;
} else {
fileUrl = freshResult;
}
plugin.loadedImages.set(file.path, fileUrl);
}
}
}
if (isVideoFile) {
bannerDiv.style.backgroundImage = "";
const existingVideo = bannerDiv.querySelector("video");
if (existingVideo) {
existingVideo.remove();
}
const videoEl = document.createElement("video");
videoEl.className = "pixel-banner-video";
videoEl.src = fileUrl;
videoEl.autoplay = true;
videoEl.loop = true;
videoEl.muted = true;
videoEl.playsInline = true;
videoEl.style.width = "100%";
videoEl.style.height = "100%";
videoEl.style.objectFit = imageDisplay || "cover";
videoEl.style.position = "absolute";
videoEl.style.top = "0";
videoEl.style.left = "0";
bannerDiv.appendChild(videoEl);
} else {
const existingVideo = bannerDiv.querySelector("video");
if (existingVideo) {
existingVideo.remove();
}
bannerDiv.style.backgroundImage = `url('${fileUrl}')`;
if (isSvg) {
bannerDiv.style.backgroundSize = imageDisplay === "contain" ? "contain" : "100% 100%";
} else {
bannerDiv.style.backgroundSize = imageDisplay || "cover";
}
bannerDiv.style.backgroundRepeat = imageRepeat ? "repeat" : "no-repeat";
}
bannerDiv.style.display = "block";
if (isEmbedded) {
bannerDiv.style.setProperty("--pixel-banner-image", `url('${imageUrl}')`);
}
const viewImageIcon = container.querySelector(":scope > .view-image-icon");
if (viewImageIcon && viewImageIcon._updateVisibility) {
const bannerValue = getFrontmatterValue(frontmatter, plugin.settings.customBannerField);
let displayUrl = bannerValue || file.path;
if (inputType === "keyword") {
if (imageUrl && typeof imageUrl === "object" && imageUrl.url) {
displayUrl = imageUrl.url;
} else if (imageUrl && typeof imageUrl === "string") {
displayUrl = imageUrl;
}
}
viewImageIcon._updateVisibility(imageUrl, displayUrl);
}
plugin.applyBannerSettings(bannerDiv, ctx, isEmbedded);
const hideEmbeddedNoteBanners2 = getFrontmatterValue(frontmatter, plugin.settings.customHideEmbeddedNoteBannersField) || (folderSpecific == null ? void 0 : folderSpecific.hideEmbeddedNoteBanners) || plugin.settings.hideEmbeddedNoteBanners || false;
let effectiveContentStart = 0;
if (!hideEmbeddedNoteBanners2 || !isEmbedded) {
const frontmatterContentStart = getFrontmatterValue(frontmatter, plugin.settings.customContentStartField);
const parsedFrontmatterStart = frontmatterContentStart ? Number(frontmatterContentStart) : null;
effectiveContentStart = (_e = (_d = parsedFrontmatterStart != null ? parsedFrontmatterStart : contentStartPosition) != null ? _d : folderSpecific == null ? void 0 : folderSpecific.contentStartPosition) != null ? _e : plugin.settings.contentStartPosition;
}
plugin.applyContentStartPosition(viewContent, effectiveContentStart);
plugin.applyBannerWidth(viewContent);
const canPin = (inputType === "keyword" || inputType === "url") && plugin.settings.showPinIcon && !isEmbedded && !isHoverPopover;
if (canPin) {
let leftOffset = plugin.settings.bannerGap + 5;
const iconEls = container.querySelectorAll(".select-image-icon, .view-image-icon");
if (iconEls == null ? void 0 : iconEls.length) {
leftOffset = 10 + 35 * iconEls.length + plugin.settings.bannerGap;
}
const pinIcon = createDiv({ cls: "pin-icon" });
pinIcon.style.position = "absolute";
pinIcon.style.top = "10px";
pinIcon.style.left = `${leftOffset}px`;
pinIcon.style.fontSize = "1.5em";
pinIcon.style.cursor = "pointer";
pinIcon.innerHTML = "\u{1F4CC}";
pinIcon._isPersistentPin = true;
pinIcon.onclick = async () => {
try {
const currentImage = plugin.loadedImages.get(file.path);
if (!currentImage) {
new import_obsidian27.Notice("Could not find the current image URL to pin.");
console.error("Error pinning image: currentImage is null or undefined for file.path:", file.path);
return;
}
const imageUrlToPin = typeof currentImage === "object" && currentImage.url ? currentImage.url : currentImage;
await handlePinIconClick(imageUrlToPin, plugin);
} catch (error) {
console.error("Error pinning image:", error);
new import_obsidian27.Notice("Failed to pin the image.");
}
};
container.appendChild(pinIcon);
leftOffset += 35;
if (inputType === "keyword" && plugin.settings.showRefreshIcon) {
const refreshIcon = createDiv({
cls: "refresh-icon",
attr: {
style: `
position: absolute;
top: 10px;
left: ${leftOffset}px;
font-size: 1.5em;
cursor: pointer;
`
}
});
refreshIcon.innerHTML = "\u{1F504}";
refreshIcon._isPersistentRefresh = true;
refreshIcon.onclick = async () => {
try {
plugin.loadedImages.delete(file.path);
plugin.lastKeywords.delete(file.path);
const originalBannerValue = getFrontmatterValue(frontmatter, plugin.settings.customBannerField);
const result = await plugin.getImageUrl(inputType, originalBannerValue || bannerImage, file.path);
if (result) {
let isVideoFile2 = false;
let fileUrl2 = "";
if (typeof result === "object" && result !== null) {
isVideoFile2 = result.isVideo === true;
fileUrl2 = result.url;
} else {
fileUrl2 = result;
}
plugin.loadedImages.set(file.path, fileUrl2);
plugin.lastKeywords.set(file.path, bannerImage);
if (isVideoFile2) {
bannerDiv.style.backgroundImage = "";
const existingVideo = bannerDiv.querySelector("video");
if (existingVideo) {
existingVideo.remove();
}
const videoEl = document.createElement("video");
videoEl.className = "pixel-banner-video";
videoEl.src = fileUrl2;
videoEl.autoplay = true;
videoEl.loop = true;
videoEl.muted = true;
videoEl.playsInline = true;
videoEl.style.width = "100%";
videoEl.style.height = "100%";
videoEl.style.objectFit = "cover";
videoEl.style.position = "absolute";
videoEl.style.top = "0";
videoEl.style.left = "0";
bannerDiv.appendChild(videoEl);
} else {
bannerDiv.style.backgroundImage = `url('${fileUrl2}')`;
const existingVideo = bannerDiv.querySelector("video");
if (existingVideo) {
existingVideo.remove();
}
}
const viewImageIcon2 = container.querySelector(":scope > .view-image-icon");
if (viewImageIcon2 && viewImageIcon2._updateVisibility) {
const bannerValue = getFrontmatterValue(frontmatter, plugin.settings.customBannerField);
let displayUrl = bannerValue || file.path;
const refreshInputType = plugin.getInputType(bannerValue, file.path);
if (refreshInputType === "keyword") {
if (fileUrl2 && typeof fileUrl2 === "object" && fileUrl2.url) {
displayUrl = fileUrl2.url;
} else if (fileUrl2 && typeof fileUrl2 === "string") {
displayUrl = fileUrl2;
}
}
viewImageIcon2._updateVisibility(fileUrl2, displayUrl);
}
new import_obsidian27.Notice("\u{1F504} Refreshed banner");
}
} catch (error) {
console.error("Error refreshing banner:", error);
new import_obsidian27.Notice("Failed to refresh the banner.");
}
};
container.appendChild(refreshIcon);
}
}
} else {
bannerDiv.style.display = "none";
plugin.loadedImages.delete(file.path);
plugin.lastKeywords.delete(file.path);
if (!isEmbedded) {
viewContent.classList.remove("pixel-banner");
}
}
}
}
var debouncedUpdateBanner = debounceFunction(updateBanner, 50);
async function updateBanner(plugin, view, isContentChange, updateMode = plugin.UPDATE_MODE.FULL_UPDATE) {
var _a, _b, _c, _d, _e, _f, _g, _h, _i, _j, _k, _l, _m, _n;
if (!view || !view.file) {
return;
}
const filePath = view.file.path;
const currentTime = Date.now();
const lastUpdateTime = updateBannerDebounceMap.get(filePath);
if (lastUpdateTime && currentTime - lastUpdateTime < UPDATE_BANNER_DEBOUNCE_DELAY) {
return;
}
updateBannerDebounceMap.set(filePath, currentTime);
if (!isContentChange) {
await new Promise((resolve) => setTimeout(resolve, 50));
}
const frontmatter = (_a = plugin.app.metadataCache.getFileCache(view.file)) == null ? void 0 : _a.frontmatter;
const contentEl = view.contentEl;
const isEmbedded = contentEl.classList.contains("internal-embed") && contentEl.classList.contains("markdown-embed");
const isHoverPopover = contentEl.closest(".hover-popover") !== null;
const viewContent = contentEl;
const nonPersistentOverlays = viewContent.querySelectorAll('.banner-icon-overlay:not([data-persistent="true"])');
nonPersistentOverlays.forEach((overlay) => overlay.remove());
["markdown-preview-sizer", "cm-sizer"].forEach((container) => {
const containerEl = viewContent.querySelector(`.${container}`);
if (containerEl) {
const bannerImage2 = containerEl.querySelector(":scope > .pixel-banner-image");
if (bannerImage2) {
const allOverlays = containerEl.querySelectorAll(':scope > .banner-icon-overlay[data-persistent="true"]');
allOverlays.forEach((overlay) => {
if (overlay.previousElementSibling !== bannerImage2) {
overlay.remove();
}
});
}
}
});
const existingBanner = contentEl.querySelector(".pixel-banner-image");
const folderSpecific = plugin.getFolderSpecificImage(view.file.path);
let bannerImage = null;
const shufflePath = getFrontmatterValue(frontmatter, plugin.settings.customBannerShuffleField) || (folderSpecific == null ? void 0 : folderSpecific.enableImageShuffle);
if (shufflePath && updateMode !== plugin.UPDATE_MODE.ENSURE_VISIBILITY) {
const randomImagePath = await plugin.getRandomImageFromFolder(shufflePath);
if (randomImagePath) {
bannerImage = randomImagePath;
}
} else if (shufflePath && updateMode === plugin.UPDATE_MODE.ENSURE_VISIBILITY) {
const cacheKey = plugin.generateCacheKey(view.file.path, plugin.app.workspace.activeLeaf.id, true);
const cachedState = plugin.bannerStateCache.get(cacheKey);
if ((_b = cachedState == null ? void 0 : cachedState.state) == null ? void 0 : _b.imageUrl) {
bannerImage = cachedState.state.imageUrl;
} else {
const randomImagePath = await plugin.getRandomImageFromFolder(shufflePath);
if (randomImagePath) {
bannerImage = randomImagePath;
}
}
}
if (!bannerImage) {
bannerImage = getFrontmatterValue(frontmatter, plugin.settings.customBannerField) || (folderSpecific == null ? void 0 : folderSpecific.image);
}
if (!isEmbedded && !bannerImage) {
contentEl.classList.remove("pixel-banner");
const previewSizer = contentEl.querySelector(".markdown-preview-sizer");
if (previewSizer) {
previewSizer.style.removeProperty("padding-top");
}
const sourceSizer = contentEl.querySelector(".cm-sizer");
if (sourceSizer) {
sourceSizer.style.removeProperty("padding-top");
}
if (existingBanner) {
existingBanner.style.backgroundImage = "";
existingBanner.style.display = "none";
}
} else if (isEmbedded && !bannerImage) {
const embedRoot = viewContent.closest(".internal-embed.markdown-embed");
if (embedRoot) {
embedRoot.style.setProperty("--pixel-banner-embed-min-height", "1%");
embedRoot.style.setProperty("--pixel-banner-content-start", "0");
}
}
if (isContentChange) {
plugin.loadedImages.delete(view.file.path);
plugin.lastKeywords.delete(view.file.path);
}
let yPosition = (_c = folderSpecific == null ? void 0 : folderSpecific.yPosition) != null ? _c : plugin.settings.yPosition;
let xPosition = (_d = folderSpecific == null ? void 0 : folderSpecific.xPosition) != null ? _d : plugin.settings.xPosition;
let contentStartPosition = (_e = folderSpecific == null ? void 0 : folderSpecific.contentStartPosition) != null ? _e : plugin.settings.contentStartPosition;
if (bannerImage) {
if (Array.isArray(bannerImage)) {
bannerImage = bannerImage.flat()[0];
}
if (typeof bannerImage === "string" && !bannerImage.startsWith("[[") && !bannerImage.startsWith("![[") && !bannerImage.startsWith("http")) {
if (bannerImage.includes(",")) {
const parts = bannerImage.split(",").map((p) => p.trim());
const isFile = (str) => SUPPORTED_EXTENSIONS.some((ext) => str.toLowerCase().endsWith(`.${ext}`));
const isKeyword = (str) => !str.includes(".");
if (parts.length > 1 && (parts.every(isFile) || parts.every(isKeyword))) {
const bannerValues = parts.filter((v) => v.length > 0);
if (bannerValues.length > 0) {
bannerImage = bannerValues[Math.floor(Math.random() * bannerValues.length)];
} else {
bannerImage = null;
}
}
}
}
if (bannerImage && typeof bannerImage === "string" && !bannerImage.startsWith("[[") && !bannerImage.startsWith("![[") && !bannerImage.startsWith("http")) {
const file = plugin.app.vault.getAbstractFileByPath(bannerImage);
if (file && "extension" in file) {
if (file.extension.match(/^(jpg|jpeg|png|gif|bmp|svg)$/i)) {
bannerImage = `![[${bannerImage}]]`;
}
}
}
}
let imageDisplay = getFrontmatterValue(frontmatter, plugin.settings.customImageDisplayField) || (folderSpecific == null ? void 0 : folderSpecific.imageDisplay) || plugin.settings.imageDisplay;
let imageRepeat = (_g = (_f = getFrontmatterValue(frontmatter, plugin.settings.customImageRepeatField)) != null ? _f : folderSpecific == null ? void 0 : folderSpecific.imageRepeat) != null ? _g : plugin.settings.imageRepeat;
let bannerHeight = (_i = (_h = getFrontmatterValue(frontmatter, plugin.settings.customBannerHeightField)) != null ? _h : folderSpecific == null ? void 0 : folderSpecific.bannerHeight) != null ? _i : plugin.settings.bannerHeight;
let fade = (_k = (_j = getFrontmatterValue(frontmatter, plugin.settings.customFadeField)) != null ? _j : folderSpecific == null ? void 0 : folderSpecific.fade) != null ? _k : plugin.settings.fade;
let borderRadius = (_m = (_l = getFrontmatterValue(frontmatter, plugin.settings.customBorderRadiusField)) != null ? _l : folderSpecific == null ? void 0 : folderSpecific.borderRadius) != null ? _m : plugin.settings.borderRadius;
let maxWidth = getFrontmatterValue(frontmatter, plugin.settings.customBannerMaxWidthField) || (folderSpecific == null ? void 0 : folderSpecific.bannerMaxWidth) || "unset";
if (bannerImage) {
await addPixelBanner(plugin, contentEl, {
frontmatter,
file: view.file,
isContentChange,
yPosition,
xPosition,
contentStartPosition,
bannerImage,
imageDisplay,
imageRepeat,
bannerHeight,
fade,
borderRadius,
maxWidth,
isReadingView: view.getMode && view.getMode() === "preview",
updateMode
});
plugin.lastYPositions.set(view.file.path, yPosition);
} else if (existingBanner) {
existingBanner.style.display = "none";
}
if (!isEmbedded) {
const embeddedNotes = contentEl.querySelectorAll(".internal-embed.markdown-embed");
for (const embed of embeddedNotes) {
const embedSrc = embed.getAttribute("src");
if (!embedSrc) continue;
const embedFile = plugin.app.metadataCache.getFirstLinkpathDest(embedSrc, view.file.path);
if (embedFile) {
const embedView = {
file: embedFile,
contentEl: embed,
getMode: () => "preview"
};
await updateBanner(plugin, embedView, false);
}
}
}
if (!bannerImage) {
const viewContent2 = view.contentEl;
const isReadingView = view.getMode && view.getMode() === "preview";
let container = isReadingView ? viewContent2.querySelector(".markdown-preview-sizer:not(.internal-embed .markdown-preview-sizer)") || viewContent2.querySelector(".markdown-preview-view") : viewContent2.querySelector(".cm-sizer") || viewContent2.querySelector(".markdown-source-view");
if (!container && viewContent2.classList.contains("markdown-preview-view")) {
container = viewContent2;
}
if (container) {
const oldViewIcons = container.querySelectorAll(".view-image-icon");
const oldPinIcons = container.querySelectorAll(".pin-icon");
const oldRefreshIcons = container.querySelectorAll(".refresh-icon");
const oldSelectIcons = container.querySelectorAll(".select-image-icon");
[...oldViewIcons, ...oldPinIcons, ...oldRefreshIcons, ...oldSelectIcons].forEach((el) => el.remove());
}
if (!isEmbedded && container && plugin.settings.showSelectImageIcon) {
const opacity = plugin.settings.selectImageIconOpacity / 100;
const existingSelectIcon = container.querySelector(".select-image-icon");
if (!existingSelectIcon) {
const selectImageIcon = createDiv({
cls: "select-image-icon",
attr: {
style: `
position: absolute;
top: 10px;
left: ${plugin.settings.bannerGap + 5}px;
font-size: 1.8em;
cursor: pointer;
opacity: ${opacity};
`
}
});
const flagColor = getFrontmatterValue(frontmatter, plugin.settings.customFlagColorField) || plugin.settings.selectImageIconFlag;
selectImageIcon.innerHTML = ``;
selectImageIcon._isPersistentSelectImage = true;
selectImageIcon.onclick = () => plugin.handleBannerIconClick();
container.insertBefore(selectImageIcon, container.firstChild);
}
}
if (container) {
const existingViewImageIcon = container.querySelector(".view-image-icon");
if (existingViewImageIcon) {
existingViewImageIcon.remove();
}
}
}
if (plugin.settings.hidePixelBannerFields && view.getMode() === "preview") {
plugin.updateFieldVisibility(view);
}
const bannerIcon = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconField);
let bannerIconImage = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconImageField);
if (Array.isArray(bannerIconImage)) {
bannerIconImage = bannerIconImage.flat()[0];
if (bannerIconImage && !bannerIconImage.startsWith("[[") && !bannerIconImage.startsWith("![[")) {
bannerIconImage = `[[${bannerIconImage}]]`;
}
}
if (isEmbedded) {
const embedContainer = contentEl.querySelector(".markdown-preview-sizer") || contentEl.querySelector(".markdown-embed-content") || contentEl;
const thisEmbedOverlays = embedContainer.querySelectorAll(':scope > .banner-icon-overlay:not([data-persistent="true"])');
thisEmbedOverlays.forEach((overlay) => overlay.remove());
} else {
["markdown-preview-view", "markdown-source-view"].forEach((viewType) => {
const viewContainer = contentEl.querySelector(`.${viewType}`);
if (viewContainer) {
const mainOverlays = viewContainer.querySelectorAll(':scope > .banner-icon-overlay:not([data-persistent="true"])');
mainOverlays.forEach((overlay) => overlay.remove());
}
});
}
if ((!bannerIcon || typeof bannerIcon === "string" && !bannerIcon.trim()) && (!bannerIconImage || typeof bannerIconImage === "string" && !bannerIconImage.trim())) {
if (isEmbedded) {
const embedContainer = contentEl.querySelector(".markdown-preview-sizer") || contentEl.querySelector(".markdown-embed-content") || contentEl;
const persistentOverlays = embedContainer.querySelectorAll(':scope > .banner-icon-overlay[data-persistent="true"]');
persistentOverlays.forEach((overlay) => {
plugin.returnIconOverlay(overlay);
overlay.remove();
});
} else {
const previewContainer = contentEl.querySelector("div.markdown-preview-sizer");
const sourceContainer = contentEl.querySelector("div.cm-sizer");
if (previewContainer) {
const previewOverlays = previewContainer.querySelectorAll(':scope > .banner-icon-overlay[data-persistent="true"]');
previewOverlays.forEach((overlay) => {
plugin.returnIconOverlay(overlay);
overlay.remove();
});
}
if (sourceContainer) {
const sourceOverlays = sourceContainer.querySelectorAll(':scope > .banner-icon-overlay[data-persistent="true"]');
sourceOverlays.forEach((overlay) => {
plugin.returnIconOverlay(overlay);
overlay.remove();
});
}
}
}
if (bannerIcon && typeof bannerIcon === "string" && bannerIcon.trim() || bannerIconImage && typeof bannerIconImage === "string" && bannerIconImage.trim()) {
const cleanIcon = bannerIcon ? bannerIcon.trim() : "";
const cacheKey = plugin.generateCacheKey(view.file.path, plugin.app.workspace.activeLeaf.id);
const cachedState = plugin.bannerStateCache.get(cacheKey);
const cachedIconState = (_n = cachedState == null ? void 0 : cachedState.state) == null ? void 0 : _n.iconState;
const createOrUpdateIconOverlay = async (banner, viewType) => {
var _a2, _b2;
if (!banner) {
return;
}
const currentIconState = {
icon: cleanIcon,
size: getFrontmatterValue(frontmatter, plugin.settings.customBannerIconSizeField) || plugin.settings.bannerIconSize,
xPosition: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconXPositionField),
plugin.settings.bannerIconXPosition
]),
opacity: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconOpacityField),
plugin.settings.bannerIconOpacity
]),
color: getFrontmatterValue(frontmatter, plugin.settings.customBannerIconColorField) || plugin.settings.bannerIconColor,
fontWeight: getFrontmatterValue(frontmatter, plugin.settings.customBannerIconFontWeightField) || plugin.settings.bannerIconFontWeight,
backgroundColor: getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBackgroundColorField) || plugin.settings.bannerIconBackgroundColor,
paddingX: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingXField),
plugin.settings.bannerIconPaddingX
]),
paddingY: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingYField),
plugin.settings.bannerIconPaddingY
]),
borderRadius: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBorderRadiusField),
plugin.settings.bannerIconBorderRadius
]),
verticalOffset: getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconVerticalOffsetField),
plugin.settings.bannerIconVerticalOffset
]),
imageAlignment: getFrontmatterValue(frontmatter, plugin.settings.customBannerIconImageAlignmentField) || plugin.settings.bannerIconImageAlignment,
viewType
};
const existingOverlay = ((_b2 = (_a2 = banner.nextElementSibling) == null ? void 0 : _a2.classList) == null ? void 0 : _b2.contains("banner-icon-overlay")) ? banner.nextElementSibling : null;
if (existingOverlay) {
if (!plugin.shouldUpdateIconOverlay(existingOverlay, currentIconState, viewType)) {
return existingOverlay;
}
plugin.returnIconOverlay(existingOverlay);
}
const bannerIconOverlay = plugin.getIconOverlay();
bannerIconOverlay.dataset.viewType = viewType;
bannerIconOverlay.dataset.persistent = "true";
bannerIconOverlay.textContent = "";
bannerIconOverlay._isPersistentBannerIcon = true;
bannerIconOverlay.innerHTML = "";
const imageAlignment = currentIconState.imageAlignment === "right" ? "right" : "left";
let imgElement = null;
if (bannerIconImage) {
const inputType = plugin.getIconImageInputType(bannerIconImage, view.file.path);
let imagePath = null;
let resolvedVaultPath = null;
if (inputType === "invalid") {
console.warn("Invalid banner icon image value detected:", bannerIconImage);
} else {
switch (inputType) {
case "obsidianLink":
const file = plugin.getPathFromObsidianLink(bannerIconImage);
if (file) {
imagePath = plugin.loadedImages.get(file.path);
if (!imagePath) {
imagePath = await plugin.getVaultImageUrl(file.path);
if (imagePath) plugin.loadedImages.set(file.path, imagePath);
}
}
break;
case "markdownImage":
const mdPath = plugin.getPathFromMarkdownImage(bannerIconImage);
if (typeof mdPath === "string") {
try {
new URL(mdPath);
imagePath = mdPath;
} catch (_) {
imagePath = plugin.loadedImages.get(mdPath);
if (!imagePath) {
imagePath = await plugin.getVaultImageUrl(mdPath);
if (imagePath) plugin.loadedImages.set(mdPath, imagePath);
}
}
} else if (mdPath) {
imagePath = plugin.loadedImages.get(mdPath.path);
if (!imagePath) {
imagePath = await plugin.getVaultImageUrl(mdPath.path);
if (imagePath) plugin.loadedImages.set(mdPath.path, imagePath);
}
}
break;
case "vaultPath":
const cleanedInput = bannerIconImage.trim().replace(/^["'](.*)["']$/, "$1");
let resolvedFile = plugin.app.vault.getAbstractFileByPath(cleanedInput);
if (!resolvedFile) {
resolvedFile = plugin.app.metadataCache.getFirstLinkpathDest(cleanedInput, view.file.path);
}
if (resolvedFile && "path" in resolvedFile) {
resolvedVaultPath = resolvedFile.path;
imagePath = plugin.loadedImages.get(resolvedVaultPath);
if (!imagePath) {
imagePath = await plugin.getVaultImageUrl(resolvedVaultPath);
if (imagePath) plugin.loadedImages.set(resolvedVaultPath, imagePath);
}
} else {
console.warn("[Icon Image] Could not resolve vault path:", cleanedInput);
}
break;
case "url":
imagePath = bannerIconImage;
break;
case "fileUrl":
console.warn("file:/// URLs are not supported for banner icon images");
break;
case "keyword":
console.warn("Keywords are not supported for banner icon images");
break;
}
if (imagePath) {
imgElement = document.createElement("img");
const imageUrl = typeof imagePath === "object" && imagePath.url ? imagePath.url : imagePath;
imgElement.src = imageUrl;
imgElement.className = "banner-icon-image";
}
}
}
let textElement = null;
if (cleanIcon) {
textElement = document.createElement("div");
textElement.className = "banner-icon-text";
textElement.textContent = cleanIcon;
}
if (imageAlignment === "right") {
if (textElement) bannerIconOverlay.appendChild(textElement);
if (imgElement) bannerIconOverlay.appendChild(imgElement);
} else {
if (imgElement) bannerIconOverlay.appendChild(imgElement);
if (textElement) bannerIconOverlay.appendChild(textElement);
}
bannerIconOverlay.style.display = "block";
bannerIconOverlay.style.fontSize = `${currentIconState.size}px`;
bannerIconOverlay.style.left = `${currentIconState.xPosition}%`;
bannerIconOverlay.style.opacity = `${currentIconState.opacity}%`;
bannerIconOverlay.style.color = currentIconState.color;
bannerIconOverlay.style.fontWeight = currentIconState.fontWeight;
bannerIconOverlay.style.backgroundColor = currentIconState.backgroundColor;
bannerIconOverlay.style.padding = `${currentIconState.paddingY}px ${currentIconState.paddingX}px`;
bannerIconOverlay.style.borderRadius = `${currentIconState.borderRadius}px`;
bannerIconOverlay.style.marginTop = `${currentIconState.verticalOffset}px`;
banner.insertAdjacentElement("afterend", bannerIconOverlay);
return bannerIconOverlay;
};
if (isEmbedded) {
const embedContainer = contentEl.querySelector(".markdown-preview-sizer") || contentEl.querySelector(".markdown-embed-content") || contentEl;
const previewBanner = embedContainer.querySelector(":scope > .pixel-banner-image");
await createOrUpdateIconOverlay(previewBanner, "preview");
} else {
const previewContainer = contentEl.querySelector("div.markdown-preview-sizer");
const sourceContainer = contentEl.querySelector("div.cm-sizer");
if (previewContainer) {
const previewBanner = previewContainer.querySelector(":scope > .pixel-banner-image");
await createOrUpdateIconOverlay(previewBanner, "preview");
}
if (sourceContainer) {
const sourceBanner = sourceContainer.querySelector(":scope > .pixel-banner-image");
await createOrUpdateIconOverlay(sourceBanner, "source");
}
}
}
}
var debouncedApplyBannerSettings = debounceAndSwallow(applyBannerSettings, 350);
function applyBannerSettings(plugin, bannerDiv, ctx, isEmbedded) {
const { frontmatter, imageDisplay, imageRepeat, bannerHeight, fade, borderRadius, maxWidth } = ctx;
const folderSpecific = plugin.getFolderSpecificImage(ctx.file.path);
let pixelBannerYPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customYPositionField),
folderSpecific == null ? void 0 : folderSpecific.yPosition,
plugin.settings.yPosition
]);
let pixelBannerXPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customXPositionField),
folderSpecific == null ? void 0 : folderSpecific.xPosition,
plugin.settings.xPosition
]);
const pixelBannerMaxWidth = getFrontmatterValue(frontmatter, plugin.settings.customBannerMaxWidthField) || (folderSpecific == null ? void 0 : folderSpecific.bannerMaxWidth) || plugin.settings.bannerMaxWidth || "unset";
const titleColor = getFrontmatterValue(frontmatter, plugin.settings.customTitleColorField) || (folderSpecific == null ? void 0 : folderSpecific.titleColor) || plugin.settings.titleColor;
const bannerIconSize = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconSizeField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconSize) || plugin.settings.bannerIconSize || 70;
const bannerIconXPosition = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconXPositionField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconXPosition,
plugin.settings.bannerIconXPosition,
25
]);
const bannerIconOpacity = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconOpacityField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconOpacity,
plugin.settings.bannerIconOpacity,
100
]);
const bannerIconColor = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconColorField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconColor) || plugin.settings.bannerIconColor || "var(--text-normal)";
const bannerIconFontWeight = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconFontWeightField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconFontWeight) || plugin.settings.bannerIconFontWeight || "normal";
const bannerIconBackgroundColor = getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBackgroundColorField) || (folderSpecific == null ? void 0 : folderSpecific.bannerIconBackgroundColor) || plugin.settings.bannerIconBackgroundColor || "transparent";
const bannerIconPaddingX = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingXField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconPaddingX,
plugin.settings.bannerIconPaddingX,
10
]);
const bannerIconPaddingY = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconPaddingYField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconPaddingY,
plugin.settings.bannerIconPaddingY,
10
]);
const bannerIconBorderRadius = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconBorderRadiusField),
folderSpecific == null ? void 0 : folderSpecific.bannerIconBorderRadius,
plugin.settings.bannerIconBorderRadius,
17
]);
const bannerIconVerticalOffset = getValueWithZeroCheck([
Number(getFrontmatterValue(frontmatter, plugin.settings.customBannerIconVerticalOffsetField)),
folderSpecific == null ? void 0 : folderSpecific.bannerIconVerticalOffset,
plugin.settings.bannerIconVerticalOffset,
0
]);
const bannerIconRotate = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconRotateField),
0
]);
const bannerIconImageSizeMultiplier = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconImageSizeMultiplierField),
plugin.settings.bannerIconImageSizeMultiplier,
1
]);
const bannerIconTextVerticalOffset = getValueWithZeroCheck([
getFrontmatterValue(frontmatter, plugin.settings.customBannerIconTextVerticalOffsetField),
plugin.settings.bannerIconTextVerticalOffset,
0
]);
const hideEmbeddedNoteBanners = getFrontmatterValue(frontmatter, plugin.settings.customHideEmbeddedNoteBannersField) || (folderSpecific == null ? void 0 : folderSpecific.hideEmbeddedNoteBanners) || plugin.settings.hideEmbeddedNoteBanners || false;
const bannerAlignment = getFrontmatterValue(frontmatter, plugin.settings.customBannerAlignmentField) || "center";
let alignmentValue = "0 auto";
if (bannerAlignment === "left") {
alignmentValue = "0 auto 0 0";
} else if (bannerAlignment === "right") {
alignmentValue = "0 0 0 auto";
}
const cssVars = {
"--pixel-banner-height": hideEmbeddedNoteBanners && isEmbedded ? "0px" : `${bannerHeight}px`,
"--pixel-banner-fade": `${fade}%`,
"--pixel-banner-fade-in-animation-duration": `${plugin.settings.bannerFadeInAnimationDuration}ms`,
"--pixel-banner-radius": `${borderRadius}px`,
"--pixel-banner-max-width": pixelBannerMaxWidth === "unset" ? "unset" : `${pixelBannerMaxWidth}px`,
"--pixel-banner-y-position": `${pixelBannerYPosition}%`,
"--pixel-banner-x-position": `${pixelBannerXPosition}%`,
"--pixel-banner-title-color": titleColor,
"--pixel-banner-icon-size": `${bannerIconSize}px`,
"--pixel-banner-icon-start": `${bannerHeight - bannerIconSize / 2}px`,
"--pixel-banner-icon-x": `${bannerIconXPosition}%`,
"--pixel-banner-icon-opacity": `${bannerIconOpacity}%`,
"--pixel-banner-icon-color": bannerIconColor,
"--pixel-banner-icon-font-weight": bannerIconFontWeight,
"--pixel-banner-icon-background-color": bannerIconBackgroundColor,
"--pixel-banner-icon-padding-x": `${bannerIconPaddingX}px`,
"--pixel-banner-icon-padding-y": `${bannerIconPaddingY}px`,
"--pixel-banner-icon-border-radius": `${bannerIconBorderRadius}px`,
"--pixel-banner-icon-vertical-offset": `${bannerIconVerticalOffset}px`,
"--pixel-banner-icon-rotate": `${bannerIconRotate}deg`,
"--pixel-banner-icon-image-size-multiplier": `${bannerIconImageSizeMultiplier}em`,
"--pixel-banner-icon-text-vertical-offset": `${bannerIconTextVerticalOffset}px`,
"--pixel-banner-embed-min-height": !hideEmbeddedNoteBanners ? `${parseInt(bannerHeight) + parseInt(bannerIconSize) / 2 + parseInt(bannerIconVerticalOffset) + parseInt(bannerIconPaddingY)}px` : "0px",
"--pixel-banner-alignment": alignmentValue
};
bannerDiv.style.backgroundSize = imageDisplay || "cover";
bannerDiv.style.backgroundRepeat = imageRepeat ? "repeat" : "no-repeat";
Object.entries(cssVars).forEach(([property, value]) => {
bannerDiv.style.setProperty(property, value);
});
const container = bannerDiv.closest(".markdown-preview-view, .markdown-source-view");
if (container) {
Object.entries(cssVars).forEach(([property, value]) => {
container.style.setProperty(property, value);
});
}
if (isEmbedded) {
const embedContainer = bannerDiv.closest(".internal-embed");
if (embedContainer) {
embedContainer.style.setProperty("--pixel-banner-content-start", cssVars["--pixel-banner-embed-min-height"]);
const embedContentDiv = embedContainer.querySelector(":scope > .markdown-embed-content");
if (embedContentDiv) {
Object.entries(cssVars).forEach(([property, value]) => {
embedContentDiv.style.setProperty(property, value);
});
const previewViewEl = embedContentDiv.querySelector(":scope > .markdown-preview-view");
if (previewViewEl) {
Object.entries(cssVars).forEach(([property, value]) => {
previewViewEl.style.setProperty(property, value);
});
}
}
}
}
}
var debouncedApplyContentStartPosition = debounceAndSwallow(applyContentStartPosition, 350);
function applyContentStartPosition(plugin, el, contentStartPosition) {
if (!el) {
return;
}
el.style.setProperty("--pixel-banner-content-start", `${contentStartPosition}px`);
}
var debouncedApplyBannerWidth = debounceAndSwallow(applyBannerWidth, 350);
function applyBannerWidth(plugin, el) {
if (!el) return;
setTimeout(() => {
if (!el.classList.contains("view-content")) {
return;
}
const theWidth = el.clientWidth;
const bannerGap = plugin.settings.bannerGap;
el.style.setProperty("--pixel-banner-width", `${theWidth - bannerGap * 2}px`);
el.style.setProperty("--pixel-banner-banner-gap", `${bannerGap}px`);
}, 50);
}
var debouncedUpdateAllBanners = debounceAndSwallow(updateAllBanners, 350);
function updateAllBanners(plugin) {
plugin.app.workspace.iterateAllLeaves((leaf) => {
if (leaf.view.getViewType() === "markdown") {
updateBanner(plugin, leaf.view, true);
}
});
}
var debouncedUpdateBannerPosition = debounceAndSwallow(updateBannerPosition, 350);
async function updateBannerPosition(plugin, file, position) {
if (!file) return;
const metadata = plugin.app.metadataCache.getFileCache(file);
if (!(metadata == null ? void 0 : metadata.frontmatter)) return;
await plugin.app.fileManager.processFrontMatter(file, (frontmatter) => {
frontmatter.banner_x = position.x;
frontmatter.banner_y = position.y;
});
}
function registerMarkdownPostProcessor(plugin) {
plugin.registerMarkdownPostProcessor((el, ctx) => {
var _a;
const isPreview = ctx.containerEl.classList.contains("markdown-preview-view");
const isHoverPopover = ctx.containerEl.closest(".hover-popover");
const debounceFile = ctx.sourcePath ? plugin.app.vault.getAbstractFileByPath(ctx.sourcePath) : null;
if (debounceFile) {
const currentTime = Date.now();
const lastProcessTime = markdownPostProcessorDebounceMap.get(debounceFile.path);
if (lastProcessTime && currentTime - lastProcessTime < MARKDOWN_PROCESSOR_DEBOUNCE_DELAY) {
return;
}
markdownPostProcessorDebounceMap.set(debounceFile.path, currentTime);
}
if (!isPreview && !isHoverPopover) return;
const file = ctx.sourcePath ? plugin.app.vault.getAbstractFileByPath(ctx.sourcePath) : null;
if (!file) return;
const frontmatter = (_a = plugin.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
let bannerImage = null;
for (const field of plugin.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
bannerImage = frontmatter[field];
break;
}
}
if (!bannerImage) {
const folderSpecific2 = plugin.getFolderSpecificImage(file.path);
if (folderSpecific2 == null ? void 0 : folderSpecific2.image) {
bannerImage = folderSpecific2.image;
}
}
if (!bannerImage) return;
const folderSpecific = plugin.getFolderSpecificImage(file.path);
const yPosition = getFrontmatterValue(frontmatter, plugin.settings.customYPositionField) || (folderSpecific == null ? void 0 : folderSpecific.yPosition) || plugin.settings.yPosition;
const xPosition = getFrontmatterValue(frontmatter, plugin.settings.customXPositionField) || (folderSpecific == null ? void 0 : folderSpecific.xPosition) || plugin.settings.xPosition;
const contentStartPosition = getFrontmatterValue(frontmatter, plugin.settings.customContentStartField) || (folderSpecific == null ? void 0 : folderSpecific.contentStartPosition) || plugin.settings.contentStartPosition;
addPixelBanner(plugin, ctx.containerEl, {
frontmatter,
file,
isContentChange: false,
yPosition,
xPosition,
contentStartPosition,
bannerImage,
isReadingView: true,
updateMode: plugin.UPDATE_MODE.FULL_UPDATE
});
});
}
// src/core/bannerUtils.js
var import_obsidian28 = require("obsidian");
function getInputType(input, sourcePath = "") {
if (Array.isArray(input)) {
input = input.flat()[0];
}
if (typeof input !== "string") {
return "invalid";
}
let cleanedInput = input.trim().replace(/^["'](.*)["']$/, "$1");
cleanedInput = cleanedInput.replace(/^!\[\[(.*)\]\]$/, "$1").replace(/^\[\[(.*)\]\]$/, "$1");
if (cleanedInput.includes("file:///")) {
return "fileUrl";
}
if (input.match(/^!?\[{2}.*\]{2}$/) || input.match(/^"?!?\[{2}.*\]{2}"?$/)) {
return "obsidianLink";
}
if (input.match(/^!\[\]\(.*\)$/) || input.match(/^"?!\[\]\(.*\)"?$/)) {
return "markdownImage";
}
try {
new URL(cleanedInput);
return "url";
} catch (_) {
const file = this.app.vault.getAbstractFileByPath(cleanedInput);
if (file && "extension" in file) {
if (file.extension.match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif|mp4|mov)$/i)) {
return "vaultPath";
}
}
const resolvedFile = this.app.metadataCache.getFirstLinkpathDest(cleanedInput, sourcePath);
if (resolvedFile && "extension" in resolvedFile) {
if (resolvedFile.extension.match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif|mp4|mov)$/i)) {
return "vaultPath";
}
}
return "keyword";
}
}
function getPathFromObsidianLink(link) {
let cleanLink = link.replace(/^["'](.*)["']$/, "$1");
cleanLink = cleanLink.startsWith("!") ? cleanLink.slice(1) : cleanLink;
let innerLink = cleanLink.startsWith("[[") ? cleanLink.slice(2) : cleanLink;
innerLink = innerLink.endsWith("]]") ? innerLink.slice(0, -2) : innerLink;
const path = innerLink.split("|")[0];
return this.app.metadataCache.getFirstLinkpathDest(path, "");
}
function getPathFromMarkdownImage(link) {
let cleanLink = link.replace(/^["'](.*)["']$/, "$1");
const match = cleanLink.match(/^!\[\]\((.*)\)$/);
if (match && match[1]) {
const path = match[1];
try {
new URL(path);
return path;
} catch (_) {
return this.app.metadataCache.getFirstLinkpathDest(path, "");
}
}
return null;
}
async function getVaultImageUrl(path) {
const file = this.app.vault.getAbstractFileByPath(path);
if (file && "extension" in file) {
try {
const fileExt = file.extension.toLowerCase();
const videoExtensions = ["mp4", "mov"];
if (videoExtensions.includes(fileExt)) {
const resourcePath = this.app.vault.getResourcePath(file);
return {
url: resourcePath,
isVideo: true,
fileType: fileExt,
// Store original path to help with caching
originalPath: path
};
}
const arrayBuffer = await this.app.vault.readBinary(file);
const mimeType = fileExt === "svg" ? "image/svg+xml" : `image/${fileExt}`;
const blob = new Blob([arrayBuffer], { type: mimeType });
const url = URL.createObjectURL(blob);
return {
url,
isVideo: false,
fileType: fileExt
};
} catch (error) {
console.error("Error reading vault file:", error);
return null;
}
}
return null;
}
function preloadImage(url) {
return new Promise((resolve, reject) => {
const img = new Image();
img.onload = () => resolve(url);
img.onerror = reject;
img.src = url;
});
}
function getIconImageInputType(input, sourcePath = "") {
if (Array.isArray(input)) {
input = input.flat()[0];
}
if (typeof input !== "string") {
return "invalid";
}
let cleanedInput = input.trim().replace(/^["'](.*)["']$/, "$1");
cleanedInput = cleanedInput.replace(/^!\[\[(.*)\]\]$/, "$1").replace(/^\[\[(.*)\]\]$/, "$1");
if (cleanedInput.includes("file:///")) {
return "fileUrl";
}
if (input.match(/^!?\[{2}.*\]{2}$/) || input.match(/^"?!?\[{2}.*\]{2}"?$/)) {
return "obsidianLink";
}
if (input.match(/^!\[\]\(.*\)$/) || input.match(/^"?!\[\]\(.*\)"?$/)) {
return "markdownImage";
}
try {
new URL(cleanedInput);
return "url";
} catch (_) {
const file = this.app.vault.getAbstractFileByPath(cleanedInput);
if (file && "extension" in file) {
if (file.extension.match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif|mp4|mov)$/i)) {
return "vaultPath";
}
}
const resolvedFile = this.app.metadataCache.getFirstLinkpathDest(cleanedInput, sourcePath);
if (resolvedFile && "extension" in resolvedFile) {
if (resolvedFile.extension.match(/^(jpg|jpeg|png|gif|bmp|svg|webp|avif|mp4|mov)$/i)) {
return "vaultPath";
}
}
return "keyword";
}
}
function getFolderPath(filePath) {
if (!filePath) return "/";
if (!filePath.includes("/")) {
return "/";
}
const lastSlashIndex = filePath.lastIndexOf("/");
return lastSlashIndex !== -1 ? filePath.substring(0, lastSlashIndex) : "";
}
function getFolderSpecificImage(filePath) {
if (!filePath) return null;
const folderPath = this.getFolderPath(filePath);
const sortedFolderImages = [...this.settings.folderImages].sort(
(a, b) => {
var _a, _b;
return (((_a = b.folder) == null ? void 0 : _a.length) || 0) - (((_b = a.folder) == null ? void 0 : _b.length) || 0);
}
);
for (const folderImage of sortedFolderImages) {
if (!folderImage.folder) continue;
if (folderImage.folder === "/") {
if (folderImage.directChildrenOnly) {
if (!filePath.includes("/")) {
return this.createFolderImageSettings(folderImage);
}
} else {
return this.createFolderImageSettings(folderImage);
}
continue;
}
const normalizedFolderPath = folderImage.folder.startsWith("/") ? folderImage.folder : "/" + folderImage.folder;
const normalizedFileFolderPath = "/" + folderPath;
if (folderImage.directChildrenOnly) {
if (normalizedFileFolderPath === normalizedFolderPath) {
return this.createFolderImageSettings(folderImage);
}
} else {
if (normalizedFileFolderPath.startsWith(normalizedFolderPath)) {
return this.createFolderImageSettings(folderImage);
}
}
}
return null;
}
function getFolderSpecificSetting(filePath, settingName) {
var _a;
const folderPath = this.getFolderPath(filePath);
for (const folderImage of this.settings.folderImages) {
if (folderPath.startsWith(folderImage.folder)) {
return (_a = folderImage[settingName]) != null ? _a : void 0;
}
}
return void 0;
}
function getRandomImageFromFolder(folderPath) {
try {
const folder = this.app.vault.getAbstractFileByPath(folderPath);
if (!folder || !folder.children) return null;
const imageFiles = folder.children.filter(
(file) => file.extension && ["png", "jpg", "jpeg", "gif", "webp", "svg", "avif"].includes(file.extension.toLowerCase())
);
if (imageFiles.length === 0) return null;
const randomImage = imageFiles[Math.floor(Math.random() * imageFiles.length)];
return randomImage.path;
} catch (error) {
console.error("Error getting random image:", error);
return null;
}
}
function getActiveApiProvider() {
if (this.settings.apiProvider !== "all") {
return this.settings.apiProvider;
}
if (this.settings.apiProviders && Array.isArray(this.settings.apiProviders)) {
for (const provider of this.settings.apiProviders) {
const hasKey = provider === "pexels" && this.settings.pexelsApiKey || provider === "pixabay" && this.settings.pixabayApiKey || provider === "flickr" && this.settings.flickrApiKey || provider === "unsplash" && this.settings.unsplashApiKey;
if (hasKey) {
return provider;
}
}
}
const availableProviders = [];
if (this.settings.pexelsApiKey) availableProviders.push("pexels");
if (this.settings.pixabayApiKey) availableProviders.push("pixabay");
if (this.settings.flickrApiKey) availableProviders.push("flickr");
if (this.settings.unsplashApiKey) availableProviders.push("unsplash");
if (availableProviders.length === 0) {
return null;
}
return availableProviders[Math.floor(Math.random() * availableProviders.length)];
}
function hasBannerFrontmatter(file) {
const metadata = this.app.metadataCache.getFileCache(file);
if (!(metadata == null ? void 0 : metadata.frontmatter)) return false;
return this.settings.customBannerField.some(
(fieldName) => metadata.frontmatter[fieldName] !== void 0
);
}
function createFolderImageSettings(folderImage) {
const settings = { ...folderImage };
if (folderImage.enableImageShuffle && folderImage.shuffleFolder) {
const randomImagePath = this.getRandomImageFromFolder(folderImage.shuffleFolder);
if (randomImagePath) {
settings.image = randomImagePath;
}
}
return settings;
}
// src/core/eventHandler.js
var import_obsidian29 = require("obsidian");
init_modals();
init_frontmatterUtils();
var bannerUpdateDebounceMap = /* @__PURE__ */ new Map();
var BANNER_UPDATE_DEBOUNCE_DELAY = 300;
async function handleActiveLeafChange(leaf) {
var _a;
if (!leaf || !(leaf.view instanceof import_obsidian29.MarkdownView) || !leaf.view.file) {
return;
}
const filePath = leaf.view.file.path;
const debounceCheckTime = Date.now();
const lastUpdateTime = bannerUpdateDebounceMap.get(filePath);
if (lastUpdateTime && debounceCheckTime - lastUpdateTime < BANNER_UPDATE_DEBOUNCE_DELAY) {
return;
}
bannerUpdateDebounceMap.set(filePath, debounceCheckTime);
this.cleanupCache();
const previousLeaf = this.app.workspace.activeLeaf;
if (previousLeaf && previousLeaf.view instanceof import_obsidian29.MarkdownView && previousLeaf !== leaf) {
this.cleanupPreviousLeaf(previousLeaf);
this.cleanupIconOverlay(previousLeaf.view);
}
const currentPath = leaf.view.file.path;
const leafId = leaf.id;
const frontmatter = (_a = this.app.metadataCache.getFileCache(leaf.view.file)) == null ? void 0 : _a.frontmatter;
const currentTime = Date.now();
const hasBannerIcon = this.settings.customBannerIconField.some((field) => frontmatter == null ? void 0 : frontmatter[field]);
if (!hasBannerIcon) {
this.cleanupIconOverlay(leaf.view);
}
try {
const hasShufflePath = !!getFrontmatterValue(frontmatter, this.settings.customBannerShuffleField);
const folderSpecific = this.getFolderSpecificImage(currentPath);
const isShuffled = hasShufflePath || (folderSpecific == null ? void 0 : folderSpecific.enableImageShuffle) || false;
const cacheKey = this.generateCacheKey(currentPath, leafId, isShuffled);
const cachedState = this.bannerStateCache.get(cacheKey);
const loadedImage = this.loadedImages.get(currentPath);
let shouldUpdateBanner = false;
if (cachedState) {
if (isShuffled && currentTime - cachedState.timestamp > this.SHUFFLE_CACHE_AGE) {
shouldUpdateBanner = true;
this.loadedImages.delete(currentPath);
this.lastKeywords.delete(currentPath);
this.imageCache.delete(currentPath);
this.bannerStateCache.delete(cacheKey);
} else {
cachedState.timestamp = currentTime;
const relevantFields = [
...this.settings.customBannerField,
...this.settings.customYPositionField,
...this.settings.customXPositionField,
...this.settings.customContentStartField,
...this.settings.customImageDisplayField,
...this.settings.customImageRepeatField,
...this.settings.customBannerHeightField,
...this.settings.customFadeField,
...this.settings.customBorderRadiusField,
...this.settings.customTitleColorField,
...this.settings.customBannerShuffleField,
...this.settings.customBannerIconField,
...this.settings.customBannerIconSizeField,
...this.settings.customBannerIconXPositionField,
...this.settings.customBannerIconOpacityField,
...this.settings.customBannerIconColorField,
...this.settings.customBannerIconFontWeightField,
...this.settings.customBannerIconBackgroundColorField,
...this.settings.customBannerIconPaddingXField,
...this.settings.customBannerIconPaddingYField,
...this.settings.customBannerIconBorderRadiusField,
...this.settings.customBannerIconVerticalOffsetField
];
const hasRelevantChanges = relevantFields.some(
(field) => {
var _a2;
return (frontmatter == null ? void 0 : frontmatter[field]) !== ((_a2 = cachedState.frontmatter) == null ? void 0 : _a2[field]);
}
);
if (hasRelevantChanges) {
shouldUpdateBanner = true;
}
}
} else {
shouldUpdateBanner = true;
}
if (!loadedImage) {
shouldUpdateBanner = true;
}
if (shouldUpdateBanner) {
await this.updateBanner(leaf.view, false, this.UPDATE_MODE.FULL_UPDATE);
const bannerIcon = getFrontmatterValue(frontmatter, this.settings.customBannerIconField);
const iconState = bannerIcon ? {
icon: bannerIcon,
size: getFrontmatterValue(frontmatter, this.settings.customBannerIconSizeField) || this.settings.bannerIconSize,
xPosition: getFrontmatterValue(frontmatter, this.settings.customBannerIconXPositionField) || this.settings.bannerIconXPosition,
opacity: getFrontmatterValue(frontmatter, this.settings.customBannerIconOpacityField) || this.settings.bannerIconOpacity,
color: getFrontmatterValue(frontmatter, this.settings.customBannerIconColorField) || this.settings.bannerIconColor,
fontWeight: getFrontmatterValue(frontmatter, this.settings.customBannerIconFontWeightField) || this.settings.bannerIconFontWeight,
backgroundColor: getFrontmatterValue(frontmatter, this.settings.customBannerIconBackgroundColorField) || this.settings.bannerIconBackgroundColor,
paddingX: getFrontmatterValue(frontmatter, this.settings.customBannerIconPaddingXField) || this.settings.bannerIconPaddingX,
paddingY: getFrontmatterValue(frontmatter, this.settings.customBannerIconPaddingYField) || this.settings.bannerIconPaddingY,
borderRadius: getFrontmatterValue(frontmatter, this.settings.customBannerIconBorderRadiusField) || this.settings.bannerIconBorderRadius,
verticalOffset: getFrontmatterValue(frontmatter, this.settings.customBannerIconVerticalOffsetField) || this.settings.bannerIconVerticalOffset
} : null;
this.bannerStateCache.set(cacheKey, {
timestamp: currentTime,
frontmatter: frontmatter ? { ...frontmatter } : null,
leafId,
isShuffled,
state: {
imageUrl: this.loadedImages.get(currentPath),
iconState
}
});
} else {
await this.updateBanner(leaf.view, false, this.UPDATE_MODE.ENSURE_VISIBILITY);
}
} catch (error) {
console.error("Error in handleActiveLeafChange:", error);
this.invalidateLeafCache(leafId);
try {
await this.updateBanner(leaf.view, false);
} catch (recoveryError) {
console.error("Failed to recover from error:", recoveryError);
}
}
}
function handleLayoutChange() {
var _a;
const currentLeafIds = new Set(
this.app.workspace.getLeavesOfType("markdown").map((leaf) => leaf.id)
);
for (const [key, entry] of this.bannerStateCache) {
if (entry.leafId && !currentLeafIds.has(entry.leafId)) {
if (((_a = entry.state) == null ? void 0 : _a.imageUrl) && typeof entry.state.imageUrl === "string" && entry.state.imageUrl.startsWith("blob:")) {
URL.revokeObjectURL(entry.state.imageUrl);
}
this.bannerStateCache.delete(key);
}
}
setTimeout(() => {
const activeLeaf = this.app.workspace.activeLeaf;
if (activeLeaf && activeLeaf.view instanceof import_obsidian29.MarkdownView) {
const contentEl = activeLeaf.view.contentEl;
if (contentEl) {
const hasBanner = contentEl.querySelector(".pixel-banner-image");
if (hasBanner) {
const cacheKey = activeLeaf.id;
const cachedState = this.bannerStateCache.get(cacheKey);
if (!cachedState) {
this.updateBanner(activeLeaf.view, false);
}
}
}
}
}, 100);
}
async function handleModeChange(leaf) {
if (leaf && leaf.view instanceof import_obsidian29.MarkdownView && leaf.view.file) {
await this.updateBanner(leaf.view, true);
if (this.settings.hidePixelBannerFields) {
this.updateFieldVisibility(leaf.view);
}
}
}
async function handleSelectImage() {
const activeFile = this.app.workspace.getActiveFile();
if (!activeFile) {
new import_obsidian29.Notice("No active file");
return;
}
new ImageSelectionModal(
this.app,
this,
async (selectedFile) => {
let imageReference = selectedFile.path;
if (this.settings.useShortPath) {
const allFiles = this.app.vault.getFiles();
const matchingFiles = allFiles.filter((f) => f.name === selectedFile.name);
imageReference = matchingFiles.length === 1 ? selectedFile.name : selectedFile.path;
}
let fileContent = await this.app.vault.read(activeFile);
const frontmatterRegex = /^---\n([\s\S]*?)\n---/;
const hasFrontmatter = frontmatterRegex.test(fileContent);
const bannerField = Array.isArray(this.settings.customBannerField) && this.settings.customBannerField.length > 0 ? this.settings.customBannerField[0] : "banner";
fileContent = fileContent.replace(/^\s+/, "");
let updatedContent;
if (hasFrontmatter) {
updatedContent = fileContent.replace(frontmatterRegex, (match, frontmatter) => {
let cleanedFrontmatter = frontmatter.trim();
this.settings.customBannerField.forEach((field) => {
const fieldRegex = new RegExp(`${field}:\\s*.+\\n?`, "g");
cleanedFrontmatter = cleanedFrontmatter.replace(fieldRegex, "");
});
const format = this.settings.imagePropertyFormat;
const bannerValue = format === "[[image]]" ? `[[${imageReference}]]` : `![[${imageReference}]]`;
const newFrontmatter = `${bannerField}: "${bannerValue}"${cleanedFrontmatter ? "\n" + cleanedFrontmatter : ""}`;
return `---
${newFrontmatter}
---`;
});
} else {
const cleanContent = fileContent.replace(/^\s+/, "");
const format = this.settings.imagePropertyFormat;
const bannerValue = format === "[[image]]" ? `[[${imageReference}]]` : `![[${imageReference}]]`;
updatedContent = `---
${bannerField}: "${bannerValue}"
---
${cleanContent}`;
}
updatedContent = updatedContent.replace(/^\s+/, "");
if (updatedContent !== fileContent) {
await this.app.vault.modify(activeFile, updatedContent);
if (this.settings.useShortPath && imageReference === selectedFile.path) {
new import_obsidian29.Notice("Banner image updated (full path used due to duplicate filenames)");
} else {
new import_obsidian29.Notice("Banner image updated");
}
}
},
this.settings.defaultSelectImagePath
).open();
}
function handleBannerIconClick() {
new SelectPixelBannerModal(this.app, this).open();
}
// src/core/domManager.js
var import_obsidian30 = require("obsidian");
function setupMutationObserver() {
if (this.observer && typeof this.observer.disconnect === "function") {
this.observer.disconnect();
}
this.observer = new MutationObserver((mutations) => {
for (let mutation of mutations) {
if (mutation.type === "childList") {
const removedNodes = Array.from(mutation.removedNodes);
const addedNodes = Array.from(mutation.addedNodes);
const bannerRemoved = removedNodes.some(
(node) => node.classList && node.classList.contains("pixel-banner-image")
);
const structuralChange = addedNodes.some(
(node) => node.nodeType === Node.ELEMENT_NODE && (node.classList.contains("markdown-preview-section") || node.classList.contains("cm-sizer"))
// Changed from cm-content to cm-sizer
);
if (bannerRemoved || structuralChange) {
const activeLeaf = this.app.workspace.activeLeaf;
if (activeLeaf && activeLeaf.view instanceof import_obsidian30.MarkdownView) {
const contentEl = activeLeaf.view.contentEl;
const hasBanner = contentEl.querySelector('.pixel-banner-image[style*="display: block"]');
if (!hasBanner) {
contentEl.classList.remove("pixel-banner");
}
if (bannerRemoved || structuralChange) {
this.debouncedEnsureBanner();
}
}
}
}
}
});
if (this.observer && typeof this.observer.observe === "function") {
this.observer.observe(document.body, {
childList: true,
subtree: true
});
}
}
function setupResizeObserver(viewContent) {
if (!viewContent.classList.contains("view-content")) {
return;
}
if (!viewContent._resizeObserver) {
const debouncedResize = debounce(() => {
this.applyBannerWidth(viewContent);
}, 100);
viewContent._resizeObserver = new ResizeObserver(debouncedResize);
if (viewContent._resizeObserver && typeof viewContent._resizeObserver.observe === "function") {
viewContent._resizeObserver.observe(viewContent);
}
}
}
function updateFieldVisibility(view) {
if (!view || view.getMode() !== "preview") return;
const fieldsToHide = [
...this.settings.customBannerField,
...this.settings.customYPositionField,
...this.settings.customXPositionField,
...this.settings.customContentStartField,
...this.settings.customImageDisplayField,
...this.settings.customImageRepeatField,
...this.settings.customBannerMaxWidthField,
...this.settings.customBannerHeightField,
...this.settings.customBannerAlignmentField,
...this.settings.customFadeField,
...this.settings.customBorderRadiusField,
...this.settings.customTitleColorField,
...this.settings.customBannerShuffleField,
...this.settings.customBannerIconField,
...this.settings.customBannerIconImageField,
...this.settings.customBannerIconImageAlignmentField,
...this.settings.customBannerIconSizeField,
...this.settings.customBannerIconImageSizeMultiplierField,
...this.settings.customBannerIconTextVerticalOffsetField,
...this.settings.customBannerIconRotateField,
...this.settings.customBannerIconXPositionField,
...this.settings.customBannerIconOpacityField,
...this.settings.customBannerIconColorField,
...this.settings.customBannerIconFontWeightField,
...this.settings.customBannerIconBackgroundColorField,
...this.settings.customBannerIconPaddingXField,
...this.settings.customBannerIconPaddingYField,
...this.settings.customBannerIconBorderRadiusField,
...this.settings.customBannerIconVerticalOffsetField,
...this.settings.customFlagColorField
];
const propertiesContainer = view.contentEl.querySelector(".metadata-container");
if (!propertiesContainer) {
return;
}
const propertyElements = propertiesContainer.querySelectorAll(".metadata-property");
let visiblePropertiesCount = 0;
let bannerPropertiesCount = 0;
propertyElements.forEach((propertyEl) => {
const key = propertyEl.getAttribute("data-property-key");
if (fieldsToHide.includes(key)) {
propertyEl.classList.add("pixel-banner-hidden-field");
bannerPropertiesCount++;
} else {
visiblePropertiesCount++;
}
});
if (this.settings.hidePropertiesSectionIfOnlyBanner && this.settings.hidePixelBannerFields && visiblePropertiesCount === 0 && bannerPropertiesCount > 0) {
propertiesContainer.classList.add("pixel-banner-hidden-section");
} else {
propertiesContainer.classList.remove("pixel-banner-hidden-section");
}
}
function updateEmbeddedTitlesVisibility() {
const styleId = "pixel-banner-embedded-titles";
let styleEl = document.getElementById(styleId);
if (this.settings.hideEmbeddedNoteTitles) {
if (!styleEl) {
styleEl = document.createElement("style");
styleEl.id = styleId;
document.head.appendChild(styleEl);
}
styleEl.textContent = ".embed-title.markdown-embed-title { display: none !important; }";
} else if (styleEl) {
styleEl.remove();
}
}
function updateEmbeddedBannersVisibility() {
const styleId = "pixel-banner-embedded-banners";
let styleEl = document.getElementById(styleId);
if (this.settings.hideEmbeddedNoteBanners) {
if (!styleEl) {
styleEl = document.createElement("style");
styleEl.id = styleId;
document.head.appendChild(styleEl);
}
styleEl.textContent = `
.internal-embed .pixel-banner-image,
.internal-embed .banner-icon-overlay {
display: none !important;
}
.internal-embed > .markdown-embed-content .cm-sizer:first-of-type,
.internal-embed > .markdown-embed-content .markdown-preview-sizer:first-of-type {
padding-top: unset !important;
}
/* hide pusher to prevent content from being pushed down */
.internal-embed > .markdown-embed-content .cm-sizer:first-of-type > .pixel-banner-image + .markdown-preview-pusher,
.internal-embed > .markdown-embed-content .markdown-preview-sizer:first-of-type > .pixel-banner-image + .markdown-preview-pusher {
display: none !important;
}
`;
} else if (styleEl) {
styleEl.remove();
}
}
function cleanupPreviousLeaf(previousLeaf) {
const previousContentEl = previousLeaf.view.contentEl;
if (!previousContentEl) {
return;
}
previousContentEl.classList.remove("pixel-banner");
["cm-sizer", "markdown-preview-sizer"].forEach((selector) => {
const container = previousContentEl.querySelector(`div.${selector}`);
if (container) {
const previousBanner = container.querySelector(".pixel-banner-image");
if (previousBanner) {
if (previousLeaf.view.file) {
const existingUrl = this.loadedImages.get(previousLeaf.view.file.path);
if (existingUrl && typeof existingUrl === "string" && existingUrl.startsWith("blob:")) {
URL.revokeObjectURL(existingUrl);
}
this.loadedImages.delete(previousLeaf.view.file.path);
}
previousBanner.style.backgroundImage = "";
previousBanner.style.display = "none";
}
const iconOverlays = container.querySelectorAll(".banner-icon-overlay");
iconOverlays.forEach((overlay) => {
if (!overlay.dataset.persistent) {
this.returnIconOverlay(overlay);
}
});
}
});
}
// src/core/pixelBannerPlugin.js
init_frontmatterUtils();
var PixelBannerPlugin = class extends import_obsidian31.Plugin {
constructor() {
super(...arguments);
// Update modes for banner refresh
__publicField(this, "UPDATE_MODE", {
FULL_UPDATE: "FULL_UPDATE",
// Complete update including new images
ENSURE_VISIBILITY: "ENSURE_VISIBILITY",
// Only ensure banner is visible with current image
SHUFFLE_UPDATE: "SHUFFLE_UPDATE"
// Update for shuffle banners only
});
__publicField(this, "debounceTimer", null);
__publicField(this, "loadedImages", /* @__PURE__ */ new Map());
__publicField(this, "lastKeywords", /* @__PURE__ */ new Map());
__publicField(this, "imageCache", /* @__PURE__ */ new Map());
__publicField(this, "rateLimiter", {
lastRequestTime: 0,
minInterval: 1e3
// 1 second between requests
});
__publicField(this, "lastYPositions", /* @__PURE__ */ new Map());
__publicField(this, "lastFrontmatter", /* @__PURE__ */ new Map());
// Enhanced cache management properties
__publicField(this, "bannerStateCache", /* @__PURE__ */ new Map());
__publicField(this, "MAX_CACHE_AGE", 30 * 60 * 1e3);
// 30 minutes in milliseconds
__publicField(this, "MAX_CACHE_ENTRIES", 30);
// Maximum number of entries to keep in cache
__publicField(this, "SHUFFLE_CACHE_AGE", 5 * 1e3);
// 5 seconds in milliseconds for shuffled banners
// Add element pool for icon overlays
__publicField(this, "iconOverlayPool", []);
__publicField(this, "MAX_POOL_SIZE", 10);
// -----------------------------
// -- debounced ensure banner --
// -----------------------------
__publicField(this, "debouncedEnsureBanner", debounce(() => {
const activeLeaf = this.app.workspace.activeLeaf;
if (activeLeaf && activeLeaf.view instanceof import_obsidian31.MarkdownView) {
const contentEl = activeLeaf.view.contentEl;
const hasBanner = contentEl.querySelector(".pixel-banner-image");
if (hasBanner) {
this.updateBanner(activeLeaf.view, false);
}
}
}, 100));
}
// ----------------------------------------------------
// -- bind imported functions to the plugin instance --
// ----------------------------------------------------
async loadSettings() {
await loadSettings(this);
}
async saveSettings() {
await saveSettings(this);
}
getIconOverlay() {
return getIconOverlay(this);
}
returnIconOverlay(overlay) {
returnIconOverlay(this, overlay);
}
shouldUpdateIconOverlay(existingOverlay, newIconState, viewType) {
return shouldUpdateIconOverlay(this, existingOverlay, newIconState, viewType);
}
generateCacheKey(filePath, leafId, isShuffled = false) {
return generateCacheKey.call(this, filePath, leafId, isShuffled);
}
getCacheEntriesForFile(filePath) {
return getCacheEntriesForFile.call(this, filePath);
}
cleanupCache(force = false) {
return cleanupCache.call(this, force);
}
invalidateLeafCache(leafId) {
return invalidateLeafCache.call(this, leafId);
}
handleSetBannerIcon() {
return handleSetBannerIcon(this);
}
addPixelBanner(el, ctx) {
return debouncedAddPixelBanner(this, el, ctx);
}
updateBanner(view, isContentChange, updateMode) {
return debouncedUpdateBanner(this, view, isContentChange, updateMode);
}
applyBannerSettings(bannerDiv, ctx, isEmbedded) {
return debouncedApplyBannerSettings(this, bannerDiv, ctx, isEmbedded);
}
applyContentStartPosition(el, contentStartPosition) {
return debouncedApplyContentStartPosition(this, el, contentStartPosition);
}
applyBannerWidth(el) {
return debouncedApplyBannerWidth(this, el);
}
updateAllBanners() {
return debouncedUpdateAllBanners(this);
}
updateBannerPosition(file, position) {
return debouncedUpdateBannerPosition(this, file, position);
}
cleanupIconOverlay(view) {
return cleanupIconOverlay(this, view);
}
// --------------------------------------------
// -- add bindings for the utility functions --
// --------------------------------------------
getInputType(input, sourcePath = "") {
return getInputType.call(this, input, sourcePath);
}
getIconImageInputType(input, sourcePath = "") {
return getIconImageInputType.call(this, input, sourcePath);
}
getPathFromObsidianLink(link) {
return getPathFromObsidianLink.call(this, link);
}
getPathFromMarkdownImage(link) {
return getPathFromMarkdownImage.call(this, link);
}
getVaultImageUrl(path) {
return getVaultImageUrl.call(this, path);
}
preloadImage(url) {
return preloadImage.call(this, url);
}
getFolderPath(filePath) {
return getFolderPath.call(this, filePath);
}
getFolderSpecificImage(filePath) {
return getFolderSpecificImage.call(this, filePath);
}
getFolderSpecificSetting(filePath, settingName) {
return getFolderSpecificSetting.call(this, filePath, settingName);
}
getRandomImageFromFolder(folderPath) {
return getRandomImageFromFolder.call(this, folderPath);
}
getActiveApiProvider() {
return getActiveApiProvider.call(this);
}
hasBannerFrontmatter(file) {
return hasBannerFrontmatter.call(this, file);
}
createFolderImageSettings(folderImage) {
return createFolderImageSettings.call(this, folderImage);
}
// -------------------------------------
// -- add bindings for event handlers --
// -------------------------------------
handleActiveLeafChange(leaf) {
return handleActiveLeafChange.call(this, leaf);
}
handleLayoutChange() {
return handleLayoutChange.call(this);
}
handleModeChange(leaf) {
return handleModeChange.call(this, leaf);
}
handleSelectImage() {
return handleSelectImage.call(this);
}
handleBannerIconClick() {
return handleBannerIconClick.call(this);
}
// -------------------------------------
// -- add bindings for DOM management --
// -------------------------------------
setupMutationObserver() {
return setupMutationObserver.call(this);
}
setupResizeObserver(viewContent) {
return setupResizeObserver.call(this, viewContent);
}
updateFieldVisibility(view) {
return updateFieldVisibility.call(this, view);
}
updateEmbeddedTitlesVisibility() {
return updateEmbeddedTitlesVisibility.call(this);
}
updateEmbeddedBannersVisibility() {
return updateEmbeddedBannersVisibility.call(this);
}
cleanupPreviousLeaf(previousLeaf) {
return cleanupPreviousLeaf.call(this, previousLeaf);
}
// --------------------------------------
// -- onload method / main entry point --
// --------------------------------------
async onload() {
await this.loadSettings();
this.pixelBannerPlusEnabled = false;
this.pixelBannerPlusBannerTokens = 0;
this.verifyPixelBannerPlusCredentials();
this.updateEmbeddedTitlesVisibility();
await this.checkVersion();
this.addSettingTab(new PixelBannerSettingTab(this.app, this));
this.addCommand({
id: "generate-banner-with-ai",
name: "\u2728 Generate Banner with AI",
checkCallback: (checking) => {
if (checking) {
return this.pixelBannerPlusEnabled;
}
new GenerateAIBannerModal(this.app, this).open();
}
});
this.addCommand({
id: "play-daily-game",
name: "\u{1F579}\uFE0F Play Daily Game",
callback: () => {
new DailyGameModal(this.app, this.settings.pixelBannerPlusEmail, this.settings.pixelBannerPlusApiKey, this).open();
}
});
this.registerEvent(
this.app.workspace.on("active-leaf-change", this.handleActiveLeafChange.bind(this))
);
this.registerEvent(
this.app.workspace.on("layout-change", this.handleLayoutChange.bind(this))
);
this.registerEvent(
this.app.workspace.on("resize", this.debouncedEnsureBanner.bind(this))
);
this.registerEvent(
this.app.metadataCache.on("changed", async (file) => {
var _a;
const frontmatter = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
if (!frontmatter) {
return;
}
const previousFrontmatter = this.lastFrontmatter.get(file.path);
const currentFrontmatterStr = JSON.stringify(frontmatter);
if (currentFrontmatterStr === JSON.stringify(previousFrontmatter)) {
return;
}
this.lastFrontmatter.set(file.path, JSON.parse(currentFrontmatterStr));
const relevantFields = [
...this.settings.customBannerField,
...this.settings.customYPositionField,
...this.settings.customXPositionField,
...this.settings.customContentStartField,
...this.settings.customImageDisplayField,
...this.settings.customImageRepeatField,
...this.settings.customBannerMaxWidthField,
...this.settings.customBannerHeightField,
...this.settings.customFadeField,
...this.settings.customBorderRadiusField,
...this.settings.customTitleColorField,
...this.settings.customBannerShuffleField,
...this.settings.customBannerIconField,
...this.settings.customBannerIconSizeField,
...this.settings.customBannerIconImageSizeMultiplierField,
...this.settings.customBannerIconTextVerticalOffsetField,
...this.settings.customBannerIconRotateField,
...this.settings.customBannerIconImageAlignmentField,
...this.settings.customBannerIconXPositionField,
...this.settings.customBannerIconOpacityField,
...this.settings.customBannerIconColorField,
...this.settings.customBannerIconFontWeightField,
...this.settings.customBannerIconBackgroundColorField,
...this.settings.customBannerIconPaddingXField,
...this.settings.customBannerIconPaddingYField,
...this.settings.customBannerIconBorderRadiusField,
...this.settings.customBannerIconVerticalOffsetField
];
const changedFields = relevantFields.filter(
(field) => frontmatter[field] !== (previousFrontmatter == null ? void 0 : previousFrontmatter[field])
);
if (changedFields.length === 0) {
return;
}
const leaves = this.app.workspace.getLeavesOfType("markdown");
for (const leaf of leaves) {
if (leaf.view instanceof import_obsidian31.MarkdownView && leaf.view.file === file) {
this.loadedImages.delete(file.path);
this.lastKeywords.delete(file.path);
await this.updateBanner(leaf.view, true);
}
}
})
);
this.registerMarkdownPostProcessor((el, ctx) => {
var _a;
const isPreview = ctx.containerEl.classList.contains("markdown-preview-view");
const isHoverPopover = ctx.containerEl.closest(".hover-popover") !== null;
if (!isPreview && !isHoverPopover) return;
const file = ctx.sourcePath ? this.app.vault.getAbstractFileByPath(ctx.sourcePath) : null;
if (!file) return;
if (isHoverPopover && !this.settings.showBannerInPopoverPreviews) return;
const frontmatter = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
let bannerImage = null;
for (const field of this.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
bannerImage = frontmatter[field];
break;
}
}
if (!bannerImage) {
const folderSpecific2 = this.getFolderSpecificImage(file.path);
if (folderSpecific2 == null ? void 0 : folderSpecific2.image) {
bannerImage = folderSpecific2.image;
}
}
if (!bannerImage) return;
const folderSpecific = this.getFolderSpecificImage(file.path);
const yPosition = getFrontmatterValue(frontmatter, this.settings.customYPositionField) || (folderSpecific == null ? void 0 : folderSpecific.yPosition) || this.settings.yPosition;
const xPosition = getFrontmatterValue(frontmatter, this.settings.customXPositionField) || (folderSpecific == null ? void 0 : folderSpecific.xPosition) || this.settings.xPosition;
const contentStartPosition = getFrontmatterValue(frontmatter, this.settings.customContentStartField) || (folderSpecific == null ? void 0 : folderSpecific.contentStartPosition) || this.settings.contentStartPosition;
if (isHoverPopover) {
this.addPixelBanner(ctx.containerEl, {
frontmatter,
file,
isContentChange: false,
yPosition,
xPosition,
contentStartPosition,
bannerImage,
isReadingView: true
});
} else {
this.updateBanner({
file,
contentEl: ctx.containerEl,
getMode: () => "preview"
}, false, this.UPDATE_MODE.ENSURE_VISIBILITY);
}
});
this.setupMutationObserver();
this.addCommand({
id: "pin-banner-image",
name: "\u{1F4CC} Pin current banner image",
checkCallback: (checking) => {
var _a;
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian31.MarkdownView);
if (!activeView || !activeView.file) return false;
const imageUrl = this.loadedImages.get(activeView.file.path);
const frontmatter = (_a = this.app.metadataCache.getFileCache(activeView.file)) == null ? void 0 : _a.frontmatter;
let bannerImage, usedField;
for (const field of this.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
bannerImage = frontmatter[field];
usedField = field;
break;
}
}
const inputType = this.getInputType(bannerImage);
const canPin = imageUrl && (inputType === "keyword" || inputType === "url") && this.settings.showPinIcon;
if (checking) return canPin;
if (canPin) {
setTimeout(() => handlePinIconClick(imageUrl, this, usedField), 0);
}
return true;
}
});
this.addCommand({
id: "refresh-banner-image",
name: "\u{1F504} Refresh current banner image",
checkCallback: (checking) => {
var _a;
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian31.MarkdownView);
if (!activeView || !activeView.file) return false;
const frontmatter = (_a = this.app.metadataCache.getFileCache(activeView.file)) == null ? void 0 : _a.frontmatter;
let bannerImage;
for (const field of this.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
bannerImage = frontmatter[field];
break;
}
}
const inputType = this.getInputType(bannerImage);
const canRefresh = inputType === "keyword" && this.settings.showPinIcon && this.settings.showRefreshIcon;
if (checking) return canRefresh;
if (canRefresh) {
this.loadedImages.delete(activeView.file.path);
this.lastKeywords.delete(activeView.file.path);
this.updateBanner(activeView, true).then(() => {
new import_obsidian31.Notice("\u{1F504} Refreshed banner image");
}).catch((error) => {
console.error("Error refreshing image:", error);
new import_obsidian31.Notice("\u{1F62D} Failed to refresh image");
});
}
return true;
}
});
this.addCommand({
id: "open-pixel-banner-select",
name: "\u{1F6A9} Pixel Banner Menu",
callback: () => {
this.handleBannerIconClick();
}
});
this.addCommand({
id: "set-banner-image",
name: "\u{1F4BE} Select Banner from Vault",
callback: () => this.handleSelectImage()
});
this.addCommand({
id: "open-banner-store",
name: "\u{1F3EA} Open Pixel Banner Plus Collection",
callback: () => this.openBannerStore()
});
this.addCommand({
id: "set-banner-icon-image",
name: "\u2B50 Select Banner Icon Image",
checkCallback: (checking) => {
var _a;
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian31.MarkdownView);
if (!activeView || !activeView.file) return false;
const frontmatter = (_a = this.app.metadataCache.getFileCache(activeView.file)) == null ? void 0 : _a.frontmatter;
let hasBanner = false;
for (const field of this.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
hasBanner = true;
break;
}
}
if (checking) return hasBanner;
if (hasBanner) {
handleSetBannerIconImage(this);
}
return true;
}
});
this.addCommand({
id: "set-banner-icon",
name: "\u{1F4F0} Set Banner Icon Text / Emoji",
checkCallback: (checking) => {
var _a;
const activeView = this.app.workspace.getActiveViewOfType(import_obsidian31.MarkdownView);
if (!activeView || !activeView.file) return false;
const frontmatter = (_a = this.app.metadataCache.getFileCache(activeView.file)) == null ? void 0 : _a.frontmatter;
let hasBanner = false;
for (const field of this.settings.customBannerField) {
if (frontmatter == null ? void 0 : frontmatter[field]) {
hasBanner = true;
break;
}
}
if (checking) return hasBanner;
if (hasBanner) {
handleSetBannerIcon(this);
}
return true;
}
});
this.addCommand({
id: "set-banner-position",
name: "\u{1F3AF} Adjust Position, Size, & Style",
checkCallback: (checking) => {
const activeFile = this.app.workspace.getActiveFile();
const hasBanner = activeFile && this.hasBannerFrontmatter(activeFile);
if (checking) {
return hasBanner;
}
if (hasBanner) {
new TargetPositionModal(
this.app,
this,
(position) => this.updateBannerPosition(activeFile, position)
).open();
return true;
}
return false;
}
});
if (this.settings.bannerGap === void 0) {
this.settings.bannerGap = DEFAULT_SETTINGS.bannerGap;
}
this.registerEvent(
this.app.metadataCache.on("resolved", () => {
var _a;
const leaf = this.app.workspace.activeLeaf;
if (leaf && leaf.view instanceof import_obsidian31.MarkdownView) {
const contentEl = leaf.view.contentEl;
const hasBanner = contentEl.querySelector(".pixel-banner-image");
if (hasBanner) {
const file = leaf.view.file;
const frontmatter = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
if (!frontmatter) return;
const previousFrontmatter = this.lastFrontmatter.get(file.path);
const currentStr = JSON.stringify(frontmatter);
if (currentStr !== JSON.stringify(previousFrontmatter)) {
this.lastFrontmatter.set(file.path, JSON.parse(currentStr));
const relevantFields = [
...this.settings.customBannerField,
...this.settings.customYPositionField,
...this.settings.customXPositionField,
...this.settings.customContentStartField,
...this.settings.customImageDisplayField,
...this.settings.customImageRepeatField,
...this.settings.customBannerMaxWidthField,
...this.settings.customBannerHeightField,
...this.settings.customFadeField,
...this.settings.customBorderRadiusField,
...this.settings.customTitleColorField,
...this.settings.customBannerShuffleField,
...this.settings.customBannerIconField,
...this.settings.customBannerIconSizeField,
...this.settings.customBannerIconImageSizeMultiplierField,
...this.settings.customBannerIconTextVerticalOffsetField,
...this.settings.customBannerIconRotateField,
...this.settings.customBannerIconImageAlignmentField,
...this.settings.customBannerIconXPositionField,
...this.settings.customBannerIconOpacityField,
...this.settings.customBannerIconColorField,
...this.settings.customBannerIconFontWeightField,
...this.settings.customBannerIconBackgroundColorField,
...this.settings.customBannerIconPaddingXField,
...this.settings.customBannerIconPaddingYField,
...this.settings.customBannerIconBorderRadiusField,
...this.settings.customBannerIconVerticalOffsetField
];
const hasRelevantChange = relevantFields.some(
(field) => frontmatter[field] !== (previousFrontmatter == null ? void 0 : previousFrontmatter[field])
);
if (hasRelevantChange) {
this.updateBanner(leaf.view, false);
}
}
}
}
})
);
this.addRibbonIcon("flag", "\u{1F6A9} Pixel Banner Menu", () => {
this.handleBannerIconClick();
});
registerMarkdownPostProcessor(this);
}
// -------------------
// -- get image url --
// -------------------
async getImageUrl(type, input, sourcePath = "") {
if (type === "url" || type === "path") {
return input;
}
if (type === "fileUrl") {
if (import_obsidian31.Platform.isMobile) {
new import_obsidian31.Notice("Local file paths are only supported on desktop.");
return null;
}
try {
const fs = require("fs");
const path = require("path");
let filePath = decodeURIComponent(input.substring(process.platform === "win32" ? 8 : 7));
filePath = filePath.replace('"', "").replace("![[", "").replace("[[", "").replace("]]", "").replace("//", "");
if (process.platform === "win32" && /^[a-zA-Z]:/.test(filePath)) {
} else if (process.platform === "win32" && filePath.startsWith("/")) {
filePath = filePath.substring(1);
}
if (!fs.existsSync(filePath)) {
console.error(`Pixel Banner: File not found at path: ${filePath}`);
new import_obsidian31.Notice(`Pixel Banner: File not found at path: ${filePath}`);
return null;
}
const data = fs.readFileSync(filePath);
const base64 = Buffer.from(data).toString("base64");
const mimeType = "image/" + path.extname(filePath).substring(1);
return `data:${mimeType};base64,${base64}`;
} catch (err) {
console.error("Pixel Banner: Error reading local file:", err);
new import_obsidian31.Notice("Pixel Banner: Error reading local file. Check console for details.");
return null;
}
}
if (type === "obsidianLink") {
const file = this.getPathFromObsidianLink(input);
if (file) {
return this.getVaultImageUrl(file.path);
}
return null;
}
if (type === "markdownImage") {
const path = this.getPathFromMarkdownImage(input);
if (typeof path === "string") {
try {
new URL(path);
return path;
} catch (_) {
return this.getVaultImageUrl(path);
}
}
if (path) {
return this.getVaultImageUrl(path.path);
}
return null;
}
if (type === "vaultPath") {
let file = this.app.vault.getAbstractFileByPath(input);
if (file && "extension" in file) {
return this.getVaultImageUrl(input);
}
const resolvedFile = this.app.metadataCache.getFirstLinkpathDest(input, sourcePath);
if (resolvedFile && "extension" in resolvedFile) {
return this.getVaultImageUrl(resolvedFile.path);
}
return null;
}
if (type === "keyword") {
const keywords = input.includes(",") ? input.split(",").map((k) => k.trim()).filter((k) => k.length > 0).filter(Boolean) : [input];
if (keywords.length > 0) {
const selectedKeyword = keywords[Math.floor(Math.random() * keywords.length)];
const availableProviders = [];
if (this.settings.apiProviders && Array.isArray(this.settings.apiProviders)) {
for (const provider of this.settings.apiProviders) {
const hasKey = provider === "pexels" && this.settings.pexelsApiKey || provider === "pixabay" && this.settings.pixabayApiKey || provider === "flickr" && this.settings.flickrApiKey || provider === "unsplash" && this.settings.unsplashApiKey;
if (hasKey) {
availableProviders.push(provider);
}
}
} else {
if (this.settings.pexelsApiKey) availableProviders.push("pexels");
if (this.settings.pixabayApiKey) availableProviders.push("pixabay");
if (this.settings.flickrApiKey) availableProviders.push("flickr");
if (this.settings.unsplashApiKey) availableProviders.push("unsplash");
}
if (availableProviders.length === 0) {
return null;
}
for (const provider of availableProviders) {
try {
let result = null;
switch (provider) {
case "pexels":
result = await fetchPexelsImage(this, selectedKeyword, true);
break;
case "pixabay":
result = await fetchPixabayImage(this, selectedKeyword);
break;
case "flickr":
result = await fetchFlickrImage(this, selectedKeyword);
break;
case "unsplash":
result = await fetchUnsplashImage(this, selectedKeyword);
break;
}
if (result) {
return result;
}
} catch (error) {
console.error(`Error with ${provider} for keyword "${selectedKeyword}":`, error);
}
}
const defaultKeywords = this.settings.defaultKeywords ? this.settings.defaultKeywords.split(",").map((k) => k.trim()).filter((k) => k.length > 0) : [];
for (const fallbackKeyword of defaultKeywords) {
for (const provider of availableProviders) {
try {
let result = null;
switch (provider) {
case "pexels":
result = await fetchPexelsImage(this, fallbackKeyword, true);
break;
case "pixabay":
result = await fetchPixabayImage(this, fallbackKeyword);
break;
case "flickr":
result = await fetchFlickrImage(this, fallbackKeyword);
break;
case "unsplash":
result = await fetchUnsplashImage(this, fallbackKeyword);
break;
}
if (result) {
return result;
}
} catch (error) {
console.error(`Error with ${provider} for fallback keyword "${fallbackKeyword}":`, error);
}
}
}
return null;
}
}
return null;
}
// --------------------
// -- post processor --
// --------------------
async postProcessor(el, ctx) {
const frontmatter = ctx.frontmatter;
let bannerImageValue = null;
if (frontmatter) {
for (const field of this.settings.customBannerField) {
if (frontmatter[field]) {
bannerImageValue = frontmatter[field];
break;
}
}
}
if (bannerImageValue) {
await this.addPixelBanner(el, {
frontmatter,
file: ctx.sourcePath,
isContentChange: false,
yPosition: getFrontmatterValue(frontmatter, this.settings.customYPositionField) || this.settings.yPosition,
contentStartPosition: getFrontmatterValue(frontmatter, this.settings.customContentStartField) || this.settings.contentStartPosition,
customBannerField: this.settings.customBannerField,
customYPositionField: this.settings.customYPositionField,
customXPositionField: this.settings.customXPositionField,
customContentStartField: this.settings.customContentStartField,
customImageDisplayField: this.settings.customImageDisplayField,
customImageRepeatField: this.settings.customImageRepeatField,
customBannerMaxWidthField: this.settings.customBannerMaxWidthField,
bannerImage: bannerImageValue
});
if (this.settings.hidePixelBannerFields) {
const frontmatterEl = el.querySelector(".frontmatter");
if (frontmatterEl) {
const fieldsToHide = [
...this.settings.customBannerField,
...this.settings.customYPositionField,
...this.settings.customXPositionField,
...this.settings.customContentStartField,
...this.settings.customImageDisplayField,
...this.settings.customImageRepeatField,
...this.settings.customBannerMaxWidthField,
...this.settings.customBannerHeightField,
...this.settings.customBannerAlignmentField,
...this.settings.customFadeField,
...this.settings.customBorderRadiusField,
...this.settings.customTitleColorField,
...this.settings.customBannerShuffleField,
...this.settings.customBannerIconField,
...this.settings.customBannerIconSizeField,
...this.settings.customBannerIconImageSizeMultiplierField,
...this.settings.customBannerIconTextVerticalOffsetField,
...this.settings.customBannerIconRotateField,
...this.settings.customBannerIconImageAlignmentField,
...this.settings.customBannerIconXPositionField,
...this.settings.customBannerIconOpacityField,
...this.settings.customBannerIconColorField,
...this.settings.customBannerIconFontWeightField,
...this.settings.customBannerIconBackgroundColorField,
...this.settings.customBannerIconPaddingXField,
...this.settings.customBannerIconPaddingYField,
...this.settings.customBannerIconBorderRadiusField,
...this.settings.customBannerIconVerticalOffsetField
];
const rows = frontmatterEl.querySelectorAll(".frontmatter-container .frontmatter-section-label");
rows.forEach((row) => {
const label = row.textContent.replace(":", "").trim();
if (fieldsToHide.includes(label)) {
row.closest(".frontmatter-section").classList.add("pixel-banner-hidden-field");
}
});
}
}
}
}
// --------------
// -- onunload --
// --------------
onunload() {
if (this.observer && typeof this.observer.disconnect === "function") {
this.observer.disconnect();
}
this.app.workspace.iterateAllLeaves((leaf) => {
if (leaf.view instanceof import_obsidian31.MarkdownView) {
const viewContent = leaf.view.contentEl;
if (viewContent._resizeObserver && typeof viewContent._resizeObserver.disconnect === "function") {
viewContent._resizeObserver.disconnect();
delete viewContent._resizeObserver;
}
}
});
this.iconOverlayPool = [];
const styleElTitle = document.getElementById("pixel-banner-embedded-titles");
if (styleElTitle) styleElTitle.remove();
const styleElBanner = document.getElementById("pixel-banner-embedded-banners");
if (styleElBanner) styleElBanner.remove();
}
// -------------------------
// -- clean orphaned pins --
// -------------------------
async cleanOrphanedPins() {
var _a;
const vault = this.app.vault;
const folderPath = this.settings.pinnedImageFolder;
let cleaned = 0;
try {
if (!await vault.adapter.exists(folderPath)) {
return { cleaned };
}
const pinnedFolder = vault.getAbstractFileByPath(folderPath);
if (!pinnedFolder || !pinnedFolder.children) {
return { cleaned };
}
const imageExtensions = ["png", "jpg", "jpeg", "gif", "bmp", "webp", "svg", "avif"];
const pinnedImages = pinnedFolder.children.filter((file) => imageExtensions.includes(file.extension.toLowerCase())).map((file) => file.path);
if (!pinnedImages.length) {
return { cleaned };
}
const markdownFiles = this.app.vault.getMarkdownFiles();
const bannerFields = this.settings.customBannerField;
const referencedImages = /* @__PURE__ */ new Set();
for (const file of markdownFiles) {
const frontmatter = (_a = this.app.metadataCache.getFileCache(file)) == null ? void 0 : _a.frontmatter;
if (frontmatter) {
for (const field of bannerFields) {
const bannerValue = frontmatter[field];
if (bannerValue && typeof bannerValue === "string") {
let cleanPath;
if (bannerValue.startsWith("[[") && bannerValue.endsWith("]]")) {
cleanPath = bannerValue.slice(2, -2).replace(/["']/g, "");
} else {
cleanPath = bannerValue.replace(/["']/g, "");
}
if (!cleanPath.startsWith(folderPath)) {
const resolvedFile = this.app.metadataCache.getFirstLinkpathDest(cleanPath, file.path);
if (resolvedFile) {
cleanPath = resolvedFile.path;
}
}
referencedImages.add(cleanPath);
}
}
}
}
for (const imagePath of pinnedImages) {
if (!referencedImages.has(imagePath)) {
await vault.trash(vault.getAbstractFileByPath(imagePath), true);
cleaned++;
}
}
return { cleaned };
} catch (error) {
console.error("Error in cleanOrphanedPins:", error);
throw error;
}
}
// -----------------------------------------
// -- show release notes for new versions --
// -----------------------------------------
async checkVersion() {
const currentVersion = this.manifest.version;
const lastVersion = this.settings.lastVersion;
if (this.settings.showReleaseNotes && (!lastVersion || lastVersion !== currentVersion)) {
const releaseNotes2 = await this.getReleaseNotes(currentVersion);
new ReleaseNotesModal(this.app, currentVersion, releaseNotes2).open();
this.settings.lastVersion = currentVersion;
await this.saveSettings();
}
if (lastVersion && lastVersion !== currentVersion) {
console.log("[Pixel Banner] Plugin updated from", lastVersion, "to", currentVersion, "- invalidating cached cloud version");
this.pixelBannerVersion = void 0;
}
}
// -----------------------------------------------
// -- get release notes for the current version --
// -----------------------------------------------
async getReleaseNotes(version) {
return releaseNotes;
}
// ------------------------------------------
// -- verify pixel banner plus credentials --
// ------------------------------------------
async verifyPixelBannerPlusCredentials() {
if (this.settings.pixelBannerPlusEnabled) {
const result = await verifyPixelBannerPlusCredentials(this);
this.pixelBannerPlusServerOnline = result.serverOnline;
this.pixelBannerPlusEnabled = result.verified;
this.pixelBannerPlusBannerTokens = result.bannerTokens;
this.pixelBannerPlusJackpot = result.jackpot;
this.pixelBannerPlusDailyGameName = result.dailyGameName;
this.pixelBannerPlusHighScore = result.highScore || "0";
this.pixelBannerPlusTopUser = result.topUser;
this.pixelBannerPlusTimeLeft = result.timeLeft;
return result;
}
return {
serverOnline: false,
verified: false,
bannerTokens: 0,
jackpot: 0,
dailyGameName: "",
highScore: "0",
topUser: "",
timeLeft: 0
};
}
// -------------------------------- //
// -- get pixel banner plus info -- //
// -------------------------------- //
async getPixelBannerInfo() {
const result = await getPixelBannerInfo(this);
this.pixelBannerVersion = result.version;
return result;
}
// --------------------------
// -- open the banner store --
// --------------------------
openBannerStore() {
new PixelBannerStoreModal(this.app, this).open();
}
};
// src/main.js
var main_default = PixelBannerPlugin;
/* nosourcemap */