3180 lines
102 KiB
JavaScript
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");
|
|
}
|
|
}
|
|
};
|