/*! smart-connections-obsidian v4.7.2 | (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_obsidian63 = __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-sources/utils/exclusions.js function normalize_exclusion_list(exclusions = []) { const values = Array.isArray(exclusions) ? exclusions : (exclusions || "").split(","); return [...new Set( values.map((value) => value.trim()).filter((value) => value && !/^[/*\\]+$/.test(value)) )]; } // 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"; var ATOMIC_ACTION_FIELDS = /* @__PURE__ */ new Set([ "tool", "action_scope", "input_schema", "output_schema" ]); 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", "modals"].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) { if (key === "actions") { const replaced = { ...comp_def }; merge_action_record_no_overwrite(replaced, target_comp); target[key][comp_key] = replaced; } else { 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 { if (key === "actions") { merge_action_record_no_overwrite(target_comp, comp_def); } 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 merge_action_record_no_overwrite(target, source, is_action_entry = true) { if (!is_plain_object3(target) || !is_plain_object3(source)) { return target; } for (const [key, value] of Object.entries(source)) { if (!Object.prototype.hasOwnProperty.call(target, key)) { target[key] = value; continue; } if (is_action_entry && ATOMIC_ACTION_FIELDS.has(key)) continue; if (is_plain_object3(target[key]) && is_plain_object3(value)) { merge_action_record_no_overwrite(target[key], value, false); } } 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, version4) { if (version4 === void 0) return; if (!record || typeof record !== "object" && typeof record !== "function") return; record.version = version4; } function set_config_record_env_version(record, version4) { if (version4 === void 0) return; if (!record || typeof record !== "object" && typeof record !== "function") return; Object.defineProperty(record, CONFIG_RECORD_ENV_VERSION_KEY, { value: version4, 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 {{main?: string, loaded?: boolean}} [opts] * @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`. * Legacy exclusion strings stay intact for older clients; array settings use separate list keys. * @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; const smart_sources_settings = settings.smart_sources; if (smart_sources_settings) { if (Array.isArray(smart_sources_settings.file_exclusions)) { const file_exclusions = normalize_exclusion_list( smart_sources_settings.file_exclusions_list ?? smart_sources_settings.file_exclusions ); smart_sources_settings.file_exclusions_list = file_exclusions; smart_sources_settings.file_exclusions = file_exclusions.filter((value) => !value.includes(",")).join(","); } if (Array.isArray(smart_sources_settings.folder_exclusions)) { const folder_exclusions2 = normalize_exclusion_list( smart_sources_settings.folder_exclusions_list ?? smart_sources_settings.folder_exclusions ); smart_sources_settings.folder_exclusions_list = folder_exclusions2; smart_sources_settings.folder_exclusions = folder_exclusions2.filter((value) => !value.includes(",")).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-environment/utils/action_entry.js function get_scope_env(scope) { if (!scope || typeof scope !== "object") { throw new TypeError("Action scope must be an object."); } const env = "env" in scope ? scope.env : scope; if (!env || typeof env !== "object" || !env.config?.actions) { throw new TypeError( "Action scope must be a SmartEnv or expose one through scope.env." ); } return env; } function resolve_action_scope(env, action_key, action_entry, params = {}) { const action_scope4 = action_entry.action_scope; if (is_disabled(action_scope4)) return env; validate_action_scope(action_scope4, action_key); if (typeof action_scope4.resolve === "function") { return action_scope4.resolve({ env, params, action_key, action_entry }); } if (action_scope4.type === "env") return env; const collection = env[action_scope4.collection_key]; if (action_scope4.type === "collection") { return collection ?? null; } return collection?.get?.(params[action_scope4.item_arg]) ?? null; } function is_action_scope_compatible(env, action_scope4, scope) { if (!is_disabled(action_scope4)) { validate_action_scope(action_scope4); } if (!scope) return false; try { if (get_scope_env(scope) !== env) return false; } catch { return false; } if (is_disabled(action_scope4)) return true; if (action_scope4.type === "env") return scope === env; const collection = env[action_scope4.collection_key]; if (!collection) return false; if (action_scope4.type === "collection") { return scope === collection; } return scope.collection === collection; } function run_action_entry(scope, action_key, params = {}, { event_source } = {}) { if (typeof action_key !== "string" || !action_key.trim()) { throw new TypeError("Action key must be a non-empty string."); } if (!params || typeof params !== "object" || Array.isArray(params)) { throw new TypeError("Action params must be an object."); } const env = get_scope_env(scope); const action_entry = env.config.actions[action_key]; if (!action_entry) { throw new Error(`Action not found: ${action_key}`); } const scoped_action = scope.actions?.[action_key]; const action = typeof scoped_action === "function" ? scoped_action : typeof action_entry.action === "function" ? action_entry.action.bind(scope) : null; if (!action) { throw new Error(`Action is not callable: ${action_key}`); } const action_params = { ...params, ...event_source === void 0 ? {} : { event_source } }; return action(action_params); } function validate_action_scope(action_scope4, action_key = "") { const suffix = action_key ? ` for action: ${action_key}` : ""; if (!action_scope4 || typeof action_scope4 !== "object" || Array.isArray(action_scope4)) { throw new TypeError(`Invalid action_scope${suffix}`); } if (!["env", "collection", "item"].includes(action_scope4.type)) { throw new TypeError(`Invalid action_scope type${suffix}`); } if (action_scope4.type !== "env" && (typeof action_scope4.collection_key !== "string" || !action_scope4.collection_key.trim())) { throw new TypeError(`Invalid action_scope collection_key${suffix}`); } if (action_scope4.type === "item" && (typeof action_scope4.item_arg !== "string" || !action_scope4.item_arg.trim())) { throw new TypeError(`Invalid action_scope item_arg${suffix}`); } if (typeof action_scope4.resolve !== "undefined" && typeof action_scope4.resolve !== "function") { throw new TypeError(`Invalid action_scope resolve${suffix}`); } } function is_disabled(value) { return value === false || value === null || typeof value === "undefined"; } // 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 = []; this._exclusions_loaded = false; 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() { this.auto_excluded_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() { if (this._exclusions_loaded) return; const gitignore_path = ".gitignore"; if (!this.env.settings.skip_excluding_gitignore) { const gitignore_exists = await this.adapter.exists(gitignore_path); if (gitignore_exists) { const gitignore_stat = await this.adapter.stat(gitignore_path); const gitignore_mtime = Number(gitignore_stat?.mtime || 0); const gitignore_exclusions_updated_at = this.env.settings.gitignore_exclusions_updated_at || 0; if (!gitignore_exclusions_updated_at || gitignore_exclusions_updated_at < gitignore_mtime) { const gitignore_content = await this.adapter.read(gitignore_path, "utf-8"); this.env.settings.gitignore_exclusions = gitignore_content.split("\n").filter((line) => !line.startsWith("#")).filter(Boolean); this.env.settings.gitignore_exclusions_updated_at = Date.now(); await this.env.smart_settings?.save?.(); } } (this.env.settings.gitignore_exclusions || []).forEach((pattern) => this.add_ignore_pattern(pattern)); } this.add_ignore_pattern(".**"); this.add_ignore_pattern("**/.**"); this.add_ignore_pattern("**/.*/**"); this.add_ignore_pattern("**/*.ajson"); this._exclusions_loaded = true; } /** * 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 whether a path is excluded by configured patterns or an adapter * compatibility policy. Adapter-generated exclusions stay runtime-only and * are rebuilt during each complete filesystem scan. * * @param {string} _path - The path to check * @returns {boolean} True if the path is excluded, false otherwise */ is_excluded(_path) { try { if (_path.includes("#")) return true; if (this.excluded_patterns.some((pattern) => pattern.test(_path))) return true; if (!this.adapter.should_exclude_path_for_length?.(_path)) return false; if (!this.auto_excluded_files.includes(_path)) { this.auto_excluded_files.push(_path); } return true; } 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); } async append_binary(rel_path, data) { try { await this.adapter.append_binary(rel_path, data); } catch (error) { console.error("Error during binary append:", error); throw error; } } /** * 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 }; } } /** * Read a file as binary data without relying on ambiguous string encodings. * * @param {string} rel_path - The relative path of the file to read * @returns {Promise} */ async read_binary(rel_path) { try { if (typeof this.adapter.read_binary === "function") { return await this.adapter.read_binary(rel_path); } return await this.adapter.read(rel_path, "binary"); } catch (error) { console.warn("Error during binary 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/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; } } async write_binary(rel_path, data) { try { await this.adapter.write_binary(rel_path, data); } catch (error) { console.error("Error during binary 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); } async append_binary(rel_path, data) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; return await this.obsidian_adapter.appendBinary(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; } /** * Source persistence uses fixed shard filenames, so source path length does * not require an additional Smart Environment exclusion. * * @returns {boolean} */ should_exclude_path_for_length() { return false; } // 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 (this.smart_fs.is_excluded(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; } /** * Read a file as binary bytes using Obsidian's native binary adapter path. * * @param {string} rel_path - The relative path of the file to read * @returns {Promise} */ async read_binary(rel_path) { if (!rel_path.startsWith(this.fs_path)) rel_path = this.fs_path + "/" + rel_path; return await this.obsidian_adapter.readBinary(rel_path); } /** * 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 * @param {boolean} [recursive=false] * @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 to get information for * @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); } async write_binary(rel_path, 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.writeBinary(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/convert_to_human_readable_size.js function convert_to_human_readable_size(size = 0) { if (size > 1e6) { return `${(size / 1e6).toFixed(1)} MB`; } if (size > 1e3) { return `${(size / 1e3).toFixed(1)} KB`; } return `${size} bytes`; } // 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 activeDocument.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=activeDocument] - The DOM element to scan. Defaults to the entire document. */ add_settings_listeners(scope, container = activeDocument) { 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); } }); } }); } /** * @deprecated keep imports in relevant files but build_plugin.js aggregates them in dist/styles.css (prevents weird detached stylesheets bug in Obsidian) */ apply_style_sheet(sheet, params = {}) { if (typeof sheet === "string") { const style_sheet_id = params.id || get_style_sheet_id(sheet); if (!style_sheet_id) return; const stylesheet_el = activeDocument.getElementById(style_sheet_id); if (stylesheet_el) { if (params.force_refresh || stylesheet_el.textContent !== sheet) { stylesheet_el.textContent = sheet; } return stylesheet_el; } const styleEl = activeDocument.createElement("style"); styleEl.id = style_sheet_id; styleEl.textContent = sheet; activeDocument.head.appendChild(styleEl); return styleEl; } if ("adoptedStyleSheets" in activeDocument) { activeDocument.adoptedStyleSheets = [ ...activeDocument.adoptedStyleSheets, sheet ]; } else { const styleEl = activeDocument.createElement("style"); if (sheet.cssRules) { styleEl.textContent = Array.from(sheet.cssRules).map((rule) => rule.cssText).join("\n"); } activeDocument.head.appendChild(styleEl); return 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 var empty_actions = Object.freeze(/* @__PURE__ */ Object.create(null)); var missing_action = Symbol("missing_action"); var registry_cache = /* @__PURE__ */ new WeakMap(); var proxy_state = /* @__PURE__ */ new WeakMap(); var ignored_meta_keys = /* @__PURE__ */ new Set(["length", "name", "prototype"]); function create_actions_proxy(ctx, actions_source) { const actions_sources = Array.isArray(actions_source) && actions_source.length ? actions_source : [actions_source || empty_actions]; const target = /* @__PURE__ */ Object.create(null); proxy_state.set(target, { ctx, registry: get_actions_registry(actions_sources) }); return new Proxy(target, actions_proxy_handler); } var actions_proxy_handler = { get(target, prop) { if (prop === Symbol.toStringTag) return "ActionsProxy"; if (has_own(target, prop)) return target[prop]; const state = proxy_state.get(target); const action_entry = state.registry.get(state.ctx?.constructor?.key, prop); const action = materialize_action(state.ctx, action_entry); target[prop] = action; return action; }, has(target, prop) { if (has_own(target, prop)) return true; const state = proxy_state.get(target); return state.registry.get(state.ctx?.constructor?.key, prop) !== missing_action; }, ownKeys(target) { const state = proxy_state.get(target); return Array.from(/* @__PURE__ */ new Set([ ...Reflect.ownKeys(target), ...state.registry.keys(state.ctx?.constructor?.key) ])); }, getOwnPropertyDescriptor(target, prop) { if (!has_own(target, prop)) { const state = proxy_state.get(target); const action_entry = state.registry.get(state.ctx?.constructor?.key, prop); if (action_entry === missing_action) return void 0; target[prop] = materialize_action(state.ctx, action_entry); } return { configurable: true, enumerable: true, value: target[prop] }; }, defineProperty(target, prop, descriptor) { if (!("value" in descriptor)) return false; target[prop] = descriptor.value; return true; }, set(target, prop, value) { target[prop] = value; return true; }, deleteProperty(target, prop) { if (has_own(target, prop)) delete target[prop]; return true; } }; function get_actions_registry(actions_sources) { let cache = registry_cache; let cache_entry; for (const actions_source of actions_sources) { const source = actions_source || empty_actions; cache_entry = cache.get(source); if (!cache_entry) { cache_entry = { children: /* @__PURE__ */ new WeakMap(), registry: null }; cache.set(source, cache_entry); } cache = cache_entry.children; } if (!cache_entry.registry) { cache_entry.registry = create_actions_registry( actions_sources.map((source) => source || empty_actions) ); } return cache_entry.registry; } function create_actions_registry(actions_sources) { const actions_bucket_cache = /* @__PURE__ */ new WeakMap(); const get_is_actions_bucket = (value) => { if (!is_plain_object4(value)) return false; if (!actions_bucket_cache.has(value)) { actions_bucket_cache.set(value, is_actions_bucket(value)); } return actions_bucket_cache.get(value); }; return { get(scope_key, action_key) { if (scope_key !== void 0 && scope_key !== null) { const scoped_actions = get_source_entry(actions_sources, scope_key); if (get_is_actions_bucket(scoped_actions) && has_own(scoped_actions, action_key)) { return scoped_actions[action_key]; } } const action_entry = get_source_entry(actions_sources, action_key); return get_is_actions_bucket(action_entry) ? missing_action : action_entry; }, keys(scope_key) { const merged_keys = /* @__PURE__ */ new Set(); for (const source of actions_sources) { for (const key of Reflect.ownKeys(source)) { if (Object.prototype.propertyIsEnumerable.call(source, key)) { merged_keys.add(key); } } } const action_keys = /* @__PURE__ */ new Set(); for (const key of merged_keys) { const action_entry = get_source_entry(actions_sources, key); if (!get_is_actions_bucket(action_entry)) action_keys.add(key); } if (scope_key !== void 0 && scope_key !== null) { const scoped_actions = get_source_entry(actions_sources, scope_key); if (get_is_actions_bucket(scoped_actions)) { for (const key of Reflect.ownKeys(scoped_actions)) { action_keys.add(key); } } } return Array.from(action_keys); } }; } function get_source_entry(actions_sources, key) { for (let i = actions_sources.length - 1; i >= 0; i -= 1) { const source = actions_sources[i]; if (Object.prototype.propertyIsEnumerable.call(source, key)) { return source[key]; } } return missing_action; } function materialize_action(ctx, action_entry) { if (action_entry === missing_action) return void 0; if (is_class_export(action_entry)) { const instance = new action_entry(ctx); const action = instance.action || instance.run || instance.execute || instance.call; if (typeof action === "function") { const bound = action.bind(instance); copy_metadata(action_entry, bound); copy_metadata(instance, bound); bound.instance = instance; return bound; } copy_metadata(action_entry, instance); return instance; } if (is_action_object(action_entry)) { const action_object = clone_with_descriptors(action_entry); const bound = action_object.action.bind(ctx); copy_metadata(action_object, bound, "action"); return bound; } if (typeof action_entry === "function") { const bound = action_entry.bind(ctx); copy_metadata(action_entry, bound); return bound; } if (is_plain_object4(action_entry)) { return clone_with_descriptors(action_entry); } return action_entry; } function copy_metadata(source, target, omitted_key = null) { if (!source || !target) return; for (const key of Reflect.ownKeys(source)) { if (ignored_meta_keys.has(key) || key === omitted_key) continue; const descriptor = Object.getOwnPropertyDescriptor(source, key); if (!descriptor) continue; try { Object.defineProperty(target, key, descriptor); } catch { target[key] = descriptor.value; } } } function clone_with_descriptors(object) { if (!is_plain_object4(object)) return object; const clone = Object.create(Object.getPrototypeOf(object) || null); for (const key of Reflect.ownKeys(object)) { const descriptor = Object.getOwnPropertyDescriptor(object, key); if (!descriptor) continue; const next = { ...descriptor }; if ("value" in next && is_plain_object4(next.value)) { next.value = clone_with_descriptors(next.value); } try { Object.defineProperty(clone, key, next); } catch { clone[key] = next.value; } } return clone; } function is_actions_bucket(value) { if (!is_plain_object4(value) || is_action_object(value)) return false; const keys = Reflect.ownKeys(value); if (!keys.length) return false; let found_action = false; for (const key of keys) { const descriptor = Object.getOwnPropertyDescriptor(value, key); if (!descriptor || !("value" in descriptor)) return false; const entry = descriptor.value; if (typeof entry === "undefined") continue; if (typeof entry === "function" || is_action_object(entry)) { found_action = true; continue; } if (is_actions_bucket(entry)) { found_action = true; continue; } return false; } return found_action; } function is_action_object(value) { return is_plain_object4(value) && typeof value.action === "function"; } function is_class_export(value) { return typeof value === "function" && /^class\s/.test(Function.prototype.toString.call(value)); } function is_plain_object4(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function has_own(object, key) { return Object.prototype.hasOwnProperty.call(object, key); } // 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) { this.env = env; env.create_env_getter(this); this.merge_defaults(); if (data) deep_merge(this.data, data); if (!this.data.class_name) this.data.class_name = this.collection.item_class_name; } get config() { return this.env?.config; } /** * 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|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]; } /** * Current secrets for the collection. * Initializes with empty object if none exist. * @this {CollectionThis} * @returns {Object|undefined} */ get secrets() { if (!this.env.smart_secrets) return; if (!this.env.smart_secrets.secrets[this.collection_key]) { this.env.smart_secrets.secrets[this.collection_key] = {}; } return this.env.smart_secrets.secrets[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) { this._actions = create_actions_proxy(this, [ this.env?.config?.actions, this.env?.config?.collections?.[this.collection_key]?.actions, this.env?.opts?.collections?.[this.collection_key]?.actions, this.opts?.actions ]); } 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[]|ArrayBufferView|Object} vec - The reference vector or an item whose `vec` resolves one. * @param {Object} [filter={}] - Optional filters (limit, exclude, etc.) * @returns {Promise>} Array of results sorted by score descending. */ async nearest(vec, filter = {}) { vec = vec?.vec || vec; if (!vec || !Array.isArray(vec) && !ArrayBuffer.isView(vec)) { throw new Error("Invalid vector input to nearest()"); } const { limit = 50 } = filter; const nearest = this.collection.filter(filter).reduce((acc, item) => { const item_vec = item.vec; if (!item_vec) return acc; const result = { item, score: cos_sim(vec, item_vec) }; results_acc(acc, result, limit); return acc; }, { min: Number.POSITIVE_INFINITY, minResult: null, 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[]|ArrayBufferView|Object} vec - The reference vector or an item whose `vec` resolves one. * @param {Object} [filter={}] - Optional filters (limit, exclude, etc.) * @returns {Promise>} Array of results sorted by score ascending (furthest). */ async furthest(vec, filter = {}) { vec = vec?.vec || vec; if (!vec || !Array.isArray(vec) && !ArrayBuffer.isView(vec)) { throw new Error("Invalid vector input to furthest()"); } const { limit = 50 } = filter; const furthest = this.collection.filter(filter).reduce((acc, item) => { const item_vec = item.vec; if (!item_vec) return acc; const result = { item, score: cos_sim(vec, item_vec) }; furthest_acc(acc, result, limit); return acc; }, { max: Number.NEGATIVE_INFINITY, maxResult: null, 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; }); return embeddings; } /** * 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) return; if (this.is_embed_queue_paused() && !this._resume_after_pause) 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 || "") }); this._is_processing_embed_queue = false; return; } try { await new Promise((resolve) => setTimeout(resolve, 1)); const embed_queue = this.collection.embed_queue; this._reset_embed_queue_stats(); const processed_keys_by_collection = {}; const changed_keys_by_collection = {}; if (this.collection.embed_model_key === "None") return; if (!this.collection.embed_model) return; if (!embed_queue.length) return; this.current_queue_total = embed_queue.length; this.embedding_started_at = Date.now(); this._start_embed_progress_state(embed_queue.length); const embed_model = this.collection.embed_model; const embed_adapter = embed_model.adapter || embed_model; const batch_size = Math.max(1, Math.floor(Number(embed_model.batch_size) || 1)); const batch_window_size = Math.max( batch_size, Math.floor(Number(embed_adapter.batch_window_size) || batch_size) ); const sort_by_input_length = embed_adapter.batch_sort_by_input_length === true; for (let window_start = 0; window_start < embed_queue.length; window_start += batch_window_size) { if (this.is_queue_halted) break; const window_items = embed_queue.slice(window_start, window_start + batch_window_size); const prepared_window = await Promise.all(window_items.map(async (item, window_item_i) => { const embed_input = await item.get_embed_input(); const input_value = typeof embed_input === "string" ? embed_input : ""; return { item, input_length: input_value.length, window_item_i }; })); if (sort_by_input_length) { prepared_window.sort((a, b) => { if (a.input_length !== b.input_length) return a.input_length - b.input_length; return a.window_item_i - b.window_item_i; }); } for (let batch_start = 0; batch_start < prepared_window.length; batch_start += batch_size) { if (this.is_queue_halted) break; const batch_entries = prepared_window.slice(batch_start, batch_start + batch_size); const batch = batch_entries.map((entry) => entry.item); let batch_results; try { const start_time = Date.now(); batch_results = await this.embed_batch(batch); this.total_time += Date.now() - start_time; } catch (error) { console.error(`Error processing ${this.collection.collection_key} embed queue:`, error); this._emit_embedding_error({ message: `Embedding failed while processing ${this.collection.collection_key}.`, details: error?.message || JSON.stringify(error || {}, null, 2), response_json: error?.response_json }); break; } batch.forEach((item, item_i) => { processed_keys_by_collection[item.collection_key] ||= []; processed_keys_by_collection[item.collection_key].push(item.key); const result = batch_results?.[item_i]; if (result?.skipped && result?.vector_changed !== true) return; item.embed_hash = item.read_hash; item.queue_save?.(); changed_keys_by_collection[item.collection_key] ||= []; changed_keys_by_collection[item.collection_key].push(item.key); }); this.embedded_total += batch.length; batch_entries.forEach((entry, entry_i) => { const result = batch_results?.[entry_i]; if (result?.skipped) return; this.total_characters += entry.input_length; this.total_tokens += result?.tokens ?? entry.input_length / 4; }); const processed_all2 = this.embedded_total >= embed_queue.length; const is_paused2 = this.is_queue_halted && !processed_all2; if (is_paused2) { 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 (is_paused2 || 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) { await this.collection.block_collection.process_save_queue(); } } } } await this.commit_embed_run(); Object.entries(processed_keys_by_collection).forEach(([collection_key, keys]) => { this.collection.env.events?.emit("items:embedded", { collection_key, keys, changed_keys: changed_keys_by_collection[collection_key] || [], 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); } if (!this._embed_run_error) { const elapsed_ms = Date.now() - this.embedding_started_at; console.log(`${this.total_characters} characters embedded in ${elapsed_ms}ms`); } } 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); } } } async commit_embed_run() { await this.collection.process_save_queue(); if (this.collection.block_collection) { await this.collection.block_collection.process_save_queue(); } } /** * @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; const is_paused = Boolean(this.progress_state?.paused); const reason = this.progress_state?.reason || ""; if (is_paused) { this._update_paused_progress_state(embed_queue_length, reason); } else { 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(), characters_embedded: this.total_characters, elapsed_ms: Date.now() - this.embedding_started_at, model_name: this.collection.embed_model_key, paused: is_paused, reason, 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(), characters_embedded: this.total_characters, elapsed_ms: Date.now() - this.embedding_started_at, 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(), characters_embedded: this.total_characters, elapsed_ms: this.embedding_started_at ? Date.now() - this.embedding_started_at : 0, 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(), characters_embedded: this.total_characters, elapsed_ms: this.embedding_started_at ? Date.now() - this.embedding_started_at : 0, 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) { 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.total_characters = 0; this.embedding_started_at = 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, characters_embedded: 0, elapsed_ms: 0, model_name: this.collection.embed_model_key }); this.collection.emit_event("embedding:started", { progress: 0, total, characters_embedded: 0, elapsed_ms: 0, 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(), characters_embedded: this.total_characters, elapsed_ms: Date.now() - this.embedding_started_at, 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(), characters_embedded: this.total_characters, elapsed_ms: Date.now() - this.embedding_started_at, model_name: this.collection.embed_model_key, reason }); } /** * @private * @this {DefaultEntitiesVectorAdapterThis} * @param {object} [params={}] * @param {string} [params.message] * @param {string} [params.details] * @param {*} [params.response_json] * @returns {void} */ _emit_embedding_error(params = {}) { const { message = "Embedding failed.", details = "", response_json } = params; this._embed_run_error = true; this.is_queue_halted = true; this._set_progress_state(null); const event = { level: "error", message, details, model_name: this.collection.embed_model_key, event_source: "process_embed_queue" }; if (response_json !== void 0) event.response_json = response_json; this.collection.emit_event("embedding:error", event); } /** * @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); } /** * Adapter for this entity's vector operations. * @type {DefaultEntityVectorAdapter} */ get entity_adapter() { if (!this._entity_adapter) { this._entity_adapter = new DefaultEntityVectorAdapter(this); } return this._entity_adapter; } set entity_adapter(entity_adapter) { this._entity_adapter = entity_adapter; } /** * 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. */ // TODO: move handling to source adapter (this remains, override in SmartSource, defer to adapter for prevents embedding of non-embeddable source types like images) 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, purpose: "query" }))); 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-sources/smart_sources.js var SmartSources = class _SmartSources 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 }; if (this._run_re_import_promise) { this._run_re_import_pending = true; return; } 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() { if (this._run_re_import_promise) { this._run_re_import_pending = true; return this._run_re_import_promise; } this._run_re_import_promise = Promise.resolve().then(async () => { try { do { this._run_re_import_pending = false; await _SmartSources.prototype.run_re_import_once.call(this); } while (this._run_re_import_pending); } finally { this._run_re_import_promise = null; this._run_re_import_pending = false; } }); return this._run_re_import_promise; } /** * Processes one targeted re-import queue snapshot. * @returns {Promise} */ async run_re_import_once() { 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" }); const re_import_embed_queue = []; 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, queue_entry] of batch) { const { source } = queue_entry; if (source.should_embed) re_import_embed_queue.push(source); if (this.block_collection?.settings?.embed_blocks) { for (const block of source.blocks || []) { if (!block.should_embed) { block._queue_embed = false; continue; } if (block._queue_embed || block.is_unembedded) { re_import_embed_queue.push(block); block._queue_embed = true; } } } if (this.sources_re_import_queue[key] === queue_entry) { 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); this.build_links_map(); await this.process_save_queue(); await this.block_collection?.process_save_queue(); if (re_import_embed_queue.length) { this._embed_queue = re_import_embed_queue; this._embed_queue_ready = true; const embed_start_at = Date.now(); try { await this.process_embed_queue(); } finally { this.mark_embed_queue_dirty?.(); } 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 }); if (!item.source_adapter) return; 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; } /** * Updates one source's contribution to the links map. * @param {import('./smart_source.js').SmartSource} source * @param {Array} previous_outlinks * @returns {Object} The updated links map. */ update_links_map(source, previous_outlinks) { if (!this.links) this.links = {}; for (const link of previous_outlinks) { const inlinks = this.links[link.key]; if (!inlinks) continue; delete inlinks[source.key]; if (!Object.keys(inlinks).length) delete this.links[link.key]; } for (const link of source.outlinks) { if (!this.links[link.key]) this.links[link.key] = {}; this.links[link.key][source.key] = { ...link }; } 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 || []).flatMap((folder) => { const pattern = folder.endsWith("/**") ? folder : folder.endsWith("/") ? `${folder}**` : `${folder}/**`; return [pattern]; }), this.env.env_data_dir + "/**" ]; } /** * Retrieves file exclusions from the array-backed list when present, * falling back to the legacy CSV setting. * @readonly * @returns {Array} An array of file exclusion patterns. */ get file_exclusions() { const settings = this.env.settings?.smart_sources || {}; return normalize_exclusion_list( settings.file_exclusions_list ?? settings.file_exclusions ); } /** * Retrieves folder exclusions from the array-backed list when present, * falling back to the legacy CSV setting. * @readonly * @returns {Array} An array of folder exclusion patterns. */ get folder_exclusions() { const settings = this.env.settings?.smart_sources || {}; return normalize_exclusion_list( settings.folder_exclusions_list ?? settings.folder_exclusions ).map((folder) => { if (folder.includes("*")) return folder; if (!folder.endsWith("/")) return folder + "/"; return folder; }); } /** * 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-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/actions/get_embed_input/markdown.js async function source_get_embed_input_markdown(params = {}) { const has_content = typeof params.content === "string"; if (has_content) { this._embed_input = null; } else if (typeof this._embed_input === "string" && this._embed_input.length) { return this._embed_input; } let content = has_content ? params.content : await this.read(); if (!content || typeof content !== "string") { 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; } // node_modules/obsidian-smart-env/node_modules/smart-sources/actions/get_embed_input/data.js async function source_get_embed_input_data(params = {}) { return await source_get_embed_input_markdown.call(this, params); } // node_modules/obsidian-smart-env/node_modules/smart-sources/actions/get_embed_input/text.js async function source_get_embed_input_text(params = {}) { return await source_get_embed_input_markdown.call(this, params); } // 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.blocks_initialized) this.queue_import(); } /** * Queues the SmartSource for import. * @returns {void} */ queue_import() { this._queue_import = true; } get blocks_initialized() { return Boolean(this.data.blocks); } get block_keys() { return Object.keys(this.data.blocks || {}); } has_block(sub_key) { return Object.prototype.hasOwnProperty.call(this.data.blocks || {}, sub_key); } get_block_lines(sub_key) { return this.data.blocks?.[sub_key]; } replace_blocks(blocks_by_sub_key = {}) { this.data.blocks = blocks_by_sub_key; } /** * Imports the SmartSource by checking for updates and parsing content. * @async * @param {Object} [params={}] * @returns {Promise} */ async import(params = {}) { this._queue_import = false; try { await this.source_adapter?.import(params); 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.blocks_initialized) 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); } /** * Resolves and runs the source adapter's canonical embed-input action. * @async * @param {string|null} [content=null] * @returns {Promise} The embed input string. */ async get_embed_input(content = null) { const source_adapter = this.source_adapter; const action_key = source_adapter?.embed_input_action_key; if (!action_key) { throw new Error(`SmartSource.get_embed_input: missing embed_input_action_key for ${this.key}`); } const action = this.actions[action_key]; if (typeof action !== "function") { throw new Error(`SmartSource.get_embed_input: missing action "${action_key}" for ${this.key}`); } return await action({ content }); } /** * Opens the SmartSource note in the SmartConnections plugin. * @returns {void} */ open() { this.env.smart_connections_plugin.open_note(this.path); } /** * 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.blocks_initialized) return this.block_collection.get_many(this.block_keys.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 all outlinks from the SmartSource. * @readonly * @returns {Array} An array of outlink objects. */ get outlinks() { return this.source_adapter?.get_outlinks() || []; } get should_embed() { if (typeof this.source_adapter?.should_embed !== "undefined") return this.source_adapter.should_embed; return this.size > (this.settings?.min_chars || 300); } /** * @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, source_get_embed_input_data, source_get_embed_input_markdown, source_get_embed_input_text } }; // node_modules/obsidian-smart-env/src/utils/embedding_item.js var DEFAULT_EMBEDDING_TYPE = "default"; function ensure_embedding_data(item) { if (!item.data) item.data = {}; if (!item.data.embedding) item.data.embedding = {}; if (!Array.isArray(item.data.embedding.history)) { item.data.embedding.history = []; } return item.data.embedding; } function has_legacy_embedding_refs(data = {}, type = DEFAULT_EMBEDDING_TYPE) { return Boolean(data?.embedding?.[type]?.file); } function migrate_embedding_refs(item, type = DEFAULT_EMBEDDING_TYPE) { const embedding = item.data?.embedding; const legacy_ref = embedding?.[type]; if (!legacy_ref?.file) return false; const refs = {}; const history = Array.isArray(embedding.history) ? embedding.history : []; history.forEach((history_ref) => { if ((history_ref?.type || DEFAULT_EMBEDDING_TYPE) !== type) return; if (!history_ref?.file) return; const model_fingerprint2 = history_ref.model_fingerprint || history_ref.file; const current_ref = refs[model_fingerprint2]; if (!current_ref || Number(history_ref.at || 0) >= Number(current_ref.at || 0)) { refs[model_fingerprint2] = copy_embedding_ref(history_ref); } }); const model_fingerprint = legacy_ref.model_fingerprint || legacy_ref.file; refs[model_fingerprint] = copy_embedding_ref(legacy_ref); embedding[type] = refs; embedding.history = history; return true; } function get_embedding_refs(item, type = DEFAULT_EMBEDDING_TYPE) { migrate_embedding_refs(item, type); return item.data?.embedding?.[type] || {}; } function get_embedding_ref(item, type = DEFAULT_EMBEDDING_TYPE, model_fingerprint = "") { const refs = get_embedding_refs(item, type); if (model_fingerprint) return refs[model_fingerprint] || null; return Object.values(refs).reduce((latest_ref, ref) => { if (!ref?.file) return latest_ref; if (!latest_ref || Number(ref.at || 0) >= Number(latest_ref.at || 0)) return ref; return latest_ref; }, null); } function set_embedding_ref(item, next_ref, type = DEFAULT_EMBEDDING_TYPE, model_fingerprint = next_ref.model_fingerprint || next_ref.file) { if (!model_fingerprint) return null; const embedding = ensure_embedding_data(item); migrate_embedding_refs(item, type); if (!embedding[type]) embedding[type] = {}; const current_ref = embedding[type][model_fingerprint]; if (current_ref?.file && (current_ref.file !== next_ref.file || current_ref.file_i !== next_ref.file_i || current_ref.read_hash !== next_ref.read_hash)) { embedding.history.push({ type, model_fingerprint, ...current_ref }); } embedding[type][model_fingerprint] = copy_embedding_ref(next_ref); delete embedding.error; return embedding[type][model_fingerprint]; } function delete_embedding_ref(item, type = DEFAULT_EMBEDDING_TYPE, model_fingerprint = "") { const embedding = item.data?.embedding; if (!embedding) return false; migrate_embedding_refs(item, type); if (!embedding[type]) return false; if (!model_fingerprint) { delete embedding[type]; return true; } if (!embedding[type][model_fingerprint]) return false; delete embedding[type][model_fingerprint]; if (!Object.keys(embedding[type]).length) delete embedding[type]; return true; } function prune_legacy_embedding_data(item) { if (item.data.embeddings) delete item.data.embeddings; if (item.data.last_embed) delete item.data.last_embed; } function copy_embedding_ref(ref = {}) { return { file: ref.file, file_i: ref.file_i, read_hash: ref.read_hash || "", at: ref.at || Date.now() }; } // node_modules/obsidian-smart-env/src/items/smart_source.js var BLOCK_EMBEDDING_SELECTION_VERSION = 2; var DEFAULT_MAX_TOKENS = 500; var EMBEDDING_CHARS_PER_TOKEN = 3.7; var SmartSource2 = class extends SmartSource { static version = 2; merge_defaults() { this.data = { class_name: "SmartSource", last_read: { hash: null, mtime: 0 }, embedding: { history: [] } }; this._embed_input = null; this._queue_load = true; } init() { if (this.is_unembedded) this.queue_embed(); if (!this.blocks_initialized) this.queue_import(); } async import(params = {}) { if (this.data.block_embedding_selection?.requires_reimport === true) { params = { ...params, refresh: true }; } return await super.import(params); } get blocks_initialized() { return Boolean(this.data.blocks_data); } get block_keys() { return Object.keys(this.data.blocks_data || {}); } has_block(sub_key) { return Object.prototype.hasOwnProperty.call(this.data.blocks_data || {}, sub_key); } get_block_lines(sub_key) { return this.data.blocks_data?.[sub_key]?.lines; } replace_blocks(blocks_by_sub_key = {}) { const current_blocks_data = this.data.blocks_data || {}; const next_blocks_data = {}; for (const sub_key in blocks_by_sub_key) { const block_data = current_blocks_data[sub_key] || { key: `${this.key}${sub_key}` }; block_data.key = `${this.key}${sub_key}`; block_data.lines = blocks_by_sub_key[sub_key]; next_blocks_data[sub_key] = block_data; } this.data.blocks_data = next_blocks_data; this.data.block_embedding_selection = null; } ensure_block_embedding_selection(params = {}) { const blocks_data = this.data.blocks_data || {}; const min_chars = params.min_chars ?? this.block_collection?.settings?.min_chars ?? 0; const max_tokens = params.max_tokens || this.env?.embedding_models?.default?.data?.max_tokens || DEFAULT_MAX_TOKENS; const max_input_chars = Math.floor(max_tokens * EMBEDDING_CHARS_PER_TOKEN); const current_policy = this.data.block_embedding_selection; const policy_matches = current_policy?.version === BLOCK_EMBEDDING_SELECTION_VERSION && current_policy.min_chars === min_chars && current_policy.max_input_chars === max_input_chars && current_policy.source_key === this.key; if (!params.force && policy_matches) { if (current_policy.requires_reimport === true) this.queue_import(); return false; } const block_sub_keys = Object.keys(blocks_data); if (block_sub_keys.some((sub_key) => blocks_data[sub_key].size == null)) { this.queue_import(); return false; } const nodes = []; let invalid_range = false; for (const sub_key of block_sub_keys) { const block_data = blocks_data[sub_key]; const lines = block_data.lines; const line_start = lines?.[0]; const line_end = lines?.[1]; if (!Array.isArray(lines) || lines.length !== 2 || !Number.isInteger(line_start) || !Number.isInteger(line_end) || line_start < 0 || line_end < line_start) { invalid_range = true; continue; } nodes.push({ sub_key, lines, size: block_data.size, line_start, line_end, breadcrumbs_length: get_block_breadcrumbs_length(`${this.key}${sub_key}`), children: [] }); } nodes.sort((a, b) => a.line_start - b.line_start || b.line_end - a.line_end || a.sub_key.length - b.sub_key.length); const roots = []; const stack = []; let overlap = null; for (const node of nodes) { while (stack.length) { const parent = stack[stack.length - 1]; if (parent.line_start <= node.line_start && parent.line_end >= node.line_end) { break; } if (!overlap && node.line_start <= parent.line_end) { overlap = { parent, node }; } stack.pop(); } if (stack.length) stack[stack.length - 1].children.push(node); else roots.push(node); stack.push(node); } const requires_reimport = invalid_range || Boolean(overlap); if (requires_reimport) this.queue_import(); const selected_sub_keys = /* @__PURE__ */ new Set(); if (overlap) { console.warn("SmartSource: overlapping block ranges rejected", { source_key: this.key, previous: { sub_key: overlap.parent.sub_key, lines: overlap.parent.lines }, current: { sub_key: overlap.node.sub_key, lines: overlap.node.lines } }); } else { for (const root of roots) { const plan = select_block_embedding_plan(root, min_chars, max_input_chars); plan.selected_sub_keys.forEach((sub_key) => selected_sub_keys.add(sub_key)); } } let selection_changed = false; for (const sub_key of block_sub_keys) { const block_data = blocks_data[sub_key]; const should_embed = selected_sub_keys.has(sub_key); if (block_data.should_embed !== should_embed) selection_changed = true; block_data.should_embed = should_embed; if (!should_embed) { const block = this.block_collection?.get?.(`${this.key}${sub_key}`); if (block) { block._queue_embed = false; block.clear_staged_embed_content?.(); } } } this.data.block_embedding_selection = { version: BLOCK_EMBEDDING_SELECTION_VERSION, min_chars, max_input_chars, source_key: this.key, requires_reimport }; if (selection_changed) { this.block_collection?.mark_embed_queue_dirty?.(); } if (selection_changed || !policy_matches || current_policy?.requires_reimport !== requires_reimport) this.queue_save(); return selection_changed; } queue_embed() { const should_queue = this.should_embed; if (Boolean(this._queue_embed) !== Boolean(should_queue)) this.collection?.mark_embed_queue_dirty?.(); this._queue_embed = should_queue; } get vec() { return this.collection.embeddings?.get_item_vector(this, DEFAULT_EMBEDDING_TYPE); } set vec(vec) { this.collection.embeddings?.set_item_vector(this, vec, DEFAULT_EMBEDDING_TYPE); } get last_embed() { return this.collection.embeddings?.get_item_embedding_ref( this, DEFAULT_EMBEDDING_TYPE ) || {}; } get embed_hash() { return this.last_embed.read_hash; } set embed_hash(hash) { if (this.data.embedding?.error) return; const embedding_ref = this.collection.embeddings?.get_item_embedding_ref( this, DEFAULT_EMBEDDING_TYPE ); if (!embedding_ref) return; embedding_ref.read_hash = hash; } get is_unembedded() { return !this.collection.embeddings?.has_current_vector_ref?.(this, DEFAULT_EMBEDDING_TYPE); } }; function select_block_embedding_plan(node, min_chars, max_input_chars) { const content_capacity = Math.max( 0, max_input_chars - node.breadcrumbs_length - 1 ); const self_is_eligible = node.size >= min_chars; const self_loss = self_is_eligible ? Math.max(0, node.size - content_capacity) : Number.POSITIVE_INFINITY; if (!node.children.length) { return self_is_eligible ? { selected_sub_keys: [node.sub_key], loss: self_loss } : { selected_sub_keys: [], loss: node.size }; } const descendant_plan = { selected_sub_keys: [], loss: 0 }; let child_size = 0; let child_line_count = 0; for (const child of node.children) { const child_plan = select_block_embedding_plan(child, min_chars, max_input_chars); descendant_plan.selected_sub_keys.push(...child_plan.selected_sub_keys); descendant_plan.loss += child_plan.loss; child_size += child.size; child_line_count += child.line_end - child.line_start + 1; } if (node.sub_key === "#") { const node_line_count = node.line_end - node.line_start + 1; const uncovered_line_count = node_line_count - child_line_count; const child_separator_count = node.children.length - 1; const uncovered_content_size = node.size - child_size - child_separator_count - uncovered_line_count; descendant_plan.loss += Math.max(0, uncovered_content_size); } if (self_is_eligible && (!descendant_plan.selected_sub_keys.length || self_loss < descendant_plan.loss)) { return { selected_sub_keys: [node.sub_key], loss: self_loss }; } return descendant_plan; } function get_block_breadcrumbs_length(block_key = "") { return block_key.split("/").join(" > ").split("#").slice(0, -1).join(" > ").replace(".md", "").length; } // node_modules/obsidian-smart-env/src/modules/embeddings.js var EMBEDDING_SAVE_CHECKPOINT_SIZE = 1e3; var EmbeddingsVectorAdapter = class extends DefaultEntitiesVectorAdapter { process_embed_queue() { if (this._process_embed_queue_promise) { this._rerun_embed_queue = true; return this._process_embed_queue_promise; } this._process_embed_queue_promise = (async () => { try { do { this._rerun_embed_queue = false; await this.process_embed_queue_once(); if (this._rerun_embed_queue) { this.collection?.mark_embed_queue_dirty?.(); } } while (this._rerun_embed_queue); } finally { this._process_embed_queue_promise = null; } })(); return this._process_embed_queue_promise; } async process_embed_queue_once() { const embed_queue = this.collection?.embed_queue || []; if (!embed_queue.length) return; const owns_embed_run = !this._is_processing_embed_queue; if (owns_embed_run) { this._stored_since_checkpoint = 0; this._uncommitted_embed_items = []; } const collections = get_embed_queue_collections(this.collection); const previous_defer_states = collections.map((collection) => ({ collection, defer_embed_saves: collection?._defer_embed_saves, defer_vector_saves: collection?.embeddings?.defer_vector_saves })); collections.forEach((collection) => { collection._defer_embed_saves = true; if (collection.embeddings) collection.embeddings.defer_vector_saves = true; }); try { const items_by_embeddings = /* @__PURE__ */ new Map(); embed_queue.forEach((item) => { const embeddings = item?.collection?.embeddings || this.collection?.embeddings; if (!embeddings) return; if (!items_by_embeddings.has(embeddings)) items_by_embeddings.set(embeddings, []); items_by_embeddings.get(embeddings).push(item); }); for (const [embeddings, embedding_items] of items_by_embeddings) { const vectors = await embeddings.prepare_vector_file(); const file_info = embeddings.get_active_file_info(); let expected_new_rows = 0; for (const item of embedding_items) { if (!embeddings.has_current_vector_ref( item, DEFAULT_EMBEDDING_TYPE, file_info )) { expected_new_rows += 1; } } if (expected_new_rows && vectors) { await embeddings.reserve_vector_capacity(expected_new_rows, file_info.file); } } return await super.process_embed_queue(); } finally { previous_defer_states.forEach((state) => { const collection = state.collection; if (!collection) return; collection._defer_embed_saves = state.defer_embed_saves; if (collection.embeddings) { collection.embeddings.defer_vector_saves = state.defer_vector_saves; } }); if (owns_embed_run) this._uncommitted_embed_items = null; } } async embed_batch(items = []) { if (this._stored_since_checkpoint >= EMBEDDING_SAVE_CHECKPOINT_SIZE) { await this.flush_embed_checkpoint(); } const groups = /* @__PURE__ */ new Map(); items.forEach((item, item_i) => { const embeddings = item?.collection?.embeddings || this.collection.embeddings; if (!embeddings) { const message = `No embeddings manager found for ${item?.key || "item"}.`; mark_embedding_error(item, message); throw new Error(message); } if (!groups.has(embeddings)) groups.set(embeddings, []); groups.get(embeddings).push({ item, item_i }); }); const results = new Array(items.length); let stored_count = 0; for (const [embeddings, group_entries] of groups) { const group_items = group_entries.map((entry) => entry.item); const group_results = await embeddings.embed_batch(group_items); if (!Array.isArray(group_results) || group_results.length !== group_items.length) { const result_count = Array.isArray(group_results) ? group_results.length : 0; const message = `Embedding batch returned ${result_count} results for ${group_items.length} items.`; group_items.forEach((item) => mark_embedding_error(item, message)); throw new Error(message); } group_results.forEach((result, group_i) => { const entry = group_entries[group_i]; results[entry.item_i] = result; if (result?.skipped && result?.vector_changed !== true) return; this._uncommitted_embed_items?.push(entry.item); }); stored_count += group_results.reduce((count, result) => { return count + (result?.skipped && result?.vector_changed !== true ? 0 : 1); }, 0); } this._stored_since_checkpoint = (this._stored_since_checkpoint || 0) + stored_count; return results; } async commit_embed_run() { await this.flush_embed_checkpoint(); } async flush_embed_checkpoint() { const collections = get_embed_queue_collections(this.collection); try { for (const collection of collections) { if (!collection?.embeddings) continue; collection.embeddings.clear_save_timeout(); await collection.embeddings.save_dirty_files(); } const source_collection = this.collection.source_collection || this.collection; const defer_embed_saves = source_collection._defer_embed_saves; source_collection._defer_embed_saves = false; try { await source_collection.process_save_queue(); } finally { source_collection._defer_embed_saves = defer_embed_saves; } for (const item of this._uncommitted_embed_items || []) { delete item._embedding_commit_pending; } if (this._uncommitted_embed_items) this._uncommitted_embed_items.length = 0; this._stored_since_checkpoint = 0; } catch (error) { for (const item of this._uncommitted_embed_items || []) { item._queue_embed = true; item._embedding_commit_pending = true; item._embed_input = null; item.collection?.mark_embed_queue_dirty?.(); item.source_collection?.mark_embed_queue_dirty?.(); } throw error; } } }; var Embeddings = class _Embeddings { static version = 2; constructor(scope, opts = {}) { this.env = scope?.collection_key ? scope.env : scope; this.collection = scope?.collection_key ? scope : opts.collection || null; this._vectors_by_file = {}; this._append_vectors_by_file = {}; this._dims_by_file = {}; this._vector_lengths_by_file = {}; this._persisted_lengths_by_file = {}; this._dirty_files = /* @__PURE__ */ new Set(); this._rewrite_files = /* @__PURE__ */ new Set(); this._save_timeout = null; this._save_dirty_files_promise = null; this.defer_vector_saves = false; this._unloaded = false; } for_collection(collection) { return new _Embeddings(collection); } get entities_vector_adapter() { if (!this._entities_vector_adapter) { this._entities_vector_adapter = new EmbeddingsVectorAdapter(this.collection); } return this._entities_vector_adapter; } get data_fs() { return this.collection?.data_fs || this.env?.data_fs; } get data_dir() { return this.collection?.data_dir || this.collection?.collection_key || "embeddings"; } get embed_model() { return this.collection?.embed_model || null; } get embed_model_item() { return this.env?.embedding_models?.default || null; } get embed_model_data() { return this.embed_model_item?.data || {}; } get embed_model_key() { return this.embed_model_data.model_key || this.embed_model_item?.model_key || ""; } get embedding_space_id() { const models = this.embed_model_item?.ProviderAdapterClass?.defaults?.models; return models?.[this.embed_model_key]?.semantic_profile?.embedding_space_id || ""; } get_model_fingerprint_key(embedding_space_id = "") { const model_data = this.embed_model_data; const fingerprint_data = { provider_key: model_data.provider_key || "", model_key: model_data.model_key || this.embed_model_key || "", dimensions: model_data.dims || model_data.dimensions || "", max_tokens: Number(model_data.max_tokens || 0) }; if (embedding_space_id) { fingerprint_data.embedding_space_id = embedding_space_id; } return JSON.stringify(fingerprint_data); } get legacy_model_fingerprint() { return `mf_${murmur_hash_32_alphanumeric(this.get_model_fingerprint_key())}`; } get dims() { const dims = Number( this.embed_model_data.dims || this.embed_model_data.dimensions || this._dims_by_file[this.active_file] || 0 ); return Number.isFinite(dims) && dims > 0 ? dims : 0; } get model_fingerprint() { const fingerprint_key = this.get_model_fingerprint_key(this.embedding_space_id); if (this._model_fingerprint_key !== fingerprint_key) { this._model_fingerprint_key = fingerprint_key; this._model_fingerprint = `mf_${murmur_hash_32_alphanumeric(fingerprint_key)}`; } return this._model_fingerprint; } get active_file() { return this.model_fingerprint; } get_active_file_info(file = this.active_file) { const dims = this._dims_by_file[file] || this.dims; return { model_fingerprint: this.model_fingerprint, file, dims, value_count: this.get_vector_value_count(file) }; } get_file_path(file = this.active_file) { return `${this.data_dir}/${file}`; } /** * Remove current-model refs before deleting and rebuilding the active vector file. * * @param {string} [type=DEFAULT_EMBEDDING_TYPE] * @returns {{file: string, model_fingerprint: string, cleared_refs: number}} */ clear_active_embedding_refs(type = DEFAULT_EMBEDDING_TYPE) { const file = this.active_file; const model_fingerprint = this.model_fingerprint; let cleared_refs = 0; Object.values(this.collection?.items || {}).forEach((item) => { if (!item?.data) return; const embedding = item.data.embedding; const ref_cleared = delete_embedding_ref( item, type, model_fingerprint ); const history = Array.isArray(embedding?.history) ? embedding.history : []; const next_history = history.filter((ref) => { return ref?.file !== file && ref?.model_fingerprint !== model_fingerprint; }); const history_cleared = next_history.length !== history.length; const error_cleared = Boolean(embedding?.error); if (!ref_cleared && !history_cleared && !error_cleared) return; if (history_cleared) embedding.history = next_history; if (error_cleared) delete embedding.error; if (ref_cleared) cleared_refs += 1; item._embed_input = null; delete item._embedding_commit_pending; item.clear_staged_embed_content?.(); item.queue_save?.(); }); this.collection?.mark_embed_queue_dirty?.(); return { file, model_fingerprint, cleared_refs }; } /** * Delete the active vector file and clear only its runtime state. * * @returns {Promise<{file: string, path: string, removed: boolean}>} */ async remove_active_vector_file() { const file = this.active_file; const path = this.get_file_path(file); this.clear_save_timeout(); if (this._save_dirty_files_promise) { await this._save_dirty_files_promise; } delete this._vectors_by_file[file]; delete this._append_vectors_by_file[file]; delete this._dims_by_file[file]; delete this._vector_lengths_by_file[file]; delete this._persisted_lengths_by_file[file]; this._dirty_files.delete(file); this._rewrite_files.delete(file); const removed = await this.data_fs.exists(path); if (removed) await remove_file(this.data_fs, path); return { file, path, removed }; } get_item_embedding_ref(item, type = DEFAULT_EMBEDDING_TYPE, model_fingerprint = this.model_fingerprint) { if (migrate_embedding_refs(item, type)) item.queue_save?.(); return get_embedding_ref(item, type, model_fingerprint); } get_item_embedding_refs(item, type = DEFAULT_EMBEDDING_TYPE) { if (migrate_embedding_refs(item, type)) item.queue_save?.(); return get_embedding_refs(item, type); } async flush() { this.clear_save_timeout(); await this.save_dirty_files(); } async unload() { this._unloaded = true; await this.flush(); this.clear_runtime_cache(); } clear_runtime_cache() { this.clear_save_timeout(); this._vectors_by_file = {}; this._append_vectors_by_file = {}; this._dims_by_file = {}; this._vector_lengths_by_file = {}; this._persisted_lengths_by_file = {}; this._dirty_files.clear(); this._rewrite_files.clear(); this._entities_vector_adapter?._reset_embed_queue_stats?.(); this._entities_vector_adapter = null; } async embed_batch(items = [], type = DEFAULT_EMBEDDING_TYPE) { const embed_model = this.embed_model; if (!embed_model) { throw new Error(`No embed_model found for ${this.collection?.collection_key || "collection"}`); } const model_fingerprint = this.model_fingerprint; const active_file = this.active_file; const model_dims = this.dims; await this.prepare_vector_file(active_file, model_dims); const expected_dims = this._dims_by_file[active_file] || model_dims; const prepared_items = []; const results = new Array(items.length).fill(null); for (let i = 0; i < items.length; i += 1) { const item = items[i]; const embed_input = await item.get_embed_input(); if (!embed_input) continue; if (!item.read_hash) { const message = `Missing read_hash for ${item?.key || "item"}.`; mark_embedding_error(item, message); throw new Error(message); } let ref = this.get_item_embedding_ref(item, type, model_fingerprint); let vector_changed = false; if (ref?.file && ref.file !== active_file && ref.read_hash === item.read_hash && expected_dims) { await this.load_vectors(ref.file, expected_dims); if (this._dims_by_file[ref.file] === expected_dims && this.get_vector_value_count(ref.file) % expected_dims === 0) { const legacy_vec = this.get_vector(ref.file, ref.file_i); if (legacy_vec) { ref = this.set_item_vector(item, legacy_vec, type, { model_fingerprint, file: active_file, read_hash: ref.read_hash, at: ref.at }); vector_changed = Boolean(ref); } } } if (ref?.file === active_file && ref.read_hash === item.read_hash && this.has_current_vector_ref(item, type)) { vector_changed ||= item._embedding_commit_pending === true; item._queue_embed = false; item._embed_input = null; results[i] = { skipped: true, vector_changed, vec: this.get_item_vector(item, type) }; continue; } prepared_items.push({ item, item_i: i, embed_input, type }); } if (!prepared_items.length) return results.filter(Boolean); const embeddings = await embed_model.embed_batch( prepared_items.map((entry) => ({ embed_input: entry.embed_input, purpose: "document" })) ); let validated_vecs; try { validated_vecs = validate_embedding_batch( embeddings, prepared_items.length, expected_dims ); } catch (error) { error.response_json = embeddings?.[0]?.response_json; prepared_items.forEach((entry) => { mark_embedding_error(entry.item, error?.message || String(error)); }); throw error; } const vector_dims = validated_vecs[0]?.length || expected_dims; const value_count = this.get_vector_value_count(active_file); if (value_count && value_count % vector_dims !== 0) { const message = `Invalid vector file shape for ${active_file}: ${value_count} values cannot form ${vector_dims}-dimension rows.`; prepared_items.forEach((entry) => mark_embedding_error(entry.item, message)); throw new Error(message); } for (let result_i = 0; result_i < prepared_items.length; result_i += 1) { const entry = prepared_items[result_i]; const result = embeddings[result_i]; const stored_ref = this.set_item_vector(entry.item, validated_vecs[result_i], entry.type, { model_fingerprint, file: active_file, read_hash: entry.item.read_hash }); if (!stored_ref) { const message = `Failed to store embedding vector for ${entry.item?.key || `item ${result_i + 1}`}.`; mark_embedding_error(entry.item, message); throw new Error(message); } results[entry.item_i] = result; } return results.filter(Boolean); } get_item_vector(item, type = DEFAULT_EMBEDDING_TYPE) { const model_fingerprint = this.model_fingerprint; const active_file = this.active_file; const ref = this.get_item_embedding_ref(item, type, model_fingerprint); if (!ref?.file) return void 0; if (ref.file !== active_file) return void 0; if (!ref.read_hash || ref.read_hash !== item.read_hash) return void 0; return this.get_vector(ref.file, ref.file_i); } set_item_vector(item, vec, type = DEFAULT_EMBEDDING_TYPE, params = {}) { const model_fingerprint = params.model_fingerprint || this.model_fingerprint; if (vec === null) { if (!delete_embedding_ref(item, type, model_fingerprint)) return null; item._embed_input = null; item.collection?.mark_embed_queue_dirty?.(); item.source_collection?.mark_embed_queue_dirty?.(); item.queue_save?.(); return null; } if (!vec?.length) return null; const file = params.file || this.active_file; const source_vec = vec instanceof Float32Array ? vec : new Float32Array(vec); const current_ref = this.get_item_embedding_ref(item, type, model_fingerprint); const read_hash = params.read_hash || item.read_hash || ""; const file_i = current_ref?.file === file && current_ref.read_hash === read_hash && Number.isInteger(current_ref.file_i) && current_ref.file_i >= 0 && this.has_vector(file, current_ref.file_i) ? current_ref.file_i : this.append_vector(file, source_vec.length); if (!this.set_vector(file, file_i, source_vec)) return null; set_embedding_ref(item, { file, file_i, read_hash, at: params.at || Date.now() }, type, model_fingerprint); item._queue_embed = false; item._embed_input = null; prune_legacy_embedding_data(item); item.collection?.mark_embed_queue_dirty?.(); item.source_collection?.mark_embed_queue_dirty?.(); item.queue_save?.(); this.queue_save_vectors(); return get_embedding_ref(item, type, model_fingerprint); } async migrate_legacy_item_vectors(type = DEFAULT_EMBEDDING_TYPE) { if (!this.collection?.items) return 0; const migration_model_fingerprint = this.embedding_space_id ? this.legacy_model_fingerprint : this.model_fingerprint; await this.load_vectors(migration_model_fingerprint, this.dims); const previous_defer_vector_saves = this.defer_vector_saves; this.defer_vector_saves = true; let changed_count = 0; try { Object.values(this.collection.items).forEach((item) => { let item_changed = migrate_embedding_refs(item, type); const legacy = item.data?.embeddings?.[this.embed_model_key]; const had_legacy_data = Boolean(item.data?.embeddings || item.data?.last_embed); if (legacy?.vec?.length) { this.set_item_vector(item, legacy.vec, type, { model_fingerprint: migration_model_fingerprint, file: migration_model_fingerprint, read_hash: legacy.last_embed?.hash || item.data?.last_embed?.hash || item.read_hash || "", at: legacy.last_embed?.at || item.data?.last_embed?.at || Date.now() }); item_changed = true; } else if (had_legacy_data) { prune_legacy_embedding_data(item); item_changed = true; } if (!item_changed) return; item.queue_save?.(); changed_count += 1; }); } finally { this.defer_vector_saves = previous_defer_vector_saves; } if (changed_count) await this.save_dirty_files(); return changed_count; } async prepare_vector_file(file = this.active_file, expected_dims = 0) { if (this._vectors_by_file[file]) return this._vectors_by_file[file]; if (this._persisted_lengths_by_file[file] !== void 0) return null; return await this.load_vectors(file, expected_dims); } async load_vectors(file = this.active_file, expected_dims = 0) { if (!file) return new Float32Array(0); if (this._vectors_by_file[file]) return this._vectors_by_file[file]; const path = this.get_file_path(file); const appended = this._append_vectors_by_file[file]; let vectors = new Float32Array(0); if (await this.data_fs.exists(path)) { const raw = await this.data_fs.read_binary(path); const buffer = to_array_buffer(raw); if (!buffer) { const detail = raw?.error ? `: ${raw.error}` : ""; throw new Error(`Failed to read vector file ${path}${detail}`); } if (buffer.byteLength % 4 !== 0) { throw new Error(`Invalid vector file ${path}: byte length ${buffer.byteLength} is not divisible by 4.`); } vectors = new Float32Array(buffer); } const persisted_value_count = vectors.length; const dims = this.resolve_file_dims(file, persisted_value_count, expected_dims); if (appended) { const merged_vectors = new Float32Array(this.get_vector_value_count(file)); merged_vectors.set(vectors); for (const [file_i, vec] of appended) { merged_vectors.set(vec, file_i * dims); } vectors = merged_vectors; } this._vectors_by_file[file] = vectors; this._vector_lengths_by_file[file] = vectors.length; this._persisted_lengths_by_file[file] = persisted_value_count; this._dims_by_file[file] = dims; delete this._append_vectors_by_file[file]; return vectors; } ensure_vectors(file = this.active_file, dims = this.dims) { if (!this._vectors_by_file[file]) { this._vectors_by_file[file] = new Float32Array(0); this._vector_lengths_by_file[file] = 0; this._persisted_lengths_by_file[file] = 0; } if (!Number.isInteger(this._vector_lengths_by_file[file])) { this._vector_lengths_by_file[file] = this._vectors_by_file[file].length; } if (!Number.isInteger(this._persisted_lengths_by_file[file])) { this._persisted_lengths_by_file[file] = this._vector_lengths_by_file[file]; } if (dims) this._dims_by_file[file] = dims; return this._vectors_by_file[file]; } async reserve_vector_capacity(expected_new_rows = 0, file = this.active_file) { if (!Number.isInteger(expected_new_rows) || expected_new_rows <= 0) { return this._vectors_by_file[file]; } await this.load_vectors(file); const dims = this._dims_by_file[file] || this.dims; if (!dims) return this._vectors_by_file[file]; const active_value_count = this.get_vector_value_count(file); return this.ensure_vector_capacity( file, dims, active_value_count + expected_new_rows * dims ); } ensure_vector_capacity(file, dims, min_value_count) { const vectors = this.ensure_vectors(file, dims); if (vectors.length >= min_value_count) return vectors; let next_length = vectors.length || 0; while (next_length < min_value_count) { next_length = next_length ? next_length * 2 : dims; } const active_value_count = this.get_vector_value_count(file); const next_vectors = new Float32Array(next_length); next_vectors.set(vectors.subarray(0, active_value_count)); this._vectors_by_file[file] = next_vectors; return next_vectors; } append_vector(file = this.active_file, dims = this.dims) { dims = Number(dims || 0); if (!dims) return -1; const existing_dims = this._dims_by_file[file]; if (existing_dims && existing_dims !== dims) { console.warn(`[embeddings] Vector dims changed for ${file}; refusing to append incompatible row.`); return -1; } const value_count = this.get_vector_value_count(file); if (value_count % dims !== 0) { console.warn(`[embeddings] Vector file ${file} has incomplete rows for ${dims} dims.`); return -1; } const file_i = value_count / dims; if (!this._vectors_by_file[file]) { this._dims_by_file[file] = dims; this._vector_lengths_by_file[file] = value_count + dims; return file_i; } this.ensure_vector_capacity(file, dims, value_count + dims); this._vector_lengths_by_file[file] = value_count + dims; return file_i; } set_vector(file, file_i, vec) { const source_vec = vec instanceof Float32Array ? vec : new Float32Array(vec || []); const dims = this._dims_by_file[file] || this.dims || source_vec.length; if (!Number.isInteger(file_i) || file_i < 0 || !dims) return false; if (source_vec.length !== dims) { console.warn(`[embeddings] Skipping incompatible vector for ${file}: ${source_vec.length} != ${dims}`); return false; } const start = file_i * dims; const end = start + dims; if (end > this.get_vector_value_count(file)) return false; if (!this._vectors_by_file[file]) { this._append_vectors_by_file[file] ||= /* @__PURE__ */ new Map(); this._append_vectors_by_file[file].set(file_i, source_vec); this._dirty_files.add(file); return true; } const vectors = this.ensure_vector_capacity(file, dims, end); vectors.set(source_vec, start); if (start < (this._persisted_lengths_by_file[file] || 0)) { this._rewrite_files.add(file); } this._dirty_files.add(file); return true; } get_vector(file, file_i) { const vectors = this._vectors_by_file[file]; const dims = this._dims_by_file[file] || this.dims; if (!Number.isInteger(file_i) || file_i < 0 || !dims) return void 0; const start = file_i * dims; const end = start + dims; if (end > this.get_vector_value_count(file)) return void 0; if (vectors) return vectors.subarray(start, end); return this._append_vectors_by_file[file]?.get(file_i); } release_appended_vector(item) { const ref = this.get_item_embedding_ref(item); const appended = this._append_vectors_by_file[ref?.file]; if (appended?.delete(ref?.file_i) && !appended.size) { delete this._append_vectors_by_file[ref.file]; } } has_vector(file, file_i) { return Boolean(this.get_vector(file, file_i)); } has_current_vector_ref(item, type = DEFAULT_EMBEDDING_TYPE, file_info = null) { const model_fingerprint = file_info?.model_fingerprint || this.model_fingerprint; const file = file_info?.file || this.active_file; const ref = this.get_item_embedding_ref(item, type, model_fingerprint); if (ref?.file !== file || !Number.isInteger(ref.file_i) || ref.file_i < 0) return false; if (!ref.read_hash || ref.read_hash !== item.read_hash) return false; const dims = file_info?.dims || this._dims_by_file[file] || this.dims; const value_count = Number.isInteger(file_info?.value_count) ? file_info.value_count : this.get_vector_value_count(file); if (!dims || !value_count) return false; const end = (ref.file_i + 1) * dims; return end <= value_count; } resolve_file_dims(file, vector_value_count = 0, expected_dims = 0) { if (expected_dims) return expected_dims; if (this.dims) return this.dims; const row_count = this.get_file_row_count(file); if (row_count && vector_value_count % row_count === 0) { return vector_value_count / row_count; } return this._dims_by_file[file] || 0; } get_file_row_count(file) { return Object.values(this.collection?.items || {}).reduce((max_file_i, item) => { const refs = this.get_item_embedding_refs(item); Object.values(refs).forEach((ref) => { if (ref?.file !== file || !Number.isInteger(ref.file_i)) return; max_file_i = Math.max(max_file_i, ref.file_i + 1); }); return max_file_i; }, 0); } get_vector_value_count(file = this.active_file) { const value_count = this._vector_lengths_by_file[file]; if (Number.isInteger(value_count) && value_count >= 0) return value_count; return this._vectors_by_file[file]?.length || 0; } queue_save_vectors() { if (this.defer_vector_saves || this._unloaded) return; if (this._save_timeout) clearTimeout(this._save_timeout); this._save_timeout = setTimeout(() => { this._save_timeout = null; this.save_dirty_files().catch((error) => { console.warn("[embeddings] Failed to save vector file", error); }); }, 100); } clear_save_timeout() { if (!this._save_timeout) return; clearTimeout(this._save_timeout); this._save_timeout = null; } async save_dirty_files() { if (this._save_dirty_files_promise) return await this._save_dirty_files_promise; this._save_dirty_files_promise = this._save_dirty_files(); try { return await this._save_dirty_files_promise; } finally { this._save_dirty_files_promise = null; } } async _save_dirty_files() { if (!this._dirty_files.size) return; while (this._dirty_files.size) { const dirty_files = Array.from(this._dirty_files); for (const file of dirty_files) { if (!this._dirty_files.delete(file)) continue; try { await this.save_vectors(file); } catch (error) { this._dirty_files.add(file); throw error; } } } } async save_vectors(file = this.active_file) { const vectors = this._vectors_by_file[file]; const appended = this._append_vectors_by_file[file]; if (!vectors && !appended) return; await this.ensure_data_dir(); const path = this.get_file_path(file); const value_count = this.get_vector_value_count(file); const persisted_value_count = this._persisted_lengths_by_file[file] || 0; if (!vectors) { const dims = this._dims_by_file[file] || this.dims; const first_file_i = persisted_value_count / dims; const row_count = (value_count - persisted_value_count) / dims; const appended_vectors = new Float32Array(row_count * dims); for (let row_i = 0; row_i < row_count; row_i += 1) { const vec = appended.get(first_file_i + row_i); if (!vec) throw new Error(`Missing appended vector row for ${path}.`); appended_vectors.set(vec, row_i * dims); } if (persisted_value_count) { if (!await this.can_append_vectors(file, persisted_value_count, value_count)) { throw new Error(`Cannot append vectors to ${path} without loading the persisted file.`); } await this.data_fs.append_binary(path, appended_vectors.buffer); } else { await this.write_vectors_file(path, appended_vectors.buffer); } this._persisted_lengths_by_file[file] = value_count; return; } const can_append = await this.can_append_vectors(file, persisted_value_count, value_count); const should_rewrite = this._rewrite_files.has(file); if (can_append && !should_rewrite) { try { await this.data_fs.append_binary( path, this.get_vectors_buffer(file, persisted_value_count, value_count) ); } catch (error) { console.warn("[embeddings] append_binary failed; rewriting vector file", { path, error }); await this.write_vectors_file(path, this.get_vectors_buffer(file, 0, value_count)); } } else { await this.write_vectors_file(path, this.get_vectors_buffer(file, 0, value_count)); } this._persisted_lengths_by_file[file] = value_count; this._rewrite_files.delete(file); } async write_vectors_file(path, buffer) { const temp_path = `${path}.tmp`; await this.data_fs.write_binary(temp_path, buffer); await replace_file_with_temp(this.data_fs, temp_path, path); } get_vectors_buffer(file, start_value_count, end_value_count) { const vectors = this._vectors_by_file[file]; const start_byte = vectors.byteOffset + start_value_count * 4; const end_byte = vectors.byteOffset + end_value_count * 4; return vectors.buffer.slice(start_byte, end_byte); } async can_append_vectors(file, persisted_value_count, value_count) { if (!persisted_value_count || value_count <= persisted_value_count) return false; if (typeof this.data_fs?.append_binary !== "function") return false; const path = this.get_file_path(file); if (!await this.data_fs.exists(path)) return false; const stat = await this.data_fs.stat(path); return stat?.size === persisted_value_count * Float32Array.BYTES_PER_ELEMENT; } async ensure_data_dir() { if (!await this.data_fs.exists(this.data_dir)) { await this.data_fs.mkdir(this.data_dir); } } }; function validate_embedding_batch(embeddings, expected_count, expected_dims = 0) { if (!Array.isArray(embeddings)) { throw new Error("Embedding model returned an invalid batch response."); } if (embeddings.length !== expected_count) { throw new Error(`Embedding model returned ${embeddings.length} results for ${expected_count} items.`); } const validated_vecs = new Array(expected_count); let dims = Number(expected_dims || 0); for (let result_i = 0; result_i < embeddings.length; result_i += 1) { const result = embeddings[result_i]; if (result?.error) { throw new Error(`Embedding result ${result_i} failed: ${String(result.error)}`); } if (!result?.vec?.length) { throw new Error(`Embedding result ${result_i} is missing a vector.`); } let vec; try { vec = result.vec instanceof Float32Array ? result.vec : new Float32Array(result.vec); } catch { throw new Error(`Embedding result ${result_i} contains an invalid vector.`); } if (!dims) dims = vec.length; if (vec.length !== dims) { throw new Error(`Embedding result ${result_i} has ${vec.length} dimensions; expected ${dims}.`); } let squared_sum = 0; for (let value_i = 0; value_i < vec.length; value_i += 1) { const value = vec[value_i]; if (!Number.isFinite(value)) { throw new Error(`Embedding result ${result_i} contains non-finite values.`); } squared_sum += value * value; } if (!Number.isFinite(squared_sum) || squared_sum <= 0) { throw new Error(`Embedding result ${result_i} has zero magnitude.`); } validated_vecs[result_i] = vec; } return validated_vecs; } function mark_embedding_error(item, message) { ensure_embedding_data(item).error = message; item._queue_embed = true; item._embed_input = null; item.collection?.mark_embed_queue_dirty?.(); item.source_collection?.mark_embed_queue_dirty?.(); item.queue_save?.(); } function get_embed_queue_collections(collection) { const collections = []; if (collection) collections.push(collection); if (collection?.block_collection && collection.block_collection !== collection) { collections.push(collection.block_collection); } return collections; } function to_array_buffer(raw) { if (!raw) return null; if (raw instanceof ArrayBuffer) return raw; if (ArrayBuffer.isView(raw)) { if (raw.byteOffset === 0 && raw.byteLength === raw.buffer.byteLength) return raw.buffer; return raw.buffer.slice(raw.byteOffset, raw.byteOffset + raw.byteLength); } return null; } async function replace_file_with_temp(fs, temp_path, final_path) { const backup_path = `${final_path}.bak`; if (await fs.exists(backup_path)) await remove_file(fs, backup_path); if (await fs.exists(final_path)) await rename_file(fs, final_path, backup_path); try { await rename_file(fs, temp_path, final_path); } catch (error) { if (await fs.exists(backup_path)) await rename_file(fs, backup_path, final_path); throw error; } if (await fs.exists(backup_path)) await remove_file(fs, backup_path); } async function rename_file(fs, old_path, new_path) { if (typeof fs.adapter?.rename === "function") { return await fs.adapter.rename(old_path, new_path); } return await fs.rename(old_path, new_path); } async function remove_file(fs, path) { if (typeof fs.adapter?.remove === "function") { return await fs.adapter.remove(path); } return await fs.remove(path); } var embeddings_default = { class: Embeddings, version: Embeddings.version }; // 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-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/src/adapters/data/ajson_sharded_sources.js var import_obsidian2 = require("obsidian"); var class_to_collection_key3 = { SmartSource: "smart_sources", SmartNote: "smart_sources", SmartBlock: "smart_blocks", SmartDirectory: "smart_directories" }; var AJSON_LINE_CHUNK_SIZE = 1e3; var AJSON_MAX_BYTES_PER_SHARD = import_obsidian2.Platform.isMobile ? 8 * 1024 * 1024 : 64 * 1024 * 1024; var AJSON_FILE_SCAN_LIMIT = 1e4; var MATERIALIZE_YIELD_EVERY = 2e4; var LEGACY_LOAD_YIELD_EVERY = 25; var utf8_encoder = new TextEncoder(); var AjsonShardedSourcesDataAdapter = class extends AjsonSingleFileCollectionDataAdapter { get legacy_data_dir() { return "multi"; } get max_bytes_per_shard() { return AJSON_MAX_BYTES_PER_SHARD; } get_ajson_file_name(file_i = 0) { const collection_key = this.collection.collection_key; return file_i > 0 ? `${collection_key}_${file_i}.ajson` : `${collection_key}.ajson`; } get_item_data_path(_key = "", file_i = 0) { return `${this.collection.data_dir}/${this.get_ajson_file_name(file_i)}`; } get_ajson_file_path(file_i = 0) { return this.get_item_data_path("", file_i); } get_ajson_file_i_from_path(file_path = "") { const file_name = get_path_basename(file_path); const collection_key = this.collection.collection_key; if (file_name === `${collection_key}.ajson`) return 0; const numbered_prefix = `${collection_key}_`; if (!file_name.startsWith(numbered_prefix) || !file_name.endsWith(".ajson")) return null; const file_i = Number(file_name.slice(numbered_prefix.length, -".ajson".length)); return Number.isInteger(file_i) && file_i > 0 ? file_i : null; } get_ajson_file_info(file_i = 0, size_bytes = 0, record_count = 0) { return { file_i, path: this.get_ajson_file_path(file_i), size_bytes: Number.isFinite(size_bytes) && size_bytes > 0 ? size_bytes : 0, record_count: Number.isInteger(record_count) && record_count > 0 ? record_count : 0 }; } async ensure_data_dir() { if (!await this.fs.exists(this.collection.data_dir)) { await this.fs.mkdir(this.collection.data_dir); } } async ensure_ajson_file(file_i = 0) { await this.ensure_data_dir(); const data_path = this.get_ajson_file_path(file_i); if (!await this.fs.exists(data_path)) { await this.fs.write(data_path, ""); } } async list_ajson_data_files(params = {}) { const { require_base = true } = params; const cached_record_counts = new Map( (this._ajson_file_infos || []).map((info) => [info.file_i, info.record_count || 0]) ); const infos_by_i = /* @__PURE__ */ new Map(); const add_info = (file_path = "") => { const file_i = this.get_ajson_file_i_from_path(file_path); if (file_i === null) return; infos_by_i.set(file_i, this.get_ajson_file_info( file_i, 0, cached_record_counts.get(file_i) || 0 )); }; const base_path = this.get_ajson_file_path(0); const has_base_file = await this.fs.exists(base_path); if (!has_base_file && require_base) return []; if (has_base_file) add_info(base_path); try { if (typeof this.fs.list === "function" && await this.fs.exists(this.collection.data_dir)) { const files = await this.fs.list(this.collection.data_dir); (files || []).forEach((file) => { const file_path = typeof file === "string" ? file : file?.path || file?.name || ""; if (!file_path) return; add_info( file_path.includes("/") || file_path.includes("\\") ? file_path : `${this.collection.data_dir}/${file_path}` ); }); } } catch (error) { console.warn("AjsonShardedSourcesDataAdapter: failed to list AJSON shards", error); } for (let file_i = 1; file_i < AJSON_FILE_SCAN_LIMIT; file_i += 1) { if (infos_by_i.has(file_i)) continue; const path = this.get_ajson_file_path(file_i); if (!await this.fs.exists(path)) break; infos_by_i.set(file_i, this.get_ajson_file_info( file_i, 0, cached_record_counts.get(file_i) || 0 )); } const infos = Array.from(infos_by_i.values()).sort((left, right) => left.file_i - right.file_i); for (const info of infos) { const stat = await this.fs.stat(info.path); info.size_bytes = Number(stat.size || 0); } this._ajson_file_infos = infos.map((info) => ({ ...info })); return infos; } upsert_ajson_file_info(next_info) { if (!next_info) return; if (!Array.isArray(this._ajson_file_infos)) this._ajson_file_infos = []; const existing_i = this._ajson_file_infos.findIndex((info) => info.file_i === next_info.file_i); if (existing_i === -1) { this._ajson_file_infos.push({ ...next_info }); } else { this._ajson_file_infos[existing_i] = { ...this._ajson_file_infos[existing_i], ...next_info }; } this._ajson_file_infos.sort((left, right) => left.file_i - right.file_i); } should_rotate_append_file(file_info, pending_size_bytes = 0, pending_record_count = 1) { if (!file_info) return true; const size_bytes = Number(file_info.size_bytes || 0); if (!size_bytes && pending_record_count <= 1) return false; return size_bytes + pending_size_bytes > this.max_bytes_per_shard; } get_next_append_file_info(file_info = null) { const file_i = Number.isInteger(file_info?.file_i) ? file_info.file_i + 1 : 0; return this.get_ajson_file_info(file_i, 0, 0); } async get_current_append_file_info() { if (this._ajson_file_infos?.length) { return { ...this._ajson_file_infos[this._ajson_file_infos.length - 1] }; } const infos = await this.list_ajson_data_files(); if (!infos.length) { const info = this.get_ajson_file_info(0, 0, 0); this.upsert_ajson_file_info(info); return info; } return { ...infos[infos.length - 1] }; } async append_lines_to_file_info(file_info, lines = [], appended_size_bytes = 0) { if (!lines.length) return 0; await this.ensure_ajson_file(file_info.file_i); await append_ajson_lines(this.fs, file_info.path, lines, { has_existing_content: file_info.size_bytes > 0 }); file_info.size_bytes = (file_info.size_bytes || 0) + appended_size_bytes; file_info.record_count = (file_info.record_count || 0) + lines.length; this.upsert_ajson_file_info(file_info); return lines.length; } async process_load_queue() { this.collection.emit_event("collection:load_started"); await this.ensure_data_dir(); const block_collection = this.env.smart_blocks; await this.collection.embeddings?.load_vectors?.(); await block_collection?.embeddings?.load_vectors?.(); const prepare_context = build_load_prepare_context(this.collection, block_collection); let loaded = 0; let migrated_legacy_data = false; const data_files = await this.list_ajson_data_files(); if (data_files.length) { loaded = await this.parse_ajson_files(data_files, prepare_context); } else if (await this.fs.exists(this.legacy_data_dir)) { loaded = await this.load_legacy_multi_files(prepare_context); migrated_legacy_data = true; } const should_rebuild_for_dims = refresh_prepare_context_file_info( prepare_context, this.collection, block_collection ); const migrated_source_count = this.collection._has_legacy_embedding_data ? await this.collection.embeddings?.migrate_legacy_item_vectors?.() || 0 : 0; const migrated_block_count = block_collection?._has_legacy_embedding_data ? await block_collection?.embeddings?.migrate_legacy_item_vectors?.() || 0 : 0; if (migrated_legacy_data) { await this.commit_legacy_migration(); } if (migrated_source_count || migrated_block_count || should_rebuild_for_dims) { await rebuild_loaded_embed_queue(this.collection, block_collection); } else { await prepare_loaded_items(this.collection.items, prepare_loaded_source, prepare_context.source_params); apply_prepared_embed_queue(this.collection, block_collection, prepare_context); } if (this.should_suggest_compaction(data_files)) { this.show_compact_notice_after_load(); } this.collection.loaded = loaded; this.collection.emit_event("collection:load_completed", { loaded }); } should_suggest_compaction(data_files = []) { if (!data_files.length) return false; const current_bytes = measure_ajson_files(data_files).bytes; let projected_compacted_bytes = 0; let shard_size_bytes = 0; let shard_record_count = 0; for (const source_key in this.collection.items || {}) { const source = this.collection.items[source_key]; if (!source || source.deleted) continue; if (source.source_adapter?.should_persist === false) continue; const line_size_bytes = get_utf8_byte_length(this.get_source_ajson(source)); const separator_size_bytes = shard_record_count ? 1 : 0; if (shard_record_count && shard_size_bytes + separator_size_bytes + line_size_bytes > this.max_bytes_per_shard) { projected_compacted_bytes += shard_size_bytes; shard_size_bytes = 0; shard_record_count = 0; } shard_size_bytes += (shard_record_count ? 1 : 0) + line_size_bytes; shard_record_count += 1; } projected_compacted_bytes += shard_size_bytes; const reclaimable_bytes = current_bytes - projected_compacted_bytes; const min_reclaimable_bytes = this.max_bytes_per_shard / 2; return reclaimable_bytes >= min_reclaimable_bytes && reclaimable_bytes >= projected_compacted_bytes; } show_compact_notice_after_load() { const wait_for_loaded = this.env?.constructor?.wait_for?.({ loaded: true }); if (wait_for_loaded && typeof wait_for_loaded.then === "function") { wait_for_loaded.then(() => this.show_compact_notice()).catch(() => this.show_compact_notice()); return; } this.show_compact_notice(); } show_compact_notice() { if (this._compact_notice_shown) return; if (typeof document === "undefined") return; this._compact_notice_shown = true; const frag = document.createDocumentFragment(); frag.createEl("p", { text: "Smart Environment source data can be compacted to reduce AJSON log size and may improve future startup performance. Compaction only runs when you click the button." }); const compact_btn = frag.createEl("button", { text: "Compact" }); let notice = null; compact_btn.addEventListener("click", async () => { if (this.collection?._defer_embed_saves || this.env.smart_blocks?._defer_embed_saves) { new import_obsidian2.Notice("Smart Environment is still saving embeddings. Try Compact again when embedding is complete."); return; } compact_btn.disabled = true; compact_btn.textContent = "Compacting..."; try { await this.env.smart_blocks?.process_save_queue?.(); await this.collection.process_save_queue?.(); const result = await this.compact_shards(); notice?.hide?.(); new import_obsidian2.Notice( `Smart Environment source data compacted in ${result.duration_ms}ms: ${result.before.bytes} -> ${result.after.bytes} bytes, ${result.before.records} -> ${result.after.records} records, ${result.before.shards} -> ${result.after.shards} shards.` ); } catch (error) { compact_btn.disabled = false; compact_btn.textContent = "Compact"; console.warn("[smart_env] Failed to compact source AJSON data", error); new import_obsidian2.Notice("Failed to compact Smart Environment source data. See console for details."); } }); notice = new import_obsidian2.Notice(frag, 0); } async optimize_source_data(params = {}) { return await this.write_optimized_source_data_files(params); } async write_optimized_source_data_files(params = {}) { const block_collection = this.env.smart_blocks; const source_embeddings = this.collection.embeddings; const block_embeddings = block_collection?.embeddings; const replace_existing_backups = params.replace_existing_backups === true; if (this.collection?._defer_embed_saves || block_collection?._defer_embed_saves) { throw new Error("Cannot optimize source data while embedding saves are deferred."); } if (!source_embeddings || !block_embeddings) { throw new Error("Cannot optimize source data without source and block embeddings."); } if (!replace_existing_backups) { await assert_no_source_data_optimization_backups([ source_embeddings, block_embeddings ]); } const persistent_sources = Object.values(this.collection.items || {}).filter((source) => { return source && !source.deleted && source.source_adapter?.should_persist !== false; }); for (const source of persistent_sources) { source.ensure_block_embedding_selection?.(); if (source.data.block_embedding_selection?.requires_reimport === true) { throw new Error(`Cannot optimize source data while ${source.key} requires re-import.`); } assert_optimization_embedding_data(source.data, source.key); for (const sub_key in source.data.blocks_data || {}) { assert_optimization_embedding_data( source.data.blocks_data[sub_key], `${source.key}${sub_key}` ); } } source_embeddings.clear_save_timeout(); block_embeddings.clear_save_timeout(); await source_embeddings.save_dirty_files(); await block_embeddings.save_dirty_files(); await block_collection?.process_save_queue?.(); await this.collection.process_save_queue?.(); await source_embeddings.load_vectors(); await block_embeddings.load_vectors(); const source_file_info = source_embeddings.get_active_file_info(); const block_file_info = block_embeddings.get_active_file_info(); const old_source_files = await this.list_ajson_data_files({ require_base: false }); const vector_files = [ get_optimization_vector_file(source_embeddings, source_file_info), get_optimization_vector_file(block_embeddings, block_file_info) ]; if (!replace_existing_backups) { await assert_no_source_data_optimization_backups([ source_embeddings, block_embeddings ]); } const optimized_source_data = /* @__PURE__ */ new Map(); const source_retained_rows = /* @__PURE__ */ new Map(); const block_retained_rows = /* @__PURE__ */ new Map(); let unexpected_refs_removed = 0; for (const source of persistent_sources) { optimized_source_data.set( source.key, JSON.parse(JSON.stringify(source.data || {})) ); const source_ref = get_current_optimization_ref( source, source_embeddings, source_file_info ); if (source.should_embed === true && source_ref) { source_retained_rows.set(source.key, source_ref.file_i); } else if (source.should_embed !== true && source_ref) { unexpected_refs_removed += 1; } for (const sub_key in source.data.blocks_data || {}) { const block_key = `${source.key}${sub_key}`; const block = block_collection.get?.(block_key); const should_embed = Boolean( block && !block.deleted && source.data.blocks_data[sub_key].should_embed === true ); const block_ref = block ? get_current_optimization_ref(block, block_embeddings, block_file_info) : null; if (should_embed && block_ref) { block_retained_rows.set(block_key, block_ref.file_i); } else if (!should_embed && block_ref) { unexpected_refs_removed += 1; } } } const source_vectors = build_optimized_vectors( source_embeddings, source_file_info, source_retained_rows.values() ); const block_vectors = build_optimized_vectors( block_embeddings, block_file_info, block_retained_rows.values() ); let removed_refs = 0; let removed_history_refs = 0; for (const source of persistent_sources) { const source_data = optimized_source_data.get(source.key); const source_old_file_i = source_retained_rows.get(source.key); const source_new_file_i = Number.isInteger(source_old_file_i) ? source_vectors.row_map.get(source_old_file_i) : null; const source_rewrite = rewrite_optimized_embedding_data( source_data, source_file_info, source_new_file_i ); removed_refs += source_rewrite.removed_refs; removed_history_refs += source_rewrite.removed_history_refs; for (const sub_key in source_data.blocks_data || {}) { const block_key = `${source.key}${sub_key}`; const block_old_file_i = block_retained_rows.get(block_key); const block_new_file_i = Number.isInteger(block_old_file_i) ? block_vectors.row_map.get(block_old_file_i) : null; const block_rewrite = rewrite_optimized_embedding_data( source_data.blocks_data[sub_key], block_file_info, block_new_file_i ); removed_refs += block_rewrite.removed_refs; removed_history_refs += block_rewrite.removed_history_refs; } } vector_files[0].vectors = source_vectors.vectors; vector_files[1].vectors = block_vectors.vectors; for (const vector_file of vector_files) { await vector_file.embeddings.ensure_data_dir(); await vector_file.fs.write_binary( vector_file.temporary_path, vector_file.vectors.buffer ); vector_file.expected_bytes = vector_file.vectors.byteLength; const stat = await vector_file.fs.stat(vector_file.temporary_path); if (Number(stat.size || 0) !== vector_file.expected_bytes) { throw new Error(`Failed to validate optimized vector file ${vector_file.temporary_path}.`); } delete vector_file.vectors; } const new_source_files = []; let lines = []; let shard_size_bytes = 0; let file_i = 0; const flush_lines = async () => { if (!lines.length) return; const canonical_path = this.get_ajson_file_path(file_i); const temporary_path = `${canonical_path}.optimize.tmp`; const content = lines.join("\n"); await this.fs.write(temporary_path, content); const stat = await this.fs.stat(temporary_path); if (Number(stat.size || 0) !== shard_size_bytes) { throw new Error(`Failed to validate optimized source data file ${temporary_path}.`); } new_source_files.push({ fs: this.fs, canonical_path, temporary_path, expected_bytes: shard_size_bytes }); lines = []; shard_size_bytes = 0; file_i += 1; await yield_to_event_loop(); }; for (const source of persistent_sources) { const line = this.get_source_ajson({ collection_key: this.collection.collection_key, key: source.key, data: optimized_source_data.get(source.key) }); const line_size_bytes = get_utf8_byte_length(line); const separator_size_bytes = lines.length ? 1 : 0; if (lines.length && shard_size_bytes + separator_size_bytes + line_size_bytes > this.max_bytes_per_shard) { await flush_lines(); } shard_size_bytes += (lines.length ? 1 : 0) + line_size_bytes; lines.push(line); } await flush_lines(); if (!new_source_files.length) { const canonical_path = this.get_ajson_file_path(0); const temporary_path = `${canonical_path}.optimize.tmp`; await this.fs.write(temporary_path, ""); const stat = await this.fs.stat(temporary_path); if (Number(stat.size || 0) !== 0) { throw new Error(`Failed to validate optimized source data file ${temporary_path}.`); } new_source_files.push({ fs: this.fs, canonical_path, temporary_path, expected_bytes: 0 }); } return { vector_files, old_source_files: old_source_files.map((file) => ({ fs: this.fs, canonical_path: file.path, backup_path: `${file.path}.backup` })), new_source_files, replace_existing_backups, stats: { unexpected_refs_removed, removed_refs, removed_history_refs, retained_source_rows: source_vectors.row_map.size, retained_block_rows: block_vectors.row_map.size, source_vector_bytes: source_vectors.vectors.byteLength, block_vector_bytes: block_vectors.vectors.byteLength, source_shards: new_source_files.length } }; } async finish_source_data_optimization(plan) { const source_embeddings = this.collection.embeddings; const block_embeddings = this.env.smart_blocks?.embeddings; const replace_existing_backups = plan.replace_existing_backups === true; for (const file of [...plan.vector_files, ...plan.new_source_files]) { if (!await file.fs.exists(file.temporary_path)) { throw new Error( `Source data optimization temporary file is missing: ${file.temporary_path}` ); } const stat = await file.fs.stat(file.temporary_path); if (Number(stat.size || 0) !== file.expected_bytes) { throw new Error( `Failed to validate source data optimization temporary file ${file.temporary_path}.` ); } } const prior_backup_files = replace_existing_backups ? await list_source_data_optimization_backup_files([ source_embeddings, block_embeddings ]) : []; if (!replace_existing_backups) { await assert_no_source_data_optimization_backups([ source_embeddings, block_embeddings ]); } await Promise.all([ source_embeddings?.unload?.(), block_embeddings?.unload?.() ]); for (const file of plan.vector_files) { if (await file.fs.exists(file.backup_path)) { if (!replace_existing_backups) { throw new Error(`Source data optimization backup already exists: ${file.backup_path}`); } await remove_file2(file.fs, file.backup_path); } if (await file.fs.exists(file.canonical_path)) { await rename_file2(file.fs, file.canonical_path, file.backup_path); } await rename_file2(file.fs, file.temporary_path, file.canonical_path); } for (const file of plan.old_source_files) { if (await file.fs.exists(file.backup_path)) { if (!replace_existing_backups) { throw new Error(`Source data optimization backup already exists: ${file.backup_path}`); } await remove_file2(file.fs, file.backup_path); } if (await file.fs.exists(file.canonical_path)) { await rename_file2(file.fs, file.canonical_path, file.backup_path); } } for (const file of plan.new_source_files) { await rename_file2(file.fs, file.temporary_path, file.canonical_path); } if (replace_existing_backups) { const current_backup_paths = [ ...plan.vector_files.map((file) => file.backup_path), ...plan.old_source_files.map((file) => file.backup_path) ]; for (const backup_file of prior_backup_files) { if (current_backup_paths.includes(backup_file.path)) continue; if (await backup_file.fs.exists(backup_file.path)) { await remove_file2(backup_file.fs, backup_file.path); } } } globalThis.window.location.reload(); } async process_save_queue() { if (this.collection?._defer_embed_saves) return; return await this.queue_ajson_write(() => this._process_save_queue()); } async _process_save_queue() { if (this.collection?._defer_embed_saves) return; this.collection.emit_event("collection:save_started"); const save_queue = Object.values(this.collection.items || {}).filter((source) => source._queue_save); if (!save_queue.length) { this.collection.emit_event("collection:save_completed", { saved: 0 }); return; } save_queue.forEach((source) => { source._queue_save = false; clear_source_block_save_flags(source); }); try { await this._append_sources(save_queue); } catch (error) { save_queue.forEach((source) => { source._queue_save = true; queue_source_block_save_flags(source); }); throw error; } save_queue.forEach((source) => { if (source.deleted) delete_source_and_blocks(source); }); this.collection.emit_event("collection:save_completed", { saved: save_queue.length }); } async append_sources(sources = []) { if (!sources.length) return; return await this.queue_ajson_write(() => this._append_sources(sources)); } async assert_ajson_base_ready(action = "append") { if (await this.fs.exists(this.get_ajson_file_path(0))) return; if (await this.fs.exists(this.legacy_data_dir)) { throw new Error(`Cannot ${action} source AJSON before legacy migration is committed.`); } const uncommitted_files = await this.list_ajson_data_files({ require_base: false }); if (!uncommitted_files.length) return; throw new Error( `Cannot ${action} source AJSON while numbered shards exist without shard 0. Run recover_uncommitted_shards() first.` ); } async _append_sources(sources = []) { await this.assert_ajson_base_ready(); await this.ensure_data_dir(); let current_file_info = await this.get_current_append_file_info(); let lines = []; let pending_size_bytes = 0; const flush_lines = async () => { if (!lines.length) return; await this.append_lines_to_file_info(current_file_info, lines, pending_size_bytes); lines = []; pending_size_bytes = 0; await yield_to_event_loop(); }; for (let i = 0; i < sources.length; i += 1) { const source = sources[i]; if (!source) continue; if (!source.deleted && source.source_adapter?.should_persist === false) { source._queue_save = false; continue; } const line = this.get_source_ajson(source); const line_size_bytes = get_utf8_byte_length(line); const separator_size_bytes = current_file_info.size_bytes || lines.length ? 1 : 0; const next_pending_size_bytes = pending_size_bytes + separator_size_bytes + line_size_bytes; if (this.should_rotate_append_file( current_file_info, next_pending_size_bytes, lines.length + 1 )) { await flush_lines(); const next_append_size_bytes = (current_file_info.size_bytes ? 1 : 0) + line_size_bytes; if (this.should_rotate_append_file(current_file_info, next_append_size_bytes, 1)) { current_file_info = this.get_next_append_file_info(current_file_info); } } pending_size_bytes += (current_file_info.size_bytes || lines.length ? 1 : 0) + line_size_bytes; lines.push(line); if (lines.length >= AJSON_LINE_CHUNK_SIZE) { await flush_lines(); } } await flush_lines(); } async compact_shards() { return await this.queue_ajson_write(() => this._compact_shards()); } async recover_uncommitted_shards() { return await this.queue_ajson_write(async () => { if (await this.fs.exists(this.get_ajson_file_path(0))) { throw new Error("Cannot recover uncommitted source AJSON after shard 0 is committed."); } if (await this.fs.exists(this.legacy_data_dir)) { throw new Error("Cannot recover uncommitted source AJSON while legacy migration is pending."); } const uncommitted_files = await this.list_ajson_data_files({ require_base: false }); if (!uncommitted_files.length) { throw new Error("Cannot recover uncommitted source AJSON because no numbered shards were found."); } return await this._compact_shards({ commit_base_last: true, include_uncommitted_files: true }); }); } async commit_legacy_migration() { const queued_sources = Object.values(this.collection.items || {}).filter((source) => source?._queue_save); const queued_blocks = Object.values(this.env.smart_blocks?.items || {}).filter((block) => block?._queue_save); queued_sources.forEach((source) => { source._queue_save = false; }); queued_blocks.forEach((block) => { block._queue_save = false; }); try { await this.queue_ajson_write(() => this._compact_shards({ commit_base_last: true, include_uncommitted_files: true })); } catch (error) { queued_sources.forEach((source) => { source._queue_save = true; }); queued_blocks.forEach((block) => { block._queue_save = true; }); throw error; } } async _compact_shards(params = {}) { if (this.collection?._defer_embed_saves || this.env.smart_blocks?._defer_embed_saves) { throw new Error("Cannot compact AJSON data while embedding saves are deferred."); } if (params.include_uncommitted_files !== true) { await this.assert_ajson_base_ready("compact"); } const started_at = Date.now(); const commit_base_last = params.commit_base_last === true; await this.ensure_data_dir(); const old_files = await this.list_ajson_data_files({ require_base: params.include_uncommitted_files !== true }); const before = measure_ajson_files(old_files); const final_infos = []; const temp_paths = []; let after_bytes = 0; let lines = []; let shard_size_bytes = 0; let file_i = 0; const flush_lines = async () => { if (!lines.length) return; const path = this.get_ajson_file_path(file_i); const temp_path = `${path}.tmp`; const content = lines.join("\n"); await this.fs.write(temp_path, content); after_bytes += shard_size_bytes; temp_paths.push({ temp_path, path }); final_infos.push(this.get_ajson_file_info(file_i, shard_size_bytes, lines.length)); lines = []; shard_size_bytes = 0; file_i += 1; await yield_to_event_loop(); }; for (const source_key in this.collection.items || {}) { const source = this.collection.items[source_key]; if (!source || source.deleted) continue; if (source.source_adapter?.should_persist === false) continue; const line = this.get_source_ajson(source); const line_size_bytes = get_utf8_byte_length(line); const separator_size_bytes = lines.length ? 1 : 0; if (lines.length && shard_size_bytes + separator_size_bytes + line_size_bytes > this.max_bytes_per_shard) { await flush_lines(); } shard_size_bytes += (lines.length ? 1 : 0) + line_size_bytes; lines.push(line); } await flush_lines(); if (!final_infos.length) { const path = this.get_ajson_file_path(0); const temp_path = `${path}.tmp`; await this.fs.write(temp_path, ""); temp_paths.push({ temp_path, path }); final_infos.push(this.get_ajson_file_info(0, 0, 0)); } const final_paths = new Set(final_infos.map((info) => info.path)); const base_path = this.get_ajson_file_path(0); if (commit_base_last) { for (const { temp_path, path } of temp_paths) { if (path === base_path) continue; await replace_file_with_temp2(this.fs, temp_path, path); } for (const old_file of old_files) { if (old_file.path === base_path || final_paths.has(old_file.path)) continue; if (!await this.fs.exists(old_file.path)) continue; await remove_file2(this.fs, old_file.path); if (await this.fs.exists(old_file.path)) { throw new Error(`Failed to remove obsolete AJSON shard ${old_file.path}`); } } const base_temp = temp_paths.find(({ path }) => path === base_path); await replace_file_with_temp2(this.fs, base_temp.temp_path, base_temp.path); } else { for (const { temp_path, path } of temp_paths) { await replace_file_with_temp2(this.fs, temp_path, path); } for (const old_file of old_files) { if (final_paths.has(old_file.path)) continue; if (!await this.fs.exists(old_file.path)) continue; await remove_file2(this.fs, old_file.path); if (await this.fs.exists(old_file.path)) { throw new Error(`Failed to remove obsolete AJSON shard ${old_file.path}`); } } } this._ajson_file_infos = final_infos; this._needs_minimal_rewrite = false; this._has_stale_source_records = false; return { before, after: { bytes: after_bytes, records: final_infos.reduce((total, info) => total + info.record_count, 0), shards: final_infos.length }, duration_ms: Date.now() - started_at }; } queue_ajson_write(write) { const next_write = Promise.resolve(this._ajson_write_promise).catch(() => { }).then(write); this._ajson_write_promise = next_write; return next_write; } get_source_ajson(source) { const data_value = source.deleted ? "null" : JSON.stringify(source.data || {}); return `${JSON.stringify(`${source.collection_key}:${source.key}`)}: ${data_value},`; } async parse_ajson(raw_data = "", prepare_context = null, params = {}) { const state = this.create_ajson_parse_state(prepare_context, params); this.reset_ajson_parse_flags(); this._needs_minimal_rewrite = false; await this.parse_ajson_raw_into_state(raw_data, state); return await this.materialize_ajson_parse_state(state, prepare_context); } async parse_ajson_files(data_files = [], prepare_context = null) { const state = this.create_ajson_parse_state(prepare_context); this.reset_ajson_parse_flags(); this._needs_minimal_rewrite = false; const sorted_data_files = data_files.slice().sort((left, right) => left.file_i - right.file_i); for (const file_info of sorted_data_files) { const raw_data = require_ajson_text( await this.fs.read(file_info.path, "utf-8", { no_cache: true }), file_info.path ); file_info.record_count = raw_data.length ? await this.parse_ajson_raw_into_state(raw_data, state) : 0; this.upsert_ajson_file_info(file_info); await yield_to_event_loop(); } return await this.materialize_ajson_parse_state(state, prepare_context); } create_ajson_parse_state(prepare_context = null, params = {}) { return { source_values: {}, legacy_block_values_by_source: {}, active_source_keys: prepare_context?.active_source_keys || null, legacy_mode: params.legacy_mode === true, tombstoned_source_keys: /* @__PURE__ */ new Set(), skipped_source_count: 0 }; } reset_ajson_parse_flags() { this.collection._has_legacy_embedding_data = false; if (this.env.smart_blocks) this.env.smart_blocks._has_legacy_embedding_data = false; } async parse_ajson_raw_into_state(raw_data = "", state = this.create_ajson_parse_state()) { const parse_records = state.legacy_mode ? parse_legacy_ajson_records : parse_ajson_records; return await parse_records(raw_data, (full_key, value) => { const { collection_key, item_key, changed } = parse_ajson_key(full_key); if (collection_key === this.collection.collection_key) { if (!should_load_source_key(state, item_key)) { if (value) { state.skipped_source_count += 1; this._has_stale_source_records = true; } return; } if (changed) this._needs_minimal_rewrite = true; if (has_legacy_embedding_data(value)) { this.collection._has_legacy_embedding_data = true; } if (!value) { delete state.source_values[item_key]; delete state.legacy_block_values_by_source[item_key]; state.tombstoned_source_keys.add(item_key); this._has_stale_source_records = true; return; } state.tombstoned_source_keys.delete(item_key); state.source_values[item_key] = value; return; } if (collection_key !== "smart_blocks") return; if (!state.legacy_mode) { this._needs_minimal_rewrite = true; return; } const source_key = get_source_key_from_block_key(item_key); if (!source_key || !should_load_source_key(state, source_key) || state.tombstoned_source_keys.has(source_key)) { if (value) { state.skipped_source_count += 1; this._has_stale_source_records = true; } return; } if (changed) this._needs_minimal_rewrite = true; if (has_legacy_embedding_data(value) && this.env.smart_blocks) { this.env.smart_blocks._has_legacy_embedding_data = true; } if (!state.legacy_block_values_by_source[source_key]) state.legacy_block_values_by_source[source_key] = {}; state.legacy_block_values_by_source[source_key][item_key] = value; this._needs_minimal_rewrite = true; }); } async materialize_ajson_parse_state(state = this.create_ajson_parse_state(), prepare_context = null) { const source_values = state.source_values || {}; const legacy_block_values_by_source = state.legacy_block_values_by_source || {}; let loaded = 0; let processed = 0; for (const source_key in source_values) { const source_data = source_values[source_key]; if (!source_data) { this.delete_source_entry(source_key); } else { const source = await this.load_source_entry( source_key, source_data, legacy_block_values_by_source[source_key], prepare_context, { legacy_mode: state.legacy_mode } ); if (source) loaded += 1; } processed += 1; if (processed % MATERIALIZE_YIELD_EVERY === 0) await yield_to_event_loop(); } return loaded; } async load_source_entry(source_key, raw_data = {}, legacy_block_values = {}, prepare_context = null, params = {}) { let source = this.collection.get(source_key); if (!source) return null; const previous_block_sub_keys = new Set(Object.keys(source?.data?.blocks_data || {})); if (!params.legacy_mode && raw_data.blocks) { this._needs_minimal_rewrite = true; } if (has_legacy_embedding_data(raw_data)) this.collection._has_legacy_embedding_data = true; const source_data = params.legacy_mode ? migrate_legacy_source_data(source_key, raw_data, legacy_block_values) : normalize_sharded_source_data(source_key, raw_data); source_data.class_name = source_data.class_name || this.collection.item_class_name; source.data = source_data; if (prepare_context) { prepare_loaded_source(source, prepare_context.source_params); } else { source._queue_load = false; source.loaded_at = Date.now(); } await this.load_source_blocks(source, previous_block_sub_keys, prepare_context); return source; } async load_source_blocks(source, previous_block_sub_keys = null, prepare_context = null) { const block_collection = this.env.smart_blocks; if (!block_collection) return; const blocks_data = source.data.blocks_data || {}; const current_block_sub_keys = /* @__PURE__ */ new Set(); const loaded_blocks = []; let processed = 0; for (const block_ref of Object.keys(blocks_data)) { const sub_key = get_sub_key_from_block_ref(source.key, block_ref); const block_data = blocks_data[block_ref]; if (!sub_key || !block_data) { delete blocks_data[block_ref]; this._needs_minimal_rewrite = true; continue; } if (has_legacy_embedding_data(block_data) && this.env.smart_blocks) { this.env.smart_blocks._has_legacy_embedding_data = true; } if (Array.isArray(block_data) || Object.prototype.hasOwnProperty.call(block_data, "path") || Object.prototype.hasOwnProperty.call(block_data, "class_name")) { this._needs_minimal_rewrite = true; } if (block_ref !== sub_key && Object.prototype.hasOwnProperty.call(blocks_data, sub_key)) { delete blocks_data[block_ref]; this._needs_minimal_rewrite = true; continue; } current_block_sub_keys.add(sub_key); const next_data = ensure_loaded_block_data(source.key, sub_key, block_data); if (block_ref !== sub_key) { delete blocks_data[block_ref]; this._needs_minimal_rewrite = true; } source.data.blocks_data[sub_key] = next_data; let block = block_collection.get(next_data.key); if (!block) { block = new block_collection.item_type(this.env); set_loaded_block_refs(block, source, sub_key, next_data); block_collection.set(block); } else { set_loaded_block_refs(block, source, sub_key, next_data); } loaded_blocks.push(block); processed += 1; if (processed % MATERIALIZE_YIELD_EVERY === 0) await yield_to_event_loop(); } for (let i = 0; i < loaded_blocks.length; i += 1) { if (prepare_context) { prepare_loaded_block(loaded_blocks[i], prepare_context.block_params); } else { loaded_blocks[i]._queue_load = false; loaded_blocks[i].loaded_at = Date.now(); } } if (!previous_block_sub_keys) return; processed = 0; previous_block_sub_keys.forEach((sub_key) => { if (current_block_sub_keys.has(sub_key)) return; delete block_collection.items[`${source.key}${sub_key}`]; processed += 1; }); if (processed) await yield_to_event_loop(); } delete_source_entry(source_key) { delete_source_and_blocks(this.collection.get(source_key)); } async list_legacy_files() { const paths = []; Object.keys(this.collection.items || {}).forEach((key) => { const source_key = key.split("#")[0]; if (source_key.length > 200) return; paths.push( "multi" + this.fs.sep + source_key.replace(/[\s\/.]/g, "_").replace(".md", "") + ".ajson" ); }); return [...new Set(paths)]; } async load_legacy_multi_files(prepare_context = null) { const files = await this.list_legacy_files(); let loaded = 0; for (let i = 0; i < files.length; i += 1) { const file = files[i]; const path = typeof file === "string" ? file : file?.path; if (!path || !path.endsWith(".ajson")) continue; const read_result = await this.fs.read(path, "utf-8"); if (read_result === null) continue; const raw_data = require_ajson_text(read_result, path); if (!raw_data.length) continue; const had_source_legacy_embedding_data = this.collection._has_legacy_embedding_data === true; const had_block_legacy_embedding_data = this.env.smart_blocks?._has_legacy_embedding_data === true; try { loaded += await this.parse_ajson(raw_data, prepare_context, { legacy_mode: true }); } catch (error) { this.collection._has_legacy_embedding_data = had_source_legacy_embedding_data; if (this.env.smart_blocks) { this.env.smart_blocks._has_legacy_embedding_data = had_block_legacy_embedding_data; } const parse_error = new Error(`Failed to parse legacy AJSON file ${path}: ${error.message}`); parse_error.cause = error; throw parse_error; } this.collection._has_legacy_embedding_data = Boolean( this.collection._has_legacy_embedding_data || had_source_legacy_embedding_data ); if (this.env.smart_blocks) { this.env.smart_blocks._has_legacy_embedding_data = Boolean( this.env.smart_blocks._has_legacy_embedding_data || had_block_legacy_embedding_data ); } if (i > 0 && i % LEGACY_LOAD_YIELD_EVERY === 0) await yield_to_event_loop(); } return loaded; } }; function parse_ajson_key(full_key = "") { let [collection_key, ...item_key_parts] = String(full_key).split(":"); let changed = false; if (class_to_collection_key3[collection_key]) { collection_key = class_to_collection_key3[collection_key]; changed = true; } return { collection_key, item_key: item_key_parts.join(":"), changed }; } function parse_legacy_ajson_records(raw_data = "", on_record) { const property_stream = String(raw_data || "").trim().replace(/,\s*$/, ""); if (!property_stream) return 0; const records = JSON.parse(`{${property_stream}}`); const entries = Object.entries(records); for (const [full_key, value] of entries) { on_record(full_key, value); } return entries.length; } async function parse_ajson_records(raw_data = "", on_record) { const text = String(raw_data || ""); let cursor = 0; let lines = []; let record_count = 0; while (cursor <= text.length) { const next_newline = text.indexOf("\n", cursor); const end = next_newline === -1 ? text.length : next_newline; const line = text.slice(cursor, end).trim(); if (line) { lines.push(line); record_count += 1; } if (lines.length >= AJSON_LINE_CHUNK_SIZE) { for (let i = 0; i < lines.length; i += 1) parse_ajson_line(lines[i], on_record); lines = []; await yield_to_event_loop(); } if (next_newline === -1) break; cursor = next_newline + 1; } if (lines.length) { for (let i = 0; i < lines.length; i += 1) parse_ajson_line(lines[i], on_record); } return record_count; } function parse_ajson_line(line = "", on_record) { const trimmed = String(line || "").trim(); const json_line = trimmed.replace(/,\s*$/, ""); if (!json_line) return; try { const parsed = parse_ajson_key_value(json_line); if (!parsed) return; on_record(parsed.key, parsed.value); } catch (error) { console.warn("AjsonShardedSourcesDataAdapter: skipped invalid AJSON line", error); } } function parse_ajson_key_value(json_line = "") { if (!json_line.startsWith('"')) { const parsed = JSON.parse(`{${json_line}}`); const key = Object.keys(parsed)[0]; return { key, value: parsed[key] }; } let key_end = -1; for (let i = 1; i < json_line.length; i += 1) { if (json_line[i] !== '"') continue; let escape_count = 0; for (let j = i - 1; j >= 0 && json_line[j] === "\\"; j -= 1) escape_count += 1; if (escape_count % 2 === 0) { key_end = i + 1; break; } } if (key_end === -1) return null; const colon_i = json_line.indexOf(":", key_end); if (colon_i === -1) return null; return { key: JSON.parse(json_line.slice(0, key_end)), value: JSON.parse(json_line.slice(colon_i + 1).trim()) }; } function should_load_source_key(state, source_key = "") { if (!state?.active_source_keys) return true; return state.active_source_keys.has(source_key); } function normalize_sharded_source_data(source_key, raw_data = {}) { const source_data = raw_data || {}; delete source_data.blocks; source_data.key = source_key; source_data.path = source_key; return source_data; } function migrate_legacy_source_data(source_key, raw_data = {}, legacy_block_values = {}) { const source_data = raw_data || {}; const blocks_initialized = Boolean(source_data.blocks); const legacy_blocks = source_data.blocks || {}; const source_blocks_data = source_data.blocks_data || {}; const blocks_data = {}; for (const block_ref in legacy_blocks) { const sub_key = get_sub_key_from_block_ref(source_key, block_ref); if (!sub_key) continue; const block_key = `${source_key}${sub_key}`; const block_data = { ...source_blocks_data[sub_key] || source_blocks_data[block_key] || {}, ...legacy_block_values[block_key] || legacy_block_values[sub_key] || {}, key: block_key, lines: legacy_blocks[block_ref] }; delete block_data.path; delete block_data.class_name; blocks_data[sub_key] = block_data; } delete source_data.blocks; if (blocks_initialized) { source_data.blocks_data = blocks_data; } else { delete source_data.blocks_data; } source_data.key = source_key; source_data.path = source_key; return source_data; } function ensure_loaded_block_data(source_key, sub_key, block_data) { const block_key = `${source_key}${sub_key}`; if (Array.isArray(block_data)) block_data = { lines: block_data }; block_data.key = block_key; delete block_data.path; delete block_data.class_name; return block_data; } function set_loaded_block_refs(block, source, sub_key, data) { block._block_key = data.key || `${source.key}${sub_key}`; block._source_key = source.key; block._sub_key = sub_key; block._source = source; block._pending_data = null; block._data_ref = data; block._data_source = source; block._data_sub_key = sub_key; block._data_source_data = source.data; block.deleted = false; } function has_legacy_embedding_data(data = {}) { return Boolean( data?.embeddings || data?.last_embed || has_legacy_embedding_refs(data) ); } function build_load_prepare_context(source_collection, block_collection, loaded_at = Date.now()) { return { active_source_keys: new Set(Object.keys(source_collection.items || {})), source_params: { loaded_at, embeddings: source_collection.embeddings, file_info: source_collection.embeddings?.get_active_file_info?.(), embed_queue: [], prepared_keys: /* @__PURE__ */ new Set() }, block_params: { loaded_at, embeddings: block_collection?.embeddings, file_info: block_collection?.embeddings?.get_active_file_info?.(), embed_queue: [], prepared_keys: /* @__PURE__ */ new Set(), min_chars: block_collection?.settings?.min_chars } }; } function refresh_prepare_context_file_info(prepare_context, source_collection, block_collection) { const had_source_dims = Boolean(prepare_context.source_params.file_info?.dims); const had_block_dims = Boolean(prepare_context.block_params.file_info?.dims); prepare_context.source_params.file_info = refresh_embedding_file_info(source_collection.embeddings); prepare_context.block_params.file_info = refresh_embedding_file_info(block_collection?.embeddings); return !had_source_dims && Boolean(prepare_context.source_params.file_info?.dims) || !had_block_dims && Boolean(prepare_context.block_params.file_info?.dims); } function refresh_embedding_file_info(embeddings) { if (!embeddings) return null; const file = embeddings.active_file; const value_count = embeddings.get_vector_value_count(file); if (value_count) { embeddings._dims_by_file[file] = embeddings.resolve_file_dims(file, value_count); } return embeddings.get_active_file_info(); } async function rebuild_loaded_embed_queue(source_collection, block_collection, loaded_at = Date.now()) { const prepare_context = build_load_prepare_context(source_collection, block_collection, loaded_at); await prepare_loaded_items(source_collection.items, prepare_loaded_source, prepare_context.source_params); await prepare_loaded_items(block_collection?.items, prepare_loaded_block, prepare_context.block_params); apply_prepared_embed_queue(source_collection, block_collection, prepare_context); } function apply_prepared_embed_queue(source_collection, block_collection, prepare_context) { const source_embed_queue = prepare_context.source_params.embed_queue; const block_embed_queue = prepare_context.block_params.embed_queue; source_collection._embed_queue = source_embed_queue.concat(block_embed_queue); source_collection._embed_queue_ready = true; if (block_collection) { block_collection._embed_queue = block_embed_queue; block_collection._embed_queue_ready = true; } } async function prepare_loaded_items(items = {}, prepare_fn, params = {}) { let processed = 0; for (const key in items || {}) { if (params.prepared_keys?.has(key)) continue; prepare_fn(items[key], params); processed += 1; if (processed % MATERIALIZE_YIELD_EVERY === 0) await yield_to_event_loop(); } } function mark_item_prepared(item, params = {}) { if (!item?.key || !params.prepared_keys) return false; if (params.prepared_keys.has(item.key)) return true; params.prepared_keys.add(item.key); return false; } function prepare_loaded_source(source, params = {}) { if (mark_item_prepared(source, params)) return; source._queue_load = false; source.loaded_at = params.loaded_at || Date.now(); const has_current_vector = params.embeddings?.has_current_vector_ref?.(source, void 0, params.file_info) === true; if (has_current_vector) source._queue_embed = false; source._queue_import = should_queue_import(source); if (source._queue_embed || !has_current_vector && source.should_embed) { params.embed_queue?.push(source); } } function prepare_loaded_block(block, params = {}) { if (mark_item_prepared(block, params)) return; block._queue_load = false; block.loaded_at = params.loaded_at || Date.now(); const has_current_vector = params.embeddings?.has_current_vector_ref?.(block, void 0, params.file_info) === true; if (has_current_vector) block._queue_embed = false; const should_embed = typeof block.get_should_embed === "function" ? block.get_should_embed({ min_chars: params.min_chars }) : block.should_embed; if (!should_embed) { block._queue_embed = false; return; } if (block._queue_embed || !has_current_vector) { params.embed_queue?.push(block); } } function should_queue_import(source) { if (!source || source.deleted) return false; if (!source.blocks_initialized) return true; if (!source.file) return false; try { return Boolean(source.source_adapter?.outdated); } catch (error) { console.warn(`AjsonShardedSourcesDataAdapter: failed import check for ${source.key}`, error); return true; } } function clear_source_block_save_flags(source) { const block_collection = source?.env?.smart_blocks; if (!source?.key || !block_collection?.items) return; for (const sub_key in source.data.blocks_data || {}) { const block = block_collection.get(`${source.key}${sub_key}`); if (block) block._queue_save = false; } } function queue_source_block_save_flags(source) { const block_collection = source?.env?.smart_blocks; if (!source?.key || !block_collection?.items) return; for (const sub_key in source.data.blocks_data || {}) { const block = block_collection.get(`${source.key}${sub_key}`); if (block) block._queue_save = true; } } function delete_source_and_blocks(source) { if (!source?.key) return; const block_collection = source.env?.smart_blocks; const known_block_sub_keys = Object.keys(source.data?.blocks_data || {}); if (known_block_sub_keys.length) { known_block_sub_keys.forEach((sub_key) => { delete block_collection?.items?.[`${source.key}${sub_key}`]; }); } else { Object.keys(block_collection?.items || {}).forEach((block_key) => { if (get_source_key_from_block_key(block_key) === source.key) { delete block_collection.items[block_key]; } }); } delete source.collection.items[source.key]; } function get_source_key_from_block_key(block_key = "") { return String(block_key).split("#")[0]; } function get_sub_key_from_block_ref(source_key, block_ref = "") { const value = String(block_ref || ""); if (value.startsWith("#")) return value; if (value.startsWith(source_key)) { const sub_key = value.slice(source_key.length); if (sub_key.startsWith("#")) return sub_key; } const parts = value.split("#"); if (parts.length < 2) return ""; return `#${parts.slice(1).join("#")}`; } function assert_optimization_embedding_data(data = {}, item_key = "") { if (data.embeddings || data.last_embed || has_legacy_embedding_refs(data)) { throw new Error(`Cannot optimize source data with legacy embedding refs: ${item_key}`); } } function get_current_optimization_ref(item, embeddings, file_info) { const ref = item.data?.embedding?.default?.[file_info.model_fingerprint]; if (!ref) return null; if (!embeddings.has_current_vector_ref(item, void 0, file_info)) return null; return ref; } function build_optimized_vectors(embeddings, file_info, retained_file_is) { const old_file_is = Array.from(new Set(retained_file_is)).sort((left, right) => left - right); const dims = Number(file_info.dims || 0); if (old_file_is.length && !dims) { throw new Error(`Cannot optimize vector file ${file_info.file} without dimensions.`); } const vectors = new Float32Array(old_file_is.length * dims); const row_map = /* @__PURE__ */ new Map(); old_file_is.forEach((old_file_i, new_file_i) => { const vec = embeddings.get_vector(file_info.file, old_file_i); if (!vec || vec.length !== dims) { throw new Error(`Cannot read vector row ${old_file_i} from ${file_info.file}.`); } vectors.set(vec, new_file_i * dims); row_map.set(old_file_i, new_file_i); }); return { vectors, row_map }; } function rewrite_optimized_embedding_data(data = {}, file_info, next_file_i = null) { const embedding = data.embedding; if (!embedding) return { removed_refs: 0, removed_history_refs: 0 }; const refs = embedding.default; if (refs?.file) { throw new Error("Cannot optimize source data with legacy embedding refs."); } let removed_refs = 0; if (refs) { for (const model_fingerprint in refs) { const ref = refs[model_fingerprint]; if (ref?.file !== file_info.file) continue; if (model_fingerprint === file_info.model_fingerprint && Number.isInteger(next_file_i)) { ref.file_i = next_file_i; continue; } delete refs[model_fingerprint]; removed_refs += 1; } if (!Object.keys(refs).length) delete embedding.default; } const history = Array.isArray(embedding.history) ? embedding.history : []; const next_history = history.filter((ref) => ref?.file !== file_info.file); const removed_history_refs = history.length - next_history.length; if (Array.isArray(embedding.history)) embedding.history = next_history; return { removed_refs, removed_history_refs }; } function get_optimization_vector_file(embeddings, file_info) { const canonical_path = embeddings.get_file_path(file_info.file); return { embeddings, fs: embeddings.data_fs, canonical_path, temporary_path: `${canonical_path}.optimize.tmp`, backup_path: `${canonical_path}.backup`, expected_bytes: 0 }; } function require_ajson_text(raw_data, path) { if (typeof raw_data === "string") return raw_data; const detail = raw_data?.error ? `: ${raw_data.error}` : ""; throw new Error(`Failed to read AJSON file ${path}${detail}`); } function measure_ajson_files(file_infos = []) { let bytes = 0; let records = 0; for (const file_info of file_infos) { bytes += Number(file_info.size_bytes || 0); records += Number(file_info.record_count || 0); } return { bytes, records, shards: file_infos.length }; } function get_utf8_byte_length(value = "") { return utf8_encoder.encode(value).byteLength; } async function append_ajson_lines(fs, path, lines = [], params = {}) { if (!lines.length) return; const prefix = params.has_existing_content ? "\n" : ""; const content = `${prefix}${lines.join("\n")}`; if (typeof fs.adapter?.append === "function") { return await fs.adapter.append(path, content); } return await fs.append(path, content); } async function replace_file_with_temp2(fs, temp_path, final_path) { const backup_path = `${final_path}.bak`; if (await fs.exists(backup_path)) await remove_file2(fs, backup_path); if (await fs.exists(final_path)) await rename_file2(fs, final_path, backup_path); try { await rename_file2(fs, temp_path, final_path); } catch (error) { if (await fs.exists(backup_path)) await rename_file2(fs, backup_path, final_path); throw error; } if (await fs.exists(backup_path)) await remove_file2(fs, backup_path); } async function assert_no_source_data_optimization_backups(embeddings_list = []) { const backup_files = await list_source_data_optimization_backup_files(embeddings_list); if (backup_files.length) { throw new Error(`Source data optimization backup already exists: ${backup_files[0].path}`); } } async function list_source_data_optimization_backup_files(embeddings_list = []) { const backup_files = []; for (const embeddings of embeddings_list) { const fs = embeddings.data_fs; const data_dir = embeddings.data_dir; if (!await fs.exists(data_dir)) continue; const files = await fs.list(data_dir); for (const file of files || []) { const file_path = file?.path || file?.name || file || ""; if (!get_path_basename(file_path).endsWith(".backup")) continue; const backup_path = file_path.includes("/") || file_path.includes("\\") ? file_path : `${data_dir}/${file_path}`; backup_files.push({ fs, path: backup_path }); } } return backup_files; } async function rename_file2(fs, old_path, new_path) { if (typeof fs.adapter?.rename === "function") { return await fs.adapter.rename(old_path, new_path); } return await fs.rename(old_path, new_path); } async function remove_file2(fs, path) { if (typeof fs.adapter?.remove === "function") { return await fs.adapter.remove(path); } return await fs.remove(path); } function get_path_basename(file_path = "") { const normalized_path = String(file_path || "").replace(/\\/g, "/"); return normalized_path.split("/").pop() || ""; } function yield_to_event_loop() { return new Promise((resolve) => setTimeout(resolve, 0)); } // node_modules/obsidian-smart-env/src/collections/smart_sources.js var SmartSources2 = class extends SmartSources { static version = 2; constructor(env, opts = {}) { super(env, opts); this.embeddings = env.embeddings?.for_collection?.(this) || new Embeddings(this); this.entities_vector_adapter = this.embeddings.entities_vector_adapter; } get data_dir() { return "smart_sources"; } get embed_queue() { if (this._embed_queue_ready) return this._embed_queue || []; const embed_queue = []; const embed_blocks = this.block_collection?.settings?.embed_blocks; const source_file_info = this.embeddings?.get_active_file_info?.(); const block_file_info = this.block_collection?.embeddings?.get_active_file_info?.(); for (const source of Object.values(this.items || {})) { const source_has_vector = this.embeddings?.has_current_vector_ref?.(source, void 0, source_file_info) === true; if (source._queue_embed || !source_has_vector && source.should_embed) { embed_queue.push(source); } if (!embed_blocks) continue; const blocks = source.blocks || []; for (let i = 0; i < blocks.length; i += 1) { const block = blocks[i]; if (!block.should_embed) { block._queue_embed = false; continue; } const block_has_vector = this.block_collection?.embeddings?.has_current_vector_ref?.(block, void 0, block_file_info) === true; if (block._queue_embed || !block_has_vector) { embed_queue.push(block); } } } this._embed_queue = embed_queue; this._embed_queue_ready = true; return this._embed_queue; } mark_embed_queue_dirty() { this._embed_queue_ready = false; this._embed_queue = []; } /** * Rebuild the active embedding model's source and block vector files. * References are persisted before the files are removed, then every * persistent source is re-imported so block inputs use current ranges and * already-read source content. * * @returns {Promise} Re-index summary. */ async reindex_embeddings() { if (this._reindex_embeddings_promise) { return await this._reindex_embeddings_promise; } const reindex_promise = (async () => { await this.env?._embedding_model_change_promise; const block_collection = this.block_collection; const embed_adapter = this.entities_vector_adapter; const import_progress = this.get_import_progress_state?.(); if (!this.embeddings) { throw new Error("Cannot re-index embeddings before Smart Sources is loaded."); } if (this._run_re_import_promise || import_progress?.active || embed_adapter?._process_embed_queue_promise || embed_adapter?.is_embed_queue_paused?.()) { throw new Error( "Cannot re-index embeddings while source import or embedding is active or paused." ); } const vec_index_runtime = this.env?.smart_vec_index_runtime; vec_index_runtime?.unload?.(); if (this.sources_re_import_timeout) { clearTimeout(this.sources_re_import_timeout); this.sources_re_import_timeout = null; } try { const source_refs = this.embeddings.clear_active_embedding_refs(); const block_refs = block_collection?.embeddings ? block_collection.embeddings.clear_active_embedding_refs() : { cleared_refs: 0 }; await block_collection?.process_save_queue?.(); await this.process_save_queue?.(); const source_file = await this.embeddings.remove_active_vector_file(); const block_file = block_collection?.embeddings ? await block_collection.embeddings.remove_active_vector_file() : { removed: false }; const sources = Object.values(this.items || {}).filter((source) => { return source && !source.deleted && source.source_adapter && source.source_adapter.should_persist !== false; }); sources.forEach((source) => { this.queue_source_re_import(source, { event_source: "reindex_embeddings" }); }); await this.run_re_import(); await vec_index_runtime?.refresh_vec_index_state?.(); return { sources_queued: sources.length, source_refs_cleared: source_refs.cleared_refs, block_refs_cleared: block_refs.cleared_refs, source_file_removed: source_file.removed, block_file_removed: block_file.removed }; } catch (error) { vec_index_runtime?.register_vec_index_sync_events?.(); throw error; } })(); this._reindex_embeddings_promise = reindex_promise; try { return await reindex_promise; } finally { this._reindex_embeddings_promise = null; } } 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 && old_key !== new_key && this.should_track_runtime_source_key(old_key)) { this.queue_deleted_source_tombstone(old_key); this.remove_source_runtime_state(old_key); } if (!new_key || !this.should_track_runtime_source_key(new_key)) return; let source = this.get(new_key); if (!source) source = this.init_file_path(new_key); if (!source) return; this.queue_source_re_import(source, { event_source: event.event_source }); } handle_source_deleted(event = {}) { if (!this.should_handle_event(event)) return; const key = this.get_event_path(event); if (!key || !this.should_track_runtime_source_key(key)) return; this.queue_deleted_source_tombstone(key); this.remove_source_runtime_state(key); } should_track_runtime_source_key(source_key = "") { if (!source_key) return false; if (this.items?.[source_key]) return true; if (this.fs?.is_excluded?.(source_key)) return false; return Boolean(this.get_extension_for_path(source_key)); } queue_deleted_source_tombstone(source_key = "") { if (!source_key) return; const source = this.get(source_key); if (source) { source.deleted = true; source._queue_save = false; } if (typeof this.data_adapter?.append_sources !== "function") return; const deleted_source = source || { key: source_key, collection_key: this.collection_key, deleted: true }; this._deleted_source_tombstone_promise = this.data_adapter.append_sources([deleted_source]).catch((error) => { console.warn("[smart_env] Failed to append deleted source tombstone", { source_key, error }); }); } remove_source_runtime_state(source_key = "") { if (!source_key) return; if (this.sources_re_import_queue?.[source_key]) { delete this.sources_re_import_queue[source_key]; } remove_file_from_fs_cache(this.fs, source_key); remove_file_from_fs_cache(this.env?.fs, source_key); remove_source_and_blocks_from_items(this, source_key); this.mark_embed_queue_dirty(); this.block_collection?.mark_embed_queue_dirty?.(); } async process_load_queue() { await this.data_adapter.process_load_queue(); if (this.block_collection) { this.block_collection.loaded = Object.keys(this.block_collection.items || {}).length; } const links_before_import = this.links; if (!this.opts.prevent_import_on_load) { await this.process_source_import_queue(this.opts); } if (this.links === links_before_import) this.build_links_map(); } }; function remove_source_and_blocks_from_items(source_collection, source_key = "") { if (!source_collection?.items || !source_key) return; const block_collection = source_collection.block_collection; const block_prefix = `${source_key}#`; Object.keys(block_collection?.items || {}).forEach((block_key) => { if (block_key === source_key || block_key.startsWith(block_prefix)) { delete block_collection.items[block_key]; } }); delete source_collection.items[source_key]; } function remove_file_from_fs_cache(fs, file_path = "") { if (!fs || !file_path) return; if (fs.files) delete fs.files[file_path]; if (Array.isArray(fs.file_paths)) { fs.file_paths = fs.file_paths.filter((path) => path !== file_path); } } var smart_sources_default = { class: SmartSources2, version: SmartSources2.version, item_type: SmartSource2, data_adapter: AjsonShardedSourcesDataAdapter, collection_key: "smart_sources", process_embed_queue: true, load_order: 100 }; // 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); } /** * Resolve source outlinks, optionally limited to a source line range. * * @param {number[]|null} [lines=null] * @param {Array|Object} [source_outlinks=this.data.outlinks] * @returns {Array} */ get_outlinks(lines = null, source_outlinks = this.data.outlinks) { let outlinks = Array.isArray(source_outlinks) ? source_outlinks : Object.values(source_outlinks || {}); if (Array.isArray(lines) && lines.length === 2) { const [line_start, line_end] = lines; outlinks = outlinks.filter((link) => { return typeof link?.line === "number" && link.line >= line_start && link.line <= line_end; }); } return outlinks.map((link) => { const link_data = link && typeof link === "object" ? link : { target: link }; const link_target = link_data.target; const link_ref = link_target?.includes?.("#") ? link_target.split("#")[0] : link_target; 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.item.file_path); return { ...link_data, key: link_path || link_ref, embedded: link_data.embedded || false, source_key: this.item.key }; }).filter(Boolean); } 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 = []; for (const [sub_key, line_range] of Object.entries(blocks_obj)) { const has_existing = this.source.has_block(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 = this.source.has_block(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 section = target.includes("#") ? target.split("#").slice(1).join("#") : void 0; const record = { title, target, line: line_no }; if (embedded) record.embedded = true; if (section) record.section = section; 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/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 = /(?} */ static async init_items(collection) { await super.init_items(collection); Object.values(collection.items || {}).forEach((source) => { if (!this.extensions.includes(source.file_type)) return; if (source.data?.outlinks_version !== MARKDOWN_OUTLINKS_VERSION) { source.queue_import?.(); } }); } /** * Import the source file content, parse blocks and links, and update `item.data`. * @async * @param {object} [params={}] * @param {boolean} [params.refresh=false] * @returns {Promise} */ async import(params = {}) { if (!this.can_import) return; const refresh = params.refresh === true || this.data.outlinks_version !== MARKDOWN_OUTLINKS_VERSION; 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 (!refresh && !is_outdated && !has_incomplete_block_coverage) { this.item.blocks.forEach((block) => { if (!block.vec) block.queue_embed(); }); return; } const content = await this.read(); if (typeof content !== "string") { return; } if (!this.item.vec) { this.item.data.last_import = null; } if (!refresh && !has_incomplete_block_coverage && this.data.last_import?.hash === this.data.last_read?.hash) { if (this.item.blocks_initialized) return; } await this.parse_content(content); await this.item.parse_content(content); this.data.outlinks_version = MARKDOWN_OUTLINKS_VERSION; 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(); if (this.item._queue_embed) { await this.item.get_embed_input(content); } else { this.item._embed_input = null; } } else { this.item._embed_input = null; } } // // 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 === null) content = await this.read(); if (typeof content !== "string") return; return get_markdown_links(content); } async get_metadata(content = null) { if (content === null) content = await this.read(); if (typeof content !== "string") 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.item.blocks_initialized || !this.item.block_collection) return false; return this.item.block_keys.some((sub_key) => { const line_range = this.item.get_block_lines(sub_key); 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_obsidian3 = 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_obsidian3.MarkdownRenderer || !import_obsidian3.htmlToMarkdown) { console.warn("Obsidian environment not found; cannot render markdown."); return content; } const container = document.createElement("div"); await import_obsidian3.MarkdownRenderer.render(app2, content, container, this.item.path, new import_obsidian3.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_obsidian3.htmlToMarkdown)(container); return newMd; } }; // node_modules/obsidian-smart-env/adapters/smart-sources/bases.js var BasesSourceContentAdapter = class extends FileSourceContentAdapter { static extensions = ["base"]; static embed_input_action_key = "source_get_embed_input_base"; get embed_input_action_key() { return this.constructor.embed_input_action_key; } get should_embed() { return false; } static async init_items(collection) { await super.init_items(collection); Object.values(collection.items || {}).forEach((source) => { if (source.file_type !== "base") return; source._queue_embed = false; if (source.vec) source.vec = null; }); } async import() { this.item._queue_embed = false; if (this.item.vec) this.item.vec = null; if (!this.item?.file) return; this.data.outlinks = []; 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"]; static embed_input_action_key = "source_get_embed_input_rendered"; get embed_input_action_key() { return this.constructor.embed_input_action_key; } 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"]; static embed_input_action_key = "source_get_embed_input_canvas"; get embed_input_action_key() { return this.constructor.embed_input_action_key; } 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) { if (!inputs.length) return []; const embed_inputs = inputs.map((item) => ({ embed_input: item.embed_input, purpose: item.purpose })); const result = await this._send_message("embed_batch", { inputs: embed_inputs }); if (!Array.isArray(result) || result.length !== inputs.length) { const result_count = Array.isArray(result) ? result.length : 0; throw new Error( `Embedding model returned ${result_count} results for ${inputs.length} 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/src/adapters/embedding-model/transformers_v4.worker.js var transformers_v4_worker_default = "// Force Transformers.js to select ONNX Runtime Web in Electron workers.\nglobalThis.process = undefined;\n\nconst transformers_v4_url = 'https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.2.0';\n\nfunction get_error_message(error) {\n return error?.message || String(error || 'Unknown error');\n}\n\nasync function is_webgpu_available() {\n if (!globalThis.navigator?.gpu) return false;\n try {\n return Boolean(await navigator.gpu.requestAdapter());\n } catch (_error) {\n return false;\n }\n}\n\nfunction copy_vector(data) {\n return data instanceof Float32Array\n ? new Float32Array(data)\n : Float32Array.from(data || [])\n ;\n}\n\nconst default_semantic_profile = {\n pooling: 'mean',\n normalize: true,\n query_prefix: '',\n document_prefix: '',\n};\n\nclass TransformersWorkerModel {\n constructor(params = {}) {\n const configured_batch_size = Number(params.batch_size);\n this.model_key = params.model_key;\n this.revision = params.revision;\n this.max_tokens = Number(params.max_tokens) || 512;\n this.use_gpu = params.use_gpu !== false;\n this.semantic_profile = {\n ...default_semantic_profile,\n ...params.semantic_profile,\n };\n this.configured_batch_size = Number.isFinite(configured_batch_size)\n && configured_batch_size > 1\n ? Math.min(16, Math.floor(configured_batch_size))\n : null\n ;\n this.dtype = params.dtype || 'auto';\n this.has_gpu = false;\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.transformers_module = null;\n }\n\n get gpu_enabled() {\n return this.use_gpu && this.has_gpu;\n }\n\n get batch_size() {\n if (this.configured_batch_size) return this.configured_batch_size;\n return this.active_config_key?.includes('webgpu') ? 16 : 8;\n }\n\n async load() {\n this.has_gpu = this.use_gpu\n ? await is_webgpu_available()\n : false\n ;\n await this.dispose_pipeline();\n\n if (this.gpu_enabled) {\n try {\n await this.load_pipeline('webgpu');\n return;\n } catch (error) {\n console.warn('[Transformers worker] WebGPU load failed, falling back to WASM', error);\n this.use_gpu = false;\n this.has_gpu = false;\n await this.dispose_pipeline();\n }\n }\n\n await this.load_pipeline('wasm');\n }\n\n async get_transformers_module() {\n if (!this.transformers_module) {\n const started_at = Date.now();\n console.log('[Transformers worker] load: CDN v4 runtime starting');\n this.transformers_module = await import(transformers_v4_url);\n const { env, LogLevel } = this.transformers_module;\n env.allowLocalModels = false;\n if (typeof env.useBrowserCache !== 'undefined') {\n env.useBrowserCache = true;\n }\n if (LogLevel) env.logLevel = LogLevel.ERROR;\n console.log(\n `[Transformers worker] load: CDN v4 runtime ready (${Date.now() - started_at} ms)`,\n );\n }\n return this.transformers_module;\n }\n\n async load_pipeline(device) {\n const { pipeline } = await this.get_transformers_module();\n const config_key = `${device}_${this.dtype}`;\n const started_at = Date.now();\n const pipeline_options = {\n device,\n dtype: this.dtype,\n };\n if (this.revision) pipeline_options.revision = this.revision;\n\n console.log(`[Transformers worker] load: ONNX v4/${config_key} starting`);\n this.pipeline = await pipeline(\n 'feature-extraction',\n this.model_key,\n pipeline_options,\n );\n this.tokenizer = this.pipeline.tokenizer;\n if (!this.tokenizer) {\n throw new Error('Transformers v4 pipeline tokenizer unavailable');\n }\n this.active_config_key = config_key;\n console.log(\n `[Transformers worker] load: ONNX v4/${config_key} ready (${Date.now() - started_at} ms)`,\n );\n }\n\n async load_wasm_pipeline() {\n this.use_gpu = false;\n this.has_gpu = false;\n await this.dispose_pipeline();\n await this.load_pipeline('wasm');\n }\n\n async dispose_pipeline() {\n const pipeline = this.pipeline;\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n if (!pipeline) return;\n\n try {\n if (typeof pipeline.dispose === 'function') {\n await pipeline.dispose();\n } else if (typeof pipeline.destroy === 'function') {\n await pipeline.destroy();\n }\n } catch (error) {\n console.warn('[Transformers worker] failed to dispose pipeline', error);\n }\n }\n\n async count_tokens(input) {\n if (!this.tokenizer) await this.load();\n const { input_ids } = await this.tokenizer(input);\n return { tokens: input_ids.data.length };\n }\n\n async embed_batch(inputs) {\n if (!inputs.length) return [];\n\n const results = new Array(inputs.length);\n const pending_inputs = [];\n\n inputs.forEach((item, input_i) => {\n if (!item.embed_input?.length) {\n results[input_i] = {\n vec: [],\n tokens: 0,\n error: 'Embedding input is empty',\n };\n return;\n }\n pending_inputs.push({ input_i, item });\n });\n\n if (!pending_inputs.length) return results;\n if (!this.pipeline) await this.load();\n\n let pending_i = 0;\n while (pending_i < pending_inputs.length) {\n const batch_size = this.batch_size;\n const batch_entries = pending_inputs.slice(pending_i, pending_i + batch_size);\n const batch_inputs = batch_entries.map((entry) => entry.item);\n const batch_results = await this.process_batch(batch_inputs);\n const result_count = Array.isArray(batch_results) ? batch_results.length : 0;\n\n if (result_count !== batch_inputs.length) {\n throw new Error(\n `Embedding batch returned ${result_count} results for ${batch_inputs.length} inputs`,\n );\n }\n\n batch_entries.forEach((entry, result_i) => {\n results[entry.input_i] = batch_results[result_i];\n });\n pending_i += batch_entries.length;\n }\n return results;\n }\n\n async process_batch(batch_inputs) {\n let batch_error;\n\n try {\n return await this.embed_prepared_batch(batch_inputs);\n } catch (error) {\n batch_error = error;\n }\n\n if (this.active_config_key?.includes('webgpu')) {\n console.warn('[Transformers worker] WebGPU batch failed, retrying on WASM', batch_error);\n await this.load_wasm_pipeline();\n try {\n return await this.embed_prepared_batch(batch_inputs);\n } catch (error) {\n batch_error = error;\n }\n }\n\n console.error('[Transformers worker] batch failed, retrying items individually', batch_error);\n return this.retry_items_individually(batch_inputs);\n }\n\n async embed_prepared_batch(batch_inputs) {\n const prepared = await Promise.all(\n batch_inputs.map((item) => this.prepare_input(item.embed_input, item.purpose)),\n );\n const embed_inputs = prepared.map((item) => item.text);\n const response = await this.pipeline(embed_inputs, {\n pooling: this.semantic_profile.pooling,\n normalize: this.semantic_profile.normalize,\n });\n\n return prepared.map((item, i) => ({\n vec: copy_vector(response[i].data),\n tokens: item.tokens,\n }));\n }\n\n async prepare_input(embed_input, purpose = 'document') {\n if (purpose !== 'query' && purpose !== 'document') {\n throw new Error(`Unsupported embedding purpose: ${purpose}`);\n }\n const prefix = purpose === 'query'\n ? this.semantic_profile.query_prefix\n : this.semantic_profile.document_prefix\n ;\n const semantic_input = `${prefix}${embed_input}`;\n let { tokens } = await this.count_tokens(semantic_input);\n if (tokens <= this.max_tokens) {\n return { text: semantic_input, tokens };\n }\n\n let truncated = semantic_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) truncated = truncated.slice(0, last_space);\n tokens = (await this.count_tokens(truncated)).tokens;\n }\n return { text: truncated, tokens };\n }\n\n async retry_items_individually(batch_inputs) {\n const results = [];\n\n for (const item of batch_inputs) {\n try {\n const prepared = await this.prepare_input(item.embed_input, item.purpose);\n const response = await this.pipeline(prepared.text, {\n pooling: this.semantic_profile.pooling,\n normalize: this.semantic_profile.normalize,\n });\n results.push({\n vec: copy_vector(response[0].data),\n tokens: prepared.tokens,\n });\n } catch (error) {\n console.error('[Transformers worker] single item failed', error);\n results.push({\n vec: [],\n tokens: 0,\n error: get_error_message(error),\n });\n }\n }\n\n return results;\n }\n}\n\nlet model = null;\nlet request_queue = Promise.resolve();\n\nfunction get_transfer_list(result) {\n if (!Array.isArray(result)) return [];\n return result\n .map((item) => item?.vec)\n .filter((vec) => vec instanceof Float32Array)\n .map((vec) => vec.buffer)\n ;\n}\n\nasync function process_message(data = {}) {\n const { method, params = {}, id } = data;\n\n try {\n let result;\n switch (method) {\n case 'load':\n if (model) await model.dispose_pipeline();\n model = new TransformersWorkerModel(params);\n await model.load();\n result = {\n model_loaded: true,\n model_config_key: model.active_config_key,\n };\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\n self.postMessage({\n id,\n result,\n model_config_key: model?.active_config_key || null,\n }, get_transfer_list(result));\n } catch (error) {\n console.error('[Transformers worker] request failed', error);\n self.postMessage({\n id,\n error: get_error_message(error),\n model_config_key: model?.active_config_key || null,\n });\n }\n}\n\nself.addEventListener('message', (event) => {\n const data = event.data;\n request_queue = request_queue\n .then(() => process_message(data))\n .catch((error) => {\n console.error('[Transformers worker] request queue failed', error);\n })\n ;\n});\n"; // node_modules/obsidian-smart-env/src/adapters/embedding-model/transformers_v4_worker.js var transformers_default_batch_sizes = Object.freeze({ webgpu: 16, cpu: 8 }); var transformers_models = { "TaylorAI/bge-micro-v2": { id: "TaylorAI/bge-micro-v2", batch_size: 1, dims: 384, max_tokens: 512, dtype: "auto", semantic_profile: { // Matches the legacy worker behavior, so no new embedding space is needed. pooling: "mean", normalize: true, query_prefix: "", document_prefix: "" }, 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, dtype: "auto", semantic_profile: { embedding_space_id: "Snowflake/snowflake-arctic-embed-xs/retrieval-v1", pooling: "cls", normalize: true, query_prefix: "Represent this sentence for searching relevant passages: ", document_prefix: "" }, name: "Snowflake Arctic Embed XS (fast)", description: "Local, 512 tokens, 384 dim", adapter: "transformers" }, "Snowflake/snowflake-arctic-embed-s": { id: "Snowflake/snowflake-arctic-embed-s", revision: "e596f507467533e48a2e17c007f0e1dacc837b33", batch_size: 2, dims: 384, max_tokens: 512, dtype: "auto", semantic_profile: { embedding_space_id: "Snowflake/snowflake-arctic-embed-s/e596f507467533e48a2e17c007f0e1dacc837b33/cls-normalized-query-instruction-v1", pooling: "cls", normalize: true, query_prefix: "Represent this sentence for searching relevant passages: ", document_prefix: "" }, name: "Snowflake Arctic Embed S", 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, dtype: "auto", semantic_profile: { embedding_space_id: "Xenova/multilingual-e5-small/retrieval-v1", pooling: "mean", normalize: true, query_prefix: "query: ", document_prefix: "passage: " }, name: "Multilingual E5 Small", description: "Local, 512 tokens, 384 dim", adapter: "transformers" }, "onnx-community/granite-embedding-97m-multilingual-r2-ONNX": { id: "onnx-community/granite-embedding-97m-multilingual-r2-ONNX", revision: "536a9f241cb3f02a9c5995a1e708c784bd274859", batch_size: 2, dims: 384, max_tokens: 512, dtype: "auto", semantic_profile: { embedding_space_id: "onnx-community/granite-embedding-97m-multilingual-r2-ONNX/536a9f241cb3f02a9c5995a1e708c784bd274859/cls-normalized-v1", pooling: "cls", normalize: true, query_prefix: "", document_prefix: "" }, name: "Granite Multilingual R2 (experimental)", description: "Local, 512 tokens, 384 dim, multilingual/code", adapter: "transformers" }, "onnx-community/DenseOn-ONNX": { id: "onnx-community/DenseOn-ONNX", revision: "8606070c911ab26c582ce23db387abd3885541f2", batch_size: 2, dims: 768, max_tokens: 512, dtype: "auto", semantic_profile: { embedding_space_id: "onnx-community/DenseOn-ONNX/8606070c911ab26c582ce23db387abd3885541f2/cls-normalized-query-document-v1", pooling: "cls", normalize: true, query_prefix: "query: ", document_prefix: "document: " }, name: "DenseOn (experimental)", description: "Local, 512 tokens, 768 dim, English", adapter: "transformers" } }; var settings_config3 = {}; var TransformersWorkerEmbeddingModelAdapter = class extends SmartEmbedMessageAdapter { static defaults = { adapter: "transformers", description: "Transformers (Local, built-in)", default_model: "TaylorAI/bge-micro-v2", models: transformers_models }; constructor(model) { super(model); const model_profile = transformers_models[model.model_key]; if (model_profile) { delete model.data.dimensions; model.data.dims = model_profile.dims; model.data.max_tokens = model_profile.max_tokens; model.data.batch_size = model_profile.batch_size; } this.worker = null; this.worker_url = null; this.active_config_key = null; this._load_promise = null; this._load_key = null; } get use_gpu() { if (typeof this.model?.data?.use_gpu === "boolean") { return this.model.data.use_gpu; } return void 0; } get semantic_profile() { return this.models[this.model.model_key]?.semantic_profile; } get revision() { return this.models[this.model.model_key]?.revision; } get dtype() { return this.models[this.model.model_key]?.dtype || "auto"; } get configured_batch_size() { const configured_batch_size = Number(this.model?.data?.batch_size); if (!Number.isFinite(configured_batch_size) || configured_batch_size <= 1) { return null; } return Math.min(16, Math.floor(configured_batch_size)); } /** * Resolve the queue batch size from the configured or active worker backend. * @returns {number} */ get batch_size() { if (this.configured_batch_size) return this.configured_batch_size; if (this.use_gpu === false) return transformers_default_batch_sizes.cpu; if (this.active_config_key && !this.active_config_key.includes("webgpu")) { return transformers_default_batch_sizes.cpu; } return transformers_default_batch_sizes.webgpu; } /** @returns {number} */ get batch_window_size() { return 64; } /** @returns {boolean} */ get batch_sort_by_input_length() { return true; } get models() { return transformers_models; } get_models() { return Promise.resolve(this.models); } load_background() { return Promise.resolve(this.load()).catch((error) => { if (error?.message === "Message adapter unloaded") return; console.error(`[${this.constructor.name}] load failed`, error); }); } get load_key() { return JSON.stringify({ model_key: this.model.model_key, revision: this.revision, max_tokens: Number(this.model?.data?.max_tokens) || 512, batch_size: this.configured_batch_size, use_gpu: this.use_gpu !== false, dtype: this.dtype, semantic_profile: this.semantic_profile }); } /** * Coalesce identical loads and retain an already loaded worker. * @returns {Promise} */ load() { const load_key = this.load_key; if (this.worker && this.state === "loaded" && this._load_key === load_key) { return Promise.resolve(); } if (this._load_promise && this._load_key === load_key) { return this._load_promise; } const load_promise = this._load_worker(); this._load_key = load_key; const tracked_promise = load_promise.finally(() => { if (this._load_promise !== tracked_promise) return; this._load_promise = null; if (this.state !== "loaded") this._load_key = null; }); this._load_promise = tracked_promise; return tracked_promise; } /** * Initialize one worker and load the model within it. * @private * @returns {Promise} */ async _load_worker() { this.unload(); this.state = "loading"; let worker = null; try { const worker_blob = new Blob([transformers_v4_worker_default], { type: "text/javascript" }); this.worker_url = URL.createObjectURL(worker_blob); worker = new Worker(this.worker_url, { type: "module" }); this.worker = worker; worker.onmessage = this._handle_message.bind(this); worker.onerror = this._handle_worker_error.bind(this); worker.onmessageerror = this._handle_worker_error.bind(this); await this._send_message("load", { model_key: this.model.model_key, revision: this.revision, max_tokens: this.model?.data?.max_tokens, batch_size: this.configured_batch_size, use_gpu: this.use_gpu, dtype: this.dtype, semantic_profile: this.semantic_profile }); } catch (error) { if (!worker || this.worker === worker) { this.destroy_worker(); this.state = "unloaded"; this.reset_model_state(); } throw error; } } reset_model_state() { this.active_config_key = null; this._load_key = null; if (!this.model) return; this.model.model_loaded = false; this.model.load_result = null; } destroy_worker() { if (this.worker) { this.worker.terminate(); this.worker = null; } if (this.worker_url) { URL.revokeObjectURL(this.worker_url); this.worker_url = null; } } unload() { this.destroy_worker(); this.reset_model_state(); super.unload(); } /** * Post a request to the worker. * @protected * @param {Object} message_data */ _post_message(message_data) { if (!this.worker) { throw new Error("Transformers worker not loaded"); } this.worker.postMessage(message_data); } /** * Handle a worker response. * @private * @param {MessageEvent} event */ _handle_message(event) { if (event.currentTarget !== this.worker) return; const { id, result, error, model_config_key } = event.data || {}; if (!id) return; this.active_config_key = model_config_key; this._handle_message_result(id, result, error); } /** * Reject pending work after an unrecoverable worker failure. * @private * @param {ErrorEvent|MessageEvent} event */ _handle_worker_error(event) { if (event.currentTarget !== this.worker) return; const error_message = event.error?.message || event.message || "Transformers worker failed"; const pending_ids = Object.keys(this.message_queue); this.destroy_worker(); this.state = "unloaded"; this.reset_model_state(); pending_ids.forEach((id) => { if (!this.message_queue[id]) return; this._handle_message_result(id, null, error_message); }); } }; var transformers_v4_worker_default2 = { class: TransformersWorkerEmbeddingModelAdapter, settings_config: settings_config3 }; // node_modules/obsidian-smart-env/node_modules/smart-blocks/actions/get_embed_input/markdown.js async function block_get_embed_input_markdown(params = {}) { const has_content = typeof params.content === "string" && params.content.length > 0; if (has_content) { this.clear_staged_embed_content?.(); this._embed_input = ""; } if (typeof this._embed_input !== "string" || !this._embed_input.length) { let content = has_content ? params.content : this.consume_staged_embed_content?.(); if (!content) content = await this.read(); this._embed_input = this.breadcrumbs + "\n" + content; } return this._embed_input; } // 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(); } /** * Resolves and runs the block adapter's canonical embed-input action. * @async * @param {string|null} [content=null] * @returns {Promise} The embed input string. */ async get_embed_input(content = null) { const block_adapter = this.block_adapter; const action_key = block_adapter?.embed_input_action_key; if (!action_key) { throw new Error(`SmartBlock.get_embed_input: missing embed_input_action_key for ${this.key}`); } const action = this.actions[action_key]; if (typeof action !== "function") { throw new Error(`SmartBlock.get_embed_input: missing action "${action_key}" for ${this.key}`); } return await action({ content }); } // CRUD /** * @method read * @description Reads the block content by delegating to the block adapter. * @async * @param {Object} [params={}] * @returns {Promise} The block content. */ async read(params = {}) { try { return await this.block_adapter.read(params); } 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.has_block(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 outlinks through the active block adapter. * @readonly * @returns {Array} An array of outlink objects. */ get outlinks() { return this.block_adapter?.get_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]; } /** * Stage raw block content until the embedding input is prepared. * * @param {string} content * @param {string} [hash=this.read_hash] * @returns {void} */ stage_embed_content(content, hash = this.read_hash) { this._embed_input = ""; this._staged_embed_content = content; this._staged_embed_content_hash = hash; } /** * Consume staged content when it still matches the block hash. * * @returns {string|null} */ consume_staged_embed_content() { if (typeof this._staged_embed_content !== "string") return null; if (this._staged_embed_content_hash !== this.read_hash) { this.clear_staged_embed_content(); return null; } const content = this._staged_embed_content; this.clear_staged_embed_content(); return content; } /** * Clear staged block content. * * @returns {void} */ clear_staged_embed_content() { this._staged_embed_content = null; this._staged_embed_content_hash = null; } }; var smart_block_default = { class: SmartBlock, actions: { block_get_embed_input_markdown, find_connections } }; // 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(); const block_content_by_key = /* @__PURE__ */ new Map(); 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]); block_content_by_key.set(block_key, block_content); 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 && !has_deprecated_block_outlinks(existing_block)) { continue; } const block_data = { key: block_key, lines: line_range, size: block_content.length, 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 }; delete existing_block.data.outlinks; delete existing_block.data.outlinks_version; existing_block.queue_save(); } } clean_and_update_source_blocks(source, blocks_obj, task_lines, tasks, codeblock_ranges); source.ensure_block_embedding_selection?.({ force: true }); for (const block of source.blocks) { if (!block.vec || block.embed_hash !== block.read_hash) { block.queue_embed(); if (block._queue_embed) { const block_content = block_content_by_key.get(block.key); if (typeof block_content === "string") { block.stage_embed_content?.(block_content, block.read_hash); } else { block.clear_staged_embed_content?.(); } } else { block.clear_staged_embed_content?.(); } } else { block.clear_staged_embed_content?.(); } } } 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; } return has_deprecated_block_outlinks(existing_block); } function has_deprecated_block_outlinks(block) { const data = block?.data || {}; return Object.prototype.hasOwnProperty.call(data, "outlinks") || Object.prototype.hasOwnProperty.call(data, "outlinks_version"); } 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.replace_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-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 version4 = 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, "_")}#${[version4, 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 version4 = 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: version4, 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 * @param {object} [data={}] * @returns {boolean} */ static detect(key, data = {}) { return data.kind === "block"; } /** * @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 /** * @param {string} key * @param {object} [data={}] * @returns {boolean} */ static detect(key, data = {}) { return data.kind === "source"; } /** * @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 * @param {object} [data={}] * @returns {boolean|string} */ static detect(key, data = {}) { if (data.kind !== "source") return false; if (image_extension_regex.test(data.source_path || key)) return "image"; return false; } /** * @this {ImageContextItemAdapterThis} * @returns {boolean} */ get exists() { return this.item.env.smart_sources.fs.exists_sync(this.item.data?.source_path || 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 source_path = this.item.data?.source_path || this.item.key; const ext = source_path.split(".").pop().toLowerCase(); try { const base64_data = await this.item.env.fs.read(source_path, "base64"); const base64_url = `data:image/${ext};base64,${base64_data}`; return { type: "image_url", key: this.item.key, name: source_path.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 * @param {object} [data={}] * @returns {boolean|string} */ static detect(key, data = {}) { if (data.kind !== "source") return false; if (String(data.source_path || 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() { const source_path = this.item.data?.source_path || this.item.key; try { const base64_data = await this.item.env.fs.read(source_path, "base64"); const base64_url = `data:application/pdf;base64,${base64_data}`; return { type: "pdf_url", key: this.item.key, name: source_path.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.data?.source_path || this.item.key); } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/context_items.js function normalize_context_item_data(key, item_data = {}) { const data = item_data && typeof item_data === "object" ? { ...item_data } : {}; const item_key = String(data.key || data.path || key || ""); if (!item_key) return data; data.key = item_key; if (!data.kind) { if (data.named_context) { data.kind = "named_context"; } else if (data.folder === true) { data.kind = "folder"; } else if (typeof data.subpath === "string" || item_key.includes("#")) { data.kind = "block"; } else { data.kind = "source"; } } if (data.kind === "named_context") { if (!data.named_context) data.named_context = true; return data; } if (data.kind === "text") return data; if (data.kind === "folder" && data.folder !== true) data.folder = true; const is_external = data.is_external === true || item_key.startsWith("external:"); const identity_key = is_external && item_key.startsWith("external:") ? item_key.slice("external:".length) : item_key; const block_idx = identity_key.indexOf("#"); if (typeof data.source_path !== "string" || !data.source_path) { data.source_path = data.kind !== "block" || block_idx === -1 ? identity_key : identity_key.slice(0, block_idx); } else if (data.source_path.startsWith("external:")) { data.source_path = data.source_path.slice("external:".length); } if (data.kind === "block" && typeof data.subpath !== "string" && block_idx !== -1) { data.subpath = identity_key.slice(block_idx + 1); } if (is_external) data.is_external = true; return data; } 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 normalized_data = normalize_context_item_data(data?.key || data?.path || "", data); const item = new this.item_type(this.env, normalized_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 = {}) { const normalized_data = normalize_context_item_data(key, item_data); if (normalized_data.kind === "named_context") { return this.load_named_context_items(key, normalized_data, params); } else { return this.new_item({ key, ...normalized_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_obsidian4 = 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_object5(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function get_source_event(params = {}) { if (is_plain_object5(params.source_event)) return params.source_event; if (is_plain_object5(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_object5(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: true, native_notice_warning: true, native_notice_error: true, native_notice_attention: true, 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_obsidian4.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/notifications_feed_modal.js var import_obsidian5 = require("obsidian"); var NotificationsFeedModal = class extends import_obsidian5.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_obsidian6 = require("obsidian"); var MILESTONES_HELP_URL = "https://smartconnections.app/smart-environment/milestones/?utm_source=milestones_modal_help"; var MilestonesModal = class extends import_obsidian6.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_obsidian6.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_obsidian7 = require("obsidian"); var BrowseSmartPlugins = class extends import_obsidian7.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.modalEl.style.maxWidth = "var(--setting-group-max-width)"; } 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: "", // Legacy CSV keys remain for SC App compatibility. Array writes use the // separate file_exclusions_list and folder_exclusions_list properties. file_exclusions: "Untitled", folder_exclusions: "" }, gitignore_exclusions: [], gitignore_exclusions_updated_at: 0, 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: { provider_key: "", model_key: "" } }; } constructor(env, data = null) { super(env, data); this.ProviderAdapterClass?.sync_model_data?.(this); } 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() { const secret_api_key = this.secrets?.api_key; if (typeof secret_api_key === "string") { if (secret_api_key.length && this.data.api_key) { console.warn(`[smart-models] Model ${this.key} has api_key in data, but secrets are enabled. Please migrate the api_key to secrets.`); } return secret_api_key; } return this.data.api_key || ""; } set api_key(api_key) { const value = String(api_key || ""); if (this.secrets) { this.secrets.api_key = value; delete this.data.api_key; return; } this.data.api_key = value; } /** * 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.ProviderAdapterClass?.sync_model_data?.(self); 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.ProviderAdapterClass?.sync_model_data?.(self); 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 adapter_defaults = this.ProviderAdapterClass.defaults || {}; const model_defaults = this.data.provider_models?.[this.data.model_key] || adapter_defaults.models?.[this.data.model_key] || {}; delete this.data.test_passed; this.data = { ...this.data, ...adapter_defaults, ...model_defaults }; } this.ProviderAdapterClass?.sync_model_data?.(this); 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, ...setting_key === "api_key" ? { secret: true } : {}, // TEMP: should add property direct in settings_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); } /** * Secure storage for sensitive information (like API keys) for this model. * @returns {Object} Proxied view of this.collection.secrets[this.key]. */ get secrets() { if (!this.collection.secrets) return; if (!this.collection.secrets[this.key]) { this.collection.secrets[this.key] = {}; } return this.collection.secrets[this.key]; } 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]; const api_key = data.api_key || existing_from_provider?.api_key || ""; delete data.api_key; const item = new this.item_type(this.env, { ...data }); this.set(item); if (api_key) item.api_key = api_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: { 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"; new_model(data = {}) { if (data.model_key === void 0) { const provider_adapter_class = this.env_config.providers?.[data.provider_key]?.class; data.model_key = provider_adapter_class?.defaults?.default_model || ""; } return super.new_model(data); } 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; // node_modules/obsidian-smart-env/src/collections/embedding_models.js embedding_models_default.providers = { transformers: transformers_v4_worker_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", { query: params.query }); 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 (!Number.isFinite(scored?.score)) { if (scored?.error) score_errors.push(scored.error); return acc; } results_acc(acc, scored, params.limit || 20); return acc; }, { min: Number.POSITIVE_INFINITY, minResult: null, results: /* @__PURE__ */ new Set() }); const results = Array.from(raw_results).sort(sort_by_score_descending); if (!results.length) return results; if (!results.some((r) => r.score > 0)) 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 = { get_results_action_key: { name: "Lookup retrieval strategy", type: "dropdown", description: "Choose the action used to retrieve and rank Lookup results.", options_callback: (scope) => { return Object.entries(scope.env.config.actions).filter(([action_key]) => { return action_key === "lookup_list_get_results" || action_key.startsWith("lookup_list_get_results_"); }).map(([value, action]) => ({ value, name: action.display_name || value })); } }, 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 { get_results_action_key: "lookup_list_get_results", 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/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; } /** * 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/_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; } /** * Retrieve source outlinks within this block's persisted line range. * * @returns {Array} */ get_outlinks() { const lines = this.data.lines; if (!Array.isArray(lines) || lines.length !== 2) return []; return this.item.source?.source_adapter?.get_outlinks(lines) || []; } /** * @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 { static embed_input_action_key = "block_get_embed_input_markdown"; get embed_input_action_key() { return this.constructor.embed_input_action_key; } /** * 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/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 { static version = 2; merge_defaults() { this._pending_data = create_empty_block_data(); this._embed_input = ""; } init() { if (!this.settings.embed_blocks) return; if (this.is_unembedded) this.queue_embed(); } /** * @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(); } queue_embed() { const should_queue = this.should_embed; if (Boolean(this._queue_embed) !== Boolean(should_queue)) { this.collection?.mark_embed_queue_dirty?.(); this.source_collection?.mark_embed_queue_dirty?.(); } this._queue_embed = should_queue; } queue_save() { const source = this.source; const sub_key = this.sub_key; const block_key = this._block_key || this._pending_data?.key || (source && sub_key ? `${source.key}${sub_key}` : ""); if (!source || !sub_key) { this._queue_save = false; return; } if (this.deleted) { remove_source_block_data(source, sub_key); if (block_key) delete this.collection.items[block_key]; } else if (this._pending_data) { this.data = this._pending_data; } else if (source_has_block(source, sub_key)) { } else { if (block_key) delete this.collection.items[block_key]; this._queue_save = false; return; } source.queue_save(); this._queue_save = false; } async remove() { await this.block_adapter.remove(); this.delete(); } async move_to(to_key) { await this.block_adapter.move_to(to_key); this.delete(); } get data() { const block_key = this._pending_data?.key || this._block_key || ""; if (!block_key) { if (!this._pending_data) this._pending_data = create_empty_block_data(); return this._pending_data; } set_block_refs(this, block_key); if (this.deleted) { if (!this._pending_data) { this._pending_data = { key: this._block_key || "" }; } return this._pending_data; } if (this._pending_data) return this._pending_data; const source = this.source; const sub_key = this.sub_key; if (!source || !sub_key) { if (!this._pending_data) this._pending_data = {}; return this._pending_data; } const source_data = source.data; if (this._data_ref && this._data_source === source && this._data_sub_key === sub_key && this._data_source_data === source_data && source_data.blocks_data?.[sub_key] === this._data_ref) { return this._data_ref; } const block_data = source_data.blocks_data?.[sub_key]; if (!block_data) { if (!this._pending_data) { this._pending_data = { key: `${source.key}${sub_key}` }; } return this._pending_data; } this._pending_data = null; this._data_ref = block_data; this._data_source = source; this._data_sub_key = sub_key; this._data_source_data = source_data; return this._data_ref; } set data(data) { data = data || {}; delete data.path; delete data.class_name; const block_key = data.key || this._block_key || ""; if (!block_key) { this._pending_data = data; clear_block_data_cache(this); return; } set_block_refs(this, block_key); const sub_key = this._sub_key || ""; if (!this._source_key || !sub_key) { this._pending_data = data; clear_block_data_cache(this); return; } const source = this.source; if (!source) { this._pending_data = data; clear_block_data_cache(this); return; } const source_data = source.data; if (!source_data.blocks_data) source_data.blocks_data = {}; data.key = `${source.key}${sub_key}`; source_data.blocks_data[sub_key] = data; this._pending_data = null; this._data_ref = data; this._data_source = source; this._data_sub_key = sub_key; this._data_source_data = source_data; source.data.block_embedding_selection = null; } get source_key() { set_block_refs(this, this._pending_data?.key || this._block_key || ""); return this._source_key || ""; } get sub_key() { set_block_refs(this, this._pending_data?.key || this._block_key || ""); return this._sub_key || ""; } get vec() { return this.collection.embeddings?.get_item_vector(this, DEFAULT_EMBEDDING_TYPE); } set vec(vec) { this.collection.embeddings?.set_item_vector(this, vec, DEFAULT_EMBEDDING_TYPE); } get last_embed() { return this.collection.embeddings?.get_item_embedding_ref( this, DEFAULT_EMBEDDING_TYPE ) || {}; } get embed_hash() { return this.last_embed.read_hash; } set embed_hash(hash) { if (this.data.embedding?.error) return; const embedding_ref = this.collection.embeddings?.get_item_embedding_ref( this, DEFAULT_EMBEDDING_TYPE ); if (!embedding_ref) return; embedding_ref.read_hash = hash; } get is_unembedded() { return !this.collection.embeddings?.has_current_vector_ref?.(this, DEFAULT_EMBEDDING_TYPE); } get should_embed() { return this.get_should_embed(); } get_should_embed(params = {}) { const source = params.source || this.source; const block_data = this.data; if (source?.data && (this._should_embed_cache === null || block_data.should_embed == null)) { source.data.block_embedding_selection = null; } if (this._should_embed_cache === null) this._should_embed_cache = void 0; source?.ensure_block_embedding_selection?.({ min_chars: params.min_chars }); return block_data.should_embed === true; } }; function create_empty_block_data() { return { text: null, length: 0, last_read: { hash: null, at: 0 } }; } function set_block_refs(block, block_key = "") { if (!block_key) return; if (block_key === block._block_key && block._source_key !== void 0) return; clear_block_data_cache(block); block._block_key = block_key; block._source_key = get_source_key_from_block_key2(block_key); block._sub_key = get_sub_key_from_block_key(block_key); } function clear_block_data_cache(block) { block._data_ref = null; block._data_source = null; block._data_sub_key = ""; block._data_source_data = null; } function get_source_key_from_block_key2(block_key = "") { const value = String(block_key || ""); const hash_i = value.indexOf("#"); return hash_i === -1 ? value : value.slice(0, hash_i); } function get_sub_key_from_block_key(block_key = "") { const value = String(block_key || ""); const hash_i = value.indexOf("#"); return hash_i === -1 ? "" : value.slice(hash_i); } function source_has_block(source, sub_key) { if (!source || !sub_key) return false; return source.has_block(sub_key); } function remove_source_block_data(source, sub_key) { if (!source?.data || !sub_key) return; if (source.data.blocks_data) delete source.data.blocks_data[sub_key]; source.data.block_embedding_selection = null; } // node_modules/obsidian-smart-env/src/collections/smart_blocks.js var SmartBlocks2 = class extends SmartBlocks { static version = 2; constructor(env, opts = {}) { super(env, opts); this.embeddings = env.embeddings?.for_collection?.(this) || new Embeddings(this); this.entities_vector_adapter = this.embeddings.entities_vector_adapter; } get data_dir() { return "smart_blocks"; } get embed_queue() { if (this._embed_queue_ready) return this._embed_queue || []; const embed_queue = []; const file_info = this.embeddings?.get_active_file_info?.(); for (const block of Object.values(this.items || {})) { if (!block.should_embed) { block._queue_embed = false; continue; } const has_vector = this.embeddings?.has_current_vector_ref?.(block, void 0, file_info) === true; if (block._queue_embed || !has_vector) { embed_queue.push(block); } } this._embed_queue = embed_queue; this._embed_queue_ready = true; return this._embed_queue; } mark_embed_queue_dirty() { this._embed_queue_ready = false; this._embed_queue = []; this.env.smart_sources?.mark_embed_queue_dirty?.(); } async process_load_queue() { this.emit_event("collection:load_started"); this.loaded = Object.keys(this.items || {}).length; this.emit_event("collection:load_completed", { loaded: this.loaded }); } async process_save_queue() { if (this._defer_embed_saves) return; this.emit_event("collection:save_started"); const save_queue = Object.values(this.items || {}).filter((block) => block._queue_save); save_queue.forEach((block) => block.queue_save()); await this.env.smart_sources?.process_save_queue?.(); this.emit_event("collection:save_completed", { saved: save_queue.length }); } async process_embed_queue() { return await this.entities_vector_adapter.process_embed_queue(); } }; var smart_blocks_default = { class: SmartBlocks2, version: SmartBlocks2.version, item_type: SmartBlock2, collection_key: "smart_blocks", load_order: 110, block_adapters: { md: MarkdownBlockContentAdapter, txt: MarkdownBlockContentAdapter, "excalidraw.md": MarkdownBlockContentAdapter } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/smart_context.js function migrate_legacy_context_item_exclusions(data = {}) { const context_items = data?.context_items; if (!context_items || typeof context_items !== "object") return []; const migrated_keys = []; Object.entries(context_items).forEach(([storage_key, item_data]) => { if (!item_data || typeof item_data !== "object" || !Object.prototype.hasOwnProperty.call(item_data, "exclude")) { return; } if (!item_data.exclude) { delete item_data.exclude; migrated_keys.push(storage_key); return; } delete context_items[storage_key]; migrated_keys.push(storage_key); const item_key = String(item_data.key || "").trim(); const exclusion_key = item_key || String(storage_key || "").trim(); const normalized_data = normalize_context_item_data( exclusion_key || storage_key, item_data ); if (normalized_data.kind === "named_context" || !exclusion_key) return; if (!data.exclusions || typeof data.exclusions !== "object" || Array.isArray(data.exclusions)) { data.exclusions = {}; } const existing_exclusion = data.exclusions[exclusion_key]; if (existing_exclusion && typeof existing_exclusion === "object") return; const has_explicit_identity = exclusion_key.startsWith("external:") || typeof item_data.kind === "string" || typeof item_data.source_path === "string" || Object.prototype.hasOwnProperty.call(item_data, "is_external"); const is_glob = item_data.glob === true || !has_explicit_identity && exclusion_key.includes("*"); const exclusion_data = is_glob ? { ...item_data, key: exclusion_key, glob: true } : normalized_data; if (is_glob) { delete exclusion_data.kind; delete exclusion_data.source_path; delete exclusion_data.subpath; delete exclusion_data.is_external; } data.exclusions[exclusion_key] = { ...exclusion_data, key: exclusion_key, exclude: true }; }); return migrated_keys; } var remove_context_item_data = (context_items, key) => { if (!key || !context_items?.[key]) return false; delete context_items[key]; return true; }; var SmartContext = class extends CollectionItem { static version = "2.0.3"; constructor(env, data = null) { super(env, data); if (migrate_legacy_context_item_exclusions(this.data).length) { this.queue_save(); } } init() { if (migrate_legacy_context_item_exclusions(this.data).length) { this.queue_save(); } } /** * @returns {{data: SmartContextData}} */ static get defaults() { return { data: { key: "", context_items: {}, exclusions: {}, 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 = normalize_context_item_data(key, { d: 0, at: Date.now(), ...existing || {}, ...typeof item === "object" ? item : {} }); if (!key) return console.error("SmartContext: add_item called with invalid item", item); delete context_item.exclude; 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.data.exclusions = {}; this.queue_save(); this.emit_event("context:updated", { cleared: true }); } /** * @this {SmartContextThis} * @returns {string[]} */ get context_item_keys() { return Object.keys(this.data?.context_items || {}); } /** * @this {SmartContextThis} * @returns {string[]} */ get excluded_context_item_keys() { return Object.keys(this.data?.exclusions || {}); } /** * @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.keys(this.data?.context_items || {}).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. * * 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.3"; /** * 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]; } async process_load_queue() { await super.process_load_queue(); Object.values(this.items || {}).forEach((item) => { if (!migrate_legacy_context_item_exclusions(item?.data).length) return; item.queue_save(); }); } /** * 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.1"; 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.entries(context_items).forEach(([key, item_data]) => { const normalized_data = normalize_context_item_data(key, item_data); if (normalized_data.kind === "named_context") return; 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 = normalize_context_item_data(key, { d: 0, at: Date.now(), ...existing || {}, ...typeof item === "object" ? item : {} }); if (!key) return console.error("SmartContext: add_item called with invalid item", item); delete context_item.exclude; const emit_payload = { add_item: key }; const remove_sub_keys = context_item.kind === "named_context" ? [] : Object.entries(this.data.context_items).filter(([existing_key, existing_data]) => { if (existing_key === key) return false; const normalized_data = normalize_context_item_data(existing_key, existing_data); return normalized_data.kind !== "named_context" && 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_obsidian8 = 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_obsidian8.Keymap.isModEvent(ev)) { const block = env.smart_blocks.get(block_key); const markdown = await block?.read(); if (markdown) { const popover = new import_obsidian8.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_obsidian8.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_obsidian9 = 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_obsidian9.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_obsidian10 = 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_obsidian10.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 app2 = context_item?.env?.plugin?.app || context_item?.env?.obsidian_app || globalThis.app || null; container.addEventListener("contextmenu", (event) => { event.preventDefault(); event.stopPropagation(); if (!app2) return; const menu = new import_obsidian10.Menu(app2); const menu_params = { ...params, context_item, event }; const item_ref2 = context_item?.item_ref || null; if (item_ref2) { context_item?.env?.build_menu?.("source:menu", menu, item_ref2, menu_params); if (menu.items?.length > 0) menu.addSeparator(); } context_item?.env?.build_menu?.("context_item:action_menu", menu, context_item, menu_params); if (!(menu.items?.length > 0)) return; menu.showAtMouseEvent(event); }); 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_obsidian10.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_obsidian11 = 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_obsidian11.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, hide_mute_button = false } = 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); const should_render_mute_button = Boolean(event_key) && hide_mute_button !== true && event?.hide_mute_button !== true; 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) || can_view_more || should_render_mute_button; 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 (should_render_mute_button) { 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_obsidian11.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/components/env_stats.css var env_stats_default = ".smart-env-stats-modal {\n width: min(980px, 94vw);\n max-height: min(880px, 92vh);\n}\n\n.smart-env-stats-modal .modal-content {\n padding: var(--size-4-3) var(--size-4-4) var(--size-4-4);\n}\n\n.smart-env-stats {\n --smart-env-stats-card-radius: calc(var(--radius-m) + var(--size-2-2));\n --smart-env-stats-surface: color-mix(\n in srgb,\n var(--background-primary) 92%,\n var(--background-secondary)\n );\n\n display: flex;\n flex-direction: column;\n gap: var(--size-4-4);\n min-width: 0;\n color: var(--text-normal);\n font-family: var(--font-interface);\n user-select: text;\n}\n\n.smart-env-stats button,\n.smart-env-stats input,\n.smart-env-stats select {\n font: inherit;\n}\n\n.smart-env-stats__header,\n.smart-env-stats__section-heading,\n.smart-env-stats__collection-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--size-4-3);\n}\n\n.smart-env-stats__heading {\n min-width: 0;\n}\n\n.smart-env-stats__eyebrow {\n margin-bottom: var(--size-2-1);\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.smart-env-stats__title {\n margin: 0;\n font-size: var(--h2-size);\n line-height: 1.15;\n}\n\n.smart-env-stats__status,\n.smart-env-stats__optimization-status,\n.smart-env-stats__section-heading p,\n.smart-env-stats__collection-state,\n.smart-env-stats__collection-footer,\n.smart-env-stats__metric-detail,\n.smart-env-stats__footer {\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n line-height: 1.35;\n}\n\n.smart-env-stats__status {\n min-height: 1.35em;\n margin: var(--size-2-2) 0 0;\n font-variant-numeric: tabular-nums;\n}\n\n.smart-env-stats__refresh,\n.smart-env-stats__optimize {\n flex: 0 0 auto;\n min-width: 96px;\n}\n\n.smart-env-stats__refresh:focus-visible {\n outline: 2px solid var(--color-accent);\n outline-offset: 2px;\n}\n\n.smart-env-stats__summary,\n.smart-env-stats__memory-grid {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n gap: var(--size-4-2);\n}\n\n.smart-env-stats__memory-grid {\n grid-template-columns: repeat(3, minmax(0, 1fr));\n}\n\n.smart-env-stats__metric,\n.smart-env-stats__collection {\n border: 1px solid var(--background-modifier-border);\n background: var(--smart-env-stats-surface);\n box-shadow: var(--shadow-xs);\n}\n\n.smart-env-stats__metric {\n min-width: 0;\n padding: var(--size-4-3);\n border-radius: var(--smart-env-stats-card-radius);\n}\n\n.smart-env-stats__metric[data-attention] {\n border-color: color-mix(in srgb, var(--color-orange) 45%, var(--background-modifier-border));\n background: color-mix(in srgb, var(--color-orange) 7%, var(--smart-env-stats-surface));\n}\n\n.smart-env-stats__metric-label {\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n font-weight: var(--font-semibold);\n letter-spacing: 0.02em;\n}\n\n.smart-env-stats__metric-value {\n margin-top: var(--size-2-2);\n overflow: hidden;\n color: var(--text-normal);\n font-size: clamp(1.25rem, 3vw, 1.65rem);\n font-variant-numeric: tabular-nums;\n font-weight: var(--font-semibold);\n letter-spacing: -0.025em;\n line-height: 1.1;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.smart-env-stats__metric-detail {\n min-height: 1.35em;\n margin-top: var(--size-2-2);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.smart-env-stats__section {\n display: flex;\n flex-direction: column;\n gap: var(--size-4-3);\n}\n\n.smart-env-stats__section-heading h3,\n.smart-env-stats__collection-header h4 {\n margin: 0;\n line-height: 1.2;\n}\n\n.smart-env-stats__section-heading p {\n margin: var(--size-2-2) 0 0;\n}\n\n.smart-env-stats__memory-section,\n.smart-env-stats__optimization {\n padding: var(--size-4-3);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--smart-env-stats-card-radius);\n background: color-mix(in srgb, var(--background-secondary) 58%, transparent);\n}\n\n.smart-env-stats__optimization-status {\n min-height: 1.35em;\n margin: 0;\n font-variant-numeric: tabular-nums;\n}\n\n.smart-env-stats__optimization-confirm[hidden] {\n display: none;\n}\n\n.smart-env-stats__optimization-confirm {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--size-4-3);\n padding: var(--size-4-2) var(--size-4-3);\n border: 1px solid color-mix(in srgb, var(--color-orange) 55%, var(--background-modifier-border));\n border-radius: var(--radius-m);\n background: color-mix(in srgb, var(--color-orange) 8%, var(--background-primary));\n font-size: var(--font-ui-small);\n line-height: 1.4;\n}\n\n.smart-env-stats__optimization-confirm p {\n margin: 0;\n}\n\n.smart-env-stats__optimization-confirm-actions {\n display: flex;\n flex: 0 0 auto;\n gap: var(--size-4-2);\n}\n\n.smart-env-stats__memory-utilization {\n flex: 0 0 auto;\n padding: var(--size-2-2) var(--size-4-2);\n border: 1px solid var(--background-modifier-border);\n border-radius: 999px;\n background: var(--background-primary);\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n font-variant-numeric: tabular-nums;\n white-space: nowrap;\n}\n\n.smart-env-stats__collections {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--size-4-3);\n}\n\n.smart-env-stats__collection {\n position: relative;\n min-width: 0;\n padding: var(--size-4-3);\n overflow: hidden;\n border-radius: var(--smart-env-stats-card-radius);\n}\n\n.smart-env-stats__collection::before {\n position: absolute;\n inset: 0 auto 0 0;\n width: 3px;\n background: var(--smart-env-stats-tone, var(--text-faint));\n content: '';\n}\n\n.smart-env-stats__collection[data-tone='loading'] {\n --smart-env-stats-tone: var(--color-accent);\n}\n\n.smart-env-stats__collection[data-tone='success'] {\n --smart-env-stats-tone: var(--color-green);\n}\n\n.smart-env-stats__collection[data-tone='attention'] {\n --smart-env-stats-tone: var(--color-orange);\n}\n\n.smart-env-stats__collection[data-tone='warning'] {\n --smart-env-stats-tone: var(--color-yellow);\n}\n\n.smart-env-stats__collection[data-tone='muted'] {\n --smart-env-stats-tone: var(--text-faint);\n opacity: 0.78;\n}\n\n.smart-env-stats__collection-header h4 {\n font-size: var(--font-ui-medium);\n}\n\n.smart-env-stats__collection-state {\n margin-top: var(--size-2-2);\n font-variant-numeric: tabular-nums;\n}\n\n.smart-env-stats__coverage-value {\n flex: 0 0 auto;\n color: var(--text-normal);\n font-size: var(--font-ui-small);\n font-variant-numeric: tabular-nums;\n font-weight: var(--font-semibold);\n text-align: right;\n}\n\n.smart-env-stats__progress {\n height: 7px;\n margin: var(--size-4-3) 0;\n overflow: hidden;\n border: 1px solid var(--background-modifier-border);\n border-radius: 999px;\n background: var(--background-secondary);\n}\n\n.smart-env-stats__progress-fill {\n width: 0;\n height: 100%;\n border-radius: inherit;\n background: var(--smart-env-stats-tone, var(--color-accent));\n transition: width 140ms ease;\n}\n\n.smart-env-stats__collection-grid {\n display: grid;\n grid-template-columns: repeat(3, minmax(0, 1fr));\n gap: var(--size-4-2);\n}\n\n.smart-env-stats__collection-value {\n min-width: 0;\n padding: var(--size-4-2);\n border: 0;\n border-radius: var(--radius-m);\n background: color-mix(in srgb, var(--background-secondary) 65%, transparent);\n color: inherit;\n font: inherit;\n text-align: left;\n}\n\n.smart-env-stats button.smart-env-stats__collection-value {\n -webkit-appearance: none;\n appearance: none;\n display: block;\n align-self: stretch;\n width: 100%;\n height: auto;\n min-height: 0;\n margin: 0;\n padding: var(--size-4-2);\n border: 0;\n border-radius: var(--radius-m);\n background: color-mix(in srgb, var(--background-secondary) 65%, transparent);\n box-shadow: none;\n color: inherit;\n cursor: pointer;\n line-height: normal;\n text-align: left;\n white-space: normal;\n transition: background-color 120ms ease, box-shadow 120ms ease;\n}\n\n.smart-env-stats button.smart-env-stats__collection-value:not(:disabled):hover {\n background: color-mix(in srgb, var(--background-modifier-hover) 75%, transparent);\n box-shadow: inset 0 0 0 1px var(--interactive-accent);\n}\n\n.smart-env-stats button.smart-env-stats__collection-value[data-selected] {\n background: color-mix(in srgb, var(--interactive-accent) 12%, var(--background-secondary));\n box-shadow: inset 0 0 0 1px var(--interactive-accent);\n}\n\n.smart-env-stats button.smart-env-stats__collection-value[data-inspect-status='unexpected'][data-selected] {\n background: color-mix(in srgb, var(--color-yellow) 10%, var(--background-secondary));\n box-shadow: inset 0 0 0 1px var(--color-yellow);\n}\n\n.smart-env-stats button.smart-env-stats__collection-value:focus-visible {\n outline: 2px solid var(--interactive-accent);\n outline-offset: 2px;\n}\n\n.smart-env-stats button.smart-env-stats__collection-value:disabled {\n cursor: default;\n opacity: 1;\n}\n\n.smart-env-stats__collection-value-heading {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--size-2-2);\n min-width: 0;\n}\n\n.smart-env-stats__collection-value-label,\n.smart-env-stats__collection-value strong {\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.smart-env-stats__collection-value-label {\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n}\n\n.smart-env-stats__inspect-label {\n display: none;\n flex: 0 0 auto;\n color: var(--text-accent);\n font-size: var(--font-ui-smaller);\n font-weight: var(--font-semibold);\n}\n\n.smart-env-stats button.smart-env-stats__collection-value[data-has-items] .smart-env-stats__inspect-label {\n display: inline;\n}\n\n.smart-env-stats button.smart-env-stats__collection-value[data-inspect-status='unexpected'] .smart-env-stats__inspect-label {\n color: var(--color-yellow);\n}\n\n.smart-env-stats__collection-value strong {\n display: block;\n margin-top: var(--size-2-1);\n font-variant-numeric: tabular-nums;\n font-weight: var(--font-semibold);\n}\n\n.smart-env-stats__collection-footer {\n min-height: 1.35em;\n margin-top: var(--size-4-2);\n font-variant-numeric: tabular-nums;\n}\n\n.smart-env-stats__inspector[hidden] {\n display: none;\n}\n\n.smart-env-stats__inspector {\n --smart-env-stats-inspector-tone: var(--text-accent);\n\n position: relative;\n min-width: 0;\n padding: var(--size-4-3);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--smart-env-stats-card-radius);\n background: color-mix(in srgb, var(--background-secondary) 55%, transparent);\n}\n\n.smart-env-stats__inspector::before {\n position: absolute;\n inset: 0 auto 0 0;\n width: 3px;\n border-radius: var(--smart-env-stats-card-radius) 0 0 var(--smart-env-stats-card-radius);\n background: var(--smart-env-stats-inspector-tone);\n content: '';\n}\n\n.smart-env-stats__inspector[data-status='unexpected'] {\n --smart-env-stats-inspector-tone: var(--color-yellow);\n}\n\n.smart-env-stats__inspector[data-error] {\n --smart-env-stats-inspector-tone: var(--text-error);\n}\n\n.smart-env-stats__inspector-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--size-4-3);\n}\n\n.smart-env-stats__inspector-heading {\n min-width: 0;\n}\n\n.smart-env-stats__inspector-eyebrow {\n margin-bottom: var(--size-2-1);\n color: var(--smart-env-stats-inspector-tone);\n font-size: var(--font-ui-smaller);\n font-weight: var(--font-semibold);\n letter-spacing: 0.06em;\n text-transform: uppercase;\n}\n\n.smart-env-stats__inspector-heading h4 {\n margin: 0;\n font-size: var(--font-ui-medium);\n}\n\n.smart-env-stats__inspector-heading p {\n max-width: 72ch;\n margin: var(--size-2-2) 0 0;\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n line-height: 1.4;\n}\n\n.smart-env-stats__inspector-toolbar {\n position: sticky;\n top: var(--size-4-2);\n z-index: 2;\n display: grid;\n grid-template-columns: minmax(0, 1fr) minmax(180px, 240px);\n gap: var(--size-4-2);\n margin: var(--size-4-3) 0;\n padding: var(--size-4-2);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-m);\n background: var(--background-primary);\n box-shadow: var(--shadow-xs);\n}\n\n.smart-env-stats__inspector-field {\n display: grid;\n grid-template-columns: auto minmax(0, 1fr);\n align-items: center;\n gap: var(--size-4-2);\n min-width: 0;\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n font-weight: var(--font-semibold);\n}\n\n.smart-env-stats__inspector-field input,\n.smart-env-stats__inspector-field select {\n width: 100%;\n min-width: 0;\n}\n\n.smart-env-stats__inspector-status {\n min-height: 1.4em;\n margin-bottom: var(--size-4-2);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n font-variant-numeric: tabular-nums;\n}\n\n.smart-env-stats__inspector-list {\n display: flex;\n flex-direction: column;\n gap: var(--size-4-2);\n min-width: 0;\n}\n\n.smart-env-stats__inspector-record {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--size-4-3);\n min-width: 0;\n padding: var(--size-4-3);\n border: 1px solid var(--background-modifier-border);\n border-left: 3px solid var(--text-faint);\n border-radius: var(--radius-m);\n background: var(--smart-env-stats-surface);\n}\n\n.smart-env-stats__inspector-record[data-tone='unexpected'] {\n border-left-color: var(--color-yellow);\n}\n\n.smart-env-stats__inspector-record-identity {\n min-width: 0;\n}\n\n.smart-env-stats__inspector-record h5 {\n margin: 0;\n overflow-wrap: anywhere;\n font-size: var(--font-ui-small);\n line-height: 1.35;\n}\n\n.smart-env-stats__inspector-record-metadata {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--size-2-2) var(--size-4-2);\n margin-top: var(--size-2-2);\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n font-variant-numeric: tabular-nums;\n}\n\n.smart-env-stats__inspector-badge {\n display: inline-flex;\n align-items: center;\n min-height: 22px;\n padding: 0 var(--size-4-2);\n border: 1px solid var(--background-modifier-border);\n border-radius: 999px;\n background: var(--background-primary);\n color: var(--text-muted);\n font-weight: var(--font-semibold);\n white-space: nowrap;\n}\n\n.smart-env-stats__inspector-badge[data-tone='unexpected'] {\n border-color: var(--color-yellow);\n color: var(--text-warning);\n}\n\n.smart-env-stats__inspector-record-reason {\n margin: var(--size-2-2) 0 0;\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n line-height: 1.4;\n}\n\n.smart-env-stats__inspector-record-reason strong {\n color: var(--text-normal);\n}\n\n.smart-env-stats__inspector-record > button {\n flex: 0 0 auto;\n}\n\n.smart-env-stats__inspector-loading,\n.smart-env-stats__inspector-empty,\n.smart-env-stats__inspector-error {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--size-4-2);\n min-height: 96px;\n padding: var(--size-4-4);\n border: 1px dashed var(--background-modifier-border);\n border-radius: var(--radius-m);\n color: var(--text-muted);\n text-align: center;\n}\n\n.smart-env-stats__inspector-error {\n color: var(--text-error);\n}\n\n.smart-env-stats__inspector-spinner {\n width: 16px;\n height: 16px;\n border: 2px solid var(--background-modifier-border);\n border-top-color: var(--interactive-accent);\n border-radius: 50%;\n animation: smart-env-stats-spin 0.8s linear infinite;\n}\n\n.smart-env-stats__inspector-load-more {\n display: block;\n margin: var(--size-4-3) auto 0;\n}\n\n.smart-env-stats__inspector-load-more[hidden] {\n display: none;\n}\n\n@keyframes smart-env-stats-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n.smart-env-stats__footer {\n min-height: 1.35em;\n padding-top: var(--size-4-2);\n border-top: 1px solid var(--background-modifier-border);\n font-variant-numeric: tabular-nums;\n text-align: right;\n}\n\n.smart-env-stats[aria-busy='true'] .smart-env-stats__refresh {\n cursor: progress;\n}\n\n@media (max-width: 760px) {\n .smart-env-stats__summary,\n .smart-env-stats__memory-grid,\n .smart-env-stats__collections {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .smart-env-stats__collection-grid {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .smart-env-stats__inspector-toolbar {\n grid-template-columns: 1fr;\n }\n}\n\n@media (max-width: 480px) {\n .smart-env-stats__header,\n .smart-env-stats__section-heading,\n .smart-env-stats__inspector-header,\n .smart-env-stats__inspector-record {\n align-items: stretch;\n flex-direction: column;\n }\n\n .smart-env-stats__refresh,\n .smart-env-stats__optimize {\n align-self: flex-start;\n }\n\n .smart-env-stats__optimization-confirm {\n align-items: stretch;\n flex-direction: column;\n }\n\n .smart-env-stats__optimization-confirm-actions {\n flex-wrap: wrap;\n }\n\n .smart-env-stats__inspector-field {\n grid-template-columns: 1fr;\n gap: var(--size-2-2);\n }\n\n .smart-env-stats__inspector-record > button {\n align-self: flex-start;\n }\n\n .smart-env-stats__summary,\n .smart-env-stats__memory-grid,\n .smart-env-stats__collections {\n grid-template-columns: 1fr;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .smart-env-stats__progress-fill,\n button.smart-env-stats__collection-value {\n transition: none;\n }\n\n .smart-env-stats__inspector-spinner {\n animation: none;\n }\n}\n"; // 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/actions/env/show_stats.js var import_obsidian12 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/embedding_diagnostics.js function has_current_embedding(item, embeddings, file_info = null, read_hash_override) { if (!item || !embeddings) return false; const resolved_file_info = file_info || get_embeddings_file_info(embeddings); const { model_fingerprint = "", file = "", dims = 0, value_count = 0 } = resolved_file_info || {}; if (!model_fingerprint || !file || !dims || !value_count) return false; let item_data; try { item_data = item.data; } catch { return false; } const embedding_refs = item_data?.embedding?.[DEFAULT_EMBEDDING_TYPE]; if (!embedding_refs) return false; let ref; if (embedding_refs.file) { const legacy_model_fingerprint = embedding_refs.model_fingerprint || embedding_refs.file; if (legacy_model_fingerprint !== model_fingerprint) return false; ref = embedding_refs; } else { ref = embedding_refs[model_fingerprint]; } if (ref?.file !== file || !Number.isInteger(ref.file_i) || ref.file_i < 0) { return false; } const read_hash = read_hash_override ?? item_data?.last_read?.hash ?? ""; if (!ref.read_hash || ref.read_hash !== read_hash) return false; return (ref.file_i + 1) * dims <= value_count; } function get_embeddings_file_info(embeddings) { if (!embeddings) return null; try { if (typeof embeddings.get_active_file_info === "function") { return embeddings.get_active_file_info(); } const model_fingerprint = embeddings.model_fingerprint || ""; const file = embeddings.active_file || model_fingerprint; const dims = Number(embeddings.dims || 0); const value_count = Number( embeddings.get_vector_value_count?.(file) ?? embeddings._vectors_by_file?.[file]?.length ?? 0 ); return { model_fingerprint, file, dims, value_count }; } catch { return null; } } function yield_to_main_thread() { return new Promise((resolve) => setTimeout(resolve, 0)); } // node_modules/obsidian-smart-env/src/actions/env/show_stats.js var DEFAULT_COLLECTION_KEYS = [ "smart_sources", "smart_blocks" ]; var DEFAULT_YIELD_AFTER_MS = 10; var YIELD_CHECK_INTERVAL = 64; function env_show_stats() { const app2 = this.main?.app || this.obsidian_app; if (!app2) return false; const modal = new EnvStatsModal(app2, this); modal.open(); return true; } var menus = { "env:status_bar_menu": { title: "Show stats", icon: "chart-pie", order: 20 } }; var EnvStatsModal = class extends import_obsidian12.Modal { constructor(app2, env) { super(app2); this.env = env; } onOpen() { this.titleEl.setText("Smart Environment"); this.modalEl?.classList?.add("smart-env-stats-modal"); this.render(); } onClose() { this.modalEl?.classList?.remove("smart-env-stats-modal"); this.contentEl.empty(); } async render() { this.contentEl.empty(); const loading_el = this.contentEl.createEl("p", { cls: "smart-env-stats-modal__loading", text: "Opening stats..." }); loading_el.setAttribute("aria-live", "polite"); try { const component = await this.env.smart_components.render_component( "env_stats", this.env ); this.contentEl.empty(); if (component) { this.contentEl.appendChild(component); return; } this.contentEl.createEl("p", { text: "Failed to load stats." }); } catch (error) { console.error("[env_stats] Failed to render stats modal", error); this.contentEl.empty(); this.contentEl.createEl("p", { text: "Failed to load stats. See the developer console for details." }); } } }; async function collect_environment_stats(env, params = {}) { const { collection_keys = DEFAULT_COLLECTION_KEYS, on_progress, on_collection, is_cancelled, yield_after_ms = DEFAULT_YIELD_AFTER_MS } = params; const started_at = now_ms(); const collections = []; for (const collection_key of collection_keys) { if (is_cancelled?.()) break; const collection = env?.[collection_key]; const state = env?.collections?.[collection_key] || "not loaded"; const stats = await collect_collection_stats(collection, { collection_key, state, is_cancelled, yield_after_ms, on_progress: (collection_stats) => { on_progress?.({ collection_key, collection_stats }); } }); collections.push(stats); on_collection?.(stats); } return { collections, totals: aggregate_collection_stats(collections), scan_time_ms: Math.max(0, Math.round(now_ms() - started_at)), cancelled: Boolean(is_cancelled?.()) }; } async function collect_collection_stats(collection, params = {}) { const { collection_key = collection?.collection_key || "", state = collection?.env?.collections?.[collection_key] || "not loaded", on_progress, is_cancelled, yield_after_ms = DEFAULT_YIELD_AFTER_MS } = params; const started_at = now_ms(); const items = collection?.items || {}; const item_keys = Object.keys(items); const stats = create_collection_stats({ collection_key, state, total_items: item_keys.length, load_time_ms: collection?.load_time_ms }); if (!collection || !collection.items || state !== "loaded") { return finalize_collection_stats(stats, started_at); } const embeddings = collection.embeddings; const file_info = get_embeddings_file_info(embeddings); let yielded_at = started_at; for (let item_i = 0; item_i < item_keys.length; item_i += 1) { if (is_cancelled?.()) { stats.cancelled = true; break; } const item = items[item_keys[item_i]]; if (!item) continue; const should_embed = get_should_embed(item); const vectorized = has_current_embedding(item, embeddings, file_info); stats.scanned_items += 1; if (should_embed) stats.should_embed += 1; else stats.should_not_embed += 1; if (vectorized) stats.vectorized += 1; if (should_embed && vectorized) stats.embedded += 1; if (should_embed && !vectorized) stats.missing_embed += 1; if (!should_embed && vectorized) stats.extraneous_embed += 1; if (item._queue_embed) stats.queued += 1; if ((item_i + 1) % YIELD_CHECK_INTERVAL !== 0) continue; const current_time = now_ms(); if (current_time - yielded_at < yield_after_ms) continue; on_progress?.(finalize_collection_stats({ ...stats }, started_at, false)); await yield_to_main_thread(); yielded_at = now_ms(); } return finalize_collection_stats(stats, started_at); } async function collect_collection_inspection_records(collection, params = {}) { const { collection_key = collection?.collection_key || "", status = "skipped", on_progress, is_cancelled, yield_after_ms = DEFAULT_YIELD_AFTER_MS } = params; if (status !== "skipped" && status !== "unexpected") { throw new TypeError(`Unsupported collection inspection status: ${status}`); } const started_at = now_ms(); const items = collection?.items || {}; const item_keys = Object.keys(items); const embeddings = collection?.embeddings; const file_info = get_embeddings_file_info(embeddings); const records = []; const reasons_by_key = /* @__PURE__ */ new Map(); const status_counts = { skipped: 0, unexpected: 0 }; let scanned_items = 0; let yielded_at = started_at; for (let item_i = 0; item_i < item_keys.length; item_i += 1) { if (is_cancelled?.()) break; const item = items[item_keys[item_i]]; if (!item) continue; const should_embed = get_should_embed(item); scanned_items += 1; if (!should_embed) { const vectorized = has_current_embedding(item, embeddings, file_info); if (status === "skipped" || vectorized) { const record = create_collection_inspection_record(item, { collection, collection_key, vectorized }); records.push(record); status_counts[record.status_key] += 1; const reason = reasons_by_key.get(record.reason_key) || { key: record.reason_key, label: record.reason_label, count: 0 }; reason.count += 1; reasons_by_key.set(record.reason_key, reason); } } if ((item_i + 1) % YIELD_CHECK_INTERVAL !== 0) continue; const current_time = now_ms(); if (current_time - yielded_at < yield_after_ms) continue; on_progress?.({ collection_key, status, scanned_items, total_items: item_keys.length, matched_items: records.length }); await yield_to_main_thread(); yielded_at = now_ms(); } records.sort(compare_inspection_records); return { collection_key, status, total_items: item_keys.length, scanned_items, records, status_counts, reasons: [...reasons_by_key.values()].sort((a, b) => b.count - a.count || a.label.localeCompare(b.label)), scan_time_ms: Math.max(0, Math.round(now_ms() - started_at)), cancelled: Boolean(is_cancelled?.()) }; } function compare_inspection_records(a, b) { return String(a?.source_key || "").localeCompare(String(b?.source_key || "")) || Number(a?.line_start || 0) - Number(b?.line_start || 0) || String(a?.key || "").localeCompare(String(b?.key || "")); } function create_collection_inspection_record(item, params = {}) { const { collection, collection_key = "", vectorized = false } = params; const key = get_item_key(item); const item_type = collection_key === "smart_blocks" ? "block" : "source"; const size = get_item_size(item); const min_chars = get_min_chars(item, collection, collection_key); const reason = get_collection_skip_reason(item, { collection_key, item_type, min_chars, size }); const line_start = item_type === "block" ? get_line_number(item, "line_start") : null; const line_end = item_type === "block" ? Math.max(line_start || 1, get_line_number(item, "line_end") || line_start || 1) : null; const file_type = get_string_value(item, "file_type"); const source_key = item_type === "block" ? get_string_value(item, "source_key") || key.split("#")[0] : key; const search_text = [ key, source_key, file_type, reason.label, reason.detail, vectorized ? "unexpected vector" : "skipped" ].filter(Boolean).join(" ").toLowerCase(); return { item, key, source_key, item_type, status_key: vectorized ? "unexpected" : "skipped", vectorized, size, min_chars, line_start, line_end, file_type, reason_key: reason.key, reason_label: reason.label, reason_detail: reason.detail, search_text }; } function get_collection_skip_reason(item, params = {}) { const { collection_key = "", item_type = "source", min_chars = 0, size = null } = params; if (item_type === "block" || collection_key === "smart_blocks") { if (Number.isFinite(size) && size < min_chars) { return { key: "below_minimum_size", label: "Below minimum size", detail: `Recorded size is below the ${min_chars}-character block minimum.` }; } return { key: "block_coverage_plan", label: "Not selected by block plan", detail: "The current non-overlapping block coverage plan did not select this block." }; } if (get_boolean_value(item, "is_gone")) { return { key: "source_unavailable", label: "Source file unavailable", detail: "The indexed source no longer resolves to a file." }; } if (get_adapter_should_embed(item) === false) { const file_type = get_string_value(item, "file_type"); const display_file_type = file_type.startsWith(".") ? file_type : `.${file_type}`; return { key: "source_type_excluded", label: "Source type excluded", detail: file_type ? `The active adapter does not embed ${display_file_type} sources.` : "The active source adapter does not embed this source type." }; } if (Number.isFinite(size) && size <= min_chars) { return { key: "below_minimum_size", label: "Below minimum size", detail: `Recorded size does not exceed the ${min_chars}-character source minimum.` }; } return { key: "current_policy", label: "Excluded by current policy", detail: "The item is not eligible under the current embedding policy." }; } function get_item_key(item) { const key = get_string_value(item, "key") || get_string_value(item, "path"); return key || "Unknown item"; } function get_item_size(item) { const candidates = []; try { candidates.push(item?.size); } catch { } try { candidates.push(item?.data?.size); candidates.push(item?.data?.last_read?.size); candidates.push(item?.file?.stat?.size); } catch { } for (const candidate of candidates) { if (candidate == null || candidate === "") continue; const value = Number(candidate); if (Number.isFinite(value) && value >= 0) return value; } return null; } function get_min_chars(item, collection, collection_key) { let item_min_chars; let collection_min_chars; try { item_min_chars = item?.settings?.min_chars; } catch { item_min_chars = void 0; } try { collection_min_chars = collection?.settings?.min_chars; } catch { collection_min_chars = void 0; } const configured_min_chars = item_min_chars ?? collection_min_chars; const normalized_min_chars = Number( collection_key === "smart_sources" ? configured_min_chars || 300 : configured_min_chars ?? 0 ); return Number.isFinite(normalized_min_chars) ? Math.max(0, normalized_min_chars) : collection_key === "smart_sources" ? 300 : 0; } function get_adapter_should_embed(item) { try { const value = item?.source_adapter?.should_embed; return typeof value === "boolean" ? value : void 0; } catch { return void 0; } } function get_string_value(item, key) { try { const value = item?.[key]; return value == null ? "" : String(value); } catch { return ""; } } function get_boolean_value(item, key) { try { return item?.[key] === true; } catch { return false; } } function get_line_number(item, key) { try { const raw_value = item?.[key]; if (raw_value == null || raw_value === "") return null; const value = Number(raw_value); return Number.isFinite(value) ? Math.max(1, Math.floor(value)) : null; } catch { return null; } } function aggregate_collection_stats(collections = []) { const totals = { total_items: 0, scanned_items: 0, should_embed: 0, should_not_embed: 0, vectorized: 0, embedded: 0, missing_embed: 0, extraneous_embed: 0, queued: 0 }; collections.forEach((stats) => { Object.keys(totals).forEach((key) => { totals[key] += Number(stats?.[key] || 0); }); }); totals.coverage_percent = totals.should_embed ? Math.round(totals.embedded / totals.should_embed * 100) : null; return totals; } function create_collection_stats(params = {}) { return { collection_key: params.collection_key || "", state: params.state || "not loaded", total_items: Number(params.total_items || 0), scanned_items: 0, should_embed: 0, should_not_embed: 0, vectorized: 0, embedded: 0, missing_embed: 0, extraneous_embed: 0, queued: 0, coverage_percent: null, load_time_ms: Number(params.load_time_ms || 0), scan_time_ms: 0, cancelled: false }; } function finalize_collection_stats(stats, started_at, round_time = true) { stats.coverage_percent = stats.should_embed ? Math.round(stats.embedded / stats.should_embed * 100) : null; const elapsed = Math.max(0, now_ms() - started_at); stats.scan_time_ms = round_time ? Math.round(elapsed) : elapsed; return stats; } function get_should_embed(item) { try { return Boolean(item.should_embed); } catch (error) { console.warn("[env_stats] Failed to evaluate should_embed", item?.key, error); return false; } } function now_ms() { return globalThis.performance?.now?.() || Date.now(); } // node_modules/obsidian-smart-env/src/utils/open_source.js var import_obsidian13 = 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_obsidian13.Keymap.isModEvent(event); const is_alt = import_obsidian13.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/components/env_stats.js var COLLECTION_KEYS = [ "smart_sources", "smart_blocks" ]; var CACHE_FRESH_MS = 1e4; var stats_cache = /* @__PURE__ */ new WeakMap(); var INSPECTOR_PAGE_SIZE = 30; var INSPECTOR_SEARCH_DEBOUNCE_MS = 120; var INSPECTION_PRESENTATION = { skipped: { title: "Skipped items", noun: "skipped items", singular_noun: "skipped item", description: "Not eligible under the current embedding policy, so these items do not reduce coverage. Unexpected vectors are included because they are also skipped." }, unexpected: { title: "Unexpected vectors", noun: "unexpected vectors", singular_noun: "unexpected vector", description: "Not eligible under the current policy, but a current vector is still stored. Each row explains why the item is now skipped." } }; var number_formatter = new Intl.NumberFormat(); var inspector_instance_i = 0; function build_html2() { return `

Embedding health

Preparing exact stats...

${build_metric_html("total_items", "Indexed items")} ${build_metric_html("should_embed", "Eligible")} ${build_metric_html("vectorized", "Current embeddings")} ${build_metric_html("missing_embed", "Needs embedding")}

Vector memory

Loaded embedding vectors and reserved capacity.

-
${build_metric_html("memory_used", "Used")} ${build_metric_html("memory_allocated", "Allocated")} ${build_metric_html("memory_unused", "Available capacity")}

Collections

Coverage uses the current model, vector file, and content hash. Select Skipped or Unexpected to inspect items.

${build_inspector_html()}

Clean up unexpected embeddings

Optimize source data and vector files to remove unexpected embeddings. This runs separately from Compact.

Waiting for exact stats...

`; } async function render3(env, opts = {}) { this.apply_style_sheet(env_stats_default); const frag = this.create_doc_fragment(build_html2(env, opts)); const container = frag.firstElementChild; post_process2.call(this, env, container, opts); return container; } function post_process2(env, container, opts = {}) { if (!container) return container; const status_el = container.querySelector(".smart-env-stats__status"); const refresh_btn = container.querySelector(".smart-env-stats__refresh"); const collections_el = container.querySelector(".smart-env-stats__collections"); const footer_el = container.querySelector(".smart-env-stats__footer"); const inspector_elements = get_inspector_elements(container); const optimize_btn = container.querySelector(".smart-env-stats__optimize"); const optimization_status_el = container.querySelector(".smart-env-stats__optimization-status"); const optimization_confirm_el = container.querySelector(".smart-env-stats__optimization-confirm"); const confirm_optimize_btn = optimization_confirm_el.querySelector('[data-action="confirm-source-data-optimization"]'); const cancel_optimize_btn = optimization_confirm_el.querySelector('[data-action="cancel-source-data-optimization"]'); const optimization_adapter = env.smart_sources.data_adapter; const inspector_cache = /* @__PURE__ */ new Map(); const inspector_state = { active_trigger: null, collection_key: "", filtered_records: [], load_id: 0, load_result: null, query: "", reason_key: "all", search_timeout: null, status: "", visible_count: INSPECTOR_PAGE_SIZE }; const inspector_id = `smart-env-stats-inspector-${++inspector_instance_i}`; let disposed = false; let scan_id = 0; let start_timeout = null; let unexpected_embedding_count = 0; let optimization_busy = false; let optimization_plan = null; if (inspector_elements.container) { inspector_elements.container.id = inspector_id; inspector_elements.container.setAttribute("aria-labelledby", `${inspector_id}-title`); } if (inspector_elements.title) inspector_elements.title.id = `${inspector_id}-title`; COLLECTION_KEYS.forEach((collection_key) => { const card = ensure_collection_card(collections_el, collection_key); card?.querySelectorAll("[data-inspect-status]").forEach((button) => { button.setAttribute("aria-controls", inspector_id); }); }); const close_inspector = ({ restore_focus = false } = {}) => { const active_trigger = inspector_state.active_trigger; inspector_state.load_id += 1; if (inspector_state.search_timeout) { clearTimeout(inspector_state.search_timeout); inspector_state.search_timeout = null; } active_trigger?.removeAttribute("data-selected"); active_trigger?.setAttribute("aria-expanded", "false"); inspector_state.active_trigger = null; inspector_state.collection_key = ""; inspector_state.filtered_records = []; inspector_state.load_result = null; inspector_state.query = ""; inspector_state.reason_key = "all"; inspector_state.status = ""; inspector_state.visible_count = INSPECTOR_PAGE_SIZE; if (inspector_elements.container) { inspector_elements.container.hidden = true; inspector_elements.container.setAttribute("aria-busy", "false"); } if (inspector_elements.search_input) inspector_elements.search_input.value = ""; if (restore_focus) active_trigger?.focus(); }; const show_inspection_result = (result) => { inspector_state.load_result = result; inspector_state.filtered_records = []; inspector_state.query = ""; inspector_state.reason_key = "all"; inspector_state.visible_count = INSPECTOR_PAGE_SIZE; if (inspector_elements.search_input) inspector_elements.search_input.value = ""; render_inspector_reason_options(inspector_elements.reason_select, result.reasons); apply_inspector_filters(inspector_elements, inspector_state); set_inspector_loading(inspector_elements, false); }; const show_optimization_count = () => { const noun = unexpected_embedding_count === 1 ? "embedding" : "embeddings"; optimize_btn.disabled = unexpected_embedding_count === 0; optimize_btn.textContent = "Optimize"; set_text( optimization_status_el, unexpected_embedding_count ? `${format_number(unexpected_embedding_count)} unexpected ${noun} can be removed.` : "No unexpected embeddings to clean up." ); }; const set_optimization_stats = (totals = {}) => { unexpected_embedding_count = totals.extraneous_embed || 0; if (optimization_busy || optimization_plan || !optimization_confirm_el.hidden) { return; } show_optimization_count(); }; const prepare_source_data_optimization = async (replace_existing_backups = false) => { optimization_busy = true; optimization_confirm_el.hidden = true; optimize_btn.disabled = true; optimize_btn.textContent = "Optimizing..."; set_text(optimization_status_el, "Preparing optimized source and vector files..."); try { optimization_plan = await optimization_adapter.optimize_source_data({ replace_existing_backups }); if (disposed) return; const backup_message = replace_existing_backups ? "Prior backup files will be replaced with the current environment data." : "Current source and vector files will be retained as .backup files."; optimize_btn.disabled = false; optimize_btn.textContent = "Finish optimization"; set_text( optimization_status_el, `Optimized files are ready. ${backup_message} Do not change sources or the embedding model before finishing.` ); } catch (error) { if (disposed) return; optimization_plan = null; optimize_btn.textContent = "Optimize"; if (!replace_existing_backups && error.message.startsWith("Source data optimization backup already exists:")) { optimization_confirm_el.hidden = false; set_text(optimization_status_el, "Backup replacement requires confirmation."); confirm_optimize_btn.focus(); return; } console.error("[env_stats] Failed to optimize source data", error); optimize_btn.disabled = unexpected_embedding_count === 0; set_text( optimization_status_el, "Optimization could not be prepared. See the developer console for details." ); } finally { optimization_busy = false; } }; const finish_source_data_optimization = async () => { optimization_busy = true; optimize_btn.disabled = true; optimize_btn.textContent = "Finishing..."; set_text(optimization_status_el, "Applying optimized source and vector files..."); try { await optimization_adapter.finish_source_data_optimization(optimization_plan); } catch (error) { if (disposed) return; console.error("[env_stats] Failed to finish source data optimization", { error, plan: optimization_plan }); optimize_btn.textContent = "Finishing failed"; set_text( optimization_status_el, "Finishing failed. Close Obsidian and restore the complete .backup file set before restarting. See the developer console for details." ); } finally { optimization_busy = false; } }; const load_inspection = async (trigger, { force = false } = {}) => { if (!trigger || trigger.disabled) return; const card = trigger.closest("[data-collection-key]"); const collection_key = card?.dataset.collectionKey || ""; const status = trigger.dataset.inspectStatus || ""; if (!collection_key || !INSPECTION_PRESENTATION[status]) return; const is_current = inspector_state.active_trigger === trigger && !inspector_elements.container?.hidden; if (is_current) { close_inspector(); return; } close_inspector(); inspector_state.active_trigger = trigger; inspector_state.collection_key = collection_key; inspector_state.status = status; trigger.dataset.selected = "true"; trigger.setAttribute("aria-expanded", "true"); configure_inspector(inspector_elements, { collection_key, status }); if (inspector_elements.container) inspector_elements.container.hidden = false; set_inspector_loading(inspector_elements, true); inspector_elements.container?.scrollIntoView?.({ block: "nearest" }); const cache_key = `${collection_key}:${status}`; const cached = !force ? inspector_cache.get(cache_key) : null; if (cached) { show_inspection_result(cached); return; } const current_load_id = ++inspector_state.load_id; try { const result = await collect_collection_inspection_records( env?.[collection_key], { collection_key, status, is_cancelled: () => disposed || current_load_id !== inspector_state.load_id, on_progress: ({ scanned_items, total_items, matched_items }) => { if (disposed || current_load_id !== inspector_state.load_id) return; set_text( inspector_elements.status, `Scanning ${format_number(scanned_items)} of ${format_number(total_items)} items / ${format_number(matched_items)} found...` ); } } ); if (disposed || current_load_id !== inspector_state.load_id || result.cancelled) { return; } inspector_cache.set(cache_key, result); show_inspection_result(result); } catch (error) { if (disposed || current_load_id !== inspector_state.load_id) return; console.error("[env_stats] Failed to inspect collection stat", error); set_inspector_loading(inspector_elements, false); render_inspector_error(inspector_elements, error); } }; const load_stats = async ({ force = false } = {}) => { const current_scan_id = ++scan_id; const cached = stats_cache.get(env); const cache_age_ms = cached ? Math.max(0, Date.now() - cached.calculated_at) : Number.POSITIVE_INFINITY; if (cached) { render_stats(container, cached); set_optimization_stats(cached.totals); set_text( status_el, cache_age_ms < CACHE_FRESH_MS ? `Showing stats calculated ${format_age(cache_age_ms)}.` : "Showing the previous result while exact stats refresh..." ); } if (!force && cached && cache_age_ms < CACHE_FRESH_MS) { container.setAttribute("aria-busy", "false"); set_button_loading(refresh_btn, false); return; } inspector_cache.clear(); close_inspector(); if (!optimization_busy && !optimization_plan) { optimization_confirm_el.hidden = true; optimize_btn.disabled = true; optimize_btn.textContent = "Optimize"; set_text(optimization_status_el, "Refreshing exact stats..."); } container.setAttribute("aria-busy", "true"); set_button_loading(refresh_btn, true); set_text(status_el, "Scanning collection metadata..."); set_text(footer_el, ""); const started_at = now_ms2(); const memory_usage_promise = get_vector_memory_usage(env).then((memory_usage2) => { if (!disposed && current_scan_id === scan_id) { render_memory_stats(container, memory_usage2); } return memory_usage2; }); const stats = await collect_environment_stats(env, { collection_keys: COLLECTION_KEYS, is_cancelled: () => disposed || current_scan_id !== scan_id, on_progress: ({ collection_key, collection_stats }) => { if (disposed || current_scan_id !== scan_id) return; render_collection_progress( ensure_collection_card(collections_el, collection_key), collection_stats ); set_text( status_el, `Scanning ${format_collection_name(collection_key)}: ${format_number(collection_stats.scanned_items)} of ${format_number(collection_stats.total_items)}...` ); }, on_collection: (collection_stats) => { if (disposed || current_scan_id !== scan_id) return; render_collection_stats( ensure_collection_card(collections_el, collection_stats.collection_key), collection_stats ); } }); if (disposed || current_scan_id !== scan_id || stats.cancelled) return; const memory_usage = await memory_usage_promise; if (disposed || current_scan_id !== scan_id) return; const result = { ...stats, memory_usage, total_time_ms: Math.max(0, Math.round(now_ms2() - started_at)), calculated_at: Date.now() }; stats_cache.set(env, result); render_stats(container, result); set_optimization_stats(result.totals); set_text( status_el, `Scanned ${format_number(result.totals.scanned_items)} items in ${format_duration(result.total_time_ms)}.` ); container.setAttribute("aria-busy", "false"); set_button_loading(refresh_btn, false); }; const handle_load_error = (error) => { if (disposed) return; console.error("[env_stats] Failed to calculate stats", error); if (!optimization_busy && !optimization_plan) { optimize_btn.disabled = true; set_text(optimization_status_el, "Exact stats could not be refreshed."); } set_text(status_el, "Failed to calculate stats. See the developer console for details."); container.setAttribute("aria-busy", "false"); set_button_loading(refresh_btn, false); }; const handle_refresh = () => { if (start_timeout) { clearTimeout(start_timeout); start_timeout = null; } inspector_cache.clear(); close_inspector(); load_stats({ force: true }).catch(handle_load_error); }; const handle_collection_click = (event) => { const trigger = event.target?.closest?.("button[data-inspect-status]"); if (!trigger || !collections_el?.contains(trigger)) return; load_inspection(trigger).catch((error) => { if (disposed) return; console.error("[env_stats] Failed to open collection inspector", error); set_inspector_loading(inspector_elements, false); render_inspector_error(inspector_elements, error); }); }; const handle_inspector_search = (event) => { inspector_state.query = String(event.currentTarget?.value || "").trim().toLowerCase(); if (inspector_state.search_timeout) clearTimeout(inspector_state.search_timeout); inspector_state.search_timeout = setTimeout(() => { inspector_state.search_timeout = null; inspector_state.visible_count = INSPECTOR_PAGE_SIZE; apply_inspector_filters(inspector_elements, inspector_state); }, INSPECTOR_SEARCH_DEBOUNCE_MS); }; const handle_reason_change = (event) => { inspector_state.reason_key = event.currentTarget?.value || "all"; inspector_state.visible_count = INSPECTOR_PAGE_SIZE; apply_inspector_filters(inspector_elements, inspector_state); }; const handle_load_more = () => { inspector_state.visible_count += INSPECTOR_PAGE_SIZE; render_inspector_records(inspector_elements, inspector_state); }; const handle_close_inspector = () => { close_inspector({ restore_focus: true }); }; const handle_inspector_list_click = async (event) => { const button = event.target?.closest?.("[data-open-record-index]"); if (!button || !inspector_elements.list?.contains(button)) return; const record_i = Number(button.dataset.openRecordIndex); const record = inspector_state.filtered_records[record_i]; if (!record?.item) return; const idle_label = button.textContent; button.disabled = true; button.textContent = "Opening..."; try { await open_source(record.item, event); } catch (error) { console.error("[env_stats] Failed to open inspected item", error); } finally { if (button.isConnected) { button.disabled = false; button.textContent = idle_label; } } }; const handle_optimize_source_data = () => { if (optimization_busy) return; if (optimization_plan) { finish_source_data_optimization(); return; } if (unexpected_embedding_count === 0) return; prepare_source_data_optimization(); }; const handle_confirm_source_data_optimization = () => { if (optimization_busy) return; prepare_source_data_optimization(true); }; const handle_cancel_source_data_optimization = () => { if (optimization_busy) return; optimization_confirm_el.hidden = true; show_optimization_count(); optimize_btn.focus(); }; refresh_btn?.addEventListener("click", handle_refresh); collections_el?.addEventListener("click", handle_collection_click); inspector_elements.search_input?.addEventListener("input", handle_inspector_search); inspector_elements.reason_select?.addEventListener("change", handle_reason_change); inspector_elements.load_more_btn?.addEventListener("click", handle_load_more); inspector_elements.close_btn?.addEventListener("click", handle_close_inspector); inspector_elements.list?.addEventListener("click", handle_inspector_list_click); optimize_btn.addEventListener("click", handle_optimize_source_data); confirm_optimize_btn.addEventListener("click", handle_confirm_source_data_optimization); cancel_optimize_btn.addEventListener("click", handle_cancel_source_data_optimization); start_timeout = setTimeout(() => { start_timeout = null; load_stats({ force: Boolean(opts.force_refresh) }).catch(handle_load_error); }, 0); this.attach_disposer?.(container, () => { disposed = true; scan_id += 1; inspector_state.load_id += 1; if (start_timeout) clearTimeout(start_timeout); if (inspector_state.search_timeout) clearTimeout(inspector_state.search_timeout); refresh_btn?.removeEventListener("click", handle_refresh); collections_el?.removeEventListener("click", handle_collection_click); inspector_elements.search_input?.removeEventListener("input", handle_inspector_search); inspector_elements.reason_select?.removeEventListener("change", handle_reason_change); inspector_elements.load_more_btn?.removeEventListener("click", handle_load_more); inspector_elements.close_btn?.removeEventListener("click", handle_close_inspector); inspector_elements.list?.removeEventListener("click", handle_inspector_list_click); optimize_btn.removeEventListener("click", handle_optimize_source_data); confirm_optimize_btn.removeEventListener("click", handle_confirm_source_data_optimization); cancel_optimize_btn.removeEventListener("click", handle_cancel_source_data_optimization); }); return container; } function build_metric_html(key, label) { return `
${label}
-
`; } function build_inspector_html() { return ``; } function get_inspector_elements(container) { const inspector = container.querySelector("[data-collection-inspector]"); return { container: inspector, close_btn: inspector?.querySelector('[data-action="close-inspector"]'), description: inspector?.querySelector("[data-inspector-description]"), eyebrow: inspector?.querySelector("[data-inspector-eyebrow]"), list: inspector?.querySelector("[data-inspector-list]"), load_more_btn: inspector?.querySelector('[data-action="load-more-inspector"]'), reason_select: inspector?.querySelector("[data-inspector-reason]"), search_input: inspector?.querySelector("[data-inspector-search]"), status: inspector?.querySelector("[data-inspector-status]"), title: inspector?.querySelector("[data-inspector-title]") }; } function configure_inspector(elements, params = {}) { const { collection_key = "", status = "skipped" } = params; const presentation = INSPECTION_PRESENTATION[status] || INSPECTION_PRESENTATION.skipped; const collection_name = format_collection_name(collection_key); if (elements.container) { elements.container.dataset.status = status; delete elements.container.dataset.error; } set_text(elements.eyebrow, `${collection_name} diagnostics`); set_text(elements.title, `${presentation.title} in ${collection_name}`); set_text(elements.description, presentation.description); set_text(elements.status, "Preparing item-level diagnostics..."); if (elements.search_input) elements.search_input.value = ""; render_inspector_reason_options(elements.reason_select, []); if (elements.list) elements.list.innerHTML = build_inspector_loading_html("Scanning item metadata..."); if (elements.load_more_btn) elements.load_more_btn.hidden = true; } function set_inspector_loading(elements, loading) { elements.container?.setAttribute("aria-busy", String(loading)); if (elements.search_input) elements.search_input.disabled = loading; if (elements.reason_select) elements.reason_select.disabled = loading; if (loading && elements.load_more_btn) elements.load_more_btn.hidden = true; } function render_inspector_reason_options(select, reasons = []) { if (!select) return; const document_ref = select.ownerDocument; const total_count = reasons.reduce((total, reason) => total + Number(reason?.count || 0), 0); const all_option = document_ref.createElement("option"); all_option.value = "all"; all_option.textContent = total_count ? `All reasons (${format_number(total_count)})` : "All reasons"; const options = [all_option]; reasons.forEach((reason) => { const option = document_ref.createElement("option"); option.value = reason.key; option.textContent = `${reason.label} (${format_number(reason.count)})`; options.push(option); }); select.replaceChildren(...options); select.value = "all"; } function apply_inspector_filters(elements, state) { const records = state.load_result?.records || []; const query = state.query || ""; const reason_key = state.reason_key || "all"; state.filtered_records = records.filter((record) => (reason_key === "all" || record.reason_key === reason_key) && (!query || record.search_text?.includes(query))); render_inspector_records(elements, state); } function render_inspector_records(elements, state) { if (!elements.list) return; const records = state.filtered_records || []; const visible_records = records.slice(0, state.visible_count); const total_count = Number(state.load_result?.records?.length || 0); const visible_count = visible_records.length; const is_filtered = Boolean(state.query || state.reason_key !== "all"); const presentation = INSPECTION_PRESENTATION[state.status] || INSPECTION_PRESENTATION.skipped; const total_noun = total_count === 1 ? presentation.singular_noun : presentation.noun; const unexpected_count = Number(state.load_result?.status_counts?.unexpected || 0); let status_text; if (is_filtered) { status_text = records.length > visible_count ? `Showing ${format_number(visible_count)} of ${format_number(records.length)} matches (${format_number(total_count)} total)` : `${format_number(records.length)} matches (${format_number(total_count)} total)`; } else if (total_count > visible_count) { status_text = `Showing ${format_number(visible_count)} of ${format_number(total_count)} ${total_noun}`; } else { status_text = `${format_number(total_count)} ${total_noun}`; } if (!is_filtered && state.status === "skipped" && unexpected_count) { status_text += `, including ${format_number(unexpected_count)} unexpected ${unexpected_count === 1 ? "vector" : "vectors"}`; } set_text(elements.status, status_text); if (!visible_records.length) { let empty_message = `No ${presentation.noun} found.`; if (total_count && state.query && state.reason_key !== "all") { empty_message = "No items match this search and reason."; } else if (total_count && state.query) { empty_message = "No items match this search."; } else if (total_count && state.reason_key !== "all") { empty_message = "No items match this reason."; } elements.list.replaceChildren(create_inspector_message( elements.list.ownerDocument, empty_message, "empty" )); } else { const document_ref = elements.list.ownerDocument; const fragment = document_ref.createDocumentFragment(); visible_records.forEach((record, record_i) => { fragment.appendChild(create_inspector_record(document_ref, record, record_i)); }); elements.list.replaceChildren(fragment); } if (!elements.load_more_btn) return; elements.load_more_btn.hidden = visible_count >= records.length; if (!elements.load_more_btn.hidden) { const remaining = records.length - visible_count; elements.load_more_btn.textContent = `Show ${format_number(Math.min(INSPECTOR_PAGE_SIZE, remaining))} more`; } } function create_inspector_record(document_ref, record, record_i) { const article = document_ref.createElement("article"); article.className = "smart-env-stats__inspector-record"; article.dataset.tone = record.status_key || "skipped"; article.setAttribute("role", "listitem"); const identity = document_ref.createElement("div"); identity.className = "smart-env-stats__inspector-record-identity"; const title = document_ref.createElement("h5"); title.textContent = format_inspection_record_label(record); title.title = record.key || ""; const metadata = document_ref.createElement("div"); metadata.className = "smart-env-stats__inspector-record-metadata"; metadata.appendChild(create_inspector_badge(document_ref, record.status_key)); append_metadata_value(metadata, record.item_type === "block" ? "Block" : "Source"); if (Number.isFinite(record.size)) { append_metadata_value( metadata, `${format_number(record.size)} ${Number(record.size) === 1 ? "char" : "chars"}` ); } if (record.line_start && record.line_end) { append_metadata_value(metadata, `Lines ${record.line_start}-${record.line_end}`); } if (record.file_type) append_metadata_value(metadata, format_file_type(record.file_type)); const reason = document_ref.createElement("p"); reason.className = "smart-env-stats__inspector-record-reason"; const reason_label = document_ref.createElement("strong"); reason_label.textContent = record.reason_label; reason.appendChild(reason_label); if (record.reason_detail) reason.append(`. ${record.reason_detail}`); identity.append(title, metadata, reason); const action = document_ref.createElement("button"); action.type = "button"; action.dataset.openRecordIndex = String(record_i); action.textContent = "Open note"; article.append(identity, action); return article; } function create_inspector_badge(document_ref, status_key) { const badge = document_ref.createElement("span"); badge.className = "smart-env-stats__inspector-badge"; badge.dataset.tone = status_key || "skipped"; badge.textContent = status_key === "unexpected" ? "Unexpected vector" : "Skipped"; badge.title = status_key === "unexpected" ? "Not eligible, but a current vector is still stored." : "Not eligible and no current vector is stored."; return badge; } function append_metadata_value(container, value) { const element = container.ownerDocument.createElement("span"); element.textContent = value; container.appendChild(element); } function format_inspection_record_label(record) { const key = String(record?.key || "Unknown item"); const [source_key, ...block_parts] = key.split("#"); const block_label = block_parts.filter(Boolean).join(" > "); return block_label ? `${source_key} > ${block_label}` : source_key; } function format_file_type(file_type) { const normalized_file_type = String(file_type || ""); if (!normalized_file_type) return ""; return normalized_file_type.startsWith(".") ? normalized_file_type : `.${normalized_file_type}`; } function render_inspector_error(elements, error) { const message = error?.message || "Unknown error"; if (elements.container) elements.container.dataset.error = "true"; set_text(elements.status, "Collection inspection failed."); if (elements.search_input) elements.search_input.disabled = true; if (elements.reason_select) elements.reason_select.disabled = true; if (elements.list) { elements.list.replaceChildren(create_inspector_message( elements.list.ownerDocument, `${message}. See the developer console for details.`, "error" )); } if (elements.load_more_btn) elements.load_more_btn.hidden = true; } function create_inspector_message(document_ref, message, tone = "empty") { const element = document_ref.createElement("div"); element.className = `smart-env-stats__inspector-${tone}`; element.textContent = message; return element; } function build_inspector_loading_html(message) { return `
${message}
`; } function render_stats(container, result) { render_memory_stats(container, result.memory_usage); render_summary_stats(container, result.totals); const collections_el = container.querySelector(".smart-env-stats__collections"); result.collections?.forEach((stats) => { render_collection_stats( ensure_collection_card(collections_el, stats.collection_key), stats ); }); const footer_el = container.querySelector(".smart-env-stats__footer"); if (result.calculated_at) { set_text( footer_el, `Calculated ${format_clock_time(result.calculated_at)}` ); } } function render_summary_stats(container, totals = {}) { set_metric(container, "total_items", format_number(totals.total_items), "Sources + blocks"); set_metric(container, "should_embed", format_number(totals.should_embed), `${format_number(totals.should_not_embed)} skipped`); set_metric(container, "vectorized", format_number(totals.vectorized), format_percent(totals.coverage_percent)); set_metric( container, "missing_embed", format_number(totals.missing_embed), totals.extraneous_embed ? `${format_number(totals.extraneous_embed)} unexpected` : totals.missing_embed ? "Eligible without a current vector" : "All eligible items are current" ); const missing_metric = container.querySelector('[data-metric="missing_embed"]'); missing_metric?.toggleAttribute("data-attention", Number(totals.missing_embed || 0) > 0); } function render_memory_stats(container, memory_usage = {}) { if (memory_usage?.error) { set_metric(container, "memory_used", "Unavailable", memory_usage.error); set_metric(container, "memory_allocated", "-", ""); set_metric(container, "memory_unused", "-", ""); set_text(container.querySelector("[data-memory-utilization]"), "Unavailable"); return; } const used_bytes = Number(memory_usage?.used_bytes || 0); const allocated_bytes = Number(memory_usage?.allocated_bytes || 0); const unused_capacity_bytes = Number(memory_usage?.unused_capacity_bytes || 0); const utilization = allocated_bytes ? Math.round(used_bytes / allocated_bytes * 100) : 0; set_metric(container, "memory_used", format_bytes(used_bytes), `${format_number(used_bytes)} bytes`); set_metric(container, "memory_allocated", format_bytes(allocated_bytes), `${format_number(allocated_bytes)} bytes`); set_metric(container, "memory_unused", format_bytes(unused_capacity_bytes), `${format_number(unused_capacity_bytes)} bytes`); set_text( container.querySelector("[data-memory-utilization]"), allocated_bytes ? `${utilization}% utilized` : "No vectors loaded" ); } function ensure_collection_card(collections_el, collection_key) { if (!collections_el) return null; let card = collections_el.querySelector(`[data-collection-key="${collection_key}"]`); if (card) return card; card = collections_el.ownerDocument.createElement("article"); card.className = "smart-env-stats__collection"; card.dataset.collectionKey = collection_key; card.innerHTML = `

${format_collection_name(collection_key)}

Preparing...
-
${build_collection_value_html("total", "Total")} ${build_collection_value_html("eligible", "Eligible")} ${build_collection_value_html("current", "Current")} ${build_collection_value_html("missing", "Missing")} ${build_collection_value_html("skipped", "Skipped", true)} ${build_collection_value_html("unexpected", "Unexpected", true)}
`; collections_el.appendChild(card); return card; } function build_collection_value_html(key, label, inspectable = false) { const tag_name = inspectable ? "button" : "div"; const attributes = inspectable ? ` type="button" data-inspect-status="${key}" aria-expanded="false" disabled` : ""; const inspect_label = inspectable ? '' : ""; return `<${tag_name} class="smart-env-stats__collection-value" data-value="${key}"${attributes}> ${label} ${inspect_label} - `; } function render_collection_progress(card, stats) { if (!card) return; card.removeAttribute("data-loaded"); card.querySelectorAll("[data-inspect-status]").forEach((button) => { button.disabled = true; button.removeAttribute("data-has-items"); button.removeAttribute("data-selected"); button.setAttribute("aria-expanded", "false"); }); const scanned = Number(stats.scanned_items || 0); const total = Number(stats.total_items || 0); const progress_percent = total ? Math.round(scanned / total * 100) : 0; set_text( card.querySelector(".smart-env-stats__collection-state"), `Scanning ${format_number(scanned)} / ${format_number(total)}` ); set_text(card.querySelector(".smart-env-stats__coverage-value"), `${progress_percent}% scanned`); set_progress(card, progress_percent, scanned, total); card.dataset.tone = "loading"; } function render_collection_stats(card, stats) { if (!card) return; const is_loaded = stats.state === "loaded"; card.toggleAttribute("data-loaded", is_loaded); const coverage_text = is_loaded ? format_percent(stats.coverage_percent) : "Not loaded"; const state_detail = is_loaded ? `${format_number(stats.embedded)} of ${format_number(stats.should_embed)} eligible` : `${format_number(stats.total_items)} items known`; const queue_detail = stats.queued ? ` / ${format_number(stats.queued)} queued` : ""; set_text( card.querySelector(".smart-env-stats__collection-state"), `${state_detail}${queue_detail}` ); set_text(card.querySelector(".smart-env-stats__coverage-value"), coverage_text); set_collection_value(card, "total", stats.total_items); set_collection_value(card, "eligible", stats.should_embed); set_collection_value(card, "current", stats.vectorized); set_collection_value(card, "missing", stats.missing_embed); set_collection_value(card, "skipped", stats.should_not_embed); set_collection_value(card, "unexpected", stats.extraneous_embed); set_progress(card, stats.coverage_percent || 0, stats.embedded, stats.should_embed); if (!is_loaded) card.dataset.tone = "muted"; else if (stats.missing_embed) card.dataset.tone = "attention"; else if (stats.extraneous_embed) card.dataset.tone = "warning"; else card.dataset.tone = "success"; } function set_collection_value(card, key, value) { const value_el = card.querySelector(`[data-value="${key}"]`); const numeric_value = Number(value || 0); const normalized_value = Number.isFinite(numeric_value) ? Math.max(0, numeric_value) : 0; set_text(value_el?.querySelector("strong"), format_number(normalized_value)); if (!value_el?.matches?.("button[data-inspect-status]")) return; const can_inspect = card.hasAttribute("data-loaded") && normalized_value > 0; const collection_name = card.querySelector("h4")?.textContent || "this collection"; const presentation = INSPECTION_PRESENTATION[key] || INSPECTION_PRESENTATION.skipped; value_el.disabled = !can_inspect; value_el.toggleAttribute("data-has-items", can_inspect); value_el.setAttribute( "aria-label", can_inspect ? `Inspect ${format_number(normalized_value)} ${presentation.noun} in ${collection_name}` : `No ${presentation.noun} in ${collection_name}` ); value_el.title = can_inspect ? presentation.description : ""; if (!can_inspect) { value_el.removeAttribute("data-selected"); value_el.setAttribute("aria-expanded", "false"); } } function set_progress(card, percent, value, total) { const progress = card.querySelector(".smart-env-stats__progress"); const fill = card.querySelector(".smart-env-stats__progress-fill"); const normalized_percent = Math.max(0, Math.min(100, Number(percent || 0))); if (fill) fill.style.width = `${normalized_percent}%`; if (!progress) return; progress.setAttribute("aria-valuenow", String(Number(value || 0))); progress.setAttribute("aria-valuemax", String(Math.max(1, Number(total || 0)))); } function set_metric(container, key, value, detail) { const metric = container.querySelector(`[data-metric="${key}"]`); if (!metric) return; set_text(metric.querySelector(".smart-env-stats__metric-value"), value); set_text(metric.querySelector(".smart-env-stats__metric-detail"), detail); } function set_button_loading(button, loading) { if (!button) return; button.disabled = loading; button.textContent = loading ? "Calculating..." : "Refresh"; } function set_text(element, value) { if (!element) return; element.textContent = value == null ? "" : String(value); } async function get_vector_memory_usage(env) { try { const usage = await run_action_entry( env, "env_get_embedding_vector_memory_usage" ); return { used_bytes: Number(usage?.used_bytes || 0), allocated_bytes: Number(usage?.allocated_bytes || 0), unused_capacity_bytes: Number(usage?.unused_capacity_bytes || 0) }; } catch (error) { console.error("[env_stats] Failed to read vector memory usage", error); return { error: error?.message || "Vector memory stats unavailable" }; } } function format_number(value) { return number_formatter.format(Number(value || 0)); } function format_percent(percent) { return Number.isFinite(percent) ? `${percent}% coverage` : "No eligible items"; } function format_bytes(bytes) { return convert_to_human_readable_size(Math.max(0, Number(bytes || 0))); } function format_duration(duration_ms) { const normalized_ms = Math.max(0, Number(duration_ms || 0)); if (normalized_ms < 1e3) return `${Math.round(normalized_ms)} ms`; return `${(normalized_ms / 1e3).toFixed(normalized_ms < 1e4 ? 1 : 0)} s`; } function format_age(age_ms) { if (age_ms < 1e3) return "just now"; return `${Math.max(1, Math.round(age_ms / 1e3))}s ago`; } function format_clock_time(timestamp) { return new Date(timestamp).toLocaleTimeString([], { hour: "numeric", minute: "2-digit", second: "2-digit" }); } function now_ms2() { return globalThis.performance?.now?.() || Date.now(); } // 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_export_progress_state(env) { return env?.export_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 export_progress = get_export_progress_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 version4 = `v${env?.constructor?.version || ""}`; const default_message = `${env?.is_pro ? "Pro" : "Smart"} ${version4}`; if (export_progress?.active) { const progress = normalize_number(export_progress.progress); const total = normalize_number(export_progress.total); const collection_key = to_non_empty_string(export_progress.collection_key); const collection_label = collection_key ? format_collection_label(collection_key) : "Smart Environment data"; const progress_status = total > 0 ? `${progress}/${total}` : "Preparing"; return { kind: "exporting", message: `Exporting ${collection_label}...`, title: `Writing ${collection_label} to JSON in buffered chunks.`, click_action: "noop", indicator_level: null, progress_value: total > 0 ? progress : null, progress_total: total > 0 ? total : null, progress_pct: get_progress_pct(progress, total), view_title: "Exporting Smart Environment data", view_status: `${collection_label} - ${progress_status}`, view_summary: "Writing selected collection data to one JSON file without building one full environment string in memory.", view_details: [ to_non_empty_string(export_progress.file_path) ? `Output file: ${export_progress.file_path}` : "" ].filter(Boolean), view_actions: [] }; } 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 [ "exporting", "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) && !["exporting", "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_text2(title_el, view_state.view_title); set_text2(status_el, view_state.view_status, { hide_empty: true }); set_text2(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_text2(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(); const status_action_keys = [ ...action_keys, ...env?.state === "loaded" ? ["env_show_stats", "env_browse_smart_plugins"] : [] ]; status_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"; case "env_show_stats": return "Environment stats"; case "env_browse_smart_plugins": return "Browse Smart Plugins"; 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; case "env_show_stats": case "env_browse_smart_plugins": await run_action_entry(env, action_key, {}, { event_source: "env_status_component" }); return; default: return; } } // node_modules/obsidian-smart-env/src/components/lean_coffee_callout.js var import_obsidian14 = 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_obsidian14.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_obsidian15 = 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_obsidian15.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); } } } /* The PRO badge button */ .sc-events-checklist__label.pro-milestone .smart-plugin-pro-badge { appearance: none; /* layout */ display: inline-flex; align-items: center; justify-content: center; min-width: 0; min-height: 0; height: auto; margin-left: 0.4em; padding: 0.08em 0.55em; border-radius: 999px; white-space: nowrap; vertical-align: middle; cursor: pointer; /* typography */ font-family: inherit; 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); } /* Interactive refinement: follow Obsidian's accent hover behavior */ .sc-events-checklist__label.pro-milestone .smart-plugin-pro-badge:hover { background-color: var(--interactive-accent-hover); filter: brightness(1.05); } .sc-events-checklist__label.pro-milestone .smart-plugin-pro-badge:focus-visible { outline: 2px solid var(--interactive-accent); outline-offset: 2px; } /* 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_obsidian16 = 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/?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(env, 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})`; const pro_badge_html = item.is_pro ? '' : ""; return `
  • ${escape_html(item.milestone)}${pro_badge_html}
  • `; } function attach_item_link_listeners(env, container) { if (!container) return; if (container.getAttribute("data-links-enabled") === "true") return; container.setAttribute("data-links-enabled", "true"); container.addEventListener("click", (evt) => { if (evt.target.closest(".smart-plugin-pro-badge")) { env.events.emit("smart_plugins:browse"); return; } 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) => { if (evt.target.closest(".smart-plugin-pro-badge")) return; 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_obsidian16.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 max_payload_depth = 4; var max_payload_output_length = 4e3; var notification_filter_keys = Object.freeze([ ...notification_levels, debug_levels_filter_key ]); var payload_truncation_marker = "[Truncated]"; var payload_depth_marker = "[MaxDepth]"; var omitted_payload_keys = /* @__PURE__ */ new Set([ "at", "collection_key", "message", "level", "btn_text", "btn_callback", "timeout", "timeout_ms", "link", "help_link", "hide_mute_button" ]); 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 normalize_payload_limit(value, fallback) { const normalized_value = Number(value); if (!Number.isFinite(normalized_value) || normalized_value <= 0) return fallback; return Math.floor(normalized_value); } function truncate_payload_text(text, max_length) { const normalized_max_length = Math.max(0, Math.floor(max_length)); if (text.length <= normalized_max_length) { return { text, truncated: false }; } if (normalized_max_length <= payload_truncation_marker.length) { return { text: payload_truncation_marker.slice(0, normalized_max_length), truncated: true }; } return { text: `${text.slice(0, normalized_max_length - payload_truncation_marker.length)}${payload_truncation_marker}`, truncated: true }; } function get_payload_constructor_name(value) { try { const constructor_name = value?.constructor?.name; return typeof constructor_name === "string" ? constructor_name : ""; } catch { return ""; } } function is_plain_payload_object(value) { if (!value || typeof value !== "object" || Array.isArray(value)) return false; try { const prototype = Object.getPrototypeOf(value); if (prototype === null) return true; return Object.prototype.hasOwnProperty.call(prototype, "constructor") && prototype.constructor?.name === "Object"; } catch { return false; } } function get_payload_object_descriptor(value) { if (!value || typeof value !== "object") return ""; if (Array.isArray(value) || is_plain_payload_object(value)) return ""; const constructor_name = get_payload_constructor_name(value); if (constructor_name === "Date") return ""; if (constructor_name.endsWith("Error")) { try { if (typeof value.message === "string" && value.message.trim()) { return `[${constructor_name}: ${value.message.trim()}]`; } } catch { } } return `[${constructor_name || "Object"}]`; } function get_payload_value_fallback_text(value) { if (value === null) return "null"; if (typeof value === "undefined") return "undefined"; if (typeof value === "bigint") return `${value}n`; if (typeof value === "function") { return `[Function${value.name ? ` ${value.name}` : ""}]`; } if (typeof value === "symbol") return String(value); if (typeof value === "object") { return `[Unserializable ${get_payload_constructor_name(value) || "Object"}]`; } return String(value); } function consume_payload_budget(state, length) { const normalized_length = Math.max(1, Math.ceil(Number(length) || 0)); if (state.remaining_length < normalized_length) { state.remaining_length = 0; state.truncated = true; return false; } state.remaining_length -= normalized_length; return true; } function get_bounded_payload_string(value, state) { const available_length = Math.max(0, state.remaining_length - 2); if (value.length <= available_length) { consume_payload_budget(state, value.length + 2); return value; } state.remaining_length = 0; state.truncated = true; return truncate_payload_text(value, available_length).text; } function get_bounded_payload_value(value, state, depth, ancestors) { if (value === null) { consume_payload_budget(state, 4); return null; } if (typeof value === "string") return get_bounded_payload_string(value, state); if (typeof value === "bigint") return get_bounded_payload_string(`${value}n`, state); if (typeof value === "function") { return get_bounded_payload_string( `[Function${value.name ? ` ${value.name}` : ""}]`, state ); } if (typeof value === "symbol") { return get_bounded_payload_string(String(value), state); } if (typeof value === "undefined") { consume_payload_budget(state, 9); return void 0; } if (typeof value !== "object") { consume_payload_budget(state, String(value).length); return value; } const object_descriptor = get_payload_object_descriptor(value); if (object_descriptor) { return get_bounded_payload_string(object_descriptor, state); } const constructor_name = get_payload_constructor_name(value); if (constructor_name === "Date") { const date_value = value.toJSON(); if (date_value === null) return null; return get_bounded_payload_string(date_value, state); } if (ancestors.has(value)) { return get_bounded_payload_string("[Circular]", state); } if (depth >= state.max_depth) { state.truncated = true; return get_bounded_payload_string(payload_depth_marker, state); } if (!consume_payload_budget(state, 2)) return payload_truncation_marker; ancestors.add(value); try { if (Array.isArray(value)) { const output2 = []; for (let index = 0; index < value.length; index += 1) { if (state.truncated) break; if (index > 0 && !consume_payload_budget(state, 1)) { output2.push(payload_truncation_marker); break; } output2.push(get_bounded_payload_value( value[index], state, depth + 1, ancestors )); } return output2; } const output = {}; let property_count = 0; for (const key in value) { if (!Object.prototype.hasOwnProperty.call(value, key)) continue; if (state.truncated) break; const property_cost = key.length + 3 + (property_count > 0 ? 1 : 0); if (!consume_payload_budget(state, property_cost)) { output["..."] = payload_truncation_marker; break; } output[key] = get_bounded_payload_value( value[key], state, depth + 1, ancestors ); property_count += 1; } return output; } finally { ancestors.delete(value); } } function get_payload_value_result(value, params = {}) { const normalized_max_depth = normalize_payload_limit( params.max_depth, max_payload_depth ); const normalized_max_length = normalize_payload_limit( params.max_length, max_payload_output_length ); if (typeof value === "string") { return truncate_payload_text(value, normalized_max_length); } if (typeof value === "bigint") { return truncate_payload_text(`${value}n`, normalized_max_length); } if (typeof value === "function") { return truncate_payload_text( `[Function${value.name ? ` ${value.name}` : ""}]`, normalized_max_length ); } if (typeof value === "symbol") { return truncate_payload_text(String(value), normalized_max_length); } const object_descriptor = get_payload_object_descriptor(value); if (object_descriptor) { return truncate_payload_text(object_descriptor, normalized_max_length); } try { const state = { max_depth: normalized_max_depth, remaining_length: normalized_max_length, truncated: false }; const bounded_value = get_bounded_payload_value( value, state, 0, /* @__PURE__ */ new Set() ); const payload_text = JSON.stringify(bounded_value); const next_result = truncate_payload_text( typeof payload_text === "string" ? payload_text : get_payload_value_fallback_text(value), normalized_max_length ); return { text: next_result.text, truncated: state.truncated || next_result.truncated }; } catch { return truncate_payload_text( get_payload_value_fallback_text(value), normalized_max_length ); } } function get_entry_payload_text(entry, params = {}) { const normalized_max_depth = normalize_payload_limit( params.max_depth, max_payload_depth ); const normalized_max_output_length = normalize_payload_limit( params.max_output_length, max_payload_output_length ); const event_obj = entry?.event && typeof entry.event === "object" ? entry.event : {}; const event_descriptor = get_payload_object_descriptor(event_obj); if (event_descriptor) { return truncate_payload_text( ` event: ${event_descriptor}`, normalized_max_output_length ).text; } const truncation_line = ` ...: ${payload_truncation_marker}`; const content_limit = Math.max( 0, normalized_max_output_length - truncation_line.length - 1 ); const payload_lines = []; let payload_length = 0; let truncated = false; try { for (const key in event_obj) { if (!Object.prototype.hasOwnProperty.call(event_obj, key)) continue; if (omitted_payload_keys.has(key)) continue; const separator_length = payload_lines.length > 0 ? 1 : 0; const line_prefix = ` ${key}: `; const remaining_length = content_limit - payload_length - separator_length - line_prefix.length; if (remaining_length <= 0) { truncated = true; break; } let value_result; try { value_result = get_payload_value_result(event_obj[key], { max_depth: normalized_max_depth, max_length: remaining_length }); } catch { value_result = truncate_payload_text( "[Unserializable Property]", remaining_length ); } const line = `${line_prefix}${value_result.text}`; payload_lines.push(line); payload_length += separator_length + line.length; if (value_result.truncated) { truncated = true; break; } } } catch { return truncate_payload_text( ` event: ${get_payload_value_fallback_text(event_obj)}`, normalized_max_output_length ).text; } const payload_text = payload_lines.join("\n"); if (!truncated) return payload_text; if (!payload_text) { return truncation_line.slice(0, normalized_max_output_length); } return `${payload_text} ${truncation_line}`; } 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_help_link(entry) { const event_obj = entry?.event && typeof entry.event === "object" ? entry.event : {}; if (typeof event_obj.link === "string" && event_obj.link.trim()) return event_obj.link.trim(); if (typeof event_obj.help_link === "string" && event_obj.help_link.trim()) return event_obj.help_link.trim(); return ""; } 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_ms5 = Date.now(); const seconds = Math.floor((now_ms5 - 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 help_link = get_entry_help_link(entry); const is_canonical = is_canonical_notification_entry(entry); const should_render_mute_button = is_canonical && entry?.event?.hide_mute_button !== true; 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 (help_link) { const help_link_btn = feed_container.ownerDocument.createElement("button"); help_link_btn.className = "smart-env-btn smart-env-btn--ghost smart-env-notification__help-link"; help_link_btn.type = "button"; help_link_btn.textContent = "Learn more"; help_link_btn.setAttribute("aria-label", "Learn more about this event"); help_link_btn.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); window.open(help_link, "_external"); }); actions.appendChild(help_link_btn); } if (should_render_mute_button) { 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_disabled2) { if (!btn) return; btn.disabled = Boolean(is_disabled2); } 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_obsidian18 = 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_obsidian17 = 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_obsidian17.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); } const secret_scope = setting_config.secret && scope.secrets; const storage_scope = secret_scope || scope.settings; const get_setting_value = () => secret_scope ? secret_scope.$get(setting_path) : get_by_path(storage_scope, setting_path); const set_setting_value = (value) => secret_scope ? secret_scope.$set(setting_path, value) : set_by_path(storage_scope, setting_path, value); 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_setting_value() || 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_setting_value(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_setting_value() || "")); text.onChange((value) => { set_setting_value(value); }); }); break; case "password": setting.addText((text) => { text.setValue(String(get_setting_value() || "")); text.inputEl.setAttribute("type", "password"); text.onChange((value) => { set_setting_value(value); }); }); break; case "number": setting.addText((text) => { text.setValue(String(get_setting_value() ?? "0")); text.inputEl.setAttribute("type", "number"); text.onChange((value) => { const num_value = Number(value); if (!isNaN(num_value)) { set_setting_value(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_setting_value() || ""); dropdown.onChange((value) => { set_setting_value(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_setting_value() || "")); 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_setting_value(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_setting_value() || min); slider.setDynamicTooltip(); slider.onChange((value) => { set_setting_value(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); } if (settng_is_pro) { const pro_badge = document.createElement("button"); pro_badge.type = "button"; pro_badge.className = "smart-plugin-pro-badge"; pro_badge.textContent = "PRO"; pro_badge.setAttribute("aria-label", "Browse Smart Plugins"); pro_badge.addEventListener("click", () => { window.smart_env.events.emit("smart_plugins:browse"); }); setting.settingEl.querySelector(".setting-item-name").appendChild(pro_badge); } }); } 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_obsidian17.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_obsidian18.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" }); if (this.collection?.collection_key === "embedding_models" && this.collection.default === model) { const reindex_description = container.createEl("p", { text: "Deletes the current model source and block embedding files, then re-imports sources and rebuilds embeddings. API providers may charge for the new embeddings." }); reindex_description.addClass("setting-item-description"); const reindex_actions_el = container.createDiv({ cls: "model-reindex-actions" }); const reindex_btn = reindex_actions_el.createEl("button", { text: "Re-index embeddings" }); const reindex_confirm_el = reindex_actions_el.createDiv({ cls: "sc-inline-confirm-row" }); reindex_confirm_el.style.display = "none"; reindex_confirm_el.createEl("span", { text: `Re-index embeddings for "${model.model_key}"? This deletes the active source and block embedding files before rebuilding them. API providers may charge for the new embeddings.` }); const cancel_reindex_btn = reindex_confirm_el.createEl("button", { text: "Cancel" }); const confirm_reindex_btn = reindex_confirm_el.createEl("button", { text: "Delete and re-index", cls: "mod-warning" }); const reindex_result_el = container.createDiv({ cls: "model-reindex-result" }); const close_reindex_confirmation = () => { reindex_confirm_el.style.display = "none"; reindex_btn.style.display = "inline-block"; }; reindex_btn.addEventListener("click", () => { reindex_btn.style.display = "none"; reindex_confirm_el.style.display = ""; }); cancel_reindex_btn.addEventListener("click", close_reindex_confirmation); confirm_reindex_btn.addEventListener("click", async () => { confirm_reindex_btn.disabled = true; cancel_reindex_btn.disabled = true; confirm_reindex_btn.textContent = "Re-indexing..."; reindex_result_el.textContent = "Removing current embeddings and rebuilding from source files..."; try { const result = await this.env.smart_sources.reindex_embeddings(); reindex_result_el.textContent = `Re-indexed embeddings from ${result.sources_queued} source${result.sources_queued === 1 ? "" : "s"}.`; new import_obsidian18.Notice("Embedding re-index completed."); } catch (error) { const message = error?.message || String(error || "Embedding re-index failed."); console.warn("[smart_env] Failed to re-index embeddings", error); reindex_result_el.textContent = message; new import_obsidian18.Notice(message); } finally { confirm_reindex_btn.disabled = false; cancel_reindex_btn.disabled = false; confirm_reindex_btn.textContent = "Delete and re-index"; close_reindex_confirmation(); } }); } 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: 0.75rem;\n margin-bottom: 0.5rem;\n background-color: var(--background-secondary);\n .smart-env-settings-header {\n gap: 1rem;\n margin-bottom: 0;\n }\n .model-info-content {\n flex: 1;\n min-width: 0;\n }\n .model-info-content > b,\n pre {\n overflow: hidden;\n text-overflow: ellipsis;\n }\n .model-info-content > b {\n display: block;\n white-space: nowrap;\n }\n pre {\n margin: 0.25rem 0 0;\n font-size: 0.9rem;\n white-space: pre;\n }\n .model-actions,\n .model-action-buttons,\n .model-delete-confirm {\n display: flex;\n align-items: center;\n flex-shrink: 0;\n }\n .model-action-buttons,\n .model-delete-confirm {\n gap: 0.5rem;\n }\n .model-action-buttons[hidden],\n .model-delete-confirm[hidden] {\n display: none;\n }\n .model-delete-confirm-label {\n color: var(--text-muted);\n white-space: nowrap;\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}\n'; // node_modules/obsidian-smart-env/src/components/settings/env_model.js var import_obsidian19 = require("obsidian"); function build_html7(model, params) { const details = [ `Provider: ${model.data.provider_key}`, `Model: ${model.data.model_key || "**MISSING - EDIT & SELECT MODEL**"}` ]; return `
    ${model.display_name}
    ${details.join("    ")}
    ${params.is_default ? "Current" : ''}
    `; } 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 delete_btn = container.querySelector(".delete-model"); const action_buttons = container.querySelector(".model-action-buttons"); const delete_confirm = container.querySelector(".model-delete-confirm"); const cancel_delete_btn = container.querySelector(".cancel-delete-model"); const confirm_delete_btn = container.querySelector(".confirm-delete-model"); const icon_el = container.querySelector(".test-result-icon"); (0, import_obsidian19.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(); }); delete_btn?.addEventListener("click", () => { action_buttons.hidden = true; delete_confirm.hidden = false; }); cancel_delete_btn?.addEventListener("click", () => { delete_confirm.hidden = true; action_buttons.hidden = false; }); confirm_delete_btn?.addEventListener("click", () => { model.delete_model(); }); 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_obsidian20 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/smart-models/provider_options.js var provider_options = { chat_completion_models: [ // { // label: 'Transformers (local, built-in)', // value: 'transformers', // disabled: true, // }, { label: "PRO: 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: "PRO: 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_obsidian20.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_model_settings = async () => { const default_model = models_collection.default; this.empty(container); if (!default_model) return; const models_group = render_settings_group( `${models_collection.model_type} models`, models_collection, {}, container.createDiv(), { heading_btn: { btn_text: "+ New", callback: (event, setting) => { show_new_model_menu(models_collection, event); } } } ); const models = models_collection.filter((model) => !model.deleted).sort((a, b) => a.display_name.localeCompare(b.display_name)).sort((a, b) => a === default_model ? -1 : b === default_model ? 1 : 0); for (const model of models) { const model_info_el = await models_collection.env.smart_components.render_component("settings_env_model", model, { is_default: model === default_model }); models_group.listEl.appendChild(model_info_el); } }; render_model_settings(); disposers.push(models_collection.on_event("model:changed", async () => { await render_model_settings(); })); 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 models_collections = [ env.embedding_models, env.chat_completion_models, env.ranking_models ].filter(Boolean); const default_settings_container = container.querySelector(".default-model-settings"); const render_default_model_settings = () => { const default_settings_config = {}; for (const models_collection of models_collections) { if (!models_collection.default) continue; const settings_config13 = ensure_settings_config( models_collection.env_config.settings_config, models_collection ); const default_setting = settings_config13.default_model_key; if (!default_setting) continue; default_settings_config[`${models_collection.collection_key}.default_model_key`] = default_setting; } this.empty(default_settings_container); render_settings_group( "Smart Environment settings", env, default_settings_config, default_settings_container ); }; render_default_model_settings(); const disposers = models_collections.map((models_collection) => { return models_collection.on_event("model:changed", render_default_model_settings); }); this.attach_disposer(container, disposers); 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_obsidian21 = 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; smart_sources_settings.folder_exclusions_list = normalize_exclusion_list( smart_sources_settings.folder_exclusions_list ?? smart_sources_settings.folder_exclusions ); smart_sources_settings.file_exclusions_list = normalize_exclusion_list( smart_sources_settings.file_exclusions_list ?? smart_sources_settings.file_exclusions ); return smart_sources_settings; } function add_exclusion(exclusions, value) { const current = normalize_exclusion_list(exclusions); const trimmed = value.trim(); if (!trimmed || /^[/*\\]+$/.test(trimmed)) return current; if (!current.includes(trimmed)) current.push(trimmed); return current; } function format_folder_exclusion(folder_path) { const trimmed = (folder_path ?? "").trim().replace(/\/+$/g, ""); if (!trimmed) return ""; if (trimmed.endsWith("/**")) return trimmed; return `${trimmed}/**`; } function remove_exclusion(exclusions, value) { const trimmed = value.trim(); return normalize_exclusion_list(exclusions).filter((entry) => entry !== trimmed); } // node_modules/obsidian-smart-env/src/modals/exclude_folders_fuzzy.js var ExcludedFoldersFuzzy = class extends import_obsidian21.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 or type an exclusion pattern..."); this.setInstructions([ { command: "Enter", purpose: "Exclude selected folder" }, { command: "Shift + Enter", purpose: "Add entered pattern directly" }, { command: "Esc", purpose: "Close" } ]); this.scope.register(["Shift"], "Enter", (evt) => { this.use_input_value = true; this.onChooseItem(this.inputEl.value, evt); return false; }); } /** * 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 = smart_sources_settings.folder_exclusions_list.map(format_folder_exclusion); const candidates = (this.env.smart_sources?.fs?.folder_paths || []).filter((path) => !folder_exclusions2.includes(format_folder_exclusion(path))); return candidates; } getItemText(item) { return item; } /** * Handle selecting a folder to exclude. * Shift-select inserts the current input directly; normal select stores the * selected folder as a recursive pattern ending in `/**`. * @param {string} item * @param {KeyboardEvent|MouseEvent} [evt] */ onChooseItem(item, evt) { const use_input_value = this.use_input_value || evt?.shiftKey; this.use_input_value = false; const exclusion = use_input_value ? this.inputEl.value.trim() : format_folder_exclusion(item); if (!exclusion) return; this.prevent_close = true; const smart_sources_settings = ensure_smart_sources_settings(this.env); smart_sources_settings.folder_exclusions_list = add_exclusion( smart_sources_settings.folder_exclusions_list, exclusion ); this.render_excluded_list(); this.updateSuggestions(); this.callback?.(); } /** * Render excluded folders and folder-like .gitignore patterns in one list. * User-managed exclusions include remove buttons; imported patterns show * their .gitignore source in the same position. */ render_excluded_list() { if (!this.modalEl) return; const smart_sources_settings = ensure_smart_sources_settings(this.env); const excluded_folders = smart_sources_settings.folder_exclusions_list; const gitignore_exclusions = (this.env.settings.gitignore_exclusions || []).filter((pattern) => !pattern.includes(".")); 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 && !gitignore_exclusions.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_list = remove_exclusion( smart_sources_settings.folder_exclusions_list, folder_path ); this.env.update_exclusions?.(); this.render_excluded_list(); this.updateSuggestions(); }); }); gitignore_exclusions.forEach((pattern) => { const li = list_el.createEl("li", { cls: "excluded-folder-item gitignore-exclusion-item" }); li.setText(pattern + " "); li.createEl("span", { text: ".gitignore", cls: "gitignore-exclusion-source", attr: { "aria-label": "Excluded via .gitignore pattern" } }); }); } 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_obsidian22 = require("obsidian"); var ExcludedSourcesModal = class extends import_obsidian22.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; for (const file_path of excluded_file_paths) { const li = list_el.createEl("li"); li.setText(file_path); } const too_long_files = this.app.vault.getMarkdownFiles().filter((file) => this.env.fs.adapter.should_exclude_path_for_length(file.path)).map((file) => file.path); if (!too_long_files.length) return; 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, 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 notice = container.querySelector(".embedding-model-change-notice") || container.createEl("div", { cls: "embedding-model-change-notice env-setting-note" }); notice.textContent = "Embedding model changed. Stored vectors are loading and missing embeddings are being generated."; }; } 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\n.gitignore-exclusion-source {\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.1rem 0.3rem;\n}\n\n.smart-fuzzy-header {\n max-height: 300px;\n overflow-y: auto;\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_obsidian23 = require("obsidian"); 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_obsidian23.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"; } function build_context_actions_menu(ctx, menu, params = {}) { if (!ctx || !menu) return menu; ctx?.env?.build_menu?.("smart_context:copy_menu", menu, ctx, params); menu.addSeparator(); ctx?.env?.build_menu?.("smart_context:action_menu", menu, ctx, params); menu.addSeparator(); ctx?.env?.build_menu?.("smart_contexts:menu", menu, ctx.collection, params); 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: "Copy text" }); button.addEventListener("click", async () => { await ctx.actions.context_copy_to_clipboard({ with_media: false }); }); 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 || 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_obsidian23.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 = "/* V1 compatibility. Retained for: ./item.js */\n/* 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_obsidian24 = 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_rules_list) { component_renderers.push(ctx.env.smart_components.render_component("smart_context_rules_list", ctx, opts)); } const [actions, tree, rules] = 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 (rules) footer.appendChild(rules); }; 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_obsidian24.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_obsidian25 = 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_obsidian25.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_node(); const normalized_items = selected_items.map((item) => ({ item, identity: get_item_identity(item) })).filter(({ identity }) => identity.key); const selected_folders = normalized_items.filter(({ identity }) => identity.kind === "folder").map(({ identity }) => get_identity_path(identity)); for (const { item, identity } of normalized_items) { if (is_redundant_path(get_identity_path(identity), selected_folders)) continue; insert_item_path(root, { identity, exists: item?.exists }); } return root; } function get_item_key2(item) { return item?.key || item?.path || ""; } function get_item_identity(item) { const item_key = get_item_key2(item); const item_data = item?.data && typeof item.data === "object" ? item.data : item; return normalize_context_item_data(item_key, item_data); } function create_tree_node() { return { name: "", children: /* @__PURE__ */ Object.create(null), 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 get_identity_path(identity) { const source_path = identity.source_path || identity.key || ""; const scoped_path = identity.is_external === true ? `external:${source_path}` : source_path; if (identity.kind !== "block" || typeof identity.subpath !== "string") { return scoped_path; } return `${scoped_path}#${identity.subpath}`; } 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 = "#"; } else if (i === block_path.length - 1) { segment = "#"; } continue; } segment += block_path[i]; } if (segment) segments.push(segment); return segments; } function split_path_segments(identity) { const has_block = identity.kind === "block" && typeof identity.subpath === "string"; const is_external = identity.is_external === true; const source_path = identity.source_path || identity.key || ""; const block_path = has_block ? `#${identity.subpath}` : ""; const source_segments = split_source_path_segments(source_path); const segments = [...source_segments]; let hidden_source_segments_count = 0; if (is_external) { while (hidden_source_segments_count < source_segments.length && [".", ".."].includes(source_segments[hidden_source_segments_count])) { hidden_source_segments_count++; } } if (block_path) { segments.push(...split_block_path_segments(block_path)); } return { segments, has_block, is_external, source_segments_count: source_segments.length, hidden_source_segments_count }; } function insert_item_path(root, params) { const { identity, exists } = params; const item_key = identity.key; const { segments, has_block, is_external, source_segments_count, hidden_source_segments_count } = split_path_segments(identity); let node = root; let running = is_external ? "external:" : ""; segments.forEach((segment, index) => { running = get_running_path(running, segment, { has_block, index, source_segments_count }); if (index < hidden_source_segments_count) 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; const is_direct_file = is_last && identity.kind !== "folder"; const segment_kind = index >= source_segments_count ? "block" : index === source_segments_count - 1 ? has_block ? "source" : identity.kind : "folder"; if (!Object.prototype.hasOwnProperty.call(node.children, segment)) { node.children[segment] = { name: segment, path: is_last ? item_key : running, kind: segment_kind, children: /* @__PURE__ */ Object.create(null), selected: false, is_file: is_block_leaf || is_source_file || is_direct_file }; } node = node.children[segment]; if (is_last) { node.path = item_key; node.kind = identity.kind; node.selected = true; node.exists = exists; node.is_file = identity.kind !== "folder"; } }); } function get_running_path(running, segment, params) { if (!running) return segment; if (running === "external:") return `${running}${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 = "/* V1 compatibility. Retained for: ./tree.js */\n.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) => !is_pending_removal(get_item_key3(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_key3(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_key3(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_key3(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_size2(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_size2(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_key3(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_key3(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_key3(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_obsidian27 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/smart_plugins.js var import_obsidian26 = 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_obsidian26.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 version4 = String(params.version || "").trim(); if (version4) { body.version = version4; } 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(); } async function disable_plugin(app2, plugin_id) { app2.plugins.enabledPlugins.delete(plugin_id); app2.plugins.requestSaveConfig(); } 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 MIN_COMPATIBLE_SMART_ENV_VERSION = "3.0.0"; 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(version4 = "") { const normalized_version = String(version4 || "").trim(); if (!normalized_version) return false; return compare_versions( normalized_version, MIN_COMPATIBLE_SMART_ENV_VERSION ) < 0; } // node_modules/obsidian-smart-env/src/components/smart-plugins/style.css var style_default2 = '.smart-env-pro-plugins-modal .pro-plugins-container {\n width: 100%;\n min-width: 0;\n padding-bottom: var(--size-4-4);\n color: var(--text-normal);\n font-weight: var(--font-normal);\n}\n\n.smart-env-pro-plugins-modal .pro-plugins-container > .smart-plugins-store {\n display: flex;\n flex-direction: column;\n gap: var(--size-4-3);\n margin: 0;\n\n .smart-plugins-intro {\n display: grid;\n grid-template-columns: repeat(2, minmax(0, 1fr));\n gap: var(--size-4-3);\n margin: 0;\n padding: var(--size-4-2) 0 var(--size-4-4);\n border-bottom: 1px solid var(--background-modifier-border);\n }\n\n .smart-plugins-track-guide-item {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n align-items: start;\n gap: var(--size-4-2);\n min-width: 0;\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n line-height: var(--line-height-normal);\n }\n\n .smart-plugins-track-guide-item + .smart-plugins-track-guide-item {\n padding-left: var(--size-4-3);\n border-left: 1px solid var(--background-modifier-border);\n }\n\n .smart-plugins-track-guide-copy {\n min-width: 0;\n }\n\n .smart-plugins-track-guide-copy a {\n color: var(--text-accent);\n }\n\n .smart-plugins-footer {\n max-width: 88ch;\n margin: 0;\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n line-height: var(--line-height-normal);\n }\n\n .smart-plugins-marketing-section {\n display: flex;\n flex-direction: column;\n gap: var(--size-4-3);\n margin-top: var(--size-4-3);\n padding-top: var(--size-4-4);\n border-top: 1px solid var(--background-modifier-border);\n }\n\n .smart-plugins-marketing-title {\n color: var(--text-normal);\n font-size: var(--font-ui-medium);\n font-weight: var(--font-semibold);\n }\n\n .smart-plugins-referral {\n overflow: hidden;\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-l);\n background: var(--background-primary-alt);\n }\n\n .smart-plugins-referral:empty {\n display: none;\n }\n\n .smart-plugins-referral .setting-item {\n margin: 0;\n padding: var(--size-4-3) var(--size-4-4);\n border: 0;\n background: transparent;\n }\n\n .smart-plugins-referral .setting-item + .setting-item {\n border-top: 1px solid var(--background-modifier-border);\n }\n\n .smart-plugins-referral .setting-item-info {\n min-width: 0;\n }\n\n .smart-plugins-login {\n min-width: 0;\n\n .setting-item {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--size-4-3) var(--size-4-4);\n margin: 0;\n padding: var(--size-4-3) var(--size-4-4);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-l);\n background: var(--background-secondary);\n }\n\n .setting-item-info {\n flex: 1 1 28rem;\n min-width: 0;\n }\n\n .setting-item-name {\n font-size: var(--font-ui-medium);\n font-weight: var(--font-medium);\n }\n\n .setting-item-description {\n margin: 0;\n color: var(--text-muted);\n line-height: var(--line-height-normal);\n }\n\n .setting-item-control {\n flex: 0 1 auto;\n margin-left: auto;\n }\n }\n\n .smart-plugins-login-actions {\n display: flex;\n flex-direction: column;\n align-items: flex-end;\n gap: var(--size-2-2);\n }\n\n .smart-plugins-login-alternative,\n .smart-plugins-login-linux-note,\n .smart-plugins-login-manual {\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n line-height: var(--line-height-normal);\n }\n\n .smart-plugins-login-alternative {\n margin: 0;\n }\n\n .smart-plugins-login-linux-note {\n margin-top: var(--size-2-2);\n }\n\n .smart-plugins-login-alternative a,\n .smart-plugins-login-linux-note a {\n color: var(--text-accent);\n }\n\n .smart-plugins-login-manual {\n width: min(32rem, 100%);\n margin: 0;\n padding: var(--size-4-3);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-m);\n background: var(--background-primary-alt);\n }\n\n .smart-plugins-login-manual-instructions {\n margin-bottom: var(--size-2-2);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n }\n\n .smart-plugins-login-manual-controls {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--size-2-2);\n }\n\n .smart-plugins-login-manual-input {\n flex: 1 1 240px;\n min-width: 0;\n font-size: var(--font-ui-small);\n }\n\n .smart-plugins-server-message {\n margin: 0;\n border-radius: var(--radius-l);\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 .smart-plugins-section {\n min-width: 0;\n }\n\n .smart-plugins-section + .smart-plugins-section {\n margin-top: var(--size-4-2);\n }\n\n .smart-plugins-official-list,\n .smart-plugins-experimental-list {\n display: flex;\n flex-direction: column;\n gap: var(--size-4-2);\n min-width: 0;\n }\n\n .smart-plugins-official-list > .smart-plugins-item-group,\n .smart-plugins-experimental-list > .smart-plugins-item-group {\n margin-block: 0;\n }\n\n .smart-plugins-experimental-section {\n padding-top: var(--size-4-5);\n border-top: 1px solid var(--background-modifier-border);\n }\n\n .smart-plugins-section-title {\n color: var(--text-normal);\n font-size: var(--font-ui-medium);\n font-weight: var(--font-semibold);\n }\n\n .smart-plugins-section-description {\n margin-top: var(--size-2-2);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n line-height: var(--line-height-normal);\n }\n\n .smart-plugins-experimental-list {\n margin-top: var(--size-4-3);\n }\n\n .smart-plugins-item-group {\n min-width: 0;\n margin: 0;\n\n > .setting-items {\n padding: 0;\n overflow: hidden;\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-l);\n background: var(--background-secondary);\n transition: border-color 120ms ease;\n }\n\n &:hover > .setting-items {\n border-color: var(--background-modifier-border-hover);\n }\n }\n\n .pro-plugins-list-item {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--size-4-2) var(--size-4-4);\n width: 100%;\n min-width: 0;\n border: 0;\n background: transparent;\n transition: background-color 120ms ease;\n\n &:hover {\n background: var(--background-modifier-hover);\n }\n\n > .setting-item-info {\n flex: 1 1 30rem;\n min-width: 0;\n padding: 0;\n }\n\n > .setting-item-control {\n flex: 0 1 auto;\n min-width: 0;\n max-width: 100%;\n margin-left: auto;\n padding: 0;\n }\n\n .setting-item-name {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n color: var(--text-normal);\n font-size: var(--font-ui-medium);\n font-weight: var(--font-semibold);\n line-height: var(--line-height-tight);\n }\n }\n\n .smart-plugins-item-group > .setting-items > .pro-plugins-list-item {\n padding: var(--size-4-3) var(--size-4-4);\n }\n\n .smart-plugins-item-group-combined {\n .setting-items > .pro-plugins-list-item + .pro-plugins-list-item {\n border-top: 1px solid var(--background-modifier-border);\n }\n\n .pro-plugins-list-item[data-row-control-state="included_in_pro"] {\n gap: var(--size-2-2) var(--size-4-3);\n padding-block: var(--size-4-2);\n background: var(--background-primary-alt);\n }\n\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]\n > .setting-item-info {\n display: grid;\n grid-template-columns: max-content minmax(0, 1fr);\n align-items: baseline;\n column-gap: var(--size-4-2);\n }\n\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]\n .setting-item-name {\n grid-column: 1;\n }\n\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]\n .smart-plugins-item-meta {\n grid-column: 2;\n margin: 0;\n }\n\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]\n .smart-plugins-item-description {\n grid-column: 1 / -1;\n margin-top: var(--size-2-1);\n overflow: hidden;\n text-overflow: ellipsis;\n white-space: nowrap;\n }\n\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]:hover {\n background: var(--background-modifier-hover);\n }\n }\n\n .smart-plugins-item-icon {\n display: inline-flex;\n flex: 0 0 auto;\n align-items: center;\n justify-content: center;\n width: 1.1em;\n height: 1.1em;\n margin-right: var(--size-2-2);\n color: var(--text-muted);\n\n svg {\n width: 100%;\n height: 100%;\n }\n }\n\n .smart-plugins-item-title {\n min-width: 0;\n }\n\n .smart-plugins-item-meta {\n margin-top: 0;\n color: var(--text-faint);\n font-size: var(--font-ui-smaller);\n line-height: var(--line-height-tight);\n }\n\n .smart-plugins-item-description {\n min-width: 0;\n margin-top: var(--size-2-1);\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n line-height: var(--line-height-normal);\n overflow-wrap: anywhere;\n\n > :first-child {\n margin-block-start: 0;\n }\n\n > :last-child {\n margin-block-end: 0;\n }\n\n > p {\n margin-block: 0;\n }\n }\n\n .smart-plugins-item-subscription-state {\n margin-top: var(--size-2-2);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n }\n\n .smart-plugins-list-item-controls {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n justify-content: flex-end;\n gap: var(--size-4-2);\n max-width: 100%;\n\n > button:not(.smart-plugins-menu-button) {\n white-space: nowrap;\n }\n }\n\n .smart-plugins-toggle-control {\n display: inline-flex;\n align-items: center;\n gap: var(--size-2-2);\n min-height: var(--input-height);\n padding-left: var(--size-4-2);\n border-left: 1px solid var(--background-modifier-border);\n }\n\n .smart-plugins-toggle-label {\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n white-space: nowrap;\n }\n\n .smart-plugins-toggle-control:first-child {\n padding-left: 0;\n border-left: 0;\n }\n\n .pro-plugins-list-item:is(\n [data-row-control-state="loaded"],\n [data-row-control-state="installed"],\n [data-row-control-state="update_available"],\n [data-row-control-state="deferred"],\n [data-row-control-state="outdated_env"]\n ) .smart-plugins-toggle-label {\n display: none;\n }\n\n .core-installed-text {\n display: inline-flex;\n align-items: center;\n gap: var(--size-2-1);\n min-height: var(--input-height);\n max-width: 100%;\n padding: 0 var(--size-4-2);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-s);\n background: var(--background-primary-alt);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n line-height: var(--line-height-tight);\n text-align: left;\n white-space: nowrap;\n }\n\n .pro-plugins-list-item:is(\n [data-row-control-state="loaded"],\n [data-row-control-state="installed"],\n [data-row-control-state="can_enable"],\n [data-row-control-state="update_available"]\n ) .core-installed-text {\n min-height: auto;\n padding: 0 var(--size-2-1);\n border-color: transparent;\n background: transparent;\n }\n\n .pro-plugins-list-item[data-row-control-state="loaded"]\n .core-installed-text::before {\n width: 6px;\n height: 6px;\n border-radius: 50%;\n background: var(--interactive-accent);\n content: "";\n flex: 0 0 auto;\n }\n\n .pro-plugins-list-item[data-row-control-state="deferred"]\n .core-installed-text,\n .pro-plugins-list-item[data-row-control-state="outdated_env"]\n .core-installed-text {\n border-color: var(--interactive-accent);\n background: var(--background-modifier-hover);\n color: var(--text-accent);\n }\n\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]\n .core-installed-text {\n min-height: auto;\n padding: 0;\n border-color: transparent;\n background: transparent;\n }\n\n .smart-plugins-list-item-controls > .smart-plugins-menu-button {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n width: var(--input-height);\n min-width: var(--input-height);\n height: var(--input-height);\n padding: 0;\n border: 0;\n border-radius: var(--radius-s);\n background: transparent;\n box-shadow: none;\n color: var(--text-muted);\n opacity: 0.78;\n\n &:hover {\n background: var(--background-modifier-hover);\n color: var(--text-normal);\n opacity: 1;\n }\n\n &:focus-visible {\n outline: 2px solid var(--interactive-accent);\n outline-offset: 2px;\n }\n\n svg {\n width: 16px;\n height: 16px;\n }\n }\n\n .smart-plugin-track-badge,\n .smart-badge.core-badge::after {\n display: inline-flex;\n align-items: center;\n justify-content: center;\n padding: 0.12em 0.55em;\n border: 1px solid var(--background-modifier-border);\n border-radius: 999px;\n box-shadow: none;\n font-family: inherit;\n font-size: 0.64em;\n font-weight: var(--font-semibold);\n letter-spacing: 0.08em;\n line-height: 1.15;\n text-transform: uppercase;\n transform: none;\n vertical-align: middle;\n white-space: nowrap;\n }\n\n .setting-item-name > .smart-plugin-track-badge,\n .smart-badge.core-badge::after {\n margin-left: var(--size-2-2);\n }\n\n .smart-plugin-track-badge {\n appearance: none;\n min-width: 0;\n min-height: 0;\n height: auto;\n cursor: pointer;\n text-decoration: none;\n }\n\n .smart-plugin-pro-badge {\n background: var(--interactive-accent);\n color: var(--text-on-accent);\n }\n\n .smart-plugin-pro-badge:hover {\n background: var(--interactive-accent-hover);\n color: var(--text-on-accent);\n }\n\n .smart-plugin-pro-badge:focus-visible,\n .smart-plugin-core-track-badge:focus-visible {\n outline: 2px solid var(--interactive-accent);\n outline-offset: 2px;\n }\n\n .smart-plugin-core-track-badge,\n .smart-badge.core-badge::after {\n background: var(--background-primary-alt);\n color: var(--text-muted);\n }\n\n .smart-plugin-core-track-badge:hover,\n .smart-badge.core-badge:hover::after {\n background: var(--background-modifier-hover);\n color: var(--text-normal);\n }\n\n .smart-badge.core-badge::after {\n content: "CORE";\n }\n\n .smart-plugins-track-guide-item > .smart-plugin-track-badge {\n margin-top: 0.1em;\n font-size: var(--font-ui-smaller);\n }\n}\n\n@media (max-width: 960px) {\n .smart-env-pro-plugins-modal .pro-plugins-container > .smart-plugins-store {\n .pro-plugins-list-item:not([data-row-control-state="included_in_pro"]) {\n align-items: flex-start;\n }\n\n .pro-plugins-list-item:not([data-row-control-state="included_in_pro"])\n > .setting-item-info,\n .pro-plugins-list-item:not([data-row-control-state="included_in_pro"])\n > .setting-item-control {\n flex-basis: 100%;\n }\n\n .pro-plugins-list-item:not([data-row-control-state="included_in_pro"])\n > .setting-item-control {\n width: 100%;\n margin-left: 0;\n }\n\n .pro-plugins-list-item:not([data-row-control-state="included_in_pro"])\n .smart-plugins-list-item-controls {\n justify-content: flex-end;\n }\n }\n}\n\n@media (max-width: 720px) {\n .smart-env-pro-plugins-modal .pro-plugins-container > .smart-plugins-store {\n gap: var(--size-4-3);\n\n .smart-plugins-intro {\n grid-template-columns: 1fr;\n gap: var(--size-4-2);\n }\n\n .smart-plugins-track-guide-item + .smart-plugins-track-guide-item {\n padding-top: var(--size-4-2);\n padding-left: 0;\n border-top: 1px solid var(--background-modifier-border);\n border-left: 0;\n }\n\n .smart-plugins-login .setting-item,\n .smart-plugins-item-group > .setting-items > .pro-plugins-list-item {\n align-items: flex-start;\n padding: var(--size-4-3);\n }\n\n .smart-plugins-login .setting-item-info,\n .smart-plugins-login .setting-item-control,\n .pro-plugins-list-item > .setting-item-info,\n .pro-plugins-list-item > .setting-item-control {\n flex-basis: 100%;\n }\n\n .smart-plugins-login .setting-item-control,\n .pro-plugins-list-item > .setting-item-control {\n justify-content: flex-start;\n margin-left: 0;\n }\n\n .smart-plugins-login-actions {\n width: 100%;\n align-items: flex-start;\n }\n\n .smart-plugins-login-manual {\n width: 100%;\n }\n\n .smart-plugins-list-item-controls {\n justify-content: flex-start;\n }\n\n .smart-plugins-toggle-control {\n padding-left: 0;\n border-left: 0;\n }\n\n .smart-plugins-item-group-combined\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]\n > .setting-item-info {\n grid-template-columns: minmax(0, 1fr);\n }\n\n .smart-plugins-item-group-combined\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]\n .setting-item-name,\n .smart-plugins-item-group-combined\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]\n .smart-plugins-item-meta,\n .smart-plugins-item-group-combined\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]\n .smart-plugins-item-description {\n grid-column: 1;\n }\n\n .smart-plugins-item-group-combined\n .pro-plugins-list-item[data-row-control-state="included_in_pro"]\n .smart-plugins-item-description {\n overflow: visible;\n text-overflow: clip;\n white-space: normal;\n }\n\n .smart-plugins-referral .setting-item {\n align-items: flex-start;\n gap: var(--size-4-2);\n }\n\n .smart-plugins-referral .setting-item-info,\n .smart-plugins-referral .setting-item-control {\n flex-basis: 100%;\n }\n\n .smart-plugins-referral .setting-item-control {\n margin-left: 0;\n }\n\n .core-installed-text {\n white-space: normal;\n }\n }\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 Essential functionality and a "just works" experience.
    Advanced configuration and features for Obsidian AI experts. Compare Smart Plugins.
    `; var PRO_PLUGINS_URL = "https://smartconnections.app/pro-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.`; 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", icon_name: "smart-connections", url: "https://smartconnections.app/smart-connections/" }, { item_type: "pro", item_name: "Connections Pro", plugin_id: "smart-connections", icon_name: "smart-connections", item_desc: "Find the notes that matter with configurable semantic models, filters, scoring, and ranking, then continue the result set in Context or Graph.", 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", icon_name: "smart-context-builder", url: "https://smartconnections.app/smart-context/" }, { item_type: "pro", item_name: "Context Pro", plugin_id: "smart-context", icon_name: "smart-context-builder", item_desc: "Choose exactly what AI should know by building reviewable context from notes, sections, Bases, media, and external sources, then copy or export it.", 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", icon_name: "smart-chat", item_repo: "brianpetro/smart-chatgpt-obsidian", url: "https://smartconnections.app/smart-chat/" }, { item_type: "pro", item_name: "Chat Pro", plugin_id: "smart-chat", icon_name: "smart-chat", item_desc: "Keep AI conversations attached to the notes they belong to with universal codeblocks for supported provider threads and Smart Chat API Extension chats.", 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", icon_name: "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", icon_name: "layout-template", 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", icon_name: "link", 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...
    More from Smart Plugins
    `; } 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 handle_pro_badge_click = (event) => { const badge = event.target?.closest?.("[data-smart-plugins-pro-badge]"); if (!badge || !container.contains(badge)) return; const source = badge.getAttribute("data-source"); window.open(build_pro_plugins_url(source), "_external"); }; container.addEventListener("click", handle_pro_badge_click); 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_obsidian27.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_obsidian27.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 { 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(() => container.removeEventListener("click", handle_pro_badge_click)); 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("smart_plugins:store_refresh", 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 build_pro_plugins_url(source = "") { const safe_source = String(source || "").trim() || "plugin-store-pro-badge"; return `${PRO_PLUGINS_URL}?utm_source=${encodeURIComponent(safe_source)}`; } 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_obsidian27.requestUrl)({ url: `https://api.github.com/repos/${repo}/releases/latest`, method: "GET", headers: { "Content-Type": "application/json" }, contentType: "application/json" }); const version4 = normalize_release_version( release?.tag_name || release?.name || item?.version || "" ); if (version4) { item.version = version4; } const last_updated = Date.parse( release?.published_at || release?.created_at || release?.updated_at || "" ); if (Number.isFinite(last_updated) && last_updated > 0) { item.last_updated = last_updated; } } 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 pending_install() { return this.env?.pending_plugin_installs?.[this.plugin_id] || null; } get pending_disable() { return this.env?.pending_plugin_disables?.[this.plugin_id] === true; } 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() { const pending_item_type = get_plugin_item_type(this.pending_install); if (pending_item_type === "core" || pending_item_type === "pro") { return pending_item_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 normalize_release_version(this.pending_install?.version) || 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.pending_disable ? false : this.should_update, has_outdated_env_compatibility: this.pending_disable ? false : this.has_outdated_env_compatibility, is_deferred: this.is_deferred || this.pending_disable, is_loaded: this.pending_disable ? false : this.is_loaded, is_enabled: this.is_enabled || this.pending_disable }); 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, track_state.item_type) }; }; 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, item_type = this.installed_type) { const toggle_value = this.installed_type === item_type && this.is_enabled; const track_version = normalize_release_version( this.get_track_plugin(item_type)?.version ); const installed_version = normalize_release_version(this.installed_version); const enabled_toggle = { type: "toggle", item_type, value: toggle_value, text: "Enabled" }; const installed_status_text = this.is_loaded ? "Active" : this.is_enabled ? "Enabled" : "Installed"; switch (control_state) { case "deferred": { let reload_text = installed_version && installed_version !== "unknown" ? `Reload required to activate v${installed_version}` : "Reload required to activate"; if (this.pending_disable) reload_text = "Reload required to disable"; return [ { type: "button", action: "restart_obsidian", text: reload_text, variant: "primary" }, enabled_toggle ]; } case "update_available": return [ { type: "status", text: installed_status_text }, { type: "button", action: "install", text: track_version && track_version !== "unknown" ? `Update to v${track_version}` : "Update", variant: "primary" }, enabled_toggle ]; case "outdated_env": return [ { type: "button", action: "restart_obsidian", text: "Reload required for Smart Environment", variant: "primary" }, enabled_toggle ]; case "loaded": return [ { type: "status", text: "Active" }, { type: "button", action: "open_settings", text: "Open settings", variant: "secondary" }, enabled_toggle ]; case "installed": return [ { type: "status", text: "Enabled" }, enabled_toggle ]; case "can_enable": return [ { type: "status", text: "Installed" }, { type: "toggle", item_type, value: false, text: "Enable" } ]; case "included_in_pro": return [ { type: "status", text: "Included in Pro" } ]; case "core_installed": return this.is_entitled ? [ { type: "status", text: "Core installed" }, { type: "toggle", item_type, value: false, text: "Install Pro" } ] : [ { type: "status", text: "Requires Pro" }, { type: "toggle", item_type, value: false, text: "Install Pro", disabled: true } ]; case "can_install_core_only": return [ { type: "toggle", item_type, value: false, text: "Install Core" } ]; case "can_install_pro": return [ { type: "toggle", item_type, value: false, text: "Install Pro" } ]; case "cant_install": return [ { type: "status", text: "Requires Pro" }, { type: "toggle", item_type, value: false, text: "Install Pro", disabled: true } ]; case "can_install": default: return [ { type: "toggle", item_type, value: false, text: "Install" } ]; } } 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_icon_name(item_type) { return get_plugin_icon_name(this.get_track_plugin(item_type)); } get_track_meta_text(item_type) { const plugin = this.get_track_plugin(item_type); const meta = []; const version4 = normalize_release_version(plugin?.version); const current_version = this.installed_type === item_type ? normalize_release_version(this.loaded_version || this.installed_manifest?.version) : ""; const last_updated = normalize_positive_epoch_ms(plugin?.last_updated); const one_month_ago = Date.now() - 30 * 24 * 60 * 60 * 1e3; if (version4 && version4 !== "unknown") { const version_difference = current_version ? compare_versions(version4, current_version) : 0; if (current_version && version_difference !== 0) { meta.push(`Current v${current_version}`); meta.push(`${version_difference > 0 ? "Available" : "Store"} v${version4}`); } else { meta.push(`v${version4}`); } } if (last_updated && last_updated >= one_month_ago) { meta.push(`Updated ${convert_to_time_ago(last_updated)}`); } return meta.join(" - "); } get_track_link_items(item_type) { const plugin = this.get_track_plugin(item_type) || this.primary_plugin; const links = []; const seen_urls = /* @__PURE__ */ new Set(); const add_link = (title, icon, url) => { const safe_url = String(url || "").trim(); if (!safe_url || seen_urls.has(safe_url)) return; seen_urls.add(safe_url); links.push({ title, icon, url: safe_url }); }; const main_url = get_plugin_main_url(plugin); const plugin_id = String(plugin?.plugin_id || "").trim(); const release_page_url = this.installed_type === item_type ? build_plugin_release_page_url(plugin, this.installed_version) : ""; add_link("Release notes", "file-text", release_page_url); add_link("Getting started", "rocket", build_getting_started_url(main_url)); if (item_type === "core" && plugin_id) { add_link( "Obsidian plugin listing", "obsidian", `https://community.obsidian.md/plugins/${encodeURIComponent(plugin_id)}` ); } add_link("Details", "info", plugin?.details_url); add_link("Documentation", "book-open", plugin?.docs_url); add_link("Learn more", "help-circle", plugin?.info_url); if (item_type !== "core") { add_link("Plugin page", "external-link", main_url); } return links; } 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"; } mark_install_deferred(plugin, version4 = plugin?.version) { if (!this.env.pending_plugin_installs) { this.env.pending_plugin_installs = {}; } this.env.pending_plugin_installs[this.plugin_id] = { item_type: get_plugin_item_type(plugin), version: normalize_release_version(version4) }; this.env.plugin_states[this.plugin_id] = "deferred"; } mark_disable_deferred() { if (!this.env.pending_plugin_disables) { this.env.pending_plugin_disables = {}; } this.env.pending_plugin_disables[this.plugin_id] = true; } clear_pending_disable() { if (!this.env.pending_plugin_disables) return; delete this.env.pending_plugin_disables[this.plugin_id]; } async handle_toggle(item_type, should_enable, params = {}) { if (should_enable) { if (this.installed_type === item_type) { return await this.enable(params); } await this.handle_track_action(item_type, "install", params); return this.installed_type === item_type && this.is_enabled; } if (this.installed_type !== item_type) return false; return await this.disable(item_type); } 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 { const loaded_plugin = this.app.plugins.plugins[this.plugin_id]; if (this.pending_disable && loaded_plugin) { this.app.plugins.enabledPlugins.add(this.plugin_id); this.app.plugins.requestSaveConfig(); } else { await enable_plugin(this.app, this.plugin_id); } this.clear_pending_disable(); this.env?.events?.emit?.("pro_plugins:enabled", { level: "debug", message: `${this.label} enabled.`, event_source: "browse_smart_plugins.list_item" }); this.env?.events?.emit?.("smart_plugins:store_refresh", { event_source: "browse_smart_plugins.list_item.enable" }); return true; } 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" }); return false; } } async disable(item_type = this.installed_type) { const plugin = this.get_track_plugin(item_type) || this.installed_plugin; const plugin_label = this.get_plugin_action_label(plugin); try { await disable_plugin(this.app, this.plugin_id); this.mark_disable_deferred(); this.env?.events?.emit?.("smart_plugins:disable_completed", { level: "attention", message: `${plugin_label} will be disabled after reloading Obsidian.`, btn_text: "Reload Obsidian", btn_callback: "app:reload", event_source: "browse_smart_plugins.list_item.disable" }); this.env?.events?.emit?.("smart_plugins:store_refresh", { event_source: "browse_smart_plugins.list_item.disable" }); return true; } catch (err) { console.error("[smart-plugins:list] Disable error:", err); this.env?.events?.emit?.("smart_plugins:disable_failed", { level: "error", message: `Disable failed: ${err.message}`, details: err?.stack || "", event_source: "browse_smart_plugins.list_item.disable" }); return false; } } 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"); } open_track_link_url(item_type, url, 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.menu" }); } window.open(with_utm_source(url, "plugin-store-menu"), "_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 version4 = release?.tag_name; await this.app.plugins.installPlugin(get_plugin_repo(plugin), version4, { 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?.("smart_plugins:store_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 version4 = 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: version4 }); files.push(build_plugin_file_record(file_name, response)); } return files; } async install_plugin(params = {}, plugin) { const was_installed = this.is_installed; const was_enabled = this.is_enabled; const should_defer_activation = was_installed && was_enabled; 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); if (should_defer_activation) { this.mark_install_deferred(plugin); } else { 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: should_defer_activation ? "attention" : "debug", message: should_defer_activation ? `${plugin_label} updated. Reload Obsidian to activate the new version.` : `${plugin_label} installed successfully.`, ...should_defer_activation ? { btn_text: "Reload Obsidian", btn_callback: "app:reload" } : {}, 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?.("smart_plugins:store_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 was_enabled = this.is_enabled; const should_defer_activation = was_installed && was_enabled; 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`) ]); if (should_defer_activation) { this.mark_install_deferred(plugin, release?.tag_name); } else { 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: should_defer_activation ? "attention" : "debug", message: should_defer_activation ? `${plugin_label} updated. Reload Obsidian to activate the new version.` : `${plugin_label} installed successfully.`, ...should_defer_activation ? { btn_text: "Reload Obsidian", btn_callback: "app:reload" } : {}, event_source: "browse_smart_plugins.list_item.install_github_release_plugin" }); env?.events?.emit?.("smart_plugins:store_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_obsidian27.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_obsidian27.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_icon_name(plugin = {}) { return String(plugin?.icon_name || "").trim(); } function get_plugin_main_url(plugin = {}) { return String(plugin?.main_url || plugin?.url || "").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 = {}, version4 = "") { const main_url = String(plugin?.main_url || plugin?.url || "").trim(); const version_slug = get_release_page_slug(version4); 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(version4 = "") { const version_pcs = normalize_release_version(version4).split("."); const major = String(version_pcs[0] || "").trim(); const minor = String(version_pcs[1] || "").trim(); if (!major || !minor) return ""; return `${major}-${minor}`; } function build_getting_started_url(url = "") { const base_url = String(url || "").split("#")[0].split("?")[0].replace(/\/+$/, ""); return base_url ? `${base_url}/getting-started/` : ""; } 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_obsidian28 = 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, icon_name: item.get_track_icon_name(item.display_item_type), meta_text: item.get_track_meta_text(item.display_item_type), 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"), icon_name: item.get_track_icon_name("core"), meta_text: item.get_track_meta_text("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"), icon_name: item.get_track_icon_name("pro"), meta_text: item.get_track_meta_text("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 icon_html = row.icon_name ? `` : ""; const meta_html = row.meta_text ? `
    ${row.meta_text}
    ` : ""; 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}"` : ""; const title_class = item_type === "core" ? "smart-badge core-badge" : ""; const pro_badge_label = `Learn about ${row.name || item.formatted_name || "this plugin"} Pro`; const pro_badge_html = item_type === "pro" ? `` : ""; return `
    ${icon_html}${row.name || ""}${pro_badge_html}
    ${meta_html}
    ${subscription_state_html}
    `; } function get_pro_badge_source(item) { const plugin_id = String(item?.plugin_id || "").trim(); return plugin_id ? `plugin-store-${plugin_id}` : "plugin-store-listing"; } 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 item_type = track_item_type || row.getAttribute("data-item-type") || item.display_item_type; const icon_el = row.querySelector(".smart-plugins-item-icon"); const icon_name = icon_el?.getAttribute("data-icon-name") || ""; if (icon_el && icon_name) { (0, import_obsidian28.setIcon)(icon_el, icon_name); } 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, item_type, 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_obsidian28.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 item_type = String(params.item_type || params.track_item_type || item.display_item_type).trim(); const control_specs = params.track_item_type ? item.get_track_control_specs(params.track_item_type) : item.control_specs; const link_items = item.get_track_link_items(item_type); const track_name = item.get_track_name(item_type); const menu_label = `Open ${track_name || "plugin"} links`; const menu_button_html = link_items.length ? `` : ""; return `
    ${control_specs.map(build_control_html).join("")}${menu_button_html}
    `; } 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 item_type = String(params.item_type || params.track_item_type || item.display_item_type).trim(); const link_items = item.get_track_link_items(item_type); const menu_button = container.querySelector(".smart-plugins-menu-button"); if (menu_button) { (0, import_obsidian28.setIcon)(menu_button, "menu"); menu_button.addEventListener("click", (event) => { const menu = new import_obsidian28.Menu(item.app); link_items.forEach((link_item) => { menu.addItem((menu_item) => { menu_item.setTitle(link_item.title).setIcon(link_item.icon).onClick(() => { item.open_track_link_url(item_type, link_item.url, params); }); }); }); menu.showAtMouseEvent(event); }); } const toggle_controls = container.querySelectorAll('[data-control-type="toggle"]'); for (const toggle_control of toggle_controls) { const toggle_host = toggle_control.querySelector(".smart-plugins-toggle-host"); if (!toggle_host) continue; const toggle_item_type = toggle_control.getAttribute("data-item-type") || item_type; const toggle_value = toggle_control.getAttribute("data-value") === "true"; const toggle_disabled = toggle_control.getAttribute("data-disabled") === "true"; const toggle_label = toggle_control.getAttribute("data-label") || "Enable plugin"; const toggle = new import_obsidian28.ToggleComponent(toggle_host).setValue(toggle_value).setDisabled(toggle_disabled); toggle.toggleEl?.setAttribute?.("aria-label", toggle_label); toggle.setTooltip?.(toggle_label); toggle.onChange(async (next_value) => { toggle.setDisabled(true); await item.handle_toggle(toggle_item_type, next_value, params); toggle.setValue(item.installed_type === toggle_item_type && item.is_enabled); toggle.setDisabled(toggle_disabled); }); } 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 = params.track_item_type ? item.get_busy_text_for_track(params.track_item_type, action) : item.get_busy_text(action); if (busy_text) { await run_busy_action(button, async () => { if (params.track_item_type) { await item.handle_track_action(params.track_item_type, action, params); return; } await item.handle_action(action, params); }, busy_text); return; } if (params.track_item_type) { await item.handle_track_action(params.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}`; } if (control_spec.type === "toggle") { return `
    ${control_spec.text || ""}
    `; } const class_name = control_spec.variant === "primary" ? "mod-cta" : ""; const title_attr = control_spec.title ? ` title="${control_spec.title}" aria-label="${control_spec.title}"` : ""; 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_obsidian29 = 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/components/smart-plugins/login.js 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 last_login_url = ""; let manual_login_el = null; let manual_login_parent = container; 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"); manual_login_parent.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_obsidian29.Setting(container).setName("Checking session...").setDesc("Validating your Smart Plugins account session."); return container; } if (auth_state === "invalid") { const setting2 = new import_obsidian29.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?.("smart_plugins:store_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_obsidian29.Setting(container).setName("Connect account").setDesc("Log in with the key provided in your Pro welcome email."); setting2.controlEl.classList.add("smart-plugins-login-actions"); manual_login_parent = setting2.controlEl; setting2.addButton((btn) => { btn.setButtonText("Login"); btn.onClick(() => { last_login_url = initiate_smart_plugins_oauth(); if (manual_login_el?.isConnected) { 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" }); }); }); const alternative = document.createElement("div"); alternative.classList.add("smart-plugins-login-alternative"); const copy_link = document.createElement("a"); copy_link.href = "#"; copy_link.textContent = "Copy link to login instead"; copy_link.addEventListener("click", (event) => { event.preventDefault(); last_login_url = build_smart_plugins_oauth_url(); render_manual_login_link(last_login_url); }); alternative.appendChild(copy_link); setting2.controlEl.appendChild(alternative); if (import_obsidian29.Platform.isLinux) { const linux_note = document.createElement("div"); linux_note.classList.add("smart-plugins-login-linux-note"); linux_note.append("Linux installations may require "); const protocol_link = document.createElement("a"); protocol_link.href = "https://obsidian.md/help/uri#Register+Obsidian+URI"; protocol_link.target = "_external"; protocol_link.textContent = "registering Obsidian URI"; linux_note.appendChild(protocol_link); linux_note.append(" to login."); setting2.descEl.appendChild(linux_note); } return container; } if (sub_exp && sub_exp < Date.now()) { const setting2 = new import_obsidian29.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?.("smart_plugins:store_refresh", { event_source: "smart_plugins_login" }); }); }); return container; } const setting = new import_obsidian29.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 build_smart_plugins_oauth_url() { const state = Math.random().toString(36).slice(2); const redirect_uri = encodeURIComponent("obsidian://smart-plugins/callback"); return `${get_smart_server_url()}/oauth?client_id=smart-plugins-op&redirect_uri=${redirect_uri}&state=${state}`; } function initiate_smart_plugins_oauth() { const url = build_smart_plugins_oauth_url(); 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_obsidian30 = 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_obsidian30.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_obsidian30.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_obsidian30.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 = '.smart-source-inspector-modal {\n --source-inspector-gap: var(--size-4-4);\n\n width: min(1040px, 94vw);\n max-width: 1040px;\n max-height: 92vh;\n}\n\n.smart-source-inspector-modal .modal-content {\n padding-top: var(--size-4-2);\n}\n\n.source-inspector {\n display: flex;\n flex-direction: column;\n gap: var(--source-inspector-gap, var(--size-4-4));\n min-width: 0;\n padding-bottom: var(--size-4-4);\n}\n\n.source-inspector button,\n.source-inspector input {\n font: inherit;\n}\n\n.source-inspector__header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--size-4-4);\n padding: var(--size-4-4);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-l);\n background: var(--background-secondary);\n}\n\n.source-inspector__heading {\n min-width: 0;\n}\n\n.source-inspector__eyebrow {\n margin-bottom: var(--size-4-1);\n color: var(--text-accent);\n font-size: var(--font-ui-smaller);\n font-weight: var(--font-semibold);\n letter-spacing: 0.08em;\n text-transform: uppercase;\n}\n\n.source-inspector__heading h2,\n.source-inspector__section-heading h3,\n.source-inspector__block h4 {\n margin: 0;\n}\n\n.source-inspector__path {\n margin: var(--size-4-1) 0 0;\n overflow: hidden;\n color: var(--text-muted);\n font-family: var(--font-monospace);\n font-size: var(--font-ui-small);\n text-overflow: ellipsis;\n white-space: nowrap;\n}\n\n.source-inspector__source-status {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--size-4-2);\n margin-top: var(--size-4-3);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n}\n\n.source-inspector__header-actions,\n.source-inspector__block-actions,\n.source-inspector__source-data-toolbar {\n display: flex;\n flex-wrap: wrap;\n align-items: center;\n gap: var(--size-4-2);\n}\n\n.source-inspector__summary {\n display: grid;\n grid-template-columns: repeat(4, minmax(0, 1fr));\n gap: var(--size-4-3);\n}\n\n.source-inspector__metric {\n min-width: 0;\n padding: var(--size-4-3);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-m);\n background: var(--background-primary-alt);\n}\n\n.source-inspector__metric-label {\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n font-weight: var(--font-semibold);\n text-transform: uppercase;\n}\n\n.source-inspector__metric-value {\n margin-top: var(--size-4-1);\n overflow: hidden;\n color: var(--text-normal);\n font-size: 1.45em;\n font-variant-numeric: tabular-nums;\n font-weight: var(--font-bold);\n line-height: 1.15;\n text-overflow: ellipsis;\n}\n\n.source-inspector__metric-detail {\n min-height: 1.35em;\n margin-top: var(--size-4-1);\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n}\n\n.source-inspector__badge {\n display: inline-flex;\n align-items: center;\n min-height: 24px;\n padding: 0 var(--size-4-2);\n border: 1px solid var(--background-modifier-border);\n border-radius: 999px;\n background: var(--background-primary-alt);\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n font-weight: var(--font-semibold);\n white-space: nowrap;\n}\n\n.source-inspector__badge[data-tone="embedded"] {\n border-color: var(--color-green);\n color: var(--color-green);\n}\n\n.source-inspector__badge[data-tone="missing"] {\n border-color: var(--text-error);\n color: var(--text-error);\n}\n\n.source-inspector__badge[data-tone="unexpected"] {\n border-color: var(--text-warning);\n color: var(--text-warning);\n}\n\n.source-inspector__badge[data-tone="loading"] {\n color: var(--text-accent);\n}\n\n.source-inspector__source-data {\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-m);\n background: var(--background-primary-alt);\n}\n\n.source-inspector__source-data > summary {\n display: flex;\n align-items: center;\n justify-content: space-between;\n gap: var(--size-4-3);\n padding: var(--size-4-3);\n cursor: pointer;\n font-weight: var(--font-semibold);\n}\n\n.source-inspector__source-data-hint {\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n font-weight: var(--font-normal);\n}\n\n.source-inspector__source-data-toolbar {\n justify-content: flex-end;\n padding: 0 var(--size-4-3) var(--size-4-2);\n}\n\n.source-inspector__source-data pre,\n.source-inspector__embed-input pre,\n.source-inspector__full-content pre {\n margin: 0;\n padding: var(--size-4-3);\n border-top: 1px solid var(--background-modifier-border);\n overflow: auto;\n background: var(--background-secondary);\n color: var(--text-normal);\n font-size: var(--font-ui-smaller);\n line-height: 1.45;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n.source-inspector__source-data pre {\n max-height: 360px;\n}\n\n.source-inspector__blocks-section {\n min-width: 0;\n}\n\n.source-inspector__section-heading {\n display: flex;\n align-items: flex-end;\n justify-content: space-between;\n gap: var(--size-4-3);\n margin-bottom: var(--size-4-3);\n}\n\n.source-inspector__section-heading p {\n margin: var(--size-4-1) 0 0;\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n}\n\n.source-inspector__timing {\n color: var(--text-muted);\n font-size: var(--font-ui-smaller);\n white-space: nowrap;\n}\n\n.source-inspector__toolbar {\n position: sticky;\n top: 0;\n z-index: 2;\n display: flex;\n flex-direction: column;\n gap: var(--size-4-2);\n margin: 0 calc(var(--size-4-2) * -1) var(--size-4-3);\n padding: var(--size-4-2);\n border: 1px solid var(--background-modifier-border);\n border-radius: var(--radius-m);\n background: var(--background-primary);\n box-shadow: var(--shadow-s);\n}\n\n.source-inspector__search {\n display: grid;\n grid-template-columns: auto minmax(160px, 1fr);\n align-items: center;\n gap: var(--size-4-2);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n font-weight: var(--font-semibold);\n}\n\n.source-inspector__search input {\n width: 100%;\n}\n\n.source-inspector__filters {\n display: flex;\n flex-wrap: wrap;\n gap: var(--size-4-1);\n}\n\n.source-inspector__filters button {\n display: inline-flex;\n align-items: center;\n gap: var(--size-4-1);\n min-height: 30px;\n padding: 0 var(--size-4-2);\n border-radius: 999px;\n color: var(--text-muted);\n}\n\n.source-inspector__filters button.is-active {\n border-color: var(--interactive-accent);\n background: var(--interactive-accent);\n color: var(--text-on-accent);\n}\n\n.source-inspector__filter-count {\n min-width: 1.3em;\n font-variant-numeric: tabular-nums;\n text-align: center;\n}\n\n.source-inspector__results {\n min-height: 1.4em;\n margin-bottom: var(--size-4-2);\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n}\n\n.source-inspector__blocks-container {\n display: flex;\n flex-direction: column;\n gap: var(--size-4-3);\n min-width: 0;\n}\n\n.source-inspector__block {\n min-width: 0;\n overflow: hidden;\n border: 1px solid var(--background-modifier-border);\n border-left-width: 3px;\n border-radius: var(--radius-m);\n background: var(--background-primary-alt);\n}\n\n.source-inspector__block[data-tone="embedded"] {\n border-left-color: var(--color-green);\n}\n\n.source-inspector__block[data-tone="missing"] {\n border-left-color: var(--text-error);\n}\n\n.source-inspector__block[data-tone="unexpected"] {\n border-left-color: var(--text-warning);\n}\n\n.source-inspector__block[data-tone="skipped"] {\n border-left-color: var(--text-faint);\n}\n\n.source-inspector__block-header {\n display: flex;\n align-items: flex-start;\n justify-content: space-between;\n gap: var(--size-4-3);\n padding: var(--size-4-3);\n border-bottom: 1px solid var(--background-modifier-border);\n}\n\n.source-inspector__block-identity {\n min-width: 0;\n}\n\n.source-inspector__block h4 {\n overflow-wrap: anywhere;\n font-size: var(--font-ui-medium);\n}\n\n.source-inspector__block-metadata {\n margin-top: var(--size-4-1);\n color: var(--text-muted);\n font-family: var(--font-monospace);\n font-size: var(--font-ui-smaller);\n}\n\n.source-inspector__block-content {\n max-height: 300px;\n margin: 0;\n padding: var(--size-4-3);\n overflow: auto;\n background: var(--background-primary);\n color: var(--text-normal);\n font-family: var(--font-monospace);\n font-size: var(--font-ui-small);\n line-height: 1.5;\n tab-size: 2;\n white-space: pre-wrap;\n overflow-wrap: anywhere;\n}\n\n.source-inspector__embed-input,\n.source-inspector__full-content {\n border-top: 1px solid var(--background-modifier-border);\n}\n\n.source-inspector__embed-input summary,\n.source-inspector__full-content summary {\n padding: var(--size-4-2) var(--size-4-3);\n cursor: pointer;\n color: var(--text-muted);\n font-size: var(--font-ui-small);\n font-weight: var(--font-semibold);\n}\n\n.source-inspector__embed-input pre {\n max-height: 300px;\n}\n\n.source-inspector__full-content pre {\n max-height: 420px;\n}\n\n.source-inspector__loading,\n.source-inspector__empty,\n.source-inspector__error {\n display: flex;\n align-items: center;\n justify-content: center;\n gap: var(--size-4-2);\n min-height: 120px;\n padding: var(--size-4-4);\n border: 1px dashed var(--background-modifier-border);\n border-radius: var(--radius-m);\n color: var(--text-muted);\n text-align: center;\n}\n\n.source-inspector__error {\n color: var(--text-error);\n}\n\n.source-inspector__spinner {\n width: 16px;\n height: 16px;\n border: 2px solid var(--background-modifier-border);\n border-top-color: var(--interactive-accent);\n border-radius: 50%;\n animation: source-inspector-spin 0.8s linear infinite;\n}\n\n.source-inspector__load-more {\n display: block;\n margin: var(--size-4-3) auto 0;\n}\n\n@keyframes source-inspector-spin {\n to {\n transform: rotate(360deg);\n }\n}\n\n@media (max-width: 720px) {\n .source-inspector__header,\n .source-inspector__section-heading,\n .source-inspector__block-header {\n align-items: stretch;\n flex-direction: column;\n }\n\n .source-inspector__header-actions,\n .source-inspector__block-actions {\n justify-content: flex-start;\n }\n\n .source-inspector__summary {\n grid-template-columns: repeat(2, minmax(0, 1fr));\n }\n\n .source-inspector__search {\n grid-template-columns: 1fr;\n }\n\n .source-inspector__timing {\n white-space: normal;\n }\n}\n\n@media (prefers-reduced-motion: reduce) {\n .source-inspector__spinner {\n animation: none;\n }\n}\n'; // node_modules/obsidian-smart-env/src/actions/env/inspect_active_note.js var import_obsidian31 = require("obsidian"); var DEFAULT_YIELD_AFTER_MS2 = 10; var YIELD_CHECK_INTERVAL2 = 64; function env_inspect_active_note() { const plugin = this.main; const active_file = plugin?.app?.workspace?.getActiveFile?.(); if (!active_file) { this?.events?.emit?.("status_bar:inspect_active_note_missing", { level: "warning", message: "No active note found", event_source: "env_inspect_active_note" }); return false; } const src = this.smart_sources?.get?.(active_file.path); if (!src) { this?.events?.emit?.("status_bar:inspect_source_missing", { level: "warning", message: "Active note is not indexed by Smart Environment", event_source: "env_inspect_active_note" }); return false; } new SmartNoteInspectModal(plugin, src).open(); return true; } var menus2 = { "env:status_bar_menu": { title: "Inspect active note", icon: "search", order: 10 } }; var SmartNoteInspectModal = class extends import_obsidian31.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.setText("Source inspector"); this.modalEl?.classList?.add("smart-source-inspector-modal"); this.render(); } onClose() { this.modalEl?.classList?.remove("smart-source-inspector-modal"); this.contentEl.empty(); } async render() { this.contentEl.empty(); const loading_el = this.contentEl.createEl("p", { cls: "smart-source-inspector-modal__loading", text: "Opening source inspector..." }); loading_el.setAttribute("aria-live", "polite"); try { const component = await this.env.smart_components.render_component( "source_inspector", this.entity ); this.contentEl.empty(); if (component) { this.contentEl.appendChild(component); return; } this.contentEl.createEl("p", { text: "Failed to load source inspector." }); } catch (error) { console.error("[source_inspector] Failed to render source inspector modal", error); this.contentEl.empty(); this.contentEl.createEl("p", { text: "Failed to load source inspector. See the developer console for details." }); } } }; async function force_re_import_source(source) { const source_collection = source?.collection || source?.env?.smart_sources; if (!source?.key) { throw new Error("Cannot re-import a source without a key"); } if (typeof source_collection?.queue_source_re_import !== "function" || typeof source_collection?.run_re_import !== "function") { throw new Error("Source re-import is unavailable"); } source_collection.queue_source_re_import(source, { event_source: "source_inspector.force_re_import" }); await source_collection.run_re_import(); if (source._queue_import) { throw new Error("Source re-import did not complete and remains queued"); } } async function load_source_inspector_records(source, params = {}) { const { on_progress, is_cancelled, yield_after_ms = DEFAULT_YIELD_AFTER_MS2 } = params; const started_at = now_ms3(); const previous_source_hash = get_item_read_hash(source); const raw_source_content = await source?.read?.(); const source_content = typeof raw_source_content === "string" ? raw_source_content : ""; const current_source_hash = get_item_read_hash(source); const source_content_changed = !previous_source_hash || !current_source_hash || previous_source_hash !== current_source_hash; const source_lines = source_content ? source_content.split("\n") : []; const source_blocks = source?.blocks; const blocks = Array.isArray(source_blocks) ? source_blocks.slice().sort(sort_blocks_by_line) : []; const file_info_by_embeddings = /* @__PURE__ */ new Map(); const records = []; const summary = create_summary(blocks.length); let yielded_at = now_ms3(); for (let block_i = 0; block_i < blocks.length; block_i += 1) { if (is_cancelled?.()) break; const block = blocks[block_i]; const should_embed = get_should_embed2(block); const line_start = normalize_line_number(block?.line_start, 1); const line_end = Math.max( line_start, normalize_line_number(block?.line_end, line_start) ); const persisted_size = Number(block?.size || 0); let current_size = Number.isFinite(persisted_size) ? persisted_size : 0; let read_hash_override; if (source_content_changed) { const current_content = extract_block_content(source_lines, { line_start, line_end }); current_size = current_content.length; read_hash_override = create_block_read_hash(block, current_content); } const vectorized = get_item_vectorized( block, file_info_by_embeddings, read_hash_override ); const status_key = get_embedding_status_key({ should_embed, vectorized }); const record = { block, key: block?.key || "", display_name: get_block_display_name3(block), line_start, line_end, size: current_size, content: null, content_loaded: false, search_text: null, should_embed, vectorized, status_key, embed_input: null, embed_input_loaded: false, embed_input_loading: false, embed_input_promise: null, embed_input_error: "" }; records.push(record); summary.processed += 1; summary[status_key] += 1; if (should_embed) summary.should_embed += 1; else summary.should_not_embed += 1; if (vectorized) summary.vectorized += 1; if ((block_i + 1) % YIELD_CHECK_INTERVAL2 !== 0) continue; const current_time = now_ms3(); if (current_time - yielded_at < yield_after_ms) continue; on_progress?.({ processed: summary.processed, total: summary.total }); await yield_to_main_thread(); yielded_at = now_ms3(); } const source_should_embed = get_should_embed2(source); const source_vectorized = get_item_vectorized(source, file_info_by_embeddings); return { source_content, source_lines, source_content_changed, line_count: source_lines.length, char_count: source_content.length, source_status: { should_embed: source_should_embed, vectorized: source_vectorized, status_key: get_embedding_status_key({ should_embed: source_should_embed, vectorized: source_vectorized }) }, records, summary, load_time_ms: Math.max(0, Math.round(now_ms3() - started_at)), cancelled: Boolean(is_cancelled?.()) }; } function extract_block_content(source_lines = [], block = {}) { if (!Array.isArray(source_lines) || !source_lines.length) return ""; const line_start = normalize_line_number(block.line_start, 1); const line_end = Math.max( line_start, normalize_line_number(block.line_end, line_start) ); return source_lines.slice(line_start - 1, line_end).join("\n"); } function materialize_block_content(record, source_lines = []) { if (record?.content_loaded) return record.content || ""; if (!record) return ""; const content = extract_block_content(source_lines, record); record.content = content; record.content_loaded = true; record.size = content.length; return content; } function get_block_display_name3(block = {}) { const sub_key = String(block.sub_key || ""); const block_key = String(block.key || ""); const display_key = sub_key || block_key.split("#").slice(1).join("#"); const segments = display_key.split("#").map((segment) => segment.trim()).filter(Boolean); return segments.length ? segments.join(" > ") : "Root block"; } function get_embedding_status_key({ should_embed, vectorized }) { if (should_embed && vectorized) return "embedded"; if (should_embed && !vectorized) return "missing"; if (!should_embed && vectorized) return "unexpected"; return "skipped"; } function create_summary(total = 0) { return { total, processed: 0, should_embed: 0, should_not_embed: 0, vectorized: 0, embedded: 0, missing: 0, skipped: 0, unexpected: 0 }; } function get_item_vectorized(item, file_info_by_embeddings, read_hash_override) { const embeddings = item?.collection?.embeddings; if (!embeddings) return false; if (!file_info_by_embeddings.has(embeddings)) { file_info_by_embeddings.set( embeddings, get_embeddings_file_info(embeddings) ); } return has_current_embedding( item, embeddings, file_info_by_embeddings.get(embeddings), read_hash_override ); } function create_block_read_hash(block, content) { try { const hash = block?.block_adapter?.create_hash?.(content); return typeof hash === "string" && hash ? hash : void 0; } catch (error) { console.warn("[source_inspector] Failed to hash block content", block?.key, error); return void 0; } } function get_item_read_hash(item) { try { return item?.data?.last_read?.hash || ""; } catch { return ""; } } function get_should_embed2(item) { try { return Boolean(item?.should_embed); } catch (error) { console.warn("[source_inspector] Failed to evaluate should_embed", item?.key, error); return false; } } function sort_blocks_by_line(a, b) { const line_delta = Number(a?.line_start || 0) - Number(b?.line_start || 0); if (line_delta) return line_delta; return String(a?.key || "").localeCompare(String(b?.key || "")); } function normalize_line_number(value, fallback) { const line_number = Number(value); if (!Number.isFinite(line_number) || line_number < 1) return fallback; return Math.floor(line_number); } function now_ms3() { return globalThis.performance?.now?.() || Date.now(); } // node_modules/obsidian-smart-env/src/components/source_inspector.js var PAGE_SIZE = 30; var BLOCK_PREVIEW_CHAR_LIMIT = 4e3; var SEARCH_DEBOUNCE_MS = 120; var FILTER_YIELD_AFTER_MS = 10; var FILTER_YIELD_INTERVAL = 64; var number_formatter2 = new Intl.NumberFormat(); var STATUS_PRESENTATION = { embedded: { label: "Current", description: "Eligible and embedded for the current content." }, missing: { label: "Needs embedding", description: "Eligible, but no current embedding is available." }, skipped: { label: "Skipped", description: "Not currently eligible for embedding." }, unexpected: { label: "Unexpected vector", description: "Not eligible, but a current vector is still present." } }; function build_html21() { return `
    Source diagnostics

    Analyzing... Reading source once...
    ${build_metric_html2("blocks", "Blocks")} ${build_metric_html2("characters", "Characters")} ${build_metric_html2("lines", "Lines")} ${build_metric_html2("coverage", "Block coverage")}
    Source data Loaded only when expanded
    Expand to inspect persisted source metadata.

    Blocks

    Preparing block diagnostics...

    ${build_filter_button_html("all", "All")} ${build_filter_button_html("missing", "Needs embedding")} ${build_filter_button_html("embedded", "Current")} ${build_filter_button_html("skipped", "Skipped")} ${build_filter_button_html("unexpected", "Unexpected")}
    Loading blocks...
    ${build_loading_html("Reading source and indexing block metadata...")}
    `; } async function render23(source, opts = {}) { this.apply_style_sheet(source_inspector_default); const frag = this.create_doc_fragment(build_html21(source, opts)); const container = frag.firstElementChild; post_process21.call(this, source, container, opts); return container; } function post_process21(source, container, opts = {}) { if (!container) return container; const elements = get_elements(container); const state = { active_filter: "all", disposed: false, filtered_records: [], filter_id: 0, load_id: 0, load_result: null, query: "", search_timeout: null, start_timeout: null, visible_count: PAGE_SIZE }; const cleanup_fns = []; set_text3(elements.source_path, source?.path || source?.key || "Unknown source"); if (typeof source?.open !== "function") elements.open_source_btn.hidden = true; const refresh = async ({ force_re_import = false } = {}) => { const current_load_id = ++state.load_id; state.filter_id += 1; state.load_result = null; state.filtered_records = []; state.visible_count = PAGE_SIZE; const source_data_loading_text = force_re_import ? "Re-importing source data..." : "Refreshing source data..."; delete elements.source_data_pre.dataset.loaded; elements.source_data_pre.textContent = elements.source_data_details.open ? source_data_loading_text : "Expand to inspect persisted source metadata."; elements.source_status.dataset.tone = "loading"; set_text3(elements.source_status, force_re_import ? "Re-importing..." : "Analyzing..."); set_text3( elements.source_status_detail, force_re_import ? "Rebuilding source data before inspection..." : "Reading source once..." ); set_text3( elements.results_summary, force_re_import ? "Re-importing source..." : "Loading blocks..." ); elements.blocks_container.innerHTML = build_loading_html( force_re_import ? "Re-importing source and rebuilding indexed blocks..." : "Reading source and indexing block metadata..." ); elements.load_more_btn.hidden = true; set_loading_state(container, elements, true, { active_action: force_re_import ? "force_re_import" : "refresh" }); try { if (force_re_import) { await force_re_import_source(source); if (state.disposed || current_load_id !== state.load_id) return; set_text3(elements.source_status, "Analyzing..."); set_text3(elements.source_status_detail, "Reading freshly imported source once..."); set_text3(elements.results_summary, "Loading refreshed blocks..."); elements.blocks_container.innerHTML = build_loading_html( "Reading freshly imported source metadata..." ); } const result = await load_source_inspector_records(source, { is_cancelled: () => state.disposed || current_load_id !== state.load_id, on_progress: ({ processed, total }) => { if (state.disposed || current_load_id !== state.load_id) return; set_text3( elements.source_status_detail, `Analyzing block ${format_number2(processed)} of ${format_number2(total)}...` ); set_text3( elements.results_summary, `Analyzing ${format_number2(processed)} of ${format_number2(total)} blocks...` ); } }); if (state.disposed || current_load_id !== state.load_id || result.cancelled) { return; } state.load_result = result; render_source_summary(elements, result); render_filter_counts(elements.filters, result.summary); if (elements.source_data_details.open) render_source_data(source, elements); await apply_filters(container, elements, state); if (state.disposed || current_load_id !== state.load_id) return; set_loading_state(container, elements, false); } catch (error) { if (state.disposed || current_load_id !== state.load_id) return; console.error("[source_inspector] Failed to inspect source", error); render_error(elements, error); set_loading_state(container, elements, false); } }; const start_refresh = (params = {}) => { if (state.start_timeout) { clearTimeout(state.start_timeout); state.start_timeout = null; } refresh(params).catch((error) => { if (state.disposed) return; console.error("[source_inspector] Failed to refresh source inspector", error); render_error(elements, error); set_loading_state(container, elements, false); }); }; const handle_refresh = () => { start_refresh(); }; const handle_force_re_import = () => { start_refresh({ force_re_import: true }); }; const handle_open_source = () => { try { source?.open?.(); } catch (error) { console.error("[source_inspector] Failed to open source", error); set_text3(elements.source_status_detail, "Failed to open this note."); } }; const handle_search_input = (event) => { state.query = String(event.currentTarget?.value || "").trim().toLowerCase(); if (state.search_timeout) clearTimeout(state.search_timeout); state.search_timeout = setTimeout(() => { state.search_timeout = null; state.visible_count = PAGE_SIZE; apply_filters(container, elements, state).catch((error) => { if (state.disposed) return; console.error("[source_inspector] Failed to search blocks", error); render_error(elements, error); }); }, SEARCH_DEBOUNCE_MS); }; const handle_filter_click = (event) => { const button = event.target?.closest?.("[data-filter]"); if (!button || !elements.filters.contains(button)) return; state.active_filter = button.dataset.filter || "all"; state.visible_count = PAGE_SIZE; update_active_filter(elements.filters, state.active_filter); apply_filters(container, elements, state).catch((error) => { if (state.disposed) return; console.error("[source_inspector] Failed to filter blocks", error); render_error(elements, error); }); }; const handle_load_more = () => { state.visible_count += PAGE_SIZE; render_blocks(container, elements, state); }; const handle_source_data_toggle = () => { if (!elements.source_data_details.open) return; render_source_data(source, elements); }; const handle_copy_source_data = async () => { const json = get_source_data_json(source, elements); await copy_with_feedback( json, elements.copy_source_data_btn, source?.env, "Copy JSON" ); }; bind_event(elements.refresh_btn, "click", handle_refresh, cleanup_fns); bind_event(elements.force_re_import_btn, "click", handle_force_re_import, cleanup_fns); bind_event(elements.open_source_btn, "click", handle_open_source, cleanup_fns); bind_event(elements.search_input, "input", handle_search_input, cleanup_fns); bind_event(elements.filters, "click", handle_filter_click, cleanup_fns); bind_event(elements.load_more_btn, "click", handle_load_more, cleanup_fns); bind_event(elements.source_data_details, "toggle", handle_source_data_toggle, cleanup_fns); bind_event(elements.copy_source_data_btn, "click", handle_copy_source_data, cleanup_fns); state.start_timeout = setTimeout(() => { state.start_timeout = null; handle_refresh(); }, 0); this.attach_disposer?.(container, () => { state.disposed = true; state.load_id += 1; state.filter_id += 1; if (state.start_timeout) clearTimeout(state.start_timeout); if (state.search_timeout) clearTimeout(state.search_timeout); cleanup_fns.forEach((cleanup) => cleanup()); }); return container; } function get_elements(container) { return { blocks_container: container.querySelector("[data-blocks-container]"), blocks_detail: container.querySelector("[data-blocks-detail]"), copy_source_data_btn: container.querySelector('[data-action="copy-source-data"]'), filters: container.querySelector(".source-inspector__filters"), force_re_import_btn: container.querySelector('[data-action="force-re-import"]'), load_more_btn: container.querySelector('[data-action="load-more"]'), load_timing: container.querySelector("[data-load-timing]"), open_source_btn: container.querySelector('[data-action="open-source"]'), refresh_btn: container.querySelector('[data-action="refresh"]'), results_summary: container.querySelector("[data-results-summary]"), search_input: container.querySelector("[data-block-search]"), source_data_details: container.querySelector(".source-inspector__source-data"), source_data_pre: container.querySelector("[data-source-data]"), source_path: container.querySelector("[data-source-path]"), source_status: container.querySelector("[data-source-status]"), source_status_detail: container.querySelector("[data-source-status-detail]") }; } async function apply_filters(container, elements, state) { if (!state.load_result) return; const current_filter_id = ++state.filter_id; const records = state.load_result.records || []; const source_lines = state.load_result.source_lines || []; const query = state.query; const active_filter = state.active_filter; const filtered_records = []; let yielded_at = now_ms4(); set_text3( elements.results_summary, query ? "Searching block content..." : "Applying filter..." ); elements.load_more_btn.hidden = true; for (let record_i = 0; record_i < records.length; record_i += 1) { if (state.disposed || current_filter_id !== state.filter_id) return; const record = records[record_i]; const matches_filter = active_filter === "all" || record.status_key === active_filter; const matches_query = matches_filter && (!query || record_matches_query(record, source_lines, query)); if (matches_filter && matches_query) filtered_records.push(record); if ((record_i + 1) % FILTER_YIELD_INTERVAL !== 0) continue; const current_time = now_ms4(); if (current_time - yielded_at < FILTER_YIELD_AFTER_MS) continue; await yield_to_main_thread(); yielded_at = now_ms4(); } if (state.disposed || current_filter_id !== state.filter_id) return; state.filtered_records = filtered_records; render_blocks(container, elements, state); } function record_matches_query(record, source_lines, query) { if (!query) return true; if (!record.search_text) { const content = materialize_block_content(record, source_lines); record.search_text = `${record.display_name} ${record.key} ${content}`.toLowerCase(); } return record.search_text.includes(query); } function render_blocks(container, elements, state) { const records = state.filtered_records || []; const visible_records = records.slice(0, state.visible_count); elements.blocks_container.replaceChildren(); if (!visible_records.length) { const empty2 = container.ownerDocument.createElement("div"); empty2.className = "source-inspector__empty"; const total_count2 = Number(state.load_result?.summary?.total || 0); empty2.textContent = !total_count2 ? "This source has no indexed blocks." : state.query ? "No blocks match this search and filter." : "No blocks match this filter."; elements.blocks_container.appendChild(empty2); } else { const fragment = container.ownerDocument.createDocumentFragment(); visible_records.forEach((record) => { fragment.appendChild(create_block_card( container.ownerDocument, record, state.load_result.source_lines || [], state.load_result )); }); elements.blocks_container.appendChild(fragment); } const visible_count = Math.min(visible_records.length, records.length); const total_count = Number(state.load_result?.summary?.total || 0); const is_unfiltered = records.length === total_count && !state.query && state.active_filter === "all"; let results_text; if (is_unfiltered) { results_text = records.length > visible_count ? `Showing ${format_number2(visible_count)} of ${format_number2(total_count)} blocks` : `${format_number2(total_count)} blocks`; } else { results_text = records.length > visible_count ? `Showing ${format_number2(visible_count)} of ${format_number2(records.length)} matches (${format_number2(total_count)} total)` : `${format_number2(records.length)} matches (${format_number2(total_count)} total)`; } set_text3(elements.results_summary, results_text); elements.load_more_btn.hidden = visible_count >= records.length; if (!elements.load_more_btn.hidden) { const remaining = records.length - visible_count; elements.load_more_btn.textContent = `Show ${format_number2(Math.min(PAGE_SIZE, remaining))} more`; } } function create_block_card(document_ref, record, source_lines, load_result) { const presentation = STATUS_PRESENTATION[record.status_key] || STATUS_PRESENTATION.skipped; const content = materialize_block_content(record, source_lines); const content_is_truncated = content.length > BLOCK_PREVIEW_CHAR_LIMIT; const preview_content = content_is_truncated ? `${content.slice(0, BLOCK_PREVIEW_CHAR_LIMIT)} ... ${format_number2(content.length - BLOCK_PREVIEW_CHAR_LIMIT)} more characters` : content; const article = document_ref.createElement("article"); article.className = "source-inspector__block"; article.dataset.tone = record.status_key; const header = document_ref.createElement("header"); header.className = "source-inspector__block-header"; const identity = document_ref.createElement("div"); identity.className = "source-inspector__block-identity"; const title = document_ref.createElement("h4"); title.textContent = record.display_name; const metadata = document_ref.createElement("div"); metadata.className = "source-inspector__block-metadata"; metadata.textContent = `${format_number2(record.size)} chars / lines ${record.line_start}-${record.line_end}`; identity.append(title, metadata); const actions = document_ref.createElement("div"); actions.className = "source-inspector__block-actions"; const badge = document_ref.createElement("span"); badge.className = "source-inspector__badge"; badge.dataset.tone = record.status_key; badge.title = presentation.description; badge.textContent = presentation.label; const copy_btn = document_ref.createElement("button"); copy_btn.type = "button"; copy_btn.textContent = "Copy"; copy_btn.addEventListener("click", () => { copy_with_feedback( content, copy_btn, record.block?.env || record.block?.collection?.env, "Copy" ); }); actions.append(badge, copy_btn); header.append(identity, actions); const content_pre = document_ref.createElement("pre"); content_pre.className = "source-inspector__block-content"; content_pre.textContent = preview_content || "(Empty block)"; let full_content_details = null; if (content_is_truncated) { full_content_details = document_ref.createElement("details"); full_content_details.className = "source-inspector__full-content"; const full_content_summary = document_ref.createElement("summary"); full_content_summary.textContent = `Full block content (${format_number2(content.length)} chars)`; const full_content_pre = document_ref.createElement("pre"); full_content_pre.textContent = "Expand to render the complete block content."; full_content_details.append(full_content_summary, full_content_pre); full_content_details.addEventListener("toggle", () => { if (!full_content_details.open || full_content_pre.dataset.loaded) return; full_content_pre.dataset.loaded = "true"; full_content_pre.textContent = content; }); } const embed_details = document_ref.createElement("details"); embed_details.className = "source-inspector__embed-input"; const embed_summary = document_ref.createElement("summary"); embed_summary.textContent = "Embed input"; const embed_pre = document_ref.createElement("pre"); embed_pre.textContent = "Expand to generate this block embed input."; embed_details.append(embed_summary, embed_pre); embed_details.addEventListener("toggle", () => { if (!embed_details.open) return; load_embed_input(record, embed_pre, load_result).catch((error) => { console.error("[source_inspector] Failed to generate embed input", error); }); }); article.append(header, content_pre); if (full_content_details) article.append(full_content_details); article.append(embed_details); return article; } async function load_embed_input(record, embed_pre, load_result) { if (!record.embed_input_loaded && !record.embed_input_promise) { record.embed_input_loading = true; record.embed_input_promise = (async () => { try { const content = materialize_block_content( record, load_result?.source_lines || [] ); const embed_input = await record.block?.get_embed_input?.(content); record.embed_input = typeof embed_input === "string" ? embed_input : String(embed_input ?? ""); record.embed_input_error = ""; } catch (error) { record.embed_input = ""; record.embed_input_error = error?.message || String(error); } finally { record.embed_input_loading = false; record.embed_input_loaded = true; } })(); } if (!record.embed_input_loaded) { embed_pre.textContent = "Generating embed input..."; await record.embed_input_promise; } embed_pre.textContent = record.embed_input_error ? `Failed to generate embed input: ${record.embed_input_error}` : record.embed_input || "(Empty embed input)"; } function render_source_summary(elements, result) { const presentation = STATUS_PRESENTATION[result.source_status?.status_key] || STATUS_PRESENTATION.skipped; elements.source_status.dataset.tone = result.source_status?.status_key || "skipped"; set_text3(elements.source_status, presentation.label); set_text3(elements.source_status_detail, presentation.description); set_metric2(elements, "blocks", result.summary?.total, `${format_number2(result.summary?.should_embed)} eligible`); set_metric2(elements, "characters", result.char_count, ""); set_metric2(elements, "lines", result.line_count, ""); const eligible = Number(result.summary?.should_embed || 0); const embedded = Number(result.summary?.embedded || 0); const coverage_percent = eligible ? Math.round(embedded / eligible * 100) : null; set_metric2( elements, "coverage", coverage_percent == null ? "N/A" : `${coverage_percent}%`, eligible ? `${format_number2(embedded)} of ${format_number2(eligible)} eligible` : "No eligible blocks" ); set_text3( elements.blocks_detail, `${format_number2(result.summary?.missing)} need embedding / ${format_number2(result.summary?.unexpected)} unexpected` ); set_text3(elements.load_timing, `Loaded in ${format_duration2(result.load_time_ms)}`); } function set_metric2(elements, key, value, detail) { const metric = elements.source_path?.closest(".source-inspector")?.querySelector(`[data-metric="${key}"]`); if (!metric) return; set_text3(metric.querySelector(".source-inspector__metric-value"), format_number_or_text(value)); set_text3(metric.querySelector(".source-inspector__metric-detail"), detail); } function render_filter_counts(filters, summary = {}) { const counts = { all: summary.total, embedded: summary.embedded, missing: summary.missing, skipped: summary.skipped, unexpected: summary.unexpected }; Object.entries(counts).forEach(([filter_key, count]) => { const count_el = filters.querySelector(`[data-filter="${filter_key}"] [data-filter-count]`); set_text3(count_el, format_number2(count)); }); } function update_active_filter(filters, active_filter) { filters.querySelectorAll("[data-filter]").forEach((button) => { const is_active = button.dataset.filter === active_filter; button.classList.toggle("is-active", is_active); button.setAttribute("aria-pressed", String(is_active)); }); } function set_loading_state(container, elements, loading, params = {}) { const active_action = params.active_action || ""; container.setAttribute("aria-busy", String(loading)); elements.refresh_btn.disabled = loading; elements.refresh_btn.textContent = loading && active_action === "refresh" ? "Refreshing..." : "Refresh"; if (elements.force_re_import_btn) { elements.force_re_import_btn.disabled = loading; elements.force_re_import_btn.textContent = loading && active_action === "force_re_import" ? "Re-importing..." : "Force re-import"; } elements.search_input.disabled = loading; elements.filters.querySelectorAll("button").forEach((button) => { button.disabled = loading; }); } function render_error(elements, error) { const message = error?.message || "Unknown error"; elements.source_status.dataset.tone = "missing"; set_text3(elements.source_status, "Failed"); set_text3(elements.source_status_detail, "Could not inspect this source."); set_text3(elements.results_summary, "Source inspection failed."); elements.blocks_container.replaceChildren(); const error_el = elements.blocks_container.ownerDocument.createElement("div"); error_el.className = "source-inspector__error"; error_el.textContent = `${message}. See the developer console for details.`; elements.blocks_container.appendChild(error_el); elements.load_more_btn.hidden = true; } function render_source_data(source, elements) { const json = get_source_data_json(source, elements); elements.source_data_pre.textContent = json; } function get_source_data_json(source, elements) { if (elements.source_data_pre.dataset.loaded === "true") { return elements.source_data_pre.textContent || ""; } let json; try { json = JSON.stringify(source?.data ?? {}, null, 2); } catch (error) { json = `Unable to serialize source data: ${error?.message || String(error)}`; } elements.source_data_pre.dataset.loaded = "true"; elements.source_data_pre.textContent = json; return json; } async function copy_with_feedback(text, button, env, idle_label) { const copied = await copy_to_clipboard(text, { env, event_source: "source_inspector" }); if (!button?.isConnected) return copied; button.textContent = copied ? "Copied" : "Copy failed"; setTimeout(() => { if (button?.isConnected) button.textContent = idle_label; }, 1200); return copied; } function bind_event(element, event_name, handler, cleanup_fns) { if (!element) return; element.addEventListener(event_name, handler); cleanup_fns.push(() => element.removeEventListener(event_name, handler)); } function build_metric_html2(key, label) { return `
    ${label}
    -
    `; } function build_filter_button_html(key, label) { const is_active = key === "all"; return ``; } function build_loading_html(message) { return `
    ${message}
    `; } function set_text3(element, value) { if (!element) return; element.textContent = value == null ? "" : String(value); } function format_number2(value) { return number_formatter2.format(Number(value || 0)); } function format_number_or_text(value) { return typeof value === "number" ? format_number2(value) : String(value ?? ""); } function format_duration2(duration_ms) { const normalized_ms = Math.max(0, Number(duration_ms || 0)); if (normalized_ms < 1e3) return `${Math.round(normalized_ms)} ms`; return `${(normalized_ms / 1e3).toFixed(normalized_ms < 1e4 ? 1 : 0)} s`; } function now_ms4() { return globalThis.performance?.now?.() || Date.now(); } // 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"); 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); env.build_menu?.("env:status_bar_menu", menu, env, { status_container, event: ev }); if (!(menu.items?.length > 0)) return; 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-progress {\n width: 7em;\n flex-shrink: 0;\n height: 0.5em;\n overflow: hidden;\n border-radius: 999px;\n background: color-mix(in srgb, var(--status-bar-text-color) 18%, transparent);\n}\n\n.smart-env-status-progress-fill {\n display: block;\n width: 0%;\n height: 100%;\n border-radius: inherit;\n background: var(--color-accent);\n transition: width 120ms linear;\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 progress_el = container?.querySelector?.(".smart-env-status-progress"); const progress_fill_el = container?.querySelector?.(".smart-env-status-progress-fill"); 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_progress = (activity_state) => { if (!progress_el || !progress_fill_el) return; const progress_pct = activity_state?.kind === "exporting" && typeof activity_state.progress_pct === "number" ? Math.max(0, Math.min(100, activity_state.progress_pct)) : null; if (progress_pct === null) { progress_el.hidden = true; progress_fill_el.style.width = "0%"; progress_el.removeAttribute("aria-valuenow"); return; } progress_el.hidden = false; progress_fill_el.style.width = `${progress_pct}%`; progress_el.setAttribute("aria-valuenow", String(progress_pct)); }; 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 activity_state = get_env_activity_state(env); 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); update_progress(activity_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/actions/context/add_items.js function context_add_items(params = {}) { const items = Array.isArray(params.items) ? params.items : []; if (!items.length) return false; return this.add_items(items); } // node_modules/obsidian-smart-env/src/actions/context/clear_context.js function context_clear_context() { this.clear_all?.(); return true; } var menus3 = { "smart_context:action_menu": { title: "Clear this context", icon: "rotate-ccw", order: 999, when() { return Number(this.scope?.item_count || 0) > 0 || Number(this.scope?.excluded_item_count || 0) > 0; } } }; // 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_segments2(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_node2() { 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_node2(); 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, filter) { const user_filter = typeof filter === "function" ? filter : null; const collection = smart_context?.context_items; if (collection && typeof collection.filter === "function") { return collection.filter((item) => { if (!item?.key) return false; if (user_filter && !user_filter(item)) 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).map(([key, item_data]) => ({ key, data: item_data || {} })).filter((item) => { if (!item.key) return false; if (user_filter && !user_filter(item)) return false; return true; }); } function get_active_file_path(smart_context) { return smart_context?.env?.obsidian_app?.workspace?.getActiveFile?.()?.path || ""; } function context_to_md_tree(smart_context, filter) { const items = list_context_items(smart_context, filter); if (!items.length) return ""; const root_node = create_tree_node2(); 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_segments2(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/actions/context/copy_link_tree.js async function context_copy_link_tree(params = {}) { const event_source = params.event_source || "smart_context.copy_link_tree"; const md_tree = context_to_md_tree(this, params.filter).trim(); if (!md_tree) { this.emit_event("context:copy_empty", { level: "warning", message: "No context items to copy.", event_source }); return false; } const copied = await copy_to_clipboard(md_tree, { env: this.env, event_source, 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; this.emit_event("context:link_tree_copied", { level: "info", message: "Copied link tree to clipboard.", event_source }); return true; } var menus4 = { "smart_context:copy_menu": { title: "Copy link tree", icon: "list-tree", order: 3, when() { return Number(this.scope?.item_count || 0) > 0; } } }; // node_modules/obsidian-smart-env/src/actions/context/copy_text_to_clipboard.js async function context_copy_text_to_clipboard(params = {}) { if (typeof this?.actions?.context_copy_to_clipboard !== "function") return false; return await this.actions.context_copy_to_clipboard({ ...params, with_media: false }); } var menus5 = { "smart_context:copy_menu": { title: "Copy text", icon: "copy", order: 0, when() { return Number(this.scope?.item_count || 0) > 0; } } }; // 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 + (this.data?.section ? `#${this.data.section}` : ""), "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 item_settings = this.context_items?.effective_settings || this.settings; const templates = get_item_templates(item_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/src/actions/context-item/open.js async function context_item_open(params = {}) { if (typeof this?.open !== "function") return false; await this.open(params.click_event || params.event || null); return true; } var menus6 = { "context_item:action_menu": { title: "Open external", icon: "external-link", order: 10, when() { return !this.scope?.item_ref; }, params(_menu_ctx, event) { return { event }; }, disabled() { return typeof this.scope?.open !== "function"; } } }; // node_modules/obsidian-smart-env/src/actions/context-item/remove.js async function context_item_remove(params = {}) { const click_event = params.click_event || params.event || null; if (params.remove_disabled === true) { if (typeof params.on_remove_disabled === "function") { await params.on_remove_disabled(click_event, this); } return false; } if (typeof params.on_remove === "function") { await params.on_remove(click_event, this); return true; } const smart_context = this?.collection?.smart_context || params.smart_context || params.ctx; if (typeof smart_context?.remove_item !== "function") return false; smart_context.remove_item(this.key); return true; } var menus7 = { "context_item:action_menu": { title: "Remove from context", icon: "x", order: 90, params(_menu_ctx, event) { return { event }; }, disabled() { return this.params.remove_disabled === true; } } }; // 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"; var display_description = "Choose a heading or block from a note."; var menus8 = { "smart_context:suggest": { title: "Sections", icon: "heading", order: 20 } }; // node_modules/obsidian-smart-env/src/actions/context-suggest/contexts.js var import_obsidian36 = require("obsidian"); var display_name2 = "Add named contexts"; var display_description2 = "Reuse a saved context or browse its sources."; var version = "3.0.2"; var menus9 = { "smart_context:suggest": { title: "Named contexts", icon: "smart-named-contexts", order: 30 } }; 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 named 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; out.push({ ...item_data && typeof item_data === "object" ? item_data : {}, key: item_data?.key || key }); } return out; } function build_direct_context_item(item_data) { const copied_data = { ...item_data }; delete copied_data.from_folder; delete copied_data.from_named_context; delete copied_data.d; delete copied_data.at; delete copied_data.size; delete copied_data.mtime; delete copied_data.group_items_ct; delete copied_data.truncated; delete copied_data.truncated_max_items; delete copied_data.missing; if (copied_data.folder !== true) delete copied_data.folder; return copied_data; } function add_named_context(ctx, context_name) { ctx.add_item({ key: context_name, kind: "named_context", named_context: true }); } function format_depth_label(depth) { if (!Number.isFinite(depth)) return ""; return `depth ${depth}`; } function build_named_context_item_suggestions(ctx, params = {}) { const payloads = get_items_from_context(params.other_ctx); 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: () => { ctx.add_item(build_direct_context_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 current_items = ctx?.data?.context_items || {}; const already_included = Boolean( current_items[other_name]?.named_context || Object.values(current_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 } = {}) => { add_named_context(ctx, other_name); 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 } = {}) => { add_named_context(ctx, other_name); 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"; var DEFAULT_SOURCE_FILE_TYPES = /* @__PURE__ */ new Set(["md", "base", "canvas"]); 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 source_filter = params?.source_filter || ((source) => DEFAULT_SOURCE_FILE_TYPES.has(source.file_type)); const sources = get_sources_list(this, { folder_path: params?.folder_path || "" }).filter(source_filter); return build_source_suggestions(this, sources); } var display_name3 = "Add sources"; var display_description3 = "Search Markdown notes, Bases, and Canvas files."; var menus10 = { "smart_context:suggest": { title: "Notes", icon: "file-text", order: 0 } }; // node_modules/obsidian-smart-env/src/actions/env/browse_smart_plugins.js function env_browse_smart_plugins(params = {}) { this?.events?.emit?.("smart_plugins:browse", { event_source: params.event_source }); return true; } var commands = { "browse-smart-plugins": { name: "Browse Smart Plugins", register_when({ plugin, env }) { return plugin === env.main; } } }; var menus11 = { "env:status_bar_menu": { title: "Browse Smart Plugins", icon: "package", order: 60 } }; // node_modules/obsidian-smart-env/src/actions/env/export_data.js var import_obsidian38 = require("obsidian"); var DEFAULT_EXPORT_CHUNK_SIZE = 1024 * 1024; var DEFAULT_EXPORT_COLLECTION_KEYS = [ "smart_sources", "smart_blocks" ]; var COLLECTION_LABELS = { smart_sources: "Sources", smart_blocks: "Blocks" }; function env_export_data(params = {}) { const app2 = this.main?.app || this.obsidian_app; if (!app2 || this.export_progress_state?.active) return false; const modal = new ExportDataModal(app2, this, params); modal.open(); return true; } var menus12 = { "env:status_bar_menu": { title: "Export data", icon: "download", order: 30, disabled() { return Boolean(this.scope?.export_progress_state?.active); } } }; var ExportDataModal = class extends import_obsidian38.Modal { constructor(app2, env, params = {}) { super(app2); this.env = env; this.params = params; this.selected_collections = Object.fromEntries( DEFAULT_EXPORT_COLLECTION_KEYS.map((collection_key) => [ collection_key, Boolean(env?.[collection_key]?.items) ]) ); this.include_vectors = false; this.controls = []; } onOpen() { this.titleEl.setText("Export data"); this.render(); } onClose() { this.contentEl.empty(); } render() { this.contentEl.empty(); this.controls = []; this.contentEl.createEl("p", { text: "Choose the Smart Environment collections to include in one JSON export file." }); DEFAULT_EXPORT_COLLECTION_KEYS.forEach((collection_key) => { this.render_collection_setting(collection_key); }); new import_obsidian38.Setting(this.contentEl).setName("Include embedding vectors").setDesc("Adds the current embedding vector as vec on each exported item. This can make exports much larger.").addToggle((toggle) => { toggle.setValue(this.include_vectors).onChange((value) => { this.include_vectors = value; }); this.controls.push(toggle); }); this.status_el = this.contentEl.createDiv({ cls: "setting-item-description", text: "The JSON file will be saved in the vault root." }); this.progress_el = this.contentEl.createEl("progress"); this.progress_el.max = 100; this.progress_el.hidden = true; this.progress_el.setAttribute( "aria-label", "Smart Environment export progress" ); this.progress_el.style.width = "100%"; this.progress_el.style.height = "0.75rem"; this.progress_el.style.marginTop = "var(--size-4-2)"; this.progress_el.style.marginBottom = "var(--size-4-2)"; new import_obsidian38.Setting(this.contentEl).addButton((button) => { this.export_button = button; button.setButtonText("Export").setCta().onClick(async () => { await this.run_export(); }); }); } render_collection_setting(collection_key) { const collection = this.env?.[collection_key]; const available = Boolean(collection?.items); const item_count = Object.keys(collection?.items || {}).length; const label = COLLECTION_LABELS[collection_key] || collection_key; const description = available ? `${item_count} ${item_count === 1 ? "item" : "items"}` : "Collection unavailable"; new import_obsidian38.Setting(this.contentEl).setName(label).setDesc(description).addToggle((toggle) => { toggle.setValue(Boolean(this.selected_collections[collection_key])).setDisabled(!available).onChange((value) => { this.selected_collections[collection_key] = value; }); if (available) this.controls.push(toggle); }); } async run_export() { const collection_keys = DEFAULT_EXPORT_COLLECTION_KEYS.filter((collection_key) => this.selected_collections[collection_key]); if (!collection_keys.length) { this.hide_progress(); this.set_status("Select at least one collection to export."); return false; } if (this.env?.export_progress_state?.active) { this.hide_progress(); this.set_status("A Smart Environment data export is already in progress."); return false; } this.set_running(true); this.set_status("Preparing export..."); this.set_progress(null); try { const result = await run_env_export(this.env, { collection_keys, include_vectors: this.include_vectors, event_source: this.params.event_source || "env_export_data", on_progress: (progress) => { this.render_progress(progress); } }); const item_label = result.item_count === 1 ? "item" : "items"; this.set_progress(100); this.set_status( `Exported ${result.item_count} ${item_label} to ${result.file_path}.` ); this.export_button?.setButtonText("Export again"); return result; } catch (error) { console.error("Smart Environment data export failed", error); this.hide_progress(); this.set_status(`Export failed: ${error?.message || error}`); this.export_button?.setButtonText("Retry export"); return false; } finally { this.set_running(false); } } render_progress(progress = {}) { if (progress.active === false) return; const collection_label = COLLECTION_LABELS[progress.collection_key] || progress.collection_key || "data"; const current = Number(progress.progress || 0); const total = Number(progress.total || 0); const progress_pct = total > 0 ? current / total * 100 : null; this.set_status(`Exporting ${collection_label}...`); this.set_progress(progress_pct); } set_progress(progress_pct) { if (!this.progress_el) return; this.progress_el.hidden = false; if (!Number.isFinite(progress_pct)) { this.progress_el.removeAttribute("value"); this.progress_el.removeAttribute("aria-valuenow"); return; } const value = Math.max(0, Math.min(100, progress_pct)); this.progress_el.value = value; this.progress_el.setAttribute("aria-valuenow", String(value)); } hide_progress() { if (!this.progress_el) return; this.progress_el.hidden = true; this.progress_el.removeAttribute("value"); this.progress_el.removeAttribute("aria-valuenow"); } set_running(running) { this.controls.forEach((control) => { control.setDisabled?.(running); }); this.export_button?.setDisabled(running); if (running) this.export_button?.setButtonText("Exporting..."); } set_status(message) { if (!this.status_el) return; if (typeof this.status_el.setText === "function") { this.status_el.setText(message); return; } this.status_el.textContent = message; } }; async function run_env_export(env, params = {}) { if (env.export_progress_state?.active) { throw new Error("Smart Environment data export is already in progress."); } const { collection_keys, event_source = "env_export_data", include_vectors = false, on_progress } = params; let progress_started = false; let last_progress = null; const update_progress = (progress = {}) => { last_progress = { ...progress, active: true }; env.export_progress_state = last_progress; call_progress_handler(on_progress, last_progress); if (progress_started) return; progress_started = true; env.events?.emit?.("smart_env:export_progress", { ...last_progress, event_source }); }; try { const result = await export_env_data(env, { collection_keys, include_vectors, on_progress: update_progress }); env.events?.emit?.("smart_env:exported", { level: "attention", message: `Exported ${result.item_count} Smart Environment items to ${result.file_path}.`, file_path: result.file_path, event_source }); return result; } catch (error) { env.events?.emit?.("smart_env:export_failed", { level: "error", message: "Smart Environment data export failed.", details: error?.stack || error?.message || String(error), event_source }); throw error; } finally { env.export_progress_state = null; const completed_progress = { ...last_progress || {}, active: false }; call_progress_handler(on_progress, completed_progress); env.events?.emit?.("smart_env:export_progress", { ...completed_progress, event_source }); } } async function export_env_data(env, params = {}) { const collection_keys = get_export_collection_keys( env, params.collection_keys ); if (!collection_keys.length) { throw new Error("No exportable Smart Environment collections selected."); } const fs = env?.fs; assert_export_fs(fs); const file_path = build_export_file_path(); const total = collection_keys.reduce((sum, collection_key) => { return sum + get_collection_item_count(env?.[collection_key]); }, 0); const json_replacer = params.include_vectors ? void 0 : function export_json_replacer(key, value) { return key === "vec" ? void 0 : value; }; let buffer = "{"; let chunk_count = 0; let write_started = false; let has_written = false; let progress = 0; let current_collection_key = collection_keys[0]; let current_collection_i = 0; let current_collection_progress = 0; let current_collection_total = get_collection_item_count( env?.[current_collection_key] ); const report_progress = () => { call_progress_handler(params.on_progress, { active: true, collection_key: current_collection_key, collection_i: current_collection_i, collection_count: collection_keys.length, collection_progress: current_collection_progress, collection_total: current_collection_total, progress, total, file_path }); }; const flush_buffer = async (force = false) => { if (!buffer) return false; if (!force && buffer.length < DEFAULT_EXPORT_CHUNK_SIZE) return false; if (has_written) { await append_export_file(fs, file_path, buffer); } else { write_started = true; await write_export_file(fs, file_path, buffer); has_written = true; } buffer = ""; chunk_count += 1; report_progress(); return true; }; try { for (let collection_i = 0; collection_i < collection_keys.length; collection_i += 1) { const collection_key = collection_keys[collection_i]; const collection = env[collection_key]; const item_keys = Object.keys(collection?.items || {}); current_collection_key = collection_key; current_collection_i = collection_i; current_collection_progress = 0; current_collection_total = item_keys.length; report_progress(); if (params.include_vectors) { await collection?.embeddings?.load_vectors?.(); } const collection_chunk = `${collection_i === 0 ? "\n" : ",\n"} ${JSON.stringify(collection_key)}: { "items": [`; if (buffer.length + collection_chunk.length > DEFAULT_EXPORT_CHUNK_SIZE) { await flush_buffer(true); } buffer += collection_chunk; for (let item_i = 0; item_i < item_keys.length; item_i += 1) { const item_key = item_keys[item_i]; const item = collection.items[item_key]; const mapped_data = map_export_item( item, params.include_vectors ); let item_json; try { item_json = JSON.stringify(mapped_data, json_replacer); } catch (error) { throw new Error( `Unable to serialize ${collection_key} item ${item_key}: ${error?.message || error}` ); } if (typeof item_json === "undefined") item_json = "null"; const item_chunk = `${item_i === 0 ? "\n " : ",\n "}${item_json}`; if (buffer.length + item_chunk.length > DEFAULT_EXPORT_CHUNK_SIZE) { await flush_buffer(true); } buffer += item_chunk; current_collection_progress = item_i + 1; progress += 1; await flush_buffer(); } const collection_end = item_keys.length ? "\n ]\n }" : "]\n }"; if (buffer.length + collection_end.length > DEFAULT_EXPORT_CHUNK_SIZE) { await flush_buffer(true); } buffer += collection_end; report_progress(); } buffer += "\n}\n"; await flush_buffer(true); } catch (error) { if (write_started) await remove_export_file(fs, file_path); throw error; } report_progress(); return { file_path, item_count: total, collection_count: collection_keys.length, chunk_count }; } function map_export_item(item, include_vectors) { const data = item?.data; if (!include_vectors) return data; const vec = item?.vec; if (!vec?.length) return data; return { ...data, vec: Array.from(vec) }; } function get_export_collection_keys(env, collection_keys) { const requested_keys = Array.isArray(collection_keys) ? collection_keys : DEFAULT_EXPORT_COLLECTION_KEYS; return Array.from(new Set(requested_keys)).filter((collection_key) => { return typeof collection_key === "string" && collection_key.length > 0 && env?.[collection_key]?.items; }); } function build_export_file_path(date = /* @__PURE__ */ new Date()) { const timestamp = date.toISOString().replace(/[:.]/g, "-"); return `smart-env-export-${timestamp}.json`; } function get_collection_item_count(collection) { return Object.keys(collection?.items || {}).length; } function assert_export_fs(fs) { const has_write = typeof fs?.write === "function" || typeof fs?.adapter?.write === "function"; const has_append = typeof fs?.append === "function" || typeof fs?.adapter?.append === "function"; if (!has_write || !has_append) { throw new TypeError("Smart Environment export requires write and append filesystem methods."); } } async function write_export_file(fs, file_path, content) { if (typeof fs?.adapter?.write === "function") { return await fs.adapter.write(file_path, content); } return await fs.write(file_path, content); } async function append_export_file(fs, file_path, content) { if (typeof fs?.adapter?.append === "function") { return await fs.adapter.append(file_path, content); } return await fs.append(file_path, content); } async function remove_export_file(fs, file_path) { try { if (typeof fs?.adapter?.remove === "function") { await fs.adapter.remove(file_path); return; } if (typeof fs?.remove === "function") { await fs.remove(file_path); } } catch (error) { console.warn(`Failed to remove incomplete export file: ${file_path}`, error); } } function call_progress_handler(on_progress, progress) { if (typeof on_progress !== "function") return; try { on_progress(progress); } catch (error) { console.warn("Smart Environment export progress handler failed", error); } } // node_modules/obsidian-smart-env/src/actions/env/get_embedding_vector_memory_usage.js function env_get_embedding_vector_memory_usage() { let used_bytes = 0; let allocated_bytes = 0; for (const collection_key of Object.keys(this.collections)) { const embeddings = this[collection_key]?.embeddings; const vectors_by_file = embeddings?._vectors_by_file; if (!vectors_by_file) continue; for (const [file, vectors] of Object.entries(vectors_by_file)) { used_bytes += embeddings.get_vector_value_count(file) * Float32Array.BYTES_PER_ELEMENT; allocated_bytes += vectors.byteLength; } } return { used_bytes, allocated_bytes, unused_capacity_bytes: allocated_bytes - used_bytes }; } var action_scope = { type: "env" }; // node_modules/obsidian-smart-env/src/actions/env/open_milestones.js function env_open_milestones() { if (typeof this?.open_milestones_modal !== "function") return false; this.open_milestones_modal(); return true; } var menus13 = { "env:status_bar_menu": { title: "Milestones", icon: "flag", order: 40 } }; // node_modules/obsidian-smart-env/src/actions/env/open_notifications.js function env_open_notifications() { if (typeof this?.open_notifications_feed_modal !== "function") return false; this.open_notifications_feed_modal(); return true; } var menus14 = { "env:status_bar_menu": { title: "Notifications", icon: "bell", order: 50 } }; // node_modules/obsidian-smart-env/src/actions/env/open_pro_link.js function env_open_pro_link() { const url = this?.is_pro ? "https://smartconnections.app/my-referrals/?utm_source=status-bar" : "https://smartconnections.app/pro-plugins/?utm_source=status-bar"; const open_url = globalThis.window?.open || globalThis.open; if (typeof open_url !== "function") return false; open_url(url, "_external"); return true; } var menus15 = { "env:status_bar_menu": { title() { return this.scope?.is_pro ? "Refer a friend (Give 30, Get 30)" : "Start 14-day Pro trial"; }, icon: "hand-heart", order: 70 } }; // node_modules/obsidian-smart-env/src/actions/env/open_status_view.js function env_open_status_view(params = {}) { if (typeof this?.open_env_status_view !== "function") return false; this.open_env_status_view(params); return true; } var commands2 = { "env-status-view": { name: "Open Environment Status View", register_when({ plugin, env }) { return plugin === env.main; } } }; var menus16 = { "env:status_bar_menu": { title: "Open status view", icon: "gauge", order: 5 } }; // node_modules/obsidian-smart-env/src/actions/lookup-list/get_results.js async function lookup_list_get_results(params = {}) { const action_key = this.settings?.get_results_action_key; if (action_key && action_key !== "lookup_list_get_results") { const selected_action = this.actions?.[action_key]; if (typeof selected_action === "function") { return await selected_action.call(this, params); } } return await this.get_results(params); } var display_name4 = "Semantic search"; // 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({ embed_input: query, purpose: "query" }); params.to_item = { ...embedding }; if (!params.score_algo_key) params.score_algo_key = "similarity"; return params; } // node_modules/obsidian-smart-env/src/actions/plugin/open_release_notes.js function plugin_open_release_notes(params = {}) { params.plugin?.ReleaseNotesView?.open(params.plugin?.app?.workspace); } var commands3 = { "release-notes": { name: "Open: Release Notes", register_when({ plugin }) { const has_release_notes_view = !!plugin.ReleaseNotesView; return has_release_notes_view; }, params({ plugin }) { return { plugin }; }, get_scope({ env }) { return env; } } }; // 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_name5 = "Cosine Similarity"; var display_description4 = "Ranks by cosine similarity between the current note and candidates."; var settings_config9 = { similarity_algo_description: { group: "Score algorithm", type: "html", name: `${display_name5} algorithm`, value: `${display_description4}` } }; // node_modules/obsidian-smart-env/src/actions/smart-block/read.js async function smart_block_read(params = {}) { if (typeof this.read !== "function") { throw new Error("Unable to read Smart Block."); } const result = await this.read(params); if (result === null || result === void 0) return ""; return typeof result === "string" ? result : JSON.stringify(result, null, 2); } var display_name6 = "Read Smart Block"; var display_description5 = "Returns the text for a Smart Block key."; var input_schema = { type: "object", properties: { key: { type: "string", minLength: 1, description: "Smart Block key." } }, required: ["key"], additionalProperties: false }; var output_schema = { type: "string" }; var action_scope2 = { type: "item", collection_key: "smart_blocks", item_arg: "key" }; var tool = { name: "smart_block_read", when({ env }) { return Boolean(env.smart_blocks); }, effects: { read_only: true, destructive: false, idempotent: true } }; // node_modules/obsidian-smart-env/src/actions/source/get_embed_input/base.js var display_name7 = "Get Base source embed input"; async function source_get_embed_input_base() { this._embed_input = ""; return ""; } // node_modules/obsidian-smart-env/src/actions/source/get_embed_input/canvas.js var display_name8 = "Get Canvas source embed input"; async function source_get_embed_input_canvas(params = {}) { return await source_get_embed_input_markdown.call(this, params); } // node_modules/obsidian-smart-env/src/actions/source/get_embed_input/rendered.js var display_name9 = "Get rendered source embed input"; async function source_get_embed_input_rendered(params = {}) { return await source_get_embed_input_markdown.call(this, params); } // node_modules/obsidian-smart-env/src/actions/source/open.js async function source_open(params = null) { const event = params?.click_event || params?.event || params || null; await open_source(this, event); } var menus17 = { "source:menu": { title: "Open source", icon: "external-link", order: 10, params(_menu_ctx, event) { return { 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, smart_sources: smart_sources_default }, items: { embedding_model: { class: EmbeddingModel, version: "1.0.3" }, lookup_list: { class: LookupList, version: "1.0.3" }, smart_block: { class: SmartBlock2, version: 2 }, smart_context: { class: SmartContext2, version: "2.1.1" }, smart_source: { class: SmartSource2, version: 2 } }, modules: { embeddings: embeddings_default }, components: { context_item_leaf: { render, version: "1.0.3" }, default_notification: { render: render2, version: "1.0.3" }, env_stats: { render: render3, version: "1.0.3" }, env_status: { render: render4, version: "1.0.3" }, lean_coffee_callout: { render: render5, version: "1.0.3" }, milestone_notification: { render: render6, version: "1.0.3" }, milestones: { render: render7, version: "1.0.3" }, notifications_feed: { render: render8, version: "1.0.3" }, settings_env_model: { render: render9, version: "1.0.3" }, settings_env_model_type: { render: render10, version: "1.0.3" }, settings_env_models: { render: render11, version: "1.0.3" }, settings_env_sources: { render: render12, version: "1.0.3" }, settings_model_actions: { render: render13, version: "1.0.3" }, settings_smart_env: { render: render14, version: "1.0.3" }, smart_context_actions: { render: render15, version: "1.0.3" }, smart_context_item: { render: render16, version: "1.0.3" }, smart_context_meta: { render: render17, version: "1.0.3" }, smart_context_tree: { render: render18, version: "1.0.3" }, smart_plugins_list: { render: render19, version: "1.0.3" }, smart_plugins_list_item: { render: render20, version: "1.0.3" }, smart_plugins_login: { render: render21, version: "1.0.3" }, smart_plugins_referral: { render: render22, version: "1.0.3" }, source_inspector: { render: render23, version: "1.0.3" }, status_bar: { render: render24, version: "1.0.3" }, suggest_display_right: { render: render25, version: "1.0.3" }, supporter_callout: { render: render26, version: "1.0.3" }, user_agreement_callout: { render: render27, version: "1.0.3" } }, actions: { context_add_items: { action: context_add_items, version: "1.0.3" }, context_clear_context: { action: context_clear_context, menus: menus3, version: "1.0.3" }, context_copy_link_tree: { action: context_copy_link_tree, menus: menus4, version: "1.0.3" }, context_copy_text_to_clipboard: { action: context_copy_text_to_clipboard, menus: menus5, version: "1.0.3" }, context_copy_to_clipboard: { action: copy_to_clipboard2, version: "1.0.3" }, context_item_merge_template: { action: merge_template, settings_config: settings_config7, default_settings: default_settings2, version: "1.0.3" }, context_item_open: { action: context_item_open, menus: menus6, version: "1.0.3" }, context_item_remove: { action: context_item_remove, menus: menus7, version: "1.0.3" }, context_merge_template: { action: merge_template2, settings_config: settings_config8, default_settings: default_settings3, version: "1.0.3" }, context_suggest_blocks: { action: context_suggest_blocks, display_name, display_description, menus: menus8, version: "1.0.3" }, context_suggest_contexts: { action: context_suggest_contexts, display_name: display_name2, display_description: display_description2, menus: menus9, version }, context_suggest_sources: { action: context_suggest_sources, display_name: display_name3, display_description: display_description3, menus: menus10, version: "1.0.3" }, env_browse_smart_plugins: { action: env_browse_smart_plugins, commands, menus: menus11, version: "1.0.3" }, env_export_data: { action: env_export_data, menus: menus12, version: "1.0.3" }, env_get_embedding_vector_memory_usage: { action: env_get_embedding_vector_memory_usage, action_scope, version: "1.0.3" }, env_inspect_active_note: { action: env_inspect_active_note, menus: menus2, version: "1.0.3" }, env_open_milestones: { action: env_open_milestones, menus: menus13, version: "1.0.3" }, env_open_notifications: { action: env_open_notifications, menus: menus14, version: "1.0.3" }, env_open_pro_link: { action: env_open_pro_link, menus: menus15, version: "1.0.3" }, env_open_status_view: { action: env_open_status_view, commands: commands2, menus: menus16, version: "1.0.3" }, env_show_stats: { action: env_show_stats, menus, version: "1.0.3" }, lookup_list_get_results: { action: lookup_list_get_results, display_name: display_name4, version: "1.0.3" }, lookup_list_pre_process: { action: pre_process, pre_process, version: "1.0.3" }, plugin_open_release_notes: { action: plugin_open_release_notes, commands: commands3, version: "1.0.3" }, similarity: { action: similarity, settings_config: settings_config9, display_name: display_name5, display_description: display_description4, version: "1.0.3" }, smart_block_read: { action: smart_block_read, display_name: display_name6, display_description: display_description5, action_scope: action_scope2, tool, input_schema, output_schema, version: "1.0.3" }, source_get_embed_input_base: { action: source_get_embed_input_base, display_name: display_name7, version: "1.0.3" }, source_get_embed_input_canvas: { action: source_get_embed_input_canvas, display_name: display_name8, version: "1.0.3" }, source_get_embed_input_rendered: { action: source_get_embed_input_rendered, display_name: display_name9, version: "1.0.3" }, source_open: { action: source_open, menus: menus17, version: "1.0.3" } } }; // 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: TransformersWorkerEmbeddingModelAdapter } } }, collections: { context_items: context_items_default, event_logs: event_logs_default2, smart_components: smart_components_default2, smart_sources: { collection_key: "smart_sources", class: SmartSources2, data_adapter: AjsonShardedSourcesDataAdapter, 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_block: smart_block_default, smart_source: smart_source_default }, default_settings, // begin obsidian-smart-env specific modules (need to update build_env_config.js to handle) modals: { context_selector: { 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); smart_env_config2.items.smart_block.actions = { ...smart_block_default.actions, ...smart_env_config2.items.smart_block.actions }; smart_env_config2.items.smart_source.actions = { ...smart_source_default.actions, ...smart_env_config2.items.smart_source.actions }; 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_copy_media_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_copy_media_icon() { (0, import_obsidian41.addIcon)("smart-copy-media", smart_copy_media_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_copy_media_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 a derived `${method_name}:open` listener. * * Usage from a plugin class: * SubClass.register_item_view(this); * Call during plugin.onload, after SmartEnv.create, so Obsidian can restore * persisted leaves before the environment loads. * * This will: * - call plugin.registerView(view_type, ...) * - add a command "Open: view" when enabled * - 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 `${method_name}:open` and open the view when emitted * * @param {import('obsidian').Plugin} plugin * @param {object} [params={}] * @param {boolean} [params.skip_command_registration=false] * @param {boolean} [params.skip_event_registration=false] * @returns {{method_name:string, open_method_name:string, event_name:string}} */ static register_item_view(plugin, params = {}) { const View = ( /** @type {typeof SmartItemView} */ this ); const method_name = View.view_type.replace(/^smart-/, "").replace(/-/g, "_"); const open_method_name = "open_" + method_name; const event_name = `${method_name}:open`; 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)); if (!params.skip_command_registration) { plugin.addCommand({ id: View.view_type, name: "Open: " + View.display_text + " view", callback: () => { plugin[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] = (...args) => { 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, ...args); }; if (!params.skip_event_registration) { const handler = (payload = {}) => { const active = typeof payload?.active === "boolean" ? payload.active : true; plugin[open_method_name]({ ...payload, active }); }; plugin.app.workspace.onLayoutReady(() => { const unsubscribe = plugin.env?.events?.on(event_name, handler); if (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" or "tab", * 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" || open_location === "tab") { leaf = existing_leaf || workspace.getLeaf(open_location === "tab" ? "tab" : 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_when_ready(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 }); const render_params = this._pending_render_params; this._pending_render_params = void 0; this._smart_item_view_initialized = true; this.render_view(render_params); } 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); } } render_view_when_ready(params = {}) { if (!this._smart_item_view_initialized) { this._pending_render_params = params; return; } return this.render_view(params); } 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 = `/* The PRO badge button */ .pro-setting .smart-plugin-pro-badge { appearance: none; /* layout */ display: inline-flex; align-items: center; justify-content: center; min-width: 0; min-height: 0; height: auto; margin-left: 0.4em; padding: 0.08em 0.55em; border-radius: 999px; white-space: nowrap; vertical-align: middle; pointer-events: auto; cursor: pointer; /* typography */ font-family: inherit; 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); } /* Interactive refinement: follow Obsidian's accent hover behavior */ .pro-setting .smart-plugin-pro-badge:hover { background-color: var(--interactive-accent-hover); filter: brightness(1.05); } .pro-setting .smart-plugin-pro-badge:focus-visible { outline: 2px solid var(--interactive-accent); outline-offset: 2px; } .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: "3.1.1", 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: { 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/src/utils/menu_actions.js function build_menu(env, menu_key, menu, scope, params = {}) { if (!env || !menu || !menu_key) return menu; const menu_contexts = resolve_menu_contexts( env, menu_key, menu, scope, params ); menu_contexts.forEach((menu_ctx) => { build_menu_entry(menu_ctx); }); return menu; } function resolve_menu_actions(env, menu_key, scope, params = {}) { if (!env || !menu_key) return []; return resolve_menu_contexts( env, menu_key, null, scope, params ).filter((menu_ctx) => !menu_ctx.menu_spec.separator).map((menu_ctx) => { const disabled = get_disabled(menu_ctx); return { action_key: menu_ctx.action_key, title: get_title(menu_ctx), icon: get_icon(menu_ctx), disabled, order: get_menu_order(menu_ctx), menu_only: typeof menu_ctx.menu_spec.build === "function", event_source: menu_ctx.event_source, async run(run_params = {}) { if (disabled) return false; return await menu_ctx.run(run_params); } }; }); } function collect_menu_entries(env, menu_key) { const entries = /* @__PURE__ */ new Map(); Object.entries(env?.config?.actions || {}).forEach(([action_key, action_entry]) => { const menu_spec = get_menu_spec(action_entry, menu_key); if (!menu_spec) return; entries.set(action_key, { action_key, action_entry, menu_spec }); }); return Array.from(entries.values()); } function resolve_menu_contexts(env, menu_key, menu, scope, params) { try { if (get_scope_env(scope) !== env) return []; } catch { return []; } return collect_menu_entries(env, menu_key).sort(compare_entries).filter(({ action_entry }) => { return is_action_scope_compatible( env, action_entry.action_scope, scope ); }).map((entry) => { return create_menu_ctx(env, menu_key, menu, scope, params, entry); }).filter(should_show); } function build_menu_entry(menu_ctx) { const { menu_spec } = menu_ctx; if (menu_spec.separator) { add_separator(menu_ctx); return; } if (typeof menu_spec.build === "function") { menu_spec.build.call(menu_ctx, menu_ctx); return; } add_item(menu_ctx); } function create_menu_ctx(env, menu_key, menu, scope, params, entry) { const menu_spec = entry.menu_spec; const menu_ctx = { env, menu_key, menu, scope, params, action_key: entry.action_key, action_entry: entry.action_entry, menu_spec, owner: entry.action_entry?.owner || null, get items() { return Array.isArray(menu?.items) ? menu.items : []; }, get event_source() { return `menu:${menu_key}:${entry.action_key}`; }, resolve_action() { const scoped_action = scope.actions?.[entry.action_key]; return typeof scoped_action === "function" ? scoped_action : entry.action_entry?.action?.bind(scope); }, async run(run_params = {}, event = null) { const spec_params = resolve_params( menu_spec.params, menu_ctx, event ); const { event: _event, click_event: _click_event, click_args: _click_args, ...base_params } = params || {}; const { menu_ctx: _menu_ctx, menu_key: _menu_key, action_key: _action_key, event_source: _event_source, ...action_params } = { ...base_params, ...spec_params, ...run_params }; return await run_action_entry( scope, entry.action_key, action_params, { event_source: menu_spec.event_source || menu_ctx.event_source } ); } }; return menu_ctx; } function add_item(menu_ctx) { if (typeof menu_ctx.menu?.addItem !== "function") return; const title = get_title(menu_ctx); const icon = get_icon(menu_ctx); const disabled = get_disabled(menu_ctx); const order = get_menu_order(menu_ctx); menu_ctx.menu.addItem((item) => { item._menu_key = menu_ctx.menu_key; item._action_key = menu_ctx.action_key; item._order = order; if (title) item.setTitle?.(title); if (icon) item.setIcon?.(icon); item.setDisabled?.(disabled); item.onClick?.(async (event) => { if (disabled) return false; return await menu_ctx.run({}, event); }); }); } function add_separator(menu_ctx) { if (typeof menu_ctx.menu?.addSeparator !== "function") return; menu_ctx.menu.addSeparator(); const item = menu_ctx.items[menu_ctx.items.length - 1]; if (!item) return; item._menu_key = menu_ctx.menu_key; item._action_key = menu_ctx.action_key; item._order = get_menu_order(menu_ctx); } function should_show(menu_ctx) { if (typeof menu_ctx.menu_spec.when === "undefined") return true; return Boolean(get_value(menu_ctx.menu_spec.when, menu_ctx)); } function get_disabled(menu_ctx) { if (typeof menu_ctx.menu_spec.disabled === "undefined") return false; return Boolean(get_value(menu_ctx.menu_spec.disabled, menu_ctx)); } function get_title(menu_ctx) { return get_value(menu_ctx.menu_spec.title, menu_ctx) || get_value(menu_ctx.menu_spec.display_name, menu_ctx) || menu_ctx.action_entry?.display_name || menu_ctx.action_entry?.title || humanize(menu_ctx.action_key); } function get_icon(menu_ctx) { return get_value(menu_ctx.menu_spec.icon, menu_ctx) || menu_ctx.action_entry?.icon || menu_ctx.action_entry?.display_icon || ""; } function get_menu_order(menu_ctx) { return get_order( menu_ctx.menu_spec.order ?? menu_ctx.action_entry?.order ); } function get_menu_spec(action_entry, menu_key) { const menus35 = action_entry?.menus; if (!menus35 || !Object.prototype.hasOwnProperty.call(menus35, menu_key)) { return null; } return normalize_menu_spec(menus35[menu_key]); } function normalize_menu_spec(menu_spec) { if (menu_spec === false || menu_spec === null || typeof menu_spec === "undefined") { return null; } if (menu_spec === true) return {}; if (typeof menu_spec === "function") return { build: menu_spec }; if (is_object2(menu_spec)) return menu_spec; throw new TypeError("Invalid menu specification."); } function get_value(value, menu_ctx) { return typeof value === "function" ? value.call(menu_ctx, menu_ctx) : value; } function resolve_params(value, menu_ctx, event = null) { const resolved = typeof value === "function" ? value.call(menu_ctx, menu_ctx, event) : value; return is_object2(resolved) ? resolved : {}; } function get_order(value) { const order = Number(value); return Number.isFinite(order) ? order : 0; } function compare_entries(left, right) { const order_delta = get_order( left.menu_spec.order ?? left.action_entry?.order ) - get_order( right.menu_spec.order ?? right.action_entry?.order ); if (order_delta) return order_delta; return left.action_key.localeCompare(right.action_key); } function humanize(action_key = "") { return String(action_key).split("_").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" "); } function is_object2(value) { return value && typeof value === "object" && !Array.isArray(value); } // node_modules/obsidian-smart-env/smart_env.js var MIN_COMPATIBLE_SMART_ENV_VERSION2 = "3.0.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}` ); } } const create_promise = super.create(plugin, opts); this.global_env?.register_env_item_views?.(); return create_promise.then((env) => { clearTimeout(env.load_timeout); env.load_timeout = null; if (!env._startup_load_registration) { env._startup_load_registration = true; 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(); this.main?.register_command_actions?.(); 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._embedding_model_change_unsubscribe) { this._embedding_model_change_unsubscribe = this.events.on("model:changed", (event = {}) => { if (event.collection_key !== "embedding_models") return; this.handle_embedding_model_change(); }); } 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(); this.main?.register_command_actions?.(); 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) { this._registered_ctx_menu_actions = true; } } handle_env_load_attempt_after_loaded() { handle_env_load_attempt_after_loaded(this); } handle_embedding_model_change() { const change_id = (this._embedding_model_change_id || 0) + 1; this._embedding_model_change_id = change_id; this.smart_sources?.mark_embed_queue_dirty?.(); this.smart_blocks?.mark_embed_queue_dirty?.(); const vec_index_runtime = this.smart_vec_index_runtime; vec_index_runtime?.unload?.(); const previous_change = this._embedding_model_change_promise || Promise.resolve(); const change_promise = previous_change.then(async () => { if (change_id !== this._embedding_model_change_id) return; await Promise.all([ this.smart_sources?.embeddings?.load_vectors(), this.smart_blocks?.embeddings?.load_vectors() ]); if (change_id !== this._embedding_model_change_id) return; this.smart_sources?.mark_embed_queue_dirty?.(); this.smart_blocks?.mark_embed_queue_dirty?.(); await this.smart_sources?.process_embed_queue?.(); if (change_id !== this._embedding_model_change_id) return; await vec_index_runtime?.refresh_vec_index_state?.(); }).catch((error) => { if (change_id !== this._embedding_model_change_id) return; console.warn("[smart_env] Failed to switch embedding model", error); }); this._embedding_model_change_promise = change_promise; return change_promise; } unload() { console.warn("Unloading SmartEnv"); this._embedding_model_change_unsubscribe?.(); this._embedding_model_change_unsubscribe = null; this._embedding_model_change_id = (this._embedding_model_change_id || 0) + 1; if (typeof this._onboarding_events_teardown === "function") { this._onboarding_events_teardown(); this._onboarding_events_teardown = null; } this._workspace_source_events_registered = false; this.unload_embeddings(); return super.unload?.(); } async unload_embeddings() { const embeddings = [ this.smart_sources?.embeddings, this.smart_blocks?.embeddings ].filter(Boolean); const results = await Promise.allSettled( embeddings.map((embedding) => embedding.unload?.()) ); results.forEach((result) => { if (result.status === "rejected") { console.warn("[smart_env] Failed to unload embeddings cleanly", result.reason); } }); } /** * 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 environment instance. * A superseding SmartEnv replaces the view class without duplicating its event listener. * @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}`; const helpers_registered = this._registered_env_item_views.has(registration_key); const view_registered = Boolean(plugin.app.viewRegistry?.viewByType?.[ViewClass.view_type]); if (helpers_registered && view_registered) return; try { const skip_helpers = helpers_registered || view_registered; ViewClass.register_item_view(plugin, { skip_command_registration: true, skip_event_registration: skip_helpers }); 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() { return this.main?.manifest?.id + ":env-status-view"; } /** * 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); }); } /** * Build registered actions for a menu instance. * * @param {string} menu_key * @param {Object} menu * @param {Object} scope * @param {Object} [params={}] * @returns {Object} */ build_menu(menu_key, menu, scope, params = {}) { return build_menu(this, menu_key, menu, scope, params); } /** * Resolve visible registered actions without constructing a native menu. * * @param {string} menu_key * @param {Object} scope * @param {Object} [params={}] * @returns {Array} */ resolve_menu_actions(menu_key, scope, params = {}) { return resolve_menu_actions(this, menu_key, scope, params); } /** * @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() { if (document.querySelector(".smart-env-pro-plugins-modal")) return; 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(version4) { return compare_versions(version4, MIN_COMPATIBLE_SMART_ENV_VERSION2) >= 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"); // node_modules/obsidian-smart-env/src/utils/command_actions.js function register_command_actions(plugin) { const { env, app: app2 } = plugin; const actions = env?.config?.actions; if (!is_object3(actions)) { throw new Error( "Command actions require converged env.config.actions." ); } const candidates = []; const command_actions = /* @__PURE__ */ new Map(); for (const [action_key, action_entry] of Object.entries(actions)) { const commands9 = action_entry?.commands; if (typeof commands9 === "undefined") continue; if (!is_object3(commands9)) { throw new TypeError( `Invalid commands export for action: ${action_key}` ); } for (const [command_id, command_spec] of Object.entries(commands9)) { if (!command_id.trim() || !is_object3(command_spec)) { throw new TypeError(`Invalid command spec: ${command_id}`); } if (typeof command_spec.register_when !== "function") { throw new TypeError( `Command register_when must be a function: ${command_id}` ); } const should_register = command_spec.register_when({ plugin, env, app: app2, action_key, command_id }); assert_synchronous( should_register, `Command register_when must be synchronous: ${command_id}` ); if (!should_register) continue; const candidate = normalize_command({ action_key, action_entry, command_id, command_spec }); const duplicate_action_key = command_actions.get(command_id); if (duplicate_action_key) { throw new Error( `Duplicate command '${command_id}' for actions '${duplicate_action_key}' and '${action_key}' in plugin '${plugin.manifest.id}'.` ); } command_actions.set(command_id, action_key); candidates.push(candidate); } } plugin._registered_command_actions ||= /* @__PURE__ */ new Map(); const registered_commands = plugin._registered_command_actions; for (const { action_key, command_id } of candidates) { const registered_action_key = registered_commands.get(command_id); if (registered_action_key && registered_action_key !== action_key) { throw new Error( `Command '${command_id}' is already registered by action '${registered_action_key}' in plugin '${plugin.manifest.id}'.` ); } } for (const candidate of candidates) { const { action_key, command_id, command_spec, context, name, hotkeys } = candidate; if (registered_commands.get(command_id) === action_key) continue; const run_command = create_command_callback({ plugin, action_key, command_id, command_spec }); const command = { id: command_id, name, ...hotkeys ? { hotkeys } : {} }; if (context === "editor") { Object.assign(command, { editorCheckCallback(checking, editor, editor_ctx) { return run_command({ checking, editor, editor_ctx }); } }); } else { Object.assign(command, { checkCallback(checking) { return run_command({ checking, editor: void 0, editor_ctx: void 0 }); } }); } plugin.addCommand(command); registered_commands.set(command_id, action_key); } } function create_command_callback({ plugin, action_key, command_id, command_spec }) { const { env, app: app2 } = plugin; const action_entry = env.config.actions[action_key]; return ({ checking, editor, editor_ctx }) => { const command_ctx = { plugin, env, app: app2, action_key, command_id, checking: Boolean(checking), editor, editor_ctx }; try { const params = resolve_params2(command_spec, command_ctx); const scope = command_spec.get_scope ? command_spec.get_scope({ ...command_ctx, params }) : resolve_action_scope( env, action_key, action_entry, params ); assert_synchronous( scope, `Command get_scope must be synchronous: ${command_id}` ); if (!is_action_scope_compatible( env, action_entry.action_scope, scope )) return false; const available = command_spec.when ? command_spec.when({ ...command_ctx, params, scope }) : true; assert_synchronous( available, `Command when must be synchronous: ${command_id}` ); if (!available) return false; if (checking) return true; try { const action_result = run_action_entry(scope, action_key, params, { event_source: `command:${plugin.manifest.id}:${command_id}` }); if (action_result && typeof action_result.catch === "function") { void action_result.catch((error) => { console.error(`Command action failed: ${command_id}`, error); }); } } catch (error) { console.error(`Command action failed: ${command_id}`, error); } return true; } catch (error) { if (!checking) { console.error( `Command action unavailable: ${command_id}`, error ); } return false; } }; } function resolve_params2(command_spec, command_ctx) { const params = typeof command_spec.params === "function" ? command_spec.params(command_ctx) : command_spec.params ?? {}; assert_synchronous( params, `Command params must be synchronous: ${command_ctx.command_id}` ); if (!is_object3(params)) { throw new TypeError( `Command params must be an object: ${command_ctx.command_id}` ); } return params; } function normalize_command({ action_key, action_entry, command_id, command_spec }) { const context = command_spec.context ?? "app"; const name = command_spec.name || action_entry.display_name || humanize2(action_key); if (context !== "app" && context !== "editor") { throw new TypeError(`Invalid command context: ${command_id}`); } if (typeof name !== "string" || !name.trim()) { throw new TypeError(`Invalid command name: ${command_id}`); } if (typeof action_entry.action !== "function") { throw new TypeError(`Action is not callable: ${action_key}`); } if (typeof command_spec.params !== "undefined" && typeof command_spec.params !== "function" && !is_object3(command_spec.params)) { throw new TypeError(`Invalid command params: ${command_id}`); } if (typeof command_spec.get_scope !== "undefined" && typeof command_spec.get_scope !== "function") { throw new TypeError(`Invalid command get_scope: ${command_id}`); } if (typeof command_spec.when !== "undefined" && typeof command_spec.when !== "function") { throw new TypeError(`Invalid command when: ${command_id}`); } if (typeof command_spec.hotkeys !== "undefined" && !Array.isArray(command_spec.hotkeys)) { throw new TypeError(`Invalid command hotkeys: ${command_id}`); } return { action_key, command_id, command_spec, context, name, hotkeys: command_spec.hotkeys }; } function assert_synchronous(value, message) { if (value && typeof value.then === "function") { throw new TypeError(message); } } function humanize2(action_key = "") { return String(action_key).split("_").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" "); } function is_object3(value) { return value && typeof value === "object" && !Array.isArray(value); } // node_modules/obsidian-smart-env/src/utils/ribbon_actions.js function register_ribbon_actions(plugin) { const { env, app: app2 } = plugin; const actions = env?.config?.actions; if (!is_object4(actions)) { throw new Error( "Ribbon actions require converged env.config.actions." ); } const candidates = []; const ribbon_actions = /* @__PURE__ */ new Map(); for (const [action_key, action_entry] of Object.entries(actions)) { const ribbon_icons4 = action_entry?.ribbon_icons; if (typeof ribbon_icons4 === "undefined") continue; if (!is_object4(ribbon_icons4)) { throw new TypeError( `Invalid ribbon_icons export for action: ${action_key}` ); } for (const [ribbon_id, ribbon_spec] of Object.entries(ribbon_icons4)) { if (!ribbon_id.trim() || !is_object4(ribbon_spec)) { throw new TypeError(`Invalid ribbon icon spec: ${ribbon_id}`); } if (typeof ribbon_spec.register_when !== "function") { throw new TypeError( `Ribbon register_when must be a function: ${ribbon_id}` ); } const should_register = ribbon_spec.register_when({ plugin, env, app: app2, action_key, ribbon_id }); assert_synchronous2( should_register, `Ribbon register_when must be synchronous: ${ribbon_id}` ); if (!should_register) continue; const candidate = normalize_ribbon({ action_key, action_entry, ribbon_id, ribbon_spec }); const duplicate_action_key = ribbon_actions.get(ribbon_id); if (duplicate_action_key) { throw new Error( `Duplicate ribbon icon '${ribbon_id}' for actions '${duplicate_action_key}' and '${action_key}' in plugin '${plugin.manifest.id}'.` ); } ribbon_actions.set(ribbon_id, action_key); candidates.push(candidate); } } plugin._registered_ribbon_actions ||= /* @__PURE__ */ new Map(); const registered_ribbons = plugin._registered_ribbon_actions; for (const { action_key, ribbon_id } of candidates) { const registered_action_key = registered_ribbons.get(ribbon_id); if (registered_action_key && registered_action_key !== action_key) { throw new Error( `Ribbon icon '${ribbon_id}' is already registered by action '${registered_action_key}' in plugin '${plugin.manifest.id}'.` ); } } for (const candidate of candidates) { const { action_key, ribbon_id, ribbon_spec, icon_name, description } = candidate; if (registered_ribbons.get(ribbon_id) === action_key) continue; const callback = create_ribbon_callback({ plugin, action_key, ribbon_id, ribbon_spec }); plugin.addRibbonIcon( icon_name, description, callback ); registered_ribbons.set(ribbon_id, action_key); } } function create_ribbon_callback({ plugin, action_key, ribbon_id, ribbon_spec }) { const { env, app: app2 } = plugin; const action_entry = env.config.actions[action_key]; return (click_event) => { const ribbon_ctx = { plugin, env, app: app2, action_key, ribbon_id, click_event }; try { const params = resolve_params3(ribbon_spec, ribbon_ctx); const scope = ribbon_spec.get_scope ? ribbon_spec.get_scope({ ...ribbon_ctx, params }) : resolve_action_scope( env, action_key, action_entry, params ); assert_synchronous2( scope, `Ribbon get_scope must be synchronous: ${ribbon_id}` ); if (!is_action_scope_compatible( env, action_entry.action_scope, scope )) return false; const available = ribbon_spec.when ? ribbon_spec.when({ ...ribbon_ctx, params, scope }) : true; assert_synchronous2( available, `Ribbon when must be synchronous: ${ribbon_id}` ); if (!available) return false; try { const action_result = run_action_entry(scope, action_key, params, { event_source: `ribbon:${plugin.manifest.id}:${ribbon_id}` }); if (action_result && typeof action_result.catch === "function") { void action_result.catch((error) => { console.error(`Ribbon action failed: ${ribbon_id}`, error); }); } } catch (error) { console.error(`Ribbon action failed: ${ribbon_id}`, error); } return true; } catch (error) { console.error( `Ribbon action unavailable: ${ribbon_id}`, error ); return false; } }; } function resolve_params3(ribbon_spec, ribbon_ctx) { const params = typeof ribbon_spec.params === "function" ? ribbon_spec.params(ribbon_ctx) : ribbon_spec.params ?? {}; assert_synchronous2( params, `Ribbon params must be synchronous: ${ribbon_ctx.ribbon_id}` ); if (!is_object4(params)) { throw new TypeError( `Ribbon params must be an object: ${ribbon_ctx.ribbon_id}` ); } return params; } function normalize_ribbon({ action_key, action_entry, ribbon_id, ribbon_spec }) { const { icon_name } = ribbon_spec; const description = ribbon_spec.description || action_entry.display_name || humanize3(action_key); if (typeof icon_name !== "string" || !icon_name.trim()) { throw new TypeError(`Invalid ribbon icon name: ${ribbon_id}`); } if (typeof description !== "string" || !description.trim()) { throw new TypeError(`Invalid ribbon description: ${ribbon_id}`); } if (typeof action_entry.action !== "function") { throw new TypeError(`Action is not callable: ${action_key}`); } if (typeof ribbon_spec.params !== "undefined" && typeof ribbon_spec.params !== "function" && !is_object4(ribbon_spec.params)) { throw new TypeError(`Invalid ribbon params: ${ribbon_id}`); } if (typeof ribbon_spec.get_scope !== "undefined" && typeof ribbon_spec.get_scope !== "function") { throw new TypeError(`Invalid ribbon get_scope: ${ribbon_id}`); } if (typeof ribbon_spec.when !== "undefined" && typeof ribbon_spec.when !== "function") { throw new TypeError(`Invalid ribbon when: ${ribbon_id}`); } return { action_key, ribbon_id, ribbon_spec, icon_name, description }; } function assert_synchronous2(value, message) { if (value && typeof value.then === "function") { throw new TypeError(message); } } function humanize3(action_key = "") { return String(action_key).split("_").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" "); } function is_object4(value) { return value && typeof value === "object" && !Array.isArray(value); } // node_modules/obsidian-smart-env/smart_plugin.js var SmartPlugin = class extends import_obsidian52.Plugin { SmartEnv = SmartEnv2; /** @type {TEnv} Set by `SmartEnv.create_env_getter`. */ env; /** * 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); }); } register_command_actions() { register_command_actions(this); } register_ribbon_actions() { register_ribbon_actions(this); } /** * 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(params = {}) { 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, params); } } } /** * 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(version4) { const data = await this.loadData() || {}; data.last_version = version4; 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 * @returns {{unload: () => void}} */ 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/obsidian-smart-env/src/utils/smart_drag_drop.js var SMART_DRAG_DATA_TYPE = "application/x-smart-env-drag+json"; var SMART_DRAG_SCHEMA = "smart-env-drag"; var SMART_DRAG_VERSION = 1; function has_smart_drag_data(data_transfer) { if (!data_transfer) return false; if (data_transfer.types != null) { try { return Array.from(data_transfer.types).includes(SMART_DRAG_DATA_TYPE); } catch { return false; } } if (typeof data_transfer.getData !== "function") return false; try { return Boolean(data_transfer.getData(SMART_DRAG_DATA_TYPE)); } catch { return false; } } function normalize_smart_drag_ref(collection_key, item_key) { if (typeof collection_key !== "string" || !collection_key.trim()) return null; if (typeof item_key !== "string" || !item_key.trim()) return null; return { collection_key, item_key }; } function write_smart_drag_data(data_transfer, item_or_items) { if (typeof data_transfer?.setData !== "function") return false; const raw_items = Array.isArray(item_or_items) ? item_or_items : [item_or_items]; const items = raw_items.map((item) => { return normalize_smart_drag_ref( item?.collection_key, item?.item_key || item?.key ); }).filter(Boolean); if (!items.length) return false; const payload = { schema: SMART_DRAG_SCHEMA, version: SMART_DRAG_VERSION, items }; try { data_transfer.setData( SMART_DRAG_DATA_TYPE, JSON.stringify(payload) ); return true; } catch { return false; } } function read_smart_drag_data(data_transfer) { if (typeof data_transfer?.getData !== "function") return null; let raw_payload = ""; try { raw_payload = data_transfer.getData(SMART_DRAG_DATA_TYPE); } catch { return null; } if (!raw_payload) return null; let payload; try { payload = JSON.parse(raw_payload); } catch { return null; } if (payload?.schema !== SMART_DRAG_SCHEMA) return null; if (payload?.version !== SMART_DRAG_VERSION) return null; if (!Array.isArray(payload.items)) return null; const items = payload.items.map((item) => { return normalize_smart_drag_ref( item?.collection_key, item?.item_key ); }).filter(Boolean); if (!items.length) return null; return { schema: SMART_DRAG_SCHEMA, version: SMART_DRAG_VERSION, items }; } // 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_object6(source[key]) && is_plain_object6(target[key])) { deep_merge2(target[key], source[key]); } else { target[key] = source[key]; } } return target; } function is_plain_object6(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 var empty_actions2 = Object.freeze(/* @__PURE__ */ Object.create(null)); var missing_action2 = Symbol("missing_action"); var registry_cache2 = /* @__PURE__ */ new WeakMap(); var proxy_state2 = /* @__PURE__ */ new WeakMap(); var ignored_meta_keys2 = /* @__PURE__ */ new Set(["length", "name", "prototype"]); function create_actions_proxy2(ctx, actions_source) { const actions_sources = Array.isArray(actions_source) && actions_source.length ? actions_source : [actions_source || empty_actions2]; const target = /* @__PURE__ */ Object.create(null); proxy_state2.set(target, { ctx, registry: get_actions_registry2(actions_sources) }); return new Proxy(target, actions_proxy_handler2); } var actions_proxy_handler2 = { get(target, prop) { if (prop === Symbol.toStringTag) return "ActionsProxy"; if (has_own2(target, prop)) return target[prop]; const state = proxy_state2.get(target); const action_entry = state.registry.get(state.ctx?.constructor?.key, prop); const action = materialize_action2(state.ctx, action_entry); target[prop] = action; return action; }, has(target, prop) { if (has_own2(target, prop)) return true; const state = proxy_state2.get(target); return state.registry.get(state.ctx?.constructor?.key, prop) !== missing_action2; }, ownKeys(target) { const state = proxy_state2.get(target); return Array.from(/* @__PURE__ */ new Set([ ...Reflect.ownKeys(target), ...state.registry.keys(state.ctx?.constructor?.key) ])); }, getOwnPropertyDescriptor(target, prop) { if (!has_own2(target, prop)) { const state = proxy_state2.get(target); const action_entry = state.registry.get(state.ctx?.constructor?.key, prop); if (action_entry === missing_action2) return void 0; target[prop] = materialize_action2(state.ctx, action_entry); } return { configurable: true, enumerable: true, value: target[prop] }; }, defineProperty(target, prop, descriptor) { if (!("value" in descriptor)) return false; target[prop] = descriptor.value; return true; }, set(target, prop, value) { target[prop] = value; return true; }, deleteProperty(target, prop) { if (has_own2(target, prop)) delete target[prop]; return true; } }; function get_actions_registry2(actions_sources) { let cache = registry_cache2; let cache_entry; for (const actions_source of actions_sources) { const source = actions_source || empty_actions2; cache_entry = cache.get(source); if (!cache_entry) { cache_entry = { children: /* @__PURE__ */ new WeakMap(), registry: null }; cache.set(source, cache_entry); } cache = cache_entry.children; } if (!cache_entry.registry) { cache_entry.registry = create_actions_registry2( actions_sources.map((source) => source || empty_actions2) ); } return cache_entry.registry; } function create_actions_registry2(actions_sources) { const actions_bucket_cache = /* @__PURE__ */ new WeakMap(); const get_is_actions_bucket = (value) => { if (!is_plain_object7(value)) return false; if (!actions_bucket_cache.has(value)) { actions_bucket_cache.set(value, is_actions_bucket2(value)); } return actions_bucket_cache.get(value); }; return { get(scope_key, action_key) { if (scope_key !== void 0 && scope_key !== null) { const scoped_actions = get_source_entry2(actions_sources, scope_key); if (get_is_actions_bucket(scoped_actions) && has_own2(scoped_actions, action_key)) { return scoped_actions[action_key]; } } const action_entry = get_source_entry2(actions_sources, action_key); return get_is_actions_bucket(action_entry) ? missing_action2 : action_entry; }, keys(scope_key) { const merged_keys = /* @__PURE__ */ new Set(); for (const source of actions_sources) { for (const key of Reflect.ownKeys(source)) { if (Object.prototype.propertyIsEnumerable.call(source, key)) { merged_keys.add(key); } } } const action_keys = /* @__PURE__ */ new Set(); for (const key of merged_keys) { const action_entry = get_source_entry2(actions_sources, key); if (!get_is_actions_bucket(action_entry)) action_keys.add(key); } if (scope_key !== void 0 && scope_key !== null) { const scoped_actions = get_source_entry2(actions_sources, scope_key); if (get_is_actions_bucket(scoped_actions)) { for (const key of Reflect.ownKeys(scoped_actions)) { action_keys.add(key); } } } return Array.from(action_keys); } }; } function get_source_entry2(actions_sources, key) { for (let i = actions_sources.length - 1; i >= 0; i -= 1) { const source = actions_sources[i]; if (Object.prototype.propertyIsEnumerable.call(source, key)) { return source[key]; } } return missing_action2; } function materialize_action2(ctx, action_entry) { if (action_entry === missing_action2) return void 0; if (is_class_export2(action_entry)) { const instance = new action_entry(ctx); const action = instance.action || instance.run || instance.execute || instance.call; if (typeof action === "function") { const bound = action.bind(instance); copy_metadata2(action_entry, bound); copy_metadata2(instance, bound); bound.instance = instance; return bound; } copy_metadata2(action_entry, instance); return instance; } if (is_action_object2(action_entry)) { const action_object = clone_with_descriptors2(action_entry); const bound = action_object.action.bind(ctx); copy_metadata2(action_object, bound, "action"); return bound; } if (typeof action_entry === "function") { const bound = action_entry.bind(ctx); copy_metadata2(action_entry, bound); return bound; } if (is_plain_object7(action_entry)) { return clone_with_descriptors2(action_entry); } return action_entry; } function copy_metadata2(source, target, omitted_key = null) { if (!source || !target) return; for (const key of Reflect.ownKeys(source)) { if (ignored_meta_keys2.has(key) || key === omitted_key) continue; const descriptor = Object.getOwnPropertyDescriptor(source, key); if (!descriptor) continue; try { Object.defineProperty(target, key, descriptor); } catch { target[key] = descriptor.value; } } } function clone_with_descriptors2(object) { if (!is_plain_object7(object)) return object; const clone = Object.create(Object.getPrototypeOf(object) || null); for (const key of Reflect.ownKeys(object)) { const descriptor = Object.getOwnPropertyDescriptor(object, key); if (!descriptor) continue; const next = { ...descriptor }; if ("value" in next && is_plain_object7(next.value)) { next.value = clone_with_descriptors2(next.value); } try { Object.defineProperty(clone, key, next); } catch { clone[key] = next.value; } } return clone; } function is_actions_bucket2(value) { if (!is_plain_object7(value) || is_action_object2(value)) return false; const keys = Reflect.ownKeys(value); if (!keys.length) return false; let found_action = false; for (const key of keys) { const descriptor = Object.getOwnPropertyDescriptor(value, key); if (!descriptor || !("value" in descriptor)) return false; const entry = descriptor.value; if (typeof entry === "undefined") continue; if (typeof entry === "function" || is_action_object2(entry)) { found_action = true; continue; } if (is_actions_bucket2(entry)) { found_action = true; continue; } return false; } return found_action; } function is_action_object2(value) { return is_plain_object7(value) && typeof value.action === "function"; } function is_class_export2(value) { return typeof value === "function" && /^class\s/.test(Function.prototype.toString.call(value)); } function is_plain_object7(value) { return typeof value === "object" && value !== null && !Array.isArray(value); } function has_own2(object, key) { return Object.prototype.hasOwnProperty.call(object, key); } // 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) { this.env = env; env.create_env_getter(this); this.merge_defaults(); if (data) deep_merge2(this.data, data); if (!this.data.class_name) this.data.class_name = this.collection.item_class_name; } get config() { return this.env?.config; } /** * 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|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]; } /** * Current secrets for the collection. * Initializes with empty object if none exist. * @this {CollectionThis} * @returns {Object|undefined} */ get secrets() { if (!this.env.smart_secrets) return; if (!this.env.smart_secrets.secrets[this.collection_key]) { this.env.smart_secrets.secrets[this.collection_key] = {}; } return this.env.smart_secrets.secrets[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) { this._actions = create_actions_proxy2(this, [ this.env?.config?.actions, this.env?.config?.collections?.[this.collection_key]?.actions, this.env?.opts?.collections?.[this.collection_key]?.actions, this.opts?.actions ]); } 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_object8(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function select_hidden_entries(hidden_connections) { if (!is_plain_object8(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_object8(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); const start_ms = Date.now(); let results = this.filter_and_score(params); const 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; this.emit_event("connections:get_results", { elapsed_ms: end_ms - start_ms }); 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 (!Number.isFinite(scored?.score)) { if (scored?.error) score_errors.push(scored.error); return acc; } results_acc2(acc, scored, params.limit); return acc; }, { min: Number.POSITIVE_INFINITY, minResult: null, results: /* @__PURE__ */ new Set() }); const results = Array.from(raw_results).sort(sort_by_score_descending2); if (!results.length) return results; if (!results.some((r) => r.score > 0)) 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", footer_connections_list_component_key: "connections_list_v3", connections_list_item_component_key: "connections_list_item_v3", frontmatter_filter_include: "", frontmatter_filter_exclude: "", components: { connections_list_v4: {}, 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_component_options() { return Object.entries(this.env.config.components || {}).filter(([key]) => key.startsWith("connections_list_") && !key.startsWith("connections_list_item_")).map(([value, component]) => ({ value, name: component.display_name || value, description: component.display_description })); } 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() { 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" } ]; } }, "connections_list_component_key": { group: "Display", name: "Connections List Component", type: "dropdown", description: "Select the component used to render the connections list.", options_callback: (scope2) => { return scope2.get_connections_list_component_options(); } }, "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." }, "footer_connections_list_component_key": { group: "Footer connections", name: "Footer connections list component", type: "dropdown", description: "Select the component used to render the connections list in note footers.", options_callback: (scope2) => scope2.get_connections_list_component_options() }, 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}"; // 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: "smart-context-builder", 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 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, render_connections: render_list } ); this.empty(list_container); list_container.appendChild(list); }; const run_action = (action_key, params = {}) => { const action = connections_list.actions?.[action_key]; if (typeof action !== "function") { console.warn(`Connections action unavailable: ${action_key}`); return false; } return action(params); }; if (!container._has_listeners) { container._has_listeners = true; const refresh_button = container.querySelector('[data-action="refresh-connections"]'); refresh_button?.addEventListener("click", async () => { await run_action("connections_list_refresh", { event_source: "connections_codeblock.refresh_connections", render_connections: render_list }); }); const expand_all_button = container.querySelector('[data-action="expand-all"]'); expand_all_button?.addEventListener("click", async () => { await run_action("connections_list_toggle_expanded", { connections_settings: connections_list.settings, container, expanded: true, event_source: "connections_codeblock.expand_all" }); }); const collapse_all_button = container.querySelector('[data-action="collapse-all"]'); collapse_all_button?.addEventListener("click", async () => { await run_action("connections_list_toggle_expanded", { connections_settings: connections_list.settings, container, expanded: false, event_source: "connections_codeblock.collapse_all" }); }); const context_button = container.querySelector('[data-action="send-to-smart-context"]'); context_button?.addEventListener("click", async () => { const visible_results = await get_visible_results_fallback(connections_list, opts); await run_action("connections_list_send_to_context", { visible_results, event_source: "connections_codeblock.send_to_smart_context" }); }); const copy_links_button = container.querySelector('[data-action="copy-as-links"]'); copy_links_button?.addEventListener("click", async () => { const visible_results = await get_visible_results_fallback(connections_list, opts); await run_action("connections_list_copy_as_links", { visible_results, event_source: "connections_codeblock.copy_as_links" }); }); const settings_button = container.querySelector('[data-action="open-settings"]'); settings_button?.addEventListener("click", async () => { await run_action("connections_list_open_settings", { event_source: "connections_codeblock.open_settings" }); }); const help_button = container.querySelector('[data-action="open-help"]'); help_button?.addEventListener("click", async () => { await run_action("connections_list_open_help", { event_source: "connections_codeblock.open_help" }); }); } render_list(); return container; } async function get_visible_results_fallback(connections_list, opts = {}) { const raw_results = await get_results_fallback(connections_list, opts); const connections_state = connections_list?.item?.data?.connections || {}; return filter_hidden_results(raw_results, connections_state); } 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.sc-footer-connections-icon {\n --icon-size: var(--icon-s, 16px);\n align-items: center;\n align-self: center;\n display: inline-flex;\n flex: 0 0 var(--icon-size);\n height: var(--icon-size);\n justify-content: center;\n line-height: 0;\n margin-inline-end: var(--size-2-2, 4px);\n width: var(--icon-size);\n\n svg {\n display: block;\n height: 100%;\n width: 100%;\n }\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(app2) { return app2.loadLocalStorage(FOOTER_FOLDED_STORAGE_KEY) === "true"; } function set_footer_connections_folded(app2, folded) { app2.saveLocalStorage(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(view.app); set_footer_connections_folded(view.app, next_folded); apply_footer_fold_state(header_container, list_container, next_folded); }); apply_footer_fold_state(header_container, list_container, get_footer_connections_folded(view.app)); if (!connections_item) { return container; } const connections_list = connections_item.connections || env.connections_lists.new_item(connections_item); const connections_list_component_key = opts.connections_list_component_key || env.connections_lists.settings.footer_connections_list_component_key || "connections_list_v3"; const list = await env.smart_components.render_component( connections_list_component_key, connections_list, { ...opts, render_connections: view.render_view.bind(view) } ); 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); write_smart_drag_data(event.dataTransfer, item); 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) || ArrayBuffer.isView(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_EXPECTED_MAJOR = "7."; var d3_import_promise = null; function validate_d3_instance(d3) { if (!d3) return; const version4 = String(d3.version || ""); if (version4 && !version4.startsWith(D3_EXPECTED_MAJOR)) { console.warn( `[connections_graph] Loaded d3 version "${version4}" but expected v${D3_EXPECTED_MAJOR}x` ); } } async function load_d3() { const g = typeof activeWindow !== "undefined" ? activeWindow : typeof window !== "undefined" ? window : {}; if (g.d3) { validate_d3_instance(g.d3); return g.d3; } if (!d3_import_promise) { d3_import_promise = import("https://cdn.jsdelivr.net/npm/d3@7/+esm").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 score = Number.isFinite(res?.score) ? +res.score : Number.isFinite(res?.og_score) ? +res.og_score : center_vec && v ? cos_sim2(center_vec, v) : null; 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, // 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 label_width = typeof this.getComputedTextLength === "function" ? this.getComputedTextLength() : 0; const { anchor, offset } = label_anchor_offset(d.x, { center_x, label_width, view_width, radius: d.radius, margin: LABEL_MARGIN }); d3.select(this).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_obsidian53 = 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 score_display = result.score_display ?? 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_display, 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 connections_list = result_scope.connections_list; const raw_results = Array.isArray(connections_list?.results) ? connections_list.results : []; const visible_results = filter_hidden_results(raw_results, source_item.data.connections); const list_container = container.closest(".connections-list") || container; const target_name = get_item_display_name3(item, component_settings) || item.key; const menu = new import_obsidian53.Menu(app2); env.build_menu?.("connections:list_item_menu", menu, connections_list, { container, prefixed_key, target_item: item, target_name }); menu.addSeparator(); env.build_menu?.("connections:list_menu", menu, connections_list, { container: list_container, connections_settings, visible_results, render_connections: params.render_connections }); 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_name10 = "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); 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_name11 = "Version 4.0 (Graph + List)"; var settings_config12 = {}; // node_modules/obsidian-smart-env/src/modals/story.js var import_obsidian54 = require("obsidian"); var StoryModal = class _StoryModal extends import_obsidian54.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_obsidian54.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/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 &.is-drag-over {\n border-radius: var(--radius-s);\n background: color-mix(in srgb, var(--interactive-accent) 6%, transparent);\n box-shadow: inset 0 0 0 2px var(--interactive-accent);\n }\n\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 }\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]; } // node_modules/obsidian-smart-env/src/utils/parse_dropped_obsidian_data.js function normalize_path(value) { const path = String(value || "").trim().replace(/\\+/g, "/"); if (path === "/" || /^[a-zA-Z]:\/$/u.test(path)) return path; return path.replace(/\/+$/g, ""); } function has_file_extension(value) { return /\.[^./\\]+$/u.test(String(value || "")); } function is_absolute_path(value) { return value.startsWith("/") || /^[a-zA-Z]:\//u.test(value); } function split_lines(value) { return String(value || "").split(/\r?\n/u).map((row) => row.trim()).filter(Boolean); } function get_obsidian_uri_file(row) { try { const url = new URL(row); return url.searchParams.get("file") || ""; } catch (_) { const match = row.match(/file=([^&\s]+)/u); if (!match) return ""; try { return decodeURIComponent(match[1]); } catch (_2) { return match[1]; } } } function add_entry(entries, seen, { raw_value, normalized_path, origin, kind_hint = null, appended_md = false }) { const path = normalize_path(normalized_path); if (!path) return; const entry = { raw_value: String(raw_value || ""), normalized_path: path, origin, kind_hint, appended_md }; const entry_key = [ entry.normalized_path, entry.origin, entry.kind_hint || "", entry.appended_md ? "1" : "0" ].join("\0"); if (seen.has(entry_key)) return; seen.add(entry_key); entries.push(entry); } function get_text_rows(data_transfer) { const rows = [ ...split_lines(data_transfer?.getData?.("text/uri-list")).map((value) => ({ value, origin: "uri_list" })), ...split_lines(data_transfer?.getData?.("text/plain")).map((value) => ({ value, origin: "plain_text" })) ]; const merged_rows = []; for (let row_i = 0; row_i < rows.length; row_i += 1) { const row = rows[row_i]; const next_row = rows[row_i + 1]; if (row.value.startsWith("obsidian:") && !row.value.startsWith("obsidian://") && next_row?.value?.startsWith("/")) { merged_rows.push({ value: row.value + next_row.value, origin: row.origin }); row_i += 1; continue; } merged_rows.push(row); } return merged_rows.flatMap((row) => { if (!row.value.startsWith("obsidian://")) return [row]; return row.value.split(/(?=obsidian:\/\/)/gu).map((value) => value.trim()).filter(Boolean).map((value) => ({ value, origin: row.origin })); }); } function parse_dropped_obsidian_entries(data_transfer) { const entries = []; const seen = /* @__PURE__ */ new Set(); if (data_transfer?.files?.length) { [...data_transfer.files].forEach((file) => { const file_path = file?.path || file?.name; if (!file_path) return; add_entry(entries, seen, { raw_value: file_path, normalized_path: file_path, origin: "file_list", kind_hint: "file" }); }); } get_text_rows(data_transfer).forEach(({ value, origin }) => { if (value.startsWith("obsidian://")) { const file_path = get_obsidian_uri_file(value); if (!file_path) return; const appended_md2 = !has_file_extension(file_path); add_entry(entries, seen, { raw_value: value, normalized_path: appended_md2 ? `${file_path}.md` : file_path, origin: "obsidian_uri", kind_hint: "file", appended_md: appended_md2 }); return; } const kind_hint = /[\\/]$/u.test(value) ? "folder" : null; const appended_md = kind_hint === null && !has_file_extension(value); add_entry(entries, seen, { raw_value: value, normalized_path: appended_md ? `${value}.md` : value, origin, kind_hint, appended_md }); }); return entries; } function get_dropped_obsidian_entry_path(entry, vault_path = "") { const entry_path = normalize_path(entry?.normalized_path); if (!entry_path) return ""; if (!is_absolute_path(entry_path)) return entry_path.replace(/^\.\//u, ""); const normalized_vault_path = normalize_path(vault_path); if (!normalized_vault_path || !is_absolute_path(normalized_vault_path)) { return ""; } const case_insensitive = /^[a-zA-Z]:\//u.test(normalized_vault_path); const comparable_entry_path = case_insensitive ? entry_path.toLowerCase() : entry_path; const comparable_vault_path = case_insensitive ? normalized_vault_path.toLowerCase() : normalized_vault_path; const vault_prefix = comparable_vault_path.endsWith("/") ? comparable_vault_path : `${comparable_vault_path}/`; if (!comparable_entry_path.startsWith(vault_prefix)) return ""; return entry_path.slice(vault_prefix.length); } function normalize_paths(paths) { return Array.from(new Set( (Array.isArray(paths) ? paths : []).map(normalize_path).filter(Boolean) )); } function get_exact_candidates({ original_path, inferred_file_path, file_paths, folder_paths }) { const candidates = []; if (file_paths.includes(original_path)) { candidates.push({ status: "exact", kind: "file", path: original_path }); } if (folder_paths.includes(original_path)) { candidates.push({ status: "exact", kind: "folder", path: original_path }); } if (inferred_file_path && inferred_file_path !== original_path && file_paths.includes(inferred_file_path)) { candidates.push({ status: "recovered", kind: "file", path: inferred_file_path }); } return candidates; } function get_recovered_candidates({ original_path, inferred_file_path, file_paths, folder_paths }) { const candidates = []; const file_inputs = inferred_file_path ? [original_path, inferred_file_path] : [original_path]; file_inputs.forEach((input_path) => { file_paths.filter((path) => path.endsWith(`/${input_path}`)).forEach((path) => candidates.push({ status: "recovered", kind: "file", path })); }); folder_paths.filter((path) => path.endsWith(`/${original_path}`)).forEach((path) => candidates.push({ status: "recovered", kind: "folder", path })); const candidates_by_key = /* @__PURE__ */ new Map(); candidates.forEach((candidate) => { const key = `${candidate.kind}:${candidate.path}`; if (!candidates_by_key.has(key)) candidates_by_key.set(key, candidate); }); return Array.from(candidates_by_key.values()); } function get_matching_candidates(candidates, kind_hint) { return kind_hint ? candidates.filter((candidate) => candidate.kind === kind_hint) : candidates; } function is_candidate_available(candidate, available_file_paths, available_folder_paths) { const available_paths = candidate.kind === "folder" ? available_folder_paths : available_file_paths; return available_paths.includes(candidate.path); } function classify_dropped_obsidian_entry(entry, options = {}) { const file_paths = normalize_paths(options.file_paths); const folder_paths = normalize_paths(options.folder_paths); const available_file_paths = options.available_file_paths === void 0 ? file_paths : normalize_paths(options.available_file_paths); const available_folder_paths = options.available_folder_paths === void 0 ? folder_paths : normalize_paths(options.available_folder_paths); const entry_path = get_dropped_obsidian_entry_path( entry, options.vault_path ); if (!entry_path) { return { status: "unresolved", kind: null, path: null }; } const appended_md = entry?.appended_md === true && entry_path.endsWith(".md"); const original_path = appended_md ? entry_path.slice(0, -3) : entry_path; const inferred_file_path = appended_md ? entry_path : ""; const kind_hint = entry?.kind_hint === "file" || entry?.kind_hint === "folder" ? entry.kind_hint : null; const exact_candidates = get_exact_candidates({ original_path, inferred_file_path, file_paths, folder_paths }); if (exact_candidates.length) { const matching_candidates2 = get_matching_candidates( exact_candidates, kind_hint ); if (matching_candidates2.length !== 1) { return { status: matching_candidates2.length > 1 ? "ambiguous" : "unresolved", kind: null, path: null }; } const candidate2 = matching_candidates2[0]; return is_candidate_available( candidate2, available_file_paths, available_folder_paths ) ? candidate2 : { status: "exact_unavailable", kind: candidate2.kind, path: candidate2.path }; } const recovered_candidates = get_recovered_candidates({ original_path, inferred_file_path, file_paths, folder_paths }); const matching_candidates = get_matching_candidates( recovered_candidates, kind_hint ); if (matching_candidates.length !== 1) { return { status: matching_candidates.length > 1 ? "ambiguous" : "unresolved", kind: null, path: null }; } const candidate = matching_candidates[0]; return is_candidate_available( candidate, available_file_paths, available_folder_paths ) ? candidate : { status: "exact_unavailable", kind: candidate.kind, path: candidate.path }; } // src/utils/resolve_dropped_connections_targets.js var SMART_CONNECTIONS_COLLECTION_KEYS = /* @__PURE__ */ new Set([ "smart_sources", "smart_blocks" ]); function get_collection_item(env, collection_key, item_key) { return env?.[collection_key]?.get?.(item_key) || env?.[collection_key]?.items?.[item_key] || null; } function is_connections_target(item) { return Boolean(item?.key && item?.vec); } function get_smart_targets(env, data_transfer) { const smart_drag_data = read_smart_drag_data(data_transfer); if (!smart_drag_data) return []; const targets = []; for (const { collection_key, item_key } of smart_drag_data.items) { if (!SMART_CONNECTIONS_COLLECTION_KEYS.has(collection_key)) return []; const target = get_collection_item(env, collection_key, item_key); if (!is_connections_target(target)) return []; targets.push(target); } return targets; } function get_native_targets(env, data_transfer) { const entries = parse_dropped_obsidian_entries(data_transfer); if (!entries.length) return []; const smart_sources = env?.smart_sources; const smart_fs = smart_sources?.fs || env?.fs; const source_items = Object.values(smart_sources?.items || {}); const file_paths = Array.from(/* @__PURE__ */ new Set([ ...smart_fs?.file_paths || [], ...source_items.map((source) => source?.key).filter(Boolean) ])); const available_file_paths = source_items.filter(is_connections_target).map((source) => source.key); const folder_paths = smart_fs?.folder_paths || []; const vault_path = smart_fs?.base_path || ""; const targets = []; for (const entry of entries) { const entry_path = get_dropped_obsidian_entry_path(entry, vault_path); const block = get_collection_item(env, "smart_blocks", entry_path); if (block) { if (!is_connections_target(block)) return []; targets.push(block); continue; } const classified_entry = classify_dropped_obsidian_entry(entry, { file_paths, folder_paths, available_file_paths, vault_path }); if (classified_entry.kind !== "file" || classified_entry.status !== "exact" && classified_entry.status !== "recovered") { return []; } const target = get_collection_item( env, "smart_sources", classified_entry.path ); if (!is_connections_target(target)) return []; targets.push(target); } return targets; } function resolve_dropped_connections_targets(env, data_transfer) { const targets = has_smart_drag_data(data_transfer) ? get_smart_targets(env, data_transfer) : get_native_targets(env, data_transfer); const unique_targets = /* @__PURE__ */ new Map(); targets.forEach((target) => { const key = `${target.collection_key || ""}:${target.key}`; if (!unique_targets.has(key)) unique_targets.set(key, target); }); return Array.from(unique_targets.values()); } // src/components/connections-view/v3.js var CONNECTIONS_TARGET_HISTORY_LIMIT = 10; 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); const connections_settings = opts.connections_settings ?? connections_list?.settings; record_connections_target_history(view, connections_item); container._connections_menu_state = { view, container, connections_list, connections_settings }; if (!container._has_listeners) { container._has_listeners = true; const pause_button = container.querySelector('[data-action="toggle-pause"]'); pause_button?.addEventListener("click", async () => { const state = container._connections_menu_state; const action = env.config?.actions?.connections_list_toggle_paused?.action; if (typeof action === "function") { await action.call(state.view, { event_source: "connections_view.toggle_pause" }); } }); const menu_button = container.querySelector('[data-action="open-menu"]'); menu_button?.addEventListener("click", (event) => { const menu = new import_obsidian57.Menu(view.plugin.app); const state = container._connections_menu_state; env.build_menu?.( "connections:item_view_list_menu", menu, state.view ); if (menu.items?.length) menu.addSeparator(); const raw_results = Array.isArray(state.connections_list?.results) ? state.connections_list.results : []; const connections_state = state.connections_list?.item?.data?.connections || {}; const visible_results = filter_hidden_results( raw_results, connections_state ); env.build_menu?.( "connections:list_menu", menu, state.connections_list, { container: state.container, connections_settings: state.connections_settings, visible_results, render_connections: state.view.render_view.bind(state.view) } ); menu.showAtMouseEvent(event); }); const open_target_menu = (event) => { event.preventDefault(); event.stopPropagation(); const state = container._connections_menu_state; const menu = new import_obsidian57.Menu(view.plugin.app); env.build_menu?.("connections:target_menu", menu, state.view); if (!(menu.items?.length > 0)) return; show_menu(menu, event, sc_top_bar_context); }; sc_top_bar_context?.addEventListener("click", open_target_menu); sc_top_bar_context?.addEventListener("keydown", (event) => { if (event.key !== "Enter" && event.key !== " ") return; open_target_menu(event); }); const set_target_drag_over = (active) => { container.classList.toggle("is-drag-over", Boolean(active)); }; const on_target_dragenter = (event) => { event.preventDefault(); set_target_drag_over(true); }; const on_target_dragover = (event) => { event.preventDefault(); set_target_drag_over(true); if (event.dataTransfer) event.dataTransfer.dropEffect = "copy"; }; const on_target_dragleave = (event) => { if (event.relatedTarget && container.contains(event.relatedTarget)) return; set_target_drag_over(false); }; const on_target_drop = async (event) => { event.preventDefault(); event.stopPropagation(); set_target_drag_over(false); const targets = resolve_dropped_connections_targets(env, event.dataTransfer); if (!targets.length) { new import_obsidian57.Notice("Drop one indexed note or block into Connections."); return; } if (targets.length > 1) { new import_obsidian57.Notice("Drop one Connections target at a time."); return; } const state = container._connections_menu_state; const action = env.config?.actions?.connections_list_select_target?.action; if (typeof action !== "function") return; await action.call(state.view, { target_item: targets[0], event_source: "connections_view.drop_target" }); }; container.addEventListener("dragenter", on_target_dragenter); container.addEventListener("dragover", on_target_dragover); container.addEventListener("dragleave", on_target_dragleave); container.addEventListener("drop", on_target_drop); } 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, container, render_connections: view.render_view.bind(view) }); 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 = entity?.key || connections_item.key; sc_top_bar_context.title = "Change connections target"; 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; } function record_connections_target_history(view, target_item) { const source_key = String(target_item?.key || "").split("#")[0]; if (!view || !source_key) return; const history = Array.isArray(view.connections_target_history) ? view.connections_target_history : []; view.connections_target_history = [ source_key, ...history.filter((key) => key !== source_key) ].slice(0, CONNECTIONS_TARGET_HISTORY_LIMIT); } function show_menu(menu, event, anchor_el) { if (typeof MouseEvent !== "undefined" && event instanceof MouseEvent) { menu.showAtMouseEvent(event); return; } const rect = anchor_el.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 })); } // 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/actions/connections-list/copy_as_links.js function get_links_payload(connections_list, params = {}) { const results = Array.isArray(params.visible_results) ? params.visible_results : connections_list?.results || []; return format_connections_as_links(results); } async function connections_list_copy_as_links(params = {}) { const event_source = params.event_source || "connections_list.copy_as_links"; const links_payload = get_links_payload(this, params); if (!links_payload) { this.env.events.emit("connections:copy_list_empty", { level: "warning", message: "No connection results to copy.", event_source }); return false; } const copied = await copy_to_clipboard(links_payload, { env: this.env, event_source, success_event_key: "connections:list_copied", error_event_key: "connections:list_copy_failed", unavailable_event_key: "connections:list_copy_unavailable" }); if (!copied) return false; this.emit_event("connections:copied_list", { level: "info", message: "Copied connection links to clipboard." }); return true; } var menus18 = { "connections:list_menu": { title: "Copy as list of links", icon: "copy", order: 20, disabled() { return !get_links_payload(this.scope, this.params); } } }; // src/actions/connections-list/get_results.js async function connections_list_get_results(params = {}) { const target_key = to_trimmed_string4(params.to); if (!target_key) throw new Error("Missing required argument: to"); const source = this.env.smart_sources?.get?.(target_key) || this.env.smart_sources?.items?.[target_key]; if (!source) { throw new Error(`No Smart Source found for "${target_key}".`); } const connections_list = source.connections || this.new_item?.(source); if (!connections_list) { throw new Error("Unable to create Smart Connections list."); } const results = await connections_list.get_results?.({}); const normalized_results = Array.isArray(results) ? results.map(to_result).filter(Boolean) : []; return { ok: true, to: source.key || target_key, total: normalized_results.length, results: normalized_results }; } var display_name12 = "List Smart Connections"; var display_description6 = "Returns ranked Smart Connections for a source key."; var input_schema2 = { type: "object", properties: { to: { type: "string", minLength: 1, description: "Source key or vault-relative path." } }, required: ["to"], additionalProperties: false }; var output_schema2 = { type: "object", properties: { ok: { type: "boolean" }, to: { type: "string" }, total: { type: "integer" }, results: { type: "array", items: { type: "object", properties: { key: { type: "string" }, collection_key: { type: "string" }, score: { type: ["number", "null"] } }, required: ["key", "collection_key", "score"], additionalProperties: false } } }, required: ["ok", "to", "total", "results"], additionalProperties: false }; var action_scope3 = { type: "collection", collection_key: "connections_lists" }; var tool2 = { name: "smart_connections_list", when({ env }) { return Boolean(env.connections_lists && env.smart_sources); }, effects: { read_only: true, destructive: false, idempotent: true } }; function to_result(result) { const item = result?.item; const key = to_trimmed_string4(item?.key) || to_trimmed_string4(item?.data?.key) || to_trimmed_string4(item?.path); if (!key) return null; return { key, collection_key: to_trimmed_string4(item?.collection_key), score: Number.isFinite(result?.score) ? result.score : null }; } function to_trimmed_string4(value) { return typeof value === "string" ? value.trim() : ""; } // src/actions/connections-list-item/hide.js var SC_RESULT_HIDDEN_CLASS2 = "sc-result-hidden-by-feedback"; function connections_list_item_hide(params = {}) { const source_item = this?.item; const target_item = params.target_item; const prefixed_key = params.prefixed_key || build_prefixed_connection_key(target_item?.collection_key, target_item?.key); const env = this?.env || source_item?.env || target_item?.env; if (!source_item || !target_item || !prefixed_key) return false; try { source_item.data.connections ||= {}; apply_hidden_state(source_item.data.connections, prefixed_key, Date.now()); if (source_item.data.hidden_connections) { delete source_item.data.hidden_connections[target_item.key]; if (!Object.keys(source_item.data.hidden_connections).length) { delete source_item.data.hidden_connections; } } source_item.queue_save(); params.container?.classList?.add(SC_RESULT_HIDDEN_CLASS2); if (params.container?.dataset) params.container.dataset.hidden = "true"; source_item.collection.save(); source_item.emit_event("connections:hidden_item"); return true; } catch (err) { env?.events?.emit?.("connections:hide_failed", { level: "error", message: "Hide failed - check console", details: err?.message || "", event_source: "connections_list_item.contextmenu" }); console.error(err); return false; } } var menus19 = { "connections:list_item_menu": { title() { return `Hide ${this.params.target_name || this.params.target_item?.key || "connection"}`; }, icon: "eye-off", order: 10 } }; // src/actions/connections-list-item/toggle_pinned.js function get_menu_pinned_state(menu_ctx) { const source_item = menu_ctx.scope?.item; const target_item = menu_ctx.params.target_item; const prefixed_key = menu_ctx.params.prefixed_key || build_prefixed_connection_key(target_item?.collection_key, target_item?.key); return is_connection_pinned(source_item?.data?.connections, prefixed_key); } function connections_list_item_toggle_pinned(params = {}) { const source_item = this?.item; const target_item = params.target_item; const prefixed_key = params.prefixed_key || build_prefixed_connection_key(target_item?.collection_key, target_item?.key); const env = this?.env || source_item?.env || target_item?.env; if (!source_item || !target_item || !prefixed_key) return false; try { source_item.data.connections ||= {}; const pinned = is_connection_pinned(source_item.data.connections, prefixed_key); if (pinned) { remove_pinned_state(source_item.data.connections, prefixed_key); params.container?.classList?.remove("sc-result-pinned"); params.container?.removeAttribute?.("data-pinned"); } else { apply_pinned_state(source_item.data.connections, prefixed_key, Date.now()); params.container?.classList?.add("sc-result-pinned"); if (params.container?.dataset) params.container.dataset.pinned = "true"; source_item.emit_event("connections:pinned_item"); } source_item.queue_save(); source_item.collection.save(); return true; } catch (err) { const title_prefix = is_connection_pinned(source_item?.data?.connections, prefixed_key) ? "Unpin" : "Pin"; env?.events?.emit?.("connections:pin_toggle_failed", { level: "error", message: `${title_prefix} failed - check console`, details: err?.message || "", event_source: "connections_list_item.contextmenu" }); console.error(err); return false; } } var menus20 = { "connections:list_item_menu": { title() { const title_prefix = get_menu_pinned_state(this) ? "Unpin" : "Pin"; return `${title_prefix} ${this.params.target_name || this.params.target_item?.key || "connection"}`; }, icon() { return get_menu_pinned_state(this) ? "pin-off" : "pin"; }, order: 20 } }; // src/actions/connections-list/open_help.js var HELP_TITLE = "Getting Started With Smart Connections"; var HELP_URL = "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=connections-view-help#page=understanding-connections-1"; var COMMAND_HELP_URL = "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=sc-op-command"; function connections_list_open_help(params = {}) { const plugin = params.plugin || this.env?.smart_connections_plugin || this.env?.plugin || this.env?.main; if (!plugin) return false; StoryModal.open(plugin, { title: HELP_TITLE, url: params.url || HELP_URL }); return true; } var commands4 = { "smart-connections-getting-started": { name: "Show: Getting started slideshow", register_when({ plugin }) { return plugin.manifest.id === "smart-connections"; }, params({ plugin }) { return { plugin, url: COMMAND_HELP_URL }; }, get_scope({ env }) { return env.connections_lists; } } }; var menus21 = { "connections:list_menu": { title: "Help & getting started", icon: "help-circle", order: 100 } }; // src/actions/connections-list/open_random_connection.js var import_obsidian59 = require("obsidian"); // 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/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_list = source.connections || env.connections_lists?.new_item?.(source); if (typeof connections_list?.get_results !== "function") return null; let connections = []; try { connections = await connections_list.get_results({ limit: DEFAULT_RESULTS_LIMIT }); } catch (err) { console.error("get_random_connection: failed to get connections", err); return null; } 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/actions/connections-list/open_random_connection.js var NATIVE_RANDOM_NOTE_COMMAND_IDS = [ "random-note:open", "random-note" ]; async function connections_list_open_random_connection(params = {}) { const env = this?.env || params.plugin?.env || params.env; const app2 = get_app(env, params); const file_path = resolve_file_path(this, params, app2); if (!file_path) { if (run_native_random_note_command(app2)) return true; new import_obsidian59.Notice("Open a note to get a random connection to the current note."); env?.events?.emit?.("connections:open_random_unavailable", { level: "warning", message: "Open a note to get a random connection to the current note.", event_source: params.event_source || "connections_list_open_random_connection" }); return false; } const rand_entity = await get_random_connection(env, file_path); const target_path = rand_entity?.item?.path || rand_entity?.item?.key; if (!target_path) { new import_obsidian59.Notice("No random connection found for the current note."); env?.events?.emit?.("connections:open_random_unavailable", { level: "warning", message: `No random connection found for ${file_path}.`, event_source: params.event_source || "connections_list_open_random_connection" }); return false; } await open_random_connection_target(env, target_path, params); env?.events?.emit?.("connections:open_random", { source_key: file_path, target_key: target_path, event_source: params.event_source || "connections_list_open_random_connection" }); return true; } function get_app(env, params = {}) { return params.app || params.plugin?.app || env?.obsidian_app || env?.plugin?.app || activeWindow.app || null; } function resolve_file_path(scope, params = {}, app2 = null) { const key = params.file_path || params.target_item?.key || params.source_item?.key || scope?.item?.key || ""; if (key) return String(key).split("#")[0]; return app2?.workspace?.getActiveFile?.()?.path || ""; } async function open_random_connection_target(env, target_path, params = {}) { const plugin = params.plugin || env?.smart_connections_plugin || env?.plugin || env?.main || null; const event = params.click_event || params.event || null; if (typeof plugin?.open_note === "function") { await plugin.open_note(target_path, event); return; } if (plugin) { await open_note(plugin, target_path, event); } } function run_native_random_note_command(app2) { const command_id = get_native_random_note_command_id(app2); if (!command_id) return false; app2.commands.executeCommandById(command_id); return true; } function get_native_random_note_command_id(app2) { const commands9 = app2?.commands?.commands || {}; for (const command_id of NATIVE_RANDOM_NOTE_COMMAND_IDS) { if (commands9[command_id]) return command_id; } const match = Object.entries(commands9).find(([command_id, command]) => { return command_id.startsWith("random-note") && /random note/i.test(command?.name || ""); }); return match?.[0] || ""; } var commands5 = { "smart-connections-random": { name: "Open: Random note from connections", register_when({ plugin }) { return plugin.manifest.id === "smart-connections"; }, params({ plugin }) { return { plugin }; }, get_scope({ env }) { return env.connections_lists; } } }; var ribbon_icons = { random_note: { icon_name: "smart-dice", description: "Smart Connections: Open random connection", register_when({ plugin }) { return plugin.manifest.id === "smart-connections"; }, get_scope({ env }) { return env.connections_lists; } } }; var menus22 = { "connections:list_menu": { title: "Open random connection", icon: "smart-dice", order: 40, params(_menu_ctx, event) { return { event }; } } }; // src/actions/connections-list/open_settings.js async function connections_list_open_settings() { const plugin = this.env?.smart_connections_plugin || this.env?.plugin || this.env?.main; const app2 = plugin?.app || this.env?.obsidian_app; if (!app2?.setting) return false; await app2.setting.open?.(); await app2.setting.openTabById?.(plugin?.manifest?.id || "smart-connections"); return true; } var menus23 = { "connections:list_menu": { title: "Connections settings", icon: "settings", order: 90 } }; // src/actions/connections-list/open_view.js async function connections_list_open_view(params = {}) { const plugin = params.plugin || this?.env?.smart_connections_plugin; if (typeof plugin?.open_connections_view !== "function") return false; await plugin.open_connections_view(); return true; } var ribbon_icons2 = { connections: { icon_name: "smart-connections", description: "Smart Connections: Open connections view", register_when({ plugin }) { return plugin.manifest.id === "smart-connections"; }, params({ plugin }) { return { plugin }; }, get_scope({ env }) { return env.connections_lists; } } }; var commands6 = { "smart-connections-view": { name: "Open: Connections view", register_when({ plugin }) { return plugin.manifest.id === "smart-connections"; }, params({ plugin }) { return { plugin }; }, get_scope({ env }) { return env.connections_lists; } } }; // 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); } }); } // src/actions/connections-list/refresh.js async function connections_list_refresh(params = {}) { const refresh_entity = this.item; if (!refresh_entity) return false; await refresh_entity.read(); refresh_entity.queue_import(); await refresh_entity.collection.process_source_import_queue?.(); await params.render_connections?.({ connections_item: refresh_entity, force: true }); return true; } var menus24 = { "connections:list_menu": { title: "Refresh connections", icon: "refresh-cw", order: 10 } }; // src/actions/connections-list/select_target.js async function connections_list_select_target(params = {}) { const target_item = params.target_item; if (!target_item || !this?.select_target) return false; return await this.select_target(target_item, { event_source: params.event_source || "connections_list_select_target" }); } var display_name13 = "Select Connections target"; var menus25 = { "connections:item_view_list_menu": { title: "Change target", icon: "crosshair", order: 15, build() { this.menu.addItem((item) => { item.setTitle("Change target").setIcon("crosshair"); const submenu = item.setSubmenu(); this.env.build_menu?.( "connections:target_menu", submenu, this.scope ); item.setDisabled?.(!(submenu.items?.length > 0)); }); } } }; // src/actions/connections-list/send_to_context.js var SMART_CONTEXT_URL = "https://smartconnections.app/smart-context/"; function connections_list_send_to_context(params = {}) { const event_source = params.event_source || "connections_list_send_to_context"; if (this?.env?.event_logs?.settings?.native_notice_attention) { this?.env?.events?.emit?.( "connections:smart_context_link_unavailable", { level: "attention", message: "Smart Context plugin is required.", event_source, link: SMART_CONTEXT_URL, hide_mute_button: true } ); return true; } const open_url = activeWindow?.open || window?.open || open; if (typeof open_url !== "function") return false; open_url(SMART_CONTEXT_URL, "_external"); return true; } var menus26 = { "connections:list_menu": { title: "Send to Smart Context", icon: "smart-context-builder", order: 20 } }; var version2 = "0.0.1"; // src/actions/connections-list/send_to_smart_graph.js var SMART_GRAPH_URL = "https://smartconnections.app/smart-graph/"; function connections_list_send_to_smart_graph() { if (this?.env?.event_logs?.settings?.native_notice_attention) { this?.env?.events?.emit?.("connections:smart_graph_link_unavailable", { level: "attention", message: "Smart Graph plugin is required.", event_source: "connections_list_send_to_smart_graph", link: SMART_GRAPH_URL, hide_mute_button: true }); return; } activeWindow.open(SMART_GRAPH_URL, "_external"); return; } var menus27 = { "connections:list_menu": { title: "Explore in Smart Graph", icon: "smart-graph", order: 30, when() { return !this.env?.smart_graph_plugin?._loaded; } } }; var version3 = "0.0.1"; // src/actions/connections-list/toggle_expanded.js function connections_list_toggle_expanded(params = {}) { const connections_settings = params.connections_settings ?? this?.settings; const current_expanded = Boolean(connections_settings?.expanded_view); const expanded = typeof params.expanded === "boolean" ? params.expanded : !current_expanded; if (connections_settings) connections_settings.expanded_view = expanded; params.container?.querySelectorAll(".sc-result").forEach((element) => { expanded ? element.classList.remove("sc-collapsed") : element.classList.add("sc-collapsed"); }); return true; } var menus28 = { "connections:list_menu": { title() { const connections_settings = this.params.connections_settings ?? this.scope?.settings; return connections_settings?.expanded_view ? "Collapse all results" : "Expand all results"; }, icon() { const connections_settings = this.params.connections_settings ?? this.scope?.settings; return connections_settings?.expanded_view ? "fold-vertical" : "unfold-vertical"; }, order: 10 } }; // src/actions/connections-list/toggle_footer_connections.js function connections_list_toggle_footer_connections() { const settings = this?.settings; if (!settings) return false; settings.footer_connections = !settings.footer_connections; return true; } var commands7 = { "toggle-footer-connections": { name: "Toggle: Footer connections", register_when({ plugin }) { return plugin.manifest.id === "smart-connections"; }, get_scope({ env }) { return env.connections_lists; } } }; var ribbon_icons3 = { footer_connections: { icon_name: "smart-footer-connections", description: "Toggle Footer Connections", register_when({ plugin }) { return plugin.manifest.id === "smart-connections"; }, get_scope({ env }) { return env.connections_lists; } } }; // src/actions/connections-list/toggle_paused.js async function connections_list_toggle_paused(params = {}) { if (!this?.toggle_paused) return false; await this.toggle_paused({ event_source: params.event_source || "connections_list_toggle_paused" }); return true; } var menus29 = { "connections:item_view_list_menu": { title() { return this.scope?.paused ? "Resume auto-refresh" : "Pause auto-refresh"; }, icon() { return this.scope?.paused ? "play-circle" : "pause-circle"; }, order: 0 } }; // src/actions/connections-list/unhide_all.js function connections_list_unhide_all(params = {}) { const source_item = this.item; if (!source_item?.data?.connections) return false; try { const changed = remove_all_hidden_states(source_item.data.connections); if (!changed) return false; if (source_item.data.hidden_connections) delete source_item.data.hidden_connections; source_item.queue_save(); params.render_connections?.({ connections_item: source_item, force: true }); source_item.collection.save(); return true; } catch (err) { this.env?.events?.emit?.("connections:unhide_failed", { level: "error", message: "Unhide failed - check console", details: err?.message || "", event_source: "connections_list.unhide_all" }); console.error(err); return false; } } var menus30 = { "connections:list_menu": { title() { const hidden_count = count_hidden_connections(this.scope?.item?.data?.connections); return `Unhide All (${hidden_count})`; }, icon: "eye", order: 60, disabled() { return !count_hidden_connections(this.scope?.item?.data?.connections); } } }; // src/actions/connections-list/unpin_all.js function connections_list_unpin_all(params = {}) { const source_item = this.item; if (!source_item?.data?.connections) return false; try { const changed = remove_all_pinned_states(source_item.data.connections); if (!changed) return false; params.container?.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(); return true; } catch (err) { this.env?.events?.emit?.("connections:unpin_failed", { level: "error", message: "Unpin failed - check console", details: err?.message || "", event_source: "connections_list.unpin_all" }); console.error(err); return false; } } var menus31 = { "connections:list_menu": { title() { const pinned_count = count_pinned_connections(this.scope?.item?.data?.connections); return `Unpin All (${pinned_count})`; }, icon: "pin-off", order: 70, disabled() { return !count_pinned_connections(this.scope?.item?.data?.connections); } } }; // src/actions/connections-target/blocks.js function connections_target_blocks() { const current_key = this.current?.key || ""; const source_key = current_key.split("#")[0]; const source = this.env.smart_sources?.get?.(source_key) || this.env.smart_sources?.items?.[source_key]; const blocks = Array.isArray(source?.blocks) ? [...source.blocks] : []; return blocks.sort((left, right) => { const left_line = get_block_first_line(left); const right_line = get_block_first_line(right); if (left_line !== right_line) return left_line - right_line; return String(left?.key || "").localeCompare(String(right?.key || "")); }); } var menus32 = { "connections:target_menu": { title: "Blocks", icon: "blocks", order: 20, build() { const blocks = resolve_target_candidates(this); this.menu.addItem((item) => { item.setTitle("Blocks").setIcon("blocks"); const submenu = item.setSubmenu(); item.setDisabled?.(!blocks.length); blocks.forEach((block) => { submenu.addItem((sub_item) => { sub_item.setTitle(get_block_title(block)).setDisabled(!block?.vec).onClick(async () => { return await run_select_target(this, block); }); }); }); }); } } }; function resolve_target_candidates(menu_ctx) { const action = menu_ctx.resolve_action?.(); if (typeof action !== "function") return []; const candidates = action(menu_ctx.params); return Array.isArray(candidates) ? candidates : []; } function get_block_first_line(block) { return Array.isArray(block?.lines) && Number.isFinite(block.lines[0]) ? block.lines[0] : Number.POSITIVE_INFINITY; } function get_block_title(block) { return get_block_display_name2(block, { show_full_path: false }) || block?.key || "Block"; } async function run_select_target(menu_ctx, target_item) { const action = menu_ctx.env.config?.actions?.connections_list_select_target?.action; if (typeof action !== "function") return false; return await action.call(menu_ctx.scope, { target_item, event_source: menu_ctx.event_source }); } // src/actions/connections-target/history.js var HISTORY_LIMIT = 10; function connections_target_history() { const history = Array.isArray(this.connections_target_history) ? this.connections_target_history : []; return history.slice(0, HISTORY_LIMIT).map((key) => this.env.smart_sources?.get?.(key) || this.env.smart_sources?.items?.[key]).filter(Boolean); } var menus33 = { "connections:target_menu": { title: "History", icon: "history", order: 10, build() { const sources = resolve_target_candidates2(this); this.menu.addItem((item) => { item.setTitle("History").setIcon("history"); const submenu = item.setSubmenu(); item.setDisabled?.(!sources.length); sources.forEach((source) => { submenu.addItem((sub_item) => { sub_item.setTitle(source.key).setIcon("file-text").onClick(async () => { return await run_select_target2(this, source); }); }); }); }); } } }; function resolve_target_candidates2(menu_ctx) { const action = menu_ctx.resolve_action?.(); if (typeof action !== "function") return []; const candidates = action(menu_ctx.params); return Array.isArray(candidates) ? candidates : []; } async function run_select_target2(menu_ctx, target_item) { const action = menu_ctx.env.config?.actions?.connections_list_select_target?.action; if (typeof action !== "function") return false; return await action.call(menu_ctx.scope, { target_item, event_source: menu_ctx.event_source }); } // 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/actions/env/insert_connections_codeblock.js function env_insert_connections_codeblock(params = {}) { const { editor } = params; if (!editor) return false; editor.replaceSelection(build_connections_codeblock()); return true; } var commands8 = { "insert-connections-codeblock": { name: "Insert: Connections codeblock", context: "editor", register_when({ plugin }) { return plugin.manifest.id === "smart-connections"; }, params({ editor }) { return { editor }; }, when({ params }) { return Boolean(params.editor); } } }; // 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"; } static get register_open_command() { return false; } constructor(leaf, plugin) { super(leaf, plugin); this.paused = false; this.pause_controls = null; this.current = null; } /** * Set whether this view follows the active source. * @param {boolean} paused * @returns {boolean} */ set_paused(paused) { this.paused = Boolean(paused); this.pause_controls?.update?.(this.paused); return this.paused; } /** * Toggle active-source following and refresh the active target when resumed. * @param {object} [params={}] * @param {string} [params.event_source] * @returns {Promise} The new paused state. */ async toggle_paused(params = {}) { const paused = this.set_paused(!this.paused); if (paused) return paused; const active_path = this.plugin.app.workspace.getActiveFile()?.path; if (!active_path) return paused; const active_item = this.env.smart_sources.get(active_path); if (active_item) { await this.render_target(active_item, { force: true, event_source: params.event_source }); } return paused; } /** * Pause active-source following and render an explicit target. * @param {object} target_item * @param {object} [params={}] * @returns {Promise} */ async select_target(target_item, params = {}) { if (!target_item) return false; this.set_paused(true); return await this.render_target(target_item, { ...params, force: true }); } /** * Render one Connections target through the view lifecycle. * @param {object} [target_item=this.current] * @param {object} [params={}] * @returns {Promise} */ async render_target(target_item = this.current, params = {}) { if (!target_item) return false; await this.render_view({ ...params, connections_item: target_item }); return true; } 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.is_media && connections_item.should_embed === false) return; 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_target(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_target(); } }); 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.select_target(item, { event_source: event.event_source || "connections:show" }); } } }); 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_target(); } }); } /** * 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); } }; 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/actions/source/view_connections.js async function source_view_connections(params = {}) { const source_item = params.source_item || params.target_item || this; const env = source_item?.env || this?.env; const workspace = params.workspace || env?.obsidian_app?.workspace || env?.plugin?.app?.workspace || env?.smart_connections_plugin?.app?.workspace || activeWindow.app?.workspace; if (!source_item?.key || !workspace) return false; const event_source = params.event_source || "source_view_connections"; const view = await get_or_open_connections_view(workspace); if (view) { await render_connections_view(view, source_item, { event_source }); return true; } env?.events?.emit?.("connections:show", { collection_key: source_item.collection_key, item_key: source_item.key, event_source }); return true; } async function get_or_open_connections_view(workspace) { const existing_view = ConnectionsItemView.get_view?.(workspace); if (existing_view) { await reveal_connections_leaf(workspace, existing_view.leaf); return existing_view; } const existing_leaf = ConnectionsItemView.get_leaf?.(workspace); if (existing_leaf) { await reveal_connections_leaf(workspace, existing_leaf); return ConnectionsItemView.get_view?.(workspace) || existing_leaf.view || null; } const opened = await ConnectionsItemView.open?.(workspace, { active: true }); const opened_view = ConnectionsItemView.get_view?.(workspace) || opened || null; const opened_leaf = ConnectionsItemView.get_leaf?.(workspace) || opened_view?.leaf; await reveal_connections_leaf(workspace, opened_leaf); return opened_view || opened_leaf?.view || null; } async function reveal_connections_leaf(workspace, leaf) { if (!workspace || !leaf) return false; expand_leaf_ancestors(leaf); if (typeof workspace.revealLeaf === "function") { await workspace.revealLeaf(leaf); return true; } if (typeof workspace.setActiveLeaf === "function") { workspace.setActiveLeaf(leaf, { focus: true }); return true; } return false; } function expand_leaf_ancestors(leaf) { let parent = leaf?.parent; while (parent) { parent.setCollapsed?.(false); parent.expand?.(); parent = parent.parent; } } async function render_connections_view(view, source_item, params = {}) { await view.select_target(source_item, { event_source: params.event_source || "source_view_connections" }); } var menus34 = { "source:menu": { title: "View connections", icon: "smart-connections", order: 20, disabled() { return !this.scope?.key || !this.env?.connections_lists; } } }; // smart_env.config.js var smart_env_config3 = { collections: { connections_lists: connections_lists_default }, items: { connections_list: { class: ConnectionsList, version: "3.1.1" } }, modules: {}, components: { connections_codeblock: { render: render28, version: "3.1.1" }, connections_footer_view: { render: render29, version: "3.1.1" }, connections_graph_v1: { render: render30, version: "3.1.1" }, connections_list_item_v3: { render: render31, settings_config: settings_config11, version: "3.1.1" }, connections_list_v3: { render: render32, display_name: display_name10, version: "3.1.1" }, connections_list_v4: { render: render33, settings_config: settings_config12, display_name: display_name11, version: "3.1.1" }, connections_settings_header: { render: render34, version: "3.1.1" }, connections_settings_lookup_callout: { render: render35, version: "3.1.1" }, connections_view_v3: { render: render36, version: "3.1.1" } }, actions: { connections_list_copy_as_links: { action: connections_list_copy_as_links, menus: menus18, version: "3.1.1" }, connections_list_get_results: { action: connections_list_get_results, display_name: display_name12, display_description: display_description6, action_scope: action_scope3, tool: tool2, input_schema: input_schema2, output_schema: output_schema2, version: "3.1.1" }, connections_list_item_hide: { action: connections_list_item_hide, menus: menus19, version: "3.1.1" }, connections_list_item_toggle_pinned: { action: connections_list_item_toggle_pinned, menus: menus20, version: "3.1.1" }, connections_list_open_help: { action: connections_list_open_help, commands: commands4, menus: menus21, version: "3.1.1" }, connections_list_open_random_connection: { action: connections_list_open_random_connection, commands: commands5, ribbon_icons, menus: menus22, version: "3.1.1" }, connections_list_open_settings: { action: connections_list_open_settings, menus: menus23, version: "3.1.1" }, connections_list_open_view: { action: connections_list_open_view, commands: commands6, ribbon_icons: ribbon_icons2, version: "3.1.1" }, connections_list_pre_process: { action: pre_process2, pre_process: pre_process2, version: "3.1.1" }, connections_list_refresh: { action: connections_list_refresh, menus: menus24, version: "3.1.1" }, connections_list_select_target: { action: connections_list_select_target, display_name: display_name13, menus: menus25, version: "3.1.1" }, connections_list_send_to_context: { action: connections_list_send_to_context, menus: menus26, version: version2 }, connections_list_send_to_smart_graph: { action: connections_list_send_to_smart_graph, menus: menus27, version: version3 }, connections_list_toggle_expanded: { action: connections_list_toggle_expanded, menus: menus28, version: "3.1.1" }, connections_list_toggle_footer_connections: { action: connections_list_toggle_footer_connections, commands: commands7, ribbon_icons: ribbon_icons3, version: "3.1.1" }, connections_list_toggle_paused: { action: connections_list_toggle_paused, menus: menus29, version: "3.1.1" }, connections_list_unhide_all: { action: connections_list_unhide_all, menus: menus30, version: "3.1.1" }, connections_list_unpin_all: { action: connections_list_unpin_all, menus: menus31, version: "3.1.1" }, connections_target_blocks: { action: connections_target_blocks, menus: menus32, version: "3.1.1" }, connections_target_history: { action: connections_target_history, menus: menus33, version: "3.1.1" }, env_insert_connections_codeblock: { action: env_insert_connections_codeblock, commands: commands8, version: "3.1.1" }, source_view_connections: { action: source_view_connections, menus: menus34, version: "3.1.1" } } }; // 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_obsidian60 = 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, version4, active = true) { const leaf = workspace.getLeaf("tab"); leaf.setViewState({ type: this.view_type, active, state: { version: version4 } }); } 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_obsidian60.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(version4) { if (!version4) return null; const safe_version = escape_regex(version4); 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 Core v4.7\n\n> [!NOTE] What's new in `v4.7.2`\n> Updated: Smart Environment\n> Updated: minimum Obsidian app version to 1.8.7\n> Added: Connections list display configuration\n\n## Related notes show up sooner. Change the anchor in place.\n\nSmart Connections still starts from the note in front of you. v4.7 removes the detour when another source should take over: choose a recent note, focus on one block, or drop a vault file onto Connections and let the list update in place.\n\nThe larger upgrade is underneath the list. Smart Environment v3 gets Connections ready sooner, adds more built-in local embedding models, and lets you switch models without restarting Obsidian or deleting the embeddings you may want to return to.\n\n![](https://smartconnections.app/assets/connections-target-menu-history-populated-core-crop-desktop-2026-07-30.png)\n\n> Update all installed Smart Plugins together, then restart Obsidian. Smart Connections Core v4.7 requires Smart Environment v3.\n\n## A stronger semantic engine before the first result\n\nConnections depends on the embedding model that turns your notes into semantic signals. v3 makes that choice less permanent and more useful:\n\n- Start using Connections sooner after opening Obsidian.\n- Choose from a broader built-in catalog, including more lightweight and multilingual local models.\n- Change the active model without deleting another model's stored embeddings.\n- Use the improved Environment Stats and source inspector when a note appears stale, skipped, or unexpectedly absent from results.\n\n![](https://smartconnections.app/assets/environment-settings-model-and-embedding-controls-embedding-chat-models-focused-crop-publication-srgb-2982b4f688a4-2026-07-29.png)\n\nLearn more about the release of [Smart Environment v3](https://smartconnections.app/smart-environment/releases/3-0/?utm_source=smart-connections-release).\n\n## Change the target without changing your workspace\n\nThe active note remains the calm default. When it is not the source you want, the target menu now gives you three direct alternatives:\n\n- Pick a recent Connections target.\n- Focus on a specific block inside the current note.\n- Drop another vault file onto the Connections view.\n\nThe related-note list updates around that source without making you navigate away first.\n\n![](https://smartconnections.app/assets/connections-target-menu-blocks-populated-core-crop-desktop-2026-07-27.png)\n\n## Keep a useful result set moving\n\nThe list menu now follows the same shared action system used across Smart Environment v3. Pause or refresh discovery, open a random connection, copy the list, or continue with the reviewed sources in Context or Graph without rebuilding the set by hand.\n\nSource menus elsewhere in the suite can also open Connections for the item you are already looking at. The workflow begins from the source, not from hunting down the right plugin command.\n\n![](https://smartconnections.app/assets/connections-list-menu-core-crop-desktop-2026-07-27.png)\n\n## Put Connections where it helps\n\nFooter Connections now uses a configurable display component instead of a single graph on/off switch. Choose the supported presentation that fits the note surface and screen size. Scores also follow the final displayed ranking score when another ranking step provides one, so the number beside a result better matches the order you see.\n\n![](https://smartconnections.app/assets/connections-settings-display-controls-display-components-focused-crop-publication-srgb-78a34afa1fca-2026-07-29.png)\n\n\n## Before / After\n\n| Before | With Smart Connections Core v4.7 |\n| --- | --- |\n| Connections could take longer to become ready after startup. | Smart Environment v3 reduces blocking and repeated startup work. |\n| Trying another embedding model felt like committing to a rebuild. | Switch models without deleting the previous model's stored embeddings. |\n| Retargeting often meant navigating to another note first. | Choose a recent note, a current-note block, or drop a file onto the view. |\n| A useful result list could become a dead end. | Continue the reviewed sources through consistent Context and Graph actions. |\n| Footer display was controlled by one graph toggle. | Choose the supported Connections component that fits the surface. |\n\n## Supporting improvements\n\n- More consistent list, result, command, ribbon, pause, and random-connection actions.\n- A shared source action for opening Connections from supported Smart Plugin menus.\n- Better score display after optional ranking.\n- Improved vector compatibility and performance reporting through Smart Environment v3.\n\n## Learn more\n\n- [Smart Connections overview](https://smartconnections.app/smart-connections/?utm_source=smart-connections-release)\n- [Smart Connections documentation](https://smartconnections.app/docs/connections/?utm_source=smart-connections-release)\n- [Smart Connections getting started](https://smartconnections.app/smart-connections/getting-started/?utm_source=smart-connections-release)\n- [Smart Connections FAQ](https://smartconnections.app/smart-connections/faq/?utm_source=smart-connections-release)\n\nUpdated: 2026-08-06\n\n[More details about the latest releases](https://smartconnections.app/smart-connections/releases/4-7/?utm_source=smart-connections-release)\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/add_icons.js var import_obsidian61 = require("obsidian"); function add_smart_dice_icon() { (0, import_obsidian61.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/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_obsidian62 = 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(params = {}) { 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 (params.force && this.container_map[entity.key] instanceof HTMLElement) { this.container_map[entity.key].remove(); delete this.container_map[entity.key]; } 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_obsidian62.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({ force: true }); } }); } 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/main.js var { requestUrl: requestUrl6 } = import_obsidian63.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_obsidian63.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_item_views({ skip_command_registration: true }); } onunload() { this.connections_footer_view?.unload(); this.notices?.unload(); this.env?.unload_main?.(this); } async initialize() { this.register_ribbon_actions(); 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.register_command_actions(); 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 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)) { 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); } } /** * 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) { return null; } const markdown_view = this.app.workspace.getActiveFileView(); if (!markdown_view) { 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}`); } } }; /* nosourcemap */