2025-05-24 21:50:23 -07:00

3180 lines
102 KiB
JavaScript

/*
THIS IS A GENERATED/BUNDLED FILE BY ESBUILD
if you want to view the source, please visit the github repository of this plugin
*/
"use strict";
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
for (var name in all)
__defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
if (from && typeof from === "object" || typeof from === "function") {
for (let key of __getOwnPropNames(from))
if (!__hasOwnProp.call(to, key) && key !== except)
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
}
return to;
};
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var __accessCheck = (obj, member, msg) => {
if (!member.has(obj))
throw TypeError("Cannot " + msg);
};
var __privateGet = (obj, member, getter) => {
__accessCheck(obj, member, "read from private field");
return getter ? getter.call(obj) : member.get(obj);
};
var __privateAdd = (obj, member, value) => {
if (member.has(obj))
throw TypeError("Cannot add the same private member more than once");
member instanceof WeakSet ? member.add(obj) : member.set(obj, value);
};
var __privateSet = (obj, member, value, setter) => {
__accessCheck(obj, member, "write to private field");
setter ? setter.call(obj, value) : member.set(obj, value);
return value;
};
// src/main.ts
var main_exports = {};
__export(main_exports, {
default: () => CalloutManagerPlugin
});
module.exports = __toCommonJS(main_exports);
var import_obsidian18 = require("obsidian");
// node_modules/obsidian-extra/dist/esm/functions/getFloatingWindows.js
function getFloatingWindows(app2) {
var _a, _b, _c, _d;
return (_d = (_c = (_b = (_a = app2 === null || app2 === void 0 ? void 0 : app2.workspace) === null || _a === void 0 ? void 0 : _a.floatingSplit) === null || _b === void 0 ? void 0 : _b.children) === null || _c === void 0 ? void 0 : _c.map((split) => split.win)) !== null && _d !== void 0 ? _d : [];
}
// node_modules/obsidian-extra/dist/esm/functions/getCurrentThemeID.js
function getCurrentThemeID(app2) {
const theme = app2.customCss.theme;
return theme === "" ? null : theme;
}
// node_modules/obsidian-extra/dist/esm/functions/getCurrentColorScheme.js
function getCurrentThemeID2(app2) {
const { body } = app2.workspace.containerEl.doc;
return body.classList.contains("theme-dark") ? "dark" : "light";
}
// node_modules/obsidian-extra/dist/esm/functions/getThemeManifest.js
function getThemeManifest(app2, themeID) {
const manifests = app2.customCss.themes;
if (!Object.prototype.hasOwnProperty.call(manifests, themeID)) {
return null;
}
return manifests[themeID];
}
// node_modules/obsidian-extra/dist/esm/functions/isThemeInstalled.js
function isThemeInstalled(app2, themeID) {
return getThemeManifest(app2, themeID) !== null;
}
// node_modules/obsidian-extra/dist/esm/functions/getThemeStyleElement.js
function getThemeStyleElement(app2) {
const currentTheme = getCurrentThemeID(app2);
if (currentTheme == null || !isThemeInstalled(app2, currentTheme)) {
return null;
}
return app2.customCss.styleEl;
}
// node_modules/obsidian-extra/dist/esm/functions/isSnippetEnabled.js
function isSnippetEnabled(app2, snippetID) {
return app2.customCss.enabledSnippets.has(snippetID);
}
// node_modules/obsidian-extra/dist/esm/functions/fetchObsidianStyles.js
var import_obsidian = require("obsidian");
// node_modules/obsidian-extra/dist/esm/internal/utils/versionCompare.js
function versionCompare(a, b) {
const aParts = a.split(".").map((n) => parseInt(n, 10));
const bParts = b.split(".").map((n) => parseInt(n, 10));
const partsSize = Math.max(aParts.length, bParts.length);
arrayPadEnd(aParts, partsSize, 0);
arrayPadEnd(bParts, partsSize, 0);
for (let i = 0; i < partsSize; i++) {
if (aParts[i] < bParts[i])
return -1;
if (aParts[i] > bParts[i])
return 1;
}
return 0;
}
function arrayPadEnd(arr, length, fill) {
const missing = length - arr.length;
if (missing > 0) {
arr.push(...new Array(missing).fill(fill));
}
}
// node_modules/obsidian-extra/dist/esm/functions/fetchObsidianStyles.js
var __awaiter = function(thisArg, _arguments, P, generator) {
function adopt(value) {
return value instanceof P ? value : new P(function(resolve) {
resolve(value);
});
}
return new (P || (P = Promise))(function(resolve, reject) {
function fulfilled(value) {
try {
step(generator.next(value));
} catch (e) {
reject(e);
}
}
function rejected(value) {
try {
step(generator["throw"](value));
} catch (e) {
reject(e);
}
}
function step(result) {
result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
}
step((generator = generator.apply(thisArg, _arguments || [])).next());
});
};
function fetchObsidianStyles(app2) {
var _a;
return __awaiter(this, void 0, void 0, function* () {
if (versionCompare(import_obsidian.apiVersion, "1.1.15") > 0) {
throw new Error(`[obsidian-extra]: Obsidian ${import_obsidian.apiVersion} has not been tested with this function`);
}
try {
const electron = (_a = globalThis.electron) !== null && _a !== void 0 ? _a : require("electron");
if (electron == null) {
throw new Error("Unable to get electron module from web renderer process");
}
const fs = require("fs/promises");
if ((fs === null || fs === void 0 ? void 0 : fs.readFile) == null) {
throw new Error("Unable to get fs module from web renderer process");
}
const resources = electron.ipcRenderer.sendSync("resources");
return yield fs.readFile(`${resources}/app.css`, "utf8");
} catch (ex) {
throw new Error(`[obsidian-extra]: Could not get Obsidian styles: ${ex}`);
}
});
}
// node_modules/obsidian-extra/dist/esm/functions/getInstalledSnippetIDs.js
function getInstalledSnippetIDs(app2) {
return app2.customCss.snippets;
}
// node_modules/obsidian-extra/dist/esm/functions/getSnippetStyleElements.js
function getSnippetStyleElements(app2) {
const styleManager = app2.customCss;
const snippets = getInstalledSnippetIDs(app2);
const map = /* @__PURE__ */ new Map();
for (let i = 0, elI = 0; i < snippets.length; i++) {
const snippetID = styleManager.snippets[i];
if (isSnippetEnabled(app2, snippetID)) {
map.set(snippetID, styleManager.extraStyleEls[elI]);
elI++;
}
}
return map;
}
// node_modules/obsidian-extra/dist/esm/functions/openPluginSettings.js
function openPluginSettings(app2, plugin) {
var _a, _b;
const settingManager = app2.setting;
const pluginId = typeof plugin === "string" ? plugin : plugin.manifest.id;
if (((_a = settingManager.activeTab) === null || _a === void 0 ? void 0 : _a.id) !== pluginId) {
settingManager.openTabById("");
}
settingManager.open();
if (((_b = settingManager.activeTab) === null || _b === void 0 ? void 0 : _b.id) !== pluginId) {
settingManager.openTabById(pluginId);
}
}
// node_modules/obsidian-extra/dist/esm/functions/createCustomStyleSheet.js
var Counter = Symbol("CustomStylesheet count");
function foreachWindow(app2, fn) {
fn(app2.workspace.containerEl.doc, true);
for (const float of getFloatingWindows(app2)) {
fn(float.document, false);
}
}
function createCustomStyleSheet(app2, plugin) {
var _a;
let result;
const pl = plugin;
const plId = plugin.manifest.id;
const ssId = ((_a = pl[Counter]) !== null && _a !== void 0 ? _a : pl[Counter] = 0).toString();
pl[Counter]++;
const styleEl = app2.workspace.containerEl.doc.createElement("style");
const styleElInFloats = [];
styleEl.setAttribute("data-source-plugin", plId);
styleEl.setAttribute("data-source-id", ssId);
function unapply() {
styleElInFloats.splice(0, styleElInFloats.length).forEach((el) => el.remove());
styleEl.detach();
foreachWindow(app2, (doc) => {
for (const styleEl2 of Array.from(doc.head.querySelectorAll("style"))) {
if (result.is(styleEl2)) {
styleEl2.remove();
}
}
});
}
function reapply() {
unapply();
foreachWindow(app2, (doc, isFloating) => {
let lastEl = doc.head.lastElementChild;
for (let el = lastEl; el != null; el = el.previousElementSibling) {
lastEl = el;
if (lastEl.tagName === "STYLE") {
break;
}
}
if (!isFloating) {
lastEl === null || lastEl === void 0 ? void 0 : lastEl.insertAdjacentElement("afterend", styleEl);
return;
}
const styleElClone = styleEl.cloneNode(true);
styleElInFloats.push(styleElClone);
lastEl === null || lastEl === void 0 ? void 0 : lastEl.insertAdjacentElement("afterend", styleElClone);
});
}
app2.workspace.on("css-change", reapply);
app2.workspace.on("layout-change", reapply);
result = Object.freeze(Object.defineProperties(() => {
unapply();
app2.workspace.off("css-change", reapply);
app2.workspace.off("layout-change", reapply);
}, {
css: {
enumerable: true,
configurable: false,
get() {
return styleEl.textContent;
},
set(v) {
styleEl.textContent = v;
for (const styleEl2 of styleElInFloats) {
styleEl2.textContent = v;
}
}
},
is: {
enumerable: false,
configurable: false,
value: (el) => {
return el.getAttribute("data-source-plugin") === plId && el.getAttribute("data-source-id") === ssId;
}
},
setAttribute: {
enumerable: false,
configurable: false,
value: (attr, value) => {
if (attr === "data-source-id" || attr === "data-source-plugin") {
throw new Error(`Cannot change attribute '${attr}' on custom style sheet.`);
}
styleEl.setAttribute(attr, value);
for (const styleEl2 of styleElInFloats) {
styleEl2.setAttribute(attr, value);
}
}
},
removeAttribute: {
enumerable: false,
configurable: false,
value: (attr) => {
if (attr === "data-source-id" || attr === "data-source-plugin") {
throw new Error(`Cannot remove attribute '${attr}' from custom style sheet.`);
}
styleEl.removeAttribute(attr);
for (const styleEl2 of styleElInFloats) {
styleEl2.removeAttribute(attr);
}
}
}
}));
reapply();
return result;
}
// src/ui/paned-setting-tab.ts
var import_obsidian3 = require("obsidian");
// node_modules/obsidian-extra/dist/esm/functions/closeSettings.js
function closeSettings(app2) {
const settingManager = app2.setting;
settingManager.close();
}
// src/ui/pane-layers.ts
var import_obsidian2 = require("obsidian");
var UIPaneLayers = class {
constructor(options) {
this.layers = [];
this.closeParent = options.close;
this.navInstance = {
open: (pane) => this.push(pane),
close: () => this.pop(),
replace: (pane) => this.top = pane
};
}
/**
* Pushes a new pane on top of the stack.
* The active pane will be suspended.
*
* @param pane The pane to push.
*/
push(pane) {
const { activePane: oldPane } = this;
if (oldPane !== void 0) {
const title = oldPane.title;
this.layers.push({
scroll: { top: this.scrollEl.scrollTop, left: this.scrollEl.scrollLeft },
state: oldPane.suspendState(),
pane: oldPane,
title: typeof title === "string" ? title : title.subtitle
});
this.setPaneVariables(oldPane, false);
this.containerEl.empty();
}
const newPane = this.activePane = pane;
this.setPaneVariables(newPane, true);
newPane.onReady();
this.doDisplay(true);
this.scrollEl.scrollTo({ top: 0, left: 0 });
}
/**
* Pops the active pane off the stack.
* The active pane will be destroyed, and the one underneath it will be restored.
*
* @param pane The pane to push.
*/
pop(options) {
var _a, _b;
if (this.activePane === void 0) {
this.closeParent();
return void 0;
}
const noDisplay = (_a = options == null ? void 0 : options.noDisplay) != null ? _a : false;
const oldPane = this.activePane;
const newPane = this.layers.pop();
this.activePane = void 0;
this.setPaneVariables(oldPane, false);
oldPane.onClose((_b = options == null ? void 0 : options.cancelled) != null ? _b : false);
if (!noDisplay) {
this.containerEl.empty();
}
if (newPane !== void 0) {
this.activePane = newPane.pane;
this.setPaneVariables(newPane.pane, true);
newPane.pane.restoreState(newPane.state);
if (!noDisplay) {
this.doDisplay(true);
this.scrollEl.scrollTo(newPane.scroll);
}
}
return oldPane;
}
/**
* Removes all panes off the stack.
* All panes will be destroyed.
*
* @param pane The pane to push.
*/
clear(options) {
const removed = [];
const opts = {
noDisplay: true,
...options != null ? options : {}
};
while (this.activePane !== void 0) {
removed.push(this.pop(opts));
}
return removed;
}
/**
* The top-most (i.e. currently active) pane in the layers.
*/
get top() {
return this.activePane;
}
set top(pane) {
const { activePane: oldTop } = this;
if (oldTop !== void 0) {
this.setPaneVariables(oldTop, false);
oldTop.onClose(false);
}
const newPane = this.activePane = pane;
this.setPaneVariables(newPane, true);
newPane.onReady();
this.doDisplay(true);
}
doDisplay(renderControls) {
const { activePane, titleEl, navEl, containerEl } = this;
if (activePane === void 0) {
return;
}
navEl.empty();
if (this.layers.length > 0) {
new import_obsidian2.ButtonComponent(this.navEl).setIcon("lucide-arrow-left-circle").setClass("clickable-icon").setTooltip(`Back to ${this.layers[this.layers.length - 1].title}`).onClick(() => this.navInstance.close());
}
titleEl.empty();
const { title } = activePane;
if (typeof title === "string") {
titleEl.createEl("h2", { text: title });
} else {
titleEl.createEl("h2", { text: title.title });
titleEl.createEl("h3", { text: title.subtitle });
}
if (renderControls) {
this.controlsEl.empty();
activePane.displayControls();
}
containerEl.empty();
activePane.display();
}
setPaneVariables(pane, attached) {
const notAttachedError = () => {
throw new Error("Not attached");
};
Object.defineProperties(pane, {
nav: {
configurable: true,
enumerable: true,
get: attached ? () => this.navInstance : notAttachedError
},
containerEl: {
configurable: true,
enumerable: true,
get: attached ? () => this.containerEl : notAttachedError
},
controlsEl: {
configurable: true,
enumerable: true,
get: attached ? () => this.controlsEl : notAttachedError
}
});
}
};
// src/ui/paned-setting-tab.ts
var UISettingTab = class extends import_obsidian3.PluginSettingTab {
constructor(plugin, createDefault) {
super(plugin.app, plugin);
this.plugin = plugin;
this.createDefault = createDefault;
this.initLayer = null;
this.layers = new UIPaneLayers({
close: () => closeSettings(this.app)
});
}
openWithPane(pane) {
this.initLayer = pane;
openPluginSettings(this.plugin.app, this.plugin);
}
/** @override */
hide() {
this.initLayer = null;
this.layers.clear();
super.hide();
}
display() {
var _a;
const { containerEl, layers } = this;
containerEl.empty();
containerEl.classList.add("calloutmanager-setting-tab", "calloutmanager-pane");
const headerEl = containerEl.createDiv({ cls: "calloutmanager-setting-tab-header" });
layers.navEl = headerEl.createDiv({ cls: "calloutmanager-setting-tab-nav" });
layers.titleEl = headerEl.createDiv({ cls: "calloutmanager-setting-tab-title" });
const controlsEl = headerEl.createDiv({ cls: "calloutmanager-setting-tab-controls" });
layers.controlsEl = controlsEl.createDiv();
layers.scrollEl = containerEl.createDiv({
cls: "calloutmanager-setting-tab-viewport vertical-tab-content"
});
layers.containerEl = layers.scrollEl.createDiv({ cls: "calloutmanager-setting-tab-content" });
controlsEl.createDiv({ cls: "modal-close-button" }, (closeButtonEl) => {
closeButtonEl.addEventListener("click", (ev) => {
if (!ev.isTrusted)
return;
closeSettings(this.app);
});
});
layers.clear();
const initLayer = (_a = this.initLayer) != null ? _a : this.createDefault();
this.initLayer = null;
layers.top = initLayer;
}
};
// src/api-v1.ts
var import_obsidian5 = require("obsidian");
// src/util/color.ts
function toHSV(color) {
if ("h" in color && "s" in color && "v" in color)
return color;
const rFloat = color.r / 255;
const gFloat = color.g / 255;
const bFloat = color.b / 255;
const cmax = Math.max(rFloat, gFloat, bFloat);
const cmin = Math.min(rFloat, gFloat, bFloat);
const delta = cmax - cmin;
let h = 0;
if (cmax !== cmin) {
switch (cmax) {
case rFloat:
h = (60 * ((gFloat - bFloat) / delta) + 360) % 360;
break;
case gFloat:
h = (60 * ((bFloat - rFloat) / delta) + 120) % 360;
break;
case bFloat:
h = (60 * ((rFloat - gFloat) / delta) + 240) % 360;
break;
}
}
const s = cmax === 0 ? 0 : delta / cmax * 100;
const v = cmax * 100;
const hsv = { h, s, v };
if ("a" in color) {
hsv.a = color.a / 255 * 100;
}
return hsv;
}
function toHexRGB(color) {
const parts = [color.r, color.g, color.b, ..."a" in color ? [color.a] : []];
return parts.map((c) => c.toString(16).padStart(2, "0")).join("");
}
var REGEX_RGB = /^\s*rgba?\(\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?)\s*[, ]\s*([\d.]+%?\s*)\)\s*$/i;
function parseColorRGB(rgb) {
const matches = REGEX_RGB.exec(rgb);
if (matches === null)
return null;
const components = matches.slice(1).map((v) => v.trim());
const rgbComponents = rgbComponentStringsToNumber(components);
if (rgbComponents === null) {
return null;
}
if (void 0 !== rgbComponents.find((v) => isNaN(v) || v < 0 || v > 255)) {
return null;
}
return {
r: rgbComponents[0],
g: rgbComponents[1],
b: rgbComponents[2]
};
}
function rgbComponentStringsToNumber(components) {
if (components[0].endsWith("%")) {
if (void 0 !== components.slice(1, 3).find((c) => !c.endsWith("%"))) {
return null;
}
return components.map((v) => parseFloat(v.substring(0, v.length - 1))).map((v) => Math.floor(v * 255 / 100));
}
if (void 0 !== components.slice(1, 3).find((c) => c.endsWith("%"))) {
return null;
}
return components.map((v) => parseInt(v, 10));
}
// src/ui/component/callout-preview.ts
var import_obsidian4 = require("obsidian");
var NO_ATTACH = Symbol();
var CalloutPreviewComponent = class extends import_obsidian4.Component {
constructor(containerEl, options) {
super();
const { color, icon, id, title, content } = options;
const frag = document.createDocumentFragment();
const calloutEl = this.calloutEl = frag.createDiv({ cls: ["callout", "calloutmanager-preview"] });
const titleElContainer = calloutEl.createDiv({ cls: "callout-title" });
this.iconEl = titleElContainer.createDiv({ cls: "callout-icon" });
const titleEl = this.titleEl = titleElContainer.createDiv({ cls: "callout-title-inner" });
const contentEl = this.contentEl = content === void 0 ? void 0 : calloutEl.createDiv({ cls: "callout-content" });
this.setIcon(icon);
this.setColor(color);
this.setCalloutID(id);
if (title == null)
titleEl.textContent = id;
else if (typeof title === "function")
title(titleEl);
else if (typeof title === "string")
titleEl.textContent = title;
else
titleEl.appendChild(title);
if (contentEl != null) {
if (typeof content === "function")
content(contentEl);
else if (typeof content === "string")
contentEl.textContent = content;
else
contentEl.appendChild(content);
}
if (containerEl != NO_ATTACH) {
CalloutPreviewComponent.prototype.attachTo.call(this, containerEl);
}
}
/**
* Changes the callout ID.
* This will *not* change the appearance of the preview.
*
* @param id The new ID to use.
*/
setCalloutID(id) {
const { calloutEl } = this;
calloutEl.setAttribute("data-callout", id);
return this;
}
/**
* Changes the callout icon.
*
* @param icon The ID of the new icon to use.
*/
setIcon(icon) {
const { iconEl, calloutEl } = this;
calloutEl.style.setProperty("--callout-icon", icon);
iconEl.empty();
const iconSvg = (0, import_obsidian4.getIcon)(icon);
if (iconSvg != null) {
this.iconEl.appendChild(iconSvg);
}
return this;
}
/**
* Changes the callout color.
*
* @param color The color to use.
*/
setColor(color) {
const { calloutEl } = this;
if (color == null) {
calloutEl.style.removeProperty("--callout-color");
return this;
}
calloutEl.style.setProperty("--callout-color", `${color.r}, ${color.g}, ${color.b}`);
return this;
}
/**
* Attaches the callout preview to a DOM element.
* This places it at the end of the element.
*
* @param containerEl The container to attach to.
*/
attachTo(containerEl) {
containerEl.appendChild(this.calloutEl);
return this;
}
/**
* Resets the `--callout-color` and `--callout-icon` CSS properties added to the callout element.
*/
resetStylePropertyOverrides() {
const { calloutEl } = this;
calloutEl.style.removeProperty("--callout-color");
calloutEl.style.removeProperty("--callout-icon");
}
};
var IsolatedCalloutPreviewComponent = class extends CalloutPreviewComponent {
constructor(containerEl, options) {
var _a, _b, _c;
super(NO_ATTACH, options);
const frag = document.createDocumentFragment();
const focused = (_a = options.focused) != null ? _a : false;
const colorScheme = options.colorScheme;
const readingView = ((_b = options.viewType) != null ? _b : "reading") === "reading";
const cssEls = (_c = options == null ? void 0 : options.cssEls) != null ? _c : getCurrentStyles(containerEl == null ? void 0 : containerEl.doc);
const shadowHostEl = this.shadowHostEl = frag.createDiv();
const shadowRoot = this.shadowRoot = shadowHostEl.attachShadow({ delegatesFocus: false, mode: "closed" });
const shadowHead = this.shadowHead = shadowRoot.createEl("head");
const shadowBody = this.shadowBody = shadowRoot.createEl("body");
const styleEls = this.styleEls = [];
for (const cssEl of cssEls) {
const cssElClone = cssEl.cloneNode(true);
if (cssEl.tagName === "STYLE") {
styleEls.push(cssElClone);
}
shadowHead.appendChild(cssElClone);
}
shadowHead.createEl("style", { text: SHADOW_DOM_RESET_STYLES });
this.customStyleEl = shadowHead.createEl("style", { attr: { "data-custom-styles": "true" } });
shadowBody.classList.add(`theme-${colorScheme}`, "obsidian-app");
const viewContentEl = shadowBody.createDiv({ cls: "app-container" }).createDiv({ cls: "horizontal-main-container" }).createDiv({ cls: "workspace" }).createDiv({ cls: "workspace-split mod-root" }).createDiv({ cls: `workspace-tabs ${focused ? "mod-active" : ""}` }).createDiv({ cls: "workspace-tab-container" }).createDiv({ cls: `workspace-leaf ${focused ? "mod-active" : ""}` }).createDiv({ cls: "workspace-leaf-content" }).createDiv({ cls: "view-content" });
const calloutParentEl = readingView ? createReadingViewContainer(viewContentEl) : createLiveViewContainer(viewContentEl);
calloutParentEl.appendChild(this.calloutEl);
if (containerEl != null) {
IsolatedCalloutPreviewComponent.prototype.attachTo.call(this, containerEl);
}
}
/**
* Replaces the `<style>` elements used by the isolated callout preview with the latest ones.
*/
updateStyles() {
return this.updateStylesWith(
getCurrentStyles(this.shadowHostEl.doc).filter((e) => e.tagName === "STYLE").map((e) => e.cloneNode(true))
);
}
/**
* Replaces the `<style>` elements used by the isolated callout preview.
* This can be used to update the preview with the latest styles.
*
* @param styleEls The new style elements to use. These will *not* be cloned.
*/
updateStylesWith(styleEls) {
const { styleEls: oldStyleEls, customStyleEl } = this;
let i, end;
let lastNode = customStyleEl.previousSibling;
for (i = 0, end = Math.min(styleEls.length, oldStyleEls.length); i < end; i++) {
const el = styleEls[i];
oldStyleEls[i].replaceWith(el);
lastNode = el;
}
for (end = styleEls.length; i < end; i++) {
const el = styleEls[i];
lastNode.insertAdjacentElement("afterend", el);
oldStyleEls.push(el);
}
const toRemove = oldStyleEls.splice(i, oldStyleEls.length - i);
for (const node of toRemove) {
node.remove();
}
return this;
}
/**
* Removes matching style elements.
* @param predicate The predicate function. If it returns true, the element is removed.
*/
removeStyles(predicate) {
for (let i = 0; i < this.styleEls.length; i++) {
const el = this.styleEls[i];
if (predicate(el)) {
el.remove();
this.styleEls.splice(i, 1);
i--;
}
}
}
/**
* Changes the color scheme.
* @param colorScheme The color scheme to use.
*/
setColorScheme(colorScheme) {
const { classList } = this.shadowBody;
classList.toggle("theme-dark", colorScheme === "dark");
classList.toggle("theme-light", colorScheme === "light");
return this;
}
/**
* Attaches the callout preview to a DOM element.
* This places it at the end of the element.
*
* @param containerEl The container to attach to.
* @override
*/
attachTo(containerEl) {
containerEl.appendChild(this.shadowHostEl);
return this;
}
};
function getCurrentStyles(doc) {
var _a;
const els = [];
let node = (doc != null ? doc : window.document).head.firstElementChild;
for (; node != null; node = node.nextElementSibling) {
const nodeTag = node.tagName;
if (nodeTag === "STYLE" || nodeTag === "LINK" && ((_a = node.getAttribute("rel")) == null ? void 0 : _a.toLowerCase()) === "stylesheet") {
els.push(node);
}
}
return els;
}
function createReadingViewContainer(viewContentEl) {
return viewContentEl.createDiv({ cls: "markdown-reading-view" }).createDiv({ cls: "markdown-preview-view markdown-rendered" }).createDiv({ cls: "markdown-preview-section" }).createDiv();
}
function createLiveViewContainer(viewContentEl) {
return viewContentEl.createDiv({ cls: "markdown-source-view cm-s-obsidian mod-cm6 is-live-preview" }).createDiv({ cls: "cm-editor \u037C1 \u037C2 \u037Cq" }).createDiv({ cls: "cm-scroller" }).createDiv({ cls: "cm-sizer" }).createDiv({ cls: "cm-contentContainer" }).createDiv({ cls: "cm-content" }).createDiv({ cls: "cm-embed-block markdown-rendered cm-callout" });
}
var SHADOW_DOM_RESET_STYLES = `
/* Reset layout and stylings for all properties up to the callout. */
.app-container,
.horizontal-main-container,
.workspace,
.workspace-split,
.workspace-tabs,
.workspace-tab-container,
.workspace-leaf,
.workspace-leaf-content,
.view-content,
.markdown-reading-view,
.markdown-source-view,
.cm-editor.\u037C1.\u037C2.\u037Cq,
.cm-editor.\u037C1.\u037C2.\u037Cq > .cm-scroller,
.cm-sizer,
.cm-contentContainer,
.cm-content,
.markdown-preview-view {
all: initial !important;
display: block !important;
}
/* Set the text color of the container for the callout. */
.markdown-preview-section,
.cm-callout {
color: var(--text-normal) !important;
}
/* Override margin on callout to keep the preview as small as possible. */
.markdown-preview-section > div > .callout,
.cm-callout > .callout,
.calloutmanager-preview.callout {
margin: 0 !important;
}
/* Set the font properties of the callout. */
.cm-callout,
.callout {
font-size: var(--font-text-size) !important;
font-family: var(--font-text) !important;
line-height: var(--line-height-normal) !important;
}
/* Use transparent background color. */
body {
background-color: transparent !important;
}
`;
// src/callout-resolver.ts
var CalloutResolver = class {
constructor() {
this.hostElement = document.body.createDiv({
cls: "calloutmanager-callout-resolver"
});
this.hostElement.style.setProperty("display", "none", "important");
this.calloutPreview = new IsolatedCalloutPreviewComponent(this.hostElement, {
id: "",
icon: "",
colorScheme: "dark"
});
this.calloutPreview.resetStylePropertyOverrides();
}
/**
* Reloads the styles of the callout resolver.
* This is necessary to get up-to-date styles when the application CSS changes.
*
* Note: This will not reload the Obsidian app.css stylesheet.
* @param styles The new style elements to use.
*/
reloadStyles() {
this.calloutPreview.setColorScheme(getCurrentThemeID2(app));
this.calloutPreview.updateStyles();
this.calloutPreview.removeStyles((el) => el.getAttribute("data-callout-manager") === "style-overrides");
}
/**
* Removes the host element.
* This should be called when the plugin is unloading.
*/
unload() {
this.hostElement.remove();
}
/**
* Gets the computed styles for a given type of callout.
* This uses the current Obsidian styles, themes, and snippets.
*
* @param id The callout ID.
* @param callback A callback function to run. The styles may only be accessed through this.
* @returns Whatever the callback function returned.
*/
getCalloutStyles(id, callback) {
const { calloutEl } = this.calloutPreview;
calloutEl.setAttribute("data-callout", id);
return callback(window.getComputedStyle(calloutEl));
}
/**
* Gets the icon and color for a given type of callout.
* This uses the current Obsidian styles, themes, and snippets.
*
* @param id The callout ID.
* @returns The callout icon and color.
*/
getCalloutProperties(id) {
return this.getCalloutStyles(id, (styles) => ({
icon: styles.getPropertyValue("--callout-icon").trim(),
color: styles.getPropertyValue("--callout-color").trim()
}));
}
get customStyleEl() {
return this.calloutPreview.customStyleEl;
}
};
function getColorFromCallout(callout) {
return parseColorRGB(`rgb(${callout.color})`);
}
// src/api-v1.ts
var _emitter;
var CalloutManagerAPI_V1 = class {
constructor(plugin, consumer) {
__privateAdd(this, _emitter, void 0);
this.plugin = plugin;
this.consumer = consumer;
__privateSet(this, _emitter, new import_obsidian5.Events());
if (consumer != null) {
console.debug("Created API V1 Handle:", { plugin: consumer.manifest.id });
}
}
/**
* Called to destroy an API handle bound to a consumer.
*/
destroy() {
const consumer = this.consumer;
console.debug("Destroyed API V1 Handle:", { plugin: consumer.manifest.id });
}
/** @override */
getCallouts() {
return this.plugin.callouts.values().map((callout) => Object.freeze({ ...callout }));
}
/** @override */
getColor(callout) {
const color = getColorFromCallout(callout);
return color != null ? color : { invalid: callout.color };
}
/** @override */
on(event, listener) {
if (this.consumer == null) {
throw new Error("Cannot listen for events without an API consumer.");
}
__privateGet(this, _emitter).on(event, listener);
}
/** @override */
off(event, listener) {
if (this.consumer == null) {
throw new Error("Cannot listen for events without an API consumer.");
}
__privateGet(this, _emitter).off(event, listener);
}
_emit(event, ...params) {
__privateGet(this, _emitter).trigger(event, params);
}
};
_emitter = new WeakMap();
// src/callout-collection.ts
var CalloutCollection = class {
constructor(resolver) {
this.resolver = resolver;
this.invalidated = /* @__PURE__ */ new Set();
this.invalidationCount = 0;
this.cacheById = /* @__PURE__ */ new Map();
this.cached = false;
this.snippets = new CalloutCollectionSnippets(this.invalidateSource.bind(this));
this.builtin = new CalloutCollectionObsidian(this.invalidateSource.bind(this));
this.theme = new CalloutCollectionTheme(this.invalidateSource.bind(this));
this.custom = new CalloutCollectionCustom(this.invalidateSource.bind(this));
}
get(id) {
if (!this.cached)
this.buildCache();
const cached = this.cacheById.get(id);
if (cached === void 0) {
return void 0;
}
if (this.invalidated.has(cached)) {
this.resolveOne(cached);
}
return cached.callout;
}
/**
* Gets all the known {@link CalloutID callout IDs}.
* @returns The callout IDs.
*/
keys() {
if (!this.cached)
this.buildCache();
return Array.from(this.cacheById.keys());
}
/**
* Gets all the known {@link Callout callouts}.
* @returns The callouts.
*/
values() {
if (!this.cached)
this.buildCache();
this.resolveAll();
return Array.from(this.cacheById.values()).map((c) => c.callout);
}
/**
* Returns a function that will return `true` if the collection has changed since the function was created.
* @returns The function.
*/
hasChanged() {
const countSnapshot = this.invalidationCount;
return () => this.invalidationCount !== countSnapshot;
}
/**
* Resolves the settings of a callout.
* This removes it from the set of invalidated callout caches.
*
* @param cached The callout's cache entry.
*/
resolveOne(cached) {
this.doResolve(cached);
this.invalidated.delete(cached);
}
/**
* Resolves the settings of all callouts.
*/
resolveAll() {
for (const cached of this.invalidated.values()) {
this.doResolve(cached);
}
this.invalidated.clear();
}
doResolve(cached) {
cached.callout = this.resolver(cached.id);
cached.callout.sources = Array.from(cached.sources.values()).map(sourceFromKey);
}
/**
* Builds the initial cache of callouts.
* This creates the cache entries and associates them to a source.
*/
buildCache() {
this.invalidated.clear();
this.cacheById.clear();
{
const source = sourceToKey({ type: "builtin" });
for (const callout of this.builtin.get()) {
this.addCalloutSource(callout, source);
}
}
if (this.theme.theme != null) {
const source = sourceToKey({ type: "theme", theme: this.theme.theme });
for (const callout of this.theme.get()) {
this.addCalloutSource(callout, source);
}
}
for (const snippet of this.snippets.keys()) {
const source = sourceToKey({ type: "snippet", snippet });
for (const callout of this.snippets.get(snippet)) {
this.addCalloutSource(callout, source);
}
}
{
const source = sourceToKey({ type: "custom" });
for (const callout of this.custom.keys()) {
this.addCalloutSource(callout, source);
}
}
this.cached = true;
}
/**
* Marks a callout as invalidated.
* This forces the callout to be resolved again.
*
* @param id The callout ID.
*/
invalidate(id) {
if (!this.cached)
return;
const callout = this.cacheById.get(id);
if (callout !== void 0) {
console.debug("Invalided Callout Cache:", id);
this.invalidated.add(callout);
}
}
addCalloutSource(id, sourceKey) {
let callout = this.cacheById.get(id);
if (callout == null) {
callout = new CachedCallout(id);
this.cacheById.set(id, callout);
}
callout.sources.add(sourceKey);
this.invalidated.add(callout);
}
removeCalloutSource(id, sourceKey) {
const callout = this.cacheById.get(id);
if (callout == null) {
return;
}
callout.sources.delete(sourceKey);
if (callout.sources.size === 0) {
this.cacheById.delete(id);
this.invalidated.delete(callout);
}
}
/**
* Called whenever a callout source has any changes.
* This will add or remove callouts from the cache, or invalidate a callout to mark it as requiring re-resolving.
*
* @param src The source that changed.
* @param data A diff of changes.
*/
invalidateSource(src, data) {
const sourceKey = sourceToKey(src);
if (!this.cached) {
return;
}
for (const removed of data.removed) {
this.removeCalloutSource(removed, sourceKey);
}
for (const added of data.added) {
this.addCalloutSource(added, sourceKey);
}
for (const changed of data.changed) {
const callout = this.cacheById.get(changed);
if (callout != null) {
this.invalidated.add(callout);
}
}
this.invalidationCount++;
}
};
var CachedCallout = class {
constructor(id) {
this.id = id;
this.sources = /* @__PURE__ */ new Set();
this.callout = null;
}
};
var CalloutCollectionSnippets = class {
constructor(invalidate) {
this.data = /* @__PURE__ */ new Map();
this.invalidate = invalidate;
}
get(id) {
const value = this.data.get(id);
if (value === void 0) {
return void 0;
}
return Array.from(value.values());
}
set(id, callouts) {
const source = { type: "snippet", snippet: id };
const old = this.data.get(id);
const updated = new Set(callouts);
this.data.set(id, updated);
if (old === void 0) {
this.invalidate(source, { added: callouts, changed: [], removed: [] });
return;
}
const diffs = diff(old, updated);
this.invalidate(source, {
added: diffs.added,
removed: diffs.removed,
changed: diffs.same
});
}
delete(id) {
const old = this.data.get(id);
const deleted = this.data.delete(id);
if (old !== void 0) {
this.invalidate(
{ type: "snippet", snippet: id },
{
added: [],
changed: [],
removed: Array.from(old.keys())
}
);
}
return deleted;
}
clear() {
for (const id of Array.from(this.data.keys())) {
this.delete(id);
}
}
keys() {
return Array.from(this.data.keys());
}
};
var CalloutCollectionObsidian = class {
constructor(invalidate) {
this.data = /* @__PURE__ */ new Set();
this.invalidate = invalidate;
}
set(callouts) {
const old = this.data;
const updated = this.data = new Set(callouts);
const diffs = diff(old, updated);
this.invalidate(
{ type: "builtin" },
{
added: diffs.added,
removed: diffs.removed,
changed: diffs.same
}
);
}
get() {
return Array.from(this.data.values());
}
};
var CalloutCollectionTheme = class {
constructor(invalidate) {
this.data = /* @__PURE__ */ new Set();
this.invalidate = invalidate;
this.oldTheme = "";
}
get theme() {
return this.oldTheme;
}
set(theme, callouts) {
const old = this.data;
const oldTheme = this.oldTheme;
const updated = this.data = new Set(callouts);
this.oldTheme = theme;
if (this.oldTheme === theme) {
const diffs = diff(old, updated);
this.invalidate(
{ type: "theme", theme },
{
added: diffs.added,
removed: diffs.removed,
changed: diffs.same
}
);
return;
}
this.invalidate(
{ type: "theme", theme: oldTheme != null ? oldTheme : "" },
{
added: [],
removed: Array.from(old.values()),
changed: []
}
);
this.invalidate(
{ type: "theme", theme },
{
added: callouts,
removed: [],
changed: []
}
);
}
delete() {
const old = this.data;
const oldTheme = this.oldTheme;
this.data = /* @__PURE__ */ new Set();
this.oldTheme = null;
this.invalidate(
{ type: "theme", theme: oldTheme != null ? oldTheme : "" },
{
added: [],
removed: Array.from(old.values()),
changed: []
}
);
}
get() {
return Array.from(this.data.values());
}
};
var CalloutCollectionCustom = class {
constructor(invalidate) {
this.data = [];
this.invalidate = invalidate;
}
has(id) {
return void 0 !== this.data.find((existingId) => existingId === id);
}
add(...ids) {
const set = new Set(this.data);
const added = [];
for (const id of ids) {
if (!set.has(id)) {
added.push(id);
set.add(id);
this.data.push(id);
}
}
if (added.length > 0) {
this.invalidate({ type: "custom" }, { added, removed: [], changed: [] });
}
}
delete(...ids) {
const { data } = this;
const removed = [];
for (const id of ids) {
const index = data.findIndex((existingId) => id === existingId);
if (index !== void 0) {
data.splice(index, 1);
removed.push(id);
}
}
if (removed.length > 0) {
this.invalidate({ type: "custom" }, { added: [], removed, changed: [] });
}
}
keys() {
return this.data.slice(0);
}
clear() {
const removed = this.data;
this.data = [];
this.invalidate({ type: "custom" }, { added: [], removed, changed: [] });
}
};
function diff(before, after) {
const added = [];
const removed = [];
const same = [];
for (const item of before.values()) {
(after.has(item) ? same : removed).push(item);
}
for (const item of after.values()) {
if (!before.has(item)) {
added.push(item);
}
}
return { added, removed, same };
}
function sourceToKey(source) {
switch (source.type) {
case "builtin":
return "builtin";
case "snippet":
return `snippet:${source.snippet}`;
case "theme":
return `theme:${source.theme}`;
case "custom":
return `custom`;
}
}
function sourceFromKey(sourceKey) {
if (sourceKey === "builtin") {
return { type: "builtin" };
}
if (sourceKey === "custom") {
return { type: "custom" };
}
if (sourceKey.startsWith("snippet:")) {
return { type: "snippet", snippet: sourceKey.substring("snippet:".length) };
}
if (sourceKey.startsWith("theme:")) {
return { type: "theme", theme: sourceKey.substring("theme:".length) };
}
throw new Error("Unknown source key: " + sourceKey);
}
// src/callout-fallback-obsidian.json
var callout_fallback_obsidian_default = [
"quote",
"cite",
"warning",
"caution",
"attention",
"help",
"faq",
"success",
"check",
"done",
"abstract",
"summary",
"tldr",
"important",
"tip",
"hint",
"info",
"todo",
"example",
"failure",
"fail",
"missing",
"danger",
"error",
"bug"
];
// src/callout-settings.ts
function currentCalloutEnvironment(app2) {
var _a;
const theme = (_a = getCurrentThemeID(app2)) != null ? _a : "<default>";
return {
theme,
colorScheme: getCurrentThemeID2(app2)
};
}
function calloutSettingsToCSS(id, settings, environment) {
const styles = calloutSettingsToStyles(settings, environment).join(";\n ");
if (styles.length === 0) {
return "";
}
return `.callout[data-callout="${id}"] {
` + styles + "\n}";
}
function calloutSettingsToStyles(settings, environment) {
const styles = [];
for (const setting of settings) {
if (!checkCondition(setting.condition, environment)) {
continue;
}
const { changes } = setting;
if (changes.color != null)
styles.push(`--callout-color: ${changes.color}`);
if (changes.icon != null)
styles.push(`--callout-icon: ${changes.icon}`);
if (changes.customStyles != null)
styles.push(changes.customStyles);
}
return styles;
}
function checkCondition(condition, environment) {
if (condition == null) {
return true;
}
if ("or" in condition && condition.or !== void 0) {
return condition.or.findIndex((p) => checkCondition(p, environment) === true) !== void 0;
}
if ("and" in condition && condition.and !== void 0) {
return condition.and.findIndex((p) => checkCondition(p, environment) === false) === void 0;
}
if ("theme" in condition && condition.theme === environment.theme) {
return true;
}
if ("colorScheme" in condition && condition.colorScheme === environment.colorScheme) {
return true;
}
return false;
}
function typeofCondition(condition) {
if (condition === void 0)
return void 0;
const hasOwnProperty = Object.prototype.hasOwnProperty.bind(condition);
if (hasOwnProperty("colorScheme"))
return "colorScheme";
if (hasOwnProperty("theme"))
return "theme";
if (hasOwnProperty("and"))
return "and";
if (hasOwnProperty("or"))
return "or";
throw new Error(`Unsupported condition: ${JSON.stringify(condition)}`);
}
// src/css-parser.ts
function getCalloutsFromCSS(css) {
var _a;
const REGEX_CALLOUT_SELECTOR = /\[data-callout([^\]]*)\]/gmi;
const REGEX_MATCH_QUOTED_STRING = {
"'": /^'([^']+)'( i)?$/,
'"': /^"([^"]+)"( i)?$/,
"": /^([^\]]+)$/
};
const attributeSelectors = [];
let matches;
while ((matches = REGEX_CALLOUT_SELECTOR.exec(css)) != null) {
attributeSelectors.push(matches[1]);
REGEX_CALLOUT_SELECTOR.lastIndex = matches.index + matches[0].length;
}
const ids = [];
for (const attributeSelector of attributeSelectors) {
let selectorString;
if (attributeSelector.startsWith("=")) {
selectorString = attributeSelector.substring(1);
} else if (attributeSelector.startsWith("^=")) {
selectorString = attributeSelector.substring(2);
} else {
continue;
}
const quoteChar = selectorString.charAt(0);
const stringRegex = (_a = REGEX_MATCH_QUOTED_STRING[quoteChar]) != null ? _a : REGEX_MATCH_QUOTED_STRING[""];
const matches2 = stringRegex.exec(selectorString);
if (matches2 != null && matches2[1] != null) {
ids.push(matches2[1]);
}
}
return ids;
}
// src/css-watcher.ts
var StylesheetWatcher = class {
constructor(app2, disableObsidianStylesheet) {
this.app = app2;
this.listeners = /* @__PURE__ */ new Map();
this.cachedSnippets = /* @__PURE__ */ new Map();
this.cachedObsidian = disableObsidianStylesheet ? false : null;
this.cachedTheme = null;
this.watching = false;
this.disableObsidianStylesheet = disableObsidianStylesheet;
}
/**
* Start watching for changes to stylesheets.
* @returns A callback function to pass to {@link Plugin.register}.
*/
watch() {
if (this.watching) {
throw new Error("Already watching.");
}
const events = [
{
event: "css-change",
listener: () => this.checkForChanges(false),
target: this.app.workspace
}
];
for (const evtl of events) {
evtl.target.on(evtl.event, evtl.listener);
}
this.checkForChanges();
return () => {
if (!this.watching) {
return;
}
for (const evtl of events) {
evtl.target.off(evtl.event, evtl.listener);
}
this.watching = false;
};
}
/**
* @internal
*/
on(event, listener) {
let listenersForEvent = this.listeners.get(event);
if (listenersForEvent === void 0) {
listenersForEvent = /* @__PURE__ */ new Set();
this.listeners.set(event, listenersForEvent);
}
listenersForEvent.add(listener);
}
/**
* Removes an event listener.
*
* @param event The event.
* @param listener The listener to remove.
*/
off(event, listener) {
const listenersForEvent = this.listeners.get(event);
if (listenersForEvent === void 0) {
return;
}
listenersForEvent.delete(listener);
if (listenersForEvent.size === 0) {
this.listeners.delete(event);
}
}
emit(event, ...data) {
const listenersForEvent = this.listeners.get(event);
if (listenersForEvent === void 0) {
return;
}
for (const listener of listenersForEvent) {
listener(...data);
}
}
/**
* Checks for any changes to the application stylesheets.
* If {@link watch} is being used, this will be called automatically.
*
* @param clear If set to true, the cache will be cleared.
* @returns True if there were any changes.
*/
async checkForChanges(clear) {
let changed = false;
this.emit("checkStarted");
if (clear === true) {
this.cachedSnippets.clear();
this.cachedTheme = null;
this.cachedObsidian = null;
}
if (this.cachedObsidian == null && !this.disableObsidianStylesheet) {
changed = await this.checkForChangesObsidian() || changed;
}
changed = this.checkForChangesSnippets() || changed;
changed = this.checkForChangesTheme() || changed;
this.emit("checkComplete", changed);
return changed;
}
/**
* Attempts to fetch the Obsidian built-in stylesheet.
* This will fail if the version of obsidian is newer than the version supported by `obsidian-extra`.
*
* @returns true if the fetch was successful.
*/
async checkForChangesObsidian() {
if (this.cachedObsidian === false) {
return false;
}
try {
this.cachedObsidian = await fetchObsidianStyles(this.app);
this.emit("change", {
type: "obsidian",
styles: this.cachedObsidian
});
return true;
} catch (ex) {
this.cachedObsidian = false;
console.warn("Unable to fetch Obsidian stylesheet.", ex);
return false;
}
}
/**
* Checks for changes in the application's theme.
*/
checkForChangesTheme() {
var _a;
const theme = getCurrentThemeID(this.app);
const themeManifest = theme == null ? null : getThemeManifest(this.app, theme);
const hasTheme = theme != null && themeManifest != null;
const styleEl = getThemeStyleElement(this.app);
const styles = (_a = styleEl == null ? void 0 : styleEl.textContent) != null ? _a : "";
if (this.cachedTheme != null && !hasTheme) {
this.emit("remove", { type: "theme", theme: this.cachedTheme.id, styles: this.cachedTheme.contents });
this.cachedTheme = null;
return true;
}
if (this.cachedTheme == null && hasTheme) {
this.cachedTheme = { id: theme, version: themeManifest.version, contents: styles };
this.emit("add", { type: "theme", theme, styles });
return true;
}
if (!hasTheme || this.cachedTheme == null) {
return false;
}
const changed = this.cachedTheme.id !== theme || // Active theme changed
this.cachedTheme.version !== themeManifest.version || // Version of active theme changed
this.cachedTheme.contents !== styles;
if (changed) {
this.cachedTheme = {
id: theme,
version: themeManifest.version,
contents: styles
};
this.emit("change", { type: "theme", theme, styles });
}
return changed;
}
/**
* Checks for changes in the enabled snippets.
*/
checkForChangesSnippets() {
let anyChanges = false;
const snippets = getSnippetStyleElements(this.app);
const knownSnippets = Array.from(this.cachedSnippets.entries());
for (const [id, cachedStyles] of knownSnippets) {
const styleEl = snippets.get(id);
if (styleEl == null) {
this.cachedSnippets.delete(id);
this.emit("remove", { type: "snippet", snippet: id, styles: cachedStyles });
anyChanges = true;
continue;
}
if (styleEl.textContent != null && styleEl.textContent !== cachedStyles) {
this.cachedSnippets.set(id, styleEl.textContent);
this.emit("change", { type: "snippet", snippet: id, styles: styleEl.textContent });
anyChanges = true;
}
}
for (const [id, styleEl] of snippets.entries()) {
if (styleEl == null)
continue;
if (!this.cachedSnippets.has(id) && styleEl.textContent != null) {
this.cachedSnippets.set(id, styleEl.textContent);
this.emit("add", { type: "snippet", snippet: id, styles: styleEl.textContent });
anyChanges = true;
}
}
return anyChanges;
}
/**
* Returns true if fetching the Obsidian stylesheet is supported.
*/
isObsidianStylesheetSupported() {
return !this.disableObsidianStylesheet && this.cachedObsidian !== false;
}
};
// src/panes/manage-callouts-pane.ts
var import_obsidian16 = require("obsidian");
// src/ui/pane.ts
var UIPane = class {
/**
* Called to display the controls for the pane.
*/
displayControls() {
}
/**
* Called when the pane is created and attached to the setting tab, but before {@link display} is called.
*/
onReady() {
}
/**
* Called when the pane is removed and ready to be destroyed.
* Any important settings should be saved here.
*
* @param cancelled If true, the user closed the pane with the escape key.
*/
onClose(cancelled) {
}
/**
* Called to save the state of the setting pane.
* This is used for suspending a pane when another pane covers it up.
*
* @returns The saved state.
*/
suspendState() {
return void 0;
}
/**
* Called to load the state of the setting pane.
* This is called before {@link display}.
*
* @param state The state to restore.
*/
restoreState(state) {
}
};
// src/panes/create-callout-pane.ts
var import_obsidian15 = require("obsidian");
// src/panes/edit-callout-pane/index.ts
var import_obsidian14 = require("obsidian");
// src/panes/edit-callout-pane/appearance-type.ts
function determineAppearanceType(settings) {
var _a;
return (_a = determineNonComplexAppearanceType(settings)) != null ? _a : {
type: "complex",
settings
};
}
function determineNonComplexAppearanceType(settings) {
var _a, _b;
const settingsWithColorSchemeCondition = [];
const settingsWithNoCondition = [];
for (const setting of settings) {
const type = typeofCondition(setting.condition);
switch (type) {
case "colorScheme":
settingsWithColorSchemeCondition.push(setting);
break;
case void 0:
settingsWithNoCondition.push(setting);
break;
case "and":
case "or":
case "theme": {
console.debug("Cannot represent callout settings with UI.", {
reason: `Has condition of type '${type}'`,
settings
});
return null;
}
}
}
const colorSchemeColor = { dark: void 0, light: void 0 };
for (const setting of settingsWithColorSchemeCondition) {
const changed = Object.keys(setting.changes);
if (changed.length === 0) {
continue;
}
if (changed.find((key) => key !== "color") !== void 0) {
console.debug("Cannot represent callout settings with UI.", {
reason: `Has 'colorScheme' condition with non-color change.`,
settings
});
return null;
}
const appearanceCond = setting.condition.colorScheme;
if (colorSchemeColor[appearanceCond] !== void 0) {
console.debug("Cannot represent callout settings with UI.", {
reason: `Has multiple 'colorScheme' conditions that change ${appearanceCond} color.`,
settings
});
return null;
}
colorSchemeColor[appearanceCond] = setting.changes.color;
}
const otherChanges = {};
for (const [change, value] of settingsWithNoCondition.flatMap((s) => Object.entries(s.changes))) {
if (value === void 0)
continue;
if (change in otherChanges) {
console.debug("Cannot represent callout settings with UI.", {
reason: `Has multiple changes to '${change}'.`,
settings
});
return null;
}
otherChanges[change] = value;
}
const otherChangesColor = otherChanges.color;
delete otherChanges.color;
if (colorSchemeColor.dark === void 0 && colorSchemeColor.light === void 0) {
if (otherChangesColor === void 0) {
return { type: "unified", color: void 0, otherChanges };
}
return { type: "unified", color: otherChangesColor, otherChanges };
}
const colorDark = (_a = colorSchemeColor.dark) != null ? _a : colorSchemeColor.light;
const colorLight = (_b = colorSchemeColor.light) != null ? _b : colorSchemeColor.dark;
return { type: "per-scheme", colorDark, colorLight, otherChanges };
}
// src/panes/edit-callout-pane/editor-complex.ts
var import_obsidian6 = require("obsidian");
// src/panes/edit-callout-pane/appearance-editor.ts
var AppearanceEditor = class {
};
// src/panes/edit-callout-pane/editor-complex.ts
var ComplexAppearanceEditor = class extends AppearanceEditor {
/** @override */
toSettings() {
return this.appearance.settings;
}
/** @override */
render() {
const { containerEl } = this;
const { settings } = this.appearance;
const complexJson = JSON.stringify(settings, void 0, " ");
containerEl.createEl("p", {
text: "This callout has been configured using the plugin's data.json file. To prevent unintentional changes to the configuration, you need to edit it manually."
});
containerEl.createEl("code", { cls: "calloutmanager-edit-callout-appearance-json" }, (el) => {
el.createEl("pre", { text: complexJson });
});
containerEl.createEl("p", {
text: "Alternatively, you can reset the callout by clicking the button below twice."
});
let resetButtonClicked = false;
const resetButton = new import_obsidian6.ButtonComponent(containerEl).setButtonText("Reset Callout").setClass("calloutmanager-edit-callout-appearance-reset").setWarning().onClick(() => {
if (!resetButtonClicked) {
resetButtonClicked = true;
resetButton.setButtonText("Are you sure?");
return;
}
this.setAppearance({ type: "unified", color: void 0, otherChanges: {} });
});
}
};
// src/ui/setting/callout-color.ts
var import_obsidian8 = require("obsidian");
// src/ui/component/reset-button.ts
var import_obsidian7 = require("obsidian");
var ResetButtonComponent = class extends import_obsidian7.ExtraButtonComponent {
constructor(containerEl) {
super(containerEl);
this.setIcon("lucide-undo");
this.extraSettingsEl.classList.add("calloutmanager-reset-button");
}
};
// src/ui/setting/callout-color.ts
var CalloutColorSetting = class extends import_obsidian8.Setting {
constructor(containerEl, callout) {
super(containerEl);
this.onChanged = void 0;
this.callout = callout;
this.isDefault = true;
this.addColorPicker((picker) => {
this.colorComponent = picker;
picker.onChange(() => {
var _a;
const { r, g, b } = this.getColor();
(_a = this.onChanged) == null ? void 0 : _a.call(this, `${r}, ${g}, ${b}`);
});
});
this.components.push(
new ResetButtonComponent(this.controlEl).then((btn) => {
this.resetComponent = btn;
btn.onClick(() => {
var _a;
return (_a = this.onChanged) == null ? void 0 : _a.call(this, void 0);
});
})
);
this.setColor(void 0);
}
/**
* Sets the color string.
* This only accepts comma-delimited RGB values.
*
* @param color The color (e.g. `255, 10, 25`) or undefined to reset the color to default.
* @returns `this`, for chaining.
*/
setColorString(color) {
var _a;
if (color == null) {
return this.setColor(void 0);
}
return this.setColor((_a = parseColorRGB(`rgb(${color})`)) != null ? _a : { r: 0, g: 0, b: 0 });
}
/**
* Sets the color.
*
* @param color The color or undefined to reset the color to default.
* @returns `this`, for chaining.
*/
setColor(color) {
var _a;
const isDefault = this.isDefault = color == null;
if (color == null) {
color = (_a = getColorFromCallout(this.callout)) != null ? _a : { r: 0, g: 0, b: 0 };
}
if (color instanceof Array) {
color = { r: color[0], g: color[1], b: color[2] };
}
this.colorComponent.setValueRgb(color);
this.resetComponent.setDisabled(isDefault).setTooltip(isDefault ? "" : "Reset Color");
return this;
}
getColor() {
return this.colorComponent.getValueRgb();
}
isDefaultColor() {
return this.isDefault;
}
onChange(cb) {
this.onChanged = cb;
return this;
}
};
// src/ui/setting/callout-icon.ts
var import_obsidian11 = require("obsidian");
// src/panes/select-icon-pane.ts
var import_obsidian10 = require("obsidian");
// src/ui/component/icon-preview.ts
var import_obsidian9 = require("obsidian");
var IconPreviewComponent = class extends import_obsidian9.Component {
constructor(containerEl) {
super();
this.componentEl = containerEl.createEl("button", { cls: "calloutmanager-icon-preview" });
this.iconEl = this.componentEl.createDiv({ cls: "calloutmanager-icon-preview--icon" });
this.idEl = this.componentEl.createDiv({ cls: "calloutmanager-icon-preview--id" });
}
/**
* Sets the icon of the icon preview component.
* This will update the label and the icon SVG.
*
* @param icon The icon name.
* @returns This, for chaining.
*/
setIcon(icon) {
const iconSvg = (0, import_obsidian9.getIcon)(icon);
this.componentEl.setAttribute("data-icon-id", icon);
this.idEl.textContent = icon;
this.iconEl.empty();
if (iconSvg != null) {
this.iconEl.appendChild(iconSvg);
}
return this;
}
/**
* Sets the `click` event listener for the component.
*
* @param listener The listener.
* @returns This, for chaining.
*/
onClick(listener) {
this.componentEl.onclick = listener;
return this;
}
};
// src/panes/select-icon-pane.ts
var recentIcons = /* @__PURE__ */ new Set();
var SelectIconPane = class extends UIPane {
constructor(plugin, title, options) {
var _a;
super();
this.title = title;
this.plugin = plugin;
this.onChoose = options.onChoose;
this.previewLimit = (_a = options.limit) != null ? _a : 250;
this.previewLimitOverage = 0;
this.searchQuery = "";
this.searchResults = [];
const usedIconIds = new Set(plugin.callouts.values().map((c) => c.icon));
const usedIcons = this.usedIcons = /* @__PURE__ */ new Map();
this.allIcons = (0, import_obsidian10.getIconIds)().map((id) => {
const icon = {
id,
searchId: id.trim().toLowerCase(),
component: null,
searchResult: null
};
if (usedIconIds.has(id)) {
this.usedIcons.set(id, icon);
}
return icon;
});
this.compareIcons = ({ id: a, searchId: aLC, searchResult: aSR }, { id: b, searchId: bLC, searchResult: bSR }) => {
var _a2, _b;
const recency = (recentIcons.has(b) ? 1 : 0) - (recentIcons.has(a) ? 1 : 0);
const suggested = (usedIcons.has(b) ? 1 : 0) - (usedIcons.has(a) ? 1 : 0);
const searchRank = ((_a2 = bSR == null ? void 0 : bSR.score) != null ? _a2 : 0) - ((_b = aSR == null ? void 0 : aSR.score) != null ? _b : 0);
const sum = recency + suggested + searchRank;
if (sum !== 0)
return sum;
return bLC.localeCompare(aLC);
};
}
/**
* Changes the search query.
* @param query The search query.
*/
search(query) {
this.searchQuery = query;
if (query === "") {
this.resetSearchResults();
} else {
const search = (0, import_obsidian10.prepareFuzzySearch)(query.trim().toLowerCase());
this.calculateSearchResults((icon) => search(icon.searchId));
}
this.display();
}
/**
* Updatse the search results list.
* @param search The search function.
*/
calculateSearchResults(search) {
this.searchResults = this.allIcons.filter((icon) => {
icon.searchResult = search(icon);
return icon.searchResult != null;
});
this.searchResults.sort(this.compareIcons);
this.previewLimitOverage = this.searchResults.splice(this.previewLimit).length;
}
/**
* Resets the search results list to show a default list of suggested icons.
*/
resetSearchResults() {
const { allIcons, previewLimit, usedIcons } = this;
this.searchResults = Array.from(
(/* @__PURE__ */ new Set([
...allIcons.slice(0, previewLimit),
...Array.from(usedIcons.values()).slice(0, previewLimit)
])).values()
);
this.searchResults.sort(this.compareIcons).slice(0, this.previewLimit);
this.previewLimitOverage = this.allIcons.length - this.searchResults.length;
}
/** @override */
display() {
const { containerEl } = this;
const gridEl = document.createDocumentFragment().createDiv({ cls: "calloutmanager-icon-picker" });
for (const icon of this.searchResults) {
if (icon.component == null) {
icon.component = new IconPreviewComponent(gridEl).setIcon(icon.id).componentEl;
} else {
gridEl.appendChild(icon.component);
}
}
gridEl.addEventListener("click", ({ targetNode }) => {
for (; targetNode != null && targetNode !== gridEl; targetNode = targetNode.parentElement) {
if (!(targetNode instanceof HTMLElement))
continue;
const iconId = targetNode.getAttribute("data-icon-id");
if (iconId != null) {
recentIcons.add(iconId);
this.nav.close();
this.onChoose(iconId);
return;
}
}
});
containerEl.empty();
containerEl.appendChild(gridEl);
const { previewLimitOverage } = this;
if (previewLimitOverage > 0) {
const { pluralIs, pluralIcon } = previewLimitOverage === 1 ? { pluralIs: "is", pluralIcon: "icon" } : { pluralIs: "are", pluralIcon: "icons" };
containerEl.createEl("p", {
text: `There ${pluralIs} ${previewLimitOverage} more ${pluralIcon} to show. Refine your search to see more.`
});
}
}
/** @override */
displayControls() {
const { controlsEl } = this;
new import_obsidian10.TextComponent(controlsEl).setValue(this.searchQuery).setPlaceholder("Search icons...").onChange(this.search.bind(this));
}
/** @override */
onReady() {
this.resetSearchResults();
}
};
// src/ui/setting/callout-icon.ts
var CalloutIconSetting = class extends import_obsidian11.Setting {
constructor(containerEl, callout, plugin, getNav) {
super(containerEl);
this.onChanged = void 0;
this.callout = callout;
this.isDefault = true;
this.iconName = void 0;
this.addButton((btn) => {
this.buttonComponent = btn;
btn.onClick(() => {
getNav().open(
new SelectIconPane(plugin, "Select Icon", { onChoose: (icon) => {
var _a;
return (_a = this.onChanged) == null ? void 0 : _a.call(this, icon);
} })
);
});
});
this.components.push(
new ResetButtonComponent(this.controlEl).then((btn) => {
this.resetComponent = btn;
btn.onClick(() => {
var _a;
return (_a = this.onChanged) == null ? void 0 : _a.call(this, void 0);
});
})
);
this.setIcon(void 0);
}
/**
* Sets the icon.
*
* @param icon The icon name or undefined to reset the color to default.
* @returns `this`, for chaining.
*/
setIcon(icon) {
const isDefault = this.isDefault = icon == null;
const iconName = this.iconName = icon != null ? icon : this.callout.icon;
const iconExists = (0, import_obsidian11.getIcon)(iconName) != null;
if (iconExists) {
this.buttonComponent.setIcon(iconName);
} else {
this.buttonComponent.setButtonText(iconExists ? "" : `(missing icon: ${iconName})`);
}
this.resetComponent.setDisabled(isDefault).setTooltip(isDefault ? "" : "Reset Icon");
return this;
}
getIcon() {
var _a;
return (_a = this.iconName) != null ? _a : this.callout.icon;
}
isDefaultIcon() {
return this.isDefault;
}
onChange(cb) {
this.onChanged = cb;
return this;
}
};
// src/panes/edit-callout-pane/editor-per-scheme.ts
var PerSchemeAppearanceEditor = class extends AppearanceEditor {
/** @override */
toSettings() {
const { otherChanges, colorDark, colorLight } = this.appearance;
const forLight = {
condition: { colorScheme: "light" },
changes: {
color: colorLight
}
};
const forDark = {
condition: { colorScheme: "dark" },
changes: {
color: colorDark
}
};
if (forLight.changes.color === void 0)
delete forLight.changes.color;
if (forDark.changes.color === void 0)
delete forDark.changes.color;
return [{ changes: otherChanges }, forLight, forDark];
}
setAppearanceOrChangeToUnified(appearance) {
const { colorDark, colorLight, otherChanges } = appearance;
if (colorDark === void 0 && colorLight === void 0) {
this.setAppearance({ type: "unified", color: void 0, otherChanges });
return;
}
this.setAppearance(appearance);
}
render() {
const { callout, containerEl, appearance, plugin, nav } = this;
const { colorDark, colorLight, otherChanges } = this.appearance;
new CalloutColorSetting(containerEl, callout).setName("Dark Color").setDesc("Change the color of the callout for the dark color scheme.").setColorString(colorDark).onChange((color) => this.setAppearanceOrChangeToUnified({ ...appearance, colorDark: color }));
new CalloutColorSetting(containerEl, callout).setName(`Light Color`).setDesc(`Change the color of the callout for the light color scheme.`).setColorString(colorLight).onChange((color) => this.setAppearanceOrChangeToUnified({ ...appearance, colorLight: color }));
new CalloutIconSetting(containerEl, callout, plugin, () => nav).setName("Icon").setDesc("Change the callout icon.").setIcon(otherChanges.icon).onChange(
(icon) => this.setAppearanceOrChangeToUnified({ ...appearance, otherChanges: { ...otherChanges, icon } })
);
}
};
// src/panes/edit-callout-pane/editor-unified.ts
var import_obsidian12 = require("obsidian");
var UnifiedAppearanceEditor = class extends AppearanceEditor {
/** @override */
toSettings() {
const { otherChanges, color } = this.appearance;
const changes = {
...otherChanges,
color
};
if (color === void 0) {
delete changes.color;
}
return [{ changes }];
}
render() {
const { plugin, containerEl, callout, setAppearance } = this;
const { color, otherChanges } = this.appearance;
const colorScheme = getCurrentThemeID2(plugin.app);
const otherColorScheme = colorScheme === "dark" ? "light" : "dark";
new CalloutColorSetting(containerEl, callout).setName("Color").setDesc("Change the color of the callout.").setColorString(color).onChange((color2) => setAppearance({ type: "unified", otherChanges, color: color2 }));
new import_obsidian12.Setting(containerEl).setName(`Color Scheme`).setDesc(`Change the color of the callout for the ${otherColorScheme} color scheme.`).addButton(
(btn) => btn.setClass("clickable-icon").setIcon("lucide-sun-moon").onClick(() => {
const currentColor = color != null ? color : callout.color;
setAppearance({
type: "per-scheme",
colorDark: currentColor,
colorLight: currentColor,
otherChanges
});
})
);
new CalloutIconSetting(containerEl, callout, plugin, () => this.nav).setName("Icon").setDesc("Change the callout icon.").setIcon(otherChanges.icon).onChange((icon) => setAppearance({ type: "unified", color, otherChanges: { ...otherChanges, icon } }));
}
};
// src/panes/edit-callout-pane/section-info.ts
function renderInfo(app2, callout, containerEl) {
const frag = document.createDocumentFragment();
const contentEl = frag.createDiv({ cls: "calloutmanager-edit-callout-section" });
contentEl.createEl("h2", { text: "About this Callout" });
contentEl.createEl("div", { cls: "calloutmanager-edit-callout-info" }, (el) => {
el.appendText("The ");
el.createSpan({ cls: "calloutmanager-edit-callout--callout-id", text: callout.id });
el.appendText(" callout");
el.appendText(" has ");
appendColorInfo(el, callout);
el.appendText(" and ");
appendIconInfo(el, callout);
if (callout.sources.length === 1) {
if (callout.sources[0].type === "builtin") {
el.appendText(". It is one of the built-in callouts.");
return;
}
el.appendText(". It was added to Obsidian by the ");
appendSourceInfo(app2, el, callout.sources[0]);
el.appendText(".");
return;
}
el.appendText(". The callout comes from:");
const sources = el.createEl("ul", { cls: "calloutmanager-edit-callout--callout-source-list" });
for (const source of callout.sources) {
const itemEl = sources.createEl("li");
itemEl.appendText("The ");
appendSourceInfo(app2, itemEl, source);
itemEl.appendText(".");
}
});
containerEl.appendChild(frag);
}
function appendIconInfo(el, callout) {
el.appendText("is using the icon ");
el.createEl("code", { cls: "calloutmanager-edit-callout--callout-icon", text: callout.icon });
}
function appendColorInfo(el, callout) {
const calloutColor = getColorFromCallout(callout);
if (calloutColor == null) {
el.appendText("an invalid color (");
el.createEl("code", {
cls: "calloutmanager-edit-callout--color-invalid",
text: callout.color.trim()
});
el.appendText(")");
return;
}
el.appendText("the color ");
el.createEl(
"code",
{ cls: "calloutmanager-edit-callout--callout-color", text: toHexRGB(calloutColor) },
(colorEl) => colorEl.style.setProperty("--resolved-callout-color", callout.color)
);
}
function appendSourceInfo(app2, el, source) {
var _a, _b;
switch (source.type) {
case "builtin":
el.appendText("built-in callouts");
return true;
case "custom":
el.appendText("custom callouts you created");
return true;
case "snippet":
el.appendText("CSS snippet ");
el.createEl("code", {
cls: "calloutmanager-edit-callout--callout-source",
text: `${source.snippet}.css`
});
return true;
case "theme": {
el.appendText("theme ");
const themeName = (_b = (_a = getThemeManifest(app2, source.theme)) == null ? void 0 : _a.name) != null ? _b : source.theme;
el.createSpan({ cls: "calloutmanager-edit-callout--callout-source", text: themeName });
return true;
}
}
}
// src/panes/edit-callout-pane/section-preview.ts
var import_obsidian13 = require("obsidian");
var EditCalloutPanePreview = class {
constructor(plugin, callout, viewOnly) {
this.previewMarkdown = "Lorem ipsum dolor sit amet.";
this.previewEditorEl = null;
this.calloutHasIconReady = false;
this.calloutId = callout.id;
this.plugin = plugin;
const frag = document.createDocumentFragment();
this.sectionEl = frag.createDiv({
cls: ["calloutmanager-preview-container", "calloutmanager-edit-callout-preview"]
});
this.preview = new IsolatedCalloutPreviewComponent(this.sectionEl, {
id: callout.id,
title: callout.id,
icon: callout.icon,
colorScheme: getCurrentThemeID2(plugin.app),
content: (containerEl) => {
containerEl.createEl("p", { text: this.previewMarkdown });
}
});
if (!viewOnly) {
this.makeEditable();
}
}
makeEditable() {
const contentEl = this.preview.contentEl;
this.previewEditorEl = null;
this.preview.calloutEl.addEventListener("click", () => {
if (this.previewEditorEl != null) {
return;
}
const height = contentEl.getBoundingClientRect().height;
contentEl.empty();
new import_obsidian13.TextAreaComponent(contentEl).setValue(this.previewMarkdown).setPlaceholder("Preview Markdown...").then((c) => {
const inputEl = this.previewEditorEl = c.inputEl;
inputEl.style.setProperty("height", `${height}px`);
inputEl.classList.add("calloutmanager-preview-editor");
inputEl.focus();
inputEl.addEventListener("blur", () => {
const value = c.getValue();
this.previewEditorEl = null;
this.previewMarkdown = value;
this.changeContent(value);
});
});
});
}
/**
* Refreshes the callout preview's icon.
* We need to do this after the preview is attached to DOM, as we can't get the correct icon until that happens.
*/
refreshPreviewIcon() {
var _a;
const { iconEl, calloutEl } = this.preview;
if (window.document.contains(this.sectionEl)) {
const icon = window.getComputedStyle(calloutEl).getPropertyValue("--callout-icon").trim();
const iconSvg = (_a = (0, import_obsidian13.getIcon)(icon)) != null ? _a : document.createElement("svg");
iconEl.empty();
iconEl.appendChild(iconSvg);
this.calloutHasIconReady = true;
}
}
/**
* Changes the preview that is displayed inside the callout.
*
* @param markdown The markdown to render.
*/
async changeContent(markdown) {
const contentEl = this.preview.contentEl;
contentEl.empty();
try {
await import_obsidian13.MarkdownRenderer.renderMarkdown(markdown, contentEl, "", void 0);
} catch (ex) {
contentEl.createEl("code").createEl("pre", { text: markdown });
}
}
/**
* Changes the settings for the callout.
* This can be used to show the customized callout.
*
* @param settings The settings to use.
*/
async changeSettings(settings) {
const { preview } = this;
const styles = calloutSettingsToCSS(this.calloutId, settings, currentCalloutEnvironment(this.plugin.app));
preview.customStyleEl.textContent = styles;
preview.resetStylePropertyOverrides();
preview.removeStyles((el) => el.getAttribute("data-callout-manager") === "style-overrides");
this.calloutHasIconReady = false;
preview.removeStyles((el) => el.getAttribute("data-inject-id") === "callout-settings");
}
/**
* Attaches the preview to a container.
* @param containerEl The container element.
*/
attach(containerEl) {
containerEl.appendChild(this.sectionEl);
if (!this.calloutHasIconReady) {
this.refreshPreviewIcon();
}
}
};
// src/panes/edit-callout-pane/index.ts
var IMPOSSIBLE_CALLOUT_ID = "[not a real callout]";
var EditCalloutPane = class extends UIPane {
constructor(plugin, id, viewOnly) {
var _a, _b;
super();
this.plugin = plugin;
this.viewOnly = viewOnly;
this.title = { title: "Callout", subtitle: id };
this.callout = (_a = plugin.callouts.get(id)) != null ? _a : {
sources: [{ type: "custom" }],
...plugin.calloutResolver.getCalloutProperties(IMPOSSIBLE_CALLOUT_ID),
id
};
this.previewSection = new EditCalloutPanePreview(plugin, this.callout, false);
this.appearanceEditorContainerEl = document.createElement("div");
this.appearanceEditorContainerEl.classList.add(
"calloutmanager-edit-callout-section",
"calloutmanager-edit-callout-appearance"
);
this.appearanceEditorContainerEl.createEl("h2", { text: "Appearance" });
this.appearanceEditorEl = this.appearanceEditorContainerEl.createDiv();
this.changeSettings((_b = plugin.getCalloutSettings(id)) != null ? _b : []);
}
changeAppearanceEditor(newAppearance) {
const oldAppearance = this.appearance;
this.appearance = newAppearance;
if (newAppearance.type !== (oldAppearance == null ? void 0 : oldAppearance.type)) {
this.appearanceEditor = new APPEARANCE_EDITORS[newAppearance.type]();
Object.defineProperties(this.appearanceEditor, {
nav: { get: () => this.nav },
plugin: { value: this.plugin },
containerEl: { value: this.appearanceEditorEl },
setAppearance: { value: this.onSetAppearance.bind(this) }
});
}
const { appearanceEditor } = this;
appearanceEditor.appearance = newAppearance;
appearanceEditor.callout = this.callout;
}
onSetAppearance(appearance) {
this.changeAppearanceEditor(appearance);
const newSettings = this.appearanceEditor.toSettings();
const { callout } = this;
const { calloutResolver } = this.plugin;
this.plugin.setCalloutSettings(callout.id, newSettings);
const { color, icon } = calloutResolver.getCalloutProperties(callout.id);
callout.color = color;
callout.icon = icon;
this.previewSection.changeSettings(newSettings);
this.appearanceEditor.callout = callout;
this.appearanceEditorEl.empty();
this.appearanceEditor.render();
this.containerEl.empty();
this.display();
}
/** @override */
display() {
const { containerEl, previewSection, appearanceEditorContainerEl } = this;
containerEl.empty();
previewSection.attach(containerEl);
renderInfo(this.plugin.app, this.callout, containerEl);
containerEl.appendChild(appearanceEditorContainerEl);
}
/** @override */
displayControls() {
const { callout, controlsEl } = this;
if (!this.viewOnly && callout.sources.length === 1 && callout.sources[0].type === "custom") {
new import_obsidian14.ButtonComponent(controlsEl).setIcon("lucide-trash").setTooltip("Delete Callout").onClick(() => {
this.plugin.removeCustomCallout(callout.id);
this.nav.close();
}).then(({ buttonEl }) => buttonEl.classList.add("clickable-icon", "mod-warning"));
}
}
/**
* Changes the preview that is displayed inside the callout.
*
* @param markdown The markdown to render.
*/
async changePreview(markdown) {
return this.previewSection.changeContent(markdown);
}
/**
* Changes the styles of the preview that is displayed inside the callout.
*
* @param markdown The markdown to render.
*/
async changeSettings(settings) {
this.changeAppearanceEditor(determineAppearanceType(settings));
this.appearanceEditorEl.empty();
this.appearanceEditor.render();
await this.previewSection.changeSettings(settings);
}
};
var APPEARANCE_EDITORS = {
complex: ComplexAppearanceEditor,
unified: UnifiedAppearanceEditor,
"per-scheme": PerSchemeAppearanceEditor
};
// src/panes/create-callout-pane.ts
var CreateCalloutPane = class extends UIPane {
constructor(plugin) {
super();
this.title = { title: "Callouts", subtitle: "New Callout" };
this.plugin = plugin;
const btnCreate = this.btnCreate = document.createElement("button");
btnCreate.textContent = "Create";
btnCreate.addEventListener("click", (evt) => {
if (!this.areInputsValid()) {
return;
}
const id = this.fieldIdEl.value;
this.plugin.createCustomCallout(id);
this.nav.replace(new EditCalloutPane(this.plugin, id, false));
});
this.fieldId = new import_obsidian15.Setting(document.createElement("div")).setHeading().setName("Callout Name").setDesc("This is how you will refer to your callout in Markdown.").addText((cmp) => {
cmp.setPlaceholder("my-awesome-callout").onChange(() => this.validateInputs()).then(({ inputEl }) => this.fieldIdEl = inputEl).then(({ inputEl }) => inputEl.setAttribute("pattern", "[a-z-]{1,}")).then(({ inputEl }) => inputEl.setAttribute("required", "required"));
});
this.validateInputs();
}
validateInputs() {
this.btnCreate.disabled = !this.areInputsValid();
}
areInputsValid() {
if (!this.fieldIdEl.validity.valid)
return false;
return true;
}
/** @override */
display() {
const { containerEl } = this;
containerEl.appendChild(this.fieldId.settingEl);
containerEl.createDiv().appendChild(this.btnCreate);
}
};
// src/panes/manage-callouts-pane.ts
var ManageCalloutsPane = class extends UIPane {
constructor(plugin) {
super();
this.title = { title: "Callouts", subtitle: "Manage" };
this.plugin = plugin;
this.searchQuery = "";
this.searchFilter = null;
this.previewCache = [];
this.viewOnly = false;
const { searchErrorDiv, searchErrorQuery } = createEmptySearchResultDiv();
this.searchErrorDiv = searchErrorDiv;
this.searchErrorQuery = searchErrorQuery;
}
/**
* Change the search query.
* @param query The search query.
*/
search(query) {
const split = query.split(":", 2);
let prefix = "id", search = query;
if (split.length === 2) {
prefix = split[0];
search = split[1].trim();
}
this.searchFilter = this.prepareSearch(search, prefix);
this.searchQuery = query;
this.searchErrorQuery.textContent = search;
this.display();
}
/**
* Prepares the search filter function.
*
* @param query The query.
* @param queryPrefix The query prefix (type of query).
*
* @returns The search filter function.
*/
prepareSearch(query, queryPrefix) {
if (queryPrefix === "id") {
const fuzzy = (0, import_obsidian16.prepareFuzzySearch)(query.toLowerCase());
return (callout) => fuzzy(callout.id);
}
if (queryPrefix === "icon") {
const fuzzy = (0, import_obsidian16.prepareFuzzySearch)(query.toLowerCase());
return (callout) => fuzzy(callout.icon.toLowerCase());
}
if (queryPrefix === "from") {
const queryLC = query.toLowerCase();
const queryIsBuiltin = queryLC === "obsidian" || queryLC === "builtin" || queryLC === "built-in";
const fuzzy = (0, import_obsidian16.prepareFuzzySearch)(queryLC);
const hasSnippetWithQueryAsId = this.plugin.callouts.snippets.keys().find((id) => id.toLowerCase() === queryLC) !== void 0;
return (callout) => {
let result = null;
if (query === "")
return { matches: [], score: 0 };
for (const source of callout.sources) {
if (hasSnippetWithQueryAsId) {
if (source.type === "snippet" && source.snippet.toLowerCase() === queryLC)
return { matches: [], score: -1 };
continue;
}
switch (source.type) {
case "builtin":
if (queryIsBuiltin)
return { matches: [], score: -1 };
break;
case "custom":
if (queryLC === "custom")
return { matches: [], score: -1 };
break;
case "theme":
if (queryLC === "theme")
return { matches: [], score: -1 };
break;
case "snippet": {
const snippetLC = source.snippet.toLowerCase();
if (snippetLC === queryLC)
return { matches: [], score: -1 };
const fuzzily = fuzzy(snippetLC);
if (fuzzily != null && (result == null || fuzzily.score < result.score)) {
result = fuzzily;
}
}
}
}
return result;
};
}
return () => null;
}
/**
* Filter and sort callout previews based on the fuzzy search query.
* If there is no search query, previews will be sorted based on color.
*
* @param previews The previews to filter and sort.
* @returns The filtered and sorted previews.
*/
filterAndSort(previews) {
const { searchFilter } = this;
if (searchFilter == null) {
return previews.sort(comparePreviewByColor);
}
const filterMapped = [];
for (const preview of previews) {
const result = searchFilter(preview);
if (result != null) {
filterMapped.push([preview, result]);
}
}
filterMapped.sort(([aPreview, aResults], [bPreview, bResults]) => {
const scoreDiff = bResults.score - aResults.score;
if (scoreDiff != 0)
return scoreDiff;
return comparePreviewByColor(aPreview, bPreview);
});
return filterMapped.map(([preview, _]) => preview);
}
/**
* Refresh the callout previews.
* This regenerates the previews and their metadata from the list of callouts known to the plugin.
*/
refreshPreviews() {
var _a;
const editButtonContent = (_a = this.viewOnly ? (0, import_obsidian16.getIcon)("lucide-view") : (0, import_obsidian16.getIcon)("lucide-edit")) != null ? _a : document.createTextNode("Edit Callout");
const editButtonHandler = (evt) => {
let id = null;
for (let target = evt.targetNode; target != null && id == null; target = target == null ? void 0 : target.parentElement) {
if (target instanceof Element) {
id = target.getAttribute("data-callout-manager-callout");
}
}
if (id != null) {
this.nav.open(new EditCalloutPane(this.plugin, id, this.viewOnly));
}
};
this.previewCache = [];
for (const callout of this.plugin.callouts.values()) {
const calloutContainerEl = document.createElement("div");
calloutContainerEl.classList.add("calloutmanager-preview-container");
calloutContainerEl.setAttribute("data-callout-manager-callout", callout.id);
this.previewCache.push(createPreview(callout, calloutContainerEl));
calloutContainerEl.classList.add("calloutmanager-preview-container-with-button");
const editButton = calloutContainerEl.createEl("button");
editButton.appendChild(editButtonContent.cloneNode(true));
editButton.addEventListener("click", editButtonHandler);
}
}
/** @override */
display() {
const { containerEl } = this;
const previews = this.filterAndSort(this.previewCache);
containerEl.empty();
for (const preview of previews) {
containerEl.appendChild(preview.calloutContainerEl);
}
if (previews.length === 0) {
containerEl.appendChild(this.searchErrorDiv);
}
}
/** @override */
displayControls() {
const { controlsEl } = this;
new import_obsidian16.TextComponent(controlsEl).setValue(this.searchQuery).setPlaceholder("Filter callouts...").onChange(this.search.bind(this));
if (!this.viewOnly) {
new import_obsidian16.ButtonComponent(controlsEl).setIcon("lucide-plus").setTooltip("New Callout").onClick(() => this.nav.open(new CreateCalloutPane(this.plugin))).then(({ buttonEl }) => buttonEl.classList.add("clickable-icon"));
}
}
/** @override */
restoreState(state) {
this.refreshPreviews();
}
/** @override */
onReady() {
this.refreshPreviews();
}
};
function createPreview(callout, calloutContainerEl) {
const { icon, id } = callout;
const color = getColorFromCallout(callout);
return {
sources: callout.sources,
icon,
id,
calloutContainerEl,
colorValid: color != null,
colorHue: color == null ? 0 : toHSV(color).h,
preview: new CalloutPreviewComponent(calloutContainerEl, {
id,
icon,
color: color != null ? color : void 0
})
};
}
function comparePreviewByColor(a, b) {
if (a.colorValid && !b.colorValid)
return -1;
if (b.colorValid && !a.colorValid)
return 1;
return a.colorHue - b.colorHue;
}
function createEmptySearchResultDiv() {
let searchErrorQuery;
const searchErrorDiv = document.createElement("div");
searchErrorDiv.className = "calloutmanager-centerbox";
const contentEl = searchErrorDiv.createDiv({ cls: "calloutmanager-search-error" });
contentEl.createEl("h2", { text: "No callouts found." });
contentEl.createEl("p", void 0, (el) => {
el.createSpan({ text: "Your search query " });
searchErrorQuery = el.createEl("code", { text: "" });
el.createSpan({ text: " did not return any results." });
});
contentEl.createDiv({ cls: "calloutmanager-search-error-suggestions" }, (el) => {
el.createDiv({ text: "Try searching:" });
el.createEl("ul", void 0, (el2) => {
el2.createEl("li", { text: "By name: " }, (el3) => {
el3.createEl("code", { text: "warning" });
});
el2.createEl("li", { text: "By icon: " }, (el3) => {
el3.createEl("code", { text: "icon:check" });
});
el2.createEl("li", { text: "Built-in callouts: " }, (el3) => {
el3.createEl("code", { text: "from:obsidian" });
});
el2.createEl("li", { text: "Theme callouts: " }, (el3) => {
el3.createEl("code", { text: "from:theme" });
});
el2.createEl("li", { text: "Snippet callouts: " }, (el3) => {
el3.createEl("code", { text: "from:my snippet" });
});
el2.createEl("li", { text: "Custom callouts: " }, (el3) => {
el3.createEl("code", { text: "from:custom" });
});
});
});
return { searchErrorDiv, searchErrorQuery };
}
// src/panes/manage-plugin-pane.ts
var import_obsidian17 = require("obsidian");
var ManagePluginPane = class extends UIPane {
constructor(plugin) {
super();
this.title = "Callout Manager Settings";
this.plugin = plugin;
}
/** @override */
display() {
const { containerEl, plugin } = this;
new import_obsidian17.Setting(containerEl).setName("Manage Callouts").setDesc("Create or edit Markdown callouts.").addButton((btn) => {
btn.setButtonText("Manage Callouts");
btn.onClick(() => this.nav.open(new ManageCalloutsPane(plugin)));
});
containerEl.createEl("h2", { text: "Callout Detection" });
new import_obsidian17.Setting(containerEl).setName("Obsidian").setDesc(
(() => {
const desc = document.createDocumentFragment();
const container = desc.createDiv();
container.createDiv({
text: plugin.settings.calloutDetection.obsidianFallbackForced ? "Include the built-in Obsidian callouts." : "Find built-in Obsidian callouts."
});
if (!plugin.cssWatcher.isObsidianStylesheetSupported() && !plugin.settings.calloutDetection.obsidianFallbackForced) {
container.createDiv({
cls: "mod-warning",
text: "Your current platform does not support automatic detection. A fallback list will be used."
});
}
return desc;
})()
).addToggle((setting) => {
setting.setValue(plugin.settings.calloutDetection.obsidian).onChange((v) => {
plugin.settings.calloutDetection.obsidian = v;
plugin.saveSettings();
plugin.refreshCalloutSources();
});
});
new import_obsidian17.Setting(containerEl).setName("Theme").setDesc("Find theme-provided callouts.").addToggle((setting) => {
setting.setValue(plugin.settings.calloutDetection.theme).onChange((v) => {
plugin.settings.calloutDetection.theme = v;
plugin.saveSettings();
plugin.refreshCalloutSources();
});
});
new import_obsidian17.Setting(containerEl).setName("Snippet").setDesc("Find callouts in custom CSS snippets.").addToggle((setting) => {
setting.setValue(plugin.settings.calloutDetection.snippet).onChange((v) => {
plugin.settings.calloutDetection.snippet = v;
plugin.saveSettings();
plugin.refreshCalloutSources();
});
});
containerEl.createEl("h2", { text: "Reset" });
new import_obsidian17.Setting(containerEl).setName("Reset Callout Settings").setDesc("Reset all the changes you made to callouts.").addButton(
withConfirm((btn) => {
btn.setButtonText("Reset").onClick(() => {
this.plugin.settings.callouts.settings = {};
this.plugin.saveSettings();
this.plugin.applyStyles();
btn.setButtonText("Reset").setDisabled(true);
});
})
);
new import_obsidian17.Setting(containerEl).setName("Reset Custom Callouts").setDesc("Removes all the custom callouts you created.").addButton(
withConfirm((btn) => {
btn.setButtonText("Reset").onClick(() => {
const { settings } = this.plugin;
for (const custom of settings.callouts.custom) {
delete settings.callouts.settings[custom];
}
settings.callouts.custom = [];
this.plugin.saveSettings();
this.plugin.callouts.custom.clear();
this.plugin.applyStyles();
this.plugin.refreshCalloutSources();
btn.setButtonText("Reset").setDisabled(true);
});
})
);
}
};
function withConfirm(callback) {
let onClickHandler = void 0;
let resetButtonClicked = false;
return (btn) => {
btn.setWarning().onClick(() => {
if (!resetButtonClicked) {
resetButtonClicked = true;
btn.setButtonText("Confirm");
return;
}
if (onClickHandler != void 0) {
onClickHandler();
}
});
btn.onClick = (handler) => {
onClickHandler = handler;
return btn;
};
callback(btn);
};
}
// src/settings.ts
function defaultSettings() {
return {
callouts: {
custom: [],
settings: {}
},
calloutDetection: {
obsidianFallbackForced: false,
obsidian: true,
theme: true,
snippet: true
}
};
}
function mergeSettings(into, from) {
var _a;
return Object.assign(into, {
...from,
calloutDetection: {
...into.calloutDetection,
...(_a = from == null ? void 0 : from.calloutDetection) != null ? _a : {}
}
});
}
// src/main.ts
var CalloutManagerPlugin = class extends import_obsidian18.Plugin {
/** @override */
async onload() {
this.apiHandles = /* @__PURE__ */ new Map();
await this.loadSettings();
await this.saveSettings();
const { settings } = this;
this.calloutResolver = new CalloutResolver();
this.register(() => this.calloutResolver.unload());
this.callouts = new CalloutCollection((id) => {
const { icon, color } = this.calloutResolver.getCalloutProperties(id);
console.debug("Resolved Callout:", id, { icon, color });
return {
id,
icon,
color
};
});
this.callouts.custom.add(...settings.callouts.custom);
this.cssApplier = createCustomStyleSheet(this.app, this);
this.cssApplier.setAttribute("data-callout-manager", "style-overrides");
this.register(this.cssApplier);
this.applyStyles();
this.cssWatcher = new StylesheetWatcher(this.app, settings.calloutDetection.obsidianFallbackForced);
this.cssWatcher.on("add", this.updateCalloutSource.bind(this));
this.cssWatcher.on("change", this.updateCalloutSource.bind(this));
this.cssWatcher.on("remove", this.removeCalloutSource.bind(this));
this.cssWatcher.on("checkComplete", () => this.maybeRefreshCalloutBuiltinsWithFallback());
this.cssWatcher.on("checkComplete", (anyChanges) => {
if (anyChanges) {
this.emitApiEventChange();
}
});
this.app.workspace.onLayoutReady(() => {
this.register(this.cssWatcher.watch());
});
this.registerEvent(
this.app.workspace.on("css-change", () => {
this.calloutResolver.reloadStyles();
this.applyStyles();
})
);
this.settingTab = new UISettingTab(this, () => new ManagePluginPane(this));
this.addSettingTab(this.settingTab);
this.addCommand({
id: "manage-callouts",
name: "Edit callouts",
callback: () => {
this.settingTab.openWithPane(new ManageCalloutsPane(this));
}
});
}
async loadSettings() {
this.settings = mergeSettings(defaultSettings(), await this.loadData());
}
async saveSettings() {
await this.saveData(this.settings);
}
/**
* Takes in a stylesheet from the watcher and updates the callout collection.
* @param ss The stylesheet.
*/
updateCalloutSource(ss) {
const callouts = getCalloutsFromCSS(ss.styles);
const { calloutDetection } = this.settings;
switch (ss.type) {
case "obsidian":
if (calloutDetection.obsidian === true && !calloutDetection.obsidianFallbackForced) {
this.callouts.builtin.set(callouts);
}
return;
case "theme":
if (calloutDetection.theme) {
this.callouts.theme.set(ss.theme, callouts);
}
return;
case "snippet":
if (calloutDetection.snippet) {
this.callouts.snippets.set(ss.snippet, callouts);
}
return;
}
}
/**
* Forces the callout sources to be refreshed.
* This is used to re-detect the sources when settings are changed.
*/
refreshCalloutSources() {
this.callouts.snippets.clear();
this.callouts.theme.delete();
this.callouts.builtin.set([]);
this.cssWatcher.checkForChanges(true).then(() => {
this.maybeRefreshCalloutBuiltinsWithFallback();
});
}
/**
* Create a custom callout and add it to Obsidian.
* @param id The custom callout ID.
*/
createCustomCallout(id) {
const { callouts, settings } = this;
callouts.custom.add(id);
settings.callouts.custom = callouts.custom.keys();
this.saveSettings();
this.emitApiEventChange(id);
}
/**
* Delete a custom callout.
* @param id The custom callout ID.
*/
removeCustomCallout(id) {
const { callouts, settings } = this;
callouts.custom.delete(id);
settings.callouts.custom = callouts.custom.keys();
this.saveSettings();
this.emitApiEventChange(id);
}
/**
* Gets the custom settings for a callout.
*
* @param id The callout ID.
* @returns The custom settings, or undefined if there are none.
*/
getCalloutSettings(id) {
const calloutSettings = this.settings.callouts.settings;
if (!Object.prototype.hasOwnProperty.call(calloutSettings, id)) {
return void 0;
}
return calloutSettings[id];
}
/**
* Sets the custom settings for a callout.
*
* @param id The callout ID.
* @param settings The callout settings.
*/
setCalloutSettings(id, settings) {
const calloutSettings = this.settings.callouts.settings;
if (settings === void 0) {
delete calloutSettings[id];
} else {
calloutSettings[id] = settings;
}
this.saveSettings();
this.applyStyles();
this.callouts.invalidate(id);
this.emitApiEventChange(id);
}
/**
* Generates the stylesheet for the user's custom callout settings and applies it to the page and the callout
* resolver's custom stylesheet.
*/
applyStyles() {
const env = currentCalloutEnvironment(this.app);
const css = [];
for (const [id, settings] of Object.entries(this.settings.callouts.settings)) {
css.push(calloutSettingsToCSS(id, settings, env));
}
const stylesheet = css.join("\n\n");
this.cssApplier.css = stylesheet;
this.calloutResolver.customStyleEl.textContent = stylesheet;
}
/**
* If the fallback list is forced or obsidian stylesheet detection is unsupported,
* add the embedded fallback list to the callout collection.
*/
maybeRefreshCalloutBuiltinsWithFallback() {
const { calloutDetection } = this.settings;
if (!calloutDetection.obsidian) {
return;
}
if (calloutDetection.obsidianFallbackForced || !this.cssWatcher.isObsidianStylesheetSupported()) {
this.callouts.builtin.set(callout_fallback_obsidian_default);
}
}
/**
* Takes in a stylesheet from the watcher and removes its callouts from the callout collection.
* @param ss The stylesheet.
*/
removeCalloutSource(ss) {
switch (ss.type) {
case "obsidian":
this.callouts.builtin.set([]);
return;
case "theme":
this.callouts.theme.delete();
return;
case "snippet":
this.callouts.snippets.delete(ss.snippet);
return;
}
}
/**
* Creates (or gets) an instance of the Callout Manager API for a plugin.
* If the plugin is undefined, only trivial functions are available.
*
* @param version The API version.
* @param consumerPlugin The plugin using the API.
*
* @internal
*/
newApiHandle(version, consumerPlugin, cleanupFunc) {
if (version !== "v1")
throw new Error(`Unsupported Callout Manager API: ${version}`);
if (consumerPlugin == null) {
return new CalloutManagerAPI_V1(this, void 0);
}
const existing = this.apiHandles.get(consumerPlugin);
if (existing != null) {
return existing;
}
consumerPlugin.register(cleanupFunc);
const handle = new CalloutManagerAPI_V1(this, consumerPlugin);
this.apiHandles.set(consumerPlugin, handle);
return handle;
}
destroyApiHandle(version, consumerPlugin) {
if (version !== "v1")
throw new Error(`Unsupported Callout Manager API: ${version}`);
const handle = this.apiHandles.get(consumerPlugin);
if (handle == null)
return;
handle.destroy();
this.apiHandles.delete(consumerPlugin);
}
emitApiEventChange(callout) {
for (const handle of this.apiHandles.values()) {
handle._emit("change");
}
}
};