From cea65450c4e8c4f1e02587f7a98fec97ae0ba4ce Mon Sep 17 00:00:00 2001 From: oskar Date: Sat, 13 Jun 2026 23:24:58 +0200 Subject: [PATCH] MacBook-Pro-de-Oscar.local 2026-6-13:23:24:58 --- .gitignore | 6 + .obsidian/community-plugins.json | 3 +- .obsidian/plugins/smart-connections/data.json | 4 + .obsidian/plugins/smart-connections/main.js | 30536 ++++++++++++++++ .../plugins/smart-connections/manifest.json | 10 + .../plugins/smart-connections/styles.css | 645 + 6 files changed, 31203 insertions(+), 1 deletion(-) create mode 100644 .obsidian/plugins/smart-connections/data.json create mode 100644 .obsidian/plugins/smart-connections/main.js create mode 100644 .obsidian/plugins/smart-connections/manifest.json create mode 100644 .obsidian/plugins/smart-connections/styles.css diff --git a/.gitignore b/.gitignore index db20abc0..afafe86e 100644 --- a/.gitignore +++ b/.gitignore @@ -14,3 +14,9 @@ idées cadeaux.md .obsidian/workspaces.json blog/ + + + + +# Ignore Smart Environment folder +.smart-env \ No newline at end of file diff --git a/.obsidian/community-plugins.json b/.obsidian/community-plugins.json index 675bd387..8c20c891 100644 --- a/.obsidian/community-plugins.json +++ b/.obsidian/community-plugins.json @@ -42,5 +42,6 @@ "break-page", "obsidian-list-callouts", "math-in-callout", - "zotlit" + "zotlit", + "smart-connections" ] \ No newline at end of file diff --git a/.obsidian/plugins/smart-connections/data.json b/.obsidian/plugins/smart-connections/data.json new file mode 100644 index 00000000..5906e7fc --- /dev/null +++ b/.obsidian/plugins/smart-connections/data.json @@ -0,0 +1,4 @@ +{ + "installed_at": 1781384970130, + "last_version": "4.5.3" +} \ No newline at end of file diff --git a/.obsidian/plugins/smart-connections/main.js b/.obsidian/plugins/smart-connections/main.js new file mode 100644 index 00000000..9d427232 --- /dev/null +++ b/.obsidian/plugins/smart-connections/main.js @@ -0,0 +1,30536 @@ +/*! smart-connections-obsidian v4.5.3 | (c) 2026 🌴 Brian (Brian Petro) */ +var __create = Object.create; +var __defProp = Object.defineProperty; +var __getOwnPropDesc = Object.getOwnPropertyDescriptor; +var __getOwnPropNames = Object.getOwnPropertyNames; +var __getProtoOf = Object.getPrototypeOf; +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 __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps( + // If the importer is in node compatibility mode or this is not an ESM + // file that has been converted to a CommonJS file using a Babel- + // compatible transform (i.e. "__esModule" has not been set), then set + // "default" to the CommonJS "module.exports" for node compatibility. + isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target, + mod +)); +var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod); + +// src/main.js +var main_exports = {}; +__export(main_exports, { + default: () => SmartConnectionsPlugin +}); +module.exports = __toCommonJS(main_exports); +var import_obsidian62 = __toESM(require("obsidian"), 1); + +// node_modules/obsidian-smart-env/smart_env.js +var import_obsidian50 = require("obsidian"); + +// node_modules/obsidian-smart-env/node_modules/smart-events/adapters/_adapter.js +var WILDCARD_KEY = "*"; +var SmartEventsAdapter = class { + constructor(instance) { + this.instance = instance; + this.handlers = /* @__PURE__ */ Object.create(null); + } + /** + * Register an event handler. + * When event_key is '*', the handler subscribes to all events. + * Handlers receive (event, event_key). + * @param {string} event_key + * @param {Function} event_callback + */ + on(event_key, event_callback) { + } + /** + * Register a one-time handler. + * When event_key is '*', the handler fires once on the next emitted event of any key. + * Handlers receive (event, event_key). + * @param {string} event_key + * @param {Function} event_callback + */ + once(event_key, event_callback) { + } + /** + * Remove an event handler. + * When event_key is '*', removes from the wildcard list only. + * @param {string} event_key + * @param {Function} event_callback + */ + off(event_key, event_callback) { + } + /** + * Emit an event. + * event_key must not be '*'. + * @param {string} event_key + * @param {Object} event + */ + emit(event_key, event) { + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-events/adapters/default.js +var DefaultEventsAdapter = class extends SmartEventsAdapter { + constructor(instance) { + super(instance); + this._next_id = 1; + } + on(event_key, event_callback = () => { + }) { + const key = event_key === WILDCARD_KEY ? WILDCARD_KEY : event_key; + const list = this.handlers[key] || (this.handlers[key] = []); + const entry = { id: this._next_id++, cb: event_callback }; + list.push(entry); + return () => this.off_entry(key, entry.id); + } + once(event_key, event_callback = () => { + }) { + const key = event_key === WILDCARD_KEY ? WILDCARD_KEY : event_key; + const list = this.handlers[key] || (this.handlers[key] = []); + const entry = { id: this._next_id++, cb: null }; + const wrapper = (event, emitted_key) => { + this.off_entry(key, entry.id); + event_callback(event, emitted_key); + }; + entry.cb = wrapper; + list.push(entry); + return () => this.off_entry(key, entry.id); + } + /** + * Public removal by function reference. + * Removes a single matching registration for the given function. + * Preference is to remove the most-recent registration (LIFO) when duplicates exist. + */ + off(event_key, event_callback) { + const list = this.handlers[event_key]; + if (!list || !event_callback) return; + for (let i = list.length - 1; i >= 0; i--) { + if (list[i].cb === event_callback) { + list.splice(i, 1); + break; + } + } + } + /** + * Internal precise removal by entry id. + * Used by unsubscribe closures returned from on/once. + */ + off_entry(event_key, entry_id) { + const list = this.handlers[event_key]; + if (!list) return; + const idx = list.findIndex((e) => e.id === entry_id); + if (idx !== -1) list.splice(idx, 1); + } + emit(event_key, event = {}) { + if (event_key === WILDCARD_KEY) { + throw new Error('emit("*") is not allowed; "*" is reserved for wildcard listeners.'); + } + const specific_list = this.handlers[event_key]; + const wildcard_list = this.handlers[WILDCARD_KEY]; + if (!specific_list && !wildcard_list) return; + const call_specific = specific_list ? [...specific_list] : []; + const call_wildcard = wildcard_list ? [...wildcard_list] : []; + for (let i = 0; i < call_specific.length; i++) { + call_specific[i].cb(event, event_key); + } + for (let i = 0; i < call_wildcard.length; i++) { + call_wildcard[i].cb(event, event_key); + } + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-events/smart_events.js +var SmartEvents = class _SmartEvents { + constructor(env, opts = {}) { + env.create_env_getter(this); + this.opts = opts; + } + static create(env, opts = {}) { + const smart_events = new _SmartEvents(env, opts); + if (!Object.getOwnPropertyDescriptor(env, "events")) { + Object.defineProperty(env, "events", { get: () => smart_events }); + } + return smart_events; + } + get adapter() { + if (!this._adapter) { + this._adapter = this.opts.adapter_class ? new this.opts.adapter_class(this) : new DefaultEventsAdapter(this); + } + return this._adapter; + } + on(event_key, event_callback = (event) => { + }) { + return this.adapter.on(event_key, event_callback); + } + once(event_key, event_callback = (event) => { + }) { + return this.adapter.once(event_key, event_callback); + } + off(event_key, event_callback = (event) => { + }) { + return this.adapter.off(event_key, event_callback); + } + /** + * Emit an event. + * @param {string} event_key + * @param {Record} [event] + * @returns {void} + */ + emit(event_key, event = {}) { + const payload = { ...event }; + if (payload.at === void 0) { + payload.at = Date.now(); + } + Object.freeze(payload); + return this.adapter.emit(event_key, payload); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-settings/smart_settings.js +var SmartSettings = class { + /** + * Creates an instance of SmartEnvSettings. + * @param {Object} main - The main object to contain the instance (smart_settings) and getter (settings) + * @param {Object} [opts={}] - Configuration options. + */ + constructor(main, opts = {}) { + this.main = main; + this.opts = opts; + this._fs = null; + this._settings = {}; + this._saved = false; + this.save_timeout = null; + this.save_delay_ms = typeof opts.save_delay_ms === "number" ? opts.save_delay_ms : 1e3; + } + static async create(main, opts = {}) { + const smart_settings = new this(main, opts); + await smart_settings.load(); + main.smart_settings = smart_settings; + Object.defineProperty(main, "settings", { + get() { + return smart_settings.settings; + }, + set(settings) { + smart_settings.settings = settings; + } + }); + return smart_settings; + } + static create_sync(main, opts = {}) { + const smart_settings = new this(main, opts); + smart_settings.load_sync(); + main.smart_settings = smart_settings; + Object.defineProperty(main, "settings", { + get() { + return smart_settings.settings; + }, + set(settings) { + smart_settings.settings = settings; + } + }); + return smart_settings; + } + /** + * Gets the current settings, wrapped with an observer to handle changes. + * @returns {Proxy} A proxy object that observes changes to the settings. + */ + get settings() { + return observe_object(this._settings, (change) => { + this.emit_settings_changed(change); + this.schedule_save(); + }); + } + /** + * Sets the current settings. + * @param {Object} settings - The new settings to apply. + */ + set settings(settings) { + this._settings = settings; + } + schedule_save() { + if (this.save_timeout) clearTimeout(this.save_timeout); + this.save_timeout = setTimeout(() => { + this.save(this._settings); + this.save_timeout = null; + }, this.save_delay_ms); + } + emit_settings_changed(change) { + const events_bus = this.resolve_events_bus(); + if (!events_bus?.emit) return; + events_bus.emit("settings:changed", build_settings_changed_event(change)); + } + resolve_events_bus() { + if (this.opts.events) return this.opts.events; + if (typeof this.opts.emit === "function") { + return { emit: this.opts.emit }; + } + if (this.main?.events) return this.main.events; + if (this.main?.env?.events) return this.main.env.events; + return null; + } + async save(settings = this._settings) { + if (typeof this.opts.save === "function") await this.opts.save(settings); + else await this.main.save_settings(settings); + } + async load() { + if (typeof this.opts.load === "function") this._settings = await this.opts.load(); + else this._settings = await this.main.load_settings(); + } + load_sync() { + if (typeof this.opts.load === "function") this._settings = this.opts.load(); + else this._settings = this.main.load_settings(); + } +}; +function observe_object(obj, on_change) { + const proxy_cache = /* @__PURE__ */ new WeakMap(); + const proxy_targets = /* @__PURE__ */ new WeakMap(); + const wrap_value = (value, path) => { + if (!is_observable(value)) return value; + if (proxy_targets.has(value)) return value; + if (proxy_cache.has(value)) return proxy_cache.get(value); + const proxy = create_proxy(value, path); + proxy_cache.set(value, proxy); + proxy_targets.set(proxy, value); + return proxy; + }; + const create_proxy = (target, path) => new Proxy(target, { + set(target2, property, value) { + const property_path = [...path, property]; + const previous_snapshot = snapshot_value(target2[property]); + const next_snapshot = snapshot_value(value); + target2[property] = wrap_value(value, property_path); + if (has_changed(previous_snapshot, next_snapshot)) { + on_change({ + type: "set", + path: property_path, + value: next_snapshot, + previous_value: previous_snapshot + }); + } + return true; + }, + get(target2, property) { + const result = target2[property]; + return wrap_value(result, [...path, property]); + }, + deleteProperty(target2, property) { + if (!Object.prototype.hasOwnProperty.call(target2, property)) { + return true; + } + const property_path = [...path, property]; + const previous_snapshot = snapshot_value(target2[property]); + delete target2[property]; + on_change({ + type: "delete", + path: property_path, + previous_value: previous_snapshot + }); + return true; + } + }); + return wrap_value(obj, []); +} +function build_settings_changed_event(change) { + const path = Array.isArray(change.path) ? change.path : []; + return { + type: change.type, + path, + path_string: path.join("."), + value: change.value, + previous_value: change.previous_value + }; +} +function snapshot_value(value) { + if (!is_observable(value)) { + return value; + } + if (typeof structuredClone === "function") { + try { + return structuredClone(value); + } catch (error) { + } + } + try { + return JSON.parse(JSON.stringify(value)); + } catch (error) { + return value; + } +} +function has_changed(previous_snapshot, next_snapshot) { + return serialize_value(previous_snapshot) !== serialize_value(next_snapshot); +} +function serialize_value(value) { + if (value === void 0) return "undefined"; + if (Number.isNaN(value)) return "number:NaN"; + if (value === Infinity) return "number:Infinity"; + if (value === -Infinity) return "number:-Infinity"; + if (!is_observable(value)) { + return `${typeof value}:${String(value)}`; + } + try { + return `object:${JSON.stringify(value)}`; + } catch (error) { + return `object:${String(value)}`; + } +} +function is_observable(value) { + return typeof value === "object" && value !== null; +} + +// node_modules/obsidian-smart-env/node_modules/smart-utils/deep_merge.js +function deep_merge(target = {}, source = {}) { + for (const key in source) { + if (!Object.prototype.hasOwnProperty.call(source, key)) continue; + if (is_plain_object(source[key]) && is_plain_object(target[key])) { + deep_merge(target[key], source[key]); + } else { + target[key] = source[key]; + } + } + return target; +} +function is_plain_object(o) { + return o && typeof o === "object" && !Array.isArray(o); +} + +// node_modules/obsidian-smart-env/node_modules/smart-utils/camel_case_to_snake_case.js +function camel_case_to_snake_case(str = "") { + return str.replace(/([A-Z])/g, (m) => `_${m.toLowerCase()}`).replace(/^_/, "").replace(/2$/, ""); +} + +// node_modules/obsidian-smart-env/node_modules/smart-environment/utils/normalize_opts.js +function normalize_opts(opts) { + if (!opts.collections) opts.collections = {}; + if (!opts.modules) opts.modules = {}; + if (!opts.items) opts.items = {}; + Object.entries(opts.collections).forEach(([key, val]) => { + if (typeof val === "function") { + opts.collections[key] = { class: val }; + } + const new_key = camel_case_to_snake_case(key); + if (new_key !== key) { + opts.collections[new_key] = opts.collections[key]; + delete opts.collections[key]; + } + if (!opts.collections[new_key].collection_key) opts.collections[new_key].collection_key = new_key; + if (val.item_type) { + const item_config_key = val.item_type.key || camel_case_to_snake_case(val.item_type.name); + opts.items[item_config_key] = { + class: val.item_type, + ...val.item_type.version ? { version: val.item_type.version } : {}, + // include version if defined on the class itself + // if already exists + ...opts.items[item_config_key] || {} + // preserve existing item config (e.g. actions) if already defined + }; + } + }); + Object.entries(opts.modules).forEach(([key, val]) => { + if (typeof val === "function") { + opts.modules[key] = { class: val }; + } + const new_key = camel_case_to_snake_case(key); + if (new_key !== key) { + opts.modules[new_key] = opts.modules[key]; + delete opts.modules[key]; + } + }); + if (!opts.items) opts.items = {}; + return opts; +} + +// node_modules/obsidian-smart-env/node_modules/smart-environment/utils/deep_clone_config.js +function is_plain_object2(value) { + if (!value || typeof value !== "object") return false; + const proto = Object.getPrototypeOf(value); + return proto === Object.prototype || proto === null; +} +function deep_clone_config(input) { + if (Array.isArray(input)) { + return input.map((item) => deep_clone_config(item)); + } + if (is_plain_object2(input)) { + const output = {}; + for (const [k, v] of Object.entries(input)) { + output[k] = deep_clone_config(v); + } + return output; + } + return input; +} + +// node_modules/obsidian-smart-env/node_modules/smart-environment/utils/compare_versions.js +function compare_versions(new_value, cur_value) { + const a = normalize_version_value(new_value); + const b = normalize_version_value(cur_value); + const len = Math.max(a.parts.length, b.parts.length); + for (let i = 0; i < len; i++) { + const av = a.parts[i] !== void 0 ? a.parts[i] : 0; + const bv = b.parts[i] !== void 0 ? b.parts[i] : 0; + if (av > bv) return 1; + if (av < bv) return -1; + } + if (a.type === b.type) return 0; + if (a.type === "semver" && b.type !== "semver") return 1; + if (b.type === "semver" && a.type !== "semver") return -1; + if (a.type === "number" && b.type === "none") { + return a.parts[0] === 0 ? 0 : 1; + } + if (b.type === "number" && a.type === "none") { + return b.parts[0] === 0 ? 0 : -1; + } + return 0; +} +function normalize_version_value(value) { + if (value === null || value === void 0) { + return { type: "none", parts: [0, 0, 0] }; + } + if (typeof value === "number") { + if (!Number.isFinite(value)) { + return { type: "none", parts: [0, 0, 0] }; + } + const major = Math.floor(value); + const minor = Math.floor((value - major) * 10); + return { type: "number", parts: [major, minor, 0] }; + } + if (typeof value === "string") { + const trimmed = value.trim(); + if (!trimmed) { + return { type: "none", parts: [0, 0, 0] }; + } + const raw_parts = trimmed.split("."); + const parts = raw_parts.map((part) => { + const match = part.match(/^\d+/); + if (!match) return 0; + const num = Number.parseInt(match[0], 10); + return Number.isNaN(num) ? 0 : num; + }); + while (parts.length < 3) { + parts.push(0); + } + return { + type: "semver", + parts + }; + } + return { type: "none", parts: [0, 0, 0] }; +} + +// node_modules/obsidian-smart-env/node_modules/smart-environment/utils/is_plain_object.js +function is_plain_object3(o) { + if (o === null) return false; + if (typeof o !== "object") return false; + if (Array.isArray(o)) return false; + if (o instanceof Function) return false; + if (o instanceof Date) return false; + return Object.getPrototypeOf(o) === Object.prototype; +} + +// node_modules/obsidian-smart-env/node_modules/smart-environment/utils/deep_merge_no_overwrite.js +function deep_merge_no_overwrite(target, source, path = []) { + if (!is_plain_object3(target) || !is_plain_object3(source)) { + return target; + } + if (path.includes(source)) { + return target; + } + path.push(source); + for (const key of Object.keys(source)) { + if (!Object.prototype.hasOwnProperty.call(source, key)) { + continue; + } + const val = source[key]; + if (Array.isArray(target[key]) && Array.isArray(val)) { + for (const item of val) { + if (typeof item === "function") { + const item_name = item.name; + const has_same_fn = target[key].some( + (el) => typeof el === "function" && el.name === item_name + ); + if (!has_same_fn) { + target[key].push(item); + } + } else if (item === null || ["string", "number", "boolean", "undefined"].includes(typeof item)) { + if (!target[key].includes(item)) { + target[key].push(item); + } + } else { + target[key].push(item); + } + } + } else if (is_plain_object3(val)) { + if (!is_plain_object3(target[key])) { + target[key] = {}; + } + deep_merge_no_overwrite(target[key], val, [...path]); + } else if (!Object.prototype.hasOwnProperty.call(target, key)) { + target[key] = val; + } + } + return target; +} + +// node_modules/obsidian-smart-env/node_modules/smart-environment/utils/merge_env_config.js +var CONFIG_RECORD_ENV_VERSION_KEY = "__smart_env_version"; +function merge_env_config(target, incoming) { + const CUR_VER = target.version; + const NEW_VER = incoming.version; + for (const [key, value] of Object.entries(incoming)) { + if (key === "version") { + if (!Object.prototype.hasOwnProperty.call(target, "version") || compare_versions(value, target.version) > 0) { + target.version = value; + } + continue; + } + if (key === "collections" && value && typeof value === "object") { + if (!target.collections) target.collections = {}; + for (const [col_key, col_def] of Object.entries(value)) { + const existing_def = target.collections[col_key]; + if (!existing_def) { + target.collections[col_key] = { ...col_def }; + set_config_record_env_version(target.collections[col_key], NEW_VER); + continue; + } + const new_version_raw = get_config_record_version(col_def); + const cur_version_raw = get_config_record_version(existing_def); + const cmp = compare_config_record_versions( + new_version_raw, + cur_version_raw, + NEW_VER, + get_config_record_env_version(existing_def, CUR_VER) + ); + if (cmp > 0) { + const replaced = { ...col_def }; + deep_merge_no_overwrite(replaced, existing_def); + set_config_record_env_version(replaced, NEW_VER); + target.collections[col_key] = replaced; + } else { + deep_merge_no_overwrite(existing_def, col_def); + } + } + continue; + } + if (["actions", "collections", "components", "modules", "items"].includes(key) && value && typeof value === "object") { + if (!target[key]) target[key] = {}; + for (const [comp_key, comp_def] of Object.entries(value)) { + if (!target[key][comp_key]) { + if (typeof comp_def === "function") { + const comp_version = get_config_record_version(comp_def); + target[key][comp_key] = { + class: comp_def, + ...comp_version !== void 0 ? { version: comp_version } : {} + }; + set_config_record_env_version(target[key][comp_key], NEW_VER); + continue; + } + target[key][comp_key] = { ...comp_def }; + set_config_record_env_version(target[key][comp_key], NEW_VER); + continue; + } + const target_comp = target[key][comp_key]; + const incoming_ver = get_config_record_version(comp_def); + const target_ver = get_config_record_version(target_comp); + const cmp = compare_config_record_versions( + incoming_ver, + target_ver, + NEW_VER, + get_config_record_env_version(target_comp, CUR_VER) + ); + if (cmp > 0) { + target[key][comp_key] = comp_def; + set_config_record_version(target[key][comp_key], incoming_ver); + set_config_record_env_version(target[key][comp_key], NEW_VER); + } else { + deep_merge_no_overwrite(target_comp, comp_def); + } + } + continue; + } + if (Array.isArray(value)) { + if (Array.isArray(target[key])) { + if (value.length > 0 && (typeof value[0] === "string" || typeof value[0] === "number" || typeof value[0] === "boolean")) { + target[key] = Array.from(/* @__PURE__ */ new Set([...target[key], ...value])); + } else { + target[key] = [...target[key], ...value]; + } + } else { + if (value.length > 0 && (typeof value[0] === "string" || typeof value[0] === "number" || typeof value[0] === "boolean")) { + target[key] = Array.from(new Set(value)); + } else { + target[key] = [...value]; + } + } + } else if (value && typeof value === "object") { + if (!target[key]) target[key] = {}; + deep_merge_no_overwrite(target[key], value); + } else { + target[key] = value; + } + } + return target; +} +function get_config_record_version(record) { + if (!record) return void 0; + if (record.version !== void 0) return record.version; + return record?.class?.version; +} +function get_config_record_env_version(record, fallback_version) { + return record?.[CONFIG_RECORD_ENV_VERSION_KEY] ?? fallback_version; +} +function compare_config_record_versions(new_record_version, cur_record_version, new_env_version, cur_env_version) { + const record_cmp = compare_versions(new_record_version, cur_record_version); + if (record_cmp !== 0) return record_cmp; + return compare_versions(new_env_version, cur_env_version); +} +function set_config_record_version(record, version) { + if (version === void 0) return; + if (!record || typeof record !== "object" && typeof record !== "function") return; + record.version = version; +} +function set_config_record_env_version(record, version) { + if (version === void 0) return; + if (!record || typeof record !== "object" && typeof record !== "function") return; + Object.defineProperty(record, CONFIG_RECORD_ENV_VERSION_KEY, { + value: version, + enumerable: false, + configurable: true, + writable: true + }); +} + +// node_modules/obsidian-smart-env/node_modules/smart-environment/package.json +var package_default = { + name: "smart-environment", + author: "Brian Joseph Petro (\u{1F334} Brian)", + license: "MIT", + version: "2.4.2", + type: "module", + description: "Implements Smart Environment best practices.", + main: "index.js", + repository: { + type: "git", + url: "brianpetro/jsbrains" + }, + bugs: { + url: "https://github.com/brianpetro/jsbrains/issues" + }, + scripts: { + test: "npx ava --verbose" + }, + homepage: "https://jsbrains.org", + dependencies: { + "smart-blocks": "*", + "smart-collections": "*", + "smart-embed-model": "*", + "smart-entities": "*", + "smart-events": "*", + "smart-file-system": "*", + "smart-http-request": "*", + "smart-model": "*", + "smart-notices": "*", + "smart-settings": "*", + "smart-sources": "*", + "smart-utils": "*", + "smart-view": "*" + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-environment/smart_env.js +var ROOT_SCOPE = typeof globalThis !== "undefined" ? globalThis : Function("return this")(); +var SmartEnv = class { + static version = package_default.version; + scope_name = "smart_env"; + static global_ref = ROOT_SCOPE; + global_ref = this.constructor.global_ref; + constructor(opts = {}) { + this.state = "init"; + this._components = {}; + this.collections = {}; + this.load_timeout = null; + this._load_promise = null; + this._collections_version_signature = null; + this._events = SmartEvents.create(this, build_events_opts(this.config?.modules?.smart_events)); + if (opts.primary_main_key) this.primary_main_key = opts.primary_main_key; + } + /** + * Builds or returns the cached configuration object. + * The cache is invalidated automatically whenever the "version signature" + * of any collection class changes (controlled by its static `version`). + * + * @returns {Object} the merged, up-to-date environment config + */ + get config() { + const signature = this.compute_collections_version_signature(); + if (this._config && signature === this._collections_version_signature) { + return this._config; + } + this._collections_version_signature = signature; + this._config = {}; + const sorted_configs = Object.entries(this.smart_env_configs).sort(([a_key], [b_key]) => { + if (!this.primary_main_key) return 0; + if (a_key === this.primary_main_key) return -1; + if (b_key === this.primary_main_key) return 1; + return 0; + }); + for (const [key, rec] of sorted_configs) { + if (!rec?.main) { + console.warn(`SmartEnv: '${key}' unloaded, skipping`); + delete this.smart_env_configs[key]; + continue; + } + if (!rec?.opts) { + console.warn(`SmartEnv: '${key}' opts missing, skipping`); + continue; + } + merge_env_config( + this._config, + deep_clone_config(normalize_opts(rec.opts)) + ); + } + return this._config; + } + /** + * Produces a deterministic string representing the current versions of every + * collection class across all mains. When any collection ships a higher + * `static version`, the signature changes - automatically invalidating the + * cached `config`. + * + * @returns {string} pipe-delimited version signature + */ + compute_collections_version_signature() { + const list = []; + for (const rec of Object.values(this.smart_env_configs)) { + const { opts } = rec || {}; + if (!opts) continue; + for (const [collection_key, def] of Object.entries(opts.collections || {})) { + const cls = def?.class; + const v = typeof cls?.version === "number" ? cls.version : 0; + list.push(`${collection_key}:${v}`); + } + } + return list.sort().join("|"); + } + // ======================================================================== + // -- GLOBAL HELPERS / STATIC API -- + // ======================================================================== + get env_start_wait_time() { + if (typeof this.config.env_start_wait_time === "number") return this.config.env_start_wait_time; + return 5e3; + } + static get global_env() { + return this.global_ref.smart_env; + } + static set global_env(env) { + this.global_ref.smart_env = env; + } + static get mains() { + return Object.keys(this.global_ref.smart_env_configs || {}); + } + get mains() { + return Object.keys(this.global_ref.smart_env_configs || {}); + } + static get should_reload() { + if (!this.global_env) return true; + if (this.global_env.state === "loaded" || is_global_env_locked(this.global_ref)) return false; + if (typeof this.global_env?.constructor?.version === "undefined") return true; + if (compare_versions(this.version, this.global_env.constructor?.version) > 0) { + console.warn( + "SmartEnv: Reloading environment because of version mismatch", + `${this.version} > ${this.global_env.constructor.version}` + ); + return true; + } + return false; + } + static get smart_env_configs() { + if (!this.global_ref.smart_env_configs) this.global_ref.smart_env_configs = {}; + return this.global_ref.smart_env_configs; + } + get smart_env_configs() { + if (!this.global_ref.smart_env_configs) this.global_ref.smart_env_configs = {}; + return this.global_ref.smart_env_configs; + } + /** + * Serializes all collection data in the environment into a plain object. + * @returns {object} + */ + to_json() { + return Object.fromEntries( + Object.entries(this).filter(([, val]) => typeof val?.collection_key !== "undefined").map(([key, collection]) => [key, collection_to_plain(collection)]) + ); + } + /** + * Waits for either a specific main to be registered in the environment, + * or (if `opts.main` is not specified) waits for environment collections to load. + * @param {object} opts + * @param {object} [opts.main] - if set, the function waits until that main is found. + * @returns {Promise} Resolves with the environment instance + */ + static wait_for(opts = {}) { + return new Promise((resolve) => { + if (opts.main) { + const interval = setInterval(() => { + if (this.global_env && this.global_env[opts.main]) { + clearInterval(interval); + resolve(this.global_env); + } + }, 1e3); + } else { + const interval = setInterval(() => { + if (this.global_env && this.global_env.state === "loaded") { + clearInterval(interval); + resolve(this.global_env); + } + }, 100); + } + }); + } + /** + * Creates or updates a SmartEnv instance. + * - If a global environment exists and is an older version or lacks 'init_main', it is replaced. + * @param {Object} main - The main object to be added to the SmartEnv instance. + * @param {Object} [env_config] - Options for configuring the SmartEnv instance. + * @returns {SmartEnv} The SmartEnv instance. + * @throws {TypeError} If an invalid main object is provided. + * @throws {Error} If there's an error creating or updating the SmartEnv instance. + */ + static async create(main, env_config) { + if (!main || typeof main !== "object") { + throw new TypeError("SmartEnv: Invalid main object provided"); + } + if (!env_config) throw new Error("SmartEnv.create: 'env_config' parameter is required."); + env_config.version = this.version; + const existing_env = this.global_env; + this.add_main(main, env_config); + if (existing_env && (existing_env.state === "loaded" || is_global_env_locked(this.global_ref))) { + return existing_env; + } + if (this.should_reload) { + const opts = {}; + const reload_env = this.global_env; + if (reload_env && reload_env.state !== "loaded" && !is_global_env_locked(this.global_ref) && compare_versions(this.version, reload_env.constructor?.version || 0) > 0) { + reload_env.state = "superceded"; + opts.primary_main_key = camel_case_to_snake_case(main.constructor.name); + } + if (this.global_env?.load_timeout) clearTimeout(this.global_env.load_timeout); + this.global_env = new this(opts); + const g = this.global_ref; + if (!g.all_envs) g.all_envs = []; + g.all_envs.push(this.global_env); + } + clearTimeout(this.global_env.load_timeout); + this.global_env.load_timeout = setTimeout(async () => { + await this.global_env.load(); + this.global_env.load_timeout = null; + }, this.global_env.env_start_wait_time); + return this.global_env; + } + static add_main(main, env_config = null) { + if (this.global_env) { + this.global_env._config = null; + this.global_env._collections_version_signature = null; + } + const main_key = camel_case_to_snake_case(main.constructor.name); + this.smart_env_configs[main_key] = { main, opts: env_config }; + this.create_env_getter(main); + } + /** + * Creates a dynamic environment getter on any instance object. + * The returned 'env' property will yield the global `smart_env`. + * @param {Object} instance_to_receive_getter + */ + static create_env_getter(instance_to_receive_getter) { + Object.defineProperty(instance_to_receive_getter, "env", { + configurable: true, + get: () => this.global_env + }); + } + create_env_getter(instance_to_receive_getter) { + this.constructor.create_env_getter(instance_to_receive_getter); + } + async load() { + if (this._load_promise) return this._load_promise; + if (this.state === "superceded") { + throw new Error("This environment instance has been superceded by a newer version and cannot be loaded."); + } + this.state = "loading"; + this._load_promise = this.run_load(); + try { + await this._load_promise; + if (this.state === "superceded") return this; + await this.after_load(); + if (this.state === "superceded") return this; + this.state = "loaded"; + const _instance = this; + Object.defineProperty(this.global_ref, "smart_env", { + get() { + return _instance; + }, + set(incoming_env) { + _instance.handle_env_load_attempt_after_loaded(incoming_env); + }, + configurable: false + }); + return this; + } catch (e) { + if (this.state === "superceded") { + console.warn("SmartEnv load aborted because this environment was superceded by a newer version."); + return this; + } + console.error("Error loading SmartEnv:", e); + this.state = "load_error"; + } finally { + } + } + /** + * Handle load attempts after load + */ + handle_env_load_attempt_after_loaded(incoming_env) { + console.warn("Received attempt to load another SmartEnv after one has already loaded", incoming_env); + console.warn(new Error("Stacktrace of SmartEnv load attempt after environment is already loaded")); + } + async run_load() { + await this.fs.load_files(); + if (!this.settings) await SmartSettings.create(this); + if (this.config.default_settings) { + deep_merge_no_overwrite(this.settings, this.config.default_settings); + } + this.smart_settings.save(); + await this.init_collections(); + for (const [main_key, { main }] of Object.entries(this.smart_env_configs)) { + this[main_key] = main; + } + await this.ready_to_load_collections(); + await this.load_collections(); + return this; + } + /** + * Must call before calling `load()`. If it returns false, the load process is aborted. + * @returns {Promise} + */ + async before_load() { + return true; + } + /** + * Called automatically after `load()` completes. Override to perform any actions that require a fully loaded environment (e.g. registering source watchers, workspace events, etc). + * Completes before state is set to 'loaded', so any async operations here will delay the environment becoming available. + * + * @returns {Promise} + */ + async after_load() { + } + /** + * Initializes collection classes if they have an 'init' function. + * @param {Object} [config=this.config] + */ + async init_collections(config = this.config) { + for (const key of Object.keys(config.collections || {})) { + const collection_config = config.collections[key] || {}; + const _class = collection_config.class; + if (_class?.default_settings) { + deep_merge_no_overwrite( + this.settings, + { + [key]: _class.default_settings + } + ); + } + if (!_class || typeof _class.init !== "function") continue; + const existing_collection = this[key]; + if (existing_collection) { + const should_replace_collection = existing_collection.constructor !== _class || get_version_number(_class) > get_version_number(existing_collection.constructor); + if (should_replace_collection) { + existing_collection.unload?.(); + this[key] = null; + this.collections[key] = null; + } else if (this.collections[key]) { + continue; + } + } + await _class.init(this, { ...collection_config }); + this.collections[key] = "init"; + } + } + /** + * Hook/Override this method to wait for any conditions before loading collections. + * @param {Object} main + */ + async ready_to_load_collections() { + } + /** + * Loads any available collections, processing their load queues. + * @param {Object} [collections=this.collections] - Key-value map of collection instances. + */ + async load_collections(collections = this.collections) { + const collection_keys = Object.keys(collections || {}).filter((key) => collections[key] !== "loaded").sort((a, b) => { + const order_a = this.config.collections?.[a]?.load_order || 0; + const order_b = this.config.collections?.[b]?.load_order || 0; + return order_a - order_b; + }); + for (const key of collection_keys) { + const time_start = Date.now(); + if (typeof this[key]?.process_load_queue === "function") { + await this[key].process_load_queue(); + this[key].load_time_ms = Date.now() - time_start; + this.collections[key] = "loaded"; + console.log(`Loaded ${this[key].collection_key} in ${this[key].load_time_ms}ms`); + } + } + } + /** + * Removes a main from the global.smart_env_configs to exclude it on reload + * @param {Class} main + * @param {Object|null} [unload_config=null] + */ + static unload_main(main) { + const main_key = camel_case_to_snake_case(main.constructor.name); + this.smart_env_configs[main_key] = null; + delete this.smart_env_configs[main_key]; + } + unload_main(main) { + this.constructor.unload_main(main); + } + /** + * Triggers a save event in all known collections. + */ + save() { + for (const key of Object.keys(this.collections)) { + this[key].process_save_queue?.(); + } + } + /** + * Initialize a module from the configured `this.opts.modules`. + * @param {string} module_key + * @param {object} opts + * @deprecated smart_env_config.modules is deprecated (2026-04-12) + * @returns {object|null} instance of the requested module or null if not found + */ + init_module(module_key, opts = {}) { + const module_config = this.opts.modules[module_key]; + if (!module_config) { + return console.warn(`SmartEnv: module ${module_key} not found`); + } + opts = { + ...{ ...module_config, class: null }, + ...opts + }; + return new module_config.class(opts); + } + /** + * @deprecated 2026-03-31 + */ + get notices() { + if (!this._notices) { + const SmartNoticesClass = this.config.modules.smart_notices.class; + this._notices = new SmartNoticesClass(this, { + adapter: this.config.modules.smart_notices.adapter + }); + } + return this._notices; + } + /** + * Renders a named component using an optional scope and options. + * @deprecated use env.smart_components.render instead (2025-10-11) + * @param {string} component_key + * @param {Object} scope + * @param {Object} [opts] + * @returns {Promise} + */ + async render_component(component_key, scope, opts = {}) { + return this.smart_components.render_component(component_key, scope, opts); + } + /** + * A built-in settings schema for this environment. + * @abstract + * @returns {Object} + */ + get settings_config() { + return {}; + } + get global_prop() { + return this.opts.global_prop ?? "smart_env"; + } + get fs_module_config() { + return this.opts.modules.smart_fs; + } + get fs() { + if (!this.smart_fs) { + this.smart_fs = new this.fs_module_config.class(this, { + adapter: this.fs_module_config.adapter, + fs_path: this.opts.env_path || "" + }); + } + return this.smart_fs; + } + get env_data_dir() { + const env_settings_files = this.fs.file_paths?.filter((path) => path.endsWith("smart_env.json")) || []; + let env_data_dir = ".smart-env"; + if (env_settings_files.length > 0) { + if (env_settings_files.length > 1) { + const env_data_dir_counts = env_settings_files.map((path) => { + const dir = path.split("/").slice(-2, -1)[0]; + return { + dir, + count: this.fs.file_paths.filter((p) => p.includes(dir)).length + }; + }); + env_data_dir = env_data_dir_counts.reduce( + (max, dir_obj) => dir_obj.count > max.count ? dir_obj : max, + env_data_dir_counts[0] + ).dir; + } else { + env_data_dir = env_settings_files[0].split("/").slice(-2, -1)[0]; + } + } + return env_data_dir; + } + get data_fs() { + if (!this._fs) { + this._fs = new this.fs_module_config.class(this, { + adapter: this.fs_module_config.adapter, + fs_path: this.data_fs_path + }); + } + return this._fs; + } + get data_fs_path() { + if (!this._data_fs_path) { + this._data_fs_path = (this.opts.env_path + (this.opts.env_path ? this.opts.env_path.includes("\\") ? "\\" : "/" : "") + this.env_data_dir).replace(/\\\\/g, "\\").replace(/\/\//g, "/"); + } + return this._data_fs_path; + } + /** + * Saves the current settings to the file system. + * @param {Object|null} [settings=null] - Optional settings to override the current settings before saving. + * @returns {Promise} + */ + async save_settings(settings) { + this._saved = false; + if (!await this.data_fs.exists("")) { + await this.data_fs.mkdir(""); + } + await this.data_fs.write("smart_env.json", JSON.stringify(settings, null, 2)); + this._saved = true; + } + /** + * Loads settings from the file system, merging with any `default_settings` + * @returns {Promise} the loaded settings + */ + async load_settings() { + if (!await this.data_fs.exists("smart_env.json")) await this.save_settings({}); + let settings = JSON.parse(JSON.stringify(this.config.default_settings || {})); + deep_merge(settings, JSON.parse(await this.data_fs.read("smart_env.json"))); + this._saved = true; + if (this.fs.auto_excluded_files) { + const existing_file_exclusions = settings.smart_sources.file_exclusions.split(",").map((s) => s.trim()).filter(Boolean); + settings.smart_sources.file_exclusions = [...existing_file_exclusions, ...this.fs.auto_excluded_files].filter((value, index, self) => self.indexOf(value) === index).join(","); + } + return settings; + } + /** + * Refreshes file-system state if exclusions changed, + * then re-renders relevant settings UI + */ + async update_exclusions() { + this.smart_sources._fs = null; + await this.smart_sources.init_fs(); + } + // DEPRECATED + /** + * Lazily instantiate the module 'smart_view'. + * @deprecated use env.smart_components instead (2025-09-30) + * @returns {object} + */ + get smart_view() { + if (!this._smart_view) { + this._smart_view = this.init_module("smart_view"); + } + return this._smart_view; + } + /** @deprecated access `this.state` and `collection.state` directly instead */ + get collections_loaded() { + return this.state === "loaded"; + } + /** @deprecated Use this['main_class_name'] instead of this.main/this.plugin */ + get main() { + return this.smart_env_configs[this.mains[0]]?.main; + } + /** + * @deprecated use component pattern instead + */ + get ejs() { + return this.opts.ejs; + } + /** + * @deprecated use component pattern instead + */ + get templates() { + return this.opts.templates; + } + /** + * @deprecated use component pattern instead + */ + get views() { + return this.opts.views; + } + /** + * @deprecated use this.config instead + */ + get opts() { + return this.config; + } + /** + * @deprecated Use this.main_class_name instead of this.plugin + */ + get plugin() { + return this.main; + } +}; +function collection_to_plain(collection) { + return { + items: Object.fromEntries( + Object.entries(collection.items || {}).map(([key, item]) => [key, item.data]) + ) + }; +} +function build_events_opts(module_config) { + if (!module_config) return {}; + if (typeof module_config === "function") { + return { adapter_class: module_config }; + } + const adapter_class = module_config.adapter_class || module_config.adapter; + return adapter_class ? { adapter_class } : {}; +} +function get_version_number(subject) { + return typeof subject?.version === "number" ? subject.version : 0; +} +function is_global_env_locked(global_ref) { + return Object.getOwnPropertyDescriptor(global_ref, "smart_env")?.configurable === false; +} + +// node_modules/obsidian-smart-env/node_modules/smart-file-system/utils/glob_to_regex.js +function create_regex(pattern, { case_sensitive, extended_glob, windows_paths }) { + const regex_pattern = glob_to_regex_pattern(pattern, extended_glob); + const adjusted_pattern = adjust_for_windows_paths(regex_pattern, windows_paths); + const flags = case_sensitive ? "" : "i"; + return new RegExp(`^${adjusted_pattern}$`, flags); +} +function adjust_for_windows_paths(pattern, windows_paths) { + return windows_paths ? pattern.replace(/\\\//g, "[\\\\/]").replace(/\\\\\\/g, "[\\\\/]") : pattern; +} +function glob_to_regex_pattern(pattern, extended_glob) { + let in_class = false; + let in_brace = 0; + let result = ""; + for (let i = 0; i < pattern.length; i++) { + const char = pattern[i]; + switch (char) { + case "\\": + if (i + 1 < pattern.length) { + result += `\\${pattern[i + 1]}`; + i++; + } else { + result += "\\\\"; + } + break; + case "/": + result += "\\/"; + break; + case "[": + if (!in_class) { + const closingIndex = pattern.indexOf("]", i + 1); + if (closingIndex === -1) { + result += "\\["; + } else { + in_class = true; + if (pattern[i + 1] === "!") { + result += "[^"; + i++; + } else { + result += "["; + } + } + } else { + result += "\\["; + } + break; + case "]": + if (in_class) { + in_class = false; + result += "]"; + } else { + result += "\\]"; + } + break; + case "{": + if (!in_class) { + const closingIndex = pattern.indexOf("}", i + 1); + if (closingIndex === -1) { + result += "\\{"; + } else { + in_brace++; + result += "("; + } + } else { + result += "\\{"; + } + break; + case "}": + if (!in_class && in_brace > 0) { + in_brace--; + result += ")"; + } else { + result += "\\}"; + } + break; + case ",": + if (!in_class && in_brace > 0) { + result += "|"; + } else { + result += ","; + } + break; + case "*": + if (!in_class) { + if (i + 1 < pattern.length && pattern[i + 1] === "*") { + result += ".*"; + i++; + } else { + result += "[^/]*"; + } + } else { + result += "\\*"; + } + break; + case "?": + if (!in_class) { + result += "[^/]"; + } else { + result += "\\?"; + } + break; + // We escape these to ensure they remain literal + case "(": + case ")": + case "+": + case "|": + case "^": + case "$": + case ".": + result += `\\${char}`; + break; + default: + result += char; + break; + } + } + if (in_class) { + result += "]"; + in_class = false; + } + if (extended_glob) { + result = result.replace(/\\\+\\\((.*?)\\\)/g, "($1)+").replace(/\\\@\\\((.*?)\\\)/g, "($1)").replace(/\\\!\\\((.*?)\\\)/g, "(?!$1).*").replace(/\\\?\\\((.*?)\\\)/g, "($1)?").replace(/\\\*\\\((.*?)\\\)/g, "($1)*"); + } + return result; +} +function glob_to_regex(pattern, options = {}) { + const default_options = { + case_sensitive: true, + extended_glob: false, + windows_paths: false + }; + const merged_options = { ...default_options, ...options }; + if (pattern === "") { + return /^$/; + } + if (pattern === "*" && !merged_options.windows_paths) { + return /^[^/]+$/; + } + if (pattern === "**" && !merged_options.windows_paths) { + return /^.+$/; + } + return create_regex(pattern, merged_options); +} + +// node_modules/obsidian-smart-env/node_modules/smart-file-system/utils/fuzzy_search.js +function fuzzy_search(arr, search_term) { + let matches = []; + for (let i = 0; i < arr.length; i++) { + const search_chars = search_term.toLowerCase().split(""); + let match = true; + let distance = 0; + const name = arr[i]; + const label_name = name.toLowerCase(); + for (let j = 0; j < search_chars.length; j++) { + const search_index = label_name.substring(distance).indexOf(search_chars[j]); + if (search_index >= 0) { + distance += search_index + 1; + } else { + match = false; + break; + } + } + if (match) matches.push({ name, distance }); + } + matches.sort((a, b) => a.distance - b.distance); + return matches.map((match) => match.name); +} + +// node_modules/obsidian-smart-env/node_modules/smart-file-system/smart_fs.js +var SmartFs = class { + /** + * Create a new SmartFs instance + * + * @param {Object} env - The Smart Environment instance + * @param {Object} [opts={}] - Optional configuration + * @param {string} [opts.fs_path] - Custom environment path + */ + constructor(env, opts = {}) { + this.env = env; + this.opts = opts; + this.fs_path = opts.fs_path || opts.env_path || ""; + if (!opts.adapter) throw new Error("SmartFs requires an adapter"); + this.adapter = new opts.adapter(this); + this.excluded_patterns = []; + if (Array.isArray(opts.exclude_patterns)) { + opts.exclude_patterns.forEach((pattern) => this.add_ignore_pattern(pattern)); + } + this.folders = {}; + this.files = {}; + this.file_paths = []; + this.folder_paths = []; + this.auto_excluded_files = []; + } + async refresh() { + this.files = {}; + this.file_paths = []; + this.folders = {}; + this.folder_paths = []; + await this.init(); + } + async init() { + await this.load_exclusions(); + await this.load_files(); + } + async load_files() { + const all = await this.list_recursive(); + this.file_paths = []; + this.folder_paths = []; + all.forEach((file) => { + if (file.type === "file") { + this.files[file.path] = file; + this.file_paths.push(file.path); + } else if (file.type === "folder") { + this.folders[file.path] = file; + this.folder_paths.push(file.path); + } + }); + } + include_file(file_path) { + const file = this.adapter.get_file(file_path); + this.files[file.path] = file; + this.file_paths.push(file.path); + return file; + } + /** + * Load .gitignore patterns + * + * @returns {Promise} Array of RegExp patterns + */ + async load_exclusions() { + const gitignore_path = ".gitignore"; + const gitignore_exists = await this.adapter.exists(gitignore_path); + if (gitignore_exists && !this.env.settings.skip_excluding_gitignore) { + const gitignore_content = await this.adapter.read(gitignore_path, "utf-8"); + gitignore_content.split("\n").filter((line) => !line.startsWith("#")).filter(Boolean).forEach((pattern) => this.add_ignore_pattern(pattern)); + } + this.add_ignore_pattern(".**"); + this.add_ignore_pattern("**/.**"); + this.add_ignore_pattern("**/.*/**"); + this.add_ignore_pattern("**/*.ajson"); + } + /** + * Add a new ignore pattern + * + * @param {string} pattern - The pattern to add + */ + add_ignore_pattern(pattern, opts = {}) { + this.excluded_patterns.push(glob_to_regex(pattern.trim(), opts)); + } + /** + * Check if a path is ignored based on gitignore patterns + * + * @param {string} _path - The path to check + * @returns {boolean} True if the path is ignored, false otherwise + */ + is_excluded(_path) { + try { + if (_path.includes("#")) return true; + if (!this.excluded_patterns.length) return false; + return this.excluded_patterns.some((pattern) => pattern.test(_path)); + } catch (e) { + console.error(`Error checking if path is excluded: ${e.message}`); + console.error(`Path: `, _path); + throw e; + } + } + /** + * Check if any path in an array of paths is excluded + * + * @param {string[]} paths - Array of paths to check + * @returns {boolean} True if any path is excluded, false otherwise + */ + has_excluded_patterns(paths) { + return paths.some((p) => this.is_excluded(p)); + } + /** + * Pre-process an array of paths, throwing an error if any path is excluded + * + * @param {string[]} paths - Array of paths to pre-process + * @throws {Error} If any path in the array is excluded + * @returns {string[]} The array of paths + */ + pre_process(paths) { + if (this.has_excluded_patterns(paths)) { + throw new Error(`Path is excluded: ${paths.find((p) => this.is_excluded(p))}`); + } + return paths; + } + /** + * Post-process the result of an operation + * + * @param {any} returned_value - The value returned by the operation + * @returns {any} The post-processed value + */ + post_process(returned_value) { + if (this.adapter.post_process) return this.adapter.post_process(returned_value); + if (Array.isArray(returned_value)) { + returned_value = returned_value.filter((r) => { + if (typeof r === "string") return !this.is_excluded(r); + if (typeof r === "object" && r.path) return !this.is_excluded(r.path); + return true; + }); + } + return returned_value; + } + // v2 + /** + * Use the adapter for a method + * runs pre_process and post_process (checks exclusions) + * @param {string} method - The method to use + * @param {string[]} paths - The paths to use + * @param {...any} args - Additional arguments for the method + * @returns {Promise} The result of the method + */ + async use_adapter(method, paths, ...args) { + if (!this.adapter[method]) throw new Error(`Method ${method} not found in adapter`); + paths = this.pre_process(paths ?? []); + let resp = await this.adapter[method](...paths, ...args); + return this.post_process(resp); + } + use_adapter_sync(method, paths, ...args) { + if (!this.adapter[method]) throw new Error(`Method ${method} not found in adapter`); + paths = this.pre_process(paths ?? []); + let resp = this.adapter[method](...paths, ...args); + return this.post_process(resp); + } + /** + * Append content to a file + * + * @param {string} rel_path - The relative path of the file to append to + * @param {string|Buffer} content - The content to append + * @returns {Promise} A promise that resolves when the operation is complete + */ + async append(rel_path, content) { + return await this.use_adapter("append", [rel_path], content); + } + /** + * Create a new directory + * + * @param {string} rel_path - The relative path of the directory to create + * @returns {Promise} A promise that resolves when the operation is complete + */ + async mkdir(rel_path, opts = { recursive: true }) { + return await this.use_adapter("mkdir", [rel_path], opts); + } + /** + * Check if a file or directory exists + * + * @param {string} rel_path - The relative path to check + * @returns {Promise} True if the path exists, false otherwise + */ + async exists(rel_path) { + return await this.use_adapter("exists", [rel_path]); + } + exists_sync(rel_path) { + return this.use_adapter_sync("exists_sync", [rel_path]); + } + /** + * List files in a directory + * + * @param {string} rel_path - The relative path to list + * @returns {Promise} Array of file paths + */ + async list(rel_path = "/") { + return await this.use_adapter("list", [rel_path]); + } + async list_recursive(rel_path = "/") { + return await this.use_adapter("list_recursive", [rel_path]); + } + async list_files(rel_path = "/") { + return await this.use_adapter("list_files", [rel_path]); + } + async list_files_recursive(rel_path = "/") { + return await this.use_adapter("list_files_recursive", [rel_path]); + } + async list_folders(rel_path = "/") { + return await this.use_adapter("list_folders", [rel_path]); + } + async list_folders_recursive(rel_path = "/") { + return await this.use_adapter("list_folders_recursive", [rel_path]); + } + /** + * Read the contents of a file + * + * @param {string} rel_path - The relative path of the file to read + * @returns {Promise} The contents of the file + */ + async read(rel_path, encoding = "utf-8") { + try { + const content = await this.adapter.read(rel_path, encoding); + return content; + } catch (error) { + console.warn("Error during read: " + error.message, rel_path); + if (error.code === "ENOENT") return null; + return { error: error.message }; + } + } + /** + * Remove a file + * + * @param {string} rel_path - The relative path of the file to remove + * @returns {Promise} A promise that resolves when the operation is complete + */ + async remove(rel_path) { + return await this.use_adapter("remove", [rel_path]); + } + /** + * Remove a directory + * + * @param {string} rel_path - The relative path of the directory to remove + * @returns {Promise} A promise that resolves when the operation is complete + */ + async remove_dir(rel_path, recursive = false) { + return await this.use_adapter("remove_dir", [rel_path], recursive); + } + /** + * Rename a file or directory + * + * @param {string} rel_path - The current relative path + * @param {string} new_rel_path - The new relative path + * @returns {Promise} A promise that resolves when the operation is complete + */ + async rename(rel_path, new_rel_path) { + await this.use_adapter("rename", [rel_path, new_rel_path]); + await this.refresh(); + } + /** + * Get file or directory statistics + * + * @param {string} rel_path - The relative path to get statistics for + * @returns {Promise} An object containing file or directory statistics + */ + async stat(rel_path) { + return await this.use_adapter("stat", [rel_path]); + } + /** + * Write content to a file + * Should handle when target path is within a folder that doesn't exist + * + * @param {string} rel_path - The relative path of the file to write to + * @param {string|Buffer} content - The content to write + * @returns {Promise} A promise that resolves when the operation is complete + */ + async write(rel_path, content) { + try { + await this.adapter.write(rel_path, content); + } catch (error) { + console.error("Error during write:", error); + throw error; + } + } + // // aliases + // async create(rel_path, content) { return await this.use_adapter('write', [rel_path], content); } + // async update(rel_path, content) { return await this.use_adapter('write', [rel_path], content); } + get_link_target_path(link_target, source_path) { + if (this.adapter.get_link_target_path) return this.adapter.get_link_target_path(link_target, source_path); + if (!this.file_paths) return console.warn("get_link_target_path: file_paths not found"); + const matching_file_paths = this.file_paths.filter((path) => path.includes(link_target)); + return fuzzy_search(matching_file_paths, link_target)[0]; + } + get sep() { + return this.adapter.sep || "/"; + } + get_full_path(rel_path = "") { + return this.adapter.get_full_path(rel_path); + } + get base_path() { + return this.adapter.get_base_path(); + } +}; + +// node_modules/obsidian-smart-env/src/adapters/smart-fs/obsidian.js +var obsidian = __toESM(require("obsidian"), 1); +var ObsidianFsAdapter = class { + /** + * Create an ObsidianFsAdapter instance + * + * @param {Object} smart_fs - The SmartFs instance + */ + constructor(smart_fs) { + this.smart_fs = smart_fs; + this.obsidian = obsidian; + this.obsidian_app = smart_fs.env.main.app; + this.obsidian_adapter = smart_fs.env.main.app.vault.adapter; + } + get fs_path() { + return this.smart_fs.fs_path; + } + get_file(file_path) { + const file = {}; + file.path = file_path.replace(/\\/g, "/").replace(this.smart_fs.fs_path, "").replace(/^\//, ""); + file.type = "file"; + file.extension = file.path.split(".").pop().toLowerCase(); + file.name = file.path.split("/").pop(); + file.basename = file.name.split(".").shift(); + Object.defineProperty(file, "stat", { + get: () => { + const tfile = this.obsidian_app.vault.getAbstractFileByPath(file_path); + if (tfile) { + return { + ctime: tfile.stat.ctime, + mtime: tfile.stat.mtime, + size: tfile.stat.size, + isDirectory: () => tfile instanceof this.obsidian.TFolder, + isFile: () => tfile instanceof this.obsidian.TFile + }; + } + return null; + } + }); + return file; + } + /** + * Append content to a file + * + * @param {string} rel_path - The relative path of the file to append to + * @param {string} data - The content to append + * @returns {Promise} A promise that resolves when the operation is complete + */ + async append(rel_path, data) { + if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; + return await this.obsidian_adapter.append(rel_path, data); + } + /** + * Create a new directory + * + * @param {string} rel_path - The relative path of the directory to create + * @returns {Promise} A promise that resolves when the operation is complete + */ + async mkdir(rel_path) { + if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; + return await this.obsidian_adapter.mkdir(rel_path); + } + /** + * Check if a file or directory exists + * + * @param {string} rel_path - The relative path to check + * @returns {Promise} True if the path exists, false otherwise + */ + async exists(rel_path) { + if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; + return await this.obsidian_adapter.exists(rel_path); + } + exists_sync(rel_path) { + return !!this.obsidian_app.vault.getAbstractFileByPath(rel_path); + } + /** + * List files in a directory (NOT up-to-date with list_recursive) + * + * @param {string} rel_path - The relative path to list + * @returns {Promise} Array of file paths + */ + async list(rel_path, opts = {}) { + if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; + if (rel_path.startsWith("/")) rel_path = rel_path.slice(1); + if (rel_path.endsWith("/")) rel_path = rel_path.slice(0, -1); + if (rel_path.includes(".")) { + const { files: file_paths } = await this.obsidian_adapter.list(rel_path); + const files2 = file_paths.map((file_path) => { + if (this.smart_fs.fs_path) file_path = file_path.replace(this.smart_fs.fs_path, "").slice(1); + const file_name = file_path.split("/").pop(); + const file = { + basename: file_name.split(".")[0], + extension: file_name.split(".").pop().toLowerCase(), + name: file_name, + path: file_path + }; + return file; + }); + return files2; + } + const files = this.obsidian_app.vault.getAllLoadedFiles().filter((file) => { + const last_slash = file.path.lastIndexOf("/"); + if (last_slash === -1 && rel_path !== "") return false; + const folder_path = file.path.slice(0, last_slash); + if (folder_path !== rel_path) return false; + return true; + }); + return files; + } + // NOTE: currently does not handle hidden files and folders + async list_recursive(rel_path, opts = {}) { + if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; + if (rel_path.startsWith("/")) rel_path = rel_path.slice(1); + if (rel_path.endsWith("/")) rel_path = rel_path.slice(0, -1); + const files = this.obsidian_app.vault.getAllLoadedFiles().filter((file) => { + if (file.path.length > 200) { + this.smart_fs.auto_excluded_files.push(file.path); + return false; + } + if (rel_path !== "" && !file.path.startsWith(rel_path)) return false; + if (file instanceof this.obsidian.TFile) { + if (opts.type === "folder") return false; + file.type = "file"; + } else if (file instanceof this.obsidian.TFolder) { + if (opts.type === "file") return false; + delete file.basename; + delete file.extension; + file.type = "folder"; + } + if (this.smart_fs.fs_path) file.path = file.path.replace(this.smart_fs.fs_path, "").slice(1); + return true; + }); + return files; + } + async list_files(rel_path) { + return await this.list(rel_path, { type: "file" }); + } + async list_files_recursive(rel_path) { + return await this.list_recursive(rel_path, { type: "file" }); + } + async list_folders(rel_path) { + return await this.list(rel_path, { type: "folder" }); + } + async list_folders_recursive(rel_path) { + return await this.list_recursive(rel_path, { type: "folder" }); + } + /** + * Read the contents of a file + * + * @param {string} rel_path - The relative path of the file to read + * @returns {Promise} The contents of the file + */ + async read(rel_path, encoding, opts = {}) { + if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; + if (encoding === "utf-8") { + if (!opts.no_cache) { + const tfile = this.obsidian_app.vault.getFileByPath(rel_path); + if (tfile) return await this.obsidian_app.vault.cachedRead(tfile); + } + return await this.obsidian_adapter.read(rel_path); + } + if (encoding === "base64") { + const array_buffer2 = await this.obsidian_adapter.readBinary(rel_path, "base64"); + const base64 = this.obsidian.arrayBufferToBase64(array_buffer2); + return base64; + } + const array_buffer = await this.obsidian_adapter.readBinary(rel_path); + return array_buffer; + } + /** + * Rename a file or directory + * + * @param {string} old_path - The current path of the file or directory + * @param {string} new_path - The new path for the file or directory + * @returns {Promise} A promise that resolves when the operation is complete + */ + async rename(old_path, new_path) { + if (!old_path.startsWith(this.fs_path)) old_path = this.fs_path + "/" + old_path; + if (!new_path.startsWith(this.fs_path)) new_path = this.fs_path + "/" + new_path; + return await this.obsidian_adapter.rename(old_path, new_path); + } + /** + * Remove a file + * + * @param {string} rel_path - The relative path of the file to remove + * @returns {Promise} A promise that resolves when the operation is complete + */ + async remove(rel_path) { + if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; + try { + return await this.obsidian_adapter.remove(rel_path); + } catch (error) { + console.warn(`Error removing file: ${rel_path}`, error); + } + } + /** + * Remove a directory + * + * @param {string} rel_path - The relative path of the directory to remove + * @returns {Promise} A promise that resolves when the operation is complete + */ + async remove_dir(rel_path, recursive = false) { + if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; + return await this.obsidian_adapter.rmdir(rel_path, recursive); + } + /** + * Get file or directory information + * + * @param {string} rel_path - The relative path of the file or directory + * @returns {Promise} An object containing file or directory information + */ + async stat(rel_path) { + if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; + return await this.obsidian_adapter.stat(rel_path); + } + /** + * Write content to a file + * + * @param {string} rel_path - The relative path of the file to write to + * @param {string} data - The content to write + * @returns {Promise} A promise that resolves when the operation is complete + */ + async write(rel_path, data) { + if (!data) data = ""; + if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; + const folder_path = rel_path.split("/").slice(0, -1).join("/"); + if (!await this.exists(folder_path)) { + await this.mkdir(folder_path); + console.log(`Created folder: ${folder_path}`); + } + return await this.obsidian_adapter.write(rel_path, data); + } + get_link_target_path(link_path, file_path) { + return this.obsidian_app.metadataCache.getFirstLinkpathDest(link_path, file_path)?.path; + } + get_base_path() { + return this.obsidian_adapter.basePath; + } + get_full_path(rel_path = "") { + const sep = rel_path.includes("/") ? "/" : "\\"; + return this.get_base_path() + sep + rel_path; + } + /** + * Registers Obsidian vault/workspace listeners that emit Smart Environment events for Smart Sources. + * @param {import('smart-sources').SmartSources} sources_collection + * @returns {boolean} + */ + register_source_watchers(sources_collection) { + if (this._source_watchers_registered) return this._source_watchers_registered; + const plugin = this.smart_fs.env?.main; + if (!plugin?.registerEvent) { + console.warn("ObsidianFsAdapter: Unable to register source watchers without plugin context"); + return false; + } + const { app: app2 } = plugin; + const emit_event = (event_key, payload) => { + if (!payload?.path && !payload?.item_key) return; + this.smart_fs.env.events?.emit(event_key, { + collection_key: sources_collection.collection_key, + item_key: payload.item_key || payload.path, + ...payload + }); + }; + plugin.registerEvent( + app2.vault.on("create", (file) => { + emit_event("sources:created", { + path: file.path, + event_source: "obsidian:vault.create" + }); + }) + ); + plugin.registerEvent( + app2.vault.on("modify", (file) => { + emit_event("sources:modified", { + path: file.path, + event_source: "obsidian:vault.modify" + }); + }) + ); + plugin.registerEvent( + app2.vault.on("rename", (file, old_path) => { + emit_event("sources:renamed", { + path: file.path, + old_path, + event_source: "obsidian:vault.rename" + }); + }) + ); + plugin.registerEvent( + app2.vault.on("delete", (file) => { + emit_event("sources:deleted", { + path: file.path, + event_source: "obsidian:vault.delete" + }); + }) + ); + plugin.registerEvent( + app2.workspace.on("editor-change", (_editor, info) => { + const file = info?.file; + if (!file) return; + emit_event("sources:modified", { + path: file.path, + event_source: "obsidian:workspace.editor-change" + }); + }) + ); + this._source_watchers_registered = true; + return true; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-view/utils/empty.js +function empty(elm) { + const range = document.createRange(); + range.selectNodeContents(elm); + range.deleteContents(); +} + +// node_modules/obsidian-smart-env/node_modules/smart-view/utils/replace_html.js +var replace_html = /* @__PURE__ */ (() => { + const cache = /* @__PURE__ */ new Map(); + return (container, html_snippet) => { + const key = html_snippet.trim(); + let tpl = cache.get(key); + if (!tpl) { + tpl = document.createElement("template"); + tpl.innerHTML = key; + cache.set(key, tpl); + } + container.replaceChildren(tpl.content.cloneNode(true)); + }; +})(); + +// node_modules/obsidian-smart-env/node_modules/smart-view/utils/replace_with_fragment.js +var replace_with_fragment = (container, html_snippet) => { + const range = document.createRange(); + const frag = range.createContextualFragment(html_snippet.trim()); + container.replaceChildren(frag); +}; + +// node_modules/obsidian-smart-env/node_modules/smart-view/utils/safe_inner_html.js +var restricted_re = /<(td|th|tr|thead|tbody|tfoot|caption|col|colgroup|option|optgroup|li|dt|dd|source|track)\b/i; +var safe_inner_html = (container, html_snippet) => { + const trimmed = html_snippet.trim(); + (restricted_re.test(trimmed) ? replace_with_fragment : replace_html)(container, trimmed); +}; + +// node_modules/obsidian-smart-env/node_modules/smart-utils/escape_html.js +function escape_html(value = "") { + return String(value).replaceAll("&", "&").replaceAll("<", "<").replaceAll(">", ">").replaceAll('"', """).replaceAll("'", "'"); +} + +// node_modules/obsidian-smart-env/node_modules/smart-utils/create_hash.js +function murmur_hash_32(input_string, seed = 0) { + let remainder = input_string.length & 3; + let bytes = input_string.length - remainder; + let h1 = seed; + let c1 = 3432918353; + let c2 = 461845907; + let i = 0; + let k1 = 0; + let chunk = 0; + while (i < bytes) { + chunk = input_string.charCodeAt(i) & 255 | (input_string.charCodeAt(i + 1) & 255) << 8 | (input_string.charCodeAt(i + 2) & 255) << 16 | (input_string.charCodeAt(i + 3) & 255) << 24; + i += 4; + k1 = chunk; + k1 = multiply_32(k1, c1); + k1 = rotate_left_32(k1, 15); + k1 = multiply_32(k1, c2); + h1 ^= k1; + h1 = rotate_left_32(h1, 13); + h1 = h1 * 5 + 3864292196 | 0; + } + k1 = 0; + switch (remainder) { + case 3: + k1 ^= (input_string.charCodeAt(i + 2) & 255) << 16; + // falls through + case 2: + k1 ^= (input_string.charCodeAt(i + 1) & 255) << 8; + // falls through + case 1: + k1 ^= input_string.charCodeAt(i) & 255; + k1 = multiply_32(k1, c1); + k1 = rotate_left_32(k1, 15); + k1 = multiply_32(k1, c2); + h1 ^= k1; + break; + } + h1 ^= input_string.length; + h1 = fmix_32(h1); + return h1 | 0; +} +function murmur_hash_32_alphanumeric(input_string, seed = 0) { + const signed_hash = murmur_hash_32(input_string, seed); + const unsigned_hash = signed_hash >>> 0; + return unsigned_hash.toString(36); +} +function multiply_32(a, b) { + return (a & 65535) * b + ((a >>> 16) * b << 16) | 0; +} +function rotate_left_32(value, shift) { + return value << shift | value >>> 32 - shift; +} +function fmix_32(h) { + h ^= h >>> 16; + h = multiply_32(h, 2246822507); + h ^= h >>> 13; + h = multiply_32(h, 3266489909); + h ^= h >>> 16; + return h | 0; +} + +// node_modules/obsidian-smart-env/node_modules/smart-utils/convert_to_time_ago.js +function convert_to_time_ago(timestamp) { + const now = Date.now(); + const ms = timestamp < 1e12 ? timestamp * 1e3 : timestamp; + const diff_ms = now - ms; + const is_future = diff_ms < 0; + const seconds = Math.floor(Math.abs(diff_ms) / 1e3); + const intervals = [ + { label: "year", seconds: 31536e3 }, + { label: "month", seconds: 2592e3 }, + { label: "day", seconds: 86400 }, + { label: "hour", seconds: 3600 }, + { label: "minute", seconds: 60 }, + { label: "second", seconds: 1 } + ]; + for (const interval of intervals) { + const count = Math.floor(seconds / interval.seconds); + if (count >= 1) { + const suffix = `${count} ${interval.label}${count > 1 ? "s" : ""}`; + return is_future ? `in ${suffix}` : `${suffix} ago`; + } + } + return "just now"; +} + +// node_modules/obsidian-smart-env/node_modules/smart-utils/cos_sim.js +function cos_sim(vector1 = [], vector2 = []) { + if (vector1.length !== vector2.length) { + throw new Error("Vectors must have the same length"); + } + let dot_product = 0; + let magnitude1 = 0; + let magnitude2 = 0; + const epsilon = 1e-8; + for (let i = 0; i < vector1.length; i++) { + dot_product += vector1[i] * vector2[i]; + magnitude1 += vector1[i] * vector1[i]; + magnitude2 += vector2[i] * vector2[i]; + } + magnitude1 = Math.sqrt(magnitude1); + magnitude2 = Math.sqrt(magnitude2); + if (magnitude1 < epsilon || magnitude2 < epsilon) return 0; + return dot_product / (magnitude1 * magnitude2); +} + +// node_modules/obsidian-smart-env/node_modules/smart-utils/get_by_path.js +function get_by_path(obj, path, scope = null) { + if (!path) return ""; + const keys = path.split("."); + if (scope) { + keys.unshift(scope); + } + const final_key = keys.pop(); + const instance = keys.reduce((acc, key) => acc && acc[key], obj); + if (instance && typeof instance[final_key] === "function") { + return instance[final_key].bind(instance); + } + return instance ? instance[final_key] : void 0; +} + +// node_modules/obsidian-smart-env/node_modules/smart-utils/set_by_path.js +function set_by_path(obj, path, value, scope = null) { + const keys = path.split("."); + if (scope) { + keys.unshift(scope); + } + const final_key = keys.pop(); + const target = keys.reduce((acc, key) => { + if (!acc[key] || typeof acc[key] !== "object") { + acc[key] = {}; + } + return acc[key]; + }, obj); + target[final_key] = value; +} + +// node_modules/obsidian-smart-env/node_modules/smart-utils/delete_by_path.js +function delete_by_path(obj, path, scope = null) { + const keys = path.split("."); + if (scope) { + keys.unshift(scope); + } + const final_key = keys.pop(); + const instance = keys.reduce((acc, key) => acc && acc[key], obj); + if (instance) { + delete instance[final_key]; + } +} + +// node_modules/obsidian-smart-env/node_modules/smart-utils/geom.js +function compute_centroid(points) { + if (!points || points.length === 0) { + return null; + } + const n = points.length; + const dim = points[0].length; + const sums = new Float64Array(dim); + for (let i = 0; i < n; i++) { + const p = points[i]; + for (let d = 0; d < dim; d++) { + sums[d] += p[d]; + } + } + for (let d = 0; d < dim; d++) { + sums[d] /= n; + } + return Array.from(sums); +} +function compute_medoid(points) { + if (!points || points.length === 0) { + return null; + } + if (points.length === 1) { + return points[0]; + } + const n = points.length; + const dim = points[0].length; + const sum_of_distances = new Float64Array(n); + for (let i = 0; i < n - 1; i++) { + const p_i = points[i]; + for (let j = i + 1; j < n; j++) { + const p_j = points[j]; + let dist_sq = 0; + for (let d = 0; d < dim; d++) { + const diff = p_i[d] - p_j[d]; + dist_sq += diff * diff; + } + const dist = Math.sqrt(dist_sq); + sum_of_distances[i] += dist; + sum_of_distances[j] += dist; + } + } + let min_index = 0; + let min_sum = sum_of_distances[0]; + for (let i = 1; i < n; i++) { + if (sum_of_distances[i] < min_sum) { + min_sum = sum_of_distances[i]; + min_index = i; + } + } + return points[min_index]; +} + +// node_modules/obsidian-smart-env/node_modules/smart-view/smart_view.js +var get_style_sheet_id = (css_text) => { + if (typeof css_text !== "string") return null; + return `style-sheet-${murmur_hash_32_alphanumeric(css_text)}`; +}; +var element_disposers = /* @__PURE__ */ new WeakMap(); +var smart_setting_listeners = /* @__PURE__ */ new WeakMap(); +var SmartView = class { + static version = 0.1; + /** + * @constructor + * @param {object} opts - Additional options or overrides for rendering. + */ + constructor(opts = {}) { + this.opts = opts; + this._adapter = null; + } + /** + * Renders all setting components within a container. + * @async + * @param {HTMLElement|DocumentFragment} container - The container element. + * @param {Object} opts - Additional options for rendering. + * @returns {Promise} + */ + async render_setting_components(container, opts = {}) { + const components = container.querySelectorAll(".setting-component"); + const promises = []; + for (const component of components) { + promises.push(this.render_setting_component(component, opts)); + } + await Promise.all(promises); + return container; + } + /** + * Creates a document fragment from HTML string. + * @param {string} html - The HTML string. + * @returns {DocumentFragment} + */ + create_doc_fragment(html) { + return document.createRange().createContextualFragment(html); + } + /** + * Gets the adapter instance used for rendering (e.g., Obsidian or Node, etc.). + * @returns {Object} The adapter instance. + */ + get adapter() { + if (!this._adapter) { + if (!this.opts.adapter) { + throw new Error("No adapter provided to SmartView. Provide a 'smart_view.adapter' in env config."); + } + const AdapterClass = this.opts.adapter; + this._adapter = new AdapterClass(this); + } + return this._adapter; + } + /** + * Gets an icon (implemented in the adapter). + * @param {string} icon_name - Name of the icon to get. + * @returns {string} The icon HTML string. + */ + get_icon_html(icon_name) { + return this.adapter.get_icon_html(icon_name); + } + /** + * Renders a single setting component (implemented in adapter). + * @async + * @param {HTMLElement} setting_elm - The DOM element for the setting. + * @param {Object} opts - Additional options for rendering. + * @returns {Promise<*>} + */ + async render_setting_component(setting_elm, opts = {}) { + return await this.adapter.render_setting_component(setting_elm, opts); + } + /** + * Renders markdown content (implemented in adapter). + * @param {string} markdown - The markdown content. + * @param {object|null} scope - The scope to pass for rendering. + * @returns {Promise} + */ + async render_markdown(markdown, scope = null) { + return await this.adapter.render_markdown(markdown, scope); + } + /** + * Gets a value from an object by path. + * @param {Object} obj - The object to search in. + * @param {string} path - The path to the value. + * @returns {*} + */ + get_by_path(obj, path, settings_scope = null) { + return get_by_path(obj, path, settings_scope); + } + /** + * Sets a value in an object by path. + * @param {Object} obj - The object to modify. + * @param {string} path - The path to set the value. + * @param {*} value - The value to set. + */ + set_by_path(obj, path, value, settings_scope = null) { + set_by_path(obj, path, value, settings_scope); + } + /** + * Deletes a value from an object by path. + * @param {Object} obj - The object to modify. + * @param {string} path - The path to delete the value. + */ + delete_by_path(obj, path, settings_scope = null) { + delete_by_path(obj, path, settings_scope); + } + /** + * Escapes HTML special characters in a string. + * @param {string} str - The string to escape. + * @returns {string} The escaped string. + */ + escape_html(str) { + return escape_html(str); + } + /** + * A convenience method to build a setting HTML snippet from a config object. + * @param {Object} setting_config + * @returns {string} + */ + render_setting_html(setting_config) { + if (setting_config.type === "html") { + return setting_config.value; + } + const attributes = Object.entries(setting_config).map(([attr, value]) => { + if (attr.includes("class")) return ""; + if (typeof value === "number") return `data-${attr.replace(/_/g, "-")}=${value}`; + return `data-${attr.replace(/_/g, "-")}="${value}"`; + }).join("\n"); + return `
`; + } + /** + * Renders settings from a config, returning a fragment. + * @async + * @deprecated Use render_settings_config utility in Obsidian (obsidian-smart-env) + * @param {Object} settings_config + * @param {Object} opts + * @param {Object} [opts.scope={}] - The scope to use when rendering settings (should have settings property). + * @returns {Promise} + */ + async render_settings(settings_config13, opts = {}) { + const is_fx = typeof settings_config13 === "function"; + const html = Object.entries(is_fx ? await settings_config13(opts.scope) : settings_config13).map(([setting_key, setting_config]) => { + if (!setting_config.setting) { + setting_config.setting = setting_key; + } + return this.render_setting_html(setting_config); + }).join("\n"); + const frag = this.create_doc_fragment(`
${html}
`); + return await this.render_setting_components(frag, opts); + } + /** + * Scans the given container for elements that have `data-smart-setting` and attaches + * a 'change' event listener that updates the corresponding path in `scope.settings`. + * + * Listener bookkeeping is done via WeakMap, not DOM attributes, to avoid + * clone/attribute-related bugs on re-render. + * + * @param {Object} scope - An object containing a `settings` property, where new values will be stored. + * @param {HTMLElement|Document} [container=document] - The DOM element to scan. Defaults to the entire document. + */ + add_settings_listeners(scope, container = document) { + if (!container || typeof container.querySelectorAll !== "function") return; + const elements = container.querySelectorAll("[data-smart-setting]"); + elements.forEach((elm) => { + const path = elm.dataset.smartSetting; + if (!path) return; + if (smart_setting_listeners.has(elm)) { + return; + } + const handler = () => { + let new_value; + if (elm instanceof HTMLInputElement) { + if (elm.type === "checkbox") { + new_value = elm.checked; + } else if (elm.type === "radio") { + if (elm.checked) { + new_value = elm.value; + } else { + return; + } + } else { + new_value = elm.value; + } + } else if (elm instanceof HTMLSelectElement || elm instanceof HTMLTextAreaElement) { + new_value = elm.value; + } else { + new_value = elm.value ?? elm.textContent; + } + this.set_by_path(scope.settings, path, new_value); + }; + smart_setting_listeners.set(elm, handler); + elm.addEventListener("change", handler); + if (elm instanceof HTMLElement) { + this.attach_disposer(elm, () => { + const existing = smart_setting_listeners.get(elm); + if (existing) { + elm.removeEventListener("change", existing); + smart_setting_listeners.delete(elm); + } + }); + } + }); + } + apply_style_sheet(sheet) { + if (typeof sheet === "string") { + const style_sheet_id = get_style_sheet_id(sheet); + if (!style_sheet_id) return; + if (document.getElementById(style_sheet_id)) { + return; + } + const styleEl = document.createElement("style"); + styleEl.id = style_sheet_id; + styleEl.textContent = sheet; + document.head.appendChild(styleEl); + return; + } + if ("adoptedStyleSheets" in Document.prototype) { + document.adoptedStyleSheets = [...document.adoptedStyleSheets, sheet]; + } else { + const styleEl = document.createElement("style"); + if (sheet.cssRules) { + styleEl.textContent = Array.from(sheet.cssRules).map((rule) => rule.cssText).join("\n"); + } + document.head.appendChild(styleEl); + } + } + empty(elm) { + empty(elm); + } + safe_inner_html(elm, html) { + safe_inner_html(elm, html); + } + /** + * Attaches one or more disposer functions to an element that will be called + * when that element has been observed in the DOM and is later removed. + * + * - Multiple calls for the same element accumulate disposer functions. + * - Disposers are only invoked once, on the first removal after the + * element has been in the DOM. + * - No DOM attributes or properties are used for bookkeeping; everything + * is tracked via WeakMap. + * + * @param {HTMLElement} el - The element to monitor. + * @param {Function|Function[]} dispose - The disposer function or array of functions to call on removal. + */ + attach_disposer(el, dispose) { + if (!el) return; + const doc = el.ownerDocument; + const win = doc && doc.defaultView; + const MutationObserverCtor = win && win.MutationObserver; + if (!doc || !win || !MutationObserverCtor || !doc.body) return; + let dispose_fns; + if (typeof dispose === "function") { + dispose_fns = [dispose]; + } else if (Array.isArray(dispose)) { + dispose_fns = dispose.filter((fn) => typeof fn === "function"); + } else { + console.warn("[smart-view] attach_disposer called with invalid disposer"); + return; + } + if (!dispose_fns.length) { + console.warn("[smart-view] attach_disposer called with no valid disposer functions"); + return; + } + let entry = element_disposers.get(el); + if (!entry) { + entry = { + dispose_fns: /* @__PURE__ */ new Set(), + observer: null, + has_been_in_dom: false, + disposed: false + }; + element_disposers.set(el, entry); + } + if (entry.disposed) { + entry.disposed = false; + entry.has_been_in_dom = false; + } + for (const fn of dispose_fns) { + entry.dispose_fns.add(fn); + } + if (!entry.observer) { + const observer = new MutationObserverCtor(() => { + const in_dom = doc.body.contains(el); + if (in_dom) { + entry.has_been_in_dom = true; + return; + } + if (!entry.has_been_in_dom || entry.disposed) { + return; + } + entry.disposed = true; + try { + for (const fn of entry.dispose_fns) { + try { + fn(); + } catch (err) { + console.error("[smart-view] disposer error", err); + } + } + } finally { + try { + observer.disconnect(); + } catch (e) { + } + if (element_disposers.get(el) === entry) { + element_disposers.delete(el); + } + } + }); + entry.observer = observer; + observer.observe(doc.body, { childList: true, subtree: true }); + } + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-view/adapters/_adapter.js +var SmartViewAdapter = class { + constructor(main) { + this.main = main; + } + // NECESSARY OVERRIDES + /** + * Retrieves the class used for settings. + * Must be overridden by subclasses to return the appropriate setting class. + * @abstract + * @returns {Function} The setting class constructor. + * @throws Will throw an error if not implemented in the subclass. + */ + get setting_class() { + throw new Error("setting_class() not implemented"); + } + /** + * Generates the HTML for a specified icon. + * Must be overridden by subclasses to provide the correct icon HTML. + * @abstract + * @param {string} icon_name - The name of the icon to generate HTML for. + * @returns {string} The HTML string representing the icon. + * @throws Will throw an error if not implemented in the subclass. + */ + get_icon_html(icon_name) { + throw new Error("get_icon_html() not implemented"); + } + /** + * Renders Markdown content within a specific scope. + * Must be overridden by subclasses to handle Markdown rendering appropriately. + * @abstract + * @param {string} markdown - The Markdown content to render. + * @param {object|null} [scope=null] - The scope within which to render the Markdown. + * @returns {Promise} A promise that resolves when rendering is complete. + * @throws Will throw an error if not implemented in the subclass. + */ + async render_markdown(markdown, scope = null) { + throw new Error("render_markdown() not implemented"); + } + /** + * Opens a specified URL. + * Should be overridden by subclasses to define how URLs are opened. + * @abstract + * @param {string} url - The URL to open. + */ + open_url(url) { + throw new Error("open_url() not implemented"); + } + /** + * Handles the selection of a folder by invoking the folder selection dialog and updating the setting. + * @abstract + * @param {string} setting - The path of the setting being modified. + * @param {string} value - The current value of the setting. + * @param {HTMLElement} elm - The HTML element associated with the setting. + * @param {object} scope - The current scope containing settings and actions. + */ + handle_folder_select(path, value, elm, scope) { + throw new Error("handle_folder_select not implemented"); + } + /** + * Handles the selection of a file by invoking the file selection dialog and updating the setting. + * @abstract + * @param {string} setting - The path of the setting being modified. + * @param {string} value - The current value of the setting. + * @param {HTMLElement} elm - The HTML element associated with the setting. + * @param {object} scope - The current scope containing settings and actions. + */ + handle_file_select(path, value, elm, scope) { + throw new Error("handle_file_select not implemented"); + } + /** + * Performs actions before a setting is changed, such as clearing notices and updating the UI. + * @abstract + * @param {string} setting - The path of the setting being changed. + * @param {*} value - The new value for the setting. + * @param {HTMLElement} elm - The HTML element associated with the setting. + * @param {object} scope - The current scope containing settings and actions. + */ + pre_change(path, value, elm) { + } + /** + * Performs actions after a setting is changed, such as updating UI elements. + * @abstract + * @param {string} setting - The path of the setting that was changed. + * @param {*} value - The new value for the setting. + * @param {HTMLElement} elm - The HTML element associated with the setting. + * @param {object} changed - Additional information about the change. + */ + post_change(path, value, elm) { + } + /** + * Reverts a setting to its previous value in case of validation failure or error. + * @abstract + * @param {string} setting - The path of the setting to revert. + * @param {HTMLElement} elm - The HTML element associated with the setting. + * @param {object} scope - The current scope containing settings. + */ + revert_setting(path, elm, scope) { + console.warn("revert_setting() not implemented"); + } + // DEFAULT IMPLEMENTATIONS (may be overridden) + get setting_renderers() { + return { + text: this.render_text_component, + string: this.render_text_component, + password: this.render_password_component, + number: this.render_number_component, + dropdown: this.render_dropdown_component, + toggle: this.render_toggle_component, + textarea: this.render_textarea_component, + textarea_array: this.render_textarea_array_component, + button: this.render_button_component, + remove: this.render_remove_component, + folder: this.render_folder_select_component, + "text-file": this.render_file_select_component, + file: this.render_file_select_component, + slider: this.render_slider_component, + html: this.render_html_component, + button_with_confirm: this.render_button_with_confirm_component, + json: this.render_json_component, + array: this.render_array_component + }; + } + async render_setting_component(elm, opts = {}) { + this.empty(elm); + const path = elm.dataset.setting; + const scope = opts.scope || this.main.main; + const settings_scope = opts.settings_scope || null; + try { + let value = elm.dataset.value ?? this.main.get_by_path(scope.settings, path, settings_scope); + if (typeof value === "undefined" && typeof elm.dataset.default !== "undefined") { + value = elm.dataset.default; + if (typeof value === "string") value = value.toLowerCase() === "true" ? true : value === "false" ? false : value; + this.main.set_by_path(scope.settings, path, value, settings_scope); + } + const renderer = this.setting_renderers[elm.dataset.type]; + if (!renderer) { + console.warn(`Unsupported setting type: ${elm.dataset.type}`); + return elm; + } + const setting = renderer.call(this, elm, path, value, scope, settings_scope); + if (elm.dataset.name) setting.setName(elm.dataset.name); + if (elm.dataset.description) { + const frag = this.main.create_doc_fragment(`${elm.dataset.description}`); + setting.setDesc(frag); + } + if (elm.dataset.tooltip) setting.setTooltip(elm.dataset.tooltip); + this.add_button_if_needed(setting, elm, path, scope); + this.handle_disabled_and_hidden(elm); + return elm; + } catch (e) { + console.error(JSON.stringify({ path, elm }, null, 2)); + console.error(JSON.stringify(e, null, 2)); + } + } + render_dropdown_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + let options; + smart_setting.addDropdown((dropdown) => { + if (elm.dataset.required) dropdown.selectEl.setAttribute("required", true); + const opts_callback = elm.dataset.optionsCallback ? this.main.get_by_path(scope, elm.dataset.optionsCallback) : null; + if (typeof opts_callback === "function") { + console.log(`getting options callback: ${elm.dataset.optionsCallback}`); + Promise.resolve(opts_callback()).then((opts) => { + opts.forEach((option) => { + const opt = dropdown.addOption(option.value, option.label ?? option.name ?? option.value); + opt.selected = option.value === value; + if (opts.length === 1 && opt.selected) dropdown.selectEl.classList.add("dropdown-no-options"); + }); + dropdown.setValue(value); + }); + } else { + if (!options || !options.length) { + options = this.get_dropdown_options(elm); + } + options.forEach((option) => { + const opt = dropdown.addOption(option.value, option.label ?? option.name ?? option.value); + opt.selected = option.value === value; + if (options.length === 1 && opt.selected) dropdown.selectEl.classList.add("dropdown-no-options"); + }); + dropdown.setValue(value); + } + dropdown.onChange((value2) => { + this.handle_on_change(path, value2, elm, scope, settings_scope); + }); + }); + return smart_setting; + } + render_text_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addText((text) => { + text.setPlaceholder(elm.dataset.placeholder || ""); + if (value) text.setValue(value); + let debounceTimer; + if (elm.dataset.button) { + smart_setting.addButton((button) => { + button.setButtonText(elm.dataset.button); + button.onClick(async () => this.handle_on_change(path, text.getValue(), elm, scope)); + }); + } else { + text.onChange(async (value2) => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => this.handle_on_change(path, value2.trim(), elm, scope, settings_scope), 2e3); + }); + } + }); + return smart_setting; + } + render_password_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addText((text) => { + text.inputEl.type = "password"; + text.setPlaceholder(elm.dataset.placeholder || ""); + if (value) text.setValue(value); + let debounceTimer; + text.onChange(async (value2) => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => this.handle_on_change(path, value2, elm, scope, settings_scope), 2e3); + }); + }); + return smart_setting; + } + render_number_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addText((number) => { + number.inputEl.type = "number"; + number.setPlaceholder(elm.dataset.placeholder || ""); + if (typeof value !== "undefined") number.inputEl.value = parseInt(value); + number.inputEl.min = elm.dataset.min || 0; + if (elm.dataset.max) number.inputEl.max = elm.dataset.max; + let debounceTimer; + number.onChange(async (value2) => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => this.handle_on_change(path, parseInt(value2), elm, scope, settings_scope), 2e3); + }); + }); + return smart_setting; + } + render_toggle_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addToggle((toggle) => { + let checkbox_val = value ?? false; + if (typeof checkbox_val === "string") { + checkbox_val = checkbox_val.toLowerCase() === "true"; + } + toggle.setValue(checkbox_val); + toggle.onChange(async (value2) => this.handle_on_change(path, value2, elm, scope, settings_scope)); + }); + return smart_setting; + } + render_textarea_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addTextArea((textarea) => { + textarea.setPlaceholder(elm.dataset.placeholder || ""); + textarea.setValue(value || ""); + let debounceTimer; + textarea.onChange(async (value2) => { + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => this.handle_on_change(path, value2, elm, scope, settings_scope), 2e3); + }); + }); + return smart_setting; + } + render_textarea_array_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addTextArea((textarea) => { + textarea.setPlaceholder(elm.dataset.placeholder || ""); + textarea.setValue(Array.isArray(value) ? value.join("\n") : value || ""); + let debounceTimer; + textarea.onChange(async (value2) => { + value2 = value2.split("\n").map((v) => v.trim()).filter((v) => v); + clearTimeout(debounceTimer); + debounceTimer = setTimeout(() => this.handle_on_change(path, value2, elm, scope, settings_scope), 2e3); + }); + }); + return smart_setting; + } + render_button_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addButton((button) => { + button.setButtonText(elm.dataset.btnText || elm.dataset.name); + button.onClick(async () => { + if (elm.dataset.confirm && !confirm(elm.dataset.confirm)) return; + if (elm.dataset.href) this.open_url(elm.dataset.href); + if (elm.dataset.callback) { + const callback = this.main.get_by_path(scope, elm.dataset.callback); + if (callback) callback(path, value, elm, scope, settings_scope); + } + }); + }); + return smart_setting; + } + render_remove_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addButton((button) => { + button.setButtonText(elm.dataset.btnText || elm.dataset.name || "Remove"); + button.onClick(async () => { + this.main.delete_by_path(scope.settings, path, settings_scope); + if (elm.dataset.callback) { + const callback = this.main.get_by_path(scope, elm.dataset.callback); + if (callback) callback(path, value, elm, scope, settings_scope); + } + }); + }); + return smart_setting; + } + render_folder_select_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addFolderSelect((folder_select) => { + folder_select.setPlaceholder(elm.dataset.placeholder || ""); + if (value) folder_select.setValue(value); + folder_select.inputEl.closest("div").addEventListener("click", () => { + this.handle_folder_select(path, value, elm, scope); + }); + folder_select.inputEl.querySelector("input").addEventListener("change", (e) => { + const folder = e.target.value; + this.handle_on_change(path, folder, elm, scope, settings_scope); + }); + }); + return smart_setting; + } + render_file_select_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addFileSelect((file_select) => { + file_select.setPlaceholder(elm.dataset.placeholder || ""); + if (value) file_select.setValue(value); + file_select.inputEl.closest("div").addEventListener("click", () => { + this.handle_file_select(path, value, elm, scope, settings_scope); + }); + }); + return smart_setting; + } + render_slider_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addSlider((slider) => { + const min = parseFloat(elm.dataset.min) || 0; + const max = parseFloat(elm.dataset.max) || 100; + const step = parseFloat(elm.dataset.step) || 1; + const currentValue = typeof value !== "undefined" ? parseFloat(value) : min; + slider.setLimits(min, max, step); + slider.setValue(currentValue); + slider.onChange((newVal) => { + const numericVal = parseFloat(newVal); + this.handle_on_change(path, numericVal, elm, scope, settings_scope); + }); + }); + return smart_setting; + } + render_html_component(elm, path, value, scope) { + this.safe_inner_html(elm, value); + return elm; + } + /** + * Renders an array setting component for managing a list of strings. + * @param {HTMLElement} elm - Container element for the setting. + * @param {string} path - Dot-notation path to store the array. + * @param {Array} value - Initial array value. + * @param {object} scope - Scope containing settings and actions. + * @param {object|null} settings_scope - Optional nested settings scope. + * @returns {object} smart_setting instance. + */ + render_array_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + let arr = Array.isArray(value) ? [...value] : []; + const items_container = document.createElement("div"); + items_container.className = "array-items-container"; + const render_items = () => { + items_container.innerHTML = ""; + arr.forEach((val, idx) => { + const row = document.createElement("div"); + row.className = "array-item-row"; + const input = document.createElement("input"); + input.type = "text"; + input.value = val; + input.placeholder = "Value"; + const remove_btn = document.createElement("button"); + remove_btn.textContent = "\u2715"; + remove_btn.title = "Remove"; + input.addEventListener("change", () => { + arr[idx] = input.value; + trigger_change(); + }); + remove_btn.addEventListener("click", () => { + arr.splice(idx, 1); + render_items(); + trigger_change(); + }); + row.appendChild(input); + row.appendChild(remove_btn); + items_container.appendChild(row); + }); + }; + const add_row = document.createElement("div"); + add_row.className = "array-add-row"; + const new_input = document.createElement("input"); + new_input.type = "text"; + new_input.placeholder = "Value"; + const add_btn = document.createElement("button"); + add_btn.textContent = "+"; + add_btn.title = "Add value"; + add_btn.addEventListener("click", () => { + const v = new_input.value.trim(); + if (!v) return; + arr.push(v); + new_input.value = ""; + render_items(); + trigger_change(); + }); + add_row.appendChild(new_input); + add_row.appendChild(add_btn); + smart_setting.controlEl.appendChild(items_container); + smart_setting.controlEl.appendChild(add_row); + const trigger_change = () => { + this.handle_on_change(path, [...arr], elm, scope, settings_scope); + }; + render_items(); + elm.appendChild(smart_setting.settingEl); + return smart_setting; + } + render_json_component(elm, path, value, scope, settings_scope) { + try { + const smart_setting = new this.setting_class(elm); + let obj = typeof value === "object" && value !== null ? { ...value } : {}; + const pairs_container = document.createElement("div"); + pairs_container.className = "json-pairs-container"; + const renderPairs = () => { + pairs_container.innerHTML = ""; + Object.entries(obj).forEach(([key, val], idx) => { + const pair_div = document.createElement("div"); + pair_div.className = "json-pair-row"; + const key_i = document.createElement("input"); + key_i.type = "text"; + key_i.value = key; + key_i.placeholder = "Property"; + const value_i = document.createElement("input"); + value_i.type = "text"; + value_i.value = val; + value_i.placeholder = "Value"; + const remove_btn = document.createElement("button"); + remove_btn.textContent = "\u2715"; + remove_btn.title = "Remove"; + key_i.addEventListener("change", () => { + const newKey = key_i.value.trim(); + if (!newKey) return; + if (newKey !== key) { + obj[newKey] = obj[key]; + delete obj[key]; + renderPairs(); + triggerChange(); + } + }); + value_i.addEventListener("change", () => { + obj[key_i.value] = value_i.value; + triggerChange(); + }); + remove_btn.addEventListener("click", () => { + delete obj[key_i.value]; + renderPairs(); + triggerChange(); + }); + pair_div.appendChild(key_i); + pair_div.appendChild(value_i); + pair_div.appendChild(remove_btn); + pairs_container.appendChild(pair_div); + }); + }; + const add_div = document.createElement("div"); + add_div.className = "json-add-row"; + const new_key_i = document.createElement("input"); + new_key_i.type = "text"; + new_key_i.placeholder = "Property"; + const new_val_i = document.createElement("input"); + new_val_i.type = "text"; + new_val_i.placeholder = "Value"; + const add_btn = document.createElement("button"); + add_btn.textContent = "+"; + add_btn.title = "Add property"; + add_btn.addEventListener("click", () => { + const k = new_key_i.value.trim(); + if (!k || k in obj) return; + obj[k] = new_val_i.value; + new_key_i.value = ""; + new_val_i.value = ""; + renderPairs(); + triggerChange(); + }); + add_div.appendChild(new_key_i); + add_div.appendChild(new_val_i); + add_div.appendChild(add_btn); + smart_setting.controlEl.appendChild(pairs_container); + smart_setting.controlEl.appendChild(add_div); + const triggerChange = () => { + this.handle_on_change(path, { ...obj }, elm, scope, settings_scope); + }; + renderPairs(); + elm.appendChild(smart_setting.settingEl); + return smart_setting; + } catch (e) { + console.error(e); + } + } + add_button_if_needed(smart_setting, elm, path, scope) { + if (elm.dataset.btn) { + smart_setting.addButton((button) => { + button.setButtonText(elm.dataset.btn); + if (elm.dataset.btnCallback || elm.dataset.btnHref || elm.dataset.callback || elm.dataset.href) { + button.inputEl.addEventListener("click", (e) => { + if (elm.dataset.btnCallback && typeof scope[elm.dataset.btnCallback] === "function") { + if (elm.dataset.btnCallbackArg) scope[elm.dataset.btnCallback](elm.dataset.btnCallbackArg); + else scope[elm.dataset.btnCallback](path, null, smart_setting, scope); + } else if (elm.dataset.btnHref) { + this.open_url(elm.dataset.btnHref); + } else if (elm.dataset.callback && typeof this.main.get_by_path(scope, elm.dataset.callback) === "function") { + this.main.get_by_path(scope, elm.dataset.callback)(path, null, smart_setting, scope); + } else if (elm.dataset.href) { + this.open_url(elm.dataset.href); + } else { + console.error("No callback or href found for button."); + } + }); + } + if (elm.dataset.btnDisabled || elm.dataset.disabled && elm.dataset.btnDisabled !== "false") { + button.inputEl.disabled = true; + } + }); + } + } + handle_disabled_and_hidden(elm) { + if (elm.dataset.disabled && elm.dataset.disabled !== "false") { + elm.classList.add("disabled"); + elm.querySelector("input, select, textarea, button").disabled = true; + } + if (elm.dataset.hidden && elm.dataset.hidden !== "false") { + elm.style.display = "none"; + } + } + get_dropdown_options(elm) { + return Object.entries(elm.dataset).reduce((acc, [k, v]) => { + if (!k.startsWith("option")) return acc; + const [value, name] = v.split("|"); + acc.push({ value, name: name || value }); + return acc; + }, []); + } + handle_on_change(path, value, elm, scope, settings_scope) { + this.pre_change(path, value, elm, scope); + if (elm.dataset.validate) { + const valid = this[elm.dataset.validate](path, value, elm, scope); + if (!valid) { + elm.querySelector(".setting-item").style.border = "2px solid red"; + this.revert_setting(path, elm, scope); + return; + } + } + this.main.set_by_path(scope.settings, path, value, settings_scope); + if (elm.dataset.callback) { + const callback = this.main.get_by_path(scope, elm.dataset.callback); + if (callback) callback(path, value, elm, scope); + } + this.post_change(path, value, elm, scope); + } + render_button_with_confirm_component(elm, path, value, scope) { + const smart_setting = new this.setting_class(elm); + smart_setting.addButton((button) => { + button.setButtonText(elm.dataset.btnText || elm.dataset.name); + elm.appendChild(this.main.create_doc_fragment(` +
+ + ${elm.dataset.confirm || "Are you sure?"} + + + + + +
+ `)); + const confirm_row = elm.querySelector(".sc-inline-confirm-row"); + const confirm_yes = confirm_row.querySelector(".sc-inline-confirm-yes"); + const confirm_cancel = confirm_row.querySelector(".sc-inline-confirm-cancel"); + button.onClick(async () => { + confirm_row.style.display = "block"; + elm.querySelector(".setting-item").style.display = "none"; + }); + confirm_yes.addEventListener("click", async () => { + if (elm.dataset.href) this.open_url(elm.dataset.href); + if (elm.dataset.callback) { + const callback = this.main.get_by_path(scope, elm.dataset.callback); + if (callback) callback(path, value, elm, scope); + } + elm.querySelector(".setting-item").style.display = "block"; + confirm_row.style.display = "none"; + }); + confirm_cancel.addEventListener("click", () => { + confirm_row.style.display = "none"; + elm.querySelector(".setting-item").style.display = "block"; + }); + }); + return smart_setting; + } + empty(elm) { + empty(elm); + } + safe_inner_html(elm, html) { + safe_inner_html(elm, html); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-view/adapters/obsidian.js +var import_obsidian = require("obsidian"); +var SmartViewObsidianAdapter = class extends SmartViewAdapter { + get setting_class() { + return import_obsidian.Setting; + } + open_url(url) { + window.open(url); + } + async render_file_select_component(elm, path, value) { + return super.render_text_component(elm, path, value); + } + async render_markdown(markdown, scope) { + const component = scope.env.smart_connections_plugin?.connections_view || new import_obsidian.Component(); + if (!scope) return console.warn("Scope required for rendering markdown in Obsidian adapter"); + const frag = this.main.create_doc_fragment("
"); + const container = frag.querySelector(".inner"); + try { + await import_obsidian.MarkdownRenderer.render( + scope.env.plugin.app, + markdown, + container, + scope?.file_path || "", + component + ); + } catch (e) { + console.warn("Error rendering markdown in Obsidian adapter", e); + } + return frag; + } + get_icon_html(name) { + return (0, import_obsidian.getIcon)(name).outerHTML; + } + render_folder_select_component(elm, path, value, scope, settings_scope) { + const smart_setting = new this.setting_class(elm); + const folders = scope.env.plugin.app.vault.getAllFolders().sort((a, b) => a.path.localeCompare(b.path)); + smart_setting.addDropdown((dropdown) => { + if (elm.dataset.required) dropdown.inputEl.setAttribute("required", true); + dropdown.addOption("", "No folder selected"); + folders.forEach((folder) => { + dropdown.addOption(folder.path, folder.path); + }); + dropdown.onChange((value2) => { + this.handle_on_change(path, value2, elm, scope, settings_scope); + }); + dropdown.setValue(value); + }); + return smart_setting; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-collections/utils/collection_instance_name_from.js +function collection_instance_name_from(class_name) { + if (class_name.endsWith("Item")) { + return class_name.replace(/Item$/, "").replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); + } + return class_name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/y$/, "ie") + "s"; +} + +// node_modules/obsidian-smart-env/node_modules/smart-collections/utils/helpers.js +function create_uid(data) { + const str = JSON.stringify(data); + let hash = 0; + if (str.length === 0) return hash; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + if (hash < 0) hash = hash * -1; + } + return hash.toString() + str.length; +} + +// node_modules/obsidian-smart-env/node_modules/smart-collections/utils/deep_equal.js +function deep_equal(obj1, obj2, visited = /* @__PURE__ */ new WeakMap()) { + if (obj1 === obj2) return true; + if (obj1 === null || obj2 === null || obj1 === void 0 || obj2 === void 0) return false; + if (typeof obj1 !== typeof obj2 || Array.isArray(obj1) !== Array.isArray(obj2)) return false; + if (Array.isArray(obj1)) { + if (obj1.length !== obj2.length) return false; + return obj1.every((item, index) => deep_equal(item, obj2[index], visited)); + } + if (typeof obj1 === "object") { + if (visited.has(obj1)) return visited.get(obj1) === obj2; + visited.set(obj1, obj2); + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + if (keys1.length !== keys2.length) return false; + return keys1.every((key) => deep_equal(obj1[key], obj2[key], visited)); + } + return obj1 === obj2; +} + +// node_modules/obsidian-smart-env/node_modules/smart-collections/utils/get_item_display_name.js +function get_item_display_name(key, show_full_path) { + if (show_full_path) { + return key.split("/").join(" > ").replace(".md", ""); + } + return key.split("/").pop().replace(".md", ""); +} + +// node_modules/obsidian-smart-env/node_modules/smart-collections/utils/create_actions_proxy.js +function create_actions_proxy(ctx, actions_source) { + const input = actions_source || {}; + const is_plain_object7 = (val) => typeof val === "object" && val !== null && !Array.isArray(val); + const is_function = (val) => typeof val === "function"; + const is_class_export = (val) => is_function(val) && /^class\s/.test(Function.prototype.toString.call(val)); + const is_action_object = (val) => is_plain_object7(val) && is_function(val.action); + const is_action_candidate = (val) => is_function(val) || is_action_object(val) || is_class_export(val); + const ignored_meta_keys = /* @__PURE__ */ new Set(["length", "name", "prototype"]); + const clone_with_descriptors = (obj) => { + if (!is_plain_object7(obj)) return obj; + const out = Object.create(Object.getPrototypeOf(obj) || null); + for (const key of Reflect.ownKeys(obj)) { + const descriptor = Object.getOwnPropertyDescriptor(obj, key); + if (!descriptor) continue; + const next = { ...descriptor }; + if ("value" in next && is_plain_object7(next.value)) { + next.value = clone_with_descriptors(next.value); + } + try { + Object.defineProperty(out, key, next); + } catch { + out[key] = next.value; + } + } + return out; + }; + const should_bucket_actions = (val) => { + if (!is_plain_object7(val)) return false; + if (is_action_object(val)) return false; + const keys = Reflect.ownKeys(val); + if (keys.length === 0) return false; + let found_candidate = false; + for (const key of keys) { + const descriptor = Object.getOwnPropertyDescriptor(val, key); + if (!descriptor) continue; + if ("value" in descriptor) { + const entry = descriptor.value; + if (is_action_candidate(entry)) { + found_candidate = true; + continue; + } + if (is_plain_object7(entry)) { + if (should_bucket_actions(entry)) { + found_candidate = true; + continue; + } + return false; + } + if (typeof entry === "undefined") continue; + return false; + } + return false; + } + return found_candidate; + }; + const clone_descriptor = (descriptor) => { + if (!descriptor) return descriptor; + if (!("value" in descriptor)) return { ...descriptor }; + const cloned = is_plain_object7(descriptor.value) ? clone_with_descriptors(descriptor.value) : descriptor.value; + return { ...descriptor, value: cloned }; + }; + const build_sources = (src) => { + const global_source2 = /* @__PURE__ */ Object.create(null); + const scoped_sources2 = /* @__PURE__ */ new Map(); + for (const key of Reflect.ownKeys(src)) { + const descriptor = Object.getOwnPropertyDescriptor(src, key); + if (!descriptor) continue; + if ("value" in descriptor && should_bucket_actions(descriptor.value)) { + scoped_sources2.set(key, clone_with_descriptors(descriptor.value)); + continue; + } + try { + Object.defineProperty(global_source2, key, clone_descriptor(descriptor)); + } catch { + global_source2[key] = descriptor.value; + } + } + return { global_source: global_source2, scoped_sources: scoped_sources2 }; + }; + const { global_source, scoped_sources } = build_sources(input); + const has_own = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); + const cache = /* @__PURE__ */ Object.create(null); + const copy_metadata = (source, target, omit = []) => { + if (!source || !target) return; + const skips = /* @__PURE__ */ new Set([...ignored_meta_keys, ...omit]); + for (const key of Reflect.ownKeys(source)) { + if (skips.has(key)) continue; + const descriptor = Object.getOwnPropertyDescriptor(source, key); + if (!descriptor) continue; + try { + Object.defineProperty(target, key, descriptor); + } catch { + target[key] = descriptor.value; + } + } + }; + const instantiate_class = (Ctor) => { + const instance = new Ctor(ctx); + const candidate = instance.action || instance.run || instance.execute || instance.call; + if (is_function(candidate)) { + const bound = candidate.bind(instance); + copy_metadata(Ctor, bound); + copy_metadata(instance, bound); + bound.instance = instance; + return bound; + } + copy_metadata(Ctor, instance); + return instance; + }; + const bind_or_clone = (val) => { + if (is_class_export(val)) { + return instantiate_class(val); + } + if (is_action_object(val)) { + const bound = val.action.bind(ctx); + copy_metadata(val, bound, ["action"]); + return bound; + } + if (is_function(val)) { + const bound = val.bind(ctx); + copy_metadata(val, bound); + return bound; + } + if (is_plain_object7(val)) { + return clone_with_descriptors(val); + } + return val; + }; + const scope_actions_for = () => { + const scope_key = ctx?.constructor?.key; + if (typeof scope_key === "undefined" || scope_key === null) return null; + const bucket = scoped_sources.get(scope_key); + return bucket && is_plain_object7(bucket) ? bucket : null; + }; + const cache_result = (target, prop, value) => { + target[prop] = value; + return value; + }; + const compute_and_cache = (target, prop) => { + const scoped = scope_actions_for(); + if (scoped && has_own(scoped, prop)) { + return cache_result(target, prop, bind_or_clone(scoped[prop])); + } + if (has_own(global_source, prop)) { + return cache_result(target, prop, bind_or_clone(global_source[prop])); + } + return cache_result(target, prop, void 0); + }; + const union_keys = () => { + const scoped = scope_actions_for(); + const keys = new Set(Reflect.ownKeys(cache)); + for (const key of Reflect.ownKeys(global_source)) { + keys.add(key); + } + if (scoped) { + for (const key of Reflect.ownKeys(scoped)) { + keys.add(key); + } + } + return Array.from(keys); + }; + const descriptor_for = (target, prop) => ({ + configurable: true, + enumerable: true, + value: target[prop] + }); + return new Proxy(cache, { + get: (target, prop) => { + if (prop === Symbol.toStringTag) return "ActionsProxy"; + if (prop in target) return target[prop]; + return compute_and_cache(target, prop); + }, + has: (target, prop) => { + if (prop in target) return true; + const scoped = scope_actions_for(); + if (scoped && has_own(scoped, prop)) return true; + return has_own(global_source, prop); + }, + ownKeys: () => union_keys(), + getOwnPropertyDescriptor: (target, prop) => { + if (has_own(target, prop)) { + return descriptor_for(target, prop); + } + const scoped = scope_actions_for(); + if (scoped && has_own(scoped, prop)) { + if (!has_own(target, prop)) { + compute_and_cache(target, prop); + } + return descriptor_for(target, prop); + } + if (has_own(global_source, prop)) { + if (!has_own(target, prop)) { + compute_and_cache(target, prop); + } + return descriptor_for(target, prop); + } + return void 0; + }, + defineProperty: (target, prop, descriptor) => { + if ("value" in descriptor) { + target[prop] = descriptor.value; + return true; + } + return false; + }, + set: (target, prop, value) => { + target[prop] = value; + return true; + }, + deleteProperty: (target, prop) => { + if (has_own(target, prop)) { + delete target[prop]; + } + return true; + } + }); +} + +// node_modules/obsidian-smart-env/node_modules/smart-collections/item.js +var CollectionItem = class _CollectionItem { + static version = "0.1.0"; + /** + * Default properties for an instance of CollectionItem. + * Override in subclasses to define different defaults. + * @returns {Object.} + */ + static get defaults() { + return { + data: {} + }; + } + /** + * @this {*} + * @param {CollectionEnv} env - The environment/context. + * @param {Partial|null} [data=null] - Initial data for the item. + */ + constructor(env, data = null) { + env.create_env_getter(this); + this.config = this.env?.config; + this.merge_defaults(); + if (data) deep_merge(this.data, data); + if (!this.data.class_name) this.data.class_name = this.collection.item_class_name; + } + /** + * Loads an item from data and initializes it. + * @param {CollectionEnv} env + * @param {Partial} data + * @returns {CollectionItem} + */ + static load(env, data) { + const item = new this(env, data); + item.init(); + return item; + } + /** + * Merge default properties from the entire inheritance chain. + * @private + * @this {CollectionItemThis} + */ + merge_defaults() { + let current_class = this.constructor; + while (current_class) { + for (let key in current_class.defaults) { + const default_val = current_class.defaults[key]; + if (typeof default_val === "object") { + this[key] = { ...default_val, ...this[key] }; + } else { + this[key] = this[key] === void 0 ? default_val : this[key]; + } + } + current_class = Object.getPrototypeOf(current_class); + } + } + /** + * Generates or retrieves a unique key for the item. + * Key syntax supports: + * - `[i]` for sequences + * - `/` for super-sources (groups, directories, clusters) + * - `#` for sub-sources (blocks) + * @this {CollectionItemThis} + * @returns {string} The unique key + */ + get_key() { + return create_uid(this.data); + } + /** + * Updates the item data and returns true if changed. + * @this {CollectionItemThis} + * @param {Partial} data + * @returns {boolean} True if data changed. + */ + update_data(data) { + const sanitized_data = this.sanitize_data(data); + const current_data = { ...this.data }; + deep_merge(current_data, sanitized_data); + const changed = !deep_equal(this.data, current_data); + if (!changed) return false; + this.data = current_data; + return true; + } + /** + * Sanitizes data for saving. Ensures no circular references. + * @this {CollectionItemThis} + * @param {*} data + * @returns {*} Sanitized data. + */ + sanitize_data(data) { + if (data instanceof _CollectionItem) return data.ref; + if (Array.isArray(data)) return data.map((val) => this.sanitize_data(val)); + if (typeof data === "object" && data !== null) { + return Object.keys(data).reduce((acc, key) => { + acc[key] = this.sanitize_data(data[key]); + return acc; + }, {}); + } + return data; + } + /** + * Initializes the item. Override as needed. + * @param {Partial} [input_data] - Additional data that might be provided on creation. + * @returns {*} + */ + init(input_data) { + } + /** + * Queues this item for saving. + * @this {CollectionItemThis} + */ + queue_save() { + this._queue_save = true; + } + /** + * Saves this item using its data adapter. + * @this {CollectionItemThis} + * @returns {Promise} + */ + async save() { + try { + await this.data_adapter.save_item(this); + this.init(); + } catch (err) { + this._queue_save = true; + console.error(err, err.stack); + } + } + /** + * Queues this item for loading. + * @this {CollectionItemThis} + */ + queue_load() { + this._queue_load = true; + } + /** + * Loads this item using its data adapter. + * @this {CollectionItemThis} + * @returns {Promise} + */ + async load() { + try { + await this.data_adapter.load_item(this); + this.init(); + } catch (err) { + this._load_error = err; + this.on_load_error(err); + } + } + /** + * Handles load errors by re-queuing for load. + * Override if needed. + * @this {CollectionItemThis} + * @param {Error} err + */ + on_load_error(err) { + this.queue_load(); + } + /** + * Validates the item before saving. Checks for presence and validity of key. + * @deprecated should be better handled 2025-12-17 (wrong scope?) + * @this {CollectionItemThis} + * @returns {boolean} + */ + validate_save() { + if (!this.key) return false; + if (this.key.trim() === "") return false; + if (this.key === "undefined") return false; + return true; + } + /** + * Marks this item as deleted. This does not immediately remove it from memory, + * but queues a save that will result in the item being removed from persistent storage. + * @this {CollectionItemThis} + */ + delete() { + this.deleted = true; + this.queue_save(); + } + /** + * Filters items in the collection based on provided options. + * functional filter (returns true or false) for filtering items in collection; called by collection class + * @this {CollectionItemThis} + * @param {CollectionFilterOptions} filter_opts - Filtering options. + * @returns {boolean} True if the item passes the filter, false otherwise. + */ + filter(filter_opts = {}) { + const { + exclude_key, + exclude_keys = exclude_key ? [exclude_key] : [], + exclude_key_starts_with, + exclude_key_starts_with_any, + exclude_key_includes, + exclude_key_includes_any, + exclude_key_ends_with, + exclude_key_ends_with_any, + key_ends_with, + key_starts_with, + key_starts_with_any, + key_includes, + key_includes_any + } = filter_opts; + if (exclude_keys?.includes(this.key)) return false; + if (exclude_key_starts_with && this.key.startsWith(exclude_key_starts_with)) return false; + if (exclude_key_starts_with_any && exclude_key_starts_with_any.some((prefix) => this.key.startsWith(prefix))) return false; + if (exclude_key_includes && this.key.includes(exclude_key_includes)) return false; + if (exclude_key_includes_any && exclude_key_includes_any.some((include) => this.key.includes(include))) return false; + if (exclude_key_ends_with && this.key.endsWith(exclude_key_ends_with)) return false; + if (exclude_key_ends_with_any && exclude_key_ends_with_any.some((suffix) => this.key.endsWith(suffix))) return false; + if (key_ends_with && !this.key.endsWith(key_ends_with)) return false; + if (key_starts_with && !this.key.startsWith(key_starts_with)) return false; + if (key_starts_with_any && !key_starts_with_any.some((prefix) => this.key.startsWith(prefix))) return false; + if (key_includes && !this.key.includes(key_includes)) return false; + if (key_includes_any && !key_includes_any.some((include) => this.key.includes(include))) return false; + return true; + } + /** + * @this {CollectionItemThis} + * @param {Object.} [params={}] + * @returns {CollectionScoreResult|null} + */ + filter_and_score(params = {}) { + if (this.filter(params.filter) === false) return null; + return this.score(params); + } + /** + * @this {CollectionItemThis} + * @param {Object.} [params={}] + * @returns {CollectionScoreResult} + */ + score(params = {}) { + const score_action = this.actions[params.score_algo_key]; + if (typeof score_action !== "function") throw new Error(`Missing score action: ${params.score_algo_key}`); + return { + ...score_action(params) || {}, + item: this + }; + } + /** + * @this {CollectionItemThis} + * @returns {Object} + */ + get actions() { + if (!this._actions) { + this._actions = create_actions_proxy(this, { + ...this.env.config.actions || {}, + // main actions scope for actions/ exports + ...this.env.opts.items?.[this.item_type_key]?.actions || {} + // DEPRECATED OR KEEP? + }); + } + return this._actions; + } + /** + * Derives the collection key from the class name. + * @returns {string} + */ + static get collection_key() { + let name = this.name; + if (name.match(/\d$/)) name = name.slice(0, -1); + return collection_instance_name_from(name); + } + /** + * @returns {string} The collection key for this item. + */ + get collection_key() { + let name = this.constructor.name; + if (name.match(/\d$/)) name = name.slice(0, -1); + return collection_instance_name_from(name); + } + /** + * Retrieves the parent collection from the environment. + * @this {CollectionItemThis} + * @returns {Collection} + */ + get collection() { + return this.env[this.collection_key]; + } + /** + * @this {CollectionItemThis} + * @returns {string} The item's key. + */ + get key() { + return this.data?.key || this.get_key(); + } + /** + * @returns {string} + */ + get item_type_key() { + let name = this.constructor.name; + if (name.match(/\d$/)) name = name.slice(0, -1); + return camel_case_to_snake_case(name); + } + /** + * Emits an event with item metadata. + * + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_event(event_key, payload = {}) { + this.env.events?.emit(event_key, { collection_key: this.collection_key, item_key: this.key, ...payload }); + } + /** + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_info_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "info", ...payload }); + } + /** + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_warning_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "warning", ...payload }); + } + /** + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_error_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "error", ...payload }); + } + /** + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventCallback} callback + * @returns {*} + */ + on_event(event_key, callback) { + return this.env.events?.on(event_key, (payload) => { + if (payload?.item_key && payload.item_key !== this.key) return; + callback(payload); + }); + } + /** + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventCallback} callback + * @returns {*} + */ + once_event(event_key, callback) { + return this.env.events?.once(event_key, (payload) => { + if (payload?.item_key && payload.item_key !== this.key) return; + callback(payload); + }); + } + /** + * @this {CollectionItemThis} + * @returns {*} The data adapter for this item's collection. + */ + get data_adapter() { + return this.collection.data_adapter; + } + /** + * @this {CollectionItemThis} + * @returns {Object} The filesystem adapter. + */ + get data_fs() { + return this.collection.data_fs; + } + /** + * Access to collection-level settings. + * @this {CollectionItemThis} + * @returns {Object} + */ + get settings() { + if (!this.env.settings[this.collection_key]) this.env.settings[this.collection_key] = {}; + return this.env.settings[this.collection_key]; + } + /** + * @this {CollectionItemThis} + * @param {Object.} settings + */ + set settings(settings) { + this.env.settings[this.collection_key] = settings; + this.env.smart_settings.save(); + } + /** + * A simple reference object for this item. + * @deprecated 2025-11-11 lacks adoption + * @this {CollectionItemThis} + * @returns {CollectionItemRef} + */ + get ref() { + return { collection_key: this.collection_key, key: this.key }; + } + /** + * @deprecated use env.smart_components~~env.smart_view~~ instead + * @this {CollectionItemThis} + */ + get smart_view() { + if (!this._smart_view) this._smart_view = this.env.init_module("smart_view"); + return this._smart_view; + } + /** + * Retrieves the display name of the collection item. + * @readonly + * @deprecated Use `get_item_display_name(key, show_full_path)` instead (keep UI logic out of collections). + * @this {CollectionItemThis} + * @returns {string} The display name. + */ + get name() { + return get_item_display_name( + this.key, + this.env.settings.smart_view_filter?.show_full_path + ); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-collections/collection.js +var AsyncFunction = Object.getPrototypeOf(async function() { +}).constructor; +var QUEUE_SAVE_DEBOUNCE_MS = 750; +var Collection = class { + /** @type {string|number} */ + static version = 1e-3; + /** + * Constructs a new Collection instance. + * + * @this {*} + * @param {CollectionEnv} env - The environment context containing configurations and adapters. + * @param {CollectionOptions} [opts={}] - Optional configuration. + */ + constructor(env, opts = {}) { + env.create_env_getter(this); + this.opts = opts; + if (opts.collection_key) this.collection_key = opts.collection_key; + this.env[this.collection_key] = this; + this.config = this.env.config; + this.items = {}; + this.loaded = null; + this._loading = false; + this.load_time_ms = null; + this.settings_container = null; + } + /** + * Initializes a new collection in the environment. Override in subclass if needed. + * + * @param {CollectionEnv} env + * @param {CollectionOptions} [opts={}] + * @returns {Promise} + */ + static async init(env, opts = {}) { + env[this.collection_key] = new this(env, opts); + await env[this.collection_key].init(); + env.collections[this.collection_key] = "init"; + } + /** + * The unique collection key derived from the class name. + * @returns {string} + */ + static get collection_key() { + let name = this.name; + if (name.match(/\d$/)) name = name.slice(0, -1); + return name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); + } + /** + * Instance-level init. Override in subclasses if necessary. + * @returns {Promise} + */ + async init() { + } + /** + * Creates or updates an item in the collection. + * - If `data` includes a key that matches an existing item, that item is updated. + * - Otherwise, a new item is created. + * After updating or creating, the item is validated. If validation fails, the item is logged and returned without being saved. + * If validation succeeds for a new item, it is added to the collection and marked for saving. + * + * If the item’s `init()` method is async, a promise is returned that resolves once init completes. + * + * NOTE: wrapping in try/catch seems to fail to catch errors thrown in async init functions when awaiting create_or_update + * + * @this {CollectionThis} + * @param {Partial} [data={}] - Data for creating/updating an item. + * @returns {Promise|CollectionItemInstance} The created or updated item. May return a promise if `init()` is async. + */ + create_or_update(data = {}) { + const existing_item = this.find_by(data); + const item = existing_item ? existing_item : new this.item_type(this.env); + item._queue_save = !existing_item; + const data_changed = item.update_data(data); + if (!existing_item && !item.validate_save()) { + return item; + } + if (!existing_item) { + this.set(item); + } + if (existing_item && !data_changed) return existing_item; + if (item.init instanceof AsyncFunction) { + return new Promise((resolve) => { + item.init(data).then(() => resolve(item)); + }); + } + item.init(data); + return item; + } + /** + * Finds an item by partial data match (first checks key). If `data.key` provided, + * returns the item with that key; otherwise attempts a match by merging data. + * + * @this {CollectionThis} + * @param {Partial} data - Data to match against. + * @returns {CollectionItemInstance|null|undefined} + */ + find_by(data) { + if (data.key) return this.get(data.key); + const temp = new this.item_type(this.env); + const temp_data = JSON.parse(JSON.stringify(data, temp.sanitize_data(data))); + deep_merge(temp.data, temp_data); + return temp.key ? this.get(temp.key) : null; + } + /** + * Filters items based on provided filter options or a custom function. + * + * @this {CollectionThis} + * @param {*} [filter_opts={}] - Filter options or a predicate function. + * @returns {CollectionItemInstance[]} Array of filtered items. + */ + filter(filter_opts = {}) { + if (typeof filter_opts === "function") { + return Object.values(this.items).filter(filter_opts); + } + const results = []; + const { first_n } = filter_opts; + for (const item of Object.values(this.items)) { + if (first_n && results.length >= first_n) break; + if (item.filter(filter_opts)) results.push(item); + } + return results; + } + /** + * Alias for `filter()` + * @this {CollectionThis} + * @param {*} filter_opts + * @returns {CollectionItemInstance[]} + */ + list(filter_opts) { + return this.filter(filter_opts); + } + /** + * Retrieves an item by key. + * @this {CollectionThis} + * @param {string} key + * @returns {CollectionItemInstance|undefined} + */ + get(key) { + return this.items[key]; + } + /** + * Retrieves multiple items by an array of keys. + * @this {CollectionThis} + * @param {string[]} keys + * @returns {Array<*>} + */ + get_many(keys = []) { + if (!Array.isArray(keys)) { + console.error("get_many called with non-array keys:", keys); + return []; + } + return keys.map((key) => this.get(key)).filter(Boolean); + } + /** + * Retrieves a random item from the collection, optionally filtered by options. + * @this {CollectionThis} + * @param {*} [opts] + * @returns {CollectionItemInstance|undefined} + */ + get_rand(opts = null) { + if (opts) { + const filtered = this.filter(opts); + return filtered[Math.floor(Math.random() * filtered.length)]; + } + const keys = this.keys; + return this.items[keys[Math.floor(Math.random() * keys.length)]]; + } + /** + * Adds or updates an item in the collection. + * @this {CollectionThis} + * @param {CollectionItemInstance} item + */ + set(item) { + if (!item.key) throw new Error("Item must have a key property"); + this.items[item.key] = item; + } + /** + * Updates multiple items by their keys. + * @this {CollectionThis} + * @param {string[]} keys + * @param {Partial} data + */ + update_many(keys = [], data = {}) { + this.get_many(keys).forEach((item) => item.update_data(data)); + } + /** + * Clears all items from the collection. + * @this {CollectionThis} + */ + clear() { + this.items = {}; + } + /** + * @this {CollectionThis} + * @returns {string} The collection key, can be overridden by opts.collection_key + */ + get collection_key() { + return this._collection_key ? this._collection_key : this.constructor.collection_key; + } + set collection_key(key) { + this._collection_key = key; + } + /** + * Lazily initializes and returns the data adapter instance for this collection. + * @this {CollectionThis} + * @returns {CollectionDataAdapterInstance} The data adapter instance. + */ + get data_adapter() { + if (!this._data_adapter) { + const AdapterClass = this.get_adapter_class("data"); + this._data_adapter = new AdapterClass(this); + } + return this._data_adapter; + } + /** + * @private + * @this {CollectionThis} + * @param {string} type + * @returns {*} + */ + get_adapter_class(type) { + const config = this.env.opts.collections?.[this.collection_key]; + const adapter_key = type + "_adapter"; + const adapter_module = config?.[adapter_key] ?? this.env.opts.collections?.smart_collections?.[adapter_key]; + if (typeof adapter_module === "function") return adapter_module; + if (typeof adapter_module?.collection === "function") return adapter_module.collection; + throw new Error(`No '${type}' adapter class found for ${this.collection_key} or smart_collections`); + } + /** + * Data directory strategy for this collection. Defaults to 'multi'. + * @deprecated should be handled in adapters (2025-12-09) + * @this {CollectionThis} + * @returns {string} + */ + get data_dir() { + return this.collection_key; + } + /** + * File system adapter from the environment. + * @this {CollectionThis} + * @returns {FileSystem} + */ + get data_fs() { + return this.env.data_fs; + } + /** + * Derives the corresponding item class name based on this collection's class name. + * @returns {string} + */ + get item_class_name() { + let name = this.constructor.name; + if (name.match(/\d$/)) name = name.slice(0, -1); + if (name.endsWith("ies")) return name.slice(0, -3) + "y"; + else if (name.endsWith("s")) return name.slice(0, -1); + return name + "Item"; + } + /** + * Derives a readable item name from the item class name. + * @this {CollectionThis} + * @returns {string} + */ + get item_name() { + return this.item_class_name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); + } + /** + * Retrieves the item type (constructor) from the environment. + * @deprecated replace with item_class with strict adherence to conventions (2025-10-28) + * @this {CollectionThis} + * @returns {CollectionItemConstructor} Item constructor. + */ + get item_type() { + if (!this._item_type) this._item_type = this.resolve_item_type(); + return this._item_type; + } + // TEMP resolver (2025-11-03): until better handled on merging configs at obsidian-smart-env startup + /** + * @private + * @this {CollectionThis} + * @returns {*} + */ + resolve_item_type() { + const available = [ + this.env.config?.items?.[this.item_name], + this.opts.item_type + // Is this necessary? (2026-04-11 needs review - ideally should be able to rely on env.config for this) + ].filter(Boolean).sort((a, b) => { + const a_version = a?.class?.version || a.version || 0; + const b_version = b?.class?.version || b.version || 0; + return b_version - a_version; + }); + if (available.length === 0) { + throw new Error(`No item_type found for collection '${this.collection_key}' with item_name '${this.item_name}' or class_name '${this.item_class_name}'`); + } + return available[0].class || available[0]; + } + /** + * Returns an array of all keys in the collection. + * @this {CollectionThis} + * @returns {string[]} + */ + get keys() { + return Object.keys(this.items); + } + /** + * @deprecated use data_adapter instead (2024-09-14) + * @this {CollectionThis} + */ + get adapter() { + return this.data_adapter; + } + /** + * @method process_save_queue + * @description + * Saves items flagged for saving (_queue_save) back to AJSON or SQLite. This ensures persistent storage + * of any updates made since last load/import. This method also writes changes to disk (AJSON files or DB). + * @this {CollectionThis} + * @param {CollectionQueueOptions} [opts={}] + * @returns {Promise} + */ + async process_save_queue(opts = {}) { + if (opts.force) { + Object.values(this.items).forEach((item) => item._queue_save = true); + } + await this.data_adapter.process_save_queue(opts); + } + /** + * @alias process_save_queue + * @this {CollectionThis} + * @param {CollectionQueueOptions} [opts={}] + * @returns {Promise} + */ + async save(opts = {}) { + await this.process_save_queue(opts); + } + /** + * @method process_load_queue + * @description + * Loads items that have been flagged for loading (_queue_load). This may involve + * reading from AJSON/SQLite or re-importing from markdown if needed. + * Called once initial environment is ready and collections are known. + * @this {CollectionThis} + * @returns {Promise} + */ + async process_load_queue() { + await this.data_adapter.process_load_queue(); + } + /** + * Retrieves processed settings configuration. + * @this {CollectionThis} + * @returns {SettingsConfig} + */ + get settings_config() { + return this.process_settings_config({}); + } + /** + * Processes given settings config, adding prefixes and handling conditionals. + * @deprecated removing settings_config from collections (2025-11-24) + * + * @private + * @this {CollectionThis} + * @param {SettingsConfig} _settings_config + * @param {string} [prefix=''] + * @returns {SettingsConfig} + */ + process_settings_config(_settings_config, prefix = "") { + const add_prefix = (key) => prefix && !key.includes(`${prefix}.`) ? `${prefix}.${key}` : key; + return Object.entries(_settings_config).reduce((acc, [key, val]) => { + let new_val = { ...val }; + if (new_val.conditional) { + if (!new_val.conditional(this)) return acc; + delete new_val.conditional; + } + if (new_val.callback) new_val.callback = add_prefix(new_val.callback); + if (new_val.btn_callback) new_val.btn_callback = add_prefix(new_val.btn_callback); + if (new_val.options_callback) new_val.options_callback = add_prefix(new_val.options_callback); + const new_key = add_prefix(this.process_setting_key(key)); + acc[new_key] = new_val; + return acc; + }, {}); + } + /** + * Processes an individual setting key. Override if needed. + * @param {string} key + * @returns {string} + */ + process_setting_key(key) { + return key; + } + /** + * Default settings for this collection. Override in subclasses as needed. + * @returns {Object} + */ + get default_settings() { + return {}; + } + /** + * Current settings for the collection. + * Initializes with default settings if none exist. + * @this {CollectionThis} + * @returns {Object} + */ + get settings() { + if (!this.env.settings[this.collection_key]) { + this.env.settings[this.collection_key] = this.default_settings; + } + return this.env.settings[this.collection_key]; + } + /** + * Unloads collection data from memory. + * @this {*} + * @returns {*} + */ + unload() { + this.clear(); + this.unloaded = true; + this.env.collections[this.collection_key] = null; + } + /** + * Emits an event with collection metadata. + * + * @this {CollectionThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_event(event_key, payload = {}) { + this.env.events?.emit(event_key, { collection_key: this.collection_key, ...payload }); + } + /** + * @this {CollectionThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_info_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "info", ...payload }); + } + /** + * @this {CollectionThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_warning_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "warning", ...payload }); + } + /** + * @this {CollectionThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_error_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "error", ...payload }); + } + /** + * @this {CollectionThis} + * @param {string} event_key + * @param {CollectionEventCallback} callback + * @returns {*} + */ + on_event(event_key, callback) { + return this.env.events?.on(event_key, (payload) => { + if (payload?.collection_key && payload.collection_key !== this.collection_key) return; + callback(payload); + }); + } + /** + * Lazily binds action functions to the collection instance. + * + * @this {CollectionThis} + * @returns {Object} Bound action functions keyed by name. + */ + get actions() { + if (!this.constructor.key) this.constructor.key = this.collection_key; + if (!this._actions) { + const actions_modules = { + ...this.env?.config?.actions || {}, + ...this.env?.config?.collections?.[this.collection_key]?.actions || {}, + ...this.env?.opts?.collections?.[this.collection_key]?.actions || {}, + ...this.opts?.actions || {} + }; + this._actions = create_actions_proxy(this, actions_modules); + } + return this._actions; + } + /** + * Clears cached actions proxy and rebuilds on next access. + * @this {CollectionThis} + * @returns {Object} Rebuilt proxy with latest source snapshot. + */ + refresh_actions() { + this._actions = null; + return this.actions; + } + // debounce running process save queue + /** + * @this {CollectionThis} + * @returns {void} + */ + queue_save() { + if (this._debounce_queue_save) clearTimeout(this._debounce_queue_save); + this._debounce_queue_save = setTimeout(() => { + this.process_save_queue(); + }, this.queue_save_debounce_ms || QUEUE_SAVE_DEBOUNCE_MS); + } + // BEGIN DEPRECATED + /** + * @deprecated use env.smart_components~~env.smart_view~~ instead (2026-02-11) + * @this {CollectionThis} + * @returns {Object} smart_view instance + */ + get smart_view() { + if (!this._smart_view) this._smart_view = this.env.init_module("smart_view"); + return this._smart_view; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-entities/adapters/_adapter.js +var EntitiesVectorAdapter = class { + /** + * @constructor + * @param {*} collection - The collection (SmartEntities or derived class) instance. + */ + constructor(collection) { + this.collection = collection; + } + /** + * Find the nearest entities to the given vector. + * @async + * @param {number[]} vec - The reference vector. + * @param {Object} [filter={}] - Optional filters (limit, exclude, etc.) + * @returns {Promise>} Array of results sorted by score descending. + * @abstract + * @throws {Error} Not implemented by default. + */ + async nearest(vec, filter = {}) { + throw new Error("EntitiesVectorAdapter.nearest() not implemented"); + } + /** + * Find the furthest entities from the given vector. + * @async + * @param {number[]} vec - The reference vector. + * @param {Object} [filter={}] - Optional filters (limit, exclude, etc.) + * @returns {Promise>} Array of results sorted by score ascending (furthest). + * @abstract + * @throws {Error} Not implemented by default. + */ + async furthest(vec, filter = {}) { + throw new Error("EntitiesVectorAdapter.furthest() not implemented"); + } + /** + * Embed a batch of entities. + * @async + * @param {Array<*>} entities - Array of entity instances to embed. + * @returns {Promise} + * @abstract + * @throws {Error} Not implemented by default. + */ + async embed_batch(entities) { + throw new Error("EntitiesVectorAdapter.embed_batch() not implemented"); + } + /** + * Process a queue of entities waiting to be embedded. + * Typically, this will call embed_batch in batches and update entities. + * @async + * @param {Array<*>} [embed_queue] - Array of entities to embed. + * @returns {Promise} + * @abstract + * @throws {Error} Not implemented by default. + */ + async process_embed_queue(embed_queue) { + throw new Error("EntitiesVectorAdapter.process_embed_queue() not implemented"); + } +}; +var EntityVectorAdapter = class { + /** + * @constructor + * @param {*} item - The SmartEntity instance that this adapter is associated with. + */ + constructor(item) { + this.item = item; + } + /** + * Retrieve the current vector embedding for this entity. + * @async + * @returns {Promise} The entity's vector or undefined if not set. + * @abstract + * @throws {Error} Not implemented by default. + */ + async get_vec() { + throw new Error("EntityVectorAdapter.get_vec() not implemented"); + } + /** + * Store/update the vector embedding for this entity. + * @async + * @param {number[]|null} vec - The vector to set. + * @returns {Promise} + * @abstract + * @throws {Error} Not implemented by default. + */ + async set_vec(vec) { + throw new Error("EntityVectorAdapter.set_vec() not implemented"); + } + /** + * Delete/remove the vector embedding for this entity. + * @async + * @returns {Promise} + * @abstract + * @throws {Error} Not implemented by default. + */ + async delete_vec() { + throw new Error("EntityVectorAdapter.delete_vec() not implemented"); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-utils/results_acc.js +function results_acc(_acc, result, ct = 10) { + if (_acc.results.size < ct) { + _acc.results.add(result); + if (_acc.results.size === ct && _acc.min === Number.POSITIVE_INFINITY) { + let { minScore, minObj } = find_min(_acc.results); + _acc.min = minScore; + _acc.minResult = minObj; + } + } else if (result.score > _acc.min) { + _acc.results.add(result); + _acc.results.delete(_acc.minResult); + let { minScore, minObj } = find_min(_acc.results); + _acc.min = minScore; + _acc.minResult = minObj; + } +} +function furthest_acc(_acc, result, ct = 10) { + if (_acc.results.size < ct) { + _acc.results.add(result); + if (_acc.results.size === ct && _acc.max === Number.NEGATIVE_INFINITY) { + let { maxScore, maxObj } = find_max(_acc.results); + _acc.max = maxScore; + _acc.maxResult = maxObj; + } + } else if (result.score < _acc.max) { + _acc.results.add(result); + _acc.results.delete(_acc.maxResult); + let { maxScore, maxObj } = find_max(_acc.results); + _acc.max = maxScore; + _acc.maxResult = maxObj; + } +} +function find_min(results) { + let minScore = Number.POSITIVE_INFINITY; + let minObj = null; + for (const obj of results) { + if (obj.score < minScore) { + minScore = obj.score; + minObj = obj; + } + } + return { minScore, minObj }; +} +function find_max(results) { + let maxScore = Number.NEGATIVE_INFINITY; + let maxObj = null; + for (const obj of results) { + if (obj.score > maxScore) { + maxScore = obj.score; + maxObj = obj; + } + } + return { maxScore, maxObj }; +} + +// node_modules/obsidian-smart-env/node_modules/smart-utils/sort_by_score.js +function sort_by_score(a, b) { + const epsilon = 1e-9; + const score_diff = a.score - b.score; + if (Math.abs(score_diff) < epsilon) return 0; + return score_diff > 0 ? -1 : 1; +} +function sort_by_score_descending(a, b) { + return sort_by_score(a, b); +} +function sort_by_score_ascending(a, b) { + return sort_by_score(a, b) * -1; +} + +// node_modules/obsidian-smart-env/node_modules/smart-entities/adapters/default.js +var DefaultEntitiesVectorAdapter = class extends EntitiesVectorAdapter { + /** + * @this {DefaultEntitiesVectorAdapterThis} + * @param {*} collection + */ + constructor(collection) { + super(collection); + this._is_processing_embed_queue = false; + this._resume_after_pause = false; + this._resume_after_pause_delay = 0; + this._resume_embed_timeout = null; + this._reset_embed_queue_stats(); + } + /** + * Find the nearest entities to the given vector. + * @async + * @this {DefaultEntitiesVectorAdapterThis} + * @param {number[]} vec - The reference vector. + * @param {Object} [filter={}] - Optional filters (limit, exclude, etc.) + * @returns {Promise>} Array of results sorted by score descending. + */ + async nearest(vec, filter = {}) { + if (!vec || !Array.isArray(vec)) { + throw new Error("Invalid vector input to nearest()"); + } + const { + limit = 50 + } = filter; + const nearest = this.collection.filter(filter).reduce((acc, item) => { + if (!item.vec) return acc; + const result = { item, score: cos_sim(vec, item.vec) }; + results_acc(acc, result, limit); + return acc; + }, { min: 0, results: /* @__PURE__ */ new Set() }); + return Array.from(nearest.results).sort(sort_by_score_descending); + } + /** + * Find the furthest entities from the given vector. + * @async + * @this {DefaultEntitiesVectorAdapterThis} + * @param {number[]} vec - The reference vector. + * @param {Object} [filter={}] - Optional filters (limit, exclude, etc.) + * @returns {Promise>} Array of results sorted by score ascending (furthest). + */ + async furthest(vec, filter = {}) { + if (!vec || !Array.isArray(vec)) { + throw new Error("Invalid vector input to furthest()"); + } + const { + limit = 50 + } = filter; + const furthest = this.collection.filter(filter).reduce((acc, item) => { + if (!item.vec) return acc; + const result = { item, score: cos_sim(vec, item.vec) }; + furthest_acc(acc, result, limit); + return acc; + }, { max: 0, results: /* @__PURE__ */ new Set() }); + return Array.from(furthest.results).sort(sort_by_score_ascending); + } + /** + * Embed a batch of entities. + * @async + * @this {DefaultEntitiesVectorAdapterThis} + * @param {Object[]} entities - Array of entity instances to embed. + * @returns {Promise} + */ + async embed_batch(entities) { + if (!this.collection.embed_model) { + throw new Error("No embed_model found in collection for embedding"); + } + await Promise.all(entities.map((entity) => entity.get_embed_input())); + const embeddings = await this.collection.embed_model.embed_batch(entities); + embeddings.forEach((embedding, index) => { + const entity = entities[index]; + entity.vec = embedding.vec; + entity.data.last_embed = entity.data.last_read; + if (embedding.tokens !== void 0) entity.tokens = embedding.tokens; + }); + } + /** + * Process a queue of entities waiting to be embedded. + * Prevents multiple concurrent runs by using `_is_processing_embed_queue`. + * Paused queues fail closed and do not restart until resume explicitly clears + * the paused state. + * @async + * @this {DefaultEntitiesVectorAdapterThis} + * @returns {Promise} + */ + async process_embed_queue() { + if (this._is_processing_embed_queue) { + console.log("process_embed_queue is already running, skipping concurrent call."); + return; + } + if (this.is_embed_queue_paused() && !this._resume_after_pause) { + console.log("process_embed_queue is paused, skipping restart until resume."); + return; + } + this._is_processing_embed_queue = true; + this._embed_run_error = false; + try { + if (!this.collection.embed_model.is_loaded) { + await this.collection.embed_model.load(); + } + } catch (error) { + this.collection.emit_event("embed_model:load_failed", { + event_source: "process_embed_queue" + }); + this._emit_embedding_error({ + message: `Failed to load embedding model ${this.collection.embed_model_key}.`, + details: error?.message || String(error || "") + }); + return; + } + try { + const datetime_start = Date.now(); + console.log(`Getting embed queue for ${this.collection.collection_key}...`); + await new Promise((resolve) => setTimeout(resolve, 1)); + const embed_queue = this.collection.embed_queue; + this._reset_embed_queue_stats(); + const embedded_keys_by_collection = {}; + if (this.collection.embed_model_key === "None") { + console.log(`Smart Connections: No active embedding model for ${this.collection.collection_key}, skipping embedding`); + return; + } + if (!this.collection.embed_model) { + console.log(`Smart Connections: No active embedding model for ${this.collection.collection_key}, skipping embedding`); + return; + } + if (!embed_queue.length) { + console.log(`Smart Connections: No items in ${this.collection.collection_key} embed queue`); + return; + } + console.log(`Time spent getting embed queue: ${Date.now() - datetime_start}ms`); + console.log(`Processing ${this.collection.collection_key} embed queue: ${embed_queue.length} items`); + this.current_queue_total = embed_queue.length; + this._start_embed_progress_state(embed_queue.length); + for (let index = 0; index < embed_queue.length; index += this.collection.embed_model.batch_size) { + if (this.is_queue_halted) { + break; + } + const batch = embed_queue.slice(index, index + this.collection.embed_model.batch_size); + await Promise.all(batch.map((item) => item.get_embed_input())); + try { + const start_time = Date.now(); + await this.embed_batch(batch); + this.total_time += Date.now() - start_time; + } catch (error) { + console.error(error); + console.error(`Error processing ${this.collection.collection_key} embed queue: ` + JSON.stringify(error || {}, null, 2)); + this._emit_embedding_error({ + message: `Embedding failed while processing ${this.collection.collection_key}.`, + details: error?.message || JSON.stringify(error || {}, null, 2) + }); + break; + } + batch.forEach((item) => { + item.embed_hash = item.read_hash; + item._queue_save = true; + embedded_keys_by_collection[item.collection_key] ||= []; + embedded_keys_by_collection[item.collection_key].push(item.key); + }); + this.embedded_total += batch.length; + this.total_tokens += batch.reduce((acc, item) => acc + (item.tokens || 0), 0); + const processed_all2 = this.embedded_total >= embed_queue.length; + if (this.is_queue_halted && !processed_all2) { + this._update_paused_progress_state(embed_queue.length, this.progress_state?.reason || ""); + } else { + this._update_embed_progress_state(embed_queue.length); + } + if (this.is_queue_halted && processed_all2) { + this.is_queue_halted = false; + } + if (this.should_show_embed_progress_notice || processed_all2) { + this._show_embed_progress_notice(embed_queue.length); + } + if (this.embedded_total - this.last_save_total > 99) { + this.last_save_total = this.embedded_total; + await this.collection.process_save_queue(); + if (this.collection.block_collection) { + console.log(`Saving ${this.collection.block_collection.collection_key} block collection`); + await this.collection.block_collection.process_save_queue(); + } + } + } + Object.entries(embedded_keys_by_collection).forEach(([collection_key, keys]) => { + this.collection.env.events?.emit("items:embedded", { + collection_key, + keys, + event_source: "process_embed_queue", + skip_save_log_collection: true + }); + }); + const processed_all = this.embedded_total >= embed_queue.length; + const is_paused = Boolean(this.progress_state?.paused) && !processed_all; + if (!is_paused && !this._embed_run_error) { + this._show_embed_completion_notice(embed_queue.length); + } + await this.collection.process_save_queue(); + if (this.collection.block_collection) { + await this.collection.block_collection.process_save_queue(); + } + } finally { + this._is_processing_embed_queue = false; + const should_resume_after_pause = this._resume_after_pause && this.is_embed_queue_paused(); + const resume_delay = this._resume_after_pause_delay || 0; + this._resume_after_pause = false; + this._resume_after_pause_delay = 0; + if (should_resume_after_pause) { + this.resume_embed_queue_processing(resume_delay); + } + } + } + /** + * @this {DefaultEntitiesVectorAdapterThis} + * @returns {boolean} + */ + get should_show_embed_progress_notice() { + if (Date.now() - (this.last_notice_time ?? 0) > 2e4) { + return true; + } + return this.embedded_total - this.last_notice_embedded_total >= 100; + } + /** + * @this {DefaultEntitiesVectorAdapterThis} + * @returns {object|null} + */ + get_progress_state() { + return this.progress_state ? { ...this.progress_state } : null; + } + /** + * Displays embed progress via env events and internal state. + * @private + * @this {DefaultEntitiesVectorAdapterThis} + * @param {number} embed_queue_length + * @returns {void} + */ + _show_embed_progress_notice(embed_queue_length) { + this.last_notice_time = Date.now(); + this.last_notice_embedded_total = this.embedded_total; + this._update_embed_progress_state(embed_queue_length); + this.collection.emit_event("embedding:progress", { + progress: this.embedded_total, + total: embed_queue_length, + tokens_per_second: this._calculate_embed_tokens_per_second(), + model_name: this.collection.embed_model_key, + event_source: "process_embed_queue", + skip_save_log_collection: true + }); + } + /** + * Displays the embedding completion notice. + * @private + * @this {DefaultEntitiesVectorAdapterThis} + * @param {number} embed_queue_length + * @returns {void} + */ + _show_embed_completion_notice(embed_queue_length) { + const payload = { + total_embeddings: this.embedded_total, + total: embed_queue_length, + tokens_per_second: this._calculate_embed_tokens_per_second(), + model_name: this.collection.embed_model_key, + event_source: "process_embed_queue" + }; + this._set_progress_state(null); + if (this.embedded_total > 100) { + this.collection.emit_event("embedding:completed", { + level: "info", + message: `Embedding completed for ${this.embedded_total} item${this.embedded_total === 1 ? "" : "s"}.`, + ...payload + }); + return; + } + this.collection.emit_event("embedding:completed", payload); + } + /** + * Halts the embed queue processing. + * The current batch is allowed to finish, then the next loop iteration latches + * the paused state and exits. This keeps the status bar stable and prevents a + * half-finished batch from corrupting queue state. + * Duplicate pause requests fail closed and do not emit extra paused events. + * + * @this {DefaultEntitiesVectorAdapterThis} + * @param {string|null} msg - Optional message. + * @returns {void} + */ + halt_embed_queue_processing(msg = null) { + const total = this.progress_state?.total || this.current_queue_total || 0; + const next_reason = msg || this.progress_state?.reason || ""; + if (this.is_embed_queue_paused()) { + this._update_paused_progress_state(total, next_reason); + return; + } + this.is_queue_halted = true; + this._set_progress_state({ + active: true, + paused: true, + progress: this.embedded_total, + total, + tokens_per_second: this._calculate_embed_tokens_per_second(), + model_name: this.collection.embed_model_key, + reason: next_reason + }); + this.collection.emit_event("embedding:paused", { + level: "attention", + message: `Embedding paused at ${this.embedded_total}/${total}.`, + details: next_reason, + progress: this.embedded_total, + total, + tokens_per_second: this._calculate_embed_tokens_per_second(), + model_name: this.collection.embed_model_key, + event_source: "halt_embed_queue_processing" + }); + } + /** + * Returns whether the adapter is currently paused. + * Paused state remains sticky until resume explicitly clears it. + * @this {DefaultEntitiesVectorAdapterThis} + * @returns {boolean} + */ + is_embed_queue_paused() { + return Boolean(this.progress_state?.paused); + } + /** + * Resumes the embed queue processing after a delay. + * If the active batch has not yet latched the pause request, resume is deferred + * until the current run exits cleanly. + * @this {DefaultEntitiesVectorAdapterThis} + * @param {number} [delay=0] - The delay in milliseconds before resuming. + * @returns {void} + */ + resume_embed_queue_processing(delay = 0) { + console.log("resume_embed_queue_processing"); + if (this._resume_embed_timeout) { + clearTimeout(this._resume_embed_timeout); + this._resume_embed_timeout = null; + } + if (this._is_processing_embed_queue && this.is_queue_halted) { + this._resume_after_pause = true; + this._resume_after_pause_delay = delay; + return; + } + this.is_queue_halted = false; + this._set_progress_state(null); + this.collection.emit_event("embedding:resumed", { + model_name: this.collection.embed_model_key, + event_source: "resume_embed_queue_processing" + }); + this._resume_embed_timeout = setTimeout(() => { + this._resume_embed_timeout = null; + this.embedded_total = 0; + this.process_embed_queue(); + }, delay); + } + /** + * Calculates the number of tokens processed per second. + * @private + * @this {DefaultEntitiesVectorAdapterThis} + * @returns {number} Tokens per second. + */ + _calculate_embed_tokens_per_second() { + const elapsed_time = this.total_time / 1e3; + return Math.round(this.total_tokens / (elapsed_time || 1)); + } + /** + * Resets the statistics related to embed queue processing. + * @private + * @this {DefaultEntitiesVectorAdapterThis} + * @returns {void} + */ + _reset_embed_queue_stats() { + this.collection._embed_queue = []; + this.embedded_total = 0; + this.is_queue_halted = false; + this.last_save_total = 0; + this.last_notice_embedded_total = 0; + this.last_notice_time = 0; + this.total_tokens = 0; + this.total_time = 0; + this.current_queue_total = 0; + this.progress_state = null; + this._embed_run_error = false; + this._resume_after_pause = false; + this._resume_after_pause_delay = 0; + if (this._resume_embed_timeout) { + clearTimeout(this._resume_embed_timeout); + this._resume_embed_timeout = null; + } + } + /** + * @private + * @this {DefaultEntitiesVectorAdapterThis} + * @param {object|null} next_state + * @returns {void} + */ + _set_progress_state(next_state = null) { + this.progress_state = next_state ? { + ...next_state, + updated_at: Date.now() + } : null; + } + /** + * @private + * @this {DefaultEntitiesVectorAdapterThis} + * @param {number} total + * @returns {void} + */ + _start_embed_progress_state(total) { + this._set_progress_state({ + active: true, + paused: false, + progress: 0, + total, + tokens_per_second: 0, + model_name: this.collection.embed_model_key + }); + this.collection.emit_event("embedding:started", { + progress: 0, + total, + model_name: this.collection.embed_model_key, + event_source: "process_embed_queue" + }); + } + /** + * @private + * @this {DefaultEntitiesVectorAdapterThis} + * @param {number} total + * @returns {void} + */ + _update_embed_progress_state(total) { + this._set_progress_state({ + active: true, + paused: false, + progress: this.embedded_total, + total, + tokens_per_second: this._calculate_embed_tokens_per_second(), + model_name: this.collection.embed_model_key + }); + } + /** + * @private + * @this {DefaultEntitiesVectorAdapterThis} + * @param {number} total + * @param {string} reason + * @returns {void} + */ + _update_paused_progress_state(total, reason = "") { + this._set_progress_state({ + active: true, + paused: true, + progress: this.embedded_total, + total, + tokens_per_second: this._calculate_embed_tokens_per_second(), + model_name: this.collection.embed_model_key, + reason + }); + } + /** + * @private + * @this {DefaultEntitiesVectorAdapterThis} + * @param {object} [params={}] + * @param {string} [params.message] + * @param {string} [params.details] + * @returns {void} + */ + _emit_embedding_error(params = {}) { + const { + message = "Embedding failed.", + details = "" + } = params; + this._embed_run_error = true; + this.is_queue_halted = true; + this._set_progress_state(null); + this.collection.emit_event("embedding:error", { + level: "error", + message, + details, + model_name: this.collection.embed_model_key, + event_source: "process_embed_queue" + }); + } + /** + * @this {DefaultEntitiesVectorAdapterThis} + * @returns {*} + */ + get notices() { + return this.collection.env.notices; + } +}; +var DefaultEntityVectorAdapter = class extends EntityVectorAdapter { + /** + * @this {DefaultEntityVectorAdapterThis} + * @returns {*} + */ + get data() { + return this.item.data; + } + /** + * Retrieve the current vector embedding for this entity. + * @async + * @this {DefaultEntityVectorAdapterThis} + * @returns {Promise} The entity's vector or undefined if not set. + */ + async get_vec() { + return this.vec; + } + /** + * Store/update the vector embedding for this entity. + * @async + * @this {DefaultEntityVectorAdapterThis} + * @param {number[]|null} vec - The vector to set. + * @returns {Promise} + */ + async set_vec(vec) { + this.vec = vec; + } + /** + * Delete/remove the vector embedding for this entity. + * @async + * @this {DefaultEntityVectorAdapterThis} + * @returns {Promise} + */ + async delete_vec() { + if (this.item.data?.embeddings?.[this.item.embed_model_key]) { + delete this.item.data.embeddings[this.item.embed_model_key].vec; + } + } + /** + * @this {DefaultEntityVectorAdapterThis} + * @returns {number[]|undefined} + */ + get vec() { + return this.item.data?.embeddings?.[this.item.embed_model_key]?.vec; + } + /** + * @this {DefaultEntityVectorAdapterThis} + * @param {number[]|null} vec + */ + set vec(vec) { + if (!this.item.data.embeddings) { + this.item.data.embeddings = {}; + } + if (!this.item.data.embeddings[this.item.embed_model_key]) { + this.item.data.embeddings[this.item.embed_model_key] = {}; + } + this.item.data.embeddings[this.item.embed_model_key].vec = vec; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-entities/actions/find_connections.js +var FRONTMATTER_SUFFIX = "---frontmatter---"; +var to_array = (value) => { + if (Array.isArray(value)) { + return value.map((entry) => typeof entry === "string" ? entry.trim() : "").filter((entry) => entry.length > 0); + } + if (typeof value === "string") { + const parts = value.includes(",") ? value.split(",") : [value]; + return parts.map((part) => part.trim()).filter((part) => part.length > 0); + } + return []; +}; +var merge_settings_with_params = (entity, params = {}) => ({ + ...entity.env.settings.smart_view_filter || {}, + ...params, + entity +}); +var remove_limit_fields = (filter_opts) => { + const next = { ...filter_opts }; + if (typeof next.limit !== "undefined") delete next.limit; + if (next.filter) { + next.filter = { ...next.filter }; + if (typeof next.filter.limit !== "undefined") delete next.filter.limit; + } + return next; +}; +var apply_frontmatter_exclusion = (filter_opts) => { + if (!filter_opts.exclude_frontmatter_blocks) return filter_opts; + const next = { ...filter_opts }; + const suffixes = Array.isArray(next.exclude_key_ends_with_any) ? [...next.exclude_key_ends_with_any] : []; + suffixes.push(FRONTMATTER_SUFFIX); + next.exclude_key_ends_with_any = suffixes; + return next; +}; +var append_entity_filters = (filter_opts, entity) => { + if (!entity) return filter_opts; + const next = { ...filter_opts }; + let exclude_starts = Array.isArray(next.exclude_key_starts_with_any) ? [...next.exclude_key_starts_with_any] : []; + if (typeof next.exclude_key_starts_with === "string") { + exclude_starts.push(next.exclude_key_starts_with); + delete next.exclude_key_starts_with; + } + const entity_key = entity.source_key || entity.key; + if (entity_key) exclude_starts.push(entity_key); + if (next.exclude_inlinks && Array.isArray(entity.inlinks) && entity.inlinks.length) { + exclude_starts = [...exclude_starts, ...entity.inlinks.map((i) => i.source_key)]; + } + if (next.exclude_outlinks && Array.isArray(entity.outlinks) && entity.outlinks.length) { + exclude_starts = [...exclude_starts, ...entity.outlinks.map((o) => o.key)]; + } + if (exclude_starts.length) next.exclude_key_starts_with_any = exclude_starts; + if (next.exclude_filter) { + const exclude_values = to_array(next.exclude_filter); + const current = Array.isArray(next.exclude_key_includes_any) ? [...next.exclude_key_includes_any] : []; + next.exclude_key_includes_any = [...current, ...exclude_values]; + } + if (next.include_filter) { + const include_values = to_array(next.include_filter); + const current = Array.isArray(next.key_includes_any) ? [...next.key_includes_any] : []; + next.key_includes_any = [...current, ...include_values]; + } + return next; +}; +var create_find_connections_filter_opts = (entity, params = {}) => { + const merged = merge_settings_with_params(entity, params); + const without_limits = remove_limit_fields(merged); + const with_frontmatter = apply_frontmatter_exclusion(without_limits); + return append_entity_filters(with_frontmatter, entity); +}; +var ENTITIES_CONNECTIONS_CACHE = {}; +function connections_from_cache(cache_key) { + return ENTITIES_CONNECTIONS_CACHE[cache_key]; +} +function connections_to_cache(cache_key, connections) { + ENTITIES_CONNECTIONS_CACHE[cache_key] = connections; +} +async function find_connections(params = {}) { + const limit = params.filter?.limit || params.limit || this.env.settings.smart_view_filter?.results_limit || 10; + const filter_opts = create_find_connections_filter_opts(this, params); + if (params.filter?.limit) delete params.filter.limit; + if (params.limit) delete params.limit; + const cache_key = this.key + murmur_hash_32_alphanumeric(JSON.stringify({ ...filter_opts, entity: null })); + if (!ENTITIES_CONNECTIONS_CACHE[cache_key]) { + const connections = (await this.collection.entities_vector_adapter.nearest(this, filter_opts)).sort(sort_by_score).slice(0, limit); + connections_to_cache(cache_key, connections); + } + return connections_from_cache(cache_key); +} +find_connections.action_type = "connections"; + +// node_modules/obsidian-smart-env/node_modules/smart-entities/smart_entity.js +var SmartEntity = class extends CollectionItem { + /** + * Creates an instance of SmartEntity. + * @constructor + * @this {SmartEntityThis} + * @param {SmartEntitiesEnv} env - The environment instance. + * @param {Partial} [opts={}] - Configuration options. + */ + constructor(env, opts = {}) { + super(env, opts); + this.entity_adapter = new DefaultEntityVectorAdapter(this); + } + /** + * Provides default values for a SmartEntity instance. + * @static + * @readonly + * @returns {Object} The default values. + */ + static get defaults() { + return { + data: { + path: null, + last_embed: { + hash: null + }, + embeddings: {} + } + }; + } + /** + * Initializes the SmartEntity instance. + * Checks if the entity has a vector and if it matches the model dimensions. + * If not, it queues an embed. + * Removes embeddings for inactive models. + * @this {SmartEntityThis} + * @returns {void} + */ + init() { + super.init(); + if (!this.vec?.length) { + this.entity_adapter.vec = null; + this.queue_embed(); + } + Object.entries(this.data.embeddings || {}).forEach(([model, embedding]) => { + if (model !== this.embed_model_key) { + this.data.embeddings[model] = null; + delete this.data.embeddings[model]; + } + }); + } + /** + * Queues the entity for embedding. + * @this {SmartEntityThis} + * @returns {void} + */ + queue_embed() { + this._queue_embed = this.should_embed; + } + /** + * Prepares the input for embedding. + * @async + * @param {string|null} [content=null] - Optional content to use instead of calling subsequent read() + * @returns {Promise} Should be overridden in child classes. + */ + async get_embed_input(content = null) { + } + // override in child class + /** + * Retrieves the embed input, either from cache or by generating it. + * @readonly + * @this {SmartEntityThis} + * @returns {*} The embed input string or a promise resolving to it. + */ + get embed_input() { + return this._embed_input ? this._embed_input : this.get_embed_input(); + } + /** + * Finds connections relevant to this entity based on provided parameters. + * @async + * @this {SmartEntityThis} + * @param {import('smart-types').FindConnectionsParams} [params={}] - Parameters for finding connections. + * @deprecated should be in actions (getter) but also see ConnectionsLists (smart-lists) (2026-02-11) + * @returns {Promise>} An array of result objects with score and item. + */ + async find_connections(params = {}) { + return await this.actions.find_connections(params); + } + /** + * @this {SmartEntityThis} + * @returns {string|null|undefined} + */ + get read_hash() { + return this.data.last_read?.hash; + } + /** + * @this {SmartEntityThis} + * @param {string|null} hash + */ + set read_hash(hash) { + if (!this.data.last_read) this.data.last_read = {}; + this.data.last_read.hash = hash; + } + /** + * @this {SmartEntityThis} + * @returns {EntityEmbeddingRecord} + */ + get embedding_data() { + if (!this.data.embeddings[this.embed_model_key]) { + this.data.embeddings[this.embed_model_key] = {}; + } + return this.data.embeddings[this.embed_model_key]; + } + /** + * @this {SmartEntityThis} + * @returns {EntityLastEmbed} + */ + get last_embed() { + if (!this.embedding_data.last_embed) { + this.embedding_data.last_embed = {}; + } + return this.embedding_data.last_embed; + } + /** + * @this {SmartEntityThis} + * @returns {string|null|undefined} + */ + get embed_hash() { + return this.last_embed?.hash; + } + /** + * @this {SmartEntityThis} + * @param {string|null} hash + */ + set embed_hash(hash) { + if (!this.embedding_data.last_embed) this.embedding_data.last_embed = {}; + this.embedding_data.last_embed.hash = hash; + } + /** + * Gets the embed link for the entity. + * @readonly + * @this {SmartEntityThis} + * @returns {string} The embed link. + */ + get embed_link() { + return `![[${this.path}]]`; + } + /** + * Gets the key of the embedding model. + * @readonly + * @this {SmartEntityThis} + * @returns {string} The embedding model key. + */ + get embed_model_key() { + return this.collection.embed_model_key; + } + /** + * Gets the embedding model instance from the collection. + * @readonly + * @this {SmartEntityThis} + * @returns {*} The embedding model instance. + */ + get embed_model() { + return this.collection.embed_model; + } + /** + * Determines if the entity should be embedded if unembedded. NOT the same as is_unembedded. + * @readonly + * @this {SmartEntityThis} + * @returns {boolean} True if no vector is set, false otherwise. + */ + get should_embed() { + return this.size > (this.settings?.min_chars || 300); + } + /** + * Sets the error for the embedding model. + * @this {SmartEntityThis} + * @param {string} error - The error message. + */ + set error(error) { + this.data.embeddings[this.embed_model_key].error = error; + } + /** + * Gets the number of tokens associated with the entity's embedding. + * @readonly + * @this {SmartEntityThis} + * @returns {number|undefined} The number of tokens, or undefined if not set. + */ + get tokens() { + return this.last_embed?.tokens; + } + /** + * Sets the number of tokens for the embedding. + * @this {SmartEntityThis} + * @param {number} tokens - The number of tokens. + */ + set tokens(tokens) { + this.last_embed.tokens = tokens; + } + /** + * Gets the vector representation from the entity adapter. + * @readonly + * @this {SmartEntityThis} + * @returns {Array|undefined} The vector or undefined if not set. + */ + get vec() { + return this.entity_adapter.vec; + } + /** + * Sets the vector representation in the entity adapter. + * @this {SmartEntityThis} + * @param {Array|null} vec - The vector to set. + */ + set vec(vec) { + this.entity_adapter.vec = vec; + this._queue_embed = false; + this._embed_input = null; + this.queue_save(); + } + /** + * Removes all embeddings from the entity. + * @this {SmartEntityThis} + * @returns {void} + */ + remove_embeddings() { + this.data.embeddings = null; + this.queue_save(); + } + /** + * Retrieves the key of the entity. + * @this {SmartEntityThis} + * @returns {*} The entity key. + */ + get_key() { + return this.data.key || this.data.path; + } + /** + * Retrieves the path of the entity. + * @readonly + * @this {SmartEntityThis} + * @returns {*} The entity path. + */ + get path() { + return this.data.path; + } + /** + * @this {SmartEntityThis} + * @returns {boolean} + */ + get is_unembedded() { + if (!this.vec) return true; + if (!this.embed_hash || this.embed_hash !== this.read_hash) return true; + return false; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-entities/smart_entities.js +var SmartEntities = class extends Collection { + /** + * Creates an instance of SmartEntities. + * @constructor + * @this {SmartEntitiesThis} + * @param {SmartEntitiesEnv} env - The environment instance. + * @param {SmartEntitiesOptions} opts - Configuration options. + */ + constructor(env, opts) { + super(env, opts); + this.entities_vector_adapter = new DefaultEntitiesVectorAdapter(this); + this.model_instance_id = null; + this._embed_queue = []; + } + /** + * Unloads the smart embedding model. + * @async + * @this {SmartEntitiesThis} + * @returns {Promise} + */ + async unload() { + if (typeof this.embed_model?.unload === "function") { + this.embed_model.unload(); + } + super.unload(); + } + /** + * Gets the key of the embedding model. + * @readonly + * @this {SmartEntitiesThis} + * @returns {string} The embedding model key. + */ + get embed_model_key() { + return this.embed_model?.model_key; + } + /** + * Gets the embedding model instance. + * @readonly + * @this {SmartEntitiesThis} + * @returns {EmbedModel|null} The embedding model instance or null if none. + */ + get embed_model() { + if (this.env.embedding_models.default) { + return this.env.embedding_models.default.instance; + } + throw new Error("DEPRECATED SMART ENVIRONMENT LOADED: UPDATE SMART PLUGINS."); + } + /** + * @this {SmartEntitiesThis} + * @param {EmbedModel} embed_model + */ + set embed_model(embed_model) { + this.env._embed_model = embed_model; + } + /** + * Gets the file name based on collection key and embedding model key. + * @readonly + * @deprecated likely unused (2025-09-29) + * @this {SmartEntitiesThis} + * @returns {string} The constructed file name. + */ + get file_name() { + return this.collection_key + "-" + this.embed_model_key.split("/").pop(); + } + /** + * Looks up entities based on hypothetical content. + * @deprecated moved to action (type=score) and retrieve using get_results() (pre-process generates hypothetical vecs) (2026-02-11) + * @async + * @this {SmartEntitiesThis} + * @param {EntityLookupParams} [params={}] - The parameters for the lookup. + * @returns {Promise|{error: string}>} The lookup results or an error object. + */ + async lookup(params = {}) { + const { hypotheticals = [] } = params; + if (!hypotheticals?.length) return { error: "hypotheticals is required" }; + if (!this.embed_model) return { error: "Embedding search is not enabled." }; + const hyp_vecs = await this.embed_model.embed_batch(hypotheticals.map((h) => ({ embed_input: h }))); + const limit = params.filter?.limit || params.k || this.env.settings.lookup_k || 10; + if (params.filter?.limit) delete params.filter.limit; + const filter = { + ...this.env.chats?.current?.scope || {}, + // DEPRECATED: since Smart Chat v1 (remove after removing legacy Smart Chat v0 from obsidian-smart-connections) + ...params.filter || {} + }; + const results = await hyp_vecs.reduce(async (acc_promise, embedding, i) => { + const acc = await acc_promise; + const results2 = await this.entities_vector_adapter.nearest(embedding.vec, filter); + results2.forEach((result) => { + if (!acc[result.item.path] || result.score > acc[result.item.path].score) { + acc[result.item.path] = { + key: result.item.key, + score: result.score, + item: result.item, + hypothetical_i: i + }; + } else { + result.score = acc[result.item.path].score; + } + }); + return acc; + }, Promise.resolve({})); + const top_k = Object.values(results).sort(sort_by_score).slice(0, limit); + console.log(`Found and returned ${top_k.length} ${this.collection_key}.`); + return top_k; + } + /** + * Gets the configuration for settings. + * @readonly + * @returns {SettingsConfig} The settings configuration. + */ + get settings_config() { + return settings_config; + } + /** + * Gets the notices from the environment. + * @deprecated use event system with levels instead of notices (2026-03-17) + * @readonly + * @this {SmartEntitiesThis} + * @returns {Object} The notices object. + */ + get notices() { + return this.env.smart_connections_plugin?.notices || this.env.main?.notices; + } + /** + * Gets the embed queue containing items to be embedded. + * @readonly + * @this {SmartEntitiesThis} + * @returns {Array} The embed queue. + */ + get embed_queue() { + if (!this._embed_queue?.length) { + console.time(`Building embed queue`); + this._embed_queue = Object.values(this.items).filter((item) => item._queue_embed || item.is_unembedded && item.should_embed); + console.timeEnd(`Building embed queue`); + } + return this._embed_queue; + } + /** + * Processes the embed queue by delegating to the default vector adapter. + * @async + * @this {SmartEntitiesThis} + * @returns {Promise} + */ + async process_embed_queue() { + await this.entities_vector_adapter.process_embed_queue(); + } + /** + * @deprecated since v4 2025-11-28 + * @returns {SettingsConfig} + */ + get connections_filter_config() { + return connections_filter_config; + } +}; +var settings_config = { + "min_chars": { + name: "Minimum length", + type: "number", + description: "Minimum length of entity to embed (in characters).", + placeholder: "Enter number ex. 300", + default: 300 + } +}; +var connections_filter_config = { + "smart_view_filter.show_full_path": { + "name": "Show full path", + "type": "toggle", + "description": "Turning on will include the folder path in the connections results." + }, + // "smart_view_filter.render_markdown": { + // "name": "Render markdown", + // "type": "toggle", + // "description": "Turn off to prevent rendering markdown and display connection results as plain text.", + // }, + "smart_view_filter.results_limit": { + "name": "Results limit", + "type": "number", + "description": "Adjust the number of connections displayed in the connections view (default 20).", + "default": 20 + }, + "smart_view_filter.exclude_inlinks": { + "name": "Exclude inlinks (backlinks)", + "type": "toggle", + "description": "Exclude notes that already link to the current note from the connections results." + }, + "smart_view_filter.exclude_outlinks": { + "name": "Exclude outlinks", + "type": "toggle", + "description": "Exclude notes that are already linked from within the current note from appearing in the connections results." + }, + "smart_view_filter.include_filter": { + "name": "Include filter", + "type": "text", + "description": "Notes must match this value in their file/folder path. Matching notes will be included in the connections results. Separate multiple values with commas." + }, + "smart_view_filter.exclude_filter": { + "name": "Exclude filter", + "type": "text", + "description": "Notes must *not* match this value in their file/folder path. Matching notes will be *excluded* from the connections results. Separate multiple values with commas." + }, + // should be better scoped at source-level (leaving here for now since connections_filter_config needs larger refactor) + "smart_view_filter.exclude_blocks_from_source_connections": { + "name": "Hide blocks in results", + "type": "toggle", + "description": "Show only sources in the connections results (no blocks)." + } + // // hide frontmatter blocks from connections results + // "smart_view_filter.exclude_frontmatter_blocks": { + // "name": "Hide frontmatter blocks in results", + // "type": "toggle", + // "description": "Show only sources in the connections results (no frontmatter blocks).", + // }, +}; + +// node_modules/obsidian-smart-env/node_modules/smart-entities/utils/frontmatter_filter.js +var to_string = (value) => `${value ?? ""}`.trim(); +var to_lower = (value) => to_string(value).toLowerCase(); +var get_frontmatter_value = (metadata = {}, key = "") => { + const metadata_key = Object.keys(metadata || {}).find((candidate_key) => to_lower(candidate_key) === key); + if (!metadata_key) return void 0; + return metadata[metadata_key]; +}; +var matches_entry = (metadata = {}, entry) => { + const metadata_value = get_frontmatter_value(metadata, entry.key); + if (metadata_value == null) return false; + if (entry.value == null) return true; + if (Array.isArray(metadata_value)) { + return metadata_value.some((value) => to_lower(value) === entry.value); + } + return to_lower(metadata_value) === entry.value; +}; +function filter_by_frontmatter(metadata = {}, frontmatter_filter = {}) { + const include = frontmatter_filter.include || []; + const exclude = frontmatter_filter.exclude || []; + if (exclude.length && exclude.some((entry) => matches_entry(metadata, entry))) { + return false; + } + if (!include.length) return true; + return include.some((entry) => matches_entry(metadata, entry)); +} + +// node_modules/obsidian-smart-env/node_modules/smart-sources/actions/find_connections.js +var SOURCE_CONNECTIONS_CACHE = {}; +function connections_from_cache2(cache_key) { + return SOURCE_CONNECTIONS_CACHE[cache_key]; +} +function connections_to_cache2(cache_key, connections) { + SOURCE_CONNECTIONS_CACHE[cache_key] = connections; +} +async function find_connections2(params = {}) { + const filter_settings = this.env.settings.smart_view_filter; + const exclude_blocks_from_source_connections = params.exclude_blocks_from_source_connections ?? filter_settings?.exclude_blocks_from_source_connections ?? false; + const limit = params.filter?.limit || params.limit || this.env.settings.smart_view_filter?.results_limit || 20; + let connections; + if (this.block_collection.settings.embed_blocks && !exclude_blocks_from_source_connections) connections = []; + else connections = await find_connections.call(this, params); + const filter_opts = create_find_connections_filter_opts(this, params); + if (params.filter?.limit) delete params.filter.limit; + if (params.limit) delete params.limit; + if (!exclude_blocks_from_source_connections) { + const cache_key = this.key + murmur_hash_32_alphanumeric(JSON.stringify({ ...filter_opts, entity: null })) + "_blocks"; + if (!SOURCE_CONNECTIONS_CACHE[cache_key]) { + const nearest = (await this.env.smart_blocks.entities_vector_adapter.nearest(this.vec, filter_opts)).sort(sort_by_score).slice(0, limit); + connections_to_cache2(cache_key, nearest); + } + connections = [ + ...connections, + ...connections_from_cache2(cache_key) + ].sort(sort_by_score).slice(0, limit); + } + return connections; +} +find_connections2.action_type = "connections"; + +// node_modules/obsidian-smart-env/node_modules/smart-sources/smart_source.js +var SmartSource = class extends SmartEntity { + /** + * Provides default values for a SmartSource instance. + * @static + * @readonly + * @returns {Object} The default values. + */ + static get defaults() { + return { + data: { + last_read: { + hash: null, + mtime: 0 + }, + embeddings: {} + }, + _embed_input: null, + // Stored temporarily + _queue_load: true + }; + } + /** + * Initializes the SmartSource instance by queuing an import if blocks are missing. + * @returns {void} + */ + init() { + super.init(); + if (!this.data.blocks) this.queue_import(); + } + /** + * Queues the SmartSource for import. + * @returns {void} + */ + queue_import() { + this._queue_import = true; + } + /** + * Imports the SmartSource by checking for updates and parsing content. + * @async + * @returns {Promise} + */ + async import() { + this._queue_import = false; + try { + await this.source_adapter?.import(); + this.emit_event("sources:imported"); + } catch (err) { + if (err.code === "ENOENT") { + console.log(`Smart Connections: Deleting ${this.path} data because it no longer exists on disk`); + this.delete(); + } else { + console.warn("Smart Connections: Error during import: re-queueing import", err); + this.queue_import(); + } + } + } + /** + * @deprecated likely extraneous + */ + async parse_content(content = null) { + const parse_fns = this.env?.opts?.collections?.smart_sources?.content_parsers || []; + for (const fn of parse_fns) { + await fn(this, content); + } + if (this.data.last_import?.hash === this.data.last_read?.hash) { + if (this.data.blocks) return; + } + } + /** + * Finds connections relevant to this SmartSource based on provided parameters. + * @async + * @deprecated use ConnectionsLists + * @param {Object} [params={}] - Parameters for finding connections. + * @param {boolean} [params.exclude_blocks_from_source_connections=false] - Whether to exclude block connections from source connections. + * @param {Object} [params.exclude_frontmatter_blocks=true] - Whether to exclude frontmatter blocks from source connections. + * @returns {Array} An array of relevant SmartSource entities. + */ + async find_connections(params = {}) { + return await this.actions.find_connections(params); + } + /** + * Prepares the embed input for the SmartSource by reading content and applying exclusions. + * @async + * @returns {Promise} The embed input string or `false` if already embedded. + */ + async get_embed_input(content = null) { + if (typeof this._embed_input === "string" && this._embed_input.length) return this._embed_input; + if (!content) content = await this.read(); + if (!content) { + console.warn("SmartSource.get_embed_input: No content available for embedding: " + this.path); + return ""; + } + if (this.excluded_lines.length) { + const content_lines = content.split("\n"); + this.excluded_lines.forEach((lines) => { + const { start, end } = lines; + for (let i = start; i <= end; i++) { + content_lines[i] = ""; + } + }); + content = content_lines.filter((line) => line.length).join("\n"); + } + const breadcrumbs = this.path.split("/").join(" > ").replace(".md", ""); + const max_tokens = this.collection.embed_model.model.data.max_tokens || 500; + const max_chars = Math.floor(max_tokens * 3.7); + this._embed_input = `${breadcrumbs}: +${content}`.substring(0, max_chars); + return this._embed_input; + } + /** + * Opens the SmartSource note in the SmartConnections plugin. + * @returns {void} + */ + open() { + this.env.smart_connections_plugin.open_note(this.path); + } + /** + * Retrieves the block associated with a specific line number. + * @param {number} line - The line number to search for. + * @returns {SmartBlock|null} The corresponding SmartBlock or `null` if not found. + */ + get_block_by_line(line) { + return Object.entries(this.data.blocks || {}).reduce((acc, [sub_key, range]) => { + if (acc) return acc; + if (range[0] <= line && range[1] >= line) { + const block = this.block_collection.get(this.key + sub_key); + if (block?.vec) return block; + } + return acc; + }, null); + } + /** + * Checks if the source file exists in the file system. + * @async + * @returns {Promise} A promise that resolves to `true` if the file exists, `false` otherwise. + */ + async has_source_file() { + return await this.fs.exists(this.path); + } + // CRUD + /** + * FILTER/SEARCH METHODS + */ + /** + * Searches for keywords within the entity's data and content. + * @async + * @param {Object} search_filter - The search filter object. + * @param {string[]} search_filter.keywords - An array of keywords to search for. + * @param {string} [search_filter.type='any'] - The type of search to perform. 'any' counts all matching keywords, 'all' counts only if all keywords match. + * @returns {Promise} A promise that resolves to the number of matching keywords. + */ + async search(search_filter = {}) { + const { keywords, type = "any", limit } = search_filter; + if (!keywords || !Array.isArray(keywords)) { + console.warn("Entity.search: keywords not set or is not an array"); + return 0; + } + if (limit && this.collection.search_results_ct >= limit) return 0; + const lowercased_keywords = keywords.map((keyword) => keyword.toLowerCase()); + const content = await this.read(); + if (!content || typeof content !== "string" || !content.length) { + if (content.mime_type) { + console.warn(`Entity.search: No content available for searching: ${this.path}, mime_type: ${content.mime_type}`); + } else { + console.warn(`Entity.search: No content available for searching: ${this.path}, content: ${content ? JSON.stringify(content) : "empty"}`); + } + return 0; + } + const lowercased_content = content.toLowerCase(); + const lowercased_path = this.path.toLowerCase(); + const matching_keywords = lowercased_keywords.filter( + (keyword) => lowercased_path.includes(keyword) || lowercased_content.includes(keyword) + ); + if (type === "all") { + return matching_keywords.length === lowercased_keywords.length ? matching_keywords.length : 0; + } else { + return matching_keywords.length; + } + } + /** + * Filters source using base key filters and optional frontmatter include/exclude filters. + * @param {Object} [filter_opts={}] + * @param {Object} [filter_opts.frontmatter] + * @returns {boolean} + */ + filter(filter_opts = {}) { + if (!super.filter(filter_opts)) return false; + if (!filter_opts.frontmatter) return true; + return filter_by_frontmatter(this.metadata || {}, filter_opts.frontmatter); + } + /** + * ADAPTER METHODS + */ + use_source_adapter(method, ...args) { + if (!this.source_adapter) { + console.warn(`No source adapter available for ${this.key}. Cannot use method ${method}.`); + return; + } + if (typeof this.source_adapter[method] !== "function") { + console.warn(`Source adapter for ${this.key} does not implement method ${method}.`); + return; + } + return this.source_adapter[method](...args); + } + /** + * Appends content to the end of the source file. + * @async + * @param {string} content - The content to append to the file. + * @returns {Promise} A promise that resolves when the operation is complete. + */ + async append(content) { + await this.use_source_adapter("append", content); + await this.import(); + } + /** + * Updates the entire content of the source file. + * @async + * @param {string} full_content - The new content to write to the file. + * @param {Object} [opts={}] - Additional options for the update. + * @returns {Promise} A promise that resolves when the operation is complete. + */ + async update(full_content, opts = {}) { + try { + await this.use_source_adapter("update", full_content, opts); + await this.import(); + } catch (error) { + console.error(`Error during update for ${this.key}:`, error); + } + } + /** + * Reads the entire content of the source file. + * @async + * @param {Object} [opts={}] - Additional options for reading. + * @returns {Promise} A promise that resolves with the content of the file. + */ + async read(opts = {}) { + try { + return await this.use_source_adapter("read", opts) || ""; + } catch (error) { + console.error(`Error during reading ${this.key} (returning empty string)`, error); + return ""; + } + } + /** + * Removes the source file from the file system and deletes the entity. + * This is different from `delete()` because it also removes the source file. + * @async + * @returns {Promise} A promise that resolves when the operation is complete. + */ + async remove() { + try { + await this.use_source_adapter("remove"); + } catch (error) { + console.error(`Error during remove for ${this.key}:`, error); + } + } + /** + * Moves the current source to a new location. + * Handles the destination as a string (new path) or entity (block or source). + * + * @async + * @param {string|SmartEntity} entity_ref - The destination path or entity to move to. + * @throws {Error} If the entity reference is invalid. + * @returns {Promise} A promise that resolves when the move operation is complete. + */ + async move_to(entity_ref) { + try { + await this.use_source_adapter("move_to", entity_ref); + } catch (error) { + console.error(`Error during move for ${this.key}:`, error); + } + } + /** + * Merges the given content into the current source. + * Parses the content into blocks and either appends to existing blocks, replaces blocks, or replaces all content. + * + * @async + * @param {string} content - The content to merge into the current source. + * @param {Object} [opts={}] - Options object. + * @param {string} [opts.mode='append'] - The merge mode: 'append', 'replace_blocks', or 'replace_all'. + * @returns {Promise} + */ + async merge(content, opts = {}) { + try { + await this.use_source_adapter("merge", content, opts); + await this.import(); + } catch (error) { + console.error(`Error during merge for ${this.key}:`, error); + } + } + /** + * Handles errors during the load process. + * @param {Error} err - The error encountered during load. + * @returns {void} + */ + on_load_error(err) { + super.on_load_error(err); + if (err.code === "ENOENT") { + this._queue_load = false; + this.queue_import(); + } + } + // GETTERS + /** + * Retrieves the block collection associated with SmartSources. + * @readonly + * @returns {SmartBlocks} The block collection instance. + */ + get block_collection() { + return this.env.smart_blocks; + } + /** + * Retrieves the vector representations of all blocks within the SmartSource. + * @readonly + * @returns {Array>} An array of vectors. + */ + get block_vecs() { + return this.blocks.map((block) => block.vec).filter((vec) => vec); + } + /** + * Retrieves all blocks associated with the SmartSource. + * @readonly + * @returns {Array} An array of SmartBlock instances. + * @description + * Uses block refs (Fastest) to get blocks without iterating over all blocks + */ + get blocks() { + if (this.data.blocks) return this.block_collection.get_many(Object.keys(this.data.blocks).map((key) => this.key + key)); + return []; + } + /** + * Determines if the SmartSource is excluded from processing. + * @readonly + * @returns {boolean} `true` if excluded, `false` otherwise. + */ + get excluded() { + return this.fs.is_excluded(this.path); + } + /** + * Retrieves the lines excluded from embedding. + * @readonly + * @returns {Array} An array of objects with `start` and `end` line numbers. + */ + get excluded_lines() { + return this.blocks.filter((block) => block.excluded).map((block) => block.lines); + } + /** + * Retrieves the file system instance from the SmartSource's collection. + * @readonly + * @returns {SmartFS} The file system instance. + */ + get fs() { + return this.collection.fs; + } + /** + * Retrieves the file object associated with the SmartSource. + * @deprecated should be replaced with adapter methods + * @readonly + * @returns {Object} The file object. + */ + get file() { + return this.fs.files[this.path]; + } + /** + * Retrieves the file name of the SmartSource. + * @readonly + * @returns {string} The file name. + */ + get file_name() { + return this.path.split("/").pop(); + } + /** + * Retrieves the file path of the SmartSource. + * @readonly + * @returns {string} The file path. + */ + get file_path() { + return this.path; + } + /** + * Retrieves the file type based on the file extension. + * @readonly + * @returns {string} The file type in lowercase. + */ + get file_type() { + if (!this._ext) { + this._ext = this.collection.get_extension_for_path(this.path) || "md"; + } + return this._ext; + } + /** + * Retrieves the modification time of the SmartSource. + * @deprecated should be replaced with adapter methods (see get size) + * @readonly + * @returns {number} The modification time. + */ + get mtime() { + return this.file?.stat?.mtime || 0; + } + /** + * Retrieves the size of the SmartSource. + * @readonly + * @returns {number} The size. + */ + get size() { + return this.source_adapter?.size || 0; + } + /** + * Retrieves the last import stat of the SmartSource. + * @readonly + * @returns {Object} The last import stat. + */ + get last_import() { + return this.data?.last_import; + } + /** + * Retrieves the last import modification time of the SmartSource. + * @readonly + * @returns {number} The last import modification time. + */ + get last_import_mtime() { + return this.last_import?.mtime || 0; + } + /** + * Retrieves the last import size of the SmartSource. + * @readonly + * @returns {number} The last import size. + */ + get last_import_size() { + return this.last_import?.size || 0; + } + /** + * Retrieves the paths of inlinks to this SmartSource. + * @readonly + * @returns {Array} An array of inlink paths. + */ + get inlinks() { + return Object.entries(this.collection.links?.[this.key] || {}).map(([link_source_key, link_data]) => { + return { + source_key: link_source_key, + ...link_data + }; + }); + } + get is_media() { + return this.source_adapter.is_media || false; + } + /** + * Determines if the SmartSource is gone (i.e., the file no longer exists). + * @readonly + * @returns {boolean} `true` if gone, `false` otherwise. + */ + get is_gone() { + return !this.file; + } + /** + * Retrieves the last read hash of the SmartSource. + * @readonly + * @returns {string|undefined} The last read hash or `undefined` if not set. + */ + get last_read() { + return this.data.last_read; + } + get metadata() { + return this.data.metadata; + } + get outdated() { + return this.source_adapter.outdated; + } + /** + * Retrieves the outlink paths from the SmartSource. + * @readonly + * @returns {Array} An array of outlink objects. + */ + get outlinks() { + return (this.data.outlinks || []).map((link) => { + const link_ref = link?.target || link; + if (typeof link_ref !== "string") return null; + if (link_ref.startsWith("http")) return null; + const link_path = this.fs.get_link_target_path(link_ref, this.file_path); + return { + ...link, + key: link_path || link_ref, + // if path resolver fails, return original ref + embedded: link.embedded || false, + source_key: this.key + }; + }).filter((link_path) => link_path); + } + /** + * @deprecated path should be derived from key (stable key principle) + */ + get path() { + return this.data.path || this.data.key; + } + get source_adapters() { + return this.collection.source_adapters; + } + get source_adapter() { + if (this._source_adapter) return this._source_adapter; + if (this.source_adapters[this.file_type]) this._source_adapter = new this.source_adapters[this.file_type](this); + else { + for (const Adapter of Object.values(this.source_adapters)) { + if (typeof Adapter.detect_type !== "function") continue; + if (Adapter.detect_type(this)) { + this._source_adapter = new Adapter(this); + break; + } + } + } + return this._source_adapter; + } + // COMPONENTS + /** + * Calculates the mean vector of all blocks within the SmartSource. + * @readonly + * @returns {Array|null} The mean vector or `null` if no vectors are present. + */ + get mean_block_vec() { + if (this._mean_block_vec) { + this._mean_block_vec = compute_centroid(this.block_vecs); + } + return this._mean_block_vec; + } + /** + * Calculates the median vector of all blocks within the SmartSource. + * @readonly + * @returns {Array|null} The median vector or `null` if no vectors are present. + */ + get median_block_vec() { + if (this._median_block_vec) { + this._median_block_vec = compute_medoid(this.block_vecs); + } + return this._median_block_vec; + } + // DEPRECATED methods + /** + * @async + * @deprecated Use `read` instead. + * @returns {Promise} A promise that resolves with the content of the file. + */ + async _read() { + return await this.source_adapter._read(); + } + /** + * @async + * @deprecated Use `remove` instead. + * @returns {Promise} A promise that resolves when the entity is destroyed. + */ + async destroy() { + await this.remove(); + } + /** + * @async + * @deprecated Use `update` instead. + * @param {string} content - The content to update. + * @returns {Promise} + */ + async _update(content) { + await this.source_adapter.update(content); + } + /** + * @deprecated Use `source` instead. + * @readonly + * @returns {SmartSource} The associated SmartSource instance. + */ + get t_file() { + return this.fs.files[this.path]; + } +}; +var smart_source_default = { + class: SmartSource, + actions: { + find_connections: find_connections2 + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-sources/smart_sources.js +var SmartSources = class extends SmartEntities { + /** + * Creates an instance of SmartSources. + * @constructor + * @param {Object} env - The environment instance. + * @param {Object} [opts={}] - Configuration options. + */ + constructor(env, opts = {}) { + super(env, opts); + this.search_results_ct = 0; + this._excluded_headings = null; + this.env_event_unsubscribers = []; + this.sources_re_import_queue = {}; + this.sources_re_import_timeout = null; + this.sources_re_import_halted = false; + this.import_progress_state = null; + } + /** + * Initializes the SmartSources instance by performing an initial scan of sources. + * @async + * @returns {Promise} + */ + async init() { + await super.init(); + await this.init_items(); + this.register_env_event_listeners(); + this.register_source_watchers(); + } + /** + * Registers env.events listeners for source lifecycle events emitted by filesystem adapters. + * @returns {void} + */ + register_env_event_listeners() { + this.unregister_env_event_listeners(); + if (!this.env?.events) return; + const listeners = [ + ["sources:created", (event) => this.handle_source_created(event)], + ["sources:modified", (event) => this.handle_source_modified(event)], + ["sources:renamed", (event) => this.handle_source_renamed(event)], + ["sources:deleted", (event) => this.handle_source_deleted(event)] + ]; + this.env_event_unsubscribers = listeners.map(([event_key, handler]) => this.env.events.on(event_key, handler)).filter(Boolean); + } + /** + * Unregisters env.events listeners that were previously attached by this collection. + * @returns {void} + */ + unregister_env_event_listeners() { + if (!Array.isArray(this.env_event_unsubscribers)) return; + while (this.env_event_unsubscribers.length) { + const unsub = this.env_event_unsubscribers.pop(); + try { + unsub?.(); + } catch (error) { + console.warn("SmartSources: Failed to unregister env event listener", error); + } + } + } + /** + * Determines whether the incoming event should be handled by this collection instance. + * @param {Object} event + * @returns {boolean} + */ + should_handle_event(event = {}) { + const { collection_key } = event; + if (collection_key && collection_key !== this.collection_key) return false; + return true; + } + /** + * Normalizes event payload keys into a canonical source path. + * @param {Object} event + * @returns {string|undefined} + */ + get_event_path(event = {}) { + return event.item_key || event.path || event.new_path; + } + /** + * Handles create events emitted by filesystem adapters. + * @param {Object} event + * @returns {void} + */ + handle_source_created(event = {}) { + if (!this.should_handle_event(event)) return; + const key = this.get_event_path(event); + if (!key) return; + const source = this.init_file_path(key) || this.get(key); + if (!source) { + console.warn("SmartSources: Unable to initialize source on create event", event); + return; + } + this.queue_source_re_import(source, { event_source: event.event_source }); + } + /** + * Handles modify events emitted by filesystem adapters. + * @param {Object} event + * @returns {void} + */ + handle_source_modified(event = {}) { + if (!this.should_handle_event(event)) return; + const key = this.get_event_path(event); + if (!key) return; + if (this.fs.is_excluded(key)) return; + let source = this.get(key); + if (!source) source = this.init_file_path(key); + if (!source) { + console.warn("SmartSources: Unable to resolve source on modify event", { key, event }); + return; + } + this.queue_source_re_import(source, { event_source: event.event_source }); + } + /** + * Handles rename events emitted by filesystem adapters. + * @param {Object} event + * @returns {void} + */ + handle_source_renamed(event = {}) { + if (!this.should_handle_event(event)) return; + const new_key = this.get_event_path(event); + const old_key = event.old_path || event.from; + if (!new_key && !old_key) return; + if (old_key && this.items[old_key]) { + const old_source = this.items[old_key]; + old_source?.delete?.(); + delete this.items[old_key]; + if (this.rename_debounce_timeout) clearTimeout(this.rename_debounce_timeout); + this.rename_debounce_timeout = setTimeout(() => { + this.process_save_queue(); + this.rename_debounce_timeout = null; + }, 1e3); + } + if (!new_key) return; + let source = this.get(new_key); + if (!source) source = this.init_file_path(new_key); + if (!source) { + console.warn("SmartSources: Unable to initialize source on rename event", event); + return; + } + this.queue_source_re_import(source, { event_source: event.event_source }); + } + /** + * Handles delete events emitted by filesystem adapters. + * @param {Object} event + * @returns {void} + */ + handle_source_deleted(event = {}) { + if (!this.should_handle_event(event)) return; + const key = this.get_event_path(event); + if (!key) return; + delete this.items[key]; + if (this.sources_re_import_queue[key]) { + delete this.sources_re_import_queue[key]; + } + } + /** + * Requests filesystem adapters to register source watchers for this collection. + * @returns {void} + */ + register_source_watchers() { + const adapter = this.fs?.adapter; + if (!adapter || typeof adapter.register_source_watchers !== "function") return; + if (this._source_watchers_registered) return; + this._source_watchers_registered = adapter.register_source_watchers(this); + } + /** + * Queues a SmartSource for re-import and schedules processing. + * @param {import('./smart_source.js').SmartSource} source + * @param {Object} [event_meta] + * @returns {void} + */ + queue_source_re_import(source, event_meta = {}) { + if (!source?.key) return; + source.data.last_import = { at: 0, hash: null, mtime: 0, size: 0 }; + this.sources_re_import_queue[source.key] = { source, event_meta }; + this.debounce_re_import_queue(); + } + /** + * Debounces re-import processing to respect the configured wait time. + * @returns {void} + */ + debounce_re_import_queue() { + this.sources_re_import_halted = true; + if (this.sources_re_import_timeout) clearTimeout(this.sources_re_import_timeout); + const queue_keys = Object.keys(this.sources_re_import_queue || {}); + if (!queue_keys.length) { + this.sources_re_import_timeout = null; + return; + } + const wait_seconds = typeof this.env?.settings?.re_import_wait_time === "number" ? this.env.settings.re_import_wait_time : 13; + this.sources_re_import_timeout = setTimeout( + () => this.run_re_import(), + wait_seconds * 1e3 + ); + } + /** + * @returns {object|null} + */ + get_import_progress_state() { + return this.import_progress_state ? { ...this.import_progress_state } : null; + } + /** + * @param {object|null} next_state + * @returns {void} + */ + set_import_progress_state(next_state = null) { + this.import_progress_state = next_state ? { + ...next_state, + updated_at: Date.now() + } : null; + } + /** + * Processes the queued re-import tasks. + * @returns {Promise} + */ + async run_re_import() { + this.sources_re_import_halted = false; + const queue_entries = Object.entries(this.sources_re_import_queue || {}); + if (!queue_entries.length) { + if (this.sources_re_import_timeout) clearTimeout(this.sources_re_import_timeout); + this.sources_re_import_timeout = null; + this.set_import_progress_state(null); + return; + } + this.set_import_progress_state({ + active: true, + stage: "reimporting", + progress: 0, + total: queue_entries.length + }); + this.emit_event("sources:reimport_started", { + progress: 0, + total: queue_entries.length, + event_source: "run_re_import" + }); + let completed_count = 0; + for (let index = 0; index < queue_entries.length; index += 100) { + const batch = queue_entries.slice(index, index + 100); + await Promise.all(batch.map(([, { source }]) => source.import())); + for (const [key, { source }] of batch) { + if (!this._embed_queue) this._embed_queue = []; + if (source.should_embed) this._embed_queue.push(source); + if (this.block_collection?.settings?.embed_blocks) { + for (const block of source.blocks || []) { + if (block._queue_embed || block.should_embed && block.is_unembedded) { + this._embed_queue.push(block); + block._queue_embed = true; + } + } + } + delete this.sources_re_import_queue[key]; + } + completed_count += batch.length; + this.set_import_progress_state({ + active: true, + stage: "reimporting", + progress: completed_count, + total: queue_entries.length + }); + this.emit_event("sources:reimport_progress", { + progress: completed_count, + total: queue_entries.length, + event_source: "run_re_import" + }); + if (this.sources_re_import_halted) { + this.debounce_re_import_queue(); + break; + } + } + this.set_import_progress_state(null); + if (this._embed_queue?.length) { + const embed_start_at = Date.now(); + await this.process_embed_queue(); + console.log(`Processed embed queue in ${Date.now() - embed_start_at}ms`); + } + if (!this.sources_re_import_halted) { + this.emit_event("sources:reimport_completed", { + count: completed_count, + total: queue_entries.length, + event_source: "run_re_import" + }); + } + if (this.sources_re_import_timeout) clearTimeout(this.sources_re_import_timeout); + this.sources_re_import_timeout = null; + } + /** + * Initializes items by letting each adapter do any necessary file-based scanning. + * @async + * @returns {Promise} + */ + async init_items() { + this.emit_event("source:initial_scan_started", { + event_source: "init_items" + }); + for (const AdapterClass of Object.values(this.source_adapters)) { + if (typeof AdapterClass.init_items === "function") { + await AdapterClass.init_items(this); + } + } + this.emit_event("source:initial_scan_completed", { + event_source: "init_items" + }); + } + /** + * Creates (or returns existing) a SmartSource for a given file path, if the extension is recognized. + * @param {string} file_path - The path to the file or pseudo-file + * @returns {SmartSource|undefined} The newly created or existing SmartSource, or undefined if no recognized extension + */ + init_file_path(file_path) { + const ext = this.get_extension_for_path(file_path); + if (!ext) { + return; + } + if (this.fs.is_excluded(file_path)) { + console.warn(`File ${file_path} is excluded from processing.`); + return; + } + if (!this.fs.files[file_path]) { + this.fs.include_file(file_path); + } + if (this.items[file_path]) return this.items[file_path]; + const item = new this.item_type(this.env, { path: file_path }); + this.items[file_path] = item; + item.queue_import(); + item.queue_load(); + return item; + } + /** + * Looks for an extension in descending order: + * e.g. split "my.file.name.github" -> ["my","file","name","github"] + * Try 'file.name.github', 'name.github', 'github' + * Return the first that is in 'source_adapters' + * @param {string} file_path + * @returns {string|undefined} recognized extension, or undefined if none + */ + get_extension_for_path(file_path) { + if (!file_path) return void 0; + const pcs = file_path.split("."); + if (pcs.length < 2) return void 0; + let last_ext; + pcs.shift(); + while (pcs.length) { + const supported_ext = pcs.join(".").toLowerCase(); + if (this.source_adapters[supported_ext]) { + return supported_ext; + } + last_ext = pcs.shift(); + } + return last_ext; + } + /** + * Builds a map of links between sources. + * @returns {Object} An object mapping link paths to source keys. + */ + build_links_map() { + const start_time = Date.now(); + this.links = {}; + for (const source of Object.values(this.items)) { + for (const link of source.outlinks) { + if (!this.links[link.key]) this.links[link.key] = {}; + this.links[link.key][source.key] = { ...link }; + } + } + const end_time = Date.now(); + console.log(`Time spent building links: ${end_time - start_time}ms`); + return this.links; + } + /** + * Creates a new source with the given key and content. + * @async + * @param {string} key - The key (path) of the new source. + * @param {string} content - The content to write to the new source. + * @returns {Promise} The created SmartSource instance. + */ + async create(key, content) { + await this.fs.write(key, content); + await this.fs.refresh(); + const source = await this.create_or_update({ path: key }); + await source.import(); + return source; + } + /** + * Performs a lexical search for matching SmartSource content. + * @async + * @deprecated uses this.actions 2025-12-02 + * @param {Object} search_filter - The filter criteria for the search. + * @param {string[]} search_filter.keywords - An array of keywords to search for. + * @param {number} [search_filter.limit] - The maximum number of results to return. + * @returns {Promise>} A promise that resolves to an array of matching SmartSource entities. + */ + async search(search_filter = {}) { + const { + keywords, + limit, + ...filter_opts + } = search_filter; + if (!keywords) { + console.warn("search_filter.keywords not set"); + return []; + } + this.search_results_ct = 0; + const initial_results = this.filter(filter_opts); + const search_results = []; + for (let index = 0; index < initial_results.length; index += 10) { + const batch = initial_results.slice(index, index + 10); + const batch_results = await Promise.all( + batch.map(async (item) => { + try { + const matches = await item.search(search_filter); + if (matches) { + this.search_results_ct++; + return { item, score: matches }; + } + return null; + } catch (error) { + console.error(`Error searching item ${item.id || "unknown"}:`, error); + return null; + } + }) + ); + search_results.push(...batch_results.filter(Boolean)); + } + return search_results.sort((a, b) => b.score - a.score).map((result) => result.item); + } + /** + * Looks up entities based on the provided parameters. + * @async + * @deprecated uses this.actions 2025-12-02 + * @param {Object} [params={}] - Parameters for the lookup. + * @param {Object} [params.filter] - Filter options. + * @param {number} [params.k] - Deprecated. Use `params.filter.limit` instead. + * @returns {Promise>} A promise that resolves to an array of matching SmartSource entities. + */ + async lookup(params = {}) { + const limit = params.filter?.limit || params.k || this.env.settings.lookup_k || 10; + if (params.filter?.limit) delete params.filter.limit; + if (params.collection) { + const collection = this.env[params.collection]; + if (collection && collection.lookup) { + delete params.collection; + params.skip_blocks = true; + const results2 = await collection.lookup(params); + if (results2.error) { + console.warn(results2.error); + return []; + } + return results2.slice(0, limit); + } + } + let results = await super.lookup(params); + if (results.error) { + console.warn(results.error); + return []; + } + if (this.block_collection?.settings?.embed_blocks && !params.skip_blocks) { + results = [ + ...results, + ...await this.block_collection.lookup(params) + ].sort(sort_by_score); + } + return results.slice(0, limit); + } + /** + * Processes the load queue by loading items and optionally importing them. + * @async + * @returns {Promise} + */ + async process_load_queue() { + await super.process_load_queue(); + if (this.collection_key === "smart_sources" && this.env.smart_blocks) { + Object.values(this.env.smart_blocks.items).forEach((item) => item.init()); + } + if (this.block_collection) { + this.block_collection.loaded = Object.keys(this.block_collection.items).length; + } + if (!this.opts.prevent_import_on_load) { + await this.process_source_import_queue(this.opts); + } + this.build_links_map(); + this.block_collection.cleanup_blocks(); + } + /** + * @method process_source_import_queue + * @description + * Imports items (SmartSources or SmartBlocks) that have been flagged for import. + */ + async process_source_import_queue(opts = {}) { + const { process_embed_queue = true, force = false } = opts; + if (force) Object.values(this.items).forEach((item) => item._queue_import = true); + const import_queue = Object.values(this.items).filter((item) => item._queue_import); + console.log("import_queue " + import_queue.length); + if (!import_queue.length) { + this.set_import_progress_state(null); + this.emit_event("sources:import_queue_empty", { + event_source: "process_source_import_queue" + }); + return; + } + const time_start = Date.now(); + this.set_import_progress_state({ + active: true, + stage: "importing", + progress: 0, + total: import_queue.length + }); + this.emit_event("sources:import_started", { + progress: 0, + total: import_queue.length, + event_source: "process_source_import_queue" + }); + for (let index = 0; index < import_queue.length; index += 100) { + const batch = import_queue.slice(index, index + 100); + await Promise.all(batch.map((item) => item.import())); + const progress = Math.min(import_queue.length, index + batch.length); + this.set_import_progress_state({ + active: true, + stage: "importing", + progress, + total: import_queue.length + }); + this.emit_event("sources:import_progress", { + progress, + total: import_queue.length, + event_source: "process_source_import_queue" + }); + } + this.set_import_progress_state(null); + this.build_links_map(); + if (process_embed_queue) await this.process_embed_queue(); + else console.log("skipping process_embed_queue"); + await this.process_save_queue(); + await this.block_collection?.process_save_queue(); + this.emit_event("sources:import_completed", { + count: import_queue.length, + time_in_seconds: (Date.now() - time_start) / 1e3, + event_source: "process_source_import_queue" + }); + } + /** + * Retrieves the source adapters based on the collection configuration. + * @readonly + * @returns {Object} An object mapping file extensions to adapter constructors. + */ + get source_adapters() { + if (!this._source_adapters) { + const source_adapters = Object.entries(this.env.opts.collections?.[this.collection_key]?.source_adapters || {}); + const _source_adapters = source_adapters.reduce((acc, [key, Adapter]) => { + if (Adapter.extensions) Adapter.extensions?.forEach((ext) => acc[ext] = Adapter); + else if (typeof Adapter.detect_type === "function") acc[key] = Adapter; + return acc; + }, {}); + if (Object.keys(_source_adapters).length) { + this._source_adapters = _source_adapters; + } + } + return this._source_adapters; + } + /** + * Retrieves the notices system from the environment. + * @readonly + * @returns {Object} The notices object. + */ + get notices() { + return this.env.smart_connections_plugin?.notices || this.env.main?.notices; + } + /** + * Retrieves the currently active note. + * @readonly + * @returns {SmartSource|null} The current SmartSource instance or null if none. + */ + get current_note() { + return this.get(this.env.smart_connections_plugin.app.workspace.getActiveFile().path); + } + /** + * Retrieves the file system instance, initializing it if necessary. + * @readonly + * @returns {SmartFS} The file system instance. + */ + get fs() { + if (!this._fs) { + this._fs = new this.env.opts.modules.smart_fs.class(this.env, { + adapter: this.env.opts.modules.smart_fs.adapter, + fs_path: this.env.opts.env_path || "", + exclude_patterns: this.excluded_patterns || [] + }); + } + return this._fs; + } + /** + * Retrieves the settings configuration by combining superclass settings and adapter-specific settings. + * @readonly + * @returns {Object} The settings configuration object. + */ + get settings_config() { + const _settings_config = { + ...super.settings_config, + ...this.process_settings_config(settings_config2), + ...Object.entries(this.source_adapters).reduce((acc, [file_extension, adapter_constructor]) => { + if (acc[adapter_constructor]) return acc; + const item = this.items[Object.keys(this.items).find((key) => key.endsWith(file_extension))]; + const adapter_instance = new adapter_constructor(item || new this.item_type(this.env, {})); + if (adapter_instance.settings_config) { + acc[adapter_constructor.name] = { + type: "html", + value: `

${adapter_constructor.name} adapter

` + }; + acc = { ...acc, ...adapter_instance.settings_config }; + } + return acc; + }, {}) + }; + return _settings_config; + } + /** + * Retrieves the block collection associated with SmartSources. + * @readonly + * @returns {SmartBlocks} The block collection instance. + */ + get block_collection() { + return this.env.smart_blocks; + } + /** + * Retrieves the embed queue containing items and their blocks to be embedded. + * @readonly + * @returns {Array} The embed queue. + */ + get embed_queue() { + if (!this._embed_queue.length) { + try { + const embed_blocks = this.block_collection.settings.embed_blocks; + this._embed_queue = Object.values(this.items).reduce((acc, item) => { + if (item._queue_embed || item.should_embed && item.is_unembedded) acc.push(item); + if (embed_blocks) item.blocks.forEach((block) => { + if (block._queue_embed || block.should_embed && block.is_unembedded) acc.push(block); + }); + return acc; + }, []); + } catch (error) { + console.error(`Error getting embed queue:`, error); + } + } + return this._embed_queue; + } + /** + * Clears all data by removing sources and blocks, reinitializing the file system, and reimporting items. + * @async + * @returns {Promise} + */ + async run_clear_all() { + this.emit_event("sources:clear_started", { + event_source: "run_clear_all" + }); + await this.data_adapter.clear_all(); + this.clear(); + this.block_collection.clear(); + this._fs = null; + await this.init_fs(); + await this.init_items(); + this._excluded_headings = null; + Object.values(this.items).forEach((item) => { + item.queue_import(); + item.queue_embed(); + item.loaded_at = Date.now() + 9999999999; + }); + this.emit_event("sources:clear_completed", { + event_source: "run_clear_all" + }); + await this.process_source_import_queue(); + } + async init_fs(opts = {}) { + const { force_refresh = false } = opts; + if (force_refresh) await this.env.fs.refresh(); + await this.fs.load_exclusions(); + this.fs.file_paths = this.fs.post_process(this.env.fs.file_paths); + this.fs.files = this.fs.file_paths.reduce((acc, file_path) => { + acc[file_path] = this.env.fs.files[file_path]; + return acc; + }, {}); + this.fs.folder_paths = this.fs.post_process(this.env.fs.folder_paths); + this.fs.folders = this.fs.folder_paths.reduce((acc, folder_path) => { + acc[folder_path] = this.env.fs.folders[folder_path]; + return acc; + }, {}); + } + /** + * Retrieves patterns for excluding files/folders from processing. + * @readonly + * @returns {Array} + */ + get excluded_patterns() { + return [ + ...this.file_exclusions?.map((file) => `${file}**`) || [], + ...(this.folder_exclusions || []).map((folder) => `${folder}**`), + this.env.env_data_dir + "/**" + ]; + } + /** + * Retrieves the file exclusion patterns from settings. + * @readonly + * @returns {Array} An array of file exclusion patterns. + */ + get file_exclusions() { + const csv = this.env.settings?.smart_sources?.file_exclusions; + return csv?.length ? csv.split(",").map((file) => file.trim()) : []; + } + /** + * Retrieves the folder exclusion patterns from settings. + * @readonly + * @returns {Array} An array of folder exclusion patterns. + */ + get folder_exclusions() { + const csv = this.env.settings?.smart_sources?.folder_exclusions; + return csv?.length ? csv.split(",").map((folder) => { + folder = folder.trim(); + if (folder === "") return false; + if (folder === "/") return false; + if (!folder.endsWith("/")) return folder + "/"; + return folder; + }).filter(Boolean) : []; + } + /** + * Retrieves the excluded headings from settings. + * @readonly + * @returns {Array} An array of excluded headings. + */ + get excluded_headings() { + if (!this._excluded_headings) { + const csv = this.env.settings?.smart_sources?.excluded_headings; + this._excluded_headings = csv?.length ? csv.split(",").map((heading) => heading.trim()) : []; + } + return this._excluded_headings; + } + /** + * Retrieves the count of included files that are not excluded. + * @readonly + * @returns {number} The number of included files. + */ + get included_files() { + const extensions = Object.keys(this.source_adapters); + return this.fs.file_paths.filter((file_path) => extensions.some((ext) => file_path.endsWith(ext)) && !this.fs.is_excluded(file_path)).length; + } + get excluded_file_paths() { + return this.env.fs.file_paths.filter((file_path) => this.fs.is_excluded(file_path)); + } + /** + * Retrieves the total number of files, regardless of exclusion. + * @readonly + * @returns {number} The total number of files. + */ + get total_files() { + return this.fs.file_paths.filter((file) => file.endsWith(".md") || file.endsWith(".canvas")).length; + } + /** + * Unloads the collection and clears registered listeners and timers. + * @returns {void} + */ + unload() { + this.unregister_env_event_listeners(); + if (this.sources_re_import_timeout) clearTimeout(this.sources_re_import_timeout); + this.sources_re_import_timeout = null; + this.sources_re_import_queue = {}; + this.set_import_progress_state(null); + super.unload(); + } + get data_dir() { + return "multi"; + } +}; +var settings_config2 = {}; + +// node_modules/obsidian-smart-env/node_modules/smart-collections/adapters/_adapter.js +var CollectionDataAdapter = class { + /** + * @constructor + * @param {Object} collection - The collection instance that this adapter manages. + */ + constructor(collection) { + this.collection = collection; + this.env = collection.env; + } + /** + * The class to use for item adapters. + * @type {typeof ItemDataAdapter} + */ + ItemDataAdapter = ItemDataAdapter; + /** + * Optional factory method to create item adapters. + * If `this.item_adapter_class` is not null, it uses that; otherwise can be overridden by subclasses. + * @param {Object} item - The item to create an adapter for. + * @returns {ItemDataAdapter} + */ + create_item_adapter(item) { + if (!this.ItemDataAdapter) { + throw new Error("No item_adapter_class specified and create_item_adapter not overridden."); + } + return new this.ItemDataAdapter(item); + } + /** + * Load a single item by its key using an `ItemDataAdapter`. + * @async + * @abstract + * @param {string} key - The key of the item to load. + * @returns {Promise} Resolves when the item is loaded. + */ + async load_item(key) { + throw new Error("Not implemented"); + } + /** + * Save a single item by its key using its associated `ItemDataAdapter`. + * @async + * @abstract + * @param {string} key - The key of the item to save. + * @returns {Promise} Resolves when the item is saved. + */ + async save_item(key) { + throw new Error("Not implemented"); + } + /** + * Delete a single item by its key. This may involve updating or removing its file, + * as handled by the `ItemDataAdapter`. + * @async + * @abstract + * @param {string} key - The key of the item to delete. + * @returns {Promise} Resolves when the item is deleted. + */ + async delete_item(key) { + throw new Error("Not implemented"); + } + /** + * Process any queued load operations. Typically orchestrates calling `load_item()` + * on items that have been flagged for loading. + * @async + * @abstract + * @returns {Promise} + */ + async process_load_queue() { + throw new Error("Not implemented"); + } + /** + * Process any queued save operations. Typically orchestrates calling `save_item()` + * on items that have been flagged for saving. + * @async + * @abstract + * @returns {Promise} + */ + async process_save_queue() { + throw new Error("Not implemented"); + } + /** + * Load the item's data from storage if it has been updated externally. + * @async + * @param {Object} item - The item to load. + * @returns {Promise} Resolves when the item is loaded. + */ + async load_item_if_updated(item) { + const adapter = this.create_item_adapter(item); + await adapter.load_if_updated(); + } + /** + * Clear all data associated with this collection. + * @async + * @abstract + * @returns {Promise} + */ + async clear_all() { + throw new Error("Not implemented"); + } +}; +var ItemDataAdapter = class { + /** + * @constructor + * @param {Object} item - The collection item instance that this adapter manages. + */ + constructor(item) { + this.item = item; + } + /** + * Load the item's data from storage. May involve reading a file and parsing + * its contents, then updating `item.data`. + * @async + * @abstract + * @returns {Promise} Resolves when the item is fully loaded. + */ + async load() { + throw new Error("Not implemented"); + } + /** + * Save the item's data to storage. May involve writing to a file or appending + * lines in an append-only format. + * @async + * @abstract + * @param {*} [ajson=null] - An optional serialized representation of the item’s data. + * If not provided, the adapter should derive it from the item. + * @returns {Promise} Resolves when the item is saved. + */ + async save(ajson = null) { + throw new Error("Not implemented"); + } + /** + * Delete the item's data from storage. May involve removing a file or writing + * a `null` entry in an append-only file to signify deletion. + * @async + * @abstract + * @returns {Promise} Resolves when the item’s data is deleted. + */ + async delete() { + throw new Error("Not implemented"); + } + /** + * Returns the file path or unique identifier used by this adapter to locate and store + * the item's data. This may be a file name derived from the item's key. + * @abstract + * @returns {string} The path or identifier for the item's data. + */ + get data_path() { + throw new Error("Not implemented"); + } + /** + * @returns {*} The collection data adapter that this item data adapter belongs to. + */ + get collection_adapter() { + return this.item.collection.data_adapter; + } + get env() { + return this.item.env; + } + /** + * Load the item's data from storage if it has been updated externally. + * @async + * @abstract + * @returns {Promise} Resolves when the item is loaded. + */ + async load_if_updated() { + throw new Error("Not implemented"); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-collections/adapters/_file.js +var FileCollectionDataAdapter = class extends CollectionDataAdapter { + /** + * The class to use for item adapters. + * @type {typeof ItemDataAdapter} + */ + ItemDataAdapter = FileItemDataAdapter; + /** + * @returns {Object} Filesystem interface derived from environment or collection settings. + */ + get fs() { + return this.collection.data_fs || this.collection.env.data_fs; + } + async clear_all() { + await this.fs.remove_dir(this.collection.data_dir, true); + } +}; +var FileItemDataAdapter = class extends ItemDataAdapter { + /** + * @returns {Object} Filesystem interface derived from environment or collection settings. + */ + get fs() { + return this.item.collection.data_fs || this.item.collection.env.data_fs; + } + /** + * Resolve the file path for the item's data. + * @abstract + * @returns {string} Path to the persisted item data. + */ + get data_path() { + throw new Error("Not implemented"); + } + async load_if_updated() { + const data_path = this.data_path; + if (await this.fs.exists(data_path)) { + const loaded_at = this.item.loaded_at || 0; + const data_file_stat = await this.fs.stat(data_path); + if (data_file_stat.mtime > loaded_at + 1 * 60 * 1e3) { + console.log(`Smart Collections: Re-loading item ${this.item.key} because it has been updated on disk`); + await this.load(); + } + } + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-collections/adapters/ajson_multi_file.js +var class_to_collection_key = { + "SmartSource": "smart_sources", + "SmartNote": "smart_sources", + // DEPRECATED + "SmartBlock": "smart_blocks", + "SmartDirectory": "smart_directories" +}; +var AjsonMultiFileCollectionDataAdapter = class extends FileCollectionDataAdapter { + /** + * The class to use for item adapters. + * @type {typeof AjsonMultiFileItemDataAdapter} + */ + ItemDataAdapter = AjsonMultiFileItemDataAdapter; + /** + * Load a single item by its key. + * @async + * @param {string} key + * @returns {Promise} + */ + async load_item(key) { + const item = this.collection.get(key); + if (!item) return; + const adapter = this.create_item_adapter(item); + await adapter.load(); + } + /** + * Save a single item by its key. + * @async + * @param {string} key + * @returns {Promise} + */ + async save_item(key) { + const item = this.collection.get(key); + if (!item) return; + const adapter = this.create_item_adapter(item); + await adapter.save(); + } + /** + * Process any queued load operations. + * @async + * @returns {Promise} + */ + async process_load_queue() { + this.collection.emit_event("collection:load_started"); + if (!await this.fs.exists(this.collection.data_dir)) { + await this.fs.mkdir(this.collection.data_dir); + } + const load_queue = Object.values(this.collection.items).filter((item) => item._queue_load); + const load_queue_length = load_queue.length; + if (load_queue_length) { + const now = Date.now(); + console.log(`Loading ${this.collection.collection_key}: ${load_queue_length} items from disk`); + const batch_size = 100; + for (let i = 0; i < load_queue.length; i += batch_size) { + const batch = load_queue.slice(i, i + batch_size); + await Promise.all(batch.map((item) => { + const adapter = this.create_item_adapter(item); + return adapter.load().catch((err) => { + console.warn(`Error loading item ${item.key}`, err); + item.queue_load(); + }); + })); + } + console.log(`Loaded ${this.collection.collection_key} from disk in ${Date.now() - now}ms`); + } + this.collection.loaded = load_queue_length; + this.collection.emit_event("collection:load_completed", { loaded: load_queue_length }); + } + /** + * Process any queued save operations. + * @async + * @returns {Promise} + */ + async process_save_queue() { + this.collection.emit_event("collection:save_started"); + const save_queue = Object.values(this.collection.items).filter((item) => item._queue_save); + const save_queue_length = save_queue.length; + console.log(`Saving ${this.collection.collection_key}: ${save_queue_length} items`); + const time_start = Date.now(); + const batch_size = 50; + for (let i = 0; i < save_queue.length; i += batch_size) { + const batch = save_queue.slice(i, i + batch_size); + await Promise.all(batch.map((item) => { + const adapter = this.create_item_adapter(item); + return adapter.save().catch((err) => { + console.warn(`Error saving item ${item.key}`, err); + item.queue_save(); + }); + })); + } + const deleted_items = Object.values(this.collection.items).filter((item) => item.deleted); + if (deleted_items.length) { + deleted_items.forEach((item) => { + delete this.collection.items[item.key]; + }); + } + console.log(`Saved ${this.collection.collection_key} in ${Date.now() - time_start}ms`); + this.collection.emit_event("collection:save_completed", { saved: save_queue_length }); + } + get_item_data_path(key) { + return [ + this.collection.data_dir || "multi", + this.fs?.sep || "/", + this.get_data_file_name(key) + ".ajson" + ].join(""); + } + /** + * Transforms the item key into a safe filename. + * Replaces spaces, slashes, and dots with underscores. + * @returns {string} safe file name + */ + get_data_file_name(key) { + return key.split("#")[0].replace(/[\s\/\.]/g, "_").replace(".md", ""); + } + /** + * Build a single AJSON line for the given item and data. + * @param {Object} item + * @returns {string} + */ + get_item_ajson(item) { + const collection_key = item.collection_key; + const key = item.key; + const data_value = item.deleted ? "null" : JSON.stringify(item.data); + return `${JSON.stringify(`${collection_key}:${key}`)}: ${data_value},`; + } +}; +var AjsonMultiFileItemDataAdapter = class extends FileItemDataAdapter { + /** + * Derives the `.ajson` file path from the collection's data_dir and item key. + * @returns {string} + */ + get data_path() { + return this.collection_adapter.get_item_data_path(this.item.key); + } + /** + * Load the item from its `.ajson` file. + * @async + * @returns {Promise} + */ + async load() { + try { + const raw_data = await this.fs.adapter.read(this.data_path, "utf-8", { no_cache: true }); + if (!raw_data) { + this.item.queue_import(); + return; + } + const { rewrite, file_data } = this._parse(raw_data); + if (rewrite) { + if (file_data.length) await this.fs.write(this.data_path, file_data); + else await this.fs.remove(this.data_path); + } + const last_import_mtime = this.item.data.last_import?.at || 0; + if (last_import_mtime && this.item.init_file_mtime > last_import_mtime) { + this.item.queue_import(); + } + } catch (e) { + this.item.queue_import(); + } + } + /** + * Parse the entire AJSON content as a JSON object, handle legacy keys, and extract final state. + * @private + * @param {string} ajson + * @returns {*} + */ + _parse(ajson) { + try { + let rewrite = false; + if (!ajson.length) return false; + ajson = ajson.trim(); + const original_line_count = ajson.split("\n").length; + const json_str = "{" + ajson.slice(0, -1) + "}"; + const data = JSON.parse(json_str); + const entries = Object.entries(data); + for (let i = 0; i < entries.length; i++) { + const [ajson_key, value] = entries[i]; + if (!value) { + delete data[ajson_key]; + rewrite = true; + continue; + } + const { collection_key, item_key, changed } = this._parse_ajson_key(ajson_key); + if (changed) { + rewrite = true; + data[collection_key + ":" + item_key] = value; + delete data[ajson_key]; + } + const collection = this.env[collection_key]; + if (!collection) continue; + const existing_item = collection.get(item_key); + if (!value.key) value.key = item_key; + if (existing_item) { + existing_item.data = value; + existing_item._queue_load = false; + existing_item.loaded_at = Date.now(); + } else { + const ItemClass = collection.item_type; + const new_item = new ItemClass(this.env, value); + new_item._queue_load = false; + new_item.loaded_at = Date.now(); + collection.set(new_item); + } + } + if (rewrite || original_line_count > entries.length) { + rewrite = true; + } + return { + rewrite, + file_data: rewrite ? Object.entries(data).map(([key, value]) => `${JSON.stringify(key)}: ${JSON.stringify(value)},`).join("\n") : null + }; + } catch (e) { + if (ajson.split("\n").some((line) => !line.endsWith(","))) { + console.warn("fixing trailing comma error"); + ajson = ajson.split("\n").map((line) => line.endsWith(",") ? line : line + ",").join("\n"); + return this._parse(ajson); + } + console.warn("Error parsing JSON:", e); + return { rewrite: true, file_data: null }; + } + } + _parse_ajson_key(ajson_key) { + let changed; + let [collection_key, ...item_key] = ajson_key.split(":"); + if (class_to_collection_key[collection_key]) { + collection_key = class_to_collection_key[collection_key]; + changed = true; + } + return { + collection_key, + item_key: item_key.join(":"), + changed + }; + } + /** + * Save the current state of the item by appending a new line to its `.ajson` file. + * @async + * @returns {Promise} + */ + async save(retries = 0) { + try { + const ajson_line = this.get_item_ajson(); + await this.fs.append(this.data_path, "\n" + ajson_line); + this.item._queue_save = false; + } catch (e) { + if (e.code === "ENOENT" && retries < 1) { + const dir = this.collection_adapter.collection.data_dir; + if (!await this.fs.exists(dir)) { + await this.fs.mkdir(dir); + } + return await this.save(retries + 1); + } + console.warn("Error saving item", this.data_path, this.item.key, e); + } + } + /** + * Build a single AJSON line for the given item and data. + * @returns {string} + */ + get_item_ajson() { + return this.collection_adapter.get_item_ajson(this.item); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-sources/adapters/data/ajson_multi_file.js +var AjsonMultiFileSourcesDataAdapter = class extends AjsonMultiFileCollectionDataAdapter { + ItemDataAdapter = AjsonMultiFileSourceDataAdapter; +}; +var AjsonMultiFileSourceDataAdapter = class extends AjsonMultiFileItemDataAdapter { +}; + +// node_modules/obsidian-smart-env/node_modules/smart-sources/adapters/_adapter.js +var SourceContentAdapter = class { + constructor(item) { + this.item = item; + } + async import() { + this.throw_not_implemented("import"); + } + async create() { + this.throw_not_implemented("create"); + } + async update() { + this.throw_not_implemented("update"); + } + async read() { + this.throw_not_implemented("read"); + } + async remove() { + this.throw_not_implemented("remove"); + } + // HELPER METHODS + get data() { + return this.item.data; + } + // async create_hash(content) { return await create_hash(content); } + create_hash(content) { + return murmur_hash_32_alphanumeric(content); + } + get settings() { + return this.item.env.settings.smart_sources[this.adapter_key]; + } + get adapter_key() { + return to_snake(this.constructor.name); + } + static get adapter_key() { + return to_snake(this.name); + } + get fs() { + return this.item.collection.fs; + } + get env() { + return this.item.env; + } +}; +function to_snake(str) { + return str[0].toLowerCase() + str.slice(1).replace(/([A-Z])/g, "_$1").toLowerCase(); +} + +// node_modules/obsidian-smart-env/node_modules/smart-blocks/parsers/markdown.js +function parse_markdown_blocks(markdown, opts = {}) { + const { start_index = 1, line_keys = false } = opts; + const lines = markdown.split("\n"); + const LIST_KEY_WORD_LEN = opts.list_key_word_len || 10; + const result = {}; + const heading_stack = []; + const heading_lines = {}; + const heading_counts = {}; + const sub_block_counts = {}; + const subheading_counts = {}; + const task_lines = []; + const tasks = {}; + let current_list_item = null; + let current_content_block = null; + let in_frontmatter = false; + let frontmatter_started = false; + const root_heading_key = "#"; + let in_code_block = false; + const codeblock_ranges = []; + let codeblock_start = null; + sub_block_counts[root_heading_key] = 0; + for (let i = 0; i < lines.length; i++) { + const line_number = i + start_index; + const line = lines[i]; + const trimmed_line = line.trim(); + if (trimmed_line === "---") { + if (!frontmatter_started && line_number === 1) { + frontmatter_started = true; + in_frontmatter = true; + heading_lines["#---frontmatter---"] = [line_number, null]; + continue; + } else if (in_frontmatter) { + in_frontmatter = false; + heading_lines["#---frontmatter---"][1] = line_number; + continue; + } + } + if (in_frontmatter) { + continue; + } + if (!in_code_block && /^[-*+]\s+\[(?: |x|X)\]/.test(trimmed_line)) { + task_lines.push(line_number); + if (/^[-*+]\s+\[ \]/.test(trimmed_line)) { + if (!tasks.incomplete) tasks.incomplete = { all: [], top: [] }; + tasks.incomplete.all.push(line_number); + } + if (/^[-*+]\s+\[ \]/.test(line)) { + tasks.incomplete.top.push(line_number); + } + } + if (trimmed_line.startsWith("```")) { + in_code_block = !in_code_block; + if (in_code_block && !codeblock_start) codeblock_start = line_number; + else if (!in_code_block && codeblock_start) { + codeblock_ranges.push([codeblock_start, line_number]); + codeblock_start = null; + } + if (!current_content_block) { + const parent_key = heading_stack.length > 0 ? heading_stack[heading_stack.length - 1].key : root_heading_key; + if (parent_key === root_heading_key && !heading_lines[root_heading_key]) { + heading_lines[root_heading_key] = [line_number, null]; + } + if (parent_key === root_heading_key) { + current_content_block = { key: root_heading_key, start_line: line_number }; + if (heading_lines[root_heading_key][1] === null || heading_lines[root_heading_key][1] < line_number) { + heading_lines[root_heading_key][1] = null; + } + } else { + if (sub_block_counts[parent_key] === void 0) { + sub_block_counts[parent_key] = 0; + } + sub_block_counts[parent_key] += 1; + const n = sub_block_counts[parent_key]; + const key = `${parent_key}#{${n}}`; + heading_lines[key] = [line_number, null]; + current_content_block = { key, start_line: line_number }; + } + } + continue; + } + const heading_match = trimmed_line.match(/^(#{1,6})\s+(.+)$/); + if (heading_match && !in_code_block) { + const level = heading_match[1].length; + let title = heading_match[2].trim(); + while (heading_stack.length > 0 && heading_stack[heading_stack.length - 1].level >= level) { + const finished_heading = heading_stack.pop(); + if (heading_lines[finished_heading.key][1] === null) { + heading_lines[finished_heading.key][1] = line_number - 1; + } + } + if (heading_stack.length === 0 && heading_lines[root_heading_key] && heading_lines[root_heading_key][1] === null) { + heading_lines[root_heading_key][1] = line_number - 1; + } + if (current_content_block) { + if (heading_lines[current_content_block.key][1] === null) { + heading_lines[current_content_block.key][1] = line_number - 1; + } + current_content_block = null; + } + if (current_list_item) { + if (heading_lines[current_list_item.key][1] === null) { + heading_lines[current_list_item.key][1] = line_number - 1; + } + current_list_item = null; + } + let parent_key = ""; + let parent_level = 0; + if (heading_stack.length > 0) { + parent_key = heading_stack[heading_stack.length - 1].key; + parent_level = heading_stack[heading_stack.length - 1].level; + } else { + parent_key = ""; + parent_level = 0; + } + if (heading_stack.length === 0) { + heading_counts[title] = (heading_counts[title] || 0) + 1; + if (heading_counts[title] > 1) { + title += `[${heading_counts[title]}]`; + } + } else { + if (!subheading_counts[parent_key]) { + subheading_counts[parent_key] = {}; + } + subheading_counts[parent_key][title] = (subheading_counts[parent_key][title] || 0) + 1; + const count = subheading_counts[parent_key][title]; + if (count > 1) { + title += `#{${count}}`; + } + } + const level_diff = level - parent_level; + const hashes = "#".repeat(level_diff); + const key = parent_key + hashes + title; + heading_lines[key] = [line_number, null]; + sub_block_counts[key] = 0; + heading_stack.push({ level, title, key }); + continue; + } + const list_match = line.match(/^(\s*)([-*]|\d+\.) (.+)$/); + if (list_match && !in_code_block) { + const indentation = list_match[1].length; + if (indentation === 0) { + if (current_list_item) { + if (heading_lines[current_list_item.key][1] === null) { + heading_lines[current_list_item.key][1] = line_number - 1; + } + current_list_item = null; + } + if (current_content_block && current_content_block.key !== root_heading_key) { + if (heading_lines[current_content_block.key][1] === null) { + heading_lines[current_content_block.key][1] = line_number - 1; + } + current_content_block = null; + } + let parent_key = heading_stack.length > 0 ? heading_stack[heading_stack.length - 1].key : root_heading_key; + if (parent_key === root_heading_key && !heading_lines[root_heading_key]) { + heading_lines[root_heading_key] = [line_number, null]; + } + if (sub_block_counts[parent_key] === void 0) { + sub_block_counts[parent_key] = 0; + } + sub_block_counts[parent_key] += 1; + const n = sub_block_counts[parent_key]; + let key; + if (line_keys) { + const content_without_task = list_match[3].replace(/^\[(?: |x|X)\]\s*/, ""); + const words = get_longest_words_in_order(content_without_task, LIST_KEY_WORD_LEN); + key = `${parent_key}#${words}`; + } else { + key = `${parent_key}#{${n}}`; + } + heading_lines[key] = [line_number, null]; + current_list_item = { key, start_line: line_number }; + continue; + } + if (current_list_item) { + continue; + } + } + if (trimmed_line === "") { + continue; + } + if (!current_content_block) { + if (current_list_item) { + if (heading_lines[current_list_item.key][1] === null) { + heading_lines[current_list_item.key][1] = line_number - 1; + } + current_list_item = null; + } + let parent_key = heading_stack.length > 0 ? heading_stack[heading_stack.length - 1].key : root_heading_key; + if (parent_key === root_heading_key) { + if (!heading_lines[root_heading_key]) { + heading_lines[root_heading_key] = [line_number, null]; + } + if (heading_lines[root_heading_key][1] === null || heading_lines[root_heading_key][1] < line_number) { + heading_lines[root_heading_key][1] = null; + } + current_content_block = { key: root_heading_key, start_line: line_number }; + } else { + if (sub_block_counts[parent_key] === void 0) { + sub_block_counts[parent_key] = 0; + } + sub_block_counts[parent_key] += 1; + const n = sub_block_counts[parent_key]; + const key = `${parent_key}#{${n}}`; + heading_lines[key] = [line_number, null]; + current_content_block = { key, start_line: line_number }; + } + } + } + const total_lines = lines.length; + while (heading_stack.length > 0) { + const finished_heading = heading_stack.pop(); + if (heading_lines[finished_heading.key][1] === null) { + heading_lines[finished_heading.key][1] = total_lines + start_index - 1; + } + } + if (current_list_item) { + if (heading_lines[current_list_item.key][1] === null) { + heading_lines[current_list_item.key][1] = total_lines + start_index - 1; + } + current_list_item = null; + } + if (current_content_block) { + if (heading_lines[current_content_block.key][1] === null) { + heading_lines[current_content_block.key][1] = total_lines + start_index - 1; + } + current_content_block = null; + } + if (heading_lines[root_heading_key] && heading_lines[root_heading_key][1] === null) { + heading_lines[root_heading_key][1] = total_lines + start_index - 1; + } + for (const key in heading_lines) { + result[key] = heading_lines[key]; + } + return { blocks: result, task_lines, tasks, codeblock_ranges }; +} +function get_longest_words_in_order(line, n = 3) { + const words = line.split(/\s+/).sort((a, b) => b.length - a.length).slice(0, n); + return words.sort((a, b) => line.indexOf(a) - line.indexOf(b)).join(" "); +} + +// node_modules/obsidian-smart-env/node_modules/smart-sources/adapters/_file.js +var FileSourceContentAdapter = class extends SourceContentAdapter { + static async init_items(collection) { + if (collection.fs_items_initialized) return; + collection._fs = null; + await collection.fs.init(); + await collection.init_fs(); + for (const file of Object.values(collection.fs.files)) { + const item = collection.init_file_path(file.path); + if (item) item.init_file_mtime = file.stat.mtime; + } + collection.fs_items_initialized = Date.now(); + } + /** + * @name fs + * @type {Object} + * @readonly + * @description + * Access the file system interface used by this adapter. Typically derived + * from `this.item.collection.fs`. + */ + get fs() { + return this.item.collection.fs; + } + /** + * @name file_path + * @type {string} + * @readonly + * @description + * The file path on disk corresponding to the source. Used for read/write operations. + */ + get file_path() { + return this.item.file_path; + } + /** + * @async + * @method create + * @param {string|null} [content=null] Initial content for the new file. + * @description + * Create a new file on disk. If content is not provided, attempts to use + * `this.item.data.content` as fallback. + */ + async create(content = null) { + if (!content) content = this.item.data.content || ""; + await this.fs.write(this.file_path, content); + } + /** + * @async + * @method update + * @param {string} content The full new content to write to the file. + * @description + * Overwrite the entire file content on disk. + */ + async update(content) { + await this.fs.write(this.file_path, content); + } + /** + * @async + * @method read + * @returns {Promise} The content of the file. + * @description + * Read the file content from disk. Updates `last_read` hash and timestamp on the entity’s data. + * If file is large or special handling is needed, override this method. + */ + async read() { + const content = await this.fs.read(this.file_path); + this.data.last_read = { + hash: this.create_hash(content || ""), + at: Date.now() + }; + return content; + } + /** + * @async + * @method remove + * @returns {Promise} + * @description + * Delete the file from disk. After removal, the source item should also be deleted or updated accordingly. + */ + async remove() { + await this.fs.remove(this.file_path); + } + async move_to(move_to_ref) { + if (!move_to_ref) { + throw new Error("Invalid entity reference for move_to operation"); + } + const move_content = await this.read(); + let has_existing = false; + if (typeof move_to_ref === "string") { + const existing = this.item.collection.get(move_to_ref); + if (existing) { + move_to_ref = existing; + has_existing = true; + } + } else { + has_existing = true; + } + if (has_existing) { + await move_to_ref.append(move_content); + } else { + move_to_ref = await this.item.collection.create(move_to_ref, move_content); + } + if (this.item.key !== move_to_ref.key) { + await this.remove(); + this.item.delete(); + } else { + console.log(`did not delete ${this.item.key} because it was moved to ${move_to_ref.key}`); + } + return move_to_ref; + } + /** + * Merge content into the source + * @param {string} content - The content to merge into the source + * @param {Object} opts - Options for the merge operation + * @param {string} opts.mode - The mode to use for the merge operation. Defaults to 'append_blocks' (may also be 'replace_blocks') + */ + async merge(content, opts = {}) { + const { mode = "append_blocks" } = opts; + const { blocks: blocks_obj, task_lines } = parse_markdown_blocks(content); + if (typeof blocks_obj !== "object" || Array.isArray(blocks_obj)) { + console.warn("merge error: Expected an object from parse_markdown_blocks, but received:", blocks_obj); + throw new Error("merge error: parse_markdown_blocks did not return an object as expected."); + } + const { new_blocks, new_with_parent_blocks, changed_blocks, same_blocks } = await this.get_changes(blocks_obj, content); + for (const block of new_blocks) { + await this.append(block.content); + } + for (const block of new_with_parent_blocks) { + const parent_block = this.item.block_collection.get(block.parent_key); + await parent_block.append(block.content); + } + for (const block of changed_blocks) { + const changed_block = this.item.block_collection.get(block.key); + if (mode === "replace_blocks") { + await changed_block.update(block.content); + } else { + await changed_block.append(block.content); + } + } + } + async get_changes(blocks_obj, content) { + const new_blocks = []; + const new_with_parent_blocks = []; + const changed_blocks = []; + const same_blocks = []; + const existing_blocks = this.source.data.blocks || {}; + for (const [sub_key, line_range] of Object.entries(blocks_obj)) { + const has_existing = !!existing_blocks[sub_key]; + const block_key = `${this.source.key}${sub_key}`; + const block_content = get_line_range(content, line_range[0], line_range[1]); + if (!has_existing) { + new_blocks.push({ + key: block_key, + state: "new", + content: block_content + }); + continue; + } + let has_parent; + let headings = sub_key.split("#"); + let parent_key; + while (!has_parent && headings.length > 0) { + headings.pop(); + parent_key = headings.join("#"); + has_parent = !!existing_blocks[parent_key]; + } + if (has_parent) { + new_with_parent_blocks.push({ + key: block_key, + parent_key: `${this.source.key}${parent_key}`, + state: "new", + content: block_content + }); + continue; + } + const block = this.item.block_collection.get(block_key); + const content_hash = await this.create_hash(block_content); + if (content_hash !== block.last_read?.hash) { + changed_blocks.push({ + key: block_key, + state: "changed", + content: block_content + }); + continue; + } + same_blocks.push({ + key: block_key, + state: "same", + content: block_content + }); + } + return { + new_blocks, + new_with_parent_blocks, + changed_blocks, + same_blocks + }; + } + /** + * Append new content to the source file, placing it at the end of the file. + * @async + * @param {string} content - The content to append. + * @returns {Promise} + */ + async append(content) { + const current_content = await this.read(); + const new_content = [ + current_content, + "", + content + ].join("\n").trim(); + await this.update(new_content); + } + get size() { + return this.item.file?.stat?.size || 0; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_markdown_links.js +function get_markdown_links(content) { + const result = []; + const markdown_link_re = /\[([^\]]+?)\]\(([^)]+?)\)/g; + const wikilink_re = /\[\[([^\|\]]+?)(?:\|([^\]]+?))?\]\]/g; + const normalise_target = (raw) => { + const trimmed = raw.trim(); + if (/^[a-zA-Z][\w+\-.]*:\/\//.test(trimmed)) return trimmed; + try { + return decodeURIComponent(trimmed); + } catch (_) { + return trimmed.replace(/%20/gi, " "); + } + }; + const is_embedded = (index) => { + if (index <= 0) return false; + return content[index - 1] === "!"; + }; + let m; + while ((m = markdown_link_re.exec(content)) !== null) { + const title = m[1]; + const target = normalise_target(m[2]); + const line_no = content.slice(0, m.index).split("\n").length; + const embedded = is_embedded(m.index); + const record = { title, target, line: line_no }; + if (embedded) record.embedded = true; + result.push(record); + } + while ((m = wikilink_re.exec(content)) !== null) { + const target_raw = m[1]; + const title = m[2] || target_raw; + const target = normalise_target(target_raw); + const line_no = content.slice(0, m.index).split("\n").length; + const embedded = is_embedded(m.index); + const record = { title, target, line: line_no }; + if (embedded) record.embedded = true; + result.push(record); + } + return result.sort( + (a, b) => a.line - b.line || a.target.localeCompare(b.target) + ); +} + +// node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_bases_cache_links.js +function get_bases_cache_links({ source, links = [], cache } = {}) { + if (!source || !Array.isArray(links) || !links.length) return []; + const cache_items = cache || source?.env?.bases_caches?.items; + if (!cache_items) return []; + const source_key = source?.key || source?.path; + if (!source_key) return []; + return links.flatMap((link) => { + if (!link?.embedded) return []; + if (typeof link.target !== "string" || !link.target.includes(".base")) return []; + const cache_key = `${source_key}#${link.target}`; + const markdown_table = get_bases_markdown_table(cache_items?.[cache_key]); + if (!markdown_table) return []; + return get_bases_table_links({ markdown_table, line_override: link.line }); + }); +} +function get_bases_file_links({ source, cache } = {}) { + if (!source || typeof source !== "object") return []; + const cache_items = cache || source?.env?.bases_caches?.items; + if (!cache_items) return []; + const source_key = source?.key || source?.path; + if (!source_key) return []; + const markdown_table = get_bases_markdown_table(cache_items?.[source_key]); + if (!markdown_table) return []; + return get_bases_table_links({ markdown_table }); +} +function get_bases_markdown_table(cache_item) { + if (!cache_item) return ""; + if (typeof cache_item.markdown_table === "string") return cache_item.markdown_table; + if (typeof cache_item.markdown_table === "function") return cache_item.markdown_table(); + if (typeof cache_item?.data?.markdown_table === "string") return cache_item.data.markdown_table; + return ""; +} +function get_bases_table_links({ markdown_table, line_override } = {}) { + if (!markdown_table) return []; + const table_links = get_markdown_links(markdown_table); + if (!table_links.length) return []; + return table_links.map((table_link) => ({ + ...table_link, + line: typeof line_override === "number" ? line_override : table_link.line, + bases_row: table_link.line - 2 + // Adjust for table header rows + })); +} + +// node_modules/obsidian-smart-env/node_modules/smart-sources/utils/parse_frontmatter.js +function parse_value(raw_value) { + const trimmed = raw_value.trim(); + if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) { + return trimmed.slice(1, -1); + } + const lower = trimmed.toLowerCase(); + if (lower === "true") return true; + if (lower === "false") return false; + if (!isNaN(trimmed) && trimmed !== "") { + return Number(trimmed); + } + return trimmed; +} +function parse_yaml_block(yaml_block) { + const lines = yaml_block.split(/\r?\n/); + const data = {}; + let i = 0; + while (i < lines.length) { + const line = lines[i]; + i++; + if (!line.trim() || line.trim().startsWith("#")) { + continue; + } + const match = line.match(/^([^:]+)\s*:\s*(.*)$/); + if (!match) { + continue; + } + const key = match[1].trim(); + let value = match[2].trim(); + if (value === ">" || value === "|") { + const multiline_lines = []; + while (i < lines.length) { + const next_line = lines[i]; + if (!/^\s+/.test(next_line) || next_line.trim().startsWith("#")) { + break; + } + multiline_lines.push(next_line.replace(/^\s+/, "")); + i++; + } + const joined = multiline_lines.join("\n"); + data[key] = parse_value(joined); + } else if (value === "") { + const arr = []; + let array_consumed = false; + while (i < lines.length) { + const next_line = lines[i]; + if (!next_line.trim().startsWith("- ")) { + break; + } + const item_value = next_line.trim().slice(2); + arr.push(parse_value(item_value)); + i++; + array_consumed = true; + } + if (array_consumed) { + data[key] = arr; + } else { + data[key] = ""; + } + } else { + data[key] = parse_value(value); + } + } + return data; +} +function parse_frontmatter(content) { + if (!content.startsWith("---")) { + return { frontmatter: {}, body: content }; + } + const lines = content.split(/\r?\n/); + let end_index = -1; + for (let i = 1; i < lines.length; i++) { + if (lines[i].trim() === "---") { + end_index = i; + break; + } + } + if (end_index === -1) { + return { frontmatter: {}, body: content }; + } + const frontmatter_lines = lines.slice(1, end_index); + const frontmatter_block = frontmatter_lines.join("\n"); + const frontmatter = parse_yaml_block(frontmatter_block); + const body_lines = lines.slice(end_index + 1); + const body = body_lines.join("\n"); + return { frontmatter, body }; +} + +// node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_markdown_tags.js +var get_markdown_tags = (content = "") => { + const tag_re = /(?} + */ + async import() { + if (!this.can_import) return; + const is_outdated = this.outdated; + const has_incomplete_block_coverage = this.has_incomplete_block_coverage(); + const repairing_block_coverage = !is_outdated && has_incomplete_block_coverage; + if (!is_outdated && !has_incomplete_block_coverage) { + this.item.blocks.forEach((block) => { + if (!block.vec) block.queue_embed(); + }); + return; + } + const content = await this.read(); + if (!content) { + return; + } + if (!this.item.vec) { + this.item.data.last_import = null; + } + if (!has_incomplete_block_coverage && this.data.last_import?.hash === this.data.last_read?.hash) { + if (this.data.blocks) return; + } + this.data.blocks = null; + await this.parse_content(content); + await this.item.parse_content(content); + const { mtime, size } = this.item.file.stat; + this.data.last_import = { + mtime, + size, + at: Date.now(), + hash: this.data.last_read.hash + }; + this.item.loaded_at = Date.now(); + this.item.queue_save(); + if (this.item.should_embed && !repairing_block_coverage) this.item.queue_embed(); + } + // // WIP: move block parsing here + // async read() { + // const current_last_read_hash = this.data.last_read?.hash; + // const content = await super.read(); + // if(!content) return console.warn(`MarkdownSourceContentAdapter: Skipping missing-file: ${this.file_path}`); + // if(current_last_read_hash === this.data.last_read?.hash) return content; + // const {blocks: blocks, task_lines} = parse_markdown_blocks(content); + // this.handle_excluded_headings(blocks); + // } + // Runs before configured content_parsers (for example, templates uses outlinks) + async parse_content(content) { + const outlinks = await this.get_links(content); + this.data.outlinks = outlinks; + const metadata = await this.get_metadata(content); + this.data.metadata = metadata; + } + async get_links(content = null) { + if (!content) content = await this.read(); + if (!content) return; + const markdown_links = get_markdown_links(content); + const bases_links = get_bases_cache_links({ + source: this.item, + links: markdown_links + }); + return [ + ...markdown_links, + ...bases_links + ]; + } + async get_metadata(content = null) { + if (!content) content = await this.read(); + if (!content) return; + const { frontmatter, body } = parse_frontmatter(content); + const tag_set = /* @__PURE__ */ new Set(); + let fm_tags = frontmatter.tags; + if (typeof fm_tags === "string") { + fm_tags = fm_tags.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean); + } + if (Array.isArray(fm_tags)) { + fm_tags.forEach((tag) => tag_set.add(tag.startsWith("#") ? tag : `#${tag}`)); + } + get_markdown_tags(body).forEach((tag) => tag_set.add(tag)); + if (tag_set.size) frontmatter.tags = [...tag_set]; + return frontmatter; + } + has_incomplete_block_coverage() { + if (!this.data.blocks || !this.item.block_collection) return false; + return Object.entries(this.data.blocks).some(([sub_key, line_range]) => { + const block = this.item.block_collection.get(this.item.key + sub_key); + if (!block) return true; + const block_lines = block.lines || []; + return block_lines[0] !== line_range[0] || block_lines[1] !== line_range[1]; + }); + } + // Erroneous reasons to skip import (logs to console) + get can_import() { + if (!this.item.file) { + console.warn(`MarkdownSourceContentAdapter: Skipping missing-file: ${this.file_path}`); + return false; + } + if (this.item.size > (this.settings?.max_import_size || 3e5)) { + console.warn(`MarkdownSourceContentAdapter: Skipping large file: ${this.file_path}`); + return false; + } + return true; + } + /** + * @deprecated use outdated instead + */ + get should_import() { + return this.outdated; + } + get outdated() { + try { + if (!this.data.last_import) { + return true; + } + if (this.data.last_read.at > this.data.last_import.at) { + if (this.data.last_import?.hash !== this.data.last_read?.hash) return true; + } + if (this.data.last_import.mtime < this.item.mtime) { + if (!this.data.last_import.size) return true; + const size_diff = Math.abs(this.data.last_import.size - this.item.size); + const size_diff_ratio = size_diff / (this.data.last_import.size || 1); + if (size_diff_ratio > 0.01) return true; + } + return false; + } catch (e) { + console.warn(`MarkdownSourceContentAdapter: error getting should_import for ${this.file_path}: ${e}`); + return true; + } + } +}; + +// node_modules/obsidian-smart-env/adapters/smart-sources/obsidian_markdown.js +var import_obsidian2 = require("obsidian"); +function merge_tags(fm_tags, cache_tags = []) { + const tag_set = /* @__PURE__ */ new Set(); + if (typeof fm_tags === "string") { + fm_tags = fm_tags.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean); + } + if (Array.isArray(fm_tags)) { + fm_tags.filter((t) => typeof t === "string").forEach((tag) => tag_set.add(tag.startsWith("#") ? tag : `#${tag}`)); + } + cache_tags.forEach(({ tag }) => tag_set.add(tag)); + return [...tag_set]; +} +var ObsidianMarkdownSourceContentAdapter = class extends MarkdownSourceContentAdapter { + /** + * Returns metadata using Obsidian's metadataCache, merging frontmatter and tags. + * @async + * @returns {Promise} + */ + async get_metadata() { + const app2 = this.item.env.main.app; + const cache = app2.metadataCache.getFileCache(this.item.file) || {}; + const tags = merge_tags(cache.frontmatter?.tags, cache.tags); + if (cache.frontmatter) { + if (tags.length) cache.frontmatter.tags = tags; + return cache.frontmatter; + } + return tags.length ? { tags } : void 0; + } + /** + * Reads the file content. If opts.render_output is true, attempts to use + * Obsidian's MarkdownRenderer to render the file to HTML, then convert it + * back to markdown via htmlToMarkdown. + * @async + * @param {Object} [opts={}] - Options for reading. + * @param {boolean} [opts.render_output=false] - If true, render MD -> HTML -> MD. + * @returns {Promise} The file content (possibly rendered). + */ + async read(opts = {}) { + const content = await super.read(opts); + if (!opts.render_output) { + return content; + } + const app2 = this.item.env.main.app; + if (!app2 || !import_obsidian2.MarkdownRenderer || !import_obsidian2.htmlToMarkdown) { + console.warn("Obsidian environment not found; cannot render markdown."); + return content; + } + const container = document.createElement("div"); + await import_obsidian2.MarkdownRenderer.render(app2, content, container, this.item.path, new import_obsidian2.Component()); + let last_html = container.innerHTML; + const max_wait = 1e4; + let wait_time = 0; + let conseq_same = 0; + let changed = true; + while (conseq_same < 7) { + await new Promise((resolve) => setTimeout(resolve, 100)); + changed = last_html !== container.innerHTML; + last_html = container.innerHTML; + if (!changed) conseq_same++; + else conseq_same = 0; + wait_time += 100; + if (wait_time > max_wait) { + console.warn("ObsidianMarkdownSourceContentAdapter: Timeout waiting for markdown to render."); + break; + } + } + const newMd = (0, import_obsidian2.htmlToMarkdown)(container); + return newMd; + } +}; + +// node_modules/obsidian-smart-env/adapters/smart-sources/bases.js +var BasesSourceContentAdapter = class extends FileSourceContentAdapter { + static extensions = ["base"]; + async import() { + if (!this.item?.file) return; + const base_links = get_bases_file_links({ source: this.item }); + this.data.outlinks = base_links; + this.data.blocks = this.data.blocks || {}; + this.data.metadata = this.data.metadata || {}; + const { mtime = 0, size = 0 } = this.item.file?.stat || {}; + this.data.last_import = { + mtime, + size, + at: Date.now(), + hash: this.data.last_read?.hash + }; + this.item.loaded_at = Date.now(); + this.item.queue_save?.(); + } +}; + +// node_modules/obsidian-smart-env/adapters/smart-sources/rendered.js +var RenderedSourceContentAdapter = class extends FileSourceContentAdapter { + static extensions = ["rendered"]; + async import() { + } +}; + +// node_modules/obsidian-smart-env/adapters/smart-sources/canvas.js +function parse_canvas_json({ content } = {}) { + if (!content) return null; + try { + return JSON.parse(content); + } catch (error) { + console.warn("CanvasSourceContentAdapter: invalid JSON content.", error); + return null; + } +} +var CanvasSourceContentAdapter = class extends FileSourceContentAdapter { + static extensions = ["canvas"]; + async import() { + if (!this.item.file) { + console.warn(`CanvasSourceContentAdapter: Skipping missing-file: ${this.file_path}`); + return; + } + const content = await this.read(); + if (!content) return; + if (this.data.last_import?.hash === this.data.last_read?.hash && Array.isArray(this.data.outlinks)) { + return; + } + const canvas_data = parse_canvas_json({ content }); + const outlinks = []; + if (Array.isArray(canvas_data?.nodes)) { + const source_collection = this.item.collection; + for (let i = 0; i < canvas_data.nodes.length; i++) { + const node = canvas_data.nodes[i]; + if (!node || typeof node !== "object") return []; + if (node.type === "text" && typeof node.text === "string") { + outlinks.push(...get_markdown_links(node.text)); + } + if (node.type === "file" && typeof node.file === "string") { + const source_key = node.file; + const source = source_collection.get(source_key); + if (source) { + let key = source_key; + outlinks.push({ + title: key, + target: key, + line: 1, + embedded: true + }); + } + } + } + } + this.data.outlinks = outlinks; + const file_stat = this.item.file?.stat; + const size = file_stat?.size ?? content.length; + const mtime = file_stat?.mtime ?? 0; + this.data.last_import = { + mtime, + size, + at: Date.now(), + hash: this.data.last_read?.hash + }; + this.item.loaded_at = Date.now(); + this.item.queue_save(); + } +}; + +// node_modules/obsidian-smart-env/adapters/smart-sources/excalidraw.js +var ExcalidrawSourceContentAdapter = class extends ObsidianMarkdownSourceContentAdapter { + static extensions = ["excalidraw.md"]; + is_media = true; + // Excalidraw files are treated as media for rendering + async read(opts = {}) { + const full_content = await super.read(opts); + const BEGIN_LINE_MATCHER = "# Text Elements"; + const END_LINE_MATCHER = "# Drawing"; + const text_elements_start = full_content.indexOf(BEGIN_LINE_MATCHER); + const drawing_lines_start = full_content.indexOf(END_LINE_MATCHER); + if (text_elements_start === -1 || drawing_lines_start === -1) { + console.warn("Excalidraw file does not contain expected sections. File: " + this.item.key); + this.item.data.last_read.size = 0; + return ""; + } + const text_content = full_content.slice(text_elements_start + BEGIN_LINE_MATCHER.length, drawing_lines_start).trim(); + const stripped_refs = text_content.split("\n").map((line) => { + if (line.trim() === "%%") return ""; + if (line.trim() === "#") return ""; + return line.replace(/\^[a-z0-9]+$/i, "").trim(); + }).filter(Boolean).join("\n"); + this.item.data.last_read.size = stripped_refs.length; + return stripped_refs; + } + get size() { + if (this.item.data?.last_read?.size) { + return this.item.data.last_read.size; + } + return this.file?.stat?.size || 0; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-model/smart_model.js +var SmartModel = class { + scope_name = "smart_model"; + static defaults = { + // override in sub-class if needed + }; + /** + * Create a SmartModel instance. + * @param {Object} opts - Configuration options + * @param {Object} opts.adapters - Map of adapter names to adapter classes + * @param {Object} opts.settings - Model settings configuration + * @param {string} [opts.model_key] - Optional model identifier to override settings + * @throws {Error} If required options are missing + */ + constructor(opts = {}) { + this.opts = opts; + this.validate_opts(opts); + this.state = "unloaded"; + this._adapter = null; + this.data = opts; + } + /** + * Initialize the model by loading the configured adapter. + * @async + * @returns {Promise} + */ + async initialize() { + this.load_adapter(this.adapter_name); + await this.load(); + } + /** + * Validate required options. + * @param {Object} opts - Configuration options + */ + validate_opts(opts) { + if (!opts.adapters) throw new Error("opts.adapters is required"); + if (!opts.settings) throw new Error("opts.settings is required"); + } + /** + * Get the current settings + * @returns {Object} Current settings + */ + get settings() { + if (!this.opts.settings) this.opts.settings = { + ...this.constructor.defaults + }; + return this.opts.settings; + } + /** + * Get the current adapter name + * @returns {string} Current adapter name + */ + get adapter_name() { + let adapter_key = this.opts.adapter || this.settings.adapter || Object.keys(this.adapters)[0]; + if (!adapter_key || !this.adapters[adapter_key]) { + console.warn(`Platform "${adapter_key}" not supported`); + adapter_key = Object.keys(this.adapters)[0]; + } + return adapter_key; + } + /** + * Get available models. + * @returns {Object} Map of model objects + */ + get models() { + return this.adapter.models; + } + /** + * Get default model key. + * @returns {string} Default model key + */ + get default_model_key() { + return this.adapter.constructor.defaults.default_model; + } + /** + * Get the current model key + * @returns {string} Current model key + */ + get model_key() { + return this.opts.model_key || this.settings.model_key || this.default_model_key; + } + /** + * Load the current adapter and transition to loaded state. + * @async + * @returns {Promise} + */ + async load() { + this.set_state("loading"); + try { + if (!this.adapter?.is_loaded) { + await this.invoke_adapter_method("load"); + } + } catch (err) { + this.set_state("unloaded"); + if (!this.reload_model_timeout) { + this.reload_model_timeout = setTimeout(async () => { + this.reload_model_timeout = null; + await this.load(); + this.set_state("loaded"); + this.env?.events?.emit("model:loaded", { model_key: this.model_key }); + }, 6e4); + } + throw new Error(`Failed to load model: ${err.message}`); + } + this.set_state("loaded"); + } + /** + * Unload the current adapter and transition to unloaded state. + * @async + * @returns {Promise} + */ + async unload() { + if (this.adapter?.is_loaded) { + this.set_state("unloading"); + await this.invoke_adapter_method("unload"); + this.set_state("unloaded"); + } + } + /** + * Set the model's state. + * @param {('unloaded'|'loading'|'loaded'|'unloading')} new_state - The new state + * @throws {Error} If the state is invalid + */ + set_state(new_state) { + const valid_states = ["unloaded", "loading", "loaded", "unloading"]; + if (!valid_states.includes(new_state)) { + throw new Error(`Invalid state: ${new_state}`); + } + this.state = new_state; + } + get is_loading() { + return this.state === "loading"; + } + get is_loaded() { + return this.state === "loaded"; + } + get is_unloading() { + return this.state === "unloading"; + } + get is_unloaded() { + return this.state === "unloaded"; + } + // ADAPTERS + /** + * Get the map of available adapters + * @returns {Object} Map of adapter names to adapter classes + */ + get adapters() { + return this.opts.adapters || {}; + } + /** + * Load a specific adapter by name. + * @async + * @param {string} adapter_name - Name of the adapter to load + * @throws {Error} If adapter not found or loading fails + * @returns {Promise} + */ + async load_adapter(adapter_name) { + this.set_adapter(adapter_name); + if (!this._adapter.loaded) { + this.set_state("loading"); + try { + await this.invoke_adapter_method("load"); + this.set_state("loaded"); + } catch (err) { + this.set_state("unloaded"); + throw new Error(`Failed to load adapter: ${err.message}`); + } + } + } + /** + * Set an adapter instance by name without loading it. + * @param {string} adapter_name - Name of the adapter to set + * @throws {Error} If adapter not found + */ + set_adapter(adapter_name) { + const AdapterClass = this.adapters[adapter_name]; + if (!AdapterClass) { + throw new Error(`Adapter "${adapter_name}" not found.`); + } + if (this._adapter?.constructor.name.toLowerCase() === adapter_name.toLowerCase()) { + return; + } + this._adapter = new AdapterClass(this); + } + /** + * Get the current active adapter instance + * @returns {Object} The active adapter instance + * @throws {Error} If adapter not found + */ + get adapter() { + const adapter_name = this.adapter_name; + if (!adapter_name) { + throw new Error(`Adapter not set for model.`); + } + if (!this._adapter) { + this.load_adapter(adapter_name); + } + return this._adapter; + } + /** + * Ensure the adapter is ready to execute a method. + * @param {string} method - Name of the method to check + * @throws {Error} If adapter not loaded or method not implemented + */ + ensure_adapter_ready(method) { + if (!this.adapter) { + throw new Error("No adapter loaded."); + } + if (typeof this.adapter[method] !== "function") { + throw new Error(`Adapter does not implement method: ${method}`); + } + } + /** + * Invoke a method on the current adapter. + * @async + * @param {string} method - Name of the method to call + * @param {...any} args - Arguments to pass to the method + * @returns {Promise} Result from the adapter method + * @throws {Error} If adapter not ready or method fails + */ + async invoke_adapter_method(method, ...args) { + this.ensure_adapter_ready(method); + return await this.adapter[method](...args); + } + /** + * Get platforms as dropdown options. + * @returns {Array} Array of {value, name} option objects + */ + get_platforms_as_options() { + return Object.entries(this.adapters).map(([key, AdapterClass]) => ({ value: key, name: AdapterClass.defaults.description || key })); + } + // SETTINGS + /** + * Get the settings configuration schema + * @returns {Object} Settings configuration object + */ + get settings_config() { + return this.process_settings_config({ + adapter: { + name: "Model Platform", + type: "dropdown", + description: "Select a model platform to use with Smart Model.", + options_callback: "get_platforms_as_options", + is_scope: true, + // trigger re-render of settings when changed + callback: "adapter_changed", + default: "default" + } + }); + } + /** + * Process settings configuration with conditionals and prefixes. + * @param {Object} _settings_config - Raw settings configuration + * @param {string} [prefix] - Optional prefix for setting keys + * @returns {Object} Processed settings configuration + */ + process_settings_config(_settings_config, prefix = null) { + return Object.entries(_settings_config).reduce((acc, [key, val]) => { + const new_key = (prefix ? prefix + "." : "") + this.process_setting_key(key); + acc[new_key] = val; + return acc; + }, {}); + } + /** + * Process an individual setting key. + * Example: replace placeholders with actual adapter names. + * @param {string} key - The setting key with placeholders. + * @returns {string} Processed setting key. + */ + process_setting_key(key) { + return key.replace(/\[ADAPTER\]/g, this.adapter_name); + } + re_render_settings() { + if (typeof this.opts.re_render_settings === "function") this.opts.re_render_settings(); + else console.warn("re_render_settings is not a function (must be passed in model opts)"); + } + /** + * Reload model. + */ + reload_model() { + if (typeof this.opts.reload_model === "function") this.opts.reload_model(); + else console.warn("reload_model is not a function (must be passed in model opts)"); + } + adapter_changed() { + this.reload_model(); + this.re_render_settings(); + } + model_changed() { + this.reload_model(); + this.re_render_settings(); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-embed-model/smart_embed_model.js +var SmartEmbedModel = class extends SmartModel { + scope_name = "smart_embed_model"; + static defaults = { + adapter: "transformers" + }; + /** + * Create a SmartEmbedModel instance + * @param {Object} opts - Configuration options + * @param {Object} [opts.adapters] - Map of available adapter implementations + * @param {number} [opts.batch_size] - Default batch size for processing + * @param {Object} [opts.settings] - User settings + * @param {string} [opts.settings.api_key] - API key for remote models + * @param {number} [opts.settings.min_chars] - Minimum text length to embed + */ + constructor(opts = {}) { + super(opts); + } + /** + * Count tokens in an input string + * @param {string} input - Text to tokenize + * @returns {Promise} Token count result + * @property {number} tokens - Number of tokens in input + * + * @example + * ```javascript + * const result = await model.count_tokens("Hello world"); + * console.log(result.tokens); // 2 + * ``` + */ + async count_tokens(input) { + return await this.invoke_adapter_method("count_tokens", input); + } + /** + * Generate embeddings for a single input + * @param {string|Object} input - Text or object with embed_input property + * @returns {Promise} Embedding result + * @property {number[]} vec - Embedding vector + * @property {number} tokens - Token count + * + * @example + * ```javascript + * const result = await model.embed("Hello world"); + * console.log(result.vec); // [0.1, 0.2, ...] + * ``` + */ + async embed(input) { + if (typeof input === "string") input = { embed_input: input }; + return (await this.embed_batch([input]))[0]; + } + /** + * Generate embeddings for multiple inputs in batch + * @param {Array} inputs - Array of texts or objects with embed_input + * @returns {Promise>} Array of embedding results + * @property {number[]} vec - Embedding vector for each input + * @property {number} tokens - Token count for each input + * + * @example + * ```javascript + * const results = await model.embed_batch([ + * { embed_input: "First text" }, + * { embed_input: "Second text" } + * ]); + * ``` + */ + async embed_batch(inputs) { + return await this.invoke_adapter_method("embed_batch", inputs); + } + /** + * Get the current batch size based on GPU settings + * @returns {number} Current batch size for processing + */ + get batch_size() { + return this.adapter.batch_size || 1; + } + /** + * Get settings configuration schema + * @returns {Object} Settings configuration object + */ + get settings_config() { + const _settings_config = { + adapter: { + name: "Embedding model platform", + type: "dropdown", + description: "Select an embedding model platform. The default 'transformers' utilizes built-in local models.", + options_callback: "get_platforms_as_options", + callback: "adapter_changed", + default: this.constructor.defaults.adapter + }, + ...this.adapter.settings_config || {} + }; + return this.process_settings_config(_settings_config); + } + process_setting_key(key) { + return key.replace(/\[ADAPTER\]/g, this.adapter_name); + } + /** + * Get available embedding model options + * @returns {Array} Array of model options with value and name + */ + get_embedding_model_options() { + return Object.entries(this.models).map(([key, model]) => ({ value: key, name: key })); + } + // /** + // * Get embedding model options including 'None' option + // * @returns {Array} Array of model options with value and name + // */ + // get_block_embedding_model_options() { + // const options = this.get_embedding_model_options(); + // options.unshift({ value: 'None', name: 'None' }); + // return options; + // } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-model/adapters/_adapter.js +var SmartModelAdapter = class { + /** + * Create a SmartModelAdapter instance. + * @param {SmartModel} model - The parent SmartModel instance + */ + constructor(model) { + this.model = model; + this.state = "unloaded"; + } + /** + * Load the adapter. + * @async + * @returns {Promise} + */ + async load() { + this.set_state("loaded"); + } + /** + * Unload the adapter. + * @returns {void} + */ + unload() { + this.set_state("unloaded"); + } + /** + * Get all settings. + * @returns {Object} All settings + */ + get settings() { + return this.model.settings; + } + /** + * Get the current model key. + * @returns {string} Current model identifier + */ + get model_key() { + return this.model.model_key; + } + /** + * Get the models. + * @returns {Object} Map of model objects + */ + get models() { + const models = this.model.data.provider_models; + if (typeof models === "object" && Object.keys(models || {}).length > 0) return models; + else { + return {}; + } + } + /** + * Get available models from the API. + * @abstract + * @param {boolean} [refresh=false] - Whether to refresh cached models + * @returns {Promise} Map of model objects + */ + async get_models(refresh = false) { + throw new Error("get_models not implemented"); + } + /** + * Get available models as dropdown options synchronously. + * @returns {Array} Array of model options. + */ + get_models_as_options() { + const models = this.models; + if (!Object.keys(models || {}).length) { + this.get_models(true); + return [{ value: "", name: "No models currently available" }]; + } + return Object.entries(models).map(([id, model]) => ({ value: id, name: model.name || id })).sort((a, b) => a.name.localeCompare(b.name)); + } + /** + * Set the adapter's state. + * @deprecated should be handled in SmartModel (only handle once) + * @param {('unloaded'|'loading'|'loaded'|'unloading')} new_state - The new state + * @throws {Error} If the state is invalid + */ + set_state(new_state) { + const valid_states = ["unloaded", "loading", "loaded", "unloading"]; + if (!valid_states.includes(new_state)) { + throw new Error(`Invalid state: ${new_state}`); + } + this.state = new_state; + } + // Replace individual state getters/setters with a unified state management + get is_loading() { + return this.state === "loading"; + } + get is_loaded() { + return this.state === "loaded"; + } + get is_unloading() { + return this.state === "unloading"; + } + get is_unloaded() { + return this.state === "unloaded"; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/_adapter.js +var SmartEmbedAdapter = class extends SmartModelAdapter { + /** + * @override in sub-class with adapter-specific default configurations + * @property {string} id - The adapter identifier + * @property {string} description - Human-readable description + * @property {string} type - Adapter type ("API") + * @property {string} endpoint - API endpoint + * @property {string} adapter - Adapter identifier + * @property {string} default_model - Default model to use + */ + static defaults = {}; + /** + * Count tokens in input text + * @abstract + * @param {string} input - Text to tokenize + * @returns {Promise} Token count result + * @property {number} tokens - Number of tokens in input + * @throws {Error} If not implemented by subclass + */ + async count_tokens(input) { + throw new Error("count_tokens method not implemented"); + } + /** + * Generate embeddings for single input + * @abstract + * @param {string|Object} input - Text to embed + * @returns {Promise} Embedding result + * @property {number[]} vec - Embedding vector + * @property {number} tokens - Number of tokens in input + * @throws {Error} If not implemented by subclass + */ + async embed(input) { + if (typeof input === "string") input = { embed_input: input }; + return (await this.embed_batch([input]))[0]; + } + /** + * Generate embeddings for multiple inputs + * @abstract + * @param {Array} inputs - Texts to embed + * @returns {Promise>} Array of embedding results + * @property {number[]} vec - Embedding vector for each input + * @property {number} tokens - Number of tokens in each input + * @throws {Error} If not implemented by subclass + */ + async embed_batch(inputs) { + throw new Error("embed_batch method not implemented"); + } + get settings_config() { + return { + "[ADAPTER].model_key": { + name: "Embedding model", + type: "dropdown", + description: "Select an embedding model.", + options_callback: "adapter.get_models_as_options", + callback: "model_changed", + default: this.constructor.defaults.default_model + } + }; + } + get dims() { + return this.model.data.dims; + } + get max_tokens() { + return this.model.data.max_tokens; + } + get batch_size() { + return this.model.data.batch_size || 1; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/_message.js +var SmartEmbedMessageAdapter = class extends SmartEmbedAdapter { + /** + * Create message adapter instance + */ + constructor(model) { + super(model); + this.message_queue = {}; + this.message_id = 0; + this.connector = null; + this.message_prefix = `msg_${Math.random().toString(36).substr(2, 9)}_`; + } + /** + * Send message and wait for response + * @protected + * @param {string} method - Method name to call + * @param {Object} params - Method parameters + * @returns {Promise} Response data + */ + async _send_message(method, params) { + return new Promise((resolve, reject) => { + const id = `${this.message_prefix}${this.message_id++}`; + this.message_queue[id] = { resolve, reject }; + try { + this._post_message({ method, params, id }); + } catch (error) { + delete this.message_queue[id]; + reject(error instanceof Error ? error : new Error(String(error || "Unknown error"))); + } + }); + } + unload() { + const unload_error = new Error("Message adapter unloaded"); + Object.values(this.message_queue).forEach((queue_entry) => { + if (!queue_entry) return; + if (typeof this.clear_message_timeout === "function") { + this.clear_message_timeout(queue_entry); + } + queue_entry.reject(unload_error); + }); + this.message_queue = {}; + super.unload(); + } + /** + * Handle response message from worker/iframe + * @protected + * @param {string} id - Message ID + * @param {*} result - Response result + * @param {Error} [error] - Response error + */ + _handle_message_result(id, result, error) { + if (!id.startsWith(this.message_prefix)) return; + if (result?.model_loaded) { + console.log("model loaded"); + this.state = "loaded"; + this.model.model_loaded = true; + this.model.load_result = result; + } + if (this.message_queue[id]) { + if (error) { + this.message_queue[id].reject(new Error(error)); + } else { + this.message_queue[id].resolve(result); + } + delete this.message_queue[id]; + } + } + /** + * Count tokens in input text + * @param {string} input - Text to tokenize + * @returns {Promise} Token count result + */ + async count_tokens(input) { + return this._send_message("count_tokens", { input }); + } + /** + * Generate embeddings for multiple inputs + * @param {Array} inputs - Array of input objects + * @returns {Promise>} Processed inputs with embeddings + */ + async embed_batch(inputs) { + inputs = inputs.filter((item) => item.embed_input?.length > 0); + if (!inputs.length) return []; + const embed_inputs = inputs.map((item) => ({ embed_input: item.embed_input })); + const result = await this._send_message("embed_batch", { inputs: embed_inputs }); + return inputs.map((item, i) => { + const item_result = result[i] || {}; + if ("vec" in item_result) item.vec = item_result.vec; + if ("tokens" in item_result) item.tokens = item_result.tokens; + if ("error" in item_result) item.error = item_result.error; + else delete item.error; + return item; + }); + } + /** + * Post message to worker/iframe + * @abstract + * @protected + * @param {Object} message_data - Message to send + * @throws {Error} If not implemented by subclass + */ + _post_message(message_data) { + throw new Error("_post_message must be implemented by subclass"); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/iframe.js +var SmartEmbedIframeAdapter = class extends SmartEmbedMessageAdapter { + /** + * Create iframe adapter instance + */ + constructor(model) { + super(model); + this.iframe = null; + this.origin = window.location.origin; + this.iframe_id = `smart_embed_iframe`; + this._bound_handle_message = this._handle_message.bind(this); + } + /** + * Initialize iframe and load model + * @returns {Promise} + */ + async load() { + this.unload(); + const existing_iframe = document.getElementById(this.iframe_id); + if (existing_iframe) { + existing_iframe.onload = null; + existing_iframe.remove(); + } + this.iframe = document.createElement("iframe"); + this.iframe.style.display = "none"; + this.iframe.id = this.iframe_id; + document.body.appendChild(this.iframe); + window.addEventListener("message", this._bound_handle_message); + this.iframe.srcdoc = ` + + + + + + `; + await new Promise((resolve) => this.iframe.onload = resolve); + const load_opts = { + // ...this.model.opts, + model_key: this.model.model_key, + adapters: null, + // cannot clone classes + settings: null, + batch_size: this.batch_size, + use_gpu: this.use_gpu + }; + await this._send_message("load", load_opts); + return new Promise((resolve) => { + const check_model_loaded = () => { + if (this.model.model_loaded) { + resolve(); + } else { + setTimeout(check_model_loaded, 100); + } + }; + check_model_loaded(); + }); + } + /** + * Detect expected cancellation caused by tearing down the iframe adapter + * while a background load is in flight. + * @param {Error|*} error + * @returns {boolean} + */ + is_unload_error(error) { + return error?.message === "Message adapter unloaded"; + } + /** + * Start loading in the background and suppress only expected unload + * cancellation from fire-and-forget call sites. + * @returns {Promise} + */ + load_background() { + if (this._load_background_promise) { + return this._load_background_promise; + } + this._load_background_promise = Promise.resolve(this.load()).catch((error) => { + if (this.is_unload_error(error)) return; + console.error(`[${this.constructor.name}] load failed`, error); + }).finally(() => { + this._load_background_promise = null; + }); + return this._load_background_promise; + } + unload() { + window.removeEventListener("message", this._bound_handle_message); + const iframe = this.iframe || document.getElementById(this.iframe_id); + if (iframe) { + iframe.onload = null; + iframe.remove(); + } + this.iframe = null; + if (this.model) { + this.model.model_loaded = false; + this.model.load_result = null; + } + super.unload(); + } + /** + * Post message to iframe + * @protected + * @param {Object} message_data - Message to send + */ + _post_message(message_data) { + if (!this.iframe?.contentWindow) { + throw new Error("Iframe not loaded"); + } + this.iframe.contentWindow.postMessage({ ...message_data, iframe_id: this.iframe_id }, this.origin); + } + /** + * Handle message from iframe + * @private + * @param {MessageEvent} event - Message event + */ + _handle_message(event) { + if (event.origin !== this.origin || event.data?.iframe_id !== this.iframe_id) return; + if (event.source !== this.iframe?.contentWindow) return; + const { id, result, error } = event.data; + this._handle_message_result(id, result, error); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-embed-model/connectors/transformers_iframe.js +var transformers_connector = 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// ../smart-model/adapters/_adapter.js\nvar SmartModelAdapter = class {\n /**\n * Create a SmartModelAdapter instance.\n * @param {SmartModel} model - The parent SmartModel instance\n */\n constructor(model2) {\n this.model = model2;\n this.state = "unloaded";\n }\n /**\n * Load the adapter.\n * @async\n * @returns {Promise}\n */\n async load() {\n this.set_state("loaded");\n }\n /**\n * Unload the adapter.\n * @returns {void}\n */\n unload() {\n this.set_state("unloaded");\n }\n /**\n * Get all settings.\n * @returns {Object} All settings\n */\n get settings() {\n return this.model.settings;\n }\n /**\n * Get the current model key.\n * @returns {string} Current model identifier\n */\n get model_key() {\n return this.model.model_key;\n }\n /**\n * Get the models.\n * @returns {Object} Map of model objects\n */\n get models() {\n const models = this.model.data.provider_models;\n if (typeof models === "object" && Object.keys(models || {}).length > 0) return models;\n else {\n return {};\n }\n }\n /**\n * Get available models from the API.\n * @abstract\n * @param {boolean} [refresh=false] - Whether to refresh cached models\n * @returns {Promise} Map of model objects\n */\n async get_models(refresh = false) {\n throw new Error("get_models not implemented");\n }\n /**\n * Get available models as dropdown options synchronously.\n * @returns {Array} Array of model options.\n */\n get_models_as_options() {\n const models = this.models;\n if (!Object.keys(models || {}).length) {\n this.get_models(true);\n return [{ value: "", name: "No models currently available" }];\n }\n return Object.entries(models).map(([id, model2]) => ({ value: id, name: model2.name || id })).sort((a, b) => a.name.localeCompare(b.name));\n }\n /**\n * Set the adapter\'s state.\n * @deprecated should be handled in SmartModel (only handle once)\n * @param {(\'unloaded\'|\'loading\'|\'loaded\'|\'unloading\')} new_state - The new state\n * @throws {Error} If the state is invalid\n */\n set_state(new_state) {\n const valid_states = ["unloaded", "loading", "loaded", "unloading"];\n if (!valid_states.includes(new_state)) {\n throw new Error(`Invalid state: ${new_state}`);\n }\n this.state = new_state;\n }\n // Replace individual state getters/setters with a unified state management\n get is_loading() {\n return this.state === "loading";\n }\n get is_loaded() {\n return this.state === "loaded";\n }\n get is_unloading() {\n return this.state === "unloading";\n }\n get is_unloaded() {\n return this.state === "unloaded";\n }\n};\n\n// adapters/_adapter.js\nvar SmartEmbedAdapter = class extends SmartModelAdapter {\n /**\n * Count tokens in input text\n * @abstract\n * @param {string} input - Text to tokenize\n * @returns {Promise} Token count result\n * @property {number} tokens - Number of tokens in input\n * @throws {Error} If not implemented by subclass\n */\n async count_tokens(input) {\n throw new Error("count_tokens method not implemented");\n }\n /**\n * Generate embeddings for single input\n * @abstract\n * @param {string|Object} input - Text to embed\n * @returns {Promise} Embedding result\n * @property {number[]} vec - Embedding vector\n * @property {number} tokens - Number of tokens in input\n * @throws {Error} If not implemented by subclass\n */\n async embed(input) {\n if (typeof input === "string") input = { embed_input: input };\n return (await this.embed_batch([input]))[0];\n }\n /**\n * Generate embeddings for multiple inputs\n * @abstract\n * @param {Array} inputs - Texts to embed\n * @returns {Promise>} Array of embedding results\n * @property {number[]} vec - Embedding vector for each input\n * @property {number} tokens - Number of tokens in each input\n * @throws {Error} If not implemented by subclass\n */\n async embed_batch(inputs) {\n throw new Error("embed_batch method not implemented");\n }\n get settings_config() {\n return {\n "[ADAPTER].model_key": {\n name: "Embedding model",\n type: "dropdown",\n description: "Select an embedding model.",\n options_callback: "adapter.get_models_as_options",\n callback: "model_changed",\n default: this.constructor.defaults.default_model\n }\n };\n }\n get dims() {\n return this.model.data.dims;\n }\n get max_tokens() {\n return this.model.data.max_tokens;\n }\n get batch_size() {\n return this.model.data.batch_size || 1;\n }\n};\n/**\n * @override in sub-class with adapter-specific default configurations\n * @property {string} id - The adapter identifier\n * @property {string} description - Human-readable description\n * @property {string} type - Adapter type ("API")\n * @property {string} endpoint - API endpoint\n * @property {string} adapter - Adapter identifier\n * @property {string} default_model - Default model to use\n */\n__publicField(SmartEmbedAdapter, "defaults", {});\n\n// adapters/transformers.js\nvar transformers_defaults = {\n adapter: "transformers",\n description: "Transformers (Local, built-in)",\n default_model: "TaylorAI/bge-micro-v2",\n models: transformers_models\n};\nvar DEVICE_CONFIGS = {\n // // WebGPU: high quality first\n webgpu_fp16: {\n device: "webgpu",\n dtype: "fp16",\n quantized: false\n },\n webgpu_fp32: {\n device: "webgpu",\n dtype: "fp32",\n quantized: false\n },\n // WebGPU: quantized tiers\n webgpu_q8: {\n device: "webgpu",\n dtype: "q8",\n quantized: true\n },\n webgpu_q4: {\n device: "webgpu",\n dtype: "q4",\n quantized: true\n },\n // Optional, if you use it\n webgpu_q4f16: {\n device: "webgpu",\n dtype: "q4f16",\n quantized: true\n },\n webgpu_bnb4: {\n device: "webgpu",\n dtype: "bnb4",\n quantized: true\n },\n // WASM: quantized CPU\n wasm_q8: {\n dtype: "q8",\n quantized: true\n },\n wasm_q4: {\n dtype: "q4",\n quantized: true\n },\n // Final universal fallback: WASM CPU, dtype = auto\n wasm_auto: {\n // NOTE: leaving out device to avoid Linux issues with \'wasm\'\n // transformers.js will pick CPU/WASM backend itself\n quantized: false\n }\n};\nvar is_webgpu_available = async () => {\n if (!("gpu" in navigator)) return false;\n const adapter = await navigator.gpu.requestAdapter();\n if (!adapter) return false;\n return true;\n};\nvar SmartEmbedTransformersAdapter = class extends SmartEmbedAdapter {\n /**\n * @param {import("../smart_embed_model.js").SmartEmbedModel} model\n */\n constructor(model2) {\n super(model2);\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.has_gpu = false;\n }\n /**\n * Load the underlying transformers pipeline with WebGPU \u2192 WASM fallback.\n * @returns {Promise}\n */\n async load() {\n this.has_gpu = await is_webgpu_available();\n try {\n if (this.loading) {\n console.warn("[Transformers v3] load already in progress, waiting...");\n while (this.loading) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n } else {\n this.loading = true;\n if (this.pipeline) {\n this.loaded = true;\n this.loading = false;\n return;\n }\n await this.load_transformers_with_fallback();\n this.loading = false;\n this.loaded = true;\n console.log(`[Transformers v3] model loaded using ${this.active_config_key}`, this);\n }\n } catch (e) {\n this.loading = false;\n this.loaded = false;\n console.error("[Transformers v3] load failed", e);\n throw e;\n }\n }\n /**\n * Unload the pipeline and free resources.\n * @returns {Promise}\n */\n async unload() {\n try {\n if (this.pipeline) {\n if (typeof this.pipeline.destroy === "function") {\n this.pipeline.destroy();\n } else if (typeof this.pipeline.dispose === "function") {\n this.pipeline.dispose();\n }\n }\n } catch (err) {\n console.warn("[Transformers v3] error while disposing pipeline", err);\n }\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.loaded = false;\n }\n /**\n * Available models \u2013 reuses the v1 transformers model catalog.\n * @returns {Object}\n */\n get models() {\n return transformers_models;\n }\n /**\n * Maximum tokens per input.\n * @returns {number}\n */\n get max_tokens() {\n return this.model.data.max_tokens || 512;\n }\n /**\n * Effective batch size.\n * Prefers small deterministic batches when not explicitly configured.\n * @returns {number}\n */\n get batch_size() {\n const configured = this.model.data.batch_size;\n if (configured && configured > 0) return configured;\n return this.gpu_enabled ? 16 : 8;\n }\n get gpu_enabled() {\n if (this.has_gpu) {\n const explicit = typeof this.model.data.use_gpu === "boolean" ? this.model.data.use_gpu : null;\n if (explicit === false) return false;\n return true;\n } else {\n return false;\n }\n }\n /**\n * Initialize transformers pipeline with WebGPU \u2192 WASM fallback.\n * @private\n * @returns {Promise}\n */\n async load_transformers_with_fallback() {\n const { pipeline, env, AutoTokenizer } = await import("@huggingface/transformers");\n env.allowLocalModels = false;\n if (typeof env.useBrowserCache !== "undefined") {\n env.useBrowserCache = true;\n }\n let last_error = null;\n const CONFIG_LIST_ORDER = Object.keys(DEVICE_CONFIGS);\n const try_create = async (config_key) => {\n const pipe = await pipeline("feature-extraction", this.model_key, DEVICE_CONFIGS[config_key]);\n return pipe;\n };\n for (const config of CONFIG_LIST_ORDER) {\n if (this.pipeline) break;\n if (config.includes("gpu") && !this.gpu_enabled) {\n console.warn(`[Transformers v3: ${config}] skipping ${config} as GPU is disabled`);\n continue;\n }\n try {\n console.log(`[Transformers v3] trying to load pipeline on ${config}`);\n this.pipeline = await try_create(config);\n this.active_config_key = config;\n break;\n } catch (err) {\n console.warn(`[Transformers v3: ${config}] failed to load pipeline on ${config}`, err);\n last_error = err;\n }\n }\n if (this.pipeline) {\n console.log(`[Transformers v3: ${this.active_config_key}] pipeline initialized using ${this.active_config_key}`);\n } else {\n throw last_error || new Error("Failed to initialize transformers pipeline");\n }\n this.tokenizer = await AutoTokenizer.from_pretrained(this.model_key);\n }\n /**\n * Count tokens in input text.\n * @param {string} input\n * @returns {Promise<{tokens:number}>}\n */\n async count_tokens(input) {\n if (!this.tokenizer) {\n await this.load();\n }\n const { input_ids } = await this.tokenizer(input);\n return { tokens: input_ids.data.length };\n }\n /**\n * Generate embeddings for multiple inputs.\n * @param {Array} inputs\n * @returns {Promise>}\n */\n async embed_batch(inputs) {\n if (!this.pipeline) {\n await this.load();\n }\n const filtered_inputs = inputs.filter((item) => item.embed_input && item.embed_input.length > 0);\n if (!filtered_inputs.length) return [];\n const results = [];\n for (let i = 0; i < filtered_inputs.length; i += this.batch_size) {\n const batch = filtered_inputs.slice(i, i + this.batch_size);\n const batch_results = await this._process_batch(batch);\n results.push(...batch_results);\n }\n return results;\n }\n /**\n * Process a single batch \u2013 with per-item retry on failure.\n * @private\n * @param {Array} batch_inputs\n * @returns {Promise>}\n */\n async _process_batch(batch_inputs) {\n const prepared = await Promise.all(\n batch_inputs.map((item) => this._prepare_input(item.embed_input))\n );\n const embed_inputs = prepared.map((p) => p.text);\n const tokens = prepared.map((p) => p.tokens);\n try {\n const resp = await this.pipeline(embed_inputs, { pooling: "mean", normalize: true });\n return batch_inputs.map((item, i) => {\n const vec = Array.from(resp[i].data).map((val) => Math.round(val * 1e8) / 1e8);\n item.vec = vec;\n item.tokens = tokens[i];\n return item;\n });\n } catch (err) {\n console.error("[Transformers v3] batch embed failed \\u2013 retrying items individually", err);\n return await this._retry_items_individually(batch_inputs);\n }\n }\n /**\n * Prepare a single input by truncating to max_tokens if necessary.\n * @private\n * @param {string} embed_input\n * @returns {Promise<{text:string,tokens:number}>}\n */\n async _prepare_input(embed_input) {\n let { tokens } = await this.count_tokens(embed_input);\n if (tokens <= this.max_tokens) {\n return { text: embed_input, tokens };\n }\n let truncated = embed_input;\n while (tokens > this.max_tokens && truncated.length > 0) {\n const pct = this.max_tokens / tokens;\n const max_chars = Math.floor(truncated.length * pct * 0.9);\n truncated = truncated.slice(0, max_chars);\n const last_space = truncated.lastIndexOf(" ");\n if (last_space > 0) {\n truncated = truncated.slice(0, last_space);\n }\n tokens = (await this.count_tokens(truncated)).tokens;\n }\n return { text: truncated, tokens };\n }\n /**\n * Retry each item individually after a batch failure.\n * @private\n * @param {Array} batch_inputs\n * @returns {Promise>}\n */\n async _retry_items_individually(batch_inputs) {\n await this._reset_pipeline_after_error();\n const results = [];\n for (const item of batch_inputs) {\n try {\n const prepared = await this._prepare_input(item.embed_input);\n const resp = await this.pipeline(prepared.text, { pooling: "mean", normalize: true });\n const vec = Array.from(resp[0].data).map((val) => Math.round(val * 1e8) / 1e8);\n results.push({\n ...item,\n vec,\n tokens: prepared.tokens\n });\n } catch (single_err) {\n console.error("[Transformers v3] single item embed failed \\u2013 skipping", single_err);\n results.push({\n ...item,\n vec: [],\n tokens: 0,\n error: single_err.message\n });\n }\n }\n return results;\n }\n /**\n * Reset pipeline after a failure \u2013 falling back to WASM if needed.\n * @private\n * @returns {Promise}\n */\n async _reset_pipeline_after_error() {\n try {\n if (this.pipeline) {\n if (typeof this.pipeline.destroy === "function") {\n this.pipeline.destroy();\n } else if (typeof this.pipeline.dispose === "function") {\n this.pipeline.dispose();\n }\n }\n } catch (err) {\n console.warn("[Transformers v3] error while resetting pipeline", err);\n }\n this.pipeline = null;\n await this.load_transformers_with_fallback();\n }\n /**\n * V2 intentionally exposes only model selection in the settings UI.\n * @returns {Object}\n */\n get settings_config() {\n return super.settings_config;\n }\n};\n__publicField(SmartEmbedTransformersAdapter, "defaults", transformers_defaults);\nvar transformers_models = {\n "TaylorAI/bge-micro-v2": {\n "id": "TaylorAI/bge-micro-v2",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "BGE-micro-v2",\n "description": "Local, 512 tokens, 384 dim (recommended)",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-xs": {\n "id": "Snowflake/snowflake-arctic-embed-xs",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed XS",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-s": {\n "id": "Snowflake/snowflake-arctic-embed-s",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed Small",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-m": {\n "id": "Snowflake/snowflake-arctic-embed-m",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed Medium",\n "description": "Local, 512 tokens, 768 dim",\n "adapter": "transformers"\n },\n "TaylorAI/gte-tiny": {\n "id": "TaylorAI/gte-tiny",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "GTE-tiny",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "onnx-community/embeddinggemma-300m-ONNX": {\n "id": "onnx-community/embeddinggemma-300m-ONNX",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "EmbeddingGemma-300M",\n "description": "Local, 2,048 tokens, 768 dim",\n "adapter": "transformers"\n },\n "Mihaiii/Ivysaur": {\n "id": "Mihaiii/Ivysaur",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Ivysaur",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "andersonbcdefg/bge-small-4096": {\n "id": "andersonbcdefg/bge-small-4096",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 4096,\n "name": "BGE-small-4K",\n "description": "Local, 4,096 tokens, 384 dim",\n "adapter": "transformers"\n },\n // Too slow and persistent crashes\n // "jinaai/jina-embeddings-v2-base-de": {\n // "id": "jinaai/jina-embeddings-v2-base-de",\n // "batch_size": 1,\n // "dims": 768,\n // "max_tokens": 4096,\n // "name": "jina-embeddings-v2-base-de",\n // "description": "Local, 4,096 tokens, 768 dim, German",\n // "adapter": "transformers"\n // },\n "Xenova/jina-embeddings-v2-base-zh": {\n "id": "Xenova/jina-embeddings-v2-base-zh",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 8192,\n "name": "Jina-v2-base-zh-8K",\n "description": "Local, 8,192 tokens, 768 dim, Chinese/English bilingual",\n "adapter": "transformers"\n },\n "Xenova/jina-embeddings-v2-small-en": {\n "id": "Xenova/jina-embeddings-v2-small-en",\n "batch_size": 1,\n "dims": 512,\n "max_tokens": 8192,\n "name": "Jina-v2-small-en",\n "description": "Local, 8,192 tokens, 512 dim",\n "adapter": "transformers"\n },\n "nomic-ai/nomic-embed-text-v1.5": {\n "id": "nomic-ai/nomic-embed-text-v1.5",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "Nomic-embed-text-v1.5",\n "description": "Local, 8,192 tokens, 768 dim",\n "adapter": "transformers"\n },\n "Xenova/bge-small-en-v1.5": {\n "id": "Xenova/bge-small-en-v1.5",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "BGE-small",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "nomic-ai/nomic-embed-text-v1": {\n "id": "nomic-ai/nomic-embed-text-v1",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "Nomic-embed-text",\n "description": "Local, 2,048 tokens, 768 dim",\n "adapter": "transformers"\n }\n};\n\n// build/transformers_iframe_script.js\nvar model = null;\nasync function process_message(data) {\n const { method, params, id, iframe_id } = data;\n try {\n let result;\n switch (method) {\n case "init":\n console.log("init");\n break;\n case "load":\n const model_params = { data: params, ...params };\n console.log("load", { model_params });\n model = new SmartEmbedTransformersAdapter(model_params);\n await model.load();\n result = { model_loaded: true, model_config_key: model.active_config_key };\n break;\n case "embed_batch":\n if (!model) throw new Error("Model not loaded");\n result = await model.embed_batch(params.inputs);\n break;\n case "count_tokens":\n if (!model) throw new Error("Model not loaded");\n result = await model.count_tokens(params);\n break;\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n return { id, result, iframe_id };\n } catch (error) {\n console.error("Error processing message:", error);\n return { id, error: error.message, iframe_id };\n }\n}\nprocess_message({ method: "init" });\n'; + +// node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/transformers.js +var transformers_defaults = { + adapter: "transformers", + description: "Transformers (Local, built-in)", + default_model: "TaylorAI/bge-micro-v2", + models: transformers_models +}; +var transformers_models = { + "TaylorAI/bge-micro-v2": { + "id": "TaylorAI/bge-micro-v2", + "batch_size": 1, + "dims": 384, + "max_tokens": 512, + "name": "BGE-micro-v2", + "description": "Local, 512 tokens, 384 dim (recommended)", + "adapter": "transformers" + }, + "Snowflake/snowflake-arctic-embed-xs": { + "id": "Snowflake/snowflake-arctic-embed-xs", + "batch_size": 1, + "dims": 384, + "max_tokens": 512, + "name": "Snowflake Arctic Embed XS", + "description": "Local, 512 tokens, 384 dim", + "adapter": "transformers" + }, + "Snowflake/snowflake-arctic-embed-s": { + "id": "Snowflake/snowflake-arctic-embed-s", + "batch_size": 1, + "dims": 384, + "max_tokens": 512, + "name": "Snowflake Arctic Embed Small", + "description": "Local, 512 tokens, 384 dim", + "adapter": "transformers" + }, + "Snowflake/snowflake-arctic-embed-m": { + "id": "Snowflake/snowflake-arctic-embed-m", + "batch_size": 1, + "dims": 768, + "max_tokens": 512, + "name": "Snowflake Arctic Embed Medium", + "description": "Local, 512 tokens, 768 dim", + "adapter": "transformers" + }, + "TaylorAI/gte-tiny": { + "id": "TaylorAI/gte-tiny", + "batch_size": 1, + "dims": 384, + "max_tokens": 512, + "name": "GTE-tiny", + "description": "Local, 512 tokens, 384 dim", + "adapter": "transformers" + }, + "onnx-community/embeddinggemma-300m-ONNX": { + "id": "onnx-community/embeddinggemma-300m-ONNX", + "batch_size": 1, + "dims": 768, + "max_tokens": 2048, + "name": "EmbeddingGemma-300M", + "description": "Local, 2,048 tokens, 768 dim", + "adapter": "transformers" + }, + "Mihaiii/Ivysaur": { + "id": "Mihaiii/Ivysaur", + "batch_size": 1, + "dims": 384, + "max_tokens": 512, + "name": "Ivysaur", + "description": "Local, 512 tokens, 384 dim", + "adapter": "transformers" + }, + "andersonbcdefg/bge-small-4096": { + "id": "andersonbcdefg/bge-small-4096", + "batch_size": 1, + "dims": 384, + "max_tokens": 4096, + "name": "BGE-small-4K", + "description": "Local, 4,096 tokens, 384 dim", + "adapter": "transformers" + }, + // Too slow and persistent crashes + // "jinaai/jina-embeddings-v2-base-de": { + // "id": "jinaai/jina-embeddings-v2-base-de", + // "batch_size": 1, + // "dims": 768, + // "max_tokens": 4096, + // "name": "jina-embeddings-v2-base-de", + // "description": "Local, 4,096 tokens, 768 dim, German", + // "adapter": "transformers" + // }, + "Xenova/jina-embeddings-v2-base-zh": { + "id": "Xenova/jina-embeddings-v2-base-zh", + "batch_size": 1, + "dims": 768, + "max_tokens": 8192, + "name": "Jina-v2-base-zh-8K", + "description": "Local, 8,192 tokens, 768 dim, Chinese/English bilingual", + "adapter": "transformers" + }, + "Xenova/jina-embeddings-v2-small-en": { + "id": "Xenova/jina-embeddings-v2-small-en", + "batch_size": 1, + "dims": 512, + "max_tokens": 8192, + "name": "Jina-v2-small-en", + "description": "Local, 8,192 tokens, 512 dim", + "adapter": "transformers" + }, + "nomic-ai/nomic-embed-text-v1.5": { + "id": "nomic-ai/nomic-embed-text-v1.5", + "batch_size": 1, + "dims": 768, + "max_tokens": 2048, + "name": "Nomic-embed-text-v1.5", + "description": "Local, 8,192 tokens, 768 dim", + "adapter": "transformers" + }, + "Xenova/bge-small-en-v1.5": { + "id": "Xenova/bge-small-en-v1.5", + "batch_size": 1, + "dims": 384, + "max_tokens": 512, + "name": "BGE-small", + "description": "Local, 512 tokens, 384 dim", + "adapter": "transformers" + }, + "nomic-ai/nomic-embed-text-v1": { + "id": "nomic-ai/nomic-embed-text-v1", + "batch_size": 1, + "dims": 768, + "max_tokens": 2048, + "name": "Nomic-embed-text", + "description": "Local, 2,048 tokens, 768 dim", + "adapter": "transformers" + } +}; +var transformers_settings_config = { + // "[ADAPTER].legacy_transformers": { + // name: 'Legacy transformers (no GPU)', + // type: "toggle", + // description: "Use legacy transformers (v2) instead of v3. This may resolve issues if the local embedding isn't working.", + // callback: 'embed_model_changed', + // default: true, + // }, +}; +var settings_config3 = { + // "legacy_transformers": { + // name: 'Legacy transformers (no GPU)', + // type: "toggle", + // description: "Use legacy transformers (v2) instead of v3. This may resolve issues if the local embedding isn't working.", + // // callback: 'embed_model_changed', + // // default: false, + // }, +}; + +// node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/transformers_iframe.js +var SmartEmbedTransformersIframeAdapter = class extends SmartEmbedIframeAdapter { + static defaults = transformers_defaults; + /** + * Create transformers iframe adapter instance + */ + constructor(model) { + super(model); + this.connector = transformers_connector.replace("@huggingface/transformers", "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.0"); + console.log("transformers iframe connector", this.model); + } + /** @returns {Object} Settings configuration for transformers adapter */ + get settings_config() { + return { + ...super.settings_config, + ...transformers_settings_config + }; + } + /** + * Get available models (hardcoded list) + * @returns {Promise} Map of model objects + */ + get_models() { + return Promise.resolve(this.models); + } + get models() { + return transformers_models; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_line_range.js +function get_line_range2(content, start_line, end_line) { + const lines = content.split("\n"); + return lines.slice(start_line - 1, end_line).join("\n"); +} + +// node_modules/obsidian-smart-env/node_modules/smart-blocks/content_parsers/parse_blocks.js +function parse_blocks(source, content) { + let { blocks: blocks_obj, task_lines, tasks, codeblock_ranges } = parse_markdown_blocks(content); + const last_read_at = source.data.last_read?.at || Date.now(); + for (const [sub_key, line_range] of Object.entries(blocks_obj)) { + const block_key = source.key + sub_key; + const existing_block = source.block_collection.get(block_key); + const block_content = get_line_range2(content, line_range[0], line_range[1]); + const next_hash = murmur_hash_32_alphanumeric(block_content); + if (existing_block && existing_block.lines?.[0] === line_range[0] && existing_block.lines?.[1] === line_range[1] && existing_block.size === block_content.length && existing_block.data?.last_read?.hash === next_hash) { + continue; + } + const block_outlinks = get_markdown_links(block_content); + const bases_links = get_bases_cache_links({ + source, + links: block_outlinks + }); + const block_data = { + key: block_key, + lines: line_range, + size: block_content.length, + outlinks: [ + ...block_outlinks, + ...bases_links + ], + last_read: { + at: last_read_at, + hash: next_hash + } + }; + const block_changed = has_block_data_changes(existing_block, block_data); + if (!block_changed && existing_block?.vec) { + continue; + } + if (!existing_block || existing_block?.data.last_read?.hash !== block_data.last_read.hash) { + const new_item = new source.block_collection.item_type(source.env, block_data); + if (block_changed) new_item.queue_save(); + source.block_collection.set(new_item); + } else if (block_changed) { + existing_block.data = { + ...existing_block.data, + ...block_data + // overwrites lines, last_read + }; + existing_block.queue_save(); + } + } + clean_and_update_source_blocks(source, blocks_obj, task_lines, tasks, codeblock_ranges); + for (const block of source.blocks) { + if (!block.vec || block.embed_hash !== block.read_hash) { + block.queue_embed(); + } + } +} +function has_block_data_changes(existing_block, block_data) { + if (!existing_block) return true; + const existing_lines = existing_block.lines || []; + if (existing_lines[0] !== block_data.lines[0] || existing_lines[1] !== block_data.lines[1]) { + return true; + } + if (existing_block.size !== block_data.size) { + return true; + } + if (existing_block?.data?.last_read?.hash !== block_data.last_read.hash) { + return true; + } + const existing_outlinks = existing_block?.data?.outlinks || []; + if (JSON.stringify(existing_outlinks) !== JSON.stringify(block_data.outlinks)) { + return true; + } + return false; +} +function clean_and_update_source_blocks(source, blocks_obj, task_lines = [], tasks = {}, codeblock_ranges = {}) { + const current_block_keys = new Set(Object.keys(blocks_obj).map((sk) => source.key + sk)); + const blocks = source.blocks; + for (let i = 0; i < blocks.length; i++) { + if (!current_block_keys.has(blocks[i].key)) { + blocks[i].deleted = true; + blocks[i].queue_save(); + } + } + source.data.blocks = blocks_obj; + source.data.task_lines = task_lines; + source.data.tasks = tasks; + source.data.codeblock_ranges = codeblock_ranges; + source.queue_save(); +} + +// node_modules/obsidian-smart-env/node_modules/smart-collections/utils/ajson_merge.js +function ajson_merge(existing, new_obj) { + if (new_obj === null) return null; + if (new_obj === void 0) return existing; + if (typeof new_obj !== "object") return new_obj; + if (typeof existing !== "object" || existing === null) existing = {}; + const keys = Object.keys(new_obj); + const length = keys.length; + for (let i = 0; i < length; i++) { + const key = keys[i]; + const new_val = new_obj[key]; + const existing_val = existing[key]; + if (Array.isArray(new_val)) { + existing[key] = new_val.slice(); + } else if (is_object(new_val)) { + existing[key] = ajson_merge(is_object(existing_val) ? existing_val : {}, new_val); + } else if (new_val !== void 0) { + existing[key] = new_val; + } + } + return existing; +} +function is_object(obj) { + return obj !== null && typeof obj === "object" && !Array.isArray(obj); +} + +// node_modules/obsidian-smart-env/node_modules/smart-collections/adapters/ajson_single_file.js +var class_to_collection_key2 = { + "SmartSource": "smart_sources", + "SmartNote": "smart_sources", + // DEPRECATED + "SmartBlock": "smart_blocks", + "SmartDirectory": "smart_directories" +}; +function _parse_ajson_key(ajson_key) { + let changed = false; + let [collection_key, ...item_key] = ajson_key.split(":"); + if (class_to_collection_key2[collection_key]) { + collection_key = class_to_collection_key2[collection_key]; + changed = true; + } + return { + collection_key, + item_key: item_key.join(":"), + changed + }; +} +var AjsonSingleFileCollectionDataAdapter = class extends AjsonMultiFileCollectionDataAdapter { + /** + * Returns the single shared `.ajson` file path for this collection. + * @param {string} [key] - (unused) Item key, ignored in single-file mode. + * @returns {string} The single .ajson file path for the entire collection. + */ + get_item_data_path(key) { + const file_name = (this.collection?.collection_key || "collection") + ".ajson"; + const sep = this.fs?.sep || "/"; + const dir = this.collection.data_dir || "data"; + return [dir, file_name].join(sep); + } + /** + * Override process_load_queue to parse the entire single-file .ajson once, + * distributing final states to items. + * + * @async + * @returns {Promise} + */ + async process_load_queue() { + this.collection.emit_event("collection:load_started"); + if (!await this.fs.exists(this.collection.data_dir)) { + await this.fs.mkdir(this.collection.data_dir); + } + const path = this.get_item_data_path(); + if (!await this.fs.exists(path)) { + for (const item of Object.values(this.collection.items)) { + if (item._queue_load) { + item.queue_import?.(); + } + } + this.collection.emit_event("collection:load_halted"); + return; + } + const raw_data = await this.fs.read(path, "utf-8", { no_cache: true }); + if (!raw_data) { + for (const item of Object.values(this.collection.items)) { + if (item._queue_load) { + item.queue_import?.(); + } + } + this.collection.emit_event("collection:load_halted"); + return; + } + const { rewrite, file_data } = this.parse_single_file_ajson(raw_data); + if (rewrite) { + if (file_data.length) { + await this.fs.write(path, file_data); + } else { + await this.fs.remove(path); + } + } + for (const item of Object.values(this.collection.items)) { + item._queue_load = false; + item.loaded_at = Date.now(); + } + this.collection.emit_event("collection:load_completed"); + } + /** + * Helper to parse single-file .ajson content, distributing states to items. + * + * @param {string} raw + * @returns {{ rewrite: boolean, file_data: string }} + */ + parse_single_file_ajson(raw) { + let rewrite = false; + const lines = raw.trim().split("\n").filter(Boolean); + let data_map = {}; + let line_count = 0; + for (let i = 0; i < lines.length; i++) { + const line = lines[i].trim(); + if (!line.endsWith(",")) { + rewrite = true; + } + const trimmed = line.replace(/,$/, ""); + const combined = "{" + trimmed + "}"; + try { + const obj = JSON.parse(combined); + const [fullKey, value] = Object.entries(obj)[0]; + let { collection_key, item_key, changed } = _parse_ajson_key(fullKey); + const newKey = `${collection_key}:${item_key}`; + if (!value) { + delete data_map[newKey]; + if (changed || newKey !== fullKey) { + delete data_map[fullKey]; + } + rewrite = true; + } else { + data_map[newKey] = value; + if (changed || newKey !== fullKey) { + delete data_map[fullKey]; + rewrite = true; + } + } + } catch (err) { + console.warn("parse error for line: ", line, err); + rewrite = true; + } + line_count++; + } + for (const [ajson_key, val] of Object.entries(data_map)) { + const [collection_key, ...rest] = ajson_key.split(":"); + const item_key = rest.join(":"); + const collection = this.collection.env[collection_key]; + if (!collection) continue; + let item = collection.get(item_key); + if (!item) { + const ItemClass = collection.item_type; + item = new ItemClass(this.env, val); + collection.set(item); + } else { + item.data = ajson_merge(item.data, val); + } + item.loaded_at = Date.now(); + item._queue_load = false; + if (!val.key) val.key = item_key; + } + if (line_count > Object.keys(data_map).length) { + rewrite = true; + } + let minimal_lines = []; + for (const [ajson_key, val] of Object.entries(data_map)) { + minimal_lines.push(`${JSON.stringify(ajson_key)}: ${JSON.stringify(val)},`); + } + return { + rewrite, + file_data: minimal_lines.join("\n") + }; + } + /** + * Override process_save_queue for single-file approach. + * We'll simply call save_item for each queued item, which appends a line to the same `.ajson`. + * + * @async + * @returns {Promise} + */ + async process_save_queue() { + this.collection.emit_event("collection:save_started"); + const save_queue = Object.values(this.collection.items).filter((item) => item._queue_save); + const time_start = Date.now(); + const batch_size = 50; + for (let i = 0; i < save_queue.length; i += batch_size) { + const batch = save_queue.slice(i, i + batch_size); + await Promise.all(batch.map((item) => { + const adapter = this.create_item_adapter(item); + return adapter.save().catch((err) => { + console.warn(`Error saving item ${item.key}`, err); + item.queue_save(); + }); + })); + } + const deleted_items = Object.values(this.collection.items).filter((item) => item.deleted); + if (deleted_items.length) { + deleted_items.forEach((item) => { + delete this.collection.items[item.key]; + }); + } + console.log(`Saved (single-file) ${this.collection.collection_key} in ${Date.now() - time_start}ms`); + this.collection.emit_event("collection:save_completed"); + } +}; +var AjsonSingleFileItemDataAdapter = class extends AjsonMultiFileItemDataAdapter { + /** + * Overridden to always return the single file path from the parent collection adapter. + * @returns {string} + */ + get data_path() { + return this.collection_adapter.get_item_data_path(this.item.key); + } + /** + * Load logic: + * In single-file mode, we typically rely on the collection's `process_load_queue()` + * to parse the entire file. This direct `load()` will do a naive re-parse as well + * if used individually. + */ + async load() { + const path = this.data_path; + if (!await this.fs.exists(path)) { + this.item.queue_import?.(); + return; + } + try { + const raw_data = await this.fs.read(path, "utf-8", { no_cache: true }); + if (!raw_data) { + this.item.queue_import?.(); + return; + } + const { rewrite } = this.collection_adapter.parse_single_file_ajson(raw_data); + } catch (err) { + console.warn(`Error loading single-file item ${this.item.key}`, err); + this.item.queue_import?.(); + } + } +}; +var ajson_single_file_default = { + collection: AjsonSingleFileCollectionDataAdapter, + item: AjsonSingleFileItemDataAdapter +}; + +// node_modules/obsidian-smart-env/node_modules/smart-components/smart_component.js +var SmartComponent = class extends CollectionItem { + static key = "smart_component"; + static collection_key = "smart_components"; + collection_key = "smart_components"; + get_key() { + if (this.data?.key) return this.data.key; + const scope_key = this.scope_key; + const component_key = this.component_key; + const version = Number.isFinite(this.data?.version) ? this.data.version : 0; + const hash = this.data?.hash || "nohash"; + const key_pcs = []; + if (!component_key.includes(scope_key) && scope_key !== "global") key_pcs.push(scope_key); + key_pcs.push(component_key); + return `${key_pcs.join("_").replace(/\./g, "_")}#${[version, hash].join("#")}`; + } + get scope_key() { + return this.data?.scope_key; + } + get component_key() { + return this.data?.component_key; + } + get component_adapter() { + return this._component_adapter; + } + /** + * Delegates render logic to the adapter. + * @param {object} component_scope + * @param {object} [opts={}] + * @returns {Promise<*>} + */ + async render(component_scope, opts = {}) { + if (!this.component_adapter) { + throw new Error(`SmartComponent: adapter missing for ${this.component_key}`); + } + return await this.component_adapter.render(component_scope, opts); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-components/adapters/_adapter.js +function parse_component_properties(component_properties = []) { + const parts = component_properties.filter(Boolean).map((part) => part.toString()); + const component_key = parts.pop(); + const scope_key = parts.length ? parts.join(".") : "global"; + return { scope_key, component_key }; +} +async function build_component_data(component_properties, component_module) { + const { scope_key, component_key } = parse_component_properties(component_properties); + if (!component_key) return null; + const render_fn = typeof component_module === "function" ? component_module : component_module?.render; + const version = typeof render_fn?.version === "number" ? render_fn.version : 0; + const hash = await murmur_hash_32_alphanumeric(render_fn.toString()); + return { scope_key, component_key, version, hash }; +} +var SmartComponentAdapter = class { + constructor(item, component_module) { + this.item = item; + this.module = component_module; + this.item.env.create_env_getter(this); + } + static should_use_adapter(component_module) { + return true; + } + static async register_component(collection, component_properties, component_module) { + if (!this.should_use_adapter(component_module)) return null; + const data = await build_component_data(component_properties, component_module); + if (!data) return null; + const item = await collection.create_or_update({ ...data }); + if (!item) return null; + item._component_module = component_module; + item._component_adapter = new this(item, component_module); + return item; + } + /** + * Render the component for the provided scope. + * @abstract + * @param {Object} scope - Render scope from the hosting environment. + * @param {Object} [opts] - Optional render options. + * @returns {Promise<*>} Rendered output for the component. + */ + async render(scope, opts) { + throw new Error("render() not implemented"); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-components/adapters/smart_view_component_adapter.js +var SmartViewComponentAdapter = class extends SmartComponentAdapter { + static should_use_adapter(component_module) { + return typeof component_module === "function" || typeof component_module?.render === "function"; + } + get smart_view() { + if (!this._smart_view) { + this._smart_view = this.env.init_module("smart_view"); + } + return this._smart_view; + } + async render(scope, opts = {}) { + const render_fn = typeof this.module === "function" ? this.module : this.module?.render; + if (typeof render_fn !== "function") { + throw new Error("SmartViewComponentAdapter: render() missing on module"); + } + return await render_fn.call(this.smart_view, scope, opts); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-components/smart_components.js +function flatten_components_config(config, path = [], acc = []) { + if (!config || typeof config !== "object") return acc; + Object.entries(config).forEach(([key, value]) => { + const next_path = [...path, key]; + if (!value) return; + if (typeof value === "function" || typeof value?.render === "function") { + acc.push({ properties: next_path, module: value }); + return; + } + if (typeof value === "object") { + flatten_components_config(value, next_path, acc); + } + }); + return acc; +} +var SmartComponents = class extends Collection { + static key = "smart_components"; + static collection_key = "smart_components"; + collection_key = "smart_components"; + async init() { + await this.load_components_from_config(); + } + get component_adapters() { + if (Array.isArray(this.opts?.component_adapters)) { + return this.opts.component_adapters; + } + if (this.opts?.component_adapters && typeof this.opts.component_adapters === "object") { + return Object.values(this.opts.component_adapters); + } + return this.constructor.default_component_adapters || []; + } + /** + * @private + * @returns {Promise} + */ + async load_components_from_config() { + const records = flatten_components_config(this.env.config?.components || {}); + for (const record of records) { + await this.register_component(record.properties, record.module); + } + } + /** + * @private + * @param {string[]} component_properties + * @param {Object|Function} component_module + * @returns {Promise} + */ + async register_component(component_properties, component_module) { + for (const AdapterClass of this.component_adapters) { + const item = await AdapterClass.register_component(this, component_properties, component_module); + if (item) return item; + } + return null; + } + async render_component(component_key, scope, opts = {}) { + const components = this.filter((item) => { + if (item.key.startsWith(component_key + "#")) return true; + return item.component_key === component_key; + }).sort((a, b) => { + const a_scope_match = a.scope_key === scope.key ? 1 : 0; + const b_scope_match = b.scope_key === scope.key ? 1 : 0; + return b_scope_match - a_scope_match; + }); + if (components.length === 0) { + throw new Error(`SmartComponents: no component found for key ${component_key}`); + } + const selected_component = components[0]; + return await selected_component.render(scope, opts); + } +}; +var smart_components_default = { + class: SmartComponents, + item_type: SmartComponent, + data_adapter: ajson_single_file_default, + component_adapters: { + SmartViewComponentAdapter + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-components/index.js +var smart_components_default2 = smart_components_default; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/context_item.js +var ContextItem = class extends CollectionItem { + static version = "1.1.0"; + // special handling because current name_to_collection_key removes "Items" suffix + /** + * @returns {string} + */ + get collection_key() { + return "context_items"; + } + /** + * @this {ContextItemThis} + * @returns {*} + */ + get context_type_adapter() { + if (!this._context_type_adapter) { + const Class = this.collection.context_item_adapters.find((adapter_class) => adapter_class.detect(this.key, this.data)); + if (!Class) throw new Error(`No context item adapter found for key: ${this.key}`); + this._context_type_adapter = new Class(this); + } + return this._context_type_adapter; + } + /** + * @this {ContextItemThis} + * @returns {boolean} + */ + get exists() { + return this.context_type_adapter.exists; + } + /** + * @this {ContextItemThis} + * @returns {string|null} + */ + get icon_type() { + return this.context_type_adapter.icon_type || null; + } + // v3 + /** + * @this {ContextItemThis} + * @returns {Promise} + */ + async get_text() { + const item_text = await this.context_type_adapter.get_text(); + if (typeof item_text !== "string") return item_text; + if (typeof this.actions.context_item_merge_template === "function") { + return await this.actions.context_item_merge_template(item_text); + } + return item_text; + } + /** + * @this {ContextItemThis} + * @returns {Promise} + */ + async get_base64() { + if (this.is_media) { + return await this.context_type_adapter.get_base64(); + } + return { error: `Context item is not media type: ${this.key}` }; + } + /** + * @this {ContextItemThis} + * @param {*} [event=null] + * @returns {Promise<*>} + */ + async open(event = null) { + return await this.context_type_adapter.open(event); + } + /** + * @this {ContextItemThis} + * @returns {boolean} + */ + get is_media() { + return this.context_type_adapter.is_media || false; + } + /** + * @this {ContextItemThis} + * @returns {CollectionItemRef|null} + */ + get item_ref() { + return this.context_type_adapter.ref || null; + } + /** + * @this {ContextItemThis} + * @returns {number} + */ + get size() { + return this.data.size || this.context_type_adapter.size || 0; + } + /** + * @this {ContextItemThis} + * @returns {number|null} + */ + get mtime() { + return this.data.mtime || this.context_type_adapter.mtime || null; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/_adapter.js +var ContextItemAdapter = class { + /** + * @param {*} item + */ + constructor(item) { + this.item = item; + } + /** + * @param {string} key + * @param {ContextItemData} [data={}] + * @returns {boolean|string} + */ + static detect(key, data = {}) { + return false; + } + /** + * @this {ContextItemAdapterThis} + * @returns {*} + */ + get env() { + return this.item.env; + } + /** + * @returns {boolean} + */ + get exists() { + return true; + } + /** + * @returns {string|null} + */ + get icon_type() { + return null; + } + // v3 API + /** + * for calculating context size + * @returns {number} + */ + get size() { + return 0; + } + /** + * @returns {Promise} + */ + async get_text() { + } + /** + * @returns {Promise<*>} + */ + async open() { + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/block.js +var BlockContextItemAdapter = class extends ContextItemAdapter { + static order = 6; + /** + * @param {string} key + * @returns {boolean} + */ + static detect(key) { + return key.includes("#"); + } + /** + * @this {BlockContextItemAdapterThis} + * @returns {*} + */ + get ref() { + return this.env.smart_blocks.get(this.item.key); + } + /** + * @this {BlockContextItemAdapterThis} + * @returns {Array<*>} + */ + get inlinks() { + return this.ref.inlinks || []; + } + /** + * @this {BlockContextItemAdapterThis} + * @returns {Array<*>} + */ + get outlinks() { + return this.ref.outlinks || []; + } + /** + * @this {BlockContextItemAdapterThis} + * @returns {boolean} + */ + get exists() { + return !!(this.ref && !this.ref.is_gone); + } + /** + * @this {BlockContextItemAdapterThis} + * @returns {number|null} + */ + get mtime() { + return this.ref?.mtime || null; + } + /** + * @this {BlockContextItemAdapterThis} + * @returns {number} + */ + get size() { + return this.ref?.size || 0; + } + /** + * @this {BlockContextItemAdapterThis} + * @returns {Promise>} + */ + async get_text() { + const block = this.ref; + if (!block) return { error: "Block not found" }; + return await block.read(); + } + /** + * @this {BlockContextItemAdapterThis} + * @param {*} [event=null] + * @returns {Promise} + */ + async open(event = null) { + this.ref.actions.source_open(event); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/source.js +var SourceContextItemAdapter = class extends ContextItemAdapter { + static order = 7; + // default lowest priority + /** + * @returns {boolean} + */ + static detect(key) { + return true; + } + /** + * @this {SourceContextItemAdapterThis} + * @returns {*} + */ + get ref() { + return this.env.smart_sources.get(this.item.key); + } + /** + * @this {SourceContextItemAdapterThis} + * @returns {Array<*>} + */ + get inlinks() { + return this.ref.inlinks || []; + } + /** + * @this {SourceContextItemAdapterThis} + * @returns {Array<*>} + */ + get outlinks() { + return this.ref.outlinks || []; + } + /** + * @this {SourceContextItemAdapterThis} + * @returns {boolean} + */ + get exists() { + return !!(this.ref && !this.ref.is_gone); + } + /** + * @this {SourceContextItemAdapterThis} + * @returns {number} + */ + get size() { + return this.ref?.size || 0; + } + /** + * @this {SourceContextItemAdapterThis} + * @returns {number|null} + */ + get mtime() { + return this.ref?.mtime || null; + } + /** + * @this {SourceContextItemAdapterThis} + * @returns {Promise} + */ + async get_text() { + return await this.ref?.read() || "MISSING SOURCE"; + } + /** + * @this {SourceContextItemAdapterThis} + * @param {*} [event=null] + * @returns {Promise} + */ + async open(event = null) { + this.ref.actions.source_open(event); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/utils/image_extension_regex.js +var image_extension_regex = /\.(png|jpe?g|gif|bmp|webp|ico|mp4)$/i; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/image.js +var ImageContextItemAdapter = class extends ContextItemAdapter { + /** + * @param {string} key + * @returns {boolean|string} + */ + static detect(key) { + if (image_extension_regex.test(key)) return "image"; + return false; + } + /** + * @this {ImageContextItemAdapterThis} + * @returns {boolean} + */ + get exists() { + return this.item.env.smart_sources.fs.exists_sync(this.item.key); + } + /** + * @returns {string} + */ + get icon_type() { + return "image-file"; + } + /** + * @returns {boolean} + */ + get is_media() { + return true; + } + /** + * @this {ImageContextItemAdapterThis} + * @returns {Promise} + */ + async get_base64() { + const ext = this.item.key.split(".").pop().toLowerCase(); + try { + const base64_data = await this.item.env.fs.read(this.item.key, "base64"); + const base64_url = `data:image/${ext};base64,${base64_data}`; + return { + type: "image_url", + key: this.item.key, + name: this.item.key.split(/[\\/]/).pop(), + url: base64_url + }; + } catch (err) { + console.warn(`Failed to convert image ${this.item.key} to base64`, err); + return { error: `Failed to convert image to base64: ${err.message}` }; + } + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/pdf.js +var PdfContextItemAdapter = class extends ContextItemAdapter { + /** + * @param {*} key + * @returns {boolean|string} + */ + static detect(key) { + if (String(key || "").toLowerCase().endsWith(".pdf")) return "pdf"; + return false; + } + /** + * @param {ContextItemAdapterSnapshot} snapshot + * @returns {Promise} + */ + async add_to_snapshot(snapshot) { + if (!snapshot.pdfs) snapshot.pdfs = []; + snapshot.pdfs.push(this.item.key); + } + /** + * @returns {string} + */ + get icon_type() { + return "file-text"; + } + /** + * @returns {boolean} + */ + get is_media() { + return true; + } + /** + * @this {PdfContextItemAdapterThis} + * @returns {Promise} + */ + async get_base64() { + try { + const base64_data = await this.item.env.fs.read(this.item.key, "base64"); + const base64_url = `data:application/pdf;base64,${base64_data}`; + return { + type: "pdf_url", + key: this.item.key, + name: this.item.key.split(/[\\/]/).pop(), + url: base64_url + }; + } catch (err) { + console.warn(`Failed to convert PDF ${this.item.key} to base64`, err); + return { error: `Failed to convert PDF to base64: ${err.message}` }; + } + } + /** + * @this {PdfContextItemAdapterThis} + * @returns {boolean} + */ + get exists() { + return this.item.env.smart_sources.fs.exists_sync(this.item.key); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/context_items.js +var ContextItems = class extends Collection { + /** + * @this {*} + * @param {*|SmartContext} smart_context + * @param {Object.} [opts={}] + */ + constructor(smart_context, opts = {}) { + super(smart_context.env || smart_context, opts); + this.smart_context = smart_context; + } + /** + * @returns {Promise} + */ + async load() { + console.log("ContextItems: load called"); + } + static version = "1.1.0"; + /** + * @this {ContextItemsThis} + * @returns {ContextItemAdapterConstructor[]} + */ + get context_item_adapters() { + if (!this._context_item_adapters) { + this._context_item_adapters = Object.values(this.opts.context_item_adapters).sort((a, b) => { + const order_a = a.order || 0; + const order_b = b.order || 0; + return order_a - order_b; + }); + } + return this._context_item_adapters; + } + /** + * @this {ContextItemsThis} + * @param {Partial & Object.} data + * @returns {ContextItemInstance} + */ + new_item(data) { + const item = new this.item_type(this.env, data); + this.set(item); + return item; + } + /** + * @returns {*} + */ + process_load_queue() { + } + /** + * @this {ContextItemsThis} + * @returns {SettingsConfig} + */ + get settings_config() { + return { + ...this.env.config.actions.context_item_merge_template?.settings_config || {} + }; + } + /** + * @returns {Object.} + */ + static get default_settings() { + return { + template_preset: "xml_structured", + template_before: '', + template_after: "" + }; + } + /** + * @this {ContextItemsThis} + * @param {ContextItemsData} context_items_data - data.context_items{} + * @param {ContextItemsLoadParams} params + * @returns {ContextItemInstance[]} + */ + load_from_data(context_items_data, params = {}) { + const loaded_items = []; + if (!this.items) this.items = {}; + const named_context_stack = Array.isArray(params.named_context_stack) ? params.named_context_stack : [this.smart_context?.data?.name].filter(Boolean); + const load_params = { + ...params, + named_context_stack + }; + const entries = Object.entries(context_items_data || {}); + for (let i = 0; i < entries.length; i++) { + const [key, item_data] = entries[i]; + const loaded = this.load_item_from_data(key, item_data, load_params); + if (loaded) { + if (Array.isArray(loaded)) { + if (!loaded.length) continue; + const total_size = loaded.reduce((sum, item) => sum + (item.size || 0), 0); + const latest_mtime = Math.max(...loaded.map((item) => item.mtime)); + item_data.size = total_size; + item_data.mtime = latest_mtime; + item_data.group_items_ct = loaded.length; + loaded_items.push(...loaded); + } else { + if (!loaded.exists) { + this.smart_context.emit_missing_context_item_event(key, "Context item does not exist"); + } + item_data.size = loaded.size; + item_data.mtime = loaded.mtime; + loaded_items.push(loaded); + } + } + } + return loaded_items; + } + /** + * @this {ContextItemsThis} + * @param {string} key + * @param {ContextItemData} item_data + * @param {ContextItemsLoadParams} params + * @return {ContextItemInstance|ContextItemInstance[]|null} + */ + load_item_from_data(key, item_data, params = {}) { + if (item_data.named_context) { + return this.load_named_context_items(key, item_data, params); + } else { + return this.new_item({ + key, + ...item_data + }); + } + } + /** + * @this {ContextItemsThis} + * @param {string} key + * @param {ContextItemData} item_data + * @param {ContextItemsLoadParams} params + * @returns {ContextItemInstance[]|null} + */ + load_named_context_items(key, item_data, params = {}) { + let resp = null; + const named_context_name = item_data?.key || key; + const named_context_stack = Array.isArray(params.named_context_stack) ? params.named_context_stack : []; + const named_context = this.env.smart_contexts.filter((ctx) => ctx.data.name === named_context_name)[0]; + if (named_context) { + if (named_context === this.smart_context || named_context_stack.includes(named_context_name)) { + return null; + } + const loaded_items = this.load_from_data(named_context.data.context_items || {}, { + ...params, + named_context_stack: [...named_context_stack, named_context_name] + }); + if (!loaded_items.length) return null; + loaded_items.forEach((item) => { + if (!item?.data || typeof item.data !== "object") return; + item.data.from_named_context = named_context_name; + }); + if (typeof params.codeblock_source_key !== "undefined") { + if (!named_context.data.codeblock_inclusions) named_context.data.codeblock_inclusions = {}; + named_context.data.codeblock_inclusions[params.codeblock_source_key] = Date.now(); + named_context.queue_save(); + } + resp = loaded_items; + } else { + const message = `Named context not found: "${named_context_name}"`; + console.warn(`ContextItems.load_from_data: ${message}`); + this.smart_context.emit_missing_context_item_event(key, message, { + message, + btn_text: "Remove missing named context" + }); + resp = null; + } + return resp; + } +}; +var context_items_default = { + version: ContextItems.version, + class: ContextItems, + collection_key: "context_items", + item_type: ContextItem, + context_item_adapters: { + BlockContextItemAdapter, + SourceContextItemAdapter, + ImageContextItemAdapter, + PdfContextItemAdapter + } +}; + +// node_modules/obsidian-smart-env/src/collections/event_logs.js +var import_obsidian3 = require("obsidian"); + +// node_modules/obsidian-smart-env/node_modules/smart-events/event_log.js +function next_log_stats(prev = {}, at_ms) { + const ct = (prev.ct || 0) + 1; + const first_at = prev.first_at ?? at_ms; + const last_at = at_ms; + return { ct, first_at, last_at }; +} +var EventLog = class extends CollectionItem { + static version = 2e-3; + /** @returns {{data: EventLogData}} */ + static get defaults() { + return { + data: { + key: null, + ct: 0, + first_at: null, + last_at: null + } + }; + } + /** + * Counters are updated via EventLogs listener. + * @param {Partial} [_input_data] + */ + init(_input_data) { + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-events/event_level_utils.js +var notification_levels = Object.freeze([ + "milestone", + "attention", + "error", + "warning", + "info" +]); +var notification_level_set = new Set(notification_levels); +var severity_order = { + attention: 1, + warning: 2, + error: 3 +}; +function normalize_event_level(level) { + if (typeof level !== "string") return null; + const normalized_level = level.trim().toLowerCase(); + if (!notification_level_set.has(normalized_level)) return null; + return normalized_level; +} +function get_legacy_notification_level(event_key = "") { + if (typeof event_key !== "string") return null; + const trimmed_event_key = event_key.trim().toLowerCase(); + if (!trimmed_event_key.startsWith("notification:")) return null; + const [, legacy_level = ""] = trimmed_event_key.split(":"); + return normalize_event_level(legacy_level); +} +function get_display_fallback_level(event_key = "") { + if (typeof event_key !== "string") return null; + const trimmed_event_key = event_key.trim().toLowerCase(); + const event_key_parts = trimmed_event_key.split(":").filter(Boolean); + const last_part = event_key_parts[event_key_parts.length - 1]; + if (last_part === "error") return "error"; + return null; +} +function get_event_level(event_key = "", event = {}, params = {}) { + const { allow_display_fallback = false } = params; + const payload_level = normalize_event_level(event?.level); + if (payload_level) return payload_level; + const legacy_level = get_legacy_notification_level(event_key); + if (legacy_level) return legacy_level; + if (allow_display_fallback) { + return get_display_fallback_level(event_key); + } + return null; +} +function get_event_severity(level) { + const normalized_level = normalize_event_level(level); + if (normalized_level === "milestone") return "attention"; + if (normalized_level === "attention") return "attention"; + if (normalized_level === "warning") return "warning"; + if (normalized_level === "error") return "error"; + return null; +} +function get_next_notification_status(current_status, event_key = "", event = {}) { + const next_status = get_event_severity(get_event_level(event_key, event)); + if (!next_status) return current_status ?? null; + const current_rank = severity_order[current_status] || 0; + const next_rank = severity_order[next_status] || 0; + if (next_rank > current_rank) return next_status; + return current_status ?? next_status; +} + +// node_modules/obsidian-smart-env/node_modules/smart-events/event_logs.js +var EXCLUDED_EVENT_KEYS = { + "collection:save_started": true, + "collection:save_completed": true, + "notifications:seen": true, + "notifications:seen_all": true, + "event_logs:mute_changed": true, + "event_log:first": true +}; +var EventLogs = class extends Collection { + static version = "0.1.0"; + constructor(env, opts = {}) { + super(env, opts); + this.session_events = []; + this.notification_status = null; + } + queue_save_debounce_ms = 7500; + /** + * Factory that attaches the collection to env and registers the wildcard listener. + * @param {Object} env + * @param {Object} [opts={}] + * @returns {EventLogs} + */ + static create(env, opts = {}) { + const instance = new this(env, opts); + instance.init(); + return instance; + } + /** Prefer an explicit item class to keep wiring thin. */ + get item_type() { + return EventLog; + } + /** + * Instance init + * - Ensure env.events exists + * - Register wildcard listener + * - Idempotent across repeated calls + */ + init() { + if (!this.env?.events) SmartEvents.create(this.env); + if (this._unsub_wildcard) this._unsub_wildcard(); + this._unsub_wildcard = this.env.events.on(WILDCARD_KEY, (event, event_key) => { + this.on_any_event(event_key, event); + }); + } + /** + * Handle any emitted event. + * Persists counters and timestamps in epoch ms. + * + * @param {string} event_key + * @param {Record} event + * @param {boolean} [event.skip_save_log_collection=false] - avoid unnecessary saves for high-frequency events + * @returns {{event_key: string, event: Record, at: number, level: string | null, unseen: boolean, native_notice_shown: boolean} | null} + */ + on_any_event(event_key, event) { + if (EXCLUDED_EVENT_KEYS[event_key]) return null; + const at_ms = typeof event?.at === "number" ? event.at : Date.now(); + const derived_level = get_event_level(event_key, event); + const session_entry = { + event_key, + event, + at: at_ms, + level: derived_level, + unseen: Boolean(derived_level), + native_notice_shown: false + }; + this.session_events.push(session_entry); + this.notification_status = get_next_notification_status(this.notification_status, event_key, event); + try { + if (typeof event_key !== "string") return session_entry; + let event_log = this.get(event_key); + if (!event_log) { + event_log = new EventLog(this.env, { key: event_key }); + this.set(event_log); + this.emit_event("event_log:first", { first_of_event_key: event_key }); + } + const next = next_log_stats( + { + ct: event_log.data.ct, + first_at: event_log.data.first_at, + last_at: event_log.data.last_at + }, + at_ms + ); + event_log.data = { ...event_log.data, ...next }; + if (event?.event_source) { + if (!event_log.data.event_sources) event_log.data.event_sources = {}; + if (!event_log.data.event_sources[event.event_source]) { + event_log.data.event_sources[event.event_source] = 0; + } + event_log.data.event_sources[event.event_source] += 1; + } + event_log.queue_save(); + if (!event.skip_save_log_collection) this.queue_save(); + } catch (err) { + console.error("[EventLogs] record failure", event_key, err); + } + return session_entry; + } + /** + * Recompute severity status from unseen session entries. + * + * @returns {'attention'|'warning'|'error'|null} + */ + refresh_notification_status() { + this.notification_status = this.session_events.reduce((current_status, session_entry) => { + if (!session_entry?.unseen) return current_status; + return get_next_notification_status(current_status, session_entry.event_key, session_entry.event); + }, null); + return this.notification_status; + } + /** + * Return unseen session entries. + * + * @returns {Array} + */ + get_unseen_notification_entries() { + return this.session_events.filter((session_entry) => session_entry?.unseen === true); + } + /** + * Count unseen canonical notifications. + * + * @returns {number} + */ + get_unseen_notification_count() { + return this.get_unseen_notification_entries().length; + } + /** + * Mark a single session entry as seen. + * + * @param {object} session_entry + * @param {object} [params={}] + * @param {boolean} [params.native_notice_shown=false] + * @returns {boolean} + */ + mark_session_entry_seen(session_entry, params = {}) { + if (!session_entry || session_entry.unseen !== true) return false; + const { native_notice_shown = false } = params; + session_entry.unseen = false; + if (native_notice_shown) { + session_entry.native_notice_shown = true; + } + this.refresh_notification_status(); + this.env?.events?.emit?.("notifications:seen", { + event_key: session_entry.event_key, + native_notice_shown + }); + return true; + } + /** + * Mark all unseen canonical notifications as seen. + * + * @returns {boolean} + */ + mark_all_notification_entries_seen() { + let seen_count = 0; + for (const session_entry of this.session_events) { + if (session_entry?.unseen !== true) continue; + session_entry.unseen = false; + seen_count += 1; + } + if (!seen_count) return false; + this.refresh_notification_status(); + this.env?.events?.emit?.("notifications:seen_all", { count: seen_count }); + return true; + } + /** + * Cleanly detach listeners and cancel pending save. + */ + unload() { + if (typeof this._unsub_wildcard === "function") { + this._unsub_wildcard(); + this._unsub_wildcard = null; + } + return super.unload(); + } +}; +var event_logs_default = { + class: EventLogs, + collection_key: "event_logs", + data_adapter: AjsonSingleFileCollectionDataAdapter, + item_type: EventLog +}; + +// node_modules/obsidian-smart-env/src/utils/event_logs_utils.js +var notice_timeout_ms = 7e3; +var milestone_notice_timeout_ms = 12e3; +function is_event_log_muted(event_log) { + return Boolean(event_log?.data?.muted); +} +function get_notification_setting_key(level) { + const normalized_level = normalize_event_level(level); + if (!normalized_level) return null; + if (!notification_levels.includes(normalized_level)) return null; + return `native_notice_${normalized_level}`; +} +function get_notice_setting_value(instance, setting_key) { + if (!setting_key) return false; + const explicit_value = instance?.settings?.[setting_key]; + if (typeof explicit_value === "boolean") return explicit_value; + const default_value = instance?.constructor?.default_settings?.[setting_key]; + if (typeof default_value === "boolean") return default_value; + return false; +} +function should_show_native_notice(instance, params = {}) { + const { event_key = "", event = {} } = params; + const level = get_event_level(event_key, event); + if (!level) return false; + const setting_key = get_notification_setting_key(level); + if (!setting_key) return false; + if (!get_notice_setting_value(instance, setting_key)) return false; + const event_log = typeof instance?.get === "function" ? instance.get(event_key) : instance?.items?.[event_key]; + if (is_event_log_muted(event_log)) return false; + return true; +} +function get_timeout_override(event = {}) { + const timeout_value = event.timeout_ms ?? event.timeout; + if (typeof timeout_value !== "number" || !Number.isFinite(timeout_value)) return null; + if (timeout_value < 0) return null; + return timeout_value; +} +function get_native_notice_timeout(event_key, event = {}) { + const timeout_override = get_timeout_override(event); + if (timeout_override !== null) return timeout_override; + const level = get_event_level(event_key, event); + if (level === "milestone") return milestone_notice_timeout_ms; + return notice_timeout_ms; +} +function get_native_notice_message(event_key, event = {}) { + if (typeof event?.message === "string" && event.message.trim()) return event.message.trim(); + if (typeof event?.details === "string" && event.details.trim()) return event.details.trim(); + if (typeof event?.milestone === "string" && event.milestone.trim()) return event.milestone.trim(); + return event_key || "notification"; +} + +// node_modules/obsidian-smart-env/src/utils/notice_action_dispatch.js +function to_trimmed_string(value) { + return typeof value === "string" ? value.trim() : ""; +} +function is_plain_object4(value) { + return Boolean(value) && typeof value === "object" && !Array.isArray(value); +} +function get_source_event(params = {}) { + if (is_plain_object4(params.source_event)) return params.source_event; + if (is_plain_object4(params.event)) return params.event; + return {}; +} +function get_source_event_key(params = {}) { + return to_trimmed_string(params.source_event_key) || to_trimmed_string(params.event_key); +} +function dispatch_btn_event_action(env, params = {}) { + const { + event_source = "native_notice_button", + source_event_key = "", + source_event = {} + } = params; + const btn_event_key = to_trimmed_string(source_event?.btn_event_key); + if (!btn_event_key) return null; + if (typeof env?.events?.emit !== "function") return false; + const btn_event_payload = is_plain_object4(source_event?.btn_event_payload) ? source_event.btn_event_payload : {}; + env.events.emit(btn_event_key, { + ...btn_event_payload, + event_source, + source_event_key, + source_event + }); + return true; +} +function dispatch_notice_action(env, callback_key, params = {}) { + const event_source = to_trimmed_string(params.event_source) || "native_notice_button"; + const source_event_key = get_source_event_key(params); + const source_event = get_source_event(params); + const btn_event_result = dispatch_btn_event_action(env, { + event_source, + source_event_key, + source_event + }); + if (btn_event_result !== null) return btn_event_result; + const next_callback_key = to_trimmed_string(callback_key); + if (!next_callback_key) return false; + const app_commands = env?.main?.app?.commands; + if (app_commands?.commands?.[next_callback_key] && typeof app_commands.executeCommandById === "function") { + app_commands.executeCommandById(next_callback_key); + return true; + } + if (typeof env?.events?.emit !== "function") return false; + env.events.emit(next_callback_key, { + event_source, + source_event_key, + source_event + }); + return true; +} + +// node_modules/obsidian-smart-env/src/collections/event_logs.js +var settings_config4 = { + native_notice_info: { + name: "Info", + description: 'Show Obsidian native notices for "info" level events.', + type: "toggle" + }, + native_notice_warning: { + name: "Warning", + description: 'Show Obsidian native notices for "warning" level events.', + type: "toggle" + }, + native_notice_error: { + name: "Error", + description: 'Show Obsidian native notices for "error" level events.', + type: "toggle" + }, + native_notice_attention: { + name: "Attention", + description: 'Show Obsidian native notices for "attention" level events.', + type: "toggle" + }, + native_notice_milestone: { + name: "Milestone", + description: 'Show Obsidian native notices for "milestone" level events.', + type: "toggle" + } +}; +var native_notice_component_key_map = Object.freeze({ + milestone: "milestone_notification" +}); +function get_native_notice_component_key(event_key, event = {}) { + const level = get_event_level(event_key, event); + if (!level) return null; + return native_notice_component_key_map[level] || "default_notification"; +} +var EventLogs2 = class extends EventLogs { + static get default_settings() { + return { + ...super.default_settings || {}, + native_notice_info: false, + native_notice_warning: true, + native_notice_error: true, + native_notice_attention: false, + native_notice_milestone: true + }; + } + get settings_config() { + return { ...settings_config4 }; + } + /** + * @param {string} event_key + * @param {Record} event + * @returns {object|null} + */ + on_any_event(event_key, event = {}) { + const session_entry = super.on_any_event(event_key, event); + this.show_native_notice(session_entry, { event_key, event }); + return session_entry; + } + /** + * @param {object|null} session_entry + * @param {object} [params={}] + * @param {string} [params.event_key=''] + * @param {Record} [params.event={}] + * @returns {boolean} + */ + async show_native_notice(session_entry, params = {}) { + const { event_key = "", event = {} } = params; + if (!should_show_native_notice(this, { event_key, event })) return false; + try { + const notice_content = await this.build_native_notice_content(event_key, event); + const notice_timeout = get_native_notice_timeout(event_key, event); + new import_obsidian3.Notice(notice_content, notice_timeout); + } catch (error) { + console.error("EventLogs: failed to show native notice", { + event_key, + error + }); + return false; + } + if (session_entry) { + this.mark_session_entry_seen(session_entry, { native_notice_shown: true }); + } + return true; + } + /** + * @param {string} event_key + * @param {Record} [event={}] + * @returns {string|DocumentFragment} + */ + async build_native_notice_content(event_key, event = {}) { + const notice_event = event?.btn_event_key && !event?.btn_callback ? { ...event, btn_callback: event.btn_event_key } : event; + const component_key = get_native_notice_component_key(event_key, notice_event); + if (component_key && this.env?.config?.components?.[component_key]) { + const component_content = await this.env.smart_components.render_component(component_key, this.env, { + event_key, + event: notice_event, + on_action: (callback_key) => this.run_notice_callback(callback_key, { event_key, event: notice_event }), + on_mute: () => this.set_event_key_muted(event_key, true) + }); + if (component_content) return component_content; + } + const notice_message = get_native_notice_message(event_key, notice_event); + const btn_text = typeof notice_event?.btn_text === "string" ? notice_event.btn_text.trim() : ""; + const btn_callback = typeof notice_event?.btn_callback === "string" ? notice_event.btn_callback.trim() : ""; + if (!btn_text || !btn_callback || typeof document === "undefined") { + return notice_message; + } + const frag = document.createDocumentFragment(); + const message_el = document.createElement("div"); + message_el.textContent = notice_message; + frag.appendChild(message_el); + const button_el = document.createElement("button"); + button_el.type = "button"; + button_el.className = "mod-cta"; + button_el.textContent = btn_text; + button_el.addEventListener("click", () => { + this.run_notice_callback(btn_callback, { event_key, event: notice_event }); + }); + frag.appendChild(button_el); + return frag; + } + /** + * @param {string} callback_key + * @param {object} [params={}] + * @param {string} [params.event_key=''] + * @param {Record} [params.event={}] + * @returns {boolean} + */ + run_notice_callback(callback_key, params = {}) { + return dispatch_notice_action(this.env, callback_key, { + event_source: "native_notice_button", + source_event_key: params.event_key, + source_event: params.event || {} + }); + } + /** + * @param {string} event_key + * @returns {boolean} + */ + is_event_key_muted(event_key) { + return is_event_log_muted(this.get?.(event_key)); + } + /** + * @param {string} event_key + * @param {boolean} [muted=true] + * @returns {boolean} + */ + set_event_key_muted(event_key, muted = true) { + const event_log = this.get?.(event_key); + if (!event_log) return false; + event_log.data = { + ...event_log.data, + muted: Boolean(muted) + }; + event_log.queue_save?.(); + this.queue_save?.(); + this.env?.events?.emit?.("event_logs:mute_changed", { + event_key, + muted: Boolean(muted) + }); + return true; + } + /** + * @param {string} event_key + * @returns {boolean} + */ + toggle_event_key_muted(event_key) { + const next_muted = !this.is_event_key_muted(event_key); + return this.set_event_key_muted(event_key, next_muted); + } +}; +var event_logs_default2 = { + ...event_logs_default, + class: EventLogs2, + settings_config: settings_config4 +}; + +// node_modules/obsidian-smart-env/src/modals/smart_fuzzy_suggest_modal.js +var import_obsidian4 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/smart_fuzzy_suggest_utils.js +function build_suggest_scope_items(modal, params = {}) { + if (!modal) return []; + const action_keys = Array.isArray(params.action_keys) ? params.action_keys : []; + const action_configs = modal?.env?.config?.actions || {}; + const action_handlers = modal?.item_or_collection?.actions || {}; + const unique_action_keys = [...new Set(action_keys)]; + return unique_action_keys.reduce((acc, action_key) => { + const action_handler = action_handlers[action_key]; + if (typeof action_handler !== "function") return acc; + const action_config = action_configs[action_key] || {}; + const display_name7 = action_config.display_name || action_key; + acc.push({ + select_action: () => { + modal.update_suggestions(action_key); + setTimeout(() => { + modal.inputEl.focus(); + }, 100); + }, + key: action_key, + display: display_name7 + }); + return acc; + }, []); +} +var should_handle_arrow_left = (modal, params = {}) => { + const input_el = modal?.inputEl; + const event_target = params.event_target; + const input_value = typeof params.input_value === "string" ? params.input_value : input_el?.value || ""; + if (event_target === input_el && input_value) { + return false; + } + return true; +}; + +// node_modules/obsidian-smart-env/src/modals/smart_fuzzy_suggest_modal.js +var SmartFuzzySuggestModal = class extends import_obsidian4.FuzzySuggestModal { + constructor(item_or_collection) { + const env = item_or_collection.env; + const plugin = env.plugin; + const app2 = plugin.app; + super(app2); + this.app = app2; + env.create_env_getter(this); + this.plugin = plugin; + this.item_or_collection = item_or_collection; + this.emptyStateText = "No suggestions available"; + this._set_custom_instructions = false; + } + /** Unique type key for this modal class. Subclasses override. */ + static get modal_type() { + return "smart-fuzzy-suggest"; + } + /** Human label used in commands. Subclasses override as needed. */ + static get display_text() { + return "Smart Fuzzy Suggest"; + } + /** Event name listened to on env.events to open this modal. */ + static get event_domain() { + return `${this.modal_type}`; + } + /** Command id used with addCommand. */ + static get command_id() { + return this.modal_type; + } + static open(item_or_collection, params) { + const Modal10 = ( + /** @type {typeof SmartFuzzySuggestModal} */ + this + ); + const modal = new Modal10(item_or_collection, params); + modal.open(params); + return modal; + } + static register_modal(plugin) { + const Modal10 = ( + /** @type {typeof SmartFuzzySuggestModal} */ + this + ); + const env = plugin?.env; + const modal_config = { + ...env.config.modals?.[this.modal_key] || {}, + class: null + }; + console.log(`Registering modal: ${this.display_text}`, { modal_config, Modal: Modal10 }); + const open_handler = (payload = {}) => { + const item = Modal10.resolve_item_from_payload(env, payload); + const modal = Modal10.open(item, { + ...modal_config, + ...payload + // spread since event payload is locked + }); + return modal; + }; + const disposers = [ + env?.events?.on?.(`${Modal10.event_domain}:open`, open_handler) + // env.events?.on?.(`${Modal.event_domain}:suggest`, suggest_handler), + ]; + const dispose_all = () => { + disposers.forEach((dispose) => typeof dispose === "function" && dispose()); + }; + if (typeof plugin.register === "function") { + plugin.register(() => dispose_all()); + } + return { + event_domain: Modal10.event_domain + }; + } + static resolve_item_from_payload(env, payload) { + const item = env?.[payload.collection_key]?.items?.[payload.item_key]; + return item; + } + setInstructions(instructions, is_custom = true) { + this._set_custom_instructions = is_custom; + super.setInstructions(instructions); + } + set_default_instructions() { + this.setInstructions([ + { command: "Enter", purpose: "Select" } + ], false); + } + open(params = {}) { + super.open(); + this.modalEl.addEventListener("keydown", (e) => { + if (e.key === "Enter") { + if (e.shiftKey) this.use_shift_select = true; + this.selectActiveSuggestion(e); + } + const is_cursor_end_of_input = this.inputEl.selectionStart === this.inputEl.value.length; + const should_handle_arrow_right = is_cursor_end_of_input || e.target !== this.inputEl || !this.inputEl.value; + const should_handle_arrow_left_action = should_handle_arrow_left(this, { + event_target: e.target, + input_value: this.inputEl.value + }); + if (e.key === "ArrowLeft" && should_handle_arrow_left_action) { + this.use_arrow_left = true; + this.selectActiveSuggestion(e); + return; + } + if (e.key === "ArrowRight" && should_handle_arrow_right) { + e.preventDefault(); + this.use_mod_select = true; + this.use_arrow_right = true; + this.selectActiveSuggestion(e); + return; + } + }); + } + getItems() { + return this.get_suggestions(); + } + getItemText(suggestion_item) { + return suggestion_item.display; + } + filter_suggestions(suggestions) { + return suggestions; + } + get_suggestions() { + if (this.suggestions?.length) { + this.suggestions = this.filter_suggestions(this.suggestions); + if (this.suggestions.length > 0) { + return this.suggestions; + } + } + if (this.default_suggest_action_keys?.length) { + if (this.default_suggest_action_keys.length === 1) { + this.update_suggestions(this.default_suggest_action_keys[0]); + return []; + } + return this.get_suggest_scopes(); + } + return []; + } + get_suggest_scopes() { + return build_suggest_scope_items(this, { + action_keys: this.default_suggest_action_keys + }); + } + async update_suggestions(suggest_ref) { + if (typeof suggest_ref === "string") { + suggest_ref = this.item_or_collection.actions[suggest_ref]; + } + if (typeof suggest_ref === "function") { + this._set_custom_instructions = false; + const result = await suggest_ref({ modal: this }); + console.log("Suggestion action result", result); + if (Array.isArray(result) && result.length) { + this.suggestions = result; + } + } else if (Array.isArray(suggest_ref)) { + this.suggestions = suggest_ref; + } + if (Array.isArray(this.suggestions) && this.suggestions.length) { + this.updateSuggestions(); + } else { + this.env.events.emit("notification:error", { message: "Invalid suggestion action" }); + console.warn("Invalid suggestion action", suggest_ref); + } + if (!this._set_custom_instructions) { + this.set_default_instructions(); + } + } + get default_suggest_action_keys() { + if (Array.isArray(this.params?.default_suggest_action_keys)) { + return this.params.default_suggest_action_keys; + } + return this.env.config.modals[this.modal_key]?.default_suggest_action_keys || []; + } + renderSuggestion(sug, el) { + super.renderSuggestion(sug, el); + const icon = sug?.icon || sug?.item?.icon; + if (icon) { + el.addClass("sc-modal-suggestion-has-icon"); + const icon_el = el.createEl("span"); + (0, import_obsidian4.setIcon)(icon_el, icon); + } + const display_right_raw = sug && Object.prototype.hasOwnProperty.call(sug, "display_right") ? sug.display_right : sug?.item?.display_right; + const display_right = display_right_raw === null || display_right_raw === void 0 ? "" : String(display_right_raw).trim(); + if (display_right) { + this.env.smart_components.render_component("suggest_display_right", display_right).then((right_el) => { + el.appendChild(right_el); + }); + } + return el; + } + onChooseSuggestion(selected, evt, ...other) { + this.prevent_close = true; + const suggestion = selected.item; + const is_arrow_left = this.use_arrow_left; + const is_arrow_right = this.use_arrow_right; + const is_shift_select = evt?.shiftKey || this.use_shift_select; + const is_mod_select = import_obsidian4.Keymap.isModifier(evt, "Mod") || this.use_mod_select; + this.use_arrow_right = false; + this.use_mod_select = false; + this.use_arrow_left = false; + this.use_shift_select = false; + if (is_arrow_left) { + if (typeof suggestion.arrow_left_action === "function") { + this.handle_choose_action(suggestion, "arrow_left_action"); + } else { + if (this.last_input_value) { + this.inputEl.value = this.last_input_value; + setTimeout(() => { + const len = this.inputEl.value.length; + this.inputEl.setSelectionRange(len, len); + }, 0); + this.last_input_value = null; + } + this.suggestions = null; + this.params.default_suggest_action_keys = null; + this.updateSuggestions(); + return; + } + } else if (is_arrow_right && typeof suggestion.arrow_right_action === "function") { + this.handle_choose_action(suggestion, "arrow_right_action"); + } else if (is_mod_select && typeof suggestion.mod_select_action === "function") { + this.handle_choose_action(suggestion, "mod_select_action"); + } else if (is_shift_select && typeof suggestion.shift_select_action === "function") { + this.handle_choose_action(suggestion, "shift_select_action"); + } else if (typeof suggestion.select_action === "function") { + this.handle_choose_action(suggestion, "select_action"); + } else { + this.env.events.emit("notification:warning", { selection_display: suggestion.display, message: "No action defined for this suggestion" }); + } + } + async handle_choose_action(suggestion, action_key) { + let chosen_action = suggestion[action_key]; + const result = await chosen_action({ modal: this }); + if (Array.isArray(result) && result.length) { + this.suggestions = result; + } else if (Array.isArray(result)) { + this.env.events.emit("notification:info", { message: "No suggestions returned from action" }); + } + const idx = this.chooser.values.findIndex((i) => i.item?.display === suggestion.display); + setTimeout(() => { + this.updateSuggestions(); + if (idx !== -1) { + this.chooser.setSelectedItem(idx); + } + }, 100); + } + close() { + setTimeout(() => { + if (!this.prevent_close) super.close(); + this.prevent_close = false; + }, 10); + } + onClose() { + this.item_or_collection.emit_event(`${this.constructor.event_domain}:closed`); + } +}; + +// node_modules/obsidian-smart-env/src/modals/context_selector.js +var import_obsidian5 = require("obsidian"); +var ContextModal = class extends SmartFuzzySuggestModal { + /** Modal identity */ + static get modal_type() { + return "context_selector"; + } + static get display_text() { + return "Context Selector"; + } + static get event_domain() { + return "context_selector"; + } + static get command_id() { + return this.modal_type; + } + static get modal_key() { + return "context_selector"; + } + get modal_key() { + return "context_selector"; + } + constructor(smart_context, params = {}) { + super(smart_context); + this.params = { ...params }; + this.smart_context = smart_context; + this.set_default_instructions(); + } + set_default_instructions() { + this.setInstructions([ + { command: "Enter", purpose: "Add to context" }, + { command: `\u2192 / \u2190`, purpose: "Toggle block view" }, + { command: "Esc", purpose: "Close" } + ]); + } + open(params = {}) { + this.params = { ...this.params, ...params }; + super.open(); + this.render(this.params); + } + async render(params = this.params) { + this.modalEl.style.display = "flex"; + this.modalEl.style.flexDirection = "column"; + this.modalEl.prepend( + await this.env.smart_components.render_component( + "smart_context_item", + this.smart_context, + params + ) + ); + } + filter_suggestions(suggestions) { + return suggestions.filter((s) => { + if (s.key && this.smart_context?.data?.context_items[s.key]) return false; + return true; + }); + } +}; + +// node_modules/obsidian-smart-env/src/modals/notifications_feed_modal.js +var import_obsidian6 = require("obsidian"); +var NotificationsFeedModal = class extends import_obsidian6.Modal { + constructor(app2, env, params = {}) { + super(app2); + this.env = env; + this.params = params; + } + async onOpen() { + if (this.modalEl?.classList) { + this.modalEl.classList.add("smart-env-notifications-modal"); + } + this.titleEl.setText("Events & notifications"); + this.contentEl.empty(); + const event_log = await this.env.smart_components.render_component("notifications_feed", this.env, { + live_updates: true, + auto_mark_seen: true, + ...this.params, + state: get_notifications_feed_state(this.params) + }); + this.contentEl.appendChild(event_log); + } + onClose() { + this.contentEl.empty(); + if (this.modalEl?.classList) { + this.modalEl.classList.remove("smart-env-notifications-modal"); + } + } +}; +function get_notifications_feed_state(params = {}) { + const state = params?.state && typeof params.state === "object" ? { ...params.state } : {}; + const target_entry_key = state.target_entry_key || get_target_entry_key(params?.event_key, params?.event); + if (!target_entry_key) return state; + const expanded_entry_keys = state.expanded_entry_keys instanceof Set ? new Set(state.expanded_entry_keys) : /* @__PURE__ */ new Set(); + expanded_entry_keys.add(target_entry_key); + return { + ...state, + target_entry_key, + expanded_entry_keys + }; +} +function get_target_entry_key(event_key = "", event = {}) { + const next_event_key = typeof event_key === "string" ? event_key.trim() : ""; + if (!next_event_key) return ""; + const at = event?.at; + if (typeof at !== "number" || !Number.isFinite(at)) return ""; + return `${next_event_key}:${at}`; +} + +// node_modules/obsidian-smart-env/src/modals/milestones_modal.js +var import_obsidian7 = require("obsidian"); +var MILESTONES_HELP_URL = "https://smartconnections.app/smart-environment/milestones/?utm_source=milestones_modal_help"; +var MilestonesModal = class extends import_obsidian7.Modal { + constructor(app2, env) { + super(app2); + this.env = env; + } + async onOpen() { + render_milestones_modal_title(this.titleEl, this.env); + this.contentEl.empty(); + const milestones = await this.env.smart_components.render_component("milestones", this.env, {}); + this.contentEl.appendChild(milestones); + } + onClose() { + this.contentEl.empty(); + } +}; +function render_milestones_modal_title(title_el, env) { + if (!title_el) return; + title_el.empty(); + title_el.classList.add("sc-milestones-modal__title"); + const row_el = document.createElement("div"); + row_el.className = "sc-milestones-modal__title-row"; + const text_el = document.createElement("div"); + text_el.className = "sc-milestones-modal__title-text"; + text_el.textContent = "Smart Milestones"; + const help_btn_el = document.createElement("button"); + help_btn_el.type = "button"; + help_btn_el.className = "sc-milestones-modal__help-btn"; + help_btn_el.setAttribute("aria-label", "Open Smart Milestones help"); + help_btn_el.setAttribute("title", "Help"); + render_help_icon(help_btn_el); + help_btn_el.addEventListener("click", (evt) => { + evt.preventDefault(); + evt.stopPropagation(); + window.open(MILESTONES_HELP_URL, "_external"); + }); + row_el.appendChild(text_el); + row_el.appendChild(help_btn_el); + title_el.appendChild(row_el); +} +function render_help_icon(icon_el) { + const ok = set_icon_with_fallback(icon_el, ["circle-help", "help-circle", "help", "info"]); + if (!ok) icon_el.textContent = "?"; +} +function set_icon_with_fallback(icon_el, icon_ids) { + if (!icon_el) return false; + const ids = Array.isArray(icon_ids) ? icon_ids : []; + for (const icon_id of ids) { + if (typeof icon_id !== "string" || icon_id.length === 0) continue; + icon_el.textContent = ""; + try { + (0, import_obsidian7.setIcon)(icon_el, icon_id); + } catch (err) { + continue; + } + if (icon_el.querySelector("svg")) return true; + } + return false; +} + +// node_modules/obsidian-smart-env/src/modals/browse_plugins_modal.js +var import_obsidian8 = require("obsidian"); +var BrowseSmartPlugins = class extends import_obsidian8.Modal { + constructor(app2, env) { + super(app2); + this.env = env; + } + async onOpen() { + if (this.modalEl?.classList) { + this.modalEl.classList.add("smart-env-pro-plugins-modal"); + } + if (this.modalEl?.style) { + this.modalEl.style.width = "min(920px, 92vw)"; + this.modalEl.style.maxHeight = "min(820px, 88vh)"; + } + this.titleEl.setText("Smart Plugins"); + this.contentEl.empty(); + const plugin_store = await this.env.smart_components.render_component("smart_plugins_list", this.env, { + event_source: "browse_plugins_modal" + }); + if (plugin_store) { + this.contentEl.appendChild(plugin_store); + } + } + onClose() { + this.contentEl.empty(); + if (this.modalEl?.classList) { + this.modalEl.classList.remove("smart-env-pro-plugins-modal"); + } + if (this.modalEl?.style) { + this.modalEl.style.width = ""; + this.modalEl.style.maxHeight = ""; + } + } +}; + +// node_modules/obsidian-smart-env/default.settings.js +var default_settings = { + is_obsidian_vault: true, + smart_blocks: { + embed_blocks: true, + min_chars: 200 + }, + smart_sources: { + min_chars: 200, + embed_model: { + adapter: "transformers", + transformers: { + model_key: "TaylorAI/bge-micro-v2" + } + }, + excluded_headings: "", + file_exclusions: "Untitled", + folder_exclusions: "" + }, + language: "en", + re_import_wait_time: 13, + smart_chat_threads: { + chat_model: { + adapter: "ollama", + ollama: {} + } + }, + smart_notices: {}, + smart_view_filter: { + expanded_view: false, + render_markdown: true, + show_full_path: false + }, + version: "", + new_user: true, + // DEPRECATED: 2025-06-05 (use localStorage instead???) + // 2025-11-26 + models: { + embedding_platform: "transformers", + chat_completion_platform: "open_router" + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-models/items/model.js +var Model = class extends CollectionItem { + /** + * Default properties for an instance of CollectionItem. + * @returns {Object} + */ + static get defaults() { + return { + data: { + api_key: "", + provider_key: "", + model_key: "" + } + }; + } + get_key() { + if (!this.data.key) { + this.data.created_at = Date.now(); + this.data.key = `${this.data.provider_key}#${this.data.created_at}`; + } + return this.data.key; + } + get provider_key() { + return this.data.provider_key; + } + get env_config() { + return this.collection.env_config; + } + get provider_config() { + return this.env_config.providers?.[this.provider_key] || {}; + } + get ProviderAdapterClass() { + return this.provider_config.class; + } + get instance() { + if (!this._instance) { + if (!this.ProviderAdapterClass) { + const new_default_model = this.collection.new_model({ provider_key: this.collection.default_provider_key }); + return new_default_model.instance; + } + const Class = this.ProviderAdapterClass; + this._instance = new Class(this); + if (typeof this._instance.load_background === "function") { + this._instance.load_background(); + } else { + this._instance.load(); + } + this.once_event("model:changed", () => { + this._instance.unload?.(); + this._instance = null; + }); + } + return this._instance; + } + async count_tokens(text) { + return this.instance.count_tokens(text); + } + get api_key() { + return this.data.api_key; + } + /** + * Create (or reuse) a proxy around a target settings object so that + * any mutations trigger queue_save on the model. + * Proxies are cached per-target via WeakMap to support deep nested objects. + * + * @param {Object} target - The settings object or nested object to wrap. + * @returns {Object} Proxied object or original value if not an object. + * @private + */ + create_settings_proxy(target) { + if (!target || typeof target !== "object") return target; + if (!this._settings_proxy_map) { + this._settings_proxy_map = /* @__PURE__ */ new WeakMap(); + } + const existing = this._settings_proxy_map.get(target); + if (existing) return existing; + const self = this; + const handler = { + get(target_obj, prop, receiver) { + const value = Reflect.get(target_obj, prop, receiver); + if (value && typeof value === "object") { + return self.create_settings_proxy(value); + } + return value; + }, + set(target_obj, prop, value, receiver) { + const previous = target_obj[prop]; + const result = Reflect.set(target_obj, prop, value, receiver); + if (previous !== value) { + self.debounce_save(); + } + return result; + }, + deleteProperty(target_obj, prop) { + const had = Object.prototype.hasOwnProperty.call(target_obj, prop); + const result = Reflect.deleteProperty(target_obj, prop); + if (had) { + self.debounce_save(); + } + return result; + } + }; + const proxy = new Proxy(target, handler); + this._settings_proxy_map.set(target, proxy); + return proxy; + } + /** + * @private + * @param {number} [ms=100] + */ + debounce_save(ms = 100) { + this.emit_event("model:changed"); + if (this._debounce_save_timeout) { + clearTimeout(this._debounce_save_timeout); + } + this._debounce_save_timeout = setTimeout(() => { + this.queue_save(); + this.collection.process_save_queue(); + this._debounce_save_timeout = null; + }, ms); + } + async get_model_key_options() { + const model_configs = await this.instance.get_models(); + return Object.entries(model_configs).map(([key, model_config]) => ({ + label: model_config.name || key, + value: model_config.key || key + })).sort((a, b) => { + if (a.label.toLowerCase().includes("free") && !b.label.toLowerCase().includes("free")) { + return -1; + } + if (!a.label.toLowerCase().includes("free") && b.label.toLowerCase().includes("free")) { + return 1; + } + return a.label.localeCompare(b.label); + }); + } + /** + * @private + * @param {string} key + * @param {*} value + * @param {*} elm + */ + model_changed(key, value, elm) { + if (key === "model_key") { + this.data.model_key = value; + const model_defaults = this.data.provider_models?.[this.data.model_key] || {}; + const adapter_defaults = this.ProviderAdapterClass.defaults || {}; + delete this.data.test_passed; + this.data = { + ...this.data, + ...adapter_defaults, + ...model_defaults + }; + } + if (!["api_key", "meta.name"].includes(key)) { + this.emit_event("model:changed"); + } + } + /** + * @abstract should be implemented by subclasses + */ + async test_model() { + } + get display_name() { + return this.data.meta?.name || `${this.data.provider_key} - ${this.data.model_key}`; + } + get settings_config() { + const model = this; + return { + provider_key: { + type: "html", + value: `

Provider: ${this.data.provider_key}

` + }, + "meta.name": { + type: "text", + name: "Name", + description: "A friendly name for this model configuration." + }, + model_key: { + type: "dropdown", + name: "Model", + description: "The model to use from the selected provider.", + options_callback() { + return model.get_model_key_options(); + }, + callback(value, setting) { + return model.model_changed("model_key", value, setting); + } + }, + // add model_changed callback to each provider setting that doesn't already have callback defined + ...Object.fromEntries( + Object.entries(this.provider_config.settings_config || {}).map( + ([setting_key, setting_config]) => { + const callback = setting_config.callback || ((value, setting) => { + return model.model_changed(setting_key, value, setting); + }); + return [setting_key, { ...setting_config, callback }]; + } + ) + ) + }; + } + delete_model() { + this.delete(); + this.debounce_save(); + } + /** + * Reactive settings view for this model. + * Mutating any property (including nested objects/arrays) via this proxy + * will call queue_save(). + * + * @returns {Object} Proxied view of this.data. + */ + get settings() { + return this.create_settings_proxy(this.data); + } + get model_key() { + return this.data.model_key; + } + /** + * @deprecated included for backward compatibility (2026-02-11) + */ + get opts() { + return this.settings; + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-models/collections/models.js +var Models = class extends Collection { + model_type = "Model type"; + // replace in subclass + new_model(data = {}) { + if (!data.provider_key) throw new Error("provider_key is required to create a new model"); + const existing_from_provider = this.filter((m) => m.provider_key === data.provider_key).sort((a, b) => b.data.created_at - a.data.created_at)[0]; + if (existing_from_provider) { + if (!data.api_key && existing_from_provider.data.api_key) { + data.api_key = existing_from_provider.data.api_key; + } + } + const item = new this.item_type(this.env, { + ...data + }); + this.set(item); + this.settings.default_model_key = item.key; + this.emit_event("model:changed"); + item.queue_save(); + return item; + } + /** + * Retrieve the provider key used when creating a default model. + * @abstract + * @returns {string} provider key for the default model. + */ + get default_provider_key() { + throw new Error("default_provider_key not implemented"); + } + get default_model_key() { + const should_update_default = !this.settings.default_model_key || !this.get(this.settings.default_model_key) || this.get(this.settings.default_model_key).deleted; + if (should_update_default) { + const existing = this.filter((m) => !m.deleted).sort((a, b) => b.data.created_at - a.data.created_at)[0]; + if (existing) { + this.settings.default_model_key = existing.key; + } else { + const new_default = this.new_model({ provider_key: this.default_provider_key }); + new_default.queue_save(); + this.process_save_queue(); + this.settings.default_model_key = new_default.key; + } + } + return this.settings.default_model_key; + } + get default() { + return this.get(this.default_model_key); + } + get env_config() { + return this.env.config.collections[this.collection_key]; + } + get_model_key_options() { + return this.filter((i) => !i.deleted && i.ProviderAdapterClass).map((model) => ({ + label: model.data.meta?.name || `${model.provider_key} - ${model.data.model_key}`, + value: model.key + })); + } +}; +function settings_config5(scope) { + return { + default_model_key: { + type: "dropdown", + name: `Default ${scope.model_type.toLowerCase()} model`, + description: `Used as the default ${scope.model_type.toLowerCase()} model when no other is specified.`, + options_callback: () => { + return scope.get_model_key_options(); + }, + callback: async (value, setting) => { + scope.emit_event("model:changed"); + } + } + }; +} + +// node_modules/obsidian-smart-env/node_modules/smart-models/items/embedding_model.js +var EmbeddingModel = class extends Model { + /** + * Default properties for an instance of CollectionItem. + * @returns {Object} + */ + static get defaults() { + return { + data: { + api_key: "", + provider_key: "transformers", + model_key: "TaylorAI/bge-micro-v2", + dims: 384, + // ??? + max_tokens: 512 + // ??? + } + }; + } + async embed(input) { + if (typeof input === "string") { + input = [{ embed_input: input }]; + } + return (await this.embed_batch(input))[0]; + } + async embed_batch(inputs) { + return this.instance.embed_batch(inputs); + } + async test_model() { + try { + const resp = await this.embed("test input"); + const success = resp && !resp?.error; + this.data.test_passed = success; + this.debounce_save(); + return { success, response: resp }; + } catch (e) { + this.data.test_passed = false; + return { error: e.message || String(e) }; + } + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-models/collections/embedding_models.js +var EmbeddingModels = class extends Models { + model_type = "Embedding"; + get default_provider_key() { + return "transformers"; + } +}; +var embedding_models_collection = { + class: EmbeddingModels, + data_dir: "embedding_models", + collection_key: "embedding_models", + data_adapter: ajson_single_file_default, + item_type: EmbeddingModel, + providers: { + // transformers // replace with platform-specific import in obsidian-smart-env + }, + settings_config: settings_config5 +}; +var embedding_models_default = embedding_models_collection; + +// dist-text:/home/runner/work/obsidian-smart-connections/obsidian-smart-connections/plugin/node_modules/obsidian-smart-env/src/adapters/embedding-model/transformers_v4.iframe.js +var transformers_v4_iframe_default = 'var __defProp = Object.defineProperty;\nvar __name = (target, value) => __defProp(target, "name", { value, configurable: true });\n\n// ../jsbrains/smart-model/adapters/_adapter.js\nvar SmartModelAdapter = class {\n static {\n __name(this, "SmartModelAdapter");\n }\n /**\n * Create a SmartModelAdapter instance.\n * @param {SmartModel} model - The parent SmartModel instance\n */\n constructor(model2) {\n this.model = model2;\n this.state = "unloaded";\n }\n /**\n * Load the adapter.\n * @async\n * @returns {Promise}\n */\n async load() {\n this.set_state("loaded");\n }\n /**\n * Unload the adapter.\n * @returns {void}\n */\n unload() {\n this.set_state("unloaded");\n }\n /**\n * Get all settings.\n * @returns {Object} All settings\n */\n get settings() {\n return this.model.settings;\n }\n /**\n * Get the current model key.\n * @returns {string} Current model identifier\n */\n get model_key() {\n return this.model.model_key;\n }\n /**\n * Get the models.\n * @returns {Object} Map of model objects\n */\n get models() {\n const models = this.model.data.provider_models;\n if (typeof models === "object" && Object.keys(models || {}).length > 0) return models;\n else {\n return {};\n }\n }\n /**\n * Get available models from the API.\n * @abstract\n * @param {boolean} [refresh=false] - Whether to refresh cached models\n * @returns {Promise} Map of model objects\n */\n async get_models(refresh = false) {\n throw new Error("get_models not implemented");\n }\n /**\n * Get available models as dropdown options synchronously.\n * @returns {Array} Array of model options.\n */\n get_models_as_options() {\n const models = this.models;\n if (!Object.keys(models || {}).length) {\n this.get_models(true);\n return [{ value: "", name: "No models currently available" }];\n }\n return Object.entries(models).map(([id, model2]) => ({ value: id, name: model2.name || id })).sort((a, b) => a.name.localeCompare(b.name));\n }\n /**\n * Set the adapter\'s state.\n * @deprecated should be handled in SmartModel (only handle once)\n * @param {(\'unloaded\'|\'loading\'|\'loaded\'|\'unloading\')} new_state - The new state\n * @throws {Error} If the state is invalid\n */\n set_state(new_state) {\n const valid_states = ["unloaded", "loading", "loaded", "unloading"];\n if (!valid_states.includes(new_state)) {\n throw new Error(`Invalid state: ${new_state}`);\n }\n this.state = new_state;\n }\n // Replace individual state getters/setters with a unified state management\n get is_loading() {\n return this.state === "loading";\n }\n get is_loaded() {\n return this.state === "loaded";\n }\n get is_unloading() {\n return this.state === "unloading";\n }\n get is_unloaded() {\n return this.state === "unloaded";\n }\n};\n\n// ../jsbrains/smart-embed-model/adapters/_adapter.js\nvar SmartEmbedAdapter = class extends SmartModelAdapter {\n static {\n __name(this, "SmartEmbedAdapter");\n }\n /**\n * @override in sub-class with adapter-specific default configurations\n * @property {string} id - The adapter identifier\n * @property {string} description - Human-readable description\n * @property {string} type - Adapter type ("API")\n * @property {string} endpoint - API endpoint\n * @property {string} adapter - Adapter identifier\n * @property {string} default_model - Default model to use\n */\n static defaults = {};\n /**\n * Count tokens in input text\n * @abstract\n * @param {string} input - Text to tokenize\n * @returns {Promise} Token count result\n * @property {number} tokens - Number of tokens in input\n * @throws {Error} If not implemented by subclass\n */\n async count_tokens(input) {\n throw new Error("count_tokens method not implemented");\n }\n /**\n * Generate embeddings for single input\n * @abstract\n * @param {string|Object} input - Text to embed\n * @returns {Promise} Embedding result\n * @property {number[]} vec - Embedding vector\n * @property {number} tokens - Number of tokens in input\n * @throws {Error} If not implemented by subclass\n */\n async embed(input) {\n if (typeof input === "string") input = { embed_input: input };\n return (await this.embed_batch([input]))[0];\n }\n /**\n * Generate embeddings for multiple inputs\n * @abstract\n * @param {Array} inputs - Texts to embed\n * @returns {Promise>} Array of embedding results\n * @property {number[]} vec - Embedding vector for each input\n * @property {number} tokens - Number of tokens in each input\n * @throws {Error} If not implemented by subclass\n */\n async embed_batch(inputs) {\n throw new Error("embed_batch method not implemented");\n }\n get settings_config() {\n return {\n "[ADAPTER].model_key": {\n name: "Embedding model",\n type: "dropdown",\n description: "Select an embedding model.",\n options_callback: "adapter.get_models_as_options",\n callback: "model_changed",\n default: this.constructor.defaults.default_model\n }\n };\n }\n get dims() {\n return this.model.data.dims;\n }\n get max_tokens() {\n return this.model.data.max_tokens;\n }\n get batch_size() {\n return this.model.data.batch_size || 1;\n }\n};\n\n// ../obsidian-smart-env/src/adapters/embedding-model/transformers_v4.iframe.js\nvar transformers_defaults = {\n adapter: "transformers",\n description: "Transformers (Local, built-in)",\n default_model: "TaylorAI/bge-micro-v2",\n models: transformers_models\n};\nvar DEVICE_CONFIGS = Object.freeze({\n webgpu: Object.freeze({\n device: "webgpu",\n preferred_dtypes: ["fp32", "fp16", "q8", "q4"]\n }),\n cpu: Object.freeze({\n preferred_dtypes: ["q8", "q4", "fp32", "fp16"]\n })\n});\nfunction build_device_configs(available_dtypes = [], params = {}) {\n const {\n use_gpu = false\n } = params;\n const normalized_available_dtypes = new Set(\n Array.isArray(available_dtypes) ? available_dtypes : []\n );\n const configs = [];\n const push_scope_configs = /* @__PURE__ */ __name((scope_key) => {\n const scope_config = DEVICE_CONFIGS[scope_key];\n if (!scope_config) return;\n scope_config.preferred_dtypes.forEach((dtype) => {\n if (normalized_available_dtypes.size && !normalized_available_dtypes.has(dtype)) return;\n configs.push({\n config_key: `${scope_key}_${dtype}`,\n ...scope_config.device ? { device: scope_config.device } : {},\n dtype\n });\n });\n }, "push_scope_configs");\n if (use_gpu) {\n push_scope_configs("webgpu");\n }\n push_scope_configs("cpu");\n if (!configs.length) {\n if (use_gpu) {\n configs.push({\n config_key: "webgpu_auto",\n device: "webgpu"\n });\n }\n configs.push({\n config_key: "cpu_auto"\n });\n return configs;\n }\n if (!configs.some(({ config_key }) => config_key === "cpu_auto")) {\n configs.push({\n config_key: "cpu_auto"\n });\n }\n return configs;\n}\n__name(build_device_configs, "build_device_configs");\nvar retryable_webgpu_error_code = "WEBGPU_RETRYABLE_ERROR";\nvar retryable_webgpu_error_patterns = [\n /no available backend found/i,\n /webgpuinit is not a function/i,\n /subgroupminsize/i\n];\nfunction get_error_message(error) {\n return error?.message || String(error || "");\n}\n__name(get_error_message, "get_error_message");\nfunction is_retryable_webgpu_error(error) {\n const error_message = get_error_message(error);\n return retryable_webgpu_error_patterns.some((pattern) => pattern.test(error_message));\n}\n__name(is_retryable_webgpu_error, "is_retryable_webgpu_error");\nfunction create_retryable_webgpu_error(error) {\n const error_message = get_error_message(error);\n if (error_message.includes(retryable_webgpu_error_code)) {\n return error instanceof Error ? error : new Error(error_message);\n }\n const wrapped_error = new Error(`${retryable_webgpu_error_code}: ${error_message}`);\n try {\n wrapped_error.cause = error;\n } catch (_error) {\n }\n return wrapped_error;\n}\n__name(create_retryable_webgpu_error, "create_retryable_webgpu_error");\nfunction should_bubble_webgpu_error(active_config_key, error) {\n return String(active_config_key || "").includes("webgpu") && is_retryable_webgpu_error(error);\n}\n__name(should_bubble_webgpu_error, "should_bubble_webgpu_error");\nvar is_webgpu_available = /* @__PURE__ */ __name(async () => {\n if (!("gpu" in navigator)) return false;\n const adapter = await navigator.gpu.requestAdapter();\n if (!adapter) return false;\n return true;\n}, "is_webgpu_available");\nvar SmartEmbedTransformersAdapter = class extends SmartEmbedAdapter {\n static {\n __name(this, "SmartEmbedTransformersAdapter");\n }\n static defaults = transformers_defaults;\n /**\n * @param {import("../smart_embed_model.js").SmartEmbedModel} model\n */\n constructor(model2) {\n super(model2);\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.has_gpu = false;\n }\n /**\n * Load the underlying transformers pipeline with WebGPU \u2192 WASM fallback.\n * @returns {Promise}\n */\n async load() {\n this.has_gpu = await is_webgpu_available();\n try {\n if (this.loading) {\n console.warn("[Transformers v4] load already in progress, waiting...");\n while (this.loading) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n } else {\n this.loading = true;\n if (this.pipeline) {\n this.loaded = true;\n this.loading = false;\n return;\n }\n await this.load_transformers_with_fallback();\n this.loading = false;\n this.loaded = true;\n console.log(`[Transformers v4] model loaded using ${this.active_config_key}`, this);\n }\n } catch (e) {\n this.loading = false;\n this.loaded = false;\n console.error("[Transformers v4] load failed", e);\n throw e;\n }\n }\n /**\n * Unload the pipeline and free resources.\n * @returns {Promise}\n */\n async unload() {\n try {\n if (this.pipeline) {\n if (typeof this.pipeline.destroy === "function") {\n this.pipeline.destroy();\n } else if (typeof this.pipeline.dispose === "function") {\n this.pipeline.dispose();\n }\n }\n } catch (err) {\n console.warn("[Transformers v4] error while disposing pipeline", err);\n }\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.loaded = false;\n }\n /**\n * Available models \u2013 reuses the v1 transformers model catalog.\n * @returns {Object}\n */\n get models() {\n return transformers_models;\n }\n /**\n * Maximum tokens per input.\n * @returns {number}\n */\n get max_tokens() {\n return this.model.data.max_tokens || 512;\n }\n /**\n * Effective batch size.\n * Prefers small deterministic batches when not explicitly configured.\n * @returns {number}\n */\n get batch_size() {\n const configured = this.model.data.batch_size;\n if (configured && configured > 0) return configured;\n return this.gpu_enabled ? 16 : 8;\n }\n get gpu_enabled() {\n if (this.has_gpu) {\n const explicit = typeof this.model.data.use_gpu === "boolean" ? this.model.data.use_gpu : null;\n if (explicit === false) return false;\n return true;\n } else {\n return false;\n }\n }\n /**\n * Initialize transformers pipeline with WebGPU \u2192 WASM fallback.\n * @private\n * @returns {Promise}\n */\n async load_transformers_with_fallback() {\n const { pipeline, env, AutoTokenizer, ModelRegistry, LogLevel } = await import("https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.1.0");\n env.logLevel = LogLevel.ERROR;\n let available_dtypes = [];\n try {\n available_dtypes = await ModelRegistry.get_available_dtypes(this.model_key);\n console.log({ available_dtypes });\n } catch (error) {\n console.warn("[Transformers v4] failed to probe available dtypes, falling back to runtime defaults", error);\n }\n env.allowLocalModels = false;\n if (typeof env.useBrowserCache !== "undefined") {\n env.useBrowserCache = true;\n }\n let last_error = null;\n const config_list = build_device_configs(available_dtypes, {\n use_gpu: this.gpu_enabled\n });\n const try_create = /* @__PURE__ */ __name(async (device_config) => {\n const pipeline_params = {};\n if (device_config.device) {\n pipeline_params.device = device_config.device;\n }\n if (device_config.dtype) {\n pipeline_params.dtype = device_config.dtype;\n }\n const pipe = await pipeline("feature-extraction", this.model_key, pipeline_params);\n return pipe;\n }, "try_create");\n for (const device_config of config_list) {\n const config_key = device_config.config_key;\n if (this.pipeline) break;\n try {\n console.log(`[Transformers v4] trying to load pipeline on ${config_key}`);\n this.pipeline = await try_create(device_config);\n this.active_config_key = config_key;\n break;\n } catch (err) {\n console.warn(`[Transformers v4: ${config_key}] failed to load pipeline on ${config_key}`, err);\n if (device_config.device === "webgpu" && is_retryable_webgpu_error(err)) {\n throw create_retryable_webgpu_error(err);\n }\n last_error = err;\n }\n }\n if (this.pipeline) {\n console.log(`[Transformers v4: ${this.active_config_key}] pipeline initialized using ${this.active_config_key}`);\n } else {\n throw last_error || new Error("Failed to initialize transformers pipeline");\n }\n this.tokenizer = await AutoTokenizer.from_pretrained(this.model_key);\n }\n /**\n * Count tokens in input text.\n * @param {string} input\n * @returns {Promise<{tokens:number}>}\n */\n async count_tokens(input) {\n if (!this.tokenizer) {\n await this.load();\n }\n const { input_ids } = await this.tokenizer(input);\n return { tokens: input_ids.data.length };\n }\n /**\n * Generate embeddings for multiple inputs.\n * @param {Array} inputs\n * @returns {Promise>}\n */\n async embed_batch(inputs) {\n if (!this.pipeline) {\n await this.load();\n }\n const filtered_inputs = inputs.filter((item) => item.embed_input && item.embed_input.length > 0);\n if (!filtered_inputs.length) return [];\n const results = [];\n for (let i = 0; i < filtered_inputs.length; i += this.batch_size) {\n const batch = filtered_inputs.slice(i, i + this.batch_size);\n const batch_results = await this._process_batch(batch);\n results.push(...batch_results);\n }\n return results;\n }\n /**\n * Process a single batch \u2013 with per-item retry on failure.\n * @private\n * @param {Array} batch_inputs\n * @returns {Promise>}\n */\n async _process_batch(batch_inputs) {\n const prepared = await Promise.all(\n batch_inputs.map((item) => this._prepare_input(item.embed_input))\n );\n const embed_inputs = prepared.map((p) => p.text);\n const tokens = prepared.map((p) => p.tokens);\n try {\n const resp = await this.pipeline(embed_inputs, { pooling: "mean", normalize: true });\n return batch_inputs.map((item, i) => {\n const vec = Array.from(resp[i].data).map((val) => Math.round(val * 1e8) / 1e8);\n item.vec = vec;\n item.tokens = tokens[i];\n return item;\n });\n } catch (err) {\n if (should_bubble_webgpu_error(this.active_config_key, err)) {\n throw create_retryable_webgpu_error(err);\n }\n console.error("[Transformers v4] batch embed failed \\u2013 retrying items individually", err);\n return await this._retry_items_individually(batch_inputs);\n }\n }\n /**\n * Prepare a single input by truncating to max_tokens if necessary.\n * @private\n * @param {string} embed_input\n * @returns {Promise<{text:string,tokens:number}>}\n */\n async _prepare_input(embed_input) {\n let { tokens } = await this.count_tokens(embed_input);\n if (tokens <= this.max_tokens) {\n return { text: embed_input, tokens };\n }\n let truncated = embed_input;\n while (tokens > this.max_tokens && truncated.length > 0) {\n const pct = this.max_tokens / tokens;\n const max_chars = Math.floor(truncated.length * pct * 0.9);\n truncated = truncated.slice(0, max_chars);\n const last_space = truncated.lastIndexOf(" ");\n if (last_space > 0) {\n truncated = truncated.slice(0, last_space);\n }\n tokens = (await this.count_tokens(truncated)).tokens;\n }\n return { text: truncated, tokens };\n }\n /**\n * Retry each item individually after a batch failure.\n * @private\n * @param {Array} batch_inputs\n * @returns {Promise>}\n */\n async _retry_items_individually(batch_inputs) {\n await this._reset_pipeline_after_error();\n const results = [];\n for (const item of batch_inputs) {\n try {\n const prepared = await this._prepare_input(item.embed_input);\n const resp = await this.pipeline(prepared.text, { pooling: "mean", normalize: true });\n const vec = Array.from(resp[0].data).map((val) => Math.round(val * 1e8) / 1e8);\n results.push({\n ...item,\n vec,\n tokens: prepared.tokens\n });\n } catch (single_err) {\n if (should_bubble_webgpu_error(this.active_config_key, single_err)) {\n throw create_retryable_webgpu_error(single_err);\n }\n console.error("[Transformers v4] single item embed failed \\u2013 skipping", single_err);\n results.push({\n ...item,\n vec: [],\n tokens: 0,\n error: single_err.message\n });\n }\n }\n return results;\n }\n /**\n * Reset pipeline after a failure \u2013 falling back to WASM if needed.\n * @private\n * @returns {Promise}\n */\n async _reset_pipeline_after_error() {\n try {\n if (this.pipeline) {\n if (typeof this.pipeline.destroy === "function") {\n this.pipeline.destroy();\n } else if (typeof this.pipeline.dispose === "function") {\n this.pipeline.dispose();\n }\n }\n } catch (err) {\n console.warn("[Transformers v4] error while resetting pipeline", err);\n }\n this.pipeline = null;\n await this.load_transformers_with_fallback();\n }\n /**\n * V2 intentionally exposes only model selection in the settings UI.\n * @returns {Object}\n */\n get settings_config() {\n return super.settings_config;\n }\n};\nvar transformers_models = {\n "TaylorAI/bge-micro-v2": {\n "id": "TaylorAI/bge-micro-v2",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "BGE-micro-v2",\n "description": "Local, 512 tokens, 384 dim (recommended)",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-xs": {\n "id": "Snowflake/snowflake-arctic-embed-xs",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed XS",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-s": {\n "id": "Snowflake/snowflake-arctic-embed-s",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed Small",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-m": {\n "id": "Snowflake/snowflake-arctic-embed-m",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed Medium",\n "description": "Local, 512 tokens, 768 dim",\n "adapter": "transformers"\n },\n "TaylorAI/gte-tiny": {\n "id": "TaylorAI/gte-tiny",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "GTE-tiny",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "onnx-community/embeddinggemma-300m-ONNX": {\n "id": "onnx-community/embeddinggemma-300m-ONNX",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "EmbeddingGemma-300M",\n "description": "Local, 2,048 tokens, 768 dim",\n "adapter": "transformers"\n },\n "Mihaiii/Ivysaur": {\n "id": "Mihaiii/Ivysaur",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Ivysaur",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "andersonbcdefg/bge-small-4096": {\n "id": "andersonbcdefg/bge-small-4096",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 4096,\n "name": "BGE-small-4K",\n "description": "Local, 4,096 tokens, 384 dim",\n "adapter": "transformers"\n },\n // Too slow and persistent crashes\n // "jinaai/jina-embeddings-v2-base-de": {\n // "id": "jinaai/jina-embeddings-v2-base-de",\n // "batch_size": 1,\n // "dims": 768,\n // "max_tokens": 4096,\n // "name": "jina-embeddings-v2-base-de",\n // "description": "Local, 4,096 tokens, 768 dim, German",\n // "adapter": "transformers"\n // },\n "Xenova/jina-embeddings-v2-base-zh": {\n "id": "Xenova/jina-embeddings-v2-base-zh",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 8192,\n "name": "Jina-v2-base-zh-8K",\n "description": "Local, 8,192 tokens, 768 dim, Chinese/English bilingual",\n "adapter": "transformers"\n },\n "Xenova/jina-embeddings-v2-small-en": {\n "id": "Xenova/jina-embeddings-v2-small-en",\n "batch_size": 1,\n "dims": 512,\n "max_tokens": 8192,\n "name": "Jina-v2-small-en",\n "description": "Local, 8,192 tokens, 512 dim",\n "adapter": "transformers"\n },\n "nomic-ai/nomic-embed-text-v1.5": {\n "id": "nomic-ai/nomic-embed-text-v1.5",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "Nomic-embed-text-v1.5",\n "description": "Local, 8,192 tokens, 768 dim",\n "adapter": "transformers"\n },\n "Xenova/bge-small-en-v1.5": {\n "id": "Xenova/bge-small-en-v1.5",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "BGE-small",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "nomic-ai/nomic-embed-text-v1": {\n "id": "nomic-ai/nomic-embed-text-v1",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "Nomic-embed-text",\n "description": "Local, 2,048 tokens, 768 dim",\n "adapter": "transformers"\n }\n};\nvar transformers_settings_config = {\n // "[ADAPTER].legacy_transformers": {\n // name: \'Legacy transformers (no GPU)\',\n // type: "toggle",\n // description: "Use legacy transformers (v2) instead of v3. This may resolve issues if the local embedding isn\'t working.",\n // callback: \'embed_model_changed\',\n // default: true,\n // },\n};\nvar settings_config = {\n // "legacy_transformers": {\n // name: \'Legacy transformers (no GPU)\',\n // type: "toggle",\n // description: "Use legacy transformers (v2) instead of v3. This may resolve issues if the local embedding isn\'t working.",\n // // callback: \'embed_model_changed\',\n // // default: false,\n // },\n};\nvar transformers_v4_iframe_default = {\n class: SmartEmbedTransformersAdapter,\n settings_config\n};\nvar model = null;\nasync function process_message(data) {\n const { method, params, id, iframe_id } = data;\n try {\n let result;\n switch (method) {\n case "init":\n console.log("init");\n break;\n case "load":\n const model_params = { data: params, ...params };\n console.log("load", { model_params });\n model = new SmartEmbedTransformersAdapter(model_params);\n await model.load();\n result = { model_loaded: true, model_config_key: model.active_config_key };\n break;\n case "embed_batch":\n if (!model) throw new Error("Model not loaded");\n result = await model.embed_batch(params.inputs);\n break;\n case "count_tokens":\n if (!model) throw new Error("Model not loaded");\n result = await model.count_tokens(params.input);\n break;\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n return { id, result, iframe_id };\n } catch (error) {\n console.error("Error processing message:", error);\n return { id, error: get_error_message(error), iframe_id };\n }\n}\n__name(process_message, "process_message");\nprocess_message({ method: "init" });\nexport {\n DEVICE_CONFIGS,\n SmartEmbedTransformersAdapter,\n transformers_v4_iframe_default as default,\n settings_config,\n transformers_defaults,\n transformers_models,\n transformers_settings_config\n};\n'; + +// node_modules/obsidian-smart-env/src/adapters/embedding-model/transformers_v4_iframe.js +var retryable_webgpu_error_patterns = [ + /\bWEBGPU_RETRYABLE_ERROR\b/i, + /no available backend found/i, + /webgpuinit is not a function/i, + /subgroupminsize/i +]; +var iframe_timeout_error_code = "IFRAME_REPLY_TIMEOUT"; +var iframe_reply_timeout_ms = Object.freeze({ + load: 12e4, + default: 3e4 +}); +function is_retryable_webgpu_error(error_message = "") { + const normalized_error_message = String(error_message || ""); + return retryable_webgpu_error_patterns.some((pattern) => pattern.test(normalized_error_message)); +} +function is_iframe_timeout_error(error_message = "") { + return new RegExp(`\\b${iframe_timeout_error_code}\\b`, "i").test(String(error_message || "")); +} +function create_iframe_timeout_error(method = "") { + const normalized_method = String(method || "unknown"); + return new Error(`${iframe_timeout_error_code}: No reply from transformers iframe for "${normalized_method}"`); +} +function to_error(error) { + return error instanceof Error ? error : new Error(String(error || "Unknown error")); +} +var TransformersIframeEmbeddingModelAdapter = class extends SmartEmbedTransformersIframeAdapter { + constructor(model) { + super(model); + const old_connector = this.connector; + this._old_connector = old_connector; + this.connector = transformers_v4_iframe_default; + this._disable_webgpu = false; + this._reload_in_v4_promise = null; + this._reload_without_webgpu_promise = null; + this._reload_with_v3_promise = null; + this._using_v3_connector = false; + console.log("transformers iframe connector", this.model); + } + get use_gpu() { + if (this._disable_webgpu) return false; + if (typeof this.model?.data?.use_gpu === "boolean") { + return this.model.data.use_gpu; + } + return void 0; + } + get models() { + return { + "TaylorAI/bge-micro-v2": { + "id": "TaylorAI/bge-micro-v2", + "batch_size": 1, + "dims": 384, + "max_tokens": 512, + "name": "BGE-micro-v2 (fastest)", + "description": "Local, 512 tokens, 384 dim (recommended)", + "adapter": "transformers" + }, + "Snowflake/snowflake-arctic-embed-xs": { + "id": "Snowflake/snowflake-arctic-embed-xs", + "batch_size": 1, + "dims": 384, + "max_tokens": 512, + "name": "Snowflake Arctic Embed XS (fast)", + "description": "Local, 512 tokens, 384 dim", + "adapter": "transformers" + }, + "Xenova/multilingual-e5-small": { + "id": "Xenova/multilingual-e5-small", + "batch_size": 1, + "dims": 384, + "max_tokens": 512, + "name": "Multilingual E5 Small", + "description": "Local, 512 tokens, 384 dim", + "adapter": "transformers" + } + }; + } + get_message_timeout_ms(method = "") { + return iframe_reply_timeout_ms[method] || iframe_reply_timeout_ms.default; + } + clear_message_timeout(queue_entry = null) { + if (!queue_entry?.timeout_id) return; + clearTimeout(queue_entry.timeout_id); + queue_entry.timeout_id = null; + } + /** + * Store method/params so a recoverable iframe error can be retried transparently. + * @protected + * @param {string} method + * @param {Object} params + * @returns {Promise} + */ + async _send_message(method, params, message_options = {}) { + return new Promise((resolve, reject) => { + const id = `${this.message_prefix}${this.message_id++}`; + const queue_entry = { + resolve, + reject, + method, + params, + retried_in_v4: Boolean(message_options.retried_in_v4), + retried_without_webgpu: Boolean(message_options.retried_without_webgpu), + fell_back_to_v3: Boolean(message_options.fell_back_to_v3), + timeout_id: null + }; + queue_entry.timeout_id = setTimeout(() => { + if (!this.message_queue[id]) return; + const timeout_error = create_iframe_timeout_error(method); + this._handle_message_result(id, null, timeout_error.message); + }, this.get_message_timeout_ms(method)); + this.message_queue[id] = queue_entry; + try { + this._post_message({ method, params, id }); + } catch (error) { + this.clear_message_timeout(queue_entry); + delete this.message_queue[id]; + reject(to_error(error)); + } + }); + } + should_retry_without_webgpu(error, queue_entry = {}) { + if (this.use_gpu === false) return false; + if (queue_entry.retried_without_webgpu) return false; + return is_retryable_webgpu_error(error) || is_iframe_timeout_error(error); + } + should_retry_in_v4(error, queue_entry = {}) { + if (queue_entry.method === "load") return false; + if (queue_entry.retried_in_v4) return false; + if (queue_entry.retried_without_webgpu) return false; + if (this._using_v3_connector) return false; + if (is_retryable_webgpu_error(error)) return false; + if (is_iframe_timeout_error(error)) return false; + return true; + } + can_fallback_to_v3(queue_entry = {}) { + return Boolean(this._old_connector) && !this._using_v3_connector && !queue_entry.fell_back_to_v3; + } + should_fallback_to_v3(error, queue_entry = {}) { + if (!this.can_fallback_to_v3(queue_entry)) return false; + return Boolean( + queue_entry.method === "load" || queue_entry.retried_in_v4 || queue_entry.retried_without_webgpu || this._disable_webgpu || is_iframe_timeout_error(error) + ); + } + async reload_iframe_in_v4() { + if (this._reload_in_v4_promise) { + return this._reload_in_v4_promise; + } + this.state = "loading"; + this.model.model_loaded = false; + this.model.load_result = null; + this._reload_in_v4_promise = this.load().finally(() => { + this._reload_in_v4_promise = null; + }); + return this._reload_in_v4_promise; + } + async reload_iframe_without_webgpu() { + if (this._reload_without_webgpu_promise) { + return this._reload_without_webgpu_promise; + } + this._disable_webgpu = true; + this.state = "loading"; + this.model.model_loaded = false; + this.model.load_result = null; + this._reload_without_webgpu_promise = this.load().finally(() => { + this._reload_without_webgpu_promise = null; + }); + return this._reload_without_webgpu_promise; + } + async reload_iframe_with_v3() { + if (this._reload_with_v3_promise) { + return this._reload_with_v3_promise; + } + if (!this._old_connector) { + throw new Error("Missing transformers v3 iframe connector"); + } + this._using_v3_connector = true; + this.connector = this._old_connector; + this.state = "loading"; + this.model.model_loaded = false; + this.model.load_result = null; + this._reload_with_v3_promise = this.load().finally(() => { + this._reload_with_v3_promise = null; + }); + return this._reload_with_v3_promise; + } + async retry_message_in_v4(queue_entry) { + await this.reload_iframe_in_v4(); + return await this._send_message(queue_entry.method, queue_entry.params, { + retried_in_v4: true, + retried_without_webgpu: queue_entry.retried_without_webgpu, + fell_back_to_v3: queue_entry.fell_back_to_v3 + }); + } + async retry_message_without_webgpu(queue_entry) { + await this.reload_iframe_without_webgpu(); + if (queue_entry.method === "load") { + return this.model.load_result || { model_loaded: true, webgpu_disabled: true }; + } + return await this._send_message(queue_entry.method, queue_entry.params, { + retried_in_v4: queue_entry.retried_in_v4, + retried_without_webgpu: true, + fell_back_to_v3: queue_entry.fell_back_to_v3 + }); + } + async fallback_to_v3_and_retry(queue_entry) { + queue_entry.fell_back_to_v3 = true; + await this.reload_iframe_with_v3(); + if (queue_entry.method === "load") { + return this.model.load_result || { model_loaded: true, v3_fallback: true }; + } + return await this._send_message(queue_entry.method, queue_entry.params, { + retried_in_v4: queue_entry.retried_in_v4, + retried_without_webgpu: queue_entry.retried_without_webgpu, + fell_back_to_v3: true + }); + } + /** + * Handle response message from worker/iframe + * ADDS WEBGPU-SPECIFIC RETRY BEFORE FALLBACK TO OLD CONNECTOR (v3.8.0) + * @protected + * @param {string} id - Message ID + * @param {*} result - Response result + * @param {Error} [error] - Response error + */ + _handle_message_result(id, result, error) { + if (!id.startsWith(this.message_prefix)) return; + if (result?.model_loaded) { + console.log("model loaded"); + this.state = "loaded"; + this.model.model_loaded = true; + this.model.load_result = result; + } + const queue_entry = this.message_queue[id]; + if (!queue_entry) return; + this.clear_message_timeout(queue_entry); + if (error) { + if (this.should_retry_without_webgpu(error, queue_entry)) { + queue_entry.retried_without_webgpu = true; + delete this.message_queue[id]; + console.warn("Retrying transformers v4 iframe without WebGPU due to recoverable error:", error); + this.retry_message_without_webgpu(queue_entry).then((retry_result) => { + queue_entry.resolve(retry_result); + }).catch((retry_error) => { + if (this.should_fallback_to_v3(retry_error, queue_entry)) { + console.warn("Falling back to transformers v3 iframe connector after v4 CPU retry failed:", retry_error); + this.fallback_to_v3_and_retry(queue_entry).then((fallback_result) => { + queue_entry.resolve(fallback_result); + }).catch((fallback_error) => { + queue_entry.reject(to_error(fallback_error)); + }); + return; + } + queue_entry.reject(to_error(retry_error)); + }); + return; + } + if (this.should_retry_in_v4(error, queue_entry)) { + queue_entry.retried_in_v4 = true; + delete this.message_queue[id]; + console.warn("Retrying transformers v4 iframe after hard non-load error:", error); + this.retry_message_in_v4(queue_entry).then((retry_result) => { + queue_entry.resolve(retry_result); + }).catch((retry_error) => { + if (this.should_fallback_to_v3(retry_error, queue_entry)) { + console.warn("Falling back to transformers v3 iframe connector after bounded v4 retry failed:", retry_error); + this.fallback_to_v3_and_retry(queue_entry).then((fallback_result) => { + queue_entry.resolve(fallback_result); + }).catch((fallback_error) => { + queue_entry.reject(to_error(fallback_error)); + }); + return; + } + queue_entry.reject(to_error(retry_error)); + }); + return; + } + if (this.should_fallback_to_v3(error, queue_entry)) { + delete this.message_queue[id]; + console.warn("Falling back to transformers v3 iframe connector due to error:", error); + this.fallback_to_v3_and_retry(queue_entry).then((fallback_result) => { + queue_entry.resolve(fallback_result); + }).catch((fallback_error) => { + queue_entry.reject(to_error(fallback_error)); + }); + return; + } + queue_entry.reject(to_error(error)); + delete this.message_queue[id]; + return; + } + queue_entry.resolve(result); + delete this.message_queue[id]; + } +}; +var transformers_v4_iframe_default2 = { + class: TransformersIframeEmbeddingModelAdapter, + settings_config: settings_config3 +}; + +// node_modules/obsidian-smart-env/src/collections/embedding_models.js +embedding_models_default.providers = { + transformers: transformers_v4_iframe_default2 +}; +var embedding_models_default2 = embedding_models_default; + +// node_modules/obsidian-smart-env/src/items/lookup_list.js +var LookupList = class extends CollectionItem { + static key = "lookup_list"; + static get defaults() { + return { data: {} }; + } + async pre_process(params) { + if (typeof this.actions.lookup_list_pre_process === "function") { + await this.actions.lookup_list_pre_process(params); + } + } + async get_results(params = {}) { + await this.pre_process(params); + if (this.env.log_perf) this.start_ms = Date.now(); + let results = this.filter_and_score(params); + if (this.env.log_perf) { + this.end_ms = Date.now(); + console.log(`filter_and_score(${params.score_algo_key}) took ${this.end_ms - this.start_ms} ms (Date.now)`); + } + if (this.should_post_process) results = await this.post_process(results, params); + this.emit_event("lookup:get_results"); + return results; + } + filter_and_score(params = {}) { + const collection = this.env[params.results_collection_key] || this.env[this.collection.results_collection_key]; + const score_errors = []; + const { results: raw_results } = Object.values(collection.items).reduce((acc, target) => { + const scored = target.filter_and_score(params); + if (!scored?.score) { + if (scored?.error) score_errors.push(scored.error); + return acc; + } + results_acc(acc, scored, params.limit || 20); + return acc; + }, { min: 0, results: /* @__PURE__ */ new Set() }); + const results = Array.from(raw_results).sort(sort_by_score_descending); + if (!results.length) return results; + while (!results.some((r) => r.score > 0.5)) { + results.forEach((r) => r.score *= 2); + } + return results; + } + async post_process(results, params = {}) { + return results; + } + get should_post_process() { + return this.settings.lookup_post_process && this.settings.lookup_post_process !== "none"; + } + // for compatibility with v3 connections list item + get item() { + return this; + } +}; + +// node_modules/obsidian-smart-env/src/collections/lookup_lists.js +var settings_config6 = { + results_collection_key: { + name: "Lookup results type", + type: "dropdown", + description: "Choose whether results should be sources or blocks.", + option_1: "smart_sources|Sources", + option_2: "smart_blocks|Blocks", + options_callback: (scope) => { + const options = [ + { value: "smart_sources", name: "Sources" } + ]; + if (scope.env.smart_blocks) { + options.push({ value: "smart_blocks", name: "Blocks" }); + } + return options; + } + } +}; +var LookupLists = class extends Collection { + static get default_settings() { + return { + results_collection_key: "smart_blocks", + score_algo_key: "similarity", + results_limit: 20 + }; + } + static version = 0.01; + new_item({ query, filter }) { + if (!query || typeof query !== "string" || !query.trim()) { + throw new Error("LookupLists.new_item requires a non-empty query string."); + } + const date = format_ymd(/* @__PURE__ */ new Date()); + const hash = murmur_hash_32_alphanumeric(query); + const key = `${date}+${hash}`; + if (this.items[key]) return this.items[key]; + const list = new this.item_type(this.env, { + key, + query, + filter + }); + this.set(list); + return list; + } + get settings_config() { + return { ...settings_config6 }; + } + process_load_queue() { + } + get results_collection_key() { + const stored_key = this.settings?.results_collection_key; + if (this.env.collections?.[stored_key]) return stored_key; + return "smart_sources"; + } +}; +function format_ymd(d) { + const y = d.getFullYear(); + const m = String(d.getMonth() + 1).padStart(2, "0"); + const day = String(d.getDate()).padStart(2, "0"); + return `${y}-${m}-${day}`; +} +var lookup_lists_default = { + class: LookupLists, + collection_key: "lookup_lists", + item_type: LookupList, + settings_config: settings_config6 +}; + +// node_modules/obsidian-smart-env/node_modules/smart-blocks/utils/get_block_display_name.js +function get_block_display_name(key, show_full_path) { + const [source_key, ...path_parts] = key.split("#").filter(Boolean); + const source_name = get_item_display_name(source_key, show_full_path); + if (show_full_path) return [source_name, ...path_parts].join(" > "); + const last_heading = path_parts.findLast((part) => part && part[0] !== "{"); + return [source_name, last_heading].join(" > "); +} + +// node_modules/obsidian-smart-env/node_modules/smart-blocks/smart_block.js +var SmartBlock = class extends SmartEntity { + /** + * Provides default values for a SmartBlock instance. + * @static + * @readonly + * @returns {Object} The default values. + */ + static get defaults() { + return { + data: { + text: null, + length: 0, + last_read: { + hash: null, + at: 0 + } + }, + _embed_input: "" + // Stored temporarily + }; + } + get block_adapter() { + if (!this._block_adapter) { + this._block_adapter = new this.collection.opts.block_adapters.md(this); + } + return this._block_adapter; + } + /** + * Initializes the SmartBlock instance by queuing an embed if embedding is enabled. + * @returns {void} + */ + init() { + if (this.settings.embed_blocks) super.init(); + } + /** + * Queues the entity for embedding. + * @returns {void} + */ + queue_embed() { + this._queue_embed = this.should_embed; + } + /** + * Queues the block for import via the source. + * @returns {void} + */ + queue_import() { + this.source?.queue_import(); + } + /** + * Prepares the embed input for the SmartBlock by reading content and generating a hash. + * @async + * @returns {Promise} The embed input string or `false` if already embedded. + */ + async get_embed_input(content = null) { + if (typeof this._embed_input !== "string" || !this._embed_input.length) { + if (!content) content = await this.read(); + this._embed_input = this.breadcrumbs + "\n" + content; + } + return this._embed_input; + } + // CRUD + /** + * @method read + * @description Reads the block content by delegating to the block adapter. + * @async + * @returns {Promise} The block content. + */ + async read() { + try { + return await this.block_adapter.read(); + } catch (e) { + if (e.message.includes("BLOCK NOT FOUND")) { + return 'BLOCK NOT FOUND (run "Prune" to remove)'; + } else { + throw e; + } + } + } + /** + * Filters block using base key filters and optional source frontmatter include/exclude filters. + * @param {Object} [filter_opts={}] + * @param {Object} [filter_opts.frontmatter] + * @returns {boolean} + */ + filter(filter_opts = {}) { + if (!super.filter(filter_opts)) return false; + if (!filter_opts.frontmatter) return true; + return filter_by_frontmatter(this.source?.metadata || {}, filter_opts.frontmatter); + } + /** + * @method append + * @description Appends content to this block by delegating to the block adapter. + * @async + * @param {string} content + * @returns {Promise} + */ + async append(content) { + await this.block_adapter.append(content); + this.queue_save(); + } + /** + * @method update + * @description Updates the block content by delegating to the block adapter. + * @async + * @param {string} new_block_content + * @param {Object} [opts={}] + * @returns {Promise} + */ + async update(new_block_content, opts = {}) { + await this.block_adapter.update(new_block_content, opts); + this.queue_save(); + } + /** + * @method remove + * @description Removes the block by delegating to the block adapter. + * @async + * @returns {Promise} + */ + async remove() { + await this.block_adapter.remove(); + this.queue_save(); + } + /** + * @method move_to + * @description Moves the block to another location by delegating to the block adapter. + * @async + * @param {string} to_key + * @returns {Promise} + */ + async move_to(to_key) { + await this.block_adapter.move_to(to_key); + this.queue_save(); + } + get_display_name(params = {}) { + return this.block_adapter?.get_display_name(params); + } + // Getters + /** + * Retrieves the breadcrumbs representing the block's path within the source. + * @readonly + * @returns {string} The breadcrumbs string. + */ + get breadcrumbs() { + return this.key.split("/").join(" > ").split("#").slice(0, -1).join(" > ").replace(".md", ""); + } + /** + * Determines if the block is excluded from embedding based on headings. + * @readonly + * @returns {boolean} `true` if excluded, `false` otherwise. + */ + get excluded() { + const block_headings = this.path.split("#").slice(1); + if (this.source_collection.excluded_headings.some((heading) => block_headings.includes(heading))) return true; + return this.source?.excluded; + } + /** + * Retrieves the file path of the SmartSource associated with the block. + * @readonly + * @returns {string} The file path. + */ + get file_path() { + return this.source?.file_path; + } + /** + * Retrieves the file type of the SmartSource associated with the block. + * @readonly + * @returns {string} The file type. + */ + get file_type() { + return this.source.file_type; + } + /** + * Retrieves the folder path of the block. + * @readonly + * @returns {string} The folder path. + */ + get folder() { + return this.path.split("/").slice(0, -1).join("/"); + } + /** + * Retrieves the embed link for the block. + * @readonly + * @returns {string} The embed link. + */ + get embed_link() { + return `![[${this.link}]]`; + } + /** + * Determines if the block has valid line range information. + * @readonly + * @returns {boolean} `true` if the block has both start and end lines, `false` otherwise. + */ + get has_lines() { + return this.lines && this.lines.length === 2; + } + /** + * Determines if the entity is a block based on its key. + * @readonly + * @returns {boolean} `true` if it's a block, `false` otherwise. + */ + get is_block() { + return this.key.includes("#"); + } + /** + * Determines if the block is gone (i.e., the source file or block data no longer exists). + * @readonly + * @returns {boolean} `true` if gone, `false` otherwise. + */ + get is_gone() { + if (!this.source?.file) return true; + if (!this.source?.data?.blocks?.[this.sub_key]) return true; + return false; + } + get last_read() { + return this.data.last_read; + } + /** + * Retrieves the sub-key of the block. + * @readonly + * @returns {string} The sub-key. + */ + get sub_key() { + return "#" + this.key.split("#").slice(1).join("#"); + } + /** + * Retrieves the lines range of the block. + * @readonly + * @returns {Array|undefined} An array containing the start and end lines or `undefined` if not set. + */ + // get lines() { return this.source?.data?.blocks?.[this.sub_key]; } + get lines() { + return this.data.lines; + } + /** + * Retrieves the starting line number of the block. + * @readonly + * @returns {number|undefined} The starting line number or `undefined` if not set. + */ + get line_start() { + return this.lines?.[0]; + } + /** + * Retrieves the ending line number of the block. + * @readonly + * @returns {number|undefined} The ending line number or `undefined` if not set. + */ + get line_end() { + return this.lines?.[1]; + } + /** + * Retrieves the link associated with the block, handling page numbers if present. + * @readonly + * @deprecated was specific to PDFs and removed this sort of PDF handling + * @returns {string} The block link. + */ + get link() { + if (/^.*page\s*(\d+).*$/i.test(this.sub_key)) { + const number = this.sub_key.match(/^.*page\s*(\d+).*$/i)[1]; + return `${this.source.path}#page=${number}`; + } else { + return this.source?.path || "MISSING SOURCE"; + } + } + // uses data.lines to get next block + get next_block() { + if (!this.data.lines) return null; + const next_line = this.data.lines[1] + 1; + return this.source.blocks?.find((block) => next_line === block.data?.lines?.[0]); + } + /** + * Retrieves the paths of outlinks from the block. + * @readonly + * @returns {Array} An array of outlink paths. + */ + get outlinks() { + return this.source.outlinks; + } + /** + * Retrieves the path of the SmartBlock. + * @readonly + * @returns {string} The path of the SmartBlock. + */ + get path() { + return this.key; + } + /** + * Determines if the block should be embedded based on its coverage and size. + * @readonly + * @returns {boolean} `true` if it should be embedded, `false` otherwise. + */ + get should_embed() { + try { + const source_hash = this.source?.data?.last_read?.hash; + const cached_should_embed = this._should_embed_cache; + if (source_hash != null && cached_should_embed?.hash === source_hash) { + return cached_should_embed.value; + } + const min_chars = this.settings?.min_chars; + let should_embed = true; + if (min_chars && this.size < min_chars) { + should_embed = false; + } else { + const match_line_start = this.line_start + 1; + const match_line_end = this.line_end; + const blocks = this.source?.data?.blocks; + const prefix = this.sub_key + "#"; + let has_line_start = null; + let has_line_end = null; + if (blocks) { + for (const key in blocks) { + if (!Object.prototype.hasOwnProperty.call(blocks, key)) continue; + if (!key.startsWith(prefix)) continue; + const range = blocks[key]; + if (range[0] === match_line_start) has_line_start = key; + if (range[1] === match_line_end) has_line_end = key; + if (has_line_start && has_line_end) break; + } + } + if (has_line_start && has_line_end) { + const start_block = this.collection.get(this.source_key + has_line_start); + if (start_block?.should_embed) { + const end_block = this.collection.get(this.source_key + has_line_end); + if (end_block?.should_embed) should_embed = false; + } + } + } + if (source_hash != null) { + this._should_embed_cache = { + hash: source_hash, + value: should_embed + }; + } + return should_embed; + } catch (e) { + console.error(e, e.stack); + console.error(`Error getting should_embed for ${this.key}: ` + JSON.stringify(e || {}, null, 2)); + } + } + /** + * Retrieves the size of the SmartBlock. + * @readonly + * @returns {number} The size of the SmartBlock. + */ + get size() { + return this.data.size; + } + /** + * Retrieves the SmartSource associated with the block. + * @readonly + * @returns {import("smart-sources").SmartSource} The associated SmartSource instance. + */ + get source() { + return this.source_collection.get(this.source_key); + } + /** + * Retrieves the SmartSources collection instance. + * @readonly + * @returns {import("smart-sources").SmartSources} The SmartSources collection. + */ + get source_collection() { + return this.env.smart_sources; + } + get source_key() { + return this.key.split("#")[0]; + } + get sub_blocks() { + return this.source?.blocks?.filter((block) => block.key.startsWith(this.key + "#") && block.line_start > this.line_start && block.line_end <= this.line_end) || []; + } + // source dependent + get excluded_lines() { + return this.source.excluded_lines; + } + get file() { + return this.source.file; + } + get is_media() { + return this.source.is_media; + } + get mtime() { + return this.source.mtime; + } + // DEPRECATED + /** + * Retrieves the display name of the block. + * @readonly + * @deprecated Use `get_item_display_name()` as platform-specific util. Move to sub-class in obsidian-smart-env to use respective util and settings (2026-03-27). + * @returns {string} The display name. + */ + get name() { + return get_block_display_name( + this.key, + this.env.settings.smart_view_filter?.show_full_path + // This probably does nothing (2026-03-27): see updated settings + ); + } + /** + * @deprecated Use `source` instead. Removing after 2025-09-01. + * @readonly + * @returns {SmartSource} The associated SmartSource instance. + */ + get note() { + return this.source; + } + /** + * @deprecated Use `source.key` instead. Removing after 2025-09-01. + * @readonly + * @returns {string} The source key. + */ + get note_key() { + return this.key.split("#")[0]; + } +}; + +// node_modules/obsidian-smart-env/src/utils/get_block_display_name.js +function get_block_display_name2(item, settings = {}) { + if (!item?.key) return ""; + const show_full_path = settings.show_full_path ?? true; + if (show_full_path) { + return item.key.replace(/#/g, " > ").replace(/\//g, " > "); + } + const pcs = []; + const [source_key, ...block_parts] = item.key.split("#"); + const filename = source_key.split("/").pop(); + pcs.push(filename); + if (block_parts.length) { + const last = block_parts[block_parts.length - 1]; + if (last.startsWith("{") && last.endsWith("}")) { + block_parts.pop(); + pcs.push(block_parts.pop()); + if (item.lines) pcs.push(`Lines: ${item.lines.join("-")}`); + } else { + pcs.push(block_parts.pop()); + } + } + return pcs.filter(Boolean).join(" > "); +} + +// node_modules/obsidian-smart-env/src/items/smart_block.js +var SmartBlock2 = class extends SmartBlock { + /** + * @param {Object} params - Parameters for display settings + * @param {boolean} params.show_full_path + */ + get_display_name(params = {}) { + const display_settings = { + ...this.env?.settings?.smart_view_filter || {}, + // DEPRECATED? settings scope + ...params + }; + return get_block_display_name2(this, display_settings); + } + // DEPRECATED + /** + * @deprecated avoid view-logic in Collection/Item AND prefix display_ where used anyway + */ + get name() { + return this.get_display_name(); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-blocks/smart_blocks.js +var SmartBlocks = class extends SmartEntities { + /** + * Initializes the SmartBlocks instance. Currently muted as processing is handled by SmartSources. + * @returns {void} + */ + init() { + } + get fs() { + return this.env.smart_sources.fs; + } + /** + * Retrieves the embedding model associated with the SmartSources collection. + * @readonly + * @returns {Object|undefined} The embedding model instance or `undefined` if not set. + */ + get embed_model() { + return this.source_collection?.embed_model; + } + /** + * Retrieves the embedding model key from the SmartSources collection. + * @readonly + * @returns {string|undefined} The embedding model key or `undefined` if not set. + */ + get embed_model_key() { + return this.source_collection?.embed_model_key; + } + /** + * Calculates the expected number of blocks based on the SmartSources collection. + * @readonly + * @returns {number} The expected count of blocks. + */ + get expected_blocks_ct() { + return Object.values(this.source_collection.items).reduce((acc, item) => acc += Object.keys(item.data.blocks || {}).length, 0); + } + /** + * Retrieves the notices system from the environment. + * @readonly + * @returns {Object} The notices object. + */ + get notices() { + return this.env.smart_connections_plugin?.notices || this.env.main?.notices; + } + /** + * Retrieves the settings configuration for SmartBlocks. + * @readonly + * @returns {Object} The settings configuration object. + */ + get settings_config() { + return this.process_settings_config({ + "embed_blocks": { + name: "Embed blocks", + type: "toggle", + description: "Blocks represent parts/sections of notes. Get more granular results.", + default: true + }, + ...super.settings_config + }); + } + get data_dir() { + return "multi"; + } + /** + * Retrieves the SmartSources collection instance. + * @readonly + * @returns {SmartSources} The SmartSources collection. + */ + get source_collection() { + return this.env.smart_sources; + } + /** + * Processes the embed queue. Currently handled by SmartSources, so this method is muted. + * @async + * @returns {Promise} + */ + async process_embed_queue() { + } + /** + * Processes the load queue. Currently muted as processing is handled by SmartSources. + * @async + * @returns {Promise} + */ + async process_load_queue() { + } + // TEMP: Methods in sources not implemented in blocks + /** + * @async + * @abstract + * @throws {Error} Throws an error indicating the method is not implemented. + * @returns {Promise} + */ + async prune() { + throw "Not implemented: prune"; + } + /** + * @throws {Error} Throws an error indicating the method is not implemented. + * @abstract + * @returns {void} + */ + build_links_map() { + throw "Not implemented: build_links_map"; + } + /** + * @async + * @abstract + * @throws {Error} Throws an error indicating the method is not implemented. + * @returns {Promise} + */ + async refresh() { + throw "Not implemented: refresh"; + } + /** + * @async + * @abstract + * @throws {Error} Throws an error indicating the method is not implemented. + * @returns {Promise} + */ + async search() { + throw "Not implemented: search"; + } + /** + * @async + * @abstract + * @throws {Error} Throws an error indicating the method is not implemented. + * @returns {Promise} + */ + async run_refresh() { + throw "Not implemented: run_refresh"; + } + /** + * @async + * @abstract + * @throws {Error} Throws an error indicating the method is not implemented. + * @returns {Promise} + */ + async run_force_refresh() { + throw "Not implemented: run_force_refresh"; + } + // clear expired blocks + // TODO/future: replaced by storing block data within source data + async cleanup_blocks() { + const expired_blocks = Object.values(this.items).filter((i) => i.is_gone); + console.log(`Removing ${expired_blocks.length} expired blocks`); + expired_blocks.forEach((i) => i.delete()); + await this.process_save_queue(); + expired_blocks.forEach((i) => { + delete this.items[i.key]; + }); + this.emit_event("blocks:cleaned", { expired_blocks_ct: expired_blocks.length }); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-blocks/adapters/data/ajson_multi_file.js +var AjsonMultiFileBlocksDataAdapter = class extends AjsonMultiFileCollectionDataAdapter { + ItemDataAdapter = AjsonMultiFileBlockDataAdapter; + /** + * Transforms the item key into a safe filename. + * Replaces spaces, slashes, and dots with underscores. + * @returns {string} safe file name + */ + // get_data_file_name(key) { + // return super.get_data_file_name(key.split('#')[0]); + // } + get_data_file_name(key) { + return key.split("#")[0].replace(/[\s\/\.]/g, "_").replace(".md", ""); + } + /** + * Process any queued save operations. + * @async + * @returns {Promise} + */ + async process_save_queue() { + this.collection.emit_event("collection:save_started"); + const save_queue = Object.values(this.collection.items).filter((item) => item._queue_save); + console.log(`Saving ${this.collection.collection_key}: ${save_queue.length} items`); + const time_start = Date.now(); + const save_files = Object.entries(save_queue.reduce((acc, item) => { + const file_name = this.get_item_data_path(item.key); + acc[file_name] = acc[file_name] || []; + acc[file_name].push(item); + return acc; + }, {})); + for (let i = 0; i < save_files.length; i++) { + const [file_name, items] = save_files[i]; + await this.fs.append( + file_name, + items.map((item) => this.get_item_ajson(item)).join("\n") + "\n" + ); + items.forEach((item) => item._queue_save = false); + } + console.log(`Saved ${this.collection.collection_key} in ${Date.now() - time_start}ms`); + this.collection.emit_event("collection:save_completed"); + } + process_load_queue() { + console.log(`Skipping loading ${this.collection.collection_key}...`); + } +}; +var AjsonMultiFileBlockDataAdapter = class extends AjsonMultiFileItemDataAdapter { +}; + +// node_modules/obsidian-smart-env/node_modules/smart-blocks/adapters/_adapter.js +var BlockContentAdapter = class { + /** + * @constructor + * @param {import('smart-blocks').SmartBlock} item - The SmartBlock instance this adapter operates on. + * The `item` should at least provide `data` and references to its parent source. + */ + constructor(item) { + this.item = item; + } + /** + * @async + * @method read + * @abstract + * @returns {Promise} The content of the block. + * @throws {Error} If not implemented by subclass. + */ + async read() { + throw new Error("Not implemented"); + } + /** + * @async + * @method append + * @abstract + * @param {string} content Content to append to the block. + * @returns {Promise} + * @throws {Error} If not implemented by subclass. + */ + async append(content) { + throw new Error("Not implemented"); + } + /** + * @async + * @method update + * @abstract + * @param {string} new_content The new content for the block. + * @param {Object} [opts={}] Additional update options. + * @returns {Promise} + * @throws {Error} If not implemented by subclass. + */ + async update(new_content, opts = {}) { + throw new Error("Not implemented"); + } + /** + * @async + * @method remove + * @abstract + * @returns {Promise} + * @throws {Error} If not implemented by subclass. + */ + async remove() { + throw new Error("Not implemented"); + } + /** + * @async + * @method move_to + * @abstract + * @param {string} to_key The destination key (source or block reference). + * @returns {Promise} + * @throws {Error} If not implemented by subclass. + */ + async move_to(to_key) { + throw new Error("Not implemented"); + } + /** + * @method get_display_name + * @abstract + * @param {Object} params Parameters for display name generation. + * @returns {string} The display name of the block. + * @throws {Error} If not implemented by subclass. + */ + get_display_name(params) { + throw new Error("Not implemented"); + } + /** + * @name data + * @type {Object} + * @readonly + * @description Access the block’s data object. Useful for updating metadata like line references or hashes. + */ + get data() { + return this.item.data; + } + /** + * @async + * @method update_last_read + * @param {string} content The current content of the block. + * @returns {Promise} + * @description Update the block’s `last_read` hash and timestamp based on the given content. + */ + async update_last_read(content) { + this.data.last_read = { + hash: this.create_hash(content), + at: Date.now() + }; + } + /** + * @method create_hash + * @param {string} content The content to hash. + * @returns {Promise} The computed hash of the content. + * @description Hash the block content to detect changes and prevent unnecessary re-embeddings. + */ + create_hash(content) { + return murmur_hash_32_alphanumeric(content); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-blocks/adapters/markdown_block.js +var MarkdownBlockContentAdapter = class extends BlockContentAdapter { + /** + * Read the content of the block. + * @async + * @returns {Promise} The block content as a string. + * @throws {Error} If the block cannot be found. + */ + async read() { + const source_content = await this.item.source?.read(); + if (!source_content) { + console.warn(`BLOCK NOT FOUND: ${this.item.key} has no source content.`); + return ""; + } + const content = this._extract_block(source_content); + this.update_last_read(content); + return content; + } + /** + * Append content to the existing block. + * This method inserts additional lines after the block's end, then re-parses the file to update line references. + * @async + * @param {string} content Content to append to the block. + * @returns {Promise} + * @throws {Error} If the block cannot be found. + */ + async append(content) { + let full_content = await this.item.source.read(); + const { line_start, line_end } = this.item; + if (!line_start || !line_end) { + throw new Error(`Cannot append to block ${this.item.key}: invalid line references.`); + } + const lines = full_content.split("\n"); + lines.splice(line_end, 0, "", content); + const updated_content = lines.join("\n"); + await this.item.source._update(updated_content); + await this._reparse_source(); + } + /** + * Update the block with new content, replacing its current lines. + * @async + * @param {string} new_content New content for the block. + * @param {Object} [opts={}] Additional options. + * @returns {Promise} + * @throws {Error} If the block cannot be found. + */ + async update(new_content, opts = {}) { + let full_content = await this.item.source.read(); + const { line_start, line_end } = this.item; + if (!line_start || !line_end) { + throw new Error(`Cannot update block ${this.item.key}: invalid line references.`); + } + const lines = full_content.split("\n"); + const updated_lines = [ + ...lines.slice(0, line_start - 1), + ...new_content.split("\n"), + ...lines.slice(line_end) + ]; + const updated_content = updated_lines.join("\n"); + await this.item.source._update(updated_content); + await this._reparse_source(); + } + /** + * Remove the block entirely from the source. + * @async + * @returns {Promise} + * @throws {Error} If the block cannot be found. + */ + async remove() { + let full_content = await this.item.source.read(); + const { line_start, line_end } = this.item; + if (!line_start || !line_end) { + throw new Error(`Cannot remove block ${this.item.key}: invalid line references.`); + } + const lines = full_content.split("\n"); + const updated_lines = [ + ...lines.slice(0, line_start - 1), + ...lines.slice(line_end) + ]; + const updated_content = updated_lines.join("\n"); + await this.item.source._update(updated_content); + await this._reparse_source(); + } + /** + * Move the block to a new location (another source or heading). + * This involves reading the block content, removing it from the current source, and appending it to the target. + * @async + * @param {string} to_key The destination path or entity reference. + * @returns {Promise} + * @throws {Error} If the block or target is invalid. + */ + async move_to(to_key) { + const content = await this.read(); + await this.remove(); + const is_block_ref = to_key.includes("#"); + let target_source_key = is_block_ref ? to_key.split("#")[0] : to_key; + const target_source = this.item.env.smart_sources.get(target_source_key); + if (!target_source) { + await this.item.env.smart_sources.create(target_source_key, content); + return; + } + if (is_block_ref) { + const target_block = this.item.env.smart_blocks.get(to_key); + if (target_block) { + await target_block.append(content); + } else { + await target_source.append(content); + } + } else { + await target_source.append(content); + } + } + /** + * Extract the block content using current line references from a full source content. + * @private + * @param {string} source_content Full source file content. + * @returns {string} Extracted block content. + * @throws {Error} If the block cannot be found. + */ + _extract_block(source_content) { + if (!source_content) { + console.warn(`BLOCK NOT FOUND: ${this.item.key} has no source content.`); + return ""; + } + const { line_start, line_end } = this.item; + if (!line_start || !line_end) { + throw new Error(`BLOCK NOT FOUND: ${this.item.key} has invalid line references.`); + } + return get_line_range2(source_content, line_start, line_end); + } + /** + * Re-parse the source file after a CRUD operation to update line references for all blocks. + * @private + * @async + * @returns {Promise} + */ + async _reparse_source() { + await this.item.source.import(); + } + get_display_name(params = {}) { + if (!this.item?.key) return ""; + const show_full_path = params.show_full_path ?? true; + if (show_full_path) { + return this.item.key.replace(/#/g, " > ").replace(/\//g, " > "); + } + const pcs = []; + const [source_key, ...block_parts] = this.item.key.split("#"); + const filename = source_key.split("/").pop(); + pcs.push(filename); + if (block_parts.length) { + const last = block_parts[block_parts.length - 1]; + if (last.startsWith("{") && last.endsWith("}")) { + block_parts.pop(); + pcs.push(block_parts.pop()); + if (this.item.lines) pcs.push(`Lines: ${this.item.lines.join("-")}`); + } else { + pcs.push(block_parts.pop()); + } + } + return pcs.filter(Boolean).join(" > "); + } +}; + +// node_modules/obsidian-smart-env/src/collections/smart_blocks.js +var smart_blocks_default = { + class: SmartBlocks, + version: SmartBlocks.version, + item_type: SmartBlock2, + data_adapter: AjsonMultiFileBlocksDataAdapter, + collection_key: "smart_blocks", + block_adapters: { + "md": MarkdownBlockContentAdapter, + "txt": MarkdownBlockContentAdapter, + "excalidraw.md": MarkdownBlockContentAdapter + // "canvas": MarkdownBlockContentAdapter, + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/smart_context.js +var remove_context_item_data = (context_items, key) => { + if (!key || !context_items?.[key]) return false; + const item_data = context_items[key]; + if (item_data.folder || item_data.from_named_context) { + if (item_data.exclude) { + delete context_items[key]; + return true; + } + item_data.exclude = true; + return true; + } + delete context_items[key]; + return true; +}; +var SmartContext = class extends CollectionItem { + static version = "2.0.2"; + /** + * @returns {{data: SmartContextData}} + */ + static get defaults() { + return { + data: { + key: "", + context_items: {}, + context_opts: {} + // REMOVE? + } + }; + } + // queue_save to debounce process save queue + /** + * @this {*} + * @returns {void} + */ + queue_save() { + super.queue_save(); + this.collection.queue_save(); + } + /** + * add_item + * @this {SmartContextThis} + * @param {string|Object.} item + * @param {SmartContextAddItemParams} [params={}] + * @returns {void|*} + */ + add_item(item, params = {}) { + const { + emit_updated = true + } = params; + let key; + if (typeof item === "object") { + key = item.key || item.path; + } else { + key = item; + } + const existing = this.data.context_items[key]; + const context_item = { + d: 0, + at: Date.now(), + ...existing || {}, + ...typeof item === "object" ? item : {} + }; + if (!key) return console.error("SmartContext: add_item called with invalid item", item); + this.data.context_items[key] = context_item; + this.queue_save(); + if (emit_updated) this.emit_event("context:updated", { add_item: key }); + } + /** + * add_items + * @this {SmartContextThis} + * @param {Array>|string|Object.} items + * @returns {void} + */ + add_items(items) { + if (!Array.isArray(items)) items = [items]; + items.forEach((item) => this.add_item(item, { emit_updated: false })); + this.emit_event("context:updated", { + added_items: items.map((item) => typeof item === "object" ? item.key || item.path : item) + }); + } + /** + * remove_item + * Removes a path/ref from context and emits context:updated + * @this {SmartContextThis} + * @param {string} key + * @param {SmartContextRemoveItemParams} params + * @returns {void} + */ + remove_item(key, params = {}) { + const { emit_updated = true } = params; + const removed = remove_context_item_data(this.data.context_items, key); + if (!removed) return; + this.queue_save(); + if (emit_updated) this.emit_event("context:updated", { removed_key: key, removed_keys: [key] }); + } + /** + * remove_items + * Removes paths/refs from context and emits context:updated once + * @this {SmartContextThis} + * @param {string[]|string} keys + * @param {SmartContextRemoveItemParams} params + * @returns {string[]} + */ + remove_items(keys, params = {}) { + const { emit_updated = true } = params; + const items = Array.isArray(keys) ? keys : [keys]; + const removed_keys = []; + items.forEach((item_key) => { + if (remove_context_item_data(this.data.context_items, item_key)) { + removed_keys.push(item_key); + } + }); + if (!removed_keys.length) return []; + this.queue_save(); + if (emit_updated) this.emit_event("context:updated", { removed_keys }); + return removed_keys; + } + /** + * @this {SmartContextThis} + * @returns {void} + */ + clear_all() { + this.data.context_items = {}; + this.queue_save(); + this.emit_event("context:updated", { cleared: true }); + } + /** + * @this {SmartContextThis} + * @returns {string[]} + */ + get context_item_keys() { + return Object.entries(this.data?.context_items || {}).filter(([, item_data]) => !item_data.exclude).map(([key]) => key); + } + /** + * @this {SmartContextThis} + * @returns {string[]} + */ + get excluded_context_item_keys() { + return Object.entries(this.data?.context_items || {}).filter(([, item_data]) => item_data?.exclude).map(([key]) => key); + } + /** + * @this {SmartContextThis} + * @returns {string} + */ + get key() { + if (!this.data.key) { + this.data.key = Date.now().toString(); + } + return this.data.key; + } + /** + * @this {SmartContextThis} + * @returns {boolean} + */ + get has_context_items() { + return this.item_count > 0; + } + /** + * @this {SmartContextThis} + * @returns {boolean} + */ + get has_excluded_context_items() { + return this.excluded_item_count > 0; + } + /** + * @this {SmartContextThis} + * @returns {number} + */ + get excluded_item_count() { + return this.excluded_context_item_keys.length; + } + /** + * @this {*} + * @returns {*} + */ + get name() { + return this.data.name; + } + /** + * @this {*} + * @param {string} name + */ + set name(name) { + if (typeof name !== "string") throw new TypeError("Name must be a string"); + const previous_name = typeof this.data.name === "string" ? this.data.name : ""; + const was_nameless = !previous_name || String(previous_name).trim().length === 0; + this.data.name = name; + if (was_nameless) { + this.emit_info_event("context:named", { name }); + } else { + this.emit_info_event("context:renamed", { + old_name: previous_name, + name + }); + } + this.queue_save(); + } + /** + * @this {SmartContextThis} + * @returns {number} + */ + get size() { + let size = 0; + Object.values(this.context_items.items || {}).forEach((item) => { + if (item.size) size += item.size; + }); + return size; + } + /** + * @this {SmartContextThis} + * @returns {number} + */ + get item_count() { + return Object.entries(this.data?.context_items || {}).filter(([, item_data]) => !item_data.exclude).length; + } + // v3 + /** + * @this {SmartContextThis} + * @param {Object.} [params={}] + * @returns {Promise} + */ + async get_text(params = {}) { + const segments = []; + const context_items = this.context_items.filter(params.filter).sort((a, b) => a.data.d - b.data.d); + console.log("get_text context_items", context_items); + for (const item of context_items) { + if (item.is_media) continue; + const item_text = await item.get_text(); + if (typeof item_text === "string") segments.push(item_text); + else this.emit_get_text_error(item, item_text); + } + const context_items_text = segments.join("\n"); + if (typeof this.actions.context_merge_template === "function") { + return await this.actions.context_merge_template(context_items_text, { context_items }); + } + return context_items_text; + } + /** + * Build a ContextItems collection on demand. + * + * The builder sometimes needs excluded entries in addition to active items, + * so this helper accepts the same params consumed by + * ContextItems.load_from_data(...). + * + * @this {SmartContextThis} + * @param {Object.} [params={}] + * @returns {ContextItemsInstance} + */ + get_context_items(params = {}) { + const config = this.env.config.collections.context_items; + const Class = config.class; + const context_items = new Class(this, { ...config, class: null }); + context_items.load_from_data(this.data.context_items || {}, params); + return context_items; + } + /** + * @this {SmartContextThis} + * @returns {ContextItemsInstance} + */ + get context_items() { + return this.get_context_items(); + } + /** + * @private + * @this {SmartContextThis} + * @param {*} item + * @param {ContextItemTextResult} item_text + * @returns {void} + */ + emit_get_text_error(item, item_text) { + this.emit_event("notification:error", { + message: `Context item did not return text: ${item.key}`, + ...item_text && typeof item_text === "object" ? item_text : {} + }); + } + /** + * Move below to pro subclass + * @this {SmartContextThis} + * @param {Object.} [params={}] + * @returns {Promise} + */ + async get_media(params = {}) { + const context_items = this.context_items.filter(params.filter); + const out = []; + for (const item of context_items) { + if (!item.is_media) continue; + const item_base64 = await item.get_base64(); + if (item_base64.error) this.emit_get_media_error(item, item_base64); + else out.push(item_base64); + } + return out; + } + /** + * @private + * @this {SmartContextThis} + * @param {*} item + * @param {ContextItemMediaResult} item_base64 + * @returns {void} + */ + emit_get_media_error(item, item_base64) { + this.emit_event("notification:error", { + message: `Context item did not return media: ${item.key}`, + ...item_base64 && typeof item_base64 === "object" ? item_base64 : {} + }); + } + /** + * Emit a missing-context-item warning once a burst of context_items hydration settles. + * + * ContextItems collections are rebuilt often by render paths, so debounce on the + * durable SmartContext instance to avoid duplicate native notices for the same + * missing item. + * + * @this {SmartContextThis} + * @param {string} key + * @param {Error|string} error + * @param {SmartContextMissingItemParams} [params={}] + * @returns {void} + */ + emit_missing_context_item_event(key, error, params = {}) { + const missing_key = String(key || "").trim(); + if (!missing_key) return; + if (!(this._missing_context_item_event_timers instanceof Map)) { + this._missing_context_item_event_timers = /* @__PURE__ */ new Map(); + } + const existing_timer = this._missing_context_item_event_timers.get(missing_key); + if (existing_timer) clearTimeout(existing_timer); + const raw_debounce_ms = Number.isFinite(params.debounce_ms) ? params.debounce_ms : 250; + const debounce_ms = Math.max(0, raw_debounce_ms); + const timer = setTimeout(() => { + this._missing_context_item_event_timers.delete(missing_key); + if (!this.data?.context_items?.[missing_key]) return; + this.emit_warning_event("smart_context:missing_item", { + message: params.message || "Failed to find context item: " + missing_key, + missing_key, + context_key: this.key, + error: error?.toString?.() || String(error || ""), + btn_text: params.btn_text || "Remove missing item", + btn_callback: "smart_contexts:remove_missing_item", + // should be able to be removed once notifications feed modal detects btn_event_key and btn_event_payload as valid action (to show button) + btn_event_key: "smart_contexts:remove_missing_item", + btn_event_payload: { + collection_key: "smart_contexts", + item_key: this.key, + missing_key + } + }); + }, debounce_ms); + this._missing_context_item_event_timers.set(missing_key, timer); + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/smart_contexts.js +var SmartContexts = class extends Collection { + static version = "2.0.1"; + /** + * new_context + * @this {SmartContextsThis} + * @param {Partial} data + * @param {Object. & {add_items?: string[]}} opts + * @returns {SmartContextInstance} + */ + new_context(data = {}, opts = {}) { + const item = new this.item_type(this.env, data); + if (Array.isArray(opts.add_items)) item.add_items(opts.add_items); + this.set(item); + item.queue_save(); + item.emit_event("context:created"); + return item; + } + /** + * @this {SmartContextsThis} + * @param {string} name + * @returns {*} + */ + get_named_context(name) { + return this.filter((ctx) => ctx.data?.name === name)[0]; + } + /** + * Default settings for all SmartContext items in this collection. + * @readonly + * @returns {Object.} + */ + static get default_settings() { + return { + template_preset: "xml_structured", + template_before: "\n{{FILE_TREE}}", + template_after: "" + }; + } + /** + * @this {SmartContextsThis} + * @returns {SettingsConfig} + */ + get settings_config() { + return { + ...this.env.config.actions.context_merge_template?.settings_config || {} + }; + } +}; +var smart_contexts_default = { + class: SmartContexts, + collection_key: "smart_contexts", + data_adapter: AjsonSingleFileCollectionDataAdapter, + item_type: SmartContext, + version: SmartContexts.version +}; + +// node_modules/obsidian-smart-env/node_modules/smart-contexts/index.js +var smart_contexts_default_config = { + class: SmartContexts, + data_adapter: AjsonSingleFileCollectionDataAdapter, + item_type: SmartContext +}; +var smart_contexts_default2 = smart_contexts_default_config; + +// node_modules/obsidian-smart-env/src/utils/smart-context/remove_path_utils.js +function normalize_remove_path(path = "") { + return String(path || "").replace(/\/+$/g, ""); +} +function item_matches_remove_path(item_key = "", target_path = "") { + const normalized_item_key = normalize_remove_path(item_key); + const normalized_target_path = normalize_remove_path(target_path); + if (!normalized_item_key || !normalized_target_path) return false; + return normalized_item_key === normalized_target_path || normalized_item_key.startsWith(normalized_target_path + "/") || normalized_item_key.startsWith(normalized_target_path + "#") || normalized_item_key.startsWith(normalized_target_path + "{"); +} +function normalize_remove_targets(target_paths = [], params = {}) { + const items = Array.isArray(target_paths) ? target_paths : [target_paths]; + const targets = []; + items.forEach((target) => { + const path = typeof target === "string" ? target : target?.path || target?.key; + const normalized_path = String(path || "").trim(); + if (!normalized_path) return; + const next_target = { + path: normalized_path, + norm_key: normalize_remove_path(normalized_path), + folder: target?.folder === true || params.folder === true + }; + for (const existing_target of targets) { + if (item_matches_remove_path(next_target.norm_key, existing_target.norm_key)) return; + } + for (let i = targets.length - 1; i >= 0; i -= 1) { + if (item_matches_remove_path(targets[i].norm_key, next_target.norm_key)) { + targets.splice(i, 1); + } + } + targets.push(next_target); + }); + return targets; +} + +// node_modules/obsidian-smart-env/src/items/smart_context.js +var SmartContext2 = class extends SmartContext { + static version = "2.1.0"; + get named_contexts() { + return Object.entries(this.data?.context_items || {}).filter(([name, item_data]) => item_data?.named_context).map(([name, item_data]) => this.env.smart_contexts.get_named_context(item_data?.key || name)).filter(Boolean); + } + /** + * @param {string} target_path + */ + remove_by_path(target_path, params = {}) { + return this.remove_by_paths([ + { + path: target_path, + ...params.folder ? { folder: true } : {} + } + ], params); + } + /** + * Remove multiple paths from context in one data pass. + * + * @param {Array} target_paths + * @param {object} [params={}] + * @param {boolean} [params.emit_updated=true] + * @param {boolean} [params.queue_save=true] + * @returns {string[]} + */ + remove_by_paths(target_paths = [], params = {}) { + const { + emit_updated = true, + queue_save = true + } = params; + const targets = normalize_remove_targets(target_paths, params); + if (!targets.length) return []; + const context_items = this.data?.context_items || {}; + const remove_keys = []; + Object.keys(context_items).forEach((key) => { + const target = targets.find((target2) => item_matches_remove_path(key, target2.norm_key)); + if (!target) return; + remove_keys.push(key); + }); + remove_keys.forEach((key) => { + delete context_items[key]; + }); + if (!remove_keys.length) return []; + if (emit_updated) { + this.emit_event("context:updated", { + removed_key: targets[0].path, + removed_keys: targets.map((target) => target.path), + ...targets.some((target) => target.folder) ? { folder: true } : {} + }); + } + if (queue_save) this.queue_save(); + return remove_keys; + } + /** + * add_item + * this override adds implementation-specific (source/block pattern) logic to remove redundant block items when a parent source is added + * @param {string|object} item + */ + add_item(item, params = {}) { + const { + emit_updated = true + } = params; + let key; + if (typeof item === "object") { + key = item.key || item.path; + } else { + key = item; + } + const existing = this.data.context_items[key]; + const context_item = { + d: 0, + at: Date.now(), + ...existing || {}, + ...typeof item === "object" ? item : {} + }; + if (!key) return console.error("SmartContext: add_item called with invalid item", item); + const emit_payload = { add_item: key }; + const remove_sub_keys = Object.entries(this.data.context_items).filter(([existing_key]) => existing_key !== key && item_matches_remove_path(existing_key, key)).map(([existing_key]) => existing_key); + if (remove_sub_keys.length) { + this.remove_items(remove_sub_keys, { emit_updated: false }); + emit_payload.removed_keys = remove_sub_keys; + emit_payload.message = "Parent item added, removed redundant sub-items"; + } + this.data.context_items[key] = context_item; + this.queue_save(); + if (emit_updated) this.emit_event("context:updated", emit_payload); + } +}; + +// node_modules/obsidian-smart-env/src/collections/smart_contexts.js +var SmartContexts2 = class extends SmartContexts { + static version = "2.1.0"; + async init() { + await super.init?.(); + this.register_remove_missing_item_handler(); + } + register_remove_missing_item_handler() { + if (this._remove_missing_item_disposer) return; + this._remove_missing_item_disposer = this.env?.events?.on?.( + "smart_contexts:remove_missing_item", + (payload = {}) => this.remove_missing_item(payload) + ); + } + /** + * @param {object} [payload={}] + * @returns {boolean} + */ + remove_missing_item(payload = {}) { + const context_key = String(payload?.item_key || payload?.context_key || "").trim(); + const missing_key = String(payload?.missing_key || "").trim(); + const ctx = context_key ? this.get(context_key) : null; + if (!ctx || !missing_key) { + this.emit_warning_event("smart_contexts:remove_missing_item_failed", { + message: "Unable to remove missing context item.", + context_key, + missing_key, + event_source: "smart_contexts.remove_missing_item" + }); + return false; + } + ctx.remove_item(missing_key); + ctx.emit_event("context:missing_item_removed", { + level: "info", + message: "Removed missing context item.", + removed_key: missing_key, + event_source: "smart_contexts.remove_missing_item" + }); + return true; + } + unload() { + this._remove_missing_item_disposer?.(); + this._remove_missing_item_disposer = null; + return super.unload?.(); + } +}; +smart_contexts_default2.class = SmartContexts2; +smart_contexts_default2.version = SmartContexts2.version; +smart_contexts_default2.item_type = SmartContext2; +var smart_contexts_default3 = smart_contexts_default2; + +// node_modules/obsidian-smart-env/src/utils/register_block_hover_popover.js +var import_obsidian9 = require("obsidian"); +function register_block_hover_popover(parent, target, env, block_key, params = {}) { + const app2 = env?.plugin?.app || window.app; + target.addEventListener("mouseover", async (ev) => { + if (import_obsidian9.Keymap.isModEvent(ev)) { + const block = env.smart_blocks.get(block_key); + const markdown = await block?.read(); + if (markdown) { + const popover = new import_obsidian9.HoverPopover(parent, target); + const frag = env.smart_view.create_doc_fragment(`
+
+
+
+
+
+
+
`); + popover.hoverEl.classList.add("smart-block-popover"); + popover.hoverEl.appendChild(frag); + const sizer = popover.hoverEl.querySelector(".markdown-preview-sizer"); + import_obsidian9.MarkdownRenderer.render(app2, markdown, sizer, "/", popover); + const event_domain = params.event_key_domain || "block"; + block.emit_event(`${event_domain}:hover_preview`); + } + } + }); +} + +// node_modules/obsidian-smart-env/src/utils/register_item_hover_popover.js +var import_obsidian10 = require("obsidian"); +function register_item_hover_popover(container, item, params = {}) { + const app2 = item.env?.plugin?.app || window.app; + if (item.key.indexOf("{") === -1) { + container.addEventListener("mouseover", (event) => { + const linktext_path = item.key.replace(/#$/, ""); + app2.workspace.trigger("hover-link", { + event, + source: "smart-connections-view", + hoverParent: container.parentElement, + targetEl: container, + linktext: linktext_path + }); + if (import_obsidian10.Keymap.isModEvent(event)) { + const event_domain = params.event_key_domain || item.collection_key || "item"; + item.emit_event(`${event_domain}:hover_preview`); + } + }); + } else { + register_block_hover_popover(container.parentElement, container, item.env, item.key); + } +} + +// node_modules/obsidian-smart-env/src/components/context-item/leaf.js +var import_obsidian11 = require("obsidian"); +function format_score(score) { + const numeric_score = typeof score === "number" ? score : Number.parseFloat(score); + if (!Number.isFinite(numeric_score)) return null; + return Number.parseFloat(numeric_score.toFixed(2)).toString(); +} +function format_size(size) { + const numeric_size = typeof size === "number" ? size : Number.parseFloat(size); + if (!Number.isFinite(numeric_size) || numeric_size < 0) return null; + const units = ["B", "KB", "MB", "GB"]; + let size_value = numeric_size; + let unit_index = 0; + while (size_value >= 1024 && unit_index < units.length - 1) { + size_value /= 1024; + unit_index += 1; + } + const precision = size_value >= 10 || Number.isInteger(size_value) ? 0 : 1; + const rounded_value = Number.parseFloat(size_value.toFixed(precision)); + return `${rounded_value.toString()} ${units[unit_index]}`; +} +function format_size_percent(size, context_size) { + const numeric_size = typeof size === "number" ? size : Number.parseFloat(size); + const numeric_context_size = typeof context_size === "number" ? context_size : Number.parseFloat(context_size); + if (!Number.isFinite(numeric_size) || numeric_size < 0) return null; + if (!Number.isFinite(numeric_context_size) || numeric_context_size <= 0) return null; + const percent_value = numeric_size / numeric_context_size * 100; + const precision = percent_value >= 10 || Number.isInteger(percent_value) ? 0 : 1; + const rounded_value = Number.parseFloat(percent_value.toFixed(precision)); + return `${rounded_value.toString()}%`; +} +function format_size_label(size, context_size) { + const size_label = format_size(size); + if (!size_label) return null; + const percent_label = format_size_percent(size, context_size); + if (!percent_label) return size_label; + return `${percent_label} (${size_label})`; +} +function build_badge_html(label, class_name, params = {}) { + if (!label) return ""; + const icon_attr = params.icon ? ` data-icon="${escape_html(params.icon)}"` : ""; + const tooltip = params.title ? ` aria-label="${escape_html(params.title)}"` : ""; + const named_context_attr = params.named_context ? ` role="button" data-named-context="${escape_html(params.named_context)}"` : ""; + const icon_html = params.icon ? '' : ""; + const label_html = params.icon ? `` : escape_html(label); + return `${icon_html}${label_html}`; +} +function get_context_item_name(context_item) { + const item_ref = context_item?.item_ref || null; + const item_key = String(context_item?.key || ""); + if (item_ref?.key) { + const item_ref_key = String(item_ref.key); + if (item_ref_key.includes("#")) { + const name_pcs = item_ref_key.split("/").pop().split("#").filter(Boolean); + const last_pc = name_pcs.pop(); + if (last_pc && last_pc.startsWith("{")) { + const lines = Array.isArray(item_ref.lines) ? item_ref.lines.join("-") : ""; + return lines ? `Lines: ${lines}` : last_pc; + } + return last_pc; + } + return item_ref_key.split("/").pop(); + } + return item_key.split("/").pop(); +} +function get_folder_source(context_item) { + const data = context_item?.data || {}; + if (typeof data.from_folder === "string" && data.from_folder.trim()) return data.from_folder; + if (typeof data.folder === "string" && data.folder.trim()) return data.folder; + if (data.folder === true) return data.key || context_item?.key || ""; + return ""; +} +function format_folder_badge_label(folder_source = "") { + const normalized = String(folder_source || "").replace(/^external:/, "").replace(/\\+/g, "/").replace(/\/+$/g, ""); + return normalized.split("/").filter(Boolean).pop() || normalized; +} +function get_origin_badges(context_item) { + const data = context_item?.data || {}; + const badges = []; + const folder_source = get_folder_source(context_item); + if (folder_source) { + badges.push({ + icon: "folder", + label: format_folder_badge_label(folder_source), + title: `Included from folder: ${folder_source}`, + class_name: "sc-context-item-origin-badge sc-context-item-origin-folder" + }); + } + const named_context = typeof data.from_named_context === "string" ? data.from_named_context.trim() : ""; + if (named_context) { + badges.push({ + icon: "smart-named-contexts", + label: named_context, + named_context, + title: `Included from named context: ${named_context}`, + class_name: "sc-context-item-origin-badge sc-context-item-origin-named-context" + }); + } + return badges; +} +function build_origin_badges_html(context_item) { + const badges = get_origin_badges(context_item); + if (!badges.length) return ""; + return `${badges.map((badge) => build_badge_html(badge.label, badge.class_name, badge)).join("")}`; +} +function build_missing_badge_html(context_item) { + if (context_item?.exists !== false) return ""; + return build_badge_html("Missing", "sc-context-item-origin-badge sc-context-item-warning-badge", { + icon: "alert-triangle", + title: "Missing source" + }); +} +function render_inline_icons(container) { + container.querySelectorAll("[data-icon]").forEach((icon_el) => { + const icon = icon_el.getAttribute("data-icon"); + if (!icon) return; + const target = icon_el.querySelector(".sc-context-item-badge-icon") || icon_el; + (0, import_obsidian11.setIcon)(target, icon); + }); +} +function set_remove_pending(remove_btn) { + remove_btn.classList.add("is-removing"); + remove_btn.textContent = ""; + remove_btn.setAttribute("aria-label", "Removing item"); + remove_btn.setAttribute("aria-busy", "true"); +} +function clear_remove_pending(remove_btn) { + remove_btn.classList.remove("is-removing"); + remove_btn.textContent = "\xD7"; + remove_btn.removeAttribute("aria-busy"); + remove_btn.setAttribute("aria-label", "Remove item"); +} +function build_html(context_item, params = {}) { + const name = get_context_item_name(context_item); + const item_key = String(context_item?.key || ""); + const score = format_score(context_item?.data?.score); + const item_size = context_item?.size ?? context_item?.data?.size; + const size = format_size_label(item_size, params.context_size); + const score_html = build_badge_html(score, "sc-context-item-score"); + const size_html = build_badge_html(size, "sc-context-item-size"); + const missing_badge_html = build_missing_badge_html(context_item); + const origin_badges_html = build_origin_badges_html(context_item); + const icon_type = context_item?.icon_type || null; + const icon_html = icon_type ? `` : ""; + const missing_class = context_item?.exists === false ? " missing" : ""; + const remove_disabled = params.remove_disabled === true; + const remove_class = remove_disabled ? "sc-context-item-remove is-disabled" : "sc-context-item-remove"; + const remove_label = remove_disabled ? "Open named context to edit included item" : "Remove item"; + const remove_disabled_attr = remove_disabled ? ' aria-disabled="true"' : ""; + return ` + \xD7 + ${score_html} + ${icon_html} + ${escape_html(name || item_key)} + ${size_html} + ${missing_badge_html} + ${origin_badges_html} + `; +} +async function render(context_item, params = {}) { + const html = build_html(context_item, params); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process.call(this, context_item, container, params); + return container; +} +async function post_process(context_item, container, params = {}) { + render_inline_icons(container); + const remove_btn = container.querySelector(".sc-context-item-remove"); + if (remove_btn) { + if (params.remove_disabled === true) { + remove_btn.setAttribute("title", "Open the named context to remove this included item."); + } + remove_btn.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + if (params.remove_disabled === true) { + if (typeof params.on_remove_disabled === "function") { + params.on_remove_disabled(event, context_item); + } + return; + } + if (typeof params.on_remove !== "function") return; + if (remove_btn.classList.contains("is-removing")) return; + set_remove_pending(remove_btn); + try { + const result = params.on_remove(event, context_item); + if (result && typeof result.catch === "function") { + result.catch((error) => { + clear_remove_pending(remove_btn); + console.warn("Smart Context: Failed to remove context item", error); + }); + } + } catch (error) { + clear_remove_pending(remove_btn); + throw error; + } + }); + } + const name = container.querySelector(".sc-context-item-name"); + if (!name) return container; + const is_missing = context_item?.exists === false; + if (is_missing) { + name.setAttribute("title", "Missing source"); + } + const item_ref = context_item?.item_ref || null; + if (item_ref && !is_missing) { + name.setAttribute("title", `Hold ${import_obsidian11.Platform.isMacOS ? "\u2318" : "Ctrl"} to preview`); + register_item_hover_popover(name, item_ref); + } + name.addEventListener("click", (event) => { + if (typeof context_item.open !== "function") return; + context_item.open(event); + }); + return container; +} + +// node_modules/obsidian-smart-env/src/components/default_notification.js +var import_obsidian12 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/components/default_notification.css +var default_notification_default = ".notice:has(.smart-env-default-notice) {\n padding: 0;\n border: none;\n background: transparent;\n box-shadow: none;\n min-width: 0;\n max-width: min(420px, calc(100vw - 2rem));\n}\n\n.notice-message:has(.smart-env-default-notice) {\n padding: 0;\n background: transparent;\n}\n\n.smart-env-default-notice {\n --smart-env-default-notice-accent: var(--text-muted);\n\n width: 100%;\n min-width: 0;\n font-family: var(--font-interface);\n}\n\n.smart-env-default-notice[data-level='error'] {\n --smart-env-default-notice-accent: var(--color-red);\n}\n\n.smart-env-default-notice[data-level='warning'] {\n --smart-env-default-notice-accent: var(--color-orange);\n}\n\n.smart-env-default-notice[data-level='attention'] {\n --smart-env-default-notice-accent: var(--color-yellow);\n}\n\n.smart-env-default-notice[data-level='milestone'] {\n --smart-env-default-notice-accent: var(--color-accent);\n}\n\n.smart-env-default-notice[data-level='info'] {\n --smart-env-default-notice-accent: var(--text-muted);\n}\n\n.smart-env-default-notice__surface {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: var(--size-4-3);\n align-items: start;\n\n width: 100%;\n box-sizing: border-box;\n padding: var(--size-4-3);\n border: 1px solid var(--background-modifier-border);\n border-inline-start: 3px solid var(--smart-env-default-notice-accent);\n border-radius: var(--radius-l);\n background: var(--background-secondary);\n}\n\n.smart-env-default-notice[data-muted='true'] .smart-env-default-notice__surface {\n opacity: 0.86;\n}\n\n.smart-env-default-notice__icon.clickable-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n align-self: start;\n\n width: calc(var(--icon-l) + var(--size-4-4));\n height: calc(var(--icon-l) + var(--size-4-4));\n min-width: auto;\n min-height: auto;\n padding: var(--size-2-2);\n\n color: var(--smart-env-default-notice-accent);\n background: var(--background-primary);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--button-radius);\n}\n\n.smart-env-default-notice__icon.clickable-icon:hover:not(:disabled) {\n color: var(--text-normal);\n border-color: var(--background-modifier-border-hover, var(--background-modifier-border));\n}\n\n.smart-env-default-notice__icon.clickable-icon:disabled {\n opacity: 0.6;\n cursor: default;\n}\n\n.smart-env-default-notice__icon svg {\n width: var(--icon-l);\n height: var(--icon-l);\n}\n\n.smart-env-default-notice__body {\n min-width: 0;\n}\n\n.smart-env-default-notice__eyebrow {\n margin: 0 0 var(--size-2-1);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n font-weight: var(--font-semibold);\n letter-spacing: 0.08em;\n line-height: 1.1;\n text-transform: uppercase;\n}\n\n.smart-env-default-notice__title {\n color: var(--text-normal);\n font-size: var(--font-text-size);\n font-weight: var(--font-semibold);\n line-height: 1.3;\n white-space: pre-wrap;\n}\n\n.smart-env-default-notice__details {\n margin-top: var(--size-2-2);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n line-height: 1.45;\n white-space: pre-wrap;\n}\n\n.smart-env-default-notice__actions {\n display: flex;\n align-items: center;\n gap: var(--size-2-2) var(--size-4-2);\n flex-wrap: wrap;\n margin-top: var(--size-4-2);\n}\n\n.smart-env-default-notice__button,\n.smart-env-default-notice__mute {\n margin: 0;\n}\n\n.smart-env-default-notice__mute {\n white-space: nowrap;\n}\n\n.smart-env-default-notice__link {\n color: var(--text-accent, var(--color-accent));\n font-size: var(--font-ui-small);\n text-decoration: underline;\n text-decoration-thickness: 1px;\n text-underline-offset: 2px;\n}\n\n@media (max-width: 520px) {\n .notice:has(.smart-env-default-notice) {\n max-width: calc(100vw - 1.2rem);\n }\n\n .smart-env-default-notice__surface {\n grid-template-columns: 1fr;\n gap: var(--size-4-2);\n }\n}\n"; + +// node_modules/obsidian-smart-env/src/components/default_notification.js +function to_trimmed_string2(value) { + return typeof value === "string" ? value.trim() : ""; +} +function get_default_notice_title(event_key, event = {}) { + const message = to_trimmed_string2(event?.message); + if (message) return message; + return event_key || "Notification"; +} +function get_default_notice_details(event = {}, title = "") { + const details = to_trimmed_string2(event?.details); + if (details && details !== title) return details; + return ""; +} +function format_level_label(level) { + const value = typeof level === "string" ? level : "info"; + return value.slice(0, 1).toUpperCase() + value.slice(1); +} +function render_icon(icon_el, level) { + if (!icon_el) return; + const icon_ids = get_icon_ids(level); + for (const icon_id of icon_ids) { + if (typeof icon_id !== "string" || icon_id.length === 0) continue; + icon_el.textContent = ""; + try { + (0, import_obsidian12.setIcon)(icon_el, icon_id); + } catch (_error) { + continue; + } + if (icon_el.querySelector("svg")) return; + } + icon_el.textContent = "\u2022"; +} +function get_icon_ids(level) { + switch (level) { + case "error": + return ["circle-alert", "alert-circle", "octagon-alert"]; + case "warning": + return ["triangle-alert", "alert-triangle", "circle-alert"]; + case "attention": + return ["bell-ring", "bell", "info"]; + case "milestone": + return ["sparkles", "badge-check", "circle-check"]; + default: + return ["info", "badge-info", "circle-alert"]; + } +} +function get_default_notice_summary(event_key, event = {}) { + const title = get_default_notice_title(event_key, event); + const details = get_default_notice_details(event, title); + if (!details) return title; + return `${title} +${details}`; +} +function open_notifications_feed(env, params = {}) { + const { + on_open_feed = null, + event_key = "", + event = {} + } = params; + const open_params = { + event_key, + event + }; + if (typeof on_open_feed === "function") { + return on_open_feed(open_params) !== false; + } + if (typeof env?.open_notifications_feed_modal === "function") { + env.open_notifications_feed_modal(open_params); + return true; + } + return false; +} +function render2(env, params = {}) { + const { + event_key = "", + event = {}, + on_action = null, + on_mute = null, + on_open_feed = null + } = params; + const level = get_event_level(event_key, event) || "info"; + const title = get_default_notice_title(event_key, event); + const details = get_default_notice_details(event, title); + const btn_text = to_trimmed_string2(event?.btn_text); + const btn_callback = to_trimmed_string2(event?.btn_callback); + const help_link = to_trimmed_string2(event?.link); + if (typeof document === "undefined") { + return get_default_notice_summary(event_key, event); + } + this.apply_style_sheet?.(default_notification_default); + const run_action = typeof on_action === "function" ? on_action : (callback_key) => dispatch_notice_action(env, callback_key, { + event_source: "default_notification", + source_event_key: event_key, + source_event: event + }); + const run_mute = typeof on_mute === "function" ? on_mute : () => false; + const can_open_feed = typeof on_open_feed === "function" || typeof env?.open_notifications_feed_modal === "function"; + const can_view_more = can_open_feed && Boolean(event_key); + const frag = document.createDocumentFragment(); + const wrapper = document.createElement("div"); + wrapper.className = "smart-env-default-notice"; + wrapper.dataset.level = level; + const surface_el = document.createElement("div"); + surface_el.className = "smart-env-default-notice__surface"; + const icon_el = document.createElement("button"); + icon_el.type = "button"; + icon_el.className = "smart-env-default-notice__icon clickable-icon"; + icon_el.setAttribute("aria-label", "Open events and notifications"); + icon_el.setAttribute("title", "Open events and notifications"); + icon_el.disabled = !can_open_feed; + render_icon(icon_el, level); + if (can_open_feed) { + icon_el.addEventListener("click", (event_obj) => { + event_obj.preventDefault(); + event_obj.stopPropagation(); + open_notifications_feed(env, { + on_open_feed, + event_key, + event + }); + }); + } + const body_el = document.createElement("div"); + body_el.className = "smart-env-default-notice__body"; + const eyebrow_el = document.createElement("div"); + eyebrow_el.className = "smart-env-default-notice__eyebrow"; + eyebrow_el.textContent = `${format_level_label(level)}`; + const title_el = document.createElement("div"); + title_el.className = "smart-env-default-notice__title"; + title_el.textContent = title; + body_el.appendChild(eyebrow_el); + body_el.appendChild(title_el); + if (details) { + const details_el = document.createElement("div"); + details_el.className = "smart-env-default-notice__details"; + details_el.textContent = details; + body_el.appendChild(details_el); + } + const should_render_actions = Boolean(btn_text && btn_callback) || Boolean(help_link) || Boolean(event_key); + if (should_render_actions) { + const actions_el = document.createElement("div"); + actions_el.className = "smart-env-default-notice__actions"; + if (btn_text && btn_callback) { + const button_el = document.createElement("button"); + button_el.type = "button"; + button_el.className = "smart-env-default-notice__button mod-cta"; + button_el.textContent = btn_text; + button_el.setAttribute("aria-label", btn_text); + button_el.addEventListener("click", () => { + run_action(btn_callback); + }); + actions_el.appendChild(button_el); + } + if (can_view_more) { + const view_more_btn_el = document.createElement("button"); + view_more_btn_el.type = "button"; + view_more_btn_el.className = "smart-env-default-notice__button"; + view_more_btn_el.textContent = "View more"; + view_more_btn_el.setAttribute("aria-label", "Open this event in the notifications feed"); + view_more_btn_el.addEventListener("click", () => { + open_notifications_feed(env, { + on_open_feed, + event_key, + event + }); + }); + actions_el.appendChild(view_more_btn_el); + } + if (help_link) { + const link_el = document.createElement("a"); + link_el.className = "smart-env-default-notice__link"; + link_el.href = help_link; + link_el.textContent = "Learn more"; + link_el.target = "_external"; + link_el.rel = "noopener noreferrer"; + link_el.setAttribute("aria-label", "Learn more about this notification"); + actions_el.appendChild(link_el); + } + if (event_key) { + const mute_btn_el = document.createElement("button"); + mute_btn_el.type = "button"; + mute_btn_el.className = "smart-env-default-notice__mute"; + mute_btn_el.textContent = "Mute"; + (0, import_obsidian12.setIcon)(mute_btn_el, "bell-off"); + mute_btn_el.setAttribute("aria-label", "Mute future native notices for this event key"); + mute_btn_el.addEventListener("click", () => { + const muted = run_mute(); + if (muted === false) return; + wrapper.dataset.muted = "true"; + mute_btn_el.disabled = true; + mute_btn_el.textContent = "Muted"; + }); + actions_el.appendChild(mute_btn_el); + } + body_el.appendChild(actions_el); + } + surface_el.appendChild(icon_el); + surface_el.appendChild(body_el); + wrapper.appendChild(surface_el); + frag.appendChild(wrapper); + return frag; +} + +// node_modules/obsidian-smart-env/src/utils/format_collection_name.js +function format_collection_name(key) { + return key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); +} + +// node_modules/obsidian-smart-env/src/components/env_stats.js +async function build_html2(env, opts = {}) { + const lines = []; + lines.push(`

Collections

`); + const collection_keys = Object.keys(env.collections).filter((key) => ["smart_sources", "smart_blocks"].includes(key)).sort((a, b) => { + if (a === "smart_sources" || a === "smart_blocks") return -1; + if (b === "smart_sources" || b === "smart_blocks") return 1; + return a.localeCompare(b); + }); + for (const collection_key of collection_keys) { + const collection = env[collection_key]; + if (!collection || !collection.items) { + lines.push(` +
+

${format_collection_name(collection_key)}

+

No valid items.

+
+ `); + continue; + } + const snippet = generate_collection_stats(collection, collection_key); + lines.push(snippet); + } + return ` +
+ ${lines.join("\n")} +
+ `; +} +async function render3(env, opts = {}) { + const html = await build_html2.call(this, env, opts); + const frag = this.create_doc_fragment(html); + return await post_process2.call(this, env, frag, opts); +} +async function post_process2(env, frag, opts = {}) { + return frag; +} +function generate_collection_stats(collection, collectionKey) { + const total_items = Object.values(collection.items).length; + const niceName = format_collection_name(collectionKey); + const state = collection.env.collections[collectionKey]; + if (state !== "loaded") { + return ` +
+

${niceName}

+

Not loaded yet (${total_items} items known).

+
+ `; + } + const load_time_html = collection.load_time_ms ? `

Load time: ${collection.load_time_ms}ms

` : ""; + const state_html = `

State: ${state}

`; + let html = get_generic_collection_stats(collection, niceName, total_items); + let embed_stats = ""; + if (typeof collection.process_embed_queue === "function") { + embed_stats = calculate_embed_coverage(collection, total_items); + } + return ` +
+

${niceName}

+ ${embed_stats} + ${html} + ${load_time_html} + ${state_html} +
+ `; +} +function get_generic_collection_stats(collection, niceName, total_items, load_time_html) { + return ` +

Total: ${total_items}

+ `; +} +function calculate_embed_coverage(collection, total_items) { + const embedded_items = Object.values(collection.items).filter((item) => item.vec); + if (!embedded_items.length) return "

No items embedded

"; + const stats = Object.values(collection.items).reduce((acc, i) => { + if (i.should_embed) acc.should_embed += 1; + else acc.should_not_embed += 1; + if (i.vec) acc.embedded += 1; + if (i.should_embed && !i.vec) acc.missing_embed += 1; + if (!i.should_embed && i.vec) acc.extraneous_embed += 1; + return acc; + }, { should_embed: 0, embedded: 0, missing_embed: 0, extraneous_embed: 0, should_not_embed: 0 }); + const pct = stats.embedded / stats.should_embed * 100; + const percent = Math.round(pct); + return `

Embedding coverage: ${percent}% (${stats.embedded} / ${stats.should_embed})

` + (stats.missing_embed ? `

Missing embeddings: ${stats.missing_embed}

` : "") + (stats.extraneous_embed ? `

Extraneous embeddings: ${stats.extraneous_embed}

` : "") + (stats.should_not_embed ? `

Other items (e.g. less than minimum length to embed): ${stats.should_not_embed}

` : ""); +} + +// node_modules/obsidian-smart-env/src/components/env_status.css +var env_status_default = ".smart-env-status-view {\n display: flex;\n flex-direction: column;\n gap: 0.85rem;\n padding: var(--size-4-4);\n}\n\n.smart-env-status-view__header {\n display: flex;\n flex-direction: column;\n gap: 0.2rem;\n}\n\n.smart-env-status-view__eyebrow {\n font-size: var(--font-ui-smaller);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n color: var(--text-muted);\n}\n\n.smart-env-status-view__title {\n margin: 0;\n font-size: var(--h2-size);\n line-height: 1.15;\n}\n\n.smart-env-status-view__status {\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n font-variant-numeric: tabular-nums;\n}\n\n.smart-env-status-view__summary {\n color: var(--text-normal);\n line-height: 1.4;\n}\n\n.smart-env-status-view__progress {\n height: 8px;\n border-radius: 999px;\n overflow: hidden;\n border: 1px solid var(--background-modifier-border);\n background: var(--background-secondary);\n}\n\n.smart-env-status-view__progress-fill {\n height: 100%;\n width: var(--smart-env-status-view-pct, 0%);\n background: var(--color-accent);\n}\n\n.smart-env-status-view__details {\n display: flex;\n flex-direction: column;\n gap: 0.45rem;\n}\n\n.smart-env-status-view__detail {\n padding: 0.55rem 0.7rem;\n border-radius: var(--radius-m);\n background: var(--background-secondary);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n line-height: 1.35;\n}\n\n.smart-env-status-view__actions {\n display: flex;\n flex-wrap: wrap;\n gap: 0.5rem;\n}\n\n.smart-env-status-view__btn {\n appearance: none;\n border: 1px solid var(--background-modifier-border);\n background: var(--background-modifier-form-field);\n color: var(--text-normal);\n border-radius: 10px;\n padding: 0.5rem 0.8rem;\n cursor: pointer;\n transition: background 120ms ease, border-color 120ms ease, transform 120ms ease;\n}\n\n.smart-env-status-view__btn:hover {\n background: var(--background-modifier-hover);\n}\n\n.smart-env-status-view__btn:active {\n transform: translateY(1px);\n}\n\n.smart-env-status-view__btn:focus-visible {\n outline: 2px solid var(--color-accent);\n outline-offset: 2px;\n}\n\n.smart-env-status-view__btn--primary {\n background: var(--color-accent);\n border-color: transparent;\n color: var(--text-on-accent, var(--text-normal));\n}\n\n.smart-env-status-view__btn--primary:hover {\n background: var(--interactive-accent-hover);\n}\n"; + +// node_modules/obsidian-smart-env/src/utils/status_bar_state.js +var indicator_level_rank = { + info: 1, + milestone: 2, + attention: 3, + warning: 4, + error: 5 +}; +function get_unseen_notification_entries(event_logs) { + const session_events = event_logs?.session_events; + if (!Array.isArray(session_events)) return []; + return session_events.filter((entry) => entry?.unseen === true); +} +function get_notification_event_count(event_logs) { + return get_unseen_notification_entries(event_logs).length; +} +function get_next_indicator_level(current_level, event_key = "", event = {}) { + const next_level = get_event_level(event_key, event); + if (!next_level) return current_level ?? null; + const current_rank = indicator_level_rank[current_level] || 0; + const next_rank = indicator_level_rank[next_level] || 0; + if (next_rank > current_rank) return next_level; + return current_level ?? next_level; +} +function get_notification_indicator_level(event_logs) { + return get_unseen_notification_entries(event_logs).reduce((current_level, entry) => { + return get_next_indicator_level(current_level, entry?.event_key, entry?.event); + }, null); +} +function get_status_bar_notice_line(event_key = "", event = {}) { + const message = get_native_notice_message(event_key, event); + return String(message || "").split(/\r?\n/u)[0].trim(); +} +function format_status_bar_notice_message(value = "") { + const text = typeof value === "string" ? value.trim() : ""; + if (!text) return ""; + if (text.length <= 20) return text; + return `${text.slice(0, 20)}\u2026`; +} +function get_status_bar_notice_preview(event_logs) { + const session_events = event_logs?.session_events; + if (!Array.isArray(session_events) || session_events.length === 0) return null; + for (let i = session_events.length - 1; i >= 0; i -= 1) { + const entry = session_events[i]; + const event_key = typeof entry?.event_key === "string" ? entry.event_key : ""; + const event = entry?.event && typeof entry.event === "object" ? entry.event : {}; + if (!get_event_level(event_key, event)) continue; + if (entry?.native_notice_shown === true) return null; + const timeout_ms = get_native_notice_timeout(event_key, event); + const expires_at = get_entry_timestamp(entry) + timeout_ms; + if (Date.now() > expires_at) return null; + const title = get_status_bar_notice_line(event_key, event); + const message = format_status_bar_notice_message(title); + if (!message) return null; + return { message, title }; + } + return null; +} +function get_import_progress_state(env) { + return env?.smart_sources?.get_import_progress_state?.() || null; +} +function get_embed_progress_state(env) { + return env?.smart_sources?.entities_vector_adapter?.get_progress_state?.() || null; +} +function get_reimport_queue_count(env) { + return Object.keys(env?.smart_sources?.sources_re_import_queue || {}).length; +} +function get_progress_pct(progress, total) { + if (typeof progress !== "number" || typeof total !== "number") return null; + if (total <= 0) return null; + return Math.round(progress / total * 1e3) / 10; +} +function normalize_number(value) { + return typeof value === "number" && Number.isFinite(value) ? value : 0; +} +function to_non_empty_string(value) { + return typeof value === "string" ? value.trim() : ""; +} +function format_collection_label(collection_key = "") { + return collection_key.split("_").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" "); +} +function get_collection_loading_state(env) { + const collection_entries = Object.entries(env?.collections || {}).filter(([collection_key]) => Boolean(collection_key)); + const total = collection_entries.length; + const loaded = collection_entries.filter(([, state]) => state === "loaded").length; + const pending_entries = collection_entries.filter(([, state]) => state !== "loaded"); + const current_key = pending_entries[0]?.[0] || ""; + return { + total, + loaded, + pending: Math.max(0, total - loaded), + current_key, + current_label: current_key ? format_collection_label(current_key) : "" + }; +} +function get_env_activity_state(env) { + const import_progress = get_import_progress_state(env); + const embed_progress = get_embed_progress_state(env); + const reimport_queue_count = get_reimport_queue_count(env); + const version = `v${env?.constructor?.version || ""}`; + const default_message = `${env?.is_pro ? "Pro" : "Smart"} ${version}`; + if (import_progress?.active) { + const progress = normalize_number(import_progress.progress); + const total = normalize_number(import_progress.total); + const stage = to_non_empty_string(import_progress.stage) || "importing"; + const is_reimporting = stage === "reimporting"; + return { + kind: is_reimporting ? "reimporting" : "importing", + message: `${is_reimporting ? "Re-importing" : "Importing"} ${progress}/${total}`, + title: is_reimporting ? "Smart Environment is re-importing queued sources." : "Smart Environment is importing sources.", + click_action: "noop", + indicator_level: null, + progress_value: progress, + progress_total: total, + progress_pct: get_progress_pct(progress, total), + view_title: is_reimporting ? "Re-importing sources" : "Importing sources", + view_status: `${progress}/${total}`, + view_summary: is_reimporting ? "Refreshing queued source changes before embeddings continue." : "Discovering and importing sources into Smart Environment.", + view_details: [ + reimport_queue_count > 0 ? `${reimport_queue_count} additional source${reimport_queue_count === 1 ? "" : "s"} queued.` : "", + env?.state === "loading" ? "Smart Environment is still loading in the background." : "" + ].filter(Boolean), + view_actions: [] + }; + } + if (embed_progress?.active) { + const progress = normalize_number(embed_progress.progress); + const total = normalize_number(embed_progress.total); + const paused = Boolean(embed_progress.paused); + const tokens_per_second = normalize_number(embed_progress.tokens_per_second); + const model_name = to_non_empty_string(embed_progress.model_name); + const reason = to_non_empty_string(embed_progress.reason); + return { + kind: paused ? "embed_paused" : "embed_active", + message: `${paused ? "Embedding paused" : "Embedding"} ${progress}/${total}`, + title: paused ? "Click to resume embedding." : "Click to pause embedding.", + click_action: paused ? "resume_embed" : "pause_embed", + indicator_level: paused ? "attention" : "milestone", + progress_value: progress, + progress_total: total, + progress_pct: get_progress_pct(progress, total), + view_title: paused ? "Embedding paused" : "Embedding in progress", + view_status: `${progress}/${total}`, + view_summary: model_name ? `Using ${model_name} for embeddings.` : "Generating embeddings for imported content.", + view_details: [ + tokens_per_second > 0 ? `${tokens_per_second} tokens/sec` : "", + reason, + reimport_queue_count > 0 ? `${reimport_queue_count} source${reimport_queue_count === 1 ? "" : "s"} still queued for re-import.` : "" + ].filter(Boolean), + view_actions: [paused ? "resume_embed" : "pause_embed"] + }; + } + if (env?.state === "loading") { + const collection_loading = get_collection_loading_state(env); + const collection_progress_value = collection_loading.total > 0 ? collection_loading.loaded : null; + const collection_progress_total = collection_loading.total > 0 ? collection_loading.total : null; + const current_collection_label = collection_loading.current_label; + const collection_status = collection_loading.total > 0 ? `${collection_loading.loaded}/${collection_loading.total}` : "In progress"; + return { + kind: "loading", + message: current_collection_label ? `Loading ${current_collection_label}\u2026` : "Loading Smart Env\u2026", + title: current_collection_label ? `Smart Environment is loading ${current_collection_label}.` : "Smart Environment is loading.", + click_action: "noop", + indicator_level: null, + progress_value: collection_progress_value, + progress_total: collection_progress_total, + progress_pct: get_progress_pct(collection_progress_value, collection_progress_total), + view_title: "Loading Smart Environment", + view_status: current_collection_label ? `${current_collection_label} \u2022 ${collection_status}` : collection_status, + view_summary: current_collection_label ? `Loading collection: ${current_collection_label}.` : "Preparing collections, sources, and shared plugin state.", + view_details: [ + collection_loading.total > 0 ? `${collection_loading.loaded} of ${collection_loading.total} collection${collection_loading.total === 1 ? "" : "s"} loaded.` : "", + collection_loading.pending > 0 ? `${collection_loading.pending} collection${collection_loading.pending === 1 ? "" : "s"} remaining.` : "", + reimport_queue_count > 0 ? `${reimport_queue_count} source${reimport_queue_count === 1 ? "" : "s"} queued for re-import after load.` : "" + ].filter(Boolean), + view_actions: [] + }; + } + if (reimport_queue_count > 0) { + return { + kind: "reimport_queued", + message: `Re-import (${reimport_queue_count})`, + title: "Click to re-import queued sources.", + click_action: "run_reimport", + indicator_level: "attention", + progress_value: null, + progress_total: null, + progress_pct: null, + view_title: "Queued re-import work", + view_status: `${reimport_queue_count} queued`, + view_summary: "Run re-import to refresh changed sources and resume downstream embedding work.", + view_details: [], + view_actions: ["run_reimport"] + }; + } + if (env?.state === "loaded") { + return { + kind: "ready", + message: default_message, + title: "Smart Environment status", + click_action: "context_menu", + indicator_level: null, + progress_value: null, + progress_total: null, + progress_pct: null, + view_title: "Smart Environment ready", + view_status: "Ready", + view_summary: "No active import or embedding work is running right now.", + view_details: [], + view_actions: ["open_notifications"] + }; + } + return { + kind: "not_loaded", + message: default_message, + title: "Smart Environment status", + click_action: "context_menu", + indicator_level: null, + progress_value: null, + progress_total: null, + progress_pct: null, + view_title: "Smart Environment not loaded", + view_status: "Idle", + view_summary: "Load Smart Environment to enable Smart Plugins.", + view_details: [], + view_actions: ["load_env"] + }; +} +function should_poll_env_activity(env) { + const activity_state = get_env_activity_state(env); + if (get_status_bar_notice_preview(env?.event_logs)) return true; + return [ + "embed_active", + "embed_paused", + "importing", + "reimporting", + "loading", + "not_loaded" + ].includes(activity_state.kind); +} +function get_status_bar_state(env) { + const notification_count = get_notification_event_count(env?.event_logs); + const notification_indicator_level = get_notification_indicator_level(env?.event_logs); + const activity_state = get_env_activity_state(env); + const embed_queue_count = get_reimport_queue_count(env); + const status_bar_notice_preview = get_status_bar_notice_preview(env?.event_logs); + const can_show_notice_preview = Boolean(status_bar_notice_preview) && !["embed_active", "embed_paused", "importing", "reimporting", "loading"].includes(activity_state.kind); + let title = activity_state.title; + let indicator_level = activity_state.indicator_level; + if (!indicator_level && notification_count > 0) { + indicator_level = notification_indicator_level; + } + if (activity_state.kind === "ready" && notification_count > 0) { + title = `${notification_count} unseen notification${notification_count === 1 ? "" : "s"}`; + } + if (can_show_notice_preview && status_bar_notice_preview?.title) { + title = status_bar_notice_preview.title; + } + return { + message: can_show_notice_preview ? status_bar_notice_preview.message : activity_state.message, + title, + indicator_count: notification_count, + indicator_level, + embed_queue_count, + click_action: activity_state.click_action + }; +} +function get_entry_timestamp(entry) { + if (typeof entry?.event?.at === "number") return entry.event.at; + if (typeof entry?.at === "number") return entry.at; + return Date.now(); +} + +// node_modules/obsidian-smart-env/src/components/env_status.js +function build_html3() { + return `
+
+
Smart Environment
+

+
+
+
+ +
+
+
`; +} +async function render4(env, params = {}) { + this.apply_style_sheet(env_status_default); + const frag = this.create_doc_fragment(build_html3()); + const container = frag.firstElementChild; + post_process3.call(this, env, container, params); + return container; +} +async function post_process3(env, container, params = {}) { + const { + live_updates = true, + event = null, + event_key = "" + } = params; + if (event_key) { + container.dataset.eventKey = event_key; + } + if (typeof event?.level === "string" && event.level.trim()) { + container.dataset.eventLevel = event.level.trim(); + } + const title_el = container.querySelector(".smart-env-status-view__title"); + const status_el = container.querySelector(".smart-env-status-view__status"); + const summary_el = container.querySelector(".smart-env-status-view__summary"); + const progress_el = container.querySelector(".smart-env-status-view__progress"); + const progress_fill_el = container.querySelector(".smart-env-status-view__progress-fill"); + const details_el = container.querySelector(".smart-env-status-view__details"); + const actions_el = container.querySelector(".smart-env-status-view__actions"); + const render_state = () => { + const view_state = get_env_activity_state(env); + set_text(title_el, view_state.view_title); + set_text(status_el, view_state.view_status, { hide_empty: true }); + set_text(summary_el, view_state.view_summary, { hide_empty: true }); + render_progress(progress_el, progress_fill_el, view_state); + render_details(details_el, view_state.view_details); + render_actions(actions_el, env, view_state.view_actions, { + event, + event_key + }); + }; + let polling_interval = null; + const set_polling = (active) => { + if (active) { + if (polling_interval) return; + polling_interval = setInterval(() => { + render_state(); + set_polling(should_poll_env_activity(env)); + }, 1e3); + return; + } + if (!polling_interval) return; + clearInterval(polling_interval); + polling_interval = null; + }; + let debounce_timeout = null; + const refresh = () => { + if (debounce_timeout) clearTimeout(debounce_timeout); + debounce_timeout = setTimeout(() => { + debounce_timeout = null; + render_state(); + set_polling(should_poll_env_activity(env)); + }, 100); + }; + render_state(); + if (!live_updates) return container; + set_polling(should_poll_env_activity(env)); + const disposers = []; + if (typeof env?.events?.on === "function") { + disposers.push(env.events.on("*", refresh)); + } + disposers.push(() => { + set_polling(false); + if (debounce_timeout) clearTimeout(debounce_timeout); + }); + this.attach_disposer(container, disposers); + return container; +} +function set_text(element, value, params = {}) { + if (!element) return; + const { hide_empty = false } = params; + const text = typeof value === "string" ? value : ""; + element.textContent = text; + if (hide_empty) element.hidden = !text; +} +function render_progress(progress_el, progress_fill_el, view_state) { + const progress_pct = typeof view_state.progress_pct === "number" ? Math.max(0, Math.min(100, view_state.progress_pct)) : null; + if (progress_pct === null) { + progress_el.hidden = true; + progress_fill_el.style.width = "0%"; + progress_el.removeAttribute("aria-valuenow"); + progress_el.setAttribute("aria-valuemax", "100"); + return; + } + progress_el.hidden = false; + progress_fill_el.style.width = `${progress_pct}%`; + if (typeof view_state.progress_value === "number") { + progress_el.setAttribute("aria-valuenow", String(view_state.progress_value)); + } else { + progress_el.removeAttribute("aria-valuenow"); + } + progress_el.setAttribute( + "aria-valuemax", + String(typeof view_state.progress_total === "number" && view_state.progress_total > 0 ? view_state.progress_total : 100) + ); +} +function render_details(details_el, details = []) { + details_el.replaceChildren(); + details.filter(Boolean).forEach((detail) => { + const detail_el = details_el.ownerDocument.createElement("div"); + detail_el.className = "smart-env-status-view__detail"; + detail_el.textContent = detail; + details_el.appendChild(detail_el); + }); + details_el.hidden = details_el.childElementCount === 0; +} +function render_actions(actions_el, env, action_keys = [], params = {}) { + actions_el.replaceChildren(); + action_keys.forEach((action_key) => { + const btn = actions_el.ownerDocument.createElement("button"); + btn.type = "button"; + btn.className = "smart-env-status-view__btn"; + bind_action_button(btn, env, action_key, params); + actions_el.appendChild(btn); + }); + actions_el.hidden = actions_el.childElementCount === 0; +} +function get_action_label(action_key) { + switch (action_key) { + case "load_env": + return "Load Smart Environment"; + case "pause_embed": + return "Pause embedding"; + case "resume_embed": + return "Resume embedding"; + case "run_reimport": + return "Run re-import"; + case "open_notifications": + return "Open events feed"; + default: + return action_key; + } +} +function bind_action_button(btn, env, action_key, params = {}) { + const label = get_action_label(action_key); + btn.textContent = label; + if (["load_env", "resume_embed", "run_reimport"].includes(action_key)) { + btn.classList.add("smart-env-status-view__btn--primary"); + } + btn.addEventListener("click", async () => { + await run_action_key(env, action_key, params); + }); +} +async function run_action_key(env, action_key, params = {}) { + switch (action_key) { + case "load_env": + if (typeof env?.start_mobile_env_load === "function") { + await env.start_mobile_env_load({ + source: "env_status_component", + open_progress_view: false, + event: params.event, + event_key: params.event_key + }); + return; + } + await env?.load?.(true); + return; + case "pause_embed": + env?.smart_sources?.entities_vector_adapter?.halt_embed_queue_processing?.(); + return; + case "resume_embed": + env?.smart_sources?.entities_vector_adapter?.resume_embed_queue_processing?.(); + return; + case "run_reimport": + await env?.run_re_import?.(); + return; + case "open_notifications": + env?.open_notifications_feed_modal?.(); + return; + default: + return; + } +} + +// node_modules/obsidian-smart-env/src/components/lean_coffee_callout.js +var import_obsidian13 = require("obsidian"); +function build_html4(env, opts = {}) { + return `
+
+
+
+ + + + + +
+
+ Community Lean Coffee +
+
+
+

+ Ask questions. Bring challenges. Request features. Show workflows. Be ready to share. +
+ Join the next Community Lean Coffee meeting. Unable to attend? Submit a question here \u{1F334} +

+
+
+
`; +} +function render5(env, opts = {}) { + const html = build_html4.call(this, env, opts); + const frag = this.create_doc_fragment(html); + const callout = frag.querySelector("#lean-coffee-callout"); + const icon_container = callout.querySelector(".callout-icon"); + const icon = (0, import_obsidian13.getIcon)("smart-chat"); + if (icon) { + this.empty(icon_container); + icon_container.appendChild(icon); + } + post_process4.call(this, env, callout, opts); + return callout; +} +function post_process4(env, callout) { +} + +// node_modules/obsidian-smart-env/src/components/milestone_notification.js +var import_obsidian14 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/components/milestone_notification.css +var milestone_notification_default = ".notice:has(.smart-env-milestone-notice) {\n padding: 0;\n border: none;\n background: transparent;\n box-shadow: none;\n min-width: 0;\n max-width: min(460px, calc(100vw - 2rem));\n}\n\n.notice-message:has(.smart-env-milestone-notice) {\n padding: 0;\n background: transparent;\n}\n\n.smart-env-milestone-notice {\n width: 100%;\n min-width: 0;\n font-family: var(--font-interface);\n}\n\n.smart-env-milestone-notice__surface {\n display: grid;\n grid-template-columns: auto 1fr;\n gap: var(--size-4-3);\n align-items: start;\n\n width: 100%;\n box-sizing: border-box;\n padding: var(--size-4-4);\n border: 1px solid var(--background-modifier-border);\n border-inline-start: 3px solid var(--color-accent);\n border-radius: var(--radius-xl);\n background: var(--background-secondary);\n}\n\n.smart-env-milestone-notice__icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n align-self: start;\n\n width: calc(var(--icon-l) + var(--size-4-6));\n height: calc(var(--icon-l) + var(--size-4-6));\n padding: var(--size-2-2);\n\n color: var(--color-accent);\n background: var(--background-primary);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-l);\n}\n\n.smart-env-milestone-notice__icon svg {\n width: var(--icon-l);\n height: var(--icon-l);\n}\n\n.smart-env-milestone-notice__body {\n min-width: 0;\n}\n\n.smart-env-milestone-notice__eyebrow {\n margin: 0 0 var(--size-2-1);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n font-weight: var(--font-semibold);\n letter-spacing: 0.08em;\n line-height: 1.1;\n text-transform: uppercase;\n}\n\n.smart-env-milestone-notice__title {\n color: var(--text-normal);\n font-size: var(--h3-size);\n font-weight: var(--font-semibold);\n line-height: 1.15;\n white-space: pre-wrap;\n}\n\n.smart-env-milestone-notice__details {\n margin-top: var(--size-2-3);\n color: var(--text-muted);\n font-size: var(--font-text-size);\n line-height: 1.42;\n white-space: pre-wrap;\n}\n\n.smart-env-milestone-notice__actions {\n display: flex;\n align-items: center;\n gap: var(--size-4-2);\n flex-wrap: wrap;\n margin-top: var(--size-4-3);\n}\n\n.smart-env-milestone-notice__button {\n margin: 0;\n}\n\n.smart-env-milestone-notice__link {\n color: var(--text-accent, var(--color-accent));\n font-size: var(--font-ui-small);\n text-decoration: underline;\n text-decoration-thickness: 1px;\n text-underline-offset: 2px;\n}\n\n@media (max-width: 520px) {\n .notice:has(.smart-env-milestone-notice) {\n max-width: calc(100vw - 1.2rem);\n }\n\n .smart-env-milestone-notice__surface {\n grid-template-columns: 1fr;\n gap: var(--size-4-2);\n padding: var(--size-4-3);\n }\n}\n"; + +// node_modules/obsidian-smart-env/src/components/milestone_notification.js +function get_milestone_notice_title(event = {}) { + if (typeof event?.message === "string" && event.message.trim()) return event.message.trim(); + return "You achieved a new Smart Milestone"; +} +function get_milestone_notice_details(event = {}) { + if (typeof event?.details === "string" && event.details.trim()) return event.details.trim(); + if (typeof event?.milestone === "string" && event.milestone.trim()) return event.milestone.trim(); + return ""; +} +function get_milestone_notice_summary(event = {}) { + const title = get_milestone_notice_title(event); + const details = get_milestone_notice_details(event); + if (!details) return title; + return `${title} +${details}`; +} +function to_trimmed_string3(value) { + return typeof value === "string" ? value.trim() : ""; +} +function render_icon2(icon_el) { + if (!icon_el) return; + try { + (0, import_obsidian14.setIcon)(icon_el, "sparkles"); + } catch (_error) { + } + if (!icon_el.querySelector("svg")) { + icon_el.textContent = "\u2605"; + } +} +function open_notifications_feed2(env, params = {}) { + const { + on_open_feed = null, + event_key = "", + event = {} + } = params; + const open_params = { + event_key, + event + }; + if (typeof on_open_feed === "function") { + return on_open_feed(open_params) !== false; + } + if (typeof env?.open_notifications_feed_modal === "function") { + env.open_notifications_feed_modal(open_params); + return true; + } + return false; +} +function render6(env, params = {}) { + const { + event_key = "", + event = {}, + on_action = null, + on_open_feed = null + } = params; + const title = get_milestone_notice_title(event); + const details = get_milestone_notice_details(event); + const btn_text = to_trimmed_string3(event?.btn_text); + const btn_callback = to_trimmed_string3(event?.btn_callback); + const help_link = to_trimmed_string3(event?.link); + if (typeof document === "undefined") { + return get_milestone_notice_summary(event); + } + this.apply_style_sheet?.(milestone_notification_default); + const run_action = typeof on_action === "function" ? on_action : (callback_key) => dispatch_notice_action(env, callback_key, { + event_source: "milestone_notification", + source_event_key: event_key, + source_event: event + }); + const can_open_feed = typeof on_open_feed === "function" || typeof env?.open_notifications_feed_modal === "function"; + const can_view_more = can_open_feed && Boolean(event_key); + const frag = document.createDocumentFragment(); + const wrapper = document.createElement("div"); + wrapper.className = "smart-env-milestone-notice"; + const surface_el = document.createElement("div"); + surface_el.className = "smart-env-milestone-notice__surface"; + const icon_el = document.createElement("div"); + icon_el.className = "smart-env-milestone-notice__icon"; + render_icon2(icon_el); + const body_el = document.createElement("div"); + body_el.className = "smart-env-milestone-notice__body"; + const eyebrow_el = document.createElement("div"); + eyebrow_el.className = "smart-env-milestone-notice__eyebrow"; + eyebrow_el.textContent = "Smart Milestone"; + const title_el = document.createElement("div"); + title_el.className = "smart-env-milestone-notice__title"; + title_el.textContent = title; + body_el.appendChild(eyebrow_el); + body_el.appendChild(title_el); + if (details) { + const details_el = document.createElement("div"); + details_el.className = "smart-env-milestone-notice__details"; + details_el.textContent = details; + body_el.appendChild(details_el); + } + const should_render_actions = Boolean(btn_text && btn_callback) || Boolean(help_link) || can_view_more; + if (should_render_actions) { + const actions_el = document.createElement("div"); + actions_el.className = "smart-env-milestone-notice__actions"; + if (btn_text && btn_callback) { + const button_el = document.createElement("button"); + button_el.type = "button"; + button_el.className = "smart-env-milestone-notice__button mod-cta"; + button_el.textContent = btn_text; + button_el.setAttribute("aria-label", btn_text); + button_el.addEventListener("click", () => { + run_action(btn_callback); + }); + actions_el.appendChild(button_el); + } + if (can_view_more) { + const view_more_btn_el = document.createElement("button"); + view_more_btn_el.type = "button"; + view_more_btn_el.className = "smart-env-milestone-notice__button"; + view_more_btn_el.textContent = "View more"; + view_more_btn_el.setAttribute("aria-label", "Open this event in the notifications feed"); + view_more_btn_el.addEventListener("click", () => { + open_notifications_feed2(env, { + on_open_feed, + event_key, + event + }); + }); + actions_el.appendChild(view_more_btn_el); + } + if (help_link) { + const help_el = document.createElement("a"); + help_el.className = "smart-env-milestone-notice__link"; + help_el.href = help_link; + help_el.textContent = "Learn more"; + help_el.target = "_external"; + help_el.rel = "noopener noreferrer"; + help_el.setAttribute("aria-label", "Learn more about this milestone"); + actions_el.appendChild(help_el); + } + body_el.appendChild(actions_el); + } + surface_el.appendChild(icon_el); + surface_el.appendChild(body_el); + wrapper.appendChild(surface_el); + frag.appendChild(wrapper); + return frag; +} + +// node_modules/obsidian-smart-env/src/components/milestones.css +var milestones_default = `.sc-events-checklist { + display: flex; + flex-direction: column; + gap: var(--size-4-3); + padding: var(--size-4-2); + + .sc-events-checklist__header { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--size-4-3); + } + + .sc-events-checklist__title { + margin: 0; + font-size: var(--h2-size); + } + + .sc-events-checklist__summary { + font-size: var(--font-ui-small); + color: var(--text-normal); + font-variant-numeric: tabular-nums; + + padding: 0.15em 0.6em; + border-radius: 999px; + background-color: var(--background-secondary); + border: 1px solid var(--background-modifier-border); + white-space: nowrap; + } + + .sc-events-checklist__hint { + font-size: var(--font-ui-small); + color: var(--text-muted); + text-align: right; + } + + .sc-events-checklist__progress { + height: 6px; + border-radius: 999px; + overflow: hidden; + background-color: var(--background-secondary); + border: 1px solid var(--background-modifier-border); + } + + .sc-events-checklist__progress-fill { + height: 100%; + width: var(--sc-events-checklist-progress, 0%); + background-color: var(--color-green, var(--color-accent)); + } + + .sc-events-checklist__group { + border-top: 1px solid var(--background-modifier-border); + padding-top: var(--size-4-3); + } + + .sc-events-checklist__group-title { + margin: 0 0 var(--size-4-2) 0; + font-size: var(--h3-size); + + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--size-4-2); + } + + .sc-events-checklist__group-name { + display: inline-flex; + align-items: center; + min-width: 0; + } + + .sc-events-checklist__group-count { + font-size: var(--font-ui-small); + color: var(--text-muted); + white-space: nowrap; + font-variant-numeric: tabular-nums; + flex: 0 0 auto; + } + + /* + Group completion badge: + - shows only when every item in the group is checked + - uses :has() (no JS needed) + - avoids false positives on empty groups by requiring at least one item + */ + .sc-events-checklist__group:has(.sc-events-checklist__item):not(:has(.sc-events-checklist__item[data-checked='false'])) { + .sc-events-checklist__group-name::after { + content: "DONE"; + + /* layout */ + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 0.5em; + padding: 0.08em 0.55em; + border-radius: 999px; + white-space: nowrap; + vertical-align: middle; + + /* typography */ + font-size: 0.65em; + font-weight: 700; + letter-spacing: 0.12em; + text-transform: uppercase; + + /* theme vars (with safe fallback) */ + color: var(--color-green, var(--text-accent)); + background-color: var(--background-secondary); + border: 1px solid var(--background-modifier-border); + + /* subtle depth, theme-aware */ + box-shadow: + 0 0 0 1px var(--background-primary), + 0 1px 3px rgba(0, 0, 0, 0.25); + transform: translateY(-0.03em); + } + } + + .sc-events-checklist__items { + list-style: none; + padding: 0; + margin: 0; + display: flex; + flex-direction: column; + gap: var(--size-2-2); + } + + .sc-events-checklist__item { + display: flex; + flex-direction: column; + gap: 2px; + padding: 6px 8px; + border-radius: var(--radius-s); + + cursor: pointer; + border: 1px solid transparent; + + transition: + background-color 120ms ease, + border-color 120ms ease, + transform 120ms ease; + + &:hover { + background: var(--background-modifier-hover); + border-color: var(--background-modifier-border); + } + + &:active { + transform: translateY(1px); + } + + &:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; + background: var(--background-modifier-hover); + border-color: var(--color-accent); + } + } + + .sc-events-checklist__label { + display: flex; + align-items: flex-start; + gap: var(--size-2-2); + cursor: pointer; + } + + .sc-events-checklist__icon { + display: inline-flex; + align-items: center; + justify-content: center; + margin-top: 2px; + width: 18px; + height: 18px; + flex: 0 0 auto; + color: var(--text-muted); + + svg { + width: 18px; + height: 18px; + } + } + + .sc-events-checklist__milestone { + line-height: 1.3; + user-select: text; + cursor: text; + } + + .sc-events-checklist__item[data-checked='true'] { + .sc-events-checklist__icon { + color: var(--color-green, var(--text-accent)); + } + + .sc-events-checklist__milestone { + color: var(--text-normal); + } + } +} + +/* 1) Host elements that should get a PRO badge */ +.sc-events-checklist__label.pro-milestone > .sc-events-checklist__milestone { + position: relative; /* safe default, keeps ::after anchored */ +} + +/* 2) The PRO badge itself */ +.sc-events-checklist__label.pro-milestone > .sc-events-checklist__milestone::after { + content: "PRO"; + + /* layout */ + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 0.4em; + padding: 0.08em 0.55em; + border-radius: 999px; + white-space: nowrap; + vertical-align: middle; + + /* typography */ + font-size: 0.7em; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + line-height: 1; + + /* color system: only Obsidian variables */ + background-color: var(--color-accent); + background-image: linear-gradient( + 135deg, + var(--color-accent), + var(--interactive-accent-hover) + ); + color: var(--text-on-accent, var(--background-primary)); + border: 1px solid var(--background-modifier-border); + + /* subtle separation & depth, theme-aware */ + box-shadow: + 0 0 0 1px var(--background-primary), + 0 1px 3px rgba(0, 0, 0, 0.35); + transform: translateY(-0.03em); +} + +/* 3) Interactive refinement: follow Obsidian's accent hover behavior */ +.sc-events-checklist__label.pro-milestone > .sc-events-checklist__milestone:hover::after { + background-color: var(--interactive-accent-hover); + filter: brightness(1.05); +} + +/* Milestones modal: title row help icon */ +.sc-milestones-modal__title { + width: 100%; +} + +.sc-milestones-modal__title-row { + display: flex; + align-items: center; + gap: var(--size-4-2); + width: 100%; +} + +.sc-milestones-modal__title-text { + min-width: 0; +} + +.sc-milestones-modal__help-btn { + display: inline-flex; + align-items: center; + justify-content: center; + + width: 28px; + height: 28px; + padding: 0; + border-radius: var(--radius-s); + + background: transparent; + border: 1px solid transparent; + color: var(--text-muted); + + cursor: pointer; +} + +.sc-milestones-modal__help-btn svg { + width: 18px; + height: 18px; +} + +.sc-milestones-modal__help-btn:hover { + background: var(--background-modifier-hover); + border-color: var(--background-modifier-border); + color: var(--text-normal); +} + +.sc-milestones-modal__help-btn:active { + transform: translateY(1px); +} + +.sc-milestones-modal__help-btn:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; + background: var(--background-modifier-hover); + border-color: var(--color-accent); +} +`; + +// node_modules/obsidian-smart-env/src/components/milestones.js +var import_obsidian15 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/onboarding_events_data.js +var PLUGIN_INSTALL_EVENT_CONFIG = { + "connections:installed": { + ids: ["smart-connections"] + }, + "connections_pro:installed": { + ids: ["smart-connections"], + require_pro_name: true + }, + "context:installed": { + ids: ["smart-context"] + }, + "context_pro:installed": { + ids: ["smart-context"], + require_pro_name: true + }, + "chat:installed": { + ids: ["smart-chatgpt", "smart-chat"] + }, + "chat_pro:installed": { + ids: ["smart-chat"], + require_pro_name: true + } +}; +var EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY = { + // Environment + "sources:import_completed": { + group: "Environment", + milestone: "Initial vault import completed (all sources discovered).", + link: "https://smartconnections.app/smart-environment/settings/?utm_source=milestones#sources" + }, + "embedding:completed": { + group: "Environment", + milestone: "Initial embedding completed, you are ready to make connections!", + link: "https://smartconnections.app/smart-environment/settings/?utm_source=milestones#embedding-models" + }, + // Connections + "connections:installed": { + group: "Connections", + milestone: "Installed Smart Connections (core plugin).", + link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones" + }, + "connections:opened": { + group: "Connections", + milestone: "Opened the connections view.", + link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#quick-start" + }, + "connections:drag_result": { + group: "Connections", + milestone: "Dragged a Smart Connections result into a note to create a link.", + link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#drag-link" + }, + "connections:open_result": { + group: "Connections", + milestone: "Opened a Smart Connections result from the UI (list item or inline popover).", + link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#core-interactions" + }, + "connections:sent_to_context": { + group: "Connections", + milestone: "Sent Connections results to Smart Context (turn discovery into a context pack).", + link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#send-to-context" + }, + "connections:copied_list": { + group: "Connections", + milestone: "Copied Connections results as a list of links.", + link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#copy-list" + }, + "connections:hover_preview": { + group: "Connections", + milestone: "Previewed a connection by holding cmd/ctrl while hovering the result.", + link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#core-interactions" + }, + "connections:open_random": { + group: "Connections", + milestone: "Opened a random connection from Smart Connections.", + link: "https://smartconnections.app/smart-connections/getting-started/?utm_source=milestones#open-a-random-connection" + }, + "connections:hidden_item": { + group: "Connections", + milestone: "Hidden a connection item from the list.", + link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#manage-noise" + }, + "connections:pinned_item": { + group: "Connections", + milestone: "Pinned a connection item in the list.", + link: "https://smartconnections.app/smart-connections/list-feature/?utm_source=milestones#manage-noise" + }, + // Connections Pro + "connections_pro:installed": { + group: "Connections Pro", + milestone: "Installed Smart Connections Pro.", + link: "https://smartconnections.app/pro-plugins/?utm_source=milestones#connections-pro", + is_pro: true + }, + // Lookup + "lookup:hover_preview": { + group: "Lookup", + milestone: "Previewed a Smart Lookup result by holding cmd/ctrl while hovering.", + link: "https://smartconnections.app/smart-connections/lookup/?utm_source=milestones#understanding-results" + }, + "lookup:get_results": { + group: "Lookup", + milestone: "Submitted a lookup query (started a semantic search).", + link: "https://smartconnections.app/smart-connections/lookup/?utm_source=milestones" + }, + "lookup:drag_result": { + group: "Lookup", + milestone: "Dragged a Smart Lookup result into a note to create a link.", + link: "https://smartconnections.app/smart-connections/lookup/?utm_source=milestones#understanding-results" + }, + "lookup:open_result": { + group: "Lookup", + milestone: "Opened a Lookup result.", + link: "https://smartconnections.app/smart-connections/lookup/?utm_source=milestones#understanding-results" + }, + // Context + "context:created": { + group: "Context", + milestone: "First context created!", + link: "https://smartconnections.app/smart-context/builder/?utm_source=milestones#quick-start" + }, + "context:copied": { + group: "Context", + milestone: "Copied context to clipboard.", + link: "https://smartconnections.app/smart-context/clipboard/?utm_source=milestones#copy-current" + }, + "context:file_nav_copied": { + group: "Context", + milestone: "Copied context from the file navigator.", + link: "https://smartconnections.app/smart-context/clipboard/?utm_source=milestones#copy-selected" + }, + "context_selector:open": { + group: "Context", + milestone: "Opened the Context Builder selector modal.", + link: "https://smartconnections.app/smart-context/builder/?utm_source=milestones#open-builder" + }, + "context:named": { + group: "Context", + milestone: "Named a Smart Context (created a reusable saved context).", + link: "https://smartconnections.app/smart-context/builder/?utm_source=milestones#save-reuse" + }, + "context:renamed": { + group: "Context", + milestone: "Renamed a Smart Context (increased clarity).", + link: "https://smartconnections.app/smart-context/builder/?utm_source=milestones#save-reuse" + }, + "context:installed": { + group: "Context", + milestone: "Installed Smart Context (core plugin).", + link: "https://smartconnections.app/smart-context/builder/?utm_source=milestones" + }, + "context:copied_with_media": { + group: "Context Pro", + milestone: "Copied context with media (images/PDF pages) for multimodal workflows.", + link: "https://smartconnections.app/smart-context/clipboard/?utm_source=milestones#copy-modes", + is_pro: true + }, + "context:custom_template_set": { + group: "Context Pro", + milestone: "Set a custom context template.", + link: "https://smartconnections.app/smart-context/settings/?utm_source=milestones#context-templates", + is_pro: true + }, + "context_item:custom_template_set": { + group: "Context Pro", + milestone: "Set a custom context item template.", + link: "https://smartconnections.app/smart-context/settings/?utm_source=milestones#item-templates", + is_pro: true + }, + // Context Pro + "context_pro:installed": { + group: "Context Pro", + milestone: "Installed Smart Context Pro.", + link: "https://smartconnections.app/pro-plugins/?utm_source=milestones#context-pro", + is_pro: true + }, + // Chat + "chat:installed": { + group: "Chat", + milestone: "Installed Smart ChatGPT.", + link: "https://smartconnections.app/smart-chat/?utm_source=milestones" + }, + "chat_codeblock:saved_thread": { + group: "Chat", + milestone: "Started a chat in a Smart Chat codeblock (opened the loop).", + link: "https://smartconnections.app/smart-chat/codeblock/?utm_source=milestones#quick-start" + }, + "chat_codeblock:marked_active": { + group: "Chat", + milestone: "Marked a chat thread as active from the Smart Chat inbox.", + link: "https://smartconnections.app/smart-chat/codeblock/?utm_source=milestones#chat-inbox" + }, + "chat_codeblock:marked_done": { + group: "Chat", + milestone: "Marked the chat thread as done (closed the loop).", + link: "https://smartconnections.app/smart-chat/codeblock/?utm_source=milestones#chat-inbox" + }, + // Chat Pro + "chat_pro:installed": { + group: "Chat Pro", + milestone: "Installed Smart Chat Pro.", + link: "https://smartconnections.app/pro-plugins/?utm_source=milestones#chat-pro", + is_pro: true + }, + "completion:completed": { + group: "Chat Pro", + milestone: "Received the first Smart Chat response (a completion finished).", + link: "https://smartconnections.app/smart-chat/api-integration/?utm_source=milestones#quick-start", + is_pro: true + }, + // Connections Pro (Inline Connections) + "inline_connections:show": { + group: "Connections Pro", + milestone: "Opened inline connections in-note (used the inline workflow).", + link: "https://smartconnections.app/smart-connections/inline/?utm_source=milestones", + is_pro: true + }, + "inline_connections:open_result": { + group: "Connections Pro", + milestone: "Opened an inline connections result (navigated from discovery to source).", + link: "https://smartconnections.app/smart-connections/inline/?utm_source=milestones", + is_pro: true + }, + "inline_connections:drag_result": { + group: "Connections Pro", + milestone: "Inserted an inline link from an inline connection (converted discovery into a durable link).", + link: "https://smartconnections.app/smart-connections/inline/?utm_source=milestones", + is_pro: true + }, + // Connect Pro + "connect_pro:ping": { + group: "Connect Pro", + milestone: "Connect Pro ping observed (local route hit or tunnel health check ran).", + link: "https://smartconnections.app/connect-pro/?utm_source=milestones#health-check", + is_pro: true + }, + "connect_pro:request": { + group: "Connect Pro", + milestone: "Connect Pro request received (remote action hit /obsidian-cli).", + link: "https://smartconnections.app/connect-pro/?utm_source=milestones#request-flow", + is_pro: true + }, + // Pro + "smart_plugins_oauth_completed": { + group: "Pro", + milestone: "Connected account (enabled Pro plugins).", + link: "https://smartconnections.app/pro-plugins/?utm_source=milestones" + }, + "referrals:copied_link": { + group: "Pro", + milestone: "Copied your referral link to share Pro.", + link: "https://smartconnections.app/pro-plugins/?utm_source=milestones#referrals", + is_pro: true + }, + "referrals:opened_dashboard": { + group: "Pro", + milestone: "Opened the referrals dashboard to view bonuses.", + link: "https://smartconnections.app/my-referrals/?utm_source=milestones", + is_pro: true + } +}; +var EVENTS_CHECKLIST_GROUP_ORDER = [ + "Environment", + "Connections", + "Lookup", + "Context", + "Chat", + "Connections Pro", + "Context Pro", + "Chat Pro", + "Connect Pro", + "Pro" +]; +function derive_events_checklist_groups(items_by_event_key) { + const group_map = Object.entries(items_by_event_key || {}).reduce( + (acc, [event_key, item]) => { + const group = item?.group || "Other"; + if (!acc[group]) acc[group] = []; + acc[group].push({ event_key, group, milestone: item?.milestone || "", ...item }); + return acc; + }, + /** @type {Record>} */ + {} + ); + const all_groups = Object.keys(group_map); + const order_index = EVENTS_CHECKLIST_GROUP_ORDER.reduce( + (acc, name, idx) => { + acc[name] = idx; + return acc; + }, + /** @type {Record} */ + {} + ); + const sorted_groups = all_groups.sort((a, b) => { + const a_has = Object.prototype.hasOwnProperty.call(order_index, a); + const b_has = Object.prototype.hasOwnProperty.call(order_index, b); + if (a_has && b_has) return order_index[a] - order_index[b]; + if (a_has) return -1; + if (b_has) return 1; + return a.localeCompare(b); + }); + return sorted_groups.map((group) => { + const items = (group_map[group] || []).slice().sort((a, b) => a.event_key.localeCompare(b.event_key)); + return { group, items }; + }); +} +function check_if_event_emitted(env, event_key) { + const plugin_event_state = resolve_plugin_install_event(env, event_key); + if (plugin_event_state === true) return true; + if (env?.event_logs?.items?.[event_key]) return true; + if (plugin_event_state === false) return false; + return false; +} +function resolve_plugin_install_event(env, event_key) { + const config = PLUGIN_INSTALL_EVENT_CONFIG[event_key]; + if (!config) return null; + const manifests = env?.plugin?.app?.plugins?.manifests || {}; + const plugin_ids = Array.isArray(config.ids) ? config.ids : []; + for (const plugin_id of plugin_ids) { + const manifest = manifests[plugin_id]; + if (!manifest) continue; + if (config.require_pro_name && !is_pro_manifest(manifest)) continue; + return true; + } + return false; +} +function is_pro_manifest(manifest) { + const name = manifest?.name; + if (typeof name !== "string") return false; + return name.toLowerCase().includes("pro"); +} + +// node_modules/obsidian-smart-env/src/utils/onboarding_events.js +function is_valid_milestone_event(event_key, params = {}) { + const { items_by_event_key = {} } = params; + if (typeof event_key !== "string" || event_key.length === 0) return false; + return Boolean(items_by_event_key && event_key in items_by_event_key); +} +function register_first_of_event_notifications(env) { + if (typeof env?.events?.on !== "function") { + return () => { + }; + } + if (!(env._milestone_first_event_keys instanceof Set)) { + env._milestone_first_event_keys = /* @__PURE__ */ new Set(); + } + const handle_first_event = (data) => { + const event_key = data?.first_of_event_key; + if (!is_valid_milestone_event(event_key, { items_by_event_key: EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY })) return; + if (env._milestone_first_event_keys.has(event_key)) return; + const item = EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY[event_key]; + if (!item) return; + env._milestone_first_event_keys.add(event_key); + env.events.emit("milestones:first_achieved", { + level: "milestone", + message: "You achieved a new Smart Milestone", + details: item.milestone, + milestone: item.milestone, + link: item.link, + first_of_event_key: event_key, + event_source: "onboarding_events", + btn_text: "View milestones", + btn_callback: "milestones_modal:open" + }); + }; + const unsubscribe = env.events.on("event_log:first", handle_first_event); + return () => { + unsubscribe?.(); + }; +} + +// node_modules/obsidian-smart-env/src/components/milestones.js +function build_html5(env, params = {}) { + const groups = derive_events_checklist_groups(EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY); + const checked_count = groups.reduce((acc, g) => { + const c = g.items.reduce((inner, item) => { + return inner + (check_if_event_emitted(env, item.event_key) ? 1 : 0); + }, 0); + return acc + c; + }, 0); + const total_count = groups.reduce((acc, g) => acc + g.items.length, 0); + const progress_pct = total_count > 0 ? Math.round(checked_count / total_count * 100) : 0; + const groups_html = groups.map((group) => { + const group_checked_count = group.items.reduce((acc, item) => { + return acc + (check_if_event_emitted(env, item.event_key) ? 1 : 0); + }, 0); + const group_total_count = group.items.length; + const items_html = group.items.map((item) => { + const checked = check_if_event_emitted(env, item.event_key) === true; + return build_item_html(item, { checked }); + }).join("\n"); + return ` +
+

+ ${escape_html(group.group)} + ${group_checked_count.toString()} / ${group_total_count.toString()} +

+
    + ${items_html} +
+
+ `; + }).join("\n"); + return ` +
+
+
+ ${checked_count.toString()} / ${total_count.toString()} +
+ +
+ +
+ +
+ +
+ ${groups_html} +
+
+ `; +} +async function render7(env, params = {}) { + this.apply_style_sheet(milestones_default); + const html = build_html5.call(this, env, params); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process5.call(this, env, container, params); + return container; +} +async function post_process5(env, container, params = {}) { + attach_item_link_listeners(container); + render_item_state_icons(container); + return container; +} +function build_item_html(item, state) { + const checked = state.checked === true; + const checked_flag = checked ? "true" : "false"; + const link = typeof item.link === "string" ? item.link : ""; + const status_label = checked ? "Completed" : "Incomplete"; + const aria_label = `Open docs: ${item.milestone || item.event_key || "milestone"} (${status_label})`; + return ` +
  • +
    + + ${escape_html(item.milestone)} +
    +
  • + `; +} +function attach_item_link_listeners(container) { + if (!container) return; + if (container.getAttribute("data-links-enabled") === "true") return; + container.setAttribute("data-links-enabled", "true"); + container.addEventListener("click", (evt) => { + const item_el = get_item_el_from_event(container, evt); + if (!item_el) return; + const selection = window.getSelection(); + if (selection && selection.toString().length > 0) return; + open_item_link(item_el); + }); + container.addEventListener("keydown", (evt) => { + const key = evt && /** @type {KeyboardEvent} */ + evt.key; + if (key !== "Enter" && key !== " ") return; + const item_el = get_item_el_from_event(container, evt); + if (!item_el) return; + evt.preventDefault(); + open_item_link(item_el); + }); +} +function open_item_link(item_el) { + const link = get_item_link(item_el); + if (typeof link === "string" && link.length > 0) { + window.open(link, "_external"); + } +} +function render_item_state_icons(container) { + if (!container) return; + const item_els = Array.from(container.querySelectorAll(".sc-events-checklist__item")); + item_els.forEach((item_el) => { + const checked = item_el.getAttribute("data-checked") === "true"; + const icon_el = item_el.querySelector(".sc-events-checklist__icon"); + if (!icon_el) return; + set_item_icon( + /** @type {HTMLElement} */ + icon_el, + checked + ); + }); +} +function set_item_icon(icon_el, checked) { + const icon_ids = checked ? ["circle-check", "check-circle", "check"] : ["circle", "circle-dashed", "dot"]; + set_icon_with_fallback2(icon_el, icon_ids); +} +function set_icon_with_fallback2(icon_el, icon_ids) { + if (!icon_el) return; + const ids = Array.isArray(icon_ids) ? icon_ids : []; + for (const icon_id of ids) { + if (typeof icon_id !== "string" || icon_id.length === 0) continue; + icon_el.textContent = ""; + try { + (0, import_obsidian15.setIcon)(icon_el, icon_id); + } catch (err) { + continue; + } + if (icon_el.querySelector("svg")) return; + } +} +function get_item_el_from_event(container, evt) { + const target = evt && /** @type {any} */ + evt.target; + if (!target || typeof target.closest !== "function") return null; + const item_el = target.closest(".sc-events-checklist__item"); + if (!item_el) return null; + if (!container.contains(item_el)) return null; + return item_el; +} +function get_item_link(item_el) { + const data_link = item_el.getAttribute("data-link"); + if (typeof data_link === "string" && data_link.length > 0) return data_link; + const event_key = item_el.getAttribute("data-event-key"); + if (typeof event_key !== "string" || event_key.length === 0) return ""; + const item = EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY[event_key]; + if (!item || typeof item.link !== "string") return ""; + return item.link; +} + +// node_modules/obsidian-smart-env/src/components/notification_feed.css +var notification_feed_default = `/* Smart Env notifications feed modal */ + +.smart-env-notifications-modal { + width: min(900px, 92vw); + max-height: min(820px, 88vh); +} + +.smart-env-notifications-modal .modal-header { + padding: var(--size-4-4) var(--size-4-4) var(--size-4-2); +} + +.smart-env-notifications-modal .modal-title { + font-weight: var(--font-semibold); + letter-spacing: -0.01em; +} + +.smart-env-notifications-modal .modal-content { + padding: var(--size-4-3) var(--size-4-4) var(--size-4-4); +} + +.smart-env-notifications { + --smart-env-notifications-toolbar-radius: calc(var(--radius-l) + var(--size-2-2)); + --smart-env-notifications-filter-radius: 999px; + --smart-env-notifications-card-radius: calc(var(--radius-m) + var(--size-2-3)); + --smart-env-notifications-code-radius: var(--radius-m); + --smart-env-notifications-empty-radius: var(--radius-l); + --smart-env-notifications-shadow: var(--shadow-s); + --smart-env-notifications-shadow-hover: var(--shadow-l); + --smart-env-notifications-surface: linear-gradient( + 180deg, + color-mix(in srgb, var(--background-primary) 96%, var(--background-secondary)), + color-mix(in srgb, var(--background-primary) 89%, var(--background-secondary)) + ); + --smart-env-notifications-muted-accent: var(--status-bar-text-color, var(--text-muted)); + + display: flex; + flex-direction: column; + gap: var(--size-4-3); + min-width: 0; + font-family: var(--font-interface); +} + +.smart-env-notifications__sticky { + position: sticky; + top: 0; + z-index: 5; + padding-top: var(--size-2-1); + padding-bottom: var(--size-2-2); + margin-top: calc(var(--size-2-1) * -1); + background: linear-gradient( + to bottom, + var(--modal-background, var(--background-primary)) 76%, + color-mix(in srgb, var(--modal-background, var(--background-primary)) 0%, transparent) + ); +} + +.smart-env-notifications__toolbar { + display: flex; + flex-wrap: wrap; + align-items: center; + gap: var(--size-4-2) var(--size-4-3); + + padding: var(--size-4-2); + border-radius: var(--smart-env-notifications-toolbar-radius); + border: 1px solid var(--background-modifier-border); + background: var(--smart-env-notifications-surface); + box-shadow: var(--smart-env-notifications-shadow); + backdrop-filter: var(--blur-l); +} + +.smart-env-notifications__meta { + display: inline-flex; + align-items: center; + justify-content: flex-end; + gap: var(--size-4-2); + min-width: 0; + margin-inline-start: auto; +} + +.smart-env-notifications__summary { + color: var(--text-muted); + font-size: var(--font-ui-small); + user-select: none; + white-space: nowrap; + font-variant-numeric: tabular-nums; + overflow: hidden; + text-overflow: ellipsis; +} + +.smart-env-notifications__actions { + display: inline-flex; + align-items: center; + gap: var(--size-4-2); +} + +.copy-all-notifications-btn { + white-space: nowrap; +} + +/* Buttons */ +.smart-env-btn { + appearance: none; + box-sizing: border-box; + border: 1px solid var(--background-modifier-border); + background: var(--background-modifier-form-field); + color: var(--text-normal); + border-radius: var(--button-radius); + padding: var(--size-4-2) calc(var(--size-4-3) + var(--size-2-1)); + font: inherit; + font-size: var(--font-ui-small); + line-height: 1.1; + cursor: pointer; + transition: + background var(--anim-duration-fast) var(--anim-motion-smooth), + border-color var(--anim-duration-fast) var(--anim-motion-smooth), + transform var(--anim-duration-fast) var(--anim-motion-smooth), + box-shadow var(--anim-duration-fast) var(--anim-motion-smooth), + opacity var(--anim-duration-fast) var(--anim-motion-smooth); +} + +.smart-env-btn:hover { + background: var(--background-modifier-hover); +} + +.smart-env-btn:active { + transform: translateY(1px); +} + +.smart-env-btn:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; +} + +.smart-env-btn[disabled] { + opacity: 0.55; + cursor: default; + transform: none; +} + +.smart-env-btn--primary { + background: var(--color-accent); + border-color: transparent; + color: var(--text-on-accent, var(--text-normal)); +} + +.smart-env-btn--primary:hover { + background: var(--interactive-accent-hover); +} + +.smart-env-btn--ghost { + background: transparent; +} + +.smart-env-btn--ghost:hover { + background: var(--background-modifier-hover); +} + +.smart-env-btn.is-copied { + box-shadow: 0 0 0 2px color-mix(in srgb, var(--color-accent) 35%, transparent); +} + +/* Filter pills */ +.smart-env-notifications-filter-controls { + display: flex; + flex: 1 1 420px; + flex-wrap: wrap; + align-items: center; + gap: var(--size-2-2); + min-width: 0; + overflow-x: auto; + overscroll-behavior-x: contain; + scrollbar-width: none; + -ms-overflow-style: none; + padding: var(--size-2-2); +} + +.smart-env-notifications-filter-controls::-webkit-scrollbar { + display: none; +} + +.smart-env-notifications-filter { + all: unset; + box-sizing: border-box; + display: inline-flex; + align-items: center; + flex: 0 0 auto; + cursor: pointer; + user-select: none; + -webkit-tap-highlight-color: transparent; +} + +.smart-env-notifications-filter__content { + box-sizing: border-box; + display: inline-flex; + align-items: center; + gap: var(--size-2-2); + padding: calc(var(--size-4-1) + 1px) var(--size-4-3); + border-radius: var(--smart-env-notifications-filter-radius); + border: 1px solid transparent; + background: transparent; + font-size: var(--font-ui-small); + line-height: 1.1; + font-variant-numeric: tabular-nums; + white-space: nowrap; + transition: + background var(--anim-duration-fast) var(--anim-motion-smooth), + border-color var(--anim-duration-fast) var(--anim-motion-smooth), + color var(--anim-duration-fast) var(--anim-motion-smooth), + box-shadow var(--anim-duration-fast) var(--anim-motion-smooth), + transform var(--anim-duration-fast) var(--anim-motion-smooth); +} + +.smart-env-notifications-filter:hover .smart-env-notifications-filter__content { + background: color-mix(in srgb, var(--background-modifier-hover) 70%, transparent); + color: var(--text-normal); +} + +.smart-env-notifications-filter.is-active:not(.smart-env-notifications-filter--all) .smart-env-notifications-filter__content { + background: color-mix(in srgb, var(--smart-env-filter-accent) 14%, var(--background-primary)); + border-color: color-mix(in srgb, var(--smart-env-filter-accent) 36%, var(--background-modifier-border)); + box-shadow: var(--shadow-xs); + color: var(--text-normal); +} + +.smart-env-notifications-filter.is-active:not(.smart-env-notifications-filter--all) .smart-env-notifications-filter__count { + background: transparent; + border-color: transparent; + color: inherit; +} + +.smart-env-notifications-filter:focus-visible { + outline: none; +} + +.smart-env-notifications-filter:focus-visible .smart-env-notifications-filter__content { + outline: 2px solid var(--color-accent); + outline-offset: 2px; +} + +.smart-env-notifications-filter__dot { + width: var(--size-4-2); + height: var(--size-4-2); + border-radius: 999px; + background: var(--smart-env-filter-accent); + opacity: 0.95; + flex: 0 0 auto; +} + +.smart-env-notifications-filter--all .smart-env-notifications-filter__dot { + width: calc(var(--size-4-2) - 1px); + height: calc(var(--size-4-2) - 1px); + background: transparent; + box-shadow: inset 0 0 0 1.5px color-mix(in srgb, var(--text-normal) 24%, var(--background-modifier-border)); +} + +.smart-env-notifications-filter__label { + white-space: nowrap; +} + +.smart-env-notifications-filter__count { + margin-left: 1px; + padding: 0 var(--size-2-2); + border-radius: 999px; + border: 1px solid var(--background-modifier-border); + background: var(--background-primary); + color: var(--text-muted); + font-size: 0.78em; + line-height: 1.4; + font-variant-numeric: tabular-nums; +} + +.smart-env-notifications-filter__count.is-zero { + display: none; +} + +.smart-env-notifications-filter--all.is-active .smart-env-notifications-filter__content { + background: var(--background-primary); + border-color: var(--background-modifier-border-hover, var(--background-modifier-border)); + box-shadow: var(--shadow-xs); + color: var(--text-normal); +} + +.smart-env-notifications-filter--all.is-active .smart-env-notifications-filter__count { + background: color-mix(in srgb, var(--background-primary) 84%, transparent); + border-color: color-mix(in srgb, var(--text-normal) 12%, var(--background-modifier-border)); + color: inherit; +} + +.smart-env-notifications-filter[data-level='all'] { + --smart-env-filter-accent: color-mix(in srgb, var(--text-normal) 30%, var(--text-muted)); +} + +.smart-env-notifications-filter[data-level='attention'] { + --smart-env-filter-accent: var(--color-yellow); +} + +.smart-env-notifications-filter[data-level='milestone'] { + --smart-env-filter-accent: var(--color-accent); +} + +.smart-env-notifications-filter[data-level='warning'] { + --smart-env-filter-accent: var(--color-orange); +} + +.smart-env-notifications-filter[data-level='error'] { + --smart-env-filter-accent: var(--color-red); +} + +.smart-env-notifications-filter[data-level='info'] { + --smart-env-filter-accent: var(--status-bar-text-color, var(--text-muted)); +} + +.smart-env-notifications-filter[data-level='debug'] { + --smart-env-filter-accent: color-mix(in srgb, var(--text-faint) 60%, var(--background-modifier-border-hover)); +} + +/* Feed */ +.smart-env-notifications-feed { + display: flex; + flex-direction: column; + padding: 1px 0 var(--size-2-3); + gap: var(--size-4-2); +} + +.smart-env-notification { + background: var(--background-primary); + border: 1px solid var(--background-modifier-border); + border-radius: var(--smart-env-notifications-card-radius); + overflow: hidden; + transition: + border-color var(--anim-duration-fast) var(--anim-motion-smooth), + box-shadow var(--anim-duration-fast) var(--anim-motion-smooth), + transform var(--anim-duration-fast) var(--anim-motion-smooth); +} + +.smart-env-notification:hover { + border-color: var(--background-modifier-border-hover, var(--background-modifier-border)); + box-shadow: var(--smart-env-notifications-shadow); +} + +.smart-env-notification[data-muted='true'] { + background: color-mix(in srgb, var(--background-primary) 88%, var(--background-secondary)); +} + +.smart-env-notification[data-level='attention'] { + --smart-env-notification-accent: var(--color-yellow); +} + +.smart-env-notification[data-level='milestone'] { + --smart-env-notification-accent: var(--color-accent); +} + +.smart-env-notification[data-level='warning'] { + --smart-env-notification-accent: var(--color-orange); +} + +.smart-env-notification[data-level='error'] { + --smart-env-notification-accent: var(--color-red); +} + +.smart-env-notification[data-level='info'] { + --smart-env-notification-accent: var(--status-bar-text-color, var(--text-muted)); +} + +.smart-env-notification[data-level='debug'], +.smart-env-notification[data-level='event'] { + --smart-env-notification-accent: color-mix(in srgb, var(--text-faint) 80%, var(--background-modifier-border-hover)); +} + +details.smart-env-notification { + user-select: text; +} +details.smart-env-notification[open] { + transform: translateY(-1px); + border-color: color-mix(in srgb, var(--smart-env-notification-accent) 76%, var(--background-modifier-border)); + box-shadow: var(--smart-env-notifications-shadow-hover); +} + +.smart-env-notification__summary { + display: flex; + align-items: flex-start; + gap: var(--size-4-2); + padding: calc(var(--size-4-2) + 1px) var(--size-4-3); + list-style: none; +} + +details.smart-env-notification > .smart-env-notification__summary { + cursor: pointer; +} + +details.smart-env-notification > .smart-env-notification__summary:focus-visible { + outline: 2px solid var(--color-accent); + outline-offset: 2px; + border-radius: var(--smart-env-notifications-card-radius); +} + +.smart-env-notification__summary::-webkit-details-marker { + display: none; +} + +.smart-env-notification__accent { + width: calc(var(--size-4-2) + 2px); + height: calc(var(--size-4-2) + 2px); + border-radius: 999px; + margin-top: var(--size-2-2); + flex: 0 0 auto; + background: var(--smart-env-notification-accent, var(--smart-env-notifications-muted-accent)); + color: var(--smart-env-notification-accent, var(--smart-env-notifications-muted-accent)); + box-shadow: 0 0 0 4px color-mix(in srgb, currentColor 12%, transparent); +} + +.smart-env-notification__summary-body { + flex: 1; + min-width: 0; + display: flex; + flex-direction: column; + gap: var(--size-2-2); +} + +.smart-env-notification__summary-top { + display: flex; + align-items: baseline; + justify-content: space-between; + gap: var(--size-4-3); +} + +details[open] .smart-env-notification__event-key { + white-space: normal; +} + +.smart-env-notification__event-key { + color: var(--text-normal); + font-weight: var(--font-semibold); + letter-spacing: -0.01em; + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; +} + +.smart-env-notification__summary-action { + margin-left: auto; + font-size: var(--font-ui-smaller); + min-height: var(--touch-size-xxs); + padding: 0 var(--size-2-3); +} + +.smart-env-notification__time { + color: var(--text-muted); + font-size: var(--font-ui-small); + white-space: nowrap; + font-variant-numeric: tabular-nums; + flex: 0 0 auto; +} + +.smart-env-notification__summary-bottom { + display: flex; + align-items: center; + gap: var(--size-2-2); + row-gap: var(--size-2-1); + flex-wrap: wrap; +} + +.smart-env-notification__collection, +.smart-env-notification__level, +.smart-env-notification__feed-only, +.smart-env-notification__muted, +.smart-env-notification__event-count { + display: inline-flex; + align-items: center; + padding: 0 var(--size-2-3); + min-height: var(--touch-size-xxs); + border-radius: 999px; + border: 1px solid var(--background-modifier-border); + background: transparent; + color: var(--text-muted); + font-size: var(--font-ui-small); + line-height: 1.1; +} + +.smart-env-notification__collection { + background: var(--background-secondary); +} + +.smart-env-notification__event-count { + background: var(--background-primary); +} + +.smart-env-notification__feed-only { + color: var(--text-faint); + border-style: dashed; +} + +.smart-env-notification__level { + border-color: color-mix(in srgb, var(--smart-env-notification-accent) 88%, var(--background-modifier-border)); + color: var(--smart-env-notification-accent, var(--text-muted)); +} + +.smart-env-notification[data-level='debug'] .smart-env-notification__level, +.smart-env-notification[data-level='event'] .smart-env-notification__level { + border-style: dashed; +} + +.smart-env-notification__chevron { + width: var(--icon-m); + height: var(--icon-m); + margin-top: 1px; + display: inline-flex; + align-items: center; + justify-content: center; + color: var(--text-faint); + flex: 0 0 auto; + transition: + transform var(--anim-duration-fast) var(--anim-motion-smooth), + color var(--anim-duration-fast) var(--anim-motion-smooth); +} + +.smart-env-notification__chevron::before { + content: ">"; + font-size: var(--font-ui-medium); + line-height: 1; +} + +details.smart-env-notification[open] .smart-env-notification__chevron { + transform: rotate(90deg); + color: var(--text-muted); +} + +.smart-env-notification__expanded { + display: flex; + flex-direction: column; + gap: var(--size-4-2); + padding: var(--size-4-3); + border-top: 1px solid var(--background-modifier-border); + background: color-mix(in srgb, var(--background-secondary) 88%, var(--background-primary)); +} + +.smart-env-notification__expanded-meta { + display: grid; + gap: var(--size-2-2); +} + +.smart-env-notification__expanded-row { + display: flex; + align-items: flex-start; + justify-content: space-between; + gap: var(--size-4-3); +} + +.smart-env-notification__expanded-label { + color: var(--text-muted); + font-size: var(--font-ui-small); + white-space: nowrap; +} + +.smart-env-notification__expanded-value { + color: var(--text-normal); + font-size: var(--font-ui-small); + text-align: right; + word-break: break-word; +} + +.smart-env-notification__expanded-actions { + display: flex; + align-items: center; + gap: var(--size-2-2); + flex-wrap: wrap; +} + +.smart-env-notification__message { + margin: 0; + padding: var(--size-4-3); + border: 1px solid var(--background-modifier-border); + border-radius: var(--smart-env-notifications-code-radius); + background: var(--background-primary); + font-family: var(--font-monospace); + font-size: var(--font-smaller); + font-variant-ligatures: none; + white-space: pre-wrap; + word-break: break-word; +} + +/* Empty states */ +.smart-env-notifications-empty-state { + padding: var(--size-4-6) var(--size-4-4); + border-radius: var(--smart-env-notifications-empty-radius); + border: 1px dashed var(--background-modifier-border); + background: color-mix(in srgb, var(--background-secondary) 65%, transparent); + text-align: center; +} + +.smart-env-notifications-empty-state__title { + color: var(--text-normal); + font-weight: var(--font-semibold); + letter-spacing: -0.01em; + margin-bottom: var(--size-2-2); +} + +.smart-env-notifications-empty-state__detail { + color: var(--text-muted); + font-size: var(--font-ui-small); + margin-bottom: var(--size-4-3); +} + +/* Footer */ +.smart-env-notifications__footer { + display: flex; + justify-content: center; + padding-top: var(--size-2-2); +} + +.smart-env-notifications__footer .smart-env-btn { + width: 100%; + max-width: 320px; +} + +@media (max-width: 720px) { + .smart-env-notifications__meta { + width: 100%; + margin-inline-start: 0; + justify-content: flex-end; + } +} + +@media (max-width: 540px) { + .smart-env-notifications-modal .modal-content { + padding: var(--size-4-2) var(--size-4-2) var(--size-4-4); + } + + .smart-env-notifications__toolbar { + border-radius: var(--radius-l); + } + + .smart-env-notifications-filter-controls { + flex-basis: 100%; + } + + .smart-env-notifications__meta { + justify-content: space-between; + } + + .smart-env-notifications__summary { + flex: 1 1 auto; + min-width: 0; + } + + .smart-env-notifications__footer .smart-env-btn { + max-width: none; + } + + .smart-env-notification__expanded-row { + flex-direction: column; + gap: var(--size-2-1); + } + + .smart-env-notification__expanded-value { + text-align: left; + } +} +`; + +// node_modules/obsidian-smart-env/src/utils/notifications_feed_utils.js +var default_page_size = 100; +var load_more_step = 100; +var all_levels_filter_key = "all"; +var debug_levels_filter_key = "debug"; +var notification_filter_keys = Object.freeze([ + ...notification_levels, + debug_levels_filter_key +]); +function normalize_filter_level(level) { + const normalized_level = normalize_event_level(level); + if (normalized_level) return normalized_level; + const raw_level = typeof level === "string" ? level.trim().toLowerCase() : ""; + if (raw_level === debug_levels_filter_key) return debug_levels_filter_key; + return null; +} +function create_all_levels_set() { + return new Set(notification_filter_keys); +} +function are_all_levels_active(active_levels = /* @__PURE__ */ new Set()) { + if (!(active_levels instanceof Set)) return false; + if (active_levels.size !== notification_filter_keys.length) return false; + return notification_filter_keys.every((level) => active_levels.has(level)); +} +function get_next_active_levels(active_levels = /* @__PURE__ */ new Set(), params = {}) { + const { + level = null, + select_all = false + } = params; + if (select_all) return create_all_levels_set(); + const normalized_level = normalize_filter_level(level); + const next_active_levels = new Set(active_levels instanceof Set ? active_levels : []); + if (!normalized_level) return next_active_levels; + if (are_all_levels_active(next_active_levels)) { + return /* @__PURE__ */ new Set([normalized_level]); + } + if (next_active_levels.has(normalized_level)) { + next_active_levels.delete(normalized_level); + if (next_active_levels.size === 0) { + return create_all_levels_set(); + } + return next_active_levels; + } + next_active_levels.add(normalized_level); + return next_active_levels; +} +function get_canonical_entry_level(entry) { + return get_event_level(entry?.event_key, entry?.event); +} +function get_entry_level(entry) { + const canonical_level = get_canonical_entry_level(entry); + if (canonical_level) return canonical_level; + return get_event_level(entry?.event_key, entry?.event, { + allow_display_fallback: true + }); +} +function is_canonical_notification_entry(entry) { + return Boolean(get_canonical_entry_level(entry)); +} +function is_debug_entry(entry) { + if (!entry || typeof entry !== "object") return false; + return !get_entry_level(entry); +} +function get_filtered_entries(entries, params = {}) { + const { active_levels = create_all_levels_set() } = params; + if (!(active_levels instanceof Set) || active_levels.size === 0) return []; + const next_entries = Array.isArray(entries) ? [...entries] : []; + if (are_all_levels_active(active_levels)) { + return next_entries; + } + return next_entries.filter((entry) => { + const level = get_entry_level(entry); + if (level) return active_levels.has(level); + return active_levels.has(debug_levels_filter_key); + }); +} +function get_visible_entries(entries, params = {}) { + const { limit = default_page_size } = params; + return entries.slice(-limit).reverse(); +} +function get_visible_count(entries_length, params = {}) { + const { page_size = default_page_size } = params; + return Math.min(entries_length, page_size); +} +function get_next_visible_count(entries_length, params = {}) { + const { current_count = 0, step_size = load_more_step } = params; + return Math.min(entries_length, current_count + step_size); +} +function should_show_load_more(entries_length, visible_count) { + return entries_length > visible_count; +} +function format_level_label2(level) { + const normalized_level = normalize_filter_level(level); + if (!normalized_level) return ""; + return normalized_level.slice(0, 1).toUpperCase() + normalized_level.slice(1); +} +function queue_live_update_entries(pending_entries, next_entries, params = {}) { + const { existing_entries = [] } = params; + const pending_list = Array.isArray(pending_entries) ? [...pending_entries] : []; + const pending_entry_ids = new Set(pending_list.map((entry) => `${get_entry_event_key(entry)}::${get_entry_timestamp2(entry)}`)); + const existing_entry_ids = new Set((Array.isArray(existing_entries) ? existing_entries : []).map((entry) => `${get_entry_event_key(entry)}::${get_entry_timestamp2(entry)}`)); + (Array.isArray(next_entries) ? next_entries : []).forEach((entry) => { + const entry_id = `${get_entry_event_key(entry)}::${get_entry_timestamp2(entry)}`; + if (existing_entry_ids.has(entry_id)) return; + if (pending_entry_ids.has(entry_id)) return; + pending_list.push(entry); + pending_entry_ids.add(entry_id); + }); + return pending_list; +} +function get_level_counts(entries) { + const counts = notification_filter_keys.reduce((acc, level) => { + acc[level] = 0; + return acc; + }, {}); + entries.forEach((entry) => { + if (!entry || typeof entry !== "object") return; + const level = get_entry_level(entry); + if (level) { + counts[level] += 1; + return; + } + counts[debug_levels_filter_key] += 1; + }); + return counts; +} +function get_entry_timestamp2(entry) { + if (typeof entry?.event?.at === "number") return entry.event.at; + if (typeof entry?.at === "number") return entry.at; + return Date.now(); +} +function get_entry_event_key(entry) { + return typeof entry?.event_key === "string" ? entry.event_key : ""; +} +function get_entry_title(entry) { + const event_obj = entry?.event && typeof entry.event === "object" ? entry.event : {}; + if (typeof event_obj.message === "string" && event_obj.message.trim()) return event_obj.message.trim(); + if (typeof event_obj.details === "string" && event_obj.details.trim()) return event_obj.details.trim(); + if (typeof event_obj.milestone === "string" && event_obj.milestone.trim()) return event_obj.milestone.trim(); + return get_entry_event_key(entry) || "event"; +} +function get_entry_payload_text(entry) { + const event_obj = entry?.event && typeof entry.event === "object" ? entry.event : {}; + return Object.entries(event_obj).filter(([key]) => !["at", "collection_key", "message", "level", "btn_text", "btn_callback", "timeout", "timeout_ms"].includes(key)).map(([key, value]) => ` ${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`).join("\n"); +} +function get_entry_summary_action(entry) { + const event_obj = entry?.event && typeof entry.event === "object" ? entry.event : {}; + const btn_text = typeof event_obj.btn_text === "string" ? event_obj.btn_text.trim() : ""; + const btn_callback = typeof event_obj.btn_callback === "string" ? event_obj.btn_callback.trim() : ""; + if (!btn_text || !btn_callback) return null; + return { btn_text, btn_callback }; +} +function get_entry_meta_text(entry) { + const collection_key = entry?.event?.collection_key ?? ""; + const event_key = get_entry_event_key(entry) || "event"; + const timestamp = get_entry_timestamp2(entry); + return `${collection_key ? `${collection_key} - ` : ""}${event_key} - ${to_time_ago(timestamp)}`; +} +function entry_to_clipboard_text(entry) { + const meta_text = get_entry_meta_text(entry); + const payload_text = get_entry_payload_text(entry); + if (!payload_text.trim().length) { + return `${meta_text} + +`; + } + return `${meta_text} +${payload_text} + +`; +} +function entries_to_clipboard_text(entries = []) { + return entries.map((entry) => entry_to_clipboard_text(entry)).join(""); +} +function to_time_ago(ms) { + const now_ms = Date.now(); + const seconds = Math.floor((now_ms - ms) / 1e3); + if (seconds < 60) return `${Math.max(0, seconds)}s ago`; + const minutes = Math.floor(seconds / 60); + if (minutes < 60) return `${minutes}m ago`; + const hours = Math.floor(minutes / 60); + if (hours < 24) return `${hours}h ago`; + const days = Math.floor(hours / 24); + return `${days}d ago`; +} + +// node_modules/obsidian-smart-env/src/components/notifications_feed.js +var feed_excluded_event_keys = /* @__PURE__ */ new Set([ + "collection:save_started", + "collection:save_completed", + "notifications:seen", + "notifications:seen_all", + "event_log:first" +]); +function build_html6() { + return `
    +
    +
    +
    +
    +
    +
    + +
    +
    +
    +
    + +
    +
    + + +
    `; +} +async function render8(env, params = {}) { + this.apply_style_sheet(notification_feed_default); + const frag = this.create_doc_fragment(build_html6()); + const container = frag.firstElementChild; + await post_process6.call(this, env, container, params); + return container; +} +async function post_process6(env, container, params = {}) { + const { + live_updates = false, + auto_mark_seen = false, + state = {} + } = params; + const feed_container = container.querySelector(".smart-env-notifications-feed"); + const copy_btn = container.querySelector(".copy-all-notifications-btn"); + const load_more_btn = container.querySelector(".load-more-notifications-btn"); + const filter_controls = container.querySelector(".smart-env-notifications-filter-controls"); + const summary_el = container.querySelector(".smart-env-notifications__summary"); + const live_updates_el = container.querySelector(".smart-env-notifications-live-updates"); + const smart_env = this; + const expanded_entry_keys = state.expanded_entry_keys instanceof Set ? state.expanded_entry_keys : /* @__PURE__ */ new Set(); + let target_entry_key = typeof state?.target_entry_key === "string" ? state.target_entry_key.trim() : ""; + let has_revealed_target_entry = false; + this.empty(feed_container); + const active_levels = state.active_levels instanceof Set ? state.active_levels : create_all_levels_set(); + let visible_count = typeof state.visible_count === "number" ? state.visible_count : null; + let filtered_count = typeof state.filtered_count === "number" ? state.filtered_count : null; + let pending_live_entries = Array.isArray(state.pending_live_entries) ? [...state.pending_live_entries] : []; + let rendered_entries_cache = []; + if (target_entry_key) { + expanded_entry_keys.add(target_entry_key); + } + state.active_levels = active_levels; + state.expanded_entry_keys = expanded_entry_keys; + state.pending_live_entries = pending_live_entries; + state.target_entry_key = target_entry_key; + const get_entries = () => { + const session_entries = Array.isArray(env?.event_logs?.session_events) ? [...env.event_logs.session_events] : []; + return session_entries; + }; + const should_refresh_for_event = (event_key) => { + if (event_key === "event_logs:mute_changed") return true; + if (feed_excluded_event_keys.has(event_key)) return false; + return true; + }; + const capture_expanded_entry_keys = () => { + expanded_entry_keys.clear(); + feed_container.querySelectorAll("details.smart-env-notification[open]").forEach((details_el) => { + const entry_key = details_el.dataset.entryKey; + if (!entry_key) return; + expanded_entry_keys.add(entry_key); + }); + if (target_entry_key) { + expanded_entry_keys.add(target_entry_key); + } + }; + const set_active_levels = (next_active_levels) => { + active_levels.clear(); + next_active_levels.forEach((level) => active_levels.add(level)); + }; + const maybe_mark_entries_seen = () => { + if (!auto_mark_seen) return; + env?.event_logs?.mark_all_notification_entries_seen?.(); + }; + const render_filters = (entries) => { + render_filter_controls(filter_controls, { + active_levels, + total_count: entries.length, + level_counts: get_level_counts(entries), + on_change: handle_filters_changed, + on_select_all: () => { + set_active_levels(create_all_levels_set()); + }, + on_select_level: (level) => { + set_active_levels(get_next_active_levels(active_levels, { level })); + } + }); + }; + const render_entries = (entries) => { + smart_env.empty(feed_container); + const filtered_entries = get_filtered_entries(entries, { active_levels }); + const previous_filtered_count = typeof filtered_count === "number" ? filtered_count : filtered_entries.length; + if (typeof visible_count !== "number") { + visible_count = get_visible_count(filtered_entries.length); + } else if (visible_count >= previous_filtered_count) { + visible_count = filtered_entries.length; + } else { + visible_count = Math.min(visible_count, filtered_entries.length); + } + const target_visible_count = get_required_visible_count_for_entry(filtered_entries, target_entry_key); + if (typeof target_visible_count === "number" && target_visible_count > visible_count) { + visible_count = target_visible_count; + } + filtered_count = filtered_entries.length; + state.visible_count = visible_count; + state.filtered_count = filtered_count; + const shown_count = Math.min(visible_count, filtered_entries.length); + update_summary(summary_el, { + total_count: entries.length, + filtered_count: filtered_entries.length, + visible_count: shown_count + }); + set_btn_disabled(copy_btn, filtered_entries.length === 0); + if (filtered_entries.length === 0) { + render_empty_state(feed_container, { + title: "No events match your filters.", + detail: "Try enabling more levels or switch back to All.", + action_text: "Reset filters", + on_action: reset_filters + }); + if (load_more_btn) load_more_btn.style.display = "none"; + return; + } + get_visible_entries(filtered_entries, { limit: visible_count }).forEach((entry) => { + append_entry(feed_container, entry, { + env, + expanded_entry_keys, + on_entry_toggle: handle_entry_toggle, + on_toggle_mute: handle_toggle_mute + }); + }); + update_load_more_button(load_more_btn, { + entries_length: filtered_entries.length, + visible_count + }); + if (!has_revealed_target_entry && target_entry_key) { + has_revealed_target_entry = reveal_target_entry(feed_container, target_entry_key); + if (has_revealed_target_entry) { + state.target_entry_key = ""; + target_entry_key = ""; + } + } + }; + const render_feed = (opts = {}) => { + const { + preserve_expanded = false, + reset_visible_count = false + } = opts; + if (preserve_expanded) capture_expanded_entry_keys(); + const entries = get_entries(); + rendered_entries_cache = entries; + if (reset_visible_count) { + const filtered_entries = get_filtered_entries(entries, { active_levels }); + visible_count = get_visible_count(filtered_entries.length); + state.visible_count = visible_count; + state.filtered_count = filtered_entries.length; + } + if (!entries.length) { + smart_env.empty(feed_container); + filter_controls?.replaceChildren?.(); + render_empty_state(feed_container, { + title: "No Smart Env events yet.", + detail: "When Smart Env emits events, they will appear here." + }); + set_btn_disabled(copy_btn, true); + if (load_more_btn) load_more_btn.style.display = "none"; + update_summary(summary_el, { total_count: 0, filtered_count: 0, visible_count: 0 }); + render_live_updates_action(live_updates_el, { + pending_count: pending_live_entries.length, + on_click: handle_show_live_updates + }); + maybe_mark_entries_seen(); + return; + } + render_filters(entries); + render_entries(entries); + render_live_updates_action(live_updates_el, { + pending_count: pending_live_entries.length, + on_click: handle_show_live_updates + }); + maybe_mark_entries_seen(); + }; + const handle_filters_changed = () => { + render_feed({ preserve_expanded: true, reset_visible_count: true }); + }; + const handle_toggle_mute = (entry) => { + const event_key = entry?.event_key; + if (!event_key || typeof env?.event_logs?.toggle_event_key_muted !== "function") return; + if (!is_canonical_notification_entry(entry)) return; + env.event_logs.toggle_event_key_muted(event_key); + render_feed({ preserve_expanded: true }); + }; + const handle_entry_toggle = (entry_key, is_open) => { + if (is_open) { + expanded_entry_keys.add(entry_key); + return; + } + expanded_entry_keys.delete(entry_key); + }; + const handle_show_live_updates = () => { + if (pending_live_entries.length === 0) return; + pending_live_entries = []; + state.pending_live_entries = pending_live_entries; + render_feed({ preserve_expanded: true }); + }; + const reset_filters = () => { + set_active_levels(create_all_levels_set()); + render_feed({ preserve_expanded: true, reset_visible_count: true }); + }; + render_feed({ reset_visible_count: true }); + if (copy_btn) { + copy_btn.addEventListener("click", async () => { + if (copy_btn.disabled) return; + const filtered_entries = get_filtered_entries(get_entries(), { active_levels }); + const newest_first = get_visible_entries(filtered_entries, { limit: filtered_entries.length }); + const all_text = entries_to_clipboard_text(newest_first); + const copied = await write_text_to_clipboard(all_text); + set_btn_copied_state(copy_btn, { + idle_text: "Copy All", + copied_text: copied ? "Copied" : "Copy failed" + }); + }); + } + if (load_more_btn) { + load_more_btn.addEventListener("click", () => { + const filtered_entries = get_filtered_entries(get_entries(), { active_levels }); + visible_count = get_next_visible_count(filtered_entries.length, { + current_count: visible_count + }); + state.visible_count = visible_count; + render_feed({ preserve_expanded: true }); + }); + } + if (live_updates && typeof env?.events?.on === "function") { + let debounce_timeout = null; + const live_update_off = env.events.on("*", (_event, event_key) => { + if (!should_refresh_for_event(event_key)) return; + if (debounce_timeout) clearTimeout(debounce_timeout); + debounce_timeout = setTimeout(() => { + debounce_timeout = null; + const next_entries = get_entries(); + pending_live_entries = queue_live_update_entries(pending_live_entries, next_entries, { + existing_entries: rendered_entries_cache + }); + state.pending_live_entries = pending_live_entries; + if (pending_live_entries.length > 0) { + render_live_updates_action(live_updates_el, { + pending_count: pending_live_entries.length, + on_click: handle_show_live_updates + }); + return; + } + render_feed({ preserve_expanded: true }); + }, 100); + }); + this.attach_disposer(container, [ + live_update_off, + () => { + if (!debounce_timeout) return; + clearTimeout(debounce_timeout); + debounce_timeout = null; + } + ]); + } +} +function render_filter_controls(container, params = {}) { + if (!container) return; + const { + active_levels = /* @__PURE__ */ new Set(), + total_count = 0, + level_counts = {}, + on_change = () => { + }, + on_select_all = () => { + }, + on_select_level = () => { + } + } = params; + container.replaceChildren(); + const all_is_active = are_all_levels_active(active_levels); + append_filter_button(container, { + level: all_levels_filter_key, + label_text: "All", + count_total: total_count, + is_active: all_is_active, + modifier_class: "smart-env-notifications-filter--all", + on_click: () => { + if (all_is_active) return; + on_select_all(); + on_change(); + } + }); + notification_levels.forEach((level) => { + append_filter_button(container, { + level, + label_text: format_level_label2(level), + count_total: typeof level_counts[level] === "number" ? level_counts[level] : 0, + is_active: !all_is_active && active_levels.has(level), + on_click: () => { + on_select_level(level); + on_change(); + } + }); + }); + append_filter_button(container, { + level: debug_levels_filter_key, + label_text: "Debug", + count_total: typeof level_counts[debug_levels_filter_key] === "number" ? level_counts[debug_levels_filter_key] : 0, + is_active: !all_is_active && active_levels.has(debug_levels_filter_key), + on_click: () => { + on_select_level(debug_levels_filter_key); + on_change(); + } + }); +} +function append_filter_button(container, params) { + const { + level, + label_text, + count_total, + is_active, + on_click, + modifier_class = "" + } = params; + const button = container.ownerDocument.createElement("button"); + button.type = "button"; + button.className = `smart-env-notifications-filter${modifier_class ? ` ${modifier_class}` : ""}`; + button.dataset.level = level; + button.setAttribute("aria-pressed", String(Boolean(is_active))); + if (is_active) button.classList.add("is-active"); + button.addEventListener("click", on_click); + const content = container.ownerDocument.createElement("span"); + content.className = "smart-env-notifications-filter__content"; + const dot = container.ownerDocument.createElement("span"); + dot.className = "smart-env-notifications-filter__dot"; + dot.setAttribute("aria-hidden", "true"); + const text = container.ownerDocument.createElement("span"); + text.className = "smart-env-notifications-filter__label"; + text.textContent = label_text; + const count = container.ownerDocument.createElement("span"); + count.className = "smart-env-notifications-filter__count"; + count.textContent = count_total > 0 ? count_total.toLocaleString() : ""; + if (count_total <= 0) count.classList.add("is-zero"); + content.appendChild(dot); + content.appendChild(text); + content.appendChild(count); + button.appendChild(content); + container.appendChild(button); +} +function update_load_more_button(button, params = {}) { + if (!button) return; + const { entries_length = 0, visible_count = 0 } = params; + const is_visible2 = should_show_load_more(entries_length, visible_count); + button.style.display = is_visible2 ? "block" : "none"; + if (is_visible2) { + const remaining_count = entries_length - visible_count; + const next_step = Math.min(load_more_step, remaining_count); + button.textContent = `Load ${next_step.toLocaleString()} more`; + } +} +function render_live_updates_action(container, params = {}) { + if (!container) return; + const { + pending_count = 0, + on_click = () => { + } + } = params; + container.replaceChildren(); + if (pending_count <= 0) return; + const button = container.ownerDocument.createElement("button"); + button.type = "button"; + button.className = "smart-env-btn smart-env-btn--ghost smart-env-notifications-live-updates__btn"; + button.textContent = `Show ${pending_count.toLocaleString()} new event${pending_count === 1 ? "" : "s"}`; + button.addEventListener("click", on_click); + container.appendChild(button); +} +function append_entry(feed_container, entry, params = {}) { + const { + env = null, + expanded_entry_keys = /* @__PURE__ */ new Set(), + on_entry_toggle = () => { + }, + on_toggle_mute = () => { + } + } = params; + const level = get_entry_level(entry); + const is_debug = is_debug_entry(entry); + const title = get_entry_title(entry); + const timestamp = get_entry_timestamp2(entry); + const collection_key = entry?.event?.collection_key ?? ""; + const event_key = get_entry_event_key(entry) || "event"; + const payload_text = get_entry_payload_text(entry); + const summary_action = get_entry_summary_action(entry); + const is_canonical = is_canonical_notification_entry(entry); + const is_muted = is_canonical && Boolean(env?.event_logs?.is_event_key_muted?.(event_key)); + const is_feed_only = Boolean(level) && !is_canonical; + const entry_row_key = get_entry_row_key(entry); + const total_count = get_event_key_total_count(env, event_key); + const row_level = level || (is_debug ? debug_levels_filter_key : "event"); + const row = feed_container.ownerDocument.createElement("details"); + row.className = "smart-env-notification"; + row.dataset.entryKey = entry_row_key; + row.dataset.level = row_level; + row.setAttribute("role", "listitem"); + if (is_muted) row.dataset.muted = "true"; + if (expanded_entry_keys.has(entry_row_key)) row.open = true; + const summary = feed_container.ownerDocument.createElement("summary"); + summary.className = "smart-env-notification__summary"; + const accent = feed_container.ownerDocument.createElement("span"); + accent.className = "smart-env-notification__accent"; + accent.setAttribute("aria-hidden", "true"); + const body = feed_container.ownerDocument.createElement("div"); + body.className = "smart-env-notification__summary-body"; + const top = feed_container.ownerDocument.createElement("div"); + top.className = "smart-env-notification__summary-top"; + const event_el = feed_container.ownerDocument.createElement("div"); + event_el.className = "smart-env-notification__event-key"; + event_el.textContent = title; + const time_el = feed_container.ownerDocument.createElement("div"); + time_el.className = "smart-env-notification__time"; + time_el.textContent = to_time_ago(timestamp); + time_el.title = format_timestamp(timestamp); + top.appendChild(event_el); + if (summary_action) { + const cta_btn = feed_container.ownerDocument.createElement("button"); + cta_btn.className = "smart-env-btn smart-env-btn--ghost smart-env-notification__summary-action"; + cta_btn.type = "button"; + cta_btn.textContent = summary_action.btn_text; + cta_btn.setAttribute("aria-label", summary_action.btn_text); + cta_btn.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + dispatch_notice_action(env, summary_action.btn_callback, { + event_key, + event: entry?.event, + event_source: "notifications_feed" + }); + }); + top.appendChild(cta_btn); + } + top.appendChild(time_el); + const bottom = feed_container.ownerDocument.createElement("div"); + bottom.className = "smart-env-notification__summary-bottom"; + if (collection_key) { + bottom.appendChild(create_tag(feed_container, "smart-env-notification__collection", collection_key)); + } + if (title !== event_key) { + bottom.appendChild(create_tag(feed_container, "smart-env-notification__collection", event_key)); + } + bottom.appendChild(create_tag( + feed_container, + "smart-env-notification__level", + level ? format_level_label2(level) : is_debug ? "Debug" : "Event" + )); + if (is_feed_only) { + bottom.appendChild(create_tag(feed_container, "smart-env-notification__feed-only", "Feed only")); + } + if (is_muted) { + bottom.appendChild(create_tag(feed_container, "smart-env-notification__muted", "Muted")); + } + body.appendChild(top); + body.appendChild(bottom); + const chevron = feed_container.ownerDocument.createElement("span"); + chevron.className = "smart-env-notification__chevron"; + chevron.setAttribute("aria-hidden", "true"); + summary.appendChild(accent); + summary.appendChild(body); + summary.appendChild(chevron); + const expanded = feed_container.ownerDocument.createElement("div"); + expanded.className = "smart-env-notification__expanded"; + const meta = feed_container.ownerDocument.createElement("div"); + meta.className = "smart-env-notification__expanded-meta"; + append_meta_row(feed_container, meta, "Occurred", format_timestamp(timestamp)); + append_meta_row(feed_container, meta, "Event key", event_key); + if (collection_key) { + append_meta_row(feed_container, meta, "Collection", collection_key); + } + expanded.appendChild(meta); + const actions = feed_container.ownerDocument.createElement("div"); + actions.className = "smart-env-notification__expanded-actions"; + actions.appendChild(create_tag( + feed_container, + "smart-env-notification__event-count", + `${Math.max(1, total_count).toLocaleString()} total` + )); + if (is_canonical) { + const mute_btn = feed_container.ownerDocument.createElement("button"); + mute_btn.className = "smart-env-btn smart-env-btn--ghost smart-env-notification__mute"; + mute_btn.type = "button"; + mute_btn.textContent = is_muted ? "Unmute native notices" : "Mute native notices"; + mute_btn.setAttribute( + "aria-label", + is_muted ? "Allow native notices for this event key" : "Mute native notices for this event key" + ); + mute_btn.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + on_toggle_mute(entry); + }); + actions.appendChild(mute_btn); + } else if (is_feed_only) { + actions.appendChild(create_tag(feed_container, "smart-env-notification__feed-only", "Display fallback only")); + } + expanded.appendChild(actions); + if (payload_text.trim().length) { + const message = feed_container.ownerDocument.createElement("pre"); + message.className = "smart-env-notification__message"; + message.textContent = payload_text; + expanded.appendChild(message); + } + row.appendChild(summary); + row.appendChild(expanded); + row.addEventListener("toggle", () => { + on_entry_toggle(entry_row_key, row.open === true); + }); + feed_container.appendChild(row); +} +function update_summary(summary_el, params = {}) { + if (!summary_el) return; + const { + total_count = 0, + filtered_count = 0, + visible_count = 0 + } = params; + if (total_count <= 0) { + summary_el.textContent = ""; + return; + } + const shown_count = Math.min(visible_count, filtered_count); + const shown_label = shown_count.toLocaleString(); + const filtered_label = filtered_count.toLocaleString(); + const total_label = total_count.toLocaleString(); + let text = shown_count === filtered_count ? `${shown_label} shown` : `${shown_label} of ${filtered_label} shown`; + if (filtered_count !== total_count) { + text += ` (${total_label} total)`; + } + summary_el.textContent = text; +} +function render_empty_state(feed_container, params = {}) { + const { + title = "Nothing here yet.", + detail = "", + action_text = "", + on_action = null + } = params; + const wrap = feed_container.ownerDocument.createElement("div"); + wrap.className = "smart-env-notifications-empty-state"; + const heading = feed_container.ownerDocument.createElement("div"); + heading.className = "smart-env-notifications-empty-state__title"; + heading.textContent = title; + wrap.appendChild(heading); + if (detail) { + const detail_el = feed_container.ownerDocument.createElement("div"); + detail_el.className = "smart-env-notifications-empty-state__detail"; + detail_el.textContent = detail; + wrap.appendChild(detail_el); + } + if (action_text && typeof on_action === "function") { + const button = feed_container.ownerDocument.createElement("button"); + button.className = "smart-env-btn smart-env-btn--ghost"; + button.type = "button"; + button.textContent = action_text; + button.addEventListener("click", () => on_action()); + wrap.appendChild(button); + } + feed_container.appendChild(wrap); +} +function set_btn_disabled(btn, is_disabled) { + if (!btn) return; + btn.disabled = Boolean(is_disabled); +} +function set_btn_copied_state(btn, params = {}) { + if (!btn) return; + const { + idle_text = "Copy", + copied_text = "Copied" + } = params; + btn.textContent = copied_text; + btn.classList.add("is-copied"); + setTimeout(() => { + btn.textContent = idle_text; + btn.classList.remove("is-copied"); + }, 1400); +} +async function write_text_to_clipboard(text = "") { + if (!text) return false; + try { + if (navigator?.clipboard?.writeText) { + await navigator.clipboard.writeText(text); + return true; + } + } catch (_err) { + } + try { + if (typeof window !== "undefined" && typeof window.require === "function") { + const { clipboard } = window.require("electron"); + if (clipboard?.writeText) { + clipboard.writeText(text); + return true; + } + } + } catch (_err) { + } + try { + if (typeof document === "undefined") return false; + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.style.position = "fixed"; + textarea.style.left = "-9999px"; + textarea.style.top = "0"; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + const copied = document.execCommand("copy"); + document.body.removeChild(textarea); + return Boolean(copied); + } catch (_err) { + return false; + } +} +function get_entry_row_key(entry) { + return `${get_entry_event_key(entry)}:${get_entry_timestamp2(entry)}`; +} +function get_required_visible_count_for_entry(entries = [], target_entry_key = "") { + if (!Array.isArray(entries) || !target_entry_key) return null; + const target_index = entries.findIndex((entry) => get_entry_row_key(entry) === target_entry_key); + if (target_index === -1) return null; + return entries.length - target_index; +} +function reveal_target_entry(feed_container, target_entry_key = "") { + if (!feed_container || !target_entry_key) return false; + const target_entry_el = Array.from(feed_container.querySelectorAll(".smart-env-notification")).find((details_el) => details_el.dataset.entryKey === target_entry_key); + if (!target_entry_el) return false; + target_entry_el.open = true; + target_entry_el.scrollIntoView?.({ block: "center", behavior: "smooth" }); + return true; +} +function get_event_key_total_count(env, event_key) { + if (!event_key) return 0; + return env?.event_logs?.get?.(event_key)?.data?.ct || 0; +} +function create_tag(feed_container, class_name, text) { + const el = feed_container.ownerDocument.createElement("span"); + el.className = class_name; + el.textContent = text; + return el; +} +function append_meta_row(feed_container, container, label, value) { + const row = feed_container.ownerDocument.createElement("div"); + row.className = "smart-env-notification__expanded-row"; + const label_el = feed_container.ownerDocument.createElement("span"); + label_el.className = "smart-env-notification__expanded-label"; + label_el.textContent = label; + const value_el = feed_container.ownerDocument.createElement("span"); + value_el.className = "smart-env-notification__expanded-value"; + value_el.textContent = value; + row.appendChild(label_el); + row.appendChild(value_el); + container.appendChild(row); +} +function format_timestamp(timestamp) { + try { + return new Date(timestamp).toLocaleString(); + } catch (_error) { + return String(timestamp); + } +} + +// node_modules/obsidian-smart-env/src/modals/smart_model_modal.js +var import_obsidian17 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/modals/smart_model_modal.css +var smart_model_modal_default = ".modal-content.smart-model-modal .setting-component:has(.dropdown-no-options) {\n display: block;\n}"; + +// node_modules/obsidian-smart-env/src/utils/render_settings_config.js +var import_obsidian16 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/settings_config_utils.js +function ensure_settings_config(settings_config13, scope) { + try { + if (typeof settings_config13 === "function") { + settings_config13 = settings_config13(scope); + } + } catch (e) { + console.error("Error evaluating settings_config function:", e); + settings_config13 = { error: { name: "Error", description: `Failed to load settings. ${e.message} (logged to console)` } }; + } + return settings_config13; +} +function build_settings_group_map(settings_config13, scope, default_group_name) { + const resolved_settings_config = ensure_settings_config(settings_config13, scope); + return Object.entries(resolved_settings_config || {}).reduce((acc, [key, config]) => { + const group = config.group || default_group_name; + if (!acc[group]) acc[group] = {}; + acc[group][key] = config; + return acc; + }, { [default_group_name]: {} }); +} +function resolve_group_settings_config(settings_config13, scope, group_name, default_group_name) { + const group_map = build_settings_group_map(settings_config13, scope, default_group_name); + return group_map[group_name] || {}; +} + +// node_modules/obsidian-smart-env/src/utils/render_settings_config.js +var SettingGroupPolyfill = class { + constructor(container) { + this.components = []; + this.groupEl = container.createDiv("setting-group"); + this.headerEl = this.groupEl.createDiv("setting-item setting-item-heading"); + this.headerInnerEl = this.headerEl.createDiv("setting-item-name"); + this.controlEl = this.headerEl.createDiv("setting-item-control"); + this.listEl = this.groupEl.createDiv("setting-items"); + } + setHeading(heading) { + this.headerInnerEl.setText(heading); + } + addSetting(callback) { + const setting = new import_obsidian16.Setting(this.listEl); + this.components.push(setting); + callback(setting); + return setting; + } + addClass(class_name) { + this.groupEl.addClass(class_name); + } +}; +function render_settings_config(settings_config13, scope, container, params = {}) { + const { + default_group_name = "Settings" + } = params; + const settings_config_source = settings_config13; + const group_map = build_settings_group_map(settings_config13, scope, default_group_name); + const group_params = params.group_params || {}; + const settings_groups = Object.entries(group_map).sort(([a], [b]) => { + const a_order = group_params[a]?.order ?? Number.POSITIVE_INFINITY; + const b_order = group_params[b]?.order ?? Number.POSITIVE_INFINITY; + if (a_order !== b_order) { + return a_order - b_order; + } + return a === default_group_name ? -1 : b === default_group_name ? 1 : 0; + }).filter(([, group_config]) => Object.keys(group_config).length > 0).map(([group_name, group_config]) => { + const group_container = container.createDiv(); + const group_params2 = { + ...params, + ...params.group_params?.[group_name] || {}, + settings_config_source + }; + return render_settings_group( + group_name, + scope, + group_config, + group_container, + group_params2 + ); + }); + return settings_groups; +} +function render_settings_group(group_name, scope, settings_config13, container, params = {}) { + const settings_config_source = params.settings_config_source || settings_config13; + const settings_config_group = params.settings_config_source ? resolve_group_settings_config( + settings_config_source, + scope, + group_name, + params.default_group_name || "Settings" + ) : settings_config13; + let SettingGroup; + try { + const obsidian_module = require("obsidian"); + if (obsidian_module.SettingGroup) { + SettingGroup = obsidian_module.SettingGroup; + } else { + SettingGroup = SettingGroupPolyfill; + } + } catch (e) { + SettingGroup = SettingGroupPolyfill; + } + settings_config13 = settings_config_group; + const { + heading_btn = null + } = params; + const render_group = params.settings_config_source ? (group_name2, scope2, settings_config14, container2, group_params) => { + const group_config = resolve_group_settings_config( + settings_config14, + scope2, + group_name2, + group_params.default_group_name || "Settings" + ); + return render_settings_group(group_name2, scope2, group_config, container2, group_params); + } : render_settings_group; + const rerender_settings_group = create_settings_group_rerender(scope, { + container, + group_name, + settings_config: settings_config_source, + group_params: params, + render_group + }); + let setting_group = new SettingGroup(container); + if (heading_btn && typeof heading_btn === "object") { + if (Array.isArray(heading_btn)) { + for (const btn_config of heading_btn) { + render_heading_button(setting_group, scope, btn_config); + } + } else { + render_heading_button(setting_group, scope, heading_btn); + } + } + setting_group.setHeading(group_name); + for (const [setting_path, setting_config] of Object.entries(settings_config13)) { + if (!setting_config || typeof setting_config !== "object") { + console.warn(`Invalid setting config for ${setting_path}:`, setting_config); + continue; + } + const settng_is_pro = setting_config.scope_class === "pro-setting"; + const env_is_pro = !!scope.env?.is_pro || !!scope.is_pro; + setting_group.addSetting((setting) => { + if (setting_config.name) setting.setName(setting_config.name); + setting.setClass(setting_path.replace(/[^a-zA-Z0-9]/g, "-")); + if (setting_config.type) setting.setClass(`setting-type-${setting_config.type}`); + if (setting_config.description) { + setting.setDesc(setting_config.description); + } + switch (setting_config.type) { + case "button": + setting.addButton((btn) => { + btn.setButtonText(setting_config.name || "Run"); + btn.onClick(async (event) => { + if (typeof setting_config.callback === "function") { + await handle_config_callback(setting, event, setting_config.callback, { scope }); + } + }); + }); + break; + case "toggle": + setting.addToggle((toggle) => { + toggle.setValue(get_by_path(scope.settings, setting_path) || false); + toggle.onChange((value) => { + if (settng_is_pro && !env_is_pro) { + scope?.env?.events?.emit?.("settings:pro_feature_blocked", { + level: "warning", + message: "Nice try! This is a PRO feature. Please upgrade to access this setting.", + event_source: "render_settings_config.toggle" + }); + return; + } + set_by_path(scope.settings, setting_path, value); + if (typeof setting_config.callback === "function") { + handle_config_callback(setting, value, setting_config.callback, { scope }); + } + }); + }); + break; + case "text": + setting.addText((text) => { + text.setValue(String(get_by_path(scope.settings, setting_path) || "")); + text.onChange((value) => { + set_by_path(scope.settings, setting_path, value); + }); + }); + break; + case "password": + setting.addText((text) => { + text.setValue(String(get_by_path(scope.settings, setting_path) || "")); + text.inputEl.setAttribute("type", "password"); + text.onChange((value) => { + set_by_path(scope.settings, setting_path, value); + }); + }); + break; + case "number": + setting.addText((text) => { + text.setValue(String(get_by_path(scope.settings, setting_path) ?? "0")); + text.inputEl.setAttribute("type", "number"); + text.onChange((value) => { + const num_value = Number(value); + if (!isNaN(num_value)) { + set_by_path(scope.settings, setting_path, num_value); + } + if (typeof setting_config.callback === "function") { + handle_config_callback(setting, num_value, setting_config.callback, { scope }); + } + }); + }); + break; + case "dropdown": + setting.addDropdown(async (dropdown) => { + const options_callback = setting_config.options_callback; + if (typeof options_callback === "function") { + const options = await options_callback.call(scope, scope); + options.forEach((opt) => { + const label = opt.label || opt.name || opt.value; + dropdown.addOption(opt.value, label); + }); + } + dropdown.setValue(get_by_path(scope.settings, setting_path) || ""); + dropdown.onChange((value) => { + set_by_path(scope.settings, setting_path, value); + if (typeof setting_config.callback === "function") { + handle_config_callback(setting, value, setting_config.callback, { scope }); + } + rerender_settings_group(); + }); + }); + break; + case "textarea": + setting.addTextArea((text) => { + text.setValue(String(get_by_path(scope.settings, setting_path) || "")); + text.onChange((value) => { + if (settng_is_pro && !env_is_pro) { + scope?.env?.events?.emit?.("settings:pro_feature_blocked", { + level: "warning", + message: "Nice try! This is a PRO feature. Please upgrade to access this setting.", + event_source: "render_settings_config.textarea" + }); + return; + } + set_by_path(scope.settings, setting_path, value); + }); + if (settng_is_pro && !env_is_pro) { + text.setDisabled(true); + } + }); + break; + case "slider": + setting.addSlider((slider) => { + const min = setting_config.min || 0; + const max = setting_config.max || 100; + const step = setting_config.step || 1; + slider.setLimits(min, max, step); + slider.setValue(get_by_path(scope.settings, setting_path) || min); + slider.setDynamicTooltip(); + slider.onChange((value) => { + set_by_path(scope.settings, setting_path, value); + if (typeof setting_config.callback === "function") { + handle_config_callback(setting, value, setting_config.callback, { scope }); + } + }); + }); + break; + case "heading": + setting.setHeading(); + break; + case "html": + if (setting_config.value) { + setting.descEl.replaceChildren( + document.createRange().createContextualFragment(setting_config.value) + ); + } + break; + default: + console.warn(`Unsupported setting type for ${setting_path}:`, setting_config.type); + break; + } + if (setting_config.scope_class) { + setting.settingEl.addClass(setting_config.scope_class); + } + if (settng_is_pro && !env_is_pro) { + setting.setDisabled(true); + } + }); + } + return setting_group; +} +function render_heading_button(setting_group, scope, heading_btn) { + const btn_el = setting_group.controlEl.createEl("button", { cls: "" }); + if (heading_btn.btn_icon) { + (0, import_obsidian16.setIcon)(btn_el, heading_btn.btn_icon); + } + if (heading_btn.btn_text) { + btn_el.setText(heading_btn.btn_text); + } + if (heading_btn.label) { + btn_el.setAttr("aria-label", heading_btn.label); + } + btn_el.addEventListener("click", async (event) => { + if (typeof heading_btn.callback === "function") { + await handle_config_callback(null, event, heading_btn.callback, { scope }); + } else { + console.warn("No callback defined for heading button"); + } + }); + setting_group.controlEl.appendChild(btn_el); +} +async function handle_config_callback(setting, event_or_value, cb, params = {}) { + const { + scope = null + } = params; + if (scope) { + return await cb.call(scope, event_or_value, setting); + } else { + return await cb(event_or_value, setting); + } +} +function create_settings_group_rerender(scope, params = {}) { + const { + container, + group_name, + settings_config: settings_config13, + group_params = {}, + render_group + } = params; + return () => { + if (!container || typeof render_group !== "function") return null; + container.replaceChildren(); + return render_group(group_name, scope, settings_config13, container, group_params); + }; +} + +// node_modules/obsidian-smart-env/src/modals/smart_model_modal.js +var SmartModelModal = class extends import_obsidian17.Modal { + /** + * @param {App} app + * @param {EditModelModalOpts} opts + */ + constructor(model, params = {}) { + const app2 = model.env.plugin.app || window.app; + super(app2); + this.model = model; + this.collection = this.model.collection; + this.env = this.model.env; + this.params = params; + } + onOpen() { + this.titleEl.setText("Edit model"); + this.contentEl.addClass("smart-model-modal"); + this.render_form(); + } + onClose() { + this.contentEl.empty(); + if (typeof this.params.on_close === "function") { + this.params.on_close(); + } + } + async render_form() { + const container = this.contentEl; + container.empty(); + const model = this.model; + const model_actions_bar = await this.env.smart_components.render_component("settings_model_actions", model, { + // these callbacks should probably be handled via events instead + on_before_new: async () => { + this.close(); + }, + on_after_delete: async () => { + this.close(); + } + }); + container.appendChild(model_actions_bar); + const settings = model.settings_config; + this.env.smart_view.apply_style_sheet(smart_model_modal_default); + const form_container = container.createDiv({ cls: "smart-model-settings-form" }); + render_settings_config(settings, model, form_container, { + default_group_name: "Model settings" + }); + const test_btn = container.createEl("button", { text: "Test model" }); + const test_results_el = container.createDiv({ cls: "model-test-container" }); + test_btn.addEventListener("click", async () => { + await this.run_test(test_results_el, model); + }); + if (this.params.test_on_open) { + await this.run_test(test_results_el, model); + } + } + async run_test(test_results_el, model) { + test_results_el.empty(); + const test_result_el = test_results_el.createEl("pre", { cls: "model-test-result", text: "Testing..." }); + test_results_el.appendChild(test_result_el); + const test_result = await model.test_model(); + test_result_el.textContent = JSON.stringify(test_result, null, 2); + } +}; + +// node_modules/obsidian-smart-env/src/components/settings/env_model.css +var env_model_default = '.model-settings .model-info {\n border-radius: var(--radius-m);\n padding: 1rem;\n margin-bottom: 1rem;\n background-color: var(--background-secondary);\n pre {\n margin: 0;\n font-size: 0.9rem;\n }\n .test-result-icon {\n vertical-align: middle;\n margin-left: 0.5rem;\n }\n .test-result-icon[data-icon="square-check-big"]{\n color: var(--color-green);\n }\n .test-result-icon[data-icon="circle-x"]{\n color: var(--color-red);\n }\n}\n\n.smart-model-modal{\n pre, .model-note {\n user-select: text;\n }\n}'; + +// node_modules/obsidian-smart-env/src/components/settings/env_model.js +var import_obsidian18 = require("obsidian"); +function build_html7(model, params) { + const details = [ + `Provider: ${model.data.provider_key}`, + `Model: ${model.data.model_key || "**MISSING - EDIT & SELECT MODEL**"}` + ]; + return `
    +
    + Current: ${model.display_name} +
    + + +
    +
    +
    ${details.join("\n")}
    +
    `; +} +async function render9(model, params) { + this.apply_style_sheet(env_model_default); + const frag = this.create_doc_fragment(build_html7.call(this, model, params)); + const container = frag.firstElementChild; + post_process7.call(this, model, container, params); + return container; +} +async function post_process7(model, container, params) { + const edit_btn = container.querySelector(".edit-model"); + const test_btn = container.querySelector(".test-model"); + const icon_el = container.querySelector(".test-result-icon"); + (0, import_obsidian18.setIcon)(icon_el, get_test_result_icon_name(model)); + edit_btn.addEventListener("click", () => { + new SmartModelModal(model).open(); + }); + test_btn.addEventListener("click", () => { + new SmartModelModal(model, { test_on_open: true }).open(); + }); + return container; +} +function get_test_result_icon_name(model) { + switch (model.data.test_passed) { + case true: + return "square-check-big"; + case false: + return "circle-x"; + default: + return "square"; + } +} + +// node_modules/obsidian-smart-env/src/utils/smart-models/show_new_model_menu.js +var import_obsidian19 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/smart-models/provider_options.js +var provider_options = { + chat_completion_models: [ + { + label: "Open Router (cloud)", + value: "open_router" + }, + { + label: "PRO: LM Studio (local, requires LM Studio app)", + value: "lm_studio", + disabled: true + }, + { + label: "PRO: Ollama (local, requires Ollama app)", + value: "ollama", + disabled: true + }, + { + label: "PRO: OpenAI (cloud)", + value: "openai", + disabled: true + }, + { + label: "PRO: Google Gemini (cloud)", + value: "google", + disabled: true + }, + { + label: "PRO: Cohere (cloud)", + value: "cohere", + disabled: true + }, + { + label: "PRO: xAI Grok (cloud)", + value: "xai", + disabled: true + }, + { + label: "PRO: Anthropic Claude (cloud)", + value: "anthropic", + disabled: true + }, + { + label: "PRO: Deepseek (cloud)", + value: "deepseek", + disabled: true + }, + { + label: "PRO: Azure OpenAI (cloud)", + value: "azure", + disabled: true + }, + { + label: "Experimental: Lite LLM (self-hosted proxy)", + value: "litellm", + disabled: true + } + ], + embedding_models: [ + { + label: "Transformers (easy, local, built-in)", + value: "transformers" + }, + { + label: "PRO: LM Studio (local, requires LM Studio app)", + value: "lm_studio", + disabled: true + }, + { + label: "PRO: Ollama (local, requires Ollama app)", + value: "ollama", + disabled: true + }, + { + label: "PRO: OpenAI (cloud)", + value: "openai", + disabled: true + }, + { + label: "PRO: Google Gemini (cloud)", + value: "gemini", + disabled: true + }, + { + label: "PRO: Open Router (cloud)", + value: "open_router", + disabled: true + } + ], + ranking_models: [ + { + label: "PRO: Cohere (cloud)", + value: "cohere", + disabled: true + } + ] +}; + +// node_modules/obsidian-smart-env/src/utils/smart-models/show_new_model_menu.js +function show_new_model_menu(models_collection, event, params = {}) { + const providers = (provider_options[models_collection.collection_key] || []).map((p) => ({ ...p, disabled: !models_collection.env_config.providers[p.value] })); + console.log("show_new_model_menu providers", providers); + if (providers.length === 0) { + if (event.target.tagName.toLowerCase() === "button") { + event.target.disabled = true; + event.title = "No providers available to create new models."; + } + } else { + const menu = new import_obsidian19.Menu(); + providers.forEach((provider) => { + menu.addItem((item) => { + item.setTitle(provider.label); + if (provider.disabled) { + item.setDisabled(true); + } + item.onClick(async () => { + if (typeof params.on_before_new === "function") { + await params.on_before_new(); + } + const model = models_collection.new_model({ provider_key: provider.value }); + const on_new_close = async () => { + }; + new SmartModelModal(model, { on_close: on_new_close }).open(); + }); + }); + }); + menu.showAtMouseEvent(event); + } +} + +// node_modules/obsidian-smart-env/src/components/settings/env_model_type.js +function build_html8(models_collection, params) { + return `
    +
    +
    `; +} +async function render10(models_collection, params) { + const frag = this.create_doc_fragment(build_html8.call(this, models_collection, params)); + const container = frag.firstElementChild; + post_process8.call(this, models_collection, container, params); + return container; +} +async function post_process8(models_collection, container, params) { + const disposers = []; + const render_current_model_info = async (current_model) => { + this.empty(container); + const [settings_group] = render_settings_config( + models_collection.env_config.settings_config, + models_collection, + container, + { + default_group_name: `${models_collection.model_type} models`, + heading_btn: { + btn_text: "+ New", + callback: (event, setting) => { + show_new_model_menu(models_collection, event); + } + } + } + ); + models_collection.env.smart_components.render_component("settings_env_model", current_model, {}).then((model_info_el) => { + settings_group.listEl.appendChild(model_info_el); + }); + }; + render_current_model_info(models_collection.default); + disposers.push(models_collection.on_event("settings:changed", async (payload) => { + const default_setting_path = `${models_collection.collection_key}.default_model_key`; + if (payload.path_string === default_setting_path) { + await render_current_model_info(models_collection.default); + } + })); + disposers.push(models_collection.on_event("model:changed", async () => { + await render_current_model_info(models_collection.default); + })); + this.attach_disposer(container, disposers); +} + +// node_modules/obsidian-smart-env/src/components/settings/env_models.js +function build_html9(env, params) { + const models_collections = [ + env.embedding_models, + env.chat_completion_models, + env.ranking_models + ].filter(Boolean); + const type_containers = models_collections.map((models_collection) => { + return `
    `; + }).join("\n"); + return `
    + ${type_containers} +
    `; +} +async function render11(env, params) { + const frag = this.create_doc_fragment(build_html9(env, params)); + const container = frag.firstElementChild; + post_process9.call(this, env, container, params); + return container; +} +async function post_process9(env, container, params) { + const collection_containers = container.querySelectorAll("div[data-collection-key]"); + for (const collection_container of collection_containers) { + const collection_key = collection_container.getAttribute("data-collection-key"); + const models_collection = env[collection_key]; + env.smart_components.render_component("settings_env_model_type", models_collection).then((model_type_el) => { + this.empty(collection_container); + collection_container.appendChild(model_type_el); + }); + } + return container; +} + +// node_modules/obsidian-smart-env/src/modals/exclude_folders_fuzzy.js +var import_obsidian20 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/exclusions.js +function ensure_smart_sources_settings(env) { + if (!env.settings) env.settings = {}; + if (!env.settings.smart_sources) env.settings.smart_sources = {}; + const smart_sources_settings = env.settings.smart_sources; + if (!smart_sources_settings.folder_exclusions) smart_sources_settings.folder_exclusions = ""; + if (!smart_sources_settings.file_exclusions) smart_sources_settings.file_exclusions = ""; + return smart_sources_settings; +} +function parse_exclusions_csv(exclusions = "") { + return exclusions.split(",").map((value) => value.trim()).filter(Boolean); +} +function add_exclusion(exclusions, value) { + const trimmed = (value ?? "").trim(); + if (!trimmed) return exclusions || ""; + const current = parse_exclusions_csv(exclusions); + if (!current.includes(trimmed)) current.push(trimmed); + return current.join(","); +} +function remove_exclusion(exclusions, value) { + const trimmed = (value ?? "").trim(); + if (!trimmed) return exclusions?.trim() || ""; + const filtered = parse_exclusions_csv(exclusions).filter((entry) => entry !== trimmed); + return filtered.join(","); +} + +// node_modules/obsidian-smart-env/src/modals/exclude_folders_fuzzy.js +var ExcludedFoldersFuzzy = class extends import_obsidian20.FuzzySuggestModal { + /** + * @param {App} app - The Obsidian app + * @param {Object} env - An environment-like object, must have .settings and .fs.folder_paths + */ + constructor(app2, env) { + super(app2); + this.env = env; + this.setPlaceholder("Select a folder to exclude..."); + } + /** + * Open the modal with an optional callback invoked after an item is chosen. + * The current exclusion list is rendered at the top of the modal. + * @param {Function} [selection_callback] + */ + open(selection_callback) { + this.callback = selection_callback; + super.open(); + this.render_excluded_list(); + } + /** + * Return candidate folder paths that are not already excluded. + * @returns {string[]} + */ + getItems() { + const smart_sources_settings = ensure_smart_sources_settings(this.env); + const folder_exclusions2 = parse_exclusions_csv(smart_sources_settings.folder_exclusions); + const candidates = (this.env.smart_sources?.fs?.folder_paths || []).filter((path) => !folder_exclusions2.includes(path)); + return candidates; + } + getItemText(item) { + return item; + } + /** + * Handle selecting a folder to exclude. + * @param {string} item + */ + onChooseItem(item) { + this.prevent_close = true; + if (!item) return; + const smart_sources_settings = ensure_smart_sources_settings(this.env); + smart_sources_settings.folder_exclusions = add_exclusion(smart_sources_settings.folder_exclusions, item); + this.render_excluded_list(); + this.updateSuggestions(); + this.callback?.(); + } + /** + * Render the current list of excluded folders at the top of the modal, + * with inline remove buttons. + */ + render_excluded_list() { + if (!this.modalEl) return; + const smart_sources_settings = ensure_smart_sources_settings(this.env); + const excluded_folders = parse_exclusions_csv(smart_sources_settings.folder_exclusions); + let header = this.modalEl.querySelector(".sc-excluded-folders-header"); + if (!header) { + header = this.modalEl.createEl("div", { cls: "sc-excluded-folders-header" }); + this.modalEl.prepend(header); + } + header.empty(); + const title_el = header.createEl("h3"); + title_el.setText("Excluded folders"); + if (!excluded_folders.length) { + const empty_el = header.createEl("p"); + empty_el.setText("No folders excluded yet."); + return; + } + const list_el = header.createEl("ul"); + excluded_folders.forEach((folder_path) => { + const li = list_el.createEl("li", { cls: "excluded-folder-item" }); + li.setText(folder_path + " "); + const remove_btn = li.createEl("button", { + text: "(x)", + cls: "remove-excluded-folder-btn" + }); + remove_btn.addEventListener("click", () => { + smart_sources_settings.folder_exclusions = remove_exclusion( + smart_sources_settings.folder_exclusions, + folder_path + ); + this.env.update_exclusions?.(); + this.render_excluded_list(); + this.updateSuggestions(); + }); + }); + } + close() { + setTimeout(() => { + if (!this.prevent_close) super.close(); + this.prevent_close = false; + }, 10); + } +}; + +// node_modules/obsidian-smart-env/src/modals/excluded_sources.js +var import_obsidian21 = require("obsidian"); +var ExcludedSourcesModal = class extends import_obsidian21.Modal { + /** + * @param {Object} app - Obsidian app + * @param {Object} env - The environment instance + */ + constructor(app2, env) { + super(app2); + this.env = env; + } + async onOpen() { + this.titleEl.setText("Excluded Sources"); + this.contentEl.addClass("excluded-sources-modal"); + this.render_excluded_list(); + } + async render_excluded_list() { + this.contentEl.empty(); + const list_el = this.contentEl.createEl("ul"); + const excluded_file_paths = this.env.smart_sources.excluded_file_paths; + const too_long_files = this.app.vault.getMarkdownFiles().filter((file) => file.path.length > 200).map((file) => file.path); + for (const file_path of excluded_file_paths) { + const li = list_el.createEl("li"); + li.setText(file_path); + } + this.contentEl.createEl("hr"); + this.contentEl.createEl("h3", { text: "Paths too long to import into Smart Environment" }); + const too_long_list_ul = this.contentEl.createEl("ul", { cls: "too-long-exclusions" }); + for (const file_path of too_long_files) { + const li = too_long_list_ul.createEl("li"); + li.setText(file_path); + } + } +}; + +// node_modules/obsidian-smart-env/src/components/settings/env_sources.js +async function build_html10(env, opts = {}) { + return ` +
    +
    + `; +} +async function render12(env, opts = {}) { + const html = await build_html10.call(this, env, opts); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process10.call(this, env, container, opts); + return container; +} +async function post_process10(env, container, opts = {}) { + const settings_config13 = { + re_import_wait_time, + folder_exclusions, + view_exclusions, + // reset_env_settings_btn, // TODO: manually tested before implementing reset button + re_import_sources + }; + render_settings_config(settings_config13, env, container, { + default_group_name: "Sources", + heading_btn: { + btn_icon: "help-circle", + callback: () => { + window.open("https://smartconnections.app/smart-environment/settings/?utm_source=source-settings", "_external"); + } + } + }); + const disposers = []; + disposers.push(env.events?.on("model:changed", highlight_reset_data(env, container))); + this.attach_disposer(container, disposers); + return container; +} +function highlight_reset_data(env, container) { + return async (payload) => { + if (payload.collection_key !== "embedding_models") return; + const re_import_setting = container.querySelector(".re-import-sources"); + re_import_setting.classList.add("env-setting-highlight"); + const notice = re_import_setting.querySelector(".reimport-notice") ? re_import_setting.querySelector(".reimport-notice") : re_import_setting.createEl("div", { cls: "reimport-notice env-setting-note" }); + notice.textContent = "Embedding model changed. Please re-import your sources to update their embeddings."; + re_import_setting.appendChild(notice); + env.events.once("sources:reimported", () => { + re_import_setting.classList.remove("env-setting-highlight"); + notice.remove(); + }); + }; +} +var re_import_wait_time = { + type: "number", + name: "Re-import wait time", + description: "Time in seconds to wait before re-importing a file after modification. Increase if re-importing is interfering with editing experience. Decrease to have changes reflected in the Smart Environment more quickly." +}; +var folder_exclusions = { + type: "button", + name: "Manage excluded folders", + description: "Manage the list of folders excluded from processing.", + btn_text: "Manage folders", + callback: async function() { + const env = this; + const fuzzy = new ExcludedFoldersFuzzy(env.main.app, env); + const selection_callback = () => { + env.update_exclusions(); + }; + fuzzy.open(selection_callback); + } +}; +var view_exclusions = { + type: "button", + name: "View all exclusions", + description: "View all excluded sources.", + btn_text: "Show", + callback: async function() { + const env = this; + const modal = new ExcludedSourcesModal(env.main.app, env); + modal.open(); + } +}; +var re_import_sources = { + type: "button", + name: "Reset data", + description: "Clear sources data and re-import.", + btn_text: "Re-import sources", + callback: async function(value, setting) { + const env = this; + const container = setting.controlEl; + const confirm_row = container.createEl("div", { cls: "sc-inline-confirm-row" }); + const reimport_btn = container.querySelector("button"); + reimport_btn.style.display = "none"; + confirm_row.setText("Are you sure you want to clear all sources data? This cannot be undone."); + let confirm_cancel = confirm_row.createEl("button", { text: "Cancel" }); + let confirm_yes = confirm_row.createEl("button", { text: "Re-import", cls: "mod-warning" }); + confirm_yes.addEventListener("click", async (event) => { + confirm_cancel.style.display = "none"; + confirm_yes.textContent = "Re-importing..."; + confirm_yes.disabled = true; + const row = event.target.closest(".sc-inline-confirm-row"); + await env.smart_sources.run_clear_all(); + const start = Date.now(); + env.smart_sources.unload(); + env.smart_blocks.unload(); + await env.init_collections(); + await env.load_collections(); + await env.smart_sources.process_embed_queue(); + const end = Date.now(); + env.events?.emit("sources:reimported", { + time_ms: end - start, + message: format_reimport_message(end - start), + event_source: "settings_env_sources.re_import_sources" + }); + row.style.display = "none"; + reimport_btn.style.display = "inline-block"; + confirm_yes.textContent = "Yes"; + confirm_yes.disabled = false; + }); + confirm_cancel.addEventListener("click", () => { + confirm_row.style.display = "none"; + reimport_btn.style.display = "inline-block"; + }, { once: true }); + } +}; +function format_reimport_message(time_ms) { + const seconds = Math.max(0, Math.round(time_ms / 100) / 10); + return `Sources re-imported in ${seconds}s.`; +} + +// node_modules/obsidian-smart-env/src/components/settings/model_actions.js +function build_html11(model, params = {}) { + return `
    + + + +
    `; +} +async function render13(model, params = {}) { + const frag = this.create_doc_fragment(build_html11(model, params)); + const container = frag.firstElementChild; + post_process11.call(this, model, container, params); + return container; +} +async function post_process11(model, container, params = {}) { + const new_model_btn = container.querySelector(".new-model-btn"); + new_model_btn.addEventListener("click", async (event) => { + const on_before_new = params.on_before_new; + const opts = {}; + if (typeof on_before_new === "function") { + opts.on_before_new = on_before_new; + } + show_new_model_menu(model.collection, event, opts); + }); + const delete_model_btn = container.querySelector(".delete-model-btn"); + const confirm_delete_container = container.querySelector(".confirm-delete-container"); + const confirm_delete_yes_btn = container.querySelector(".confirm-delete-yes-btn"); + const confirm_delete_no_btn = container.querySelector(".confirm-delete-no-btn"); + delete_model_btn.addEventListener("click", async () => { + confirm_delete_container.style.display = ""; + }); + confirm_delete_no_btn.addEventListener("click", async () => { + confirm_delete_container.style.display = "none"; + }); + confirm_delete_yes_btn.addEventListener("click", async () => { + await model.delete_model(); + if (typeof params.on_after_delete === "function") { + params.on_after_delete(); + } + }); + return container; +} + +// node_modules/obsidian-smart-env/src/components/settings/style.css +var style_default = ".sc-env-settings-container {\n margin: 1rem 0;\n}\n\n.smart-env-settings-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.5rem;\n}\n\n.toggle-env-settings-btn {\n cursor: pointer;\n}\n\n\n.setting-group .setting-items .setting-item.env-setting-highlight {\n border: 1px solid var(--color-accent);\n background-color: var(--interactive-hover);\n padding: 0.5rem;\n margin: 0.5rem 0;\n}\n\n.settings-group {\n .setting-item {\n border-top: none;\n }\n}\n\n.sc-inline-confirm-row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-top: 0.5rem;\n}\n"; + +// node_modules/obsidian-smart-env/src/components/settings/smart_env.js +async function build_html12(env, params = {}) { + return `
    +
    +

    Sources

    +
    +
    +

    Models

    +
    +
    +

    Notifications

    +
    +
    `; +} +async function render14(env, params = {}) { + this.apply_style_sheet(style_default); + const html = await build_html12.call(this, env, params); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process12.call(this, env, container, params); + return container; +} +async function post_process12(env, container, opts = {}) { + const models_container = container.querySelector(".models-container"); + const sources_container = container.querySelector(".sources-container"); + const notifications_container = container.querySelector(".notifications-container"); + render_if_available.call(this, "settings_env_sources", env, sources_container); + render_if_available.call(this, "settings_env_models", env, models_container); + render_settings_config( + env.event_logs.settings_config, + env.event_logs, + notifications_container, + { + default_group_name: "Notifications" + } + ); + return container; +} +function render_if_available(component_key, env, container) { + if (env.config.components[component_key]) { + const placeholder = this.create_doc_fragment(`
    `).firstElementChild; + container.appendChild(placeholder); + env.smart_components.render_component(component_key, env).then((comp_el) => { + this.empty(placeholder); + placeholder.appendChild(comp_el); + }); + } +} + +// node_modules/obsidian-smart-env/src/utils/smart-context/copy_actions.js +var import_obsidian22 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/copy_to_clipboard.js +function emit_clipboard_event(env, event_key, payload = {}) { + if (!event_key) return; + env?.events?.emit?.(event_key, payload); +} +async function write_with_navigator_clipboard(text) { + if (typeof navigator === "undefined") return false; + if (!navigator.clipboard || typeof navigator.clipboard.writeText !== "function") { + return false; + } + await navigator.clipboard.writeText(text); + return true; +} +function write_with_electron_clipboard(text) { + if (typeof window === "undefined" || typeof window.require !== "function") { + return false; + } + const electron = window.require("electron"); + if (!electron?.clipboard || typeof electron.clipboard.writeText !== "function") { + return false; + } + electron.clipboard.writeText(text); + return true; +} +function write_with_exec_command(text) { + if (typeof document === "undefined") return false; + if (typeof document.execCommand !== "function") return false; + if (!document.body?.appendChild) return false; + const textarea = document.createElement("textarea"); + textarea.value = text; + textarea.setAttribute("readonly", "true"); + textarea.style.position = "fixed"; + textarea.style.opacity = "0"; + textarea.style.pointerEvents = "none"; + document.body.appendChild(textarea); + textarea.focus(); + textarea.select(); + let copied = false; + try { + copied = Boolean(document.execCommand("copy")); + } finally { + textarea.remove(); + } + return copied; +} +async function copy_to_clipboard(text = "", params = {}) { + const value = typeof text === "string" ? text : String(text ?? ""); + const env = params.env || null; + const event_source = params.event_source || "copy_to_clipboard"; + const success_event_key = params.success_event_key || "clipboard:copied"; + const error_event_key = params.error_event_key || "clipboard:copy_failed"; + const unavailable_event_key = params.unavailable_event_key || "clipboard:copy_unavailable"; + let last_error = null; + try { + if (await write_with_navigator_clipboard(value)) { + emit_clipboard_event(env, success_event_key, { + message: "Text copied to clipboard.", + event_source + }); + return true; + } + } catch (error) { + last_error = error; + } + try { + if (write_with_electron_clipboard(value)) { + emit_clipboard_event(env, success_event_key, { + message: "Text copied to clipboard.", + event_source + }); + return true; + } + } catch (error) { + last_error = error; + } + try { + if (write_with_exec_command(value)) { + emit_clipboard_event(env, success_event_key, { + message: "Text copied to clipboard.", + event_source + }); + return true; + } + } catch (error) { + last_error = error; + } + if (last_error) { + emit_clipboard_event(env, error_event_key, { + level: "error", + message: "Failed to copy text to clipboard.", + details: last_error?.message || "", + event_source + }); + return false; + } + emit_clipboard_event(env, unavailable_event_key, { + level: "warning", + message: "Clipboard copy is unavailable in this environment.", + event_source + }); + return false; +} + +// node_modules/obsidian-smart-env/src/utils/smart-context/to_md_tree.js +function normalize_context_item_key(key = "") { + if (typeof key !== "string") return ""; + return key.trim().replace(/^external:/, "").replace(/^selection:/, "").replace(/\\+/g, "/").replace(/\/+/g, "/").replace(/^\.\//, ""); +} +function parse_context_item_key(key = "") { + const raw_key = typeof key === "string" ? key.trim() : ""; + const is_external = raw_key.startsWith("external:"); + const is_selection = raw_key.startsWith("selection:"); + const normalized_key = normalize_context_item_key(raw_key); + let display_key = normalized_key; + if (is_external && display_key.startsWith("../")) { + display_key = display_key.slice(3); + } + return { + raw_key, + normalized_key, + display_key, + is_external, + is_selection + }; +} +function split_path_segments(normalized_key = "") { + if (typeof normalized_key !== "string" || normalized_key.length === 0) return []; + return normalized_key.split("/").map((segment) => segment.trim()).filter(Boolean); +} +function format_wikilink_target(file_segment = "") { + if (typeof file_segment !== "string") return ""; + const trimmed = file_segment.trim(); + if (!trimmed) return ""; + const hash_index = trimmed.indexOf("#"); + if (hash_index === -1) { + return trimmed.replace(/\.md$/i, ""); + } + const source_name = trimmed.slice(0, hash_index).replace(/\.md$/i, ""); + const fragment = trimmed.slice(hash_index); + return `${source_name}${fragment}`; +} +function escape_markdown_link_text(text = "") { + return String(text).replace(/\\/g, "\\\\").replace(/\]/g, "\\]"); +} +function create_tree_node() { + return { + children: [], + dir_nodes: /* @__PURE__ */ new Map(), + file_keys: /* @__PURE__ */ new Set() + }; +} +function ensure_dir_node(node, dir_name) { + const existing = node.dir_nodes.get(dir_name); + if (existing) return existing; + const dir_node = create_tree_node(); + node.dir_nodes.set(dir_name, dir_node); + node.children.push({ + type: "dir", + name: dir_name, + node: dir_node + }); + return dir_node; +} +function ensure_path(root_node, dir_segments = []) { + let node = root_node; + for (let i = 0; i < dir_segments.length; i += 1) { + node = ensure_dir_node(node, dir_segments[i]); + } + return node; +} +function get_vault_adapter(smart_context) { + const app2 = smart_context?.env?.plugin?.app || smart_context?.env?.app || smart_context?.app || globalThis.app || null; + return app2?.vault?.adapter || null; +} +function get_vault_base_path(smart_context) { + const adapter = get_vault_adapter(smart_context); + if (!adapter) return ""; + if (typeof adapter.getBasePath === "function") { + const base_path = adapter.getBasePath(); + if (typeof base_path === "string" && base_path.trim()) { + return base_path.trim(); + } + } + if (typeof adapter.getFullPath === "function") { + try { + const full_path = adapter.getFullPath(""); + if (typeof full_path === "string" && full_path.trim()) { + return full_path.trim(); + } + } catch (err) { + } + } + if (typeof adapter.basePath === "string" && adapter.basePath.trim()) { + return adapter.basePath.trim(); + } + return ""; +} +function encode_file_path_segments(normalized_path = "") { + return normalized_path.split("/").map((segment, index) => { + if (index === 0 && /^[a-zA-Z]:$/.test(segment)) { + return segment; + } + return encodeURIComponent(segment); + }).join("/"); +} +function absolute_path_to_file_href(absolute_path = "") { + const trimmed_path = typeof absolute_path === "string" ? absolute_path.trim() : ""; + if (!trimmed_path) return ""; + if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(trimmed_path)) { + return trimmed_path; + } + const normalized_path = trimmed_path.replace(/\\+/g, "/"); + const encoded_path = encode_file_path_segments(normalized_path); + if (/^[a-zA-Z]:\//.test(normalized_path)) { + return `file:///${encoded_path}`; + } + if (normalized_path.startsWith("//")) { + return `file:${encoded_path}`; + } + if (normalized_path.startsWith("/")) { + return `file://${encoded_path}`; + } + return ""; +} +function resolve_file_href_from_base_path(vault_base_path, normalized_path) { + const base_href = absolute_path_to_file_href(vault_base_path); + if (!base_href) return ""; + const directory_href = base_href.endsWith("/") ? base_href : `${base_href}/`; + const encoded_relative_path = encode_file_path_segments(normalized_path); + try { + return new URL(encoded_relative_path, directory_href).href; + } catch (err) { + return ""; + } +} +function resolve_external_href(smart_context, key = "") { + const parsed = parse_context_item_key(key); + if (!parsed.is_external) return ""; + if (!parsed.normalized_key) return ""; + if (/^[a-zA-Z][a-zA-Z\d+.-]*:\/\//.test(parsed.normalized_key)) { + return parsed.normalized_key; + } + const absolute_key_href = absolute_path_to_file_href(parsed.normalized_key); + if (absolute_key_href) { + return absolute_key_href; + } + const adapter = get_vault_adapter(smart_context); + if (adapter && typeof adapter.getFilePath === "function") { + try { + const file_href = adapter.getFilePath(parsed.normalized_key); + if (typeof file_href === "string" && file_href.trim()) { + return file_href.trim(); + } + } catch (err) { + } + } + if (adapter && typeof adapter.getFullPath === "function") { + try { + const full_path = adapter.getFullPath(parsed.normalized_key); + const full_path_href = absolute_path_to_file_href(full_path); + if (full_path_href) { + return full_path_href; + } + } catch (err) { + } + } + const vault_base_path = get_vault_base_path(smart_context); + if (!vault_base_path) { + return parsed.normalized_key; + } + return resolve_file_href_from_base_path(vault_base_path, parsed.normalized_key) || parsed.normalized_key; +} +function render_tree_lines(node, depth = 0, lines = []) { + for (let i = 0; i < node.children.length; i += 1) { + const child = node.children[i]; + if (!child) continue; + if (child.type === "dir") { + lines.push(`${" ".repeat(depth)}- ${child.name}`); + render_tree_lines(child.node, depth + 1, lines); + continue; + } + const current_suffix = child.is_current ? " (current)" : ""; + if (child.type === "external_file") { + const safe_label = escape_markdown_link_text(child.label); + lines.push(`${" ".repeat(depth)}- [${safe_label}](${child.href})${current_suffix}`); + continue; + } + if (child.type === "file") { + lines.push(`${" ".repeat(depth)}- [[${child.target}]]${current_suffix}`); + } + } + return lines; +} +function list_context_items(smart_context) { + const collection = smart_context?.context_items; + if (collection && typeof collection.filter === "function") { + return collection.filter((item) => { + if (!item?.key) return false; + if (item?.data?.exclude) return false; + return true; + }); + } + const raw_items = smart_context?.data?.context_items; + if (!raw_items || typeof raw_items !== "object") return []; + return Object.entries(raw_items).filter(([key, item_data]) => { + if (!key) return false; + if (item_data?.exclude) return false; + return true; + }).map(([key, item_data]) => ({ key, data: item_data || {} })); +} +function get_active_file_path(smart_context) { + return smart_context?.env?.obsidian_app?.workspace?.getActiveFile?.()?.path || ""; +} +function context_to_md_tree(smart_context) { + const items = list_context_items(smart_context); + if (!items.length) return ""; + const root_node = create_tree_node(); + const seen_keys = /* @__PURE__ */ new Set(); + const active_file_path = get_active_file_path(smart_context); + let did_mark_current = false; + for (let i = 0; i < items.length; i += 1) { + const item = items[i]; + const parsed_key = parse_context_item_key(item?.key); + if (!parsed_key.raw_key || seen_keys.has(parsed_key.raw_key)) continue; + seen_keys.add(parsed_key.raw_key); + const raw_folder_value = item?.data?.folder; + const normalized_folder_value = typeof raw_folder_value === "string" ? normalize_context_item_key(raw_folder_value) : ""; + const is_folder = raw_folder_value === true || parsed_key.display_key.endsWith("/") || parsed_key.normalized_key.endsWith("/") || normalized_folder_value.length > 0 && normalized_folder_value === parsed_key.normalized_key; + const path_segments = split_path_segments(parsed_key.display_key); + if (!path_segments.length) continue; + if (is_folder) { + ensure_path(root_node, path_segments); + continue; + } + const file_segment = path_segments.pop(); + const parent_node = ensure_path(root_node, path_segments); + const is_current = !did_mark_current && active_file_path && parsed_key.normalized_key.split("#")[0] === active_file_path; + if (parsed_key.is_external) { + const href = resolve_external_href(smart_context, parsed_key.raw_key); + if (!href) continue; + const external_key = `external:${href}`; + if (parent_node.file_keys.has(external_key)) continue; + parent_node.file_keys.add(external_key); + parent_node.children.push({ + type: "external_file", + label: file_segment, + href, + is_current + }); + if (is_current) did_mark_current = true; + continue; + } + const wikilink_target = format_wikilink_target(file_segment); + if (!wikilink_target) continue; + const internal_key = `internal:${wikilink_target}`; + if (parent_node.file_keys.has(internal_key)) continue; + parent_node.file_keys.add(internal_key); + parent_node.children.push({ + type: "file", + target: wikilink_target, + is_current + }); + if (is_current) did_mark_current = true; + } + return render_tree_lines(root_node).join("\n"); +} + +// node_modules/obsidian-smart-env/src/utils/smart-context/copy_actions.js +function has_active_context_items(ctx) { + return Number(ctx?.item_count || 0) > 0; +} +function has_any_context_state(ctx) { + return has_active_context_items(ctx) || Number(ctx?.excluded_item_count || 0) > 0; +} +function show_menu_at_button(button, event, menu) { + if (typeof MouseEvent !== "undefined" && event instanceof MouseEvent) { + menu.showAtMouseEvent(event); + return; + } + const rect = button.getBoundingClientRect(); + if (typeof menu.showAtPosition === "function") { + menu.showAtPosition({ x: rect.left, y: rect.bottom }); + return; + } + menu.showAtMouseEvent(new MouseEvent("contextmenu", { + bubbles: true, + cancelable: true, + clientX: rect.left, + clientY: rect.bottom + })); +} +function create_button(container, params = {}) { + const button = container.createEl("button", { + text: params.icon_only ? "" : params.text || "" + }); + if (params.icon) { + button.classList.add("clickable-icon"); + (0, import_obsidian22.setIcon)(button, params.icon); + } + if (params.aria_label || params.text) { + button.setAttribute("aria-label", params.aria_label || params.text); + if (!params.icon_only && !params.text) { + button.textContent = params.aria_label; + } + } + return button; +} +function get_help_url(ctx) { + const ctx_key = String(ctx?.key || ""); + if (ctx_key.endsWith("#codeblock")) { + return "https://smartconnections.app/smart-context/codeblock/?utm_source=context-actions"; + } + return "https://smartconnections.app/smart-context/builder/?utm_source=context-actions"; +} +async function copy_link_tree(ctx) { + const md_tree = context_to_md_tree(ctx).trim(); + if (!md_tree) { + ctx.emit_event("context:copy_empty", { + level: "warning", + message: "No context items to copy.", + event_source: "smart_context.copy_link_tree" + }); + return false; + } + const copied = await copy_to_clipboard(md_tree, { + env: ctx.env, + event_source: "smart_context.copy_link_tree", + success_event_key: "context:clipboard_raw_copied", + error_event_key: "context:clipboard_raw_copy_failed", + unavailable_event_key: "context:clipboard_copy_unavailable" + }); + if (!copied) return false; + ctx.emit_event("context:link_tree_copied", { + level: "info", + message: "Copied link tree to clipboard.", + event_source: "smart_context.copy_link_tree" + }); + return true; +} +function register_copy_menu_actions(env) { + env.register_menu_action("smart_context:copy_menu", (menu, ctx) => { + if (!menu || !ctx || !has_active_context_items(ctx)) return; + const descriptors = [ + { + key: "copy_text", + title: "Copy text", + icon: "copy", + run: async () => { + return await ctx.actions.context_copy_to_clipboard({ with_media: false }); + } + }, + { + key: "copy_link_tree", + title: "Copy link tree", + icon: "list-tree", + order: 3, + run: async () => { + return await copy_link_tree(ctx); + } + } + ]; + descriptors.forEach((descriptor) => { + menu_add_from_desc(menu, descriptor); + }); + }); +} +function register_context_menu_actions(env) { + env.register_menu_action("smart_context:actions_menu", (menu, ctx) => { + if (!menu || !ctx || !has_any_context_state(ctx)) return; + const descriptors = [ + { + key: "final_separator", + separator: true, + order: 998 + }, + { + key: "clear_context", + title: "Clear this context", + icon: "rotate-ccw", + order: 999, + run: async () => { + ctx.clear_all?.(); + return true; + } + } + ]; + descriptors.forEach((descriptor) => { + menu_add_from_desc(menu, descriptor); + }); + }); +} +function menu_add_from_desc(menu, descriptor) { + if (descriptor.separator) { + menu.addSeparator(); + menu.items[menu.items.length - 1]._order = descriptor.order || 0; + return; + } + menu.addItem((mi) => { + mi.setTitle(descriptor.title).setIcon(descriptor.icon).onClick(async () => { + await descriptor.run(); + }); + mi._order = descriptor.order || 0; + }); +} +function build_context_actions_menu(ctx, menu, params = {}) { + if (!ctx || !menu) return menu; + ctx?.env?.build_menu?.("smart_context:copy_menu", menu, ctx); + ctx?.env?.build_menu?.("smart_context:actions_menu", menu, ctx); + if (menu.items?.sort) { + menu.items.sort((a, b) => { + const order_a = a._order || 0; + const order_b = b._order || 0; + return order_a - order_b; + }); + } + return menu; +} +function render_btn_quick_copy(ctx, container, params = {}) { + if (!has_active_context_items(ctx)) return null; + const button = create_button(container, { + icon: "smart-copy-note", + icon_only: true, + aria_label: "Smart Copy" + }); + button.addEventListener("click", async () => { + await ctx.actions.context_copy_to_clipboard(); + }); + return button; +} +function render_btn_copy_menu(ctx, container, params = {}) { + if (!has_active_context_items(ctx)) return null; + const app2 = ctx?.env?.plugin?.app || ctx?.env?.obsidian_app || window?.app || globalThis?.app || null; + if (!app2) return null; + const button = create_button(container, { + icon: "chevron-down", + text: "Copy", + aria_label: "Open copy menu" + }); + const open_menu = (event) => { + const menu = new import_obsidian22.Menu(app2); + build_context_actions_menu(ctx, menu, params); + show_menu_at_button(button, event, menu); + }; + button.addEventListener("click", (event) => { + event.preventDefault(); + open_menu(event); + }); + button.addEventListener("keydown", (event) => { + if (event.key !== "Enter" && event.key !== " ") return; + event.preventDefault(); + open_menu(event); + }); + return button; +} +function render_btn_clear_context(ctx, container) { + if (!has_any_context_state(ctx)) return null; + const button = create_button(container, { + text: "Clear", + aria_label: "Clear context", + icon: "rotate-ccw", + icon_only: true + }); + button.addEventListener("click", () => { + if (!button.dataset.confirmed) { + button.dataset.confirmed = "true"; + button.style.backgroundColor = "var(--color-red)"; + return; + } + ctx.clear_all?.(); + button.dataset.confirmed = ""; + button.style.backgroundColor = ""; + }); + return button; +} +function render_btn_help(ctx, container) { + const button = create_button(container, { + icon: "help-circle", + aria_label: "Help", + icon_only: true + }); + button.addEventListener("click", () => { + const url = get_help_url(ctx); + if (typeof globalThis.open === "function") { + globalThis.open(url, "_external"); + } + }); + return button; +} + +// node_modules/obsidian-smart-env/src/components/smart-context/actions.js +function build_html13() { + return ` +
    +
    +
    +
    +
    +
    + `; +} +async function render15(ctx, opts = {}) { + const html = build_html13(); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process13.call(this, ctx, container, opts); + return container; +} +async function post_process13(ctx, container, opts = {}) { + const render_ctx_actions = () => { + const actions_left = container.querySelector(".sc-context-actions-left"); + this.empty(actions_left); + ctx.env.smart_components.render_component("smart_context_meta", ctx, opts).then((meta) => { + if (meta) actions_left.appendChild(meta); + }); + const actions_right = container.querySelector(".sc-context-actions-right"); + this.empty(actions_right); + render_btn_quick_copy(ctx, actions_right); + render_btn_copy_menu(ctx, actions_right, opts); + render_btn_clear_context(ctx, actions_right); + render_btn_help(ctx, actions_right); + }; + render_ctx_actions(); + const disposers = []; + disposers.push(ctx.on_event("context:updated", render_ctx_actions)); + this.attach_disposer(container, disposers); + return container; +} + +// node_modules/obsidian-smart-env/src/components/smart-context/styles.css +var styles_default = "/* Modal view adjustments */\n.modal-container .sc-context-view {\n max-height: 42vh;\n display: flex;\n flex-direction: column;\n\n .sc-context-view-body {\n overflow: auto;\n display: flex;\n flex-direction: column;\n gap: var(--size-4-2);\n }\n\n .sc-context-view-header {\n padding: var(--size-4-2);\n }\n\n .sc-context-actions {\n display: flex;\n justify-content: space-between;\n }\n\n .sc-context-actions-left,\n .sc-context-actions-right {\n display: flex;\n gap: var(--size-4-2);\n align-items: center;\n }\n\n .sc-context-view-body {\n padding: var(--size-4-2);\n }\n\n .sc-context-view-footer {\n padding: var(--size-4-2);\n }\n}\n\n/* make hover popover work in builder modal */\n.hover-popover {\n z-index: 100;\n}\n"; + +// node_modules/obsidian-smart-env/src/components/smart-context/item.js +var import_obsidian23 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/render_utils.js +function create_render_scheduler(render_fn) { + let handle = null; + let pending = false; + let last_args = []; + let last_result = null; + const clear_handle = () => { + if (handle === null) return; + if (typeof cancelAnimationFrame === "function") { + cancelAnimationFrame(handle); + } else { + clearTimeout(handle); + } + handle = null; + }; + const run = async () => { + pending = false; + handle = null; + last_result = await render_fn(...last_args); + return last_result; + }; + const schedule = (...args) => { + last_args = args; + if (pending) return; + pending = true; + if (typeof requestAnimationFrame === "function") { + handle = requestAnimationFrame(() => { + void run(); + }); + return; + } + handle = setTimeout(() => { + void run(); + }, 0); + }; + schedule.cancel = () => { + pending = false; + clear_handle(); + }; + schedule.flush = async (...args) => { + if (args.length) { + last_args = args; + } + if (!pending) { + last_result = await render_fn(...last_args); + return last_result; + } + schedule.cancel(); + last_result = await render_fn(...last_args); + return last_result; + }; + return schedule; +} + +// node_modules/obsidian-smart-env/src/components/smart-context/item.js +function build_html14(ctx, opts = {}) { + return `
    +
    +
    +
    +
    +
    +
    +
    + +
    +
    `; +} +async function render16(ctx, opts = {}) { + const html = build_html14(ctx, opts); + this.apply_style_sheet(styles_default); + const frag = this.create_doc_fragment(html); + const container = frag.querySelector(".sc-context-view"); + post_process14.call(this, ctx, container, opts); + return container; +} +async function post_process14(ctx, container, opts = {}) { + const disposers = []; + const render_children = async () => { + const component_renderers = [ + ctx.env.smart_components.render_component("smart_context_actions", ctx, opts), + ctx.env.smart_components.render_component("smart_context_tree", ctx, opts) + ]; + if (ctx.env._config.components.smart_context_exclusions_list) { + component_renderers.push(ctx.env.smart_components.render_component("smart_context_exclusions_list", ctx, opts)); + } + const [actions, tree, exclusions] = await Promise.all(component_renderers); + const header = container.querySelector(".sc-context-view-header"); + this.empty(header); + if (actions) header.appendChild(actions); + const body = container.querySelector(".sc-context-view-body"); + this.empty(body); + if (tree) body.appendChild(tree); + const footer = container.querySelector(".sc-context-view-footer"); + this.empty(footer); + if (exclusions) footer.appendChild(exclusions); + }; + const schedule_render_children = create_render_scheduler(render_children); + const plugin = ctx.env.plugin; + const app2 = plugin?.app || window.app; + const register = plugin?.registerDomEvent?.bind(plugin) || ((el, evt, cb) => el.addEventListener(evt, cb)); + register(container, "contextmenu", (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + if (!app2) return; + const menu = new import_obsidian23.Menu(app2); + build_context_actions_menu(ctx, menu, opts); + menu.showAtMouseEvent(ev); + }); + await render_children(); + disposers.push(ctx.on_event("context:updated", schedule_render_children)); + this.attach_disposer(container, disposers); + return container; +} + +// node_modules/obsidian-smart-env/src/components/smart-context/meta.js +var import_obsidian24 = require("obsidian"); +function estimate_tokens(char_count) { + return Math.ceil((char_count || 0) / 4); +} +function build_html15() { + return ` +
    + `; +} +async function render17(ctx, params = {}) { + const html = build_html15(); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process15.call(this, ctx, container, params); + return container; +} +async function post_process15(ctx, container, params = {}) { + const render_meta = () => { + if (ctx?.has_context_items) { + const chars = ctx.size || 0; + const tokens = estimate_tokens(chars); + container.textContent = `\u2248 ${chars.toLocaleString()} chars \xB7 ${tokens.toLocaleString()} tokens`; + } else { + container.textContent = "No context items selected"; + } + }; + render_meta(); + const disposers = []; + disposers.push(ctx.on_event("context:updated", render_meta)); + disposers.push(ctx.on_event("smart_context:missing_item", () => { + console.warn("Context item missing for context", ctx.key); + if (container.querySelector(".sc-missing-item-warning")) return; + const warning_icon = this.create_doc_fragment('
    ').firstElementChild; + container.appendChild(warning_icon); + (0, import_obsidian24.setIcon)(warning_icon, "alert-triangle"); + warning_icon.setAttribute("title", "One or more context items are missing. Click to manage."); + })); + this.attach_disposer(container, disposers); + return container; +} + +// node_modules/obsidian-smart-env/src/utils/smart-context/build_path_tree.js +function build_path_tree(selected_items = []) { + const root = create_tree_node2(); + const selected_folders = selected_items.map(get_item_key).filter((item_key) => item_key && is_folder_item_key(item_key)); + for (const item of selected_items) { + const item_key = get_item_key(item); + if (!item_key) continue; + if (is_redundant_path(item_key, selected_folders)) continue; + insert_item_path(root, { + item_key, + exists: item?.exists + }); + } + return root; +} +function get_item_key(item) { + return item?.key || item?.path || ""; +} +function create_tree_node2() { + return { name: "", children: {}, selected: false }; +} +function is_redundant_path(item_key, selected_folders) { + return selected_folders.some((folder_key) => { + if (folder_key === item_key) return false; + return item_key.startsWith(`${folder_key}/`); + }); +} +function is_folder_item_key(item_key) { + const block_idx = find_first_block_separator(item_key); + const source_path = block_idx === -1 ? item_key : item_key.slice(0, block_idx); + return !source_path.match(/\.[a-zA-Z0-9]+$/u); +} +function find_first_block_separator(value = "") { + let in_wikilink = false; + for (let i = 0; i < value.length; i++) { + if (!in_wikilink && value.slice(i, i + 2) === "[[") { + in_wikilink = true; + i++; + continue; + } + if (in_wikilink && value.slice(i, i + 2) === "]]") { + in_wikilink = false; + i++; + continue; + } + if (!in_wikilink && value[i] === "#") return i; + } + return -1; +} +function split_source_path_segments(source_path = "") { + const segments = []; + let segment = ""; + let in_wikilink = false; + for (let i = 0; i < source_path.length; i++) { + if (!in_wikilink && source_path.slice(i, i + 2) === "[[") { + in_wikilink = true; + segment += "[["; + i++; + continue; + } + if (in_wikilink && source_path.slice(i, i + 2) === "]]") { + in_wikilink = false; + segment += "]]"; + i++; + continue; + } + if (!in_wikilink && source_path[i] === "/") { + if (segment) segments.push(segment); + segment = ""; + continue; + } + segment += source_path[i]; + } + if (segment) segments.push(segment); + return segments; +} +function split_block_path_segments(block_path = "") { + const segments = []; + let segment = ""; + let in_wikilink = false; + for (let i = 0; i < block_path.length; i++) { + if (!in_wikilink && block_path.slice(i, i + 2) === "[[") { + in_wikilink = true; + segment += "[["; + i++; + continue; + } + if (in_wikilink && block_path.slice(i, i + 2) === "]]") { + in_wikilink = false; + segment += "]]"; + i++; + continue; + } + if (!in_wikilink && block_path[i] === "#") { + if (segment) { + segments.push(segment); + segment = ""; + } + if (block_path[i + 1] === "#") { + segment = "#"; + while (block_path[i + 1] === "#") { + i++; + segment += "#"; + } + } else if (block_path[i + 1] === "{") { + segment = "#"; + } + continue; + } + segment += block_path[i]; + } + if (segment) segments.push(segment); + return segments; +} +function split_path_segments2(item_path) { + const block_idx = find_first_block_separator(item_path); + const has_block = block_idx !== -1; + const source_path = has_block ? item_path.slice(0, block_idx) : item_path; + const block_path = has_block ? item_path.slice(block_idx) : ""; + const source_segments = split_source_path_segments(source_path); + const segments = [...source_segments]; + if (block_path) { + segments.push(...split_block_path_segments(block_path)); + } + return { + segments, + has_block, + source_segments_count: source_segments.length + }; +} +function insert_item_path(root, params = {}) { + const { item_key, exists } = params; + const { segments, has_block, source_segments_count } = split_path_segments2(item_key); + let node = root; + let running = ""; + segments.forEach((segment, index) => { + running = get_running_path(running, segment, { + has_block, + index, + source_segments_count + }); + if (segment.startsWith("external:..")) return; + const is_last = index === segments.length - 1; + const is_block_leaf = is_last && has_block; + const is_source_file = has_block && index === source_segments_count - 1; + if (!node.children[segment]) { + node.children[segment] = { + name: segment, + path: is_block_leaf ? item_key : running, + // For blocks we store an empty *array* so AVA can assert `children.length === 0`. + children: is_block_leaf ? [] : {}, + selected: false, + is_file: is_block_leaf || is_source_file || is_last && segment.includes(".") + }; + } + node = node.children[segment]; + if (is_last) { + node.selected = true; + node.exists = exists; + } + }); +} +function get_running_path(running, segment, params = {}) { + if (!running) return segment; + if (!params.has_block || params.index < params.source_segments_count) { + return `${running}/${segment}`; + } + return segment.startsWith("#") ? `${running}${segment}` : `${running}#${segment}`; +} + +// node_modules/obsidian-smart-env/src/components/smart-context/tree.css +var tree_default = ".sc-context-tree {\n ul {\n padding-inline-start: 1.7rem;\n }\n li:has(> .sc-context-item-remove),\n li:has(> .sc-context-item-leaf > .sc-context-item-remove) {\n list-style-type: none;\n }\n .sc-context-item-remove:hover {\n font-weight: bold;\n filter: brightness(1.8);\n }\n .sc-context-item-remove {\n padding: 0 0.2rem;\n margin-left: -1.4rem;\n }\n .sc-context-item-remove.is-disabled {\n color: var(--text-faint, var(--text-muted));\n cursor: not-allowed;\n filter: none;\n opacity: 0.65;\n }\n .sc-context-item-remove.is-disabled:hover {\n font-weight: normal;\n filter: none;\n }\n .sc-context-item-remove.is-removing {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1.2em;\n height: 1.2em;\n padding: 0;\n color: var(--text-muted);\n cursor: default;\n pointer-events: none;\n filter: none;\n }\n .sc-context-item-remove.is-removing:hover {\n font-weight: normal;\n filter: none;\n }\n .sc-context-item-remove.is-removing::after {\n content: '';\n width: 0.75em;\n height: 0.75em;\n border: 2px solid currentColor;\n border-top-color: transparent;\n border-radius: 999px;\n animation: sc-context-remove-spin 0.8s linear infinite;\n }\n}\n.sc-context-item-leaf, .sc-context-item-remove {\n cursor: pointer;\n}\n.sc-context-item-score,\n.sc-context-item-size,\n.sc-context-item-origin-badge {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 4.5ch;\n height: 1.7em;\n line-height: 1.7em;\n text-align: center;\n font-weight: 600 !important;\n font-size: 0.8em !important;\n color: var(--nav-item-color) !important;\n background: var(--background-modifier-hover);\n border-radius: 6px;\n padding: 0 0.4em;\n margin-right: 0.35em;\n vertical-align: middle;\n}\n.sc-context-item-size,\n.sc-context-item-origin-badge {\n min-width: 0;\n}\n.sc-context-item-type-icon {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: 1em;\n height: 1em;\n margin-right: 0.25em;\n color: var(--text-muted);\n vertical-align: middle;\n}\n.sc-context-item-origin-badges {\n display: inline-flex;\n align-items: center;\n flex-wrap: wrap;\n gap: 0.25em;\n margin-left: 0.35em;\n vertical-align: middle;\n}\n.sc-context-item-origin-badge {\n gap: 0.25em;\n color: var(--text-muted) !important;\n}\n.sc-context-item-warning-badge {\n color: var(--text-warning, var(--color-yellow)) !important;\n}\n.sc-context-item-name.missing {\n color: var(--text-warning, var(--color-yellow));\n}\n.sc-context-item-badge-icon svg,\n.sc-context-item-type-icon svg {\n width: 0.9em;\n height: 0.9em;\n}\n.sc-context-item-badge-label {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n max-width: 18ch;\n}\n@keyframes sc-context-remove-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n\n"; + +// node_modules/obsidian-smart-env/src/components/smart-context/tree.js +var remove_debounce_ms = 1500; +function build_html16(ctx, params = {}) { + return ` +
    + `; +} +async function render18(ctx, params = {}) { + this.apply_style_sheet(tree_default); + const html = build_html16(ctx, params); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process16.call(this, ctx, container, params); + return container; +} +async function post_process16(ctx, container, params = {}) { + let render_token = 0; + const pending_removals = get_pending_removals(ctx); + const is_pending_removal = (item_key) => { + for (const target_path of pending_removals.keys()) { + if (item_matches_remove_path(item_key, target_path)) return true; + } + return false; + }; + const render_tree_leaves = () => { + render_token += 1; + const token = render_token; + const env = ctx.env; + const items = ctx.context_items.filter(params.filter); + const included_items = items.filter((item) => !item?.data?.exclude).filter((item) => !is_pending_removal(get_item_key2(item))); + const context_size = get_total_context_size(included_items); + const tree_root = build_path_tree(included_items); + const context_item_by_key = included_items.reduce((acc, item) => { + const item_key = get_item_key2(item); + if (item_key) acc.set(item_key, item); + return acc; + }, /* @__PURE__ */ new Map()); + const list_el = render_tree_list.call(this, tree_root, { + ctx, + env, + context_item_by_key, + context_size, + render_token: token, + get_render_token: () => render_token, + on_remove_path: (target_path, remove_params, tree_item_el) => { + remove_tree_item_from_ui(tree_item_el); + queue_remove_by_path(target_path, remove_params); + } + }); + this.empty(container); + if (list_el) container.appendChild(list_el); + }; + const schedule_render_tree_leaves = create_render_scheduler(render_tree_leaves); + const flush_pending_removals = () => { + if (ctx._remove_by_path_timer) { + clearTimeout(ctx._remove_by_path_timer); + ctx._remove_by_path_timer = null; + } + const removals = Array.from(pending_removals.entries()).map(([target_path, remove_params]) => ({ + path: target_path, + ...remove_params?.folder ? { folder: true } : {} + })); + pending_removals.clear(); + if (!removals.length) return; + try { + ctx.remove_by_paths(removals); + } catch (error) { + console.warn("Smart Context: Failed to remove queued paths", error); + schedule_render_tree_leaves(); + } + }; + const queue_remove_by_path = (target_path, remove_params = {}) => { + if (!target_path) return; + for (const pending_path of Array.from(pending_removals.keys())) { + if (item_matches_remove_path(target_path, pending_path)) return; + if (item_matches_remove_path(pending_path, target_path)) { + pending_removals.delete(pending_path); + } + } + pending_removals.set(target_path, remove_params); + if (ctx._remove_by_path_timer) clearTimeout(ctx._remove_by_path_timer); + ctx._remove_by_path_timer = setTimeout(flush_pending_removals, remove_debounce_ms); + }; + render_tree_leaves(); + const on_tree_remove_click = (event) => { + const target = event.target?.closest?.(".sc-context-item-remove"); + if (!target) return; + if (target.closest(".sc-context-item-leaf")) return; + event.preventDefault(); + event.stopPropagation(); + const target_path = target.getAttribute("data-path"); + if (!target_path) return; + if (target.classList.contains("is-disabled")) { + emit_named_context_remove_blocked_notice(ctx, target.getAttribute("data-named-context") || ""); + return; + } + const tree_item_el = target.closest(".sc-tree-item"); + const is_folder = tree_item_el?.classList.contains("dir"); + remove_tree_item_from_ui(tree_item_el); + queue_remove_by_path(target_path, { + ...is_folder ? { folder: true } : {} + }); + }; + const on_named_context_badge_click = (event) => { + const badge = event.target?.closest?.(".sc-context-item-origin-named-context"); + if (!badge) return; + const ctx_name = badge.getAttribute("data-named-context"); + if (!ctx_name) return; + event.preventDefault(); + event.stopPropagation(); + const named_ctx = ctx.env.smart_contexts.get_named_context(ctx_name); + if (!named_ctx) return console.warn(`Smart Context: Failed to find named context with name: ${ctx_name}`); + named_ctx.emit_event("context_selector:open"); + }; + const disposers = []; + container.addEventListener("click", on_tree_remove_click); + container.addEventListener("click", on_named_context_badge_click); + disposers.push(() => container.removeEventListener("click", on_tree_remove_click)); + disposers.push(() => container.removeEventListener("click", on_named_context_badge_click)); + disposers.push(ctx.on_event("context:updated", schedule_render_tree_leaves)); + ctx.named_contexts.forEach((named_ctx) => { + disposers.push(named_ctx.on_event("context:updated", schedule_render_tree_leaves)); + }); + this.attach_disposer(container, disposers); + return container; +} +function get_item_key2(item) { + return item?.key || item?.path || ""; +} +function get_context_item_named_context(item) { + const named_context = item?.data?.from_named_context; + return typeof named_context === "string" ? named_context.trim() : ""; +} +function get_pending_removals(ctx) { + if (!(ctx._pending_remove_by_path instanceof Map)) { + ctx._pending_remove_by_path = /* @__PURE__ */ new Map(); + } + return ctx._pending_remove_by_path; +} +function is_core_context(ctx) { + return ctx?.env?.is_pro !== true; +} +function get_tree_item_named_contexts(tree_item, context_item_by_key, named_contexts = /* @__PURE__ */ new Set()) { + const context_item = context_item_by_key.get(get_item_key2(tree_item)); + const named_context = get_context_item_named_context(context_item); + if (named_context) named_contexts.add(named_context); + get_child_nodes(tree_item).forEach((child) => { + get_tree_item_named_contexts(child, context_item_by_key, named_contexts); + }); + return named_contexts; +} +function emit_named_context_remove_blocked_notice(ctx, named_context_name = "") { + const context_name = String(named_context_name || "").trim(); + const named_ctx = context_name ? ctx?.env?.smart_contexts?.get_named_context?.(context_name) : null; + const message = context_name ? `This item is included from named context "${context_name}". Open that named context to remove it there.` : "This item is included from a named context. Open the named context to remove it there."; + ctx?.emit_event?.("context:named_context_remove_blocked", { + level: "warning", + message, + event_source: "smart_context_tree.remove_named_context_child", + named_context_name: context_name, + ...named_ctx ? { + btn_text: "Open named context", + btn_callback: "context_selector:open", + btn_event_key: "context_selector:open", + btn_event_payload: { + collection_key: "smart_contexts", + item_key: named_ctx.key + } + } : {} + }); +} +function get_item_size(item) { + const size = Number(item?.size ?? item?.data?.size); + if (!Number.isFinite(size) || size < 0) return 0; + return size; +} +function get_total_context_size(items = []) { + return items.reduce((sum, item) => sum + get_item_size(item), 0); +} +function get_child_nodes(node) { + if (!node?.children || Array.isArray(node.children)) return []; + return Object.values(node.children); +} +function sort_tree_items(left, right) { + if (left.is_file !== right.is_file) return left.is_file ? 1 : -1; + return left.name.localeCompare(right.name); +} +function render_tree_list(node, params = {}) { + const child_nodes = get_child_nodes(node); + if (!child_nodes.length) return null; + const list_el = document.createElement("ul"); + child_nodes.sort(sort_tree_items).forEach((child) => { + list_el.appendChild(render_tree_item.call(this, child, params)); + }); + return list_el; +} +function render_tree_item(tree_item, params = {}) { + const key = get_item_key2(tree_item); + const li = document.createElement("li"); + li.dataset.path = key; + li.classList.add("sc-tree-item", tree_item.is_file ? "file" : "dir"); + if (key.startsWith("external:")) li.classList.add("sc-external"); + const child_list = render_tree_list.call(this, tree_item, params); + const context_item = params.context_item_by_key.get(key); + render_tree_item_shell(tree_item, li, { + ...params, + child_list, + is_context_item: Boolean(context_item) + }); + if (context_item) { + const named_context = get_context_item_named_context(context_item); + const remove_disabled = is_core_context(params.ctx) && Boolean(named_context); + params.env.smart_components.render_component("context_item_leaf", context_item, { + context_size: params.context_size, + remove_disabled, + on_remove_disabled: () => { + emit_named_context_remove_blocked_notice(params.ctx, named_context); + }, + on_remove: (_event, item = context_item) => { + const item_key = get_item_key2(item); + if (!item_key) return; + params.on_remove_path?.( + item_key, + { + ...tree_item.is_file ? {} : { folder: true } + }, + li + ); + } + }).then((leaf) => { + if (params.get_render_token() !== params.render_token) return; + if (!leaf) return; + this.empty(li); + li.appendChild(leaf); + if (child_list) li.appendChild(child_list); + }).catch((error) => { + console.warn(`Smart Context: Failed to render tree leaf for path: ${key}`, error); + }); + } + return li; +} +function render_tree_item_shell(tree_item, li, params = {}) { + const key = get_item_key2(tree_item); + const has_children = Boolean(params.child_list); + if (!params.is_context_item && has_children) { + let named_context = ""; + let remove_disabled = false; + if (is_core_context(params.ctx)) { + const named_contexts = get_tree_item_named_contexts(tree_item, params.context_item_by_key); + remove_disabled = named_contexts.size > 0; + if (named_contexts.size === 1) named_context = Array.from(named_contexts)[0]; + } + const remove_btn = document.createElement("span"); + remove_btn.classList.add("sc-context-item-remove"); + if (remove_disabled) remove_btn.classList.add("is-disabled"); + remove_btn.dataset.path = key; + if (named_context) remove_btn.dataset.namedContext = named_context; + remove_btn.textContent = "\xD7"; + remove_btn.setAttribute( + "aria-label", + remove_disabled ? "Open named context to edit included items" : "Remove folder from context" + ); + if (remove_disabled) { + remove_btn.setAttribute("aria-disabled", "true"); + remove_btn.setAttribute("title", "Open the named context to remove included items."); + } + li.appendChild(remove_btn); + } + const label = document.createElement("span"); + label.classList.add("sc-tree-label", "sc-context-item-name"); + if (params.is_context_item) label.classList.add("sc-context-item-placeholder"); + if (tree_item.exists === false) label.classList.add("missing"); + label.textContent = tree_item.name || key; + li.appendChild(label); + if (params.child_list) li.appendChild(params.child_list); +} +function remove_tree_item_from_ui(tree_item_el) { + let current = tree_item_el; + while (current) { + const parent_list = current.parentElement; + const parent_item = parent_list?.closest?.(".sc-tree-item"); + current.remove(); + if (!parent_list) return; + if (parent_list.children.length === 0) parent_list.remove(); + if (!parent_item) return; + if (parent_item.querySelector(":scope > .sc-context-item-leaf")) return; + const child_list = parent_item.querySelector(":scope > ul"); + if (child_list && child_list.children.length > 0) return; + current = parent_item; + } +} + +// node_modules/obsidian-smart-env/src/components/smart-plugins/list.js +var import_obsidian26 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/smart_plugins.js +var import_obsidian25 = require("obsidian"); +function get_smart_server_url() { + if (typeof window !== "undefined" && window.SMART_SERVER_URL_OVERRIDE) { + return window.SMART_SERVER_URL_OVERRIDE; + } + return "https://connect.smartconnections.app"; +} +var install_file_names = ["manifest.json", "main.js", "styles.css"]; +async function write_files_with_adapter(adapter, baseFolder, files) { + const hasWriteBinary = typeof adapter.writeBinary === "function"; + if (!await adapter.exists(baseFolder)) { + await adapter.mkdir(baseFolder); + } + for (const { fileName, data, accessed_at } of files) { + const fullPath = baseFolder + "/" + fileName; + if (hasWriteBinary) { + await adapter.writeBinary(fullPath, data, { ctime: accessed_at, mtime: accessed_at }); + } else { + const text = new TextDecoder("utf-8").decode(data); + await adapter.write(fullPath, text, { ctime: accessed_at, mtime: accessed_at }); + } + } +} +async function authenticated_smart_plugins_request(params = {}) { + const request_path = String(params.path || "").trim(); + if (!request_path) { + throw new Error("path required"); + } + const token = String(params.token || "").trim(); + const headers = { + ...params.headers || {} + }; + if (token) { + headers.Authorization = `Bearer ${token}`; + } + const url = request_path.startsWith("http") ? request_path : `${get_smart_server_url()}${request_path}`; + return await (0, import_obsidian25.requestUrl)({ + url, + method: params.method || "POST", + headers, + body: params.body, + contentType: params.contentType, + throw: false + }); +} +async function fetch_plugin_file(repo_name, token, params = {}) { + const file = String(params.file || "").trim(); + if (!file) { + throw new Error("file required"); + } + const body = { + repo: repo_name, + file + }; + const version = String(params.version || "").trim(); + if (version) { + body.version = version; + } + const resp = await authenticated_smart_plugins_request({ + token, + path: "/plugin_download", + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify(body) + }); + if (resp.status !== 200) { + throw new Error( + get_response_message(resp) || `plugin_download error ${resp.status}` + ); + } + return resp; +} +function get_response_header_value(response, header_name) { + const normalized_header_name = String(header_name || "").trim().toLowerCase(); + if (!normalized_header_name) return ""; + const headers = response?.headers; + if (!headers) return ""; + if (typeof headers.get === "function") { + return String( + headers.get(header_name) || headers.get(normalized_header_name) || "" + ).trim(); + } + if (Array.isArray(headers)) { + const match = headers.find(([key]) => { + return String(key || "").trim().toLowerCase() === normalized_header_name; + }); + return String(match?.[1] || "").trim(); + } + if (typeof headers === "object") { + const key = Object.keys(headers).find((candidate_key) => { + return String(candidate_key || "").trim().toLowerCase() === normalized_header_name; + }); + return String(key && headers[key] || "").trim(); + } + return ""; +} +function normalize_positive_epoch_ms(value) { + const numeric_value = Number(value); + if (!Number.isFinite(numeric_value) || numeric_value <= 0) { + return null; + } + return Math.round(numeric_value); +} +function get_plugin_file_text(response, file_name) { + if (typeof response?.text === "string" && response.text.length) { + return response.text; + } + if (response?.arrayBuffer instanceof ArrayBuffer) { + return new TextDecoder("utf-8").decode(response.arrayBuffer); + } + if (file_name === "manifest.json" && response?.json) { + return JSON.stringify(response.json, null, 2); + } + return ""; +} +function build_plugin_file_record(file_name, response) { + return { + fileName: file_name, + data: new TextEncoder().encode(get_plugin_file_text(response, file_name)), + accessed_at: normalize_positive_epoch_ms( + get_response_header_value(response, "accessed_at") + ) || Date.now() + }; +} +async function enable_plugin(app2, plugin_id) { + await app2.plugins.enablePlugin(plugin_id); + app2.plugins.enabledPlugins.add(plugin_id); + app2.plugins.requestSaveConfig(); + app2.plugins.loadManifests(); +} +function get_oauth_storage_prefix(app2) { + const vault_name = app2?.vault?.getName?.() || ""; + const safe = vault_name.toLowerCase().replace(/[^a-z0-9]/g, "_"); + return `${safe}_smart_plugins_oauth_`; +} +function get_response_json(response) { + return response?.json && typeof response.json === "object" ? response.json : {}; +} +function get_response_message(response) { + return String( + get_response_json(response)?.message || get_response_json(response)?.error || response?.text || "" + ).trim(); +} +async function fetch_server_plugin_list(token) { + const resp = await authenticated_smart_plugins_request({ + token, + path: "/plugin_list", + method: "POST", + headers: { + "Content-Type": "application/json" + }, + body: JSON.stringify({}) + }); + if (resp.status !== 200) { + return { + list: [], + sub_exp: null, + status: resp.status, + message: get_response_message(resp) + }; + } + const response_json = get_response_json(resp); + return { + ...response_json, + status: resp.status, + message: String(response_json?.message || "").trim() + }; +} +async function fetch_referral_stats(params = {}) { + const token = String(params.token || "").trim(); + if (!token) return { ok: false, error: "missing_token" }; + const resp = await authenticated_smart_plugins_request({ + token, + path: "/api/referrals/stats", + method: "GET" + }); + if (resp.status === 401) { + return { + ok: false, + message: get_response_message(resp) || "Invalid or expired token. Please log in again." + }; + } + if (resp.status !== 200) { + throw new Error( + get_response_message(resp) || `referrals stats error ${resp.status}` + ); + } + return get_response_json(resp); +} + +// node_modules/obsidian-smart-env/src/utils/smart_plugins_state.js +var pro_plugin_ids_without_pro_in_name = /* @__PURE__ */ new Set(["smart-file-nav"]); +function infer_installed_plugin_type(params = {}) { + const plugin_id = String(params.plugin_id || "").trim(); + if (pro_plugin_ids_without_pro_in_name.has(plugin_id)) { + return "pro"; + } + const manifest_name = String(params.manifest_name || ""); + return manifest_name.includes("Pro") ? "pro" : "core"; +} +function should_block_pro_install(params = {}) { + return String(params.item_type || "").trim() === "pro" && params.is_entitled !== true; +} +function should_offer_plugin_update(params = {}) { + const item_type = String(params.item_type || "").trim(); + const installed_type = String(params.installed_type || "").trim(); + if (!item_type || item_type !== installed_type) { + return false; + } + if (should_block_pro_install({ + item_type, + is_entitled: params.is_entitled + })) { + return false; + } + const server_version = String(params.server_version || "").trim(); + const installed_version = String(params.installed_version || "").trim(); + if (!server_version || !installed_version) { + return false; + } + if (typeof params.compare_versions !== "function") { + return false; + } + return params.compare_versions(server_version, installed_version) > 0; +} +function should_signal_outdated_env_compatibility(params = {}) { + const item_type = String(params.item_type || "").trim(); + const installed_type = String(params.installed_type || "").trim(); + if (!item_type || item_type !== installed_type) { + return false; + } + if (should_block_pro_install({ + item_type, + is_entitled: params.is_entitled + })) { + return false; + } + if (params.is_enabled !== true) { + return false; + } + if (params.is_loaded === true || params.is_deferred === true) { + return false; + } + return has_outdated_smart_env_version(params.loaded_env_version); +} +function compute_plugin_track_state(params = {}) { + const item_type = String(params.item_type || "").trim(); + const has_core_plugin = params.has_core_plugin === true; + const has_pro_plugin = params.has_pro_plugin === true; + const has_group_ui = has_core_plugin && has_pro_plugin; + const installed_type = String(params.installed_type || "").trim(); + if (item_type !== "core" && item_type !== "pro") { + return null; + } + if (item_type === "core" && !has_core_plugin || item_type === "pro" && !has_pro_plugin) { + return null; + } + let control_state = "cant_install"; + if (installed_type === item_type) { + if (params.should_update === true) { + control_state = "update_available"; + } else if (params.has_outdated_env_compatibility === true) { + control_state = "outdated_env"; + } else if (params.is_deferred === true) { + control_state = "deferred"; + } else if (params.is_loaded === true) { + control_state = "loaded"; + } else if (params.is_enabled !== true) { + control_state = "can_enable"; + } else { + control_state = "installed"; + } + } else if (item_type === "core") { + if (has_group_ui && (installed_type === "pro" || params.is_entitled === true)) { + control_state = "included_in_pro"; + } else if (has_group_ui) { + control_state = "can_install_core_only"; + } else { + control_state = "can_install"; + } + } else if (item_type === "pro") { + if (installed_type === "core") { + control_state = "core_installed"; + } else if (params.is_entitled === true) { + control_state = "can_install_pro"; + } else { + control_state = "cant_install"; + } + } + return { + item_type, + is_installed_here: installed_type === item_type, + control_state + }; +} +function compute_plugin_list_item_state(params = {}) { + const has_core_plugin = params.has_core_plugin === true; + const has_pro_plugin = params.has_pro_plugin === true; + const has_group_ui = has_core_plugin && has_pro_plugin; + const installed_type = String(params.installed_type || "").trim(); + const display_item_type = String(params.display_item_type || "").trim() || (has_pro_plugin ? "pro" : "core"); + const track_states = { + core: compute_plugin_track_state({ + ...params, + has_core_plugin, + has_pro_plugin, + installed_type, + item_type: "core" + }), + pro: compute_plugin_track_state({ + ...params, + has_core_plugin, + has_pro_plugin, + installed_type, + item_type: "pro" + }) + }; + let row = null; + if (has_group_ui) { + row = ["core", "pro"].includes(installed_type) ? track_states[installed_type] : params.is_entitled === true ? track_states.pro : track_states.core; + } else { + row = track_states[display_item_type] || null; + } + return { + row, + track_states + }; +} +function get_install_enable_behavior(params = {}) { + const was_installed = params.was_installed === true; + return { + should_enable_after_install: !was_installed + }; +} +function has_outdated_smart_env_version(version = "") { + if (version === "") return false; + const version_pcs = String(version || "").trim().split("."); + const version_minor = Number.parseInt(version_pcs[1] || "0", 10); + if (!Number.isFinite(version_minor)) { + return false; + } + return version_minor < 4; +} + +// node_modules/obsidian-smart-env/src/components/smart-plugins/style.css +var style_default2 = ".get-core-link {\n text-wrap: nowrap;\n font-size: var(--font-ui-small);\n}\n.core-installed-text {\n text-wrap: nowrap;\n font-size: var(--font-ui-small);\n color: var(--text-muted);\n}\n\n.smart-plugins-core-list,\n.pro-plugins-list {\n display: flex;\n flex-direction: column;\n row-gap: var(--size-4-3);\n margin-top: var(--size-4-2);\n}\n\n.smart-plugins-section + .smart-plugins-section {\n margin-top: var(--size-4-4);\n}\n\n.smart-plugins-section-title {\n padding: 0 var(--size-4-4);\n font-size: var(--font-ui-medium);\n font-weight: var(--font-semibold);\n}\n\n.smart-plugins-section-description {\n padding: 0 var(--size-4-4);\n margin-top: var(--size-2-2);\n font-size: var(--font-ui-small);\n color: var(--text-muted);\n}\n\n.smart-plugins-server-message {\n margin: var(--size-4-3) 0;\n\n .callout-content,\n .callout-content > :first-child {\n margin-block-start: 0;\n }\n\n .callout-content > :last-child {\n margin-block-end: 0;\n }\n\n .callout-icon svg {\n width: 16px;\n height: 16px;\n }\n}\n\n.pro-plugins-list-item {\n display: flex;\n align-items: center;\n padding: 0.75em 0;\n border-top: 1px solid var(--background-modifier-border);\n row-gap: var(--size-4-3);\n\n .setting-item {\n width: 100%;\n }\n\n .setting-item-name {\n position: relative;\n }\n}\n\n.smart-plugins-item-description,\n.smart-plugins-item-description > :first-child {\n margin-block-start: 0;\n}\n\n.smart-plugins-item-description > :last-child {\n margin-block-end: 0;\n}\n\n.smart-plugins-item-subscription-state {\n margin-top: var(--size-2-2);\n font-size: var(--font-ui-small);\n color: var(--text-muted);\n}\n\n.smart-plugins-item-group-combined .setting-items > .pro-plugins-list-item:first-child {\n border-top: none;\n}\n\n\n.smart-plugins-list-item-controls {\n display: flex;\n align-items: center;\n justify-content: flex-end;\n gap: var(--size-2-2);\n\n > button,\n > span {\n white-space: nowrap;\n }\n}\n\n.smart-plugins-details-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n min-width: 32px;\n width: 32px;\n padding: 0;\n}\n\n.smart-plugins-details-button svg {\n width: 16px;\n height: 16px;\n}\n\n.smart-badge.pro-badge::after,\n.smart-badge.core-badge::after {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n margin-left: 0.4em;\n padding: 0.08em 0.55em;\n border-radius: 999px;\n white-space: nowrap;\n vertical-align: middle;\n font-size: 0.7em;\n font-weight: 600;\n letter-spacing: 0.14em;\n text-transform: uppercase;\n line-height: 1;\n border: 1px solid var(--background-modifier-border);\n box-shadow:\n 0 0 0 1px var(--background-primary),\n 0 1px 3px rgba(0, 0, 0, 0.35);\n transform: translateY(-0.03em);\n}\n\n.smart-badge.pro-badge::after {\n content: 'PRO';\n background-color: var(--color-accent);\n background-image: linear-gradient(\n 135deg,\n var(--color-accent),\n var(--interactive-accent-hover)\n );\n color: var(--text-on-accent, var(--background-primary));\n}\n\n.smart-badge.core-badge::after {\n content: 'CORE';\n background-color: var(--background-secondary);\n color: var(--color-normal, var(--text-normal));\n}\n\n.smart-badge.pro-badge:hover::after {\n background-color: var(--interactive-accent-hover);\n filter: brightness(1.05);\n}\n\n.smart-badge.core-badge:hover::after {\n background-color: var(--background-modifier-hover);\n}\n\n.smart-plugins-login .setting-item {\n gap: var(--size-4-3);\n}\n\n.smart-plugins-login-manual {\n margin-top: var(--size-4-3);\n}\n\n.smart-plugins-login-manual-instructions {\n font-size: var(--font-ui-small);\n color: var(--text-muted);\n margin-bottom: var(--size-2-2);\n}\n\n.smart-plugins-login-manual-controls {\n display: flex;\n gap: var(--size-2-2);\n align-items: center;\n}\n\n.smart-plugins-login-manual-input {\n flex: 1;\n min-width: 240px;\n font-size: var(--font-ui-small);\n}\n"; + +// node_modules/obsidian-smart-env/node_modules/smart-utils/convert_to_time_until.js +function convert_to_time_until(timestamp) { + const now = Date.now(); + const ms = timestamp < 1e12 ? timestamp * 1e3 : timestamp; + const diff_ms = ms - now; + if (diff_ms < 0) return "expired"; + const seconds = Math.floor(diff_ms / 1e3); + const intervals = [ + { label: "decade", seconds: 31536e4 }, + { label: "year", seconds: 31536e3 }, + { label: "month", seconds: 2592e3 }, + { label: "day", seconds: 86400 }, + { label: "hour", seconds: 3600 }, + { label: "minute", seconds: 60 }, + { label: "second", seconds: 1 } + ]; + for (const interval of intervals) { + const count = Math.floor(seconds / interval.seconds); + if (count >= 1) { + if (interval.label === "decade") { + return "never (lifetime access)"; + } + return `in ${count} ${interval.label}${count > 1 ? "s" : ""}`; + } + } + return "in a few seconds"; +} + +// node_modules/obsidian-smart-env/src/components/smart-plugins/list.js +var SMART_PLUGINS_DESC = `Core plugins provide essential functionality and a "just works" experience. Pro plugins enable advanced configuration and features for Obsidian AI experts. Learn more about Smart Plugins.`; +var PRO_PLUGINS_FOOTER = `All Pro plugins include advanced configurations and additional model providers. Pro users get priority support via email. Learn more about Pro Plugins.`; +var PRO_PLUGINS_URL = "https://smartconnections.app/pro-plugins/"; +function default_smart_plugins_list() { + return [ + { + item_type: "core", + install_method: "obsidian", + item_name: "Smart Connections", + item_desc: "See notes related to what you are working on right now.", + item_repo: "brianpetro/obsidian-smart-connections", + plugin_id: "smart-connections", + url: "https://smartconnections.app/smart-connections/" + }, + { + item_type: "pro", + item_name: "Connections Pro", + plugin_id: "smart-connections", + item_desc: "More opportunities for connections. Graph view for visualizing. Inline and footer views (great for mobile!). Configurable algorithms and additional embedding model providers.", + url: "https://smartconnections.app/smart-connections/" + }, + { + item_type: "core", + install_method: "obsidian", + item_name: "Smart Context", + item_desc: "Assemble notes into AI-ready context with selectors, links, and templates.", + item_repo: "brianpetro/smart-context-obsidian", + plugin_id: "smart-context", + url: "https://smartconnections.app/smart-context/" + }, + { + item_type: "pro", + item_name: "Context Pro", + plugin_id: "smart-context", + item_desc: "Advanced tools for context engineering. Utilize Bases, images, and external sources (great for coders!) in your contexts.", + url: "https://smartconnections.app/smart-context/" + }, + { + item_type: "core", + install_method: "obsidian", + item_name: "Smart Chat", + item_desc: "Run chat workflows in Obsidian with Smart Environment context.", + plugin_id: "smart-chatgpt", + item_repo: "brianpetro/smart-chatgpt-obsidian", + url: "https://smartconnections.app/smart-chat/" + }, + { + item_type: "pro", + item_name: "Chat Pro (API)", + plugin_id: "smart-chat", + item_desc: "Configure chat to use Local and Cloud API providers (Ollama, LM Studio, OpenAI, Gemini, Anthropic, Open Router, and more).", + url: "https://smartconnections.app/smart-chat/" + }, + { + item_type: "core", + item_name: "Smart Lookup", + item_desc: "Run semantic search as a dedicated Smart Plugin.", + item_repo: "brianpetro/smart-lookup-obsidian", + plugin_id: "smart-lookup", + url: "https://smartconnections.app/smart-lookup/", + install_method: "github" + }, + { + item_type: "core", + install_method: "obsidian", + item_name: "Smart Templates", + item_repo: "brianpetro/obsidian-smart-templates", + plugin_id: "smart-templates", + item_desc: "Create structured templates designed for Smart Plugins workflows.", + url: "https://smartconnections.app/smart-templates/" + }, + { + item_type: "pro", + item_name: "Connect Pro", + plugin_id: "smart-connect-pro", + item_desc: "Integrate with ChatGPT. Use a GPT that has access to Obsidian CLI.", + url: "https://smartconnections.app/connect-pro/" + } + ]; +} +var SMART_PLUGINS_LIST = default_smart_plugins_list(); +function build_html17(env, params = {}) { + return ` +
    +
    +
    +
    + +
    +
    +

    ${SMART_PLUGINS_DESC}

    + +
    +
    Loading...
    +
    + +

    ${PRO_PLUGINS_FOOTER}

    +
    +
    +
    + `; +} +async function render19(env, params = {}) { + this.apply_style_sheet(style_default2); + const html = build_html17.call(this, env, params); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process17.call(this, env, container, params); + return container; +} +async function post_process17(env, container, params = {}) { + const plugin = env.plugin || null; + const app2 = plugin?.app || window.app; + const oauth_storage_prefix = get_oauth_storage_prefix(app2); + const login_container = container.querySelector(".smart-plugins-login"); + const referral_container = container.querySelector(".smart-plugins-referral"); + const official_list_el = container.querySelector(".smart-plugins-official-list"); + const experimental_section_el = container.querySelector(".smart-plugins-experimental-section"); + const experimental_list_el = container.querySelector(".smart-plugins-experimental-list"); + const server_message_el = container.querySelector(".smart-plugins-server-message"); + const server_message_icon_el = server_message_el?.querySelector(".callout-icon"); + const server_message_title_el = server_message_el?.querySelector(".callout-title-inner"); + const server_message_content_el = server_message_el?.querySelector(".callout-content"); + const render_component = env.smart_components.render_component.bind(env.smart_components); + const render_login = async (login_params = {}) => { + const login_el = await render_component("smart_plugins_login", env, { + ...params, + ...login_params + }); + this.empty(login_container); + if (login_el) login_container.appendChild(login_el); + }; + const render_referrals = async (referral_params = {}) => { + const referral_el = await render_component("smart_plugins_referral", env, { + ...params, + ...referral_params + }); + this.empty(referral_container); + if (referral_el) referral_container.appendChild(referral_el); + }; + const render_markdown = async (target_el, markdown = "") => { + if (!target_el) return; + this.empty(target_el); + const safe_markdown = String(markdown || "").trim(); + if (!safe_markdown) return; + await import_obsidian26.MarkdownRenderer.render(app2, safe_markdown, target_el, "", plugin); + target_el.querySelectorAll("a").forEach((a) => { + a.setAttribute("target", "_external"); + }); + }; + const render_server_message = async (message = "", opts = {}) => { + const safe_message = String(message || "").trim(); + if (!safe_message) { + if (server_message_el?.style) server_message_el.style.display = "none"; + if (server_message_content_el) this.empty(server_message_content_el); + return; + } + const safe_title = String(opts.title || "Store message").trim() || "Store message"; + const callout_type = String(opts.callout_type || "note").trim() || "note"; + if (server_message_el?.style) server_message_el.style.display = ""; + server_message_el?.setAttribute?.("data-callout", callout_type); + if (server_message_title_el) server_message_title_el.textContent = safe_title; + if (server_message_content_el) await render_markdown(server_message_content_el, safe_message); + if (server_message_icon_el) { + (0, import_obsidian26.setIcon)(server_message_icon_el, callout_type === "warning" ? "alert-triangle" : "info"); + } + }; + const render_plugin_items = async (list_container, plugin_items = []) => { + this.empty(list_container); + for (const item of plugin_items) { + const row = await render_component("smart_plugins_list_item", item, { + ...params, + app: app2, + env, + token: item.auth_token, + sub_exp: item.root_sub_exp + }); + if (!row) continue; + if (item.has_group_ui) { + list_container.appendChild(row); + continue; + } + const group_frag = this.create_doc_fragment('
    '); + const group_el = group_frag.firstElementChild; + const group_items_el = group_el.querySelector(".setting-items"); + group_items_el.appendChild(row); + list_container.appendChild(group_el); + } + }; + let viewed_event_emitted = false; + let handled_token_rejection = false; + const render_smart_plugins = async () => { + const token = localStorage.getItem(oauth_storage_prefix + "token") || ""; + const has_token = Boolean(token); + if (!viewed_event_emitted) { + viewed_event_emitted = true; + emit_store_event(env, "pro_plugins:viewed", params, { + event_source: "smart_plugins_list", + recommended_track: "pro" + }); + } + await render_login({ + auth_state: has_token ? "checking" : "signed_out", + sub_exp: null + }); + this.empty(referral_container); + await render_server_message(); + experimental_section_el.style.display = "none"; + this.empty(experimental_list_el); + try { + await app2.plugins.loadManifests(); + let resp = await fetch_server_plugin_list(token); + let auth_state = has_token ? "signed_in" : "signed_out"; + let server_message = String(resp?.message || "").trim(); + if (resp?.status === 401 && has_token) { + auth_state = "invalid"; + if (!handled_token_rejection) { + handled_token_rejection = true; + emit_store_event(env, "pro_plugins:oauth_token_rejected", params, { + level: "warning", + message: "Session expired. Please log in again.", + event_source: "smart_plugins_list", + recommended_track: "pro" + }); + } + server_message = build_invalid_credentials_message(server_message); + const guest_resp = await fetch_server_plugin_list(""); + if (guest_resp?.status === 200) { + resp = { + ...guest_resp, + status: resp.status + }; + } else { + resp = { + list: [], + sub_exp: null, + status: resp.status + }; + } + } else { + handled_token_rejection = false; + } + if (resp?.status && ![200, 401].includes(resp.status)) { + throw new Error(`plugin_list error ${resp.status}: ${resp?.message || "Unknown error"}`); + } + const sub_exp = resp?.sub_exp ?? null; + await render_login({ + auth_state, + sub_exp + }); + await render_referrals({ + token: auth_state === "signed_in" ? token : "", + sub_exp, + auth_state + }); + await render_server_message(server_message, { + callout_type: auth_state === "invalid" ? "warning" : "note", + title: auth_state === "invalid" ? "Account message" : "Store message" + }); + SMART_PLUGINS_LIST = await hydrate_plugins_list(resp, env, { + root_sub_exp: sub_exp + }); + const { + official_items, + experimental_items + } = partition_plugin_items(SMART_PLUGINS_LIST); + await render_plugin_items(official_list_el, official_items); + if (experimental_items.length) { + experimental_section_el.style.display = ""; + await render_plugin_items(experimental_list_el, experimental_items); + } else { + experimental_section_el.style.display = "none"; + this.empty(experimental_list_el); + } + } catch (err) { + console.error("[smart-plugins:list] Failed to fetch plugin list:", err); + this.empty(official_list_el); + this.empty(experimental_list_el); + experimental_section_el.style.display = "none"; + await render_server_message(); + official_list_el.appendChild(this.create_doc_fragment('

    Failed to load plugin information.

    ')); + SMART_PLUGINS_LIST = default_smart_plugins_list(); + const retry_button = official_list_el.querySelector(".retry"); + if (retry_button) { + retry_button.addEventListener("click", render_smart_plugins); + } + } + }; + const disposers = []; + disposers.push(env.events.on("smart_plugins_oauth_completed", render_smart_plugins)); + disposers.push(env.events.on("pro_plugins:logged_out", render_smart_plugins)); + disposers.push(env.events.on("pro_plugins:refresh", render_smart_plugins)); + this.attach_disposer?.(container, disposers); + await render_smart_plugins(); + return container; +} +function normalize_release_version(value) { + const normalized_value = String(value || "").trim(); + if (!normalized_value) { + return ""; + } + return normalized_value.replace(/^v/i, ""); +} +function get_source_surface(params = {}) { + const source_surface = String( + params.source_surface || params.event_source || "plugin_store" + ).trim(); + return source_surface || "plugin_store"; +} +function build_store_event_payload(params = {}, extra = {}) { + return { + plugin_key: null, + feature_key: null, + source_surface: get_source_surface(params), + recommended_track: null, + ...extra + }; +} +function emit_store_event(env, event_key, params = {}, extra = {}) { + env?.events?.emit?.(event_key, build_store_event_payload(params, extra)); +} +async function hydrate_plugins_list(server_resp, env, params = {}) { + const smart_plugins_list = default_smart_plugins_list(); + const { list = [] } = server_resp || {}; + for (const server_item of list) { + const server_item_type = get_plugin_item_type(server_item) || "pro"; + const local_item = smart_plugins_list.find((item) => { + return item.plugin_id === server_item.plugin_id && get_plugin_item_type(item) === server_item_type; + }); + if (!local_item) { + smart_plugins_list.push({ + item_type: server_item_type, + ...server_item + }); + continue; + } + Object.assign(local_item, server_item, { + item_type: server_item_type + }); + } + await hydrate_core_plugin_versions(smart_plugins_list); + return build_plugin_list_items(smart_plugins_list, env, params); +} +async function hydrate_core_plugin_versions(smart_plugins_list = []) { + for (const item of smart_plugins_list) { + if (get_plugin_item_type(item) !== "core") continue; + const repo = get_plugin_repo(item); + if (!repo) continue; + try { + const { json: release } = await (0, import_obsidian26.requestUrl)({ + url: `https://api.github.com/repos/${repo}/releases/latest`, + method: "GET", + headers: { + "Content-Type": "application/json" + }, + contentType: "application/json" + }); + const version = normalize_release_version( + release?.tag_name || release?.name || item?.version || "" + ); + if (version) { + item.version = version; + } + } catch (error) { + console.warn(`[smart-plugins:list] Failed to hydrate latest core release for ${repo}`, error); + } + } +} +function build_plugin_list_items(smart_plugins_list = [], env, params = {}) { + const plugin_map = /* @__PURE__ */ new Map(); + for (const plugin of smart_plugins_list) { + const group_key = get_plugin_group_key(plugin); + if (!plugin_map.has(group_key)) { + plugin_map.set(group_key, { core: null, pro: null }); + } + const plugin_group = plugin_map.get(group_key); + if (get_plugin_item_type(plugin) === "core") { + plugin_group.core = plugin; + } else { + plugin_group.pro = plugin; + } + } + return Array.from(plugin_map.values()).map((plugin_group) => { + return new PluginListItem(env, plugin_group, params); + }); +} +var PluginListItem = class { + constructor(env, plugins = {}, params = {}) { + this.env = env; + this.app = env.obsidian_app; + this.core_plugin = plugins.core || null; + this.pro_plugin = plugins.pro || null; + this.root_sub_exp = normalize_positive_epoch_ms(params.root_sub_exp); + } + get auth_token() { + const oauth_storage_prefix = get_oauth_storage_prefix(this.app); + return localStorage.getItem(oauth_storage_prefix + "token") || ""; + } + get plugin_id() { + return String( + this.core_plugin?.plugin_id || this.pro_plugin?.plugin_id || "" + ).trim(); + } + get has_core_plugin() { + return Boolean(this.core_plugin); + } + get has_pro_plugin() { + return Boolean(this.pro_plugin); + } + get has_group_ui() { + return this.has_core_plugin && this.has_pro_plugin; + } + get display_item_type() { + if (this.has_group_ui) return "group"; + return get_plugin_item_type(this.primary_plugin) || "pro"; + } + get primary_plugin() { + return this.core_plugin || this.pro_plugin || null; + } + get installed_manifest() { + return this.app.plugins?.manifests?.[this.plugin_id] || null; + } + get is_enabled() { + return this.app.plugins.enabledPlugins.has(this.plugin_id); + } + get env_plugin_state() { + return this.env?.plugin_states?.[this.plugin_id] || null; + } + get installed_type() { + if (!this.installed_manifest) return null; + if (this.has_pro_plugin && !this.has_core_plugin) { + return "pro"; + } + if (this.has_core_plugin && !this.has_pro_plugin) { + return "core"; + } + return infer_installed_plugin_type({ + plugin_id: this.plugin_id, + manifest_name: this.installed_manifest.name + }); + } + get installed_plugin() { + return this.get_track_plugin(this.installed_type); + } + get loaded_version() { + return this.app.plugins.plugins[this.plugin_id]?.manifest?.version || null; + } + get loaded_env_version() { + return this.app.plugins.plugins[this.plugin_id]?.SmartEnv?.version || ""; + } + get installed_version() { + return this.installed_manifest?.version || null; + } + get is_entitled() { + return this.pro_plugin?.entitled === true; + } + get tags() { + return [.../* @__PURE__ */ new Set([ + ...get_plugin_tags(this.core_plugin), + ...get_plugin_tags(this.pro_plugin) + ])]; + } + get is_experimental() { + return this.tags.includes("experimental"); + } + get canonical_url() { + if (!this.has_group_ui) { + return this.get_track_canonical_url(this.display_item_type); + } + return this.get_track_canonical_url(this.installed_type || "pro") || this.get_track_canonical_url("core") || ""; + } + get details_url() { + if (!this.has_group_ui) { + return this.get_track_details_url(this.display_item_type); + } + return this.get_track_details_url(this.installed_type || "pro") || this.get_track_details_url("core") || ""; + } + get item_sub_exp() { + return normalize_positive_epoch_ms(this.pro_plugin?.sub_exp); + } + get should_show_item_subscription_state() { + return this.has_pro_plugin && this.root_sub_exp === null && this.item_sub_exp !== null; + } + get subscription_status_text() { + if (!this.should_show_item_subscription_state) { + return ""; + } + if (this.item_sub_exp < Date.now()) { + return `Subscription expired ${convert_to_time_ago(this.item_sub_exp)}.`; + } + return `Subscription active, renews ${convert_to_time_until(this.item_sub_exp)}.`; + } + get formatted_name() { + if (this.has_group_ui) { + return format_plugin_name(get_plugin_name(this.core_plugin)) || format_plugin_name(get_plugin_name(this.pro_plugin)) || this.plugin_id; + } + return format_plugin_name(get_plugin_name(this.primary_plugin)) || this.plugin_id; + } + get label() { + return get_plugin_label(this.primary_plugin) || this.plugin_id || "plugin"; + } + get formatted_description() { + return get_plugin_description(this.primary_plugin); + } + get is_loaded() { + if (!this.installed_type) return false; + return this.is_enabled && this.env_plugin_state === "loaded"; + } + get is_deferred() { + if (!this.installed_type) return false; + return this.is_enabled && (this.env_plugin_state === "deferred" || this.loaded_version && this.installed_version && this.loaded_version !== this.installed_version); + } + get should_update() { + const installed_type = this.installed_type; + if (!installed_type) return false; + const installed_plugin = this.get_track_plugin(installed_type); + return should_offer_plugin_update({ + item_type: installed_type, + installed_type, + is_entitled: installed_type === "pro" ? this.is_entitled : true, + server_version: normalize_release_version(installed_plugin?.version), + installed_version: this.installed_version, + compare_versions + }); + } + get has_outdated_env_compatibility() { + const installed_type = this.installed_type; + if (!installed_type) return false; + return should_signal_outdated_env_compatibility({ + item_type: installed_type, + installed_type, + is_entitled: installed_type === "pro" ? this.is_entitled : true, + is_enabled: this.is_enabled, + is_loaded: this.is_loaded, + is_deferred: this.is_deferred, + loaded_env_version: this.loaded_env_version + }); + } + get is_installed() { + return Boolean(this.installed_type); + } + get computed_state() { + const computed_state = compute_plugin_list_item_state({ + has_core_plugin: this.has_core_plugin, + has_pro_plugin: this.has_pro_plugin, + display_item_type: this.display_item_type, + installed_type: this.installed_type, + is_entitled: this.is_entitled, + should_update: this.should_update, + has_outdated_env_compatibility: this.has_outdated_env_compatibility, + is_deferred: this.is_deferred, + is_loaded: this.is_loaded, + is_enabled: this.is_enabled + }); + const hydrate_track_state = (track_state) => { + if (!track_state) return null; + return { + ...track_state, + plugin: this.get_track_plugin(track_state.item_type), + control_specs: this.get_control_specs_for_state(track_state.control_state) + }; + }; + return { + row: hydrate_track_state(computed_state.row), + track_states: { + core: hydrate_track_state(computed_state.track_states.core), + pro: hydrate_track_state(computed_state.track_states.pro) + } + }; + } + get control_specs() { + return this.computed_state.row?.control_specs || []; + } + get_track_state(item_type) { + return this.computed_state.track_states?.[item_type] || null; + } + get_control_specs_for_state(control_state) { + switch (control_state) { + case "deferred": { + const is_updated = this.loaded_version && this.loaded_version !== this.installed_version; + return [ + { type: "status", text: `${is_updated ? "Update ready." : "Installed & enabled."} Reload to activate` }, + { type: "button", action: "restart_obsidian", text: "Reload", variant: "primary" } + ]; + } + case "update_available": + return [ + { type: "status", text: "Update available" }, + { type: "button", action: "install", text: "Update", variant: "primary" } + ]; + case "outdated_env": + return [ + { type: "status", text: "Reload required for current Smart Environment" }, + { type: "button", action: "restart_obsidian", text: "Reload", variant: "primary" } + ]; + case "loaded": + return [ + { type: "status", text: "Active" }, + { type: "button", action: "open_settings", text: "Open settings", variant: "secondary" } + ]; + case "installed": + return [ + { type: "status", text: "Installed" } + ]; + case "can_enable": + return [ + { type: "button", action: "enable", text: "Enable", variant: "primary" } + ]; + case "included_in_pro": + return [ + { type: "status", text: "Included in Pro" } + ]; + case "core_installed": + return [ + { type: "status", text: "Core installed" }, + ...this.is_entitled ? [{ type: "button", action: "install", text: "Install Pro", variant: "primary" }] : [], + { type: "button", action: "learn_more", text: "Learn more", variant: "secondary" } + ]; + case "can_install_core_only": + return [ + { type: "button", action: "install", text: "Install Core", variant: "primary" } + ]; + case "can_install_pro": + return [ + { type: "button", action: "install", text: "Install Pro", variant: "primary" }, + { type: "button", action: "learn_more", text: "Learn more", variant: "secondary" } + ]; + case "cant_install": + return [ + { type: "button", action: "learn_more", text: "Learn more", variant: "secondary" } + ]; + case "can_install": + default: + return [ + { type: "button", action: "install", text: "Install", variant: "primary" }, + { type: "button", action: "learn_more", text: "Learn more", variant: "secondary" } + ]; + } + } + get_track_control_specs(item_type) { + return this.get_track_state(item_type)?.control_specs || []; + } + get_busy_text(action) { + const control_state = this.computed_state.row?.control_state || ""; + switch (action) { + case "install": + return ["update_available", "outdated_env"].includes(control_state) ? "Updating\u2026" : "Installing\u2026"; + case "enable": + return "Enabling\u2026"; + default: + return ""; + } + } + get_busy_text_for_track(item_type, action) { + const control_state = this.get_track_state(item_type)?.control_state || ""; + switch (action) { + case "install": + return ["update_available", "outdated_env"].includes(control_state) ? "Updating\u2026" : "Installing\u2026"; + case "enable": + return "Enabling\u2026"; + default: + return this.get_busy_text(action); + } + } + get install_target_plugin() { + const control_state = this.computed_state.row?.control_state || "can_install"; + if (["update_available", "outdated_env"].includes(control_state)) { + return this.installed_plugin; + } + if (control_state === "can_install_pro" || control_state === "core_installed") { + return this.pro_plugin; + } + if (control_state === "can_install_core_only") { + return this.core_plugin; + } + if (control_state === "can_install") { + return this.core_plugin || this.pro_plugin; + } + return this.installed_plugin || this.core_plugin || this.pro_plugin; + } + get install_target_item_type() { + return get_plugin_item_type(this.install_target_plugin); + } + get install_target_label() { + return get_plugin_label(this.install_target_plugin) || this.label || this.plugin_id || "plugin"; + } + get_track_plugin(item_type) { + if (item_type === "core") return this.core_plugin; + if (item_type === "pro") return this.pro_plugin; + return null; + } + get_track_name(item_type) { + return format_plugin_name(get_plugin_name(this.get_track_plugin(item_type))) || this.formatted_name || this.plugin_id; + } + get_track_description(item_type) { + return get_plugin_description(this.get_track_plugin(item_type)); + } + get_track_canonical_url(item_type) { + const plugin = this.get_track_plugin(item_type) || this.primary_plugin; + return get_plugin_canonical_url(plugin) || get_plugin_details_url(plugin) || PRO_PLUGINS_URL; + } + get_track_details_url(item_type) { + const plugin = this.get_track_plugin(item_type) || this.primary_plugin; + const release_page_url = this.installed_type === item_type ? build_plugin_release_page_url(plugin, this.installed_version) : ""; + return release_page_url || get_plugin_details_url(plugin) || get_plugin_canonical_url(plugin) || ""; + } + get_track_subscription_status_text(item_type) { + if (item_type !== "pro") return ""; + return this.subscription_status_text; + } + get_plugin_action_label(plugin) { + return get_plugin_label(plugin) || this.install_target_label || this.label || this.plugin_id || "plugin"; + } + async handle_action(action, params = {}) { + switch (action) { + case "install": + if (should_block_pro_install({ + item_type: this.install_target_item_type, + is_entitled: this.is_entitled + })) { + emit_store_event(this.env, "pro_plugins:install_blocked", params, { + plugin_key: this.plugin_id, + recommended_track: "pro", + level: "warning", + message: `${this.install_target_label} requires an active Pro entitlement.`, + event_source: "browse_smart_plugins.list_item.install" + }); + return; + } + await this.install(params); + return; + case "enable": + await this.enable(params); + return; + case "restart_obsidian": + this.restart_obsidian(); + return; + case "open_settings": + this.open_settings(); + return; + case "learn_more": + this.open_plugin_url(params); + return; + case "open_details": + this.open_details_url(); + return; + default: + return; + } + } + async handle_track_action(item_type, action, params = {}) { + if (action === "install") { + const plugin = this.get_track_plugin(item_type); + const track_item_type = get_plugin_item_type(plugin); + const track_label = get_plugin_label(plugin) || this.install_target_label || this.label; + if (should_block_pro_install({ + item_type: track_item_type, + is_entitled: this.is_entitled + })) { + emit_store_event(this.env, "pro_plugins:install_blocked", params, { + plugin_key: this.plugin_id, + recommended_track: "pro", + level: "warning", + message: `${track_label} requires an active Pro entitlement.`, + event_source: "browse_smart_plugins.list_item.install" + }); + return; + } + await this.install(params, plugin); + return; + } + if (action === "open_details") { + this.open_track_details_url(item_type); + return; + } + if (action === "learn_more") { + this.open_track_plugin_url(item_type, params); + return; + } + await this.handle_action(action, params); + } + async install(params = {}, plugin = this.install_target_plugin) { + if (!plugin) return; + if (get_plugin_item_type(plugin) === "core") { + if (get_plugin_install_method(plugin) === "github") { + await this.install_github_release_plugin(params, plugin); + return; + } + await this.install_core_plugin(plugin); + return; + } + emit_store_event(this.env, "pro_plugin_install_clicked", params, { + plugin_key: this.plugin_id, + recommended_track: "pro", + event_source: "browse_smart_plugins.list_item.install" + }); + await this.install_plugin(params, plugin); + } + async enable(params = {}) { + try { + await enable_plugin(this.app, this.plugin_id); + this.env?.events?.emit?.("pro_plugins:enabled", { + level: "debug", + message: `${this.label} enabled.`, + event_source: "browse_smart_plugins.list_item" + }); + this.env?.events?.emit?.("pro_plugins:refresh", { + event_source: "browse_smart_plugins.list_item.enable" + }); + } catch (err) { + console.error("[smart-plugins:list] Enable error:", err); + this.env?.events?.emit?.("pro_plugins:enable_failed", { + level: "error", + message: `Enable failed: ${err.message}`, + details: err?.stack || "", + event_source: "browse_smart_plugins.list_item" + }); + } + } + restart_obsidian() { + if (typeof this.app?.commands?.executeCommandById === "function") { + this.app.commands.executeCommandById("app:reload"); + return; + } + window.location.reload(); + } + open_settings() { + this.app.setting.open(); + this.app.setting.openTabById(this.plugin_id); + } + open_details_url() { + const details_url = this.details_url || PRO_PLUGINS_URL; + window.open(with_utm_source(details_url, "plugin-store"), "_external"); + } + open_track_details_url(item_type) { + const details_url = this.get_track_details_url(item_type) || PRO_PLUGINS_URL; + window.open(with_utm_source(details_url, "plugin-store"), "_external"); + } + open_plugin_url(params = {}) { + if (this.has_pro_plugin && !this.is_entitled) { + emit_store_event(this.env, "pro_trial_cta_clicked", params, { + plugin_key: this.plugin_id, + recommended_track: "pro", + event_source: "browse_smart_plugins.list_item.learn_more" + }); + } + const url = this.canonical_url || PRO_PLUGINS_URL; + window.open(with_utm_source(url, "plugin-store"), "_external"); + } + open_track_plugin_url(item_type, params = {}) { + if (item_type === "pro" && this.has_pro_plugin && !this.is_entitled) { + emit_store_event(this.env, "pro_trial_cta_clicked", params, { + plugin_key: this.plugin_id, + recommended_track: "pro", + event_source: "browse_smart_plugins.list_item.learn_more" + }); + } + const url = this.get_track_canonical_url(item_type) || PRO_PLUGINS_URL; + window.open(with_utm_source(url, "plugin-store"), "_external"); + } + async install_core_plugin(plugin) { + const was_installed = this.is_installed; + const plugin_label = this.get_plugin_action_label(plugin); + const install_enable_behavior = get_install_enable_behavior({ + was_installed + }); + try { + this.env?.events?.emit?.("smart_plugins:install_started", { + level: "debug", + message: `Installing "${plugin_label}" ...`, + event_source: "browse_smart_plugins.list_item" + }); + const { json: release } = await this.get_latest_github_release(plugin); + const version = release?.tag_name; + await this.app.plugins.installPlugin(get_plugin_repo(plugin), version, { + id: this.plugin_id, + name: plugin_label + }); + if (install_enable_behavior.should_enable_after_install) { + await this.enable(); + } + this.env?.events?.emit?.("smart_plugins:install_completed", { + level: "debug", + message: `${plugin_label} installed successfully.`, + event_source: "browse_smart_plugins.list_item" + }); + this.env?.events?.emit?.("pro_plugins:refresh", { + event_source: "browse_smart_plugins.list_item.core_install" + }); + } catch (err) { + console.error("[smart-plugins:list] Core install error:", err); + this.env?.events?.emit?.("smart_plugins:install_failed", { + level: "error", + message: `Install failed: ${err.message}`, + details: err?.stack || "", + event_source: "browse_smart_plugins.list_item" + }); + } + } + async download_plugin_files(plugin) { + if (!this.auth_token) { + throw new Error("Login required to install this plugin."); + } + const version = String(plugin?.version || "").trim() || null; + const repo = get_plugin_repo(plugin); + const files = []; + for (const file_name of install_file_names) { + const response = await fetch_plugin_file(repo, this.auth_token, { + file: file_name, + version + }); + files.push(build_plugin_file_record(file_name, response)); + } + return files; + } + async install_plugin(params = {}, plugin) { + const was_installed = this.is_installed; + const plugin_label = this.get_plugin_action_label(plugin); + const install_enable_behavior = get_install_enable_behavior({ + was_installed + }); + try { + this.env?.events?.emit?.("pro_plugins:install_started", { + level: "debug", + message: `Installing "${plugin_label}" ...`, + event_source: "browse_smart_plugins.list_item" + }); + const files = await this.download_plugin_files(plugin); + const folder_name = String(this.plugin_id || "").trim(); + if (!folder_name) { + throw new Error(`Missing plugin id for "${plugin_label}".`); + } + const base_folder = `${this.app.vault.configDir}/plugins/${folder_name}`; + await write_files_with_adapter(this.app.vault.adapter, base_folder, files); + await this.app.plugins.loadManifests(); + if (install_enable_behavior.should_enable_after_install) { + await enable_plugin(this.app, this.plugin_id); + } + this.env?.events?.emit?.("pro_plugins:install_completed", { + level: "debug", + message: `${plugin_label} installed successfully.`, + event_source: "browse_smart_plugins.list_item" + }); + emit_store_event(this.env, "pro_plugin_installed", params, { + plugin_key: this.plugin_id, + recommended_track: "pro", + event_source: "browse_smart_plugins.list_item.install" + }); + this.env?.events?.emit?.("pro_plugins:refresh", { + event_source: "browse_smart_plugins.list_item.install" + }); + if (typeof params.on_installed === "function") { + await params.on_installed(); + } + } catch (err) { + console.error("[smart-plugins:list] Install error:", err); + this.env?.events?.emit?.("pro_plugins:install_failed", { + level: "error", + message: `Install failed: ${err.message}`, + details: err?.stack || "", + event_source: "browse_smart_plugins.list_item" + }); + } + } + async install_github_release_plugin(params = {}, plugin) { + const app2 = params.app || this.app; + const env = params.env || null; + const repo = get_plugin_repo(plugin); + const was_installed = this.is_installed; + const plugin_label = this.get_plugin_action_label(plugin); + const install_enable_behavior = get_install_enable_behavior({ + was_installed + }); + if (!repo) { + throw new Error(`Missing GitHub repo for "${plugin_label}".`); + } + if (!this.plugin_id) { + throw new Error(`Missing plugin id for "${plugin_label}".`); + } + try { + env?.events?.emit?.("smart_plugins:install_started", { + level: "debug", + message: `Installing "${plugin_label}" ...`, + event_source: "browse_smart_plugins.list_item" + }); + const { json: release } = await this.get_latest_github_release(plugin); + const assets = release?.assets || []; + const main_asset = assets.find((asset) => asset.name === "main.js"); + const manifest_asset = assets.find((asset) => asset.name === "manifest.json"); + const styles_asset = assets.find((asset) => asset.name === "styles.css"); + if (!main_asset || !manifest_asset || !styles_asset) { + throw new Error("Failed to find necessary assets in the latest GitHub release."); + } + const plugin_folder = `${app2.vault.configDir}/plugins/${this.plugin_id}`; + if (!await app2.vault.adapter.exists(plugin_folder)) { + await app2.vault.adapter.mkdir(plugin_folder); + } + await Promise.all([ + this.download_and_write_release_asset(app2, main_asset.browser_download_url, `${plugin_folder}/main.js`), + this.download_and_write_release_asset(app2, manifest_asset.browser_download_url, `${plugin_folder}/manifest.json`), + this.download_and_write_release_asset(app2, styles_asset.browser_download_url, `${plugin_folder}/styles.css`) + ]); + await app2.plugins.loadManifests(); + if (install_enable_behavior.should_enable_after_install) { + await enable_plugin(app2, this.plugin_id); + } + env?.events?.emit?.("smart_plugins:install_completed", { + level: "debug", + message: `${plugin_label} installed successfully.`, + event_source: "browse_smart_plugins.list_item.install_github_release_plugin" + }); + env?.events?.emit?.("pro_plugins:refresh", { + event_source: "browse_smart_plugins.list_item.install_github_release_plugin" + }); + if (typeof params.on_installed === "function") { + await params.on_installed(); + } + } catch (err) { + console.error("[smart-plugins:list] GitHub install error:", err); + env?.events?.emit?.("smart_plugins:install_failed", { + level: "error", + message: `Install failed: ${err.message}`, + details: err?.stack || "", + event_source: "browse_smart_plugins.list_item.install_github_release_plugin" + }); + } + } + async get_latest_github_release(plugin) { + const repo = get_plugin_repo(plugin) || get_plugin_repo(this.install_target_plugin); + return await (0, import_obsidian26.requestUrl)({ + url: `https://api.github.com/repos/${repo}/releases/latest`, + method: "GET", + headers: { + "Content-Type": "application/json" + }, + contentType: "application/json" + }); + } + async download_and_write_release_asset(app2, download_url, output_path) { + const resp = await (0, import_obsidian26.requestUrl)({ + url: download_url, + method: "GET" + }); + await app2.vault.adapter.write(output_path, resp.text); + } +}; +function get_plugin_group_key(plugin) { + return plugin?.plugin_id || `${get_plugin_item_type(plugin)}:${get_plugin_name(plugin)}`; +} +function get_plugin_item_type(plugin = {}) { + return String(plugin?.item_type || "").trim(); +} +function get_plugin_repo(plugin = {}) { + return String(plugin?.item_repo || plugin?.repo || "").trim(); +} +function get_plugin_name(plugin = {}) { + return String(plugin?.item_name || plugin?.name || plugin?.plugin_id || "").trim(); +} +function get_plugin_label(plugin = {}) { + return String( + plugin?.item_name || plugin?.name || plugin?.plugin_id || plugin?.repo || "plugin" + ).trim(); +} +function get_plugin_description(plugin = {}) { + return String(plugin?.item_desc || plugin?.description || "").trim(); +} +function get_plugin_canonical_url(plugin = {}) { + return String( + plugin?.main_url || plugin?.url || plugin?.info_url || plugin?.details_url || plugin?.docs_url || "" + ).trim(); +} +function get_plugin_details_url(plugin = {}) { + return String( + plugin?.details_url || plugin?.docs_url || plugin?.info_url || plugin?.url || "" + ).trim(); +} +function build_plugin_release_page_url(plugin = {}, version = "") { + const main_url = String(plugin?.main_url || plugin?.url || "").trim(); + const version_slug = get_release_page_slug(version); + if (!main_url || !version_slug) return ""; + const base_url = main_url.split("#")[0].split("?")[0].replace(/\/+$/, ""); + if (!base_url) return ""; + return `${base_url}/releases/${version_slug}/`; +} +function get_release_page_slug(version = "") { + const version_pcs = normalize_release_version(version).split("."); + const major = String(version_pcs[0] || "").trim(); + const minor = String(version_pcs[1] || "").trim(); + if (!major || !minor) return ""; + return `${major}-${minor}`; +} +function get_plugin_install_method(plugin = {}) { + return String(plugin?.install_method || "server").trim() || "server"; +} +function get_plugin_tags(plugin = {}) { + return Array.isArray(plugin?.tags) ? plugin.tags.map((tag) => String(tag || "").trim().toLowerCase()).filter(Boolean) : []; +} +function format_plugin_name(name = "") { + return String(name || "").replace(/\bSmart\s+/g, "").replace(/\bPro\b/g, "").replace(/\s{2,}/g, " ").trim(); +} +function partition_plugin_items(plugin_items = []) { + return plugin_items.reduce((acc, item) => { + if (item.is_experimental) { + acc.experimental_items.push(item); + } else { + acc.official_items.push(item); + } + return acc; + }, { + official_items: [], + experimental_items: [] + }); +} +function with_utm_source(url, source) { + if (!url) return PRO_PLUGINS_URL; + return url.includes("?") ? `${url}&utm_source=${source}` : `${url}?utm_source=${source}`; +} +function build_invalid_credentials_message(server_message = "") { + const default_message = "Invalid account credentials. Log out and log in again."; + const safe_server_message = String(server_message || "").trim(); + if (!safe_server_message) { + return default_message; + } + const normalized_safe_message = safe_server_message.toLowerCase(); + if (normalized_safe_message === "unauthorized" || normalized_safe_message === default_message.toLowerCase()) { + return default_message; + } + return `${default_message} + +${safe_server_message}`; +} + +// node_modules/obsidian-smart-env/src/components/smart-plugins/list_item.js +var import_obsidian27 = require("obsidian"); +function build_html18(item, params = {}) { + if (item.has_group_ui) { + return build_group_html(item, params); + } + const row_state = item.computed_state.row; + return build_row_html(item, { + item_type: item.display_item_type, + name: item.formatted_name, + subscription_status_text: item.subscription_status_text, + control_state: row_state?.control_state || "can_install" + }); +} +function build_group_html(item, params = {}) { + const core_state = item.get_track_state("core"); + const pro_state = item.get_track_state("pro"); + return `
    +
    + ${build_row_html(item, { + item_type: "core", + track_item_type: "core", + name: item.get_track_name("core"), + subscription_status_text: "", + control_state: core_state?.control_state || "cant_install" + })} + ${build_row_html(item, { + item_type: "pro", + track_item_type: "pro", + name: item.get_track_name("pro"), + subscription_status_text: item.get_track_subscription_status_text("pro"), + control_state: pro_state?.control_state || "cant_install" + })} +
    +
    `; +} +function build_row_html(item, row = {}) { + const item_type = row.item_type || item.display_item_type; + const control_state = row.control_state || item.computed_state.row?.control_state || "can_install"; + const subscription_state_html = row.subscription_status_text ? `
    ${row.subscription_status_text}
    ` : ""; + const track_item_type_attr = row.track_item_type ? ` data-track-item-type="${row.track_item_type}"` : ""; + return `
    +
    +
    ${row.name || ""}
    +
    + ${subscription_state_html} +
    +
    +
    `; +} +async function render20(item, params = {}) { + const html = build_html18(item, params); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + await post_process18.call(this, item, container, params); + return container; +} +async function post_process18(item, container, params = {}) { + const rows = item.has_group_ui ? Array.from(container.querySelectorAll(".pro-plugins-list-item[data-track-item-type]")) : [container]; + for (const row of rows) { + const track_item_type = row.getAttribute("data-track-item-type") || ""; + const description_container = row.querySelector(".setting-item-description"); + await render_description.call(this, item, description_container, get_row_description(item, track_item_type), params); + const control_container = row.querySelector(".setting-item-control"); + if (!control_container) continue; + const controls = await render_controls.call(this, item, { + ...params, + track_item_type + }); + this.empty(control_container); + if (controls) control_container.appendChild(controls); + } + return container; +} +function get_row_description(item, track_item_type = "") { + const safe_track_item_type = String(track_item_type || "").trim(); + if (safe_track_item_type) { + return item.get_track_description(safe_track_item_type); + } + return item.formatted_description; +} +async function render_description(item, container, markdown = "", params = {}) { + if (!container) return; + this.empty(container); + const safe_markdown = String(markdown || "").trim(); + if (!safe_markdown) return; + const app2 = params.app || item.app || item.env?.obsidian_app || window.app; + const component = item.env?.main || item.env?.plugin || null; + await import_obsidian27.MarkdownRenderer.render(app2, safe_markdown, container, "", component); + container.querySelectorAll("a").forEach((a) => { + a.setAttribute("target", "_external"); + try { + const url = new URL(a.href); + if (!url.searchParams.has("utm_source")) { + url.searchParams.set("utm_source", "plugin_list_description"); + a.href = url.toString(); + } + } catch (e) { + } + }); +} +function build_controls_html(item, params = {}) { + const track_item_type = String(params.track_item_type || "").trim(); + const details_url = track_item_type ? item.get_track_details_url(track_item_type) : item.details_url; + const control_specs = track_item_type ? item.get_track_control_specs(track_item_type) : item.control_specs; + const details_button_html = details_url ? '' : ""; + return `
    ${details_button_html}${control_specs.map(build_control_html).join("")}
    `; +} +async function render_controls(item, params = {}) { + const html = build_controls_html(item, params); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process_controls.call(this, item, container, params); + return container; +} +async function post_process_controls(item, container, params = {}) { + const track_item_type = String(params.track_item_type || "").trim(); + const details_buttons = container.querySelectorAll(".smart-plugins-details-button"); + for (const details_button of details_buttons) { + (0, import_obsidian27.setIcon)(details_button, "info"); + } + const buttons = container.querySelectorAll("button[data-action]"); + for (const button of buttons) { + button.addEventListener("click", async () => { + const action = button.getAttribute("data-action"); + if (!action) return; + const busy_text = track_item_type ? item.get_busy_text_for_track(track_item_type, action) : item.get_busy_text(action); + if (busy_text) { + await run_busy_action(button, async () => { + if (track_item_type) { + await item.handle_track_action(track_item_type, action, params); + return; + } + await item.handle_action(action, params); + }, busy_text); + return; + } + if (track_item_type) { + await item.handle_track_action(track_item_type, action, params); + return; + } + await item.handle_action(action, params); + }); + } + return container; +} +function build_control_html(control_spec) { + if (control_spec.type === "status") { + return `${control_spec.text}`; + } + const class_name = control_spec.variant === "primary" ? "mod-cta" : ""; + return ``; +} +async function run_busy_action(button, callback, busy_text) { + if (!button || typeof callback !== "function") return; + const idle_text = button.textContent; + button.disabled = true; + if (busy_text) button.textContent = busy_text; + try { + await callback(); + } finally { + button.disabled = false; + button.textContent = idle_text; + } +} + +// node_modules/obsidian-smart-env/src/components/smart-plugins/login.js +var import_obsidian28 = require("obsidian"); +function build_html19(env, params = {}) { + return ``; +} +async function render21(env, params = {}) { + const html = build_html19.call(this, env, params); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + await post_process19.call(this, env, container, params); + return container; +} +async function post_process19(env, container, params = {}) { + const app2 = env?.plugin?.app || window.app; + const oauth_storage_prefix = get_oauth_storage_prefix(app2); + const sub_exp = Number(params.sub_exp ?? 0) || 0; + let login_click_count = 0; + let last_login_url = ""; + let manual_login_el = null; + const token = localStorage.getItem(oauth_storage_prefix + "token") || ""; + const auth_state = String(params.auth_state || "").trim() || (token ? "signed_in" : "signed_out"); + const logout = () => { + localStorage.removeItem(oauth_storage_prefix + "token"); + localStorage.removeItem(oauth_storage_prefix + "refresh"); + env?.events?.emit?.("pro_plugins:logged_out", { + level: "info", + message: "Logged out of Smart Plugins", + event_source: "smart_plugins_login" + }); + }; + const render_manual_login_link = (login_url) => { + if (!login_url) return; + if (!manual_login_el || !manual_login_el.isConnected) { + manual_login_el = document.createElement("div"); + manual_login_el.classList.add("smart-plugins-login-manual"); + container.appendChild(manual_login_el); + } + manual_login_el.innerHTML = ""; + const instructions = document.createElement("div"); + instructions.classList.add("smart-plugins-login-manual-instructions"); + instructions.textContent = "If the login page did not open, copy this link and paste it into your browser to open the login page:"; + manual_login_el.appendChild(instructions); + const controls = document.createElement("div"); + controls.classList.add("smart-plugins-login-manual-controls"); + manual_login_el.appendChild(controls); + const input = document.createElement("input"); + input.classList.add("smart-plugins-login-manual-input"); + input.type = "text"; + input.value = login_url; + input.readOnly = true; + input.addEventListener("focus", () => input.select()); + controls.appendChild(input); + const btn = document.createElement("button"); + btn.classList.add("mod-cta"); + btn.textContent = "Copy"; + btn.addEventListener("click", async () => { + await copy_to_clipboard(login_url, { + env, + event_source: "smart_plugins_login.manual_link_copy", + success_event_key: "pro_plugins:login_link_copied", + error_event_key: "pro_plugins:login_link_copy_failed", + unavailable_event_key: "pro_plugins:login_link_copy_unavailable" + }); + }); + controls.appendChild(btn); + }; + if (auth_state === "checking") { + new import_obsidian28.Setting(container).setName("Checking session...").setDesc("Validating your Smart Plugins account session."); + return container; + } + if (auth_state === "invalid") { + const setting2 = new import_obsidian28.Setting(container).setName("Session needs refresh").setDesc("Invalid account credentials. Log out and log in again."); + setting2.addButton((btn) => { + btn.setButtonText("Refresh"); + btn.onClick(() => { + env?.events?.emit?.("pro_plugins:refresh", { + event_source: "smart_plugins_login" + }); + }); + }); + setting2.addButton((btn) => { + btn.setButtonText("Logout"); + btn.onClick(() => { + logout(); + }); + }); + return container; + } + if (auth_state === "signed_out" || !token) { + const setting2 = new import_obsidian28.Setting(container).setName("Connect account").setDesc("Log in with the key provided in your Pro welcome email."); + setting2.addButton((btn) => { + btn.setButtonText("Login"); + btn.onClick(() => { + login_click_count += 1; + last_login_url = initiate_smart_plugins_oauth(); + if (login_click_count >= 2) { + render_manual_login_link(last_login_url); + } + env?.events?.emit?.("pro_plugins:oauth_browser_login_requested", { + level: "info", + message: "Please complete the login in your browser.", + event_source: "smart_plugins_login" + }); + }); + }); + return container; + } + if (sub_exp && sub_exp < Date.now()) { + const setting2 = new import_obsidian28.Setting(container).setName("All-access subscription expired").setDesc("Your Smart Plugins all-access subscription has expired. Please update your subscription to retain access to Pro plugins."); + setting2.addButton((btn) => { + btn.setButtonText("Get Pro"); + btn.onClick(() => { + window.open("https://smartconnections.app/subscribe/", "_external"); + }); + }); + setting2.addButton((btn) => { + btn.setButtonText("Update subscription"); + btn.onClick(() => { + window.open("https://smartconnections.app/subscription-update/", "_external"); + }); + }); + setting2.addButton((btn) => { + btn.setButtonText("Refresh"); + btn.onClick(() => { + env?.events?.emit?.("pro_plugins:refresh", { + event_source: "smart_plugins_login" + }); + }); + }); + return container; + } + const setting = new import_obsidian28.Setting(container); + const subscription_text = subscription_status(sub_exp); + setting.setDesc( + subscription_text ? `Signed in to Smart Plugins account. ${subscription_text}` : "Signed in to Smart Plugins account." + ); + setting.addButton((btn) => { + btn.setButtonText("Logout"); + btn.onClick(() => { + logout(); + }); + }); + return container; +} +function initiate_smart_plugins_oauth() { + const state = Math.random().toString(36).slice(2); + const redirect_uri = encodeURIComponent("obsidian://smart-plugins/callback"); + const url = `${get_smart_server_url()}/oauth?client_id=smart-plugins-op&redirect_uri=${redirect_uri}&state=${state}`; + window.open(url, "_external"); + return url; +} +function subscription_status(sub_exp) { + const normalized_sub_exp = Number(sub_exp || 0); + if (!Number.isFinite(normalized_sub_exp) || normalized_sub_exp <= 0) { + return ""; + } + if (normalized_sub_exp < Date.now()) { + return `All-access subscription expired ${convert_to_time_ago(normalized_sub_exp)}.`; + } + return `All-access subscription active, expires ${convert_to_time_until(normalized_sub_exp)}.`; +} + +// node_modules/obsidian-smart-env/src/components/smart-plugins/referral.js +var import_obsidian29 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/components/smart-plugins/onboarding_signup.js +var DEFAULT_ONBOARDING_START_URL = "https://smartconnections.app/onboarding/start/"; +function build_onboarding_start_url(params = {}) { + const source = String(params.source || "").trim(); + if (!source) return DEFAULT_ONBOARDING_START_URL; + const url = new URL(DEFAULT_ONBOARDING_START_URL); + url.searchParams.set("source", source); + return url.toString(); +} +function get_onboarding_signup_setting_copy() { + return { + name: "Get the 12-part getting started email series", + description: "Receive a practical onboarding sequence with focused Smart Plugins workflows.", + button_text: "Subscribe" + }; +} + +// node_modules/obsidian-smart-env/src/components/smart-plugins/referral.js +function build_html20(env, params = {}) { + return `
    `; +} +async function render22(env, params = {}) { + const html = build_html20.call(this, env, params); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + await post_process20.call(this, env, container, params); + return container; +} +async function post_process20(env, container, params = {}) { + const render_onboarding_signup_section = () => { + const copy = get_onboarding_signup_setting_copy(); + const setting = new import_obsidian29.Setting(container).setName(copy.name).setDesc(copy.description); + setting.addButton((btn) => { + btn.setButtonText(copy.button_text); + btn.onClick(() => { + const onboarding_url = build_onboarding_start_url({ source: "plugins_settings" }); + window.open(onboarding_url, "_external"); + env?.events?.emit?.("onboarding:opened_signup", { + event_source: "smart_plugins_referral" + }); + }); + }); + }; + const emit_referral_event = (event_key) => { + env?.events?.emit?.(event_key, { + event_source: "smart_plugins_referral" + }); + }; + render_onboarding_signup_section(); + const token = String(params.token || "").trim(); + if (!token) { + const setting = new import_obsidian29.Setting(container).setName("Give $30 off Pro. Get 30 days of Pro").setDesc("Start a free trial to unlock your referral link."); + setting.addButton((btn) => { + btn.setButtonText("Start free trial"); + btn.onClick(() => { + window.open("https://smartconnections.app/pro-plugins/", "_external"); + }); + }); + return container; + } + const sub_exp = Number(params.sub_exp ?? 0) || 0; + if (sub_exp && sub_exp < Date.now()) { + return container; + } + try { + const stats = await fetch_referral_stats({ token }); + const referral_link = String(stats?.referral_link || "").trim(); + if (!referral_link) return container; + const setting = new import_obsidian29.Setting(container).setName("Referral link").setDesc("Give $30 off Pro. Get 30 days of Pro."); + setting.addButton((btn) => { + btn.setButtonText("Copy link"); + btn.onClick(async () => { + const copied = await copy_to_clipboard(referral_link, { + env, + event_source: "smart_plugins_referral.copy_link", + success_event_key: "referrals:copied_link_notice", + error_event_key: "referrals:copy_link_failed", + unavailable_event_key: "referrals:copy_link_unavailable" + }); + if (copied) emit_referral_event("referrals:copied_link"); + }); + }); + setting.addButton((btn) => { + btn.setButtonText("Open referrals"); + btn.onClick(() => { + window.open("https://smartconnections.app/my-referrals/", "_external"); + emit_referral_event("referrals:opened_dashboard"); + }); + }); + } catch (err) { + console.error("[smart-plugins:referral] Failed to load referral stats:", err); + } + return container; +} + +// node_modules/obsidian-smart-env/src/components/source_inspector.css +var source_inspector_default = ".source-inspector {\n background-color: var(--background-secondary-alt);\n margin: var(--size-4-3) 0;\n padding: var(--size-4-3);\n border-radius: var(--radius-m);\n}\n\n.source-inspector-blocks-container {\n margin-top: var(--size-4-2);\n display: flex;\n flex-direction: column;\n gap: var(--size-4-3);\n}\n\n.source-inspector-blocks-container blockquote {\n margin-left: var(--size-4-3);\n padding-left: var(--size-4-3);\n border-left: 2px solid var(--text-faint);\n}\n"; + +// node_modules/obsidian-smart-env/src/components/source_inspector.js +function build_html21(source, opts = {}) { + return `
    +
    + + +
    +
    +

    Blocks

    +
    +
    +
    `; +} +async function render23(source, opts = {}) { + const html = build_html21(source, opts); + const frag = this.create_doc_fragment(html); + this.apply_style_sheet(source_inspector_default); + await post_process21.call(this, source, frag, opts); + return frag; +} +async function post_process21(source, frag, opts = {}) { + const container = frag.querySelector(".source-inspector .source-inspector-blocks-container"); + if (!container) return frag; + const source_info = frag.querySelector(".source-inspector-source-info"); + const btn = frag.querySelector(".source-inspector-show-data-btn"); + const data_div = frag.querySelector(".source-inspector-source-data"); + const pre = data_div?.querySelector("pre"); + if (btn && data_div && pre) { + btn.addEventListener("click", () => { + if (data_div.style.display === "none") { + pre.textContent = JSON.stringify(source.data, null, 2); + data_div.style.display = ""; + btn.textContent = "Hide source data"; + } else { + data_div.style.display = "none"; + btn.textContent = "Show source data"; + } + }); + } + const source_should_embed = source.should_embed ? `should embed` : `embedding skipped`; + const source_embed_status = source.vec ? `vectorized` : `not vectorized`; + const source_info_frag = this.create_doc_fragment(`

    ${source_should_embed} | ${source_embed_status}

    `); + source_info.appendChild(source_info_frag); + if (!source || !source.blocks || source.blocks.length === 0) { + this.safe_inner_html(container, `

    No blocks

    `); + return frag; + } + const sorted_blocks = source.blocks.sort((a, b) => a.line_start - b.line_start); + for (const block of sorted_blocks) { + const sub_key_display = block.sub_key.split("#").join(" > "); + const block_info = `${sub_key_display} (${block.size} chars; lines: ${block.line_start}-${block.line_end})`; + const should_embed = block.should_embed ? `should embed` : `embedding skipped`; + const embed_status = block.vec ? `vectorized` : `not vectorized`; + let block_content = ""; + let embed_input = ""; + try { + const raw = await block.read(); + block_content = raw.replace(//g, ">").replace(/\n/g, "
    ").replace(/\t/g, "  "); + const embed_raw = await block.get_embed_input(raw); + embed_input = embed_raw.replace(//g, ">"); + } catch (err) { + console.error("[source_inspector] Error reading block:", err); + block_content = `Error reading block content`; + } + const block_frag = this.create_doc_fragment(` +

    + ${block_info}
    + ${should_embed} | ${embed_status} +

    +
    + Embed input +
    ${embed_input}
    +
    +
    ${block_content}
    +
    + `); + container.appendChild(block_frag); + } + return frag; +} + +// node_modules/obsidian-smart-env/src/components/status_bar.js +var import_obsidian33 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/register_status_bar_context_menu.js +var import_obsidian32 = require("obsidian"); + +// node_modules/obsidian-smart-env/views/source_inspector.js +var import_obsidian30 = require("obsidian"); +var SmartNoteInspectModal = class extends import_obsidian30.Modal { + constructor(smart_connections_plugin, entity) { + super(smart_connections_plugin.app); + this.smart_connections_plugin = smart_connections_plugin; + this.entity = entity; + } + get env() { + return this.smart_connections_plugin.env; + } + onOpen() { + this.titleEl.innerText = this.entity.key; + this.render(); + } + async render() { + this.contentEl.empty(); + const frag = await this.env.smart_components.render_component("source_inspector", this.entity); + this.contentEl.appendChild(frag); + } +}; + +// node_modules/obsidian-smart-env/src/modals/env_stats.js +var import_obsidian31 = require("obsidian"); +var EnvStatsModal = class extends import_obsidian31.Modal { + constructor(app2, env) { + super(app2); + this.env = env; + } + onOpen() { + this.titleEl.setText("Smart Environment"); + this.contentEl.empty(); + this.contentEl.createEl("p", { text: "Loading stats..." }); + setTimeout(this.render.bind(this), 100); + } + async render() { + const frag = await this.env.smart_components.render_component("env_stats", this.env); + this.contentEl.empty(); + if (frag) { + this.contentEl.appendChild(frag); + } else { + this.contentEl.createEl("p", { text: "Failed to load stats." }); + } + } +}; + +// node_modules/obsidian-smart-env/src/utils/register_status_bar_context_menu.js +function register_status_bar_context_menu(env, status_container, deps = {}) { + const { Menu: MenuClass = import_obsidian32.Menu } = deps; + const plugin = env.main; + const on_context_menu = (ev) => { + ev.preventDefault(); + ev.stopPropagation(); + const menu = new MenuClass(plugin.app); + menu.addItem( + (item) => item.setTitle("Inspect active note").setIcon("search").onClick(async () => { + const active_file = plugin.app.workspace.getActiveFile(); + if (!active_file) { + env?.events?.emit?.("status_bar:inspect_active_note_missing", { + level: "warning", + message: "No active note found", + event_source: "register_status_bar_context_menu.inspect" + }); + return; + } + const src = env.smart_sources?.get(active_file.path); + if (!src) { + env?.events?.emit?.("status_bar:inspect_source_missing", { + level: "warning", + message: "Active note is not indexed by Smart Environment", + event_source: "register_status_bar_context_menu.inspect" + }); + return; + } + new SmartNoteInspectModal(plugin, src).open(); + }) + ); + menu.addItem( + (item) => item.setTitle("Show stats").setIcon("chart-pie").onClick(() => { + const modal = new EnvStatsModal(plugin.app, env); + modal.open(); + }) + ); + menu.addItem( + (item) => item.setTitle("Export data").setIcon("download").onClick(() => { + env.export_json(); + env?.events?.emit?.("smart_env:exported", { + level: "attention", + message: "Smart Env exported", + event_source: "register_status_bar_context_menu.export" + }); + }) + ); + menu.addItem( + (item) => item.setTitle("Milestones").setIcon("flag").onClick(() => { + env.open_milestones_modal(); + }) + ); + menu.addItem( + (item) => item.setTitle("Notifications").setIcon("bell").onClick(() => { + env.open_notifications_feed_modal(); + }) + ); + menu.addSeparator(); + menu.addItem( + (item) => item.setTitle("Browse Smart Plugins").setIcon("package").onClick(() => { + env.events?.emit?.("smart_plugins:browse", { + event_source: "status_bar" + }); + }) + ); + if (env.is_pro) { + menu.addItem( + (item) => item.setTitle("Refer a friend (Give 30, Get 30)").setIcon("hand-heart").onClick(() => { + const url = "https://smartconnections.app/my-referrals/?utm_source=status-bar"; + window.open(url, "_external"); + }) + ); + } else { + menu.addItem( + (item) => item.setTitle("Start 14-day Pro trial").setIcon("hand-heart").onClick(() => { + const url = "https://smartconnections.app/pro-plugins/?utm_source=status-bar"; + window.open(url, "_external"); + }) + ); + } + menu.showAtPosition({ x: ev.pageX, y: ev.pageY }); + }; + plugin.registerDomEvent(status_container, "contextmenu", on_context_menu); + return on_context_menu; +} + +// node_modules/obsidian-smart-env/src/components/status_bar.css +var status_bar_default = ".status-bar-item:has(.smart-env-status-container) {\n padding: 0 0.5em;\n\n &:hover {\n background-color: var(--background-modifier-hover);\n }\n & > .smart-env-status-container {\n display: flex;\n align-items: center;\n gap: 0.5em;\n text-decoration: none;\n color: var(--status-bar-text-color);\n }\n}\n\n.smart-env-status-indicator {\n --smart-env-status-indicator-color: var(--status-bar-text-color);\n --smart-env-status-indicator-glow-size: 0.28em;\n --smart-env-status-indicator-glow-opacity: 0.38;\n\n position: relative;\n isolation: isolate;\n\n width: 0.62em;\n height: 0.62em;\n min-width: 0.62em;\n min-height: 0.62em;\n border-radius: 999px;\n\n color: var(--smart-env-status-indicator-color);\n background-color: currentColor;\n opacity: 1;\n transform: scale(1);\n box-shadow:\n 0 0 0 1px color-mix(in srgb, currentColor 24%, transparent),\n inset 0 0 0 1px color-mix(in srgb, var(--background-primary) 78%, transparent);\n transition:\n background-color 150ms ease,\n box-shadow 150ms ease,\n transform 150ms ease,\n color 150ms ease;\n cursor: pointer;\n}\n\n.smart-env-status-indicator::before {\n content: '';\n position: absolute;\n inset: calc(var(--smart-env-status-indicator-glow-size) * -1);\n border-radius: inherit;\n pointer-events: none;\n z-index: -1;\n opacity: var(--smart-env-status-indicator-glow-opacity);\n transform: scale(1);\n filter: blur(7px);\n background:\n radial-gradient(\n circle at center,\n color-mix(in srgb, currentColor 74%, transparent) 0%,\n color-mix(in srgb, currentColor 46%, transparent) 34%,\n color-mix(in srgb, currentColor 18%, transparent) 58%,\n transparent 80%\n );\n transition:\n opacity 150ms ease,\n transform 150ms ease,\n filter 150ms ease;\n}\n\n.smart-env-status-indicator::after {\n content: '';\n position: absolute;\n inset: -1px;\n border-radius: inherit;\n pointer-events: none;\n box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 28%, transparent);\n opacity: 0.82;\n}\n\n.smart-env-status-indicator:hover::before,\n.smart-env-status-indicator:focus-visible::before {\n opacity: max(var(--smart-env-status-indicator-glow-opacity), 0.56);\n transform: scale(1.08);\n}\n\n.smart-env-status-indicator:focus-visible {\n outline: 2px solid var(--color-accent);\n outline-offset: 2px;\n}\n\n.smart-env-status-indicator[data-level='default'] {\n --smart-env-status-indicator-color: var(--status-bar-text-color);\n --smart-env-status-indicator-glow-size: 0.24em;\n --smart-env-status-indicator-glow-opacity: 0.32;\n}\n\n.smart-env-status-indicator[data-level='info'] {\n --smart-env-status-indicator-color: var(--status-bar-text-color);\n --smart-env-status-indicator-glow-size: 0.28em;\n --smart-env-status-indicator-glow-opacity: 0.4;\n}\n\n.smart-env-status-indicator[data-level='milestone'] {\n --smart-env-status-indicator-color: var(--color-accent);\n --smart-env-status-indicator-glow-size: 0.52em;\n --smart-env-status-indicator-glow-opacity: 0.86;\n}\n\n.smart-env-status-indicator[data-level='attention'] {\n --smart-env-status-indicator-color: var(--color-yellow);\n --smart-env-status-indicator-glow-size: 0.5em;\n --smart-env-status-indicator-glow-opacity: 0.84;\n}\n\n.smart-env-status-indicator[data-level='warning'] {\n --smart-env-status-indicator-color: var(--color-orange);\n --smart-env-status-indicator-glow-size: 0.54em;\n --smart-env-status-indicator-glow-opacity: 0.9;\n}\n\n.smart-env-status-indicator[data-level='error'] {\n --smart-env-status-indicator-color: var(--color-red);\n --smart-env-status-indicator-glow-size: 0.58em;\n --smart-env-status-indicator-glow-opacity: 0.96;\n}\n\n.smart-env-status-indicator[data-count] {\n transform: scale(1.08);\n box-shadow:\n 0 0 0 1px color-mix(in srgb, currentColor 30%, transparent),\n inset 0 0 0 1px color-mix(in srgb, var(--background-primary) 84%, transparent);\n}\n\n.smart-env-status-indicator[data-count]::before {\n opacity: 1;\n transform: scale(1.12);\n filter: blur(7px);\n animation: smart-env-status-indicator-glow 2200ms ease-in-out infinite;\n}\n\n.status-bar-mobile {\n position: var(--status-bar-position);\n bottom: 0;\n border-radius: 0 8px 0 0;\n border-style: solid;\n border-width: 1px;\n border-color: var(--status-bar-border-color);\n background-color: var(--status-bar-background);\n color: var(--status-bar-text-color);\n font-size: var(--status-bar-font-size);\n min-height: 18px;\n padding: var(--size-4-1);\n user-select: none;\n z-index: var(--layer-status-bar);\n font-variant-numeric: tabular-nums;\n & > .smart-env-status-container {\n padding: 5px 5px 5px 0;\n }\n}\n\n/* footer view on mobile */\n.embedded-backlinks > .status-bar-mobile {\n position: relative;\n border-style: none;\n}\n\n@keyframes smart-env-status-indicator-glow {\n 0%,\n 100% {\n transform: scale(0.88);\n opacity: 0.92;\n }\n\n 50% {\n transform: scale(1.23);\n opacity: 1;\n }\n}\n"; + +// node_modules/obsidian-smart-env/src/components/status_bar.js +function build_html22() { + return ` + + + + + + `; +} +async function render24(env, opts = {}) { + this.apply_style_sheet(status_bar_default); + const frag = this.create_doc_fragment(build_html22()); + const anchor = frag.firstElementChild; + post_process22.call(this, env, anchor, opts); + return anchor; +} +function post_process22(env, container, opts = {}) { + const icon_slot = container?.querySelector?.(".smart-env-status-icon"); + const status_indicator = container?.querySelector?.(".smart-env-status-indicator"); + const status_msg = container?.querySelector?.(".smart-env-status-msg"); + const pause_embedding = () => { + return env?.smart_sources?.entities_vector_adapter?.halt_embed_queue_processing?.(); + }; + const resume_embedding = () => { + return env?.smart_sources?.entities_vector_adapter?.resume_embed_queue_processing?.(); + }; + const set_status_message = (message) => { + if (!status_msg) return; + if (typeof status_msg.setText === "function") { + status_msg.setText(message); + return; + } + status_msg.textContent = message; + }; + const open_notifications_feed3 = (event) => { + event.preventDefault?.(); + event.stopPropagation?.(); + env.open_notifications_feed_modal?.(); + }; + const update_indicator = (status_state) => { + if (!status_indicator) return; + const { + indicator_count, + indicator_level + } = status_state; + status_indicator.dataset.level = indicator_level || "default"; + if (indicator_count > 0) { + status_indicator.dataset.count = String(indicator_count); + } else { + status_indicator.removeAttribute("data-count"); + } + const indicator_title = indicator_count > 0 ? `${indicator_count} unseen notification${indicator_count === 1 ? "" : "s"}` : "Open notifications feed"; + status_indicator.setAttribute("title", indicator_title); + }; + const action_handlers = { + pause_embed(event) { + event.preventDefault?.(); + event.stopPropagation?.(); + pause_embedding(); + }, + resume_embed(event) { + event.preventDefault?.(); + event.stopPropagation?.(); + resume_embedding(); + }, + run_reimport(event) { + event.preventDefault?.(); + event.stopPropagation?.(); + set_status_message("Re-importing\u2026"); + env.run_re_import?.(); + }, + noop() { + }, + context_menu(event) { + const context_event = new MouseEvent("contextmenu", { + bubbles: true, + cancelable: true, + clientX: event?.clientX || 0, + clientY: event?.clientY || 0 + }); + container.dispatchEvent?.(context_event); + } + }; + const render_status_elm = () => { + const status_state = get_status_bar_state(env); + const { + message, + title + } = status_state; + if (icon_slot) { + (0, import_obsidian33.setIcon)(icon_slot, "smart-connections"); + } + update_indicator(status_state); + set_status_message(message); + container.setAttribute?.("title", title); + container.removeAttribute?.("href"); + container.removeAttribute?.("target"); + }; + const run_container_action = (event) => { + const status_state = get_status_bar_state(env); + const action_key = Object.prototype.hasOwnProperty.call(action_handlers, status_state.click_action) ? status_state.click_action : "context_menu"; + action_handlers[action_key](event); + }; + register_status_bar_context_menu(env, container); + let progress_poll_interval = null; + const set_polling = (active) => { + if (active) { + if (progress_poll_interval) return; + progress_poll_interval = setInterval(() => { + refresh_status_bar(); + }, 1e3); + return; + } + if (!progress_poll_interval) return; + clearInterval(progress_poll_interval); + progress_poll_interval = null; + }; + const refresh_status_bar = () => { + render_status_elm(); + set_polling(should_poll_env_activity(env)); + }; + refresh_status_bar(); + bind_once(status_indicator, "_click_handler", "click", open_notifications_feed3); + bind_once(status_indicator, "_keydown_handler", "keydown", (event) => { + if (event.key !== "Enter" && event.key !== " ") return; + open_notifications_feed3(event); + }); + bind_once(container, "_click_handler", "click", (event) => { + run_container_action(event); + }); + bind_once(container, "_keydown_handler", "keydown", (event) => { + if (event.key !== "Enter" && event.key !== " ") return; + event.preventDefault(); + run_container_action(event); + }); + let debounce_timeout = null; + const debounce_refresh_status_bar = () => { + if (debounce_timeout) clearTimeout(debounce_timeout); + debounce_timeout = setTimeout(() => { + refresh_status_bar(); + debounce_timeout = null; + }, 100); + }; + const disposers = []; + disposers.push(env.events.on("*", debounce_refresh_status_bar)); + disposers.push(() => { + set_polling(false); + if (debounce_timeout) clearTimeout(debounce_timeout); + }); + this.attach_disposer(container, disposers); +} +function bind_once(element, handler_key, event_name, handler) { + if (!element || typeof handler !== "function") return; + if (element[handler_key]) return; + element[handler_key] = handler; + element.addEventListener(event_name, handler); +} + +// node_modules/obsidian-smart-env/src/components/suggest_display_right.css +var suggest_display_right_default = ".sc-modal-suggestion-right {\n margin-left: auto;\n text-align: right;\n white-space: nowrap;\n font-size: var(--font-ui-smaller);\n color: var(--text-muted);\n font-variant-numeric: tabular-nums;\n float: right;\n}"; + +// node_modules/obsidian-smart-env/src/components/suggest_display_right.js +function build_html23(display_right, params = {}) { + return `${display_right}`; +} +function render25(display_right, params = {}) { + this.apply_style_sheet(suggest_display_right_default); + const frag = this.create_doc_fragment(build_html23(display_right, params)); + const container = frag.firstElementChild; + return container; +} + +// node_modules/obsidian-smart-env/src/components/supporter_callout.js +var import_obsidian34 = require("obsidian"); +function build_html24(plugin, opts = {}) { + const { plugin_name = plugin.manifest.name } = opts; + return `
    + +
    `; +} +function render26(plugin, opts = {}) { + const html = build_html24.call(this, plugin, opts); + const frag = this.create_doc_fragment(html); + const container = frag.querySelector(".wrapper"); + post_process23.call(this, plugin, container, opts); + return container; +} +async function post_process23(plugin, container) { + const icon_container = container.querySelector(".callout-icon"); + const icon = (0, import_obsidian34.getIcon)("hand-heart"); + if (icon) { + this.empty(icon_container); + icon_container.appendChild(icon); + } + const oauth_storage_prefix = plugin.app.vault.getName().toLowerCase().replace(/[^a-z0-9]/g, "_") + "_smart_plugins_oauth_"; + const is_logged_in = !!localStorage.getItem(oauth_storage_prefix + "token"); + if (is_logged_in) container.querySelector("#footer-callout").style.display = "none"; + await this.render_setting_components(container, { scope: plugin.env }); +} + +// node_modules/obsidian-smart-env/src/components/user_agreement_callout.js +var import_obsidian35 = require("obsidian"); +function build_html25(plugin, opts = {}) { + const { plugin_name = plugin.manifest.name } = opts; + return `
    + +
    `; +} +function render27(plugin, opts = {}) { + const html = build_html25.call(this, plugin, opts); + const frag = this.create_doc_fragment(html); + const callout = frag.querySelector("#footer-callout"); + const icon_container = callout.querySelector(".callout-icon"); + const icon = (0, import_obsidian35.getIcon)("smart-connections"); + if (icon) { + this.empty(icon_container); + icon_container.appendChild(icon); + } + post_process24.call(this, plugin, callout, opts); + return callout; +} +function post_process24(plugin, callout) { +} + +// node_modules/obsidian-smart-env/src/utils/smart-context/format_stats_message.js +function format_stats_message(stats = {}) { + const item_count = Number.isFinite(stats.item_count) ? stats.item_count : 0; + const char_count = Number.isFinite(stats.char_count) ? stats.char_count : 0; + const segments = []; + segments.push(`${item_count} file(s)`); + segments.push(`${format_char_count(char_count)} chars`); + if (Number.isFinite(stats.max_depth)) { + segments.push(`depth\u2264${stats.max_depth}`); + } + const excluded_total = sum_exclusions(stats.exclusions); + if (excluded_total > 0) { + segments.push(`${excluded_total} section(s) excluded`); + } + return `Copied to clipboard! (${segments.join(", ")})`; +} +function format_char_count(char_count) { + if (!Number.isFinite(char_count)) return "0"; + if (char_count >= 1e5) { + return `~${Math.round(char_count / 1e3)}k`; + } + return char_count.toLocaleString(); +} +function sum_exclusions(exclusions) { + if (!exclusions) return 0; + return Object.values(exclusions).reduce((total, value) => { + const numeric = Number.isFinite(value) ? value : 0; + return total + numeric; + }, 0); +} + +// node_modules/obsidian-smart-env/src/actions/context/copy_to_clipboard.js +async function copy_to_clipboard2(params = {}) { + const re_import_queue = this.env?.smart_sources?.sources_re_import_queue || {}; + const re_import_count = Object.keys(re_import_queue).length; + if (re_import_count && typeof this.env?.run_re_import === "function") { + this.emit_event("context:reimport_before_action", { + level: "info", + message: "Updating changed sources before copying context.", + count: re_import_count, + event_source: "context_actions.copy_to_clipboard" + }); + await this.env.run_re_import(); + } + const context_items = this.context_items.filter(params.filter); + if (!context_items.length) { + this.emit_event("context:copy_empty", { + level: "warning", + message: "No context items to copy.", + event_source: "context_actions.copy_to_clipboard" + }); + return false; + } + const content = await this.get_text(params); + const copied = await copy_to_clipboard(content, { + env: this.env, + event_source: "context_actions.copy_to_clipboard.base_copy", + success_event_key: "context:clipboard_raw_copied", + error_event_key: "context:clipboard_raw_copy_failed", + unavailable_event_key: "context:clipboard_copy_unavailable" + }); + if (!copied) return false; + const message = format_stats_message({ + item_count: context_items.length, + char_count: content.length, + max_depth: params.max_depth, + exclusions: params.exclusions + }); + this.emit_event("context:copied", { + level: "info", + message, + event_source: "context_actions.copy_to_clipboard" + }); + return true; +} + +// node_modules/obsidian-smart-env/src/utils/smart-context/template_presets.js +var DEFAULT_TEMPLATE_PRESET = "xml_structured"; +var template_presets = { + xml_structured: { + label: "XML-style (default)", + context_template_before: "\n{{FILE_TREE}}", + context_template_after: "", + item_template_before: '', + item_template_after: "" + }, + markdown_headings: { + label: "Markdown headings", + context_template_before: "{{FILE_TREE}}", + context_template_after: "", + item_template_before: [ + "## {{KEY}} {{IS_CURRENT}}", + "Updated: {{TIME_AGO}} | Depth: {{LINK_DEPTH}}", + "````{{EXT}}" + ].join("\n"), + item_template_after: "````\n" + }, + json_structured: { + label: "JSON structured", + context_template_before: '{\n "context": {', + context_template_after: " }\n}", + item_template_before: ' "{{KEY}}": { "name": "{{ITEM_NAME}}", "updated": "{{TIME_AGO}}", "depth": {{LINK_DEPTH}}, "content": ', + item_template_after: " },", + json_stringify: true + }, + // PRO + custom: { + label: "Custom (PRO)" + } +}; +var get_preset_key = (settings = {}) => { + const preset_key = settings.template_preset || DEFAULT_TEMPLATE_PRESET; + if (template_presets[preset_key]) return preset_key; + return "custom"; +}; +var get_template_value = (settings, defaults, preset_field_key, settings_field_key) => { + const preset_key = get_preset_key(settings); + const preset = template_presets[preset_key]; + const value_from_settings = settings?.[settings_field_key]; + if (preset_key !== "custom" && preset && typeof preset[preset_field_key] === "string") { + return preset[preset_field_key]; + } + if (preset_key === "custom" && typeof value_from_settings === "string") { + return value_from_settings; + } + return defaults?.[settings_field_key]; +}; +function get_template_preset_options() { + return Object.entries(template_presets).map(([value, config]) => ({ + value, + label: config.label || value + })); +} +function get_context_templates(settings = {}, defaults = {}) { + return { + template_before: get_template_value(settings, defaults, "context_template_before", "template_before"), + template_after: get_template_value(settings, defaults, "context_template_after", "template_after") + }; +} +function get_item_templates(settings = {}, defaults = {}) { + const preset_key = get_preset_key(settings); + const preset = template_presets[preset_key]; + const include_json_stringify = preset_key === "custom" && typeof settings.json_stringify === "boolean"; + return { + ...preset && typeof preset === "object" ? preset : {}, + // include all preset fields + ...include_json_stringify ? { json_stringify: settings.json_stringify } : {}, + template_before: get_template_value(settings, defaults, "item_template_before", "template_before"), + template_after: get_template_value(settings, defaults, "item_template_after", "template_after") + }; +} + +// node_modules/obsidian-smart-env/src/actions/context-item/merge_template.js +var derive_item_name_from_key = (key = "") => { + if (typeof key !== "string" || key.trim().length === 0) return ""; + const [filename_with_fragment] = key.split(/[\\/]/).slice(-1); + const [source_name, ...block_parts] = (filename_with_fragment || "").split("#"); + const src_no_ext = source_name.includes(".") ? source_name.slice(0, source_name.lastIndexOf(".")) : source_name; + if (block_parts.length > 0) { + return `${src_no_ext}#${block_parts.join("#")}`; + } + return src_no_ext; +}; +var get_item_name = (context_item) => { + return derive_item_name_from_key(context_item.key); +}; +async function merge_template(item_text, params = {}) { + const active_file_path = this.env?.obsidian_app?.workspace?.getActiveFile?.()?.path; + const item_source_path = this.key.split("#")[0]; + const MERGE_VARS = { + "KEY": this.key, + "ITEM_NAME": get_item_name(this), + "TIME_AGO": convert_to_time_ago(this.mtime) || "Missing", + "LINK_DEPTH": this.data.d || "0", + "EXT": this.item_ref?.file_type || "", + "IS_CURRENT": active_file_path && item_source_path === active_file_path ? "is-current" : "" + }; + const replace_vars = async (template) => { + const re_var = /{{([\w_]+)}}/g; + const number_of_var_matches = (template.match(re_var) || []).length; + for (let i = 0; i < number_of_var_matches; i++) { + template = template.replace(/{{(\w+)}}/g, (match, p1) => { + return MERGE_VARS[p1] || ""; + }); + } + return template; + }; + const templates = get_item_templates(this.settings, default_settings2); + if (params.json_stringify || templates.json_stringify) { + item_text = JSON.stringify(item_text); + } + const before = await replace_vars(templates.template_before); + const after = await replace_vars(templates.template_after); + return ["", before, item_text, after, ""].join("\n"); +} +var settings_config7 = { + template_preset: { + group: "Item templates", + type: "dropdown", + name: "Select template", + description: "Wraps each context item with a pre-configured template.", + options_callback: () => get_template_preset_options(), + callback(template_value) { + const is_pro = this?.env?.is_pro; + if (!is_pro) return; + if (template_value !== "custom") return; + this.emit_event("context_item:custom_template_set"); + } + }, + template_before: { + group: "Item templates", + type: "textarea", + name: "Template Before", + description: "Template to wrap before the context item content.", + scope_class: "pro-setting" + }, + template_after: { + group: "Item templates", + type: "textarea", + name: "Template After", + description: "Template to wrap after the context item content.", + scope_class: "pro-setting" + }, + item_explanation: { + type: "html", + group: "Item templates", + value: ` + Available variables: +
      +
    • {{KEY}} - Full path of the item
    • +
    • {{ITEM_NAME}} - Source file or block name without folder path or file extension
    • +
    • {{TIME_AGO}} - Time since the item was last modified
    • +
    • {{LINK_DEPTH}} - Depth level of the item
    • +
    • {{EXT}} - File extension of the item
    • +
    • {{IS_CURRENT}} - is-current when the item belongs to the active note
    • +
    + ` + }, + json_stringify: { + group: "Item templates", + type: "toggle", + name: "JSON Stringify", + description: "Convert the item content to a JSON string (forces full content into single line in quotes).", + scope_class: "pro-setting" + } +}; +var default_settings2 = { + template_preset: "xml_structured", + template_before: '', + template_after: "" +}; + +// node_modules/obsidian-smart-env/node_modules/smart-utils/file_tree.js +function build_file_tree_string(paths = []) { + if (!Array.isArray(paths) || paths.length === 0) return ""; + const root = {}; + for (const path of paths) { + const isFolder = is_folder_path(path); + const parts = path.split("/").filter(Boolean); + let node = root; + for (let i = 0; i < parts.length; i++) { + const part = parts[i]; + const isLast = i === parts.length - 1; + if (isLast) { + if (isFolder) { + node[part] = node[part] ?? { __isExplicitFolder: true }; + } else { + node[part] = null; + } + } else { + node = node[part] ??= {}; + } + } + } + compress_single_child_dirs(root); + return build_tree_string(root).trimEnd(); +} +function is_folder_path(path) { + return typeof path === "string" && path.endsWith("/"); +} +function compress_single_child_dirs(node) { + if (!node || typeof node !== "object") return; + for (const key of Object.keys(node)) { + const child = node[key]; + if (child && typeof child === "object") { + if (child.__isExplicitFolder) { + delete child.__isExplicitFolder; + compress_single_child_dirs(child); + continue; + } + const childKeys = Object.keys(child); + if (childKeys.length === 1 && child[childKeys[0]] !== null && !child[childKeys[0]].__isExplicitFolder) { + const mergedKey = `${key}/${childKeys[0]}`; + node[mergedKey] = child[childKeys[0]]; + delete node[key]; + compress_single_child_dirs(node[mergedKey]); + } else { + compress_single_child_dirs(child); + } + } + } +} +function build_tree_string(node, prefix = "") { + let output = ""; + const entries = Object.entries(node).sort((a, b) => { + const aIsDir = a[1] !== null; + const bIsDir = b[1] !== null; + if (aIsDir && !bIsDir) return -1; + if (!aIsDir && bIsDir) return 1; + return a[0].localeCompare(b[0]); + }); + entries.forEach(([name, child], idx) => { + const isLast = idx === entries.length - 1; + const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "; + if (child === null) { + output += `${prefix}${connector}${name} +`; + } else { + output += `${prefix}${connector}${name}/ +`; + output += build_tree_string(child, prefix + (isLast ? " " : "\u2502 ")); + } + }); + return output; +} + +// node_modules/obsidian-smart-env/src/actions/context/merge_template.js +async function merge_template2(context_items_text, params = {}) { + const context_items = params.context_items || []; + const MERGE_VARS = { + "FILE_TREE": () => { + const active_file_path = this.env?.obsidian_app?.workspace?.getActiveFile?.()?.path; + let did_mark_current = false; + const tree_keys = context_items.map((item) => { + if (!did_mark_current && active_file_path && item.key.split("#")[0] === active_file_path) { + did_mark_current = true; + return `${item.key} (current)`; + } + return item.key; + }); + return build_file_tree_string(tree_keys); + } + }; + const replace_vars = async (template) => { + const number_of_var_matches = (template.match(/{{(\w+)}}/g) || []).length; + for (let i = 0; i < number_of_var_matches; i++) { + template = template.replace(/{{(\w+)}}/gi, (match, p1) => { + return MERGE_VARS[p1]?.() || ""; + }); + } + return template; + }; + const templates = get_context_templates(this.settings, default_settings3); + const before = await replace_vars(templates.template_before); + const after = await replace_vars(templates.template_after); + return [before, context_items_text, after].join("\n"); +} +var settings_config8 = { + template_preset: { + type: "dropdown", + group: "Context templates", + name: "Select template", + description: "Wraps the full context with a pre-configured template.", + options_callback: () => get_template_preset_options(), + callback(template_value) { + const is_pro = this?.env?.is_pro; + if (!is_pro) return; + if (template_value !== "custom") return; + this.emit_event("context:custom_template_set"); + } + }, + template_before: { + type: "textarea", + group: "Context templates", + name: "Template Before", + description: "Template to wrap before the context.", + scope_class: "pro-setting" + }, + template_after: { + type: "textarea", + group: "Context templates", + name: "Template After", + description: "Template to wrap after the context.", + scope_class: "pro-setting" + }, + context_explanation: { + type: "html", + group: "Context templates", + value: `Available variables: +
      +
    • {{FILE_TREE}} - Shows hierarchical view of all files and marks the active note with (current)
    • +
    + ` + } +}; +var default_settings3 = { + template_preset: "xml_structured", + template_before: "\n{{FILE_TREE}}", + template_after: "" +}; + +// node_modules/obsidian-smart-env/src/actions/context-suggest/blocks.js +function context_suggest_blocks(params = {}) { + params?.modal?.setInstructions([ + { command: "Enter", purpose: "Add block to context" }, + { command: "\u2190", purpose: "Back to sources" } + ]); + let blocks = []; + if (params.source_key) { + const src = this.env.smart_sources.get(params.source_key); + blocks = src.blocks; + } else { + blocks = Object.values(this.env.smart_blocks.items); + } + return blocks.sort((a, b) => { + const a_line = Array.isArray(a.lines) && a.lines.length ? a.lines[0] : Infinity; + const b_line = Array.isArray(b.lines) && b.lines.length ? b.lines[0] : Infinity; + return a_line - b_line; + }).map((block) => ({ + key: block.key, + display: get_block_display_name2(block, { show_full_path: false }), + select_action: () => { + this.add_item(block.key); + }, + arrow_left_action: ({ modal }) => { + modal.update_suggestions("context_suggest_sources"); + } + })); +} +var display_name = "Add blocks"; + +// node_modules/obsidian-smart-env/src/actions/context-suggest/contexts.js +var import_obsidian36 = require("obsidian"); +var display_name2 = "Add named contexts"; +var MOD_CHAR = import_obsidian36.Platform.isMacOS ? "\u2318" : "Ctrl"; +function set_named_context_list_instructions(modal) { + modal?.setInstructions?.([ + { command: "Enter / \u2192", purpose: "Browse context items" }, + { command: `${MOD_CHAR} + Enter`, purpose: "Add all items from context" } + ]); +} +function set_named_context_item_instructions(modal, params = {}) { + const context_name = params.context_name; + modal?.setInstructions?.([ + { command: "Enter", purpose: `Add item from ${context_name || "context"}` }, + { command: "\u2190", purpose: "Back to named contexts" } + ]); +} +function list_context_items2(env) { + const collection = env?.smart_contexts; + const items = collection?.items; + if (!items || typeof items !== "object") return []; + return Object.values(items).filter(Boolean); +} +function get_items_from_context(other_ctx) { + const data = other_ctx?.data?.context_items || {}; + const entries = Object.entries(data); + const out = []; + for (let i = 0; i < entries.length; i += 1) { + const [key, item_data] = entries[i]; + if (!key) continue; + if (item_data?.exclude) continue; + out.push({ key }); + } + return out; +} +function build_named_context_item_payloads(ctx, params = {}) { + const other_ctx = params.other_ctx; + const context_name = params.context_name; + const include_named_context = Boolean(params.include_named_context); + const items = get_items_from_context(other_ctx); + for (let i = 0; i < items.length; i += 1) { + const item = items[i]; + if (!item || typeof item.key !== "string") continue; + if (!include_named_context) continue; + item.from_named_context = context_name; + } + return items; +} +function format_depth_label(depth) { + if (!Number.isFinite(depth)) return ""; + return `depth ${depth}`; +} +function build_named_context_item_suggestions(ctx, params = {}) { + const payloads = build_named_context_item_payloads(ctx, { + ...params, + include_named_context: false + }); + set_named_context_item_instructions(params?.modal, { context_name: params.context_name }); + return payloads.filter((payload) => typeof payload?.key === "string" && payload.key.length).map((payload) => ({ + key: payload.key, + display: payload.key, + display_right: format_depth_label(payload.d), + select_action: ({ modal } = {}) => { + ctx.add_item(payload); + }, + arrow_left_action: ({ modal } = {}) => { + return context_suggest_contexts.call(ctx, { modal }); + } + })); +} +async function context_suggest_contexts(params = {}) { + const ctx = this; + const env = ctx?.env; + const modal = params?.modal; + set_named_context_list_instructions(modal); + const contexts = list_context_items2(env).filter((context_item) => { + const name = context_item?.data?.name; + return typeof name === "string" && name.trim().length > 0; + }).sort((a, b) => { + const name_a = String(a.data.name).trim().toLowerCase(); + const name_b = String(b.data.name).trim().toLowerCase(); + return name_a.localeCompare(name_b); + }); + if (!contexts.length) { + return [{ key: "contexts:none", display: "No named contexts found" }]; + } + const suggestions = []; + for (let i = 0; i < contexts.length; i += 1) { + const other = contexts[i]; + const other_key = other?.key || other?.data?.key; + const other_name = String(other?.data?.name || "").trim(); + const already_included = Boolean( + ctx?.data?.context_items && Object.values(ctx.data.context_items).some( + (item) => item?.from_named_context === other_name || item?.key === other_key + ) + ); + if (already_included) continue; + const item_count = other?.item_count || Object.keys(other?.data?.context_items || {}).length; + suggestions.push({ + key: `named_context:${other_key}`, + display: `${other_name} (${item_count})`, + item: other, + select_action: ({ modal: modal2 }) => { + return [ + { + key: other_name, + display: `Add all: ${other_name} (${item_count})`, + item: other, + select_action: ({ modal: modal3 } = {}) => { + ctx.add_item({ + key: `${other_name}`, + named_context: true + }); + return context_suggest_contexts.call(ctx, { modal: modal3 }); + }, + arrow_left_action: ({ modal: modal3 } = {}) => { + return context_suggest_contexts.call(ctx, { modal: modal3 }); + } + }, + ...build_named_context_item_suggestions(ctx, { + other_ctx: other, + context_name: other_name, + modal: modal2 + }) + ]; + }, + arrow_right_action: ({ modal: modal2 }) => { + return build_named_context_item_suggestions(ctx, { + other_ctx: other, + context_name: other_name, + modal: modal2 + }); + }, + mod_select_action: ({ modal: modal2 } = {}) => { + ctx.add_item({ + key: `${other_name}`, + named_context: true + }); + return context_suggest_contexts.call(ctx, { modal: modal2 }); + } + }); + } + return suggestions; +} + +// node_modules/obsidian-smart-env/src/actions/context-suggest/sources.js +var import_obsidian37 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/smart-context/source_folder_utils.js +function normalize_folder_path(folder_path = "") { + return String(folder_path ?? "").trim().replace(/\\+/g, "/").replace(/\/+$/g, ""); +} +function reset_modal_input(modal) { + if (!modal?.inputEl) return; + modal.last_input_value = modal.inputEl.value; + modal.inputEl.value = ""; +} +function get_sources_list(ctx, params = {}) { + const folder_path = params?.folder_path || ""; + const normalized_folder_path = normalize_folder_path(folder_path); + if (!normalized_folder_path) { + return Object.values(ctx?.env?.smart_sources?.items || {}); + } + const starts_with_folder_path = `${normalized_folder_path}/`; + return ctx?.env?.smart_sources?.filter((item) => { + if (item.key === normalized_folder_path) return true; + return item.key.startsWith(starts_with_folder_path); + }); +} + +// node_modules/obsidian-smart-env/src/actions/context-suggest/sources.js +var MOD_CHAR2 = import_obsidian37.Platform.isMacOS ? "\u2318" : "Ctrl"; +function build_source_suggestions(ctx, sources) { + return sources.map((source) => ({ + key: source.key, + display: source.key, + select_action: () => { + ctx.add_item(source.key); + }, + mod_select_action: ({ modal } = {}) => { + reset_modal_input(modal); + return context_suggest_blocks.call(ctx, { source_key: source.key, modal }); + }, + arrow_right_action: ({ modal } = {}) => { + reset_modal_input(modal); + return context_suggest_blocks.call(ctx, { source_key: source.key, modal }); + } + })); +} +function context_suggest_sources(params = {}) { + const modal = params?.modal; + if (modal) { + modal.setInstructions([ + { command: "Enter", purpose: "Add source to context" }, + { command: `${MOD_CHAR2} + Enter / \u2192`, purpose: "Suggest source blocks" } + ]); + } + const sources = get_sources_list(this, { folder_path: params?.folder_path || "" }); + return build_source_suggestions(this, sources); +} +var display_name3 = "Add sources"; + +// node_modules/obsidian-smart-env/src/actions/lookup-list/pre_process.js +async function pre_process(params) { + const query = params.query; + if (!query || typeof query !== "string" || query.trim().length === 0) { + throw new Error("Invalid or empty query provided to lookup list."); + } + const embed_model = this.env.smart_sources.embed_model; + if (!embed_model) { + throw new Error("No embed model available in environment for lookup list."); + } + const embedding = await embed_model.embed(query); + params.to_item = { ...embedding }; + if (!params.score_algo_key) params.score_algo_key = "similarity"; + return params; +} + +// node_modules/obsidian-smart-env/src/actions/similarity.js +function similarity(params) { + if (!this.vec) return { score: null, error: `Missing this.vec for ${this.key}` }; + if (!params.to_item?.vec) return { score: null, error: "Missing params.to_item.vec" }; + return { + score: cos_sim(this.vec || [], params.to_item.vec || []) + }; +} +similarity.action_type = "score"; +var display_name4 = "Cosine Similarity"; +var display_description = "Ranks by cosine similarity between the current note and candidates."; +var settings_config9 = { + similarity_algo_description: { + group: "Score algorithm", + type: "html", + name: `${display_name4} algorithm`, + value: `${display_description}` + } +}; + +// node_modules/obsidian-smart-env/src/utils/open_source.js +var import_obsidian38 = require("obsidian"); +async function open_source(item, event = null) { + try { + const env = item.env; + const obsidian_app = env.obsidian_app; + let target_path = item.key; + if (target_path.endsWith("#")) target_path = target_path.slice(0, -1); + let target_file; + if (target_path.includes("#")) { + const [file_path] = target_path.split("#"); + target_file = obsidian_app.metadataCache.getFirstLinkpathDest(file_path, ""); + } else { + target_file = obsidian_app.metadataCache.getFirstLinkpathDest(target_path, ""); + } + if (!target_file) { + const message = `Unable to resolve file for ${target_path}`; + console.warn(`[open_source] ${message}`); + item.emit_event("sources:open_failed", { + level: "warning", + message, + event_source: "open_source" + }); + return; + } + let leaf; + if (event) { + const is_mod = import_obsidian38.Keymap.isModEvent(event); + const is_alt = import_obsidian38.Keymap.isModifier(event, "Alt"); + if (is_mod && is_alt) { + leaf = obsidian_app.workspace.splitActiveLeaf("vertical"); + } else if (is_mod) { + leaf = obsidian_app.workspace.getLeaf(true); + } else { + leaf = obsidian_app.workspace.getMostRecentLeaf(); + } + } else { + leaf = obsidian_app.workspace.getMostRecentLeaf(); + } + await leaf.openFile(target_file); + if (typeof item?.line_start === "number") { + const { editor } = leaf.view; + const pos = { line: item.line_start, ch: 0 }; + editor.setCursor(pos); + editor.scrollIntoView({ to: pos, from: pos }, true); + } + item.emit_event("sources:opened", { event_source: "open_source" }); + } catch (error) { + console.error("Error in open_source:", error); + item.emit_event("sources:open_failed", { + level: "error", + message: error?.message || "Failed to open source.", + details: error?.stack || "", + event_source: "open_source" + }); + } +} + +// node_modules/obsidian-smart-env/src/actions/source/open.js +async function source_open(event = null) { + await open_source(this, event); +} + +// node_modules/obsidian-smart-env/smart_env.config.js +var smart_env_config = { + collections: { + embedding_models: embedding_models_default2, + event_logs: event_logs_default2, + lookup_lists: lookup_lists_default, + smart_blocks: smart_blocks_default, + smart_contexts: smart_contexts_default3 + }, + items: { + embedding_model: { class: EmbeddingModel, version: "1.0.1" }, + lookup_list: { class: LookupList, version: "1.0.1" }, + smart_block: { class: SmartBlock2, version: "1.0.1" }, + smart_context: { class: SmartContext2, version: "2.1.0" } + }, + modules: {}, + components: { + context_item_leaf: { render, version: "1.0.1" }, + default_notification: { render: render2, version: "1.0.1" }, + env_stats: { render: render3, version: "1.0.1" }, + env_status: { render: render4, version: "1.0.1" }, + lean_coffee_callout: { render: render5, version: "1.0.1" }, + milestone_notification: { render: render6, version: "1.0.1" }, + milestones: { render: render7, version: "1.0.1" }, + notifications_feed: { render: render8, version: "1.0.1" }, + settings_env_model: { render: render9, version: "1.0.1" }, + settings_env_model_type: { render: render10, version: "1.0.1" }, + settings_env_models: { render: render11, version: "1.0.1" }, + settings_env_sources: { render: render12, version: "1.0.1" }, + settings_model_actions: { render: render13, version: "1.0.1" }, + settings_smart_env: { render: render14, version: "1.0.1" }, + smart_context_actions: { render: render15, version: "1.0.1" }, + smart_context_item: { render: render16, version: "1.0.1" }, + smart_context_meta: { render: render17, version: "1.0.1" }, + smart_context_tree: { render: render18, version: "1.0.1" }, + smart_plugins_list: { render: render19, version: "1.0.1" }, + smart_plugins_list_item: { render: render20, version: "1.0.1" }, + smart_plugins_login: { render: render21, version: "1.0.1" }, + smart_plugins_referral: { render: render22, version: "1.0.1" }, + source_inspector: { render: render23, version: "1.0.1" }, + status_bar: { render: render24, version: "1.0.1" }, + suggest_display_right: { render: render25, version: "1.0.1" }, + supporter_callout: { render: render26, version: "1.0.1" }, + user_agreement_callout: { render: render27, version: "1.0.1" } + }, + actions: { + context_copy_to_clipboard: { action: copy_to_clipboard2, version: "1.0.1" }, + context_item_merge_template: { action: merge_template, settings_config: settings_config7, default_settings: default_settings2, version: "1.0.1" }, + context_merge_template: { action: merge_template2, settings_config: settings_config8, default_settings: default_settings3, version: "1.0.1" }, + context_suggest_blocks: { action: context_suggest_blocks, display_name, version: "1.0.1" }, + context_suggest_contexts: { action: context_suggest_contexts, display_name: display_name2, version: "1.0.1" }, + context_suggest_sources: { action: context_suggest_sources, display_name: display_name3, version: "1.0.1" }, + lookup_list_pre_process: { action: pre_process, pre_process, version: "1.0.1" }, + similarity: { action: similarity, settings_config: settings_config9, display_name: display_name4, display_description, version: "1.0.1" }, + source_open: { action: source_open, version: "1.0.1" } + } +}; + +// node_modules/obsidian-smart-env/default.config.js +var smart_env_config2 = { + env_path: "", + modules: { + smart_fs: { + class: SmartFs, + adapter: ObsidianFsAdapter + }, + smart_view: { + class: SmartView, + adapter: SmartViewObsidianAdapter + }, + smart_embed_model: { + class: SmartEmbedModel, + adapters: { + transformers: SmartEmbedTransformersIframeAdapter + } + } + }, + collections: { + context_items: context_items_default, + event_logs: event_logs_default2, + smart_components: smart_components_default2, + smart_sources: { + collection_key: "smart_sources", + class: SmartSources, + data_adapter: AjsonMultiFileSourcesDataAdapter, + source_adapters: { + "md": ObsidianMarkdownSourceContentAdapter, + "txt": ObsidianMarkdownSourceContentAdapter, + "excalidraw.md": ExcalidrawSourceContentAdapter, + "base": BasesSourceContentAdapter, + "canvas": CanvasSourceContentAdapter, + "rendered": RenderedSourceContentAdapter + // "canvas": MarkdownSourceContentAdapter, + // "default": MarkdownSourceContentAdapter, + }, + content_parsers: [ + parse_blocks + ], + // process_embed_queue: false, + process_embed_queue: true, + // trigger embedding on load + load_order: 100 + // load last + } + // smart_blocks: { + // collection_key: 'smart_blocks', + // class: SmartBlocks, + // data_adapter: AjsonMultiFileBlocksDataAdapter, + // block_adapters: { + // "md": MarkdownBlockContentAdapter, + // "txt": MarkdownBlockContentAdapter, + // "excalidraw.md": MarkdownBlockContentAdapter, + // // "canvas": MarkdownBlockContentAdapter, + // }, + // }, + }, + items: { + smart_source: smart_source_default + // smart_block, + }, + default_settings, + // begin obsidian-smart-env specific modules (need to update build_env_config.js to handle) + modals: { + context_selector: { + class: ContextModal, + default_suggest_action_keys: [ + "context_suggest_sources" + ] + }, + milestones_modal: { + class: MilestonesModal + }, + notifications_feed_modal: { + class: NotificationsFeedModal + }, + browse_plugins_modal: { + class: BrowseSmartPlugins + } + } +}; +merge_env_config(smart_env_config2, smart_env_config); +var default_config_default = smart_env_config2; + +// node_modules/obsidian-smart-env/utils/add_icons.js +var import_obsidian41 = require("obsidian"); +var svg_wrap_24 = (inner_svg) => { + return `${inner_svg}`; +}; +var smart_copy_note_svg = svg_wrap_24(` + + + + + +`); +var smart_context_builder_svg = svg_wrap_24(` + + + + + + +`); +var smart_inline_connections_svg = svg_wrap_24(` + + + + + +`); +function add_smart_chat_icon() { + (0, import_obsidian41.addIcon)("smart-chat", ` + + + + + + + +`); +} +function add_smart_connections_icon() { + (0, import_obsidian41.addIcon)("smart-connections", ` + + + + + + `); +} +function add_smart_lookup_icon() { + (0, import_obsidian41.addIcon)("smart-lookup", ` + + + + + + + + + + + + + + + + + +`); +} +function add_smart_copy_context_icon() { + (0, import_obsidian41.addIcon)("smart-copy-note", smart_copy_note_svg); +} +function add_smart_context_icon() { + (0, import_obsidian41.addIcon)("smart-context-builder", smart_context_builder_svg); +} +function add_inline_connections_icon() { + (0, import_obsidian41.addIcon)("smart-inline-connections", smart_inline_connections_svg); +} +var smart_footer_connections_svg = svg_wrap_24(` + + + + + +`); +function add_footer_connections_icon() { + (0, import_obsidian41.addIcon)("smart-footer-connections", smart_footer_connections_svg); +} +function add_smart_dupe_detector_icon() { + (0, import_obsidian41.addIcon)("smart-dupe-detector", ` + + + + + + + + + + +`); +} +function add_smart_named_contexts_icon() { + (0, import_obsidian41.addIcon)("smart-named-contexts", ` + + + + + + +`); +} +var smart_graph_svg = ` + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +`; +function add_smart_graph_icon() { + (0, import_obsidian41.addIcon)("smart-graph", smart_graph_svg); +} +function add_smart_icons() { + add_smart_copy_context_icon(); + add_smart_context_icon(); + add_inline_connections_icon(); + add_footer_connections_icon(); + add_smart_dupe_detector_icon(); + add_smart_named_contexts_icon(); + add_smart_graph_icon(); +} + +// node_modules/obsidian-smart-env/node_modules/smart-notices/smart_notices.js +var import_obsidian42 = require("obsidian"); + +// node_modules/obsidian-smart-env/node_modules/smart-notices/notices.js +var NOTICES = { + item_excluded: { + en: "Cannot show Smart Connections for excluded entity: {{entity_key}}" + }, + load_env: { + en: "Mobile detected: to prevent performance issues, click to load Smart Environment when ready.", + button: { + en: `Load Smart Env`, + callback: (env) => { + env.load(true); + } + }, + timeout: 1e4 + }, + /** @deprecated in favor of in-component insctructions (2025-06-22) */ + missing_entity: { + en: "No entity found for key: {{key}}" + }, + notice_muted: { + en: "Notice muted" + }, + new_version_available: { + en: "A new version is available! (v{{version}})", + timeout: 15e3, + button: { + en: "Release notes", + callback: (scope) => { + window.open("https://github.com/brianpetro/obsidian-smart-connections/releases", "_blank"); + } + } + }, + new_early_access_version_available: { + en: "A new early access version is available! (v{{version}})" + }, + supporter_key_required: { + en: "Supporter license key required for early access update" + }, + revert_to_stable_release: { + en: 'Click "Check for Updates" in the community plugins tab and complete the update for Smart Connections to finish reverting to the stable release.', + timeout: 0 + }, + action_installed: { + en: 'Installed action "{{name}}"' + }, + action_install_error: { + en: 'Error installing action "{{name}}": {{error}}', + timeout: 0 + }, + embed_model_not_loaded: { + en: "Embed model not loaded. Please wait for the model to load and try again." + }, + embed_search_text_failed: { + en: "Failed to embed search text." + }, + error_in_embedding_search: { + en: "Error in embedding search. See console for details." + }, + copied_to_clipboard: { + en: "Message: {{content}} copied successfully." + }, + copy_failed: { + en: "Unable to copy message to clipboard." + }, + copied_chatgpt_url_to_clipboard: { + en: "ChatGPT URL copied to clipboard." + }, + loading_collection: { + en: "Loading {{collection_key}}..." + }, + done_loading_collection: { + en: "{{collection_key}} loaded." + }, + saving_collection: { + en: "Saving {{collection_key}}..." + }, + initial_scan: { + en: "[{{collection_key}}] Starting initial scan...", + timeout: 0 + }, + done_initial_scan: { + en: "[{{collection_key}}] Initial scan complete.", + timeout: 3e3 + }, + pruning_collection: { + en: "Pruning {{collection_key}}..." + }, + done_pruning_collection: { + en: "Pruned {{count}} items from {{collection_key}}." + }, + embedding_progress: { + en: "Embedding progress: {{progress}} / {{total}}\n{{tokens_per_second}} tokens/sec using {{model_name}}", + button: { + en: "Pause", + callback: (env) => { + console.log("pausing"); + env.smart_sources.entities_vector_adapter.halt_embed_queue_processing(); + } + }, + timeout: 0 + }, + embedding_complete: { + en: "Embedding complete. {{total_embeddings}} embeddings created. {{tokens_per_second}} tokens/sec using {{model_name}}", + timeout: 0 + }, + embedding_paused: { + en: "Embedding paused. Progress: {{progress}} / {{total}}\n{{tokens_per_second}} tokens/sec using {{model_name}}", + button: { + en: "Resume", + callback: (env) => { + env.smart_sources.entities_vector_adapter.resume_embed_queue_processing(100); + } + }, + timeout: 0 + }, + embedding_error: { + en: "Error embedding: {{error}}", + timeout: 0 + }, + import_progress: { + en: "Importing... {{progress}} / {{total}} sources", + timeout: 0 + }, + done_import: { + en: "Import complete. {{count}} sources imported in {{time_in_seconds}}s", + timeout: 0 + }, + no_import_queue: { + en: "No items in import queue" + }, + clearing_all: { + en: "Clearing all data...", + timeout: 0 + }, + done_clearing_all: { + en: "All data cleared and reimported", + timeout: 3e3 + }, + image_extracting: { + en: "Extracting text from Image(s)", + timeout: 0 + }, + pdf_extracting: { + en: "Extracting text from PDF(s)", + timeout: 0 + }, + insufficient_settings: { + en: "Insufficient settings for {{key}}, missing: {{missing}}", + timeout: 0 + }, + unable_to_init_source: { + en: "Unable to initialize source: {{key}}", + timeout: 0 + }, + reload_sources: { + en: "Reloaded sources in {{time_ms}}ms" + } +}; + +// node_modules/obsidian-smart-env/node_modules/smart-notices/smart_notices.js +function define_default_create_methods(notices) { + for (const key of Object.keys(notices)) { + const notice_obj = notices[key]; + if (typeof notice_obj.create !== "function") { + notice_obj.create = function(opts = {}) { + let text = this.en ?? key; + for (const [k, v] of Object.entries(opts)) { + text = text.replace(new RegExp(`{{${k}}}`, "g"), String(v)); + } + let button; + if (!opts.button && this.button) { + const btn_label = typeof this.button.en === "string" ? this.button.en : "OK"; + button = { + text: btn_label, + callback: typeof this.button.callback === "function" ? this.button.callback : () => { + } + // no-op + }; + } else { + button = opts.button; + } + let final_timeout = opts.timeout ?? this.timeout ?? 5e3; + return { + text, + button, + timeout: final_timeout, + confirm: opts.confirm, + // pass any user-provided confirm + immutable: opts.immutable + // pass any user-provided immutable + }; + }; + } + } + return notices; +} +var SmartNotices = class { + /** + * @param {Object} scope - The main plugin instance + */ + constructor(env, opts = {}) { + env?.create_env_getter(this); + this.active = {}; + this.adapter = opts.adapter || this.env.config.modules.smart_notices.adapter; + define_default_create_methods(NOTICES); + } + /** plugin settings for notices (muted, etc.) */ + get settings() { + if (!this.env?.settings?.smart_notices) { + this.env.settings.smart_notices = {}; + } + if (!this.env?.settings?.smart_notices?.muted) { + this.env.settings.smart_notices.muted = {}; + } + return this.env?.settings?.smart_notices; + } + /** + * Displays a notice by key or custom message. + * Usage: + * notices.show('load_env', { scope: this }); + * + * @param {string} id - The notice key or custom ID + * @param {object} opts - Additional user opts + * @deprecated in favor of event system with levels + */ + show(id, opts = {}) { + let message = null; + if (typeof opts === "string") { + message = opts; + } else { + opts = opts || {}; + } + const normalized_id = this._normalize_notice_key(id); + if (this.settings?.muted?.[normalized_id]) { + if (opts.confirm?.callback) { + opts.confirm.callback(); + } + return; + } + const notice_entry = NOTICES[id]; + let derived = { + text: message || id, + timeout: opts.timeout ?? 5e3, + button: opts.button, + immutable: opts.immutable, + confirm: opts.confirm + }; + if (notice_entry?.create) { + const result = notice_entry.create({ ...opts }); + derived.text = message || result.text; + derived.timeout = result.timeout; + derived.button = result.button; + derived.immutable = result.immutable; + derived.confirm = result.confirm; + } + const content_fragment = this._build_fragment(normalized_id, derived.text, derived); + if (this.active[normalized_id]?.noticeEl?.isConnected) { + return this.active[normalized_id].setMessage(content_fragment, derived.timeout); + } + return this._render_notice(normalized_id, content_fragment, derived); + } + /** + * Normalizes the notice key to a safe string. + */ + _normalize_notice_key(key) { + return key.replace(/[^a-zA-Z0-9_-]/g, "_"); + } + /** + * Creates and tracks the notice instance + */ + _render_notice(normalized_id, content_fragment, { timeout }) { + this.active[normalized_id] = new this.adapter(content_fragment, timeout); + return this.active[normalized_id]; + } + /** + * Builds a DocumentFragment with notice text & possible buttons + */ + _build_fragment(id, text, { button, confirm: confirm2, immutable }) { + const frag = document.createDocumentFragment(); + frag.createEl("p", { + cls: "sc-notice-head", + text: `[Smart Env v${this.env.constructor.version}]` + }); + const content = frag.createEl("p", { cls: "sc-notice-content", text }); + const actions = frag.createEl("div", { cls: "sc-notice-actions" }); + if (confirm2?.text && typeof confirm2.callback === "function") { + this._add_button(confirm2, actions); + } + if (button?.text && typeof button.callback === "function") { + this._add_button(button, actions); + } + if (!immutable) { + this._add_mute_button(id, actions); + } + return frag; + } + /** + * Creates a "); + container.querySelector("button").addEventListener("click", () => { + clicked_load_env = true; + if (typeof scope.env.start_mobile_env_load === "function") { + scope.env.start_mobile_env_load({ source: "wait_for_env_to_load" }); + return; + } + scope.env.load(true); + }); + } else { + console.log("Waiting for env to load (mobile)..."); + } + await new Promise((r) => setTimeout(r, 2e3)); + } + while (!wait_for_states.includes(scope.env.state)) { + if (container) { + const loading_msg = scope.env?.obsidian_is_syncing ? "Waiting for Obsidian Sync to finish..." : "Loading Obsidian Smart Environment..."; + container.empty(); + scope.env.smart_view.safe_inner_html(container, loading_msg); + } else { + console.log("Waiting for env to load..."); + } + await new Promise((r) => setTimeout(r, 2e3)); + } + } +} + +// node_modules/obsidian-smart-env/views/smart_item_view.js +var SmartItemView = class extends import_obsidian45.ItemView { + /** + * Creates an instance of SmartItemView. + * @param {any} leaf + * @param {any} plugin + */ + constructor(leaf, plugin) { + super(leaf); + this.app = plugin.app; + this.plugin = plugin; + } + /** + * The unique view type. Must be implemented in subclasses. + * @returns {string} + */ + static get view_type() { + throw new Error("view_type must be implemented in subclass"); + } + /** + * The display text for this view. Must be implemented in subclasses. + * @returns {string} + */ + static get display_text() { + throw new Error("display_text must be implemented in subclass"); + } + /** + * The icon name for this view. + * @returns {string} + */ + static get icon_name() { + return "smart-connections"; + } + /** + * Whether the view should wait for the environment to be loaded before rendering. + * Override in subclasses that must stay visible during environment load. + * @returns {boolean} + */ + static get wait_for_env() { + return true; + } + /** + * Registers this ItemView subclass against a plugin instance and + * installs ergonomic accessors, an open helper, and an `${view_type}:open` listener. + * + * Usage from a plugin class: + * SubClass.register_item_view(this); + * + * This will: + * - call plugin.registerView(view_type, ...) + * - add a command "Open: view" + * - define a getter on plugin: plugin[method_name] -> the view instance + * - define a method on plugin: plugin["open_" + method_name]() -> opens the view + * - listen for env events named `${view_type}:open` and open the view when emitted + * + * @param {import('obsidian').Plugin} plugin + * @returns {{method_name:string, open_method_name:string, event_name:string}} + */ + static register_item_view(plugin) { + const View = ( + /** @type {typeof SmartItemView} */ + this + ); + if (plugin.app.viewRegistry?.viewByType?.[View.view_type]) { + plugin.app.viewRegistry.unregisterView(View.view_type); + console.warn(`View type "${View.view_type}" was already registered. Overwriting with new registration.`); + } + plugin.registerView(View.view_type, (leaf) => new View(leaf, plugin)); + plugin.addCommand({ + id: View.view_type, + name: "Open: " + View.display_text + " view", + callback: () => { + View.open(plugin.app.workspace); + } + }); + const method_name = View.view_type.replace(/^smart-/, "").replace(/-/g, "_"); + const open_method_name = "open_" + method_name; + if (!Object.getOwnPropertyDescriptor(plugin, method_name)) { + Object.defineProperty(plugin, method_name, { + configurable: true, + enumerable: false, + get: () => View.get_view(plugin.app.workspace) + }); + } + plugin[open_method_name] = (params = {}) => { + if (!plugin.app.viewRegistry?.viewByType?.[View.view_type]) { + console.warn(`View type "${View.view_type}" not registered when calling ${open_method_name}. Registering now. This should NOT happen frequently.`); + plugin.registerView(View.view_type, (leaf) => new View(leaf, plugin)); + } + View.open(plugin.app.workspace, params); + }; + const event_name = `${method_name}:open`; + const handler = (payload = {}) => { + const active = typeof payload?.active === "boolean" ? payload.active : true; + View.open(plugin.app.workspace, { ...payload, active }); + }; + const unsubscribe = plugin?.env?.events.on(event_name, handler); + if (typeof plugin.register === "function" && typeof unsubscribe === "function") { + plugin.register(() => unsubscribe()); + } + return { method_name, open_method_name, event_name }; + } + /** + * Retrieves the Leaf instance for this view type if it exists. + * @param {import("obsidian").Workspace} workspace + * @returns {import("obsidian").WorkspaceLeaf | undefined} + */ + static get_leaf(workspace) { + return workspace.getLeavesOfType(this.view_type)[0]; + } + /** + * Retrieves the view instance if it exists. + * @param {import("obsidian").Workspace} workspace + * @returns {SmartItemView | undefined} + */ + static get_view(workspace) { + const leaf = this.get_leaf(workspace); + return leaf ? leaf.view : void 0; + } + /** + * Opens the view. If `this.default_open_location` is "root", + * it opens (or reveals) in a root leaf; otherwise in a sidebar leaf. + * + * @param {import("obsidian").Workspace} workspace + * @param {boolean|object} [params={}] + */ + static open(workspace, params = {}) { + if (typeof params === "boolean") { + params = { active: params }; + } + const { + active = true, + state = null + } = params; + const existing_leaf = this.get_leaf(workspace); + const open_location = this.default_open_location; + let leaf; + if (open_location === "root") { + leaf = existing_leaf || workspace.getLeaf(false); + } else if (open_location === "left") { + leaf = existing_leaf || workspace.getLeftLeaf(false); + if (workspace.leftSplit?.collapsed) { + workspace.leftSplit.toggle(); + } + } else { + leaf = existing_leaf || workspace.getRightLeaf(false); + if (workspace.rightSplit?.collapsed) { + workspace.rightSplit.toggle(); + } + } + const view_state = { type: this.view_type, active }; + if (state && typeof state === "object") { + view_state.state = state; + } + Promise.resolve(leaf?.setViewState?.(view_state)).then(() => { + if (active) { + try { + workspace.revealLeaf?.(leaf); + } catch (error) { + console.warn(`Failed to reveal item view "${this.view_type}"`, error); + } + } + setTimeout(() => { + this.get_view(workspace)?.render_view(params); + }, 100); + }).catch((error) => { + console.error(`Failed to open item view "${this.view_type}"`, error); + }); + } + static is_open(workspace) { + return this.get_leaf(workspace)?.view instanceof this; + } + // instance + getViewType() { + return this.constructor.view_type; + } + getDisplayText() { + return this.constructor.display_text; + } + getIcon() { + return this.constructor.icon_name; + } + async onOpen() { + this.app.workspace.onLayoutReady(this.initialize.bind(this)); + } + async initialize() { + await this.render_mobile_status_bar(); + const should_wait_for_env = this.constructor.wait_for_env !== false; + const wait_for_states = ["loaded"]; + if (should_wait_for_env) { + await wait_for_env_to_load(this, { wait_for_states }); + } + this.container?.empty?.(); + this.register_plugin_events(); + this.app.workspace.registerHoverLinkSource(this.constructor.view_type, { display: this.getDisplayText(), defaultMod: true }); + this.render_view(); + } + async render_mobile_status_bar() { + if (!import_obsidian45.Platform.isMobile) return; + if (!this.env?.smart_view) return; + const status_bar_container = this.containerEl.querySelector(".status-bar-mobile") ?? this.containerEl.createDiv({ cls: "status-bar-mobile" }); + status_bar_container.empty?.(); + const status_bar_item = status_bar_container.createDiv({ cls: "status-bar-item" }); + try { + const status_bar = await render24.call(this.env.smart_view, this.env); + if (status_bar) status_bar_item.appendChild(status_bar); + } catch (error) { + console.error("Failed to render mobile Smart Env status bar", error); + } + } + register_plugin_events() { + } + render_view(params = {}) { + throw new Error("render_view must be implemented in subclass"); + } + get container() { + return this.containerEl.children[1]; + } + get env() { + return this.plugin.env; + } + async open_settings() { + await this.app.setting.open(); + await this.app.setting.openTabById(this.plugin.manifest.id); + } +}; + +// node_modules/obsidian-smart-env/src/views/env_status_view.js +var EnvStatusView = class extends SmartItemView { + static view_type = "smart-env-status-view"; + static display_text = "Smart Environment Status"; + static get icon_name() { + return "gauge"; + } + static get default_open_location() { + return import_obsidian46.Platform.isMobile ? "root" : "right"; + } + static get wait_for_env() { + return false; + } + async onOpen() { + this.titleEl?.setText?.("Smart Environment"); + await super.onOpen(); + } + register_plugin_events() { + if (this._env_status_view_cleanup_registered) return; + this._env_status_view_cleanup_registered = true; + this.register(() => { + this.stop_renderer_upgrade_polling(); + this._active_renderer_key = null; + this._last_render_params = null; + this._render_component_promise = null; + this._env_status_view_cleanup_registered = false; + }); + } + render_view(params = {}) { + this._last_render_params = params; + this.render_component_view(params); + } + async render_component_view(params = {}) { + const container = this.container; + if (!container) return; + if (this._render_component_promise) return this._render_component_promise; + this._render_component_promise = render_env_status(this, params).then(({ component_el, renderer_key }) => { + if (!component_el) return; + if (this.container !== container) return; + empty_element(container); + container.appendChild(component_el); + this._active_renderer_key = renderer_key; + if (renderer_key === "smart_components") { + this.stop_renderer_upgrade_polling(); + } else { + this.start_renderer_upgrade_polling(); + } + }).catch((error) => { + console.error("Failed to render env_status component", error); + }).finally(() => { + this._render_component_promise = null; + }); + return this._render_component_promise; + } + start_renderer_upgrade_polling() { + if (this._env_status_renderer_upgrade_interval) return; + this._env_status_renderer_upgrade_interval = setInterval(() => { + if (!can_render_via_smart_components(this.env)) return; + if (this._active_renderer_key === "smart_components") { + this.stop_renderer_upgrade_polling(); + return; + } + this.render_component_view(this._last_render_params || {}); + }, 500); + } + stop_renderer_upgrade_polling() { + if (!this._env_status_renderer_upgrade_interval) return; + clearInterval(this._env_status_renderer_upgrade_interval); + this._env_status_renderer_upgrade_interval = null; + } + async onClose() { + this.stop_renderer_upgrade_polling(); + } +}; +function can_render_via_smart_components(env) { + return Boolean(env?.config?.components?.env_status) && typeof env?.smart_components?.render_component === "function"; +} +async function render_env_status(view, params = {}) { + const component_params = { + ...params, + live_updates: true, + event: params.event, + event_key: params.event_key + }; + if (can_render_via_smart_components(view.env)) { + try { + const component_el = await view.env.smart_components.render_component("env_status", view.env, component_params); + return { + component_el, + renderer_key: "smart_components" + }; + } catch (error) { + console.error("Failed to render env_status via smart_components, falling back to direct component render", error); + } + } + return { + component_el: await render4.call(view.env.smart_view, view.env, component_params), + renderer_key: "direct" + }; +} +function empty_element(element) { + if (!element) return; + if (typeof element.empty === "function") { + element.empty(); + return; + } + element.replaceChildren?.(); +} + +// node_modules/obsidian-smart-env/src/views/smart_env_settings_tab.js +var import_obsidian49 = require("obsidian"); + +// node_modules/obsidian-smart-env/src/utils/render_pre_env_load.js +var import_obsidian47 = require("obsidian"); +function render_pre_env_load(scope) { + const container = scope.containerEl; + const env = scope.env; + if (env.state !== "loaded") { + if (env.state === "loading") { + container.createEl("p", { text: "Smart Environment is loading\u2026" }); + const status_btn = container.createEl("button", { text: "Show loading status" }); + status_btn.addEventListener("click", () => { + env.open_env_status_view(); + }); + } else { + container.createEl("p", { text: "Smart Environment not yet initialized." }); + const load_btn = container.createEl("button", { text: "Load Smart Environment" }); + load_btn.addEventListener("click", async () => { + load_btn.disabled = true; + load_btn.textContent = "Loading Smart Environment\u2026"; + if (import_obsidian47.Platform.isMobile && typeof env.start_mobile_env_load === "function") { + await env.start_mobile_env_load({ source: "settings_tab" }); + return; + } + await env.load(true); + }); + } + } +} + +// node_modules/obsidian-smart-env/src/utils/render_plugin_store_setting.js +var import_obsidian48 = require("obsidian"); +function render_plugin_store_setting(scope, container) { + if (!container) return null; + container.empty?.(); + const setting = new import_obsidian48.Setting(container).setName("Browse Smart Plugins").setDesc("Discover Core (free) and Pro Smart Plugins to supercharge your Obsidian AI experience."); + setting.addButton((btn) => { + btn.setButtonText("Browse Smart Plugins"); + btn.onClick(() => { + scope.env?.events?.emit?.("smart_plugins:browse", { + event_source: `${scope.id}-settings` + }); + }); + }); + return setting; +} + +// node_modules/obsidian-smart-env/src/views/settings.css +var settings_default = `/* 1) Host elements that should get a PRO badge */ +:is( + .pro-setting .setting-item-name +) { + position: relative; /* safe default, keeps ::after anchored */ +} + +/* 2) The PRO badge itself */ +:is( + .pro-setting .setting-item-name:not(:empty) +)::after { + content: "PRO"; + + /* layout */ + display: inline-flex; + align-items: center; + justify-content: center; + margin-left: 0.4em; + padding: 0.08em 0.55em; + border-radius: 999px; + white-space: nowrap; + vertical-align: middle; + + /* typography */ + font-size: 0.7em; + font-weight: 600; + letter-spacing: 0.14em; + text-transform: uppercase; + line-height: 1; + + /* color system: only Obsidian variables */ + background-color: var(--color-accent); + background-image: linear-gradient( + 135deg, + var(--color-accent), + var(--interactive-accent-hover) + ); + color: var(--text-on-accent, var(--background-primary)); + border: 1px solid var(--background-modifier-border); + + /* subtle separation & depth, theme-aware */ + box-shadow: + 0 0 0 1px var(--background-primary), + 0 1px 3px rgba(0, 0, 0, 0.35); + transform: translateY(-0.03em); +} + +/* 3) Interactive refinement: follow Obsidian's accent hover behavior */ +:is( + .pro-setting .setting-item-name +):hover::after { + background-color: var(--interactive-accent-hover); + filter: brightness(1.05); +} + +.smart-plugin-settings-header .actions-container { + display: flex; + flex-wrap: wrap; + gap: var(--pill-padding-y); +} + +.setting-component:has(.dropdown-no-options) { + display: none; +} + +/* wrap Obsidian native styles within smart plugin settings main class */ +.smart-plugin-settings-main, .smart-plugin-settings-env { + .setting-group { + margin-top: var(--size-4-6); + margin-bottom: var(--size-4-6); + } + /* polyfill */ + .setting-group .setting-items { + background-color: var(--setting-items-background, var(--background-primary-alt)); + padding: var(--setting-items-padding, var(--size-4-5)); + border-radius: var(--setting-items-radius, var(--radius-l)); + border: var(--setting-items-border-width, 0) solid var(--setting-items-border-color, var(--background-modifier-border)); + } +} + +/* show icon for all smart-* plugins except the main smart environment settings tab */ +.vertical-tab-header-group-items[data-section="community-plugins"] [data-setting-id^="smart-"]:not([data-setting-id^="smart-environment"]) .vertical-tab-nav-item-icon { + display: flex; +}`; + +// node_modules/obsidian-smart-env/src/views/smart_env_settings_tab.js +var SmartEnvSettingTab = class extends import_obsidian49.PluginSettingTab { + constructor(app2, plugin, icon = "smart-connections") { + super(app2, plugin, icon); + this.plugin = plugin; + this.header_container = null; + this.plugin_container = null; + this.global_settings_container = null; + this.plugin?.env?.create_env_getter?.(this); + this.plugin = plugin; + this.name = " Smart Environment"; + this.id = "smart-environment"; + if (!this.icon && icon) this.icon = icon; + this.smart_view.apply_style_sheet(settings_default); + } + get smart_view() { + return this.env?.smart_view; + } + display() { + this.render(); + } + async render_component(name, scope, params = {}) { + return await this.env.smart_components.render_component(name, scope, params); + } + async render() { + this.containerEl.empty(); + if (!this.env || ["init", "loading"].includes(this.env.state)) { + render_pre_env_load(this); + await this.env.constructor.wait_for({ loaded: true }); + } + this.containerEl.empty(); + if (Object.values(this.env.plugin_states).some((state) => state === "deferred")) { + this.containerEl.createDiv({ text: "Smart Plugins are waiting to be loaded. Restart Obsidian to finish loading the Smart Environment." }); + const button = this.containerEl.createEl("button", { text: "Restart Obsidian" }); + button.addEventListener("click", () => { + window.location.reload(); + }); + } + this.header_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-header" }); + this.plugin_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-main" }); + this.pro_plugins_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-pro-plugins" }); + this.header_container.createEl("p", { text: "Manage all global Smart Environment settings from one tab. These settings apply to all Smart Plugins." }); + const settings_smart_env = await this.render_component("settings_smart_env", this.env); + if (settings_smart_env) this.plugin_container.appendChild(settings_smart_env); + render_plugin_store_setting(this, this.pro_plugins_container); + } +}; + +// node_modules/obsidian-smart-env/package.json +var package_default2 = { + name: "obsidian-smart-env", + author: "Brian Joseph Petro (\u{1F334} Brian)", + license: "SEE LICENSE IN LICENSE", + version: "2.4.6", + type: "module", + description: "Implements Smart Environment best practices for Obsidian.", + main: "index.js", + repository: { + type: "git", + url: "brianpetro/jsbrains" + }, + bugs: { + url: "https://github.com/brianpetro/jsbrains/issues" + }, + scripts: { + build: "node build/build.js", + test: "npx ava --verbose" + }, + homepage: "https://jsbrains.org", + dependencies: { + obsidian: "^1.11.0", + "smart-blocks": "file:../jsbrains/smart-blocks", + "smart-collections": "file:../jsbrains/smart-collections", + "smart-completions": "file:../jsbrains/smart-completions", + "smart-contexts": "file:../jsbrains/smart-contexts", + "smart-embed-model": "file:../jsbrains/smart-embed-model", + "smart-entities": "file:../jsbrains/smart-entities", + "smart-environment": "file:../jsbrains/smart-environment", + "smart-file-system": "file:../jsbrains/smart-fs", + "smart-model": "file:../jsbrains/smart-model", + "smart-models": "file:../jsbrains/smart-models", + "smart-notices": "file:../jsbrains/smart-notices", + "smart-settings": "file:../jsbrains/smart-settings", + "smart-sources": "file:../jsbrains/smart-sources", + "smart-types": "file:../jsbrains/smart-types", + "smart-utils": "file:../jsbrains/smart-utils", + "smart-view": "file:../jsbrains/smart-view" + }, + devDependencies: { + "@xenova/transformers": "latest", + ava: "^6.3.0", + dotenv: "^17.2.3", + eslint: "^9.39.4", + "eslint-plugin-obsidianmd": "^0.3.0", + globals: "^17.6.0", + "js-tiktoken": "^1.0.19", + readline: "^1.3.0", + typescript: "^6.0.3", + "typescript-eslint": "^8.59.3" + }, + workspaces: [ + "../jsbrains/*" + ] +}; + +// node_modules/obsidian-smart-env/smart_env.js +var MIN_COMPATIBLE_SMART_ENV_VERSION = "2.4.0"; +var SmartEnv2 = class extends SmartEnv { + static version = package_default2.version; + constructor(opts = {}) { + super(opts); + this.plugin_states = {}; + } + /** + * Override to prevent loading outdated plugins + * --- + * This also stinks, replace in v3 (2026-03-31) + */ + get config() { + const signature = this.compute_collections_version_signature(); + if (this._config && signature === this._collections_version_signature) { + return this._config; + } + this._collections_version_signature = signature; + this._config = {}; + const sorted_configs = Object.entries(this.smart_env_configs).sort(([a_key], [b_key]) => { + if (!this.primary_main_key) return 0; + if (a_key === this.primary_main_key) return -1; + if (b_key === this.primary_main_key) return 1; + return 0; + }); + for (const [key, rec] of sorted_configs) { + if (!rec?.main) { + console.warn(`SmartEnv: '${key}' unloaded, skipping`); + delete this.smart_env_configs[key]; + continue; + } + if (!rec?.opts) { + console.warn(`SmartEnv: '${key}' opts missing, skipping`); + continue; + } + if (!is_supported_smart_env_version(rec.opts.version)) { + delete this.smart_env_configs[key]; + continue; + } + merge_env_config( + this._config, + deep_clone_config(normalize_opts(rec.opts)) + ); + } + return this._config; + } + /** + * Creates and initializes a SmartEnv instance tailored for Obsidian. + * @deprecated 2026-03-31 This code is so stinky I cringe. It will be replaced in next major version. Continue using but expect replacement. Do not depend on returned SmartEnv instance. + * @param {Object} plugin - The Obsidian plugin instance. + * @param {Object} [env_config] - Required environment configuration object. + * @returns {Promise} The initialized SmartEnv instance. + */ + static create(plugin, env_config) { + handle_outdated_plugins(); + if (!plugin) throw new Error("SmartEnv.create: 'plugin' parameter is required."); + if (!env_config) throw new Error("SmartEnv.create: 'env_config' parameter is required."); + env_config.version = this.version; + add_smart_chat_icon(); + add_smart_connections_icon(); + add_smart_lookup_icon(); + add_smart_icons(); + const opts = merge_env_config(env_config, default_config_default); + opts.env_path = ""; + const existing_env = this.global_env; + if (existing_env && (existing_env.state === "loaded" || is_global_env_locked2(this.global_ref))) { + handle_env_load_attempt_after_loaded(existing_env); + this.create_env_getter(plugin); + return existing_env; + } + if (existing_env && existing_env.state !== "init") { + const is_newer_env = compare_versions(this.version, existing_env.constructor?.version) > 0; + if (is_newer_env) { + console.warn( + "SmartEnv: Superseding environment before load lock because a newer SmartEnv version is available", + `${this.version} > ${existing_env.constructor?.version}` + ); + } + } + return super.create(plugin, opts).then((env) => { + clearTimeout(env.load_timeout); + env.load_timeout = null; + if (!env._startup_load_registration) { + env._startup_load_registration = true; + plugin.registerEvent(plugin.app.workspace.onLayoutReady(async () => { + if (env.state !== "init") { + console.warn(`Skipping SmartEnv (v${env.constructor.version}) load on layout ready; env.state: "${env.state}".`); + return; + } + const continue_load = await env.before_load(); + if (continue_load === false) { + console.warn("SmartEnv before_load returned false, skipping load."); + return; + } + await env.load(); + })); + } + return env; + }); + } + async before_load() { + this.run_migrations(); + this.register_notification_dispatchers(); + this.register_env_item_views(); + this.register_env_settings_tab(); + if (typeof this._onboarding_events_teardown !== "function") { + this._onboarding_events_teardown = register_first_of_event_notifications(this); + } + const protocol_handlers = this.plugin.app.workspace.protocolHandlers || this.plugin.app.workspace.protocolHandler?.handlers; + if (!protocol_handlers.has("smart-plugins/callback")) { + this.plugin.registerObsidianProtocolHandler("smart-plugins/callback", async (params) => { + await this.handle_smart_plugins_oauth_callback(params); + }); + } + if (import_obsidian50.Platform.isDesktop) this.register_status_bar(); + if (import_obsidian50.Platform.isMobile && this.state !== "loaded") { + const frag = this.smart_view.create_doc_fragment( + "

    Smart Environment loading deferred on mobile.

    " + ); + frag.querySelector("button").addEventListener("click", () => { + this.start_mobile_env_load({ source: "mobile_deferred_notice" }); + }); + new import_obsidian50.Notice(frag, 0); + return false; + } + return true; + } + async after_load() { + this.smart_sources?.register_source_watchers?.(this.smart_sources); + this.register_workspace_source_events(); + if (this._config.collections.smart_completions?.completion_adapters?.SmartCompletionVariableAdapter) { + register_completion_variable_adapter_replacements(this._config.collections.smart_completions.completion_adapters.SmartCompletionVariableAdapter); + } + this.register_configured_modals(); + this.refresh_status_bar(); + if (!this._registered_browse_smart_plugins_command) { + this._registered_browse_smart_plugins_command = this.plugin.manifest?.id + ":browse-smart-plugins"; + this.plugin.addCommand({ + id: "browse-smart-plugins", + name: "Browse Smart Plugins", + callback: () => { + this.events.emit("smart_plugins:browse", { + event_source: "command_palette" + }); + } + }); + } + if (!this._registered_env_status_view_command) { + this._registered_env_status_view_command = this.plugin.manifest?.id + ":env-status-view"; + this.plugin.addCommand({ + id: "env-status-view", + name: "Open Environment Status View", + callback: () => { + this.open_env_status_view(); + } + }); + } + this.mains.forEach((main_key) => { + const plugin_id = this.smart_env_configs[main_key]?.main?.manifest?.id || "unknown-plugin"; + this.plugin_states[plugin_id] = "loaded"; + }); + if (!this._registered_ctx_menu_actions) { + register_copy_menu_actions(this); + register_context_menu_actions(this); + this._registered_ctx_menu_actions = true; + } + } + handle_env_load_attempt_after_loaded() { + handle_env_load_attempt_after_loaded(this); + } + unload() { + console.warn("Unloading SmartEnv"); + if (typeof this._onboarding_events_teardown === "function") { + this._onboarding_events_teardown(); + this._onboarding_events_teardown = null; + } + this._workspace_source_events_registered = false; + return super.unload?.(); + } + /** + * Register event dispatchers used by native notice buttons and event-first flows. + * + * @returns {void} + */ + register_notification_dispatchers() { + if (this._notification_dispatchers_registered) return; + this._notification_dispatchers_registered = true; + this.events.on("milestones_modal:open", () => { + this.open_milestones_modal?.(); + }); + this.events.on("notifications_feed_modal:open", () => { + this.open_notifications_feed_modal?.(); + }); + this.events.on("smart_plugins:browse", () => { + this.browse_smart_plugins?.(); + }); + this.events.on("smart_env:load_mobile_requested", () => { + this.start_mobile_env_load({ source: "native_notice_button" }); + }); + } + /** + * Register environment-owned item views once per plugin. + * @returns {void} + */ + register_env_item_views() { + const plugin = this.main; + if (!plugin?.registerView) return; + if (!(this._registered_env_item_views instanceof Set)) { + this._registered_env_item_views = /* @__PURE__ */ new Set(); + } + const plugin_key = plugin.manifest?.id || "unknown-plugin"; + const view_classes = [EnvStatusView]; + view_classes.forEach((ViewClass) => { + const registration_key = `${plugin_key}:${ViewClass.view_type}`; + if (this._registered_env_item_views.has(registration_key)) return; + try { + ViewClass.register_item_view(plugin); + this._registered_env_item_views.add(registration_key); + } catch (error) { + console.error(`Failed to register item view "${ViewClass.view_type}"`, error); + } + }); + } + /** + * Open the mobile-friendly status view item view. + * + * @param {object} [params={}] + * @returns {void} + */ + open_env_status_view(params = {}) { + this.register_env_item_views(); + EnvStatusView.open(this.obsidian_app.workspace, params); + } + get env_status_view_command_id() { + const command_id = this._registered_env_item_views.find((id) => id.endsWith(EnvStatusView.view_type)); + console.log({ command_id }); + return command_id || this.plugin.manifest?.id + ":" + EnvStatusView.view_type; + } + /** + * Centralized mobile load flow used by native notices, settings tabs, and views. + * Opens the persistent progress surface first, then starts loading if needed. + * + * @param {object} [params={}] + * @param {boolean} [params.open_progress_view=true] + * @returns {Promise|SmartEnv} + */ + async start_mobile_env_load(params = {}) { + const { + open_progress_view = true + } = params; + if (open_progress_view) { + this.open_env_status_view({ active: true }); + } + if (this.state === "loaded") { + this.refresh_status_bar(); + return this; + } + if (this.state === "loading" && this._load_promise) { + return this._load_promise; + } + await this.load(); + return this; + } + /** + * Register every modal declared in config that exposes a static register_modal helper. + * @returns {void} + */ + register_configured_modals() { + const modal_entries = Object.entries(this._config?.modals || {}); + if (!this._registered_modal_keys) { + this._registered_modal_keys = /* @__PURE__ */ new Set(); + } + for (const [modal_key, modal_config] of modal_entries) { + const ModalClass = modal_config?.class; + if (typeof ModalClass?.register_modal !== "function") continue; + if (this._registered_modal_keys.has(modal_key)) continue; + try { + ModalClass.register_modal(this.main); + this._registered_modal_keys.add(modal_key); + } catch (error) { + console.error(`Failed to register modal "${modal_key}"`, error); + } + } + } + register_workspace_source_events() { + if (this._workspace_source_events_registered) return; + const plugin = this.main; + if (!plugin?.registerEvent) return; + this._workspace_source_events_registered = true; + plugin.registerEvent( + plugin.app.workspace.on("active-leaf-change", (leaf) => { + this.smart_sources?.debounce_re_import_queue?.(); + const current_path = leaf.view?.file?.path; + this.emit_source_opened(current_path, "active-leaf-change"); + }) + ); + plugin.registerEvent( + plugin.app.workspace.on("file-open", (file) => { + this.smart_sources?.debounce_re_import_queue?.(); + const current_path = file?.path; + this.emit_source_opened(current_path, "file-open"); + }) + ); + } + emit_source_opened(current_path, event_source = null) { + if (this._current_opened_source === current_path) return; + const current_source = this.smart_sources.get(current_path); + if (current_source) { + this._current_opened_source = current_path; + current_source.emit_event("sources:opened", { event_source }); + } + } + queue_source_re_import(source) { + this.smart_sources?.queue_source_re_import?.(source); + } + debounce_re_import_queue() { + this.smart_sources?.debounce_re_import_queue?.(); + } + async run_re_import() { + await this.smart_sources?.run_re_import?.(); + } + register_status_bar() { + const add_status_bar_item = this.main?.addStatusBarItem?.bind(this.main); + if (typeof add_status_bar_item !== "function") return; + const status_container = this.main?.app?.statusBar?.containerEl; + const existing_status_item = status_container?.querySelector?.(".smart-env-status-container")?.closest?.(".status-bar-item"); + if (existing_status_item && this.status_elm !== existing_status_item) { + existing_status_item.remove?.(); + } + if (!this.status_elm || !this.status_elm.isConnected) { + this.status_elm = add_status_bar_item(); + } + this.refresh_status_bar(); + } + refresh_status_bar() { + if (!this.status_elm) return; + render24.call(this.smart_view, this).then((container) => { + this.status_elm.empty?.(); + this.status_elm.appendChild(container); + }).catch((error) => { + console.error("Failed to render Smart Env status bar", error); + }); + } + register_menu_action(menu_key, fn) { + if (!this._registered_menu_actions) { + this._registered_menu_actions = {}; + } + if (!this._registered_menu_actions[menu_key]) { + this._registered_menu_actions[menu_key] = /* @__PURE__ */ new Set(); + } + if (this._registered_menu_actions[menu_key].has(fn)) return; + this._registered_menu_actions[menu_key].add(fn); + } + build_menu(menu_key, menu, scope) { + const registered_actions = this._registered_menu_actions?.[menu_key] ?? /* @__PURE__ */ new Set(); + registered_actions.forEach((menu_action) => { + menu_action(menu, scope); + }); + } + /** + * @deprecated 2026-03-17 remove by next major release (keeping for backward compatibility during migration period) + */ + get notices() { + if (!this._notices) { + this._notices = new SmartNotices(this, { + adapter: import_obsidian50.Notice + }); + } + return this._notices; + } + // Smart Plugins + /** + * Handles the OAuth callback from the Smart Plugins server. + * @param {Object} params - The URL parameters from the OAuth callback. + */ + async handle_smart_plugins_oauth_callback(params) { + const code = params.code; + if (!code) { + this.events.emit("smart_plugins_oauth_failed", { + level: "error", + message: "No OAuth code provided in URL. Login failed.", + event_source: "handle_smart_plugins_oauth_callback" + }); + return; + } + try { + await exchange_code_for_tokens(code, this.plugin); + this.events.emit("smart_plugins_oauth_completed"); + } catch (err) { + console.error("OAuth callback error", err); + this.events.emit("smart_plugins_oauth_failed", { + level: "error", + message: `OAuth callback error: ${err.message}`, + details: err.stack || "", + event_source: "handle_smart_plugins_oauth_callback" + }); + } + } + /** + * Serializes the environment and, when in a browser, triggers a download. + * @param {string} [filename='smart_env.json'] + * @returns {string} stringified JSON + */ + export_json(filename = "smart_env.json") { + const json = JSON.stringify(this.to_json(), null, 2); + if (typeof document !== "undefined") { + download_json(json, filename); + } + return json; + } + // WAIT FOR OBSIDIAN SYNC + async ready_to_load_collections() { + await new Promise((r) => setTimeout(r, 3e3)); + await this.wait_for_obsidian_sync(); + } + async wait_for_obsidian_sync() { + while (this.obsidian_is_syncing) { + console.log("Smart Connections: Waiting for Obsidian Sync to finish"); + await new Promise((r) => setTimeout(r, 1e3)); + if (!this.plugin) throw new Error("Plugin disabled while waiting for obsidian sync, reload required."); + } + } + get obsidian_is_syncing() { + const obsidian_sync_instance = this.plugin?.app?.internalPlugins?.plugins?.sync?.instance; + if (!obsidian_sync_instance) return false; + if (obsidian_sync_instance?.syncStatus.startsWith("Uploading")) return false; + if (obsidian_sync_instance?.syncStatus.startsWith("Fully synced")) return false; + return obsidian_sync_instance?.syncing; + } + get obsidian_app() { + return this.plugin?.app ?? window.app; + } + open_notifications_feed_modal(params = {}) { + const NotificationsModalClass = this.config.modals.notifications_feed_modal.class; + const modal = new NotificationsModalClass(this.obsidian_app, this, params); + modal.open(); + } + open_milestones_modal() { + const MilestonesModalClass = this.config.modals.milestones_modal.class; + const modal = new MilestonesModalClass(this.obsidian_app, this); + modal.open(); + } + browse_smart_plugins() { + const BrowseSmartPluginsClass = this.config?.modals?.browse_plugins_modal?.class; + if (typeof BrowseSmartPluginsClass !== "function") return; + const modal = new BrowseSmartPluginsClass(this.obsidian_app, this); + modal.open(); + } + run_migrations() { + remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-plugins"] }); + remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-editor"] }); + remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-sources"] }); + remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-claude"] }); + remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-gemini"] }); + remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-deepseek"] }); + remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-perplexity"] }); + remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-grok"] }); + remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-aistudio"] }); + } + // Detect Existing Smart Environment Settings Tab + get env_settings_tab() { + const app2 = this.plugin.app || window.app; + return app2.setting.pluginTabs.find((t) => t.id === "smart-environment"); + } + register_env_settings_tab() { + if (this.env_settings_tab) return; + this.plugin.addSettingTab(new SmartEnvSettingTab(this.plugin.app, this.plugin)); + } +}; +function download_json(json, filename) { + const blob = new Blob([json], { type: "application/json" }); + const url = URL.createObjectURL(blob); + const anchor = document.createElement("a"); + anchor.href = url; + anchor.download = filename; + document.body.appendChild(anchor); + anchor.click(); + document.body.removeChild(anchor); + URL.revokeObjectURL(url); +} +function handle_env_load_attempt_after_loaded(env) { + console.warn("Received attempt to load another SmartEnv after one has already loaded"); + const deferred_smart_plugins = Object.keys(env.plugin.app.plugins.plugins || {}).reduce((acc, plugin_id) => { + if (plugin_id.startsWith("smart-") && env.plugin_states?.[plugin_id] !== "loaded" && env.main?.manifest?.id !== plugin_id) { + acc[plugin_id] = "deferred"; + } + return acc; + }, {}); + env.plugin_states = { + ...env.plugin_states, + ...deferred_smart_plugins + }; + const notice_message = `Smart Plugins waiting to load. Restart Obsidian to load ${Object.keys(deferred_smart_plugins).join(", ")} into the Smart Environment.`; + if (is_supported_smart_env_version(env.constructor.version)) { + env.events.emit("smart_env:restart_required", { + level: "attention", + message: notice_message, + event_source: "SmartEnv.create", + btn_text: "Restart Obsidian", + btn_callback: "app:reload", + timeout: 1e4 + // 10 seconds + }); + } else { + const notice_frag = document.createDocumentFragment(); + notice_frag.createEl("p", { text: notice_message }); + notice_frag.createEl("button", { text: "Restart Obsidian" }).addEventListener("click", (e) => { + app.commands.executeCommandById("app:reload"); + e.target.textContent = "Reloading..."; + }); + new import_obsidian50.Notice(notice_frag, 0); + } + console.log("Unloading deferred Smart Plugins:", Object.keys(deferred_smart_plugins)); + Object.keys(deferred_smart_plugins).forEach((plugin_id) => { + const plugin_instance = env.plugin.app.plugins.plugins[plugin_id]; + if (plugin_instance) { + setTimeout(() => { + try { + console.log(`Unloading deferred plugin "${plugin_id}"`, plugin_instance); + plugin_instance._loaded = true; + plugin_instance.unload(); + } catch (error) { + console.warn(`Error unloading deferred plugin "${plugin_id}" during load attempt of new SmartEnv. This should resolve itself after restart.`, error); + } + }, 3e3); + } else { + console.warn(`Plugin "${plugin_id}" not found during unload attempt of deferred plugins. This should resolve itself after restart.`); + } + }); +} +function handle_outdated_plugins() { + const smart_plugin_ids = Array.from(app.plugins.enabledPlugins).filter((id) => id.startsWith("smart-")); + const all_enabled_initialized = smart_plugin_ids.every((plugin_id) => { + return app.plugins.plugins[plugin_id]; + }); + if (!all_enabled_initialized) { + setTimeout(() => { + handle_outdated_plugins(); + }, 1); + return; + } + const outdated_smart_plugins = smart_plugin_ids.reduce((acc, plugin_id) => { + const plugin_instance = app.plugins.plugins[plugin_id]; + if (plugin_instance?.SmartEnv?.version && !is_supported_smart_env_version(plugin_instance.SmartEnv.version)) { + plugin_instance.onload = () => { + console.warn("prevented onload of outdated plugin", plugin_id); + }; + acc[plugin_id] = "outdated"; + } + return acc; + }, {}); + if (Object.keys(outdated_smart_plugins).length === 0) return; + console.warn("Outdated Smart Plugins detected:", Object.keys(outdated_smart_plugins)); + const notice_frag = document.createDocumentFragment(); + notice_frag.createEl("p", { text: `Detected outdated plugins: ${Object.keys(outdated_smart_plugins).join(", ")} in Smart Environment.` }); + notice_frag.createEl("p", { text: `Please update ${Object.keys(outdated_smart_plugins).join(", ")} then restart Obsidian to load all Smart Plugins.` }); + notice_frag.createEl("button", { text: "Check for Updates" }).addEventListener("click", () => { + window.smart_env?.events.emit("smart_plugins:browse", { + event_source: "outdated_env_notice_button" + }); + }); + new import_obsidian50.Notice(notice_frag, 0); + Object.keys(outdated_smart_plugins).forEach((plugin_id) => { + const plugin_instance = app.plugins.plugins[plugin_id]; + if (plugin_instance) { + setTimeout(() => { + try { + console.log(`Unloading outdated plugin "${plugin_id}"`, plugin_instance); + plugin_instance._loaded = true; + plugin_instance.unload(); + } catch (error) { + console.warn(`Error unloading outdated plugin "${plugin_id}" during load attempt of new SmartEnv. This should resolve itself after restart.`, error); + } + }, 3e3); + } else { + console.warn(`Plugin "${plugin_id}" not found during unload attempt of outdated plugins. This should resolve itself after restart.`); + } + }); +} +function is_global_env_locked2(global_ref) { + return Object.getOwnPropertyDescriptor(global_ref, "smart_env")?.configurable === false; +} +function is_supported_smart_env_version(version) { + return compare_versions(version, MIN_COMPATIBLE_SMART_ENV_VERSION) >= 0; +} + +// node_modules/obsidian-smart-env/src/views/smart_plugin_settings_tab.js +var import_obsidian51 = require("obsidian"); +var SmartPluginSettingsTab = class extends import_obsidian51.PluginSettingTab { + constructor(app2, plugin, icon = "smart-connections") { + super(app2, plugin, icon); + this.plugin = plugin; + this.header_container = null; + this.plugin_container = null; + this.global_settings_container = null; + this.plugin?.env?.create_env_getter?.(this); + if (!this.icon && icon) this.icon = icon; + this.name = this.name.replace("Smart ", " "); + } + get smart_view() { + return this.env?.smart_view; + } + async display() { + await this.render(); + } + async render() { + this.containerEl.empty(); + if (!this.env || ["init", "loading"].includes(this.env.state)) { + render_pre_env_load(this); + await this.env.constructor.wait_for({ loaded: true }); + } + if (this.env.plugin_states?.[this.plugin?.manifest?.id] === "deferred") { + this.containerEl.createDiv({ text: "Restart Obsidian to load this plugin into the Smart Environment." }); + const button = this.containerEl.createEl("button", { text: "Restart Obsidian" }); + button.addEventListener("click", () => { + window.location.reload(); + }); + return; + } + this.prepare_layout(); + await this.render_header(this.header_container); + await this.render_plugin_settings(this.plugin_container); + await this.render_global_settings(this.global_settings_container); + } + prepare_layout() { + this.smart_view.apply_style_sheet(settings_default); + this.containerEl.empty(); + this.header_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-header" }); + this.plugin_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-main" }); + this.global_settings_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-env" }); + this.pro_plugins_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-pro-plugins" }); + } + /** + * @abstract + */ + async render_header(container) { + } + /** + * @abstract + */ + async render_plugin_settings(container) { + } + async render_global_settings(container) { + if (!container) return; + container.empty?.(); + if (!this.env) return; + const settings_item_div = container.createDiv({ cls: "setting-item" }); + const info_div = settings_item_div.createDiv({ cls: "setting-item-info" }); + info_div.createDiv({ cls: "setting-item-name", text: "Smart Environment" }); + info_div.createDiv({ + cls: "setting-item-description", + text: "Manage global settings in the dedicated Smart Environment settings tab." + }); + const control_div = settings_item_div.createDiv({ cls: "setting-item-control" }); + const button = control_div.createEl("button", { text: "Open settings" }); + button.addEventListener("click", () => { + this.app.setting.openTabById("smart-environment"); + }); + render_plugin_store_setting(this, this.pro_plugins_container); + } + async render_component(name, scope, params = {}) { + return await this.env.smart_components.render_component(name, scope, params); + } +}; + +// node_modules/obsidian-smart-env/smart_plugin.js +var import_obsidian52 = require("obsidian"); +var SmartPlugin = class extends import_obsidian52.Plugin { + SmartEnv = SmartEnv2; + /** + * override in subclass to provide commands. + * use property key to override commands in further subclasses. + */ + get commands() { + return {}; + } + register_commands() { + Object.values(this.commands).forEach((cmd) => { + this.addCommand(cmd); + }); + } + /** + * override in subclass to provide ribbon icons. + * use property key to override ribbon icons in further subclasses. + */ + get ribbon_icons() { + return {}; + } + register_ribbon_icons() { + const icons = Object.values(this.ribbon_icons); + for (let i = 0; i < icons.length; i++) { + const ri = icons[i]; + this.addRibbonIcon(ri.icon_name, ri.description, ri.callback); + } + } + get item_views() { + return {}; + } + register_item_views() { + const views = Object.values(this.item_views); + for (let i = 0; i < views.length; i++) { + const ViewClass = views[i]; + if (typeof ViewClass.register_item_view === "function") { + ViewClass.register_item_view(this); + } + } + } + /** + * user version and first seen handling + */ + async is_new_user() { + const data = await this.loadData() || {}; + if (!data.installed_at) { + data.installed_at = Date.now(); + await this.saveData(data); + return true; + } + return false; + } + /** + * Returns the last saved plugin version or an empty string. + * @returns {Promise} + */ + async get_last_known_version() { + const data = await this.loadData() || {}; + return data.last_version || ""; + } + /** + * Persists the provided plugin version as last shown. + * @param {string} version + * @returns {Promise} + */ + async set_last_known_version(version) { + const data = await this.loadData() || {}; + data.last_version = version; + await this.saveData(data); + } + /** + * Determines if release notes should be shown for `current_version`. + * @param {string} current_version + * @returns {Promise} + */ + async is_new_plugin_version(current_version) { + return await this.get_last_known_version() !== current_version; + } + async check_for_updates() { + if (this.ReleaseNotesView && await this.is_new_plugin_version(this.manifest.version)) { + console.log("opening release notes modal"); + try { + this.ReleaseNotesView.open(this.app.workspace, this.manifest.version); + } catch (e) { + console.error("Failed to open ReleaseNotesView", e); + } + await this.set_last_known_version(this.manifest.version); + } + } + /** + * @deprecated use SmartEnv.notices instead + */ + get notices() { + if (this.env?.notices) return this.env.notices; + if (!this._notices) this._notices = new SmartNotices(this.env, import_obsidian52.Notice); + return this._notices; + } +}; + +// node_modules/smart-collections/utils/collection_instance_name_from.js +function collection_instance_name_from2(class_name) { + if (class_name.endsWith("Item")) { + return class_name.replace(/Item$/, "").replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); + } + return class_name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/y$/, "ie") + "s"; +} + +// node_modules/smart-utils/deep_merge.js +function deep_merge2(target = {}, source = {}) { + for (const key in source) { + if (!Object.prototype.hasOwnProperty.call(source, key)) continue; + if (is_plain_object5(source[key]) && is_plain_object5(target[key])) { + deep_merge2(target[key], source[key]); + } else { + target[key] = source[key]; + } + } + return target; +} +function is_plain_object5(o) { + return o && typeof o === "object" && !Array.isArray(o); +} + +// node_modules/smart-collections/utils/helpers.js +function create_uid2(data) { + const str = JSON.stringify(data); + let hash = 0; + if (str.length === 0) return hash; + for (let i = 0; i < str.length; i++) { + const char = str.charCodeAt(i); + hash = (hash << 5) - hash + char; + hash = hash & hash; + if (hash < 0) hash = hash * -1; + } + return hash.toString() + str.length; +} + +// node_modules/smart-utils/camel_case_to_snake_case.js +function camel_case_to_snake_case2(str = "") { + return str.replace(/([A-Z])/g, (m) => `_${m.toLowerCase()}`).replace(/^_/, "").replace(/2$/, ""); +} + +// node_modules/smart-collections/utils/deep_equal.js +function deep_equal2(obj1, obj2, visited = /* @__PURE__ */ new WeakMap()) { + if (obj1 === obj2) return true; + if (obj1 === null || obj2 === null || obj1 === void 0 || obj2 === void 0) return false; + if (typeof obj1 !== typeof obj2 || Array.isArray(obj1) !== Array.isArray(obj2)) return false; + if (Array.isArray(obj1)) { + if (obj1.length !== obj2.length) return false; + return obj1.every((item, index) => deep_equal2(item, obj2[index], visited)); + } + if (typeof obj1 === "object") { + if (visited.has(obj1)) return visited.get(obj1) === obj2; + visited.set(obj1, obj2); + const keys1 = Object.keys(obj1); + const keys2 = Object.keys(obj2); + if (keys1.length !== keys2.length) return false; + return keys1.every((key) => deep_equal2(obj1[key], obj2[key], visited)); + } + return obj1 === obj2; +} + +// node_modules/smart-collections/utils/get_item_display_name.js +function get_item_display_name2(key, show_full_path) { + if (show_full_path) { + return key.split("/").join(" > ").replace(".md", ""); + } + return key.split("/").pop().replace(".md", ""); +} + +// node_modules/smart-collections/utils/create_actions_proxy.js +function create_actions_proxy2(ctx, actions_source) { + const input = actions_source || {}; + const is_plain_object7 = (val) => typeof val === "object" && val !== null && !Array.isArray(val); + const is_function = (val) => typeof val === "function"; + const is_class_export = (val) => is_function(val) && /^class\s/.test(Function.prototype.toString.call(val)); + const is_action_object = (val) => is_plain_object7(val) && is_function(val.action); + const is_action_candidate = (val) => is_function(val) || is_action_object(val) || is_class_export(val); + const ignored_meta_keys = /* @__PURE__ */ new Set(["length", "name", "prototype"]); + const clone_with_descriptors = (obj) => { + if (!is_plain_object7(obj)) return obj; + const out = Object.create(Object.getPrototypeOf(obj) || null); + for (const key of Reflect.ownKeys(obj)) { + const descriptor = Object.getOwnPropertyDescriptor(obj, key); + if (!descriptor) continue; + const next = { ...descriptor }; + if ("value" in next && is_plain_object7(next.value)) { + next.value = clone_with_descriptors(next.value); + } + try { + Object.defineProperty(out, key, next); + } catch { + out[key] = next.value; + } + } + return out; + }; + const should_bucket_actions = (val) => { + if (!is_plain_object7(val)) return false; + if (is_action_object(val)) return false; + const keys = Reflect.ownKeys(val); + if (keys.length === 0) return false; + let found_candidate = false; + for (const key of keys) { + const descriptor = Object.getOwnPropertyDescriptor(val, key); + if (!descriptor) continue; + if ("value" in descriptor) { + const entry = descriptor.value; + if (is_action_candidate(entry)) { + found_candidate = true; + continue; + } + if (is_plain_object7(entry)) { + if (should_bucket_actions(entry)) { + found_candidate = true; + continue; + } + return false; + } + if (typeof entry === "undefined") continue; + return false; + } + return false; + } + return found_candidate; + }; + const clone_descriptor = (descriptor) => { + if (!descriptor) return descriptor; + if (!("value" in descriptor)) return { ...descriptor }; + const cloned = is_plain_object7(descriptor.value) ? clone_with_descriptors(descriptor.value) : descriptor.value; + return { ...descriptor, value: cloned }; + }; + const build_sources = (src) => { + const global_source2 = /* @__PURE__ */ Object.create(null); + const scoped_sources2 = /* @__PURE__ */ new Map(); + for (const key of Reflect.ownKeys(src)) { + const descriptor = Object.getOwnPropertyDescriptor(src, key); + if (!descriptor) continue; + if ("value" in descriptor && should_bucket_actions(descriptor.value)) { + scoped_sources2.set(key, clone_with_descriptors(descriptor.value)); + continue; + } + try { + Object.defineProperty(global_source2, key, clone_descriptor(descriptor)); + } catch { + global_source2[key] = descriptor.value; + } + } + return { global_source: global_source2, scoped_sources: scoped_sources2 }; + }; + const { global_source, scoped_sources } = build_sources(input); + const has_own = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); + const cache = /* @__PURE__ */ Object.create(null); + const copy_metadata = (source, target, omit = []) => { + if (!source || !target) return; + const skips = /* @__PURE__ */ new Set([...ignored_meta_keys, ...omit]); + for (const key of Reflect.ownKeys(source)) { + if (skips.has(key)) continue; + const descriptor = Object.getOwnPropertyDescriptor(source, key); + if (!descriptor) continue; + try { + Object.defineProperty(target, key, descriptor); + } catch { + target[key] = descriptor.value; + } + } + }; + const instantiate_class = (Ctor) => { + const instance = new Ctor(ctx); + const candidate = instance.action || instance.run || instance.execute || instance.call; + if (is_function(candidate)) { + const bound = candidate.bind(instance); + copy_metadata(Ctor, bound); + copy_metadata(instance, bound); + bound.instance = instance; + return bound; + } + copy_metadata(Ctor, instance); + return instance; + }; + const bind_or_clone = (val) => { + if (is_class_export(val)) { + return instantiate_class(val); + } + if (is_action_object(val)) { + const bound = val.action.bind(ctx); + copy_metadata(val, bound, ["action"]); + return bound; + } + if (is_function(val)) { + const bound = val.bind(ctx); + copy_metadata(val, bound); + return bound; + } + if (is_plain_object7(val)) { + return clone_with_descriptors(val); + } + return val; + }; + const scope_actions_for = () => { + const scope_key = ctx?.constructor?.key; + if (typeof scope_key === "undefined" || scope_key === null) return null; + const bucket = scoped_sources.get(scope_key); + return bucket && is_plain_object7(bucket) ? bucket : null; + }; + const cache_result = (target, prop, value) => { + target[prop] = value; + return value; + }; + const compute_and_cache = (target, prop) => { + const scoped = scope_actions_for(); + if (scoped && has_own(scoped, prop)) { + return cache_result(target, prop, bind_or_clone(scoped[prop])); + } + if (has_own(global_source, prop)) { + return cache_result(target, prop, bind_or_clone(global_source[prop])); + } + return cache_result(target, prop, void 0); + }; + const union_keys = () => { + const scoped = scope_actions_for(); + const keys = new Set(Reflect.ownKeys(cache)); + for (const key of Reflect.ownKeys(global_source)) { + keys.add(key); + } + if (scoped) { + for (const key of Reflect.ownKeys(scoped)) { + keys.add(key); + } + } + return Array.from(keys); + }; + const descriptor_for = (target, prop) => ({ + configurable: true, + enumerable: true, + value: target[prop] + }); + return new Proxy(cache, { + get: (target, prop) => { + if (prop === Symbol.toStringTag) return "ActionsProxy"; + if (prop in target) return target[prop]; + return compute_and_cache(target, prop); + }, + has: (target, prop) => { + if (prop in target) return true; + const scoped = scope_actions_for(); + if (scoped && has_own(scoped, prop)) return true; + return has_own(global_source, prop); + }, + ownKeys: () => union_keys(), + getOwnPropertyDescriptor: (target, prop) => { + if (has_own(target, prop)) { + return descriptor_for(target, prop); + } + const scoped = scope_actions_for(); + if (scoped && has_own(scoped, prop)) { + if (!has_own(target, prop)) { + compute_and_cache(target, prop); + } + return descriptor_for(target, prop); + } + if (has_own(global_source, prop)) { + if (!has_own(target, prop)) { + compute_and_cache(target, prop); + } + return descriptor_for(target, prop); + } + return void 0; + }, + defineProperty: (target, prop, descriptor) => { + if ("value" in descriptor) { + target[prop] = descriptor.value; + return true; + } + return false; + }, + set: (target, prop, value) => { + target[prop] = value; + return true; + }, + deleteProperty: (target, prop) => { + if (has_own(target, prop)) { + delete target[prop]; + } + return true; + } + }); +} + +// node_modules/smart-collections/item.js +var CollectionItem2 = class _CollectionItem { + static version = "0.1.0"; + /** + * Default properties for an instance of CollectionItem. + * Override in subclasses to define different defaults. + * @returns {Object.} + */ + static get defaults() { + return { + data: {} + }; + } + /** + * @this {*} + * @param {CollectionEnv} env - The environment/context. + * @param {Partial|null} [data=null] - Initial data for the item. + */ + constructor(env, data = null) { + env.create_env_getter(this); + this.config = this.env?.config; + this.merge_defaults(); + if (data) deep_merge2(this.data, data); + if (!this.data.class_name) this.data.class_name = this.collection.item_class_name; + } + /** + * Loads an item from data and initializes it. + * @param {CollectionEnv} env + * @param {Partial} data + * @returns {CollectionItem} + */ + static load(env, data) { + const item = new this(env, data); + item.init(); + return item; + } + /** + * Merge default properties from the entire inheritance chain. + * @private + * @this {CollectionItemThis} + */ + merge_defaults() { + let current_class = this.constructor; + while (current_class) { + for (let key in current_class.defaults) { + const default_val = current_class.defaults[key]; + if (typeof default_val === "object") { + this[key] = { ...default_val, ...this[key] }; + } else { + this[key] = this[key] === void 0 ? default_val : this[key]; + } + } + current_class = Object.getPrototypeOf(current_class); + } + } + /** + * Generates or retrieves a unique key for the item. + * Key syntax supports: + * - `[i]` for sequences + * - `/` for super-sources (groups, directories, clusters) + * - `#` for sub-sources (blocks) + * @this {CollectionItemThis} + * @returns {string} The unique key + */ + get_key() { + return create_uid2(this.data); + } + /** + * Updates the item data and returns true if changed. + * @this {CollectionItemThis} + * @param {Partial} data + * @returns {boolean} True if data changed. + */ + update_data(data) { + const sanitized_data = this.sanitize_data(data); + const current_data = { ...this.data }; + deep_merge2(current_data, sanitized_data); + const changed = !deep_equal2(this.data, current_data); + if (!changed) return false; + this.data = current_data; + return true; + } + /** + * Sanitizes data for saving. Ensures no circular references. + * @this {CollectionItemThis} + * @param {*} data + * @returns {*} Sanitized data. + */ + sanitize_data(data) { + if (data instanceof _CollectionItem) return data.ref; + if (Array.isArray(data)) return data.map((val) => this.sanitize_data(val)); + if (typeof data === "object" && data !== null) { + return Object.keys(data).reduce((acc, key) => { + acc[key] = this.sanitize_data(data[key]); + return acc; + }, {}); + } + return data; + } + /** + * Initializes the item. Override as needed. + * @param {Partial} [input_data] - Additional data that might be provided on creation. + * @returns {*} + */ + init(input_data) { + } + /** + * Queues this item for saving. + * @this {CollectionItemThis} + */ + queue_save() { + this._queue_save = true; + } + /** + * Saves this item using its data adapter. + * @this {CollectionItemThis} + * @returns {Promise} + */ + async save() { + try { + await this.data_adapter.save_item(this); + this.init(); + } catch (err) { + this._queue_save = true; + console.error(err, err.stack); + } + } + /** + * Queues this item for loading. + * @this {CollectionItemThis} + */ + queue_load() { + this._queue_load = true; + } + /** + * Loads this item using its data adapter. + * @this {CollectionItemThis} + * @returns {Promise} + */ + async load() { + try { + await this.data_adapter.load_item(this); + this.init(); + } catch (err) { + this._load_error = err; + this.on_load_error(err); + } + } + /** + * Handles load errors by re-queuing for load. + * Override if needed. + * @this {CollectionItemThis} + * @param {Error} err + */ + on_load_error(err) { + this.queue_load(); + } + /** + * Validates the item before saving. Checks for presence and validity of key. + * @deprecated should be better handled 2025-12-17 (wrong scope?) + * @this {CollectionItemThis} + * @returns {boolean} + */ + validate_save() { + if (!this.key) return false; + if (this.key.trim() === "") return false; + if (this.key === "undefined") return false; + return true; + } + /** + * Marks this item as deleted. This does not immediately remove it from memory, + * but queues a save that will result in the item being removed from persistent storage. + * @this {CollectionItemThis} + */ + delete() { + this.deleted = true; + this.queue_save(); + } + /** + * Filters items in the collection based on provided options. + * functional filter (returns true or false) for filtering items in collection; called by collection class + * @this {CollectionItemThis} + * @param {CollectionFilterOptions} filter_opts - Filtering options. + * @returns {boolean} True if the item passes the filter, false otherwise. + */ + filter(filter_opts = {}) { + const { + exclude_key, + exclude_keys = exclude_key ? [exclude_key] : [], + exclude_key_starts_with, + exclude_key_starts_with_any, + exclude_key_includes, + exclude_key_includes_any, + exclude_key_ends_with, + exclude_key_ends_with_any, + key_ends_with, + key_starts_with, + key_starts_with_any, + key_includes, + key_includes_any + } = filter_opts; + if (exclude_keys?.includes(this.key)) return false; + if (exclude_key_starts_with && this.key.startsWith(exclude_key_starts_with)) return false; + if (exclude_key_starts_with_any && exclude_key_starts_with_any.some((prefix) => this.key.startsWith(prefix))) return false; + if (exclude_key_includes && this.key.includes(exclude_key_includes)) return false; + if (exclude_key_includes_any && exclude_key_includes_any.some((include) => this.key.includes(include))) return false; + if (exclude_key_ends_with && this.key.endsWith(exclude_key_ends_with)) return false; + if (exclude_key_ends_with_any && exclude_key_ends_with_any.some((suffix) => this.key.endsWith(suffix))) return false; + if (key_ends_with && !this.key.endsWith(key_ends_with)) return false; + if (key_starts_with && !this.key.startsWith(key_starts_with)) return false; + if (key_starts_with_any && !key_starts_with_any.some((prefix) => this.key.startsWith(prefix))) return false; + if (key_includes && !this.key.includes(key_includes)) return false; + if (key_includes_any && !key_includes_any.some((include) => this.key.includes(include))) return false; + return true; + } + /** + * @this {CollectionItemThis} + * @param {Object.} [params={}] + * @returns {CollectionScoreResult|null} + */ + filter_and_score(params = {}) { + if (this.filter(params.filter) === false) return null; + return this.score(params); + } + /** + * @this {CollectionItemThis} + * @param {Object.} [params={}] + * @returns {CollectionScoreResult} + */ + score(params = {}) { + const score_action = this.actions[params.score_algo_key]; + if (typeof score_action !== "function") throw new Error(`Missing score action: ${params.score_algo_key}`); + return { + ...score_action(params) || {}, + item: this + }; + } + /** + * @this {CollectionItemThis} + * @returns {Object} + */ + get actions() { + if (!this._actions) { + this._actions = create_actions_proxy2(this, { + ...this.env.config.actions || {}, + // main actions scope for actions/ exports + ...this.env.opts.items?.[this.item_type_key]?.actions || {} + // DEPRECATED OR KEEP? + }); + } + return this._actions; + } + /** + * Derives the collection key from the class name. + * @returns {string} + */ + static get collection_key() { + let name = this.name; + if (name.match(/\d$/)) name = name.slice(0, -1); + return collection_instance_name_from2(name); + } + /** + * @returns {string} The collection key for this item. + */ + get collection_key() { + let name = this.constructor.name; + if (name.match(/\d$/)) name = name.slice(0, -1); + return collection_instance_name_from2(name); + } + /** + * Retrieves the parent collection from the environment. + * @this {CollectionItemThis} + * @returns {Collection} + */ + get collection() { + return this.env[this.collection_key]; + } + /** + * @this {CollectionItemThis} + * @returns {string} The item's key. + */ + get key() { + return this.data?.key || this.get_key(); + } + /** + * @returns {string} + */ + get item_type_key() { + let name = this.constructor.name; + if (name.match(/\d$/)) name = name.slice(0, -1); + return camel_case_to_snake_case2(name); + } + /** + * Emits an event with item metadata. + * + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_event(event_key, payload = {}) { + this.env.events?.emit(event_key, { collection_key: this.collection_key, item_key: this.key, ...payload }); + } + /** + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_info_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "info", ...payload }); + } + /** + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_warning_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "warning", ...payload }); + } + /** + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_error_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "error", ...payload }); + } + /** + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventCallback} callback + * @returns {*} + */ + on_event(event_key, callback) { + return this.env.events?.on(event_key, (payload) => { + if (payload?.item_key && payload.item_key !== this.key) return; + callback(payload); + }); + } + /** + * @this {CollectionItemThis} + * @param {string} event_key + * @param {CollectionEventCallback} callback + * @returns {*} + */ + once_event(event_key, callback) { + return this.env.events?.once(event_key, (payload) => { + if (payload?.item_key && payload.item_key !== this.key) return; + callback(payload); + }); + } + /** + * @this {CollectionItemThis} + * @returns {*} The data adapter for this item's collection. + */ + get data_adapter() { + return this.collection.data_adapter; + } + /** + * @this {CollectionItemThis} + * @returns {Object} The filesystem adapter. + */ + get data_fs() { + return this.collection.data_fs; + } + /** + * Access to collection-level settings. + * @this {CollectionItemThis} + * @returns {Object} + */ + get settings() { + if (!this.env.settings[this.collection_key]) this.env.settings[this.collection_key] = {}; + return this.env.settings[this.collection_key]; + } + /** + * @this {CollectionItemThis} + * @param {Object.} settings + */ + set settings(settings) { + this.env.settings[this.collection_key] = settings; + this.env.smart_settings.save(); + } + /** + * A simple reference object for this item. + * @deprecated 2025-11-11 lacks adoption + * @this {CollectionItemThis} + * @returns {CollectionItemRef} + */ + get ref() { + return { collection_key: this.collection_key, key: this.key }; + } + /** + * @deprecated use env.smart_components~~env.smart_view~~ instead + * @this {CollectionItemThis} + */ + get smart_view() { + if (!this._smart_view) this._smart_view = this.env.init_module("smart_view"); + return this._smart_view; + } + /** + * Retrieves the display name of the collection item. + * @readonly + * @deprecated Use `get_item_display_name(key, show_full_path)` instead (keep UI logic out of collections). + * @this {CollectionItemThis} + * @returns {string} The display name. + */ + get name() { + return get_item_display_name2( + this.key, + this.env.settings.smart_view_filter?.show_full_path + ); + } +}; + +// node_modules/smart-collections/collection.js +var AsyncFunction2 = Object.getPrototypeOf(async function() { +}).constructor; +var QUEUE_SAVE_DEBOUNCE_MS2 = 750; +var Collection2 = class { + /** @type {string|number} */ + static version = 1e-3; + /** + * Constructs a new Collection instance. + * + * @this {*} + * @param {CollectionEnv} env - The environment context containing configurations and adapters. + * @param {CollectionOptions} [opts={}] - Optional configuration. + */ + constructor(env, opts = {}) { + env.create_env_getter(this); + this.opts = opts; + if (opts.collection_key) this.collection_key = opts.collection_key; + this.env[this.collection_key] = this; + this.config = this.env.config; + this.items = {}; + this.loaded = null; + this._loading = false; + this.load_time_ms = null; + this.settings_container = null; + } + /** + * Initializes a new collection in the environment. Override in subclass if needed. + * + * @param {CollectionEnv} env + * @param {CollectionOptions} [opts={}] + * @returns {Promise} + */ + static async init(env, opts = {}) { + env[this.collection_key] = new this(env, opts); + await env[this.collection_key].init(); + env.collections[this.collection_key] = "init"; + } + /** + * The unique collection key derived from the class name. + * @returns {string} + */ + static get collection_key() { + let name = this.name; + if (name.match(/\d$/)) name = name.slice(0, -1); + return name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); + } + /** + * Instance-level init. Override in subclasses if necessary. + * @returns {Promise} + */ + async init() { + } + /** + * Creates or updates an item in the collection. + * - If `data` includes a key that matches an existing item, that item is updated. + * - Otherwise, a new item is created. + * After updating or creating, the item is validated. If validation fails, the item is logged and returned without being saved. + * If validation succeeds for a new item, it is added to the collection and marked for saving. + * + * If the item’s `init()` method is async, a promise is returned that resolves once init completes. + * + * NOTE: wrapping in try/catch seems to fail to catch errors thrown in async init functions when awaiting create_or_update + * + * @this {CollectionThis} + * @param {Partial} [data={}] - Data for creating/updating an item. + * @returns {Promise|CollectionItemInstance} The created or updated item. May return a promise if `init()` is async. + */ + create_or_update(data = {}) { + const existing_item = this.find_by(data); + const item = existing_item ? existing_item : new this.item_type(this.env); + item._queue_save = !existing_item; + const data_changed = item.update_data(data); + if (!existing_item && !item.validate_save()) { + return item; + } + if (!existing_item) { + this.set(item); + } + if (existing_item && !data_changed) return existing_item; + if (item.init instanceof AsyncFunction2) { + return new Promise((resolve) => { + item.init(data).then(() => resolve(item)); + }); + } + item.init(data); + return item; + } + /** + * Finds an item by partial data match (first checks key). If `data.key` provided, + * returns the item with that key; otherwise attempts a match by merging data. + * + * @this {CollectionThis} + * @param {Partial} data - Data to match against. + * @returns {CollectionItemInstance|null|undefined} + */ + find_by(data) { + if (data.key) return this.get(data.key); + const temp = new this.item_type(this.env); + const temp_data = JSON.parse(JSON.stringify(data, temp.sanitize_data(data))); + deep_merge2(temp.data, temp_data); + return temp.key ? this.get(temp.key) : null; + } + /** + * Filters items based on provided filter options or a custom function. + * + * @this {CollectionThis} + * @param {*} [filter_opts={}] - Filter options or a predicate function. + * @returns {CollectionItemInstance[]} Array of filtered items. + */ + filter(filter_opts = {}) { + if (typeof filter_opts === "function") { + return Object.values(this.items).filter(filter_opts); + } + const results = []; + const { first_n } = filter_opts; + for (const item of Object.values(this.items)) { + if (first_n && results.length >= first_n) break; + if (item.filter(filter_opts)) results.push(item); + } + return results; + } + /** + * Alias for `filter()` + * @this {CollectionThis} + * @param {*} filter_opts + * @returns {CollectionItemInstance[]} + */ + list(filter_opts) { + return this.filter(filter_opts); + } + /** + * Retrieves an item by key. + * @this {CollectionThis} + * @param {string} key + * @returns {CollectionItemInstance|undefined} + */ + get(key) { + return this.items[key]; + } + /** + * Retrieves multiple items by an array of keys. + * @this {CollectionThis} + * @param {string[]} keys + * @returns {Array<*>} + */ + get_many(keys = []) { + if (!Array.isArray(keys)) { + console.error("get_many called with non-array keys:", keys); + return []; + } + return keys.map((key) => this.get(key)).filter(Boolean); + } + /** + * Retrieves a random item from the collection, optionally filtered by options. + * @this {CollectionThis} + * @param {*} [opts] + * @returns {CollectionItemInstance|undefined} + */ + get_rand(opts = null) { + if (opts) { + const filtered = this.filter(opts); + return filtered[Math.floor(Math.random() * filtered.length)]; + } + const keys = this.keys; + return this.items[keys[Math.floor(Math.random() * keys.length)]]; + } + /** + * Adds or updates an item in the collection. + * @this {CollectionThis} + * @param {CollectionItemInstance} item + */ + set(item) { + if (!item.key) throw new Error("Item must have a key property"); + this.items[item.key] = item; + } + /** + * Updates multiple items by their keys. + * @this {CollectionThis} + * @param {string[]} keys + * @param {Partial} data + */ + update_many(keys = [], data = {}) { + this.get_many(keys).forEach((item) => item.update_data(data)); + } + /** + * Clears all items from the collection. + * @this {CollectionThis} + */ + clear() { + this.items = {}; + } + /** + * @this {CollectionThis} + * @returns {string} The collection key, can be overridden by opts.collection_key + */ + get collection_key() { + return this._collection_key ? this._collection_key : this.constructor.collection_key; + } + set collection_key(key) { + this._collection_key = key; + } + /** + * Lazily initializes and returns the data adapter instance for this collection. + * @this {CollectionThis} + * @returns {CollectionDataAdapterInstance} The data adapter instance. + */ + get data_adapter() { + if (!this._data_adapter) { + const AdapterClass = this.get_adapter_class("data"); + this._data_adapter = new AdapterClass(this); + } + return this._data_adapter; + } + /** + * @private + * @this {CollectionThis} + * @param {string} type + * @returns {*} + */ + get_adapter_class(type) { + const config = this.env.opts.collections?.[this.collection_key]; + const adapter_key = type + "_adapter"; + const adapter_module = config?.[adapter_key] ?? this.env.opts.collections?.smart_collections?.[adapter_key]; + if (typeof adapter_module === "function") return adapter_module; + if (typeof adapter_module?.collection === "function") return adapter_module.collection; + throw new Error(`No '${type}' adapter class found for ${this.collection_key} or smart_collections`); + } + /** + * Data directory strategy for this collection. Defaults to 'multi'. + * @deprecated should be handled in adapters (2025-12-09) + * @this {CollectionThis} + * @returns {string} + */ + get data_dir() { + return this.collection_key; + } + /** + * File system adapter from the environment. + * @this {CollectionThis} + * @returns {FileSystem} + */ + get data_fs() { + return this.env.data_fs; + } + /** + * Derives the corresponding item class name based on this collection's class name. + * @returns {string} + */ + get item_class_name() { + let name = this.constructor.name; + if (name.match(/\d$/)) name = name.slice(0, -1); + if (name.endsWith("ies")) return name.slice(0, -3) + "y"; + else if (name.endsWith("s")) return name.slice(0, -1); + return name + "Item"; + } + /** + * Derives a readable item name from the item class name. + * @this {CollectionThis} + * @returns {string} + */ + get item_name() { + return this.item_class_name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); + } + /** + * Retrieves the item type (constructor) from the environment. + * @deprecated replace with item_class with strict adherence to conventions (2025-10-28) + * @this {CollectionThis} + * @returns {CollectionItemConstructor} Item constructor. + */ + get item_type() { + if (!this._item_type) this._item_type = this.resolve_item_type(); + return this._item_type; + } + // TEMP resolver (2025-11-03): until better handled on merging configs at obsidian-smart-env startup + /** + * @private + * @this {CollectionThis} + * @returns {*} + */ + resolve_item_type() { + const available = [ + this.env.config?.items?.[this.item_name], + this.opts.item_type + // Is this necessary? (2026-04-11 needs review - ideally should be able to rely on env.config for this) + ].filter(Boolean).sort((a, b) => { + const a_version = a?.class?.version || a.version || 0; + const b_version = b?.class?.version || b.version || 0; + return b_version - a_version; + }); + if (available.length === 0) { + throw new Error(`No item_type found for collection '${this.collection_key}' with item_name '${this.item_name}' or class_name '${this.item_class_name}'`); + } + return available[0].class || available[0]; + } + /** + * Returns an array of all keys in the collection. + * @this {CollectionThis} + * @returns {string[]} + */ + get keys() { + return Object.keys(this.items); + } + /** + * @deprecated use data_adapter instead (2024-09-14) + * @this {CollectionThis} + */ + get adapter() { + return this.data_adapter; + } + /** + * @method process_save_queue + * @description + * Saves items flagged for saving (_queue_save) back to AJSON or SQLite. This ensures persistent storage + * of any updates made since last load/import. This method also writes changes to disk (AJSON files or DB). + * @this {CollectionThis} + * @param {CollectionQueueOptions} [opts={}] + * @returns {Promise} + */ + async process_save_queue(opts = {}) { + if (opts.force) { + Object.values(this.items).forEach((item) => item._queue_save = true); + } + await this.data_adapter.process_save_queue(opts); + } + /** + * @alias process_save_queue + * @this {CollectionThis} + * @param {CollectionQueueOptions} [opts={}] + * @returns {Promise} + */ + async save(opts = {}) { + await this.process_save_queue(opts); + } + /** + * @method process_load_queue + * @description + * Loads items that have been flagged for loading (_queue_load). This may involve + * reading from AJSON/SQLite or re-importing from markdown if needed. + * Called once initial environment is ready and collections are known. + * @this {CollectionThis} + * @returns {Promise} + */ + async process_load_queue() { + await this.data_adapter.process_load_queue(); + } + /** + * Retrieves processed settings configuration. + * @this {CollectionThis} + * @returns {SettingsConfig} + */ + get settings_config() { + return this.process_settings_config({}); + } + /** + * Processes given settings config, adding prefixes and handling conditionals. + * @deprecated removing settings_config from collections (2025-11-24) + * + * @private + * @this {CollectionThis} + * @param {SettingsConfig} _settings_config + * @param {string} [prefix=''] + * @returns {SettingsConfig} + */ + process_settings_config(_settings_config, prefix = "") { + const add_prefix = (key) => prefix && !key.includes(`${prefix}.`) ? `${prefix}.${key}` : key; + return Object.entries(_settings_config).reduce((acc, [key, val]) => { + let new_val = { ...val }; + if (new_val.conditional) { + if (!new_val.conditional(this)) return acc; + delete new_val.conditional; + } + if (new_val.callback) new_val.callback = add_prefix(new_val.callback); + if (new_val.btn_callback) new_val.btn_callback = add_prefix(new_val.btn_callback); + if (new_val.options_callback) new_val.options_callback = add_prefix(new_val.options_callback); + const new_key = add_prefix(this.process_setting_key(key)); + acc[new_key] = new_val; + return acc; + }, {}); + } + /** + * Processes an individual setting key. Override if needed. + * @param {string} key + * @returns {string} + */ + process_setting_key(key) { + return key; + } + /** + * Default settings for this collection. Override in subclasses as needed. + * @returns {Object} + */ + get default_settings() { + return {}; + } + /** + * Current settings for the collection. + * Initializes with default settings if none exist. + * @this {CollectionThis} + * @returns {Object} + */ + get settings() { + if (!this.env.settings[this.collection_key]) { + this.env.settings[this.collection_key] = this.default_settings; + } + return this.env.settings[this.collection_key]; + } + /** + * Unloads collection data from memory. + * @this {*} + * @returns {*} + */ + unload() { + this.clear(); + this.unloaded = true; + this.env.collections[this.collection_key] = null; + } + /** + * Emits an event with collection metadata. + * + * @this {CollectionThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_event(event_key, payload = {}) { + this.env.events?.emit(event_key, { collection_key: this.collection_key, ...payload }); + } + /** + * @this {CollectionThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_info_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "info", ...payload }); + } + /** + * @this {CollectionThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_warning_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "warning", ...payload }); + } + /** + * @this {CollectionThis} + * @param {string} event_key + * @param {CollectionEventPayload & Object.} [payload={}] + * @returns {void} + */ + emit_error_event(event_key, payload = {}) { + this.emit_event(event_key, { level: "error", ...payload }); + } + /** + * @this {CollectionThis} + * @param {string} event_key + * @param {CollectionEventCallback} callback + * @returns {*} + */ + on_event(event_key, callback) { + return this.env.events?.on(event_key, (payload) => { + if (payload?.collection_key && payload.collection_key !== this.collection_key) return; + callback(payload); + }); + } + /** + * Lazily binds action functions to the collection instance. + * + * @this {CollectionThis} + * @returns {Object} Bound action functions keyed by name. + */ + get actions() { + if (!this.constructor.key) this.constructor.key = this.collection_key; + if (!this._actions) { + const actions_modules = { + ...this.env?.config?.actions || {}, + ...this.env?.config?.collections?.[this.collection_key]?.actions || {}, + ...this.env?.opts?.collections?.[this.collection_key]?.actions || {}, + ...this.opts?.actions || {} + }; + this._actions = create_actions_proxy2(this, actions_modules); + } + return this._actions; + } + /** + * Clears cached actions proxy and rebuilds on next access. + * @this {CollectionThis} + * @returns {Object} Rebuilt proxy with latest source snapshot. + */ + refresh_actions() { + this._actions = null; + return this.actions; + } + // debounce running process save queue + /** + * @this {CollectionThis} + * @returns {void} + */ + queue_save() { + if (this._debounce_queue_save) clearTimeout(this._debounce_queue_save); + this._debounce_queue_save = setTimeout(() => { + this.process_save_queue(); + }, this.queue_save_debounce_ms || QUEUE_SAVE_DEBOUNCE_MS2); + } + // BEGIN DEPRECATED + /** + * @deprecated use env.smart_components~~env.smart_view~~ instead (2026-02-11) + * @this {CollectionThis} + * @returns {Object} smart_view instance + */ + get smart_view() { + if (!this._smart_view) this._smart_view = this.env.init_module("smart_view"); + return this._smart_view; + } +}; + +// node_modules/smart-entities/utils/frontmatter_filter.js +var to_string2 = (value) => `${value ?? ""}`.trim(); +var to_lower2 = (value) => to_string2(value).toLowerCase(); +var to_lines = (value = "") => { + if (Array.isArray(value)) return value; + return to_string2(value).split("\n"); +}; +function parse_frontmatter_filter_lines(value = "") { + return to_lines(value).map((line) => to_string2(line)).filter((line) => line.length).map((line) => { + const separator_index = line.indexOf(":"); + if (separator_index === -1) { + return { key: to_lower2(line), value: null }; + } + const key = to_lower2(line.slice(0, separator_index)); + const entry_value = to_string2(line.slice(separator_index + 1)); + return { key, value: entry_value.length ? to_lower2(entry_value) : null }; + }).filter((entry) => entry.key.length); +} + +// node_modules/smart-utils/results_acc.js +function results_acc2(_acc, result, ct = 10) { + if (_acc.results.size < ct) { + _acc.results.add(result); + if (_acc.results.size === ct && _acc.min === Number.POSITIVE_INFINITY) { + let { minScore, minObj } = find_min2(_acc.results); + _acc.min = minScore; + _acc.minResult = minObj; + } + } else if (result.score > _acc.min) { + _acc.results.add(result); + _acc.results.delete(_acc.minResult); + let { minScore, minObj } = find_min2(_acc.results); + _acc.min = minScore; + _acc.minResult = minObj; + } +} +function find_min2(results) { + let minScore = Number.POSITIVE_INFINITY; + let minObj = null; + for (const obj of results) { + if (obj.score < minScore) { + minScore = obj.score; + minObj = obj; + } + } + return { minScore, minObj }; +} + +// node_modules/smart-utils/sort_by_score.js +function sort_by_score2(a, b) { + const epsilon = 1e-9; + const score_diff = a.score - b.score; + if (Math.abs(score_diff) < epsilon) return 0; + return score_diff > 0 ? -1 : 1; +} +function sort_by_score_descending2(a, b) { + return sort_by_score2(a, b); +} + +// src/utils/merge_pinned_results.js +function merge_pinned_results(base_results, params) { + if (!params.pinned?.length) return base_results; + const pinned_keys = new Set(params.pinned_keys || params.pinned.map((item) => item.key)); + const pinned_results = params.pinned.map((item) => ({ + item, + ...item.score?.(params) || {} + })); + const filtered_results = base_results.filter((result) => { + const key = result?.item?.key; + return key ? !pinned_keys.has(key) : true; + }); + return [...pinned_results, ...filtered_results]; +} + +// migrations/migrate_hidden_connections.js +var BLOCK_KEY_MARKER = "#"; +var COLLECTION_SEPARATOR = ":"; +var SOURCE_COLLECTION = "smart_sources"; +var BLOCK_COLLECTION = "smart_blocks"; +function is_plain_object6(value) { + return Boolean(value) && typeof value === "object" && !Array.isArray(value); +} +function select_hidden_entries(hidden_connections) { + if (!is_plain_object6(hidden_connections)) return []; + return Object.entries(hidden_connections).filter(([, timestamp]) => timestamp !== void 0 && timestamp !== null); +} +function get_collection_prefix(key) { + return key.includes(BLOCK_KEY_MARKER) ? BLOCK_COLLECTION : SOURCE_COLLECTION; +} +function ensure_prefixed_key(key) { + if (typeof key !== "string") return key; + if (key.includes(COLLECTION_SEPARATOR)) return key; + return `${get_collection_prefix(key)}${COLLECTION_SEPARATOR}${key}`; +} +function migrate_hidden_connections(source) { + const hidden_entries = select_hidden_entries(source?.data?.hidden_connections); + if (!hidden_entries.length) return false; + if (!is_plain_object6(source.data.connections)) { + source.data.connections = {}; + } + let migrated = false; + hidden_entries.forEach(([key, timestamp]) => { + const prefixed_key = ensure_prefixed_key(key); + if (typeof prefixed_key !== "string") return; + const connection_state = source.data.connections[prefixed_key] || {}; + if (connection_state.hidden === void 0 || connection_state.hidden === null) { + connection_state.hidden = timestamp; + migrated = true; + } + source.data.connections[prefixed_key] = connection_state; + }); + if (migrated) { + delete source.data.hidden_connections; + } + return migrated || hidden_entries.length > 0; +} + +// src/items/connections_list.js +var ConnectionsList = class extends CollectionItem2 { + static key = "connections_list"; + static get defaults() { + return { data: {} }; + } + get_key() { + return `${this.data.collection_key}:${this.data.item_key}`; + } + async pre_process(params) { + migrate_hidden_connections(this.item); + if (typeof this.actions.connections_list_pre_process === "function") { + await this.actions.connections_list_pre_process(params); + } + if (typeof this.env.config?.actions?.[params.score_algo_key]?.pre_process === "function") { + await this.env.config.actions[params.score_algo_key].pre_process.call(this.item, params); + } + } + /** + * Produce ranked connections for the current source item. + * @param {object} params + * @note cannot call with different params until promise resolves + * @returns {Promise} + */ + async get_results(params = {}) { + if (this._results_promise) return this._results_promise; + const p = this._get_results(params); + this._results_promise = p; + this._results_promise.finally(() => { + if (this._results_promise === p) { + this._results_promise = null; + } + }); + return this._results_promise; + } + async _get_results(params = {}) { + await this.pre_process(params); + if (this.env.log_perf) this.start_ms = Date.now(); + let results = this.filter_and_score(params); + if (this.env.log_perf) { + this.end_ms = Date.now(); + } + results = await this.post_process(results, params); + results = merge_pinned_results(results, params); + results = results.map((r) => Object.assign(r, { connections_list: this })); + this.results = results; + return results; + } + filter_and_score(params = {}) { + const collection = this.env[params.results_collection_key]; + const score_errors = []; + const { results: raw_results } = Object.values(collection.items).reduce((acc, target) => { + const scored = target.filter_and_score(params); + if (!scored?.score) { + if (scored?.error) score_errors.push(scored.error); + return acc; + } + results_acc2(acc, scored, params.limit); + return acc; + }, { min: 0, results: /* @__PURE__ */ new Set() }); + const results = Array.from(raw_results).sort(sort_by_score_descending2); + if (!results.length) return results; + while (!results.some((r) => r.score > 0.5)) { + results.forEach((r) => r.score *= 2); + } + return results; + } + async post_process(results, params = {}) { + if (!results?.length) { + console.warn("No results to post-process, received:", results); + return []; + } + const action_key = this.settings.connections_post_process; + const post_process_action = this.actions[action_key]; + let processed_results = results; + if (typeof post_process_action === "function") { + const response = await post_process_action(results, params); + if (Array.isArray(response)) { + processed_results = response.filter(Boolean); + if (!processed_results.length) processed_results = results; + } else if (response !== void 0 && response !== null) { + console.warn(`connections post_process '${action_key}' returned non-array`, response); + } + } else if (action_key && action_key !== "none") { + console.warn(`Post-process action "${action_key}" not found, falling back to base results.`); + } + return processed_results; + } + get item() { + return this.env[this.data.collection_key]?.items[this.data.item_key]; + } + get connections_list_component_key() { + const stored_key = this.data.connections_list_component_key || this.settings?.connections_list_component_key; + if (this.env.config.components[stored_key]) return stored_key; + return "connections_list_v4"; + } +}; + +// migrations/migrate_connections_lists_settings.js +var MIGRATED_SETTING_KEYS = [ + "inline_connections", + "inline_connections_score_threshold", + "footer_connections" +]; +function migrate_connections_lists_settings(env) { + const settings = env?.settings; + if (!settings) return false; + const legacy = settings.connections_pro; + if (!legacy) return false; + const target = settings.connections_lists ||= {}; + let migrated = false; + for (const key of MIGRATED_SETTING_KEYS) { + if (!(key in legacy)) continue; + if (!(key in target)) { + target[key] = legacy[key]; + migrated = true; + } + delete legacy[key]; + } + if ("rank_model" in legacy) { + if (!target.actions) target.actions = {}; + if (!("rank_connections" in target.actions)) { + target.actions.rank_connections = legacy.rank_model; + migrated = true; + } + delete legacy.rank_model; + } + return migrated; +} + +// src/utils/insert_settings_after.js +function insert_settings_after(anchor_key, config, merge_object) { + const config_entries = Object.entries({ ...config }); + const anchor_i = config_entries.findIndex(([key]) => key === anchor_key); + if (anchor_i !== -1) { + if (merge_object && Object.keys(merge_object).length > 0) { + const entries = Object.entries(merge_object); + config_entries.splice(anchor_i + 1, 0, ...entries); + } + } + return Object.fromEntries(config_entries); +} + +// src/collections/connections_lists.js +var ConnectionsLists = class extends Collection2 { + static version = 1; + process_load_queue() { + } + // no persisting data (for now) + constructor(env, opts = {}) { + migrate_connections_lists_settings(env); + super(env, opts); + } + static get default_settings() { + return { + results_collection_key: "smart_sources", + score_algo_key: "similarity", + connections_post_process: "none", + results_limit: 20, + connections_view_location: "right", + exclude_frontmatter_blocks: true, + connections_list_component_key: "connections_list_v4", + connections_list_item_component_key: "connections_list_item_v3", + frontmatter_filter_include: "", + frontmatter_filter_exclude: "", + components: { + connections_list_v4: { + show_graph: true + }, + connections_list_item_v3: { + render_markdown: true, + show_full_path: false + } + } + }; + } + get settings_config() { + return settings_config10(this); + } + new_item(item) { + const connections_list = new this.item_type(this.env, { + collection_key: item.collection_key, + item_key: item.key + }); + this.set(connections_list); + Object.defineProperty(item, "connections", { + get: () => connections_list, + configurable: true + }); + return connections_list; + } + get_connections_list_item_options() { + return Object.entries(this.env.config.components || {}).filter(([key, fn]) => key.startsWith("connections_list_item_")).map(([value, fn]) => ({ value, name: fn.display_name || value, description: fn.display_description })); + } + get score_algo_key() { + const stored_key = this.settings?.score_algo_key; + if (this.env.config?.actions?.[stored_key]) return stored_key; + return "similarity"; + } + get results_collection_key() { + const stored_key = this.settings?.results_collection_key; + if (this.env[stored_key]) return stored_key; + return "smart_sources"; + } + get frontmatter_inclusions() { + return parse_frontmatter_filter_lines(this.settings.frontmatter_filter_include); + } + get frontmatter_exclusions() { + return parse_frontmatter_filter_lines(this.settings.frontmatter_filter_exclude); + } + get connections_list_component_settings_config() { + if (!this.settings?.connections_list_component_key || !this.env.is_pro && ["none", "connections_list_v4_2", "connections_list_v3"].includes(this.settings.connections_list_component_key)) { + this.settings.connections_list_component_key = "connections_list_v4"; + } + if (!this.settings?.components?.connections_list_v4) { + if (!this.settings.components) this.settings.components = {}; + this.settings.components.connections_list_v4 = { ...this.constructor.default_settings.components.connections_list_v4 }; + } + const component_key = this.settings.connections_list_component_key; + if (!component_key || component_key === "none") return null; + const component_module = this.env.config.components?.[component_key]; + const config = typeof component_module?.settings_config === "function" ? component_module.settings_config(this) : component_module?.settings_config; + if (!config) return null; + return Object.fromEntries( + Object.entries(config).map(([k, v]) => { + return [`components.${component_key}.${k}`, v]; + }) + ); + } +}; +function settings_config10(scope) { + let config = { + "results_collection_key": { + name: "Connection results type", + type: "dropdown", + description: "Choose whether results should be sources or blocks.", + option_1: "smart_sources|Sources", + // DEPRECATED + option_2: "smart_blocks|Blocks", + // DEPRECATED + options_callback: () => { + const options = [ + { value: "smart_sources", name: "Sources" } + ]; + if (scope.env.smart_blocks) { + options.push({ value: "smart_blocks", name: "Blocks" }); + } + return options; + } + }, + "results_limit": { + name: "Results limit", + type: "number", + description: "Adjust the number of connections displayed in the connections view (default 20)." + }, + "connections_view_location": { + group: "Display", + name: "Connections sidebar location", + type: "dropdown", + description: "Choose which sidebar opens when showing the Connections view.", + option_1: "right|Right sidebar", + // DEPRECATED + option_2: "left|Left sidebar", + // DEPRECATED + options_callback: () => { + return [ + { value: "right", name: "Right sidebar" }, + { value: "left", name: "Left sidebar" } + ]; + } + }, + "inline_connections": { + group: "Inline connections", + name: "Show inline connections", + type: "toggle", + scope_class: "pro-setting", + description: "Shows connections for each block within the note. Hover connections icon to see list of connections." + }, + "footer_connections": { + group: "Footer connections", + name: "Show footer connections", + type: "toggle", + description: "Show connections at the bottom of each note." + }, + filters_helper: { + group: "Connections filters", + type: "html", + value: [ + '

    Filter tips: Use comma-separated folder or file path fragments such as Projects/Clients. Values are trimmed automatically and compared using case-sensitive substring matches.

    ', + '

    Result vs ingestion: Connections filters only hide results after Smart Environment builds its dataset. To stop notes from being indexed, adjust Smart Environment include/exclude settings in the Environment window or plugin settings.

    ', + '

    Precedence: Entries in the exclude filter always win when they match, even if the same path fragment appears in the include filter.

    ' + ].join("") + }, + "exclude_inlinks": { + group: "Connections filters", + name: "Exclude inlinks (backlinks)", + type: "toggle", + scope_class: "pro-setting", + description: "Exclude notes that already link to the current note from the connections results." + }, + "exclude_outlinks": { + group: "Connections filters", + name: "Exclude outlinks", + type: "toggle", + scope_class: "pro-setting", + description: "Exclude notes that are already linked from within the current note from appearing in the connections results." + }, + "include_filter": { + group: "Connections filters", + name: "Include filter", + type: "text", + scope_class: "pro-setting", + description: "Comma-separated path fragments that must appear in the note path. Matches use case-sensitive substring checks; trim spaces or wrap folder names like `Daily/`. This only affects the results list; Smart Environment still embeds matching notes unless excluded there." + }, + "exclude_filter": { + group: "Connections filters", + name: "Exclude filter", + type: "text", + scope_class: "pro-setting", + description: "Comma-separated path fragments to omit from results. Exclusions run before includes, so any matching fragment removes the note even if it also appears in `Include filter`. Use Smart Environment include/exclude settings to stop notes from being embedded altogether." + }, + "frontmatter_filter_include": { + group: "Connections filters", + name: "Frontmatter include filter", + type: "text", + scope_class: "pro-setting", + description: "Newline-delimited frontmatter matchers (ex. status or status:open). Case-insensitive key and value matching." + }, + "frontmatter_filter_exclude": { + group: "Connections filters", + name: "Frontmatter exclude filter", + type: "text", + scope_class: "pro-setting", + description: "Newline-delimited frontmatter matchers removed from results. Exclude entries take precedence over include entries." + }, + // hide frontmatter blocks from connections results + "exclude_frontmatter_blocks": { + group: "Connections filters", + name: "Hide frontmatter blocks in results", + type: "toggle", + scope_class: "pro-setting", + description: "Show only sources in the connections results (no frontmatter blocks)." + } + }; + if (!scope.env.smart_blocks.settings.embed_blocks) { + config.results_collection_key = { + type: "html", + value: '

    Enable "Embed blocks" in Smart Blocks settings to use block connections.

    ', + name: "Connection results type" + }; + } + if (scope.connections_list_component_settings_config) { + config = insert_settings_after("results_limit", config, scope.connections_list_component_settings_config); + } + return config; +} +var connections_lists_default = { + class: ConnectionsLists, + collection_key: "connections_lists", + item_type: ConnectionsList, + settings_config: settings_config10 +}; + +// src/components/connections_codeblock.css +var connections_codeblock_default = ".connections-codeblock {\n .connections-top-bar {\n display: flex;\n gap: 0.5rem 1rem;\n\n .connections-actions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n align-items: center;\n gap: 0.5rem;\n span {\n font-weight: 600;\n justify-self: end;\n }\n }\n }\n .connections-list {\n padding-top: 0.85rem;\n\n > .sc-collapsed ul {\n display: none;\n }\n > .sc-collapsed span svg {\n transform: rotate(-90deg);\n }\n > .sc-result-pinned {\n box-shadow: 0 0 0 1px var(--interactive-accent);\n border-radius: var(--radius-s);\n background-color: var(--background-modifier-hover);\n }\n }\n}"; + +// node_modules/obsidian-smart-env/src/modals/story.js +var import_obsidian53 = require("obsidian"); +var StoryModal = class _StoryModal extends import_obsidian53.Modal { + constructor(plugin, { title, url }) { + super(plugin.app); + this.plugin = plugin; + this.title = title; + this.url = url; + } + static open(plugin, story_url) { + const modal = new _StoryModal(plugin, story_url); + modal.open(); + } + onOpen() { + this.titleEl.setText(this.title); + this.modalEl.addClass("sc-story-modal"); + this.modalEl.style.width = "80%"; + this.modalEl.style.height = "80%"; + const container = this.contentEl.createEl("div", { + cls: "sc-story-container" + }); + container.style.display = "flex"; + container.style.flexDirection = "column"; + container.style.height = "100%"; + if (import_obsidian53.Platform.isMobile) { + const btn = container.createEl("button", { text: "Open in browser" }); + btn.addEventListener("click", () => { + window.open(this.url, "_external"); + this.close(); + }); + return; + } else { + const webview = container.createEl("webview", { + attr: { src: this.url, allowpopups: "" } + }); + webview.style.width = "100%"; + webview.style.height = "100%"; + webview.addEventListener("did-navigate", (event) => { + const new_url = event.url || webview.getAttribute("src"); + if (new_url && new_url !== this.url) { + window.open(new_url, "_external"); + this.close(); + } + }); + } + } + onClose() { + this.contentEl.empty(); + } +}; + +// src/utils/connections_context_items.js +function build_connections_context_items(params = {}) { + const { source_item, results = [] } = params; + const items = []; + const seen_keys = /* @__PURE__ */ new Set(); + const append_item = (item, score) => { + const key = item?.key; + if (!key || seen_keys.has(key)) return; + seen_keys.add(key); + items.push({ key, score }); + }; + if (source_item) append_item(source_item, source_item.score ?? 1); + results.forEach((result) => append_item(result?.item, result?.score)); + return items; +} + +// src/utils/format_connections_as_links.js +function format_connections_as_links(results = []) { + if (!Array.isArray(results) || !results.length) return ""; + return results.map(({ item }) => format_connection_item(item)).filter(Boolean).join("\n"); +} +function format_connection_item(item) { + if (!item?.key) return ""; + const link = get_wikilink(item); + if (!link) return ""; + const lines = get_lines_label(item); + if (!lines) return link; + return `${link.replace(/\#\{\d+\}/, "")} (${lines})`; +} +function get_lines_label(item) { + if (!item?.key?.endsWith("}")) return ""; + if (!Array.isArray(item.lines) || !item.lines.length) return ""; + return `Lines: ${item.lines.join("-")}`; +} +function get_wikilink(item) { + if (!item?.key) return ""; + const [source_key, ...block_parts] = item.key.split("#"); + const filename = source_key.split("/").pop().replace(/\.md$/i, ""); + if (block_parts.length) { + const block_ref = block_parts.pop(); + return `- [[${filename}#${block_ref}]]`; + } + return `- [[${filename}]]`; +} + +// src/utils/connections_list_item_state.js +function build_prefixed_connection_key(collection_key, item_key) { + if (typeof item_key !== "string" || !item_key.length) return item_key; + if (item_key.includes(":")) return item_key; + if (typeof collection_key !== "string" || !collection_key.length) return item_key; + return `${collection_key}:${item_key}`; +} +function apply_hidden_state(connections, prefixed_key, hidden_at = Date.now()) { + if (!connections || typeof connections !== "object") return connections; + if (typeof prefixed_key !== "string" || !prefixed_key.length) return connections; + const state = connections[prefixed_key] || {}; + state.hidden = hidden_at; + connections[prefixed_key] = state; + return connections; +} +function apply_pinned_state(connections, prefixed_key, pinned_at = Date.now()) { + if (!connections || typeof connections !== "object") return connections; + if (typeof prefixed_key !== "string" || !prefixed_key.length) return connections; + const state = connections[prefixed_key] || {}; + state.pinned = pinned_at; + connections[prefixed_key] = state; + return connections; +} +function remove_pinned_state(connections, prefixed_key) { + if (!connections || typeof connections !== "object") return connections; + if (typeof prefixed_key !== "string" || !prefixed_key.length) return connections; + const state = connections[prefixed_key]; + if (!state || typeof state !== "object") return connections; + delete state.pinned; + if (!Object.keys(state).length) delete connections[prefixed_key]; + return connections; +} +function remove_all_hidden_states(connections) { + if (!connections || typeof connections !== "object") return false; + let changed = false; + Object.entries(connections).forEach(([key, state]) => { + if (!state || typeof state !== "object") return; + if (state.hidden === void 0 || state.hidden === null) return; + delete state.hidden; + if (!Object.keys(state).length) delete connections[key]; + changed = true; + }); + return changed; +} +function remove_all_pinned_states(connections) { + if (!connections || typeof connections !== "object") return false; + let changed = false; + Object.entries(connections).forEach(([key, state]) => { + if (!state || typeof state !== "object") return; + if (state.pinned === void 0 || state.pinned === null) return; + delete state.pinned; + if (!Object.keys(state).length) delete connections[key]; + changed = true; + }); + return changed; +} +function count_hidden_connections(connections) { + if (!connections || typeof connections !== "object") return 0; + return Object.values(connections).reduce((count, state) => { + if (state && typeof state === "object" && state.hidden !== void 0 && state.hidden !== null) { + return count + 1; + } + return count; + }, 0); +} +function count_pinned_connections(connections) { + if (!connections || typeof connections !== "object") return 0; + return Object.values(connections).reduce((count, state) => { + if (state && typeof state === "object" && state.pinned !== void 0 && state.pinned !== null) { + return count + 1; + } + return count; + }, 0); +} +function is_connection_pinned(connections, prefixed_key) { + if (!connections || typeof connections !== "object") return false; + if (typeof prefixed_key !== "string" || !prefixed_key.length) return false; + const state = connections[prefixed_key]; + if (!state || typeof state !== "object") return false; + return state.pinned !== void 0 && state.pinned !== null; +} +function is_connection_hidden(connections, prefixed_key) { + if (!connections || typeof connections !== "object") return false; + if (typeof prefixed_key !== "string" || !prefixed_key.length) return false; + const state = connections[prefixed_key]; + if (!state || typeof state !== "object") return false; + return state.hidden !== void 0 && state.hidden !== null; +} + +// src/utils/filter_hidden_results.js +function filter_hidden_results(results = [], connections_state = {}) { + if (!Array.isArray(results) || !results.length) return []; + if (!connections_state || typeof connections_state !== "object") return results; + return results.filter((result) => { + const item = result?.item; + if (!item) return false; + const prefixed_key = build_prefixed_connection_key(item.collection_key, item.key); + const state = connections_state[prefixed_key]; + if (!state || typeof state !== "object") return true; + const hidden = state.hidden !== void 0 && state.hidden !== null; + const pinned = state.pinned !== void 0 && state.pinned !== null; + return !(hidden && !pinned); + }); +} + +// src/components/connections_codeblock.js +async function build_html26(connections_list, opts = {}) { + const top_bar_buttons = [ + { + title: "Refresh connections", + icon: "refresh-cw", + attrs: 'data-action="refresh-connections"' + }, + { + title: "Expand all", + icon: "unfold-vertical", + attrs: 'data-action="expand-all"' + }, + { + title: "Collapse all", + icon: "fold-vertical", + attrs: 'data-action="collapse-all"' + }, + { + title: "Send results to Smart Context", + icon: "briefcase", + attrs: 'data-action="send-to-smart-context"' + }, + { + title: "Copy as list of links", + icon: "copy", + attrs: 'data-action="copy-as-links"' + }, + { + title: "Connections settings", + icon: "settings", + attrs: 'data-action="open-settings"' + }, + { + title: "Help & getting started", + icon: "help-circle", + attrs: 'data-action="open-help"' + } + ].map((btn) => ` + + `).join(""); + const html = `
    +
    +
    + ${top_bar_buttons} + Smart Connections +
    +
    +
    +
    +
    `; + return html; +} +async function render28(connections_list, opts = {}) { + const html = await build_html26.call(this, connections_list, opts); + const frag = this.create_doc_fragment(html); + this.apply_style_sheet(connections_codeblock_default); + const container = frag.firstElementChild; + post_process25.call(this, connections_list, container, opts); + return frag; +} +async function post_process25(connections_list, container, opts = {}) { + const list_container = container.querySelector(".connections-list-container"); + const env = connections_list.env; + const app2 = env.plugin.app || window.app; + const render_list = async () => { + const connections_list_component_key = opts.connections_list_component_key || connections_list.connections_list_component_key || "connections_list_v4"; + const list = await env.smart_components.render_component( + connections_list_component_key, + connections_list, + opts + ); + this.empty(list_container); + list_container.appendChild(list); + }; + if (!container._has_listeners) { + container._has_listeners = true; + const refresh_button = container.querySelector('[data-action="refresh-connections"]'); + refresh_button?.addEventListener("click", async () => { + const refresh_entity = connections_list.item; + if (refresh_entity) { + await refresh_entity.read(); + refresh_entity.queue_import(); + await refresh_entity.collection.process_source_import_queue?.(); + render_list(); + } else { + console.warn("No entity found for refresh"); + } + }); + const expand_all_button = container.querySelector('[data-action="expand-all"]'); + expand_all_button?.addEventListener("click", () => { + container.querySelectorAll(".sc-result").forEach((elm) => { + elm.classList.remove("sc-collapsed"); + }); + }); + const collapse_all_button = container.querySelector('[data-action="collapse-all"]'); + collapse_all_button?.addEventListener("click", () => { + container.querySelectorAll(".sc-result").forEach((elm) => { + elm.classList.add("sc-collapsed"); + }); + }); + const context_button = container.querySelector('[data-action="send-to-smart-context"]'); + context_button?.addEventListener("click", async () => { + const raw_results = await get_results_fallback(connections_list, opts); + if (!raw_results.length) { + env?.events?.emit?.("connections:send_to_context_empty", { + level: "warning", + message: "No connection results to send to Smart Context", + event_source: "connections_codeblock.send_to_smart_context" + }); + return; + } + const connections_state = connections_list?.item?.data?.connections || {}; + const visible_results = filter_hidden_results(raw_results, connections_state); + const context_items = build_connections_context_items({ + source_item: connections_list?.item, + results: visible_results + }); + if (!context_items.length) { + env?.events?.emit?.("connections:send_to_context_empty", { + level: "warning", + message: "No visible connection results to send to Smart Context", + event_source: "connections_codeblock.send_to_smart_context" + }); + return; + } + const smart_context = env.smart_contexts.new_context(); + smart_context.add_items(context_items); + smart_context.emit_event("context_selector:open"); + connections_list.emit_event("connections:sent_to_context"); + }); + const copy_links_button = container.querySelector('[data-action="copy-as-links"]'); + copy_links_button?.addEventListener("click", async () => { + const raw_results = await get_results_fallback(connections_list, opts); + if (!raw_results.length) { + env?.events?.emit?.("connections:copy_list_empty", { + level: "warning", + message: "No connection results to copy", + event_source: "connections_codeblock.copy_as_links" + }); + return; + } + const connections_state = connections_list?.item?.data?.connections || {}; + const visible_results = filter_hidden_results(raw_results, connections_state); + const links_payload = format_connections_as_links(visible_results); + if (!links_payload) { + env?.events?.emit?.("connections:copy_list_empty", { + level: "warning", + message: "No visible connection results to copy", + event_source: "connections_codeblock.copy_as_links" + }); + return; + } + await copy_to_clipboard(links_payload, { + env, + event_source: "connections_codeblock.copy_as_links", + success_event_key: "connections:list_copied", + error_event_key: "connections:list_copy_failed", + unavailable_event_key: "connections:list_copy_unavailable" + }); + connections_list.emit_event("connections:copied_list"); + }); + const settings_button = container.querySelector('[data-action="open-settings"]'); + settings_button?.addEventListener("click", () => { + app2.setting.open(); + app2.setting.openTabById("smart-connections"); + }); + const open_help = () => { + StoryModal.open(env.plugin, { + title: "Getting Started With Smart Connections", + url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=connections-view-help#page=understanding-connections-1" + }); + }; + const help_button = container.querySelector('[data-action="open-help"]'); + help_button?.addEventListener("click", open_help); + } + render_list(); + return container; +} +async function get_results_fallback(connections_list, opts = {}) { + const cached = Array.isArray(connections_list?.results) ? connections_list.results : []; + if (cached.length) return cached; + try { + const results = await connections_list.get_results({ ...opts }); + return Array.isArray(results) ? results : []; + } catch (err) { + console.error("Failed to fetch connections results", err); + return []; + } +} + +// src/components/connections_footer_view.css +var connections_footer_view_default = ".sc-connections-footer-hidden {\n display: none;\n}\n\n.sc-footer-backlink-pane {\n position: relative;\n}\n\n.connections-footer-view-shell {\n .connections-list-container.sc-footer-list-collapsed {\n display: none;\n }\n\n .connections-list {\n padding-top: 0.85rem;\n\n > .sc-collapsed ul {\n display: none;\n }\n > .sc-collapsed span svg {\n transform: rotate(-90deg);\n }\n > .sc-result-pinned {\n box-shadow: 0 0 0 1px var(--interactive-accent);\n border-radius: var(--radius-s);\n background-color: var(--background-modifier-hover);\n }\n }\n}\n\n"; + +// src/components/connections_footer_view.js +var FOOTER_FOLDED_STORAGE_KEY = "sc_footer_connections_folded"; +var FOOTER_LIST_COLLAPSED_CLASS = "sc-footer-list-collapsed"; +function get_footer_connections_folded() { + if (typeof localStorage === "undefined") return false; + return localStorage.getItem(FOOTER_FOLDED_STORAGE_KEY) === "true"; +} +function set_footer_connections_folded(folded) { + if (typeof localStorage === "undefined") return; + localStorage.setItem(FOOTER_FOLDED_STORAGE_KEY, String(folded)); +} +function apply_footer_fold_state(header_container, list_container, folded) { + if (!header_container || !list_container) return; + list_container.classList.toggle(FOOTER_LIST_COLLAPSED_CLASS, Boolean(folded)); + if (folded) { + header_container.setAttribute("aria-label", "Click to expand"); + header_container.classList.add("is-collapsed"); + return; + } + header_container.setAttribute("aria-label", "Click to collapse"); + header_container.classList.remove("is-collapsed"); +} +async function build_html27(view, opts = {}) { + const html = ``; + return html; +} +async function render29(view, opts = {}) { + const html = await build_html27.call(this, view, opts); + const frag = this.create_doc_fragment(html); + this.apply_style_sheet(connections_footer_view_default); + const container = frag.firstElementChild; + await post_process26.call(this, view, container, opts); + return container; +} +async function post_process26(view, container, opts = {}) { + const list_container = container.querySelector(".connections-list-container"); + const header_container = container.querySelector(".is-clickable"); + const env = view.env; + const connections_item = opts.connections_item; + if (!list_container) return container; + header_container?.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + const next_folded = !get_footer_connections_folded(); + set_footer_connections_folded(next_folded); + apply_footer_fold_state(header_container, list_container, next_folded); + }); + apply_footer_fold_state(header_container, list_container, get_footer_connections_folded()); + if (!connections_item) { + return container; + } + const connections_list = connections_item.connections || env.connections_lists.new_item(connections_item); + const list = await env.smart_components.render_component("connections_list_v4", connections_list, { ...opts }); + this.empty(list_container); + list_container.appendChild(list); + return container; +} + +// src/components/connections-graph/v1.css +var v1_default = ".connections-graph {\n position: relative;\n width: 100%;\n block-size: auto;\n container-type: inline-size;\n}\n\n.sc-graph-svg {\n width: 100%;\n aspect-ratio: 1 / 1; /* keep square; height follows width */\n height: auto;\n display: block;\n border-radius: var(--radius-m);\n border: 1px solid var(--background-modifier-border);\n background: var(--background-secondary);\n box-shadow: inset 0 0 0 1px color-mix(in srgb, var(--background-modifier-hover) 50%, transparent);\n}\n\n/* Details mount */\n.sc-graph-details {\n margin-block-start: 10px;\n}\n\n/* Nodes (small, Obsidian-like) */\n.sc-graph-node .sc-graph-node-dot {\n fill: var(--background-primary);\n stroke: var(--color-base-100);\n stroke-width: 1.25px;\n transition: stroke-width 120ms ease, stroke 120ms ease, fill 120ms ease;\n}\n\n.sc-graph-node-center .sc-graph-node-dot {\n fill: color-mix(in srgb, var(--interactive-accent) 18%, var(--background-primary));\n stroke: var(--interactive-accent);\n stroke-width: 1.5px;\n}\n\n.sc-graph-node-hover .sc-graph-node-dot {\n fill: var(--background-modifier-hover);\n stroke: var(--text-accent);\n stroke-width: 2px;\n}\n\n/* Score as native SVG text */\n.sc-score-text {\n font-size: 0.8em;\n fill: var(--text-muted);\n opacity: 0.95;\n pointer-events: none;\n user-select: none;\n}\n\n.sc-graph-node-hover .sc-score-text {\n display: none;\n}\n\n/* Label text */\n.sc-node-label {\n font-size: 0.85em;\n font-weight: 500;\n fill: var(--nav-item-color);\n opacity: 0;\n pointer-events: none;\n user-select: none;\n paint-order: stroke fill;\n stroke: color-mix(in srgb, var(--background-primary) 65%, transparent);\n stroke-width: 2px;\n transition: opacity 120ms ease-in-out;\n}\n\n.sc-graph-node-hover .sc-node-label {\n opacity: 0.96;\n fill: var(--nav-item-color-active);\n}\n\n/* Explicitly hide any label rendered for the center (defense-in-depth) */\n.connections-list-container .connections-graph-container .connections-graph .sc-graph-node-center .sc-node-label {\n display: none;\n}\n\n/* Pinned state */\n.sc-graph-node.sc-result-pinned .sc-graph-node-dot {\n fill: color-mix(in srgb, var(--interactive-accent) 12%, var(--background-primary));\n stroke: var(--interactive-accent);\n stroke-width: 2px;\n filter: drop-shadow(0 0 0.25rem color-mix(in srgb, var(--interactive-accent) 50%, transparent));\n}\n\n.sc-graph-node.sc-result-hidden .sc-graph-node-dot {\n fill: color-mix(in srgb, var(--background-modifier-border) 40%, transparent);\n stroke: color-mix(in srgb, var(--text-muted) 65%, transparent);\n stroke-width: 1px;\n opacity: 0.6;\n}\n\n/* Hidden nodes: scores always visible but muted */\n.sc-graph-node.sc-result-hidden .sc-score-text {\n opacity: 0.4;\n fill: var(--text-faint);\n}\n\n/* Hidden nodes: labels only appear on hover, with a softer style */\n.sc-graph-node.sc-result-hidden.sc-graph-node-hover .sc-node-label {\n opacity: 0.85;\n fill: var(--text-faint);\n}\n\n.sc-node-drag-handle-inner {\n width: 100%;\n height: 100%;\n background: transparent;\n cursor: grab;\n pointer-events: auto;\n}\n\n.sc-node-drag-handle-inner:active {\n cursor: grabbing;\n}\n"; + +// node_modules/obsidian-smart-env/src/utils/get_item_display_name.js +var DISPLAY_SEPARATOR = " \u203A "; +function get_item_display_name3(item, settings = {}) { + if (!item?.key) return ""; + const show_full_path = settings.show_full_path ?? true; + if (show_full_path) { + return item.key.replace(/#/g, DISPLAY_SEPARATOR).replace(/\//g, DISPLAY_SEPARATOR); + } + const pcs = []; + const [source_key, ...block_parts] = item.key.split("#"); + const filename = source_key.split("/").pop(); + pcs.push(filename); + if (block_parts.length) { + pcs.push(block_parts.pop()); + } + return pcs.join(DISPLAY_SEPARATOR); +} + +// node_modules/smart-utils/cos_sim.js +function cos_sim2(vector1 = [], vector2 = []) { + if (vector1.length !== vector2.length) { + throw new Error("Vectors must have the same length"); + } + let dot_product = 0; + let magnitude1 = 0; + let magnitude2 = 0; + const epsilon = 1e-8; + for (let i = 0; i < vector1.length; i++) { + dot_product += vector1[i] * vector2[i]; + magnitude1 += vector1[i] * vector1[i]; + magnitude2 += vector2[i] * vector2[i]; + } + magnitude1 = Math.sqrt(magnitude1); + magnitude2 = Math.sqrt(magnitude2); + if (magnitude1 < epsilon || magnitude2 < epsilon) return 0; + return dot_product / (magnitude1 * magnitude2); +} + +// node_modules/obsidian-smart-env/utils/parse_item_key_to_wikilink.js +function parse_item_key_to_wikilink(key) { + if (!key) return ""; + const [file_path, ...parts] = key.split("#"); + const file_name = file_path.split("/").pop().replace(/\.md$/, ""); + if (!parts.length) return `[[${file_name}]]`; + const heading = parts.filter((part) => !part.startsWith("{")).pop(); + if (!heading) return `[[${file_name}]]`; + return `[[${file_name}#${heading}]]`; +} + +// node_modules/obsidian-smart-env/src/utils/register_item_drag.js +function handle_connection_drag(obsidian_app, item, params, event) { + const drag_manager = obsidian_app.dragManager; + const link_text = parse_item_key_to_wikilink(item.key); + const drag_data = drag_manager.dragLink(event, link_text); + drag_manager.onDragStart(event, drag_data); + if (params.drag_event_key) { + item.emit_event(params.drag_event_key); + } else { + item.emit_event("connections:drag_result"); + } +} +function register_item_drag(container, item, params = {}) { + const env = item.env; + const app2 = env.obsidian_app; + container.setAttribute("draggable", "true"); + container.addEventListener("dragstart", handle_connection_drag.bind(null, app2, item, params)); +} + +// src/components/connections-graph/v1.util.js +function hash_to_unit(str = "") { + let h = 2166136261 >>> 0; + for (let i = 0; i < str.length; i++) { + h ^= str.charCodeAt(i); + h = Math.imul(h, 16777619); + } + h += h << 13; + h ^= h >>> 7; + h += h << 3; + h ^= h >>> 17; + h += h << 5; + return (h >>> 0) / 4294967295; +} +function seeded_angle(key = "", offset = -Math.PI / 2) { + const seed = hash_to_unit(key); + return offset + seed * Math.PI * 2; +} +function normalize_scores(scores = []) { + const vals = scores.map((s) => Number.isFinite(s) ? +s : null); + const present = vals.filter((v) => v !== null); + if (!present.length) { + return { norm: vals.map(() => 0.5), min: 0, max: 1 }; + } + const min = Math.min(...present); + const max = Math.max(...present); + const den = max - min || 1; + const norm2 = vals.map((v) => v === null ? 0.5 : (v - min) / den); + return { norm: norm2, min, max }; +} +function score_to_radius(t, min_r, max_r) { + const tt = Math.max(0, Math.min(1, Number(t) || 0)); + return Math.round(min_r + (1 - tt) * (max_r - min_r)); +} +function is_vec(v) { + return Array.isArray(v) && v.length > 0; +} +function norm(a = []) { + let s = 0; + for (let i = 0; i < a.length; i++) { + const x = a[i] || 0; + s += x * x; + } + return Math.sqrt(s); +} +function to_unit(a = []) { + const n = norm(a); + if (!Number.isFinite(n) || n === 0) return null; + return a.map((x) => x / n); +} +function mean_unit(vectors = []) { + if (!vectors.length) return null; + const dim = vectors[0].length; + const acc = new Array(dim).fill(0); + let c = 0; + for (const v of vectors) { + if (!v) continue; + for (let i = 0; i < dim; i++) acc[i] += v[i] || 0; + c++; + } + if (!c) return null; + const m = acc.map((x) => x / c); + return to_unit(m); +} +function prng_from_seed(seed_int) { + let a = seed_int >>> 0; + return function rand() { + a |= 0; + a = a + 1831565813 | 0; + let t = Math.imul(a ^ a >>> 15, 1 | a); + t ^= t + Math.imul(t ^ t >>> 7, 61 | t); + return ((t ^ t >>> 14) >>> 0) / 4294967296; + }; +} +function kmeans_cosine_unit(vecs = [], k = 4, max_iter = 100, seed = 1337) { + const xs = vecs.map((v) => v ? to_unit(v) : null); + const idx = xs.map((v, i) => v ? i : -1).filter((i) => i >= 0); + const m = idx.length; + if (m === 0) return { centers: [], assign: new Array(vecs.length).fill(-1), sims: new Array(vecs.length).fill(0) }; + const rand = prng_from_seed(seed); + const D = new Array(m).fill(1); + const centers = []; + centers.push(xs[idx[Math.floor(rand() * m)]]); + while (centers.length < Math.min(k, m)) { + for (let ii = 0; ii < m; ii++) { + const v = xs[idx[ii]]; + let best = -Infinity; + for (const c of centers) { + const s = cos_sim2(v, c); + if (s > best) best = s; + } + const d = 1 - Math.max(0, Math.min(1, best)); + D[ii] = d * d; + } + const sumD = D.reduce((a, b) => a + b, 0) || 1; + let r = rand() * sumD; + let chosen = 0; + for (let ii = 0; ii < m; ii++) { + r -= D[ii]; + if (r <= 0) { + chosen = ii; + break; + } + } + centers.push(xs[idx[chosen]]); + } + let assign = new Array(m).fill(-1); + let sims = new Array(m).fill(0); + for (let it = 0; it < max_iter; it++) { + let changed = false; + for (let ii = 0; ii < m; ii++) { + const v = xs[idx[ii]]; + let best_c = 0, best_s = -Infinity; + for (let c = 0; c < centers.length; c++) { + const s = cos_sim2(v, centers[c]); + if (s > best_s) { + best_s = s; + best_c = c; + } + } + if (assign[ii] !== best_c) { + assign[ii] = best_c; + changed = true; + } + sims[ii] = Math.max(0, Math.min(1, best_s)); + } + const buckets = centers.map(() => []); + for (let ii = 0; ii < m; ii++) buckets[assign[ii]].push(xs[idx[ii]]); + const next = centers.map((_, c) => mean_unit(buckets[c])); + for (let c = 0; c < centers.length; c++) { + if (!next[c]) next[c] = centers[c]; + centers[c] = next[c]; + } + if (!changed) break; + } + const full_assign = new Array(vecs.length).fill(-1); + const full_sims = new Array(vecs.length).fill(0); + for (let j = 0; j < m; j++) { + full_assign[idx[j]] = assign[j]; + full_sims[idx[j]] = sims[j]; + } + return { centers, assign: full_assign, sims: full_sims }; +} +function quadrant_anchors(cx, cy, radii = [100, 100, 100, 100]) { + const angs = [Math.PI / 4, 3 * Math.PI / 4, 5 * Math.PI / 4, 7 * Math.PI / 4]; + return angs.map((ang, i) => { + const r = radii[i] ?? radii[radii.length - 1] ?? 100; + return { x: Math.round(cx + r * Math.cos(ang)), y: Math.round(cy + r * Math.sin(ang)) }; + }); +} +function radial_strength_for(ring_r, min_r, max_r) { + if (!Number.isFinite(ring_r) || !Number.isFinite(min_r) || !Number.isFinite(max_r) || min_r >= max_r) { + return 0.45; + } + const t = 1 - (ring_r - min_r) / (max_r - min_r); + return 0.45 + 0.55 * Math.max(0, Math.min(1, t)); +} +function compute_radii(width, height, padding = 24) { + const w = Math.max(1, Number(width) || 100); + const h = Math.max(1, Number(height) || w); + const half_min = Math.min(w, h) / 2; + const min_r_base = Math.round(half_min * 0.18); + const min_r = Math.max(12, min_r_base); + const outer_r_exact = Math.round(w * 0.45); + const outer_r = Math.max(min_r + 8, outer_r_exact); + return { min_r, outer_r, half_min }; +} +function truncate_middle(text = "", max = 70) { + const str = String(text ?? ""); + const limit = Math.max(0, Number.isFinite(max) ? Math.floor(max) : 0); + if (!limit) return ""; + if (str.length <= limit) return str; + if (limit <= 3) return str.slice(0, limit); + const ellipsis = "\u2026"; + const available = limit - ellipsis.length; + const front = Math.ceil(available / 2); + const back = available - front; + return `${str.slice(0, front)}${ellipsis}${str.slice(str.length - back)}`; +} +function label_anchor_offset(node_x, ctx = {}) { + const { + center_x = 0, + label_width = 0, + view_width = 0, + radius = 0, + margin = 10 + } = ctx; + const safe_margin = Math.max(2, margin); + const boundary_min = safe_margin; + const boundary_max = Math.max(boundary_min, view_width - safe_margin); + const anchor = node_x >= center_x ? "start" : "end"; + const base = radius + safe_margin; + if (anchor === "start") { + let offset2 = base; + let abs_start2 = node_x + offset2; + let abs_end2 = abs_start2 + label_width; + if (abs_end2 > boundary_max) { + const delta = abs_end2 - boundary_max; + offset2 -= delta; + abs_start2 -= delta; + } + if (abs_start2 < boundary_min) { + offset2 += boundary_min - abs_start2; + } + return { anchor, offset: offset2 }; + } + let offset = -base; + let abs_end = node_x + offset; + let abs_start = abs_end - label_width; + if (abs_start < boundary_min) { + const delta = boundary_min - abs_start; + offset += delta; + abs_end += delta; + } + if (abs_end > boundary_max) { + offset -= abs_end - boundary_max; + } + return { anchor, offset }; +} +function is_hover_preview_eligible(node = {}) { + if (!node || node.isCenter) return false; + const item = node.item; + if (!item) return false; + return Boolean(item.collection_key && item.key); +} +function resolve_drag_item(node = {}) { + const item = node?.item; + if (!item) return null; + if (!item.collection_key || !item.key) return null; + if (!item.env?.obsidian_app) return null; + return item; +} +function project_anchor_to_ring(cx, cy, ax, ay, r) { + const dx = ax - cx; + const dy = ay - cy; + const len = Math.hypot(dx, dy) || 1; + const ux = dx / len; + const uy = dy / len; + return { + x: Math.round(cx + ux * r), + y: Math.round(cy + uy * r) + }; +} + +// src/components/connections-graph/v1.js +function build_prefixed_key_set(results = []) { + const prefixed_keys = /* @__PURE__ */ new Set(); + for (const result of results) { + const prefixed = prefixed_key_for_item(result?.item); + if (prefixed) prefixed_keys.add(prefixed); + } + return prefixed_keys; +} +function prefixed_key_for_item(item) { + if (!item) return void 0; + return build_prefixed_connection_key(item.collection_key, item.key); +} +function collect_hidden_entries({ + connections_state = {}, + existing_keys = /* @__PURE__ */ new Set(), + resolve_item +} = {}) { + if (typeof resolve_item !== "function") return []; + const hidden_entries = []; + for (const [prefixed_key, state] of Object.entries(connections_state)) { + if (!state?.hidden || state?.pinned) continue; + if (existing_keys.has(prefixed_key)) continue; + const parsed = parse_prefixed_key(prefixed_key); + if (!parsed) continue; + const item = resolve_item(parsed.collection_key, parsed.item_key); + if (!item) continue; + existing_keys.add(prefixed_key); + hidden_entries.push({ + item, + score: null, + is_hidden: true, + prefixed_key + }); + } + return hidden_entries; +} +function build_node_classname({ is_center = false, is_hidden = false, is_pinned = false } = {}) { + const classes = ["sc-graph-node"]; + if (is_center) classes.push("sc-graph-node-center"); + if (is_pinned) classes.push("sc-result-pinned"); + if (is_hidden) classes.push("sc-result-hidden"); + return classes.join(" ").trim(); +} +function parse_prefixed_key(prefixed_key) { + if (typeof prefixed_key !== "string" || !prefixed_key.includes(":")) return null; + const [collection_key, ...rest] = prefixed_key.split(":"); + if (!collection_key || !rest.length) return null; + return { collection_key, item_key: rest.join(":") }; +} +var D3_CDN_URL = "https://cdn.jsdelivr.net/npm/d3@7/+esm"; +var D3_EXPECTED_MAJOR = "7."; +var d3_import_promise = null; +function get_d3_cdn_url() { + return D3_CDN_URL; +} +function validate_d3_instance(d3) { + if (!d3) return; + const version = String(d3.version || ""); + if (version && !version.startsWith(D3_EXPECTED_MAJOR)) { + console.warn( + `[connections_graph] Loaded d3 version "${version}" but expected v${D3_EXPECTED_MAJOR}x` + ); + } +} +async function load_d3() { + const g = typeof activeWindow !== "undefined" ? activeWindow : typeof globalThis !== "undefined" ? globalThis : typeof window !== "undefined" ? window : {}; + if (g.d3) { + validate_d3_instance(g.d3); + return g.d3; + } + if (!d3_import_promise) { + const d3_cdn_url = get_d3_cdn_url(); + d3_import_promise = import(d3_cdn_url).then((d3) => { + validate_d3_instance(d3); + if (!g.d3) g.d3 = d3; + return d3; + }).catch((err) => { + d3_import_promise = null; + throw err; + }); + } + return d3_import_promise; +} +async function build_html28(connections_list, params = {}) { + const to_item = params?.to_item || connections_list?.item; + const width = params.width ?? 100; + const height = params.height ?? 100; + return ` +
    + + + + + +
    +
    + `; +} +async function render30(connections_list, params = {}) { + this.apply_style_sheet(v1_default); + const html = await build_html28.call(this, connections_list, params); + const frag = this.create_doc_fragment(html); + const container = frag.querySelector(".connections-graph"); + post_process27.call(this, connections_list, container, params); + return container; +} +async function post_process27(connections_list, container, params = {}) { + const { + results = await connections_list.get_results(params) + } = params; + try { + const d3 = await load_d3(); + const to_item = params.to_item || connections_list?.item; + if (!to_item) throw new Error("connections_graph: could not resolve center item."); + const env = to_item.env; + const connection_state = to_item?.data?.connections || {}; + const event_key_domain = params.event_key_domain || "connections"; + const drag_event_key = `${event_key_domain}:drag_result`; + const base_prefixed_keys = build_prefixed_key_set(results); + const hidden_entries = collect_hidden_entries({ + connections_state: connection_state, + existing_keys: base_prefixed_keys, + resolve_item: (collection_key, item_key) => connections_list?.env?.[collection_key]?.get(item_key) + }); + const result_entries = [...results, ...hidden_entries]; + const svg = container.querySelector("svg.sc-graph-svg"); + const viewport = svg.querySelector("g.sc-graph-viewport"); + const g_nodes = viewport.querySelector("g.nodes"); + const CENTER_R = 8; + const NODE_R = 5; + const PADDING = 24; + const LABEL_MARGIN = 10; + let sim = null; + let node_sel = null; + const build_label_text = (item) => { + const display = get_item_display_name3(item, { show_full_path: false }) || item?.key || ""; + return truncate_middle(display, 70); + }; + let view_width = +svg.getAttribute("width") || 100; + const layout = () => { + const width = container.clientWidth || +svg.getAttribute("width") || 100; + const height = width; + view_width = width; + svg.setAttribute("viewBox", `0 0 ${width} ${height}`); + svg.setAttribute("width", width); + svg.setAttribute("height", height); + const center_x = Math.round(width * 0.5); + const center_y = Math.round(height * 0.5); + const { min_r, outer_r, half_min } = compute_radii(width, height, PADDING); + const center_vec = is_vec(to_item?.vec) ? to_unit(to_item.vec) : null; + const non_center_vecs = result_entries.map((r) => is_vec(r?.item?.vec) ? to_unit(r.item.vec) : null); + const sims_to_center_raw = non_center_vecs.map((v) => center_vec && v ? Math.max(0, Math.min(1, cos_sim2(center_vec, v))) : null); + const { norm: sims_center_norm } = normalize_scores(sims_to_center_raw); + const center_node = { + id: "__center__", + item: to_item, + score: 1, + radius: CENTER_R, + isCenter: true, + x: center_x, + y: center_y, + fx: center_x, + fy: center_y + }; + const seed = Math.floor(hash_to_unit(result_entries.map((r) => r?.item?.key || "").join("|")) * 1e9); + const { centers, assign } = kmeans_cosine_unit(non_center_vecs, 4, 300, seed); + const center_to_cluster_sims = (centers.length ? centers : [null, null, null, null]).slice(0, 4).map((c) => center_vec && c ? Math.max(0, Math.min(1, cos_sim2(center_vec, c))) : 0.5); + const cluster_anchor_radii = center_to_cluster_sims.map((s) => score_to_radius(s, min_r, outer_r)); + while (cluster_anchor_radii.length < 4) cluster_anchor_radii.push(cluster_anchor_radii[cluster_anchor_radii.length - 1] || Math.round((min_r + outer_r) / 2)); + const anchors = quadrant_anchors(center_x, center_y, cluster_anchor_radii); + const nodes_non_center = result_entries.map((res, i) => { + const r_item = res?.item; + if (!r_item) return null; + const t = sims_center_norm[i] ?? 0.5; + const rr = score_to_radius(t, min_r, outer_r); + const angle = seeded_angle(r_item.key || String(i), -Math.PI / 2); + const x0 = Math.round(center_x + rr * Math.cos(angle)); + const y0 = Math.round(center_y + rr * Math.sin(angle)); + const cluster = Number.isInteger(assign[i]) ? assign[i] : Math.floor(hash_to_unit(r_item.key || String(i)) * 4); + const v = non_center_vecs[i]; + const cluster_vec = centers[cluster] || null; + const node_to_cluster_sim = v && cluster_vec ? Math.max(0, Math.min(1, cos_sim2(v, cluster_vec))) : 0; + const prefixed_key = prefixed_key_for_item(r_item); + const isPinned = prefixed_key ? is_connection_pinned(connection_state, prefixed_key) : false; + const isHidden = Boolean(res?.is_hidden) || (prefixed_key ? is_connection_hidden(connection_state, prefixed_key) : false); + return { + id: r_item.key, + item: r_item, + score: Number.isFinite(res?.score) ? +res.score : center_vec && v ? cos_sim2(center_vec, v) : null, + // display only + ring_r: rr, + angle, + radius: NODE_R, + isCenter: false, + x: x0, + y: y0, + cluster, + node_to_cluster_sim, + label_text: build_label_text(r_item), + prefixed_key, + isPinned, + isHidden + }; + }).filter(Boolean); + const nodes = [center_node, ...nodes_non_center]; + node_sel = d3.select(g_nodes).selectAll("g.sc-graph-node").data(nodes, (d) => d.id).join((enter) => { + const g = enter.append("g").attr("class", (d) => build_node_classname({ + is_center: d.isCenter, + is_hidden: d.isHidden, + is_pinned: d.isPinned + })).attr("title", (d) => (d.item.path || "").replace(/"/g, """)).attr("data-collection", (d) => d.item.collection_key).attr("data-key", (d) => d.item.key).attr("data-path", (d) => (d.item.path || "").replace(/"/g, """)).attr("data-link", (d) => (d.item.link || "").replace(/"/g, """)).attr("data-score", (d) => `${d.score ?? ""}`).attr("data-cluster", (d) => d.isCenter ? "" : String(d.cluster)).attr("data-hidden", (d) => d.isHidden ? "true" : null).attr("data-pinned", (d) => d.isPinned ? "true" : null).attr("data-prefixed-key", (d) => d.prefixed_key || ""); + g.append("circle").attr("r", (d) => d.radius).attr("class", "sc-graph-node-dot"); + g.append("text").attr("class", "sc-score-text").attr("y", -NODE_R - 6).attr("text-anchor", "middle").attr("alignment-baseline", "baseline").text((d) => typeof d.score === "number" && !d.isCenter ? d.score.toFixed(2) : ""); + g.filter((d) => !d.isCenter).append("text").attr("class", "sc-node-label").attr("y", -NODE_R - 6).attr("text-anchor", "middle").attr("alignment-baseline", "baseline").text((d) => d.label_text.replace(".md", "")); + const drag_handle_size = (NODE_R + 6) * 2; + const drag_handle = g.append("foreignObject").attr("class", "sc-node-drag-handle").attr("x", -(drag_handle_size / 2)).attr("y", -(drag_handle_size / 2)).attr("width", drag_handle_size).attr("height", drag_handle_size); + drag_handle.append("xhtml:div").attr("class", "sc-node-drag-handle-inner"); + return g; + }); + node_sel.attr("class", (d) => build_node_classname({ + is_center: d.isCenter, + is_hidden: d.isHidden, + is_pinned: d.isPinned + })).attr("data-hidden", (d) => d.isHidden ? "true" : null).attr("data-pinned", (d) => d.isPinned ? "true" : null).attr("data-prefixed-key", (d) => d.prefixed_key || "").attr("transform", (d) => `translate(${d.x},${d.y})`); + const clear_hover = () => { + node_sel.classed("sc-graph-node-hover", false); + }; + const set_hover = (node) => { + if (!node) return; + clear_hover(); + node.classList.add("sc-graph-node-hover"); + }; + node_sel.on("mouseenter", function() { + clear_hover(); + d3.select(this).classed("sc-graph-node-hover", true); + }).on("mouseleave", function() { + d3.select(this).classed("sc-graph-node-hover", false); + }).on("click", function(event, datum) { + handle_node_click({ + node: datum, + container, + env, + center_item: to_item + }); + }); + node_sel.select("foreignObject.sc-node-drag-handle").select("div.sc-node-drag-handle-inner").each(function(datum) { + if (this.dataset.scDragBound === "true") return; + this.dataset.scDragBound = "true"; + const drag_item = resolve_drag_item(datum); + if (drag_item) register_item_drag(this, drag_item, { drag_event_key }); + if (this.getAttribute("data-sc-hover-preview-bound") !== "true" && is_hover_preview_eligible(datum)) { + this.setAttribute("data-sc-hover-preview-bound", "true"); + register_item_hover_popover(this, datum.item, { event_key_domain }); + } + const node = this.closest("g.sc-graph-node"); + this.addEventListener("mouseenter", () => set_hover(node)); + this.addEventListener("mouseleave", () => { + if (node) node.classList.remove("sc-graph-node-hover"); + }); + this.addEventListener("click", (event) => { + event.preventDefault(); + event.stopPropagation(); + handle_node_click({ + node: datum, + container, + env, + center_item: to_item + }); + }); + }); + const radial_force = d3.forceRadial( + (d) => d.isCenter ? 0 : d.ring_r, + center_x, + center_y + ).strength((d) => d.isCenter ? 1 : radial_strength_for(d.ring_r, min_r, outer_r)); + const collide_force = d3.forceCollide((d) => d.radius + 16).strength(0.9).iterations(2); + const charge_force = d3.forceManyBody().strength(-80).distanceMax(half_min); + const cluster_strength_fn = (d) => { + if (d.isCenter) return 0; + const base = 0.04; + const node_c = Math.max(0, Math.min(1, d.node_to_cluster_sim || 0)); + const center_c = center_to_cluster_sims[(d.cluster ?? 0) % 4] || 0.5; + return base + 0.28 * node_c * (0.5 + 0.5 * center_c); + }; + function force_cluster(anchors_in = [], strength_fn = () => 0.2, cx = 0, cy = 0) { + let nodes_f = []; + function force(alpha) { + for (let i = 0; i < nodes_f.length; i++) { + const d = nodes_f[i]; + if (d.isCenter) continue; + const idx = Number.isInteger(d.cluster) ? d.cluster % anchors_in.length : 0; + const a = anchors_in[idx]; + const s = Math.max(0, Math.min(1, strength_fn(d))); + if (!a || s <= 0) continue; + const pr = project_anchor_to_ring(cx, cy, a.x, a.y, d.ring_r); + d.vx += (pr.x - d.x) * s * alpha; + d.vy += (pr.y - d.y) * s * alpha; + } + } + force.initialize = function(_nodes) { + nodes_f = _nodes; + }; + return force; + } + const cluster_force = force_cluster(anchors, cluster_strength_fn, center_x, center_y); + if (!sim) { + sim = d3.forceSimulation(nodes).alpha(0.95).alphaDecay(0.08).force("radial", radial_force).force("cluster", cluster_force).force("collide", collide_force).force("charge", charge_force).on("tick", () => { + nodes[0].fx = center_x; + nodes[0].fy = center_y; + node_sel.attr("transform", (d) => `translate(${d.x},${d.y})`); + node_sel.select("text.sc-node-label").each(function(d) { + if (d.isCenter) return; + const node = this; + const label_width = typeof node.getComputedTextLength === "function" ? node.getComputedTextLength() : 0; + const { anchor, offset } = label_anchor_offset(d.x, { + center_x, + label_width, + view_width, + radius: d.radius, + margin: LABEL_MARGIN + }); + d3.select(node).attr("text-anchor", anchor).attr("x", offset); + }); + }); + } else { + sim.nodes(nodes); + sim.force("radial", radial_force); + sim.force("cluster", cluster_force); + sim.force("collide", collide_force); + sim.force("charge", charge_force); + nodes[0].fx = center_x; + nodes[0].fy = center_y; + sim.alpha(0.8).restart(); + } + }; + layout(); + const ro = new ResizeObserver(() => layout()); + ro.observe(container); + } catch (err) { + console.error("[connections_graph] post_process error:", err); + const fallback = activeDocument.createElement("p"); + fallback.className = "sc-no-results"; + fallback.textContent = "Unable to render graph. See console for details."; + container.appendChild(fallback); + } + return container; +} +function handle_node_click({ node, container, env, center_item }) { + if (!node?.item) return; + const detail = build_result_detail(node, center_item); + if (!detail) return; + if (typeof CustomEvent === "function" && container?.dispatchEvent) { + container.dispatchEvent(new CustomEvent("connections:result", { + detail, + bubbles: true + })); + } + env?.events?.emit?.("connections:result", detail); + node.item.emit_event?.("connections:result", detail); + node.item.emit_event?.("connections:open_result", detail); +} +function build_result_detail(node, center_item) { + const collection_key = node?.item?.collection_key; + const item_key = node?.item?.key; + if (!collection_key || !item_key) return null; + return { + collection_key, + item_key, + prefixed_key: node.prefixed_key || prefixed_key_for_item(node.item) || build_prefixed_connection_key(collection_key, item_key), + score: typeof node.score === "number" ? node.score : null, + is_hidden: Boolean(node.isHidden), + is_pinned: Boolean(node.isPinned), + event_source: "connections-graph-v1", + center_key: center_item?.key + }; +} + +// src/components/connections-list-item/v3.js +var import_obsidian54 = require("obsidian"); + +// src/components/connections-list-item/v3.css +var v3_default = ".sc-result a.sc-result-file-title {\n text-decoration: none;\n}\n\n.sc-result a.sc-result-file-title > small.sc-score {\n display: inline-block;\n width: auto;\n height: 1.7em;\n padding: 0 0.3em;\n line-height: 1.7em;\n text-align: center;\n font-weight: 600;\n font-size: var(--font-smallest);\n color: var(--nav-item-color);\n background: var(--background-modifier-hover);\n border-radius: 6px;\n margin-right: 0.2em;\n}\n\na.sc-result-file-title > small.sc-title {\n font-weight: 500;\n text-decoration: underline;\n}\n\na.sc-result-file-title > .sc-breadcrumb:not(.sc-title, .sc-score) {\n font-style: italic;\n}\n\na.sc-result-file-title > small.sc-breadcrumb-separator {\n color: color-mix(in srgb, var(--text-normal) 50%, transparent);\n}\n\n.sc-result.sc-result-hidden-by-feedback {\n display: none;\n}\n\n.sc-result.sc-result-graph-focus {\n outline: 2px solid color-mix(in srgb, var(--interactive-accent) 70%, transparent);\n border-radius: var(--radius-s);\n box-shadow:\n 0 0 0 1px color-mix(in srgb, var(--background-primary) 80%, transparent),\n 0 0 0.5rem color-mix(in srgb, var(--interactive-accent) 45%, transparent);\n transition: outline 120ms ease, box-shadow 120ms ease;\n}\n\n\n/* hide separator if immediately preceded by score */\n.sc-result {\n small.sc-score + .sc-breadcrumb-separator {\n display: none;\n }\n}"; + +// src/components/connections-list-item/v3.js +var SC_RESULT_HIDDEN_CLASS = "sc-result-hidden-by-feedback"; +async function build_html29(result, params = {}) { + const item = result.item; + const env = item.env; + const score = result.score; + const connections_settings = params.connections_settings ?? env.connections_lists.settings; + const component_settings = connections_settings.components?.connections_list_item_v3 || {}; + const header_html = get_result_header_html(score, item, component_settings); + const all_expanded = connections_settings.expanded_view; + return `
    +
    + + ${this.get_icon_html("right-triangle")} + + ${header_html} + + +
      +
    • +
    +
    +
    `; +} +async function render31(result_scope, params = {}) { + this.apply_style_sheet(v3_default); + let html = await build_html29.call(this, result_scope, params); + const frag = this.create_doc_fragment(html); + const container = frag.querySelector(".sc-result"); + post_process28.call(this, result_scope, container, params); + return container; +} +async function post_process28(result_scope, container, params = {}) { + const { item } = result_scope; + const env = item.env; + const plugin = env.smart_connections_plugin; + const app2 = plugin.app; + const connections_settings = params.connections_settings ?? env.connections_lists.settings; + const component_settings = connections_settings.components?.connections_list_item_v3 || {}; + const should_render_markdown = component_settings?.render_markdown ?? true; + if (!should_render_markdown) container.classList.add("sc-result-plaintext"); + const source_item = result_scope.connections_list?.item; + const prefixed_key = build_prefixed_connection_key(item.collection_key, item.key); + container.dataset.prefixedKey = prefixed_key; + const connection_state = source_item?.data?.connections; + if (is_connection_hidden(connection_state, prefixed_key)) { + container.classList.add(SC_RESULT_HIDDEN_CLASS); + container.dataset.hidden = "true"; + } + if (is_connection_pinned(connection_state, prefixed_key)) { + container.classList.add("sc-result-pinned"); + container.dataset.pinned = "true"; + } + const render_result = async (_result_elm) => { + if (!_result_elm.querySelector("li").innerHTML) { + const collection_key = _result_elm.dataset.collection; + const entity = env[collection_key].get(_result_elm.dataset.path); + let markdown; + if (should_render_embed(entity)) markdown = `${entity.embed_link} + +${await entity.read()}`; + else markdown = process_for_rendering(await entity.read()); + let entity_frag; + if (should_render_markdown) entity_frag = await this.render_markdown(markdown, entity); + else entity_frag = this.create_doc_fragment(markdown); + container.querySelector("li").appendChild(entity_frag); + } + }; + const toggle_fold_elm = container.querySelector(".header .svg-icon.right-triangle"); + toggle_fold_elm.addEventListener("click", toggle_result); + const event_key_domain = params.event_key_domain || "connections"; + const drag_event_key = `${event_key_domain}:drag_result`; + register_item_drag(container, item, { drag_event_key }); + register_item_hover_popover(container, item, { event_key_domain }); + container.addEventListener("click", (event) => { + open_source(item, event); + item.emit_event(`${event_key_domain}:open_result`, { event_source: "connections-list-item-v3" }); + }); + const observer = new MutationObserver((mutations) => { + const has_expansion_change = mutations.some((mutation) => { + const target = mutation.target; + return mutation.attributeName === "class" && mutation.oldValue?.includes("sc-collapsed") !== target.classList.contains("sc-collapsed"); + }); + if (has_expansion_change && !mutations[0].target.classList.contains("sc-collapsed")) { + render_result(mutations[0].target); + } + }); + observer.observe(container, { + attributes: true, + attributeOldValue: true, + attributeFilter: ["class"] + }); + plugin.registerDomEvent(container, "contextmenu", (event) => { + event.preventDefault(); + event.stopPropagation(); + if (!source_item) return; + source_item.data.connections ||= {}; + const prefixed_key2 = build_prefixed_connection_key( + item.collection_key, + item.key + ); + const pinned = is_connection_pinned(source_item.data.connections, prefixed_key2); + const hidden_count = count_hidden_connections(source_item.data.connections); + const pinned_count = count_pinned_connections(source_item.data.connections); + const results = result_scope.connections_list?.results || []; + const target_name = get_item_display_name3(item, component_settings) || item.key; + const menu = new import_obsidian54.Menu(app2); + menu.addItem((menu_item) => { + menu_item.setTitle(`Hide ${target_name}`).setIcon("eye-off").onClick(() => { + try { + apply_hidden_state(source_item.data.connections, prefixed_key2, Date.now()); + if (source_item.data.hidden_connections) { + delete source_item.data.hidden_connections[item.key]; + if (!Object.keys(source_item.data.hidden_connections).length) delete source_item.data.hidden_connections; + } + source_item.queue_save(); + container.classList.add(SC_RESULT_HIDDEN_CLASS); + container.dataset.hidden = "true"; + source_item.collection.save(); + source_item.emit_event("connections:hidden_item"); + } catch (err) { + env?.events?.emit?.("connections:hide_failed", { + level: "error", + message: "Hide failed \u2013 check console", + details: err?.message || "", + event_source: "connections_list_item.contextmenu" + }); + console.error(err); + } + }); + }); + menu.addItem((menu_item) => { + const title_prefix = pinned ? "Unpin" : "Pin"; + menu_item.setTitle(`${title_prefix} ${target_name}`).setIcon(pinned ? "pin-off" : "pin").onClick(() => { + try { + if (pinned) { + remove_pinned_state(source_item.data.connections, prefixed_key2); + container.classList.remove("sc-result-pinned"); + container.removeAttribute("data-pinned"); + } else { + apply_pinned_state(source_item.data.connections, prefixed_key2, Date.now()); + container.classList.add("sc-result-pinned"); + container.dataset.pinned = "true"; + source_item.emit_event("connections:pinned_item"); + } + source_item.queue_save(); + source_item.collection.save(); + } catch (err) { + env?.events?.emit?.("connections:pin_toggle_failed", { + level: "error", + message: `${title_prefix} failed \u2013 check console`, + details: err?.message || "", + event_source: "connections_list_item.contextmenu" + }); + console.error(err); + } + }); + }); + menu.addSeparator(); + const links_payload = format_connections_as_links(results); + if (links_payload) { + menu.addItem((menu_item) => { + menu_item.setTitle("Copy as list of links").setIcon("copy").onClick(async () => { + await copy_to_clipboard(links_payload, { + env, + event_source: "connections_list_item.copy_as_links", + success_event_key: "connections:list_copied", + error_event_key: "connections:list_copy_failed", + unavailable_event_key: "connections:list_copy_unavailable" + }); + result_scope.connections_list.emit_event("connections:copied_list"); + }); + }); + } + menu.addSeparator(); + menu.addItem((menu_item) => { + menu_item.setTitle(`Unhide All (${hidden_count})`).setIcon("eye").setDisabled(!hidden_count).onClick(() => { + try { + if (!source_item.data.connections) return; + const changed = remove_all_hidden_states(source_item.data.connections); + if (!changed) return; + if (source_item.data.hidden_connections) delete source_item.data.hidden_connections; + source_item.queue_save(); + container.closest(".sc-connections-view")?.querySelector('[title="Refresh"]')?.click(); + source_item.collection.save(); + } catch (err) { + env?.events?.emit?.("connections:unhide_failed", { + level: "error", + message: "Unhide failed \u2013 check console", + details: err?.message || "", + event_source: "connections_list_item.contextmenu" + }); + console.error(err); + } + }); + }); + menu.addItem((menu_item) => { + menu_item.setTitle(`Unpin All (${pinned_count})`).setIcon("pin-off").setDisabled(!pinned_count).onClick(() => { + try { + if (!source_item.data.connections) return; + const changed = remove_all_pinned_states(source_item.data.connections); + if (!changed) return; + const list_root = container.closest(".connections-list"); + list_root?.querySelectorAll(".sc-result[data-pinned]").forEach((result_el) => { + result_el.classList.remove("sc-result-pinned"); + result_el.removeAttribute("data-pinned"); + }); + source_item.queue_save(); + source_item.collection.save(); + } catch (err) { + env?.events?.emit?.("connections:unpin_failed", { + level: "error", + message: "Unpin failed \u2013 check console", + details: err?.message || "", + event_source: "connections_list_item.contextmenu" + }); + console.error(err); + } + }); + }); + menu.showAtMouseEvent(event); + }); + if (!container.classList.contains("sc-collapsed")) { + render_result(container); + } + return container; +} +function get_result_header_html(score, item, component_settings = {}) { + const raw_parts = get_item_display_name3(item, component_settings).split(DISPLAY_SEPARATOR).filter(Boolean); + const parts = format_item_parts(raw_parts, item?.lines); + const name = parts.pop(); + const formatted_score = typeof score === "number" ? score.toFixed(2) : score; + const separator = ' > '; + const parts_html = parts.map((part) => `${part}`).join(separator); + return [ + `${formatted_score}`, + `${parts_html}${separator}`, + `${name.endsWith(".md") ? name.replace(/\.md$/, "") : name}` + ].join(""); +} +function format_item_parts(parts, lines = []) { + if (!Array.isArray(parts) || !parts.length) return []; + const has_line_marker = Array.isArray(lines) && lines.length; + return parts.map((part) => { + if (has_line_marker && part.startsWith("{")) { + return `Lines: ${lines.join("-")}`; + } + return part; + }); +} +function should_render_embed(entity) { + if (!entity) return false; + if (entity.is_media) return true; + return false; +} +function process_for_rendering(content) { + if (content.includes("```dataview")) content = content.replace(/```dataview/g, "```\\dataview"); + if (content.includes("```smart-context")) content = content.replace(/```smart-context/g, "```\\smart-context"); + if (content.includes("```smart-chatgpt")) content = content.replace(/```smart-chatgpt/g, "```\\smart-chatgpt"); + if (content.includes("![[")) content = content.replace(/\!\[\[/g, "! [["); + return content; +} +function toggle_result(event) { + event.preventDefault(); + event.stopPropagation(); + const _result_elm = event.target.closest(".sc-result"); + _result_elm.classList.toggle("sc-collapsed"); +} +var settings_config11 = { + "show_full_path": { + name: "Show full path", + type: "toggle", + description: "Turning on will include the folder path in the connections results.", + // default: true, + group: "Connections list item" + }, + "render_markdown": { + name: "Render markdown", + type: "toggle", + description: "Turn off to prevent rendering markdown and display connection results as plain text.", + // default: true, + group: "Connections list item" + } +}; + +// src/components/connections-list/v3.js +async function build_html30(connections_list, opts = {}) { + return `
    `; +} +async function render32(connections_list, opts = {}) { + const html = await build_html30.call(this, connections_list, opts); + const frag = this.create_doc_fragment(html); + const container = frag.querySelector(".connections-list"); + post_process29.call(this, connections_list, container, opts); + return container; +} +async function post_process29(connections_list, container, opts = {}) { + container.dataset.key = connections_list.item.key; + const results = await connections_list.get_results(opts); + if (!results || !Array.isArray(results) || results.length === 0) { + const no_results = this.create_doc_fragment(`

    No results found.
    Try using the refresh button. If that doesn't work, try running "Clear sources data" and then "Reload sources" in the Smart Environment settings.

    `); + container.appendChild(no_results); + return container; + } + const smart_components = connections_list.env.smart_components; + const result_frags = await Promise.all(results.map((result) => { + return smart_components.render_component("connections_list_item_v3", result, { ...opts }); + })); + result_frags.forEach((result_frag) => container.appendChild(result_frag)); + return container; +} +var display_name5 = "List only"; + +// src/components/connections-list/v4.js +async function build_html31(connections_list, opts = {}) { + return `
    +
    +
    +
    `; +} +async function render33(connections_list, opts = {}) { + const html = await build_html31.call(this, connections_list, opts); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process30.call(this, connections_list, container, opts); + return container; +} +async function post_process30(connections_list, container, opts = {}) { + const env = connections_list.env; + const graph_container = container.querySelector(".connections-graph-container"); + const list_container = container.querySelector(".connections-list.sc-list"); + container.dataset.key = connections_list.item.key; + const results = await connections_list.get_results(opts); + const connections_settings = opts.connections_settings ?? env.connections_lists.settings; + const component_settings = connections_settings.components?.connections_list_v4 || {}; + const show_graph = opts.show_graph ?? component_settings.show_graph; + if (show_graph) { + try { + const graph = await env.smart_components.render_component("connections_graph_v1", connections_list, { ...opts, results }); + this.empty(graph_container); + graph_container.appendChild(graph); + register_graph_events(graph, list_container); + } catch (_err) { + this.empty(graph_container); + const error_message = this.create_doc_fragment(`

    Unable to load graph visualization: ${typeof _err?.message === "string" ? _err.message : "Unknown error"}

    `); + graph_container.appendChild(error_message); + } + } + if (!results || !Array.isArray(results) || results.length === 0) { + const no_results = this.create_doc_fragment(`

    No results found.
    Try using the refresh button. If that doesn't work, try running "Clear sources data" and then "Reload sources" in the Smart Environment settings.

    `); + list_container.appendChild(no_results); + return container; + } + const smart_components = connections_list.env.smart_components; + const result_frags = await Promise.all(results.map((result) => { + return smart_components.render_component("connections_list_item_v3", result, { ...opts }); + })); + result_frags.forEach((result_frag) => list_container.appendChild(result_frag)); + return container; +} +var GRAPH_FOCUS_CLASS = "sc-result-graph-focus"; +var GRAPH_FOCUS_TIMEOUT_MS = 2400; +function register_graph_events(graph, list_container) { + if (!graph || !list_container) return; + graph.addEventListener("connections:result", (event) => { + focus_result_from_graph(list_container, event?.detail || {}); + }); +} +function focus_result_from_graph(list_container, detail = {}) { + const target = find_result_element(list_container, detail); + if (!target) return; + if (target.classList.contains("sc-collapsed")) target.classList.remove("sc-collapsed"); + target.scrollIntoView?.({ block: "center", behavior: "smooth" }); + target.classList.add(GRAPH_FOCUS_CLASS); + window.setTimeout?.(() => target.classList.remove(GRAPH_FOCUS_CLASS), GRAPH_FOCUS_TIMEOUT_MS); +} +function find_result_element(list_container, detail = {}) { + if (!list_container) return null; + const { collection_key, item_key } = detail; + if (!collection_key || !item_key) return null; + return Array.from(list_container.querySelectorAll(".sc-result")).find((node) => { + return node.dataset.collection === collection_key && node.dataset.key === item_key; + }) || null; +} +var display_name6 = "Version 4.0 (Graph + List)"; +var settings_config12 = { + "show_graph": { + name: "Show graph", + type: "toggle", + description: "Show a graph visualization of the connections above the list.", + // default: true, + group: "Connections lists" + } +}; + +// src/components/connections-settings/header.js +var import_obsidian55 = require("obsidian"); +async function build_html32(scope_plugin) { + return ` +
    +
    +
    + + + + +
    +
    +
    + `; +} +async function render34(scope_plugin) { + const html = await build_html32.call(this, scope_plugin); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process31.call(this, scope_plugin, container); + return container; +} +async function post_process31(scope_plugin, frag) { + const user_agreement_container = frag.querySelector("[data-user-agreement]"); + if (user_agreement_container) { + const user_agreement = await scope_plugin.env.smart_components.render_component( + "user_agreement_callout", + scope_plugin + ); + user_agreement_container.appendChild(user_agreement); + } + const header_link = frag.querySelector("#header-callout a"); + if (header_link) { + header_link.addEventListener("click", (e) => { + e.preventDefault(); + window.open(header_link.href, "_external"); + }); + } + frag.querySelector(".sc-getting-started-button")?.addEventListener("click", () => { + StoryModal.open(scope_plugin, { + title: "Getting Started With Smart Connections", + url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=sc-op-settings" + }); + }); + frag.querySelector(".sc-report-bug-button")?.addEventListener("click", () => { + if (scope_plugin.env?.is_pro) { + new ScProSupportModal(scope_plugin.app).open(); + return; + } + window.open( + "https://github.com/brianpetro/obsidian-smart-connections/issues/new?template=bug_report.yml", + "_external" + ); + }); + frag.querySelector(".sc-request-feature-button")?.addEventListener("click", () => { + window.open( + "https://github.com/brianpetro/obsidian-smart-connections/issues/new?template=feature_request.yml", + "_external" + ); + }); + frag.querySelector(".sc-share-workflow-button")?.addEventListener("click", () => { + window.open( + "https://github.com/brianpetro/obsidian-smart-connections/discussions/new?category=showcase", + "_external" + ); + }); + const smart_lookup_header_callout = frag.querySelector("#smart-lookup-header-callout"); + if (smart_lookup_header_callout) { + const smart_lookup_callout = await scope_plugin.env.smart_components.render_component( + "connections_settings_lookup_callout", + scope_plugin + ); + smart_lookup_header_callout.appendChild(smart_lookup_callout); + } + return frag; +} +var ScProSupportModal = class extends import_obsidian55.Modal { + open() { + super.open(); + this.titleEl.setText("Need help and support?"); + const content = this.contentEl.createDiv({ cls: "sc-pro-support-modal" }); + content.createEl("p", { + text: "Reply to your Pro plugins welcome email for priority support." + }); + const reportBugButton = content.createEl("button", { text: "Report a bug", cls: "mod-warning" }); + reportBugButton.addEventListener("click", () => { + window.open( + "https://github.com/brianpetro/obsidian-smart-connections/issues/new?template=bug_report.yml", + "_external" + ); + }); + const closeButton = content.createEl("button", { text: "Close" }); + closeButton.addEventListener("click", () => { + this.close(); + }); + } +}; + +// src/components/connections-settings/lookup_callout.js +var import_obsidian56 = require("obsidian"); +function build_html33(plugin, opts = {}) { + return `
    + +
    `; +} +function render35(plugin, params = {}) { + const html = build_html33.call(this, plugin, params); + const frag = this.create_doc_fragment(html); + const container = frag.firstElementChild; + post_process32.call(this, plugin, container, params); + return container; +} +function post_process32(plugin, container) { + const icon_container = container.querySelector(".callout-icon"); + (0, import_obsidian56.setIcon)(icon_container, "smart-lookup"); + const has_lookup = plugin.app.plugins.enabledPlugins.has("smart-lookup"); + const content_container = container.querySelector(".callout-content"); + const callout_text = content_container.querySelector("p"); + const callout_btn = content_container.querySelector("button"); + if (has_lookup) { + callout_text.textContent = "Lookup now has its own settings tab."; + callout_btn.textContent = "Open lookup settings"; + callout_btn.addEventListener("click", () => { + plugin.app.setting.openTabById("smart-lookup"); + }); + } else { + callout_text.textContent = "Lookup is moving to a dedicated plugin. Please install to continue using lookup features and access lookup settings."; + callout_btn.textContent = "Install"; + callout_btn.addEventListener("click", () => install_smart_lookup(plugin)); + } +} +async function install_smart_lookup(plugin) { + const app2 = plugin.app; + const env = plugin.env; + const adapter = app2.vault.adapter; + async function download_and_write(url, _path) { + try { + const resp = await (0, import_obsidian56.requestUrl)({ + url, + method: "GET" + }); + await adapter.write(_path, resp.text); + return true; + } catch (error) { + console.error(`Failed to download or write file from ${url} to ${_path}:`, error); + env.events.emit("plugin:install_failed", { + level: "error", + message: `Failed to download or write Smart Lookup file "${_path.split("/").slice(-1)[0]}" from "${url}"`, + event_source: "install_smart_lookup" + }); + return false; + } + } + const { json: response } = await (0, import_obsidian56.requestUrl)({ + url: "https://api.github.com/repos/brianpetro/smart-lookup-obsidian/releases/latest", + method: "GET", + headers: { + "Content-Type": "application/json" + }, + contentType: "application/json" + }); + const assets = response.assets; + const main_asset = assets.find((asset) => asset.name === "main.js"); + const manifest_asset = assets.find((asset) => asset.name === "manifest.json"); + const styles_asset = assets.find((asset) => asset.name === "styles.css"); + if (!main_asset || !manifest_asset || !styles_asset) { + env.events.emit("plugin:install_failed", { + level: "error", + message: "Failed to find necessary assets for Smart Lookup. Installation failed.", + event_source: "install_smart_lookup" + }); + return; + } + const main_url = main_asset.browser_download_url; + const manifest_url = manifest_asset.browser_download_url; + const styles_url = styles_asset.browser_download_url; + const plugin_folder = `${app2.vault.configDir}/plugins/smart-lookup`; + if (!await adapter.exists(plugin_folder)) { + await adapter.mkdir(plugin_folder); + } + const results = await Promise.all([ + download_and_write(main_url, `${plugin_folder}/main.js`), + download_and_write(manifest_url, `${plugin_folder}/manifest.json`), + download_and_write(styles_url, `${plugin_folder}/styles.css`) + ]); + if (results.some((result) => result === false)) { + return; + } + await app2.plugins.loadManifests(); + if (!app2.plugins.enabledPlugins.has("smart-lookup")) { + enable_plugin(app2, "smart-lookup"); + } + env.events.emit("plugin:install_completed", { + level: "info", + message: "Smart Lookup installed.", + event_source: "install_smart_lookup" + }); +} + +// src/components/connections-view/v3.css +var v3_default2 = ".connections-view-early {\n .connections-top-bar {\n display: flex;\n gap: 0.5rem 1rem;\n\n .connections-actions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n flex: 0 0 auto;\n }\n\n .sc-context {\n display: flex;\n flex-direction: column;\n margin: 0;\n gap: 0.1rem;\n width: auto;\n flex: 1 1 auto;\n\n .sc-context-line {\n line-height: 1.1;\n }\n\n .sc-context-line--parent {\n color: var(--text-muted);\n font-size: 0.85em;\n }\n\n .sc-context-line--focus {\n color: var(--text-normal);\n font-size: 1.05em;\n font-weight: 600;\n }\n }\n }\n .connections-list {\n padding-top: 0.85rem;\n\n > .sc-collapsed ul {\n display: none;\n }\n > .sc-collapsed span svg {\n transform: rotate(-90deg);\n }\n > .sc-result-pinned {\n box-shadow: 0 0 0 1px var(--interactive-accent);\n border-radius: var(--radius-s);\n background-color: var(--background-modifier-hover);\n }\n }\n}"; + +// src/components/connections-view/v3.js +var import_obsidian57 = require("obsidian"); + +// src/utils/context_lines.js +var BLOCK_SEPARATOR = "#"; +var DISPLAY_SEPARATOR2 = " \u203A "; +var PATH_SEPARATOR = "/"; +function get_context_lines(item) { + const key = item.key; + let top_line = ""; + let bottom_line = ""; + let parts = []; + if (key.includes(BLOCK_SEPARATOR)) { + parts = key.split(BLOCK_SEPARATOR); + bottom_line = parts.pop().trim(); + if (bottom_line[0] === "{") { + const lines = item.lines.join("-"); + bottom_line = parts.pop().trim() + ` Lines: ${lines}`; + } + top_line = parts.filter(Boolean).join(BLOCK_SEPARATOR); + } else if (key.includes(PATH_SEPARATOR)) { + parts = key.split(PATH_SEPARATOR); + bottom_line = parts.pop().trim(); + } + top_line = parts.filter(Boolean).join(DISPLAY_SEPARATOR2); + return [top_line, bottom_line]; +} + +// src/utils/connections_view_refresh_handler.js +async function connections_view_refresh_handler(event) { + const view_container = event.target.closest(".connections-view"); + const list_el = view_container?.querySelector(".connections-list"); + const entity_key = list_el?.dataset?.key; + const refresh_entity = this.env.smart_sources.get(entity_key); + if (refresh_entity) { + await refresh_entity.read(); + refresh_entity.queue_import(); + await refresh_entity.collection.process_source_import_queue?.(); + this.render_view(refresh_entity); + } else { + } +} + +// src/components/connections-view/v3.js +async function build_html34(view, opts = {}) { + const is_paused = Boolean(view.paused); + const pause_title = is_paused ? "Resume auto-refresh" : "Pause auto-refresh"; + const top_bar_buttons = [ + { + title: pause_title, + icon: is_paused ? "play-circle" : "pause-circle", + attrs: `data-action="toggle-pause" aria-pressed="${is_paused}"` + }, + { + title: "More actions", + icon: "menu", + attrs: 'data-action="open-menu"' + } + ].map((btn) => ` + + `).join(""); + const html = `
    +
    +
    + ${top_bar_buttons} +
    +

    + Loading... +

    +
    +
    +
    +
    `; + return html; +} +async function render36(view, opts = {}) { + const html = await build_html34.call(this, view, opts); + const frag = this.create_doc_fragment(html); + this.apply_style_sheet(v3_default2); + const container = frag.querySelector(".sc-connections-view"); + post_process33.call(this, view, container, opts); + return frag; +} +async function post_process33(view, container, opts = {}) { + const list_container = container.querySelector(".connections-list-container"); + const sc_top_bar_context = container.querySelector(".sc-top-bar .sc-context"); + const env = view.env; + let connections_item = opts.connections_item; + if (!connections_item) { + list_container.textContent = "No source item detected for current active view."; + return container; + } + let connections_list = connections_item.connections || env.connections_lists.new_item(connections_item); + if (!container._has_listeners) { + container._has_listeners = true; + const pause_button = container.querySelector('[data-action="toggle-pause"]'); + pause_button?.addEventListener("click", () => { + view.toggle_connections_paused(); + }); + const open_help = () => { + StoryModal.open(view.plugin, { + title: "Getting Started With Smart Connections", + url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=connections-view-help#page=understanding-connections-1" + }); + }; + const menu_button = container.querySelector('[data-action="open-menu"]'); + menu_button?.addEventListener("click", (event) => { + const menu = new import_obsidian57.Menu(view.plugin.app); + const raw_results = Array.isArray(connections_list?.results) ? connections_list.results : []; + const connections_state = connections_list?.item?.data?.connections || {}; + const visible_results = filter_hidden_results(raw_results, connections_state); + menu.addItem((menu_item) => { + menu_item.setTitle("Refresh connections").setIcon("refresh-cw").onClick(() => { + connections_view_refresh_handler.call(view, { target: container }); + }); + }); + menu.addItem((menu_item) => { + const context_items = build_connections_context_items({ + source_item: connections_item, + results: visible_results + }); + menu_item.setTitle("Send results to context").setIcon("briefcase").setDisabled(!context_items.length).onClick(async () => { + if (!context_items.length) { + env.events.emit("connections:send_to_context_empty", { + level: "warning", + message: "No connection results to send to context.", + event_source: "connections_view_menu" + }); + return; + } + const smart_context = env.smart_contexts.new_context(); + smart_context.add_items(context_items); + smart_context.emit_event("context_selector:open"); + connections_list.emit_event("connections:sent_to_context"); + }); + }); + menu.addItem((menu_item) => { + const links_payload = format_connections_as_links(visible_results); + menu_item.setTitle("Copy as list of links").setIcon("copy").setDisabled(!links_payload).onClick(async () => { + if (!links_payload) { + env.events.emit("connections:copy_list_empty", { + level: "warning", + message: "No connection results to copy.", + event_source: "connections_view_menu" + }); + return; + } + await copy_to_clipboard(links_payload); + connections_list.emit_event("connections:copied_list"); + }); + }); + menu.addItem((menu_item) => { + const connections_settings = opts.connections_settings ?? connections_list?.settings; + const expanded = connections_settings?.expanded_view; + const title = expanded ? "Collapse all results" : "Expand all results"; + const icon_name = expanded ? "fold-vertical" : "unfold-vertical"; + menu_item.setTitle(title).setIcon(icon_name).onClick(() => { + const curr_settings = opts.connections_settings ?? connections_list?.settings; + const curr_expanded = curr_settings?.expanded_view; + if (curr_settings) curr_settings.expanded_view = !curr_expanded; + container.querySelectorAll(".sc-result").forEach((element) => { + curr_expanded ? element.classList.add("sc-collapsed") : element.classList.remove("sc-collapsed"); + }); + }); + }); + env.build_menu?.("connections_list", menu, connections_list); + menu.addSeparator(); + menu.addItem((menu_item) => { + menu_item.setTitle("Connections settings").setIcon("settings").onClick(() => { + view.open_settings(); + }); + }); + menu.addItem((menu_item) => { + menu_item.setTitle("Help & getting started").setIcon("help-circle").onClick(open_help); + }); + menu.showAtMouseEvent(event); + }); + } + const connections_list_component_key = opts.connections_list_component_key || connections_list.connections_list_component_key || "connections_list_v4"; + const list = await env.smart_components.render_component(connections_list_component_key, connections_list, opts); + this.empty(list_container); + list_container.appendChild(list); + const entity = connections_list?.item || connections_item; + const [top_line, bottom_line] = get_context_lines(entity); + this.empty(sc_top_bar_context); + const doc = sc_top_bar_context.ownerDocument; + const context_spans = [ + { text: top_line, class_name: "sc-context-line sc-context-line--parent" }, + { text: bottom_line, class_name: "sc-context-line sc-context-line--focus" } + ]; + context_spans.forEach((line) => { + const span = doc.createElement("span"); + span.className = line.class_name; + span.textContent = line.text || "\xA0"; + sc_top_bar_context.appendChild(span); + }); + sc_top_bar_context.dataset.key = connections_item.key; + const pause_btn = container.querySelector('[data-action="toggle-pause"]'); + if (pause_btn) { + const update_pause_button = (paused) => { + const next_title = paused ? "Resume auto-refresh" : "Pause auto-refresh"; + pause_btn.title = next_title; + pause_btn.setAttribute("aria-label", `${next_title}`); + pause_btn.setAttribute("aria-pressed", String(paused)); + this.safe_inner_html( + pause_btn, + this.get_icon_html(paused ? "play-circle" : "pause-circle") + ); + }; + update_pause_button(Boolean(view.paused)); + view.register_pause_controls({ update: update_pause_button }); + } + return container; +} + +// src/actions/connections-list/pre_process.js +function pre_process2(params) { + if (!params.limit) params.limit = this.settings?.results_limit ?? 20; + if (!params.results_collection_key) { + params.results_collection_key = this.collection.results_collection_key; + } + if (!params.filter) params.filter = {}; + if (!params.score_algo_key) params.score_algo_key = this.collection.score_algo_key; + params.to_item = this.item; + if (!params.filter.exclude_keys) params.filter.exclude_keys = []; + if (!params.filter.exclude_key_starts_with_any) { + params.filter.exclude_key_starts_with_any = []; + } + if (!params.filter.frontmatter) params.filter.frontmatter = {}; + if (!params.filter.frontmatter.include) { + params.filter.frontmatter.include = this.collection.frontmatter_inclusions; + } + if (!params.filter.frontmatter.exclude) { + params.filter.frontmatter.exclude = this.collection.frontmatter_exclusions; + } + get_connections_feedback_items(this, params); + const exclude_keys_set = new Set(params.filter.exclude_keys); + exclude_keys_set.add(this.item.key); + params.hidden_keys.forEach((key) => exclude_keys_set.add(key)); + params.pinned_keys.forEach((key) => exclude_keys_set.add(key)); + params.filter.exclude_keys = Array.from(exclude_keys_set); + if (params.results_collection_key === "smart_blocks") { + const exclude_starts_set = new Set(params.filter.exclude_key_starts_with_any); + exclude_starts_set.add(this.item.key); + params.hidden_keys.forEach((key) => exclude_starts_set.add(key)); + params.pinned_keys.forEach((key) => exclude_starts_set.add(key)); + params.filter.exclude_key_starts_with_any = Array.from(exclude_starts_set); + if (this.collection.settings.exclude_frontmatter_blocks) { + if (!params.filter.exclude_key_ends_with_any || !Array.isArray(params.filter.exclude_key_ends_with_any)) { + params.filter.exclude_key_ends_with_any = []; + } + params.filter.exclude_key_ends_with_any.push("---frontmatter---"); + } + } +} +function get_connections_feedback_items(connections_list, params) { + params.hidden = []; + params.hidden_keys = []; + params.pinned = []; + params.pinned_keys = []; + const connections_state = connections_list.item.data?.connections || {}; + Object.entries(connections_state).forEach(([key, state]) => { + if (!state || state.hidden == null && state.pinned == null) return; + const [collection_key, ...item_key_parts] = key.split(":"); + if (!collection_key || !item_key_parts.length) return; + const item_key = item_key_parts.join(":"); + const collection = connections_list.env[collection_key]; + if (!collection) return; + const item = collection.get(item_key); + if (!item) return; + if (state.hidden && !state.pinned) { + params.hidden.push(item); + params.hidden_keys.push(item_key); + } + if (state.pinned) { + params.pinned.push(item); + params.pinned_keys.push(item_key); + } + }); +} + +// smart_env.config.js +var smart_env_config3 = { + collections: { + connections_lists: connections_lists_default + }, + items: { + connections_list: { class: ConnectionsList, version: "2.4.6" } + }, + modules: {}, + components: { + connections_codeblock: { render: render28, version: "2.4.6" }, + connections_footer_view: { render: render29, version: "2.4.6" }, + connections_graph_v1: { render: render30, version: "2.4.6" }, + connections_list_item_v3: { render: render31, settings_config: settings_config11, version: "2.4.6" }, + connections_list_v3: { render: render32, display_name: display_name5, version: "2.4.6" }, + connections_list_v4: { render: render33, settings_config: settings_config12, display_name: display_name6, version: "2.4.6" }, + connections_settings_header: { render: render34, version: "2.4.6" }, + connections_settings_lookup_callout: { render: render35, version: "2.4.6" }, + connections_view_v3: { render: render36, version: "2.4.6" } + }, + actions: { + connections_list_pre_process: { action: pre_process2, pre_process: pre_process2, version: "2.4.6" } + } +}; + +// node_modules/obsidian-smart-env/utils/open_note.js +var import_obsidian58 = require("obsidian"); +async function open_note(plugin, target_path, event = null, opts = {}) { + const { new_tab = false } = opts; + const env = plugin.env; + if (target_path.endsWith("#")) target_path = target_path.slice(0, -1); + let target_file; + let block = null; + if (target_path.includes("#")) { + const [file_path] = target_path.split("#"); + target_file = plugin.app.metadataCache.getFirstLinkpathDest(file_path, ""); + block = env.smart_blocks.get(target_path); + } else { + target_file = plugin.app.metadataCache.getFirstLinkpathDest(target_path, ""); + } + if (!target_file) { + console.warn(`[open_note] Unable to resolve file for ${target_path}`); + return; + } + let leaf; + if (event) { + const is_mod = import_obsidian58.Keymap.isModEvent(event); + const is_alt = import_obsidian58.Keymap.isModifier(event, "Alt"); + if (is_mod && is_alt) { + leaf = plugin.app.workspace.splitActiveLeaf("vertical"); + } else if (is_mod || new_tab) { + leaf = plugin.app.workspace.getLeaf(true); + } else { + leaf = plugin.app.workspace.getMostRecentLeaf(); + } + } else { + leaf = plugin.app.workspace.getMostRecentLeaf(); + } + await leaf.openFile(target_file); + if (typeof block?.line_start === "number") { + const { editor } = leaf.view; + const pos = { line: block.line_start, ch: 0 }; + editor.setCursor(pos); + editor.scrollIntoView({ to: pos, from: pos }, true); + } +} + +// src/views/settings_tab.js +var ScEarlySettingsTab = class extends SmartPluginSettingsTab { + constructor(app2, plugin) { + super(app2, plugin); + this.plugin = plugin; + } + hide() { + super.hide?.(); + this.plugin_container?.empty?.(); + this.turn_off_listener?.(); + } + async render_header(container) { + const header = await this.env.smart_components.render_component("connections_settings_header", this.plugin); + container.appendChild(header); + } + async render_plugin_settings(container) { + if (!container) return; + container.empty?.(); + container.createDiv({ text: "Loading main settings...", cls: "sc-loading" }); + container.empty?.(); + const cl_container = container.createDiv({ + cls: "sc-settings-tab__section", + attr: { "data-section-key": "connections_lists" } + }); + cl_container.createEl("h1", { text: "Connections" }); + const connections_lists_settings_config = this.env.config.collections.connections_lists.settings_config; + render_settings_config( + connections_lists_settings_config, + this.env.connections_lists, + cl_container, + { + default_group_name: "Connections lists", + group_params: { + "Connections lists": { + heading_btn: [ + { + label: "Learn about Connections Lists", + btn_text: "Learn more", + callback: () => window.open("https://smartconnections.app/smart-connections/list-feature/?utm_source=connections-settings-tab", "_external") + }, + { + label: "Settings documentation for Connections Lists", + btn_icon: "help-circle", + callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#connections-lists", "_external") + } + ], + order: 1 + }, + "Display": { + heading_btn: { + label: "Settings documentation for Display", + btn_icon: "help-circle", + callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#display", "_external") + }, + order: 2 + }, + "Connections list item": { + heading_btn: { + label: "Settings documentation for Connections List Items", + btn_icon: "help-circle", + callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#connections-list-item", "_external") + }, + order: 3 + }, + "Score algorithm": { + heading_btn: { + label: "Settings documentation for Score Algorithms", + btn_icon: "help-circle", + callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#score-algorithm", "_external") + }, + order: 4 + }, + "Ranking algorithm": { + heading_btn: { + label: "Settings documentation for Ranking Algorithms", + btn_icon: "help-circle", + callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#ranking-algorithm", "_external") + }, + order: 5 + }, + "Connections filters": { + heading_btn: { + label: "Settings documentation for Filters", + btn_icon: "help-circle", + callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#filters", "_external") + }, + order: 6 + }, + "Inline connections": { + heading_btn: [ + { + label: "Learn about the inline connections feature", + btn_text: "Learn more", + callback: () => window.open("https://smartconnections.app/smart-connections/inline/?utm_source=connections-settings-tab", "_external") + }, + { + label: "Settings documentation for inline connections", + btn_icon: "help-circle", + callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#inline-connections", "_external") + } + ], + order: 7 + }, + "Footer connections": { + heading_btn: { + label: "Settings documentation for Footer Connections", + btn_icon: "help-circle", + callback: () => window.open("https://smartconnections.app/smart-connections/settings/?utm_source=connections-settings-tab#footer-connections", "_external") + }, + order: 8 + } + } + } + ); + this.register_env_events(); + } + register_env_events() { + if (this.turn_off_listener || !this.env?.events) return; + this.turn_off_listener = this.env.events.on("settings:changed", (event) => { + if (event.path?.includes("connections_post_process") || event.path?.includes("score_algo_key") || event.path?.includes("connections_list_item")) { + this.render_plugin_settings(this.plugin_container); + } + }); + } +}; + +// node_modules/obsidian-smart-env/views/release_notes_view.js +var import_obsidian59 = require("obsidian"); +var ReleaseNotesView = class extends SmartItemView { + static view_type = "smart-release-notes-view"; + static display_text = "Release Notes"; + static icon_name = "file-text"; + static plugin_id = ""; + static release_notes_md = ""; + static open(workspace, version, active = true) { + const leaf = workspace.getLeaf("tab"); + leaf.setViewState({ type: this.view_type, active, state: { version } }); + } + onOpen() { + this.titleEl.setText(`What's new in v${this.version}? (${this.constructor.plugin_id})`); + this.render(); + } + get container() { + const content = this.containerEl?.querySelector(".view-content"); + let preview = content?.querySelector(".markdown-preview-view"); + if (!preview) { + const main = content?.createDiv("cm-scroller is-readable-line-width"); + preview = main?.createDiv("markdown-preview-view markdown-rendered"); + } + return preview; + } + async render() { + while (!this.container) { + await new Promise((resolve) => setTimeout(resolve, 100)); + console.warn("Waiting for containerEl to be ready...", this.container); + } + await import_obsidian59.MarkdownRenderer.render( + this.app, + this.constructor.release_notes_md, + this.container, + "", + this + ); + this.container.querySelectorAll("a").forEach((a) => { + a.setAttribute("target", "_external"); + }); + requestAnimationFrame(() => this.scroll_to_version()); + } + get version() { + const view_version = this.leaf.viewState?.state?.version; + if (view_version) return view_version; + const plugin_id = this.constructor.plugin_id; + return this.app.plugins.getPlugin(plugin_id)?.manifest.version ?? ""; + } + scroll_to_version() { + const matcher = build_version_matcher(this.version); + if (!matcher) return; + this.container.querySelectorAll("h1,h2,h3,h4,h5,h6").forEach((heading) => { + if (!heading_matches_version({ matcher, heading_text: heading.textContent })) return; + heading.scrollIntoView({ behavior: "smooth", block: "start" }); + }); + } +}; +function escape_regex(value) { + return value.replace(/[.*+?^${}()|[\]\\]/g, "\\$&"); +} +function build_version_matcher(version) { + if (!version) return null; + const safe_version = escape_regex(version); + return new RegExp(`\\bv?${safe_version}\\b`, "i"); +} +function heading_matches_version({ matcher, heading_text }) { + if (!matcher) return false; + return matcher.test(heading_text ?? ""); +} + +// releases/latest_release.md +var latest_release_default = "# Smart Connections `v4.5`\n\n> [!NOTE] What's new in `v4.5.3`\n> refactor: update D3 load to use ESM bundle and improve loading logic\n> refactor: update CSS selectors for improved specificity and remove redundant styles\n\n## Connections Footer is now a Core feature!\n[Footer connections](https://smartconnections.app/smart-connections/footer/) are now included in Smart Connections Core, bringing the most mobile-friendly and no-panel writing surface to every install. Connections Pro continues to add inline discovery, Bases workflows, and advanced ranking control.\n- Place your connections list in the footer of every note.\n- Toggle footer connections from the command palette (hotkey), ribbon icon, or settings.\n\n## Recent highlights\n- Connections lists can now open with a graph view.\n- [Substrate Update.](https://smartconnections.app/smart-plugins/substrate-update/)\n\nUpdated: 2026-06-04\n\n[More details about the latest releases](https://smartconnections.app/smart-connections/releases/4-5/)\n"; + +// src/views/release_notes_view.js +var ReleaseNotesView2 = class extends ReleaseNotesView { + static view_type = "smart-release-notes-view"; + static plugin_id = "smart-connections"; + static release_notes_md = latest_release_default; +}; + +// src/utils/get_random_connection.js +var DEFAULT_RESULTS_LIMIT = 20; +async function get_random_connection(env, file_path, { rng = Math.random } = {}) { + if (!env?.smart_sources || !file_path) return null; + const source = env.smart_sources.get(file_path); + if (!source?.should_embed) return null; + const connections = await source.connections.get_results({ limit: DEFAULT_RESULTS_LIMIT }); + if (!Array.isArray(connections) || connections.length === 0) return null; + return pick_weighted_connection(connections, { rng }); +} +function pick_weighted_connection(connections, { rng }) { + const scored_connections = connections.map((connection) => ({ + connection, + score: Math.max(0, typeof connection?.score === "number" ? connection.score : 0) + })); + const total_score = scored_connections.reduce((sum, { score }) => sum + score, 0); + if (total_score === 0) return connections[0]; + const threshold = rng() * total_score; + let cumulative = 0; + for (const { connection, score } of scored_connections) { + cumulative += score; + if (threshold < cumulative) return connection; + } + return connections.at(-1); +} + +// src/utils/add_icons.js +var import_obsidian60 = require("obsidian"); +function add_smart_dice_icon() { + (0, import_obsidian60.addIcon)("smart-dice", ` + + + + + + + + + + + + +`); +} + +// src/utils/view_leaf_location.js +var is_descendant_of = (node, ancestor) => { + let current = node; + while (current) { + if (current === ancestor) { + return true; + } + current = current.parent; + } + return false; +}; +var get_leaf_location = ({ workspace, leaf }) => { + if (!workspace || !leaf) { + return "root"; + } + const { leftSplit, rightSplit } = workspace; + if (is_descendant_of(leaf, leftSplit)) { + return "left"; + } + if (is_descendant_of(leaf, rightSplit)) { + return "right"; + } + return "root"; +}; +var should_relocate_leaf = ({ workspace, leaf, desired_location }) => { + if (!leaf || desired_location !== "left" && desired_location !== "right") { + return false; + } + const leaf_location = get_leaf_location({ workspace, leaf }); + return leaf_location !== desired_location; +}; + +// src/utils/pause_controls.js +function apply_pause_state(target, paused) { + const value = Boolean(paused); + target.paused = value; + target.pause_controls?.update?.(value); + return value; +} +function toggle_pause_state(target) { + const next = !target.paused; + target.paused = next; + target.pause_controls?.update?.(next); + return next; +} + +// src/views/connections_item_view.js +var ConnectionsItemView = class extends SmartItemView { + static get view_type() { + return "smart-connections-view"; + } + static get display_text() { + return "Connections"; + } + static get icon_name() { + return "smart-connections"; + } + constructor(leaf, plugin) { + super(leaf, plugin); + this.paused = false; + this.pause_controls = null; + this.current = null; + } + async render_view(params = {}, container = this.container) { + if (!params.connections_item) { + const active_path = this.plugin.app.workspace.getActiveFile()?.path; + params.connections_item = this.env.smart_sources.get(active_path); + } + this.current = params.connections_item; + this.pause_controls = null; + const frag = await this.env.smart_components.render_component("connections_view_v3", this, { + connections_item: params.connections_item + }); + container.empty(); + container.appendChild(frag); + this.register_env_listeners(); + this.env.events.emit("connections:opened"); + } + async open_settings() { + await this.app.setting.open(); + await this.app.setting.openTabById(this.plugin.manifest.id); + } + register_env_listeners() { + let handle_current_source_debounce; + register_env_event_listener(this, "sources:opened", (event = {}) => { + if (this.paused) return; + if (!is_visible(this.container)) return; + const connections_item = this.env[event.collection_key || "smart_sources"]?.get(event.item_key || event.key); + if (connections_item.key === this.current?.key) return; + if (handle_current_source_debounce) window.clearTimeout(handle_current_source_debounce); + handle_current_source_debounce = window.setTimeout(() => { + this.render_view({ connections_item }); + }, 250); + }); + register_env_event_listener(this, "settings:changed", (event) => { + if (event.path?.includes("expanded_view")) return; + if (event.path?.includes("connections_lists") && is_visible(this.container)) { + this.render_view({ connections_item: this.current }); + } + }); + register_env_event_listener(this, "connections:show", (event) => { + if (event.collection_key && event.item_key) { + const collection = this.env[event.collection_key]; + const item = collection.get(event.item_key); + if (item) { + this.set_connections_paused(true); + this.render_view({ connections_item: item }); + } + } + }); + register_env_event_listener(this, "items:embedded", (event = {}) => { + if (event.collection_key === this.current?.collection_key && event.keys?.includes(this.current?.key) && is_visible(this.container)) { + this.render_view({ connections_item: this.current }); + } + }); + } + /** + * Register UI controls for reflecting pause state. + * @param {{ update: (paused: boolean) => void }} controls + */ + register_pause_controls(controls) { + this.pause_controls = controls; + this.pause_controls?.update(this.paused); + } + /** + * Set the paused state and sync UI controls. + * @param {boolean} paused + * @returns {boolean} + */ + set_connections_paused(paused) { + return apply_pause_state(this, paused); + } + /** + * Toggle the paused state and sync UI controls. + * @returns {boolean} + */ + toggle_connections_paused() { + return toggle_pause_state(this); + } +}; +function is_visible(container) { + if (!container) { + return false; + } + if (!container.isConnected) { + console.warn("Connections container is not connected to DOM"); + return false; + } + if (typeof container.checkVisibility === "function" && container.checkVisibility() === false) { + return false; + } + return true; +} +var view_event_registry = /* @__PURE__ */ new WeakMap(); +var get_registry = (view) => { + if (!view_event_registry.has(view)) { + view_event_registry.set(view, /* @__PURE__ */ new Map()); + } + return view_event_registry.get(view); +}; +var register_env_event_listener = (view, event_key, callback) => { + if (!view || typeof view.env?.events?.on !== "function") { + console.warn("View or event system not available for registering event listener"); + return () => { + }; + } + const registry = get_registry(view); + const previous_dispose = registry.get(event_key); + if (typeof previous_dispose === "function") { + previous_dispose(); + } + const off = view.env.events.on(event_key, (event) => { + callback(event); + }); + let active = true; + const dispose = () => { + if (!active) return; + active = false; + off?.(); + if (registry.get(event_key) === dispose) { + registry.delete(event_key); + } + }; + registry.set(event_key, dispose); + if (typeof view.register === "function") { + view.register(() => dispose()); + } + return dispose; +}; + +// src/views/connections_footer_deco.js +var import_view = require("@codemirror/view"); +var import_state = require("@codemirror/state"); +var set_connections_footer_dom_effect = import_state.StateEffect.define(); +var FOOTER_HIDDEN_CLASS = "sc-connections-footer-hidden"; +var connections_footer_plugin = import_view.ViewPlugin.fromClass( + class { + /* ------------------------------------------------------ lifecycle ---- */ + constructor(view) { + this.view = view; + this.connections_footer_frag = null; + this.container_el = null; + } + destroy() { + if (this.container_el?.isConnected) { + this.container_el.remove(); + } + } + /* ----------------------------------------------------- view updates -- */ + update(update) { + for (const tr of update.transactions) { + for (const ef of tr.effects) { + if (ef.is(set_connections_footer_dom_effect)) { + if (ef.value === null) { + this.#set_footer_visibility(false); + } else { + this.render_footer(ef.value); + } + } + } + } + } + render_footer(container = null) { + if (container) { + if (this.container_el && this.container_el !== container && this.container_el.isConnected) { + this.container_el.remove(); + } + this.connections_footer_frag = container; + this.container_el = container; + this.#ensure_dom_inserted(); + } + if (!this.container_el) return; + this.#set_footer_visibility(Boolean(this.connections_footer_frag)); + } + #set_footer_visibility(visible) { + if (!this.container_el) return; + this.container_el.classList.toggle(FOOTER_HIDDEN_CLASS, !visible); + } + #ensure_dom_inserted() { + if (!this.container_el || this.container_el.isConnected) return; + const cm_container = this.view.dom.querySelector(".cm-contentContainer"); + cm_container?.parentNode?.insertBefore( + this.container_el, + cm_container.nextSibling + ); + } + } +); + +// src/views/connections_footer_view.js +var import_obsidian61 = require("obsidian"); +var get_active_entity = (env, workspace) => { + const active_file = workspace.getActiveFile(); + if (!active_file) return null; + return env.smart_sources?.get(active_file.path) || null; +}; +var is_last_line_visible = (editor_view) => { + try { + if (!editor_view?.state?.doc) return false; + const doc = editor_view.state.doc; + const last_line = doc.line(doc.lines); + const start = last_line.from; + const end = last_line.to; + return (Array.isArray(editor_view.visibleRanges) ? editor_view.visibleRanges : []).some((range) => range.from <= end && range.to >= start); + } catch (err) { + console.warn("[Smart Connections] last-line visibility check failed", err); + return false; + } +}; +var schedule_next_frame = (callback) => { + if (typeof window.requestAnimationFrame === "function") { + window.requestAnimationFrame(callback); + return; + } + window.setTimeout(callback, 0); +}; +var ConnectionsFooterView = class { + constructor(plugin) { + this.plugin = plugin; + this.app = plugin.app; + this.register_env_listeners(); + this.container_map = {}; + this._detach_visibility_guard = null; + } + get env() { + return this.plugin.env; + } + /** + * Attach a lightweight scroll/resize guard that waits until the last line becomes visible. + * When the condition is met, it detaches itself and triggers render_view() again. + * @param {import('@codemirror/view').EditorView} editor_view + */ + attach_visibility_guard(editor_view) { + this.detach_visibility_guard(); + if (!editor_view) return; + const scroll_target = editor_view.scrollDOM || editor_view.dom || null; + if (!scroll_target) return; + let ticking = false; + const check_and_render = () => { + if (ticking) return; + ticking = true; + schedule_next_frame(() => { + ticking = false; + if (is_last_line_visible(editor_view)) { + this.detach_visibility_guard(); + this.render_view(); + } + }); + }; + const on_scroll = () => check_and_render(); + const on_resize = () => check_and_render(); + scroll_target.addEventListener("scroll", on_scroll, { passive: true }); + window.addEventListener("resize", on_resize); + this._detach_visibility_guard = () => { + try { + scroll_target.removeEventListener("scroll", on_scroll); + } catch { + } + try { + window.removeEventListener("resize", on_resize); + } catch { + } + this._detach_visibility_guard = null; + }; + } + detach_visibility_guard() { + if (typeof this._detach_visibility_guard === "function") { + this._detach_visibility_guard(); + } + } + async render_view() { + if (!this.env.connections_lists?.settings?.footer_connections) return this.remove(); + const editor_view = this.plugin.get_editor_view(); + if (!editor_view) return; + if (!is_last_line_visible(editor_view)) { + try { + editor_view.dispatch({ effects: [set_connections_footer_dom_effect.of(null)] }); + } catch (err) { + console.warn("[Smart Connections] footer hide dispatch failed", err); + } + this.attach_visibility_guard(editor_view); + return; + } + this.detach_visibility_guard(); + const entity = get_active_entity(this.env, this.app.workspace); + if (!entity) { + editor_view.dispatch({ effects: [set_connections_footer_dom_effect.of(null)] }); + return; + } + if (this.container_map[entity.key]?.isConnected) { + editor_view.dispatch({ effects: [set_connections_footer_dom_effect.of(this.container_map[entity.key])] }); + return; + } + const footer_container = await render29.call( + this.env.smart_view, + this, + { + connections_item: entity + } + ); + if (this.container_map[entity.key] && this.container_map[entity.key] instanceof HTMLElement) { + this.container_map[entity.key].remove(); + } + this.container_map[entity.key] = footer_container; + editor_view.dispatch({ effects: [set_connections_footer_dom_effect.of(footer_container)] }); + if (import_obsidian61.Platform.isMobile) { + const container = this.container_map[entity.key]; + const status_bar_container = container.querySelector(".status-bar-mobile") ?? container.createDiv({ cls: "status-bar-mobile" }); + status_bar_container.empty(); + const status_bar_item = status_bar_container.createDiv({ cls: "status-bar-item" }); + this.env.smart_components.render_component("status_bar", this.env).then((el) => status_bar_item.appendChild(el)); + } + } + remove() { + const editor_view = this.plugin.get_editor_view(); + this.detach_visibility_guard(); + if (editor_view) { + try { + editor_view.dispatch({ effects: [set_connections_footer_dom_effect.of(null)] }); + } catch (err) { + console.warn("[Smart Connections] footer remove dispatch failed", err); + } + } + } + register_env_listeners() { + let handle_current_source_debounce; + this.register_env_listener("sources:opened", () => { + if (handle_current_source_debounce) window.clearTimeout(handle_current_source_debounce); + handle_current_source_debounce = window.setTimeout(() => { + this.render_view(); + }, 250); + }); + this.register_env_listener("settings:changed", (event) => { + if (event.path?.includes("connections_lists")) { + this.render_view(); + } + }); + } + register_env_listener(event_key, callback) { + if (!this.env_listeners) this.env_listeners = []; + this.env_listeners.push(this.env.events.on(event_key, callback)); + } + async open_settings() { + await this.app.setting.open(); + await this.app.setting.openTabById(this.plugin.manifest.id); + } + unload() { + this.detach_visibility_guard(); + if (this.env_listeners) { + this.env_listeners.forEach((off) => off()); + this.env_listeners = []; + } + } +}; + +// src/views/connections_codeblock.js +async function register_smart_connections_codeblock(plugin) { + plugin.registerMarkdownCodeBlockProcessor( + "smart-connections", + async (cb_content, container, mpp_ctx) => { + container.empty(); + container.createEl("span", { text: "Loading\u2026" }); + const cb_config = JSON.parse(cb_content.trim() || "{}"); + const env = plugin.env; + const entity = env.smart_sources.get(mpp_ctx.sourcePath) ?? env.smart_sources.init_file_path(mpp_ctx.sourcePath); + const smart_view = env.smart_view; + if (!entity) { + container.empty(); + container.createEl("p", { text: "Entity not found: " + mpp_ctx.sourcePath }); + return; + } + const render_codeblock = async () => { + const connections_list = entity.connections; + if (!connections_list?.env) { + container.empty(); + container.createEl("p", { text: "Loading connections environment..." }); + const retry_button = container.createEl("button", { text: "Retry" }); + retry_button.addEventListener("click", () => { + render_codeblock(); + }); + return; + } + const connections_container = await plugin.env.smart_components.render_component( + "connections_codeblock", + connections_list, + { + ...cb_config + // FUTURE: handling codeblock config options + } + ); + container.empty(); + container.appendChild(connections_container); + }; + if (!container._has_listeners) { + container._has_listeners = true; + const disposers = []; + disposers.push(env.events.on("settings:changed", (event) => { + if (event.path?.includes("connections_lists")) { + render_codeblock(); + } + })); + smart_view.attach_disposer(container, disposers); + } + render_codeblock(); + } + ); +} + +// src/utils/build_connections_codeblock.js +function build_connections_codeblock(settings = null) { + const json = settings ? JSON.stringify(settings, null, 2) : ""; + return `\`\`\`smart-connections +${json} +\`\`\` +`; +} + +// src/main.js +var { + Plugin: Plugin2, + requestUrl: requestUrl6, + Platform: Platform14 +} = import_obsidian62.default; +var SmartConnectionsPlugin = class extends SmartPlugin { + SmartEnv = SmartEnv2; + ReleaseNotesView = ReleaseNotesView2; + get smart_env_config() { + if (!this._smart_env_config) { + this._smart_env_config = { ...smart_env_config3 }; + } + return this._smart_env_config; + } + ConnectionsSettingsTab = ScEarlySettingsTab; + get item_views() { + return { + ConnectionsItemView, + ReleaseNotesView: this.ReleaseNotesView + // DEPRECATED 2026-05-14: Smart Lookup is a standalone plugin available in plugin index. + // Keep the legacy Connections-hosted Lookup view disabled to avoid importing smart-lookup-obsidian here. + // ...(!this.app.plugins.enabledPlugins.has('smart-lookup') ? { ConnectionsLookupItemView } : {}), + }; + } + get obsidian() { + return import_obsidian62.default; + } + get api() { + return this._api; + } + onload() { + this.app.workspace.onLayoutReady(this.initialize.bind(this)); + this.SmartEnv.create(this, this.smart_env_config); + this.addSettingTab(new this.ConnectionsSettingsTab(this.app, this)); + add_smart_dice_icon(); + this.register_commands(); + this.register_item_views(); + this.register_ribbon_icons(); + } + onunload() { + console.log("Unloading Smart Connections plugin"); + this.connections_footer_view?.unload(); + this.notices?.unload(); + this.env?.unload_main?.(this); + } + async initialize() { + this.smart_connections_view = null; + this.is_new_user().then(async (is_new) => { + if (!is_new) return; + window.setTimeout(() => { + StoryModal.open(this, { + title: "Getting Started With Smart Connections", + url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=sc-op-new-user" + }); + }, 1e3); + await this.SmartEnv.wait_for({ loaded: true }); + window.setTimeout(() => { + this.apply_connections_view_location(); + this.open_connections_view(); + }, 1e3); + this.add_to_gitignore("\n\n# Ignore Smart Environment folder\n.smart-env"); + }); + await this.SmartEnv.wait_for({ loaded: true }); + this.wrap_connections_view_open(); + this.apply_connections_view_location(); + this.register_connections_view_location_listener(); + register_smart_connections_codeblock(this); + if (!this.connections_footer_view) { + this.registerEditorExtension(connections_footer_plugin); + this.connections_footer_view = new ConnectionsFooterView(this); + } + this.toggled_footer_connections(); + await this.check_for_updates(); + } + get ribbon_icons() { + return { + connections: { + icon_name: "smart-connections", + description: "Smart Connections: Open connections view", + callback: () => { + this.open_connections_view(); + } + }, + footer_connections: { + description: "Toggle Footer Connections", + icon_name: "smart-footer-connections", + callback: () => { + const settings = this.env.connections_lists.settings; + settings.footer_connections = !settings.footer_connections; + } + }, + random_note: { + icon_name: "smart-dice", + description: "Smart Connections: Open random connection", + callback: () => { + this.open_random_connection(); + } + } + // DEPRECATED 2026-05-14: Smart Lookup is a standalone plugin available in plugin index. + // The legacy Smart Connections Lookup ribbon icon depended on ConnectionsLookupItemView. + // ...(app.plugins.enabledPlugins.has('smart-lookup') + // ? {} + // : { + // lookup: { + // icon_name: "smart-lookup", + // description: "Smart Lookup: Open lookup view", + // callback: () => { this.open_lookup_view_connections(); } + // }, + // } + // ), + }; + } + get settings() { + return this.env?.settings || {}; + } + /** + * Sync connections view location with settings. + * @returns {void} + */ + apply_connections_view_location() { + const connections_view_location = this.env?.connections_lists?.settings?.connections_view_location ?? "right"; + ConnectionsItemView.default_open_location = connections_view_location === "left" ? "left" : "right"; + this.ensure_connections_view_leaf_location(); + } + wrap_connections_view_open() { + if (this._open_connections_view_base || typeof this.open_connections_view !== "function") { + return; + } + this._open_connections_view_base = this.open_connections_view.bind(this); + this.open_connections_view = (...args) => { + this.ensure_connections_view_leaf_location(); + return this._open_connections_view_base(...args); + }; + } + ensure_connections_view_leaf_location() { + const workspace = this.app?.workspace; + if (!workspace) { + return; + } + const desired_location = ConnectionsItemView.default_open_location; + const connections_leaf = ConnectionsItemView.get_leaf(workspace); + if (!should_relocate_leaf({ workspace, leaf: connections_leaf, desired_location })) { + return; + } + connections_leaf.detach(); + } + register_connections_view_location_listener() { + if (this.connections_view_location_listener || !this.env?.events) return; + this.connections_view_location_listener = this.env.events.on("settings:changed", (event) => { + if (!event?.path?.includes?.("connections_view_location")) return; + this.apply_connections_view_location(); + }); + } + async check_for_updates() { + if (await this.is_new_plugin_version(this.manifest.version)) { + console.log("opening release notes modal"); + try { + this.ReleaseNotesView.open(this.app.workspace, this.manifest.version); + } catch (error) { + console.error("Failed to open ReleaseNotesView", error); + } + await this.set_last_known_version(this.manifest.version); + } + window.setTimeout(this.check_for_update.bind(this), 3e3); + } + async check_for_update() { + try { + const { json: response } = await requestUrl6({ + url: "https://api.github.com/repos/brianpetro/obsidian-smart-connections/releases/latest", + method: "GET", + headers: { + "Content-Type": "application/json" + }, + contentType: "application/json" + }); + const latest_release = response.tag_name; + if (latest_release !== this.manifest.version) { + if (!this.update_available || this.latest_release_version !== latest_release) { + this.env?.events?.emit("plugin:new_version_available", { + level: "attention", + message: `Smart Connections ${latest_release} is available.`, + version: latest_release, + event_source: "check_for_update" + }); + } + this.latest_release_version = latest_release; + this.update_available = true; + } + } catch (error) { + console.error(error); + } + } + get commands() { + return { + ...super.commands, + random_connection: { + id: "smart-connections-random", + name: "Open: Random note from connections", + callback: async () => { + await this.open_random_connection(); + } + }, + getting_started: { + id: "smart-connections-getting-started", + name: "Show: Getting started slideshow", + callback: () => { + StoryModal.open(this, { + title: "Getting Started With Smart Connections", + url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=sc-op-command" + }); + } + }, + insert_connections_codeblock: { + id: "insert-connections-codeblock", + name: "Insert: Connections codeblock", + editorCallback: (editor) => { + editor.replaceSelection(build_connections_codeblock()); + } + }, + toggle_footer_connections: { + id: "toggle-footer-connections", + name: "Toggle: Footer connections", + callback: () => { + const settings = this.env.connections_lists.settings; + settings.footer_connections = !settings.footer_connections; + } + } + }; + } + async open_random_connection() { + const curr_file = this.app.workspace.getActiveFile(); + if (!curr_file) { + this.env?.events?.emit("connections:open_random_unavailable", { + level: "warning", + message: "No active file to find connections for.", + event_source: "open_random_connection" + }); + return; + } + const rand_entity = await get_random_connection(this.env, curr_file.path); + if (!rand_entity) { + this.env?.events?.emit("connections:open_random_unavailable", { + level: "warning", + message: `Cannot open random connection for non-embedded source: ${curr_file.path}`, + event_source: "open_random_connection" + }); + return; + } + this.open_note(rand_entity.item.path); + this.env?.events?.emit?.("connections:open_random"); + } + /** + * Attempts to retrieve the CodeMirror 6 EditorView for the active markdown file. + * @returns {EditorView|null} + */ + get_editor_view() { + const file = this.app.workspace.getActiveFile(); + if (!file) { + console.log("Smart Connections: No active file found"); + return null; + } + const markdown_view = this.app.workspace.getActiveFileView(); + if (!markdown_view) { + console.log("Smart Connections: No active file view found"); + return null; + } + return markdown_view.editor?.cm || null; + } + toggled_footer_connections() { + const view = this.get_editor_view(); + if (view && this.env.connections_lists.settings.footer_connections) { + this.connections_footer_view?.render_view(); + } else { + this.connections_footer_view?.remove(); + } + } + async open_note(target_path, event = null) { + await open_note(this, target_path, event); + } + /** + * @deprecated extract into utility + */ + async add_to_gitignore(ignore, message = null) { + if (!await this.app.vault.adapter.exists(".gitignore")) return; + let gitignore_file = await this.app.vault.adapter.read(".gitignore"); + if (gitignore_file.indexOf(ignore) < 0) { + await this.app.vault.adapter.append(".gitignore", ` + +${message ? "# " + message + "\n" : ""}${ignore}`); + console.log("Added to .gitignore: " + ignore); + } + } +}; + +/* nosourcemap */ \ No newline at end of file diff --git a/.obsidian/plugins/smart-connections/manifest.json b/.obsidian/plugins/smart-connections/manifest.json new file mode 100644 index 00000000..47662cc6 --- /dev/null +++ b/.obsidian/plugins/smart-connections/manifest.json @@ -0,0 +1,10 @@ +{ + "id": "smart-connections", + "name": "Smart Connections", + "author": "Brian Petro", + "description": "AI link discovery copilot. See related notes as you write. Lookup using semantic (vector) search across your vault. Zero-setup local model for embeddings, no API keys, private.", + "minAppVersion": "1.1.0", + "authorUrl": "https://smartconnections.app", + "isDesktopOnly": false, + "version": "4.5.3" +} \ No newline at end of file diff --git a/.obsidian/plugins/smart-connections/styles.css b/.obsidian/plugins/smart-connections/styles.css new file mode 100644 index 00000000..b2c2f03f --- /dev/null +++ b/.obsidian/plugins/smart-connections/styles.css @@ -0,0 +1,645 @@ +/* deprecated positioning, use bottom bar instead */ +.view-content > .sc-brand { + position: fixed; + bottom: 0; + right: 0; + background-color: var(--titlebar-background); +} + +.sc-brand { + > svg, + > p { + display: inline; + margin: 0 0.1rem 0 0.3rem; + color: var(--text-muted); + font-size: var(--font-smallest); + line-height: 1; + height: 0.88rem; + width: auto; + } + + > p > a { + color: var(--text-muted); + } +} + +.sc-list { + padding-bottom: 20px; + + .tree-item-self { + cursor: pointer; + + small { + color: var(--color-gray-40); + } + } + + > .sc-collapsed ul { + display: none; + } + + > .sc-collapsed span svg { + transform: rotate(-90deg); + } + + > :not(.sc-collapsed) span svg { + transform: rotate(0deg); + } + + > div { + span svg { + height: auto; + margin: auto 0.5em auto 0; + flex: none; + } + + > span { + display: inline-flex; + width: 100%; + padding-left: 0; + } + + ul { + margin: 0; + padding-left: 1.3rem; + } + + > a { + display: block; + } + + > ul > li { + display: block; + } + } + .sc-result { + > ul { + list-style: none; + padding-left: 0; + } + } + + .sc-result.sc-result-plaintext { + font-size: var(--font-ui-smaller); + line-height: var(--line-height-tight); + background-color: var(--search-result-background); + border-radius: var(--radius-s); + overflow: hidden; + margin: var(--size-4-1) 0 var(--size-4-2); + color: var(--text-muted); + box-shadow: 0 0 0 1px var(--background-modifier-border); + + & > * li { + cursor: var(--cursor); + position: relative; + padding: var(--size-4-2) var(--size-4-5) var(--size-4-2) var(--size-4-3); + white-space: pre-wrap; + width: 100%; + border-bottom: 1px solid var(--background-modifier-border); + } + } + + .sc-result:not(.sc-result-plaintext) { + cursor: pointer; + padding: var(--nav-item-padding); + padding-left: 0; + margin-bottom: 1px; + align-items: baseline; + border-radius: var(--radius-s); + font-weight: var(--nav-item-weight); + + &:hover { + color: var(--nav-item-color-active); + background-color: var(--nav-item-background-active); + font-weight: var(--nav-item-weight-active); + } + + span { + color: var(--h5-color); + } + + small { + color: var(--h5-color); + font-size: 0.8rem; + } + + p { + margin-top: 0.3rem; + margin-bottom: 0.3rem; + } + + ul > li { + h1 { + font-size: 1.3rem; + } + + h2 { + font-size: 1.25rem; + } + + h3 { + font-size: 1.2rem; + } + + h4 { + font-size: 1.15rem; + } + + h5 { + font-size: 1.1rem; + } + + h6 { + font-size: 1.05rem; + } + + h1, + h2, + h3, + h4, + h5, + h6 { + margin-block-start: calc(var(--p-spacing)/2); + margin-block-end: calc(var(--p-spacing)/2); + } + } + } +} /* end .sc-list */ + +/* Only on right sidebar */ +.mod-right-split .sc-list .sc-result { + font-size: var(--font-smallest); +} + +.sc-top-bar { + display: flex; + width: 100%; + justify-content: end; + + .sc-context { + color: var(--nav-item-color); + font-size: var(--nav-item-size); + margin: 0.5em 0.5em 1em; + width: 100%; + } +} + +/* Chat */ +.sc-chat-container { + display: flex; + flex-direction: column; + align-items: center; + justify-content: flex-end; + height: 100%; + + .sc-top-bar-container { + align-self: flex-end; + display: flex; + width: 100%; + + .sc-chat-name-input { + flex-grow: 1; + min-width: 20px; + } + } + + .sc-thread { + display: flex; + flex-direction: column; + align-items: flex-start; + height: 100%; + width: 100%; + overflow: hidden; + user-select: text; + overflow-y: auto; + + .sc-message-container { + border: 1px solid var(--divider-color); + border-radius: 10px; + margin: 0.5rem 0; + display: flex; + flex-direction: column; + align-items: flex-start; + width: 100%; + height: 100%; + overflow-y: auto; + background-color: var(--background-primary-alt); + + .sc-message { + max-width: 90ch; + width: 90%; + margin: 10px; + padding: 10px; + border-radius: 1.5rem; + word-break: break-word; + + &.user { + align-self: flex-end; + color: var(--text-normal); + background-color: var(--background-primary); + } + + &.assistant, + &.system { + background-color: var(--background-primary-alt); + color: var(--text-normal); + } + + .sc-message-content { + margin: 0; + padding: 1rem; + + > * p { + margin: 0; + } + } + } + } + } + + #settings { + display: flex; + flex-direction: column; + max-width: 100%; + width: 100%; + } +} + +.sc-system { + align-self: center; + font-style: italic; + color: var(--text-faint); +} + +.sc-msg-button { + cursor: pointer; + float: right; + margin-left: 5px; + opacity: 0.8; + + &.cycle-branch { + float: left; + display: flex; + } + + &:hover { + opacity: 1; + } +} + +#sc-abort-button { + cursor: pointer; + padding: 10px; + border-radius: 5px; + + &:hover { + background-color: var(--background-primary); + } +} + +.notice .sc-notice-actions { + display: flex; + justify-content: space-between; + flex-direction: row-reverse; +} + +.sc-chat-container { + #settings { + display: flex; + flex-direction: column; + max-width: 100%; + width: 100%; + } + + .sc-config-error-notice { + display: flex; + align-items: center; + justify-content: space-between; + padding: 10px; + background-color: #ffcccc; + border: 1px solid #ff0000; + border-radius: 5px; + margin: 10px 0; + font-size: 14px; + font-weight: bold; + color: #ff0000; + width: 100%; + + span { + flex-grow: 1; + } + + button { + margin-left: 10px; + } + } +} + +.sc-bottom-bar { + position: absolute; + bottom: 0; + right: 0; + left: 0; + width: 100%; + display: flex; + justify-content: space-between; + align-items: center; + background-color: var(--titlebar-background); + padding: 0 0.5rem; + + .sc-brand { + flex-shrink: 0; + } + + .sc-context { + flex-grow: 1; + font-size: var(--font-smallest); + overflow: hidden; + text-overflow: ellipsis; + white-space: nowrap; + } +} + +.setting-component[data-setting*="/"][data-setting*="api_key"] { + display: none; +} + +.setting-component[data-setting*="gpu"]:not([data-setting*="/"]) { + display: none; +} + +/* SINCE COMPONENT PATTERN SETTINGS */ + +.setting-component[data-setting="smart_change.active"] { + display: none; +} + +.group-header { + display: flex; + text-wrap: nowrap; + flex-wrap: wrap; + align-items: baseline; + + > h2 { + width: 100%; + margin-bottom: 0; + } + + > * { + flex-grow: 1; + margin-bottom: 10px; + } +} + +/* SMART CHAT v2 */ +.sc-context-list { + list-style: none; + margin: 0; + padding: 0 1rem 1rem; + display: none; + flex-direction: column; + gap: 0.5rem; +} + +.sc-context-header[aria-expanded="true"] + .sc-context-list { + display: flex; +} + +.sc-context-header[aria-expanded="false"] + .sc-context-list { + display: none; +} + +.sc-context-toggle-icon { + margin-left: 0.5rem; + transition: transform 0.3s ease; +} + +.sc-context-header[aria-expanded="true"] .sc-context-toggle-icon { + transform: rotate(180deg); +} + +.sc-context-item { + padding: 0.5rem; + border-radius: var(--radius-s); + background-color: var(--background-secondary); + color: var(--text-normal); + display: flex; + justify-content: space-between; + align-items: baseline; + font-size: var(--font-smallest); + flex-wrap: wrap; + + &:hover { + background-color: var(--background-secondary-hover); + } +} + +.sc-context-item-path { + font-weight: var(--font-medium); + text-overflow: ellipsis; + overflow: hidden; + white-space: nowrap; + max-width: 70%; +} + +.sc-context-item-score { + font-size: var(--font-small); + color: var(--color-gray-40); +} + +/* System Message Styles */ +.sc-system-message-container { + margin: 1rem 0; + border: 1px solid var(--background-modifier-border); + border-radius: 6px; + background: var(--background-secondary); + flex-shrink: 0; +} + +.sc-system-message-header { + display: flex; + justify-content: space-between; + align-items: center; + padding: 0.75rem 1rem; + cursor: pointer; + font-weight: 500; + border-bottom: 1px solid transparent; + transition: background-color 0.2s ease; + + &:hover { + background: var(--background-modifier-hover); + } + + span { + display: flex; + align-items: center; + gap: 0.5rem; + } + + .sc-system-message-toggle-icon { + transition: transform 0.2s ease; + } + + &[aria-expanded="true"] { + border-bottom-color: var(--background-modifier-border); + } +} + +.sc-system-message-content { + padding: 1rem; + position: relative; + background: var(--background-primary); + border-bottom-left-radius: 6px; + border-bottom-right-radius: 6px; + + .sc-system-message-text { + font-family: var(--font-monospace); + white-space: pre-wrap; + word-break: break-word; + margin-right: 2rem; + } + + .sc-system-message-copy { + position: absolute; + top: 1rem; + right: 1rem; + padding: 0.4rem; + background: transparent; + border: none; + cursor: pointer; + opacity: 0.6; + transition: opacity 0.2s ease; + + &:hover { + opacity: 1; + } + + &.sc-copied { + color: var(--text-accent); + } + } +} + +.sc-chat-container { + .smart-chat-overlay { + position: absolute; + top: 0; + left: 0; + right: 0; + bottom: 0; + background-color: var(--background-primary-alt); + z-index: 100; + overflow: auto; + + .smart-chat-overlay-header { + display: flex; + justify-content: flex-end; + } + .setting-item { + flex-direction: column; + align-items: flex-start; + } + } +} + +/* keyframes must be at root level */ +@keyframes typing-bounce { + 0%, + 80%, + 100% { + transform: scale(0.6); + } + 40% { + transform: scale(1); + } +} + + +/* Side Panes */ +div.workspace-leaf-content[data-type^="smart-"] { + > .view-content { + display: flex; + flex-direction: column; + } +} + +.sc-inline-confirm-row { + margin-top: 10px; + padding: 6px; + border: 1px solid var(--interactive-normal); +} +.sc-inline-confirm-row-buttons { + display: flex; + justify-content: flex-end; + gap: 10px; + margin-top: 10px; + + & .sc-inline-confirm-yes { + font-weight: bold; + } + & .sc-inline-confirm-cancel { + font-weight: normal; + } +} + +.sc-story-container { + display: flex; + flex-direction: column; + height: 100%; +} + +.sc-other-plugins { + display: flex; + flex-direction: column; + gap: 10px; + margin-top: 20px; + + button { + padding: 10px; + border-radius: var(--radius-s); + background-color: var(--background-primary); + color: var(--text-normal); + border: none; + cursor: pointer; + font-size: var(--font-small); + + &:hover { + background-color: var(--background-primary-hover); + } + } +} + + +/* Connections results (redundant with v3.css inc components, should be safe to remove) */ +.sc-result a.sc-result-file-title { + text-decoration: none; +} + +.sc-result a.sc-result-file-title > small.sc-score { + display: inline-block; + width: auto; + height: 1.7em; + padding: 0 0.3em; + line-height: 1.7em; + text-align: center; + font-weight: 600; + font-size: var(--font-smallest); + color: var(--nav-item-color); + background: var(--background-modifier-hover); + border-radius: 6px; + margin-right: 0.2em; +} + +a.sc-result-file-title > small.sc-title { + font-weight: 500; + text-decoration: underline; +} + + +a.sc-result-file-title > .sc-breadcrumb:not(.sc-path, .sc-title, .sc-score) { + font-style: italic; +} + +a.sc-result-file-title > small.sc-breadcrumb-separator { + color: color-mix(in srgb, var(--text-normal) 50%, transparent); +}