/*
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)
// src/main.ts
var main_exports = {}
__export(main_exports, {
default: () => CalloutManagerPlugin,
})
module.exports = __toCommonJS(main_exports)
var import_obsidian25 = 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/fetchObsidianStyleSheet.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/fetchObsidianStyleSheet.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 fetchObsidianStyleSheet(app2) {
return __awaiter(this, void 0, void 0, function* () {
let errors = []
const orElse = (cb) => (ex) => {
errors.push(ex)
return cb()
}
const result = yield viaElectron("app.css")
.catch(orElse(() => viaFetch("app.css")))
.catch(orElse(() => viaDom("app.css")))
result._errors = errors
return result
})
}
function viaFetch(path) {
return __awaiter(this, void 0, void 0, function* () {
if (import_obsidian.Platform.isDesktopApp) {
throw new Error("Obsidian styles via fetch() does not work under Electron.")
}
return fetch(`/${path}`)
.then((r) => r.text())
.then((t) => ({
method: "fetch",
cssText: t,
}))
})
}
function viaElectron(path) {
var _a
return __awaiter(this, void 0, void 0, function* () {
if (versionCompare(import_obsidian.apiVersion, "1.1.16") > 0) {
throw new Error(
`Obsidian ${import_obsidian.apiVersion} has not been tested with this function`,
)
}
const require2 = globalThis.require
const electron =
(_a = globalThis.electron) !== null && _a !== void 0
? _a
: require2 === null || require2 === void 0
? void 0
: require2("electron")
if (electron == null) {
throw new Error("Unable to get electron module from web renderer process")
}
const fs = require2 === null || require2 === void 0 ? void 0 : require2("fs/promises")
if ((fs === null || fs === void 0 ? void 0 : fs.readFile) == null) {
throw new Error("Unable to get fs/promises module from web renderer process")
}
const resources = electron.ipcRenderer.sendSync("resources")
return fs.readFile(`${resources}/${path}`, "utf8").then((t) => ({
method: "electron",
cssText: t,
}))
})
}
function viaDom(path) {
let found = false
const lines = []
for (const styleSheet of Array.from(document.styleSheets)) {
if (!(styleSheet.ownerNode instanceof HTMLLinkElement)) continue
const href = styleSheet.ownerNode.getAttribute("href")
if (href !== path && href !== `/${path}`) continue
found = true
for (const rule of Array.from(styleSheet.cssRules)) {
lines.push(rule.cssText)
}
}
if (!found) {
throw new Error("Unable to find element for Obsidian's stylesheet")
}
return {
method: "dom",
cssText: lines.join("\n"),
}
}
// 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
}
// node_modules/obsidian-extra/dist/esm/functions/detectPlatformBrowser.js
var import_obsidian2 = require("obsidian")
// node_modules/obsidian-extra/dist/esm/functions/detectPlatformRuntime.js
var import_obsidian3 = require("obsidian")
// node_modules/obsidian-extra/dist/esm/functions/detectPlatformOperatingSystem.js
var import_obsidian4 = require("obsidian")
// src/ui/paned-setting-tab.ts
var import_obsidian6 = 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_obsidian5 = 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) {
if (this.activePane === void 0) {
this.closeParent()
return void 0
}
const noDisplay = options?.noDisplay ?? false
const oldPane = this.activePane
const newPane = this.layers.pop()
this.activePane = void 0
this.setPaneVariables(oldPane, false)
oldPane.onClose(options?.cancelled ?? 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 ?? {}),
}
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_obsidian5.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_obsidian6.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() {
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 = this.initLayer ?? this.createDefault()
this.initLayer = null
layers.top = initLayer
}
}
// src/api-common.ts
var emitter = Symbol("emitter")
var destroy = Symbol("destroy")
// src/api-v1.ts
var import_obsidian7 = 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 matches2 = REGEX_RGB.exec(rgb)
if (matches2 === null) return null
const components = matches2.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/callout-util.ts
function getColorFromCallout(callout) {
return parseColorRGB(`rgb(${callout.color})`)
}
function getTitleFromCallout(callout) {
const matches2 = /^(.)(.*)/u.exec(callout.id)
if (matches2 == null) return callout.id
const firstChar = matches2[1].toLocaleUpperCase()
const remainingChars = matches2[2].toLocaleLowerCase().replace(/-+/g, " ")
return `${firstChar}${remainingChars}`
}
// src/api-v1.ts
var CalloutManagerAPI_V1 = class {
constructor(plugin, consumer) {
this.plugin = plugin
this.consumer = consumer
this[emitter] = new import_obsidian7.Events()
if (consumer != null) {
console.debug("Created API V1 Handle:", { plugin: consumer.manifest.id })
}
}
/**
* Called to destroy an API handle bound to a consumer.
*/
[(emitter, 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 ?? { invalid: callout.color }
}
/** @override */
getTitle(callout) {
return getTitleFromCallout(callout)
}
/** @override */
on(event, listener) {
if (this.consumer == null) {
throw new Error("Cannot listen for events without an API consumer.")
}
this[emitter].on(event, listener)
}
/** @override */
off(event, listener) {
if (this.consumer == null) {
throw new Error("Cannot listen for events without an API consumer.")
}
this[emitter].off(event, listener)
}
}
// src/apis.ts
var CalloutManagerAPIs = class {
constructor(plugin) {
this.plugin = plugin
this.handles = /* @__PURE__ */ new Map()
}
/**
* 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
*/
async newHandle(version, consumerPlugin, cleanupFunc) {
if (version !== "v1") throw new Error(`Unsupported Callout Manager API: ${version}`)
if (consumerPlugin == null) {
return new CalloutManagerAPI_V1(this.plugin, void 0)
}
const existing = this.handles.get(consumerPlugin)
if (existing != null) {
return existing
}
consumerPlugin.register(cleanupFunc)
const handle = new CalloutManagerAPI_V1(this.plugin, consumerPlugin)
this.handles.set(consumerPlugin, handle)
return handle
}
/**
* Destroys an API handle created by {@link newHandle}.
*
* @param version The API version.
* @param consumerPlugin The plugin using the API.
*
* @internal
*/
destroyHandle(version, consumerPlugin) {
if (version !== "v1") throw new Error(`Unsupported Callout Manager API: ${version}`)
const handle = this.handles.get(consumerPlugin)
if (handle == null) return
handle[destroy]()
this.handles.delete(consumerPlugin)
}
emitEventForCalloutChange(id) {
for (const handle of this.handles.values()) {
handle[emitter].trigger("change")
}
}
}
// 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
}
/**
* Checks if a callout with this ID is in the collection.
* @param id The callout ID.
* @returns True if the callout is in the collection.
*/
has(id) {
if (!this.cached) this.buildCache()
return this.cacheById.has(id)
}
/**
* 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 ?? "" },
{
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 ?? "" },
{
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/ui/component/callout-preview.ts
var import_obsidian8 = require("obsidian")
var NO_ATTACH = Symbol()
var CalloutPreviewComponent = class extends import_obsidian8.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_obsidian8.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) {
super(NO_ATTACH, options)
const frag = document.createDocumentFragment()
const focused = options.focused ?? false
const colorScheme = options.colorScheme
const readingView = (options.viewType ?? "reading") === "reading"
const cssEls = options?.cssEls ?? getCurrentStyles(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 `