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

${adapter_constructor.name} adapter

` }; acc = { ...acc, ...adapter_instance.settings_config }; } return acc; }, {}) }; return _settings_config; } /** * Retrieves the block collection associated with SmartSources. * @readonly * @returns {SmartBlocks} The block collection instance. */ get block_collection() { return this.env.smart_blocks; } /** * Retrieves the embed queue containing items and their blocks to be embedded. * @readonly * @returns {Array} The embed queue. */ get embed_queue() { if (!this._embed_queue.length) { try { const embed_blocks = this.block_collection.settings.embed_blocks; this._embed_queue = Object.values(this.items).reduce((acc, item) => { if (item._queue_embed || item.should_embed && item.is_unembedded) acc.push(item); if (embed_blocks) item.blocks.forEach((block) => { if (block._queue_embed || block.should_embed && block.is_unembedded) acc.push(block); }); return acc; }, []); } catch (error) { console.error(`Error getting embed queue:`, error); } } return this._embed_queue; } /** * Clears all data by removing sources and blocks, reinitializing the file system, and reimporting items. * @async * @returns {Promise} */ async run_clear_all() { this.emit_event("sources:clear_started", { event_source: "run_clear_all" }); await this.data_adapter.clear_all(); this.clear(); this.block_collection.clear(); this._fs = null; await this.init_fs(); await this.init_items(); this._excluded_headings = null; Object.values(this.items).forEach((item) => { item.queue_import(); item.queue_embed(); item.loaded_at = Date.now() + 9999999999; }); this.emit_event("sources:clear_completed", { event_source: "run_clear_all" }); await this.process_source_import_queue(); } async init_fs(opts = {}) { const { force_refresh = false } = opts; if (force_refresh) await this.env.fs.refresh(); await this.fs.load_exclusions(); this.fs.file_paths = this.fs.post_process(this.env.fs.file_paths); this.fs.files = this.fs.file_paths.reduce((acc, file_path) => { acc[file_path] = this.env.fs.files[file_path]; return acc; }, {}); this.fs.folder_paths = this.fs.post_process(this.env.fs.folder_paths); this.fs.folders = this.fs.folder_paths.reduce((acc, folder_path) => { acc[folder_path] = this.env.fs.folders[folder_path]; return acc; }, {}); } /** * Retrieves patterns for excluding files/folders from processing. * @readonly * @returns {Array} */ get excluded_patterns() { return [ ...this.file_exclusions?.map((file) => `${file}**`) || [], ...(this.folder_exclusions || []).map((folder) => `${folder}**`), this.env.env_data_dir + "/**" ]; } /** * Retrieves the file exclusion patterns from settings. * @readonly * @returns {Array} An array of file exclusion patterns. */ get file_exclusions() { const csv = this.env.settings?.smart_sources?.file_exclusions; return csv?.length ? csv.split(",").map((file) => file.trim()) : []; } /** * Retrieves the folder exclusion patterns from settings. * @readonly * @returns {Array} An array of folder exclusion patterns. */ get folder_exclusions() { const csv = this.env.settings?.smart_sources?.folder_exclusions; return csv?.length ? csv.split(",").map((folder) => { folder = folder.trim(); if (folder === "") return false; if (folder === "/") return false; if (!folder.endsWith("/")) return folder + "/"; return folder; }).filter(Boolean) : []; } /** * Retrieves the excluded headings from settings. * @readonly * @returns {Array} An array of excluded headings. */ get excluded_headings() { if (!this._excluded_headings) { const csv = this.env.settings?.smart_sources?.excluded_headings; this._excluded_headings = csv?.length ? csv.split(",").map((heading) => heading.trim()) : []; } return this._excluded_headings; } /** * Retrieves the count of included files that are not excluded. * @readonly * @returns {number} The number of included files. */ get included_files() { const extensions = Object.keys(this.source_adapters); return this.fs.file_paths.filter((file_path) => extensions.some((ext) => file_path.endsWith(ext)) && !this.fs.is_excluded(file_path)).length; } get excluded_file_paths() { return this.env.fs.file_paths.filter((file_path) => this.fs.is_excluded(file_path)); } /** * Retrieves the total number of files, regardless of exclusion. * @readonly * @returns {number} The total number of files. */ get total_files() { return this.fs.file_paths.filter((file) => file.endsWith(".md") || file.endsWith(".canvas")).length; } /** * Unloads the collection and clears registered listeners and timers. * @returns {void} */ unload() { this.unregister_env_event_listeners(); if (this.sources_re_import_timeout) clearTimeout(this.sources_re_import_timeout); this.sources_re_import_timeout = null; this.sources_re_import_queue = {}; this.set_import_progress_state(null); super.unload(); } get data_dir() { return "multi"; } }; var settings_config2 = {}; // node_modules/obsidian-smart-env/node_modules/smart-collections/adapters/_adapter.js var CollectionDataAdapter = class { /** * @constructor * @param {Object} collection - The collection instance that this adapter manages. */ constructor(collection) { this.collection = collection; this.env = collection.env; } /** * The class to use for item adapters. * @type {typeof ItemDataAdapter} */ ItemDataAdapter = ItemDataAdapter; /** * Optional factory method to create item adapters. * If `this.item_adapter_class` is not null, it uses that; otherwise can be overridden by subclasses. * @param {Object} item - The item to create an adapter for. * @returns {ItemDataAdapter} */ create_item_adapter(item) { if (!this.ItemDataAdapter) { throw new Error("No item_adapter_class specified and create_item_adapter not overridden."); } return new this.ItemDataAdapter(item); } /** * Load a single item by its key using an `ItemDataAdapter`. * @async * @abstract * @param {string} key - The key of the item to load. * @returns {Promise} Resolves when the item is loaded. */ async load_item(key) { throw new Error("Not implemented"); } /** * Save a single item by its key using its associated `ItemDataAdapter`. * @async * @abstract * @param {string} key - The key of the item to save. * @returns {Promise} Resolves when the item is saved. */ async save_item(key) { throw new Error("Not implemented"); } /** * Delete a single item by its key. This may involve updating or removing its file, * as handled by the `ItemDataAdapter`. * @async * @abstract * @param {string} key - The key of the item to delete. * @returns {Promise} Resolves when the item is deleted. */ async delete_item(key) { throw new Error("Not implemented"); } /** * Process any queued load operations. Typically orchestrates calling `load_item()` * on items that have been flagged for loading. * @async * @abstract * @returns {Promise} */ async process_load_queue() { throw new Error("Not implemented"); } /** * Process any queued save operations. Typically orchestrates calling `save_item()` * on items that have been flagged for saving. * @async * @abstract * @returns {Promise} */ async process_save_queue() { throw new Error("Not implemented"); } /** * Load the item's data from storage if it has been updated externally. * @async * @param {string} key - The key of 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 {string|null} [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 {CollectionDataAdapter} 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 ItemDataAdapter} */ 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 {boolean} */ _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. * @param {Object} item * @returns {string} */ get_item_ajson() { return this.collection_adapter.get_item_ajson(this.item); } }; // node_modules/obsidian-smart-env/node_modules/smart-sources/adapters/data/ajson_multi_file.js var AjsonMultiFileSourcesDataAdapter = class extends AjsonMultiFileCollectionDataAdapter { ItemDataAdapter = AjsonMultiFileSourceDataAdapter; }; var AjsonMultiFileSourceDataAdapter = class extends AjsonMultiFileItemDataAdapter { }; // node_modules/obsidian-smart-env/node_modules/smart-sources/adapters/_adapter.js var SourceContentAdapter = class { constructor(item) { this.item = item; } async import() { this.throw_not_implemented("import"); } async create() { this.throw_not_implemented("create"); } async update() { this.throw_not_implemented("update"); } async read() { this.throw_not_implemented("read"); } async remove() { this.throw_not_implemented("remove"); } // HELPER METHODS get data() { return this.item.data; } // async create_hash(content) { return await create_hash(content); } create_hash(content) { return murmur_hash_32_alphanumeric(content); } get settings() { return this.item.env.settings.smart_sources[this.adapter_key]; } get adapter_key() { return to_snake(this.constructor.name); } static get adapter_key() { return to_snake(this.name); } get fs() { return this.item.collection.fs; } get env() { return this.item.env; } }; function to_snake(str) { return str[0].toLowerCase() + str.slice(1).replace(/([A-Z])/g, "_$1").toLowerCase(); } // node_modules/obsidian-smart-env/node_modules/smart-blocks/parsers/markdown.js function parse_markdown_blocks(markdown, opts = {}) { const { start_index = 1, line_keys = false } = opts; const lines = markdown.split("\n"); const LIST_KEY_WORD_LEN = opts.list_key_word_len || 10; const result = {}; const heading_stack = []; const heading_lines = {}; const heading_counts = {}; const sub_block_counts = {}; const subheading_counts = {}; const task_lines = []; const tasks = {}; let current_list_item = null; let current_content_block = null; let in_frontmatter = false; let frontmatter_started = false; const root_heading_key = "#"; let in_code_block = false; const codeblock_ranges = []; let codeblock_start = null; sub_block_counts[root_heading_key] = 0; for (let i = 0; i < lines.length; i++) { const line_number = i + start_index; const line = lines[i]; const trimmed_line = line.trim(); if (trimmed_line === "---") { if (!frontmatter_started && line_number === 1) { frontmatter_started = true; in_frontmatter = true; heading_lines["#---frontmatter---"] = [line_number, null]; continue; } else if (in_frontmatter) { in_frontmatter = false; heading_lines["#---frontmatter---"][1] = line_number; continue; } } if (in_frontmatter) { continue; } if (!in_code_block && /^[-*+]\s+\[(?: |x|X)\]/.test(trimmed_line)) { task_lines.push(line_number); if (/^[-*+]\s+\[ \]/.test(trimmed_line)) { if (!tasks.incomplete) tasks.incomplete = { all: [], top: [] }; tasks.incomplete.all.push(line_number); } if (/^[-*+]\s+\[ \]/.test(line)) { tasks.incomplete.top.push(line_number); } } if (trimmed_line.startsWith("```")) { in_code_block = !in_code_block; if (in_code_block && !codeblock_start) codeblock_start = line_number; else if (!in_code_block && codeblock_start) { codeblock_ranges.push([codeblock_start, line_number]); codeblock_start = null; } if (!current_content_block) { const parent_key = heading_stack.length > 0 ? heading_stack[heading_stack.length - 1].key : root_heading_key; if (parent_key === root_heading_key && !heading_lines[root_heading_key]) { heading_lines[root_heading_key] = [line_number, null]; } if (parent_key === root_heading_key) { current_content_block = { key: root_heading_key, start_line: line_number }; if (heading_lines[root_heading_key][1] === null || heading_lines[root_heading_key][1] < line_number) { heading_lines[root_heading_key][1] = null; } } else { if (sub_block_counts[parent_key] === void 0) { sub_block_counts[parent_key] = 0; } sub_block_counts[parent_key] += 1; const n = sub_block_counts[parent_key]; const key = `${parent_key}#{${n}}`; heading_lines[key] = [line_number, null]; current_content_block = { key, start_line: line_number }; } } continue; } const heading_match = trimmed_line.match(/^(#{1,6})\s+(.+)$/); if (heading_match && !in_code_block) { const level = heading_match[1].length; let title = heading_match[2].trim(); while (heading_stack.length > 0 && heading_stack[heading_stack.length - 1].level >= level) { const finished_heading = heading_stack.pop(); if (heading_lines[finished_heading.key][1] === null) { heading_lines[finished_heading.key][1] = line_number - 1; } } if (heading_stack.length === 0 && heading_lines[root_heading_key] && heading_lines[root_heading_key][1] === null) { heading_lines[root_heading_key][1] = line_number - 1; } if (current_content_block) { if (heading_lines[current_content_block.key][1] === null) { heading_lines[current_content_block.key][1] = line_number - 1; } current_content_block = null; } if (current_list_item) { if (heading_lines[current_list_item.key][1] === null) { heading_lines[current_list_item.key][1] = line_number - 1; } current_list_item = null; } let parent_key = ""; let parent_level = 0; if (heading_stack.length > 0) { parent_key = heading_stack[heading_stack.length - 1].key; parent_level = heading_stack[heading_stack.length - 1].level; } else { parent_key = ""; parent_level = 0; } if (heading_stack.length === 0) { heading_counts[title] = (heading_counts[title] || 0) + 1; if (heading_counts[title] > 1) { title += `[${heading_counts[title]}]`; } } else { if (!subheading_counts[parent_key]) { subheading_counts[parent_key] = {}; } subheading_counts[parent_key][title] = (subheading_counts[parent_key][title] || 0) + 1; const count = subheading_counts[parent_key][title]; if (count > 1) { title += `#{${count}}`; } } const level_diff = level - parent_level; const hashes = "#".repeat(level_diff); const key = parent_key + hashes + title; heading_lines[key] = [line_number, null]; sub_block_counts[key] = 0; heading_stack.push({ level, title, key }); continue; } const list_match = line.match(/^(\s*)([-*]|\d+\.) (.+)$/); if (list_match && !in_code_block) { const indentation = list_match[1].length; if (indentation === 0) { if (current_list_item) { if (heading_lines[current_list_item.key][1] === null) { heading_lines[current_list_item.key][1] = line_number - 1; } current_list_item = null; } if (current_content_block && current_content_block.key !== root_heading_key) { if (heading_lines[current_content_block.key][1] === null) { heading_lines[current_content_block.key][1] = line_number - 1; } current_content_block = null; } let parent_key = heading_stack.length > 0 ? heading_stack[heading_stack.length - 1].key : root_heading_key; if (parent_key === root_heading_key && !heading_lines[root_heading_key]) { heading_lines[root_heading_key] = [line_number, null]; } if (sub_block_counts[parent_key] === void 0) { sub_block_counts[parent_key] = 0; } sub_block_counts[parent_key] += 1; const n = sub_block_counts[parent_key]; let key; if (line_keys) { const content_without_task = list_match[3].replace(/^\[(?: |x|X)\]\s*/, ""); const words = get_longest_words_in_order(content_without_task, LIST_KEY_WORD_LEN); key = `${parent_key}#${words}`; } else { key = `${parent_key}#{${n}}`; } heading_lines[key] = [line_number, null]; current_list_item = { key, start_line: line_number }; continue; } if (current_list_item) { continue; } } if (trimmed_line === "") { continue; } if (!current_content_block) { if (current_list_item) { if (heading_lines[current_list_item.key][1] === null) { heading_lines[current_list_item.key][1] = line_number - 1; } current_list_item = null; } let parent_key = heading_stack.length > 0 ? heading_stack[heading_stack.length - 1].key : root_heading_key; if (parent_key === root_heading_key) { if (!heading_lines[root_heading_key]) { heading_lines[root_heading_key] = [line_number, null]; } if (heading_lines[root_heading_key][1] === null || heading_lines[root_heading_key][1] < line_number) { heading_lines[root_heading_key][1] = null; } current_content_block = { key: root_heading_key, start_line: line_number }; } else { if (sub_block_counts[parent_key] === void 0) { sub_block_counts[parent_key] = 0; } sub_block_counts[parent_key] += 1; const n = sub_block_counts[parent_key]; const key = `${parent_key}#{${n}}`; heading_lines[key] = [line_number, null]; current_content_block = { key, start_line: line_number }; } } } const total_lines = lines.length; while (heading_stack.length > 0) { const finished_heading = heading_stack.pop(); if (heading_lines[finished_heading.key][1] === null) { heading_lines[finished_heading.key][1] = total_lines + start_index - 1; } } if (current_list_item) { if (heading_lines[current_list_item.key][1] === null) { heading_lines[current_list_item.key][1] = total_lines + start_index - 1; } current_list_item = null; } if (current_content_block) { if (heading_lines[current_content_block.key][1] === null) { heading_lines[current_content_block.key][1] = total_lines + start_index - 1; } current_content_block = null; } if (heading_lines[root_heading_key] && heading_lines[root_heading_key][1] === null) { heading_lines[root_heading_key][1] = total_lines + start_index - 1; } for (const key in heading_lines) { result[key] = heading_lines[key]; } return { blocks: result, task_lines, tasks, codeblock_ranges }; } function get_longest_words_in_order(line, n = 3) { const words = line.split(/\s+/).sort((a, b) => b.length - a.length).slice(0, n); return words.sort((a, b) => line.indexOf(a) - line.indexOf(b)).join(" "); } // node_modules/obsidian-smart-env/node_modules/smart-sources/adapters/_file.js var FileSourceContentAdapter = class extends SourceContentAdapter { static async init_items(collection) { if (collection.fs_items_initialized) return; collection._fs = null; await collection.fs.init(); await collection.init_fs(); for (const file of Object.values(collection.fs.files)) { const item = collection.init_file_path(file.path); if (item) item.init_file_mtime = file.stat.mtime; } collection.fs_items_initialized = Date.now(); } /** * @name fs * @type {Object} * @readonly * @description * Access the file system interface used by this adapter. Typically derived * from `this.item.collection.fs`. */ get fs() { return this.item.collection.fs; } /** * @name file_path * @type {string} * @readonly * @description * The file path on disk corresponding to the source. Used for read/write operations. */ get file_path() { return this.item.file_path; } /** * @async * @method create * @param {string|null} [content=null] Initial content for the new file. * @description * Create a new file on disk. If content is not provided, attempts to use * `this.item.data.content` as fallback. */ async create(content = null) { if (!content) content = this.item.data.content || ""; await this.fs.write(this.file_path, content); } /** * @async * @method update * @param {string} content The full new content to write to the file. * @description * Overwrite the entire file content on disk. */ async update(content) { await this.fs.write(this.file_path, content); } /** * @async * @method read * @returns {Promise} The content of the file. * @description * Read the file content from disk. Updates `last_read` hash and timestamp on the entity’s data. * If file is large or special handling is needed, override this method. */ async read() { const content = await this.fs.read(this.file_path); this.data.last_read = { hash: this.create_hash(content || ""), at: Date.now() }; return content; } /** * @async * @method remove * @returns {Promise} * @description * Delete the file from disk. After removal, the source item should also be deleted or updated accordingly. */ async remove() { await this.fs.remove(this.file_path); } async move_to(move_to_ref) { if (!move_to_ref) { throw new Error("Invalid entity reference for move_to operation"); } const move_content = await this.read(); let has_existing = false; if (typeof move_to_ref === "string") { const existing = this.item.collection.get(move_to_ref); if (existing) { move_to_ref = existing; has_existing = true; } } else { has_existing = true; } if (has_existing) { await move_to_ref.append(move_content); } else { move_to_ref = await this.item.collection.create(move_to_ref, move_content); } if (this.item.key !== move_to_ref.key) { await this.remove(); this.item.delete(); } else { console.log(`did not delete ${this.item.key} because it was moved to ${move_to_ref.key}`); } return move_to_ref; } /** * Merge content into the source * @param {string} content - The content to merge into the source * @param {Object} opts - Options for the merge operation * @param {string} opts.mode - The mode to use for the merge operation. Defaults to 'append_blocks' (may also be 'replace_blocks') */ async merge(content, opts = {}) { const { mode = "append_blocks" } = opts; const { blocks: blocks_obj, task_lines } = parse_markdown_blocks(content); if (typeof blocks_obj !== "object" || Array.isArray(blocks_obj)) { console.warn("merge error: Expected an object from parse_markdown_blocks, but received:", blocks_obj); throw new Error("merge error: parse_markdown_blocks did not return an object as expected."); } const { new_blocks, new_with_parent_blocks, changed_blocks, same_blocks } = await this.get_changes(blocks_obj, content); for (const block of new_blocks) { await this.append(block.content); } for (const block of new_with_parent_blocks) { const parent_block = this.item.block_collection.get(block.parent_key); await parent_block.append(block.content); } for (const block of changed_blocks) { const changed_block = this.item.block_collection.get(block.key); if (mode === "replace_blocks") { await changed_block.update(block.content); } else { await changed_block.append(block.content); } } } async get_changes(blocks_obj, content) { const new_blocks = []; const new_with_parent_blocks = []; const changed_blocks = []; const same_blocks = []; const existing_blocks = this.source.data.blocks || {}; for (const [sub_key, line_range] of Object.entries(blocks_obj)) { const has_existing = !!existing_blocks[sub_key]; const block_key = `${this.source.key}${sub_key}`; const block_content = get_line_range(content, line_range[0], line_range[1]); if (!has_existing) { new_blocks.push({ key: block_key, state: "new", content: block_content }); continue; } let has_parent; let headings = sub_key.split("#"); let parent_key; while (!has_parent && headings.length > 0) { headings.pop(); parent_key = headings.join("#"); has_parent = !!existing_blocks[parent_key]; } if (has_parent) { new_with_parent_blocks.push({ key: block_key, parent_key: `${this.source.key}${parent_key}`, state: "new", content: block_content }); continue; } const block = this.item.block_collection.get(block_key); const content_hash = await this.create_hash(block_content); if (content_hash !== block.last_read?.hash) { changed_blocks.push({ key: block_key, state: "changed", content: block_content }); continue; } same_blocks.push({ key: block_key, state: "same", content: block_content }); } return { new_blocks, new_with_parent_blocks, changed_blocks, same_blocks }; } /** * Append new content to the source file, placing it at the end of the file. * @async * @param {string} content - The content to append. * @returns {Promise} */ async append(content) { const current_content = await this.read(); const new_content = [ current_content, "", content ].join("\n").trim(); await this.update(new_content); } get size() { return this.item.file?.stat?.size || 0; } }; // node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_markdown_links.js function get_markdown_links(content) { const result = []; const markdown_link_re = /\[([^\]]+?)\]\(([^)]+?)\)/g; const wikilink_re = /\[\[([^\|\]]+?)(?:\|([^\]]+?))?\]\]/g; const normalise_target = (raw) => { const trimmed = raw.trim(); if (/^[a-zA-Z][\w+\-.]*:\/\//.test(trimmed)) return trimmed; try { return decodeURIComponent(trimmed); } catch (_) { return trimmed.replace(/%20/gi, " "); } }; const is_embedded = (index) => { if (index <= 0) return false; return content[index - 1] === "!"; }; let m; while ((m = markdown_link_re.exec(content)) !== null) { const title = m[1]; const target = normalise_target(m[2]); const line_no = content.slice(0, m.index).split("\n").length; const embedded = is_embedded(m.index); const record = { title, target, line: line_no }; if (embedded) record.embedded = true; result.push(record); } while ((m = wikilink_re.exec(content)) !== null) { const target_raw = m[1]; const title = m[2] || target_raw; const target = normalise_target(target_raw); const line_no = content.slice(0, m.index).split("\n").length; const embedded = is_embedded(m.index); const record = { title, target, line: line_no }; if (embedded) record.embedded = true; result.push(record); } return result.sort( (a, b) => a.line - b.line || a.target.localeCompare(b.target) ); } // node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_bases_cache_links.js function get_bases_cache_links({ source, links = [], cache } = {}) { if (!source || !Array.isArray(links) || !links.length) return []; const cache_items = cache || source?.env?.bases_caches?.items; if (!cache_items) return []; const source_key = source?.key || source?.path; if (!source_key) return []; return links.flatMap((link) => { if (!link?.embedded) return []; if (typeof link.target !== "string" || !link.target.includes(".base")) return []; const cache_key = `${source_key}#${link.target}`; const markdown_table = get_bases_markdown_table(cache_items?.[cache_key]); if (!markdown_table) return []; return get_bases_table_links({ markdown_table, line_override: link.line }); }); } function get_bases_file_links({ source, cache } = {}) { if (!source || typeof source !== "object") return []; const cache_items = cache || source?.env?.bases_caches?.items; if (!cache_items) return []; const source_key = source?.key || source?.path; if (!source_key) return []; const markdown_table = get_bases_markdown_table(cache_items?.[source_key]); if (!markdown_table) return []; return get_bases_table_links({ markdown_table }); } function get_bases_markdown_table(cache_item) { if (!cache_item) return ""; if (typeof cache_item.markdown_table === "string") return cache_item.markdown_table; if (typeof cache_item.markdown_table === "function") return cache_item.markdown_table(); if (typeof cache_item?.data?.markdown_table === "string") return cache_item.data.markdown_table; return ""; } function get_bases_table_links({ markdown_table, line_override } = {}) { if (!markdown_table) return []; const table_links = get_markdown_links(markdown_table); if (!table_links.length) return []; return table_links.map((table_link) => ({ ...table_link, line: typeof line_override === "number" ? line_override : table_link.line, bases_row: table_link.line - 2 // Adjust for table header rows })); } // node_modules/obsidian-smart-env/node_modules/smart-sources/utils/parse_frontmatter.js function parse_value(raw_value) { const trimmed = raw_value.trim(); if (trimmed.startsWith('"') && trimmed.endsWith('"') || trimmed.startsWith("'") && trimmed.endsWith("'")) { return trimmed.slice(1, -1); } const lower = trimmed.toLowerCase(); if (lower === "true") return true; if (lower === "false") return false; if (!isNaN(trimmed) && trimmed !== "") { return Number(trimmed); } return trimmed; } function parse_yaml_block(yaml_block) { const lines = yaml_block.split(/\r?\n/); const data = {}; let i = 0; while (i < lines.length) { const line = lines[i]; i++; if (!line.trim() || line.trim().startsWith("#")) { continue; } const match = line.match(/^([^:]+)\s*:\s*(.*)$/); if (!match) { continue; } const key = match[1].trim(); let value = match[2].trim(); if (value === ">" || value === "|") { const multiline_lines = []; while (i < lines.length) { const next_line = lines[i]; if (!/^\s+/.test(next_line) || next_line.trim().startsWith("#")) { break; } multiline_lines.push(next_line.replace(/^\s+/, "")); i++; } const joined = multiline_lines.join("\n"); data[key] = parse_value(joined); } else if (value === "") { const arr = []; let array_consumed = false; while (i < lines.length) { const next_line = lines[i]; if (!next_line.trim().startsWith("- ")) { break; } const item_value = next_line.trim().slice(2); arr.push(parse_value(item_value)); i++; array_consumed = true; } if (array_consumed) { data[key] = arr; } else { data[key] = ""; } } else { data[key] = parse_value(value); } } return data; } function parse_frontmatter(content) { if (!content.startsWith("---")) { return { frontmatter: {}, body: content }; } const lines = content.split(/\r?\n/); let end_index = -1; for (let i = 1; i < lines.length; i++) { if (lines[i].trim() === "---") { end_index = i; break; } } if (end_index === -1) { return { frontmatter: {}, body: content }; } const frontmatter_lines = lines.slice(1, end_index); const frontmatter_block = frontmatter_lines.join("\n"); const frontmatter = parse_yaml_block(frontmatter_block); const body_lines = lines.slice(end_index + 1); const body = body_lines.join("\n"); return { frontmatter, body }; } // node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_markdown_tags.js var get_markdown_tags = (content = "") => { const tag_re = /(?} */ async import() { if (!this.can_import) return; const is_outdated = this.outdated; const has_incomplete_block_coverage = this.has_incomplete_block_coverage(); const repairing_block_coverage = !is_outdated && has_incomplete_block_coverage; if (!is_outdated && !has_incomplete_block_coverage) { this.item.blocks.forEach((block) => { if (!block.vec) block.queue_embed(); }); return; } const content = await this.read(); if (!content) { return; } if (!this.item.vec) { this.item.data.last_import = null; } if (!has_incomplete_block_coverage && this.data.last_import?.hash === this.data.last_read?.hash) { if (this.data.blocks) return; } this.data.blocks = null; await this.parse_content(content); await this.item.parse_content(content); const { mtime, size } = this.item.file.stat; this.data.last_import = { mtime, size, at: Date.now(), hash: this.data.last_read.hash }; this.item.loaded_at = Date.now(); this.item.queue_save(); if (this.item.should_embed && !repairing_block_coverage) this.item.queue_embed(); } // // WIP: move block parsing here // async read() { // const current_last_read_hash = this.data.last_read?.hash; // const content = await super.read(); // if(!content) return console.warn(`MarkdownSourceContentAdapter: Skipping missing-file: ${this.file_path}`); // if(current_last_read_hash === this.data.last_read?.hash) return content; // const {blocks: blocks, task_lines} = parse_markdown_blocks(content); // this.handle_excluded_headings(blocks); // } // Runs before configured content_parsers (for example, templates uses outlinks) async parse_content(content) { const outlinks = await this.get_links(content); this.data.outlinks = outlinks; const metadata = await this.get_metadata(content); this.data.metadata = metadata; } async get_links(content = null) { if (!content) content = await this.read(); if (!content) return; const markdown_links = get_markdown_links(content); const bases_links = get_bases_cache_links({ source: this.item, links: markdown_links }); return [ ...markdown_links, ...bases_links ]; } async get_metadata(content = null) { if (!content) content = await this.read(); if (!content) return; const { frontmatter, body } = parse_frontmatter(content); const tag_set = /* @__PURE__ */ new Set(); let fm_tags = frontmatter.tags; if (typeof fm_tags === "string") { fm_tags = fm_tags.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean); } if (Array.isArray(fm_tags)) { fm_tags.forEach((tag) => tag_set.add(tag.startsWith("#") ? tag : `#${tag}`)); } get_markdown_tags(body).forEach((tag) => tag_set.add(tag)); if (tag_set.size) frontmatter.tags = [...tag_set]; return frontmatter; } has_incomplete_block_coverage() { if (!this.data.blocks || !this.item.block_collection) return false; return Object.entries(this.data.blocks).some(([sub_key, line_range]) => { const block = this.item.block_collection.get(this.item.key + sub_key); if (!block) return true; const block_lines = block.lines || []; return block_lines[0] !== line_range[0] || block_lines[1] !== line_range[1]; }); } // Erroneous reasons to skip import (logs to console) get can_import() { if (!this.item.file) { console.warn(`MarkdownSourceContentAdapter: Skipping missing-file: ${this.file_path}`); return false; } if (this.item.size > (this.settings?.max_import_size || 3e5)) { console.warn(`MarkdownSourceContentAdapter: Skipping large file: ${this.file_path}`); return false; } return true; } /** * @deprecated use outdated instead */ get should_import() { return this.outdated; } get outdated() { try { if (!this.data.last_import) { return true; } if (this.data.last_read.at > this.data.last_import.at) { if (this.data.last_import?.hash !== this.data.last_read?.hash) return true; } if (this.data.last_import.mtime < this.item.mtime) { if (!this.data.last_import.size) return true; const size_diff = Math.abs(this.data.last_import.size - this.item.size); const size_diff_ratio = size_diff / (this.data.last_import.size || 1); if (size_diff_ratio > 0.01) return true; } return false; } catch (e) { console.warn(`MarkdownSourceContentAdapter: error getting should_import for ${this.file_path}: ${e}`); return true; } } }; // node_modules/obsidian-smart-env/adapters/smart-sources/obsidian_markdown.js var import_obsidian2 = require("obsidian"); function merge_tags(fm_tags, cache_tags = []) { const tag_set = /* @__PURE__ */ new Set(); if (typeof fm_tags === "string") { fm_tags = fm_tags.replace(/[\[\]]/g, "").split(",").map((t) => t.trim()).filter(Boolean); } if (Array.isArray(fm_tags)) { fm_tags.filter((t) => typeof t === "string").forEach((tag) => tag_set.add(tag.startsWith("#") ? tag : `#${tag}`)); } cache_tags.forEach(({ tag }) => tag_set.add(tag)); return [...tag_set]; } var ObsidianMarkdownSourceContentAdapter = class extends MarkdownSourceContentAdapter { /** * Returns metadata using Obsidian's metadataCache, merging frontmatter and tags. * @async * @returns {Promise} */ async get_metadata() { const app2 = this.item.env.main.app; const cache = app2.metadataCache.getFileCache(this.item.file) || {}; const tags = merge_tags(cache.frontmatter?.tags, cache.tags); if (cache.frontmatter) { if (tags.length) cache.frontmatter.tags = tags; return cache.frontmatter; } return tags.length ? { tags } : void 0; } /** * Reads the file content. If opts.render_output is true, attempts to use * Obsidian's MarkdownRenderer to render the file to HTML, then convert it * back to markdown via htmlToMarkdown. * @async * @param {Object} [opts={}] - Options for reading. * @param {boolean} [opts.render_output=false] - If true, render MD -> HTML -> MD. * @returns {Promise} The file content (possibly rendered). */ async read(opts = {}) { const content = await super.read(opts); if (!opts.render_output) { return content; } const app2 = this.item.env.main.app; if (!app2 || !import_obsidian2.MarkdownRenderer || !import_obsidian2.htmlToMarkdown) { console.warn("Obsidian environment not found; cannot render markdown."); return content; } const container = document.createElement("div"); await import_obsidian2.MarkdownRenderer.render(app2, content, container, this.item.path, new import_obsidian2.Component()); let last_html = container.innerHTML; const max_wait = 1e4; let wait_time = 0; let conseq_same = 0; let changed = true; while (conseq_same < 7) { await new Promise((resolve) => setTimeout(resolve, 100)); changed = last_html !== container.innerHTML; last_html = container.innerHTML; if (!changed) conseq_same++; else conseq_same = 0; wait_time += 100; if (wait_time > max_wait) { console.warn("ObsidianMarkdownSourceContentAdapter: Timeout waiting for markdown to render."); break; } } const newMd = (0, import_obsidian2.htmlToMarkdown)(container); return newMd; } }; // node_modules/obsidian-smart-env/adapters/smart-sources/bases.js var BasesSourceContentAdapter = class extends FileSourceContentAdapter { static extensions = ["base"]; async import() { if (!this.item?.file) return; const base_links = get_bases_file_links({ source: this.item }); this.data.outlinks = base_links; this.data.blocks = this.data.blocks || {}; this.data.metadata = this.data.metadata || {}; const { mtime = 0, size = 0 } = this.item.file?.stat || {}; this.data.last_import = { mtime, size, at: Date.now(), hash: this.data.last_read?.hash }; this.item.loaded_at = Date.now(); this.item.queue_save?.(); } }; // node_modules/obsidian-smart-env/adapters/smart-sources/rendered.js var RenderedSourceContentAdapter = class extends FileSourceContentAdapter { static extensions = ["rendered"]; async import() { } }; // node_modules/obsidian-smart-env/adapters/smart-sources/canvas.js function parse_canvas_json({ content } = {}) { if (!content) return null; try { return JSON.parse(content); } catch (error) { console.warn("CanvasSourceContentAdapter: invalid JSON content.", error); return null; } } function build_link_record({ target, title } = {}) { if (!target) return null; return { title: title || target, target, line: 1 }; } function get_canvas_node_links({ node } = {}) { if (!node || typeof node !== "object") return []; if (node.type === "text" && typeof node.text === "string") { return get_markdown_links(node.text); } if (node.type === "file" && typeof node.file === "string") { const subpath = typeof node.subpath === "string" ? node.subpath : ""; const target = `${node.file}${subpath}`; const record = build_link_record({ target, title: node.file }); return record ? [record] : []; } if (node.type === "link" && typeof node.url === "string") { const record = build_link_record({ target: node.url, title: node.url }); return record ? [record] : []; } return []; } function get_canvas_links_from_nodes({ nodes = [] } = {}) { if (!Array.isArray(nodes)) return []; return nodes.reduce((links, node) => { links.push(...get_canvas_node_links({ node })); return links; }, []); } function get_canvas_links({ content } = {}) { const canvas_data = parse_canvas_json({ content }); if (!canvas_data?.nodes) return []; return get_canvas_links_from_nodes({ nodes: canvas_data.nodes }); } var CanvasSourceContentAdapter = class extends FileSourceContentAdapter { static extensions = ["canvas"]; async import() { if (!this.item.file) { console.warn(`CanvasSourceContentAdapter: Skipping missing-file: ${this.file_path}`); return; } const content = await this.read(); if (!content) return; if (this.data.last_import?.hash === this.data.last_read?.hash && Array.isArray(this.data.outlinks)) { return; } this.data.outlinks = get_canvas_links({ content }); 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-http-request/smart_http_request.js var SmartHttpRequest = class { /** * @param {object} opts - Options for the SmartHttpRequest class * @param {SmartHttpRequestAdapter} opts.adapter - The adapter constructor to use for making HTTP requests * @param {Obsidian.requestUrl} opts.obsidian_request_adapter - For use with Obsidian adapter */ constructor(opts = {}) { this.opts = opts; if (!opts.adapter) throw new Error("HttpRequestAdapter is required"); this.adapter = new opts.adapter(this); } /** * Returns a well-formed response object * @param {object} request_params - Parameters for the HTTP request * @param {string} request_params.url - The URL to make the request to * @param {string} [request_params.method='GET'] - The HTTP method to use * @param {object} [request_params.headers] - Headers to include in the request * @param {*} [request_params.body] - The body of the request (for POST, PUT, etc.) * @returns {SmartHttpResponseAdapter} instance of the SmartHttpResponseAdapter class * @example * const response = await smart_http_request.request({ * url: 'https://api.example.com/data', * method: 'GET', * headers: { 'Content-Type': 'application/json' } * }); * console.log(await response.json()); */ async request(request_params, throw_on_error = false) { return await this.adapter.request(request_params, throw_on_error); } }; // node_modules/obsidian-smart-env/node_modules/smart-http-request/adapters/_adapter.js var SmartHttpRequestAdapter = class { constructor(main) { this.main = main; } /** * Execute an HTTP request using adapter-specific transport. * @abstract * @param {Object} request_params - Parameters for the outbound request. * @returns {Promise} Adapter-specific response wrapper. */ async request(request_params) { throw new Error("request not implemented"); } }; var SmartHttpResponseAdapter = class { constructor(response) { this.response = response; } /** * Retrieve response headers. * @abstract * @returns {Promise} Headers object for the response. */ async headers() { throw new Error("headers not implemented"); } /** * Parse the response body as JSON. * @abstract * @returns {Promise<*>} Parsed JSON payload. */ async json() { throw new Error("json not implemented"); } /** * Get the HTTP status code. * @abstract * @returns {Promise} Response status code. */ async status() { throw new Error("status not implemented"); } /** * Read the raw text body. * @abstract * @returns {Promise} Response body as text. */ async text() { throw new Error("text not implemented"); } }; // node_modules/obsidian-smart-env/node_modules/smart-http-request/adapters/obsidian.js var SmartHttpObsidianRequestAdapter = class extends SmartHttpRequestAdapter { async request(request_params, throw_on_error = false) { let response; try { if (!this.main.opts.obsidian_request_url) { throw new Error("obsidian_request_url is required in SmartHttp constructor opts"); } response = await this.main.opts.obsidian_request_url({ ...request_params, throw: throw_on_error }); if (throw_on_error && response.status === 400) throw new Error("Obsidian request failed"); return new SmartHttpObsidianResponseAdapter(response); } catch (error) { console.error("Error in SmartHttpObsidianRequestAdapter.request():"); console.error(JSON.stringify(request_params, null, 2)); console.error(response); console.error(error); return null; } } }; var SmartHttpObsidianResponseAdapter = class extends SmartHttpResponseAdapter { async status() { return this.response.status; } async json() { return await this.response.json; } async text() { return await this.response.text; } async headers() { return this.response.headers; } }; // node_modules/obsidian-smart-env/node_modules/smart-http-request/adapters/fetch.js var SmartHttpRequestFetchAdapter = class extends SmartHttpRequestAdapter { async request(request_params) { const { url, ...opts } = request_params; const resp = await fetch(url, opts); return new SmartHttpResponseFetchAdapter(resp); } }; var SmartHttpResponseFetchAdapter = class extends SmartHttpResponseAdapter { async headers() { return this.response.headers; } async json() { if (!this._json) { this._json = await this.response.json(); } return this._json; } async status() { return this.response.status; } async text() { if (!this._text) { this._text = await this.response.text(); } return this._text; } }; // node_modules/obsidian-smart-env/node_modules/js-tiktoken/dist/chunk-ZDNLBERF.js var import_base64_js = __toESM(require_base64_js(), 1); var __defProp2 = Object.defineProperty; var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value; var __publicField = (obj, key, value) => { __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value); return value; }; function bytePairMerge(piece, ranks) { let parts = Array.from( { length: piece.length }, (_, i) => ({ start: i, end: i + 1 }) ); while (parts.length > 1) { let minRank = null; for (let i = 0; i < parts.length - 1; i++) { const slice = piece.slice(parts[i].start, parts[i + 1].end); const rank = ranks.get(slice.join(",")); if (rank == null) continue; if (minRank == null || rank < minRank[0]) { minRank = [rank, i]; } } if (minRank != null) { const i = minRank[1]; parts[i] = { start: parts[i].start, end: parts[i + 1].end }; parts.splice(i + 1, 1); } else { break; } } return parts; } function bytePairEncode(piece, ranks) { if (piece.length === 1) return [ranks.get(piece.join(","))]; return bytePairMerge(piece, ranks).map((p) => ranks.get(piece.slice(p.start, p.end).join(","))).filter((x) => x != null); } function escapeRegex(str) { return str.replace(/[\\^$*+?.()|[\]{}]/g, "\\$&"); } var _Tiktoken = class { /** @internal */ specialTokens; /** @internal */ inverseSpecialTokens; /** @internal */ patStr; /** @internal */ textEncoder = new TextEncoder(); /** @internal */ textDecoder = new TextDecoder("utf-8"); /** @internal */ rankMap = /* @__PURE__ */ new Map(); /** @internal */ textMap = /* @__PURE__ */ new Map(); constructor(ranks, extendedSpecialTokens) { this.patStr = ranks.pat_str; const uncompressed = ranks.bpe_ranks.split("\n").filter(Boolean).reduce((memo, x) => { const [_, offsetStr, ...tokens] = x.split(" "); const offset = Number.parseInt(offsetStr, 10); tokens.forEach((token, i) => memo[token] = offset + i); return memo; }, {}); for (const [token, rank] of Object.entries(uncompressed)) { const bytes = import_base64_js.default.toByteArray(token); this.rankMap.set(bytes.join(","), rank); this.textMap.set(rank, bytes); } this.specialTokens = { ...ranks.special_tokens, ...extendedSpecialTokens }; this.inverseSpecialTokens = Object.entries(this.specialTokens).reduce((memo, [text, rank]) => { memo[rank] = this.textEncoder.encode(text); return memo; }, {}); } encode(text, allowedSpecial = [], disallowedSpecial = "all") { const regexes = new RegExp(this.patStr, "ug"); const specialRegex = _Tiktoken.specialTokenRegex( Object.keys(this.specialTokens) ); const ret = []; const allowedSpecialSet = new Set( allowedSpecial === "all" ? Object.keys(this.specialTokens) : allowedSpecial ); const disallowedSpecialSet = new Set( disallowedSpecial === "all" ? Object.keys(this.specialTokens).filter( (x) => !allowedSpecialSet.has(x) ) : disallowedSpecial ); if (disallowedSpecialSet.size > 0) { const disallowedSpecialRegex = _Tiktoken.specialTokenRegex([ ...disallowedSpecialSet ]); const specialMatch = text.match(disallowedSpecialRegex); if (specialMatch != null) { throw new Error( `The text contains a special token that is not allowed: ${specialMatch[0]}` ); } } let start = 0; while (true) { let nextSpecial = null; let startFind = start; while (true) { specialRegex.lastIndex = startFind; nextSpecial = specialRegex.exec(text); if (nextSpecial == null || allowedSpecialSet.has(nextSpecial[0])) break; startFind = nextSpecial.index + 1; } const end = nextSpecial?.index ?? text.length; for (const match of text.substring(start, end).matchAll(regexes)) { const piece = this.textEncoder.encode(match[0]); const token2 = this.rankMap.get(piece.join(",")); if (token2 != null) { ret.push(token2); continue; } ret.push(...bytePairEncode(piece, this.rankMap)); } if (nextSpecial == null) break; let token = this.specialTokens[nextSpecial[0]]; ret.push(token); start = nextSpecial.index + nextSpecial[0].length; } return ret; } decode(tokens) { const res = []; let length = 0; for (let i2 = 0; i2 < tokens.length; ++i2) { const token = tokens[i2]; const bytes = this.textMap.get(token) ?? this.inverseSpecialTokens[token]; if (bytes != null) { res.push(bytes); length += bytes.length; } } const mergedArray = new Uint8Array(length); let i = 0; for (const bytes of res) { mergedArray.set(bytes, i); i += bytes.length; } return this.textDecoder.decode(mergedArray); } }; var Tiktoken = _Tiktoken; __publicField(Tiktoken, "specialTokenRegex", (tokens) => { return new RegExp(tokens.map((i) => escapeRegex(i)).join("|"), "g"); }); // node_modules/obsidian-smart-env/node_modules/smart-embed-model/utils/fetch_cache.js async function fetch_json_cached(url, cache_key = url) { const is_browser = typeof window !== "undefined" && typeof window.document !== "undefined"; if (is_browser) { const cached_text = window.localStorage.getItem(cache_key); if (cached_text) return JSON.parse(cached_text); const remote2 = await do_fetch(url); window.localStorage.setItem(cache_key, JSON.stringify(remote2)); return remote2; } const fs = await import("node:fs/promises"); const path = await import("node:path"); const os = await import("node:os"); const cache_dir = path.join(os.homedir(), ".cache", "smart-embed-model"); const cache_file = path.join(cache_dir, cache_key); try { const txt = await fs.readFile(cache_file, "utf8"); return JSON.parse(txt); } catch { } const remote = await do_fetch(url); await fs.mkdir(cache_dir, { recursive: true }); await fs.writeFile(cache_file, JSON.stringify(remote), "utf8"); return remote; } async function do_fetch(url) { const resp = await fetch(url); if (!resp.ok) throw new Error(`failed to download ${url} \u2013 ${resp.status}`); return await resp.json(); } // node_modules/obsidian-smart-env/node_modules/smart-utils/normalize_error.js function is_json_compatible(value) { if (value === null) { return true; } const type = typeof value; if (type === "string" || type === "number" || type === "boolean") { return true; } if (Array.isArray(value)) { return value.every(is_json_compatible); } if (type === "object") { const obj = ( /** @type {Record} */ value ); return Object.values(obj).every(is_json_compatible); } return false; } function extract_json_details(source, exclude_keys) { const details = {}; for (const [key, value] of Object.entries(source)) { if (exclude_keys.includes(key)) { continue; } if (!is_json_compatible(value)) { continue; } details[key] = value; } return details; } function is_empty_object(obj) { return Object.keys(obj).length === 0; } function merge_details(first, second) { if (is_empty_object(first)) { return second; } if (is_empty_object(second)) { return first; } return { ...first, ...second }; } function get_message_from_object(value) { const raw = value.message; if (typeof raw === "string") { const trimmed = raw.trim(); if (trimmed.length > 0) { return trimmed; } } return null; } function normalize_error(error, http_status = null) { if (Array.isArray(error) && error.length > 0) { return normalize_error(error[0], http_status); } if (error == null) { return { message: "Unknown error", details: null, http_status }; } if (typeof error === "string") { return { message: error, details: null, http_status }; } if (error instanceof Error) { const message = (error.message || "").trim() || "Unknown error"; const extra_details = extract_json_details( /** @type {Record} */ error, ["message"] ); return { message, details: is_empty_object(extra_details) ? null : extra_details, http_status }; } if (typeof error === "object") { const obj = ( /** @type {Record} */ error ); if ("error" in obj && obj.error != null) { const nested_error = obj.error; if (typeof nested_error === "object") { const nested_obj = ( /** @type {Record} */ nested_error ); const nested_message = get_message_from_object(nested_obj); const nested_details = extract_json_details(nested_obj, ["message"]); const outer_details = extract_json_details(obj, ["message", "error"]); const combined_details = merge_details(outer_details, nested_details); const message = nested_message || get_message_from_object(obj) || "Unknown error"; return { message, details: is_empty_object(combined_details) ? null : combined_details, http_status }; } return normalize_error(nested_error); } const object_message = get_message_from_object(obj); if (object_message) { const details = extract_json_details(obj, ["message"]); return { message: object_message, details: is_empty_object(details) ? null : details, http_status }; } } return { message: "Unknown error", details: null, http_status }; } // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/_api.js var CL100K_URL = "https://raw.githubusercontent.com/brianpetro/jsbrains/refs/heads/main/smart-embed-model/cl100k_base.json"; var SmartEmbedModelApiAdapter = class extends SmartEmbedAdapter { /** * Get the request adapter class. * @returns {SmartEmbedModelRequestAdapter} The request adapter class */ get req_adapter() { return SmartEmbedModelRequestAdapter; } /** * Get the response adapter class. * @returns {SmartEmbedModelResponseAdapter} The response adapter class */ get res_adapter() { return SmartEmbedModelResponseAdapter; } /** @returns {string} API endpoint URL */ get endpoint() { return this.model.data.endpoint; } /** * Get HTTP request adapter instance * @returns {SmartHttpRequest} HTTP request handler */ get http_adapter() { if (!this._http_adapter) { if (this.model.opts.http_adapter) this._http_adapter = this.model.opts.http_adapter; else this._http_adapter = new SmartHttpRequest({ adapter: SmartHttpRequestFetchAdapter }); } return this._http_adapter; } /** * Get API key for authentication * @returns {string} API key */ get api_key() { return this.model.data.api_key; } /** * Count tokens in input text * @abstract * @param {string} input - Text to tokenize * @returns {Promise} Token count result * @throws {Error} If not implemented by subclass */ async count_tokens(input) { throw new Error("count_tokens not implemented"); } /** * Estimate token count for input text * Uses character-based estimation (3.7 chars per token) * @param {string|Object} input - Input to estimate tokens for * @returns {number} Estimated token count */ estimate_tokens(input) { if (typeof input === "object") input = JSON.stringify(input); return Math.ceil(input.length / 3.7); } /** * Process a batch of inputs for embedding * @param {Array} inputs - Array of input objects * @returns {Promise>} Processed inputs with embeddings * @throws {Error} If API key is not set */ async embed_batch(inputs) { if (!this.api_key) throw new Error("API key not set"); inputs = inputs.filter((item) => item.embed_input?.length > 0); if (inputs.length === 0) { console.log("Empty batch (or all items have empty embed_input)"); return []; } const embed_inputs = await Promise.all( inputs.map((item) => this.prepare_embed_input(item.embed_input)) ); const _req = new this.req_adapter(this, embed_inputs); const request_params = _req.to_platform(); const resp = await this.request(request_params); if (!resp) { console.error("No response received for embedding request."); return []; } if (resp.error) return [resp]; const _res = new this.res_adapter(this, resp); const embeddings = _res.to_openai(); if (!embeddings) { console.error("Failed to parse embeddings."); return []; } return inputs.map((item, i) => { item.vec = embeddings[i].vec; item.tokens = embeddings[i].tokens; return item; }); } /** * Prepare input text for embedding * @abstract * @param {string} embed_input - Raw input text * @returns {Promise} Processed input text * @throws {Error} If not implemented by subclass */ async prepare_embed_input(embed_input) { throw new Error("prepare_embed_input not implemented"); } /** * Prepare request headers * @returns {Object} Headers object with authorization */ prepare_request_headers() { let headers = { "Content-Type": "application/json" }; if (this.api_key) { headers["Authorization"] = `Bearer ${this.api_key}`; } return headers; } /** * Make API request with retry logic * @param {Object} req - Request configuration * @param {number} [retries=0] - Number of retries attempted * @returns {Promise} API response */ async request(req, retries = 0) { try { req.throw = false; const resp = await this.http_adapter.request({ url: this.endpoint, ...req }); const resp_json = await this.get_resp_json(resp); if (resp_json.error) { return { error: normalize_error(resp_json, resp.status()) }; } return resp_json; } catch (error) { console.warn("Request error:", error); return await this.handle_request_err(error, req, retries); } } /** * Handle API request errors with retry logic * @param {Error|Object} error - Error object * @param {Object} req - Original request * @param {number} retries - Number of retries attempted * @returns {Promise} Retry response or null */ async handle_request_err(error, req, retries) { if (error.status === 429 && retries < 3) { const backoff = Math.pow(retries + 1, 2); console.log(`Retrying request (429) in ${backoff} seconds...`); await new Promise((r) => setTimeout(r, 1e3 * backoff)); return await this.request(req, retries + 1); } console.error(error); return null; } /** * Parse response body as JSON * @param {Response} resp - Response object * @returns {Promise} Parsed JSON */ async get_resp_json(resp) { return typeof resp.json === "function" ? await resp.json() : await resp.json; } /** * Validate API key by making test request * @returns {Promise} True if API key is valid */ async validate_api_key() { const resp = await this.embed_batch([{ embed_input: "test" }]); return Array.isArray(resp) && resp.length > 0 && resp[0].vec !== null; } /** * Trim input text to satisfy `max_tokens`. * @param {string} embed_input - Input text * @param {number} tokens_ct - Existing token count * @returns {Promise} Trimmed text */ async trim_input_to_max_tokens(embed_input, tokens_ct) { const reduce_ratio = (tokens_ct - this.max_tokens) / tokens_ct; const new_length = Math.floor(embed_input.length * (1 - reduce_ratio)); let trimmed_input = embed_input.slice(0, new_length); const last_space_index = trimmed_input.lastIndexOf(" "); if (last_space_index > 0) trimmed_input = trimmed_input.slice(0, last_space_index); const prepared = await this.prepare_embed_input(trimmed_input); if (prepared === null) return null; return prepared; } async load_tiktoken() { const cl100k_base = await fetch_json_cached(CL100K_URL, "cl100k_base.json"); this.tiktoken = new Tiktoken(cl100k_base); } }; var SmartEmbedModelRequestAdapter = class { /** * @constructor * @param {SmartEmbedModelApiAdapter} adapter - The SmartEmbedModelApiAdapter instance * @param {Array} embed_inputs - The array of input texts */ constructor(adapter, embed_inputs) { this.adapter = adapter; this.embed_inputs = embed_inputs; } get model_id() { return this.adapter.model.data.model_key; } get model_dims() { return this.adapter.model.data.dims; } /** * Get request headers * @returns {Object} Headers object */ get_headers() { return this.adapter.prepare_request_headers(); } /** * Convert request to platform-specific format * @returns {Object} Platform-specific request parameters */ to_platform() { return { method: "POST", headers: this.get_headers(), body: JSON.stringify(this.prepare_request_body()) }; } /** * Prepare request body for API call * @abstract * @returns {Object} Request body object * @throws {Error} If not implemented by subclass */ prepare_request_body() { throw new Error("prepare_request_body not implemented"); } }; var SmartEmbedModelResponseAdapter = class { /** * @constructor * @param {SmartEmbedModelApiAdapter} adapter - The SmartEmbedModelApiAdapter instance * @param {Object} response - The response object */ constructor(adapter, response) { this.adapter = adapter; this.response = response; } /** * Convert response to standard format * @returns {Array} Array of embedding results */ to_openai() { return this.parse_response(); } /** * Parse API response * @abstract * @returns {Array} Parsed embedding results * @throws {Error} If not implemented by subclass */ parse_response() { throw new Error("parse_response not implemented"); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/openai.js var SmartEmbedOpenAIAdapter = class extends SmartEmbedModelApiAdapter { static defaults = { adapter: "openai", description: "OpenAI (API)", default_model: "text-embedding-3-small", endpoint: "https://api.openai.com/v1/embeddings" }; /** * Count tokens in input text using OpenAI's tokenizer * @param {string} input - Text to tokenize * @returns {Promise} Token count result */ async count_tokens(input) { if (!this.tiktoken) await this.load_tiktoken(); return { tokens: this.tiktoken.encode(input).length }; } /** * Prepare input text for embedding * Handles token limit truncation * @param {string} embed_input - Raw input text * @returns {Promise} Processed input text */ async prepare_embed_input(embed_input) { if (typeof embed_input !== "string") { throw new TypeError("embed_input must be a string"); } if (embed_input.length === 0) { console.log("Warning: prepare_embed_input received an empty string"); return null; } const { tokens } = await this.count_tokens(embed_input); if (tokens <= this.max_tokens) { return embed_input; } return await this.trim_input_to_max_tokens(embed_input, tokens); } /** * Trim input text to fit token limit * @private * @param {string} embed_input - Input text to trim * @param {number} tokens_ct - Current token count * @returns {Promise} Trimmed input text */ async trim_input_to_max_tokens(embed_input, tokens_ct) { const reduce_ratio = (tokens_ct - this.max_tokens) / tokens_ct; const new_length = Math.floor(embed_input.length * (1 - reduce_ratio)); let trimmed_input = embed_input.slice(0, new_length); const last_space_index = trimmed_input.lastIndexOf(" "); if (last_space_index > 0) { trimmed_input = trimmed_input.slice(0, last_space_index); } const prepared_input = await this.prepare_embed_input(trimmed_input); if (prepared_input === null) { console.log( "Warning: prepare_embed_input resulted in an empty string after trimming" ); return null; } return prepared_input; } /** * Get the request adapter class. * @returns {SmartEmbedOpenAIRequestAdapter} The request adapter class */ get req_adapter() { return SmartEmbedOpenAIRequestAdapter; } /** * Get the response adapter class. * @returns {SmartEmbedOpenAIResponseAdapter} The response adapter class */ get res_adapter() { return SmartEmbedOpenAIResponseAdapter; } /** @returns {number} Maximum tokens per input */ get max_tokens() { return this.model.data.max_tokens || 8191; } /** @returns {Object} Settings configuration for OpenAI adapter */ get settings_config() { return { ...super.settings_config, "[ADAPTER].api_key": { name: "OpenAI API key for embeddings", type: "password", description: "Required for OpenAI embedding models.", placeholder: "Enter OpenAI API key" } }; } /** * Get available models (hardcoded list) * @returns {Promise} Map of model objects */ get_models() { return Promise.resolve(this.models); } get models() { return { "text-embedding-3-small": { "id": "text-embedding-3-small", "batch_size": 50, "dims": 1536, "max_tokens": 8191, "name": "OpenAI Text-3 Small", "description": "API, 8,191 tokens, 1,536 dim", "endpoint": "https://api.openai.com/v1/embeddings", "adapter": "openai" }, "text-embedding-3-large": { "id": "text-embedding-3-large", "batch_size": 50, "dims": 3072, "max_tokens": 8191, "name": "OpenAI Text-3 Large", "description": "API, 8,191 tokens, 3,072 dim", "endpoint": "https://api.openai.com/v1/embeddings", "adapter": "openai" }, // "text-embedding-3-small-512": { // "id": "text-embedding-3-small", // "batch_size": 50, // "dims": 512, // "max_tokens": 8191, // "name": "OpenAI Text-3 Small - 512", // "description": "API, 8,191 tokens, 512 dim", // "endpoint": "https://api.openai.com/v1/embeddings", // "adapter": "openai" // }, // "text-embedding-3-large-256": { // "id": "text-embedding-3-large", // "batch_size": 50, // "dims": 256, // "max_tokens": 8191, // "name": "OpenAI Text-3 Large - 256", // "description": "API, 8,191 tokens, 256 dim", // "endpoint": "https://api.openai.com/v1/embeddings", // "adapter": "openai" // }, "text-embedding-ada-002": { "id": "text-embedding-ada-002", "batch_size": 50, "dims": 1536, "max_tokens": 8191, "name": "OpenAI Ada", "description": "API, 8,191 tokens, 1,536 dim", "endpoint": "https://api.openai.com/v1/embeddings", "adapter": "openai" } }; } }; var SmartEmbedOpenAIRequestAdapter = class extends SmartEmbedModelRequestAdapter { /** * Prepare request body for OpenAI API * @returns {Object} Request body for API */ prepare_request_body() { const body = { model: this.model_id, input: this.embed_inputs }; if (this.model_id.startsWith("text-embedding-3")) { body.dimensions = this.model_dims; } return body; } }; var SmartEmbedOpenAIResponseAdapter = class extends SmartEmbedModelResponseAdapter { /** * Parse OpenAI API response * @returns {Array} Parsed embedding results */ parse_response() { const resp = this.response; if (!resp || !resp.data || !resp.usage) { console.error("Invalid response format", resp); return []; } const avg_tokens = resp.usage.total_tokens / resp.data.length; return resp.data.map((item) => ({ vec: item.embedding, tokens: avg_tokens // OpenAI doesn't provide tokens per item in batch requests })); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/_message.js var SmartEmbedMessageAdapter = class extends SmartEmbedAdapter { /** * Create message adapter instance */ constructor(model) { super(model); this.message_queue = {}; this.message_id = 0; this.connector = null; this.message_prefix = `msg_${Math.random().toString(36).substr(2, 9)}_`; } /** * Send message and wait for response * @protected * @param {string} method - Method name to call * @param {Object} params - Method parameters * @returns {Promise} Response data */ async _send_message(method, params) { return new Promise((resolve, reject) => { const id = `${this.message_prefix}${this.message_id++}`; this.message_queue[id] = { resolve, reject }; try { this._post_message({ method, params, id }); } catch (error) { delete this.message_queue[id]; reject(error instanceof Error ? error : new Error(String(error || "Unknown error"))); } }); } unload() { const unload_error = new Error("Message adapter unloaded"); Object.values(this.message_queue).forEach((queue_entry) => { if (!queue_entry) return; if (typeof this.clear_message_timeout === "function") { this.clear_message_timeout(queue_entry); } queue_entry.reject(unload_error); }); this.message_queue = {}; super.unload(); } /** * Handle response message from worker/iframe * @protected * @param {string} id - Message ID * @param {*} result - Response result * @param {Error} [error] - Response error */ _handle_message_result(id, result, error) { if (!id.startsWith(this.message_prefix)) return; if (result?.model_loaded) { console.log("model loaded"); this.state = "loaded"; this.model.model_loaded = true; this.model.load_result = result; } if (this.message_queue[id]) { if (error) { this.message_queue[id].reject(new Error(error)); } else { this.message_queue[id].resolve(result); } delete this.message_queue[id]; } } /** * Count tokens in input text * @param {string} input - Text to tokenize * @returns {Promise} Token count result */ async count_tokens(input) { return this._send_message("count_tokens", { input }); } /** * Generate embeddings for multiple inputs * @param {Array} inputs - Array of input objects * @returns {Promise>} Processed inputs with embeddings */ async embed_batch(inputs) { inputs = inputs.filter((item) => item.embed_input?.length > 0); if (!inputs.length) return []; const embed_inputs = inputs.map((item) => ({ embed_input: item.embed_input })); const result = await this._send_message("embed_batch", { inputs: embed_inputs }); return inputs.map((item, i) => { const item_result = result[i] || {}; if ("vec" in item_result) item.vec = item_result.vec; if ("tokens" in item_result) item.tokens = item_result.tokens; if ("error" in item_result) item.error = item_result.error; else delete item.error; return item; }); } /** * Post message to worker/iframe * @abstract * @protected * @param {Object} message_data - Message to send * @throws {Error} If not implemented by subclass */ _post_message(message_data) { throw new Error("_post_message must be implemented by subclass"); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/iframe.js var SmartEmbedIframeAdapter = class extends SmartEmbedMessageAdapter { /** * Create iframe adapter instance */ constructor(model) { super(model); this.iframe = null; this.origin = window.location.origin; this.iframe_id = `smart_embed_iframe`; this._bound_handle_message = this._handle_message.bind(this); } /** * Initialize iframe and load model * @returns {Promise} */ async load() { this.unload(); const existing_iframe = document.getElementById(this.iframe_id); if (existing_iframe) { existing_iframe.onload = null; existing_iframe.remove(); } this.iframe = document.createElement("iframe"); this.iframe.style.display = "none"; this.iframe.id = this.iframe_id; document.body.appendChild(this.iframe); window.addEventListener("message", this._bound_handle_message); this.iframe.srcdoc = ` `; await new Promise((resolve) => this.iframe.onload = resolve); const load_opts = { // ...this.model.opts, model_key: this.model.model_key, adapters: null, // cannot clone classes settings: null, batch_size: this.batch_size, use_gpu: this.use_gpu }; await this._send_message("load", load_opts); return new Promise((resolve) => { const check_model_loaded = () => { if (this.model.model_loaded) { resolve(); } else { setTimeout(check_model_loaded, 100); } }; check_model_loaded(); }); } /** * Detect expected cancellation caused by tearing down the iframe adapter * while a background load is in flight. * @param {Error|*} error * @returns {boolean} */ is_unload_error(error) { return error?.message === "Message adapter unloaded"; } /** * Start loading in the background and suppress only expected unload * cancellation from fire-and-forget call sites. * @returns {Promise} */ load_background() { if (this._load_background_promise) { return this._load_background_promise; } this._load_background_promise = Promise.resolve(this.load()).catch((error) => { if (this.is_unload_error(error)) return; console.error(`[${this.constructor.name}] load failed`, error); }).finally(() => { this._load_background_promise = null; }); return this._load_background_promise; } unload() { window.removeEventListener("message", this._bound_handle_message); const iframe = this.iframe || document.getElementById(this.iframe_id); if (iframe) { iframe.onload = null; iframe.remove(); } this.iframe = null; if (this.model) { this.model.model_loaded = false; this.model.load_result = null; } super.unload(); } /** * Post message to iframe * @protected * @param {Object} message_data - Message to send */ _post_message(message_data) { if (!this.iframe?.contentWindow) { throw new Error("Iframe not loaded"); } this.iframe.contentWindow.postMessage({ ...message_data, iframe_id: this.iframe_id }, this.origin); } /** * Handle message from iframe * @private * @param {MessageEvent} event - Message event */ _handle_message(event) { if (event.origin !== this.origin || event.data?.iframe_id !== this.iframe_id) return; if (event.source !== this.iframe?.contentWindow) return; const { id, result, error } = event.data; this._handle_message_result(id, result, error); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/connectors/transformers_iframe.js var transformers_connector = 'var __defProp = Object.defineProperty;\nvar __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;\nvar __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);\n\n// ../smart-model/adapters/_adapter.js\nvar SmartModelAdapter = class {\n /**\n * Create a SmartModelAdapter instance.\n * @param {SmartModel} model - The parent SmartModel instance\n */\n constructor(model2) {\n this.model = model2;\n this.state = "unloaded";\n }\n /**\n * Load the adapter.\n * @async\n * @returns {Promise}\n */\n async load() {\n this.set_state("loaded");\n }\n /**\n * Unload the adapter.\n * @returns {void}\n */\n unload() {\n this.set_state("unloaded");\n }\n /**\n * Get all settings.\n * @returns {Object} All settings\n */\n get settings() {\n return this.model.settings;\n }\n /**\n * Get the current model key.\n * @returns {string} Current model identifier\n */\n get model_key() {\n return this.model.model_key;\n }\n /**\n * Get the models.\n * @returns {Object} Map of model objects\n */\n get models() {\n const models = this.model.data.provider_models;\n if (typeof models === "object" && Object.keys(models || {}).length > 0) return models;\n else {\n return {};\n }\n }\n /**\n * Get available models from the API.\n * @abstract\n * @param {boolean} [refresh=false] - Whether to refresh cached models\n * @returns {Promise} Map of model objects\n */\n async get_models(refresh = false) {\n throw new Error("get_models not implemented");\n }\n /**\n * Get available models as dropdown options synchronously.\n * @returns {Array} Array of model options.\n */\n get_models_as_options() {\n const models = this.models;\n if (!Object.keys(models || {}).length) {\n this.get_models(true);\n return [{ value: "", name: "No models currently available" }];\n }\n return Object.entries(models).map(([id, model2]) => ({ value: id, name: model2.name || id })).sort((a, b) => a.name.localeCompare(b.name));\n }\n /**\n * Set the adapter\'s state.\n * @deprecated should be handled in SmartModel (only handle once)\n * @param {(\'unloaded\'|\'loading\'|\'loaded\'|\'unloading\')} new_state - The new state\n * @throws {Error} If the state is invalid\n */\n set_state(new_state) {\n const valid_states = ["unloaded", "loading", "loaded", "unloading"];\n if (!valid_states.includes(new_state)) {\n throw new Error(`Invalid state: ${new_state}`);\n }\n this.state = new_state;\n }\n // Replace individual state getters/setters with a unified state management\n get is_loading() {\n return this.state === "loading";\n }\n get is_loaded() {\n return this.state === "loaded";\n }\n get is_unloading() {\n return this.state === "unloading";\n }\n get is_unloaded() {\n return this.state === "unloaded";\n }\n};\n\n// adapters/_adapter.js\nvar SmartEmbedAdapter = class extends SmartModelAdapter {\n /**\n * Count tokens in input text\n * @abstract\n * @param {string} input - Text to tokenize\n * @returns {Promise} Token count result\n * @property {number} tokens - Number of tokens in input\n * @throws {Error} If not implemented by subclass\n */\n async count_tokens(input) {\n throw new Error("count_tokens method not implemented");\n }\n /**\n * Generate embeddings for single input\n * @abstract\n * @param {string|Object} input - Text to embed\n * @returns {Promise} Embedding result\n * @property {number[]} vec - Embedding vector\n * @property {number} tokens - Number of tokens in input\n * @throws {Error} If not implemented by subclass\n */\n async embed(input) {\n if (typeof input === "string") input = { embed_input: input };\n return (await this.embed_batch([input]))[0];\n }\n /**\n * Generate embeddings for multiple inputs\n * @abstract\n * @param {Array} inputs - Texts to embed\n * @returns {Promise>} Array of embedding results\n * @property {number[]} vec - Embedding vector for each input\n * @property {number} tokens - Number of tokens in each input\n * @throws {Error} If not implemented by subclass\n */\n async embed_batch(inputs) {\n throw new Error("embed_batch method not implemented");\n }\n get settings_config() {\n return {\n "[ADAPTER].model_key": {\n name: "Embedding model",\n type: "dropdown",\n description: "Select an embedding model.",\n options_callback: "adapter.get_models_as_options",\n callback: "model_changed",\n default: this.constructor.defaults.default_model\n }\n };\n }\n get dims() {\n return this.model.data.dims;\n }\n get max_tokens() {\n return this.model.data.max_tokens;\n }\n get batch_size() {\n return this.model.data.batch_size || 1;\n }\n};\n/**\n * @override in sub-class with adapter-specific default configurations\n * @property {string} id - The adapter identifier\n * @property {string} description - Human-readable description\n * @property {string} type - Adapter type ("API")\n * @property {string} endpoint - API endpoint\n * @property {string} adapter - Adapter identifier\n * @property {string} default_model - Default model to use\n */\n__publicField(SmartEmbedAdapter, "defaults", {});\n\n// adapters/transformers.js\nvar transformers_defaults = {\n adapter: "transformers",\n description: "Transformers (Local, built-in)",\n default_model: "TaylorAI/bge-micro-v2",\n models: transformers_models\n};\nvar DEVICE_CONFIGS = {\n // // WebGPU: high quality first\n webgpu_fp16: {\n device: "webgpu",\n dtype: "fp16",\n quantized: false\n },\n webgpu_fp32: {\n device: "webgpu",\n dtype: "fp32",\n quantized: false\n },\n // WebGPU: quantized tiers\n webgpu_q8: {\n device: "webgpu",\n dtype: "q8",\n quantized: true\n },\n webgpu_q4: {\n device: "webgpu",\n dtype: "q4",\n quantized: true\n },\n // Optional, if you use it\n webgpu_q4f16: {\n device: "webgpu",\n dtype: "q4f16",\n quantized: true\n },\n webgpu_bnb4: {\n device: "webgpu",\n dtype: "bnb4",\n quantized: true\n },\n // WASM: quantized CPU\n wasm_q8: {\n dtype: "q8",\n quantized: true\n },\n wasm_q4: {\n dtype: "q4",\n quantized: true\n },\n // Final universal fallback: WASM CPU, dtype = auto\n wasm_auto: {\n // NOTE: leaving out device to avoid Linux issues with \'wasm\'\n // transformers.js will pick CPU/WASM backend itself\n quantized: false\n }\n};\nvar is_webgpu_available = async () => {\n if (!("gpu" in navigator)) return false;\n const adapter = await navigator.gpu.requestAdapter();\n if (!adapter) return false;\n return true;\n};\nvar SmartEmbedTransformersAdapter = class extends SmartEmbedAdapter {\n /**\n * @param {import("../smart_embed_model.js").SmartEmbedModel} model\n */\n constructor(model2) {\n super(model2);\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.has_gpu = false;\n }\n /**\n * Load the underlying transformers pipeline with WebGPU \u2192 WASM fallback.\n * @returns {Promise}\n */\n async load() {\n this.has_gpu = await is_webgpu_available();\n try {\n if (this.loading) {\n console.warn("[Transformers v3] load already in progress, waiting...");\n while (this.loading) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n } else {\n this.loading = true;\n if (this.pipeline) {\n this.loaded = true;\n this.loading = false;\n return;\n }\n await this.load_transformers_with_fallback();\n this.loading = false;\n this.loaded = true;\n console.log(`[Transformers v3] model loaded using ${this.active_config_key}`, this);\n }\n } catch (e) {\n this.loading = false;\n this.loaded = false;\n console.error("[Transformers v3] load failed", e);\n throw e;\n }\n }\n /**\n * Unload the pipeline and free resources.\n * @returns {Promise}\n */\n async unload() {\n try {\n if (this.pipeline) {\n if (typeof this.pipeline.destroy === "function") {\n this.pipeline.destroy();\n } else if (typeof this.pipeline.dispose === "function") {\n this.pipeline.dispose();\n }\n }\n } catch (err) {\n console.warn("[Transformers v3] error while disposing pipeline", err);\n }\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.loaded = false;\n }\n /**\n * Available models \u2013 reuses the v1 transformers model catalog.\n * @returns {Object}\n */\n get models() {\n return transformers_models;\n }\n /**\n * Maximum tokens per input.\n * @returns {number}\n */\n get max_tokens() {\n return this.model.data.max_tokens || 512;\n }\n /**\n * Effective batch size.\n * Prefers small deterministic batches when not explicitly configured.\n * @returns {number}\n */\n get batch_size() {\n const configured = this.model.data.batch_size;\n if (configured && configured > 0) return configured;\n return this.gpu_enabled ? 16 : 8;\n }\n get gpu_enabled() {\n if (this.has_gpu) {\n const explicit = typeof this.model.data.use_gpu === "boolean" ? this.model.data.use_gpu : null;\n if (explicit === false) return false;\n return true;\n } else {\n return false;\n }\n }\n /**\n * Initialize transformers pipeline with WebGPU \u2192 WASM fallback.\n * @private\n * @returns {Promise}\n */\n async load_transformers_with_fallback() {\n const { pipeline, env, AutoTokenizer } = await import("@huggingface/transformers");\n env.allowLocalModels = false;\n if (typeof env.useBrowserCache !== "undefined") {\n env.useBrowserCache = true;\n }\n let last_error = null;\n const CONFIG_LIST_ORDER = Object.keys(DEVICE_CONFIGS);\n const try_create = async (config_key) => {\n const pipe = await pipeline("feature-extraction", this.model_key, DEVICE_CONFIGS[config_key]);\n return pipe;\n };\n for (const config of CONFIG_LIST_ORDER) {\n if (this.pipeline) break;\n if (config.includes("gpu") && !this.gpu_enabled) {\n console.warn(`[Transformers v3: ${config}] skipping ${config} as GPU is disabled`);\n continue;\n }\n try {\n console.log(`[Transformers v3] trying to load pipeline on ${config}`);\n this.pipeline = await try_create(config);\n this.active_config_key = config;\n break;\n } catch (err) {\n console.warn(`[Transformers v3: ${config}] failed to load pipeline on ${config}`, err);\n last_error = err;\n }\n }\n if (this.pipeline) {\n console.log(`[Transformers v3: ${this.active_config_key}] pipeline initialized using ${this.active_config_key}`);\n } else {\n throw last_error || new Error("Failed to initialize transformers pipeline");\n }\n this.tokenizer = await AutoTokenizer.from_pretrained(this.model_key);\n }\n /**\n * Count tokens in input text.\n * @param {string} input\n * @returns {Promise<{tokens:number}>}\n */\n async count_tokens(input) {\n if (!this.tokenizer) {\n await this.load();\n }\n const { input_ids } = await this.tokenizer(input);\n return { tokens: input_ids.data.length };\n }\n /**\n * Generate embeddings for multiple inputs.\n * @param {Array} inputs\n * @returns {Promise>}\n */\n async embed_batch(inputs) {\n if (!this.pipeline) {\n await this.load();\n }\n const filtered_inputs = inputs.filter((item) => item.embed_input && item.embed_input.length > 0);\n if (!filtered_inputs.length) return [];\n const results = [];\n for (let i = 0; i < filtered_inputs.length; i += this.batch_size) {\n const batch = filtered_inputs.slice(i, i + this.batch_size);\n const batch_results = await this._process_batch(batch);\n results.push(...batch_results);\n }\n return results;\n }\n /**\n * Process a single batch \u2013 with per-item retry on failure.\n * @private\n * @param {Array} batch_inputs\n * @returns {Promise>}\n */\n async _process_batch(batch_inputs) {\n const prepared = await Promise.all(\n batch_inputs.map((item) => this._prepare_input(item.embed_input))\n );\n const embed_inputs = prepared.map((p) => p.text);\n const tokens = prepared.map((p) => p.tokens);\n try {\n const resp = await this.pipeline(embed_inputs, { pooling: "mean", normalize: true });\n return batch_inputs.map((item, i) => {\n const vec = Array.from(resp[i].data).map((val) => Math.round(val * 1e8) / 1e8);\n item.vec = vec;\n item.tokens = tokens[i];\n return item;\n });\n } catch (err) {\n console.error("[Transformers v3] batch embed failed \\u2013 retrying items individually", err);\n return await this._retry_items_individually(batch_inputs);\n }\n }\n /**\n * Prepare a single input by truncating to max_tokens if necessary.\n * @private\n * @param {string} embed_input\n * @returns {Promise<{text:string,tokens:number}>}\n */\n async _prepare_input(embed_input) {\n let { tokens } = await this.count_tokens(embed_input);\n if (tokens <= this.max_tokens) {\n return { text: embed_input, tokens };\n }\n let truncated = embed_input;\n while (tokens > this.max_tokens && truncated.length > 0) {\n const pct = this.max_tokens / tokens;\n const max_chars = Math.floor(truncated.length * pct * 0.9);\n truncated = truncated.slice(0, max_chars);\n const last_space = truncated.lastIndexOf(" ");\n if (last_space > 0) {\n truncated = truncated.slice(0, last_space);\n }\n tokens = (await this.count_tokens(truncated)).tokens;\n }\n return { text: truncated, tokens };\n }\n /**\n * Retry each item individually after a batch failure.\n * @private\n * @param {Array} batch_inputs\n * @returns {Promise>}\n */\n async _retry_items_individually(batch_inputs) {\n await this._reset_pipeline_after_error();\n const results = [];\n for (const item of batch_inputs) {\n try {\n const prepared = await this._prepare_input(item.embed_input);\n const resp = await this.pipeline(prepared.text, { pooling: "mean", normalize: true });\n const vec = Array.from(resp[0].data).map((val) => Math.round(val * 1e8) / 1e8);\n results.push({\n ...item,\n vec,\n tokens: prepared.tokens\n });\n } catch (single_err) {\n console.error("[Transformers v3] single item embed failed \\u2013 skipping", single_err);\n results.push({\n ...item,\n vec: [],\n tokens: 0,\n error: single_err.message\n });\n }\n }\n return results;\n }\n /**\n * Reset pipeline after a failure \u2013 falling back to WASM if needed.\n * @private\n * @returns {Promise}\n */\n async _reset_pipeline_after_error() {\n try {\n if (this.pipeline) {\n if (typeof this.pipeline.destroy === "function") {\n this.pipeline.destroy();\n } else if (typeof this.pipeline.dispose === "function") {\n this.pipeline.dispose();\n }\n }\n } catch (err) {\n console.warn("[Transformers v3] error while resetting pipeline", err);\n }\n this.pipeline = null;\n await this.load_transformers_with_fallback();\n }\n /**\n * V2 intentionally exposes only model selection in the settings UI.\n * @returns {Object}\n */\n get settings_config() {\n return super.settings_config;\n }\n};\n__publicField(SmartEmbedTransformersAdapter, "defaults", transformers_defaults);\nvar transformers_models = {\n "TaylorAI/bge-micro-v2": {\n "id": "TaylorAI/bge-micro-v2",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "BGE-micro-v2",\n "description": "Local, 512 tokens, 384 dim (recommended)",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-xs": {\n "id": "Snowflake/snowflake-arctic-embed-xs",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed XS",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-s": {\n "id": "Snowflake/snowflake-arctic-embed-s",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed Small",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-m": {\n "id": "Snowflake/snowflake-arctic-embed-m",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed Medium",\n "description": "Local, 512 tokens, 768 dim",\n "adapter": "transformers"\n },\n "TaylorAI/gte-tiny": {\n "id": "TaylorAI/gte-tiny",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "GTE-tiny",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "onnx-community/embeddinggemma-300m-ONNX": {\n "id": "onnx-community/embeddinggemma-300m-ONNX",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "EmbeddingGemma-300M",\n "description": "Local, 2,048 tokens, 768 dim",\n "adapter": "transformers"\n },\n "Mihaiii/Ivysaur": {\n "id": "Mihaiii/Ivysaur",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Ivysaur",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "andersonbcdefg/bge-small-4096": {\n "id": "andersonbcdefg/bge-small-4096",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 4096,\n "name": "BGE-small-4K",\n "description": "Local, 4,096 tokens, 384 dim",\n "adapter": "transformers"\n },\n // Too slow and persistent crashes\n // "jinaai/jina-embeddings-v2-base-de": {\n // "id": "jinaai/jina-embeddings-v2-base-de",\n // "batch_size": 1,\n // "dims": 768,\n // "max_tokens": 4096,\n // "name": "jina-embeddings-v2-base-de",\n // "description": "Local, 4,096 tokens, 768 dim, German",\n // "adapter": "transformers"\n // },\n "Xenova/jina-embeddings-v2-base-zh": {\n "id": "Xenova/jina-embeddings-v2-base-zh",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 8192,\n "name": "Jina-v2-base-zh-8K",\n "description": "Local, 8,192 tokens, 768 dim, Chinese/English bilingual",\n "adapter": "transformers"\n },\n "Xenova/jina-embeddings-v2-small-en": {\n "id": "Xenova/jina-embeddings-v2-small-en",\n "batch_size": 1,\n "dims": 512,\n "max_tokens": 8192,\n "name": "Jina-v2-small-en",\n "description": "Local, 8,192 tokens, 512 dim",\n "adapter": "transformers"\n },\n "nomic-ai/nomic-embed-text-v1.5": {\n "id": "nomic-ai/nomic-embed-text-v1.5",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "Nomic-embed-text-v1.5",\n "description": "Local, 8,192 tokens, 768 dim",\n "adapter": "transformers"\n },\n "Xenova/bge-small-en-v1.5": {\n "id": "Xenova/bge-small-en-v1.5",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "BGE-small",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "nomic-ai/nomic-embed-text-v1": {\n "id": "nomic-ai/nomic-embed-text-v1",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "Nomic-embed-text",\n "description": "Local, 2,048 tokens, 768 dim",\n "adapter": "transformers"\n }\n};\n\n// build/transformers_iframe_script.js\nvar model = null;\nasync function process_message(data) {\n const { method, params, id, iframe_id } = data;\n try {\n let result;\n switch (method) {\n case "init":\n console.log("init");\n break;\n case "load":\n const model_params = { data: params, ...params };\n console.log("load", { model_params });\n model = new SmartEmbedTransformersAdapter(model_params);\n await model.load();\n result = { model_loaded: true, model_config_key: model.active_config_key };\n break;\n case "embed_batch":\n if (!model) throw new Error("Model not loaded");\n result = await model.embed_batch(params.inputs);\n break;\n case "count_tokens":\n if (!model) throw new Error("Model not loaded");\n result = await model.count_tokens(params);\n break;\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n return { id, result, iframe_id };\n } catch (error) {\n console.error("Error processing message:", error);\n return { id, error: error.message, iframe_id };\n }\n}\nprocess_message({ method: "init" });\n'; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/transformers.js var transformers_defaults = { adapter: "transformers", description: "Transformers (Local, built-in)", default_model: "TaylorAI/bge-micro-v2", models: transformers_models }; var transformers_models = { "TaylorAI/bge-micro-v2": { "id": "TaylorAI/bge-micro-v2", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "BGE-micro-v2", "description": "Local, 512 tokens, 384 dim (recommended)", "adapter": "transformers" }, "Snowflake/snowflake-arctic-embed-xs": { "id": "Snowflake/snowflake-arctic-embed-xs", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "Snowflake Arctic Embed XS", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "Snowflake/snowflake-arctic-embed-s": { "id": "Snowflake/snowflake-arctic-embed-s", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "Snowflake Arctic Embed Small", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "Snowflake/snowflake-arctic-embed-m": { "id": "Snowflake/snowflake-arctic-embed-m", "batch_size": 1, "dims": 768, "max_tokens": 512, "name": "Snowflake Arctic Embed Medium", "description": "Local, 512 tokens, 768 dim", "adapter": "transformers" }, "TaylorAI/gte-tiny": { "id": "TaylorAI/gte-tiny", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "GTE-tiny", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "onnx-community/embeddinggemma-300m-ONNX": { "id": "onnx-community/embeddinggemma-300m-ONNX", "batch_size": 1, "dims": 768, "max_tokens": 2048, "name": "EmbeddingGemma-300M", "description": "Local, 2,048 tokens, 768 dim", "adapter": "transformers" }, "Mihaiii/Ivysaur": { "id": "Mihaiii/Ivysaur", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "Ivysaur", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "andersonbcdefg/bge-small-4096": { "id": "andersonbcdefg/bge-small-4096", "batch_size": 1, "dims": 384, "max_tokens": 4096, "name": "BGE-small-4K", "description": "Local, 4,096 tokens, 384 dim", "adapter": "transformers" }, // Too slow and persistent crashes // "jinaai/jina-embeddings-v2-base-de": { // "id": "jinaai/jina-embeddings-v2-base-de", // "batch_size": 1, // "dims": 768, // "max_tokens": 4096, // "name": "jina-embeddings-v2-base-de", // "description": "Local, 4,096 tokens, 768 dim, German", // "adapter": "transformers" // }, "Xenova/jina-embeddings-v2-base-zh": { "id": "Xenova/jina-embeddings-v2-base-zh", "batch_size": 1, "dims": 768, "max_tokens": 8192, "name": "Jina-v2-base-zh-8K", "description": "Local, 8,192 tokens, 768 dim, Chinese/English bilingual", "adapter": "transformers" }, "Xenova/jina-embeddings-v2-small-en": { "id": "Xenova/jina-embeddings-v2-small-en", "batch_size": 1, "dims": 512, "max_tokens": 8192, "name": "Jina-v2-small-en", "description": "Local, 8,192 tokens, 512 dim", "adapter": "transformers" }, "nomic-ai/nomic-embed-text-v1.5": { "id": "nomic-ai/nomic-embed-text-v1.5", "batch_size": 1, "dims": 768, "max_tokens": 2048, "name": "Nomic-embed-text-v1.5", "description": "Local, 8,192 tokens, 768 dim", "adapter": "transformers" }, "Xenova/bge-small-en-v1.5": { "id": "Xenova/bge-small-en-v1.5", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "BGE-small", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "nomic-ai/nomic-embed-text-v1": { "id": "nomic-ai/nomic-embed-text-v1", "batch_size": 1, "dims": 768, "max_tokens": 2048, "name": "Nomic-embed-text", "description": "Local, 2,048 tokens, 768 dim", "adapter": "transformers" } }; var transformers_settings_config = { // "[ADAPTER].legacy_transformers": { // name: 'Legacy transformers (no GPU)', // type: "toggle", // description: "Use legacy transformers (v2) instead of v3. This may resolve issues if the local embedding isn't working.", // callback: 'embed_model_changed', // default: true, // }, }; var settings_config3 = { // "legacy_transformers": { // name: 'Legacy transformers (no GPU)', // type: "toggle", // description: "Use legacy transformers (v2) instead of v3. This may resolve issues if the local embedding isn't working.", // // callback: 'embed_model_changed', // // default: false, // }, }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/transformers_iframe.js var SmartEmbedTransformersIframeAdapter = class extends SmartEmbedIframeAdapter { static defaults = transformers_defaults; /** * Create transformers iframe adapter instance */ constructor(model) { super(model); this.connector = transformers_connector.replace("@huggingface/transformers", "https://cdn.jsdelivr.net/npm/@huggingface/transformers@3.8.0"); console.log("transformers iframe connector", this.model); } /** @returns {Object} Settings configuration for transformers adapter */ get settings_config() { return { ...super.settings_config, ...transformers_settings_config }; } /** * Get available models (hardcoded list) * @returns {Promise} Map of model objects */ get_models() { return Promise.resolve(this.models); } get models() { return transformers_models; } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/ollama.js var SmartEmbedOllamaAdapter = class extends SmartEmbedModelApiAdapter { static defaults = { description: "Ollama (Local)", type: "API", host: "http://localhost:11434", endpoint: "/api/embed", models_endpoint: "/api/tags", api_key: "na", // Not required for local instance streaming: false, // Ollama's embed API does not support streaming max_tokens: 512, // Example default, adjust based on model capabilities signup_url: null, // Not applicable for local instance batch_size: 30, models: {} }; /** * @override * always return 'something' to allow this adapter to be used without an API key since it's connecting to a local instance that doesn't require authentication * Problem/Reason( 2026-04-20): embed_batch method on parent class throws if this is falsy * should be better handed in future */ get api_key() { return this.model.data.api_key || "something"; } get host() { return this.model.data.host || this.constructor.defaults.host; } get endpoint() { return `${this.host}${this.constructor.defaults.endpoint}`; } get models_endpoint() { return `${this.host}${this.constructor.defaults.models_endpoint}`; } get model_show_endpoint() { return `${this.host}/api/show`; } async load() { await this.get_models(); await super.load(); } /** * Estimate token count for input text. * Ollama does not expose a tokenizer so we use a character based heuristic. * @param {string} input - Text to tokenize * @returns {Promise} Token count result */ async count_tokens(input) { return { tokens: this.estimate_tokens(input) }; } /** * Prepare input text and ensure it fits within `max_tokens`. * @param {string} embed_input - Raw input text * @returns {Promise} Processed input text */ async prepare_embed_input(embed_input) { if (typeof embed_input !== "string") throw new TypeError("embed_input must be a string"); if (embed_input.length === 0) return null; const { tokens } = await this.count_tokens(embed_input); if (tokens <= this.max_tokens) return embed_input; return await this.trim_input_to_max_tokens(embed_input, tokens); } /** * Trim input text to satisfy `max_tokens`. * @private * @param {string} embed_input - Input text * @param {number} tokens_ct - Existing token count * @returns {Promise} Trimmed text */ async trim_input_to_max_tokens(embed_input, tokens_ct) { const reduce_ratio = (tokens_ct - this.max_tokens) / tokens_ct; const new_length = Math.floor(embed_input.length * (1 - reduce_ratio)); let trimmed_input = embed_input.slice(0, new_length); const last_space_index = trimmed_input.lastIndexOf(" "); if (last_space_index > 0) trimmed_input = trimmed_input.slice(0, last_space_index); const prepared = await this.prepare_embed_input(trimmed_input); if (prepared === null) return null; return prepared; } /** @returns {number} Maximum tokens for an input */ get max_tokens() { return this.model.data.max_tokens || this.constructor.defaults.max_tokens; } /** * Get the request adapter class. * @returns {SmartEmbedModelOllamaRequestAdapter} The request adapter class */ get req_adapter() { return SmartEmbedModelOllamaRequestAdapter; } /** * Get the response adapter class. * @returns {SmartEmbedModelOllamaResponseAdapter} The response adapter class */ get res_adapter() { return SmartEmbedModelOllamaResponseAdapter; } /** * Get available models from local Ollama instance. * @param {boolean} [refresh=false] - Whether to refresh cached models * @returns {Promise} Map of model objects */ async get_models(refresh = false) { if (!this.model_data || refresh) { const list_resp = await this.http_adapter.request({ url: this.models_endpoint, method: "GET" }); if (list_resp.ok === false) { throw new Error(`Failed to fetch models list: ${list_resp.statusText}`); } const list_data = await list_resp.json(); const models_raw = []; for (const m of filter_embedding_models(list_data.models || [])) { const detail_resp = await this.http_adapter.request({ url: this.model_show_endpoint, method: "POST", body: JSON.stringify({ model: m.name }) }); models_raw.push({ ...await detail_resp.json(), name: m.name }); } const model_data = this.parse_model_data(models_raw); this.model_data = model_data; if (typeof this.model.re_render_settings === "function") { this.model.re_render_settings(); } return model_data; } return this.model_data; } /** * Get available models as dropdown options synchronously. * @returns {Array} Array of model options. */ get_models_as_options() { const models = this.model_data; if (!Object.keys(models || {}).length) { this.get_models(true); return [{ value: "", name: "No models currently available" }]; } return Object.values(models).map((model) => ({ value: model.id, name: model.name || model.id })).sort((a, b) => a.name.localeCompare(b.name)); } /** * Parse model data from Ollama API response. * @param {Object} model_data - Raw model data from Ollama * @returns {Object} Map of model objects with capabilities and limits */ parse_model_data(model_data) { if (!Array.isArray(model_data)) { this.model_data = {}; console.error("Invalid model data format from Ollama:", model_data); return {}; } if (model_data.length === 0) { this.model_data = { "no_models_available": { id: "no_models_available", name: "No models currently available" } }; return this.model_data; } this.model_data = model_data.reduce((acc, model) => { const info = model.model_info || {}; const ctx = Object.entries(info).find(([k]) => k.includes("context_length"))?.[1]; const dims = Object.entries(info).find(([k]) => k.includes("embedding_length"))?.[1]; acc[model.name] = { model_name: model.name, id: model.name, multimodal: false, max_tokens: ctx || this.max_tokens, dims, description: model.description || `Model: ${model.name}` }; return acc; }, {}); this._models = this.model_data; return this.model_data; } /** * Get the models. * @returns {Object} Map of model objects */ get models() { if (typeof this._models === "object" && Object.keys(this._models || {}).length > 0) return this._models; else { return {}; } } /** * Override settings config to remove API key setting since not needed for local instance. * @returns {Object} Settings configuration object */ get settings_config() { const config = super.settings_config; delete config["[ADAPTER].api_key"]; config["[ADAPTER].host"] = { name: "Ollama host", type: "text", description: "Enter the host for your Ollama instance", default: this.constructor.defaults.host }; return config; } }; var SmartEmbedModelOllamaRequestAdapter = class extends SmartEmbedModelRequestAdapter { /** * Convert request to Ollama's embed API format. * @returns {Object} Request parameters in Ollama's format */ to_platform() { const ollama_body = { model: this.model_id, input: this.embed_inputs }; return { url: this.adapter.endpoint, method: "POST", headers: this.get_headers(), body: JSON.stringify(ollama_body) }; } /** * Prepare request headers for Ollama API. * @returns {Object} Headers object */ get_headers() { return { "Content-Type": "application/json" }; } }; var SmartEmbedModelOllamaResponseAdapter = class extends SmartEmbedModelResponseAdapter { /** * Convert Ollama's response to a standardized OpenAI-like format. * @returns {Array} Array of embedding results */ to_openai() { const resp = this.response; if (!resp || !resp.embeddings) { console.error("Invalid response format from Ollama:", resp); return []; } const tokens = Math.ceil(resp.prompt_eval_count / this.adapter.batch_size); const embeddings = resp.embeddings.map((vec) => ({ vec, tokens })); return embeddings; } /** * Parse the response object. * @returns {Array} Parsed embedding results */ parse_response() { return this.to_openai(); } }; var is_embedding_model = (mod) => { return ["embed", "embedding", "bge"].some((keyword) => mod.name.toLowerCase().includes(keyword)); }; var filter_embedding_models = (models) => { if (!Array.isArray(models)) { throw new TypeError("models must be an array"); } return models.filter(is_embedding_model); }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/gemini.js var GeminiEmbedModelAdapter = class extends SmartEmbedModelApiAdapter { static defaults = { adapter: "gemini", description: "Google Gemini (API)", default_model: "gemini-embedding-001", endpoint: "https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:batchEmbedContents", dims: 768, max_tokens: 2048, batch_size: 50 }; /** * Count tokens in input text using tokenizer * @param {string} input - Text to tokenize * @returns {Promise} Token count result */ async count_tokens(input) { if (!this.tiktoken) await this.load_tiktoken(); return { tokens: this.tiktoken.encode(input).length }; } /** * Prepare input text for embedding * Handles token limit truncation * @param {string} embed_input - Raw input text * @returns {Promise} Processed input text */ async prepare_embed_input(embed_input) { if (typeof embed_input !== "string") { throw new TypeError("embed_input must be a string"); } if (embed_input.length === 0) { console.log("Warning: prepare_embed_input received an empty string"); return null; } const { tokens } = await this.count_tokens(embed_input); if (tokens <= this.max_tokens) { return embed_input; } return await this.trim_input_to_max_tokens(embed_input, tokens); } /** * Trim input text to fit token limit * @private * @param {string} embed_input - Input text to trim * @param {number} tokens_ct - Current token count * @returns {Promise} Trimmed input text */ async trim_input_to_max_tokens(embed_input, tokens_ct) { const reduce_ratio = (tokens_ct - this.max_tokens) / tokens_ct; const new_length = Math.floor(embed_input.length * (1 - reduce_ratio)); let trimmed_input = embed_input.slice(0, new_length); const last_space_index = trimmed_input.lastIndexOf(" "); if (last_space_index > 0) { trimmed_input = trimmed_input.slice(0, last_space_index); } const prepared_input = await this.prepare_embed_input(trimmed_input); if (prepared_input === null) { console.log( "Warning: prepare_embed_input resulted in an empty string after trimming" ); return null; } return prepared_input; } /** * Get the request adapter class. * @returns {SmartEmbedGeminiRequestAdapter} The request adapter class */ get req_adapter() { return SmartEmbedGeminiRequestAdapter; } /** * Get the response adapter class. * @returns {SmartEmbedGeminiResponseAdapter} The response adapter class */ get res_adapter() { return SmartEmbedGeminiResponseAdapter; } /** @returns {Object} Settings configuration for Gemini adapter */ get settings_config() { return { ...super.settings_config, "[ADAPTER].api_key": { name: "Google API Key for Gemini embeddings", type: "password", description: "Required for Gemini embedding models", placeholder: "Enter Google API Key" } }; } /** * Get available models (hardcoded list) * @returns {Promise} Map of model objects */ get_models() { return Promise.resolve(this.models); } get models() { return { "gemini-embedding-001": { "id": "gemini-embedding-001", "batch_size": 50, "dims": 768, "max_tokens": 2048, "name": "Gemini Embedding", "description": "API, 2,048 tokens, 768 dim", "endpoint": "https://generativelanguage.googleapis.com/v1beta/models/gemini-embedding-001:batchEmbedContents", "adapter": "gemini" } }; } prepare_request_headers() { return { "Content-Type": "application/json", "x-goog-api-key": this.api_key }; } backoff_wait_time = 5e3; // initial backoff wait time in ms backoff_factor = 1; // no usaqge stats from LM Studio so need to estimate tokens async embed_batch(inputs, retries = 0) { if (smart_env.smart_sources.entities_vector_adapter.is_queue_halted) { throw new Error("Embedding queue halted during backoff wait due to rate limit errors."); } const token_cts = inputs.map((item) => this.estimate_tokens(item.embed_input)); const resp = await super.embed_batch(inputs); if (resp[0].error && resp[0].error.details && resp[0].error.details.code === 429) { console.warn("Rate limit error detected in Gemini embed_batch response.", resp); if (retries > 3) { console.error("Max retries reached for rate limit errors."); throw new Error("Max retries reached for rate limit errors."); } console.warn(resp[0].error.message); const retry_detail = resp[0].error.details?.details?.find((d) => d.retryDelay); if (retry_detail.retryDelay) { const wait_time_ms = parseInt(retry_detail.retryDelay) * 1e3 * 2; console.warn(`Using server-specified retry delay of ${wait_time_ms} ms`); await new Promise((resolve) => setTimeout(resolve, wait_time_ms)); return await this.embed_batch(inputs, retries + 1); } else { this.backoff_factor += 1; console.warn(`Rate limit exceeded, backing off for ${this.backoff_wait_time * this.backoff_factor} ms`); await new Promise((resolve) => setTimeout(resolve, this.backoff_wait_time * this.backoff_factor)); return await this.embed_batch(inputs, retries + 1); } } else if (resp[0].error) { console.error("Error in Gemini embed_batch response:", resp[0].error); throw new Error(`Gemini embed_batch error: ${resp[0].error.message}`); } resp.forEach((item, idx) => { item.tokens = token_cts[idx]; }); console.log("Gemini embed_batch response:", resp); return resp; } }; var SmartEmbedGeminiRequestAdapter = class extends SmartEmbedModelRequestAdapter { get model_id() { let model_id = this.adapter.model.data.model_key; return `models/${model_id}`; } /** * Prepare request body for Gemini API * @returns {Object} Request body for API */ prepare_request_body() { const requests = this.embed_inputs.map((input) => { const [title, ...content] = input.split("\n"); const doc_content = content.join("\n").trim() || ""; if (doc_content.length) { return { model: this.model_id, content: { parts: [{ text: doc_content }] }, outputDimensionality: this.model_dims, taskType: "RETRIEVAL_DOCUMENT", title }; } else { return { model: this.model_id, content: { parts: [{ text: title }] }, outputDimensionality: this.model_dims, taskType: "RETRIEVAL_DOCUMENT" }; } }); return { requests }; } }; var SmartEmbedGeminiResponseAdapter = class extends SmartEmbedModelResponseAdapter { /** * Parse Gemini API response * @returns {Array} Parsed embedding results */ parse_response() { const resp = this.response; console.log("Gemini response:", resp); if (!resp || !resp.embeddings || !resp.embeddings[0].values) { console.error("Invalid Gemini embedding response format", resp); return []; } return resp.embeddings.map((embedding, i) => { if (!embedding.values || embedding.values.length === 0) { console.warn(`No values for embedding at index ${i}`); return { vec: [], tokens: 0 }; } return { vec: embedding.values, tokens: null // not provided }; }); } }; // node_modules/obsidian-smart-env/node_modules/smart-embed-model/adapters/lm_studio.js function parse_lm_studio_models(list, adapter_key = "lm_studio") { if (list.object !== "list" || !Array.isArray(list.data)) { return { _: { id: "No models found." } }; } console.log("LM Studio models", list); return list.data.filter((m) => m.id && m.type === "embeddings").reduce((acc, m) => { acc[m.id] = { id: m.id, model_name: m.id, max_tokens: m.loaded_context_length || 512, description: `LM Studio model: ${m.id}`, adapter: adapter_key }; return acc; }, {}); } var LmStudioEmbedModelAdapter = class extends SmartEmbedModelApiAdapter { static key = "lm_studio"; static defaults = { description: "LM Studio", type: "API", host: "http://localhost:1234", // endpoint: "/v1/embeddings", endpoint: "/api/v0/embeddings", models_endpoint: "/api/v0/models", default_model: "", // user picks from dropdown streaming: false, api_key: "na", // not used batch_size: 10, max_tokens: 512 }; get req_adapter() { return LmStudioEmbedModelRequestAdapter; } get res_adapter() { return LmStudioEmbedModelResponseAdapter; } get host() { return this.model.data.host || this.constructor.defaults.host; } get endpoint() { return `${this.host}${this.constructor.defaults.endpoint}`; } get models_endpoint() { return `${this.host}${this.constructor.defaults.models_endpoint}`; } get settings_config() { const cfg = { ...super.settings_config }; delete cfg["[ADAPTER].api_key"]; cfg["[ADAPTER].refresh_models"] = { name: "Refresh Models", type: "button", description: "Refresh the list of available models.", callback: "adapter.refresh_models" }; cfg["[ADAPTER].current_model"] = { type: "html", value: `

Embedding Model Max Tokens: ${this.max_tokens} (may be configured in LM Studio)

` }; cfg["[ADAPTER].batch_size"] = { name: "Embedding Batch Size", type: "number", description: "Number of embeddings to process in parallel. Adjusting this may improve performance.", value: this.batch_size, default: this.constructor.defaults.batch_size }; cfg["[ADAPTER].cors_note"] = { name: "CORS required", type: "html", // The renderer treats `value` as innerHTML. value: `

Before you can use LM Studio you must Enable CORS inside LM Studio \u2192 Developer \u2192 Settings

` }; return cfg; } async get_models(refresh = false) { if (!refresh && this.model.data.provider_models) return this.model.data.provider_models; const resp = await this.http_adapter.request({ url: this.models_endpoint, method: "GET" }); const raw = await resp.json(); const parsed = this.parse_model_data(raw); this.model.data.provider_models = parsed; this.model.re_render_settings(); return parsed; } parse_model_data(list) { return parse_lm_studio_models(list, this.constructor.key); } async count_tokens(input) { return { tokens: this.estimate_tokens(input) }; } /** * Prepare input text and ensure it fits within `max_tokens`. * @param {string} embed_input - Raw input text * @returns {Promise} Processed input text */ async prepare_embed_input(embed_input) { if (typeof embed_input !== "string") throw new TypeError("embed_input must be a string"); if (embed_input.length === 0) return null; const { tokens } = await this.count_tokens(embed_input); if (tokens <= this.max_tokens) return embed_input; return await this.trim_input_to_max_tokens(embed_input, tokens); } /** * Refresh available models. */ refresh_models() { console.log("refresh_models"); this.get_models(true); } // no usaqge stats from LM Studio so need to estimate tokens async embed_batch(inputs) { const token_cts = inputs.map((item) => this.estimate_tokens(item.embed_input)); const resp = await super.embed_batch(inputs); resp.forEach((item, idx) => { item.tokens = token_cts[idx]; }); return resp; } }; var LmStudioEmbedModelRequestAdapter = class extends SmartEmbedModelRequestAdapter { /** * Prepare request body for LM Studio API * @returns {Object} Request body for API */ prepare_request_body() { const body = { model: this.model_id, input: this.embed_inputs }; return body; } }; var LmStudioEmbedModelResponseAdapter = class extends SmartEmbedModelResponseAdapter { /** * Parse LM Studio API response * @returns {Array} Parsed embedding results */ parse_response() { const resp = this.response; if (!resp || !resp.data) { console.error("Invalid response format", resp); return []; } return resp.data.map((item) => ({ vec: item.embedding, tokens: null // LM Studio doesn't provide token usage })); } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/smart_chat_model.js var SmartChatModel = class extends SmartModel { scope_name = "smart_chat_model"; static defaults = { adapter: "openai" }; /** * Create a SmartChatModel instance. * @param {Object} opts - Configuration options * @param {string} opts.adapter - Adapter to use * @param {Object} opts.adapters - Map of adapter names to adapter classes * @param {Object} opts.settings - Model settings configuration */ constructor(opts = {}) { super(opts); } /** * Get available models. * @returns {Object} Map of model objects */ get models() { return this.adapter.models; } get can_stream() { return this.adapter.constructor.defaults.streaming; } /** * Complete a chat request. * @param {Object} req - Request parameters * @returns {Promise} Completion result */ async complete(req) { const resp = await this.invoke_adapter_method("complete", req); if (resp.error) { throw normalize_error(resp.error); } return resp; } /** * Stream chat responses. * @param {Object} req - Request parameters * @param {Object} handlers - Event handlers for streaming * @param {Function} handlers.chunk - Handler for chunks: receives response object * @param {Function} handlers.error - Handler for errors: receives error object * @param {Function} handlers.done - Handler for completion: receives final response object * @returns {Promise} Complete response text */ async stream(req, handlers = {}) { return await this.invoke_adapter_method("stream", req, handlers); } /** * Stop active stream. */ stop_stream() { this.invoke_adapter_method("stop_stream"); } /** * Count tokens in input text. * @param {string|Object} input - Text to count tokens for * @returns {Promise} Token count */ async count_tokens(input) { return await this.invoke_adapter_method("count_tokens", input); } /** * Test if API key is valid. * @deprecated in favor of smart_model.test_model (should be safe to remove 2026-02-10) * @returns {Promise} True if API key is valid */ async test_api_key() { await this.invoke_adapter_method("test_api_key"); this.re_render_settings(); } /** * Get default model key. * @returns {string} Default model key */ get default_model_key() { return this.adapter.constructor.defaults.default_model; } /** * Get current settings. * @returns {Object} Settings object */ get settings() { return this.opts.settings; } /** * Get settings configuration. * @returns {Object} Settings configuration object */ get settings_config() { const _settings_config = { adapter: { name: "Chat Model Platform", type: "dropdown", description: "Select a platform/provider for chat models.", options_callback: "get_platforms_as_options", is_scope: true, // trigger re-render of settings when changed callback: "adapter_changed" }, // Merge adapter-specific settings ...this.adapter.settings_config || {} }; return this.process_settings_config(_settings_config); } /** * Process setting key. * @param {string} key - Setting key * @returns {string} Processed key */ process_setting_key(key) { return key.replace(/\[CHAT_ADAPTER\]/g, this.adapter_name); } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/streamer.js var SmartStreamer = class { constructor(url, options = {}) { const { method = "GET", headers = {}, body = null, withCredentials = false } = options; this.url = url; this.method = method; this.headers = headers; this.body = body; this.withCredentials = withCredentials; this.listeners = {}; this.readyState = this.CONNECTING; this.progress = 0; this.chunk = ""; this.last_event_id = ""; this.xhr = null; this.FIELD_SEPARATOR = ":"; this.INITIALIZING = -1; this.CONNECTING = 0; this.OPEN = 1; this.CLOSED = 2; this.chunk_accumulator = ""; this.chunk_splitting_regex = options.chunk_splitting_regex || /(\r\n|\n|\r)/g; } /** * Adds an event listener for the specified event type. * * @param {string} type - The type of the event. * @param {Function} listener - The listener function to be called when the event is triggered. */ addEventListener(type, listener) { if (!this.listeners[type]) this.listeners[type] = []; if (!this.listeners[type].includes(listener)) this.listeners[type].push(listener); } /** * Removes an event listener from the SmartStreamer instance. * * @param {string} type - The type of event to remove the listener from. * @param {Function} listener - The listener function to remove. */ removeEventListener(type, listener) { if (!this.listeners[type]) return; this.listeners[type] = this.listeners[type].filter((callback) => callback !== listener); if (this.listeners[type].length === 0) delete this.listeners[type]; } /** * Dispatches an event to the appropriate event handlers. * * @param {Event} event - The event to be dispatched. * @returns {boolean} - Returns true if the event was successfully dispatched, false otherwise. */ dispatchEvent(event) { if (!event) return true; event.source = this; const onHandler = "on" + event.type; if (Object.prototype.hasOwnProperty.call(this, onHandler)) { this[onHandler].call(this, event); if (event.defaultPrevented) return false; } if (this.listeners[event.type]) { this.listeners[event.type].forEach((callback) => { callback(event); return !event.defaultPrevented; }); } return true; } /** * Initiates the streaming process. */ stream() { this.#setReadyState(this.CONNECTING); this.xhr = new XMLHttpRequest(); this.xhr.addEventListener("progress", this.#onStreamProgress.bind(this)); this.xhr.addEventListener("load", this.#onStreamLoaded.bind(this)); this.xhr.addEventListener("readystatechange", this.#checkStreamClosed.bind(this)); this.xhr.addEventListener("error", this.#onStreamFailure.bind(this)); this.xhr.addEventListener("abort", this.#onStreamAbort.bind(this)); this.xhr.open(this.method, this.url); for (const header in this.headers) { this.xhr.setRequestHeader(header, this.headers[header]); } if (this.last_event_id) this.xhr.setRequestHeader("Last-Event-ID", this.last_event_id); this.xhr.withCredentials = this.withCredentials; this.xhr.send(this.body); } /** * Ends the streamer connection. * Aborts the current XHR request and sets the ready state to CLOSED. */ end() { if (this.readyState === this.CLOSED) return; this.xhr.abort(); this.xhr = null; this.#setReadyState(this.CLOSED); } // private methods #setReadyState(state) { const event = new CustomEvent("readyStateChange"); event.readyState = state; this.readyState = state; this.dispatchEvent(event); } #onStreamFailure(e) { const event = new CustomEvent("error"); try { const parsed = JSON.parse(e.currentTarget.response); if (typeof parsed === "object") { event.data = parsed; } else { event.data = e.currentTarget.response; } } catch { event.data = e.currentTarget.response; } this.dispatchEvent(event); this.end(); } #onStreamAbort(e) { const event = new CustomEvent("abort"); this.end(); } #onStreamProgress(e) { if (!this.xhr) return; if (this.xhr.status !== 200) { this.#onStreamFailure(e); return; } if (this.readyState === this.CONNECTING) { this.dispatchEvent(new CustomEvent("open")); this.#setReadyState(this.OPEN); } const data = this.xhr.responseText.substring(this.progress); this.progress += data.length; const parts = data.split(this.chunk_splitting_regex); parts.forEach((part, index) => { if (part.trim().length === 0) { if (this.chunk) { this.dispatchEvent(this.#parseEventChunk(this.chunk.trim())); this.chunk = ""; } } else { this.chunk += part; if (index === parts.length - 1 && this.xhr.readyState === XMLHttpRequest.DONE) { this.dispatchEvent(this.#parseEventChunk(this.chunk.trim())); this.chunk = ""; } } }); } #onStreamLoaded(e) { this.#onStreamProgress(e); this.dispatchEvent(this.#parseEventChunk(this.chunk)); this.chunk = ""; } #parseEventChunk(chunk) { if (!chunk) return console.log("no chunk"); const event = new CustomEvent("message"); event.data = chunk; event.last_event_id = this.last_event_id; return event; } #checkStreamClosed() { if (!this.xhr) return; if (this.xhr.readyState === XMLHttpRequest.DONE) this.#setReadyState(this.CLOSED); } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/_adapter.js var SmartChatModelAdapter = 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 {boolean} streaming - Whether streaming is supported * @property {string} adapter - Adapter identifier * @property {string} models_endpoint - Endpoint for retrieving models * @property {string} default_model - Default model to use * @property {string} signup_url - URL for API key signup */ static defaults = {}; /** * Create a SmartChatModelAdapter instance. * @param {SmartChatModel} model - The parent SmartChatModel instance */ constructor(model) { super(model); this.smart_chat = model; this.main = model; } /** * Complete a chat request. * @abstract * @param {Object} req - Request parameters * @returns {Promise} Completion result */ async complete(req) { throw new Error("complete not implemented"); } /** * Count tokens in input text. * @abstract * @param {string|Object} input - Text to count tokens for * @returns {Promise} Token count */ async count_tokens(input) { throw new Error("count_tokens not implemented"); } /** * Stream chat responses. * @abstract * @param {Object} req - Request parameters * @param {Object} handlers - Event handlers for streaming * @returns {Promise} Complete response text */ async stream(req, handlers = {}) { throw new Error("stream not implemented"); } /** * Test if API key is valid. * @abstract * @deprecated in favor of smart_model.test_model (should be safe to remove 2026-02-10) * @returns {Promise} True if API key is valid */ async test_api_key() { throw new Error("test_api_key not implemented"); } /** * Refresh available models. */ refresh_models() { console.log("refresh_models"); this.get_models(true); } /** * Get settings configuration. * @returns {Object} Settings configuration object */ get settings_config() { return { "[CHAT_ADAPTER].model_key": { name: "Chat Model", type: "dropdown", description: "Select a chat model.", options_callback: "adapter.get_models_as_options", callback: "reload_model", default: this.constructor.defaults.default_model }, "[CHAT_ADAPTER].refresh_models": { name: "Refresh Models", type: "button", description: "Refresh the list of available models.", callback: "adapter.refresh_models" } }; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/_api.js var MODEL_ADAPTER_CACHE = {}; var MODELS_DEV_CACHE = { data: null, fetched_at: 0 }; var SmartChatModelApiAdapter = class extends SmartChatModelAdapter { constructor(model) { super(model); this.model_data_loaded_at = 0; } /** * Get the request adapter class. * @returns {SmartChatModelRequestAdapter} The request adapter class */ get req_adapter() { return SmartChatModelRequestAdapter; } /** * Get the response adapter class. * @returns {SmartChatModelResponseAdapter} The response adapter class */ get res_adapter() { return SmartChatModelResponseAdapter; } /** * Get or initialize the HTTP adapter. * @returns {SmartHttpRequest} The HTTP adapter instance */ get http_adapter() { if (!this._http_adapter) { if (this.model.http_adapter) this._http_adapter = this.model.http_adapter; else if (this.model.opts.http_adapter) this._http_adapter = this.model.opts.http_adapter; else this._http_adapter = new SmartHttpRequest({ adapter: SmartHttpRequestFetchAdapter }); } return this._http_adapter; } /** * Get the settings configuration for the API adapter. * @deprecated migrating to module export * @returns {Object} Settings configuration object with API key and other settings */ get settings_config() { return { ...super.settings_config, "[CHAT_ADAPTER].api_key": { name: "API Key", type: "password", description: "Enter your API key for the chat model platform.", callback: "test_api_key", is_scope: true // trigger re-render of settings when changed (reload models dropdown) } }; } /** * Count tokens in the input text. * @abstract * @param {string|Object} input - Text or message object to count tokens for * @returns {Promise} Number of tokens in the input */ async count_tokens(input) { throw new Error("count_tokens not implemented"); } /** * Get the parameters for requesting available models. * @returns {Object} Request parameters for models endpoint */ get models_request_params() { return { url: this.models_endpoint, method: this.models_endpoint_method, headers: { "Authorization": `Bearer ${this.api_key}` } }; } async get_enriched_model_data() { const provider_key = this.constructor.models_dev_key || this.constructor.key; await this.get_models_dev_index(); const provider_data = MODELS_DEV_CACHE.data[provider_key] || {}; const get_limit_i = (model) => model.limit?.context || 1e4; const get_limit_o = (model) => model.limit?.output || 1e4; const get_multimodal = (model) => model.modalities?.input?.includes("image") || false; if (Object.keys(this.model_data || {}).length > 0) { for (const [key, model] of Object.entries(this.model_data)) { const enriched = provider_data?.models?.[model.id]; if (!enriched) continue; this.model_data[key].models_dev = enriched; this.model_data[key].name = enriched.name || model.name; this.model_data[key].max_input_tokens = get_limit_i(enriched); this.model_data[key].max_output_tokens = get_limit_o(enriched); this.model_data[key].multimodal = get_multimodal(enriched); this.model_data[key].cost = enriched.cost; } } else { for (const [key, model] of Object.entries(provider_data?.models || {})) { this.model_data[key] = { ...model, model_name: model.name, description: model.name, max_input_tokens: get_limit_i(model), max_output_tokens: get_limit_o(model), multimodal: get_multimodal(model) }; } } return this.model_data; } valid_model_data() { return typeof this.model_data === "object" && Object.keys(this.model_data || {}).length > 0 && this.model_data_loaded_at && Date.now() - this.model_data_loaded_at < 1 * 60 * 60 * 1e3; } /** * Get available models from the API. * @param {boolean} [refresh=false] - Whether to refresh cached models * @returns {Promise} Map of model objects */ async get_models(refresh = false) { if (!refresh && this.valid_model_data()) return this.model_data; if (this.api_key) { let response; try { response = await this.http_adapter.request(this.models_request_params); this.model_data = this.parse_model_data(await response.json()); } catch (error) { console.error("Failed to fetch model data:", { error, response }); } } this.model_data = await this.get_enriched_model_data(); this.model_data_loaded_at = Date.now(); if (this.model.data) { this.model.data.provider_models = this.model_data; } if (this.valid_model_data() && typeof this.model.re_render_settings === "function") setTimeout(() => { this.model.re_render_settings(); }, 100); else console.warn("Invalid model data, not re-rendering settings"); return this.model_data; } /** * Parses the raw model data from OpenAI API and transforms it into a more usable format. * @param {Object} model_data - The raw model data received from OpenAI API. * @abstract * @returns {Array} An array of parsed model objects with the following properties: * @property {string} model_name - The name/ID of the model as returned by the API. * @property {string} id - The id used to identify the model (usually same as model_name). * @property {boolean} multimodal - Indicates if the model supports multimodal inputs. * @property {number} [max_input_tokens] - The maximum number of input tokens the model can process. * @property {string} [description] - A description of the model's context and output capabilities. */ parse_model_data(model_data) { throw new Error("parse_model_data not implemented"); } /** * Complete a chat request. * @param {Object} req - Request parameters * @returns {Promise} Completion response in OpenAI format */ async complete(req) { const _req = new this.req_adapter(this, { ...req, stream: false }); const request_params = await _req.to_platform(); const http_resp = await this.http_adapter.request(request_params); if (!http_resp) return null; const _res = new this.res_adapter(this, await http_resp.json()); try { const resp = _res.to_openai(); return resp; } catch (error) { const normalized_error = normalize_error(error?.data || error); console.error("Error in SmartChatModelApiAdapter.complete():", { normalized_error, error }); console.error(http_resp); return normalized_error; } } // STREAMING /** * Stream chat responses. * @param {Object} req - Request parameters * @param {Object} handlers - Event handlers for streaming * @param {Function} handlers.chunk - Handler for response objects * @param {Function} handlers.error - Handler for errors * @param {Function} handlers.done - Handler for completion * @returns {Promise} Complete response object */ async stream(req, handlers = {}) { let request_params; try { const _req = new this.req_adapter(this, req); request_params = await _req.to_platform(true); if (this.streaming_chunk_splitting_regex) request_params.chunk_splitting_regex = this.streaming_chunk_splitting_regex; } catch (error) { const normalized_error = normalize_error(error?.data || error); console.error("Failed to start stream (request prep):", { error, normalized_error }); if (typeof handlers?.error === "function") handlers.error(normalized_error); this.stop_stream(); throw normalized_error; } return await new Promise((resolve, reject) => { try { this.active_stream = new SmartStreamer(this.endpoint_streaming, request_params); const resp_adapter = new this.res_adapter(this); this.active_stream.addEventListener("message", async (e) => { if (this.is_end_of_stream(e)) { await resp_adapter.handle_chunk(e.data); this.stop_stream(); const final_resp = resp_adapter.to_openai(); handlers.done && await handlers.done(final_resp); resolve(final_resp); return; } try { const raw = resp_adapter.handle_chunk(e.data); handlers.chunk && await handlers.chunk({ ...resp_adapter.to_openai(), raw }); } catch (error) { const normalized_error = normalize_error({ ...e.data, ...error }); console.error("Error processing stream chunk:", { e, error, normalized_error }); handlers.error && handlers.error(normalized_error); this.stop_stream(); reject(normalized_error); } }); this.active_stream.addEventListener("error", (e) => { console.error("Stream error:", e); const normalized_error = normalize_error(e?.data || e); handlers.error && handlers.error(normalized_error); this.stop_stream(); reject(normalized_error); }); this.active_stream.stream(); } catch (err) { console.error("Failed to start stream:", err); const normalized_error = normalize_error(err?.data || err); handlers.error && handlers.error(normalized_error); this.stop_stream(); reject(normalized_error); } }); } /** * Check if a stream event indicates end of stream. * @param {Event} event - Stream event * @returns {boolean} True if end of stream */ is_end_of_stream(event) { return event.data === "data: [DONE]"; } /** * Stop active stream. */ stop_stream() { if (this.active_stream) { this.active_stream.end(); this.active_stream = null; } } /** * Get the API key. * @returns {string} The API key. */ get api_key() { return this.model.api_key || this.main.opts.api_key; } get models_endpoint() { return this.constructor.defaults.models_endpoint; } get models_endpoint_method() { return "POST"; } /** * Get the endpoint URL. * @returns {string} The endpoint URL. */ get endpoint() { return this.constructor.defaults.endpoint; } /** * Get the streaming endpoint URL. * @returns {string} The streaming endpoint URL. */ get endpoint_streaming() { return this.constructor.defaults.endpoint_streaming || this.endpoint; } /** * Get the maximum output tokens. * @returns {number} The maximum output tokens. */ get max_output_tokens() { return this.model.data.max_output_tokens || 3e3; } async get_models_dev_index(ttl_ms = 60 * 60 * 1e3) { const now = Date.now(); if (MODELS_DEV_CACHE?.data && now - MODELS_DEV_CACHE?.fetched_at < ttl_ms) { return MODELS_DEV_CACHE.data; } try { const req = { url: "https://models.dev/api.json", method: "GET", headers: { "Content-Type": "application/json" } }; const resp = await this.http_adapter.request(req); const data = await resp.json(); MODELS_DEV_CACHE.data = data; MODELS_DEV_CACHE.fetched_at = now; console.log({ MODELS_DEV_CACHE }); return data; } catch (err) { console.warn("models.dev fetch failed; continuing without enrichment", err); return MODELS_DEV_CACHE.data || []; } } /** * Get available models as dropdown options synchronously. * @returns {Array} Array of model options. */ get_models_as_options() { if (Object.keys(this.model_data || {}).length) { return Object.entries(this.model_data).map(([id, model]) => ({ value: id, name: model.name || id })).sort((a, b) => a.name.localeCompare(b.name)); } this.get_models(true); return [{ value: "", name: "No models currently available" }]; } get model_data() { if (!MODEL_ADAPTER_CACHE[this.constructor.key]) MODEL_ADAPTER_CACHE[this.constructor.key] = {}; return MODEL_ADAPTER_CACHE[this.constructor.key]; } set model_data(data) { if (!MODEL_ADAPTER_CACHE[this.constructor.key]) MODEL_ADAPTER_CACHE[this.constructor.key] = {}; MODEL_ADAPTER_CACHE[this.constructor.key] = data; } }; var SmartChatModelRequestAdapter = class { /** * @constructor * @param {SmartChatModelAdapter} adapter - The SmartChatModelAdapter instance * @param {Object} req - The incoming request object */ constructor(adapter, req = {}) { this.adapter = adapter; this._req = req; } /** * Get the messages array from the request * @returns {Array} Array of message objects */ get messages() { return this._req.messages || []; } /** * Get the model identifier * @returns {string} Model ID */ get model_id() { return this._req.model || this.adapter.model.model_key || this.adapter.model.data.id; } /** * Get the temperature setting * @returns {number} Temperature value */ get temperature() { return this._req.temperature; } /** * Get the maximum tokens setting * @returns {number} Max tokens value */ get max_tokens() { return this._req.max_tokens || this.adapter.max_output_tokens; } /** * Get the streaming flag * @returns {boolean} Whether to stream responses */ get stream() { return this._req.stream; } /** * Get the tools array * @returns {Array|null} Array of tool objects or null */ get tools() { return this._req.tools || null; } /** * Get the tool choice setting * @returns {string|Object|null} Tool choice configuration */ get tool_choice() { return this._req.tool_choice || null; } get frequency_penalty() { return this._req.frequency_penalty; } get presence_penalty() { return this._req.presence_penalty; } get top_p() { return this._req.top_p; } /** * Get request headers * @returns {Object} Headers object */ get_headers() { const headers = { "Content-Type": "application/json", ...this.adapter.constructor.defaults.headers || {} }; const api_key_header = this.adapter.constructor.defaults.api_key_header; if (api_key_header !== "none") { if (api_key_header) { headers[api_key_header] = this.adapter.api_key; } else if (this.adapter.api_key) { headers["Authorization"] = `Bearer ${this.adapter.api_key}`; } } return headers; } /** * Convert request to platform-specific format * @returns {Object} Platform-specific request parameters */ to_platform(streaming = false) { return this.to_openai(streaming); } /** * Convert request to OpenAI format * @returns {Object} Request parameters in OpenAI format */ to_openai(streaming = false) { const body = { messages: this._transform_messages_to_openai(), model: this.model_id, // TODO max_completion_tokens temperature: this.temperature, stream: streaming, ...this.tools && { tools: this._transform_tools_to_openai() } }; if (body.tools?.length > 0 && this.tool_choice && this.tool_choice !== "none") { body.tool_choice = this.tool_choice; } if (this.model_id?.startsWith("o1-")) { body.messages = body.messages.filter((m) => m.role !== "system"); delete body.temperature; } if (typeof this._req.top_p === "number") body.top_p = this._req.top_p; if (typeof this._req.presence_penalty === "number") body.presence_penalty = this._req.presence_penalty; if (typeof this._req.frequency_penalty === "number") body.frequency_penalty = this._req.frequency_penalty; return { url: this.adapter.endpoint, method: "POST", headers: this.get_headers(), body: JSON.stringify(body) }; } /** * Transform messages to OpenAI format * @returns {Array} Transformed messages array * @private */ _transform_messages_to_openai() { return this.messages.map((message) => this._transform_single_message_to_openai(message)); } /** * Transform a single message to OpenAI format * @param {Object} message - Message object to transform * @returns {Object} Transformed message object * @private */ _transform_single_message_to_openai(message) { const transformed = { role: this._get_openai_role(message.role), content: this._get_openai_content(message) }; if (message.name) transformed.name = message.name; if (message.tool_calls) transformed.tool_calls = this._transform_tool_calls_to_openai(message.tool_calls); if (message.image_url) transformed.image_url = message.image_url; if (message.tool_call_id) transformed.tool_call_id = message.tool_call_id; return transformed; } /** * Get the OpenAI role for a given role. * @param {string} role - The role to transform. * @returns {string} The transformed role. * @private */ _get_openai_role(role) { return role; } /** * Get the OpenAI content for a given content. * @param {string} content - The content to transform. * @returns {string} The transformed content. * @private */ _get_openai_content(message) { return message.content; } /** * Transform tool calls to OpenAI format. * @param {Array} tool_calls - Array of tool call objects. * @returns {Array} Transformed tool calls array. * @private */ _transform_tool_calls_to_openai(tool_calls) { return tool_calls.map((tool_call) => ({ id: tool_call.id, type: tool_call.type, function: { name: tool_call.function.name, arguments: tool_call.function.arguments } })); } /** * Transform tools to OpenAI format. * @returns {Array} Transformed tools array. * @private */ _transform_tools_to_openai() { return this.tools.map((tool) => ({ type: tool.type, function: { name: tool.function.name, description: tool.function.description, parameters: tool.function.parameters } })); } }; var SmartChatModelResponseAdapter = class { // must be getter to prevent erroneous assignment static get platform_res() { return { id: "", object: "chat.completion", created: 0, model: "", choices: [], usage: {} }; } /** * @constructor * @param {SmartChatModelAdapter} adapter - The SmartChatModelAdapter instance * @param {Object} res - The response object */ constructor(adapter, res, status = null) { this.adapter = adapter; this._res = res || this.constructor.platform_res; this.status = status; } /** * Get response ID * @returns {string|null} Response ID */ get id() { return this._res.id || null; } /** * Get response object type * @returns {string|null} Object type */ get object() { return this._res.object || null; } /** * Get creation timestamp * @returns {number|null} Creation timestamp */ get created() { return this._res.created || null; } /** * Get response choices * @returns {Array} Array of choice objects */ get choices() { return this._res.choices || []; } /** * Get first tool call if present * @returns {Object|null} Tool call object */ get tool_call() { return this.message.tool_calls?.[0] || null; } /** * Get tool name from first tool call * @returns {string|null} Tool name */ get tool_name() { return this.tool_call?.tool_name || null; } /** * Get tool call parameters * @returns {Object|null} Tool parameters */ get tool_call_content() { return this.tool_call?.parameters || null; } /** * Get token usage statistics * @returns {Object|null} Usage statistics */ get usage() { return this._res.usage || null; } get error() { return this._res.error || null; } /** * Convert response to OpenAI format * @returns {Object} Response in OpenAI format */ to_openai() { if (this.error) return { error: normalize_error(this.error, this.status) }; const res = { id: this.id, object: this.object, created: this.created, choices: this._transform_choices_to_openai(), usage: this._transform_usage_to_openai(), raw: this._res }; return res; } /** * Parse chunk adds delta to content as expected output format */ handle_chunk(chunk) { if (chunk === "data: [DONE]") return; chunk = JSON.parse(chunk.split("data: ")[1] || "{}"); if (Object.keys(chunk).length === 0) return; if (!this._res.choices[0]) { this._res.choices.push({ message: { index: 0, role: "assistant", content: "" } }); } if (!this._res.id) { this._res.id = chunk.id; } let raw; if (chunk.choices?.[0]?.delta?.content) { const content = chunk.choices[0].delta.content; raw = content; this._res.choices[0].message.content += content; } if (chunk.choices?.[0]?.delta?.tool_calls) { if (!this._res.choices[0].message.tool_calls) { this._res.choices[0].message.tool_calls = [{ id: "", type: "function", function: { name: "", arguments: "" } }]; } if (chunk.choices[0].delta.tool_calls[0].id) { this._res.choices[0].message.tool_calls[0].id += chunk.choices[0].delta.tool_calls[0].id; } if (chunk.choices[0].delta.tool_calls[0].function.name) { this._res.choices[0].message.tool_calls[0].function.name += chunk.choices[0].delta.tool_calls[0].function.name; } if (chunk.choices[0].delta.tool_calls[0].function.arguments) { this._res.choices[0].message.tool_calls[0].function.arguments += chunk.choices[0].delta.tool_calls[0].function.arguments; } } return raw; } /** * Transform choices to OpenAI format. * @returns {Array} Transformed choices array. * @private */ _transform_choices_to_openai() { return this.choices.map((choice) => ({ index: choice.index, message: this._transform_message_to_openai(choice.message), finish_reason: this._get_openai_finish_reason(choice.finish_reason) })); } /** * Transform a single message to OpenAI format. * @param {Object} message - The message object to transform. * @returns {Object} Transformed message object. * @private */ _transform_message_to_openai(message = {}) { const transformed = { role: this._get_openai_role(message.role), content: this._get_openai_content(message) }; if (message.name) transformed.name = message.name; if (message.tool_calls) transformed.tool_calls = this._transform_tool_calls_to_openai(message.tool_calls); if (message.image_url) transformed.image_url = message.image_url; return transformed; } /** * Get the OpenAI role for a given role. * @param {string} role - The role to transform. * @returns {string} The transformed role. * @private */ _get_openai_role(role) { return role; } /** * Get the OpenAI content for a given content. * @param {string} content - The content to transform. * @returns {string} The transformed content. * @private */ _get_openai_content(message) { return message.content; } /** * Get the OpenAI finish reason for a given finish reason. * @param {string} finish_reason - The finish reason to transform. * @returns {string} The transformed finish reason. * @private */ _get_openai_finish_reason(finish_reason) { return finish_reason; } /** * Transform usage to OpenAI format. * @returns {Object} Transformed usage object. * @private */ _transform_usage_to_openai() { return this.usage; } /** * Transform tool calls to OpenAI format. * @param {Array} tool_calls - Array of tool call objects. * @returns {Array} Transformed tool calls array. * @private */ _transform_tool_calls_to_openai(tool_calls) { return tool_calls.map((tool_call) => ({ id: tool_call.id, type: tool_call.type, function: { name: tool_call.function.name, arguments: tool_call.function.arguments } })); } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/anthropic.js var SmartChatModelAnthropicAdapter = class extends SmartChatModelApiAdapter { static key = "anthropic"; static defaults = { description: "Anthropic Claude", type: "API", endpoint: "https://api.anthropic.com/v1/messages", streaming: true, api_key_header: "x-api-key", headers: { "anthropic-version": "2023-06-01", "anthropic-beta": "tools-2024-04-04", "anthropic-dangerous-direct-browser-access": true }, adapter: "Anthropic", models_endpoint: false, default_model: "claude-opus-4-1-20250805", signup_url: "https://console.anthropic.com/login?returnTo=%2Fsettings%2Fkeys" }; /** * Get request adapter class * @returns {typeof SmartChatModelAnthropicRequestAdapter} Request adapter class */ get req_adapter() { return SmartChatModelAnthropicRequestAdapter; } /** * Get response adapter class * @returns {typeof SmartChatModelAnthropicResponseAdapter} Response adapter class */ res_adapter = SmartChatModelAnthropicResponseAdapter; /** * Get available models (hardcoded list) and enrich via models.dev * @returns {Promise} Map of model objects */ async get_models() { try { this.model_data = await this.get_enriched_model_data(); this.model_data_loaded_at = Date.now(); this.model.data.provider_models = this.model_data; setTimeout(() => { this.model.re_render_settings(); }, 100); return this.model_data; } catch { return this.anthropic_models; } } is_end_of_stream(event) { const data = String(event?.data || "").trim(); if (!data.startsWith("data: ")) return false; try { return data.includes("message_stop"); } catch { return false; } } /** * Get hardcoded list of available models * @deprecated use get_enriched_model_data() instead (remove after no-incidents) * @returns {Object} Map of model objects with capabilities and limits */ get anthropic_models() { return { // ── Claude 4 family ────────────────────────────────────────────────────── "claude-opus-4-1-20250805": { name: "Claude Opus 4.1 (2025-08-05)", id: "claude-opus-4-1-20250805", model_name: "claude-opus-4-1-20250805", description: "Anthropic Claude Opus 4.1 snapshot (2025-08-05)", max_input_tokens: 2e5, max_output_tokens: 32e3, multimodal: true }, "claude-opus-4-20250514": { name: "Claude Opus 4 (2025-05-14)", id: "claude-opus-4-20250514", model_name: "claude-opus-4-20250514", description: "Anthropic Claude Opus 4 snapshot (2025-05-14)", max_input_tokens: 2e5, max_output_tokens: 32e3, multimodal: true }, "claude-sonnet-4-20250514": { name: "Claude Sonnet 4 (2025-05-14)", id: "claude-sonnet-4-20250514", model_name: "claude-sonnet-4-20250514", description: "Anthropic Claude Sonnet 4 snapshot (2025-05-14)", max_input_tokens: 2e5, max_output_tokens: 64e3, multimodal: true }, // ── Claude 3.7 family ─────────────────────────────────────────────────── "claude-3-7-sonnet-latest": { name: "Claude 3.7 Sonnet (latest)", id: "claude-3-7-sonnet-latest", model_name: "claude-3-7-sonnet-latest", description: "Anthropic Claude 3.7 Sonnet (rolling-latest)", max_input_tokens: 2e5, max_output_tokens: 64e3, multimodal: true }, "claude-3-7-sonnet-20250219": { name: "Claude 3.7 Sonnet (2025-02-19)", id: "claude-3-7-sonnet-20250219", model_name: "claude-3-7-sonnet-20250219", description: "Anthropic Claude 3.7 Sonnet snapshot (2025-02-19)", max_input_tokens: 2e5, max_output_tokens: 64e3, multimodal: true }, // ── Claude 3.5 family ─────────────────────────────────────────────────── "claude-3-5-sonnet-latest": { name: "Claude 3.5 Sonnet (latest)", id: "claude-3-5-sonnet-latest", model_name: "claude-3-5-sonnet-latest", description: "Anthropic Claude 3.5 Sonnet (rolling-latest)", max_input_tokens: 2e5, max_output_tokens: 8192, multimodal: true }, "claude-3-5-sonnet-20241022": { name: "Claude 3.5 Sonnet (2024-10-22)", id: "claude-3-5-sonnet-20241022", model_name: "claude-3-5-sonnet-20241022", description: "Anthropic Claude 3.5 Sonnet snapshot (2024-10-22)", max_input_tokens: 2e5, max_output_tokens: 8192, multimodal: true }, "claude-3-5-haiku-latest": { name: "Claude 3.5 Haiku (latest)", id: "claude-3-5-haiku-latest", model_name: "claude-3-5-haiku-latest", description: "Anthropic Claude 3.5 Haiku (rolling-latest)", max_input_tokens: 2e5, max_output_tokens: 8192 }, "claude-3-5-haiku-20241022": { name: "Claude 3.5 Haiku (2024-10-22)", id: "claude-3-5-haiku-20241022", model_name: "claude-3-5-haiku-20241022", description: "Anthropic Claude 3.5 Haiku snapshot (2024-10-22)", max_input_tokens: 2e5, max_output_tokens: 8192 }, // ── Claude 3 family ───────────────────────────────────────────────────── "claude-3-opus-latest": { name: "Claude 3 Opus (latest)", id: "claude-3-opus-latest", model_name: "claude-3-opus-latest", description: "Anthropic Claude 3 Opus (rolling-latest)", max_input_tokens: 2e5, max_output_tokens: 4096, multimodal: true }, "claude-3-opus-20240229": { name: "Claude 3 Opus (2024-02-29)", id: "claude-3-opus-20240229", model_name: "claude-3-opus-20240229", description: "Anthropic Claude 3 Opus snapshot (2024-02-29)", max_input_tokens: 2e5, max_output_tokens: 4096, multimodal: true }, "claude-3-sonnet-20240229": { name: "Claude 3 Sonnet (2024-02-29)", id: "claude-3-sonnet-20240229", model_name: "claude-3-sonnet-20240229", description: "Anthropic Claude 3 Sonnet snapshot (2024-02-29)", max_input_tokens: 2e5, max_output_tokens: 4096, multimodal: true }, "claude-3-haiku-20240307": { name: "Claude 3 Haiku (2024-03-07)", id: "claude-3-haiku-20240307", model_name: "claude-3-haiku-20240307", description: "Anthropic Claude 3 Haiku snapshot (2024-03-07)", max_input_tokens: 2e5, max_output_tokens: 4096, multimodal: true } }; } }; var SmartChatModelAnthropicRequestAdapter = class extends SmartChatModelRequestAdapter { /** * Convert request to Anthropic format * @returns {Object} Request parameters in Anthropic format */ to_platform(streaming = false) { return this.to_anthropic(streaming); } /** * Convert request to Anthropic format * @returns {Object} Request parameters in Anthropic format */ to_anthropic(streaming = false) { this.anthropic_body = { model: this.model_id, max_tokens: this.max_tokens, temperature: this.temperature, stream: streaming }; this.anthropic_body.messages = this._transform_messages_to_anthropic(); if (this.tools) { this.anthropic_body.tools = this._transform_tools_to_anthropic(); } if (this.tool_choice) { this.anthropic_body.tool_choice = this.tool_choice === "auto" ? { type: "auto" } : { type: "tool", name: this.tool_choice.function.name }; } return { url: this.adapter.endpoint, method: "POST", headers: this.get_headers(), body: JSON.stringify(this.anthropic_body) }; } /** * Transform messages to Anthropic format * @returns {Array} Messages in Anthropic format * @private */ _transform_messages_to_anthropic() { let anthropic_messages = []; for (const message of this.messages) { if (message.role === "system") { if (!this.anthropic_body.system) this.anthropic_body.system = ""; else this.anthropic_body.system += "\n\n"; this.anthropic_body.system += Array.isArray(message.content) ? message.content.map((part) => part.text).join("\n") : message.content; } else if (message.role === "tool") { const msg = { role: "user", content: [ { type: "tool_result", tool_use_id: message.tool_call_id, content: message.content } ] }; anthropic_messages.push(msg); } else { const msg = { role: this._get_anthropic_role(message.role), content: this._get_anthropic_content(message.content) }; if (message.tool_calls?.length > 0) msg.content = this._transform_tool_calls_to_content(message.tool_calls); anthropic_messages.push(msg); } } return anthropic_messages; } /** * Transform tool calls to Anthropic format * @param {Array} tool_calls - Tool calls * @returns {Array} Tool calls in Anthropic format * @private */ _transform_tool_calls_to_content(tool_calls) { return tool_calls.map((tool_call) => ({ type: "tool_use", id: tool_call.id, name: tool_call.function.name, input: JSON.parse(tool_call.function.arguments) })); } /** * Transform role to Anthropic format * @param {string} role - Original role * @returns {string} Role in Anthropic format * @private */ _get_anthropic_role(role) { const role_map = { function: "assistant", // Anthropic doesn't have a function role, so we'll treat it as assistant tool: "user" }; return role_map[role] || role; } /** * Transform content to Anthropic format * @param {string|Array} content - Original content * @returns {string|Array} Content in Anthropic format * @private */ _get_anthropic_content(content) { if (Array.isArray(content)) { return content.map((item) => { if (item.type === "text") return { type: "text", text: item.text }; if (item.type === "image_url") { return { type: "image", source: { type: "base64", media_type: item.image_url.url.split(";")[0].split(":")[1], data: item.image_url.url.split(",")[1] } }; } if (item.type === "file" && item.file?.filename?.toLowerCase().endsWith(".pdf")) { if (item.file?.file_data) { return { type: "document", source: { type: "base64", media_type: "application/pdf", data: item.file.file_data.split(",")[1] } }; } } return item; }); } return content; } /** * Transform tools to Anthropic format * @returns {Array} Tools in Anthropic format * @private */ _transform_tools_to_anthropic() { if (!this.tools) return void 0; return this.tools.map((tool) => ({ name: tool.function.name, description: tool.function.description, input_schema: tool.function.parameters })); } }; var SmartChatModelAnthropicResponseAdapter = class extends SmartChatModelResponseAdapter { static get platform_res() { return { content: [], id: "", model: "", role: "assistant", stop_reason: null, stop_sequence: null, type: "message", usage: { input_tokens: 0, output_tokens: 0 } }; } /** * Convert response to OpenAI format * @returns {Object} Response in OpenAI format */ to_openai() { if (this.error) return { error: normalize_error(this.error, this.status) }; return { id: this._res.id, object: "chat.completion", created: Date.now(), choices: [ { index: 0, message: this._transform_message_to_openai(), finish_reason: this._get_openai_finish_reason(this._res.stop_reason) } ], usage: this._transform_usage_to_openai() }; } /** * Transform message to OpenAI format * @returns {Object} Message in OpenAI format * @private */ _transform_message_to_openai() { const message = { role: "assistant", content: "", tool_calls: [] }; if (Array.isArray(this._res.content)) { for (const content of this._res.content) { if (content.type === "text") { message.content += (message.content ? "\n\n" : "") + content.text; } else if (content.type === "tool_use") { message.tool_calls.push({ id: content.id, type: "function", function: { name: content.name, arguments: JSON.stringify(content.input) } }); } } } else { message.content = this._res.content; } if (message.tool_calls.length === 0) { delete message.tool_calls; } return message; } /** * Transform finish reason to OpenAI format * @param {string} stop_reason - Original finish reason * @returns {string} Finish reason in OpenAI format * @private */ _get_openai_finish_reason(stop_reason) { const reason_map = { "end_turn": "stop", "max_tokens": "length", "tool_use": "function_call" }; return reason_map[stop_reason] || stop_reason; } /** * Transform usage statistics to OpenAI format * @returns {Object} Usage statistics in OpenAI format * @private */ _transform_usage_to_openai() { if (!this._res.usage) { return { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }; } return { prompt_tokens: this._res.usage.input_tokens || 0, completion_tokens: this._res.usage.output_tokens || 0, total_tokens: (this._res.usage.input_tokens || 0) + (this._res.usage.output_tokens || 0) }; } handle_chunk(chunk) { if (!chunk.startsWith("data: ")) return; chunk = JSON.parse(chunk.slice(6)); if (!this._res.content.length) { this._res.content = [ { type: "text", text: "" } ]; } if (chunk.message?.id) { this._res.id = chunk.message.id; } if (chunk.message?.model) { this._res.model = chunk.message.model; } if (chunk.message?.role) { this._res.role = chunk.message.role; } let raw; if (chunk.delta?.type === "text_delta") { const content = chunk.delta?.text; raw = content; this._res.content[0].text += content; } if (chunk.delta?.stop_reason) { this._res.stop_reason = chunk.delta.stop_reason; } if (chunk.usage) { this._res.usage = { ...this._res.usage, ...chunk.usage }; } return raw; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/openai.js var EXCLUDED_PREFIXES = [ "text-", "davinci", "babbage", "ada", "curie", "dall-e", "whisper", "omni", "tts", "gpt-4o-mini-tts", "computer-use", "codex", "gpt-4o-transcribe", "gpt-4o-mini-transcribe", "gpt-4o-mini-realtime", "gpt-4o-realtime", "o4-mini-deep-research", "o3-deep-research", "gpt-image" ]; var SmartChatModelOpenaiAdapter = class extends SmartChatModelApiAdapter { static key = "openai"; static defaults = { description: "OpenAI", type: "API", endpoint: "https://api.openai.com/v1/chat/completions", streaming: true, models_endpoint: "https://api.openai.com/v1/models", default_model: "gpt-5-nano", signup_url: "https://platform.openai.com/api-keys" }; res_adapter = SmartChatModelOpenaiResponseAdapter; /** * Parse model data from OpenAI API response. * Filters for GPT models and adds context window information. * @param {Object} model_data - Raw model data from OpenAI * @returns {Object} Map of model objects with capabilities and limits */ parse_model_data(model_data) { return model_data.data.filter((model) => !EXCLUDED_PREFIXES.some((m) => model.id.startsWith(m)) && !model.id.includes("-instruct")).reduce((acc, model) => { const out = { model_name: model.id, id: model.id, multimodal: true, max_input_tokens: get_max_input_tokens(model.id) }; acc[model.id] = out; return acc; }, {}); } /** * Override the HTTP method for fetching models. */ models_endpoint_method = "GET"; /** * Test the API key by attempting to fetch models. * @deprecated in favor of smart_model.test_model (should be safe to remove 2026-02-10) * @returns {Promise} True if API key is valid */ async test_api_key() { const models = await this.get_models(); return models.length > 0; } /** * Get settings configuration for OpenAI adapter. * Adds image resolution setting for multimodal models. * @returns {Object} Settings configuration object */ get settings_config() { const config = super.settings_config; config["[CHAT_ADAPTER].open_ai_note"] = { name: "Note about using OpenAI", type: "html", value: "OpenAI models: Some models require extra verification steps in your OpenAI account for them to appear in the model list." }; return config; } }; function get_max_input_tokens(model_id) { if (model_id.startsWith("gpt-4.1")) { return 1e6; } if (model_id.startsWith("o")) { return 2e5; } if (model_id.startsWith("gpt-5")) { return 4e5; } if (model_id.startsWith("gpt-4o") || model_id.startsWith("gpt-4.5") || model_id.startsWith("gpt-4-turbo")) { return 128e3; } if (model_id.startsWith("gpt-4")) { return 8192; } if (model_id.startsWith("gpt-3")) { return 16385; } return 8e3; } var SmartChatModelOpenaiResponseAdapter = class extends SmartChatModelResponseAdapter { }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/azure.js var SmartChatModelAzureAdapter = class extends SmartChatModelOpenaiAdapter { static key = "azure"; static defaults = { description: "Azure OpenAI", type: "API", adapter: "AzureOpenAI", streaming: true, api_key_header: "api-key", azure_resource_name: "", azure_deployment_name: "", azure_api_version: "2024-10-01-preview", default_model: "gpt-35-turbo", signup_url: "https://learn.microsoft.com/azure/cognitive-services/openai/quickstart?tabs=command-line", models_endpoint: "https://{azure_resource_name}.openai.azure.com/openai/deployments?api-version={azure_api_version}" }; /** * Override the settings configuration to include Azure-specific fields. */ get settings_config() { return { ...super.settings_config, "[CHAT_ADAPTER].azure_resource_name": { name: "Azure Resource Name", type: "text", description: "The name of your Azure OpenAI resource (e.g. 'my-azure-openai').", default: "" }, "[CHAT_ADAPTER].azure_deployment_name": { name: "Azure Deployment Name", type: "text", description: "The name of your specific model deployment (e.g. 'gpt35-deployment').", default: "" }, "[CHAT_ADAPTER].azure_api_version": { name: "Azure API Version", type: "text", description: "The API version for Azure OpenAI (e.g. '2024-10-01-preview').", default: "2024-10-01-preview" } }; } /** * Build the endpoint dynamically based on Azure settings. * Example: * https://.openai.azure.com/openai/deployments//chat/completions?api-version=2023-05-15 */ get endpoint() { const { azure_resource_name, azure_deployment_name, azure_api_version } = this.model.data; return `https://${azure_resource_name}.openai.azure.com/openai/deployments/${azure_deployment_name}/chat/completions?api-version=${azure_api_version}`; } /** * For streaming, we can reuse the same endpoint. * The request body includes `stream: true` which the base class uses. */ get endpoint_streaming() { return this.endpoint; } /** * The models endpoint for retrieving a list of your deployments. * E.g.: * https://.openai.azure.com/openai/deployments?api-version=2023-05-15 */ get models_endpoint() { const { azure_resource_name, azure_api_version } = this.model.data; return `https://${azure_resource_name}.openai.azure.com/openai/deployments?api-version=${azure_api_version}`; } /** * Azure returns a list of deployments in the shape: * { * "object": "list", * "data": [ * { * "id": "mydeployment", * "model": "gpt-35-turbo", * "status": "succeeded", * "createdAt": ... * "updatedAt": ... * ... * }, * ... * ] * } * We'll parse them into a dictionary keyed by deployment ID. */ parse_model_data(model_data) { if (model_data.object !== "list" || !Array.isArray(model_data.data)) { return { "_": { id: "No deployments found." } }; } const parsed = {}; for (const d of model_data.data) { parsed[d.id] = { model_name: d.id, id: d.id, raw: d, // You can add more details if you want: description: `Model: ${d.model}, Status: ${d.status}`, // Hard to guess tokens; omit or guess: max_input_tokens: 4e3 }; } return parsed; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/google.js var SmartChatModelGoogleAdapter = class extends SmartChatModelApiAdapter { static key = "google"; static defaults = { description: "Google (Gemini)", type: "API", api_key_header: "none", endpoint: "https://generativelanguage.googleapis.com/v1beta/models/MODEL_NAME:generateContent", endpoint_streaming: "https://generativelanguage.googleapis.com/v1beta/models/MODEL_NAME:streamGenerateContent", streaming: true, adapter: "Gemini", models_endpoint: "https://generativelanguage.googleapis.com/v1beta/models", default_model: "gemini-1.5-pro", signup_url: "https://ai.google.dev/" }; streaming_chunk_splitting_regex = /(\r\n|\n|\r){2}/g; // handle Google's BS (split on double newlines only) /** * Get request adapter class */ req_adapter = SmartChatModelGeminiRequestAdapter; /** * Get response adapter class */ res_adapter = SmartChatModelGeminiResponseAdapter; /** * Uses Gemini's dedicated token counting endpoint */ async count_tokens(input) { const req = { url: `https://generativelanguage.googleapis.com/v1beta/models/${this.model_key}:countTokens?key=${this.api_key}`, method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify(this.prepare_token_count_body(input)) }; const resp = await this.http_adapter.request(req); return resp.json.totalTokens; } /** * Formats input for token counting based on type * @private */ prepare_token_count_body(input) { if (typeof input === "string") { return { contents: [{ parts: [{ text: input }] }] }; } else if (Array.isArray(input)) { return { contents: input.map((msg) => this.transform_message_for_token_count(msg)) }; } else if (typeof input === "object") { return { contents: [this.transform_message_for_token_count(input)] }; } throw new Error("Invalid input for count_tokens"); } /** * Transforms message for token counting, handling text and images * @private */ transform_message_for_token_count(message) { return { role: message.role === "assistant" ? "model" : message.role, parts: Array.isArray(message.content) ? message.content.map((part) => { if (part.type === "text") return { text: part.text }; if (part.type === "image_url") return { inline_data: { mime_type: part.image_url.url.split(";")[0].split(":")[1], data: part.image_url.url.split(",")[1] } }; return part; }) : [{ text: message.content }] }; } /** * Builds endpoint URLs with model and API key */ get endpoint() { return `https://generativelanguage.googleapis.com/v1beta/models/${this.model_key}:generateContent?key=${this.api_key}`; } get endpoint_streaming() { return `https://generativelanguage.googleapis.com/v1beta/models/${this.model_key}:streamGenerateContent?key=${this.api_key}`; } // /** // * Extracts text from Gemini's streaming format // */ // get_text_chunk_from_stream(event) { // const data = JSON.parse(event.data); // return data.candidates[0]?.content?.parts[0]?.text || ''; // } /** * Get models endpoint URL with API key * @returns {string} Complete models endpoint URL */ get models_endpoint() { return `${this.constructor.defaults.models_endpoint}?key=${this.api_key}`; } /** * Get HTTP method for models endpoint * @returns {string} HTTP method ("GET") */ get models_endpoint_method() { return "GET"; } get models_request_params() { return { url: this.models_endpoint, method: this.models_endpoint_method }; } /** * Parse model data from Gemini API response * @param {Object} model_data - Raw model data from API * @returns {Object} Map of model objects with capabilities and limits */ parse_model_data(model_data) { return model_data.models.filter((model) => model.name.startsWith("models/gemini")).reduce((acc, model) => { const out = { model_name: model.name.split("/").pop(), id: model.name.split("/").pop(), max_input_tokens: model.inputTokenLimit, max_output_tokens: model.maxOutputTokens, description: model.description, multimodal: model.name.includes("vision") || model.description.includes("multimodal"), raw: model }; acc[model.name.split("/").pop()] = out; return acc; }, {}); } is_end_of_stream(event) { return event.data.includes('"finishReason"'); return false; } }; var SmartChatModelGeminiRequestAdapter = class extends SmartChatModelRequestAdapter { to_platform(streaming = false) { return this.to_gemini(streaming); } to_gemini(streaming = false) { const gemini_body = { contents: this._transform_messages_to_gemini(), generationConfig: { temperature: this.temperature, maxOutputTokens: this.max_tokens, topK: this._req.topK || 1, topP: this._req.topP || 1, stopSequences: this._req.stop || [] }, safetySettings: [ { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_NONE" }, { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_NONE" } ] }; if (this.tools) gemini_body.tools = this._transform_tools_to_gemini(); if (gemini_body.tools && this.tool_choice !== "none") gemini_body.tool_config = this._transform_tool_choice_to_gemini(); return { url: streaming ? this.adapter.endpoint_streaming : this.adapter.endpoint, method: "POST", headers: this.get_headers(), body: JSON.stringify(gemini_body) }; } _transform_messages_to_gemini() { let gemini_messages = []; let system_message = ""; for (const message of this.messages) { if (message.role === "system") { system_message += message.content + "\n"; } else { gemini_messages.push({ role: this._get_gemini_role(message.role), parts: this._transform_content_to_gemini(message.content) }); } } if (system_message) { gemini_messages.unshift({ role: "user", parts: [{ text: system_message.trim() }] }); } return gemini_messages; } _get_gemini_role(role) { const role_map = { user: "user", assistant: "model", function: "model" // Gemini doesn't have a function role, so we'll treat it as model }; return role_map[role] || role; } _transform_content_to_gemini(content) { if (Array.isArray(content)) { return content.map((part) => { if (part.type === "text") return { text: part.text }; if (part.type === "image_url") { let mime_type = part.image_url.url.split(";")[0].split(":")[1]; if (mime_type === "image/jpg") mime_type = "image/jpeg"; return { inline_data: { mime_type, data: part.image_url.url.split(",")[1] } }; } if (part.type === "file" && part.file?.filename?.toLowerCase().endsWith(".pdf")) { if (part.file?.file_data) { return { inline_data: { mime_type: "application/pdf", data: part.file.file_data.split(",")[1] } }; } } return part; }); } return [{ text: content }]; } _transform_tools_to_gemini() { return [{ function_declarations: this.tools.map((tool) => ({ name: tool.function.name, description: tool.function.description, parameters: tool.function.parameters })) }]; } _transform_tool_choice_to_gemini() { return { function_calling_config: { mode: "ANY", allowed_function_names: this.tools.map((tool) => tool.function.name) } }; } }; var SmartChatModelGeminiResponseAdapter = class extends SmartChatModelResponseAdapter { static get platform_res() { return { candidates: [{ content: { parts: [ { text: "" } ], role: "" }, finishReason: "" }], promptFeedback: {}, usageMetadata: {} }; } to_openai() { if (this.error) return { error: normalize_error(this.error, this.status) }; const first_candidate = this._res.candidates[0]; if (!this._res.id) this._res.id = "gemini-" + Date.now().toString(); return { id: this._res.id, object: "chat.completion", created: Date.now(), model: this.adapter.model_key, choices: [{ index: 0, message: first_candidate?.content ? this._transform_message_to_openai(first_candidate.content) : "", finish_reason: this._get_openai_finish_reason(first_candidate.finishReason) }], usage: this._transform_usage_to_openai() }; } _transform_message_to_openai(content) { const message = { role: "assistant", content: content.parts.filter((part) => part.text).map((part) => part.text).join("") }; const function_call = content.parts.find((part) => part.functionCall); if (function_call) { message.tool_calls = [{ type: "function", function: { name: function_call.functionCall.name, arguments: JSON.stringify(function_call.functionCall.args) } }]; } return message; } _get_openai_finish_reason(finish_reason) { const reason_map = { "STOP": "stop", "MAX_TOKENS": "length", "SAFETY": "content_filter", "RECITATION": "content_filter", "OTHER": "null" }; return reason_map[finish_reason] || finish_reason.toLowerCase(); } _transform_usage_to_openai() { if (!this._res.usageMetadata) { return { prompt_tokens: null, completion_tokens: null, total_tokens: null }; } return { prompt_tokens: this._res.usageMetadata.promptTokenCount || null, completion_tokens: this._res.usageMetadata.candidatesTokenCount || null, total_tokens: this._res.usageMetadata.totalTokenCount || null }; } handle_chunk(chunk) { let chunk_trimmed = chunk.trim(); if (["[", ","].includes(chunk_trimmed[0])) chunk_trimmed = chunk_trimmed.slice(1); if (["]", ","].includes(chunk_trimmed[chunk_trimmed.length - 1])) chunk_trimmed = chunk_trimmed.slice(0, -1); const data = JSON.parse(chunk_trimmed); let raw; if (data.candidates?.[0]?.content?.parts?.[0]?.text?.length) { const content = data.candidates[0].content.parts[0].text; raw = content; this._res.candidates[0].content.parts[0].text += content; } if (data.candidates?.[0]?.content?.role?.length) { this._res.candidates[0].content.role = data.candidates[0].content.role; } if (data.candidates?.[0]?.finishReason?.length) { this._res.candidates[0].finishReason += data.candidates[0].finishReason; } if (data.promptFeedback) { this._res.promptFeedback = { ...this._res.promptFeedback || {}, ...data.promptFeedback }; } if (data.usageMetadata) { this._res.usageMetadata = { ...this._res.usageMetadata || {}, ...data.usageMetadata }; } if (data.candidates?.[0]?.content?.parts?.[0]?.functionCall) { if (!this._res.candidates[0].content.parts[0].functionCall) { this._res.candidates[0].content.parts[0].functionCall = { name: "", args: {} }; } this._res.candidates[0].content.parts[0].functionCall.name += data.candidates[0].content.parts[0].functionCall.name; if (data.candidates[0].content.parts[0].functionCall.args) { Object.entries(data.candidates[0].content.parts[0].functionCall.args).forEach(([key, value]) => { if (!this._res.candidates[0].content.parts[0].functionCall.args[key]) { this._res.candidates[0].content.parts[0].functionCall.args[key] = ""; } this._res.candidates[0].content.parts[0].functionCall.args[key] += value; }); } } return raw; } }; var SmartChatModelGeminiAdapter = class extends SmartChatModelGoogleAdapter { static key = "gemini"; static defaults = { description: "Gemini (SWITCH TO **GOOGLE** ADAPTER)", type: "API", api_key_header: "none", endpoint: "https://generativelanguage.googleapis.com/v1beta/models/MODEL_NAME:generateContent", endpoint_streaming: "https://generativelanguage.googleapis.com/v1beta/models/MODEL_NAME:streamGenerateContent", streaming: true, adapter: "Gemini", models_endpoint: "https://generativelanguage.googleapis.com/v1beta/models", default_model: "gemini-1.5-pro", signup_url: "https://ai.google.dev/" }; }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/open_router.js var SmartChatModelOpenRouterAdapter = class extends SmartChatModelApiAdapter { static key = "open_router"; static models_dev_key = "openrouter"; static defaults = { description: "Open Router", type: "API", endpoint: "https://openrouter.ai/api/v1/chat/completions", streaming: true, adapter: "OpenRouter", models_endpoint: "https://openrouter.ai/api/v1/models", default_model: "mistralai/mistral-7b-instruct:free", signup_url: "https://accounts.openrouter.ai/sign-up?redirect_url=https%3A%2F%2Fopenrouter.ai%2Fkeys" }; /** * Get request adapter class * @returns {typeof SmartChatModelOpenRouterRequestAdapter} Request adapter class */ get req_adapter() { return SmartChatModelOpenRouterRequestAdapter; } /** * Get response adapter class * @returns {typeof SmartChatModelOpenRouterResponseAdapter} Response adapter class */ get res_adapter() { return SmartChatModelOpenRouterResponseAdapter; } /** * Count tokens in input text (rough estimate) * @param {string|Object} input - Text to count tokens for * @returns {Promise} Estimated token count */ async count_tokens(input) { const text = typeof input === "string" ? input : JSON.stringify(input); return Math.ceil(text.length / 4); } get models_request_params() { return { url: this.models_endpoint, method: "GET" }; } /** * Parse model data from OpenRouter API response * @param {Object} model_data - Raw model data * @returns {Object} Map of model objects with capabilities and limits */ parse_model_data(model_data) { if (model_data.data) { model_data = model_data.data; } if (model_data.error) throw new Error(model_data.error); return model_data.reduce((acc, model) => { acc[model.id] = { model_name: model.id, id: model.id, max_input_tokens: model.context_length, name: model.name, description: model.name, long_desc: model.description, multimodal: model.architecture.modality === "multimodal", raw: model }; return acc; }, {}); } }; var SmartChatModelOpenRouterRequestAdapter = class extends SmartChatModelRequestAdapter { to_platform(stream = false) { const req = this.to_openai(stream); return req; } _get_openai_content(message) { if (message.role === "user") { if (Array.isArray(message.content) && message.content.every((part) => part.type === "text")) { return message.content.map((part) => part.text).join("\n"); } } return message.content; } }; var SmartChatModelOpenRouterResponseAdapter = class extends SmartChatModelResponseAdapter { static get platform_res() { return { id: "", object: "chat.completion", created: 0, model: "", choices: [], usage: {} }; } to_platform() { return this.to_openai(); } get object() { return "chat.completion"; } get error() { if (!this._res.error) return null; const error = this._res.error; if (!error.message) error.message = ""; if (this._res.error.metadata?.raw) { if (typeof this._res.error.metadata.raw === "string") { error.message += ` ${this._res.error.metadata.raw}`; } else { error.message += ` ${JSON.stringify(this._res.error.metadata.raw, null, 2)}`; } } if (error.message.startsWith("No cookie auth")) { error.suggested_action = "Ensure your Open Router API key is set correctly."; } return error; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/lm_studio.js var SmartChatModelLmStudioAdapter = class extends SmartChatModelApiAdapter { static key = "lm_studio"; /** @type {import('./_adapter.js').SmartChatModelAdapter['constructor']['defaults']} */ static defaults = { description: "LM Studio (OpenAI\u2011compatible)", type: "API", endpoint: "http://localhost:1234/v1/chat/completions", streaming: true, adapter: "LM_Studio_OpenAI_Compat", models_endpoint: "http://localhost:1234/v1/models", default_model: "", signup_url: "https://lmstudio.ai/docs/api/openai-api", api_key: "no api key required" }; /* ------------------------------------------------------------------ * * Request / Response classes * ------------------------------------------------------------------ */ get req_adapter() { return SmartChatModelLmStudioRequestAdapter; } get res_adapter() { return SmartChatModelLmStudioResponseAdapter; } /* ------------------------------------------------------------------ * * Settings * ------------------------------------------------------------------ */ /** * Extend the base settings with a read‑only HTML block that reminds the * user to enable CORS inside LM Studio. The Smart View renderer treats * `type: "html"` as a static fragment, so no extra runtime logic is needed. */ get settings_config() { const config = super.settings_config; delete config["[CHAT_ADAPTER].api_key"]; return { ...config, "[CHAT_ADAPTER].cors_instructions": { /* visible only when this adapter is selected */ name: "CORS required", type: "html", value: `

Before you can use LM Studio you must Enable CORS inside LM Studio \u2192 Developer \u2192 Settings

` } }; } /* ------------------------------------------------------------------ * * Model list helpers * ------------------------------------------------------------------ */ /** * LM Studio returns an OpenAI‑style list; normalise to the project shape. */ parse_model_data(model_data) { if (model_data.object !== "list" || !Array.isArray(model_data.data)) { return { _: { id: "No models found." } }; } const out = {}; for (const m of model_data.data) { out[m.id] = { id: m.id, model_name: m.id, description: `LM Studio model: ${m.id}`, multimodal: false }; } return out; } get models_endpoint_method() { return "get"; } /** * Count tokens in input text (no dedicated endpoint) * Rough estimate: 1 token ~ 4 chars * @param {string|Object} input * @returns {Promise} */ async count_tokens(input) { const text = typeof input === "string" ? input : JSON.stringify(input); return Math.ceil(text.length / 4); } /** * Test API key - LM Studio doesn't require API key. Always true. * @deprecated in favor of smart_model.test_model (should be safe to remove 2026-02-10) * @returns {Promise} */ async test_api_key() { return true; } get api_key() { return "no api key required"; } }; var SmartChatModelLmStudioRequestAdapter = class extends SmartChatModelRequestAdapter { to_platform(streaming = false) { const req = this.to_openai(streaming); const body = JSON.parse(req.body); if (this.tool_choice?.function?.name) { const last_msg = body.messages[body.messages.length - 1]; if (typeof last_msg.content === "string") { last_msg.content = [ { type: "text", text: last_msg.content } ]; } last_msg.content.push({ type: "text", text: `Use the "${this.tool_choice.function.name}" tool.` }); body.tool_choice = "required"; } else if (body.tool_choice && typeof body.tool_choice === "object") { body.tool_choice = "auto"; } req.body = JSON.stringify(body); return req; } }; var SmartChatModelLmStudioResponseAdapter = class extends SmartChatModelResponseAdapter { }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/ollama.js var SmartChatModelOllamaAdapter = class extends SmartChatModelApiAdapter { static key = "ollama"; static defaults = { description: "Ollama (Local)", type: "API", // models_endpoint: "http://localhost:11434/api/tags", // endpoint: "http://localhost:11434/api/chat", api_key: "na", host: "http://localhost:11434", endpoint: "/api/chat", models_endpoint: "/api/tags", // streaming: false, // TODO: Implement streaming streaming: true }; req_adapter = SmartChatModelOllamaRequestAdapter; res_adapter = SmartChatModelOllamaResponseAdapter; get host() { return this.model.data.host || this.constructor.defaults.host; } get endpoint() { return `${this.host}${this.constructor.defaults.endpoint}`; } get models_endpoint() { return `${this.host}${this.constructor.defaults.models_endpoint}`; } get model_show_endpoint() { return `${this.host}/api/show`; } get models_endpoint_method() { return "GET"; } /** * Get available models from local Ollama instance * @param {boolean} [refresh=false] - Whether to refresh cached models * @returns {Promise} Map of model objects */ async get_models(refresh = false) { if (!refresh && typeof this.model_data === "object" && Object.keys(this.model_data || {}).length > 0 && this.model_data_loaded_at && time_now - this.model_data_loaded_at < 1 * 60 * 60 * 1e3) return this.model_data; try { const list_resp = await this.http_adapter.request(this.models_request_params); const list_data = await list_resp.json(); const models_raw_data = []; for (const model of list_data.models) { const model_details_resp = await this.http_adapter.request({ url: this.model_show_endpoint, method: "POST", body: JSON.stringify({ model: model.name }) }); const model_details_data = await model_details_resp.json(); models_raw_data.push({ ...model_details_data, name: model.name }); } this.model_data = this.parse_model_data(models_raw_data); await this.get_enriched_model_data(); this.model.data.provider_models = this.model_data; if (typeof this.model.re_render_settings === "function") { this.model.re_render_settings(); } this.model_data_loaded_at = Date.now(); return this.model_data; } catch (error) { console.error("Failed to fetch model data:", error); return { "_": { id: `Failed to fetch models from ${this.model.adapter_name}` } }; } } /** * Parse model data from Ollama API response * @param {Object[]} model_data - Raw model data from Ollama * @returns {Object} Map of model objects with capabilities and limits */ parse_model_data(model_data) { if (!Array.isArray(model_data)) { this.model_data = {}; console.error("Invalid model data format from Ollama:", model_data); return {}; } if (model_data.length === 0) { this.model_data = { "no_models_available": { id: "no_models_available", name: "No models currently available" } }; return this.model_data; } return model_data.reduce((acc, model) => { if (model.name.includes("embed")) return acc; const context_entry = Object.entries(model.model_info || {}).find((m) => m[0].includes(".context_length")); const out = { model_name: model.name, id: model.name, multimodal: false, max_input_tokens: context_entry?.[1] || 4096 }; acc[model.name] = out; return acc; }, {}); } /** * Override settings config to remove API key setting since not needed for local instance * @returns {Object} Settings configuration object */ get settings_config() { const config = super.settings_config; delete config["[CHAT_ADAPTER].api_key"]; config["[CHAT_ADAPTER].host"] = { name: "Ollama host", type: "text", description: "Enter the host for your Ollama instance", default: this.constructor.defaults.host }; return config; } is_end_of_stream(event) { return event.data.includes('"done_reason"'); } }; var SmartChatModelOllamaRequestAdapter = class extends SmartChatModelRequestAdapter { /** * Convert request to Ollama format * @returns {Object} Request parameters in Ollama format */ to_platform(streaming = false) { const ollama_body = { model: this.model_id, messages: this._transform_messages_to_ollama(), options: this._transform_parameters_to_ollama(), stream: streaming || this.stream // format: 'json', // only used for tool calls since returns JSON in content body }; if (this.tools) { ollama_body.tools = this._transform_functions_to_tools(); if (this.tool_choice?.function?.name) { ollama_body.messages[ollama_body.messages.length - 1].content += ` Use the "${this.tool_choice.function.name}" tool.`; ollama_body.format = "json"; } } return { url: this.adapter.endpoint, method: "POST", body: JSON.stringify(ollama_body) }; } /** * Transform messages to Ollama format * @returns {Array} Messages in Ollama format * @private */ _transform_messages_to_ollama() { return this.messages.map((message) => { const ollama_message = { role: message.role, content: this._transform_content_to_ollama(message.content) }; const images = this._extract_images_from_content(message.content); if (images.length > 0) { ollama_message.images = images.map((img) => img.replace(/^data:image\/[^;]+;base64,/, "")); } return ollama_message; }); } /** * Transform content to Ollama format * @param {string|Array} content - Message content * @returns {string} Content in Ollama format * @private */ _transform_content_to_ollama(content) { if (Array.isArray(content)) { return content.filter((item) => item.type === "text").map((item) => item.text).join("\n"); } return content; } /** * Extract images from content * @param {string|Array} content - Message content * @returns {Array} Array of image URLs * @private */ _extract_images_from_content(content) { if (!Array.isArray(content)) return []; return content.filter((item) => item.type === "image_url").map((item) => item.image_url.url); } /** * Transform functions to tools format * @returns {Array} Tools array in Ollama format * @private */ _transform_functions_to_tools() { return this.tools; } /** * Transform parameters to Ollama options format * @returns {Object} Options in Ollama format * @private */ _transform_parameters_to_ollama() { const options = {}; if (this.max_tokens) options.num_predict = this.max_tokens; if (this.temperature) options.temperature = this.temperature; if (this.top_p) options.top_p = this.top_p; if (this.frequency_penalty) options.frequency_penalty = this.frequency_penalty; if (this.presence_penalty) options.presence_penalty = this.presence_penalty; return options; } }; var SmartChatModelOllamaResponseAdapter = class extends SmartChatModelResponseAdapter { static get platform_res() { return { model: "", created_at: null, message: { role: "", content: "" }, total_duration: 0, load_duration: 0, prompt_eval_count: 0, prompt_eval_duration: 0, eval_count: 0, eval_duration: 0 }; } /** * Convert response to OpenAI format * @returns {Object} Response in OpenAI format */ to_openai() { if (this.error) return { error: normalize_error(this.error, this.status) }; return { id: this._res.created_at, object: "chat.completion", created: Date.now(), model: this._res.model, choices: [ { index: 0, message: this._transform_message_to_openai(), finish_reason: this._res.done_reason } ], usage: this._transform_usage_to_openai() }; } /** * Transform message to OpenAI format * @returns {Object} Message in OpenAI format * @private */ _transform_message_to_openai() { return { role: this._res.message.role, content: this._res.message.content, tool_calls: this._res.message.tool_calls }; } /** * Transform usage statistics to OpenAI format * @returns {Object} Usage statistics in OpenAI format * @private */ _transform_usage_to_openai() { return { prompt_tokens: this._res.prompt_eval_count || 0, completion_tokens: this._res.eval_count || 0, total_tokens: (this._res.prompt_eval_count || 0) + (this._res.eval_count || 0) }; } /** * Parse chunk adds delta to content as expected output format */ handle_chunk(chunk) { chunk = JSON.parse(chunk || "{}"); if (chunk.created_at && !this._res.created_at) { this._res.created_at = chunk.created_at; } let raw; if (chunk.message?.content) { const content = chunk.message.content; raw = content; this._res.message.content += content; } if (chunk.message?.role) { this._res.message.role = chunk.message.role; } if (chunk.model) { this._res.model = chunk.model; } if (chunk.message?.tool_calls) { if (!this._res.message.tool_calls) { this._res.message.tool_calls = [{ id: "", type: "function", function: { name: "", arguments: "" } }]; } if (chunk.message.tool_calls[0].id) { this._res.message.tool_calls[0].id += chunk.message.tool_calls[0].id; } if (chunk.message.tool_calls[0].function.name) { this._res.message.tool_calls[0].function.name += chunk.message.tool_calls[0].function.name; } if (chunk.message.tool_calls[0].function.arguments) { if (typeof chunk.message.tool_calls[0].function.arguments === "string") { this._res.message.tool_calls[0].function.arguments += chunk.message.tool_calls[0].function.arguments; } else { this._res.message.tool_calls[0].function.arguments = chunk.message.tool_calls[0].function.arguments; } } } return raw; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/_custom.js var adapters_map = { "openai": { req: SmartChatModelRequestAdapter, res: SmartChatModelResponseAdapter }, "anthropic": { req: SmartChatModelAnthropicRequestAdapter, res: SmartChatModelAnthropicResponseAdapter }, "gemini": { req: SmartChatModelGeminiRequestAdapter, res: SmartChatModelGeminiResponseAdapter }, "lm_studio": { req: SmartChatModelLmStudioRequestAdapter, res: SmartChatModelLmStudioResponseAdapter }, "ollama": { req: SmartChatModelOllamaRequestAdapter, res: SmartChatModelOllamaResponseAdapter } }; var SmartChatModelCustomAdapter = class extends SmartChatModelApiAdapter { static key = "custom"; static defaults = { description: "Custom API (Local or Remote, OpenAI format)", type: "API", /** * new default property: 'api_adapter' indicates which * request/response adapter set to use internally */ api_adapter: "openai" }; /** * Provide dynamic request/response classes */ /** * @override * @returns {typeof SmartChatModelRequestAdapter} */ get req_adapter() { const adapter_name = this.model.data.api_adapter || "openai"; const map_entry = adapters_map[adapter_name]; return map_entry && map_entry.req ? map_entry.req : SmartChatModelRequestAdapter; } /** * @override * @returns {typeof SmartChatModelResponseAdapter} */ get res_adapter() { const adapter_name = this.model.data.api_adapter || "openai"; const map_entry = adapters_map[adapter_name]; return map_entry && map_entry.res ? map_entry.res : SmartChatModelResponseAdapter; } /** * Synthesize a custom endpoint from the config fields. * All fields are optional; fallback to a minimal default. * @returns {string} */ get endpoint() { const protocol = this.model.data.protocol || "http"; const hostname = this.model.data.hostname || "localhost"; const port = this.model.data.port ? `:${this.model.data.port}` : ""; let path = this.model.data.path || ""; if (path && !path.startsWith("/")) path = `/${path}`; return `${protocol}://${hostname}${port}${path}`; } get_adapters_as_options() { return Object.keys(adapters_map).map((adapter_name) => ({ value: adapter_name, name: adapter_name })); } /** * Provide custom settings for configuring * the user-defined fields plus the new 'api_adapter'. * @override * @returns {Object} settings configuration */ get settings_config() { return { /** * Select which specialized request/response adapter * you'd like to use for your custom endpoint. */ "[CHAT_ADAPTER].api_adapter": { name: "API Adapter", type: "dropdown", description: "Pick a built-in or external adapter to parse request/response data.", // Provide a short selection set, or dynamically gather from keys of adapters_map // options_callback: 'adapter.get_adapters_as_options', options_callback: () => { this.get_adapters_as_options(); }, // UNTESTED default: "openai" }, "[CHAT_ADAPTER].id": { name: "Model Name", type: "text", description: "Enter the model name for your endpoint if needed." }, "[CHAT_ADAPTER].protocol": { name: "Protocol", type: "text", description: "e.g. http or https" }, "[CHAT_ADAPTER].hostname": { name: "Hostname", type: "text", description: "e.g. localhost or some.remote.host" }, "[CHAT_ADAPTER].port": { name: "Port", type: "number", description: "Port number or leave blank" }, "[CHAT_ADAPTER].path": { name: "Path", type: "text", description: "Path portion of the URL (leading slash optional)" }, "[CHAT_ADAPTER].streaming": { name: "Streaming", type: "toggle", description: "Enable streaming if your API supports it." }, "[CHAT_ADAPTER].max_input_tokens": { name: "Max Input Tokens", type: "number", description: "Max number of tokens your model can handle in the prompt." }, "[CHAT_ADAPTER].api_key": { name: "API Key", type: "password", description: "If your service requires an API key, add it here." } }; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/groq.js var SmartChatModelGroqAdapter = class extends SmartChatModelApiAdapter { static key = "groq"; static defaults = { description: "Groq", type: "API", endpoint: "https://api.groq.com/openai/v1/chat/completions", streaming: true, adapter: "Groq", models_endpoint: "https://api.groq.com/openai/v1/models", default_model: "llama3-8b-8192", signup_url: "https://groq.com" }; /** * Request adapter class * @returns {typeof SmartChatModelGroqRequestAdapter} */ get req_adapter() { return SmartChatModelGroqRequestAdapter; } /** * Response adapter class * @returns {typeof SmartChatModelGroqResponseAdapter} */ get res_adapter() { return SmartChatModelGroqResponseAdapter; } get models_endpoint_method() { return "GET"; } /** * Parse model data from Groq API format to a dictionary keyed by model ID. * The API returns a list of model objects like: * { * "object": "list", * "data": [ { "id": "...", "object": "model", ... }, ... ] * } * * We'll convert each model to: * { * model_name: model.id, * id: model.id, * max_input_tokens: model.context_window, * description: `Owned by: ${model.owned_by}, context: ${model.context_window}`, * multimodal: Check if model name or description suggests multimodality * } */ parse_model_data(model_data) { if (model_data.object !== "list" || !Array.isArray(model_data.data)) { return { "_": { id: "No models found." } }; } const parsed = {}; for (const m of model_data.data) { parsed[m.id] = { model_name: m.id, id: m.id, max_input_tokens: m.context_window || 8192, description: `Owned by: ${m.owned_by}, context: ${m.context_window}`, // A basic heuristic for multimodal: if 'vision' or 'tool' is in model id // Adjust as needed based on known capabilities multimodal: m.id.includes("vision") }; } return parsed; } }; var SmartChatModelGroqRequestAdapter = class extends SmartChatModelRequestAdapter { _get_openai_content(message) { if (["assistant", "tool"].includes(message.role)) { if (Array.isArray(message.content)) { return message.content.map((part) => { if (typeof part === "string") return part; if (part?.text) return part.text; return ""; }).join("\n"); } } return message.content; } }; var SmartChatModelGroqResponseAdapter = class extends SmartChatModelResponseAdapter { }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/xai.js var SmartChatModelXaiAdapter = class extends SmartChatModelApiAdapter { static key = "xai"; static defaults = { description: "xAI Grok", type: "API", adapter: "xAI_Grok", endpoint: "https://api.x.ai/v1/chat/completions", streaming: true, models_endpoint: "https://api.x.ai/v1/models", default_model: "grok-3-mini-beta", signup_url: "https://ide.x.ai" }; get req_adapter() { return XaiCompletionRequestAdapter; } get res_adapter() { return XaiCompletionResponseAdapter; } /* ------------------------------------------------------------------ * * Model-list helpers * ------------------------------------------------------------------ */ get models_endpoint_method() { return "GET"; } parse_model_data(model_data = {}) { const list = model_data.data || model_data.models || []; return list.reduce((acc, m) => { const id = m.id || m.name; acc[id] = { id, model_name: id, description: m.description || `context: ${m.context_length || "n/a"}`, max_input_tokens: m.context_length || 128e3, multimodal: !!m.modality && m.modality.includes("vision"), raw: m }; return acc; }, {}); } /* ------------------------------------------------------------------ * * Files helpers * ------------------------------------------------------------------ */ get files_endpoint() { try { const origin = new URL(this.endpoint).origin; return `${origin}/v1/files`; } catch { return "https://api.x.ai/v1/files"; } } get_file_delete_endpoint(file_id) { const safe_id = encodeURIComponent(String(file_id || "").trim()); return `${this.files_endpoint}/${safe_id}`; } /* ------------------------------------------------------------------ * * Complete / Stream overrides * ------------------------------------------------------------------ */ async complete(req) { let uploaded_file_ids = []; try { const prepared = await this.prepare_req_with_uploaded_files(req); uploaded_file_ids = prepared.uploaded_file_ids; const resp = await super.complete(prepared.req); return resp; } catch (error) { return { error: normalize_error(error?.data || error) }; } finally { await this.delete_uploaded_files(uploaded_file_ids); } } async stream(req, handlers = {}) { let uploaded_file_ids = []; try { const prepared = await this.prepare_req_with_uploaded_files(req); uploaded_file_ids = prepared.uploaded_file_ids; this.active_uploaded_file_ids = uploaded_file_ids.slice(); return await super.stream(prepared.req, handlers); } catch (error) { const normalized = normalize_error(error?.data || error); if (typeof handlers?.error === "function") handlers.error(normalized); throw normalized; } finally { const to_delete = Array.isArray(this.active_uploaded_file_ids) ? this.active_uploaded_file_ids.slice() : uploaded_file_ids; this.active_uploaded_file_ids = []; await this.delete_uploaded_files(to_delete); } } stop_stream() { super.stop_stream(); const to_delete = Array.isArray(this.active_uploaded_file_ids) ? this.active_uploaded_file_ids.slice() : []; this.active_uploaded_file_ids = []; this.delete_uploaded_files(to_delete).catch((e) => { console.warn("xAI file cleanup failed during stop_stream():", e); }); } /** * Upload inline files and replace them with file references. * Returns: * - req: transformed request with inline file bytes removed * - uploaded_file_ids: list of newly uploaded file ids to cleanup after request */ async prepare_req_with_uploaded_files(req = {}) { if (!req || !Array.isArray(req.messages) || req.messages.length === 0) { return { req, uploaded_file_ids: [] }; } const needs_upload = req.messages.some((m) => this.message_has_inline_file(m)); if (!needs_upload) { return { req, uploaded_file_ids: [] }; } const uploaded_file_ids = []; const messages = await Promise.all( req.messages.map(async (message) => { return await this.transform_message_for_xai(message, uploaded_file_ids); }) ); return { req: { ...req, messages }, uploaded_file_ids }; } message_has_inline_file(message = {}) { if (!Array.isArray(message.content)) return false; return message.content.some((part) => { if (!part || typeof part !== "object") return false; if (part.type === "file") { const file = part.file || {}; return typeof file.file_data === "string" || typeof file.data === "string"; } if (part.type === "input_file") { return typeof part.file_data === "string"; } return false; }); } async transform_message_for_xai(message = {}, uploaded_file_ids = []) { if (!Array.isArray(message.content)) return message; const content = await Promise.all( message.content.map(async (part) => { return await this.transform_content_part_for_xai(part, uploaded_file_ids); }) ); return { ...message, content }; } async transform_content_part_for_xai(part, uploaded_file_ids = []) { if (!part || typeof part !== "object") return part; if (part.type === "image_url") { return this.normalize_image_part(part); } if (part.type === "file") { return await this.normalize_or_upload_file_part(part, uploaded_file_ids); } if (part.type === "input_file") { return await this.normalize_or_upload_input_file_part(part, uploaded_file_ids); } return part; } normalize_image_part(part = {}) { const image_url = part.image_url; if (typeof image_url === "string") { return { ...part, image_url: { url: image_url } }; } return part; } async normalize_or_upload_file_part(part = {}, uploaded_file_ids = []) { const file = part.file || {}; const existing_file_id = file.file_id || file.id || part.file_id || part.id; if (typeof existing_file_id === "string" && existing_file_id.length > 0) { return { type: "file", file: { file_id: existing_file_id } }; } const file_data = file.file_data || file.data; if (typeof file_data !== "string" || file_data.length === 0) return part; const filename = file.filename || file.name || part.filename || "document"; const { mime_type, base64_data } = this.extract_mime_and_base64( file_data, file.mime_type || file.content_type ); const content_type = mime_type || this.infer_mime_type_from_filename(filename) || "application/octet-stream"; const uploaded = await this.upload_file_to_xai({ name: filename, content_type, base64_data }); const file_id = uploaded?.file_id || uploaded?.id; if (!file_id) { throw new Error(`xAI file upload succeeded but no file id was returned: ${JSON.stringify(uploaded || {})}`); } uploaded_file_ids.push(file_id); return { type: "file", file: { file_id } }; } async normalize_or_upload_input_file_part(part = {}, uploaded_file_ids = []) { const existing_file_id = part.file_id || part.id; if (typeof existing_file_id === "string" && existing_file_id.length > 0) { return { type: "file", file: { file_id: existing_file_id } }; } const file_data = part.file_data; if (typeof file_data !== "string" || file_data.length === 0) return part; const filename = part.filename || "document"; const { mime_type, base64_data } = this.extract_mime_and_base64(file_data, part.mime_type); const content_type = mime_type || this.infer_mime_type_from_filename(filename) || "application/octet-stream"; const uploaded = await this.upload_file_to_xai({ name: filename, content_type, base64_data }); const file_id = uploaded?.file_id || uploaded?.id; if (!file_id) { throw new Error(`xAI file upload succeeded but no file id was returned: ${JSON.stringify(uploaded || {})}`); } uploaded_file_ids.push(file_id); return { type: "file", file: { file_id } }; } extract_mime_and_base64(file_data, fallback_mime_type = null) { const trimmed = String(file_data || "").trim(); const match = trimmed.match(/^data:([^;]+);base64,(.*)$/); if (match) { return { mime_type: match[1] || fallback_mime_type, base64_data: match[2] || "" }; } return { mime_type: fallback_mime_type, base64_data: trimmed }; } infer_mime_type_from_filename(filename = "") { const lower = String(filename).toLowerCase(); if (lower.endsWith(".pdf")) return "application/pdf"; if (lower.endsWith(".txt")) return "text/plain"; if (lower.endsWith(".md")) return "text/markdown"; if (lower.endsWith(".json")) return "application/json"; if (lower.endsWith(".csv")) return "text/csv"; if (lower.endsWith(".png")) return "image/png"; if (lower.endsWith(".jpg") || lower.endsWith(".jpeg")) return "image/jpeg"; return null; } base64_to_uint8_array(base64_data = "") { const clean = String(base64_data || "").trim().replace(/^data:[^;]+;base64,/, ""); if (typeof Buffer !== "undefined") { return new Uint8Array(Buffer.from(clean, "base64")); } if (typeof atob === "function") { const binary = atob(clean); const bytes = new Uint8Array(binary.length); for (let i = 0; i < binary.length; i++) { bytes[i] = binary.charCodeAt(i); } return bytes; } throw new Error("xAI file upload failed: no base64 decoder available (Buffer/atob)"); } base64_to_blob(base64_data = "", content_type = "application/octet-stream") { if (typeof Blob !== "function") { throw new Error("xAI file upload failed: Blob is not available in this runtime"); } const bytes = this.base64_to_uint8_array(base64_data); return new Blob([bytes], { type: content_type }); } safe_parse_json(text = "") { try { return JSON.parse(String(text || "")); } catch { return null; } } /** * Upload a file to xAI's Files API. * * MUST be multipart/form-data with: * - form field: "file" * - form field: "purpose" (commonly "assistants") * * IMPORTANT: Do NOT set Content-Type manually (boundary must be auto-generated). */ async upload_file_to_xai({ name, content_type, base64_data }) { if (!this.api_key) { throw new Error("xAI file upload failed: missing API key"); } if (typeof FormData !== "function") { throw new Error("xAI file upload failed: FormData is not available in this runtime"); } const file_blob = this.base64_to_blob(base64_data, content_type); const form_data = new FormData(); form_data.append("file", file_blob, name); form_data.append("purpose", "assistants"); const headers = { // DO NOT set Content-Type here "Authorization": `Bearer ${this.api_key}` }; if (typeof fetch === "function") { const resp = await fetch(this.files_endpoint, { method: "POST", headers, body: form_data }); const body_text2 = await resp.text(); const json2 = this.safe_parse_json(body_text2); if (!resp.ok) { const err = new Error(`xAI file upload failed: HTTP ${resp.status} ${resp.statusText || ""}`.trim()); err.details = { status: resp.status, status_text: resp.statusText, raw_text: body_text2?.slice?.(0, 2e3) || body_text2 }; if (json2) err.details.json = json2; throw err; } if (!json2) { throw new Error(`xAI file upload failed: expected JSON response, got: ${String(body_text2 || "").slice(0, 200)}`); } if (json2.error) { throw normalize_error(json2.error); } return json2; } const http_resp = await this.http_adapter.request({ url: this.files_endpoint, method: "POST", headers, body: form_data }); const status = typeof http_resp?.status === "number" ? http_resp.status : typeof http_resp?.status === "function" ? await http_resp.status() : null; const body_text = typeof http_resp?.text === "function" ? await http_resp.text() : typeof http_resp?.response?.text === "function" ? await http_resp.response.text() : ""; const json = this.safe_parse_json(body_text); if (!status || status < 200 || status >= 300) { const err = new Error(`xAI file upload failed: HTTP ${status || "unknown"}`); err.details = { status: status || null, raw_text: body_text?.slice?.(0, 2e3) || body_text }; if (json) err.details.json = json; throw err; } if (!json) { throw new Error(`xAI file upload failed: expected JSON response, got: ${String(body_text || "").slice(0, 200)}`); } if (json.error) { throw normalize_error(json.error); } return json; } /** * Delete a file from xAI Files API (best-effort cleanup). * Endpoint: DELETE /v1/files/{file_id} :contentReference[oaicite:5]{index=5} */ async delete_file_from_xai(file_id) { const id = String(file_id || "").trim(); if (!id) return null; if (!this.api_key) { throw new Error("xAI file delete failed: missing API key"); } const url = this.get_file_delete_endpoint(id); const headers = { "Authorization": `Bearer ${this.api_key}` }; if (typeof fetch === "function") { const resp = await fetch(url, { method: "DELETE", headers }); const body_text2 = await resp.text(); const json2 = this.safe_parse_json(body_text2); if (!resp.ok) { const err = new Error(`xAI file delete failed: HTTP ${resp.status} ${resp.statusText || ""}`.trim()); err.details = { status: resp.status, status_text: resp.statusText, raw_text: body_text2?.slice?.(0, 2e3) || body_text2 }; if (json2) err.details.json = json2; throw err; } return json2 || { deleted: true, id }; } const http_resp = await this.http_adapter.request({ url, method: "DELETE", headers }); const status = typeof http_resp?.status === "number" ? http_resp.status : typeof http_resp?.status === "function" ? await http_resp.status() : null; const body_text = typeof http_resp?.text === "function" ? await http_resp.text() : typeof http_resp?.response?.text === "function" ? await http_resp.response.text() : ""; const json = this.safe_parse_json(body_text); if (!status || status < 200 || status >= 300) { const err = new Error(`xAI file delete failed: HTTP ${status || "unknown"}`); err.details = { status: status || null, raw_text: body_text?.slice?.(0, 2e3) || body_text }; if (json) err.details.json = json; throw err; } return json || { deleted: true, id }; } /** * Delete all uploaded files (deduped), best-effort. */ async delete_uploaded_files(file_ids = []) { const unique_ids = Array.from(new Set((file_ids || []).filter(Boolean).map((v) => String(v).trim()))).filter(Boolean); if (unique_ids.length === 0) return; const results = await Promise.allSettled( unique_ids.map(async (id) => await this.delete_file_from_xai(id)) ); const rejected = results.filter((r) => r.status === "rejected"); if (rejected.length > 0) { console.warn("xAI cleanup: some file deletions failed:", rejected.map((r) => r.reason)); } } }; var XaiCompletionRequestAdapter = class extends SmartChatModelRequestAdapter { /** * Convert request to xAI format * @param {boolean} streaming * @returns {Object} Request params */ to_platform(streaming = false) { return this.to_xai(streaming); } /** * xAI is largely OpenAI-compatible for /v1/chat/completions, * but file parts must be shaped as "input_file". * @param {boolean} streaming * @returns {Object} Request params */ to_xai(streaming = false) { const req = super.to_openai(streaming); let body = {}; try { body = JSON.parse(req.body || "{}"); } catch { body = {}; } body.messages = (body.messages || []).map((message) => { return this._transform_message_for_xai(message); }); req.body = JSON.stringify(body); return req; } _transform_message_for_xai(message = {}) { if (!message || typeof message !== "object") return message; if (!Array.isArray(message.content)) return message; const content = message.content.map((part) => this._transform_content_part_for_xai(part)); return { ...message, content }; } _transform_content_part_for_xai(part) { if (!part || typeof part !== "object") return part; if (part.type === "image_url") { const image_url = part.image_url; if (typeof image_url === "string") { return { ...part, image_url: { url: image_url } }; } return part; } if (part.type === "file") { const file = part.file || {}; const file_id = file.file_id || file.id || part.file_id || part.id; if (typeof file_id === "string" && file_id.length > 0) { return { type: "input_file", file_id }; } return part; } if (part.type === "input_file") { if (typeof part.file_id === "string" && part.file_id.length > 0) return part; const file_id = part.file?.file_id || part.file?.id || part.id; if (typeof file_id === "string" && file_id.length > 0) { return { type: "input_file", file_id }; } return part; } return part; } }; var XaiCompletionResponseAdapter = class extends SmartChatModelResponseAdapter { static get platform_res() { return { id: "", object: "chat.completion", created: 0, model: "", choices: [], usage: {} }; } /** * Convert response to OpenAI-like format. * @returns {Object} */ to_openai() { if (this.error) return { error: normalize_error(this.error, this.status) }; const object = this._normalize_object_type(this._res.object); return { id: this._res.id || `xai_${Date.now()}`, object: object || "chat.completion", created: this._res.created || Math.floor(Date.now() / 1e3), model: this._res.model || this.adapter?.model_key || "", choices: this._transform_choices_to_openai(), usage: this._transform_usage_to_openai(), raw: this._res }; } _normalize_object_type(object_value) { if (typeof object_value !== "string") return object_value; if (object_value.endsWith(".chunk")) return object_value.replace(".chunk", ""); return object_value; } _transform_usage_to_openai() { if (!this._res.usage) { return { prompt_tokens: 0, completion_tokens: 0, total_tokens: 0 }; } return this._res.usage; } /** * Streaming chunk handler. * xAI streams data lines in OpenAI-like SSE form: * "data: {...json...}" * and ends with: * "data: [DONE]" */ handle_chunk(chunk) { if (chunk === "data: [DONE]") return; const json_text = String(chunk || "").startsWith("data: ") ? String(chunk).slice(6) : String(chunk); let parsed; try { parsed = JSON.parse(json_text); } catch { return; } if (!parsed || typeof parsed !== "object") return; if (!this._res.id && parsed.id) this._res.id = parsed.id; if (parsed.model) this._res.model = parsed.model; if (parsed.created) this._res.created = parsed.created; if (parsed.object) this._res.object = parsed.object; if (parsed.usage) this._res.usage = parsed.usage; if (!Array.isArray(this._res.choices)) this._res.choices = []; if (!this._res.choices[0]) { this._res.choices[0] = { index: 0, message: { role: "assistant", content: "" } }; } const delta = parsed.choices?.[0]?.delta; if (!delta || typeof delta !== "object") return; if (delta.role) { this._res.choices[0].message.role = delta.role; } let raw; if (typeof delta.content === "string") { raw = delta.content; this._res.choices[0].message.content += delta.content; } if (Array.isArray(delta.tool_calls)) { if (!Array.isArray(this._res.choices[0].message.tool_calls)) { this._res.choices[0].message.tool_calls = [{ id: "", type: "function", function: { name: "", arguments: "" } }]; } const incoming = delta.tool_calls[0] || {}; const target = this._res.choices[0].message.tool_calls[0]; if (incoming.id) target.id += incoming.id; if (incoming.function?.name) target.function.name += incoming.function.name; if (incoming.function?.arguments) target.function.arguments += incoming.function.arguments; } const finish_reason = parsed.choices?.[0]?.finish_reason; if (finish_reason) { this._res.choices[0].finish_reason = finish_reason; } return raw; } }; // node_modules/obsidian-smart-env/node_modules/smart-chat-model/adapters/deepseek.js var SmartChatModelDeepseekAdapter = class extends SmartChatModelApiAdapter { static key = "deepseek"; static defaults = { description: "DeepSeek", type: "API", endpoint: "https://api.deepseek.com/chat/completions", streaming: true, adapter: "DeepSeek", models_endpoint: "https://api.deepseek.com/models", default_model: "deepseek-base", signup_url: "https://deepseek.com/signup" }; /** * Get the request adapter class * @returns {typeof SmartChatModelDeepseekRequestAdapter} Request adapter class */ get req_adapter() { return SmartChatModelDeepseekRequestAdapter; } /** * Get the response adapter class * @returns {typeof SmartChatModelDeepseekResponseAdapter} Response adapter class */ get res_adapter() { return SmartChatModelDeepseekResponseAdapter; } get models_endpoint_method() { return "GET"; } /** * Parse the raw model data from DeepSeek's /v1/models endpoint * into a structured map of model objects keyed by model ID. * @param {Object} model_data - Raw JSON from DeepSeek * @returns {Object} Map of model objects */ parse_model_data(model_data) { if (!model_data?.data || !Array.isArray(model_data.data)) { return { "_": { id: "No models found." } }; } const parsed = {}; for (const m of model_data.data) { parsed[m.id] = { model_name: m.id, id: m.id, max_input_tokens: m.context_size || 8192, description: m.description || m.name || m.id, raw: m }; } return parsed; } /** * Estimate tokens in user input. * @param {string|Object} input - Input text or structured message * @returns {Promise} Token count estimate */ async count_tokens(input) { const text = typeof input === "string" ? input : JSON.stringify(input); return Math.ceil(text.length / 4); } /** * Check if an incoming streaming chunk signals end of stream. * @param {CustomEvent} event - SSE event with data * @returns {boolean} True if end of stream */ is_end_of_stream(event) { if (!event?.data) return false; return event.data.includes('"done":true') || event.data.includes("[DONE]"); } }; var SmartChatModelDeepseekRequestAdapter = class extends SmartChatModelRequestAdapter { /** * Convert incoming request to DeepSeek's expected format * Often just reuse the base "to_openai()" if that matches DeepSeek's design * @param {boolean} streaming - True if streaming * @returns {Object} Request parameters */ to_platform(streaming = false) { return this.to_openai(streaming); } }; var SmartChatModelDeepseekResponseAdapter = class extends SmartChatModelResponseAdapter { }; // node_modules/obsidian-smart-env/default.config.js var import_obsidian41 = require("obsidian"); // node_modules/obsidian-smart-env/node_modules/smart-sources/utils/get_line_range.js function get_line_range2(content, start_line, end_line) { const lines = content.split("\n"); return lines.slice(start_line - 1, end_line).join("\n"); } // node_modules/obsidian-smart-env/node_modules/smart-blocks/content_parsers/parse_blocks.js function parse_blocks(source, content) { let { blocks: blocks_obj, task_lines, tasks, codeblock_ranges } = parse_markdown_blocks(content); const last_read_at = source.data.last_read?.at || Date.now(); for (const [sub_key, line_range] of Object.entries(blocks_obj)) { const block_key = source.key + sub_key; const existing_block = source.block_collection.get(block_key); const block_content = get_line_range2(content, line_range[0], line_range[1]); const next_hash = murmur_hash_32_alphanumeric(block_content); if (existing_block && existing_block.lines?.[0] === line_range[0] && existing_block.lines?.[1] === line_range[1] && existing_block.size === block_content.length && existing_block.data?.last_read?.hash === next_hash) { continue; } const block_outlinks = get_markdown_links(block_content); const bases_links = get_bases_cache_links({ source, links: block_outlinks }); const block_data = { key: block_key, lines: line_range, size: block_content.length, outlinks: [ ...block_outlinks, ...bases_links ], last_read: { at: last_read_at, hash: next_hash } }; const block_changed = has_block_data_changes(existing_block, block_data); if (!block_changed && existing_block?.vec) { continue; } if (!existing_block || existing_block?.data.last_read?.hash !== block_data.last_read.hash) { const new_item = new source.block_collection.item_type(source.env, block_data); if (block_changed) new_item.queue_save(); source.block_collection.set(new_item); } else if (block_changed) { existing_block.data = { ...existing_block.data, ...block_data // overwrites lines, last_read }; existing_block.queue_save(); } } clean_and_update_source_blocks(source, blocks_obj, task_lines, tasks, codeblock_ranges); for (const block of source.blocks) { if (!block.vec || block.embed_hash !== block.read_hash) { block.queue_embed(); } } } function has_block_data_changes(existing_block, block_data) { if (!existing_block) return true; const existing_lines = existing_block.lines || []; if (existing_lines[0] !== block_data.lines[0] || existing_lines[1] !== block_data.lines[1]) { return true; } if (existing_block.size !== block_data.size) { return true; } if (existing_block?.data?.last_read?.hash !== block_data.last_read.hash) { return true; } const existing_outlinks = existing_block?.data?.outlinks || []; if (JSON.stringify(existing_outlinks) !== JSON.stringify(block_data.outlinks)) { return true; } return false; } function clean_and_update_source_blocks(source, blocks_obj, task_lines = [], tasks = {}, codeblock_ranges = {}) { const current_block_keys = new Set(Object.keys(blocks_obj).map((sk) => source.key + sk)); const blocks = source.blocks; for (let i = 0; i < blocks.length; i++) { if (!current_block_keys.has(blocks[i].key)) { blocks[i].deleted = true; blocks[i].queue_save(); } } source.data.blocks = blocks_obj; source.data.task_lines = task_lines; source.data.tasks = tasks; source.data.codeblock_ranges = codeblock_ranges; source.queue_save(); } // node_modules/obsidian-smart-env/node_modules/smart-collections/utils/ajson_merge.js function ajson_merge(existing, new_obj) { if (new_obj === null) return null; if (new_obj === void 0) return existing; if (typeof new_obj !== "object") return new_obj; if (typeof existing !== "object" || existing === null) existing = {}; const keys = Object.keys(new_obj); const length = keys.length; for (let i = 0; i < length; i++) { const key = keys[i]; const new_val = new_obj[key]; const existing_val = existing[key]; if (Array.isArray(new_val)) { existing[key] = new_val.slice(); } else if (is_object(new_val)) { existing[key] = ajson_merge(is_object(existing_val) ? existing_val : {}, new_val); } else if (new_val !== void 0) { existing[key] = new_val; } } return existing; } function is_object(obj) { return obj !== null && typeof obj === "object" && !Array.isArray(obj); } // node_modules/obsidian-smart-env/node_modules/smart-collections/adapters/ajson_single_file.js var class_to_collection_key2 = { "SmartSource": "smart_sources", "SmartNote": "smart_sources", // DEPRECATED "SmartBlock": "smart_blocks", "SmartDirectory": "smart_directories" }; function _parse_ajson_key(ajson_key) { let changed = false; let [collection_key, ...item_key] = ajson_key.split(":"); if (class_to_collection_key2[collection_key]) { collection_key = class_to_collection_key2[collection_key]; changed = true; } return { collection_key, item_key: item_key.join(":"), changed }; } var AjsonSingleFileCollectionDataAdapter = class extends AjsonMultiFileCollectionDataAdapter { /** * Returns the single shared `.ajson` file path for this collection. * @param {string} [key] - (unused) Item key, ignored in single-file mode. * @returns {string} The single .ajson file path for the entire collection. */ get_item_data_path(key) { const file_name = (this.collection?.collection_key || "collection") + ".ajson"; const sep = this.fs?.sep || "/"; const dir = this.collection.data_dir || "data"; return [dir, file_name].join(sep); } /** * Override process_load_queue to parse the entire single-file .ajson once, * distributing final states to items. * * @async * @returns {Promise} */ async process_load_queue() { this.collection.emit_event("collection:load_started"); if (!await this.fs.exists(this.collection.data_dir)) { await this.fs.mkdir(this.collection.data_dir); } const path = this.get_item_data_path(); if (!await this.fs.exists(path)) { for (const item of Object.values(this.collection.items)) { if (item._queue_load) { item.queue_import?.(); } } this.collection.emit_event("collection:load_halted"); return; } const raw_data = await this.fs.read(path, "utf-8", { no_cache: true }); if (!raw_data) { for (const item of Object.values(this.collection.items)) { if (item._queue_load) { item.queue_import?.(); } } this.collection.emit_event("collection:load_halted"); return; } const { rewrite, file_data } = this.parse_single_file_ajson(raw_data); if (rewrite) { if (file_data.length) { await this.fs.write(path, file_data); } else { await this.fs.remove(path); } } for (const item of Object.values(this.collection.items)) { item._queue_load = false; item.loaded_at = Date.now(); } this.collection.emit_event("collection:load_completed"); } /** * Helper to parse single-file .ajson content, distributing states to items. * * @param {string} raw * @returns {{ rewrite: boolean, file_data: string }} */ parse_single_file_ajson(raw) { let rewrite = false; const lines = raw.trim().split("\n").filter(Boolean); let data_map = {}; let line_count = 0; for (let i = 0; i < lines.length; i++) { const line = lines[i].trim(); if (!line.endsWith(",")) { rewrite = true; } const trimmed = line.replace(/,$/, ""); const combined = "{" + trimmed + "}"; try { const obj = JSON.parse(combined); const [fullKey, value] = Object.entries(obj)[0]; let { collection_key, item_key, changed } = _parse_ajson_key(fullKey); const newKey = `${collection_key}:${item_key}`; if (!value) { delete data_map[newKey]; if (changed || newKey !== fullKey) { delete data_map[fullKey]; } rewrite = true; } else { data_map[newKey] = value; if (changed || newKey !== fullKey) { delete data_map[fullKey]; rewrite = true; } } } catch (err) { console.warn("parse error for line: ", line, err); rewrite = true; } line_count++; } for (const [ajson_key, val] of Object.entries(data_map)) { const [collection_key, ...rest] = ajson_key.split(":"); const item_key = rest.join(":"); const collection = this.collection.env[collection_key]; if (!collection) continue; let item = collection.get(item_key); if (!item) { const ItemClass = collection.item_type; item = new ItemClass(this.env, val); collection.set(item); } else { item.data = ajson_merge(item.data, val); } item.loaded_at = Date.now(); item._queue_load = false; if (!val.key) val.key = item_key; } if (line_count > Object.keys(data_map).length) { rewrite = true; } let minimal_lines = []; for (const [ajson_key, val] of Object.entries(data_map)) { minimal_lines.push(`${JSON.stringify(ajson_key)}: ${JSON.stringify(val)},`); } return { rewrite, file_data: minimal_lines.join("\n") }; } /** * Override process_save_queue for single-file approach. * We'll simply call save_item for each queued item, which appends a line to the same `.ajson`. * * @async * @returns {Promise} */ async process_save_queue() { this.collection.emit_event("collection:save_started"); const save_queue = Object.values(this.collection.items).filter((item) => item._queue_save); const time_start = Date.now(); const batch_size = 50; for (let i = 0; i < save_queue.length; i += batch_size) { const batch = save_queue.slice(i, i + batch_size); await Promise.all(batch.map((item) => { const adapter = this.create_item_adapter(item); return adapter.save().catch((err) => { console.warn(`Error saving item ${item.key}`, err); item.queue_save(); }); })); } const deleted_items = Object.values(this.collection.items).filter((item) => item.deleted); if (deleted_items.length) { deleted_items.forEach((item) => { delete this.collection.items[item.key]; }); } console.log(`Saved (single-file) ${this.collection.collection_key} in ${Date.now() - time_start}ms`); this.collection.emit_event("collection:save_completed"); } }; var AjsonSingleFileItemDataAdapter = class extends AjsonMultiFileItemDataAdapter { /** * Overridden to always return the single file path from the parent collection adapter. * @returns {string} */ get data_path() { return this.collection_adapter.get_item_data_path(this.item.key); } /** * Load logic: * In single-file mode, we typically rely on the collection's `process_load_queue()` * to parse the entire file. This direct `load()` will do a naive re-parse as well * if used individually. */ async load() { const path = this.data_path; if (!await this.fs.exists(path)) { this.item.queue_import?.(); return; } try { const raw_data = await this.fs.read(path, "utf-8", { no_cache: true }); if (!raw_data) { this.item.queue_import?.(); return; } const { rewrite } = this.collection_adapter.parse_single_file_ajson(raw_data); } catch (err) { console.warn(`Error loading single-file item ${this.item.key}`, err); this.item.queue_import?.(); } } }; var ajson_single_file_default = { collection: AjsonSingleFileCollectionDataAdapter, item: AjsonSingleFileItemDataAdapter }; // node_modules/obsidian-smart-env/node_modules/smart-components/smart_component.js var SmartComponent = class extends CollectionItem { static key = "smart_component"; static collection_key = "smart_components"; collection_key = "smart_components"; get_key() { if (this.data?.key) return this.data.key; const scope_key = this.scope_key; const component_key = this.component_key; const 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 { // special handling because current name_to_collection_key removes "Items" suffix get collection_key() { return "context_items"; } 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; } get exists() { return this.context_type_adapter.exists; } get icon_type() { return this.context_type_adapter.icon_type || null; } // v3 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; } 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}` }; } async open(event = null) { return await this.context_type_adapter.open(event); } get is_media() { return this.context_type_adapter.is_media || false; } get item_ref() { return this.context_type_adapter.ref || null; } get size() { return this.data.size || this.context_type_adapter.size || 0; } 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 { constructor(item) { this.item = item; } static detect(key, data = {}) { return false; } get env() { return this.item.env; } get exists() { return true; } get icon_type() { return null; } // v3 API /** * for calculating context size */ get size() { return 0; } async get_text() { } async open() { } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/block.js var BlockContextItemAdapter = class extends ContextItemAdapter { static order = 6; static detect(key) { return key.includes("#"); } get ref() { return this.env.smart_blocks.get(this.item.key); } get inlinks() { return this.ref.inlinks || []; } get outlinks() { return this.ref.outlinks || []; } get exists() { return !!(this.ref && !this.ref.is_gone); } get mtime() { return this.ref?.mtime || null; } get size() { return this.ref?.size || 0; } async get_text() { const block = this.ref; if (!block) return { error: "Block not found" }; return await block.read(); } 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 static detect(key) { return true; } get ref() { return this.env.smart_sources.get(this.item.key); } get inlinks() { return this.ref.inlinks || []; } get outlinks() { return this.ref.outlinks || []; } get exists() { return !!(this.ref && !this.ref.is_gone); } get size() { return this.ref?.size || 0; } get mtime() { return this.ref?.mtime || null; } async get_text() { return await this.ref?.read() || "MISSING SOURCE"; } 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 { static detect(key) { if (image_extension_regex.test(key)) return "image"; return false; } get exists() { return this.item.env.smart_sources.fs.exists_sync(this.item.key); } get icon_type() { return "image-file"; } get is_media() { return true; } async get_base64() { const ext = this.item.key.split(".").pop().toLowerCase(); try { const base64_data = await this.item.env.fs.read(this.item.key, "base64"); const base64_url = `data:image/${ext};base64,${base64_data}`; return { type: "image_url", key: this.item.key, name: this.item.key.split(/[\\/]/).pop(), url: base64_url }; } catch (err) { console.warn(`Failed to convert image ${this.item.key} to base64`, err); return { error: `Failed to convert image to base64: ${err.message}` }; } } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/adapters/context-items/pdf.js var PdfContextItemAdapter = class extends ContextItemAdapter { static detect(key) { if (String(key || "").toLowerCase().endsWith(".pdf")) return "pdf"; return false; } async add_to_snapshot(snapshot) { if (!snapshot.pdfs) snapshot.pdfs = []; snapshot.pdfs.push(this.item.key); } get icon_type() { return "file-text"; } get is_media() { return true; } async get_base64() { try { const base64_data = await this.item.env.fs.read(this.item.key, "base64"); const base64_url = `data:application/pdf;base64,${base64_data}`; return { type: "pdf_url", key: this.item.key, name: this.item.key.split(/[\\/]/).pop(), url: base64_url }; } catch (err) { console.warn(`Failed to convert PDF ${this.item.key} to base64`, err); return { error: `Failed to convert PDF to base64: ${err.message}` }; } } get exists() { return this.item.env.smart_sources.fs.exists_sync(this.item.key); } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/context_items.js var ContextItems = class extends Collection { constructor(smart_context, opts = {}) { super(smart_context.env || smart_context, opts); this.smart_context = smart_context; } async load() { console.log("ContextItems: load called"); } static version = 1; 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; } new_item(data) { const item = new this.item_type(this.env, data); this.set(item); return item; } process_load_queue() { } get settings_config() { return { ...this.env.config.actions.context_item_merge_template?.settings_config || {} }; } static get default_settings() { return { template_preset: "xml_structured", template_before: '', template_after: "" }; } /** * @param {object} context_items_data - data.context_items{} * @param {object} params * @param {string} [params.codeblock_source_key] - Optional key of the current source for codeblock context (glues name change sync) * @returns {ContextItem[]} */ 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 { item_data.size = loaded.size; item_data.mtime = loaded.mtime; loaded_items.push(loaded); } } } return loaded_items; } /** * @param {string} key * @param {object} item_data * @param {object} params * @param {string} [params.codeblock_source_key] - Optional key of the current source for codeblock context (glues name change sync) * @return {ContextItem|ContextItem[]|null} */ load_item_from_data(key, item_data, params = {}) { if (item_data.named_context) { return this.load_named_context_items(key, item_data, params); } else { return this.new_item({ key, ...item_data }); } } 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 { console.warn(`ContextItems.load_from_data: named context "${item_data.key}" not found`); this.emit_error_event("context_items:load_from_data", { message: "Named context not found", named_context: item_data.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 = 4e-3; constructor(env, opts = {}) { super(env, opts); this.session_events = []; this.notification_status = null; } /** * 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 (this._save_timer) { clearTimeout(this._save_timer); this._save_timer = null; } if (typeof this._unsub_wildcard === "function") { this._unsub_wildcard(); this._unsub_wildcard = null; } return super.unload(); } }; var event_logs_default = { class: EventLogs, collection_key: "event_logs", data_adapter: AjsonSingleFileCollectionDataAdapter, item_type: EventLog }; // node_modules/obsidian-smart-env/src/utils/event_logs_utils.js var notice_timeout_ms = 7e3; var milestone_notice_timeout_ms = 12e3; function is_event_log_muted(event_log) { return Boolean(event_log?.data?.muted); } function get_notification_setting_key(level) { const normalized_level = normalize_event_level(level); if (!normalized_level) return null; if (!notification_levels.includes(normalized_level)) return null; return `native_notice_${normalized_level}`; } function get_notice_setting_value(instance, setting_key) { if (!setting_key) return false; const explicit_value = instance?.settings?.[setting_key]; if (typeof explicit_value === "boolean") return explicit_value; const default_value = instance?.constructor?.default_settings?.[setting_key]; if (typeof default_value === "boolean") return default_value; return false; } function should_show_native_notice(instance, params = {}) { const { event_key = "", event = {} } = params; const level = get_event_level(event_key, event); if (!level) return false; const setting_key = get_notification_setting_key(level); if (!setting_key) return false; if (!get_notice_setting_value(instance, setting_key)) return false; const event_log = typeof instance?.get === "function" ? instance.get(event_key) : instance?.items?.[event_key]; if (is_event_log_muted(event_log)) return false; return true; } function get_timeout_override(event = {}) { const timeout_value = event.timeout_ms ?? event.timeout; if (typeof timeout_value !== "number" || !Number.isFinite(timeout_value)) return null; if (timeout_value < 0) return null; return timeout_value; } function get_native_notice_timeout(event_key, event = {}) { const timeout_override = get_timeout_override(event); if (timeout_override !== null) return timeout_override; const level = get_event_level(event_key, event); if (level === "milestone") return milestone_notice_timeout_ms; return notice_timeout_ms; } function get_native_notice_message(event_key, event = {}) { if (typeof event?.message === "string" && event.message.trim()) return event.message.trim(); if (typeof event?.details === "string" && event.details.trim()) return event.details.trim(); if (typeof event?.milestone === "string" && event.milestone.trim()) return event.milestone.trim(); return event_key || "notification"; } // node_modules/obsidian-smart-env/src/utils/notice_action_dispatch.js function to_trimmed_string(value) { return typeof value === "string" ? value.trim() : ""; } function is_plain_object4(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function get_source_event(params = {}) { if (is_plain_object4(params.source_event)) return params.source_event; if (is_plain_object4(params.event)) return params.event; return {}; } function get_source_event_key(params = {}) { return to_trimmed_string(params.source_event_key) || to_trimmed_string(params.event_key); } function dispatch_btn_event_action(env, params = {}) { const { event_source = "native_notice_button", source_event_key = "", source_event = {} } = params; const btn_event_key = to_trimmed_string(source_event?.btn_event_key); if (!btn_event_key) return null; if (typeof env?.events?.emit !== "function") return false; const btn_event_payload = is_plain_object4(source_event?.btn_event_payload) ? source_event.btn_event_payload : {}; env.events.emit(btn_event_key, { ...btn_event_payload, event_source, source_event_key, source_event }); return true; } function dispatch_notice_action(env, callback_key, params = {}) { const event_source = to_trimmed_string(params.event_source) || "native_notice_button"; const source_event_key = get_source_event_key(params); const source_event = get_source_event(params); const btn_event_result = dispatch_btn_event_action(env, { event_source, source_event_key, source_event }); if (btn_event_result !== null) return btn_event_result; const next_callback_key = to_trimmed_string(callback_key); if (!next_callback_key) return false; const app_commands = env?.main?.app?.commands; if (app_commands?.commands?.[next_callback_key] && typeof app_commands.executeCommandById === "function") { app_commands.executeCommandById(next_callback_key); return true; } if (typeof env?.events?.emit !== "function") return false; env.events.emit(next_callback_key, { event_source, source_event_key, source_event }); return true; } // node_modules/obsidian-smart-env/src/collections/event_logs.js var settings_config4 = { native_notice_info: { name: "Info", description: 'Show Obsidian native notices for "info" level events.', type: "toggle" }, native_notice_warning: { name: "Warning", description: 'Show Obsidian native notices for "warning" level events.', type: "toggle" }, native_notice_error: { name: "Error", description: 'Show Obsidian native notices for "error" level events.', type: "toggle" }, native_notice_attention: { name: "Attention", description: 'Show Obsidian native notices for "attention" level events.', type: "toggle" }, native_notice_milestone: { name: "Milestone", description: 'Show Obsidian native notices for "milestone" level events.', type: "toggle" } }; var native_notice_component_key_map = Object.freeze({ milestone: "milestone_notification" }); function get_native_notice_component_key(event_key, event = {}) { const level = get_event_level(event_key, event); if (!level) return null; return native_notice_component_key_map[level] || "default_notification"; } var EventLogs2 = class extends EventLogs { static version = 4e-3; static get default_settings() { return { ...super.default_settings || {}, native_notice_info: false, native_notice_warning: true, native_notice_error: true, native_notice_attention: false, native_notice_milestone: true }; } get settings_config() { return { ...settings_config4 }; } /** * @param {string} event_key * @param {Record} event * @returns {object|null} */ on_any_event(event_key, event = {}) { const session_entry = super.on_any_event(event_key, event); this.show_native_notice(session_entry, { event_key, event }); return session_entry; } /** * @param {object|null} session_entry * @param {object} [params={}] * @param {string} [params.event_key=''] * @param {Record} [params.event={}] * @returns {boolean} */ async show_native_notice(session_entry, params = {}) { const { event_key = "", event = {} } = params; if (!should_show_native_notice(this, { event_key, event })) return false; try { const notice_content = await this.build_native_notice_content(event_key, event); const notice_timeout = get_native_notice_timeout(event_key, event); new import_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/smart_fuzzy_suggest_modal.js var import_obsidian5 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/smart_fuzzy_suggest_utils.js function build_suggest_scope_items(modal, params = {}) { if (!modal) return []; const action_keys = Array.isArray(params.action_keys) ? params.action_keys : []; const action_configs = modal?.env?.config?.actions || {}; const action_handlers = modal?.item_or_collection?.actions || {}; const unique_action_keys = [...new Set(action_keys)]; return unique_action_keys.reduce((acc, action_key) => { const action_handler = action_handlers[action_key]; if (typeof action_handler !== "function") return acc; const action_config = action_configs[action_key] || {}; const display_name7 = action_config.display_name || action_key; acc.push({ select_action: () => { modal.update_suggestions(action_key); setTimeout(() => { modal.inputEl.focus(); }, 100); }, key: action_key, display: display_name7 }); return acc; }, []); } var should_handle_arrow_left = (modal, params = {}) => { const input_el = modal?.inputEl; const event_target = params.event_target; const input_value = typeof params.input_value === "string" ? params.input_value : input_el?.value || ""; if (event_target === input_el && input_value) { return false; } return true; }; // node_modules/obsidian-smart-env/src/modals/smart_fuzzy_suggest_modal.js var SmartFuzzySuggestModal = class extends import_obsidian5.FuzzySuggestModal { constructor(item_or_collection) { const env = item_or_collection.env; const plugin = env.plugin; const app2 = plugin.app; super(app2); this.app = app2; env.create_env_getter(this); this.plugin = plugin; this.item_or_collection = item_or_collection; this.emptyStateText = "No suggestions available"; this._set_custom_instructions = false; } /** Unique type key for this modal class. Subclasses override. */ static get modal_type() { return "smart-fuzzy-suggest"; } /** Human label used in commands. Subclasses override as needed. */ static get display_text() { return "Smart Fuzzy Suggest"; } /** Event name listened to on env.events to open this modal. */ static get event_domain() { return `${this.modal_type}`; } /** Command id used with addCommand. */ static get command_id() { return this.modal_type; } static open(item_or_collection, params) { const Modal12 = ( /** @type {typeof SmartFuzzySuggestModal} */ this ); const modal = new Modal12(item_or_collection, params); modal.open(params); return modal; } static register_modal(plugin) { const Modal12 = ( /** @type {typeof SmartFuzzySuggestModal} */ this ); const env = plugin?.env; const modal_config = { ...env.config.modals?.[this.modal_key] || {}, class: null }; console.log(`Registering modal: ${this.display_text}`, { modal_config, Modal: Modal12 }); const open_handler = (payload = {}) => { const item = Modal12.resolve_item_from_payload(env, payload); const modal = Modal12.open(item, { ...modal_config, ...payload // spread since event payload is locked }); return modal; }; const disposers = [ env?.events?.on?.(`${Modal12.event_domain}:open`, open_handler) // env.events?.on?.(`${Modal.event_domain}:suggest`, suggest_handler), ]; const dispose_all = () => { disposers.forEach((dispose) => typeof dispose === "function" && dispose()); }; if (typeof plugin.register === "function") { plugin.register(() => dispose_all()); } return { event_domain: Modal12.event_domain }; } static resolve_item_from_payload(env, payload) { const item = env?.[payload.collection_key]?.items?.[payload.item_key]; return item; } setInstructions(instructions, is_custom = true) { this._set_custom_instructions = is_custom; super.setInstructions(instructions); } set_default_instructions() { this.setInstructions([ { command: "Enter", purpose: "Select" } ], false); } open(params = {}) { super.open(); this.modalEl.addEventListener("keydown", (e) => { if (e.key === "Enter") { if (e.shiftKey) this.use_shift_select = true; this.selectActiveSuggestion(e); } const is_cursor_end_of_input = this.inputEl.selectionStart === this.inputEl.value.length; const should_handle_arrow_right = is_cursor_end_of_input || e.target !== this.inputEl || !this.inputEl.value; const should_handle_arrow_left_action = should_handle_arrow_left(this, { event_target: e.target, input_value: this.inputEl.value }); if (e.key === "ArrowLeft" && should_handle_arrow_left_action) { this.use_arrow_left = true; this.selectActiveSuggestion(e); return; } if (e.key === "ArrowRight" && should_handle_arrow_right) { e.preventDefault(); this.use_mod_select = true; this.use_arrow_right = true; this.selectActiveSuggestion(e); return; } }); } getItems() { return this.get_suggestions(); } getItemText(suggestion_item) { return suggestion_item.display; } filter_suggestions(suggestions) { return suggestions; } get_suggestions() { if (this.suggestions?.length) { this.suggestions = this.filter_suggestions(this.suggestions); if (this.suggestions.length > 0) { return this.suggestions; } } if (this.default_suggest_action_keys?.length) { if (this.default_suggest_action_keys.length === 1) { this.update_suggestions(this.default_suggest_action_keys[0]); return []; } return this.get_suggest_scopes(); } return []; } get_suggest_scopes() { return build_suggest_scope_items(this, { action_keys: this.default_suggest_action_keys }); } async update_suggestions(suggest_ref) { if (typeof suggest_ref === "string") { suggest_ref = this.item_or_collection.actions[suggest_ref]; } if (typeof suggest_ref === "function") { this._set_custom_instructions = false; const result = await suggest_ref({ modal: this }); console.log("Suggestion action result", result); if (Array.isArray(result) && result.length) { this.suggestions = result; } } else if (Array.isArray(suggest_ref)) { this.suggestions = suggest_ref; } if (Array.isArray(this.suggestions) && this.suggestions.length) { this.updateSuggestions(); } else { this.env.events.emit("notification:error", { message: "Invalid suggestion action" }); console.warn("Invalid suggestion action", suggest_ref); } if (!this._set_custom_instructions) { this.set_default_instructions(); } } get default_suggest_action_keys() { if (Array.isArray(this.params?.default_suggest_action_keys)) { return this.params.default_suggest_action_keys; } return this.env.config.modals[this.modal_key]?.default_suggest_action_keys || []; } renderSuggestion(sug, el) { super.renderSuggestion(sug, el); const icon = sug?.icon || sug?.item?.icon; if (icon) { el.addClass("sc-modal-suggestion-has-icon"); const icon_el = el.createEl("span"); (0, import_obsidian5.setIcon)(icon_el, icon); } const display_right_raw = sug && Object.prototype.hasOwnProperty.call(sug, "display_right") ? sug.display_right : sug?.item?.display_right; const display_right = display_right_raw === null || display_right_raw === void 0 ? "" : String(display_right_raw).trim(); if (display_right) { this.env.smart_components.render_component("suggest_display_right", display_right).then((right_el) => { el.appendChild(right_el); }); } return el; } onChooseSuggestion(selected, evt, ...other) { this.prevent_close = true; const suggestion = selected.item; const is_arrow_left = this.use_arrow_left; const is_arrow_right = this.use_arrow_right; const is_shift_select = evt?.shiftKey || this.use_shift_select; const is_mod_select = import_obsidian5.Keymap.isModifier(evt, "Mod") || this.use_mod_select; this.use_arrow_right = false; this.use_mod_select = false; this.use_arrow_left = false; this.use_shift_select = false; if (is_arrow_left) { if (typeof suggestion.arrow_left_action === "function") { this.handle_choose_action(suggestion, "arrow_left_action"); } else { if (this.last_input_value) { this.inputEl.value = this.last_input_value; setTimeout(() => { const len = this.inputEl.value.length; this.inputEl.setSelectionRange(len, len); }, 0); this.last_input_value = null; } this.suggestions = null; this.params.default_suggest_action_keys = null; this.updateSuggestions(); return; } } else if (is_arrow_right && typeof suggestion.arrow_right_action === "function") { this.handle_choose_action(suggestion, "arrow_right_action"); } else if (is_mod_select && typeof suggestion.mod_select_action === "function") { this.handle_choose_action(suggestion, "mod_select_action"); } else if (is_shift_select && typeof suggestion.shift_select_action === "function") { this.handle_choose_action(suggestion, "shift_select_action"); } else if (typeof suggestion.select_action === "function") { this.handle_choose_action(suggestion, "select_action"); } else { this.env.events.emit("notification:warning", { selection_display: suggestion.display, message: "No action defined for this suggestion" }); } } async handle_choose_action(suggestion, action_key) { let chosen_action = suggestion[action_key]; const result = await chosen_action({ modal: this }); if (Array.isArray(result) && result.length) { this.suggestions = result; } else if (Array.isArray(result)) { this.env.events.emit("notification:info", { message: "No suggestions returned from action" }); } const idx = this.chooser.values.findIndex((i) => i.item?.display === suggestion.display); setTimeout(() => { this.updateSuggestions(); if (idx !== -1) { this.chooser.setSelectedItem(idx); } }, 100); } close() { setTimeout(() => { if (!this.prevent_close) super.close(); this.prevent_close = false; }, 10); } onClose() { this.item_or_collection.emit_event(`${this.constructor.event_domain}:closed`); } }; // node_modules/obsidian-smart-env/src/modals/context_selector.js var import_obsidian6 = require("obsidian"); var ContextModal = class extends SmartFuzzySuggestModal { /** Modal identity */ static get modal_type() { return "context_selector"; } static get display_text() { return "Context Selector"; } static get event_domain() { return "context_selector"; } static get command_id() { return this.modal_type; } static get modal_key() { return "context_selector"; } get modal_key() { return "context_selector"; } constructor(smart_context, params = {}) { super(smart_context); this.params = { ...params }; this.smart_context = smart_context; this.set_default_instructions(); } set_default_instructions() { this.setInstructions([ { command: "Enter", purpose: "Add to context" }, { command: `\u2192 / \u2190`, purpose: "Toggle block view" }, { command: "Esc", purpose: "Close" } ]); } open(params = {}) { this.params = { ...this.params, ...params }; super.open(); this.render(this.params); } async render(params = this.params) { this.modalEl.style.display = "flex"; this.modalEl.style.flexDirection = "column"; this.modalEl.prepend( await this.env.smart_components.render_component( "smart_context_item", this.smart_context, params ) ); } filter_suggestions(suggestions) { return suggestions.filter((s) => { if (s.key && this.smart_context?.data?.context_items[s.key]) return false; return true; }); } }; // node_modules/obsidian-smart-env/src/modals/notifications_feed_modal.js var import_obsidian7 = require("obsidian"); var NotificationsFeedModal = class extends import_obsidian7.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_obsidian8 = require("obsidian"); var MILESTONES_HELP_URL = "https://smartconnections.app/smart-environment/milestones/?utm_source=milestones_modal_help"; var MilestonesModal = class extends import_obsidian8.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_obsidian8.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_obsidian9 = require("obsidian"); var BrowseSmartPlugins = class extends import_obsidian9.Modal { constructor(app2, env) { super(app2); this.env = env; } async onOpen() { if (this.modalEl?.classList) { this.modalEl.classList.add("smart-env-pro-plugins-modal"); } if (this.modalEl?.style) { this.modalEl.style.width = "min(920px, 92vw)"; this.modalEl.style.maxHeight = "min(820px, 88vh)"; } this.titleEl.setText("Smart Plugins"); this.contentEl.empty(); const plugin_store = await this.env.smart_components.render_component("smart_plugins_list", this.env, { event_source: "browse_plugins_modal" }); if (plugin_store) { this.contentEl.appendChild(plugin_store); } } onClose() { this.contentEl.empty(); if (this.modalEl?.classList) { this.modalEl.classList.remove("smart-env-pro-plugins-modal"); } if (this.modalEl?.style) { this.modalEl.style.width = ""; this.modalEl.style.maxHeight = ""; } } }; // node_modules/obsidian-smart-env/default.settings.js var default_settings = { is_obsidian_vault: true, smart_blocks: { embed_blocks: true, min_chars: 200 }, smart_sources: { min_chars: 200, embed_model: { adapter: "transformers", transformers: { model_key: "TaylorAI/bge-micro-v2" } }, excluded_headings: "", file_exclusions: "Untitled", folder_exclusions: "" }, language: "en", re_import_wait_time: 13, smart_chat_threads: { chat_model: { adapter: "ollama", ollama: {} } }, smart_notices: {}, smart_view_filter: { expanded_view: false, render_markdown: true, show_full_path: false }, version: "", new_user: true, // DEPRECATED: 2025-06-05 (use localStorage instead???) // 2025-11-26 models: { embedding_platform: "transformers", chat_completion_platform: "open_router" } }; // node_modules/obsidian-smart-env/node_modules/smart-models/items/model.js var Model = class extends CollectionItem { /** * Default properties for an instance of CollectionItem. * @returns {Object} */ static get defaults() { return { data: { api_key: "", provider_key: "", model_key: "" } }; } get_key() { if (!this.data.key) { this.data.created_at = Date.now(); this.data.key = `${this.data.provider_key}#${this.data.created_at}`; } return this.data.key; } get provider_key() { return this.data.provider_key; } get env_config() { return this.collection.env_config; } get provider_config() { return this.env_config.providers?.[this.provider_key] || {}; } get ProviderAdapterClass() { return this.provider_config.class; } get instance() { if (!this._instance) { if (!this.ProviderAdapterClass) { const new_default_model = this.collection.new_model({ provider_key: this.collection.default_provider_key }); return new_default_model.instance; } const Class = this.ProviderAdapterClass; this._instance = new Class(this); if (typeof this._instance.load_background === "function") { this._instance.load_background(); } else { this._instance.load(); } this.once_event("model:changed", () => { this._instance.unload?.(); this._instance = null; }); } return this._instance; } async count_tokens(text) { return this.instance.count_tokens(text); } get api_key() { return this.data.api_key; } /** * Create (or reuse) a proxy around a target settings object so that * any mutations trigger queue_save on the model. * Proxies are cached per-target via WeakMap to support deep nested objects. * * @param {Object} target - The settings object or nested object to wrap. * @returns {Object} Proxied object or original value if not an object. * @private */ create_settings_proxy(target) { if (!target || typeof target !== "object") return target; if (!this._settings_proxy_map) { this._settings_proxy_map = /* @__PURE__ */ new WeakMap(); } const existing = this._settings_proxy_map.get(target); if (existing) return existing; const self = this; const handler = { get(target_obj, prop, receiver) { const value = Reflect.get(target_obj, prop, receiver); if (value && typeof value === "object") { return self.create_settings_proxy(value); } return value; }, set(target_obj, prop, value, receiver) { const previous = target_obj[prop]; const result = Reflect.set(target_obj, prop, value, receiver); if (previous !== value) { self.debounce_save(); } return result; }, deleteProperty(target_obj, prop) { const had = Object.prototype.hasOwnProperty.call(target_obj, prop); const result = Reflect.deleteProperty(target_obj, prop); if (had) { self.debounce_save(); } return result; } }; const proxy = new Proxy(target, handler); this._settings_proxy_map.set(target, proxy); return proxy; } /** * @private * @param {number} [ms=100] */ debounce_save(ms = 100) { this.emit_event("model:changed"); if (this._debounce_save_timeout) { clearTimeout(this._debounce_save_timeout); } this._debounce_save_timeout = setTimeout(() => { this.queue_save(); this.collection.process_save_queue(); this._debounce_save_timeout = null; }, ms); } async get_model_key_options() { const model_configs = await this.instance.get_models(); return Object.entries(model_configs).map(([key, model_config]) => ({ label: model_config.name || key, value: model_config.key || key })).sort((a, b) => { if (a.label.toLowerCase().includes("free") && !b.label.toLowerCase().includes("free")) { return -1; } if (!a.label.toLowerCase().includes("free") && b.label.toLowerCase().includes("free")) { return 1; } return a.label.localeCompare(b.label); }); } /** * @private * @param {string} key * @param {*} value * @param {*} elm */ model_changed(key, value, elm) { if (key === "model_key") { this.data.model_key = value; const model_defaults = this.data.provider_models?.[this.data.model_key] || {}; const adapter_defaults = this.ProviderAdapterClass.defaults || {}; delete this.data.test_passed; this.data = { ...this.data, ...adapter_defaults, ...model_defaults }; } if (!["api_key", "meta.name"].includes(key)) { this.emit_event("model:changed"); } } /** * @abstract should be implemented by subclasses */ async test_model() { } get display_name() { return this.data.meta?.name || `${this.data.provider_key} - ${this.data.model_key}`; } get settings_config() { const model = this; return { provider_key: { type: "html", value: `

Provider: ${this.data.provider_key}

` }, "meta.name": { type: "text", name: "Name", description: "A friendly name for this model configuration." }, model_key: { type: "dropdown", name: "Model", description: "The model to use from the selected provider.", options_callback() { return model.get_model_key_options(); }, callback(value, setting) { return model.model_changed("model_key", value, setting); } }, // add model_changed callback to each provider setting that doesn't already have callback defined ...Object.fromEntries( Object.entries(this.provider_config.settings_config || {}).map( ([setting_key, setting_config]) => { const callback = setting_config.callback || ((value, setting) => { return model.model_changed(setting_key, value, setting); }); return [setting_key, { ...setting_config, callback }]; } ) ) }; } delete_model() { this.delete(); this.debounce_save(); } /** * Reactive settings view for this model. * Mutating any property (including nested objects/arrays) via this proxy * will call queue_save(). * * @returns {Object} Proxied view of this.data. */ get settings() { return this.create_settings_proxy(this.data); } get model_key() { return this.data.model_key; } /** * @deprecated included for backward compatibility (2026-02-11) */ get opts() { return this.settings; } }; // node_modules/obsidian-smart-env/node_modules/smart-models/collections/models.js var Models = class extends Collection { model_type = "Model type"; // replace in subclass new_model(data = {}) { if (!data.provider_key) throw new Error("provider_key is required to create a new model"); const existing_from_provider = this.filter((m) => m.provider_key === data.provider_key).sort((a, b) => b.data.created_at - a.data.created_at)[0]; if (existing_from_provider) { if (!data.api_key && existing_from_provider.data.api_key) { data.api_key = existing_from_provider.data.api_key; } } const item = new this.item_type(this.env, { ...data }); this.set(item); this.settings.default_model_key = item.key; this.emit_event("model:changed"); item.queue_save(); return item; } /** * Retrieve the provider key used when creating a default model. * @abstract * @returns {string} provider key for the default model. */ get default_provider_key() { throw new Error("default_provider_key not implemented"); } get default_model_key() { const should_update_default = !this.settings.default_model_key || !this.get(this.settings.default_model_key) || this.get(this.settings.default_model_key).deleted; if (should_update_default) { const existing = this.filter((m) => !m.deleted).sort((a, b) => b.data.created_at - a.data.created_at)[0]; if (existing) { this.settings.default_model_key = existing.key; } else { const new_default = this.new_model({ provider_key: this.default_provider_key }); new_default.queue_save(); this.process_save_queue(); this.settings.default_model_key = new_default.key; } } return this.settings.default_model_key; } get default() { return this.get(this.default_model_key); } get env_config() { return this.env.config.collections[this.collection_key]; } get_model_key_options() { return this.filter((i) => !i.deleted && i.ProviderAdapterClass).map((model) => ({ label: model.data.meta?.name || `${model.provider_key} - ${model.data.model_key}`, value: model.key })); } }; function settings_config5(scope) { return { default_model_key: { type: "dropdown", name: `Default ${scope.model_type.toLowerCase()} model`, description: `Used as the default ${scope.model_type.toLowerCase()} model when no other is specified.`, options_callback: () => { return scope.get_model_key_options(); }, callback: async (value, setting) => { scope.emit_event("model:changed"); } } }; } // node_modules/obsidian-smart-env/node_modules/smart-models/items/embedding_model.js var EmbeddingModel = class extends Model { /** * Default properties for an instance of CollectionItem. * @returns {Object} */ static get defaults() { return { data: { api_key: "", provider_key: "transformers", model_key: "TaylorAI/bge-micro-v2", dims: 384, // ??? max_tokens: 512 // ??? } }; } async embed(input) { if (typeof input === "string") { input = [{ embed_input: input }]; } return (await this.embed_batch(input))[0]; } async embed_batch(inputs) { return this.instance.embed_batch(inputs); } async test_model() { try { const resp = await this.embed("test input"); const success = resp && !resp?.error; this.data.test_passed = success; this.debounce_save(); return { success, response: resp }; } catch (e) { this.data.test_passed = false; return { error: e.message || String(e) }; } } }; // node_modules/obsidian-smart-env/node_modules/smart-models/collections/embedding_models.js var EmbeddingModels = class extends Models { model_type = "Embedding"; get default_provider_key() { return "transformers"; } }; var embedding_models_collection = { class: EmbeddingModels, data_dir: "embedding_models", collection_key: "embedding_models", data_adapter: ajson_single_file_default, item_type: EmbeddingModel, providers: { // transformers // replace with platform-specific import in obsidian-smart-env }, settings_config: settings_config5 }; var embedding_models_default = embedding_models_collection; // dist-text:C:\Users\brian\Documents\smart-connections-obsidian\node_modules\obsidian-smart-env\src\adapters\embedding-model\transformers_v4.iframe.js var transformers_v4_iframe_default = 'var __defProp = Object.defineProperty;\nvar __name = (target, value) => __defProp(target, "name", { value, configurable: true });\n\n// ../jsbrains/smart-model/adapters/_adapter.js\nvar SmartModelAdapter = class {\n static {\n __name(this, "SmartModelAdapter");\n }\n /**\n * Create a SmartModelAdapter instance.\n * @param {SmartModel} model - The parent SmartModel instance\n */\n constructor(model2) {\n this.model = model2;\n this.state = "unloaded";\n }\n /**\n * Load the adapter.\n * @async\n * @returns {Promise}\n */\n async load() {\n this.set_state("loaded");\n }\n /**\n * Unload the adapter.\n * @returns {void}\n */\n unload() {\n this.set_state("unloaded");\n }\n /**\n * Get all settings.\n * @returns {Object} All settings\n */\n get settings() {\n return this.model.settings;\n }\n /**\n * Get the current model key.\n * @returns {string} Current model identifier\n */\n get model_key() {\n return this.model.model_key;\n }\n /**\n * Get the models.\n * @returns {Object} Map of model objects\n */\n get models() {\n const models = this.model.data.provider_models;\n if (typeof models === "object" && Object.keys(models || {}).length > 0) return models;\n else {\n return {};\n }\n }\n /**\n * Get available models from the API.\n * @abstract\n * @param {boolean} [refresh=false] - Whether to refresh cached models\n * @returns {Promise} Map of model objects\n */\n async get_models(refresh = false) {\n throw new Error("get_models not implemented");\n }\n /**\n * Get available models as dropdown options synchronously.\n * @returns {Array} Array of model options.\n */\n get_models_as_options() {\n const models = this.models;\n if (!Object.keys(models || {}).length) {\n this.get_models(true);\n return [{ value: "", name: "No models currently available" }];\n }\n return Object.entries(models).map(([id, model2]) => ({ value: id, name: model2.name || id })).sort((a, b) => a.name.localeCompare(b.name));\n }\n /**\n * Set the adapter\'s state.\n * @deprecated should be handled in SmartModel (only handle once)\n * @param {(\'unloaded\'|\'loading\'|\'loaded\'|\'unloading\')} new_state - The new state\n * @throws {Error} If the state is invalid\n */\n set_state(new_state) {\n const valid_states = ["unloaded", "loading", "loaded", "unloading"];\n if (!valid_states.includes(new_state)) {\n throw new Error(`Invalid state: ${new_state}`);\n }\n this.state = new_state;\n }\n // Replace individual state getters/setters with a unified state management\n get is_loading() {\n return this.state === "loading";\n }\n get is_loaded() {\n return this.state === "loaded";\n }\n get is_unloading() {\n return this.state === "unloading";\n }\n get is_unloaded() {\n return this.state === "unloaded";\n }\n};\n\n// ../jsbrains/smart-embed-model/adapters/_adapter.js\nvar SmartEmbedAdapter = class extends SmartModelAdapter {\n static {\n __name(this, "SmartEmbedAdapter");\n }\n /**\n * @override in sub-class with adapter-specific default configurations\n * @property {string} id - The adapter identifier\n * @property {string} description - Human-readable description\n * @property {string} type - Adapter type ("API")\n * @property {string} endpoint - API endpoint\n * @property {string} adapter - Adapter identifier\n * @property {string} default_model - Default model to use\n */\n static defaults = {};\n /**\n * Count tokens in input text\n * @abstract\n * @param {string} input - Text to tokenize\n * @returns {Promise} Token count result\n * @property {number} tokens - Number of tokens in input\n * @throws {Error} If not implemented by subclass\n */\n async count_tokens(input) {\n throw new Error("count_tokens method not implemented");\n }\n /**\n * Generate embeddings for single input\n * @abstract\n * @param {string|Object} input - Text to embed\n * @returns {Promise} Embedding result\n * @property {number[]} vec - Embedding vector\n * @property {number} tokens - Number of tokens in input\n * @throws {Error} If not implemented by subclass\n */\n async embed(input) {\n if (typeof input === "string") input = { embed_input: input };\n return (await this.embed_batch([input]))[0];\n }\n /**\n * Generate embeddings for multiple inputs\n * @abstract\n * @param {Array} inputs - Texts to embed\n * @returns {Promise>} Array of embedding results\n * @property {number[]} vec - Embedding vector for each input\n * @property {number} tokens - Number of tokens in each input\n * @throws {Error} If not implemented by subclass\n */\n async embed_batch(inputs) {\n throw new Error("embed_batch method not implemented");\n }\n get settings_config() {\n return {\n "[ADAPTER].model_key": {\n name: "Embedding model",\n type: "dropdown",\n description: "Select an embedding model.",\n options_callback: "adapter.get_models_as_options",\n callback: "model_changed",\n default: this.constructor.defaults.default_model\n }\n };\n }\n get dims() {\n return this.model.data.dims;\n }\n get max_tokens() {\n return this.model.data.max_tokens;\n }\n get batch_size() {\n return this.model.data.batch_size || 1;\n }\n};\n\n// ../obsidian-smart-env/src/adapters/embedding-model/transformers_v4.iframe.js\nvar transformers_defaults = {\n adapter: "transformers",\n description: "Transformers (Local, built-in)",\n default_model: "TaylorAI/bge-micro-v2",\n models: transformers_models\n};\nvar DEVICE_CONFIGS = Object.freeze({\n webgpu: Object.freeze({\n device: "webgpu",\n preferred_dtypes: ["fp32", "fp16", "q8", "q4"]\n }),\n cpu: Object.freeze({\n preferred_dtypes: ["q8", "q4", "fp32", "fp16"]\n })\n});\nfunction build_device_configs(available_dtypes = [], params = {}) {\n const {\n use_gpu = false\n } = params;\n const normalized_available_dtypes = new Set(\n Array.isArray(available_dtypes) ? available_dtypes : []\n );\n const configs = [];\n const push_scope_configs = /* @__PURE__ */ __name((scope_key) => {\n const scope_config = DEVICE_CONFIGS[scope_key];\n if (!scope_config) return;\n scope_config.preferred_dtypes.forEach((dtype) => {\n if (normalized_available_dtypes.size && !normalized_available_dtypes.has(dtype)) return;\n configs.push({\n config_key: `${scope_key}_${dtype}`,\n ...scope_config.device ? { device: scope_config.device } : {},\n dtype\n });\n });\n }, "push_scope_configs");\n if (use_gpu) {\n push_scope_configs("webgpu");\n }\n push_scope_configs("cpu");\n if (!configs.length) {\n if (use_gpu) {\n configs.push({\n config_key: "webgpu_auto",\n device: "webgpu"\n });\n }\n configs.push({\n config_key: "cpu_auto"\n });\n return configs;\n }\n if (!configs.some(({ config_key }) => config_key === "cpu_auto")) {\n configs.push({\n config_key: "cpu_auto"\n });\n }\n return configs;\n}\n__name(build_device_configs, "build_device_configs");\nvar retryable_webgpu_error_code = "WEBGPU_RETRYABLE_ERROR";\nvar retryable_webgpu_error_patterns = [\n /no available backend found/i,\n /webgpuinit is not a function/i,\n /subgroupminsize/i\n];\nfunction get_error_message(error) {\n return error?.message || String(error || "");\n}\n__name(get_error_message, "get_error_message");\nfunction is_retryable_webgpu_error(error) {\n const error_message = get_error_message(error);\n return retryable_webgpu_error_patterns.some((pattern) => pattern.test(error_message));\n}\n__name(is_retryable_webgpu_error, "is_retryable_webgpu_error");\nfunction create_retryable_webgpu_error(error) {\n const error_message = get_error_message(error);\n if (error_message.includes(retryable_webgpu_error_code)) {\n return error instanceof Error ? error : new Error(error_message);\n }\n const wrapped_error = new Error(`${retryable_webgpu_error_code}: ${error_message}`);\n try {\n wrapped_error.cause = error;\n } catch (_error) {\n }\n return wrapped_error;\n}\n__name(create_retryable_webgpu_error, "create_retryable_webgpu_error");\nfunction should_bubble_webgpu_error(active_config_key, error) {\n return String(active_config_key || "").includes("webgpu") && is_retryable_webgpu_error(error);\n}\n__name(should_bubble_webgpu_error, "should_bubble_webgpu_error");\nvar is_webgpu_available = /* @__PURE__ */ __name(async () => {\n if (!("gpu" in navigator)) return false;\n const adapter = await navigator.gpu.requestAdapter();\n if (!adapter) return false;\n return true;\n}, "is_webgpu_available");\nvar SmartEmbedTransformersAdapter = class extends SmartEmbedAdapter {\n static {\n __name(this, "SmartEmbedTransformersAdapter");\n }\n static defaults = transformers_defaults;\n /**\n * @param {import("../smart_embed_model.js").SmartEmbedModel} model\n */\n constructor(model2) {\n super(model2);\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.has_gpu = false;\n }\n /**\n * Load the underlying transformers pipeline with WebGPU \u2192 WASM fallback.\n * @returns {Promise}\n */\n async load() {\n this.has_gpu = await is_webgpu_available();\n try {\n if (this.loading) {\n console.warn("[Transformers v4] load already in progress, waiting...");\n while (this.loading) {\n await new Promise((resolve) => setTimeout(resolve, 100));\n }\n } else {\n this.loading = true;\n if (this.pipeline) {\n this.loaded = true;\n this.loading = false;\n return;\n }\n await this.load_transformers_with_fallback();\n this.loading = false;\n this.loaded = true;\n console.log(`[Transformers v4] model loaded using ${this.active_config_key}`, this);\n }\n } catch (e) {\n this.loading = false;\n this.loaded = false;\n console.error("[Transformers v4] load failed", e);\n throw e;\n }\n }\n /**\n * Unload the pipeline and free resources.\n * @returns {Promise}\n */\n async unload() {\n try {\n if (this.pipeline) {\n if (typeof this.pipeline.destroy === "function") {\n this.pipeline.destroy();\n } else if (typeof this.pipeline.dispose === "function") {\n this.pipeline.dispose();\n }\n }\n } catch (err) {\n console.warn("[Transformers v4] error while disposing pipeline", err);\n }\n this.pipeline = null;\n this.tokenizer = null;\n this.active_config_key = null;\n this.loaded = false;\n }\n /**\n * Available models \u2013 reuses the v1 transformers model catalog.\n * @returns {Object}\n */\n get models() {\n return transformers_models;\n }\n /**\n * Maximum tokens per input.\n * @returns {number}\n */\n get max_tokens() {\n return this.model.data.max_tokens || 512;\n }\n /**\n * Effective batch size.\n * Prefers small deterministic batches when not explicitly configured.\n * @returns {number}\n */\n get batch_size() {\n const configured = this.model.data.batch_size;\n if (configured && configured > 0) return configured;\n return this.gpu_enabled ? 16 : 8;\n }\n get gpu_enabled() {\n if (this.has_gpu) {\n const explicit = typeof this.model.data.use_gpu === "boolean" ? this.model.data.use_gpu : null;\n if (explicit === false) return false;\n return true;\n } else {\n return false;\n }\n }\n /**\n * Initialize transformers pipeline with WebGPU \u2192 WASM fallback.\n * @private\n * @returns {Promise}\n */\n async load_transformers_with_fallback() {\n const { pipeline, env, AutoTokenizer, ModelRegistry, LogLevel } = await import("https://cdn.jsdelivr.net/npm/@huggingface/transformers@4.1.0");\n env.logLevel = LogLevel.ERROR;\n let available_dtypes = [];\n try {\n available_dtypes = await ModelRegistry.get_available_dtypes(this.model_key);\n console.log({ available_dtypes });\n } catch (error) {\n console.warn("[Transformers v4] failed to probe available dtypes, falling back to runtime defaults", error);\n }\n env.allowLocalModels = false;\n if (typeof env.useBrowserCache !== "undefined") {\n env.useBrowserCache = true;\n }\n let last_error = null;\n const config_list = build_device_configs(available_dtypes, {\n use_gpu: this.gpu_enabled\n });\n const try_create = /* @__PURE__ */ __name(async (device_config) => {\n const pipeline_params = {};\n if (device_config.device) {\n pipeline_params.device = device_config.device;\n }\n if (device_config.dtype) {\n pipeline_params.dtype = device_config.dtype;\n }\n const pipe = await pipeline("feature-extraction", this.model_key, pipeline_params);\n return pipe;\n }, "try_create");\n for (const device_config of config_list) {\n const config_key = device_config.config_key;\n if (this.pipeline) break;\n try {\n console.log(`[Transformers v4] trying to load pipeline on ${config_key}`);\n this.pipeline = await try_create(device_config);\n this.active_config_key = config_key;\n break;\n } catch (err) {\n console.warn(`[Transformers v4: ${config_key}] failed to load pipeline on ${config_key}`, err);\n if (device_config.device === "webgpu" && is_retryable_webgpu_error(err)) {\n throw create_retryable_webgpu_error(err);\n }\n last_error = err;\n }\n }\n if (this.pipeline) {\n console.log(`[Transformers v4: ${this.active_config_key}] pipeline initialized using ${this.active_config_key}`);\n } else {\n throw last_error || new Error("Failed to initialize transformers pipeline");\n }\n this.tokenizer = await AutoTokenizer.from_pretrained(this.model_key);\n }\n /**\n * Count tokens in input text.\n * @param {string} input\n * @returns {Promise<{tokens:number}>}\n */\n async count_tokens(input) {\n if (!this.tokenizer) {\n await this.load();\n }\n const { input_ids } = await this.tokenizer(input);\n return { tokens: input_ids.data.length };\n }\n /**\n * Generate embeddings for multiple inputs.\n * @param {Array} inputs\n * @returns {Promise>}\n */\n async embed_batch(inputs) {\n if (!this.pipeline) {\n await this.load();\n }\n const filtered_inputs = inputs.filter((item) => item.embed_input && item.embed_input.length > 0);\n if (!filtered_inputs.length) return [];\n const results = [];\n for (let i = 0; i < filtered_inputs.length; i += this.batch_size) {\n const batch = filtered_inputs.slice(i, i + this.batch_size);\n const batch_results = await this._process_batch(batch);\n results.push(...batch_results);\n }\n return results;\n }\n /**\n * Process a single batch \u2013 with per-item retry on failure.\n * @private\n * @param {Array} batch_inputs\n * @returns {Promise>}\n */\n async _process_batch(batch_inputs) {\n const prepared = await Promise.all(\n batch_inputs.map((item) => this._prepare_input(item.embed_input))\n );\n const embed_inputs = prepared.map((p) => p.text);\n const tokens = prepared.map((p) => p.tokens);\n try {\n const resp = await this.pipeline(embed_inputs, { pooling: "mean", normalize: true });\n return batch_inputs.map((item, i) => {\n const vec = Array.from(resp[i].data).map((val) => Math.round(val * 1e8) / 1e8);\n item.vec = vec;\n item.tokens = tokens[i];\n return item;\n });\n } catch (err) {\n if (should_bubble_webgpu_error(this.active_config_key, err)) {\n throw create_retryable_webgpu_error(err);\n }\n console.error("[Transformers v4] batch embed failed \\u2013 retrying items individually", err);\n return await this._retry_items_individually(batch_inputs);\n }\n }\n /**\n * Prepare a single input by truncating to max_tokens if necessary.\n * @private\n * @param {string} embed_input\n * @returns {Promise<{text:string,tokens:number}>}\n */\n async _prepare_input(embed_input) {\n let { tokens } = await this.count_tokens(embed_input);\n if (tokens <= this.max_tokens) {\n return { text: embed_input, tokens };\n }\n let truncated = embed_input;\n while (tokens > this.max_tokens && truncated.length > 0) {\n const pct = this.max_tokens / tokens;\n const max_chars = Math.floor(truncated.length * pct * 0.9);\n truncated = truncated.slice(0, max_chars);\n const last_space = truncated.lastIndexOf(" ");\n if (last_space > 0) {\n truncated = truncated.slice(0, last_space);\n }\n tokens = (await this.count_tokens(truncated)).tokens;\n }\n return { text: truncated, tokens };\n }\n /**\n * Retry each item individually after a batch failure.\n * @private\n * @param {Array} batch_inputs\n * @returns {Promise>}\n */\n async _retry_items_individually(batch_inputs) {\n await this._reset_pipeline_after_error();\n const results = [];\n for (const item of batch_inputs) {\n try {\n const prepared = await this._prepare_input(item.embed_input);\n const resp = await this.pipeline(prepared.text, { pooling: "mean", normalize: true });\n const vec = Array.from(resp[0].data).map((val) => Math.round(val * 1e8) / 1e8);\n results.push({\n ...item,\n vec,\n tokens: prepared.tokens\n });\n } catch (single_err) {\n if (should_bubble_webgpu_error(this.active_config_key, single_err)) {\n throw create_retryable_webgpu_error(single_err);\n }\n console.error("[Transformers v4] single item embed failed \\u2013 skipping", single_err);\n results.push({\n ...item,\n vec: [],\n tokens: 0,\n error: single_err.message\n });\n }\n }\n return results;\n }\n /**\n * Reset pipeline after a failure \u2013 falling back to WASM if needed.\n * @private\n * @returns {Promise}\n */\n async _reset_pipeline_after_error() {\n try {\n if (this.pipeline) {\n if (typeof this.pipeline.destroy === "function") {\n this.pipeline.destroy();\n } else if (typeof this.pipeline.dispose === "function") {\n this.pipeline.dispose();\n }\n }\n } catch (err) {\n console.warn("[Transformers v4] error while resetting pipeline", err);\n }\n this.pipeline = null;\n await this.load_transformers_with_fallback();\n }\n /**\n * V2 intentionally exposes only model selection in the settings UI.\n * @returns {Object}\n */\n get settings_config() {\n return super.settings_config;\n }\n};\nvar transformers_models = {\n "TaylorAI/bge-micro-v2": {\n "id": "TaylorAI/bge-micro-v2",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "BGE-micro-v2",\n "description": "Local, 512 tokens, 384 dim (recommended)",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-xs": {\n "id": "Snowflake/snowflake-arctic-embed-xs",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed XS",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-s": {\n "id": "Snowflake/snowflake-arctic-embed-s",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed Small",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "Snowflake/snowflake-arctic-embed-m": {\n "id": "Snowflake/snowflake-arctic-embed-m",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 512,\n "name": "Snowflake Arctic Embed Medium",\n "description": "Local, 512 tokens, 768 dim",\n "adapter": "transformers"\n },\n "TaylorAI/gte-tiny": {\n "id": "TaylorAI/gte-tiny",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "GTE-tiny",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "onnx-community/embeddinggemma-300m-ONNX": {\n "id": "onnx-community/embeddinggemma-300m-ONNX",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "EmbeddingGemma-300M",\n "description": "Local, 2,048 tokens, 768 dim",\n "adapter": "transformers"\n },\n "Mihaiii/Ivysaur": {\n "id": "Mihaiii/Ivysaur",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "Ivysaur",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "andersonbcdefg/bge-small-4096": {\n "id": "andersonbcdefg/bge-small-4096",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 4096,\n "name": "BGE-small-4K",\n "description": "Local, 4,096 tokens, 384 dim",\n "adapter": "transformers"\n },\n // Too slow and persistent crashes\n // "jinaai/jina-embeddings-v2-base-de": {\n // "id": "jinaai/jina-embeddings-v2-base-de",\n // "batch_size": 1,\n // "dims": 768,\n // "max_tokens": 4096,\n // "name": "jina-embeddings-v2-base-de",\n // "description": "Local, 4,096 tokens, 768 dim, German",\n // "adapter": "transformers"\n // },\n "Xenova/jina-embeddings-v2-base-zh": {\n "id": "Xenova/jina-embeddings-v2-base-zh",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 8192,\n "name": "Jina-v2-base-zh-8K",\n "description": "Local, 8,192 tokens, 768 dim, Chinese/English bilingual",\n "adapter": "transformers"\n },\n "Xenova/jina-embeddings-v2-small-en": {\n "id": "Xenova/jina-embeddings-v2-small-en",\n "batch_size": 1,\n "dims": 512,\n "max_tokens": 8192,\n "name": "Jina-v2-small-en",\n "description": "Local, 8,192 tokens, 512 dim",\n "adapter": "transformers"\n },\n "nomic-ai/nomic-embed-text-v1.5": {\n "id": "nomic-ai/nomic-embed-text-v1.5",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "Nomic-embed-text-v1.5",\n "description": "Local, 8,192 tokens, 768 dim",\n "adapter": "transformers"\n },\n "Xenova/bge-small-en-v1.5": {\n "id": "Xenova/bge-small-en-v1.5",\n "batch_size": 1,\n "dims": 384,\n "max_tokens": 512,\n "name": "BGE-small",\n "description": "Local, 512 tokens, 384 dim",\n "adapter": "transformers"\n },\n "nomic-ai/nomic-embed-text-v1": {\n "id": "nomic-ai/nomic-embed-text-v1",\n "batch_size": 1,\n "dims": 768,\n "max_tokens": 2048,\n "name": "Nomic-embed-text",\n "description": "Local, 2,048 tokens, 768 dim",\n "adapter": "transformers"\n }\n};\nvar transformers_settings_config = {\n // "[ADAPTER].legacy_transformers": {\n // name: \'Legacy transformers (no GPU)\',\n // type: "toggle",\n // description: "Use legacy transformers (v2) instead of v3. This may resolve issues if the local embedding isn\'t working.",\n // callback: \'embed_model_changed\',\n // default: true,\n // },\n};\nvar settings_config = {\n // "legacy_transformers": {\n // name: \'Legacy transformers (no GPU)\',\n // type: "toggle",\n // description: "Use legacy transformers (v2) instead of v3. This may resolve issues if the local embedding isn\'t working.",\n // // callback: \'embed_model_changed\',\n // // default: false,\n // },\n};\nvar transformers_v4_iframe_default = {\n class: SmartEmbedTransformersAdapter,\n settings_config\n};\nvar model = null;\nasync function process_message(data) {\n const { method, params, id, iframe_id } = data;\n try {\n let result;\n switch (method) {\n case "init":\n console.log("init");\n break;\n case "load":\n const model_params = { data: params, ...params };\n console.log("load", { model_params });\n model = new SmartEmbedTransformersAdapter(model_params);\n await model.load();\n result = { model_loaded: true, model_config_key: model.active_config_key };\n break;\n case "embed_batch":\n if (!model) throw new Error("Model not loaded");\n result = await model.embed_batch(params.inputs);\n break;\n case "count_tokens":\n if (!model) throw new Error("Model not loaded");\n result = await model.count_tokens(params.input);\n break;\n default:\n throw new Error(`Unknown method: ${method}`);\n }\n return { id, result, iframe_id };\n } catch (error) {\n console.error("Error processing message:", error);\n return { id, error: get_error_message(error), iframe_id };\n }\n}\n__name(process_message, "process_message");\nprocess_message({ method: "init" });\nexport {\n DEVICE_CONFIGS,\n SmartEmbedTransformersAdapter,\n transformers_v4_iframe_default as default,\n settings_config,\n transformers_defaults,\n transformers_models,\n transformers_settings_config\n};\n'; // node_modules/obsidian-smart-env/src/adapters/embedding-model/transformers_v4_iframe.js var retryable_webgpu_error_patterns = [ /\bWEBGPU_RETRYABLE_ERROR\b/i, /no available backend found/i, /webgpuinit is not a function/i, /subgroupminsize/i ]; var iframe_timeout_error_code = "IFRAME_REPLY_TIMEOUT"; var iframe_reply_timeout_ms = Object.freeze({ load: 12e4, default: 3e4 }); function is_retryable_webgpu_error(error_message = "") { const normalized_error_message = String(error_message || ""); return retryable_webgpu_error_patterns.some((pattern) => pattern.test(normalized_error_message)); } function is_iframe_timeout_error(error_message = "") { return new RegExp(`\\b${iframe_timeout_error_code}\\b`, "i").test(String(error_message || "")); } function create_iframe_timeout_error(method = "") { const normalized_method = String(method || "unknown"); return new Error(`${iframe_timeout_error_code}: No reply from transformers iframe for "${normalized_method}"`); } function to_error(error) { return error instanceof Error ? error : new Error(String(error || "Unknown error")); } var TransformersIframeEmbeddingModelAdapter = class extends SmartEmbedTransformersIframeAdapter { constructor(model) { super(model); const old_connector = this.connector; this._old_connector = old_connector; this.connector = transformers_v4_iframe_default; this._disable_webgpu = false; this._reload_in_v4_promise = null; this._reload_without_webgpu_promise = null; this._reload_with_v3_promise = null; this._using_v3_connector = false; console.log("transformers iframe connector", this.model); } get use_gpu() { if (this._disable_webgpu) return false; if (typeof this.model?.data?.use_gpu === "boolean") { return this.model.data.use_gpu; } return void 0; } get models() { return { "TaylorAI/bge-micro-v2": { "id": "TaylorAI/bge-micro-v2", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "BGE-micro-v2 (fastest)", "description": "Local, 512 tokens, 384 dim (recommended)", "adapter": "transformers" }, "Snowflake/snowflake-arctic-embed-xs": { "id": "Snowflake/snowflake-arctic-embed-xs", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "Snowflake Arctic Embed XS (fast)", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" }, "Xenova/multilingual-e5-small": { "id": "Xenova/multilingual-e5-small", "batch_size": 1, "dims": 384, "max_tokens": 512, "name": "Multilingual E5 Small", "description": "Local, 512 tokens, 384 dim", "adapter": "transformers" } }; } get_message_timeout_ms(method = "") { return iframe_reply_timeout_ms[method] || iframe_reply_timeout_ms.default; } clear_message_timeout(queue_entry = null) { if (!queue_entry?.timeout_id) return; clearTimeout(queue_entry.timeout_id); queue_entry.timeout_id = null; } /** * Store method/params so a recoverable iframe error can be retried transparently. * @protected * @param {string} method * @param {Object} params * @returns {Promise} */ async _send_message(method, params, message_options = {}) { return new Promise((resolve, reject) => { const id = `${this.message_prefix}${this.message_id++}`; const queue_entry = { resolve, reject, method, params, retried_in_v4: Boolean(message_options.retried_in_v4), retried_without_webgpu: Boolean(message_options.retried_without_webgpu), fell_back_to_v3: Boolean(message_options.fell_back_to_v3), timeout_id: null }; queue_entry.timeout_id = setTimeout(() => { if (!this.message_queue[id]) return; const timeout_error = create_iframe_timeout_error(method); this._handle_message_result(id, null, timeout_error.message); }, this.get_message_timeout_ms(method)); this.message_queue[id] = queue_entry; try { this._post_message({ method, params, id }); } catch (error) { this.clear_message_timeout(queue_entry); delete this.message_queue[id]; reject(to_error(error)); } }); } should_retry_without_webgpu(error, queue_entry = {}) { if (this.use_gpu === false) return false; if (queue_entry.retried_without_webgpu) return false; return is_retryable_webgpu_error(error) || is_iframe_timeout_error(error); } should_retry_in_v4(error, queue_entry = {}) { if (queue_entry.method === "load") return false; if (queue_entry.retried_in_v4) return false; if (queue_entry.retried_without_webgpu) return false; if (this._using_v3_connector) return false; if (is_retryable_webgpu_error(error)) return false; if (is_iframe_timeout_error(error)) return false; return true; } can_fallback_to_v3(queue_entry = {}) { return Boolean(this._old_connector) && !this._using_v3_connector && !queue_entry.fell_back_to_v3; } should_fallback_to_v3(error, queue_entry = {}) { if (!this.can_fallback_to_v3(queue_entry)) return false; return Boolean( queue_entry.method === "load" || queue_entry.retried_in_v4 || queue_entry.retried_without_webgpu || this._disable_webgpu || is_iframe_timeout_error(error) ); } async reload_iframe_in_v4() { if (this._reload_in_v4_promise) { return this._reload_in_v4_promise; } this.state = "loading"; this.model.model_loaded = false; this.model.load_result = null; this._reload_in_v4_promise = this.load().finally(() => { this._reload_in_v4_promise = null; }); return this._reload_in_v4_promise; } async reload_iframe_without_webgpu() { if (this._reload_without_webgpu_promise) { return this._reload_without_webgpu_promise; } this._disable_webgpu = true; this.state = "loading"; this.model.model_loaded = false; this.model.load_result = null; this._reload_without_webgpu_promise = this.load().finally(() => { this._reload_without_webgpu_promise = null; }); return this._reload_without_webgpu_promise; } async reload_iframe_with_v3() { if (this._reload_with_v3_promise) { return this._reload_with_v3_promise; } if (!this._old_connector) { throw new Error("Missing transformers v3 iframe connector"); } this._using_v3_connector = true; this.connector = this._old_connector; this.state = "loading"; this.model.model_loaded = false; this.model.load_result = null; this._reload_with_v3_promise = this.load().finally(() => { this._reload_with_v3_promise = null; }); return this._reload_with_v3_promise; } async retry_message_in_v4(queue_entry) { await this.reload_iframe_in_v4(); return await this._send_message(queue_entry.method, queue_entry.params, { retried_in_v4: true, retried_without_webgpu: queue_entry.retried_without_webgpu, fell_back_to_v3: queue_entry.fell_back_to_v3 }); } async retry_message_without_webgpu(queue_entry) { await this.reload_iframe_without_webgpu(); if (queue_entry.method === "load") { return this.model.load_result || { model_loaded: true, webgpu_disabled: true }; } return await this._send_message(queue_entry.method, queue_entry.params, { retried_in_v4: queue_entry.retried_in_v4, retried_without_webgpu: true, fell_back_to_v3: queue_entry.fell_back_to_v3 }); } async fallback_to_v3_and_retry(queue_entry) { queue_entry.fell_back_to_v3 = true; await this.reload_iframe_with_v3(); if (queue_entry.method === "load") { return this.model.load_result || { model_loaded: true, v3_fallback: true }; } return await this._send_message(queue_entry.method, queue_entry.params, { retried_in_v4: queue_entry.retried_in_v4, retried_without_webgpu: queue_entry.retried_without_webgpu, fell_back_to_v3: true }); } /** * Handle response message from worker/iframe * ADDS WEBGPU-SPECIFIC RETRY BEFORE FALLBACK TO OLD CONNECTOR (v3.8.0) * @protected * @param {string} id - Message ID * @param {*} result - Response result * @param {Error} [error] - Response error */ _handle_message_result(id, result, error) { if (!id.startsWith(this.message_prefix)) return; if (result?.model_loaded) { console.log("model loaded"); this.state = "loaded"; this.model.model_loaded = true; this.model.load_result = result; } const queue_entry = this.message_queue[id]; if (!queue_entry) return; this.clear_message_timeout(queue_entry); if (error) { if (this.should_retry_without_webgpu(error, queue_entry)) { queue_entry.retried_without_webgpu = true; delete this.message_queue[id]; console.warn("Retrying transformers v4 iframe without WebGPU due to recoverable error:", error); this.retry_message_without_webgpu(queue_entry).then((retry_result) => { queue_entry.resolve(retry_result); }).catch((retry_error) => { if (this.should_fallback_to_v3(retry_error, queue_entry)) { console.warn("Falling back to transformers v3 iframe connector after v4 CPU retry failed:", retry_error); this.fallback_to_v3_and_retry(queue_entry).then((fallback_result) => { queue_entry.resolve(fallback_result); }).catch((fallback_error) => { queue_entry.reject(to_error(fallback_error)); }); return; } queue_entry.reject(to_error(retry_error)); }); return; } if (this.should_retry_in_v4(error, queue_entry)) { queue_entry.retried_in_v4 = true; delete this.message_queue[id]; console.warn("Retrying transformers v4 iframe after hard non-load error:", error); this.retry_message_in_v4(queue_entry).then((retry_result) => { queue_entry.resolve(retry_result); }).catch((retry_error) => { if (this.should_fallback_to_v3(retry_error, queue_entry)) { console.warn("Falling back to transformers v3 iframe connector after bounded v4 retry failed:", retry_error); this.fallback_to_v3_and_retry(queue_entry).then((fallback_result) => { queue_entry.resolve(fallback_result); }).catch((fallback_error) => { queue_entry.reject(to_error(fallback_error)); }); return; } queue_entry.reject(to_error(retry_error)); }); return; } if (this.should_fallback_to_v3(error, queue_entry)) { delete this.message_queue[id]; console.warn("Falling back to transformers v3 iframe connector due to error:", error); this.fallback_to_v3_and_retry(queue_entry).then((fallback_result) => { queue_entry.resolve(fallback_result); }).catch((fallback_error) => { queue_entry.reject(to_error(fallback_error)); }); return; } queue_entry.reject(to_error(error)); delete this.message_queue[id]; return; } queue_entry.resolve(result); delete this.message_queue[id]; } }; var transformers_v4_iframe_default2 = { class: TransformersIframeEmbeddingModelAdapter, settings_config: settings_config3 }; // node_modules/obsidian-smart-env/src/collections/embedding_models.js embedding_models_default.providers = { transformers: transformers_v4_iframe_default2 }; var embedding_models_default2 = embedding_models_default; // node_modules/obsidian-smart-env/src/items/lookup_list.js var LookupList = class extends CollectionItem { static key = "lookup_list"; static get defaults() { return { data: {} }; } async pre_process(params) { if (typeof this.actions.lookup_list_pre_process === "function") { await this.actions.lookup_list_pre_process(params); } } async get_results(params = {}) { await this.pre_process(params); if (this.env.log_perf) this.start_ms = Date.now(); let results = this.filter_and_score(params); if (this.env.log_perf) { this.end_ms = Date.now(); console.log(`filter_and_score(${params.score_algo_key}) took ${this.end_ms - this.start_ms} ms (Date.now)`); } if (this.should_post_process) results = await this.post_process(results, params); this.emit_event("lookup:get_results"); return results; } filter_and_score(params = {}) { const collection = this.env[params.results_collection_key] || this.env[this.collection.results_collection_key]; const score_errors = []; const { results: raw_results } = Object.values(collection.items).reduce((acc, target) => { const scored = target.filter_and_score(params); if (!scored?.score) { if (scored?.error) score_errors.push(scored.error); return acc; } results_acc(acc, scored, params.limit || 20); return acc; }, { min: 0, results: /* @__PURE__ */ new Set() }); const results = Array.from(raw_results).sort(sort_by_score_descending); if (!results.length) return results; while (!results.some((r) => r.score > 0.5)) { results.forEach((r) => r.score *= 2); } return results; } async post_process(results, params = {}) { return results; } get should_post_process() { return this.settings.lookup_post_process && this.settings.lookup_post_process !== "none"; } // for compatibility with v3 connections list item get item() { return this; } }; // node_modules/obsidian-smart-env/src/collections/lookup_lists.js var settings_config6 = { results_collection_key: { name: "Lookup results type", type: "dropdown", description: "Choose whether results should be sources or blocks.", option_1: "smart_sources|Sources", option_2: "smart_blocks|Blocks", options_callback: (scope) => { const options = [ { value: "smart_sources", name: "Sources" } ]; if (scope.env.smart_blocks) { options.push({ value: "smart_blocks", name: "Blocks" }); } return options; } } }; var LookupLists = class extends Collection { static get default_settings() { return { results_collection_key: "smart_blocks", score_algo_key: "similarity", results_limit: 20 }; } static version = 0.01; new_item({ query, filter }) { if (!query || typeof query !== "string" || !query.trim()) { throw new Error("LookupLists.new_item requires a non-empty query string."); } const date = format_ymd(/* @__PURE__ */ new Date()); const hash = murmur_hash_32_alphanumeric(query); const key = `${date}+${hash}`; if (this.items[key]) return this.items[key]; const list = new this.item_type(this.env, { key, query, filter }); this.set(list); return list; } get settings_config() { return { ...settings_config6 }; } process_load_queue() { } get results_collection_key() { const stored_key = this.settings?.results_collection_key; if (this.env.collections?.[stored_key]) return stored_key; return "smart_sources"; } }; function format_ymd(d) { const y = d.getFullYear(); const m = String(d.getMonth() + 1).padStart(2, "0"); const day = String(d.getDate()).padStart(2, "0"); return `${y}-${m}-${day}`; } var lookup_lists_default = { class: LookupLists, collection_key: "lookup_lists", item_type: LookupList, settings_config: settings_config6 }; // node_modules/obsidian-smart-env/node_modules/smart-blocks/utils/get_block_display_name.js function get_block_display_name(key, show_full_path) { const [source_key, ...path_parts] = key.split("#").filter(Boolean); const source_name = get_item_display_name(source_key, show_full_path); if (show_full_path) return [source_name, ...path_parts].join(" > "); const last_heading = path_parts.findLast((part) => part && part[0] !== "{"); return [source_name, last_heading].join(" > "); } // node_modules/obsidian-smart-env/node_modules/smart-blocks/smart_block.js var SmartBlock = class extends SmartEntity { /** * Provides default values for a SmartBlock instance. * @static * @readonly * @returns {Object} The default values. */ static get defaults() { return { data: { text: null, length: 0, last_read: { hash: null, at: 0 } }, _embed_input: "" // Stored temporarily }; } get block_adapter() { if (!this._block_adapter) { this._block_adapter = new this.collection.opts.block_adapters.md(this); } return this._block_adapter; } /** * Initializes the SmartBlock instance by queuing an embed if embedding is enabled. * @returns {void} */ init() { if (this.settings.embed_blocks) super.init(); } /** * Queues the entity for embedding. * @returns {void} */ queue_embed() { this._queue_embed = this.should_embed; } /** * Queues the block for import via the source. * @returns {void} */ queue_import() { this.source?.queue_import(); } /** * Prepares the embed input for the SmartBlock by reading content and generating a hash. * @async * @returns {Promise} The embed input string or `false` if already embedded. */ async get_embed_input(content = null) { if (typeof this._embed_input !== "string" || !this._embed_input.length) { if (!content) content = await this.read(); this._embed_input = this.breadcrumbs + "\n" + content; } return this._embed_input; } // CRUD /** * @method read * @description Reads the block content by delegating to the block adapter. * @async * @returns {Promise} The block content. */ async read() { try { return await this.block_adapter.read(); } catch (e) { if (e.message.includes("BLOCK NOT FOUND")) { return 'BLOCK NOT FOUND (run "Prune" to remove)'; } else { throw e; } } } /** * Filters block using base key filters and optional source frontmatter include/exclude filters. * @param {Object} [filter_opts={}] * @param {Object} [filter_opts.frontmatter] * @returns {boolean} */ filter(filter_opts = {}) { if (!super.filter(filter_opts)) return false; if (!filter_opts.frontmatter) return true; return filter_by_frontmatter(this.source?.metadata || {}, filter_opts.frontmatter); } /** * @method append * @description Appends content to this block by delegating to the block adapter. * @async * @param {string} content * @returns {Promise} */ async append(content) { await this.block_adapter.append(content); this.queue_save(); } /** * @method update * @description Updates the block content by delegating to the block adapter. * @async * @param {string} new_block_content * @param {Object} [opts={}] * @returns {Promise} */ async update(new_block_content, opts = {}) { await this.block_adapter.update(new_block_content, opts); this.queue_save(); } /** * @method remove * @description Removes the block by delegating to the block adapter. * @async * @returns {Promise} */ async remove() { await this.block_adapter.remove(); this.queue_save(); } /** * @method move_to * @description Moves the block to another location by delegating to the block adapter. * @async * @param {string} to_key * @returns {Promise} */ async move_to(to_key) { await this.block_adapter.move_to(to_key); this.queue_save(); } get_display_name(params = {}) { return this.block_adapter?.get_display_name(params); } // Getters /** * Retrieves the breadcrumbs representing the block's path within the source. * @readonly * @returns {string} The breadcrumbs string. */ get breadcrumbs() { return this.key.split("/").join(" > ").split("#").slice(0, -1).join(" > ").replace(".md", ""); } /** * Determines if the block is excluded from embedding based on headings. * @readonly * @returns {boolean} `true` if excluded, `false` otherwise. */ get excluded() { const block_headings = this.path.split("#").slice(1); if (this.source_collection.excluded_headings.some((heading) => block_headings.includes(heading))) return true; return this.source?.excluded; } /** * Retrieves the file path of the SmartSource associated with the block. * @readonly * @returns {string} The file path. */ get file_path() { return this.source?.file_path; } /** * Retrieves the file type of the SmartSource associated with the block. * @readonly * @returns {string} The file type. */ get file_type() { return this.source.file_type; } /** * Retrieves the folder path of the block. * @readonly * @returns {string} The folder path. */ get folder() { return this.path.split("/").slice(0, -1).join("/"); } /** * Retrieves the embed link for the block. * @readonly * @returns {string} The embed link. */ get embed_link() { return `![[${this.link}]]`; } /** * Determines if the block has valid line range information. * @readonly * @returns {boolean} `true` if the block has both start and end lines, `false` otherwise. */ get has_lines() { return this.lines && this.lines.length === 2; } /** * Determines if the entity is a block based on its key. * @readonly * @returns {boolean} `true` if it's a block, `false` otherwise. */ get is_block() { return this.key.includes("#"); } /** * Determines if the block is gone (i.e., the source file or block data no longer exists). * @readonly * @returns {boolean} `true` if gone, `false` otherwise. */ get is_gone() { if (!this.source?.file) return true; if (!this.source?.data?.blocks?.[this.sub_key]) return true; return false; } get last_read() { return this.data.last_read; } /** * Retrieves the sub-key of the block. * @readonly * @returns {string} The sub-key. */ get sub_key() { return "#" + this.key.split("#").slice(1).join("#"); } /** * Retrieves the lines range of the block. * @readonly * @returns {Array|undefined} An array containing the start and end lines or `undefined` if not set. */ // get lines() { return this.source?.data?.blocks?.[this.sub_key]; } get lines() { return this.data.lines; } /** * Retrieves the starting line number of the block. * @readonly * @returns {number|undefined} The starting line number or `undefined` if not set. */ get line_start() { return this.lines?.[0]; } /** * Retrieves the ending line number of the block. * @readonly * @returns {number|undefined} The ending line number or `undefined` if not set. */ get line_end() { return this.lines?.[1]; } /** * Retrieves the link associated with the block, handling page numbers if present. * @readonly * @deprecated was specific to PDFs and removed this sort of PDF handling * @returns {string} The block link. */ get link() { if (/^.*page\s*(\d+).*$/i.test(this.sub_key)) { const number = this.sub_key.match(/^.*page\s*(\d+).*$/i)[1]; return `${this.source.path}#page=${number}`; } else { return this.source?.path || "MISSING SOURCE"; } } // uses data.lines to get next block get next_block() { if (!this.data.lines) return null; const next_line = this.data.lines[1] + 1; return this.source.blocks?.find((block) => next_line === block.data?.lines?.[0]); } /** * Retrieves the paths of outlinks from the block. * @readonly * @returns {Array} An array of outlink paths. */ get outlinks() { return this.source.outlinks; } /** * Retrieves the path of the SmartBlock. * @readonly * @returns {string} The path of the SmartBlock. */ get path() { return this.key; } /** * Determines if the block should be embedded based on its coverage and size. * @readonly * @returns {boolean} `true` if it should be embedded, `false` otherwise. */ get should_embed() { try { const source_hash = this.source?.data?.last_read?.hash; const cached_should_embed = this._should_embed_cache; if (source_hash != null && cached_should_embed?.hash === source_hash) { return cached_should_embed.value; } const min_chars = this.settings?.min_chars; let should_embed = true; if (min_chars && this.size < min_chars) { should_embed = false; } else { const match_line_start = this.line_start + 1; const match_line_end = this.line_end; const blocks = this.source?.data?.blocks; const prefix = this.sub_key + "#"; let has_line_start = null; let has_line_end = null; if (blocks) { for (const key in blocks) { if (!Object.prototype.hasOwnProperty.call(blocks, key)) continue; if (!key.startsWith(prefix)) continue; const range = blocks[key]; if (range[0] === match_line_start) has_line_start = key; if (range[1] === match_line_end) has_line_end = key; if (has_line_start && has_line_end) break; } } if (has_line_start && has_line_end) { const start_block = this.collection.get(this.source_key + has_line_start); if (start_block?.should_embed) { const end_block = this.collection.get(this.source_key + has_line_end); if (end_block?.should_embed) should_embed = false; } } } if (source_hash != null) { this._should_embed_cache = { hash: source_hash, value: should_embed }; } return should_embed; } catch (e) { console.error(e, e.stack); console.error(`Error getting should_embed for ${this.key}: ` + JSON.stringify(e || {}, null, 2)); } } /** * Retrieves the size of the SmartBlock. * @readonly * @returns {number} The size of the SmartBlock. */ get size() { return this.data.size; } /** * Retrieves the SmartSource associated with the block. * @readonly * @returns {import("smart-sources").SmartSource} The associated SmartSource instance. */ get source() { return this.source_collection.get(this.source_key); } /** * Retrieves the SmartSources collection instance. * @readonly * @returns {import("smart-sources").SmartSources} The SmartSources collection. */ get source_collection() { return this.env.smart_sources; } get source_key() { return this.key.split("#")[0]; } get sub_blocks() { return this.source?.blocks?.filter((block) => block.key.startsWith(this.key + "#") && block.line_start > this.line_start && block.line_end <= this.line_end) || []; } // source dependent get excluded_lines() { return this.source.excluded_lines; } get file() { return this.source.file; } get is_media() { return this.source.is_media; } get mtime() { return this.source.mtime; } // DEPRECATED /** * Retrieves the display name of the block. * @readonly * @deprecated Use `get_item_display_name()` as platform-specific util. Move to sub-class in obsidian-smart-env to use respective util and settings (2026-03-27). * @returns {string} The display name. */ get name() { return get_block_display_name( this.key, this.env.settings.smart_view_filter?.show_full_path // This probably does nothing (2026-03-27): see updated settings ); } /** * @deprecated Use `source` instead. Removing after 2025-09-01. * @readonly * @returns {SmartSource} The associated SmartSource instance. */ get note() { return this.source; } /** * @deprecated Use `source.key` instead. Removing after 2025-09-01. * @readonly * @returns {string} The source key. */ get note_key() { return this.key.split("#")[0]; } }; // node_modules/obsidian-smart-env/src/utils/get_block_display_name.js function get_block_display_name2(item, settings = {}) { if (!item?.key) return ""; const show_full_path = settings.show_full_path ?? true; if (show_full_path) { return item.key.replace(/#/g, " > ").replace(/\//g, " > "); } const pcs = []; const [source_key, ...block_parts] = item.key.split("#"); const filename = source_key.split("/").pop(); pcs.push(filename); if (block_parts.length) { const last = block_parts[block_parts.length - 1]; if (last.startsWith("{") && last.endsWith("}")) { block_parts.pop(); pcs.push(block_parts.pop()); if (item.lines) pcs.push(`Lines: ${item.lines.join("-")}`); } else { pcs.push(block_parts.pop()); } } return pcs.filter(Boolean).join(" > "); } // node_modules/obsidian-smart-env/src/items/smart_block.js var SmartBlock2 = class extends SmartBlock { /** * @param {Object} params - Parameters for display settings * @param {boolean} params.show_full_path */ get_display_name(params = {}) { const display_settings = { ...this.env?.settings?.smart_view_filter || {}, // DEPRECATED? settings scope ...params }; return get_block_display_name2(this, display_settings); } // DEPRECATED /** * @deprecated avoid view-logic in Collection/Item AND prefix display_ where used anyway */ get name() { return this.get_display_name(); } }; // node_modules/obsidian-smart-env/node_modules/smart-blocks/smart_blocks.js var SmartBlocks = class extends SmartEntities { /** * Initializes the SmartBlocks instance. Currently muted as processing is handled by SmartSources. * @returns {void} */ init() { } get fs() { return this.env.smart_sources.fs; } /** * Retrieves the embedding model associated with the SmartSources collection. * @readonly * @returns {Object|undefined} The embedding model instance or `undefined` if not set. */ get embed_model() { return this.source_collection?.embed_model; } /** * Retrieves the embedding model key from the SmartSources collection. * @readonly * @returns {string|undefined} The embedding model key or `undefined` if not set. */ get embed_model_key() { return this.source_collection?.embed_model_key; } /** * Calculates the expected number of blocks based on the SmartSources collection. * @readonly * @returns {number} The expected count of blocks. */ get expected_blocks_ct() { return Object.values(this.source_collection.items).reduce((acc, item) => acc += Object.keys(item.data.blocks || {}).length, 0); } /** * Retrieves the notices system from the environment. * @readonly * @returns {Object} The notices object. */ get notices() { return this.env.smart_connections_plugin?.notices || this.env.main?.notices; } /** * Retrieves the settings configuration for SmartBlocks. * @readonly * @returns {Object} The settings configuration object. */ get settings_config() { return this.process_settings_config({ "embed_blocks": { name: "Embed blocks", type: "toggle", description: "Blocks represent parts/sections of notes. Get more granular results.", default: true }, ...super.settings_config }); } get data_dir() { return "multi"; } /** * Retrieves the SmartSources collection instance. * @readonly * @returns {SmartSources} The SmartSources collection. */ get source_collection() { return this.env.smart_sources; } /** * Processes the embed queue. Currently handled by SmartSources, so this method is muted. * @async * @returns {Promise} */ async process_embed_queue() { } /** * Processes the load queue. Currently muted as processing is handled by SmartSources. * @async * @returns {Promise} */ async process_load_queue() { } // TEMP: Methods in sources not implemented in blocks /** * @async * @abstract * @throws {Error} Throws an error indicating the method is not implemented. * @returns {Promise} */ async prune() { throw "Not implemented: prune"; } /** * @throws {Error} Throws an error indicating the method is not implemented. * @abstract * @returns {void} */ build_links_map() { throw "Not implemented: build_links_map"; } /** * @async * @abstract * @throws {Error} Throws an error indicating the method is not implemented. * @returns {Promise} */ async refresh() { throw "Not implemented: refresh"; } /** * @async * @abstract * @throws {Error} Throws an error indicating the method is not implemented. * @returns {Promise} */ async search() { throw "Not implemented: search"; } /** * @async * @abstract * @throws {Error} Throws an error indicating the method is not implemented. * @returns {Promise} */ async run_refresh() { throw "Not implemented: run_refresh"; } /** * @async * @abstract * @throws {Error} Throws an error indicating the method is not implemented. * @returns {Promise} */ async run_force_refresh() { throw "Not implemented: run_force_refresh"; } // clear expired blocks // TODO/future: replaced by storing block data within source data async cleanup_blocks() { const expired_blocks = Object.values(this.items).filter((i) => i.is_gone); console.log(`Removing ${expired_blocks.length} expired blocks`); expired_blocks.forEach((i) => i.delete()); await this.process_save_queue(); expired_blocks.forEach((i) => { delete this.items[i.key]; }); this.emit_event("blocks:cleaned", { expired_blocks_ct: expired_blocks.length }); } }; // node_modules/obsidian-smart-env/node_modules/smart-blocks/adapters/data/ajson_multi_file.js var AjsonMultiFileBlocksDataAdapter = class extends AjsonMultiFileCollectionDataAdapter { ItemDataAdapter = AjsonMultiFileBlockDataAdapter; /** * Transforms the item key into a safe filename. * Replaces spaces, slashes, and dots with underscores. * @returns {string} safe file name */ // get_data_file_name(key) { // return super.get_data_file_name(key.split('#')[0]); // } get_data_file_name(key) { return key.split("#")[0].replace(/[\s\/\.]/g, "_").replace(".md", ""); } /** * Process any queued save operations. * @async * @returns {Promise} */ async process_save_queue() { this.collection.emit_event("collection:save_started"); const save_queue = Object.values(this.collection.items).filter((item) => item._queue_save); console.log(`Saving ${this.collection.collection_key}: ${save_queue.length} items`); const time_start = Date.now(); const save_files = Object.entries(save_queue.reduce((acc, item) => { const file_name = this.get_item_data_path(item.key); acc[file_name] = acc[file_name] || []; acc[file_name].push(item); return acc; }, {})); for (let i = 0; i < save_files.length; i++) { const [file_name, items] = save_files[i]; await this.fs.append( file_name, items.map((item) => this.get_item_ajson(item)).join("\n") + "\n" ); items.forEach((item) => item._queue_save = false); } console.log(`Saved ${this.collection.collection_key} in ${Date.now() - time_start}ms`); this.collection.emit_event("collection:save_completed"); } process_load_queue() { console.log(`Skipping loading ${this.collection.collection_key}...`); } }; var AjsonMultiFileBlockDataAdapter = class extends AjsonMultiFileItemDataAdapter { }; // node_modules/obsidian-smart-env/node_modules/smart-blocks/adapters/_adapter.js var BlockContentAdapter = class { /** * @constructor * @param {import('smart-blocks').SmartBlock} item - The SmartBlock instance this adapter operates on. * The `item` should at least provide `data` and references to its parent source. */ constructor(item) { this.item = item; } /** * @async * @method read * @abstract * @returns {Promise} The content of the block. * @throws {Error} If not implemented by subclass. */ async read() { throw new Error("Not implemented"); } /** * @async * @method append * @abstract * @param {string} content Content to append to the block. * @returns {Promise} * @throws {Error} If not implemented by subclass. */ async append(content) { throw new Error("Not implemented"); } /** * @async * @method update * @abstract * @param {string} new_content The new content for the block. * @param {Object} [opts={}] Additional update options. * @returns {Promise} * @throws {Error} If not implemented by subclass. */ async update(new_content, opts = {}) { throw new Error("Not implemented"); } /** * @async * @method remove * @abstract * @returns {Promise} * @throws {Error} If not implemented by subclass. */ async remove() { throw new Error("Not implemented"); } /** * @async * @method move_to * @abstract * @param {string} to_key The destination key (source or block reference). * @returns {Promise} * @throws {Error} If not implemented by subclass. */ async move_to(to_key) { throw new Error("Not implemented"); } /** * @method get_display_name * @abstract * @param {Object} params Parameters for display name generation. * @returns {string} The display name of the block. * @throws {Error} If not implemented by subclass. */ get_display_name(params) { throw new Error("Not implemented"); } /** * @name data * @type {Object} * @readonly * @description Access the block’s data object. Useful for updating metadata like line references or hashes. */ get data() { return this.item.data; } /** * @async * @method update_last_read * @param {string} content The current content of the block. * @returns {Promise} * @description Update the block’s `last_read` hash and timestamp based on the given content. */ async update_last_read(content) { this.data.last_read = { hash: this.create_hash(content), at: Date.now() }; } /** * @method create_hash * @param {string} content The content to hash. * @returns {Promise} The computed hash of the content. * @description Hash the block content to detect changes and prevent unnecessary re-embeddings. */ create_hash(content) { return murmur_hash_32_alphanumeric(content); } }; // node_modules/obsidian-smart-env/node_modules/smart-blocks/adapters/markdown_block.js var MarkdownBlockContentAdapter = class extends BlockContentAdapter { /** * Read the content of the block. * @async * @returns {Promise} The block content as a string. * @throws {Error} If the block cannot be found. */ async read() { const source_content = await this.item.source?.read(); if (!source_content) { console.warn(`BLOCK NOT FOUND: ${this.item.key} has no source content.`); return ""; } const content = this._extract_block(source_content); this.update_last_read(content); return content; } /** * Append content to the existing block. * This method inserts additional lines after the block's end, then re-parses the file to update line references. * @async * @param {string} content Content to append to the block. * @returns {Promise} * @throws {Error} If the block cannot be found. */ async append(content) { let full_content = await this.item.source.read(); const { line_start, line_end } = this.item; if (!line_start || !line_end) { throw new Error(`Cannot append to block ${this.item.key}: invalid line references.`); } const lines = full_content.split("\n"); lines.splice(line_end, 0, "", content); const updated_content = lines.join("\n"); await this.item.source._update(updated_content); await this._reparse_source(); } /** * Update the block with new content, replacing its current lines. * @async * @param {string} new_content New content for the block. * @param {Object} [opts={}] Additional options. * @returns {Promise} * @throws {Error} If the block cannot be found. */ async update(new_content, opts = {}) { let full_content = await this.item.source.read(); const { line_start, line_end } = this.item; if (!line_start || !line_end) { throw new Error(`Cannot update block ${this.item.key}: invalid line references.`); } const lines = full_content.split("\n"); const updated_lines = [ ...lines.slice(0, line_start - 1), ...new_content.split("\n"), ...lines.slice(line_end) ]; const updated_content = updated_lines.join("\n"); await this.item.source._update(updated_content); await this._reparse_source(); } /** * Remove the block entirely from the source. * @async * @returns {Promise} * @throws {Error} If the block cannot be found. */ async remove() { let full_content = await this.item.source.read(); const { line_start, line_end } = this.item; if (!line_start || !line_end) { throw new Error(`Cannot remove block ${this.item.key}: invalid line references.`); } const lines = full_content.split("\n"); const updated_lines = [ ...lines.slice(0, line_start - 1), ...lines.slice(line_end) ]; const updated_content = updated_lines.join("\n"); await this.item.source._update(updated_content); await this._reparse_source(); } /** * Move the block to a new location (another source or heading). * This involves reading the block content, removing it from the current source, and appending it to the target. * @async * @param {string} to_key The destination path or entity reference. * @returns {Promise} * @throws {Error} If the block or target is invalid. */ async move_to(to_key) { const content = await this.read(); await this.remove(); const is_block_ref = to_key.includes("#"); let target_source_key = is_block_ref ? to_key.split("#")[0] : to_key; const target_source = this.item.env.smart_sources.get(target_source_key); if (!target_source) { await this.item.env.smart_sources.create(target_source_key, content); return; } if (is_block_ref) { const target_block = this.item.env.smart_blocks.get(to_key); if (target_block) { await target_block.append(content); } else { await target_source.append(content); } } else { await target_source.append(content); } } /** * Extract the block content using current line references from a full source content. * @private * @param {string} source_content Full source file content. * @returns {string} Extracted block content. * @throws {Error} If the block cannot be found. */ _extract_block(source_content) { if (!source_content) { console.warn(`BLOCK NOT FOUND: ${this.item.key} has no source content.`); return ""; } const { line_start, line_end } = this.item; if (!line_start || !line_end) { throw new Error(`BLOCK NOT FOUND: ${this.item.key} has invalid line references.`); } return get_line_range2(source_content, line_start, line_end); } /** * Re-parse the source file after a CRUD operation to update line references for all blocks. * @private * @async * @returns {Promise} */ async _reparse_source() { await this.item.source.import(); } get_display_name(params = {}) { if (!this.item?.key) return ""; const show_full_path = params.show_full_path ?? true; if (show_full_path) { return this.item.key.replace(/#/g, " > ").replace(/\//g, " > "); } const pcs = []; const [source_key, ...block_parts] = this.item.key.split("#"); const filename = source_key.split("/").pop(); pcs.push(filename); if (block_parts.length) { const last = block_parts[block_parts.length - 1]; if (last.startsWith("{") && last.endsWith("}")) { block_parts.pop(); pcs.push(block_parts.pop()); if (this.item.lines) pcs.push(`Lines: ${this.item.lines.join("-")}`); } else { pcs.push(block_parts.pop()); } } return pcs.filter(Boolean).join(" > "); } }; // node_modules/obsidian-smart-env/src/collections/smart_blocks.js var smart_blocks_default = { class: SmartBlocks, version: SmartBlocks.version, item_type: SmartBlock2, data_adapter: AjsonMultiFileBlocksDataAdapter, collection_key: "smart_blocks", block_adapters: { "md": MarkdownBlockContentAdapter, "txt": MarkdownBlockContentAdapter, "excalidraw.md": MarkdownBlockContentAdapter // "canvas": MarkdownBlockContentAdapter, } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/smart_context.js var remove_context_item_data = (context_items, key) => { if (!key || !context_items?.[key]) return false; const item_data = context_items[key]; if (item_data.folder || item_data.from_named_context) { if (item_data.exclude) { delete context_items[key]; return true; } item_data.exclude = true; return true; } delete context_items[key]; return true; }; var SmartContext = class extends CollectionItem { static version = "2.0.2"; static get defaults() { return { data: { key: "", context_items: {}, context_opts: {} // REMOVE? } }; } // queue_save to debounce process save queue queue_save() { super.queue_save(); this.collection.queue_save(); } /** * add_item * @param {string|object} item */ add_item(item, params = {}) { const { emit_updated = true } = params; let key; if (typeof item === "object") { key = item.key || item.path; } else { key = item; } const existing = this.data.context_items[key]; const context_item = { d: 0, at: Date.now(), ...existing || {}, ...typeof item === "object" ? item : {} }; if (!key) return console.error("SmartContext: add_item called with invalid item", item); this.data.context_items[key] = context_item; this.queue_save(); if (emit_updated) this.emit_event("context:updated", { add_item: key }); } /** * add_items * @param {string[]|object[]} items */ 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 * @param {string} key * @param {object} params * @param {boolean} params.emit_updated */ 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 * @param {string[]|string} keys * @param {object} params * @param {boolean} params.emit_updated * @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; } clear_all() { this.data.context_items = {}; this.queue_save(); this.emit_event("context:updated", { cleared: true }); } get context_item_keys() { return Object.entries(this.data?.context_items || {}).filter(([, item_data]) => !item_data.exclude).map(([key]) => key); } get excluded_context_item_keys() { return Object.entries(this.data?.context_items || {}).filter(([, item_data]) => item_data?.exclude).map(([key]) => key); } get key() { if (!this.data.key) { this.data.key = Date.now().toString(); } return this.data.key; } get has_context_items() { return this.item_count > 0; } get has_excluded_context_items() { return this.excluded_item_count > 0; } get excluded_item_count() { return this.excluded_context_item_keys.length; } get name() { return this.data.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(); } get size() { let size = 0; Object.values(this.context_items.items || {}).forEach((item) => { if (item.size) size += item.size; }); return size; } get item_count() { return Object.entries(this.data?.context_items || {}).filter(([, item_data]) => !item_data.exclude).length; } // v3 async get_text(params = {}) { const segments = []; const context_items = this.context_items.filter(params.filter).sort((a, b) => a.data.d - b.data.d); console.log("get_text context_items", context_items); for (const item of context_items) { if (item.is_media) continue; const item_text = await item.get_text(); if (typeof item_text === "string") segments.push(item_text); else this.emit_get_text_error(item, item_text); } const context_items_text = segments.join("\n"); if (typeof this.actions.context_merge_template === "function") { return await this.actions.context_merge_template(context_items_text, { context_items }); } return context_items_text; } /** * Build a ContextItems collection on demand. * * The builder sometimes needs excluded entries in addition to active items, * so this helper accepts the same params consumed by * ContextItems.load_from_data(...). * * @param {object} [params={}] * @returns {import('smart-contexts/context_items.js').ContextItems} */ 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; } get context_items() { return this.get_context_items(); } /** * @private */ 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 */ 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 */ 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 : {} }); } }; // node_modules/obsidian-smart-env/node_modules/smart-contexts/smart_contexts.js var SmartContexts = class extends Collection { static version = "2.0.1"; /** * new_context * @param {object} data * @param {object} opts * @param {string[]} opts.add_items * @returns {SmartContext} */ 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; } get_named_context(name) { return this.filter((ctx) => ctx.data?.name === name)[0]; } /** * Default settings for all SmartContext items in this collection. * @readonly */ static get default_settings() { return { template_preset: "xml_structured", template_before: "\n{{FILE_TREE}}", template_after: "" }; } 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/items/smart_context.js var SmartContext2 = class extends SmartContext { get named_contexts() { return Object.entries(this.data?.context_items || {}).filter(([name, item_data]) => item_data?.named_context).map(([name, item_data]) => this.env.smart_contexts.get_named_context(item_data?.key || name)).filter(Boolean); } /** * @param {string} target_path */ remove_by_path(target_path, params = {}) { return this.remove_by_paths([ { path: target_path, ...params.folder ? { folder: true } : {} } ], params); } /** * Remove multiple paths from context in one data pass. * * @param {Array} target_paths * @param {object} [params={}] * @param {boolean} [params.emit_updated=true] * @param {boolean} [params.queue_save=true] * @returns {string[]} */ remove_by_paths(target_paths = [], params = {}) { const { emit_updated = true, queue_save = true } = params; const targets = normalize_remove_targets(target_paths, params); if (!targets.length) return []; const context_items = this.data?.context_items || {}; const remove_keys = []; Object.keys(context_items).forEach((key) => { const target = targets.find((target2) => item_matches_remove_path(key, target2.norm_key)); if (!target) return; remove_keys.push(key); }); remove_keys.forEach((key) => { delete context_items[key]; }); if (!remove_keys.length) return []; if (emit_updated) { this.emit_event("context:updated", { removed_key: targets[0].path, removed_keys: targets.map((target) => target.path), ...targets.some((target) => target.folder) ? { folder: true } : {} }); } if (queue_save) this.queue_save(); return remove_keys; } /** * add_item * this override adds implementation-specific (source/block pattern) logic to remove redundant block items when a parent source is added * @param {string|object} item */ add_item(item, params = {}) { const { emit_updated = true } = params; let key; if (typeof item === "object") { key = item.key || item.path; } else { key = item; } const existing = this.data.context_items[key]; const context_item = { d: 0, at: Date.now(), ...existing || {}, ...typeof item === "object" ? item : {} }; if (!key) return console.error("SmartContext: add_item called with invalid item", item); const emit_payload = { add_item: key }; const remove_sub_keys = Object.entries(this.data.context_items).filter(([existing_key]) => existing_key !== key && existing_key.startsWith(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); } }; 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; 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; } 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 + "{"); } // node_modules/obsidian-smart-env/src/collections/smart_contexts.js smart_contexts_default2.class = SmartContexts; smart_contexts_default2.version = SmartContexts.version; smart_contexts_default2.item_type = SmartContext2; var smart_contexts_default3 = smart_contexts_default2; // node_modules/obsidian-smart-env/src/utils/format_collection_name.js function format_collection_name(key) { return key.replace(/_/g, " ").replace(/\b\w/g, (c) => c.toUpperCase()); } // node_modules/obsidian-smart-env/src/components/collection_settings.js async function build_html(collection, opts = {}) { const settings_html = Object.entries(collection.settings_config).map(([setting_key, setting_config]) => { if (!setting_config.setting) setting_config.setting = setting_key; return this.render_setting_html(setting_config); }).join("\n"); const html = `

${format_collection_name(collection.collection_key)}

${settings_html}
`; return html; } async function render(collection, opts = {}) { const html = await build_html.call(this, collection, opts); const frag = this.create_doc_fragment(html); await this.render_setting_components(frag, { scope: collection }); if (opts.settings_container) { this.empty(opts.settings_container); opts.settings_container.appendChild(frag.querySelector(".collection-settings")); } else { collection.settings_container = frag.querySelector(".collection-settings-container"); } return collection.settings_container; } // node_modules/obsidian-smart-env/src/utils/register_block_hover_popover.js var import_obsidian10 = 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_obsidian10.Keymap.isModEvent(ev)) { const block = env.smart_blocks.get(block_key); const markdown = await block?.read(); if (markdown) { const popover = new import_obsidian10.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_obsidian10.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_obsidian11 = 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_obsidian11.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_obsidian12 = 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 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_obsidian12.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_html2(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 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} ${origin_badges_html} `; } async function render2(context_item, params = {}) { const html = build_html2(context_item, params); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process.call(this, context_item, container, params); return container; } async function post_process(context_item, container, params = {}) { render_inline_icons(container); const remove_btn = container.querySelector(".sc-context-item-remove"); if (remove_btn) { if (params.remove_disabled === true) { remove_btn.setAttribute("title", "Open the named context to remove this included item."); } remove_btn.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); if (params.remove_disabled === true) { if (typeof params.on_remove_disabled === "function") { params.on_remove_disabled(event, context_item); } return; } if (typeof params.on_remove !== "function") return; if (remove_btn.classList.contains("is-removing")) return; set_remove_pending(remove_btn); try { const result = params.on_remove(event, context_item); if (result && typeof result.catch === "function") { result.catch((error) => { clear_remove_pending(remove_btn); console.warn("Smart Context: Failed to remove context item", error); }); } } catch (error) { clear_remove_pending(remove_btn); throw error; } }); } const name = container.querySelector(".sc-context-item-name"); if (!name) return container; const is_missing = context_item?.exists === false; if (is_missing) { name.setAttribute("title", "Missing source"); } const item_ref = context_item?.item_ref || null; if (item_ref && !is_missing) { name.setAttribute("title", `Hold ${import_obsidian12.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_obsidian13 = 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_obsidian13.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 render3(env, params = {}) { const { event_key = "", event = {}, on_action = null, on_mute = null, on_open_feed = null } = params; const level = get_event_level(event_key, event) || "info"; const title = get_default_notice_title(event_key, event); const details = get_default_notice_details(event, title); const btn_text = to_trimmed_string2(event?.btn_text); const btn_callback = to_trimmed_string2(event?.btn_callback); const help_link = to_trimmed_string2(event?.link); if (typeof document === "undefined") { return get_default_notice_summary(event_key, event); } this.apply_style_sheet?.(default_notification_default); const run_action = typeof on_action === "function" ? on_action : (callback_key) => dispatch_notice_action(env, callback_key, { event_source: "default_notification", source_event_key: event_key, source_event: event }); const run_mute = typeof on_mute === "function" ? on_mute : () => false; const can_open_feed = typeof on_open_feed === "function" || typeof env?.open_notifications_feed_modal === "function"; const can_view_more = can_open_feed && Boolean(event_key); const frag = document.createDocumentFragment(); const wrapper = document.createElement("div"); wrapper.className = "smart-env-default-notice"; wrapper.dataset.level = level; const surface_el = document.createElement("div"); surface_el.className = "smart-env-default-notice__surface"; const icon_el = document.createElement("button"); icon_el.type = "button"; icon_el.className = "smart-env-default-notice__icon clickable-icon"; icon_el.setAttribute("aria-label", "Open events and notifications"); icon_el.setAttribute("title", "Open events and notifications"); icon_el.disabled = !can_open_feed; render_icon(icon_el, level); if (can_open_feed) { icon_el.addEventListener("click", (event_obj) => { event_obj.preventDefault(); event_obj.stopPropagation(); open_notifications_feed(env, { on_open_feed, event_key, event }); }); } const body_el = document.createElement("div"); body_el.className = "smart-env-default-notice__body"; const eyebrow_el = document.createElement("div"); eyebrow_el.className = "smart-env-default-notice__eyebrow"; eyebrow_el.textContent = `${format_level_label(level)}`; const title_el = document.createElement("div"); title_el.className = "smart-env-default-notice__title"; title_el.textContent = title; body_el.appendChild(eyebrow_el); body_el.appendChild(title_el); if (details) { const details_el = document.createElement("div"); details_el.className = "smart-env-default-notice__details"; details_el.textContent = details; body_el.appendChild(details_el); } const should_render_actions = Boolean(btn_text && btn_callback) || Boolean(help_link) || Boolean(event_key); if (should_render_actions) { const actions_el = document.createElement("div"); actions_el.className = "smart-env-default-notice__actions"; if (btn_text && btn_callback) { const button_el = document.createElement("button"); button_el.type = "button"; button_el.className = "smart-env-default-notice__button mod-cta"; button_el.textContent = btn_text; button_el.setAttribute("aria-label", btn_text); button_el.addEventListener("click", () => { run_action(btn_callback); }); actions_el.appendChild(button_el); } if (can_view_more) { const view_more_btn_el = document.createElement("button"); view_more_btn_el.type = "button"; view_more_btn_el.className = "smart-env-default-notice__button"; view_more_btn_el.textContent = "View more"; view_more_btn_el.setAttribute("aria-label", "Open this event in the notifications feed"); view_more_btn_el.addEventListener("click", () => { open_notifications_feed(env, { on_open_feed, event_key, event }); }); actions_el.appendChild(view_more_btn_el); } if (help_link) { const link_el = document.createElement("a"); link_el.className = "smart-env-default-notice__link"; link_el.href = help_link; link_el.textContent = "Learn more"; link_el.target = "_external"; link_el.rel = "noopener noreferrer"; link_el.setAttribute("aria-label", "Learn more about this notification"); actions_el.appendChild(link_el); } if (event_key) { const mute_btn_el = document.createElement("button"); mute_btn_el.type = "button"; mute_btn_el.className = "smart-env-default-notice__mute"; mute_btn_el.textContent = "Mute"; (0, import_obsidian13.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.js async function build_html3(env, opts = {}) { const lines = []; lines.push(`

Collections

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

${format_collection_name(collection_key)}

No valid items.

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

${niceName}

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

`; } const load_time_html = collection.load_time_ms ? `

Load time: ${collection.load_time_ms}ms

` : ""; const state_html = `

State: ${state}

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

${niceName}

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

Total: ${total_items}

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

No items embedded

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

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

` + (stats.missing_embed ? `

Missing embeddings: ${stats.missing_embed}

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

Extraneous embeddings: ${stats.extraneous_embed}

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

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

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

`; } async function render5(env, params = {}) { this.apply_style_sheet(env_status_default); const frag = this.create_doc_fragment(build_html4()); const container = frag.firstElementChild; post_process3.call(this, env, container, params); return container; } async function post_process3(env, container, params = {}) { const { live_updates = true, event = null, event_key = "" } = params; if (event_key) { container.dataset.eventKey = event_key; } if (typeof event?.level === "string" && event.level.trim()) { container.dataset.eventLevel = event.level.trim(); } const title_el = container.querySelector(".smart-env-status-view__title"); const status_el = container.querySelector(".smart-env-status-view__status"); const summary_el = container.querySelector(".smart-env-status-view__summary"); const progress_el = container.querySelector(".smart-env-status-view__progress"); const progress_fill_el = container.querySelector(".smart-env-status-view__progress-fill"); const details_el = container.querySelector(".smart-env-status-view__details"); const actions_el = container.querySelector(".smart-env-status-view__actions"); const render_state = () => { const view_state = get_env_activity_state(env); set_text(title_el, view_state.view_title); set_text(status_el, view_state.view_status, { hide_empty: true }); set_text(summary_el, view_state.view_summary, { hide_empty: true }); render_progress(progress_el, progress_fill_el, view_state); render_details(details_el, view_state.view_details); render_actions(actions_el, env, view_state.view_actions, { event, event_key }); }; let polling_interval = null; const set_polling = (active) => { if (active) { if (polling_interval) return; polling_interval = setInterval(() => { render_state(); set_polling(should_poll_env_activity(env)); }, 1e3); return; } if (!polling_interval) return; clearInterval(polling_interval); polling_interval = null; }; let debounce_timeout = null; const refresh = () => { if (debounce_timeout) clearTimeout(debounce_timeout); debounce_timeout = setTimeout(() => { debounce_timeout = null; render_state(); set_polling(should_poll_env_activity(env)); }, 100); }; render_state(); if (!live_updates) return container; set_polling(should_poll_env_activity(env)); const disposers = []; if (typeof env?.events?.on === "function") { disposers.push(env.events.on("*", refresh)); } disposers.push(() => { set_polling(false); if (debounce_timeout) clearTimeout(debounce_timeout); }); this.attach_disposer(container, disposers); return container; } function set_text(element, value, params = {}) { if (!element) return; const { hide_empty = false } = params; const text = typeof value === "string" ? value : ""; element.textContent = text; if (hide_empty) element.hidden = !text; } function render_progress(progress_el, progress_fill_el, view_state) { const progress_pct = typeof view_state.progress_pct === "number" ? Math.max(0, Math.min(100, view_state.progress_pct)) : null; if (progress_pct === null) { progress_el.hidden = true; progress_fill_el.style.width = "0%"; progress_el.removeAttribute("aria-valuenow"); progress_el.setAttribute("aria-valuemax", "100"); return; } progress_el.hidden = false; progress_fill_el.style.width = `${progress_pct}%`; if (typeof view_state.progress_value === "number") { progress_el.setAttribute("aria-valuenow", String(view_state.progress_value)); } else { progress_el.removeAttribute("aria-valuenow"); } progress_el.setAttribute( "aria-valuemax", String(typeof view_state.progress_total === "number" && view_state.progress_total > 0 ? view_state.progress_total : 100) ); } function render_details(details_el, details = []) { details_el.replaceChildren(); details.filter(Boolean).forEach((detail) => { const detail_el = details_el.ownerDocument.createElement("div"); detail_el.className = "smart-env-status-view__detail"; detail_el.textContent = detail; details_el.appendChild(detail_el); }); details_el.hidden = details_el.childElementCount === 0; } function render_actions(actions_el, env, action_keys = [], params = {}) { actions_el.replaceChildren(); action_keys.forEach((action_key) => { const btn = actions_el.ownerDocument.createElement("button"); btn.type = "button"; btn.className = "smart-env-status-view__btn"; bind_action_button(btn, env, action_key, params); actions_el.appendChild(btn); }); actions_el.hidden = actions_el.childElementCount === 0; } function get_action_label(action_key) { switch (action_key) { case "load_env": return "Load Smart Environment"; case "pause_embed": return "Pause embedding"; case "resume_embed": return "Resume embedding"; case "run_reimport": return "Run re-import"; case "open_notifications": return "Open events feed"; default: return action_key; } } function bind_action_button(btn, env, action_key, params = {}) { const label = get_action_label(action_key); btn.textContent = label; if (["load_env", "resume_embed", "run_reimport"].includes(action_key)) { btn.classList.add("smart-env-status-view__btn--primary"); } btn.addEventListener("click", async () => { await run_action_key(env, action_key, params); }); } async function run_action_key(env, action_key, params = {}) { switch (action_key) { case "load_env": if (typeof env?.start_mobile_env_load === "function") { await env.start_mobile_env_load({ source: "env_status_component", open_progress_view: false, event: params.event, event_key: params.event_key }); return; } await env?.load?.(true); return; case "pause_embed": env?.smart_sources?.entities_vector_adapter?.halt_embed_queue_processing?.(); return; case "resume_embed": env?.smart_sources?.entities_vector_adapter?.resume_embed_queue_processing?.(); return; case "run_reimport": await env?.run_re_import?.(); return; case "open_notifications": env?.open_notifications_feed_modal?.(); return; default: return; } } // node_modules/obsidian-smart-env/src/components/lean_coffee_callout.js var import_obsidian14 = require("obsidian"); function build_html5(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 render6(env, opts = {}) { const html = build_html5.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 render7(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 {\r display: flex;\r flex-direction: column;\r gap: var(--size-4-3);\r padding: var(--size-4-2);\r \r .sc-events-checklist__header {\r display: flex;\r align-items: baseline;\r justify-content: space-between;\r gap: var(--size-4-3);\r }\r \r .sc-events-checklist__title {\r margin: 0;\r font-size: var(--h2-size);\r }\r \r .sc-events-checklist__summary {\r font-size: var(--font-ui-small);\r color: var(--text-normal);\r font-variant-numeric: tabular-nums;\r \r padding: 0.15em 0.6em;\r border-radius: 999px;\r background-color: var(--background-secondary);\r border: 1px solid var(--background-modifier-border);\r white-space: nowrap;\r }\r \r .sc-events-checklist__hint {\r font-size: var(--font-ui-small);\r color: var(--text-muted);\r text-align: right;\r }\r \r .sc-events-checklist__progress {\r height: 6px;\r border-radius: 999px;\r overflow: hidden;\r background-color: var(--background-secondary);\r border: 1px solid var(--background-modifier-border);\r }\r \r .sc-events-checklist__progress-fill {\r height: 100%;\r width: var(--sc-events-checklist-progress, 0%);\r background-color: var(--color-green, var(--color-accent));\r }\r \r .sc-events-checklist__group {\r border-top: 1px solid var(--background-modifier-border);\r padding-top: var(--size-4-3);\r }\r \r .sc-events-checklist__group-title {\r margin: 0 0 var(--size-4-2) 0;\r font-size: var(--h3-size);\r \r display: flex;\r align-items: baseline;\r justify-content: space-between;\r gap: var(--size-4-2);\r }\r \r .sc-events-checklist__group-name {\r display: inline-flex;\r align-items: center;\r min-width: 0;\r }\r \r .sc-events-checklist__group-count {\r font-size: var(--font-ui-small);\r color: var(--text-muted);\r white-space: nowrap;\r font-variant-numeric: tabular-nums;\r flex: 0 0 auto;\r }\r \r /*\r Group completion badge:\r - shows only when every item in the group is checked\r - uses :has() (no JS needed)\r - avoids false positives on empty groups by requiring at least one item\r */\r .sc-events-checklist__group:has(.sc-events-checklist__item):not(:has(.sc-events-checklist__item[data-checked='false'])) {\r .sc-events-checklist__group-name::after {\r content: "DONE";\r \r /* layout */\r display: inline-flex;\r align-items: center;\r justify-content: center;\r margin-left: 0.5em;\r padding: 0.08em 0.55em;\r border-radius: 999px;\r white-space: nowrap;\r vertical-align: middle;\r \r /* typography */\r font-size: 0.65em;\r font-weight: 700;\r letter-spacing: 0.12em;\r text-transform: uppercase;\r \r /* theme vars (with safe fallback) */\r color: var(--color-green, var(--text-accent));\r background-color: var(--background-secondary);\r border: 1px solid var(--background-modifier-border);\r \r /* subtle depth, theme-aware */\r box-shadow:\r 0 0 0 1px var(--background-primary),\r 0 1px 3px rgba(0, 0, 0, 0.25);\r transform: translateY(-0.03em);\r }\r }\r \r .sc-events-checklist__items {\r list-style: none;\r padding: 0;\r margin: 0;\r display: flex;\r flex-direction: column;\r gap: var(--size-2-2);\r }\r \r .sc-events-checklist__item {\r display: flex;\r flex-direction: column;\r gap: 2px;\r padding: 6px 8px;\r border-radius: var(--radius-s);\r \r cursor: pointer;\r border: 1px solid transparent;\r \r transition:\r background-color 120ms ease,\r border-color 120ms ease,\r transform 120ms ease;\r \r &:hover {\r background: var(--background-modifier-hover);\r border-color: var(--background-modifier-border);\r }\r \r &:active {\r transform: translateY(1px);\r }\r \r &:focus-visible {\r outline: 2px solid var(--color-accent);\r outline-offset: 2px;\r background: var(--background-modifier-hover);\r border-color: var(--color-accent);\r }\r }\r \r .sc-events-checklist__label {\r display: flex;\r align-items: flex-start;\r gap: var(--size-2-2);\r cursor: pointer;\r }\r \r .sc-events-checklist__icon {\r display: inline-flex;\r align-items: center;\r justify-content: center;\r margin-top: 2px;\r width: 18px;\r height: 18px;\r flex: 0 0 auto;\r color: var(--text-muted);\r \r svg {\r width: 18px;\r height: 18px;\r }\r }\r \r .sc-events-checklist__milestone {\r line-height: 1.3;\r user-select: text;\r cursor: text;\r }\r \r .sc-events-checklist__item[data-checked='true'] {\r .sc-events-checklist__icon {\r color: var(--color-green, var(--text-accent));\r }\r \r .sc-events-checklist__milestone {\r color: var(--text-normal);\r }\r }\r }\r \r /* 1) Host elements that should get a PRO badge */\r .sc-events-checklist__label.pro-milestone > .sc-events-checklist__milestone {\r position: relative; /* safe default, keeps ::after anchored */\r }\r \r /* 2) The PRO badge itself */\r .sc-events-checklist__label.pro-milestone > .sc-events-checklist__milestone::after {\r content: "PRO";\r \r /* layout */\r display: inline-flex;\r align-items: center;\r justify-content: center;\r margin-left: 0.4em;\r padding: 0.08em 0.55em;\r border-radius: 999px;\r white-space: nowrap;\r vertical-align: middle;\r \r /* typography */\r font-size: 0.7em;\r font-weight: 600;\r letter-spacing: 0.14em;\r text-transform: uppercase;\r line-height: 1;\r \r /* color system: only Obsidian variables */\r background-color: var(--color-accent);\r background-image: linear-gradient(\r 135deg,\r var(--color-accent),\r var(--interactive-accent-hover)\r );\r color: var(--text-on-accent, var(--background-primary));\r border: 1px solid var(--background-modifier-border);\r \r /* subtle separation & depth, theme-aware */\r box-shadow:\r 0 0 0 1px var(--background-primary),\r 0 1px 3px rgba(0, 0, 0, 0.35);\r transform: translateY(-0.03em);\r }\r \r /* 3) Interactive refinement: follow Obsidian's accent hover behavior */\r .sc-events-checklist__label.pro-milestone > .sc-events-checklist__milestone:hover::after {\r background-color: var(--interactive-accent-hover);\r filter: brightness(1.05);\r }\r \r /* Milestones modal: title row help icon */\r .sc-milestones-modal__title {\r width: 100%;\r }\r \r .sc-milestones-modal__title-row {\r display: flex;\r align-items: center;\r gap: var(--size-4-2);\r width: 100%;\r }\r \r .sc-milestones-modal__title-text {\r min-width: 0;\r }\r \r .sc-milestones-modal__help-btn {\r display: inline-flex;\r align-items: center;\r justify-content: center;\r \r width: 28px;\r height: 28px;\r padding: 0;\r border-radius: var(--radius-s);\r \r background: transparent;\r border: 1px solid transparent;\r color: var(--text-muted);\r \r cursor: pointer;\r }\r \r .sc-milestones-modal__help-btn svg {\r width: 18px;\r height: 18px;\r }\r \r .sc-milestones-modal__help-btn:hover {\r background: var(--background-modifier-hover);\r border-color: var(--background-modifier-border);\r color: var(--text-normal);\r }\r \r .sc-milestones-modal__help-btn:active {\r transform: translateY(1px);\r }\r \r .sc-milestones-modal__help-btn:focus-visible {\r outline: 2px solid var(--color-accent);\r outline-offset: 2px;\r background: var(--background-modifier-hover);\r border-color: var(--color-accent);\r }\r `; // 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-integration/?utm_source=milestones#quick-start", is_pro: true }, // Connections Pro (Inline Connections) "inline_connections:show": { group: "Connections Pro", milestone: "Opened inline connections in-note (used the inline workflow).", link: "https://smartconnections.app/smart-connections/inline/?utm_source=milestones", is_pro: true }, "inline_connections:open_result": { group: "Connections Pro", milestone: "Opened an inline connections result (navigated from discovery to source).", link: "https://smartconnections.app/smart-connections/inline/?utm_source=milestones", is_pro: true }, "inline_connections:drag_result": { group: "Connections Pro", milestone: "Inserted an inline link from an inline connection (converted discovery into a durable link).", link: "https://smartconnections.app/smart-connections/inline/?utm_source=milestones", is_pro: true }, // Connect Pro "connect_pro:ping": { group: "Connect Pro", milestone: "Connect Pro ping observed (local route hit or tunnel health check ran).", link: "https://smartconnections.app/connect-pro/?utm_source=milestones#health-check", is_pro: true }, "connect_pro:request": { group: "Connect Pro", milestone: "Connect Pro request received (remote action hit /obsidian-cli).", link: "https://smartconnections.app/connect-pro/?utm_source=milestones#request-flow", is_pro: true }, // Pro "smart_plugins_oauth_completed": { group: "Pro", milestone: "Connected account (enabled Pro plugins).", link: "https://smartconnections.app/pro-plugins/?utm_source=milestones" }, "referrals:copied_link": { group: "Pro", milestone: "Copied your referral link to share Pro.", link: "https://smartconnections.app/pro-plugins/?utm_source=milestones#referrals", is_pro: true }, "referrals:opened_dashboard": { group: "Pro", milestone: "Opened the referrals dashboard to view bonuses.", link: "https://smartconnections.app/my-referrals/?utm_source=milestones", is_pro: true } }; var EVENTS_CHECKLIST_GROUP_ORDER = [ "Environment", "Connections", "Lookup", "Context", "Chat", "Connections Pro", "Context Pro", "Chat Pro", "Connect Pro", "Pro" ]; function derive_events_checklist_groups(items_by_event_key) { const group_map = Object.entries(items_by_event_key || {}).reduce( (acc, [event_key, item]) => { const group = item?.group || "Other"; if (!acc[group]) acc[group] = []; acc[group].push({ event_key, group, milestone: item?.milestone || "", ...item }); return acc; }, /** @type {Record>} */ {} ); const all_groups = Object.keys(group_map); const order_index = EVENTS_CHECKLIST_GROUP_ORDER.reduce( (acc, name, idx) => { acc[name] = idx; return acc; }, /** @type {Record} */ {} ); const sorted_groups = all_groups.sort((a, b) => { const a_has = Object.prototype.hasOwnProperty.call(order_index, a); const b_has = Object.prototype.hasOwnProperty.call(order_index, b); if (a_has && b_has) return order_index[a] - order_index[b]; if (a_has) return -1; if (b_has) return 1; return a.localeCompare(b); }); return sorted_groups.map((group) => { const items = (group_map[group] || []).slice().sort((a, b) => a.event_key.localeCompare(b.event_key)); return { group, items }; }); } function check_if_event_emitted(env, event_key) { const plugin_event_state = resolve_plugin_install_event(env, event_key); if (plugin_event_state === true) return true; if (env?.event_logs?.items?.[event_key]) return true; if (plugin_event_state === false) return false; return false; } function resolve_plugin_install_event(env, event_key) { const config = PLUGIN_INSTALL_EVENT_CONFIG[event_key]; if (!config) return null; const manifests = env?.plugin?.app?.plugins?.manifests || {}; const plugin_ids = Array.isArray(config.ids) ? config.ids : []; for (const plugin_id of plugin_ids) { const manifest = manifests[plugin_id]; if (!manifest) continue; if (config.require_pro_name && !is_pro_manifest(manifest)) continue; return true; } return false; } function is_pro_manifest(manifest) { const name = manifest?.name; if (typeof name !== "string") return false; return name.toLowerCase().includes("pro"); } // node_modules/obsidian-smart-env/src/utils/onboarding_events.js function is_valid_milestone_event(event_key, params = {}) { const { items_by_event_key = {} } = params; if (typeof event_key !== "string" || event_key.length === 0) return false; return Boolean(items_by_event_key && event_key in items_by_event_key); } function register_first_of_event_notifications(env) { if (typeof env?.events?.on !== "function") { return () => { }; } if (!(env._milestone_first_event_keys instanceof Set)) { env._milestone_first_event_keys = /* @__PURE__ */ new Set(); } const handle_first_event = (data) => { const event_key = data?.first_of_event_key; if (!is_valid_milestone_event(event_key, { items_by_event_key: EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY })) return; if (env._milestone_first_event_keys.has(event_key)) return; const item = EVENTS_CHECKLIST_ITEMS_BY_EVENT_KEY[event_key]; if (!item) return; env._milestone_first_event_keys.add(event_key); env.events.emit("milestones:first_achieved", { level: "milestone", message: "You achieved a new Smart Milestone", details: item.milestone, milestone: item.milestone, link: item.link, first_of_event_key: event_key, event_source: "onboarding_events", btn_text: "View milestones", btn_callback: "milestones_modal:open" }); }; const unsubscribe = env.events.on("event_log:first", handle_first_event); return () => { unsubscribe?.(); }; } // node_modules/obsidian-smart-env/src/components/milestones.js function build_html6(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 render8(env, params = {}) { this.apply_style_sheet(milestones_default); const html = build_html6.call(this, env, params); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process5.call(this, env, container, params); return container; } async function post_process5(env, container, params = {}) { attach_item_link_listeners(container); render_item_state_icons(container); return container; } function build_item_html(item, state) { const checked = state.checked === true; const checked_flag = checked ? "true" : "false"; const link = typeof item.link === "string" ? item.link : ""; const status_label = checked ? "Completed" : "Incomplete"; const aria_label = `Open docs: ${item.milestone || item.event_key || "milestone"} (${status_label})`; return `
  • ${escape_html(item.milestone)}
  • `; } function attach_item_link_listeners(container) { if (!container) return; if (container.getAttribute("data-links-enabled") === "true") return; container.setAttribute("data-links-enabled", "true"); container.addEventListener("click", (evt) => { const item_el = get_item_el_from_event(container, evt); if (!item_el) return; const selection = window.getSelection(); if (selection && selection.toString().length > 0) return; open_item_link(item_el); }); container.addEventListener("keydown", (evt) => { const key = evt && /** @type {KeyboardEvent} */ evt.key; if (key !== "Enter" && key !== " ") return; const item_el = get_item_el_from_event(container, evt); if (!item_el) return; evt.preventDefault(); open_item_link(item_el); }); } function open_item_link(item_el) { const link = get_item_link(item_el); if (typeof link === "string" && link.length > 0) { window.open(link, "_external"); } } function render_item_state_icons(container) { if (!container) return; const item_els = Array.from(container.querySelectorAll(".sc-events-checklist__item")); item_els.forEach((item_el) => { const checked = item_el.getAttribute("data-checked") === "true"; const icon_el = item_el.querySelector(".sc-events-checklist__icon"); if (!icon_el) return; set_item_icon( /** @type {HTMLElement} */ icon_el, checked ); }); } function set_item_icon(icon_el, checked) { const icon_ids = checked ? ["circle-check", "check-circle", "check"] : ["circle", "circle-dashed", "dot"]; set_icon_with_fallback2(icon_el, icon_ids); } function set_icon_with_fallback2(icon_el, icon_ids) { if (!icon_el) return; const ids = Array.isArray(icon_ids) ? icon_ids : []; for (const icon_id of ids) { if (typeof icon_id !== "string" || icon_id.length === 0) continue; icon_el.textContent = ""; try { (0, import_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 notification_filter_keys = Object.freeze([ ...notification_levels, debug_levels_filter_key ]); function normalize_filter_level(level) { const normalized_level = normalize_event_level(level); if (normalized_level) return normalized_level; const raw_level = typeof level === "string" ? level.trim().toLowerCase() : ""; if (raw_level === debug_levels_filter_key) return debug_levels_filter_key; return null; } function create_all_levels_set() { return new Set(notification_filter_keys); } function are_all_levels_active(active_levels = /* @__PURE__ */ new Set()) { if (!(active_levels instanceof Set)) return false; if (active_levels.size !== notification_filter_keys.length) return false; return notification_filter_keys.every((level) => active_levels.has(level)); } function get_next_active_levels(active_levels = /* @__PURE__ */ new Set(), params = {}) { const { level = null, select_all = false } = params; if (select_all) return create_all_levels_set(); const normalized_level = normalize_filter_level(level); const next_active_levels = new Set(active_levels instanceof Set ? active_levels : []); if (!normalized_level) return next_active_levels; if (are_all_levels_active(next_active_levels)) { return /* @__PURE__ */ new Set([normalized_level]); } if (next_active_levels.has(normalized_level)) { next_active_levels.delete(normalized_level); if (next_active_levels.size === 0) { return create_all_levels_set(); } return next_active_levels; } next_active_levels.add(normalized_level); return next_active_levels; } function get_canonical_entry_level(entry) { return get_event_level(entry?.event_key, entry?.event); } function get_entry_level(entry) { const canonical_level = get_canonical_entry_level(entry); if (canonical_level) return canonical_level; return get_event_level(entry?.event_key, entry?.event, { allow_display_fallback: true }); } function is_canonical_notification_entry(entry) { return Boolean(get_canonical_entry_level(entry)); } function is_debug_entry(entry) { if (!entry || typeof entry !== "object") return false; return !get_entry_level(entry); } function get_filtered_entries(entries, params = {}) { const { active_levels = create_all_levels_set() } = params; if (!(active_levels instanceof Set) || active_levels.size === 0) return []; const next_entries = Array.isArray(entries) ? [...entries] : []; if (are_all_levels_active(active_levels)) { return next_entries; } return next_entries.filter((entry) => { const level = get_entry_level(entry); if (level) return active_levels.has(level); return active_levels.has(debug_levels_filter_key); }); } function get_visible_entries(entries, params = {}) { const { limit = default_page_size } = params; return entries.slice(-limit).reverse(); } function get_visible_count(entries_length, params = {}) { const { page_size = default_page_size } = params; return Math.min(entries_length, page_size); } function get_next_visible_count(entries_length, params = {}) { const { current_count = 0, step_size = load_more_step } = params; return Math.min(entries_length, current_count + step_size); } function should_show_load_more(entries_length, visible_count) { return entries_length > visible_count; } function format_level_label2(level) { const normalized_level = normalize_filter_level(level); if (!normalized_level) return ""; return normalized_level.slice(0, 1).toUpperCase() + normalized_level.slice(1); } function queue_live_update_entries(pending_entries, next_entries, params = {}) { const { existing_entries = [] } = params; const pending_list = Array.isArray(pending_entries) ? [...pending_entries] : []; const pending_entry_ids = new Set(pending_list.map((entry) => `${get_entry_event_key(entry)}::${get_entry_timestamp2(entry)}`)); const existing_entry_ids = new Set((Array.isArray(existing_entries) ? existing_entries : []).map((entry) => `${get_entry_event_key(entry)}::${get_entry_timestamp2(entry)}`)); (Array.isArray(next_entries) ? next_entries : []).forEach((entry) => { const entry_id = `${get_entry_event_key(entry)}::${get_entry_timestamp2(entry)}`; if (existing_entry_ids.has(entry_id)) return; if (pending_entry_ids.has(entry_id)) return; pending_list.push(entry); pending_entry_ids.add(entry_id); }); return pending_list; } function get_level_counts(entries) { const counts = notification_filter_keys.reduce((acc, level) => { acc[level] = 0; return acc; }, {}); entries.forEach((entry) => { if (!entry || typeof entry !== "object") return; const level = get_entry_level(entry); if (level) { counts[level] += 1; return; } counts[debug_levels_filter_key] += 1; }); return counts; } function get_entry_timestamp2(entry) { if (typeof entry?.event?.at === "number") return entry.event.at; if (typeof entry?.at === "number") return entry.at; return Date.now(); } function get_entry_event_key(entry) { return typeof entry?.event_key === "string" ? entry.event_key : ""; } function get_entry_title(entry) { const event_obj = entry?.event && typeof entry.event === "object" ? entry.event : {}; if (typeof event_obj.message === "string" && event_obj.message.trim()) return event_obj.message.trim(); if (typeof event_obj.details === "string" && event_obj.details.trim()) return event_obj.details.trim(); if (typeof event_obj.milestone === "string" && event_obj.milestone.trim()) return event_obj.milestone.trim(); return get_entry_event_key(entry) || "event"; } function get_entry_payload_text(entry) { const event_obj = entry?.event && typeof entry.event === "object" ? entry.event : {}; return Object.entries(event_obj).filter(([key]) => !["at", "collection_key", "message", "level", "btn_text", "btn_callback", "timeout", "timeout_ms"].includes(key)).map(([key, value]) => ` ${key}: ${typeof value === "string" ? value : JSON.stringify(value)}`).join("\n"); } function get_entry_summary_action(entry) { const event_obj = entry?.event && typeof entry.event === "object" ? entry.event : {}; const btn_text = typeof event_obj.btn_text === "string" ? event_obj.btn_text.trim() : ""; const btn_callback = typeof event_obj.btn_callback === "string" ? event_obj.btn_callback.trim() : ""; if (!btn_text || !btn_callback) return null; return { btn_text, btn_callback }; } function get_entry_meta_text(entry) { const collection_key = entry?.event?.collection_key ?? ""; const event_key = get_entry_event_key(entry) || "event"; const timestamp = get_entry_timestamp2(entry); return `${collection_key ? `${collection_key} - ` : ""}${event_key} - ${to_time_ago(timestamp)}`; } function entry_to_clipboard_text(entry) { const meta_text = get_entry_meta_text(entry); const payload_text = get_entry_payload_text(entry); if (!payload_text.trim().length) { return `${meta_text} `; } return `${meta_text} ${payload_text} `; } function entries_to_clipboard_text(entries = []) { return entries.map((entry) => entry_to_clipboard_text(entry)).join(""); } function to_time_ago(ms) { const now_ms = Date.now(); const seconds = Math.floor((now_ms - ms) / 1e3); if (seconds < 60) return `${Math.max(0, seconds)}s ago`; const minutes = Math.floor(seconds / 60); if (minutes < 60) return `${minutes}m ago`; const hours = Math.floor(minutes / 60); if (hours < 24) return `${hours}h ago`; const days = Math.floor(hours / 24); return `${days}d ago`; } // node_modules/obsidian-smart-env/src/components/notifications_feed.js var feed_excluded_event_keys = /* @__PURE__ */ new Set([ "collection:save_started", "collection:save_completed", "notifications:seen", "notifications:seen_all", "event_log:first" ]); function build_html7() { return `
    `; } async function render9(env, params = {}) { this.apply_style_sheet(notification_feed_default); const frag = this.create_doc_fragment(build_html7()); 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_env2 = 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_env2.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_env2.empty(feed_container); filter_controls?.replaceChildren?.(); render_empty_state(feed_container, { title: "No Smart Env events yet.", detail: "When Smart Env emits events, they will appear here." }); set_btn_disabled(copy_btn, true); if (load_more_btn) load_more_btn.style.display = "none"; update_summary(summary_el, { total_count: 0, filtered_count: 0, visible_count: 0 }); render_live_updates_action(live_updates_el, { pending_count: pending_live_entries.length, on_click: handle_show_live_updates }); maybe_mark_entries_seen(); return; } render_filters(entries); render_entries(entries); render_live_updates_action(live_updates_el, { pending_count: pending_live_entries.length, on_click: handle_show_live_updates }); maybe_mark_entries_seen(); }; const handle_filters_changed = () => { render_feed({ preserve_expanded: true, reset_visible_count: true }); }; const handle_toggle_mute = (entry) => { const event_key = entry?.event_key; if (!event_key || typeof env?.event_logs?.toggle_event_key_muted !== "function") return; if (!is_canonical_notification_entry(entry)) return; env.event_logs.toggle_event_key_muted(event_key); render_feed({ preserve_expanded: true }); }; const handle_entry_toggle = (entry_key, is_open) => { if (is_open) { expanded_entry_keys.add(entry_key); return; } expanded_entry_keys.delete(entry_key); }; const handle_show_live_updates = () => { if (pending_live_entries.length === 0) return; pending_live_entries = []; state.pending_live_entries = pending_live_entries; render_feed({ preserve_expanded: true }); }; const reset_filters = () => { set_active_levels(create_all_levels_set()); render_feed({ preserve_expanded: true, reset_visible_count: true }); }; render_feed({ reset_visible_count: true }); if (copy_btn) { copy_btn.addEventListener("click", async () => { if (copy_btn.disabled) return; const filtered_entries = get_filtered_entries(get_entries(), { active_levels }); const newest_first = get_visible_entries(filtered_entries, { limit: filtered_entries.length }); const all_text = entries_to_clipboard_text(newest_first); const copied = await write_text_to_clipboard(all_text); set_btn_copied_state(copy_btn, { idle_text: "Copy All", copied_text: copied ? "Copied" : "Copy failed" }); }); } if (load_more_btn) { load_more_btn.addEventListener("click", () => { const filtered_entries = get_filtered_entries(get_entries(), { active_levels }); visible_count = get_next_visible_count(filtered_entries.length, { current_count: visible_count }); state.visible_count = visible_count; render_feed({ preserve_expanded: true }); }); } if (live_updates && typeof env?.events?.on === "function") { let debounce_timeout = null; const live_update_off = env.events.on("*", (_event, event_key) => { if (!should_refresh_for_event(event_key)) return; if (debounce_timeout) clearTimeout(debounce_timeout); debounce_timeout = setTimeout(() => { debounce_timeout = null; const next_entries = get_entries(); pending_live_entries = queue_live_update_entries(pending_live_entries, next_entries, { existing_entries: rendered_entries_cache }); state.pending_live_entries = pending_live_entries; if (pending_live_entries.length > 0) { render_live_updates_action(live_updates_el, { pending_count: pending_live_entries.length, on_click: handle_show_live_updates }); return; } render_feed({ preserve_expanded: true }); }, 100); }); this.attach_disposer(container, [ live_update_off, () => { if (!debounce_timeout) return; clearTimeout(debounce_timeout); debounce_timeout = null; } ]); } } function render_filter_controls(container, params = {}) { if (!container) return; const { active_levels = /* @__PURE__ */ new Set(), total_count = 0, level_counts = {}, on_change = () => { }, on_select_all = () => { }, on_select_level = () => { } } = params; container.replaceChildren(); const all_is_active = are_all_levels_active(active_levels); append_filter_button(container, { level: all_levels_filter_key, label_text: "All", count_total: total_count, is_active: all_is_active, modifier_class: "smart-env-notifications-filter--all", on_click: () => { if (all_is_active) return; on_select_all(); on_change(); } }); notification_levels.forEach((level) => { append_filter_button(container, { level, label_text: format_level_label2(level), count_total: typeof level_counts[level] === "number" ? level_counts[level] : 0, is_active: !all_is_active && active_levels.has(level), on_click: () => { on_select_level(level); on_change(); } }); }); append_filter_button(container, { level: debug_levels_filter_key, label_text: "Debug", count_total: typeof level_counts[debug_levels_filter_key] === "number" ? level_counts[debug_levels_filter_key] : 0, is_active: !all_is_active && active_levels.has(debug_levels_filter_key), on_click: () => { on_select_level(debug_levels_filter_key); on_change(); } }); } function append_filter_button(container, params) { const { level, label_text, count_total, is_active, on_click, modifier_class = "" } = params; const button = container.ownerDocument.createElement("button"); button.type = "button"; button.className = `smart-env-notifications-filter${modifier_class ? ` ${modifier_class}` : ""}`; button.dataset.level = level; button.setAttribute("aria-pressed", String(Boolean(is_active))); if (is_active) button.classList.add("is-active"); button.addEventListener("click", on_click); const content = container.ownerDocument.createElement("span"); content.className = "smart-env-notifications-filter__content"; const dot = container.ownerDocument.createElement("span"); dot.className = "smart-env-notifications-filter__dot"; dot.setAttribute("aria-hidden", "true"); const text = container.ownerDocument.createElement("span"); text.className = "smart-env-notifications-filter__label"; text.textContent = label_text; const count = container.ownerDocument.createElement("span"); count.className = "smart-env-notifications-filter__count"; count.textContent = count_total > 0 ? count_total.toLocaleString() : ""; if (count_total <= 0) count.classList.add("is-zero"); content.appendChild(dot); content.appendChild(text); content.appendChild(count); button.appendChild(content); container.appendChild(button); } function update_load_more_button(button, params = {}) { if (!button) return; const { entries_length = 0, visible_count = 0 } = params; const is_visible2 = should_show_load_more(entries_length, visible_count); button.style.display = is_visible2 ? "block" : "none"; if (is_visible2) { const remaining_count = entries_length - visible_count; const next_step = Math.min(load_more_step, remaining_count); button.textContent = `Load ${next_step.toLocaleString()} more`; } } function render_live_updates_action(container, params = {}) { if (!container) return; const { pending_count = 0, on_click = () => { } } = params; container.replaceChildren(); if (pending_count <= 0) return; const button = container.ownerDocument.createElement("button"); button.type = "button"; button.className = "smart-env-btn smart-env-btn--ghost smart-env-notifications-live-updates__btn"; button.textContent = `Show ${pending_count.toLocaleString()} new event${pending_count === 1 ? "" : "s"}`; button.addEventListener("click", on_click); container.appendChild(button); } function append_entry(feed_container, entry, params = {}) { const { env = null, expanded_entry_keys = /* @__PURE__ */ new Set(), on_entry_toggle = () => { }, on_toggle_mute = () => { } } = params; const level = get_entry_level(entry); const is_debug = is_debug_entry(entry); const title = get_entry_title(entry); const timestamp = get_entry_timestamp2(entry); const collection_key = entry?.event?.collection_key ?? ""; const event_key = get_entry_event_key(entry) || "event"; const payload_text = get_entry_payload_text(entry); const summary_action = get_entry_summary_action(entry); const is_canonical = is_canonical_notification_entry(entry); const is_muted = is_canonical && Boolean(env?.event_logs?.is_event_key_muted?.(event_key)); const is_feed_only = Boolean(level) && !is_canonical; const entry_row_key = get_entry_row_key(entry); const total_count = get_event_key_total_count(env, event_key); const row_level = level || (is_debug ? debug_levels_filter_key : "event"); const row = feed_container.ownerDocument.createElement("details"); row.className = "smart-env-notification"; row.dataset.entryKey = entry_row_key; row.dataset.level = row_level; row.setAttribute("role", "listitem"); if (is_muted) row.dataset.muted = "true"; if (expanded_entry_keys.has(entry_row_key)) row.open = true; const summary = feed_container.ownerDocument.createElement("summary"); summary.className = "smart-env-notification__summary"; const accent = feed_container.ownerDocument.createElement("span"); accent.className = "smart-env-notification__accent"; accent.setAttribute("aria-hidden", "true"); const body = feed_container.ownerDocument.createElement("div"); body.className = "smart-env-notification__summary-body"; const top = feed_container.ownerDocument.createElement("div"); top.className = "smart-env-notification__summary-top"; const event_el = feed_container.ownerDocument.createElement("div"); event_el.className = "smart-env-notification__event-key"; event_el.textContent = title; const time_el = feed_container.ownerDocument.createElement("div"); time_el.className = "smart-env-notification__time"; time_el.textContent = to_time_ago(timestamp); time_el.title = format_timestamp(timestamp); top.appendChild(event_el); if (summary_action) { const cta_btn = feed_container.ownerDocument.createElement("button"); cta_btn.className = "smart-env-btn smart-env-btn--ghost smart-env-notification__summary-action"; cta_btn.type = "button"; cta_btn.textContent = summary_action.btn_text; cta_btn.setAttribute("aria-label", summary_action.btn_text); cta_btn.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); dispatch_notice_action(env, summary_action.btn_callback, { event_key, event: entry?.event, event_source: "notifications_feed" }); }); top.appendChild(cta_btn); } top.appendChild(time_el); const bottom = feed_container.ownerDocument.createElement("div"); bottom.className = "smart-env-notification__summary-bottom"; if (collection_key) { bottom.appendChild(create_tag(feed_container, "smart-env-notification__collection", collection_key)); } if (title !== event_key) { bottom.appendChild(create_tag(feed_container, "smart-env-notification__collection", event_key)); } bottom.appendChild(create_tag( feed_container, "smart-env-notification__level", level ? format_level_label2(level) : is_debug ? "Debug" : "Event" )); if (is_feed_only) { bottom.appendChild(create_tag(feed_container, "smart-env-notification__feed-only", "Feed only")); } if (is_muted) { bottom.appendChild(create_tag(feed_container, "smart-env-notification__muted", "Muted")); } body.appendChild(top); body.appendChild(bottom); const chevron = feed_container.ownerDocument.createElement("span"); chevron.className = "smart-env-notification__chevron"; chevron.setAttribute("aria-hidden", "true"); summary.appendChild(accent); summary.appendChild(body); summary.appendChild(chevron); const expanded = feed_container.ownerDocument.createElement("div"); expanded.className = "smart-env-notification__expanded"; const meta = feed_container.ownerDocument.createElement("div"); meta.className = "smart-env-notification__expanded-meta"; append_meta_row(feed_container, meta, "Occurred", format_timestamp(timestamp)); append_meta_row(feed_container, meta, "Event key", event_key); if (collection_key) { append_meta_row(feed_container, meta, "Collection", collection_key); } expanded.appendChild(meta); const actions = feed_container.ownerDocument.createElement("div"); actions.className = "smart-env-notification__expanded-actions"; actions.appendChild(create_tag( feed_container, "smart-env-notification__event-count", `${Math.max(1, total_count).toLocaleString()} total` )); if (is_canonical) { const mute_btn = feed_container.ownerDocument.createElement("button"); mute_btn.className = "smart-env-btn smart-env-btn--ghost smart-env-notification__mute"; mute_btn.type = "button"; mute_btn.textContent = is_muted ? "Unmute native notices" : "Mute native notices"; mute_btn.setAttribute( "aria-label", is_muted ? "Allow native notices for this event key" : "Mute native notices for this event key" ); mute_btn.addEventListener("click", (event) => { event.preventDefault(); event.stopPropagation(); on_toggle_mute(entry); }); actions.appendChild(mute_btn); } else if (is_feed_only) { actions.appendChild(create_tag(feed_container, "smart-env-notification__feed-only", "Display fallback only")); } expanded.appendChild(actions); if (payload_text.trim().length) { const message = feed_container.ownerDocument.createElement("pre"); message.className = "smart-env-notification__message"; message.textContent = payload_text; expanded.appendChild(message); } row.appendChild(summary); row.appendChild(expanded); row.addEventListener("toggle", () => { on_entry_toggle(entry_row_key, row.open === true); }); feed_container.appendChild(row); } function update_summary(summary_el, params = {}) { if (!summary_el) return; const { total_count = 0, filtered_count = 0, visible_count = 0 } = params; if (total_count <= 0) { summary_el.textContent = ""; return; } const shown_count = Math.min(visible_count, filtered_count); const shown_label = shown_count.toLocaleString(); const filtered_label = filtered_count.toLocaleString(); const total_label = total_count.toLocaleString(); let text = shown_count === filtered_count ? `${shown_label} shown` : `${shown_label} of ${filtered_label} shown`; if (filtered_count !== total_count) { text += ` (${total_label} total)`; } summary_el.textContent = text; } function render_empty_state(feed_container, params = {}) { const { title = "Nothing here yet.", detail = "", action_text = "", on_action = null } = params; const wrap = feed_container.ownerDocument.createElement("div"); wrap.className = "smart-env-notifications-empty-state"; const heading = feed_container.ownerDocument.createElement("div"); heading.className = "smart-env-notifications-empty-state__title"; heading.textContent = title; wrap.appendChild(heading); if (detail) { const detail_el = feed_container.ownerDocument.createElement("div"); detail_el.className = "smart-env-notifications-empty-state__detail"; detail_el.textContent = detail; wrap.appendChild(detail_el); } if (action_text && typeof on_action === "function") { const button = feed_container.ownerDocument.createElement("button"); button.className = "smart-env-btn smart-env-btn--ghost"; button.type = "button"; button.textContent = action_text; button.addEventListener("click", () => on_action()); wrap.appendChild(button); } feed_container.appendChild(wrap); } function set_btn_disabled(btn, is_disabled) { if (!btn) return; btn.disabled = Boolean(is_disabled); } function set_btn_copied_state(btn, params = {}) { if (!btn) return; const { idle_text = "Copy", copied_text = "Copied" } = params; btn.textContent = copied_text; btn.classList.add("is-copied"); setTimeout(() => { btn.textContent = idle_text; btn.classList.remove("is-copied"); }, 1400); } async function write_text_to_clipboard(text = "") { if (!text) return false; try { if (navigator?.clipboard?.writeText) { await navigator.clipboard.writeText(text); return true; } } catch (_err) { } try { if (typeof window !== "undefined" && typeof window.require === "function") { const { clipboard } = window.require("electron"); if (clipboard?.writeText) { clipboard.writeText(text); return true; } } } catch (_err) { } try { if (typeof document === "undefined") return false; const textarea = document.createElement("textarea"); textarea.value = text; textarea.style.position = "fixed"; textarea.style.left = "-9999px"; textarea.style.top = "0"; document.body.appendChild(textarea); textarea.focus(); textarea.select(); const copied = document.execCommand("copy"); document.body.removeChild(textarea); return Boolean(copied); } catch (_err) { return false; } } function get_entry_row_key(entry) { return `${get_entry_event_key(entry)}:${get_entry_timestamp2(entry)}`; } function get_required_visible_count_for_entry(entries = [], target_entry_key = "") { if (!Array.isArray(entries) || !target_entry_key) return null; const target_index = entries.findIndex((entry) => get_entry_row_key(entry) === target_entry_key); if (target_index === -1) return null; return entries.length - target_index; } function reveal_target_entry(feed_container, target_entry_key = "") { if (!feed_container || !target_entry_key) return false; const target_entry_el = Array.from(feed_container.querySelectorAll(".smart-env-notification")).find((details_el) => details_el.dataset.entryKey === target_entry_key); if (!target_entry_el) return false; target_entry_el.open = true; target_entry_el.scrollIntoView?.({ block: "center", behavior: "smooth" }); return true; } function get_event_key_total_count(env, event_key) { if (!event_key) return 0; return env?.event_logs?.get?.(event_key)?.data?.ct || 0; } function create_tag(feed_container, class_name, text) { const el = feed_container.ownerDocument.createElement("span"); el.className = class_name; el.textContent = text; return el; } function append_meta_row(feed_container, container, label, value) { const row = feed_container.ownerDocument.createElement("div"); row.className = "smart-env-notification__expanded-row"; const label_el = feed_container.ownerDocument.createElement("span"); label_el.className = "smart-env-notification__expanded-label"; label_el.textContent = label; const value_el = feed_container.ownerDocument.createElement("span"); value_el.className = "smart-env-notification__expanded-value"; value_el.textContent = value; row.appendChild(label_el); row.appendChild(value_el); container.appendChild(row); } function format_timestamp(timestamp) { try { return new Date(timestamp).toLocaleString(); } catch (_error) { return String(timestamp); } } // node_modules/obsidian-smart-env/src/modals/smart_model_modal.js var import_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) {\r\n display: block;\r\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); } switch (setting_config.type) { case "button": setting.addButton((btn) => { btn.setButtonText(setting_config.name || "Run"); btn.onClick(async (event) => { if (typeof setting_config.callback === "function") { await handle_config_callback(setting, event, setting_config.callback, { scope }); } }); }); break; case "toggle": setting.addToggle((toggle) => { toggle.setValue(get_by_path(scope.settings, setting_path) || false); toggle.onChange((value) => { if (settng_is_pro && !env_is_pro) { scope?.env?.events?.emit?.("settings:pro_feature_blocked", { level: "warning", message: "Nice try! This is a PRO feature. Please upgrade to access this setting.", event_source: "render_settings_config.toggle" }); return; } set_by_path(scope.settings, setting_path, value); if (typeof setting_config.callback === "function") { handle_config_callback(setting, value, setting_config.callback, { scope }); } }); }); break; case "text": setting.addText((text) => { text.setValue(String(get_by_path(scope.settings, setting_path) || "")); text.onChange((value) => { set_by_path(scope.settings, setting_path, value); }); }); break; case "password": setting.addText((text) => { text.setValue(String(get_by_path(scope.settings, setting_path) || "")); text.inputEl.setAttribute("type", "password"); text.onChange((value) => { set_by_path(scope.settings, setting_path, value); }); }); break; case "number": setting.addText((text) => { text.setValue(String(get_by_path(scope.settings, setting_path) ?? "0")); text.inputEl.setAttribute("type", "number"); text.onChange((value) => { const num_value = Number(value); if (!isNaN(num_value)) { set_by_path(scope.settings, setting_path, num_value); } if (typeof setting_config.callback === "function") { handle_config_callback(setting, num_value, setting_config.callback, { scope }); } }); }); break; case "dropdown": setting.addDropdown(async (dropdown) => { const options_callback = setting_config.options_callback; if (typeof options_callback === "function") { const options = await options_callback.call(scope, scope); options.forEach((opt) => { const label = opt.label || opt.name || opt.value; dropdown.addOption(opt.value, label); }); } dropdown.setValue(get_by_path(scope.settings, setting_path) || ""); dropdown.onChange((value) => { set_by_path(scope.settings, setting_path, value); if (typeof setting_config.callback === "function") { handle_config_callback(setting, value, setting_config.callback, { scope }); } rerender_settings_group(); }); }); break; case "textarea": setting.addTextArea((text) => { text.setValue(String(get_by_path(scope.settings, setting_path) || "")); text.onChange((value) => { if (settng_is_pro && !env_is_pro) { scope?.env?.events?.emit?.("settings:pro_feature_blocked", { level: "warning", message: "Nice try! This is a PRO feature. Please upgrade to access this setting.", event_source: "render_settings_config.textarea" }); return; } set_by_path(scope.settings, setting_path, value); }); if (settng_is_pro && !env_is_pro) { text.setDisabled(true); } }); break; case "slider": setting.addSlider((slider) => { const min = setting_config.min || 0; const max = setting_config.max || 100; const step = setting_config.step || 1; slider.setLimits(min, max, step); slider.setValue(get_by_path(scope.settings, setting_path) || min); slider.setDynamicTooltip(); slider.onChange((value) => { set_by_path(scope.settings, setting_path, value); if (typeof setting_config.callback === "function") { handle_config_callback(setting, value, setting_config.callback, { scope }); } }); }); break; case "heading": setting.setHeading(); break; case "html": if (setting_config.value) { setting.descEl.replaceChildren( document.createRange().createContextualFragment(setting_config.value) ); } break; default: console.warn(`Unsupported setting type for ${setting_path}:`, setting_config.type); break; } if (setting_config.scope_class) { setting.settingEl.addClass(setting_config.scope_class); } if (settng_is_pro && !env_is_pro) { setting.setDisabled(true); } }); } return setting_group; } function render_heading_button(setting_group, scope, heading_btn) { const btn_el = setting_group.controlEl.createEl("button", { cls: "" }); if (heading_btn.btn_icon) { (0, import_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" }); 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 {\r\n border-radius: var(--radius-m);\r\n padding: 1rem;\r\n margin-bottom: 1rem;\r\n background-color: var(--background-secondary);\r\n pre {\r\n margin: 0;\r\n font-size: 0.9rem;\r\n }\r\n .test-result-icon {\r\n vertical-align: middle;\r\n margin-left: 0.5rem;\r\n }\r\n .test-result-icon[data-icon="square-check-big"]{\r\n color: var(--color-green);\r\n }\r\n .test-result-icon[data-icon="circle-x"]{\r\n color: var(--color-red);\r\n }\r\n}\r\n\r\n.smart-model-modal{\r\n pre, .model-note {\r\n user-select: text;\r\n }\r\n}'; // node_modules/obsidian-smart-env/src/components/settings/env_model.js var import_obsidian19 = require("obsidian"); function build_html8(model, params) { const details = [ `Provider: ${model.data.provider_key}`, `Model: ${model.data.model_key || "**MISSING - EDIT & SELECT MODEL**"}` ]; return `
    Current: ${model.display_name}
    ${details.join("\n")}
    `; } async function render10(model, params) { this.apply_style_sheet(env_model_default); const frag = this.create_doc_fragment(build_html8.call(this, model, params)); const container = frag.firstElementChild; post_process7.call(this, model, container, params); return container; } async function post_process7(model, container, params) { const edit_btn = container.querySelector(".edit-model"); const test_btn = container.querySelector(".test-model"); const icon_el = container.querySelector(".test-result-icon"); (0, import_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(); }); 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: "Open Router (cloud)", value: "open_router" }, { label: "PRO: LM Studio (local, requires LM Studio app)", value: "lm_studio", disabled: true }, { label: "PRO: Ollama (local, requires Ollama app)", value: "ollama", disabled: true }, { label: "PRO: OpenAI (cloud)", value: "openai", disabled: true }, { label: "PRO: Google Gemini (cloud)", value: "google", disabled: true }, { label: "PRO: Cohere (cloud)", value: "cohere", disabled: true }, { label: "PRO: xAI Grok (cloud)", value: "xai", disabled: true }, { label: "PRO: Anthropic Claude (cloud)", value: "anthropic", disabled: true }, { label: "PRO: Deepseek (cloud)", value: "deepseek", disabled: true }, { label: "PRO: Azure OpenAI (cloud)", value: "azure", disabled: true }, { label: "Experimental: Lite LLM (self-hosted proxy)", value: "litellm", disabled: true } ], embedding_models: [ { label: "Transformers (easy, local, built-in)", value: "transformers" }, { label: "PRO: LM Studio (local, requires LM Studio app)", value: "lm_studio", disabled: true }, { label: "PRO: Ollama (local, requires Ollama app)", value: "ollama", disabled: true }, { label: "PRO: OpenAI (cloud)", value: "openai", disabled: true }, { label: "PRO: Google Gemini (cloud)", value: "gemini", disabled: true }, { label: "PRO: Open Router (cloud)", value: "open_router", disabled: true } ], ranking_models: [ { label: "PRO: Cohere (cloud)", value: "cohere", disabled: true } ] }; // node_modules/obsidian-smart-env/src/utils/smart-models/show_new_model_menu.js function show_new_model_menu(models_collection, event, params = {}) { const providers = (provider_options[models_collection.collection_key] || []).map((p) => ({ ...p, disabled: !models_collection.env_config.providers[p.value] })); console.log("show_new_model_menu providers", providers); if (providers.length === 0) { if (event.target.tagName.toLowerCase() === "button") { event.target.disabled = true; event.title = "No providers available to create new models."; } } else { const menu = new import_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_html9(models_collection, params) { return `
    `; } async function render11(models_collection, params) { const frag = this.create_doc_fragment(build_html9.call(this, models_collection, params)); const container = frag.firstElementChild; post_process8.call(this, models_collection, container, params); return container; } async function post_process8(models_collection, container, params) { const disposers = []; const render_current_model_info = async (current_model) => { this.empty(container); const [settings_group] = render_settings_config( models_collection.env_config.settings_config, models_collection, container, { default_group_name: `${models_collection.model_type} models`, heading_btn: { btn_text: "+ New", callback: (event, setting) => { show_new_model_menu(models_collection, event); } } } ); models_collection.env.smart_components.render_component("settings_env_model", current_model, {}).then((model_info_el) => { settings_group.listEl.appendChild(model_info_el); }); }; render_current_model_info(models_collection.default); disposers.push(models_collection.on_event("settings:changed", async (payload) => { const default_setting_path = `${models_collection.collection_key}.default_model_key`; if (payload.path_string === default_setting_path) { await render_current_model_info(models_collection.default); } })); disposers.push(models_collection.on_event("model:changed", async () => { await render_current_model_info(models_collection.default); })); this.attach_disposer(container, disposers); } // node_modules/obsidian-smart-env/src/components/settings/env_models.js function build_html10(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 render12(env, params) { const frag = this.create_doc_fragment(build_html10(env, params)); const container = frag.firstElementChild; post_process9.call(this, env, container, params); return container; } async function post_process9(env, container, params) { const collection_containers = container.querySelectorAll("div[data-collection-key]"); for (const collection_container of collection_containers) { const collection_key = collection_container.getAttribute("data-collection-key"); const models_collection = env[collection_key]; env.smart_components.render_component("settings_env_model_type", models_collection).then((model_type_el) => { this.empty(collection_container); collection_container.appendChild(model_type_el); }); } return container; } // node_modules/obsidian-smart-env/src/modals/exclude_folders_fuzzy.js var import_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; if (!smart_sources_settings.folder_exclusions) smart_sources_settings.folder_exclusions = ""; if (!smart_sources_settings.file_exclusions) smart_sources_settings.file_exclusions = ""; return smart_sources_settings; } function parse_exclusions_csv(exclusions = "") { return exclusions.split(",").map((value) => value.trim()).filter(Boolean); } function add_exclusion(exclusions, value) { const trimmed = (value ?? "").trim(); if (!trimmed) return exclusions || ""; const current = parse_exclusions_csv(exclusions); if (!current.includes(trimmed)) current.push(trimmed); return current.join(","); } function remove_exclusion(exclusions, value) { const trimmed = (value ?? "").trim(); if (!trimmed) return exclusions?.trim() || ""; const filtered = parse_exclusions_csv(exclusions).filter((entry) => entry !== trimmed); return filtered.join(","); } // node_modules/obsidian-smart-env/src/modals/exclude_folders_fuzzy.js var ExcludedFoldersFuzzy = class extends import_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 to exclude..."); } /** * Open the modal with an optional callback invoked after an item is chosen. * The current exclusion list is rendered at the top of the modal. * @param {Function} [selection_callback] */ open(selection_callback) { this.callback = selection_callback; super.open(); this.render_excluded_list(); } /** * Return candidate folder paths that are not already excluded. * @returns {string[]} */ getItems() { const smart_sources_settings = ensure_smart_sources_settings(this.env); const folder_exclusions2 = parse_exclusions_csv(smart_sources_settings.folder_exclusions); const candidates = (this.env.smart_sources?.fs?.folder_paths || []).filter((path) => !folder_exclusions2.includes(path)); return candidates; } getItemText(item) { return item; } /** * Handle selecting a folder to exclude. * @param {string} item */ onChooseItem(item) { this.prevent_close = true; if (!item) return; const smart_sources_settings = ensure_smart_sources_settings(this.env); smart_sources_settings.folder_exclusions = add_exclusion(smart_sources_settings.folder_exclusions, item); this.render_excluded_list(); this.updateSuggestions(); this.callback?.(); } /** * Render the current list of excluded folders at the top of the modal, * with inline remove buttons. */ render_excluded_list() { if (!this.modalEl) return; const smart_sources_settings = ensure_smart_sources_settings(this.env); const excluded_folders = parse_exclusions_csv(smart_sources_settings.folder_exclusions); let header = this.modalEl.querySelector(".sc-excluded-folders-header"); if (!header) { header = this.modalEl.createEl("div", { cls: "sc-excluded-folders-header" }); this.modalEl.prepend(header); } header.empty(); const title_el = header.createEl("h3"); title_el.setText("Excluded folders"); if (!excluded_folders.length) { const empty_el = header.createEl("p"); empty_el.setText("No folders excluded yet."); return; } const list_el = header.createEl("ul"); excluded_folders.forEach((folder_path) => { const li = list_el.createEl("li", { cls: "excluded-folder-item" }); li.setText(folder_path + " "); const remove_btn = li.createEl("button", { text: "(x)", cls: "remove-excluded-folder-btn" }); remove_btn.addEventListener("click", () => { smart_sources_settings.folder_exclusions = remove_exclusion( smart_sources_settings.folder_exclusions, folder_path ); this.env.update_exclusions?.(); this.render_excluded_list(); this.updateSuggestions(); }); }); } close() { setTimeout(() => { if (!this.prevent_close) super.close(); this.prevent_close = false; }, 10); } }; // node_modules/obsidian-smart-env/src/modals/excluded_sources.js var import_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; const too_long_files = this.app.vault.getMarkdownFiles().filter((file) => file.path.length > 200).map((file) => file.path); for (const file_path of excluded_file_paths) { const li = list_el.createEl("li"); li.setText(file_path); } this.contentEl.createEl("hr"); this.contentEl.createEl("h3", { text: "Paths too long to import into Smart Environment" }); const too_long_list_ul = this.contentEl.createEl("ul", { cls: "too-long-exclusions" }); for (const file_path of too_long_files) { const li = too_long_list_ul.createEl("li"); li.setText(file_path); } } }; // node_modules/obsidian-smart-env/src/components/settings/env_sources.js async function build_html11(env, opts = {}) { return `
    `; } async function render13(env, opts = {}) { const html = await build_html11.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 = { folder_exclusions, view_exclusions, // reset_env_settings_btn, // TODO: manually tested before implementing reset button re_import_sources }; render_settings_config(settings_config13, env, container, { default_group_name: "Sources", heading_btn: { btn_icon: "help-circle", callback: () => { window.open("https://smartconnections.app/smart-environment/settings/?utm_source=source-settings", "_external"); } } }); const disposers = []; disposers.push(env.events?.on("model:changed", highlight_reset_data(env, container))); this.attach_disposer(container, disposers); return container; } function highlight_reset_data(env, container) { return async (payload) => { if (payload.collection_key !== "embedding_models") return; const re_import_setting = container.querySelector(".re-import-sources"); re_import_setting.classList.add("env-setting-highlight"); const notice = re_import_setting.querySelector(".reimport-notice") ? re_import_setting.querySelector(".reimport-notice") : re_import_setting.createEl("div", { cls: "reimport-notice env-setting-note" }); notice.textContent = "Embedding model changed. Please re-import your sources to update their embeddings."; re_import_setting.appendChild(notice); env.events.once("sources:reimported", () => { re_import_setting.classList.remove("env-setting-highlight"); notice.remove(); }); }; } var 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_html12(model, params = {}) { return `
    `; } async function render14(model, params = {}) { const frag = this.create_doc_fragment(build_html12(model, params)); const container = frag.firstElementChild; post_process11.call(this, model, container, params); return container; } async function post_process11(model, container, params = {}) { const new_model_btn = container.querySelector(".new-model-btn"); new_model_btn.addEventListener("click", async (event) => { const on_before_new = params.on_before_new; const opts = {}; if (typeof on_before_new === "function") { opts.on_before_new = on_before_new; } show_new_model_menu(model.collection, event, opts); }); const delete_model_btn = container.querySelector(".delete-model-btn"); const confirm_delete_container = container.querySelector(".confirm-delete-container"); const confirm_delete_yes_btn = container.querySelector(".confirm-delete-yes-btn"); const confirm_delete_no_btn = container.querySelector(".confirm-delete-no-btn"); delete_model_btn.addEventListener("click", async () => { confirm_delete_container.style.display = ""; }); confirm_delete_no_btn.addEventListener("click", async () => { confirm_delete_container.style.display = "none"; }); confirm_delete_yes_btn.addEventListener("click", async () => { await model.delete_model(); if (typeof params.on_after_delete === "function") { params.on_after_delete(); } }); return container; } // node_modules/obsidian-smart-env/src/components/settings/style.css var style_default = ".sc-env-settings-container {\n margin: 1rem 0;\n}\n\n.smart-env-settings-header {\n display: flex;\n align-items: center;\n justify-content: space-between;\n margin-bottom: 0.5rem;\n}\n\n.toggle-env-settings-btn {\n cursor: pointer;\n}\n\n\n.setting-group .setting-items .setting-item.env-setting-highlight {\n border: 1px solid var(--color-accent);\n background-color: var(--interactive-hover);\n padding: 0.5rem;\n margin: 0.5rem 0;\n}\n\n.settings-group {\n .setting-item {\n border-top: none;\n }\n}\n\n.sc-inline-confirm-row {\n display: flex;\n align-items: center;\n gap: 0.5rem;\n margin-top: 0.5rem;\n}\n"; // node_modules/obsidian-smart-env/src/components/settings/smart_env.js async function build_html13(env, params = {}) { return `

    Sources

    Models

    Notifications

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

    ${SMART_PLUGINS_DESC}

    Loading...

    ${PRO_PLUGINS_FOOTER}

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

    Failed to load plugin information.

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

    Blocks

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

    ${source_should_embed} | ${source_embed_status}

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

    No blocks

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

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

    Embed input
    ${embed_input}
    ${block_content}

    `); container.appendChild(block_frag); } return frag; } // node_modules/obsidian-smart-env/src/components/status_bar.js var import_obsidian33 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/register_status_bar_context_menu.js var import_obsidian32 = require("obsidian"); // node_modules/obsidian-smart-env/views/source_inspector.js var import_obsidian30 = require("obsidian"); var SmartNoteInspectModal = class extends import_obsidian30.Modal { constructor(smart_connections_plugin, entity) { super(smart_connections_plugin.app); this.smart_connections_plugin = smart_connections_plugin; this.entity = entity; } get env() { return this.smart_connections_plugin.env; } onOpen() { this.titleEl.innerText = this.entity.key; this.render(); } async render() { this.contentEl.empty(); const frag = await this.env.smart_components.render_component("source_inspector", this.entity); this.contentEl.appendChild(frag); } }; // node_modules/obsidian-smart-env/src/modals/env_stats.js var import_obsidian31 = require("obsidian"); var EnvStatsModal = class extends import_obsidian31.Modal { constructor(app2, env) { super(app2); this.env = env; } onOpen() { this.titleEl.setText("Smart Environment"); this.contentEl.empty(); this.contentEl.createEl("p", { text: "Loading stats..." }); setTimeout(this.render.bind(this), 100); } async render() { const frag = await this.env.smart_components.render_component("env_stats", this.env); this.contentEl.empty(); if (frag) { this.contentEl.appendChild(frag); } else { this.contentEl.createEl("p", { text: "Failed to load stats." }); } } }; // node_modules/obsidian-smart-env/src/utils/register_status_bar_context_menu.js function register_status_bar_context_menu(env, status_container, deps = {}) { const { Menu: MenuClass = import_obsidian32.Menu } = deps; const plugin = env.main; const on_context_menu = (ev) => { ev.preventDefault(); ev.stopPropagation(); const menu = new MenuClass(plugin.app); menu.addItem( (item) => item.setTitle("Inspect active note").setIcon("search").onClick(async () => { const active_file = plugin.app.workspace.getActiveFile(); if (!active_file) { env?.events?.emit?.("status_bar:inspect_active_note_missing", { level: "warning", message: "No active note found", event_source: "register_status_bar_context_menu.inspect" }); return; } const src = env.smart_sources?.get(active_file.path); if (!src) { env?.events?.emit?.("status_bar:inspect_source_missing", { level: "warning", message: "Active note is not indexed by Smart Environment", event_source: "register_status_bar_context_menu.inspect" }); return; } new SmartNoteInspectModal(plugin, src).open(); }) ); menu.addItem( (item) => item.setTitle("Show stats").setIcon("chart-pie").onClick(() => { const modal = new EnvStatsModal(plugin.app, env); modal.open(); }) ); menu.addItem( (item) => item.setTitle("Export data").setIcon("download").onClick(() => { env.export_json(); env?.events?.emit?.("smart_env:exported", { level: "attention", message: "Smart Env exported", event_source: "register_status_bar_context_menu.export" }); }) ); menu.addItem( (item) => item.setTitle("Milestones").setIcon("flag").onClick(() => { env.open_milestones_modal(); }) ); menu.addItem( (item) => item.setTitle("Notifications").setIcon("bell").onClick(() => { env.open_notifications_feed_modal(); }) ); menu.addSeparator(); menu.addItem( (item) => item.setTitle("Browse Smart Plugins").setIcon("package").onClick(() => { env.events?.emit?.("smart_plugins:browse", { event_source: "status_bar" }); }) ); if (env.is_pro) { menu.addItem( (item) => item.setTitle("Refer a friend (Give 30, Get 30)").setIcon("hand-heart").onClick(() => { const url = "https://smartconnections.app/my-referrals/?utm_source=status-bar"; window.open(url, "_external"); }) ); } else { menu.addItem( (item) => item.setTitle("Start 14-day Pro trial").setIcon("hand-heart").onClick(() => { const url = "https://smartconnections.app/pro-plugins/?utm_source=status-bar"; window.open(url, "_external"); }) ); } menu.showAtPosition({ x: ev.pageX, y: ev.pageY }); }; plugin.registerDomEvent(status_container, "contextmenu", on_context_menu); return on_context_menu; } // node_modules/obsidian-smart-env/src/components/status_bar.css var status_bar_default = ".status-bar-item:has(.smart-env-status-container) {\n padding: 0 0.5em;\n\n &:hover {\n background-color: var(--background-modifier-hover);\n }\n & > .smart-env-status-container {\n display: flex;\n align-items: center;\n gap: 0.5em;\n text-decoration: none;\n color: var(--status-bar-text-color);\n }\n}\n\n.smart-env-status-indicator {\n --smart-env-status-indicator-color: var(--status-bar-text-color);\n --smart-env-status-indicator-glow-size: 0.28em;\n --smart-env-status-indicator-glow-opacity: 0.38;\n\n position: relative;\n isolation: isolate;\n\n width: 0.62em;\n height: 0.62em;\n min-width: 0.62em;\n min-height: 0.62em;\n border-radius: 999px;\n\n color: var(--smart-env-status-indicator-color);\n background-color: currentColor;\n opacity: 1;\n transform: scale(1);\n box-shadow:\n 0 0 0 1px color-mix(in srgb, currentColor 24%, transparent),\n inset 0 0 0 1px color-mix(in srgb, var(--background-primary) 78%, transparent);\n transition:\n background-color 150ms ease,\n box-shadow 150ms ease,\n transform 150ms ease,\n color 150ms ease;\n cursor: pointer;\n}\n\n.smart-env-status-indicator::before {\n content: '';\n position: absolute;\n inset: calc(var(--smart-env-status-indicator-glow-size) * -1);\n border-radius: inherit;\n pointer-events: none;\n z-index: -1;\n opacity: var(--smart-env-status-indicator-glow-opacity);\n transform: scale(1);\n filter: blur(7px);\n background:\n radial-gradient(\n circle at center,\n color-mix(in srgb, currentColor 74%, transparent) 0%,\n color-mix(in srgb, currentColor 46%, transparent) 34%,\n color-mix(in srgb, currentColor 18%, transparent) 58%,\n transparent 80%\n );\n transition:\n opacity 150ms ease,\n transform 150ms ease,\n filter 150ms ease;\n}\n\n.smart-env-status-indicator::after {\n content: '';\n position: absolute;\n inset: -1px;\n border-radius: inherit;\n pointer-events: none;\n box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 28%, transparent);\n opacity: 0.82;\n}\n\n.smart-env-status-indicator:hover::before,\n.smart-env-status-indicator:focus-visible::before {\n opacity: max(var(--smart-env-status-indicator-glow-opacity), 0.56);\n transform: scale(1.08);\n}\n\n.smart-env-status-indicator:focus-visible {\n outline: 2px solid var(--color-accent);\n outline-offset: 2px;\n}\n\n.smart-env-status-indicator[data-level='default'] {\n --smart-env-status-indicator-color: var(--status-bar-text-color);\n --smart-env-status-indicator-glow-size: 0.24em;\n --smart-env-status-indicator-glow-opacity: 0.32;\n}\n\n.smart-env-status-indicator[data-level='info'] {\n --smart-env-status-indicator-color: var(--status-bar-text-color);\n --smart-env-status-indicator-glow-size: 0.28em;\n --smart-env-status-indicator-glow-opacity: 0.4;\n}\n\n.smart-env-status-indicator[data-level='milestone'] {\n --smart-env-status-indicator-color: var(--color-accent);\n --smart-env-status-indicator-glow-size: 0.52em;\n --smart-env-status-indicator-glow-opacity: 0.86;\n}\n\n.smart-env-status-indicator[data-level='attention'] {\n --smart-env-status-indicator-color: var(--color-yellow);\n --smart-env-status-indicator-glow-size: 0.5em;\n --smart-env-status-indicator-glow-opacity: 0.84;\n}\n\n.smart-env-status-indicator[data-level='warning'] {\n --smart-env-status-indicator-color: var(--color-orange);\n --smart-env-status-indicator-glow-size: 0.54em;\n --smart-env-status-indicator-glow-opacity: 0.9;\n}\n\n.smart-env-status-indicator[data-level='error'] {\n --smart-env-status-indicator-color: var(--color-red);\n --smart-env-status-indicator-glow-size: 0.58em;\n --smart-env-status-indicator-glow-opacity: 0.96;\n}\n\n.smart-env-status-indicator[data-count] {\n transform: scale(1.08);\n box-shadow:\n 0 0 0 1px color-mix(in srgb, currentColor 30%, transparent),\n inset 0 0 0 1px color-mix(in srgb, var(--background-primary) 84%, transparent);\n}\n\n.smart-env-status-indicator[data-count]::before {\n opacity: 1;\n transform: scale(1.12);\n filter: blur(7px);\n animation: smart-env-status-indicator-glow 2200ms ease-in-out infinite;\n}\n\n.status-bar-mobile {\n position: var(--status-bar-position);\n bottom: 0;\n border-radius: 0 8px 0 0;\n border-style: solid;\n border-width: 1px;\n border-color: var(--status-bar-border-color);\n background-color: var(--status-bar-background);\n color: var(--status-bar-text-color);\n font-size: var(--status-bar-font-size);\n min-height: 18px;\n padding: var(--size-4-1);\n user-select: none;\n z-index: var(--layer-status-bar);\n font-variant-numeric: tabular-nums;\n & > .smart-env-status-container {\n padding: 5px 5px 5px 0;\n }\n}\n\n/* footer view on mobile */\n.embedded-backlinks > .status-bar-mobile {\n position: relative;\n border-style: none;\n}\n\n@keyframes smart-env-status-indicator-glow {\n 0%,\n 100% {\n transform: scale(0.88);\n opacity: 0.92;\n }\n\n 50% {\n transform: scale(1.23);\n opacity: 1;\n }\n}\n"; // node_modules/obsidian-smart-env/src/components/status_bar.js function build_html23() { return ` `; } async function render25(env, opts = {}) { this.apply_style_sheet(status_bar_default); const frag = this.create_doc_fragment(build_html23()); const anchor = frag.firstElementChild; post_process22.call(this, env, anchor, opts); return anchor; } function post_process22(env, container, opts = {}) { const icon_slot = container?.querySelector?.(".smart-env-status-icon"); const status_indicator = container?.querySelector?.(".smart-env-status-indicator"); const status_msg = container?.querySelector?.(".smart-env-status-msg"); const pause_embedding = () => { return env?.smart_sources?.entities_vector_adapter?.halt_embed_queue_processing?.(); }; const resume_embedding = () => { return env?.smart_sources?.entities_vector_adapter?.resume_embed_queue_processing?.(); }; const set_status_message = (message) => { if (!status_msg) return; if (typeof status_msg.setText === "function") { status_msg.setText(message); return; } status_msg.textContent = message; }; const open_notifications_feed3 = (event) => { event.preventDefault?.(); event.stopPropagation?.(); env.open_notifications_feed_modal?.(); }; const update_indicator = (status_state) => { if (!status_indicator) return; const { indicator_count, indicator_level } = status_state; status_indicator.dataset.level = indicator_level || "default"; if (indicator_count > 0) { status_indicator.dataset.count = String(indicator_count); } else { status_indicator.removeAttribute("data-count"); } const indicator_title = indicator_count > 0 ? `${indicator_count} unseen notification${indicator_count === 1 ? "" : "s"}` : "Open notifications feed"; status_indicator.setAttribute("title", indicator_title); }; const action_handlers = { pause_embed(event) { event.preventDefault?.(); event.stopPropagation?.(); pause_embedding(); }, resume_embed(event) { event.preventDefault?.(); event.stopPropagation?.(); resume_embedding(); }, run_reimport(event) { event.preventDefault?.(); event.stopPropagation?.(); set_status_message("Re-importing\u2026"); env.run_re_import?.(); }, noop() { }, context_menu(event) { const context_event = new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: event?.clientX || 0, clientY: event?.clientY || 0 }); container.dispatchEvent?.(context_event); } }; const render_status_elm = () => { const status_state = get_status_bar_state(env); const { message, title } = status_state; if (icon_slot) { (0, import_obsidian33.setIcon)(icon_slot, "smart-connections"); } update_indicator(status_state); set_status_message(message); container.setAttribute?.("title", title); container.removeAttribute?.("href"); container.removeAttribute?.("target"); }; const run_container_action = (event) => { const status_state = get_status_bar_state(env); const action_key = Object.prototype.hasOwnProperty.call(action_handlers, status_state.click_action) ? status_state.click_action : "context_menu"; action_handlers[action_key](event); }; register_status_bar_context_menu(env, container); let progress_poll_interval = null; const set_polling = (active) => { if (active) { if (progress_poll_interval) return; progress_poll_interval = setInterval(() => { refresh_status_bar(); }, 1e3); return; } if (!progress_poll_interval) return; clearInterval(progress_poll_interval); progress_poll_interval = null; }; const refresh_status_bar = () => { render_status_elm(); set_polling(should_poll_env_activity(env)); }; refresh_status_bar(); bind_once(status_indicator, "_click_handler", "click", open_notifications_feed3); bind_once(status_indicator, "_keydown_handler", "keydown", (event) => { if (event.key !== "Enter" && event.key !== " ") return; open_notifications_feed3(event); }); bind_once(container, "_click_handler", "click", (event) => { run_container_action(event); }); bind_once(container, "_keydown_handler", "keydown", (event) => { if (event.key !== "Enter" && event.key !== " ") return; event.preventDefault(); run_container_action(event); }); let debounce_timeout = null; const debounce_refresh_status_bar = () => { if (debounce_timeout) clearTimeout(debounce_timeout); debounce_timeout = setTimeout(() => { refresh_status_bar(); debounce_timeout = null; }, 100); }; const disposers = []; disposers.push(env.events.on("*", debounce_refresh_status_bar)); disposers.push(() => { set_polling(false); if (debounce_timeout) clearTimeout(debounce_timeout); }); this.attach_disposer(container, disposers); } function bind_once(element, handler_key, event_name, handler) { if (!element || typeof handler !== "function") return; if (element[handler_key]) return; element[handler_key] = handler; element.addEventListener(event_name, handler); } // node_modules/obsidian-smart-env/src/components/suggest_display_right.css var suggest_display_right_default = ".sc-modal-suggestion-right {\r\n margin-left: auto;\r\n text-align: right;\r\n white-space: nowrap;\r\n font-size: var(--font-ui-smaller);\r\n color: var(--text-muted);\r\n font-variant-numeric: tabular-nums;\r\n float: right;\r\n}"; // node_modules/obsidian-smart-env/src/components/suggest_display_right.js function build_html24(display_right, params = {}) { return `${display_right}`; } function render26(display_right, params = {}) { this.apply_style_sheet(suggest_display_right_default); const frag = this.create_doc_fragment(build_html24(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_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 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_html26(plugin, opts = {}) { const { plugin_name = plugin.manifest.name } = opts; return `
    `; } function render28(plugin, opts = {}) { const html = build_html26.call(this, plugin, opts); const frag = this.create_doc_fragment(html); const callout = frag.querySelector("#footer-callout"); const icon_container = callout.querySelector(".callout-icon"); const icon = (0, import_obsidian35.getIcon)("smart-connections"); if (icon) { this.empty(icon_container); icon_container.appendChild(icon); } post_process24.call(this, plugin, callout, opts); return callout; } function post_process24(plugin, callout) { } // node_modules/obsidian-smart-env/src/utils/smart-context/format_stats_message.js function format_stats_message(stats = {}) { const item_count = Number.isFinite(stats.item_count) ? stats.item_count : 0; const char_count = Number.isFinite(stats.char_count) ? stats.char_count : 0; const segments = []; segments.push(`${item_count} file(s)`); segments.push(`${format_char_count(char_count)} chars`); if (Number.isFinite(stats.max_depth)) { segments.push(`depth\u2264${stats.max_depth}`); } const excluded_total = sum_exclusions(stats.exclusions); if (excluded_total > 0) { segments.push(`${excluded_total} section(s) excluded`); } return `Copied to clipboard! (${segments.join(", ")})`; } function format_char_count(char_count) { if (!Number.isFinite(char_count)) return "0"; if (char_count >= 1e5) { return `~${Math.round(char_count / 1e3)}k`; } return char_count.toLocaleString(); } function sum_exclusions(exclusions) { if (!exclusions) return 0; return Object.values(exclusions).reduce((total, value) => { const numeric = Number.isFinite(value) ? value : 0; return total + numeric; }, 0); } // node_modules/obsidian-smart-env/src/actions/context/copy_to_clipboard.js async function copy_to_clipboard2(params = {}) { const 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}}", "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 MERGE_VARS = { "KEY": this.key, "ITEM_NAME": get_item_name(this), "TIME_AGO": convert_to_time_ago(this.mtime) || "Missing", "LINK_DEPTH": this.data.d || "0", "EXT": this.item_ref?.file_type || "" }; const replace_vars = async (template) => { const re_var = /{{([\w_]+)}}/g; const number_of_var_matches = (template.match(re_var) || []).length; for (let i = 0; i < number_of_var_matches; i++) { template = template.replace(/{{(\w+)}}/g, (match, p1) => { return MERGE_VARS[p1] || ""; }); } return template; }; const templates = get_item_templates(this.settings, default_settings2); if (params.json_stringify || templates.json_stringify) { item_text = JSON.stringify(item_text); } const before = await replace_vars(templates.template_before); const after = await replace_vars(templates.template_after); return ["", before, item_text, after, ""].join("\n"); } var settings_config7 = { template_preset: { group: "Item templates", type: "dropdown", name: "Select template", description: "Wraps each context item with a pre-configured template.", options_callback: () => get_template_preset_options(), callback(template_value) { const is_pro = this?.env?.is_pro; if (!is_pro) return; if (template_value !== "custom") return; this.emit_event("context_item:custom_template_set"); } }, template_before: { group: "Item templates", type: "textarea", name: "Template Before", description: "Template to wrap before the context item content.", scope_class: "pro-setting" }, template_after: { group: "Item templates", type: "textarea", name: "Template After", description: "Template to wrap after the context item content.", scope_class: "pro-setting" }, item_explanation: { type: "html", group: "Item templates", value: ` Available variables:
    • {{KEY}} - Full path of the item
    • {{ITEM_NAME}} - Source file or block name without folder path or file extension
    • {{TIME_AGO}} - Time since the item was last modified
    • {{LINK_DEPTH}} - Depth level of the item
    • {{EXT}} - File extension of the item
    ` }, json_stringify: { group: "Item templates", type: "toggle", name: "JSON Stringify", description: "Convert the item content to a JSON string (forces full content into single line in quotes).", scope_class: "pro-setting" } }; var default_settings2 = { template_preset: "xml_structured", template_before: '', template_after: "" }; // node_modules/obsidian-smart-env/node_modules/smart-utils/file_tree.js function build_file_tree_string(paths = []) { if (!Array.isArray(paths) || paths.length === 0) return ""; const root = {}; for (const path of paths) { const isFolder = is_folder_path(path); const parts = path.split("/").filter(Boolean); let node = root; for (let i = 0; i < parts.length; i++) { const part = parts[i]; const isLast = i === parts.length - 1; if (isLast) { if (isFolder) { node[part] = node[part] ?? { __isExplicitFolder: true }; } else { node[part] = null; } } else { node = node[part] ??= {}; } } } compress_single_child_dirs(root); return build_tree_string(root).trimEnd(); } function is_folder_path(path) { return typeof path === "string" && path.endsWith("/"); } function compress_single_child_dirs(node) { if (!node || typeof node !== "object") return; for (const key of Object.keys(node)) { const child = node[key]; if (child && typeof child === "object") { if (child.__isExplicitFolder) { delete child.__isExplicitFolder; compress_single_child_dirs(child); continue; } const childKeys = Object.keys(child); if (childKeys.length === 1 && child[childKeys[0]] !== null && !child[childKeys[0]].__isExplicitFolder) { const mergedKey = `${key}/${childKeys[0]}`; node[mergedKey] = child[childKeys[0]]; delete node[key]; compress_single_child_dirs(node[mergedKey]); } else { compress_single_child_dirs(child); } } } } function build_tree_string(node, prefix = "") { let output = ""; const entries = Object.entries(node).sort((a, b) => { const aIsDir = a[1] !== null; const bIsDir = b[1] !== null; if (aIsDir && !bIsDir) return -1; if (!aIsDir && bIsDir) return 1; return a[0].localeCompare(b[0]); }); entries.forEach(([name, child], idx) => { const isLast = idx === entries.length - 1; const connector = isLast ? "\u2514\u2500\u2500 " : "\u251C\u2500\u2500 "; if (child === null) { output += `${prefix}${connector}${name} `; } else { output += `${prefix}${connector}${name}/ `; output += build_tree_string(child, prefix + (isLast ? " " : "\u2502 ")); } }); return output; } // node_modules/obsidian-smart-env/src/actions/context/merge_template.js async function merge_template2(context_items_text, params = {}) { const context_items = params.context_items || []; const MERGE_VARS = { "FILE_TREE": () => { return build_file_tree_string(context_items.map((c) => c.key)); } }; 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
    ` } }; var default_settings3 = { template_preset: "xml_structured", template_before: "\n{{FILE_TREE}}", template_after: "" }; // node_modules/obsidian-smart-env/src/actions/context-suggest/blocks.js function context_suggest_blocks(params = {}) { params?.modal?.setInstructions([ { command: "Enter", purpose: "Add block to context" }, { command: "\u2190", purpose: "Back to sources" } ]); let blocks = []; if (params.source_key) { const src = this.env.smart_sources.get(params.source_key); blocks = src.blocks; } else { blocks = Object.values(this.env.smart_blocks.items); } return blocks.sort((a, b) => { const a_line = Array.isArray(a.lines) && a.lines.length ? a.lines[0] : Infinity; const b_line = Array.isArray(b.lines) && b.lines.length ? b.lines[0] : Infinity; return a_line - b_line; }).map((block) => ({ key: block.key, display: get_block_display_name2(block, { show_full_path: false }), select_action: () => { this.add_item(block.key); }, arrow_left_action: ({ modal }) => { modal.update_suggestions("context_suggest_sources"); } })); } var display_name = "Add blocks"; // node_modules/obsidian-smart-env/src/actions/context-suggest/contexts.js var import_obsidian36 = require("obsidian"); var display_name2 = "Add named contexts"; var MOD_CHAR = import_obsidian36.Platform.isMacOS ? "\u2318" : "Ctrl"; function set_named_context_list_instructions(modal) { modal?.setInstructions?.([ { command: "Enter / \u2192", purpose: "Browse context items" }, { command: `${MOD_CHAR} + Enter`, purpose: "Add all items from context" } ]); } function set_named_context_item_instructions(modal, params = {}) { const context_name = params.context_name; modal?.setInstructions?.([ { command: "Enter", purpose: `Add item from ${context_name || "context"}` }, { command: "\u2190", purpose: "Back to named contexts" } ]); } function list_context_items2(env) { const collection = env?.smart_contexts; const items = collection?.items; if (!items || typeof items !== "object") return []; return Object.values(items).filter(Boolean); } function get_items_from_context(other_ctx) { const data = other_ctx?.data?.context_items || {}; const entries = Object.entries(data); const out = []; for (let i = 0; i < entries.length; i += 1) { const [key, item_data] = entries[i]; if (!key) continue; if (item_data?.exclude) continue; out.push({ key }); } return out; } function build_named_context_item_payloads(ctx, params = {}) { const other_ctx = params.other_ctx; const context_name = params.context_name; const include_named_context = Boolean(params.include_named_context); const items = get_items_from_context(other_ctx); for (let i = 0; i < items.length; i += 1) { const item = items[i]; if (!item || typeof item.key !== "string") continue; if (!include_named_context) continue; item.from_named_context = context_name; } return items; } function format_depth_label(depth) { if (!Number.isFinite(depth)) return ""; return `depth ${depth}`; } function build_named_context_item_suggestions(ctx, params = {}) { const payloads = build_named_context_item_payloads(ctx, { ...params, include_named_context: false }); set_named_context_item_instructions(params?.modal, { context_name: params.context_name }); return payloads.filter((payload) => typeof payload?.key === "string" && payload.key.length).map((payload) => ({ key: payload.key, display: payload.key, display_right: format_depth_label(payload.d), select_action: ({ modal } = {}) => { ctx.add_item(payload); }, arrow_left_action: ({ modal } = {}) => { return context_suggest_contexts.call(ctx, { modal }); } })); } async function context_suggest_contexts(params = {}) { const ctx = this; const env = ctx?.env; const modal = params?.modal; set_named_context_list_instructions(modal); const contexts = list_context_items2(env).filter((context_item) => { const name = context_item?.data?.name; return typeof name === "string" && name.trim().length > 0; }).sort((a, b) => { const name_a = String(a.data.name).trim().toLowerCase(); const name_b = String(b.data.name).trim().toLowerCase(); return name_a.localeCompare(name_b); }); if (!contexts.length) { return [{ key: "contexts:none", display: "No named contexts found" }]; } const suggestions = []; for (let i = 0; i < contexts.length; i += 1) { const other = contexts[i]; const other_key = other?.key || other?.data?.key; const other_name = String(other?.data?.name || "").trim(); const already_included = Boolean( ctx?.data?.context_items && Object.values(ctx.data.context_items).some( (item) => item?.from_named_context === other_name || item?.key === other_key ) ); if (already_included) continue; const item_count = other?.item_count || Object.keys(other?.data?.context_items || {}).length; suggestions.push({ key: `named_context:${other_key}`, display: `${other_name} (${item_count})`, item: other, select_action: ({ modal: modal2 }) => { return [ { key: other_name, display: `Add all: ${other_name} (${item_count})`, item: other, select_action: ({ modal: modal3 } = {}) => { ctx.add_item({ key: `${other_name}`, named_context: true }); return context_suggest_contexts.call(ctx, { modal: modal3 }); }, arrow_left_action: ({ modal: modal3 } = {}) => { return context_suggest_contexts.call(ctx, { modal: modal3 }); } }, ...build_named_context_item_suggestions(ctx, { other_ctx: other, context_name: other_name, modal: modal2 }) ]; }, arrow_right_action: ({ modal: modal2 }) => { return build_named_context_item_suggestions(ctx, { other_ctx: other, context_name: other_name, modal: modal2 }); }, mod_select_action: ({ modal: modal2 } = {}) => { ctx.add_item({ key: `${other_name}`, named_context: true }); return context_suggest_contexts.call(ctx, { modal: modal2 }); } }); } return suggestions; } // node_modules/obsidian-smart-env/src/actions/context-suggest/sources.js var import_obsidian37 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/smart-context/source_folder_utils.js function normalize_folder_path(folder_path = "") { return String(folder_path ?? "").trim().replace(/\\+/g, "/").replace(/\/+$/g, ""); } function reset_modal_input(modal) { if (!modal?.inputEl) return; modal.last_input_value = modal.inputEl.value; modal.inputEl.value = ""; } function get_sources_list(ctx, params = {}) { const folder_path = params?.folder_path || ""; const normalized_folder_path = normalize_folder_path(folder_path); if (!normalized_folder_path) { return Object.values(ctx?.env?.smart_sources?.items || {}); } const starts_with_folder_path = `${normalized_folder_path}/`; return ctx?.env?.smart_sources?.filter((item) => { if (item.key === normalized_folder_path) return true; return item.key.startsWith(starts_with_folder_path); }); } // node_modules/obsidian-smart-env/src/actions/context-suggest/sources.js var MOD_CHAR2 = import_obsidian37.Platform.isMacOS ? "\u2318" : "Ctrl"; function build_source_suggestions(ctx, sources) { return sources.map((source) => ({ key: source.key, display: source.key, select_action: () => { ctx.add_item(source.key); }, mod_select_action: ({ modal } = {}) => { reset_modal_input(modal); return context_suggest_blocks.call(ctx, { source_key: source.key, modal }); }, arrow_right_action: ({ modal } = {}) => { reset_modal_input(modal); return context_suggest_blocks.call(ctx, { source_key: source.key, modal }); } })); } function context_suggest_sources(params = {}) { const modal = params?.modal; if (modal) { modal.setInstructions([ { command: "Enter", purpose: "Add source to context" }, { command: `${MOD_CHAR2} + Enter / \u2192`, purpose: "Suggest source blocks" } ]); } const sources = get_sources_list(this, { folder_path: params?.folder_path || "" }); return build_source_suggestions(this, sources); } var display_name3 = "Add sources"; // node_modules/obsidian-smart-env/src/actions/lookup-list/pre_process.js async function pre_process(params) { const query = params.query; if (!query || typeof query !== "string" || query.trim().length === 0) { throw new Error("Invalid or empty query provided to lookup list."); } const embed_model = this.env.smart_sources.embed_model; if (!embed_model) { throw new Error("No embed model available in environment for lookup list."); } const embedding = await embed_model.embed(query); params.to_item = { ...embedding }; if (!params.score_algo_key) params.score_algo_key = "similarity"; return params; } // node_modules/obsidian-smart-env/src/actions/similarity.js function similarity(params) { if (!this.vec) return { score: null, error: `Missing this.vec for ${this.key}` }; if (!params.to_item?.vec) return { score: null, error: "Missing params.to_item.vec" }; return { score: cos_sim(this.vec || [], params.to_item.vec || []) }; } similarity.action_type = "score"; var display_name4 = "Cosine Similarity"; var display_description = "Ranks by cosine similarity between the current note and candidates."; var settings_config9 = { similarity_algo_description: { group: "Score algorithm", type: "html", name: `${display_name4} algorithm`, value: `${display_description}` } }; // node_modules/obsidian-smart-env/src/utils/open_source.js var import_obsidian38 = require("obsidian"); async function open_source(item, event = null) { try { const env = item.env; const obsidian_app = env.obsidian_app; let target_path = item.key; if (target_path.endsWith("#")) target_path = target_path.slice(0, -1); let target_file; if (target_path.includes("#")) { const [file_path] = target_path.split("#"); target_file = obsidian_app.metadataCache.getFirstLinkpathDest(file_path, ""); } else { target_file = obsidian_app.metadataCache.getFirstLinkpathDest(target_path, ""); } if (!target_file) { const message = `Unable to resolve file for ${target_path}`; console.warn(`[open_source] ${message}`); item.emit_event("sources:open_failed", { level: "warning", message, event_source: "open_source" }); return; } let leaf; if (event) { const is_mod = import_obsidian38.Keymap.isModEvent(event); const is_alt = import_obsidian38.Keymap.isModifier(event, "Alt"); if (is_mod && is_alt) { leaf = obsidian_app.workspace.splitActiveLeaf("vertical"); } else if (is_mod) { leaf = obsidian_app.workspace.getLeaf(true); } else { leaf = obsidian_app.workspace.getMostRecentLeaf(); } } else { leaf = obsidian_app.workspace.getMostRecentLeaf(); } await leaf.openFile(target_file); if (typeof item?.line_start === "number") { const { editor } = leaf.view; const pos = { line: item.line_start, ch: 0 }; editor.setCursor(pos); editor.scrollIntoView({ to: pos, from: pos }, true); } item.emit_event("sources:opened", { event_source: "open_source" }); } catch (error) { console.error("Error in open_source:", error); item.emit_event("sources:open_failed", { level: "error", message: error?.message || "Failed to open source.", details: error?.stack || "", event_source: "open_source" }); } } // node_modules/obsidian-smart-env/src/actions/source/open.js async function source_open(event = null) { await open_source(this, event); } // node_modules/obsidian-smart-env/smart_env.config.js var smart_env_config = { collections: { embedding_models: embedding_models_default2, event_logs: event_logs_default2, lookup_lists: lookup_lists_default, smart_blocks: smart_blocks_default, smart_contexts: smart_contexts_default3 }, items: { embedding_model: { class: EmbeddingModel, version: "1.0.0" }, lookup_list: { class: LookupList, version: "1.0.0" }, smart_block: { class: SmartBlock2, version: "1.0.0" }, smart_context: { class: SmartContext2, version: "1.0.0" } }, modules: {}, components: { collection_settings: { render, version: "1.0.0" }, context_item_leaf: { render: render2, version: "1.0.0" }, default_notification: { render: render3, version: "1.0.0" }, env_stats: { render: render4, version: "1.0.0" }, env_status: { render: render5, version: "1.0.0" }, lean_coffee_callout: { render: render6, version: "1.0.0" }, milestone_notification: { render: render7, version: "1.0.0" }, milestones: { render: render8, version: "1.0.0" }, notifications_feed: { render: render9, version: "1.0.0" }, settings_env_model: { render: render10, version: "1.0.0" }, settings_env_model_type: { render: render11, version: "1.0.0" }, settings_env_models: { render: render12, version: "1.0.0" }, settings_env_sources: { render: render13, version: "1.0.0" }, settings_model_actions: { render: render14, version: "1.0.0" }, settings_smart_env: { render: render15, version: "1.0.0" }, smart_context_actions: { render: render16, version: "1.0.0" }, smart_context_item: { render: render17, version: "1.0.0" }, smart_context_meta: { render: render18, version: "1.0.0" }, smart_context_tree: { render: render19, version: "1.0.0" }, smart_plugins_list: { render: render20, version: "1.0.0" }, smart_plugins_list_item: { render: render21, version: "1.0.0" }, smart_plugins_login: { render: render22, version: "1.0.0" }, smart_plugins_referral: { render: render23, version: "1.0.0" }, source_inspector: { render: render24, version: "1.0.0" }, status_bar: { render: render25, version: "1.0.0" }, suggest_display_right: { render: render26, version: "1.0.0" }, supporter_callout: { render: render27, version: "1.0.0" }, user_agreement_callout: { render: render28, version: "1.0.0" } }, actions: { context_copy_to_clipboard: { action: copy_to_clipboard2, version: "1.0.0" }, context_item_merge_template: { action: merge_template, settings_config: settings_config7, default_settings: default_settings2, version: "1.0.0" }, context_merge_template: { action: merge_template2, settings_config: settings_config8, default_settings: default_settings3, version: "1.0.0" }, context_suggest_blocks: { action: context_suggest_blocks, display_name, version: "1.0.0" }, context_suggest_contexts: { action: context_suggest_contexts, display_name: display_name2, version: "1.0.0" }, context_suggest_sources: { action: context_suggest_sources, display_name: display_name3, version: "1.0.0" }, lookup_list_pre_process: { action: pre_process, pre_process, version: "1.0.0" }, similarity: { action: similarity, settings_config: settings_config9, display_name: display_name4, display_description, version: "1.0.0" }, source_open: { action: source_open, version: "1.0.0" } } }; // node_modules/obsidian-smart-env/default.config.js var smart_env_config2 = { env_path: "", modules: { smart_fs: { class: SmartFs, adapter: ObsidianFsAdapter }, smart_view: { class: SmartView, adapter: SmartViewObsidianAdapter }, smart_embed_model: { class: SmartEmbedModel, adapters: { transformers: SmartEmbedTransformersIframeAdapter, openai: SmartEmbedOpenAIAdapter, ollama: SmartEmbedOllamaAdapter, gemini: GeminiEmbedModelAdapter, lm_studio: LmStudioEmbedModelAdapter } }, smart_chat_model: { class: SmartChatModel, // DEPRECATED FORMAT: will be changed (requires SmartModel adapters getters update) adapters: { anthropic: SmartChatModelAnthropicAdapter, azure: SmartChatModelAzureAdapter, custom: SmartChatModelCustomAdapter, google: SmartChatModelGoogleAdapter, gemini: SmartChatModelGeminiAdapter, groq: SmartChatModelGroqAdapter, lm_studio: SmartChatModelLmStudioAdapter, ollama: SmartChatModelOllamaAdapter, open_router: SmartChatModelOpenRouterAdapter, openai: SmartChatModelOpenaiAdapter, xai: SmartChatModelXaiAdapter, deepseek: SmartChatModelDeepseekAdapter }, http_adapter: new SmartHttpRequest({ adapter: SmartHttpObsidianRequestAdapter, obsidian_request_url: import_obsidian41.requestUrl }) }, http_adapter: { class: SmartHttpRequest, adapter: SmartHttpObsidianRequestAdapter, obsidian_request_url: import_obsidian41.requestUrl } }, collections: { context_items: context_items_default, event_logs: event_logs_default2, smart_components: smart_components_default2, smart_sources: { collection_key: "smart_sources", class: SmartSources, data_adapter: AjsonMultiFileSourcesDataAdapter, source_adapters: { "md": ObsidianMarkdownSourceContentAdapter, "txt": ObsidianMarkdownSourceContentAdapter, "excalidraw.md": ExcalidrawSourceContentAdapter, "base": BasesSourceContentAdapter, "canvas": CanvasSourceContentAdapter, "rendered": RenderedSourceContentAdapter // "canvas": MarkdownSourceContentAdapter, // "default": MarkdownSourceContentAdapter, }, content_parsers: [ parse_blocks ], // process_embed_queue: false, process_embed_queue: true, // trigger embedding on load load_order: 100 // load last } // smart_blocks: { // collection_key: 'smart_blocks', // class: SmartBlocks, // data_adapter: AjsonMultiFileBlocksDataAdapter, // block_adapters: { // "md": MarkdownBlockContentAdapter, // "txt": MarkdownBlockContentAdapter, // "excalidraw.md": MarkdownBlockContentAdapter, // // "canvas": MarkdownBlockContentAdapter, // }, // }, }, items: { smart_source: smart_source_default // smart_block, }, default_settings, // begin obsidian-smart-env specific modules (need to update build_env_config.js to handle) modals: { context_selector: { class: ContextModal, default_suggest_action_keys: [ "context_suggest_sources" ] }, milestones_modal: { class: MilestonesModal }, notifications_feed_modal: { class: NotificationsFeedModal }, browse_plugins_modal: { class: BrowseSmartPlugins } } }; merge_env_config(smart_env_config2, smart_env_config); var default_config_default = smart_env_config2; // node_modules/obsidian-smart-env/utils/add_icons.js var import_obsidian42 = require("obsidian"); var svg_wrap_24 = (inner_svg) => { return `${inner_svg}`; }; var smart_copy_note_svg = svg_wrap_24(` `); var smart_context_builder_svg = svg_wrap_24(` `); var smart_inline_connections_svg = svg_wrap_24(` `); function add_smart_chat_icon() { (0, import_obsidian42.addIcon)("smart-chat", ` `); } function add_smart_connections_icon() { (0, import_obsidian42.addIcon)("smart-connections", ` `); } function add_smart_lookup_icon() { (0, import_obsidian42.addIcon)("smart-lookup", ` `); } function add_smart_copy_context_icon() { (0, import_obsidian42.addIcon)("smart-copy-note", smart_copy_note_svg); } function add_smart_context_icon() { (0, import_obsidian42.addIcon)("smart-context-builder", smart_context_builder_svg); } function add_inline_connections_icon() { (0, import_obsidian42.addIcon)("smart-inline-connections", smart_inline_connections_svg); } var smart_footer_connections_svg = svg_wrap_24(` `); function add_footer_connections_icon() { (0, import_obsidian42.addIcon)("smart-footer-connections", smart_footer_connections_svg); } function add_smart_dupe_detector_icon() { (0, import_obsidian42.addIcon)("smart-dupe-detector", ` `); } function add_smart_named_contexts_icon() { (0, import_obsidian42.addIcon)("smart-named-contexts", ` `); } var smart_graph_svg = ` `; function add_smart_graph_icon() { (0, import_obsidian42.addIcon)("smart-graph", smart_graph_svg); } function add_smart_icons() { add_smart_copy_context_icon(); add_smart_context_icon(); add_inline_connections_icon(); add_footer_connections_icon(); add_smart_dupe_detector_icon(); add_smart_named_contexts_icon(); add_smart_graph_icon(); } // node_modules/obsidian-smart-env/node_modules/smart-notices/smart_notices.js var import_obsidian43 = 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_obsidian46.ItemView { /** * Creates an instance of SmartItemView. * @param {any} leaf * @param {any} plugin */ constructor(leaf, plugin) { super(leaf); this.app = plugin.app; this.plugin = plugin; } /** * The unique view type. Must be implemented in subclasses. * @returns {string} */ static get view_type() { throw new Error("view_type must be implemented in subclass"); } /** * The display text for this view. Must be implemented in subclasses. * @returns {string} */ static get display_text() { throw new Error("display_text must be implemented in subclass"); } /** * The icon name for this view. * @returns {string} */ static get icon_name() { return "smart-connections"; } /** * Whether the view should wait for the environment to be loaded before rendering. * Override in subclasses that must stay visible during environment load. * @returns {boolean} */ static get wait_for_env() { return true; } /** * Registers this ItemView subclass against a plugin instance and * installs ergonomic accessors, an open helper, and an `${view_type}:open` listener. * * Usage from a plugin class: * SubClass.register_item_view(this); * * This will: * - call plugin.registerView(view_type, ...) * - add a command "Open: view" * - define a getter on plugin: plugin[method_name] -> the view instance * - define a method on plugin: plugin["open_" + method_name]() -> opens the view * - listen for env events named `${view_type}:open` and open the view when emitted * * @param {import('obsidian').Plugin} plugin * @returns {{method_name:string, open_method_name:string, event_name:string}} */ static register_item_view(plugin) { const View = ( /** @type {typeof SmartItemView} */ this ); if (plugin.app.viewRegistry?.viewByType?.[View.view_type]) { plugin.app.viewRegistry.unregisterView(View.view_type); console.warn(`View type "${View.view_type}" was already registered. Overwriting with new registration.`); } plugin.registerView(View.view_type, (leaf) => new View(leaf, plugin)); plugin.addCommand({ id: View.view_type, name: "Open: " + View.display_text + " view", callback: () => { View.open(plugin.app.workspace); } }); const method_name = View.view_type.replace(/^smart-/, "").replace(/-/g, "_"); const open_method_name = "open_" + method_name; if (!Object.getOwnPropertyDescriptor(plugin, method_name)) { Object.defineProperty(plugin, method_name, { configurable: true, enumerable: false, get: () => View.get_view(plugin.app.workspace) }); } plugin[open_method_name] = (params = {}) => { if (!plugin.app.viewRegistry?.viewByType?.[View.view_type]) { console.warn(`View type "${View.view_type}" not registered when calling ${open_method_name}. Registering now. This should NOT happen frequently.`); plugin.registerView(View.view_type, (leaf) => new View(leaf, plugin)); } View.open(plugin.app.workspace, params); }; const event_name = `${method_name}:open`; const handler = (payload = {}) => { const active = typeof payload?.active === "boolean" ? payload.active : true; View.open(plugin.app.workspace, { ...payload, active }); }; const unsubscribe = plugin?.env?.events.on(event_name, handler); if (typeof plugin.register === "function" && typeof unsubscribe === "function") { plugin.register(() => unsubscribe()); } return { method_name, open_method_name, event_name }; } /** * Retrieves the Leaf instance for this view type if it exists. * @param {import("obsidian").Workspace} workspace * @returns {import("obsidian").WorkspaceLeaf | undefined} */ static get_leaf(workspace) { return workspace.getLeavesOfType(this.view_type)[0]; } /** * Retrieves the view instance if it exists. * @param {import("obsidian").Workspace} workspace * @returns {SmartItemView | undefined} */ static get_view(workspace) { const leaf = this.get_leaf(workspace); return leaf ? leaf.view : void 0; } /** * Opens the view. If `this.default_open_location` is "root", * it opens (or reveals) in a root leaf; otherwise in a sidebar leaf. * * @param {import("obsidian").Workspace} workspace * @param {boolean|object} [params={}] */ static open(workspace, params = {}) { if (typeof params === "boolean") { params = { active: params }; } const { active = true, state = null } = params; const existing_leaf = this.get_leaf(workspace); const open_location = this.default_open_location; let leaf; if (open_location === "root") { leaf = existing_leaf || workspace.getLeaf(false); } else if (open_location === "left") { leaf = existing_leaf || workspace.getLeftLeaf(false); if (workspace.leftSplit?.collapsed) { workspace.leftSplit.toggle(); } } else { leaf = existing_leaf || workspace.getRightLeaf(false); if (workspace.rightSplit?.collapsed) { workspace.rightSplit.toggle(); } } const view_state = { type: this.view_type, active }; if (state && typeof state === "object") { view_state.state = state; } Promise.resolve(leaf?.setViewState?.(view_state)).then(() => { if (active) { try { workspace.revealLeaf?.(leaf); } catch (error) { console.warn(`Failed to reveal item view "${this.view_type}"`, error); } } setTimeout(() => { this.get_view(workspace)?.render_view(params); }, 100); }).catch((error) => { console.error(`Failed to open item view "${this.view_type}"`, error); }); } static is_open(workspace) { return this.get_leaf(workspace)?.view instanceof this; } // instance getViewType() { return this.constructor.view_type; } getDisplayText() { return this.constructor.display_text; } getIcon() { return this.constructor.icon_name; } async onOpen() { this.app.workspace.onLayoutReady(this.initialize.bind(this)); } async initialize() { await this.render_mobile_status_bar(); const should_wait_for_env = this.constructor.wait_for_env !== false; const wait_for_states = ["loaded"]; if (should_wait_for_env) { await wait_for_env_to_load(this, { wait_for_states }); } this.container?.empty?.(); this.register_plugin_events(); this.app.workspace.registerHoverLinkSource(this.constructor.view_type, { display: this.getDisplayText(), defaultMod: true }); this.render_view(); } async render_mobile_status_bar() { if (!import_obsidian46.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 render25.call(this.env.smart_view, this.env); if (status_bar) status_bar_item.appendChild(status_bar); } catch (error) { console.error("Failed to render mobile Smart Env status bar", error); } } register_plugin_events() { } render_view(params = {}) { throw new Error("render_view must be implemented in subclass"); } get container() { return this.containerEl.children[1]; } get env() { return this.plugin.env; } async open_settings() { await this.app.setting.open(); await this.app.setting.openTabById(this.plugin.manifest.id); } }; // node_modules/obsidian-smart-env/src/views/env_status_view.js var EnvStatusView = class extends SmartItemView { static view_type = "smart-env-status-view"; static display_text = "Smart Environment Status"; static get icon_name() { return "gauge"; } static get default_open_location() { return import_obsidian47.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 render5.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_obsidian50 = require("obsidian"); // node_modules/obsidian-smart-env/src/utils/render_pre_env_load.js var import_obsidian48 = 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_obsidian48.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_obsidian49 = require("obsidian"); function render_plugin_store_setting(scope, container) { if (!container) return null; container.empty?.(); const setting = new import_obsidian49.Setting(container).setName("Browse Smart Plugins").setDesc("Discover Core (free) and Pro Smart Plugins to supercharge your Obsidian AI experience."); setting.addButton((btn) => { btn.setButtonText("Browse Smart Plugins"); btn.onClick(() => { scope.env?.events?.emit?.("smart_plugins:browse", { event_source: `${scope.id}-settings` }); }); }); return setting; } // node_modules/obsidian-smart-env/src/views/settings.css var settings_default = `/* 1) Host elements that should get a PRO badge */\r :is(\r .pro-setting .setting-item-name\r ) {\r position: relative; /* safe default, keeps ::after anchored */\r }\r \r /* 2) The PRO badge itself */\r :is(\r .pro-setting .setting-item-name:not(:empty)\r )::after {\r content: "PRO";\r \r /* layout */\r display: inline-flex;\r align-items: center;\r justify-content: center;\r margin-left: 0.4em;\r padding: 0.08em 0.55em;\r border-radius: 999px;\r white-space: nowrap;\r vertical-align: middle;\r \r /* typography */\r font-size: 0.7em;\r font-weight: 600;\r letter-spacing: 0.14em;\r text-transform: uppercase;\r line-height: 1;\r \r /* color system: only Obsidian variables */\r background-color: var(--color-accent);\r background-image: linear-gradient(\r 135deg,\r var(--color-accent),\r var(--interactive-accent-hover)\r );\r color: var(--text-on-accent, var(--background-primary));\r border: 1px solid var(--background-modifier-border);\r \r /* subtle separation & depth, theme-aware */\r box-shadow:\r 0 0 0 1px var(--background-primary),\r 0 1px 3px rgba(0, 0, 0, 0.35);\r transform: translateY(-0.03em);\r }\r \r /* 3) Interactive refinement: follow Obsidian's accent hover behavior */\r :is(\r .pro-setting .setting-item-name\r ):hover::after {\r background-color: var(--interactive-accent-hover);\r filter: brightness(1.05);\r }\r \r .smart-plugin-settings-header .actions-container {\r display: flex;\r flex-wrap: wrap;\r gap: var(--pill-padding-y);\r }\r \r .setting-component:has(.dropdown-no-options) {\r display: none;\r }\r \r /* wrap Obsidian native styles within smart plugin settings main class */\r .smart-plugin-settings-main, .smart-plugin-settings-env {\r .setting-group {\r margin-top: var(--size-4-6);\r margin-bottom: var(--size-4-6);\r }\r /* polyfill */\r .setting-group .setting-items {\r background-color: var(--setting-items-background, var(--background-primary-alt));\r padding: var(--setting-items-padding, var(--size-4-5));\r border-radius: var(--setting-items-radius, var(--radius-l));\r border: var(--setting-items-border-width, 0) solid var(--setting-items-border-color, var(--background-modifier-border));\r }\r }\r \r /* show icon for all smart-* plugins except the main smart environment settings tab */\r .vertical-tab-header-group-items[data-section="community-plugins"] [data-setting-id^="smart-"]:not([data-setting-id^="smart-environment"]) .vertical-tab-nav-item-icon {\r display: flex;\r }`; // node_modules/obsidian-smart-env/src/views/smart_env_settings_tab.js var SmartEnvSettingTab = class extends import_obsidian50.PluginSettingTab { constructor(app2, plugin, icon = "smart-connections") { super(app2, plugin, icon); this.plugin = plugin; this.header_container = null; this.plugin_container = null; this.global_settings_container = null; this.plugin?.env?.create_env_getter?.(this); this.plugin = plugin; this.name = " Smart Environment"; this.id = "smart-environment"; if (!this.icon && icon) this.icon = icon; this.smart_view.apply_style_sheet(settings_default); } get smart_view() { return this.env?.smart_view; } display() { this.render(); } async render_component(name, scope, params = {}) { return await this.env.smart_components.render_component(name, scope, params); } async render() { this.containerEl.empty(); if (!this.env || ["init", "loading"].includes(this.env.state)) { render_pre_env_load(this); await this.env.constructor.wait_for({ loaded: true }); } this.containerEl.empty(); if (Object.values(this.env.plugin_states).some((state) => state === "deferred")) { this.containerEl.createDiv({ text: "Smart Plugins are waiting to be loaded. Restart Obsidian to finish loading the Smart Environment." }); const button = this.containerEl.createEl("button", { text: "Restart Obsidian" }); button.addEventListener("click", () => { window.location.reload(); }); } this.header_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-header" }); this.plugin_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-main" }); this.pro_plugins_container = this.containerEl.createDiv({ cls: "smart-plugin-settings-pro-plugins" }); this.header_container.createEl("p", { text: "Manage all global Smart Environment settings from one tab. These settings apply to all Smart Plugins." }); const settings_smart_env = await this.render_component("settings_smart_env", this.env); if (settings_smart_env) this.plugin_container.appendChild(settings_smart_env); render_plugin_store_setting(this, this.pro_plugins_container); } }; // node_modules/obsidian-smart-env/package.json var package_default2 = { name: "obsidian-smart-env", author: "Brian Joseph Petro (\u{1F334} Brian)", license: "SEE LICENSE IN LICENSE", version: "2.4.4", 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-chat-model": "file:../jsbrains/smart-chat-model", "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-http-request": "file:../jsbrains/smart-http-request", "smart-model": "file:../jsbrains/smart-model", "smart-models": "file:../jsbrains/smart-models", "smart-notices": "file:../jsbrains/smart-notices", "smart-settings": "file:../jsbrains/smart-settings", "smart-sources": "file:../jsbrains/smart-sources", "smart-types": "file:../jsbrains/smart-types", "smart-utils": "file:../jsbrains/smart-utils", "smart-view": "file:../jsbrains/smart-view" }, devDependencies: { "@xenova/transformers": "latest", archiver: "^7.0.1", ava: "^6.3.0", axios: "^1.13.2", dotenv: "^17.2.3", "js-tiktoken": "^1.0.19", readline: "^1.3.0" }, workspaces: [ "../jsbrains/*" ] }; // node_modules/obsidian-smart-env/smart_env.js var MIN_COMPATIBLE_SMART_ENV_VERSION = "2.4.0"; var SmartEnv2 = class extends SmartEnv { static version = package_default2.version; constructor(opts = {}) { super(opts); this.plugin_states = {}; } /** * Override to prevent loading outdated plugins * --- * This also stinks, replace in v3 (2026-03-31) */ get config() { const signature = this.compute_collections_version_signature(); if (this._config && signature === this._collections_version_signature) { return this._config; } this._collections_version_signature = signature; this._config = {}; const sorted_configs = Object.entries(this.smart_env_configs).sort(([a_key], [b_key]) => { if (!this.primary_main_key) return 0; if (a_key === this.primary_main_key) return -1; if (b_key === this.primary_main_key) return 1; return 0; }); for (const [key, rec] of sorted_configs) { if (!rec?.main) { console.warn(`SmartEnv: '${key}' unloaded, skipping`); delete this.smart_env_configs[key]; continue; } if (!rec?.opts) { console.warn(`SmartEnv: '${key}' opts missing, skipping`); continue; } if (!is_supported_smart_env_version(rec.opts.version)) { delete this.smart_env_configs[key]; continue; } merge_env_config( this._config, deep_clone_config(normalize_opts(rec.opts)) ); } return this._config; } /** * Creates and initializes a SmartEnv instance tailored for Obsidian. * @deprecated 2026-03-31 This code is so stinky I cringe. It will be replaced in next major version. Continue using but expect replacement. Do not depend on returned SmartEnv instance. * @param {Object} plugin - The Obsidian plugin instance. * @param {Object} [env_config] - Required environment configuration object. * @returns {Promise} The initialized SmartEnv instance. */ static create(plugin, env_config) { handle_outdated_plugins(); if (!plugin) throw new Error("SmartEnv.create: 'plugin' parameter is required."); if (!env_config) throw new Error("SmartEnv.create: 'env_config' parameter is required."); env_config.version = this.version; add_smart_chat_icon(); add_smart_connections_icon(); add_smart_lookup_icon(); add_smart_icons(); const opts = merge_env_config(env_config, default_config_default); opts.env_path = ""; const existing_env = this.global_env; if (existing_env && (existing_env.state === "loaded" || is_global_env_locked2(this.global_ref))) { handle_env_load_attempt_after_loaded(existing_env); this.create_env_getter(plugin); return existing_env; } if (existing_env && existing_env.state !== "init") { const is_newer_env = compare_versions(this.version, existing_env.constructor?.version) > 0; if (is_newer_env) { console.warn( "SmartEnv: Superseding environment before load lock because a newer SmartEnv version is available", `${this.version} > ${existing_env.constructor?.version}` ); } } return super.create(plugin, opts).then((env) => { clearTimeout(env.load_timeout); env.load_timeout = null; if (!env._startup_load_registration) { env._startup_load_registration = true; plugin.registerEvent(plugin.app.workspace.onLayoutReady(async () => { if (env.state !== "init") { console.warn(`Skipping SmartEnv (v${env.constructor.version}) load on layout ready; env.state: "${env.state}".`); return; } const continue_load = await env.before_load(); if (continue_load === false) { console.warn("SmartEnv before_load returned false, skipping load."); return; } await env.load(); })); } return env; }); } async before_load() { this.run_migrations(); this.register_notification_dispatchers(); this.register_env_item_views(); this.register_env_settings_tab(); if (typeof this._onboarding_events_teardown !== "function") { this._onboarding_events_teardown = register_first_of_event_notifications(this); } if (!this.plugin.app.workspace.protocolHandlers.has("smart-plugins/callback")) { this.plugin.registerObsidianProtocolHandler("smart-plugins/callback", async (params) => { await this.handle_smart_plugins_oauth_callback(params); }); } if (import_obsidian51.Platform.isDesktop) this.register_status_bar(); if (import_obsidian51.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_obsidian51.Notice(frag, 0); return false; } return true; } async after_load() { this.smart_sources?.register_source_watchers?.(this.smart_sources); this.register_workspace_source_events(); if (this._config.collections.smart_completions?.completion_adapters?.SmartCompletionVariableAdapter) { register_completion_variable_adapter_replacements(this._config.collections.smart_completions.completion_adapters.SmartCompletionVariableAdapter); } this.register_configured_modals(); this.refresh_status_bar(); if (!this._registered_browse_smart_plugins_command) { this._registered_browse_smart_plugins_command = this.plugin.manifest?.id + ":browse-smart-plugins"; this.plugin.addCommand({ id: "browse-smart-plugins", name: "Browse Smart Plugins", callback: () => { this.events.emit("smart_plugins:browse", { event_source: "command_palette" }); } }); } if (!this._registered_env_status_view_command) { this._registered_env_status_view_command = this.plugin.manifest?.id + ":env-status-view"; this.plugin.addCommand({ id: "env-status-view", name: "Open Environment Status View", callback: () => { this.open_env_status_view(); } }); } this.mains.forEach((main_key) => { const plugin_id = this.smart_env_configs[main_key]?.main?.manifest?.id || "unknown-plugin"; this.plugin_states[plugin_id] = "loaded"; }); if (!this._registered_ctx_menu_actions) { register_copy_menu_actions(this); register_context_menu_actions(this); this._registered_ctx_menu_actions = true; } } handle_env_load_attempt_after_loaded() { handle_env_load_attempt_after_loaded(this); } unload() { console.warn("Unloading SmartEnv"); if (typeof this._onboarding_events_teardown === "function") { this._onboarding_events_teardown(); this._onboarding_events_teardown = null; } this._workspace_source_events_registered = false; return super.unload?.(); } /** * Register event dispatchers used by native notice buttons and event-first flows. * * @returns {void} */ register_notification_dispatchers() { if (this._notification_dispatchers_registered) return; this._notification_dispatchers_registered = true; this.events.on("milestones_modal:open", () => { this.open_milestones_modal?.(); }); this.events.on("notifications_feed_modal:open", () => { this.open_notifications_feed_modal?.(); }); this.events.on("smart_plugins:browse", () => { this.browse_smart_plugins?.(); }); this.events.on("smart_env:load_mobile_requested", () => { this.start_mobile_env_load({ source: "native_notice_button" }); }); } /** * Register environment-owned item views once per plugin. * @returns {void} */ register_env_item_views() { const plugin = this.main; if (!plugin?.registerView) return; if (!(this._registered_env_item_views instanceof Set)) { this._registered_env_item_views = /* @__PURE__ */ new Set(); } const plugin_key = plugin.manifest?.id || "unknown-plugin"; const view_classes = [EnvStatusView]; view_classes.forEach((ViewClass) => { const registration_key = `${plugin_key}:${ViewClass.view_type}`; if (this._registered_env_item_views.has(registration_key)) return; try { ViewClass.register_item_view(plugin); this._registered_env_item_views.add(registration_key); } catch (error) { console.error(`Failed to register item view "${ViewClass.view_type}"`, error); } }); } /** * Open the mobile-friendly status view item view. * * @param {object} [params={}] * @returns {void} */ open_env_status_view(params = {}) { this.register_env_item_views(); EnvStatusView.open(this.obsidian_app.workspace, params); } get env_status_view_command_id() { const command_id = this._registered_env_item_views.find((id) => id.endsWith(EnvStatusView.view_type)); console.log({ command_id }); return command_id || this.plugin.manifest?.id + ":" + EnvStatusView.view_type; } /** * Centralized mobile load flow used by native notices, settings tabs, and views. * Opens the persistent progress surface first, then starts loading if needed. * * @param {object} [params={}] * @param {boolean} [params.open_progress_view=true] * @returns {Promise|SmartEnv} */ async start_mobile_env_load(params = {}) { const { open_progress_view = true } = params; if (open_progress_view) { this.open_env_status_view({ active: true }); } if (this.state === "loaded") { this.refresh_status_bar(); return this; } if (this.state === "loading" && this._load_promise) { return this._load_promise; } await this.load(); return this; } /** * Register every modal declared in config that exposes a static register_modal helper. * @returns {void} */ register_configured_modals() { const modal_entries = Object.entries(this._config?.modals || {}); if (!this._registered_modal_keys) { this._registered_modal_keys = /* @__PURE__ */ new Set(); } for (const [modal_key, modal_config] of modal_entries) { const ModalClass = modal_config?.class; if (typeof ModalClass?.register_modal !== "function") continue; if (this._registered_modal_keys.has(modal_key)) continue; try { ModalClass.register_modal(this.main); this._registered_modal_keys.add(modal_key); } catch (error) { console.error(`Failed to register modal "${modal_key}"`, error); } } } register_workspace_source_events() { if (this._workspace_source_events_registered) return; const plugin = this.main; if (!plugin?.registerEvent) return; this._workspace_source_events_registered = true; plugin.registerEvent( plugin.app.workspace.on("active-leaf-change", (leaf) => { this.smart_sources?.debounce_re_import_queue?.(); const current_path = leaf.view?.file?.path; this.emit_source_opened(current_path, "active-leaf-change"); }) ); plugin.registerEvent( plugin.app.workspace.on("file-open", (file) => { this.smart_sources?.debounce_re_import_queue?.(); const current_path = file?.path; this.emit_source_opened(current_path, "file-open"); }) ); } emit_source_opened(current_path, event_source = null) { if (this._current_opened_source === current_path) return; const current_source = this.smart_sources.get(current_path); if (current_source) { this._current_opened_source = current_path; current_source.emit_event("sources:opened", { event_source }); } } queue_source_re_import(source) { this.smart_sources?.queue_source_re_import?.(source); } debounce_re_import_queue() { this.smart_sources?.debounce_re_import_queue?.(); } async run_re_import() { await this.smart_sources?.run_re_import?.(); } register_status_bar() { const add_status_bar_item = this.main?.addStatusBarItem?.bind(this.main); if (typeof add_status_bar_item !== "function") return; const status_container = this.main?.app?.statusBar?.containerEl; const existing_status_item = status_container?.querySelector?.(".smart-env-status-container")?.closest?.(".status-bar-item"); if (existing_status_item && this.status_elm !== existing_status_item) { existing_status_item.remove?.(); } if (!this.status_elm || !this.status_elm.isConnected) { this.status_elm = add_status_bar_item(); } this.refresh_status_bar(); } refresh_status_bar() { if (!this.status_elm) return; render25.call(this.smart_view, this).then((container) => { this.status_elm.empty?.(); this.status_elm.appendChild(container); }).catch((error) => { console.error("Failed to render Smart Env status bar", error); }); } register_menu_action(menu_key, fn) { if (!this._registered_menu_actions) { this._registered_menu_actions = {}; } if (!this._registered_menu_actions[menu_key]) { this._registered_menu_actions[menu_key] = /* @__PURE__ */ new Set(); } if (this._registered_menu_actions[menu_key].has(fn)) return; this._registered_menu_actions[menu_key].add(fn); } build_menu(menu_key, menu, scope) { const registered_actions = this._registered_menu_actions?.[menu_key] ?? /* @__PURE__ */ new Set(); registered_actions.forEach((menu_action) => { menu_action(menu, scope); }); } /** * @deprecated 2026-03-17 remove by next major release (keeping for backward compatibility during migration period) */ get notices() { if (!this._notices) { this._notices = new SmartNotices(this, { adapter: import_obsidian51.Notice }); } return this._notices; } // Smart Plugins /** * Handles the OAuth callback from the Smart Plugins server. * @param {Object} params - The URL parameters from the OAuth callback. */ async handle_smart_plugins_oauth_callback(params) { const code = params.code; if (!code) { this.events.emit("smart_plugins_oauth_failed", { level: "error", message: "No OAuth code provided in URL. Login failed.", event_source: "handle_smart_plugins_oauth_callback" }); return; } try { await exchange_code_for_tokens(code, this.plugin); this.events.emit("smart_plugins_oauth_completed"); } catch (err) { console.error("OAuth callback error", err); this.events.emit("smart_plugins_oauth_failed", { level: "error", message: `OAuth callback error: ${err.message}`, details: err.stack || "", event_source: "handle_smart_plugins_oauth_callback" }); } } /** * Serializes the environment and, when in a browser, triggers a download. * @param {string} [filename='smart_env.json'] * @returns {string} stringified JSON */ export_json(filename = "smart_env.json") { const json = JSON.stringify(this.to_json(), null, 2); if (typeof document !== "undefined") { download_json(json, filename); } return json; } // WAIT FOR OBSIDIAN SYNC async ready_to_load_collections() { await new Promise((r) => setTimeout(r, 3e3)); await this.wait_for_obsidian_sync(); } async wait_for_obsidian_sync() { while (this.obsidian_is_syncing) { console.log("Smart Connections: Waiting for Obsidian Sync to finish"); await new Promise((r) => setTimeout(r, 1e3)); if (!this.plugin) throw new Error("Plugin disabled while waiting for obsidian sync, reload required."); } } get obsidian_is_syncing() { const obsidian_sync_instance = this.plugin?.app?.internalPlugins?.plugins?.sync?.instance; if (!obsidian_sync_instance) return false; if (obsidian_sync_instance?.syncStatus.startsWith("Uploading")) return false; if (obsidian_sync_instance?.syncStatus.startsWith("Fully synced")) return false; return obsidian_sync_instance?.syncing; } get obsidian_app() { return this.plugin?.app ?? window.app; } open_notifications_feed_modal(params = {}) { const NotificationsModalClass = this.config.modals.notifications_feed_modal.class; const modal = new NotificationsModalClass(this.obsidian_app, this, params); modal.open(); } open_milestones_modal() { const MilestonesModalClass = this.config.modals.milestones_modal.class; const modal = new MilestonesModalClass(this.obsidian_app, this); modal.open(); } browse_smart_plugins() { const BrowseSmartPluginsClass = this.config?.modals?.browse_plugins_modal?.class; if (typeof BrowseSmartPluginsClass !== "function") return; const modal = new BrowseSmartPluginsClass(this.obsidian_app, this); modal.open(); } run_migrations() { remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-plugins"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-editor"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-sources"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-claude"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-gemini"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-deepseek"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-perplexity"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-grok"] }); remove_smart_plugins_plugin({ app: this.plugin.app, plugin_ids: ["smart-aistudio"] }); } // Detect Existing Smart Environment Settings Tab get env_settings_tab() { const app2 = this.plugin.app || window.app; return app2.setting.pluginTabs.find((t) => t.id === "smart-environment"); } register_env_settings_tab() { if (this.env_settings_tab) return; this.plugin.addSettingTab(new SmartEnvSettingTab(this.plugin.app, this.plugin)); } }; function download_json(json, filename) { const blob = new Blob([json], { type: "application/json" }); const url = URL.createObjectURL(blob); const anchor = document.createElement("a"); anchor.href = url; anchor.download = filename; document.body.appendChild(anchor); anchor.click(); document.body.removeChild(anchor); URL.revokeObjectURL(url); } function handle_env_load_attempt_after_loaded(env) { console.warn("Received attempt to load another SmartEnv after one has already loaded"); const deferred_smart_plugins = Object.keys(env.plugin.app.plugins.plugins || {}).reduce((acc, plugin_id) => { if (plugin_id.startsWith("smart-") && env.plugin_states?.[plugin_id] !== "loaded" && env.main?.manifest?.id !== plugin_id) { acc[plugin_id] = "deferred"; } return acc; }, {}); env.plugin_states = { ...env.plugin_states, ...deferred_smart_plugins }; const notice_message = `Smart Plugins waiting to load. Restart Obsidian to load ${Object.keys(deferred_smart_plugins).join(", ")} into the Smart Environment.`; if (is_supported_smart_env_version(env.constructor.version)) { env.events.emit("smart_env:restart_required", { level: "attention", message: notice_message, event_source: "SmartEnv.create", btn_text: "Restart Obsidian", btn_callback: "app:reload", timeout: 1e4 // 10 seconds }); } else { const notice_frag = document.createDocumentFragment(); notice_frag.createEl("p", { text: notice_message }); notice_frag.createEl("button", { text: "Restart Obsidian" }).addEventListener("click", (e) => { app.commands.executeCommandById("app:reload"); e.target.textContent = "Reloading..."; }); new import_obsidian51.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_obsidian51.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_VERSION) >= 0; } // node_modules/obsidian-smart-env/src/views/smart_plugin_settings_tab.js var import_obsidian52 = require("obsidian"); var SmartPluginSettingsTab = class extends import_obsidian52.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_obsidian53 = require("obsidian"); var SmartPlugin = class extends import_obsidian53.Plugin { SmartEnv = SmartEnv2; /** * override in subclass to provide commands. * use property key to override commands in further subclasses. */ get commands() { return {}; } register_commands() { Object.values(this.commands).forEach((cmd) => { this.addCommand(cmd); }); } /** * override in subclass to provide ribbon icons. * use property key to override ribbon icons in further subclasses. */ get ribbon_icons() { return {}; } register_ribbon_icons() { const icons = Object.values(this.ribbon_icons); for (let i = 0; i < icons.length; i++) { const ri = icons[i]; this.addRibbonIcon(ri.icon_name, ri.description, ri.callback); } } get item_views() { return {}; } register_item_views() { const views = Object.values(this.item_views); for (let i = 0; i < views.length; i++) { const ViewClass = views[i]; if (typeof ViewClass.register_item_view === "function") { ViewClass.register_item_view(this); } } } /** * user version and first seen handling */ async is_new_user() { const data = await this.loadData() || {}; if (!data.installed_at) { data.installed_at = Date.now(); await this.saveData(data); return true; } return false; } /** * Returns the last saved plugin version or an empty string. * @returns {Promise} */ async get_last_known_version() { const data = await this.loadData() || {}; return data.last_version || ""; } /** * Persists the provided plugin version as last shown. * @param {string} version * @returns {Promise} */ async set_last_known_version(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 */ get notices() { if (this.env?.notices) return this.env.notices; if (!this._notices) this._notices = new SmartNotices(this.env, import_obsidian53.Notice); return this._notices; } }; // node_modules/smart-collections/utils/collection_instance_name_from.js function collection_instance_name_from2(class_name) { if (class_name.endsWith("Item")) { return class_name.replace(/Item$/, "").replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase(); } return class_name.replace(/([a-z])([A-Z])/g, "$1_$2").toLowerCase().replace(/y$/, "ie") + "s"; } // node_modules/smart-utils/deep_merge.js function deep_merge2(target = {}, source = {}) { for (const key in source) { if (!Object.prototype.hasOwnProperty.call(source, key)) continue; if (is_plain_object5(source[key]) && is_plain_object5(target[key])) { deep_merge2(target[key], source[key]); } else { target[key] = source[key]; } } return target; } function is_plain_object5(o) { return o && typeof o === "object" && !Array.isArray(o); } // node_modules/smart-collections/utils/helpers.js function create_uid2(data) { const str = JSON.stringify(data); let hash = 0; if (str.length === 0) return hash; for (let i = 0; i < str.length; i++) { const char = str.charCodeAt(i); hash = (hash << 5) - hash + char; hash = hash & hash; if (hash < 0) hash = hash * -1; } return hash.toString() + str.length; } // node_modules/smart-utils/camel_case_to_snake_case.js function camel_case_to_snake_case2(str = "") { return str.replace(/([A-Z])/g, (m) => `_${m.toLowerCase()}`).replace(/^_/, "").replace(/2$/, ""); } // node_modules/smart-collections/utils/deep_equal.js function deep_equal2(obj1, obj2, visited = /* @__PURE__ */ new WeakMap()) { if (obj1 === obj2) return true; if (obj1 === null || obj2 === null || obj1 === void 0 || obj2 === void 0) return false; if (typeof obj1 !== typeof obj2 || Array.isArray(obj1) !== Array.isArray(obj2)) return false; if (Array.isArray(obj1)) { if (obj1.length !== obj2.length) return false; return obj1.every((item, index) => deep_equal2(item, obj2[index], visited)); } if (typeof obj1 === "object") { if (visited.has(obj1)) return visited.get(obj1) === obj2; visited.set(obj1, obj2); const keys1 = Object.keys(obj1); const keys2 = Object.keys(obj2); if (keys1.length !== keys2.length) return false; return keys1.every((key) => deep_equal2(obj1[key], obj2[key], visited)); } return obj1 === obj2; } // node_modules/smart-collections/utils/get_item_display_name.js function get_item_display_name2(key, show_full_path) { if (show_full_path) { return key.split("/").join(" > ").replace(".md", ""); } return key.split("/").pop().replace(".md", ""); } // node_modules/smart-collections/utils/create_actions_proxy.js function create_actions_proxy2(ctx, actions_source) { const input = actions_source || {}; const is_plain_object7 = (val) => typeof val === "object" && val !== null && !Array.isArray(val); const is_function = (val) => typeof val === "function"; const is_class_export = (val) => is_function(val) && /^class\s/.test(Function.prototype.toString.call(val)); const is_action_object = (val) => is_plain_object7(val) && is_function(val.action); const is_action_candidate = (val) => is_function(val) || is_action_object(val) || is_class_export(val); const ignored_meta_keys = /* @__PURE__ */ new Set(["length", "name", "prototype"]); const clone_with_descriptors = (obj) => { if (!is_plain_object7(obj)) return obj; const out = Object.create(Object.getPrototypeOf(obj) || null); for (const key of Reflect.ownKeys(obj)) { const descriptor = Object.getOwnPropertyDescriptor(obj, key); if (!descriptor) continue; const next = { ...descriptor }; if ("value" in next && is_plain_object7(next.value)) { next.value = clone_with_descriptors(next.value); } try { Object.defineProperty(out, key, next); } catch { out[key] = next.value; } } return out; }; const should_bucket_actions = (val) => { if (!is_plain_object7(val)) return false; if (is_action_object(val)) return false; const keys = Reflect.ownKeys(val); if (keys.length === 0) return false; let found_candidate = false; for (const key of keys) { const descriptor = Object.getOwnPropertyDescriptor(val, key); if (!descriptor) continue; if ("value" in descriptor) { const entry = descriptor.value; if (is_action_candidate(entry)) { found_candidate = true; continue; } if (is_plain_object7(entry)) { if (should_bucket_actions(entry)) { found_candidate = true; continue; } return false; } if (typeof entry === "undefined") continue; return false; } return false; } return found_candidate; }; const clone_descriptor = (descriptor) => { if (!descriptor) return descriptor; if (!("value" in descriptor)) return { ...descriptor }; const cloned = is_plain_object7(descriptor.value) ? clone_with_descriptors(descriptor.value) : descriptor.value; return { ...descriptor, value: cloned }; }; const build_sources = (src) => { const global_source2 = /* @__PURE__ */ Object.create(null); const scoped_sources2 = /* @__PURE__ */ new Map(); for (const key of Reflect.ownKeys(src)) { const descriptor = Object.getOwnPropertyDescriptor(src, key); if (!descriptor) continue; if ("value" in descriptor && should_bucket_actions(descriptor.value)) { scoped_sources2.set(key, clone_with_descriptors(descriptor.value)); continue; } try { Object.defineProperty(global_source2, key, clone_descriptor(descriptor)); } catch { global_source2[key] = descriptor.value; } } return { global_source: global_source2, scoped_sources: scoped_sources2 }; }; const { global_source, scoped_sources } = build_sources(input); const has_own = (obj, prop) => Object.prototype.hasOwnProperty.call(obj, prop); const cache = /* @__PURE__ */ Object.create(null); const copy_metadata = (source, target, omit = []) => { if (!source || !target) return; const skips = /* @__PURE__ */ new Set([...ignored_meta_keys, ...omit]); for (const key of Reflect.ownKeys(source)) { if (skips.has(key)) continue; const descriptor = Object.getOwnPropertyDescriptor(source, key); if (!descriptor) continue; try { Object.defineProperty(target, key, descriptor); } catch { target[key] = descriptor.value; } } }; const instantiate_class = (Ctor) => { const instance = new Ctor(ctx); const candidate = instance.action || instance.run || instance.execute || instance.call; if (is_function(candidate)) { const bound = candidate.bind(instance); copy_metadata(Ctor, bound); copy_metadata(instance, bound); bound.instance = instance; return bound; } copy_metadata(Ctor, instance); return instance; }; const bind_or_clone = (val) => { if (is_class_export(val)) { return instantiate_class(val); } if (is_action_object(val)) { const bound = val.action.bind(ctx); copy_metadata(val, bound, ["action"]); return bound; } if (is_function(val)) { const bound = val.bind(ctx); copy_metadata(val, bound); return bound; } if (is_plain_object7(val)) { return clone_with_descriptors(val); } return val; }; const scope_actions_for = () => { const scope_key = ctx?.constructor?.key; if (typeof scope_key === "undefined" || scope_key === null) return null; const bucket = scoped_sources.get(scope_key); return bucket && is_plain_object7(bucket) ? bucket : null; }; const cache_result = (target, prop, value) => { target[prop] = value; return value; }; const compute_and_cache = (target, prop) => { const scoped = scope_actions_for(); if (scoped && has_own(scoped, prop)) { return cache_result(target, prop, bind_or_clone(scoped[prop])); } if (has_own(global_source, prop)) { return cache_result(target, prop, bind_or_clone(global_source[prop])); } return cache_result(target, prop, void 0); }; const union_keys = () => { const scoped = scope_actions_for(); const keys = new Set(Reflect.ownKeys(cache)); for (const key of Reflect.ownKeys(global_source)) { keys.add(key); } if (scoped) { for (const key of Reflect.ownKeys(scoped)) { keys.add(key); } } return Array.from(keys); }; const descriptor_for = (target, prop) => ({ configurable: true, enumerable: true, value: target[prop] }); return new Proxy(cache, { get: (target, prop) => { if (prop === Symbol.toStringTag) return "ActionsProxy"; if (prop in target) return target[prop]; return compute_and_cache(target, prop); }, has: (target, prop) => { if (prop in target) return true; const scoped = scope_actions_for(); if (scoped && has_own(scoped, prop)) return true; return has_own(global_source, prop); }, ownKeys: () => union_keys(), getOwnPropertyDescriptor: (target, prop) => { if (has_own(target, prop)) { return descriptor_for(target, prop); } const scoped = scope_actions_for(); if (scoped && has_own(scoped, prop)) { if (!has_own(target, prop)) { compute_and_cache(target, prop); } return descriptor_for(target, prop); } if (has_own(global_source, prop)) { if (!has_own(target, prop)) { compute_and_cache(target, prop); } return descriptor_for(target, prop); } return void 0; }, defineProperty: (target, prop, descriptor) => { if ("value" in descriptor) { target[prop] = descriptor.value; return true; } return false; }, set: (target, prop, value) => { target[prop] = value; return true; }, deleteProperty: (target, prop) => { if (has_own(target, prop)) { delete target[prop]; } return true; } }); } // node_modules/smart-collections/item.js var CollectionItem2 = class _CollectionItem { static version = 2e-3; /** * Default properties for an instance of CollectionItem. * Override in subclasses to define different defaults. * @returns {Object} */ static get defaults() { return { data: {} }; } /** * @param {Object} env - The environment/context. * @param {Object|null} [data=null] - Initial data for the item. */ constructor(env, data = null) { env.create_env_getter(this); this.config = this.env?.config; this.merge_defaults(); if (data) deep_merge2(this.data, data); if (!this.data.class_name) this.data.class_name = this.collection.item_class_name; } /** * Loads an item from data and initializes it. * @param {Object} env * @param {Object} 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 */ 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) * @returns {string} The unique key */ get_key() { return create_uid2(this.data); } /** * Updates the item data and returns true if changed. * @param {Object} 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. * @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 {Object} [input_data] - Additional data that might be provided on creation. */ init(input_data) { } /** * Queues this item for saving. */ queue_save() { this._queue_save = true; } /** * Saves this item using its data adapter. * @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. */ queue_load() { this._queue_load = true; } /** * Loads this item using its data adapter. * @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. * @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?) * @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. */ 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 * @param {Object} filter_opts - Filtering options. * @param {string} [filter_opts.exclude_key] - A single key to exclude. * @param {string[]} [filter_opts.exclude_keys] - An array of keys to exclude. If exclude_key is provided, it's added to this array. * @param {string} [filter_opts.exclude_key_starts_with] - Exclude keys starting with this string. * @param {string[]} [filter_opts.exclude_key_starts_with_any] - Exclude keys starting with any of these strings. * @param {string} [filter_opts.exclude_key_includes] - Exclude keys that include this string. * @param {string[]} [filter_opts.exclude_key_includes_any] - Exclude keys that include any of these strings. * @param {string} [filter_opts.exclude_key_ends_with] - Exclude keys ending with this string. * @param {string[]} [filter_opts.exclude_key_ends_with_any] - Exclude keys ending with any of these strings. * @param {string} [filter_opts.key_ends_with] - Include only keys ending with this string. * @param {string} [filter_opts.key_starts_with] - Include only keys starting with this string. * @param {string[]} [filter_opts.key_starts_with_any] - Include only keys starting with any of these strings. * @param {string} [filter_opts.key_includes] - Include only keys that include this string. * @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; } filter_and_score(params = {}) { if (this.filter(params.filter) === false) return null; return this.score(params); } 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 }; } 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. * @returns {Collection} */ get collection() { return this.env[this.collection_key]; } /** * @returns {string} The item's key. */ get key() { return this.data?.key || this.get_key(); } 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. * * @param {string} event_key * @param {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 }); } emit_info_event(event_key, payload = {}) { this.emit_event(event_key, { level: "info", ...payload }); } emit_error_event(event_key, payload = {}) { this.emit_event(event_key, { level: "error", ...payload }); } 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); }); } 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); }); } /** * @returns {Object} The data adapter for this item's collection. */ get data_adapter() { return this.collection.data_adapter; } /** * @returns {Object} The filesystem adapter. */ get data_fs() { return this.collection.data_fs; } /** * Access to collection-level settings. * @returns {Object} */ get settings() { if (!this.env.settings[this.collection_key]) this.env.settings[this.collection_key] = {}; return this.env.settings[this.collection_key]; } 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 * @returns {{collection_key: string, key: string}} */ get ref() { return { collection_key: this.collection_key, key: this.key }; } /** * @deprecated use env.smart_components~~env.smart_view~~ instead */ 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). * @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 Collection2 = class { static version = 1e-3; /** * Constructs a new Collection instance. * * @param {Object} env - The environment context containing configurations and adapters. * @param {Object} [opts={}] - Optional configuration. * @param {string} [opts.collection_key] - Custom key to override default collection name. * @param {string} [opts.data_dir] - Custom data directory path. */ 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 {Object} env * @param {Object} [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 * * @param {Object} [data={}] - Data for creating/updating an item. * @returns {Promise|Item} 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. * * @param {Object} data - Data to match against. * @returns {Item|null} */ 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. * * @param {Object|Function} [filter_opts={}] - Filter options or a predicate function. * @returns {Item[]} 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()` * @param {Object|Function} filter_opts * @returns {Item[]} */ list(filter_opts) { return this.filter(filter_opts); } /** * Retrieves an item by key. * @param {string} key * @returns {Item|undefined} */ get(key) { return this.items[key]; } /** * Retrieves multiple items by an array of keys. * @param {string[]} keys * @returns {Item[]} */ 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. * @param {Object} [opts] * @returns {Item|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. * @param {Item} 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. * @param {string[]} keys * @param {Object} data */ update_many(keys = [], data = {}) { this.get_many(keys).forEach((item) => item.update_data(data)); } /** * Clears all items from the collection. */ clear() { this.items = {}; } /** * @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. * @returns {Object} 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 * @param {string} type * @returns {Function} */ 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) * @returns {string} */ get data_dir() { return this.collection_key; } /** * File system adapter from the environment. * @returns {Object} */ 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. * @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) * @returns {Function} 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 * @returns {Function} */ 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. * @returns {string[]} */ get keys() { return Object.keys(this.items); } /** * @deprecated use data_adapter instead (2024-09-14) */ 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). */ 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 * @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. */ async process_load_queue() { await this.data_adapter.process_load_queue(); } /** * Retrieves processed settings configuration. * @returns {Object} */ 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 * @param {Object} _settings_config * @param {string} [prefix=''] * @returns {Object} */ 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. * @returns {Object} */ get settings() { if (!this.env.settings[this.collection_key]) { this.env.settings[this.collection_key] = this.default_settings; } return this.env.settings[this.collection_key]; } /** * Unloads collection data from memory. */ unload() { this.clear(); this.unloaded = true; this.env.collections[this.collection_key] = null; } /** * Emits an event with collection metadata. * * @param {string} event_key * @param {Object} [payload={}] * @returns {void} */ emit_event(event_key, payload = {}) { this.env.events?.emit(event_key, { collection_key: this.collection_key, ...payload }); } emit_info_event(event_key, payload = {}) { this.emit_event(event_key, { level: "info", ...payload }); } emit_warning_event(event_key, payload = {}) { this.emit_event(event_key, { level: "warning", ...payload }); } emit_error_event(event_key, payload = {}) { this.emit_event(event_key, { level: "error", ...payload }); } 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. * * @returns {Object} Bound action functions keyed by name. */ get actions() { if (!this.constructor.key) this.constructor.key = this.collection_key; if (!this._actions) { const actions_modules = { ...this.env?.config?.actions || {}, ...this.env?.config?.collections?.[this.collection_key]?.actions || {}, ...this.env?.opts?.collections?.[this.collection_key]?.actions || {}, ...this.opts?.actions || {} }; this._actions = create_actions_proxy2(this, actions_modules); } return this._actions; } /** * Clears cached actions proxy and rebuilds on next access. * @returns {Object} Rebuilt proxy with latest source snapshot. */ refresh_actions() { this._actions = null; return this.actions; } // debounce running process save queue queue_save() { if (this._debounce_queue_save) clearTimeout(this._debounce_queue_save); this._debounce_queue_save = setTimeout(() => { this.process_save_queue(); }, 750); } // BEGIN DEPRECATED /** * @deprecated use env.smart_components~~env.smart_view~~ instead (2026-02-11) * @returns {Object} smart_view instance */ get smart_view() { if (!this._smart_view) this._smart_view = this.env.init_module("smart_view"); return this._smart_view; } }; // node_modules/smart-entities/utils/frontmatter_filter.js var to_string2 = (value) => `${value ?? ""}`.trim(); var to_lower2 = (value) => to_string2(value).toLowerCase(); var to_lines = (value = "") => { if (Array.isArray(value)) return value; return to_string2(value).split("\n"); }; function parse_frontmatter_filter_lines(value = "") { return to_lines(value).map((line) => to_string2(line)).filter((line) => line.length).map((line) => { const separator_index = line.indexOf(":"); if (separator_index === -1) { return { key: to_lower2(line), value: null }; } const key = to_lower2(line.slice(0, separator_index)); const entry_value = to_string2(line.slice(separator_index + 1)); return { key, value: entry_value.length ? to_lower2(entry_value) : null }; }).filter((entry) => entry.key.length); } // node_modules/smart-utils/results_acc.js function results_acc2(_acc, result, ct = 10) { if (_acc.results.size < ct) { _acc.results.add(result); if (_acc.results.size === ct && _acc.min === Number.POSITIVE_INFINITY) { let { minScore, minObj } = find_min2(_acc.results); _acc.min = minScore; _acc.minResult = minObj; } } else if (result.score > _acc.min) { _acc.results.add(result); _acc.results.delete(_acc.minResult); let { minScore, minObj } = find_min2(_acc.results); _acc.min = minScore; _acc.minResult = minObj; } } function find_min2(results) { let minScore = Number.POSITIVE_INFINITY; let minObj = null; for (const obj of results) { if (obj.score < minScore) { minScore = obj.score; minObj = obj; } } return { minScore, minObj }; } // node_modules/smart-utils/sort_by_score.js function sort_by_score2(a, b) { const epsilon = 1e-9; const score_diff = a.score - b.score; if (Math.abs(score_diff) < epsilon) return 0; return score_diff > 0 ? -1 : 1; } function sort_by_score_descending2(a, b) { return sort_by_score2(a, b); } // src/utils/merge_pinned_results.js function merge_pinned_results(base_results, params) { if (!params.pinned?.length) return base_results; const pinned_keys = new Set(params.pinned_keys || params.pinned.map((item) => item.key)); const pinned_results = params.pinned.map((item) => ({ item, ...item.score?.(params) || {} })); const filtered_results = base_results.filter((result) => { const key = result?.item?.key; return key ? !pinned_keys.has(key) : true; }); return [...pinned_results, ...filtered_results]; } // migrations/migrate_hidden_connections.js var BLOCK_KEY_MARKER = "#"; var COLLECTION_SEPARATOR = ":"; var SOURCE_COLLECTION = "smart_sources"; var BLOCK_COLLECTION = "smart_blocks"; function is_plain_object6(value) { return Boolean(value) && typeof value === "object" && !Array.isArray(value); } function select_hidden_entries(hidden_connections) { if (!is_plain_object6(hidden_connections)) return []; return Object.entries(hidden_connections).filter(([, timestamp]) => timestamp !== void 0 && timestamp !== null); } function get_collection_prefix(key) { return key.includes(BLOCK_KEY_MARKER) ? BLOCK_COLLECTION : SOURCE_COLLECTION; } function ensure_prefixed_key(key) { if (typeof key !== "string") return key; if (key.includes(COLLECTION_SEPARATOR)) return key; return `${get_collection_prefix(key)}${COLLECTION_SEPARATOR}${key}`; } function migrate_hidden_connections(source) { const hidden_entries = select_hidden_entries(source?.data?.hidden_connections); if (!hidden_entries.length) return false; if (!is_plain_object6(source.data.connections)) { source.data.connections = {}; } let migrated = false; hidden_entries.forEach(([key, timestamp]) => { const prefixed_key = ensure_prefixed_key(key); if (typeof prefixed_key !== "string") return; const connection_state = source.data.connections[prefixed_key] || {}; if (connection_state.hidden === void 0 || connection_state.hidden === null) { connection_state.hidden = timestamp; migrated = true; } source.data.connections[prefixed_key] = connection_state; }); if (migrated) { delete source.data.hidden_connections; } return migrated || hidden_entries.length > 0; } // src/items/connections_list.js var ConnectionsList = class extends CollectionItem2 { static key = "connections_list"; static get defaults() { return { data: {} }; } get_key() { return `${this.data.collection_key}:${this.data.item_key}`; } async pre_process(params) { migrate_hidden_connections(this.item); if (typeof this.actions.connections_list_pre_process === "function") { await this.actions.connections_list_pre_process(params); } if (typeof this.env.config?.actions?.[params.score_algo_key]?.pre_process === "function") { await this.env.config.actions[params.score_algo_key].pre_process.call(this.item, params); } } /** * Produce ranked connections for the current source item. * @param {object} params * @note cannot call with different params until promise resolves * @returns {Promise} */ async get_results(params = {}) { if (this._results_promise) return this._results_promise; const p = this._get_results(params); this._results_promise = p; this._results_promise.finally(() => { if (this._results_promise === p) { this._results_promise = null; } }); return this._results_promise; } async _get_results(params = {}) { await this.pre_process(params); if (this.env.log_perf) this.start_ms = Date.now(); let results = this.filter_and_score(params); if (this.env.log_perf) { this.end_ms = Date.now(); console.log(`filter_and_score(${params.score_algo_key}) took ${this.end_ms - this.start_ms} ms (Date.now)`); } results = await this.post_process(results, params); results = merge_pinned_results(results, params); results = results.map((r) => Object.assign(r, { connections_list: this })); this.results = results; return results; } filter_and_score(params = {}) { const collection = this.env[params.results_collection_key]; const score_errors = []; const { results: raw_results } = Object.values(collection.items).reduce((acc, target) => { const scored = target.filter_and_score(params); if (!scored?.score) { if (scored?.error) score_errors.push(scored.error); return acc; } results_acc2(acc, scored, params.limit); return acc; }, { min: 0, results: /* @__PURE__ */ new Set() }); const results = Array.from(raw_results).sort(sort_by_score_descending2); if (!results.length) return results; while (!results.some((r) => r.score > 0.5)) { results.forEach((r) => r.score *= 2); } return results; } async post_process(results, params = {}) { if (!results?.length) { console.warn("No results to post-process, received:", results); return []; } const action_key = this.settings.connections_post_process; const post_process_action = this.actions[action_key]; let processed_results = results; if (typeof post_process_action === "function") { const response = await post_process_action(results, params); if (Array.isArray(response)) { processed_results = response.filter(Boolean); if (!processed_results.length) processed_results = results; } else if (response !== void 0 && response !== null) { console.warn(`connections post_process '${action_key}' returned non-array`, response); } } else if (action_key && action_key !== "none") { console.warn(`Post-process action "${action_key}" not found, falling back to base results.`); } return processed_results; } get item() { return this.env[this.data.collection_key]?.items[this.data.item_key]; } get connections_list_component_key() { const stored_key = this.data.connections_list_component_key || this.settings?.connections_list_component_key; if (this.env.config.components[stored_key]) return stored_key; return "connections_list_v4"; } }; // migrations/migrate_connections_lists_settings.js var MIGRATED_SETTING_KEYS = [ "inline_connections", "inline_connections_score_threshold", "footer_connections" ]; function migrate_connections_lists_settings(env) { const settings = env?.settings; if (!settings) return false; const legacy = settings.connections_pro; if (!legacy) return false; const target = settings.connections_lists ||= {}; let migrated = false; for (const key of MIGRATED_SETTING_KEYS) { if (!(key in legacy)) continue; if (!(key in target)) { target[key] = legacy[key]; migrated = true; } delete legacy[key]; } if ("rank_model" in legacy) { if (!target.actions) target.actions = {}; if (!("rank_connections" in target.actions)) { target.actions.rank_connections = legacy.rank_model; migrated = true; } delete legacy.rank_model; } return migrated; } // src/utils/insert_settings_after.js function insert_settings_after(anchor_key, config, merge_object) { const config_entries = Object.entries({ ...config }); const anchor_i = config_entries.findIndex(([key]) => key === anchor_key); if (anchor_i !== -1) { if (merge_object && Object.keys(merge_object).length > 0) { const entries = Object.entries(merge_object); config_entries.splice(anchor_i + 1, 0, ...entries); } } return Object.fromEntries(config_entries); } // src/collections/connections_lists.js var ConnectionsLists = class extends Collection2 { static version = 1; process_load_queue() { } // no persisting data (for now) constructor(env, opts = {}) { migrate_connections_lists_settings(env); super(env, opts); } static get default_settings() { return { results_collection_key: "smart_sources", score_algo_key: "similarity", connections_post_process: "none", results_limit: 20, connections_view_location: "right", exclude_frontmatter_blocks: true, connections_list_component_key: "connections_list_v4", connections_list_item_component_key: "connections_list_item_v3", frontmatter_filter_include: "", frontmatter_filter_exclude: "", components: { connections_list_v4: { show_graph: true }, connections_list_item_v3: { render_markdown: true, show_full_path: false } } }; } get settings_config() { return settings_config10(this); } new_item(item) { const connections_list = new this.item_type(this.env, { collection_key: item.collection_key, item_key: item.key }); this.set(connections_list); Object.defineProperty(item, "connections", { get: () => connections_list, configurable: true }); return connections_list; } get_connections_list_item_options() { return Object.entries(this.env.config.components || {}).filter(([key, fn]) => key.startsWith("connections_list_item_")).map(([value, fn]) => ({ value, name: fn.display_name || value, description: fn.display_description })); } get score_algo_key() { const stored_key = this.settings?.score_algo_key; if (this.env.config?.actions?.[stored_key]) return stored_key; return "similarity"; } get results_collection_key() { const stored_key = this.settings?.results_collection_key; if (this.env[stored_key]) return stored_key; return "smart_sources"; } get frontmatter_inclusions() { return parse_frontmatter_filter_lines(this.settings.frontmatter_filter_include); } get frontmatter_exclusions() { return parse_frontmatter_filter_lines(this.settings.frontmatter_filter_exclude); } get connections_list_component_settings_config() { if (!this.settings?.connections_list_component_key || !this.env.is_pro && ["none", "connections_list_v4_2", "connections_list_v3"].includes(this.settings.connections_list_component_key)) { this.settings.connections_list_component_key = "connections_list_v4"; } if (!this.settings?.components?.connections_list_v4) { if (!this.settings.components) this.settings.components = {}; this.settings.components.connections_list_v4 = { ...this.constructor.default_settings.components.connections_list_v4 }; } const component_key = this.settings.connections_list_component_key; if (!component_key || component_key === "none") return null; const component_module = this.env.config.components?.[component_key]; const config = typeof component_module?.settings_config === "function" ? component_module.settings_config(this) : component_module?.settings_config; if (!config) return null; return Object.fromEntries( Object.entries(config).map(([k, v]) => { return [`components.${component_key}.${k}`, v]; }) ); } }; function settings_config10(scope) { let config = { "results_collection_key": { name: "Connection results type", type: "dropdown", description: "Choose whether results should be sources or blocks.", option_1: "smart_sources|Sources", // DEPRECATED option_2: "smart_blocks|Blocks", // DEPRECATED options_callback: () => { const options = [ { value: "smart_sources", name: "Sources" } ]; if (scope.env.smart_blocks) { options.push({ value: "smart_blocks", name: "Blocks" }); } return options; } }, "results_limit": { name: "Results limit", type: "number", description: "Adjust the number of connections displayed in the connections view (default 20)." }, "connections_view_location": { group: "Display", name: "Connections sidebar location", type: "dropdown", description: "Choose which sidebar opens when showing the Connections view.", option_1: "right|Right sidebar", // DEPRECATED option_2: "left|Left sidebar", // DEPRECATED options_callback: () => { return [ { value: "right", name: "Right sidebar" }, { value: "left", name: "Left sidebar" } ]; } }, "inline_connections": { group: "Inline connections", name: "Show inline connections", type: "toggle", scope_class: "pro-setting", description: "Shows connections for each block within the note. Hover connections icon to see list of connections." }, "footer_connections": { group: "Footer connections", name: "Show footer connections", type: "toggle", description: "Show connections at the bottom of each note." }, filters_helper: { group: "Connections filters", type: "html", value: [ '

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

    ', '

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

    ', '

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

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

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

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

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

    `); container.appendChild(no_results); return container; } const smart_components = connections_list.env.smart_components; const result_frags = await Promise.all(results.map((result) => { return smart_components.render_component("connections_list_item_v3", result, { ...opts }); })); result_frags.forEach((result_frag) => container.appendChild(result_frag)); return container; } var display_name5 = "List only"; // src/components/connections-list/v4.js async function build_html32(connections_list, opts = {}) { return `
    `; } async function render34(connections_list, opts = {}) { const html = await build_html32.call(this, connections_list, opts); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process30.call(this, connections_list, container, opts); return container; } async function post_process30(connections_list, container, opts = {}) { const env = connections_list.env; const graph_container = container.querySelector(".connections-graph-container"); const list_container = container.querySelector(".connections-list.sc-list"); container.dataset.key = connections_list.item.key; const results = await connections_list.get_results(opts); const connections_settings = opts.connections_settings ?? env.connections_lists.settings; const component_settings = connections_settings.components?.connections_list_v4 || {}; const show_graph = opts.show_graph ?? component_settings.show_graph; if (show_graph) { try { const graph = await env.smart_components.render_component("connections_graph_v1", connections_list, { ...opts, results }); this.empty(graph_container); graph_container.appendChild(graph); register_graph_events(graph, list_container); } catch (_err) { this.empty(graph_container); } } 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); const schedule = typeof window !== "undefined" ? window.setTimeout : setTimeout; schedule?.(() => target.classList.remove(GRAPH_FOCUS_CLASS), GRAPH_FOCUS_TIMEOUT_MS); } function find_result_element(list_container, detail = {}) { if (!list_container) return null; const { collection_key, item_key } = detail; if (!collection_key || !item_key) return null; return Array.from(list_container.querySelectorAll(".sc-result")).find((node) => { return node.dataset.collection === collection_key && node.dataset.key === item_key; }) || null; } var display_name6 = "Version 4.0 (Graph + List)"; var settings_config12 = { "show_graph": { name: "Show graph", type: "toggle", description: "Show a graph visualization of the connections above the list.", // default: true, group: "Connections lists" } }; // src/components/connections-settings/header.js var import_obsidian56 = require("obsidian"); async function build_html33(scope_plugin) { return `
    `; } async function render35(scope_plugin) { const html = await build_html33.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_obsidian56.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 Smart Environment Pro 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_obsidian57 = require("obsidian"); function build_html34(plugin, opts = {}) { return `
    `; } function render36(plugin, params = {}) { const html = build_html34.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_obsidian57.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 = "Smart 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 = "Smart Lookup is moving to a dedicated plugin. Please install Smart Lookup to continue using lookup features and access Lookup settings."; callout_btn.textContent = "Install Smart Lookup"; 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_obsidian57.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_obsidian57.requestUrl)({ url: "https://api.github.com/repos/brianpetro/smart-lookup-obsidian/releases/latest", method: "GET", headers: { "Content-Type": "application/json" }, contentType: "application/json" }); const assets = response.assets; const main_asset = assets.find((asset) => asset.name === "main.js"); const manifest_asset = assets.find((asset) => asset.name === "manifest.json"); const styles_asset = assets.find((asset) => asset.name === "styles.css"); if (!main_asset || !manifest_asset || !styles_asset) { env.events.emit("plugin:install_failed", { level: "error", message: "Failed to find necessary assets for Smart Lookup. Installation failed.", event_source: "install_smart_lookup" }); return; } const main_url = main_asset.browser_download_url; const manifest_url = manifest_asset.browser_download_url; const styles_url = styles_asset.browser_download_url; const plugin_folder = `${app2.vault.configDir}/plugins/smart-lookup`; if (!await adapter.exists(plugin_folder)) { await adapter.mkdir(plugin_folder); } const results = await Promise.all([ download_and_write(main_url, `${plugin_folder}/main.js`), download_and_write(manifest_url, `${plugin_folder}/manifest.json`), download_and_write(styles_url, `${plugin_folder}/styles.css`) ]); if (results.some((result) => result === false)) { return; } await app2.plugins.loadManifests(); if (!app2.plugins.enabledPlugins.has("smart-lookup")) { enable_plugin(app2, "smart-lookup"); } env.events.emit("plugin:install_completed", { level: "info", message: "Smart Lookup installed.", event_source: "install_smart_lookup" }); } // src/components/connections-view/v3.css var v3_default2 = ".connections-view-early {\n .connections-top-bar {\n display: flex;\n gap: 0.5rem 1rem;\n\n .connections-actions {\n display: flex;\n flex-direction: row;\n flex-wrap: wrap;\n flex: 0 0 auto;\n }\n\n .sc-context {\n display: flex;\n flex-direction: column;\n margin: 0;\n gap: 0.1rem;\n width: auto;\n flex: 1 1 auto;\n\n .sc-context-line {\n line-height: 1.1;\n }\n\n .sc-context-line--parent {\n color: var(--text-muted);\n font-size: 0.85em;\n }\n\n .sc-context-line--focus {\n color: var(--text-normal);\n font-size: 1.05em;\n font-weight: 600;\n }\n }\n }\n .connections-list {\n padding-top: 0.85rem;\n\n > .sc-collapsed ul {\n display: none;\n }\n > .sc-collapsed span svg {\n transform: rotate(-90deg);\n }\n > .sc-result-pinned {\n box-shadow: 0 0 0 1px var(--interactive-accent);\n border-radius: var(--radius-s);\n background-color: var(--background-modifier-hover);\n }\n }\n}"; // src/components/connections-view/v3.js var import_obsidian58 = require("obsidian"); // src/utils/context_lines.js var BLOCK_SEPARATOR = "#"; var DISPLAY_SEPARATOR2 = " \u203A "; var PATH_SEPARATOR = "/"; function get_context_lines(item) { const key = item.key; let top_line = ""; let bottom_line = ""; let parts = []; if (key.includes(BLOCK_SEPARATOR)) { parts = key.split(BLOCK_SEPARATOR); bottom_line = parts.pop().trim(); if (bottom_line[0] === "{") { const lines = item.lines.join("-"); bottom_line = parts.pop().trim() + ` Lines: ${lines}`; } top_line = parts.filter(Boolean).join(BLOCK_SEPARATOR); } else if (key.includes(PATH_SEPARATOR)) { parts = key.split(PATH_SEPARATOR); bottom_line = parts.pop().trim(); } top_line = parts.filter(Boolean).join(DISPLAY_SEPARATOR2); return [top_line, bottom_line]; } // src/utils/connections_view_refresh_handler.js async function connections_view_refresh_handler(event) { const view_container = event.target.closest(".connections-view"); const list_el = view_container?.querySelector(".connections-list"); const entity_key = list_el?.dataset?.key; console.log(`Refreshing smart connections view entity ${entity_key}`); const refresh_entity = this.env.smart_sources.get(entity_key); if (refresh_entity) { await refresh_entity.read(); refresh_entity.queue_import(); await refresh_entity.collection.process_source_import_queue?.(); this.render_view(refresh_entity); } else { console.warn("No entity found for refresh"); } } // src/components/connections-view/v3.js async function build_html35(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 render37(view, opts = {}) { const html = await build_html35.call(this, view, opts); const frag = this.create_doc_fragment(html); this.apply_style_sheet(v3_default2); const container = frag.querySelector(".sc-connections-view"); post_process33.call(this, view, container, opts); return frag; } async function post_process33(view, container, opts = {}) { const list_container = container.querySelector(".connections-list-container"); const sc_top_bar_context = container.querySelector(".sc-top-bar .sc-context"); const env = view.env; let connections_item = opts.connections_item; if (!connections_item) { list_container.textContent = "No source item detected for current active view."; return container; } let connections_list = connections_item.connections || env.connections_lists.new_item(connections_item); if (!container._has_listeners) { container._has_listeners = true; const pause_button = container.querySelector('[data-action="toggle-pause"]'); pause_button?.addEventListener("click", () => { view.toggle_connections_paused(); }); const open_help = () => { StoryModal.open(view.plugin, { title: "Getting Started With Smart Connections", url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=connections-view-help#page=understanding-connections-1" }); }; const menu_button = container.querySelector('[data-action="open-menu"]'); menu_button?.addEventListener("click", (event) => { const menu = new import_obsidian58.Menu(view.plugin.app); const raw_results = Array.isArray(connections_list?.results) ? connections_list.results : []; const connections_state = connections_list?.item?.data?.connections || {}; const visible_results = filter_hidden_results(raw_results, connections_state); menu.addItem((menu_item) => { menu_item.setTitle("Refresh connections").setIcon("refresh-cw").onClick(() => { connections_view_refresh_handler.call(view, { target: container }); }); }); menu.addItem((menu_item) => { const context_items = build_connections_context_items({ source_item: connections_item, results: visible_results }); menu_item.setTitle("Send results to Smart Context").setIcon("briefcase").setDisabled(!context_items.length).onClick(async () => { if (!context_items.length) { env.events.emit("connections:send_to_context_empty", { level: "warning", message: "No connection results to send to Smart Context.", event_source: "connections_view_menu" }); return; } const smart_context = env.smart_contexts.new_context(); smart_context.add_items(context_items); smart_context.emit_event("context_selector:open"); connections_list.emit_event("connections:sent_to_context"); }); }); menu.addItem((menu_item) => { const links_payload = format_connections_as_links(visible_results); menu_item.setTitle("Copy as list of links").setIcon("copy").setDisabled(!links_payload).onClick(async () => { if (!links_payload) { env.events.emit("connections:copy_list_empty", { level: "warning", message: "No connection results to copy.", event_source: "connections_view_menu" }); return; } await copy_to_clipboard(links_payload); connections_list.emit_event("connections:copied_list"); }); }); menu.addItem((menu_item) => { const connections_settings = opts.connections_settings ?? connections_list?.settings; const expanded = connections_settings?.expanded_view; const title = expanded ? "Collapse all results" : "Expand all results"; const icon_name = expanded ? "fold-vertical" : "unfold-vertical"; menu_item.setTitle(title).setIcon(icon_name).onClick(() => { const curr_settings = opts.connections_settings ?? connections_list?.settings; const curr_expanded = curr_settings?.expanded_view; if (curr_settings) curr_settings.expanded_view = !curr_expanded; container.querySelectorAll(".sc-result").forEach((element) => { curr_expanded ? element.classList.add("sc-collapsed") : element.classList.remove("sc-collapsed"); }); }); }); env.build_menu?.("connections_list", menu, connections_list); menu.addSeparator(); menu.addItem((menu_item) => { menu_item.setTitle("Connections settings").setIcon("settings").onClick(() => { view.open_settings(); }); }); menu.addItem((menu_item) => { menu_item.setTitle("Help & getting started").setIcon("help-circle").onClick(open_help); }); menu.showAtMouseEvent(event); }); } const connections_list_component_key = opts.connections_list_component_key || connections_list.connections_list_component_key || "connections_list_v4"; const list = await env.smart_components.render_component(connections_list_component_key, connections_list, opts); this.empty(list_container); list_container.appendChild(list); const entity = connections_list?.item || connections_item; const [top_line, bottom_line] = get_context_lines(entity); this.empty(sc_top_bar_context); const doc = sc_top_bar_context.ownerDocument; const context_spans = [ { text: top_line, class_name: "sc-context-line sc-context-line--parent" }, { text: bottom_line, class_name: "sc-context-line sc-context-line--focus" } ]; context_spans.forEach((line) => { const span = doc.createElement("span"); span.className = line.class_name; span.textContent = line.text || "\xA0"; sc_top_bar_context.appendChild(span); }); sc_top_bar_context.dataset.key = connections_item.key; const pause_btn = container.querySelector('[data-action="toggle-pause"]'); if (pause_btn) { const update_pause_button = (paused) => { const next_title = paused ? "Resume auto-refresh" : "Pause auto-refresh"; pause_btn.title = next_title; pause_btn.setAttribute("aria-label", `${next_title}`); pause_btn.setAttribute("aria-pressed", String(paused)); this.safe_inner_html( pause_btn, this.get_icon_html(paused ? "play-circle" : "pause-circle") ); }; update_pause_button(Boolean(view.paused)); view.register_pause_controls({ update: update_pause_button }); } return container; } // node_modules/smart-lookup-obsidian/src/components/lookup/item_view.css var item_view_default = ".lookup-item-view {\n display: flex;\n flex-direction: column;\n gap: 1rem;\n}\n\n.lookup-item-view .lookup-query-form {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n\n.lookup-item-view .lookup-query-label {\n font-size: 0.75rem;\n font-weight: 600;\n letter-spacing: 0.03em;\n text-transform: uppercase;\n color: var(--text-muted);\n}\n\n.lookup-item-view .lookup-query-input {\n resize: vertical;\n min-height: 5rem;\n padding: 0.75rem;\n border-radius: var(--radius-m);\n border: 1px solid var(--background-modifier-border);\n background-color: var(--background-secondary);\n color: var(--text-normal);\n font-size: 0.95rem;\n line-height: 1.5;\n}\n\n.lookup-item-view .lookup-query-input:focus {\n outline: none;\n border-color: var(--interactive-accent);\n box-shadow: 0 0 0 2px color-mix(in srgb, var(--interactive-accent) 30%, transparent);\n}\n\n.lookup-item-view .lookup-query-controls {\n display: flex;\n align-items: center;\n gap: 0.75rem;\n flex-wrap: wrap;\n}\n\n.lookup-item-view .lookup-query-toggle {\n display: inline-flex;\n align-items: center;\n gap: 0.5rem;\n color: var(--text-muted);\n font-size: 0.85rem;\n}\n\n.lookup-item-view .lookup-query-toggle input {\n margin: 0;\n}\n\n.lookup-item-view .lookup-query-submit {\n margin-left: auto;\n}\n\n.lookup-item-view .smart-lookup-list-container {\n display: flex;\n flex-direction: column;\n gap: 0.5rem;\n}\n"; // node_modules/smart-lookup-obsidian/src/utils/lookup_query_utils.js var DEFAULT_DEBOUNCE_MS = 300; function create_debounced_submit(handler, delay = DEFAULT_DEBOUNCE_MS) { let timeout_id; const schedule = (value) => { if (timeout_id) clearTimeout(timeout_id); timeout_id = setTimeout(() => { timeout_id = void 0; handler(value); }, delay); }; schedule.cancel = () => { if (timeout_id) { clearTimeout(timeout_id); timeout_id = void 0; } }; return schedule; } function sanitize_query(value) { if (typeof value !== "string") return ""; return value.trim(); } // node_modules/smart-lookup-obsidian/src/components/lookup/item_view.js var REQUIRED_MESSAGE = "Enter a lookup query to continue."; var PLACEHOLDER = "Describe the idea, topic, or question you want to explore\u2026"; var INFO = "Use semantic (embeddings) search to surface relevant notes. Results are sorted by similarity to your query. Note: returns different results than lexical (keyword) search."; var AUTO_SUBMIT_LABEL = "Auto-submit"; var AUTO_SUBMIT_INFO = "Automatically run lookup after you pause typing. Turn off to submit manually."; var SUBMIT_LABEL = "Lookup"; async function build_html36(view, params = {}) { const auto_submit_checked = params.auto_submit === false ? "" : "checked"; return `

    ${INFO}

    `; } async function render38(view, params = {}) { this.apply_style_sheet(item_view_default); const html = await build_html36.call(this, view, params); const frag = this.create_doc_fragment(html); const container = frag.querySelector(".lookup-item-view"); post_process34.call(this, view, container, params); return container; } async function post_process34(view, container, params = {}) { const query_input = container.querySelector(".lookup-query-input"); const query_form = container.querySelector(".lookup-query-form"); const auto_submit_input = container.querySelector(".lookup-query-auto-submit"); const submit_btn = container.querySelector(".lookup-query-submit"); const list_container = container.querySelector(".smart-lookup-list-container"); const state = { last_query: null, active_request_id: 0 }; const render_info_state = () => { this.empty(list_container); list_container.innerHTML = `

    ${INFO}

    `; }; const sync_form_state = () => { const query = sanitize_query(query_input.value); update_query_validity({ input_el: query_input, query }); update_submit_state({ submit_btn, query }); return query; }; const submit_query = async (raw_query) => { const query = sanitize_query(raw_query); const request_id = ++state.active_request_id; update_query_validity({ input_el: query_input, query }); update_submit_state({ submit_btn, query }); if (!query) { state.last_query = null; render_info_state(); return; } if (query === state.last_query) return; state.last_query = query; const next_params = { ...params, query, auto_submit: auto_submit_input.checked }; const lookup_list = view.env.lookup_lists.new_item(next_params); const rendered_list = await view.env.smart_components.render_component("lookup_v3_list", lookup_list, next_params); if (request_id !== state.active_request_id) return; if (sanitize_query(query_input.value) !== query) return; this.empty(list_container); list_container.appendChild(rendered_list); }; const debounced_submit = create_debounced_submit(submit_query); query_input.addEventListener("input", () => { const query = sync_form_state(); if (!query) { debounced_submit.cancel?.(); submit_query(query); return; } if (!auto_submit_input.checked) { debounced_submit.cancel?.(); return; } debounced_submit(query); }); auto_submit_input.addEventListener("change", () => { const query = sync_form_state(); if (!auto_submit_input.checked) { debounced_submit.cancel?.(); return; } if (query) debounced_submit(query); }); query_form.addEventListener("submit", (event) => { event.preventDefault(); const query = sync_form_state(); debounced_submit.cancel?.(); submit_query(query); }); sync_form_state(); return container; } function update_query_validity({ input_el, query }) { if (!input_el?.setCustomValidity) return; if (!query) input_el.setCustomValidity(REQUIRED_MESSAGE); else input_el.setCustomValidity(""); } function update_submit_state({ submit_btn, query }) { if (!submit_btn) return; submit_btn.disabled = !query; } // src/components/lookup/item_view.js var version = "0.0.1"; // node_modules/smart-lookup-obsidian/src/components/lookup/v3/styles.css var styles_default2 = ".lookup-item-view .smart-lookup-list {\n a.lookup-result-file-title {\n text-decoration: none !important;\n }\n\n a.lookup-result-file-title > .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 !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 }\n\n a.lookup-result-file-title > .sc-path {\n margin-right: -3px;\n }\n\n a.lookup-result-file-title > .sc-path,\n a.lookup-result-file-title > .sc-title {\n font-weight: bold !important;\n text-decoration: underline;\n }\n\n a.lookup-result-file-title > .sc-breadcrumb:not(.sc-path, .sc-title, .sc-score) {\n font-style: italic;\n }\n\n a.lookup-result-file-title > .sc-breadcrumb-separator {\n color: color-mix(in srgb, var(--text-normal) 50%, transparent) !important;\n }\n}\n\n\n.smart-lookup-list {\n padding-bottom: 20px;\n\n .tree-item-self {\n cursor: pointer;\n\n small {\n color: var(--color-gray-40);\n }\n }\n\n > .sc-collapsed ul {\n display: none;\n }\n\n > .sc-collapsed span svg {\n transform: rotate(-90deg);\n }\n\n > :not(.sc-collapsed) span svg {\n transform: rotate(0deg);\n }\n\n > div {\n span svg {\n height: auto;\n margin: auto 0.5em auto 0;\n flex: none;\n }\n\n > span {\n display: inline-flex;\n width: 100%;\n padding-left: 0;\n }\n\n ul {\n margin: 0;\n padding-left: 1.3rem;\n }\n\n > a {\n display: block;\n }\n\n > ul > li {\n display: block;\n }\n }\n\n .lookup-result {\n > ul {\n list-style: none;\n padding-left: 0;\n }\n }\n\n .lookup-result.lookup-result-plaintext {\n font-size: var(--font-ui-smaller);\n line-height: var(--line-height-tight);\n background-color: var(--search-result-background);\n border-radius: var(--radius-s);\n overflow: hidden;\n margin: var(--size-4-1) 0 var(--size-4-2);\n color: var(--text-muted);\n box-shadow: 0 0 0 1px var(--background-modifier-border);\n\n & > * li {\n cursor: var(--cursor);\n position: relative;\n padding: var(--size-4-2) var(--size-4-5) var(--size-4-2) var(--size-4-3);\n white-space: pre-wrap;\n width: 100%;\n border-bottom: 1px solid var(--background-modifier-border);\n }\n }\n\n .lookup-result:not(.lookup-result-plaintext) {\n cursor: pointer;\n padding: var(--nav-item-padding);\n padding-left: 0;\n margin-bottom: 1px;\n align-items: baseline;\n border-radius: var(--radius-s);\n font-weight: var(--nav-item-weight);\n\n &:hover {\n color: var(--nav-item-color-active);\n background-color: var(--nav-item-background-active);\n font-weight: var(--nav-item-weight-active);\n }\n\n span {\n color: var(--h5-color);\n }\n\n small {\n color: var(--h5-color);\n font-size: 0.8rem;\n font-weight: 500;\n }\n\n p {\n margin-top: 0.3rem;\n margin-bottom: 0.3rem;\n }\n\n ul > li {\n h1 {\n font-size: 1.3rem;\n }\n\n h2 {\n font-size: 1.25rem;\n }\n\n h3 {\n font-size: 1.2rem;\n }\n\n h4 {\n font-size: 1.15rem;\n }\n\n h5 {\n font-size: 1.1rem;\n }\n\n h6 {\n font-size: 1.05rem;\n }\n\n h1,\n h2,\n h3,\n h4,\n h5,\n h6 {\n margin-block-start: calc(var(--p-spacing) / 2);\n margin-block-end: calc(var(--p-spacing) / 2);\n }\n }\n }\n}\n\n.mod-right-split .smart-lookup-list .lookup-result {\n font-size: 0.88rem;\n}\n"; // node_modules/smart-lookup-obsidian/src/components/lookup/v3/list.js async function build_html37(lookup_list, opts = {}) { return `
    `; } async function render39(lookup_list, opts = {}) { this.apply_style_sheet(styles_default2); const html = await build_html37.call(this, lookup_list, opts); const frag = this.create_doc_fragment(html); const container = frag.firstElementChild; post_process35.call(this, lookup_list, container, opts); return container; } async function post_process35(lookup_list, container, opts = {}) { container.dataset.key = lookup_list.key; const results = await lookup_list.get_results(opts); if (!results || !Array.isArray(results) || results.length === 0) { const no_results = this.create_doc_fragment('

    No results found

    '); container.appendChild(no_results); return container; } const smart_components = lookup_list.env.smart_components; const result_frags = await Promise.all(results.map((result) => { return smart_components.render_component("lookup_v3_list_item", result, { event_key_domain: "lookup", ...opts }); })); result_frags.forEach((result_frag) => container.appendChild(result_frag)); return container; } // src/components/lookup/v3/list.js var version2 = "0.0.1"; // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/utils/get_item_display_name.js var DISPLAY_SEPARATOR3 = " \u203A "; function get_item_display_name4(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_SEPARATOR3).replace(/\//g, DISPLAY_SEPARATOR3); } 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_SEPARATOR3); } // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/utils/register_block_hover_popover.js var import_obsidian59 = require("obsidian"); function register_block_hover_popover2(parent, target, env, block_key, params = {}) { const app2 = env?.plugin?.app || window.app; target.addEventListener("mouseover", async (ev) => { if (import_obsidian59.Keymap.isModEvent(ev)) { const block = env.smart_blocks.get(block_key); const markdown = await block?.read(); if (markdown) { const popover = new import_obsidian59.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_obsidian59.MarkdownRenderer.render(app2, markdown, sizer, "/", popover); const event_domain = params.event_key_domain || "block"; block.emit_event(`${event_domain}:hover_preview`); } } }); } // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/utils/register_item_hover_popover.js var import_obsidian60 = require("obsidian"); function register_item_hover_popover2(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_obsidian60.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_popover2(container.parentElement, container, item.env, item.key); } } // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/utils/parse_item_key_to_wikilink.js function parse_item_key_to_wikilink2(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/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/utils/register_item_drag.js function handle_connection_drag2(obsidian_app, item, params, event) { const drag_manager = obsidian_app.dragManager; const link_text = parse_item_key_to_wikilink2(item.key); const drag_data = drag_manager.dragLink(event, link_text); drag_manager.onDragStart(event, drag_data); if (params.drag_event_key) { item.emit_event(params.drag_event_key); } else { item.emit_event("connections:drag_result"); } } function register_item_drag2(container, item, params = {}) { const env = item.env; const app2 = env.obsidian_app; container.setAttribute("draggable", "true"); container.addEventListener("dragstart", handle_connection_drag2.bind(null, app2, item, params)); } // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/utils/open_source.js var import_obsidian61 = require("obsidian"); async function open_source2(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_obsidian61.Keymap.isModEvent(event); const is_alt = import_obsidian61.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/smart-lookup-obsidian/src/components/lookup/v3/list_item.js async function build_html38(result, params = {}) { const item = result.item; const env = item.env; const score = result.score; const lookup_settings = params.lookup_settings ?? env.lookup_lists.settings; const component_settings = lookup_settings.components?.lookup_v3_list_item || {}; const header_html = get_result_header_html2(score, item, component_settings); const all_expanded = lookup_settings.expanded_view; return `
    ${this.get_icon_html("right-triangle")} ${header_html}
    `; } async function render40(result_scope, params = {}) { let html = await build_html38.call(this, result_scope, params); const frag = this.create_doc_fragment(html); const container = frag.querySelector(".lookup-result"); post_process36.call(this, result_scope, container, params); return container; } async function post_process36(result_scope, container, params = {}) { const { item } = result_scope; const env = item.env; const app2 = env.obsidian_app; const lookup_settings = params.lookup_settings ?? env.lookup_lists.settings; const component_settings = lookup_settings.components?.lookup_v3_list_item || {}; const should_render_markdown = component_settings?.render_markdown ?? true; if (!should_render_markdown) container.classList.add("lookup-result-plaintext"); 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_embed2(entity)) markdown = `${entity.embed_link} ${await entity.read()}`; else markdown = process_for_rendering2(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_result2); const event_key_domain = params.event_key_domain || "lookup"; const drag_event_key = `${event_key_domain}:drag_result`; register_item_drag2(container, item, { drag_event_key }); register_item_hover_popover2(container, item, { event_key_domain }); container.addEventListener("click", (event) => { open_source2(item, event); item.emit_event(`${event_key_domain}:open_result`, { event_source: "lookup-v3-list-item" }); }); 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"] }); if (!container.classList.contains("sc-collapsed")) { render_result(container); } return container; } function get_result_header_html2(score, item, component_settings = {}) { const raw_parts = get_item_display_name4(item, component_settings).split(DISPLAY_SEPARATOR3).filter(Boolean); const parts = format_item_parts2(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_parts2(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_embed2(entity) { if (!entity) return false; if (entity.is_media) return true; return false; } function process_for_rendering2(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_result2(event) { event.preventDefault(); event.stopPropagation(); const _result_elm = event.target.closest(".lookup-result"); _result_elm.classList.toggle("sc-collapsed"); } // src/components/lookup/v3/list_item.js var version3 = "0.0.1"; // src/actions/connections-list/pre_process.js function pre_process2(params) { if (!params.limit) params.limit = this.settings?.results_limit ?? 20; if (!params.results_collection_key) { params.results_collection_key = this.collection.results_collection_key; } if (!params.filter) params.filter = {}; if (!params.score_algo_key) params.score_algo_key = this.collection.score_algo_key; params.to_item = this.item; if (!params.filter.exclude_keys) params.filter.exclude_keys = []; if (!params.filter.exclude_key_starts_with_any) { params.filter.exclude_key_starts_with_any = []; } if (!params.filter.frontmatter) params.filter.frontmatter = {}; if (!params.filter.frontmatter.include) { params.filter.frontmatter.include = this.collection.frontmatter_inclusions; } if (!params.filter.frontmatter.exclude) { params.filter.frontmatter.exclude = this.collection.frontmatter_exclusions; } get_connections_feedback_items(this, params); const exclude_keys_set = new Set(params.filter.exclude_keys); exclude_keys_set.add(this.item.key); params.hidden_keys.forEach((key) => exclude_keys_set.add(key)); params.pinned_keys.forEach((key) => exclude_keys_set.add(key)); params.filter.exclude_keys = Array.from(exclude_keys_set); if (params.results_collection_key === "smart_blocks") { const exclude_starts_set = new Set(params.filter.exclude_key_starts_with_any); exclude_starts_set.add(this.item.key); params.hidden_keys.forEach((key) => exclude_starts_set.add(key)); params.pinned_keys.forEach((key) => exclude_starts_set.add(key)); params.filter.exclude_key_starts_with_any = Array.from(exclude_starts_set); if (this.collection.settings.exclude_frontmatter_blocks) { if (!params.filter.exclude_key_ends_with_any || !Array.isArray(params.filter.exclude_key_ends_with_any)) { params.filter.exclude_key_ends_with_any = []; } params.filter.exclude_key_ends_with_any.push("---frontmatter---"); } } } function get_connections_feedback_items(connections_list, params) { params.hidden = []; params.hidden_keys = []; params.pinned = []; params.pinned_keys = []; const connections_state = connections_list.item.data?.connections || {}; Object.entries(connections_state).forEach(([key, state]) => { if (!state || state.hidden == null && state.pinned == null) return; const [collection_key, ...item_key_parts] = key.split(":"); if (!collection_key || !item_key_parts.length) return; const item_key = item_key_parts.join(":"); const collection = connections_list.env[collection_key]; if (!collection) return; const item = collection.get(item_key); if (!item) return; if (state.hidden && !state.pinned) { params.hidden.push(item); params.hidden_keys.push(item_key); } if (state.pinned) { params.pinned.push(item); params.pinned_keys.push(item_key); } }); } // smart_env.config.js var smart_env_config3 = { collections: { connections_lists: connections_lists_default }, items: { connections_list: { class: ConnectionsList, version: "2.4.4" } }, modules: {}, components: { connections_codeblock: { render: render29, version: "2.4.4" }, connections_footer_view: { render: render30, version: "2.4.4" }, connections_graph_v1: { render: render31, version: "2.4.4" }, connections_list_item_v3: { render: render32, settings_config: settings_config11, version: "2.4.4" }, connections_list_v3: { render: render33, display_name: display_name5, version: "2.4.4" }, connections_list_v4: { render: render34, settings_config: settings_config12, display_name: display_name6, version: "2.4.4" }, connections_settings_header: { render: render35, version: "2.4.4" }, connections_settings_lookup_callout: { render: render36, version: "2.4.4" }, connections_view_v3: { render: render37, version: "2.4.4" }, lookup_item_view: { render: render38, version }, lookup_v3_list: { render: render39, version: version2 }, lookup_v3_list_item: { render: render40, version: version3 } }, actions: { connections_list_pre_process: { action: pre_process2, pre_process: pre_process2, version: "2.4.4" } } }; // node_modules/obsidian-smart-env/utils/open_note.js var import_obsidian62 = 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_obsidian62.Keymap.isModEvent(event); const is_alt = import_obsidian62.Keymap.isModifier(event, "Alt"); if (is_mod && is_alt) { leaf = plugin.app.workspace.splitActiveLeaf("vertical"); } else if (is_mod || new_tab) { leaf = plugin.app.workspace.getLeaf(true); } else { leaf = plugin.app.workspace.getMostRecentLeaf(); } } else { leaf = plugin.app.workspace.getMostRecentLeaf(); } await leaf.openFile(target_file); if (typeof block?.line_start === "number") { const { editor } = leaf.view; const pos = { line: block.line_start, ch: 0 }; editor.setCursor(pos); editor.scrollIntoView({ to: pos, from: pos }, true); } } // src/views/settings_tab.js var ScEarlySettingsTab = class extends SmartPluginSettingsTab { constructor(app2, plugin) { super(app2, plugin); this.plugin = plugin; } hide() { super.hide?.(); this.plugin_container?.empty?.(); this.turn_off_listener?.(); } async render_header(container) { const header = await this.env.smart_components.render_component("connections_settings_header", this.plugin); container.appendChild(header); } async render_plugin_settings(container) { if (!container) return; container.empty?.(); container.innerHTML = '
    Loading main settings...
    '; 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_obsidian63 = 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_obsidian63.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 `v4.5`\n## Connections Footer is now a Core feature!\nFooter connections are now included in Smart Connections Core, bringing the most mobile-friendly and no-panel writing surface to every install. Connections Pro continues to add inline discovery, Bases workflows, and advanced ranking control.\n- Place your connections list in the footer of every note.\n- Toggle footer connections from the command palette (hotkey), ribbon icon, or settings.\n\n## Recent highlights\n- Connections lists can now open with a graph view.\n- [Substrate Update.](https://smartconnections.app/smart-plugins/substrate-update/)\n\n## Additional notes\n\n- Fixed: transformers embedding model should fallback to non-GPU v4 usage and subsequently v3 if that still fails\n- Fixed: should only calculate connections results once (improves performance)\n\n[More details about the latest releases](https://smartconnections.app/smart-connections/releases/4-5/)\n"; // src/views/release_notes_view.js var ReleaseNotesView2 = class extends ReleaseNotesView { static view_type = "smart-release-notes-view"; static plugin_id = "smart-connections"; static release_notes_md = latest_release_default; }; // src/utils/get_random_connection.js var DEFAULT_RESULTS_LIMIT = 20; async function get_random_connection(env, file_path, { rng = Math.random } = {}) { if (!env?.smart_sources || !file_path) return null; const source = env.smart_sources.get(file_path); if (!source?.should_embed) return null; const connections = await source.connections.get_results({ limit: DEFAULT_RESULTS_LIMIT }); if (!Array.isArray(connections) || connections.length === 0) return null; return pick_weighted_connection(connections, { rng }); } function pick_weighted_connection(connections, { rng }) { const scored_connections = connections.map((connection) => ({ connection, score: Math.max(0, typeof connection?.score === "number" ? connection.score : 0) })); const total_score = scored_connections.reduce((sum, { score }) => sum + score, 0); if (total_score === 0) return connections[0]; const threshold = rng() * total_score; let cumulative = 0; for (const { connection, score } of scored_connections) { cumulative += score; if (threshold < cumulative) return connection; } return connections.at(-1); } // src/utils/add_icons.js var import_obsidian64 = require("obsidian"); function add_smart_dice_icon() { (0, import_obsidian64.addIcon)("smart-dice", ` `); } // src/utils/view_leaf_location.js var is_descendant_of = (node, ancestor) => { let current = node; while (current) { if (current === ancestor) { return true; } current = current.parent; } return false; }; var get_leaf_location = ({ workspace, leaf }) => { if (!workspace || !leaf) { return "root"; } const { leftSplit, rightSplit } = workspace; if (is_descendant_of(leaf, leftSplit)) { return "left"; } if (is_descendant_of(leaf, rightSplit)) { return "right"; } return "root"; }; var should_relocate_leaf = ({ workspace, leaf, desired_location }) => { if (!leaf || desired_location !== "left" && desired_location !== "right") { return false; } const leaf_location = get_leaf_location({ workspace, leaf }); return leaf_location !== desired_location; }; // src/utils/pause_controls.js function apply_pause_state(target, paused) { const value = Boolean(paused); target.paused = value; target.pause_controls?.update?.(value); return value; } function toggle_pause_state(target) { const next = !target.paused; target.paused = next; target.pause_controls?.update?.(next); return next; } // src/views/connections_item_view.js var ConnectionsItemView = class extends SmartItemView { static get view_type() { return "smart-connections-view"; } static get display_text() { return "Connections"; } static get icon_name() { return "smart-connections"; } constructor(leaf, plugin) { super(leaf, plugin); this.paused = false; this.pause_controls = null; this.current = null; } async render_view(params = {}, container = this.container) { if (!params.connections_item) { const active_path = this.plugin.app.workspace.getActiveFile()?.path; params.connections_item = this.env.smart_sources.get(active_path); } this.current = params.connections_item; this.pause_controls = null; const frag = await this.env.smart_components.render_component("connections_view_v3", this, { connections_item: params.connections_item }); container.empty(); container.appendChild(frag); this.register_env_listeners(); this.env.events.emit("connections:opened"); } async open_settings() { await this.app.setting.open(); await this.app.setting.openTabById(this.plugin.manifest.id); } register_env_listeners() { let handle_current_source_debounce; register_env_event_listener(this, "sources:opened", (event = {}) => { if (this.paused) return; if (!is_visible(this.container)) return; const connections_item = this.env[event.collection_key || "smart_sources"]?.get(event.item_key || event.key); if (connections_item.key === this.current?.key) return; if (handle_current_source_debounce) clearTimeout(handle_current_source_debounce); handle_current_source_debounce = setTimeout(() => { this.render_view({ connections_item }); }, 250); }); register_env_event_listener(this, "settings:changed", (event) => { if (event.path?.includes("expanded_view")) return; if (event.path?.includes("connections_lists") && is_visible(this.container)) { this.render_view({ connections_item: this.current }); } }); register_env_event_listener(this, "connections:show", (event) => { console.log("connections:show event received", { event }); if (event.collection_key && event.item_key) { const collection = this.env[event.collection_key]; const item = collection.get(event.item_key); console.log({ collection, item }); if (item) { this.set_connections_paused(true); this.render_view({ connections_item: item }); } } }); register_env_event_listener(this, "items:embedded", (event = {}) => { if (event.collection_key === this.current?.collection_key && event.keys?.includes(this.current?.key) && is_visible(this.container)) { this.render_view({ connections_item: this.current }); } }); } /** * Register UI controls for reflecting pause state. * @param {{ update: (paused: boolean) => void }} controls */ register_pause_controls(controls) { this.pause_controls = controls; this.pause_controls?.update(this.paused); } /** * Set the paused state and sync UI controls. * @param {boolean} paused * @returns {boolean} */ set_connections_paused(paused) { return apply_pause_state(this, paused); } /** * Toggle the paused state and sync UI controls. * @returns {boolean} */ toggle_connections_paused() { return toggle_pause_state(this); } }; function is_visible(container) { if (!container) { return false; } if (!container.isConnected) { console.warn("Connections container is not connected to DOM"); return false; } if (typeof container.checkVisibility === "function" && container.checkVisibility() === false) { console.log("Connections container is not visible"); 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; }; // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/views/smart_item_view.js var import_obsidian70 = require("obsidian"); // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/utils/wait_for_env_to_load.js var import_obsidian65 = require("obsidian"); async function wait_for_env_to_load2(scope, opts = {}) { const { wait_for_states = ["loaded"] } = opts; const container = scope.container || scope.containerEl; if (!wait_for_states.includes(scope.env?.state)) { let clicked_load_env = false; while (scope.env.state === "init" && import_obsidian65.Platform.isMobile && !clicked_load_env) { if (container) { container.empty(); scope.env.smart_view.safe_inner_html(container, ""); 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/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/components/status_bar.js var import_obsidian69 = require("obsidian"); // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/utils/register_status_bar_context_menu.js var import_obsidian68 = require("obsidian"); // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/views/source_inspector.js var import_obsidian66 = require("obsidian"); var SmartNoteInspectModal2 = class extends import_obsidian66.Modal { constructor(smart_connections_plugin, entity) { super(smart_connections_plugin.app); this.smart_connections_plugin = smart_connections_plugin; this.entity = entity; } get env() { return this.smart_connections_plugin.env; } onOpen() { this.titleEl.innerText = this.entity.key; this.render(); } async render() { this.contentEl.empty(); const frag = await this.env.smart_components.render_component("source_inspector", this.entity); this.contentEl.appendChild(frag); } }; // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/modals/env_stats.js var import_obsidian67 = require("obsidian"); var EnvStatsModal2 = class extends import_obsidian67.Modal { constructor(app2, env) { super(app2); this.env = env; } onOpen() { this.titleEl.setText("Smart Environment"); this.contentEl.empty(); this.contentEl.createEl("p", { text: "Loading stats..." }); setTimeout(this.render.bind(this), 100); } async render() { const frag = await this.env.smart_components.render_component("env_stats", this.env); this.contentEl.empty(); if (frag) { this.contentEl.appendChild(frag); } else { this.contentEl.createEl("p", { text: "Failed to load stats." }); } } }; // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/utils/register_status_bar_context_menu.js function register_status_bar_context_menu2(env, status_container, deps = {}) { const { Menu: MenuClass = import_obsidian68.Menu } = deps; const plugin = env.main; const on_context_menu = (ev) => { ev.preventDefault(); ev.stopPropagation(); const menu = new MenuClass(plugin.app); menu.addItem( (item) => item.setTitle("Inspect active note").setIcon("search").onClick(async () => { const active_file = plugin.app.workspace.getActiveFile(); if (!active_file) { env?.events?.emit?.("status_bar:inspect_active_note_missing", { level: "warning", message: "No active note found", event_source: "register_status_bar_context_menu.inspect" }); return; } const src = env.smart_sources?.get(active_file.path); if (!src) { env?.events?.emit?.("status_bar:inspect_source_missing", { level: "warning", message: "Active note is not indexed by Smart Environment", event_source: "register_status_bar_context_menu.inspect" }); return; } new SmartNoteInspectModal2(plugin, src).open(); }) ); menu.addItem( (item) => item.setTitle("Show stats").setIcon("chart-pie").onClick(() => { const modal = new EnvStatsModal2(plugin.app, env); modal.open(); }) ); menu.addItem( (item) => item.setTitle("Export data").setIcon("download").onClick(() => { env.export_json(); env?.events?.emit?.("smart_env:exported", { level: "attention", message: "Smart Env exported", event_source: "register_status_bar_context_menu.export" }); }) ); menu.addItem( (item) => item.setTitle("Milestones").setIcon("flag").onClick(() => { env.open_milestones_modal(); }) ); menu.addItem( (item) => item.setTitle("Notifications").setIcon("bell").onClick(() => { env.open_notifications_feed_modal(); }) ); menu.addSeparator(); menu.addItem( (item) => item.setTitle("Browse Smart Plugins").setIcon("package").onClick(() => { env.events?.emit?.("smart_plugins:browse", { event_source: "status_bar" }); }) ); if (env.is_pro) { menu.addItem( (item) => item.setTitle("Refer a friend (Give 30, Get 30)").setIcon("hand-heart").onClick(() => { const url = "https://smartconnections.app/my-referrals/?utm_source=status-bar"; window.open(url, "_external"); }) ); } else { menu.addItem( (item) => item.setTitle("Start 14-day Pro trial").setIcon("hand-heart").onClick(() => { const url = "https://smartconnections.app/pro-plugins/?utm_source=status-bar"; window.open(url, "_external"); }) ); } menu.showAtPosition({ x: ev.pageX, y: ev.pageY }); }; plugin.registerDomEvent(status_container, "contextmenu", on_context_menu); return on_context_menu; } // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/node_modules/smart-events/event_level_utils.js var notification_levels2 = Object.freeze([ "milestone", "attention", "error", "warning", "info" ]); var notification_level_set2 = new Set(notification_levels2); function normalize_event_level2(level) { if (typeof level !== "string") return null; const normalized_level = level.trim().toLowerCase(); if (!notification_level_set2.has(normalized_level)) return null; return normalized_level; } function get_legacy_notification_level2(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_level2(legacy_level); } function get_display_fallback_level2(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_level2(event_key = "", event = {}, params = {}) { const { allow_display_fallback = false } = params; const payload_level = normalize_event_level2(event?.level); if (payload_level) return payload_level; const legacy_level = get_legacy_notification_level2(event_key); if (legacy_level) return legacy_level; if (allow_display_fallback) { return get_display_fallback_level2(event_key); } return null; } // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/utils/event_logs_utils.js var notice_timeout_ms2 = 7e3; var milestone_notice_timeout_ms2 = 12e3; function get_timeout_override2(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_timeout2(event_key, event = {}) { const timeout_override = get_timeout_override2(event); if (timeout_override !== null) return timeout_override; const level = get_event_level2(event_key, event); if (level === "milestone") return milestone_notice_timeout_ms2; return notice_timeout_ms2; } function get_native_notice_message2(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/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/utils/status_bar_state.js var indicator_level_rank2 = { info: 1, milestone: 2, attention: 3, warning: 4, error: 5 }; function get_unseen_notification_entries2(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_count2(event_logs) { return get_unseen_notification_entries2(event_logs).length; } function get_next_indicator_level2(current_level, event_key = "", event = {}) { const next_level = get_event_level2(event_key, event); if (!next_level) return current_level ?? null; const current_rank = indicator_level_rank2[current_level] || 0; const next_rank = indicator_level_rank2[next_level] || 0; if (next_rank > current_rank) return next_level; return current_level ?? next_level; } function get_notification_indicator_level2(event_logs) { return get_unseen_notification_entries2(event_logs).reduce((current_level, entry) => { return get_next_indicator_level2(current_level, entry?.event_key, entry?.event); }, null); } function get_status_bar_notice_line2(event_key = "", event = {}) { const message = get_native_notice_message2(event_key, event); return String(message || "").split(/\r?\n/u)[0].trim(); } function format_status_bar_notice_message2(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_preview2(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_level2(event_key, event)) continue; if (entry?.native_notice_shown === true) return null; const timeout_ms = get_native_notice_timeout2(event_key, event); const expires_at = get_entry_timestamp3(entry) + timeout_ms; if (Date.now() > expires_at) return null; const title = get_status_bar_notice_line2(event_key, event); const message = format_status_bar_notice_message2(title); if (!message) return null; return { message, title }; } return null; } function get_import_progress_state2(env) { return env?.smart_sources?.get_import_progress_state?.() || null; } function get_embed_progress_state2(env) { return env?.smart_sources?.entities_vector_adapter?.get_progress_state?.() || null; } function get_reimport_queue_count2(env) { return Object.keys(env?.smart_sources?.sources_re_import_queue || {}).length; } function get_progress_pct2(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_number2(value) { return typeof value === "number" && Number.isFinite(value) ? value : 0; } function to_non_empty_string2(value) { return typeof value === "string" ? value.trim() : ""; } function format_collection_label2(collection_key = "") { return collection_key.split("_").filter(Boolean).map((part) => part.slice(0, 1).toUpperCase() + part.slice(1)).join(" "); } function get_collection_loading_state2(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_label2(current_key) : "" }; } function get_env_activity_state2(env) { const import_progress = get_import_progress_state2(env); const embed_progress = get_embed_progress_state2(env); const reimport_queue_count = get_reimport_queue_count2(env); const version4 = `v${env?.constructor?.version || ""}`; const default_message = `${env?.is_pro ? "Pro" : "Smart"} ${version4}`; if (import_progress?.active) { const progress = normalize_number2(import_progress.progress); const total = normalize_number2(import_progress.total); const stage = to_non_empty_string2(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_pct2(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_number2(embed_progress.progress); const total = normalize_number2(embed_progress.total); const paused = Boolean(embed_progress.paused); const tokens_per_second = normalize_number2(embed_progress.tokens_per_second); const model_name = to_non_empty_string2(embed_progress.model_name); const reason = to_non_empty_string2(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_pct2(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_state2(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_pct2(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_activity2(env) { const activity_state = get_env_activity_state2(env); if (get_status_bar_notice_preview2(env?.event_logs)) return true; return [ "embed_active", "embed_paused", "importing", "reimporting", "loading", "not_loaded" ].includes(activity_state.kind); } function get_status_bar_state2(env) { const notification_count = get_notification_event_count2(env?.event_logs); const notification_indicator_level = get_notification_indicator_level2(env?.event_logs); const activity_state = get_env_activity_state2(env); const embed_queue_count = get_reimport_queue_count2(env); const status_bar_notice_preview = get_status_bar_notice_preview2(env?.event_logs); const can_show_notice_preview = Boolean(status_bar_notice_preview) && !["embed_active", "embed_paused", "importing", "reimporting", "loading"].includes(activity_state.kind); let title = activity_state.title; let indicator_level = activity_state.indicator_level; if (!indicator_level && notification_count > 0) { indicator_level = notification_indicator_level; } if (activity_state.kind === "ready" && notification_count > 0) { title = `${notification_count} unseen notification${notification_count === 1 ? "" : "s"}`; } if (can_show_notice_preview && status_bar_notice_preview?.title) { title = status_bar_notice_preview.title; } return { message: can_show_notice_preview ? status_bar_notice_preview.message : activity_state.message, title, indicator_count: notification_count, indicator_level, embed_queue_count, click_action: activity_state.click_action }; } function get_entry_timestamp3(entry) { if (typeof entry?.event?.at === "number") return entry.event.at; if (typeof entry?.at === "number") return entry.at; return Date.now(); } // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/components/status_bar.css var status_bar_default2 = ".status-bar-item:has(.smart-env-status-container) {\n padding: 0 0.5em;\n\n &:hover {\n background-color: var(--background-modifier-hover);\n }\n & > .smart-env-status-container {\n display: flex;\n align-items: center;\n gap: 0.5em;\n text-decoration: none;\n color: var(--status-bar-text-color);\n }\n}\n\n.smart-env-status-indicator {\n --smart-env-status-indicator-color: var(--status-bar-text-color);\n --smart-env-status-indicator-glow-size: 0.28em;\n --smart-env-status-indicator-glow-opacity: 0.38;\n\n position: relative;\n isolation: isolate;\n\n width: 0.62em;\n height: 0.62em;\n min-width: 0.62em;\n min-height: 0.62em;\n border-radius: 999px;\n\n color: var(--smart-env-status-indicator-color);\n background-color: currentColor;\n opacity: 1;\n transform: scale(1);\n box-shadow:\n 0 0 0 1px color-mix(in srgb, currentColor 24%, transparent),\n inset 0 0 0 1px color-mix(in srgb, var(--background-primary) 78%, transparent);\n transition:\n background-color 150ms ease,\n box-shadow 150ms ease,\n transform 150ms ease,\n color 150ms ease;\n cursor: pointer;\n}\n\n.smart-env-status-indicator::before {\n content: '';\n position: absolute;\n inset: calc(var(--smart-env-status-indicator-glow-size) * -1);\n border-radius: inherit;\n pointer-events: none;\n z-index: -1;\n opacity: var(--smart-env-status-indicator-glow-opacity);\n transform: scale(1);\n filter: blur(7px);\n background:\n radial-gradient(\n circle at center,\n color-mix(in srgb, currentColor 74%, transparent) 0%,\n color-mix(in srgb, currentColor 46%, transparent) 34%,\n color-mix(in srgb, currentColor 18%, transparent) 58%,\n transparent 80%\n );\n transition:\n opacity 150ms ease,\n transform 150ms ease,\n filter 150ms ease;\n}\n\n.smart-env-status-indicator::after {\n content: '';\n position: absolute;\n inset: -1px;\n border-radius: inherit;\n pointer-events: none;\n box-shadow: 0 0 0 1px color-mix(in srgb, currentColor 28%, transparent);\n opacity: 0.82;\n}\n\n.smart-env-status-indicator:hover::before,\n.smart-env-status-indicator:focus-visible::before {\n opacity: max(var(--smart-env-status-indicator-glow-opacity), 0.56);\n transform: scale(1.08);\n}\n\n.smart-env-status-indicator:focus-visible {\n outline: 2px solid var(--color-accent);\n outline-offset: 2px;\n}\n\n.smart-env-status-indicator[data-level='default'] {\n --smart-env-status-indicator-color: var(--status-bar-text-color);\n --smart-env-status-indicator-glow-size: 0.24em;\n --smart-env-status-indicator-glow-opacity: 0.32;\n}\n\n.smart-env-status-indicator[data-level='info'] {\n --smart-env-status-indicator-color: var(--status-bar-text-color);\n --smart-env-status-indicator-glow-size: 0.28em;\n --smart-env-status-indicator-glow-opacity: 0.4;\n}\n\n.smart-env-status-indicator[data-level='milestone'] {\n --smart-env-status-indicator-color: var(--color-accent);\n --smart-env-status-indicator-glow-size: 0.52em;\n --smart-env-status-indicator-glow-opacity: 0.86;\n}\n\n.smart-env-status-indicator[data-level='attention'] {\n --smart-env-status-indicator-color: var(--color-yellow);\n --smart-env-status-indicator-glow-size: 0.5em;\n --smart-env-status-indicator-glow-opacity: 0.84;\n}\n\n.smart-env-status-indicator[data-level='warning'] {\n --smart-env-status-indicator-color: var(--color-orange);\n --smart-env-status-indicator-glow-size: 0.54em;\n --smart-env-status-indicator-glow-opacity: 0.9;\n}\n\n.smart-env-status-indicator[data-level='error'] {\n --smart-env-status-indicator-color: var(--color-red);\n --smart-env-status-indicator-glow-size: 0.58em;\n --smart-env-status-indicator-glow-opacity: 0.96;\n}\n\n.smart-env-status-indicator[data-count] {\n transform: scale(1.08);\n box-shadow:\n 0 0 0 1px color-mix(in srgb, currentColor 30%, transparent),\n inset 0 0 0 1px color-mix(in srgb, var(--background-primary) 84%, transparent);\n}\n\n.smart-env-status-indicator[data-count]::before {\n opacity: 1;\n transform: scale(1.12);\n filter: blur(7px);\n animation: smart-env-status-indicator-glow 2200ms ease-in-out infinite;\n}\n\n.status-bar-mobile {\n position: var(--status-bar-position);\n bottom: 0;\n border-radius: 0 8px 0 0;\n border-style: solid;\n border-width: 1px;\n border-color: var(--status-bar-border-color);\n background-color: var(--status-bar-background);\n color: var(--status-bar-text-color);\n font-size: var(--status-bar-font-size);\n min-height: 18px;\n padding: var(--size-4-1);\n user-select: none;\n z-index: var(--layer-status-bar);\n font-variant-numeric: tabular-nums;\n & > .smart-env-status-container {\n padding: 5px 5px 5px 0;\n }\n}\n\n/* footer view on mobile */\n.embedded-backlinks > .status-bar-mobile {\n position: relative;\n border-style: none;\n}\n\n@keyframes smart-env-status-indicator-glow {\n 0%,\n 100% {\n transform: scale(0.88);\n opacity: 0.92;\n }\n\n 50% {\n transform: scale(1.23);\n opacity: 1;\n }\n}\n"; // node_modules/smart-lookup-obsidian/node_modules/obsidian-smart-env/src/components/status_bar.js function build_html39() { return ` `; } async function render41(env, opts = {}) { this.apply_style_sheet(status_bar_default2); const frag = this.create_doc_fragment(build_html39()); const anchor = frag.firstElementChild; post_process37.call(this, env, anchor, opts); return anchor; } function post_process37(env, container, opts = {}) { const icon_slot = container?.querySelector?.(".smart-env-status-icon"); const status_indicator = container?.querySelector?.(".smart-env-status-indicator"); const status_msg = container?.querySelector?.(".smart-env-status-msg"); const pause_embedding = () => { return env?.smart_sources?.entities_vector_adapter?.halt_embed_queue_processing?.(); }; const resume_embedding = () => { return env?.smart_sources?.entities_vector_adapter?.resume_embed_queue_processing?.(); }; const set_status_message = (message) => { if (!status_msg) return; if (typeof status_msg.setText === "function") { status_msg.setText(message); return; } status_msg.textContent = message; }; const open_notifications_feed3 = (event) => { event.preventDefault?.(); event.stopPropagation?.(); env.open_notifications_feed_modal?.(); }; const update_indicator = (status_state) => { if (!status_indicator) return; const { indicator_count, indicator_level } = status_state; status_indicator.dataset.level = indicator_level || "default"; if (indicator_count > 0) { status_indicator.dataset.count = String(indicator_count); } else { status_indicator.removeAttribute("data-count"); } const indicator_title = indicator_count > 0 ? `${indicator_count} unseen notification${indicator_count === 1 ? "" : "s"}` : "Open notifications feed"; status_indicator.setAttribute("title", indicator_title); }; const action_handlers = { pause_embed(event) { event.preventDefault?.(); event.stopPropagation?.(); pause_embedding(); }, resume_embed(event) { event.preventDefault?.(); event.stopPropagation?.(); resume_embedding(); }, run_reimport(event) { event.preventDefault?.(); event.stopPropagation?.(); set_status_message("Re-importing\u2026"); env.run_re_import?.(); }, noop() { }, context_menu(event) { const context_event = new MouseEvent("contextmenu", { bubbles: true, cancelable: true, clientX: event?.clientX || 0, clientY: event?.clientY || 0 }); container.dispatchEvent?.(context_event); } }; const render_status_elm = () => { const status_state = get_status_bar_state2(env); const { message, title } = status_state; if (icon_slot) { (0, import_obsidian69.setIcon)(icon_slot, "smart-connections"); } update_indicator(status_state); set_status_message(message); container.setAttribute?.("title", title); container.removeAttribute?.("href"); container.removeAttribute?.("target"); }; const run_container_action = (event) => { const status_state = get_status_bar_state2(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_menu2(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_activity2(env)); }; refresh_status_bar(); bind_once2(status_indicator, "_click_handler", "click", open_notifications_feed3); bind_once2(status_indicator, "_keydown_handler", "keydown", (event) => { if (event.key !== "Enter" && event.key !== " ") return; open_notifications_feed3(event); }); bind_once2(container, "_click_handler", "click", (event) => { run_container_action(event); }); bind_once2(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_once2(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/smart-lookup-obsidian/node_modules/obsidian-smart-env/views/smart_item_view.js var SmartItemView2 = class extends import_obsidian70.ItemView { /** * Creates an instance of SmartItemView. * @param {any} leaf * @param {any} plugin */ constructor(leaf, plugin) { super(leaf); this.app = plugin.app; this.plugin = plugin; } /** * The unique view type. Must be implemented in subclasses. * @returns {string} */ static get view_type() { throw new Error("view_type must be implemented in subclass"); } /** * The display text for this view. Must be implemented in subclasses. * @returns {string} */ static get display_text() { throw new Error("display_text must be implemented in subclass"); } /** * The icon name for this view. * @returns {string} */ static get icon_name() { return "smart-connections"; } /** * Whether the view should wait for the environment to be loaded before rendering. * Override in subclasses that must stay visible during environment load. * @returns {boolean} */ static get wait_for_env() { return true; } /** * Registers this ItemView subclass against a plugin instance and * installs ergonomic accessors, an open helper, and an `${view_type}:open` listener. * * Usage from a plugin class: * SubClass.register_item_view(this); * * This will: * - call plugin.registerView(view_type, ...) * - add a command "Open: view" * - define a getter on plugin: plugin[method_name] -> the view instance * - define a method on plugin: plugin["open_" + method_name]() -> opens the view * - listen for env events named `${view_type}:open` and open the view when emitted * * @param {import('obsidian').Plugin} plugin * @returns {{method_name:string, open_method_name:string, event_name:string}} */ static register_item_view(plugin) { const View = ( /** @type {typeof SmartItemView} */ this ); if (plugin.app.viewRegistry?.viewByType?.[View.view_type]) { plugin.app.viewRegistry.unregisterView(View.view_type); console.warn(`View type "${View.view_type}" was already registered. Overwriting with new registration.`); } plugin.registerView(View.view_type, (leaf) => new View(leaf, plugin)); plugin.addCommand({ id: View.view_type, name: "Open: " + View.display_text + " view", callback: () => { View.open(plugin.app.workspace); } }); const method_name = View.view_type.replace(/^smart-/, "").replace(/-/g, "_"); const open_method_name = "open_" + method_name; if (!Object.getOwnPropertyDescriptor(plugin, method_name)) { Object.defineProperty(plugin, method_name, { configurable: true, enumerable: false, get: () => View.get_view(plugin.app.workspace) }); } plugin[open_method_name] = (params = {}) => { if (!plugin.app.viewRegistry?.viewByType?.[View.view_type]) { console.warn(`View type "${View.view_type}" not registered when calling ${open_method_name}. Registering now. This should NOT happen frequently.`); plugin.registerView(View.view_type, (leaf) => new View(leaf, plugin)); } View.open(plugin.app.workspace, params); }; const event_name = `${method_name}:open`; const handler = (payload = {}) => { const active = typeof payload?.active === "boolean" ? payload.active : true; View.open(plugin.app.workspace, { ...payload, active }); }; const unsubscribe = plugin?.env?.events.on(event_name, handler); if (typeof plugin.register === "function" && typeof unsubscribe === "function") { plugin.register(() => unsubscribe()); } return { method_name, open_method_name, event_name }; } /** * Retrieves the Leaf instance for this view type if it exists. * @param {import("obsidian").Workspace} workspace * @returns {import("obsidian").WorkspaceLeaf | undefined} */ static get_leaf(workspace) { return workspace.getLeavesOfType(this.view_type)[0]; } /** * Retrieves the view instance if it exists. * @param {import("obsidian").Workspace} workspace * @returns {SmartItemView | undefined} */ static get_view(workspace) { const leaf = this.get_leaf(workspace); return leaf ? leaf.view : void 0; } /** * Opens the view. If `this.default_open_location` is "root", * it opens (or reveals) in a root leaf; otherwise in a sidebar leaf. * * @param {import("obsidian").Workspace} workspace * @param {boolean|object} [params={}] */ static open(workspace, params = {}) { if (typeof params === "boolean") { params = { active: params }; } const { active = true, state = null } = params; const existing_leaf = this.get_leaf(workspace); const open_location = this.default_open_location; let leaf; if (open_location === "root") { leaf = existing_leaf || workspace.getLeaf(false); } else if (open_location === "left") { leaf = existing_leaf || workspace.getLeftLeaf(false); if (workspace.leftSplit?.collapsed) { workspace.leftSplit.toggle(); } } else { leaf = existing_leaf || workspace.getRightLeaf(false); if (workspace.rightSplit?.collapsed) { workspace.rightSplit.toggle(); } } const view_state = { type: this.view_type, active }; if (state && typeof state === "object") { view_state.state = state; } Promise.resolve(leaf?.setViewState?.(view_state)).then(() => { if (active) { try { workspace.revealLeaf?.(leaf); } catch (error) { console.warn(`Failed to reveal item view "${this.view_type}"`, error); } } setTimeout(() => { this.get_view(workspace)?.render_view(params); }, 100); }).catch((error) => { console.error(`Failed to open item view "${this.view_type}"`, error); }); } static is_open(workspace) { return this.get_leaf(workspace)?.view instanceof this; } // instance getViewType() { return this.constructor.view_type; } getDisplayText() { return this.constructor.display_text; } getIcon() { return this.constructor.icon_name; } async onOpen() { this.app.workspace.onLayoutReady(this.initialize.bind(this)); } async initialize() { await this.render_mobile_status_bar(); const should_wait_for_env = this.constructor.wait_for_env !== false; const wait_for_states = ["loaded"]; if (should_wait_for_env) { await wait_for_env_to_load2(this, { wait_for_states }); } this.container?.empty?.(); this.register_plugin_events(); this.app.workspace.registerHoverLinkSource(this.constructor.view_type, { display: this.getDisplayText(), defaultMod: true }); this.render_view(); } async render_mobile_status_bar() { if (!import_obsidian70.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 render41.call(this.env.smart_view, this.env); if (status_bar) status_bar_item.appendChild(status_bar); } catch (error) { console.error("Failed to render mobile Smart Env status bar", error); } } register_plugin_events() { } render_view(params = {}) { throw new Error("render_view must be implemented in subclass"); } get container() { return this.containerEl.children[1]; } get env() { return this.plugin.env; } async open_settings() { await this.app.setting.open(); await this.app.setting.openTabById(this.plugin.manifest.id); } }; // node_modules/smart-lookup-obsidian/src/views/lookup_item_view.js var LookupItemView = class extends SmartItemView2 { static get view_type() { return "smart-lookup-view"; } static get display_text() { return "Lookup"; } static get icon_name() { return "smart-lookup"; } async render_view(lookup_params, container = this.container) { const frag = await this.env.smart_components.render_component("lookup_item_view", this, lookup_params); container.empty(); container.appendChild(frag); } }; // src/views/lookup_item_view.js var ConnectionsLookupItemView = class extends LookupItemView { static get view_type() { return "smart-lookup-view-connections"; } }; // 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 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) { if (this.container_el) this.container_el.style.display = "none"; } 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.container_el.style.display = this.connections_footer_frag ? "block" : "none"; } #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_obsidian71 = 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 requestAnimationFrame === "function") { requestAnimationFrame(callback); return; } setTimeout(callback, 0); }; var ConnectionsFooterView = class { constructor(plugin) { this.plugin = plugin; this.app = plugin.app; this.register_env_listeners(); this.container_map = {}; this._detach_visibility_guard = null; } get env() { return this.plugin.env; } /** * Attach a lightweight scroll/resize guard that waits until the last line becomes visible. * When the condition is met, it detaches itself and triggers render_view() again. * @param {import('@codemirror/view').EditorView} editor_view */ attach_visibility_guard(editor_view) { this.detach_visibility_guard(); if (!editor_view) return; const scroll_target = editor_view.scrollDOM || editor_view.dom || null; if (!scroll_target) return; let ticking = false; const check_and_render = () => { if (ticking) return; ticking = true; schedule_next_frame(() => { ticking = false; if (is_last_line_visible(editor_view)) { this.detach_visibility_guard(); this.render_view(); } }); }; const on_scroll = () => check_and_render(); const on_resize = () => check_and_render(); scroll_target.addEventListener("scroll", on_scroll, { passive: true }); window.addEventListener("resize", on_resize); this._detach_visibility_guard = () => { try { scroll_target.removeEventListener("scroll", on_scroll); } catch { } try { window.removeEventListener("resize", on_resize); } catch { } this._detach_visibility_guard = null; }; } detach_visibility_guard() { if (typeof this._detach_visibility_guard === "function") { this._detach_visibility_guard(); } } async render_view() { if (!this.env.connections_lists?.settings?.footer_connections) return this.remove(); const editor_view = this.plugin.get_editor_view(); if (!editor_view) return; if (!is_last_line_visible(editor_view)) { try { editor_view.dispatch({ effects: [set_connections_footer_dom_effect.of(null)] }); } catch (err) { console.warn("[Smart Connections] footer hide dispatch failed", err); } this.attach_visibility_guard(editor_view); return; } this.detach_visibility_guard(); const entity = get_active_entity(this.env, this.app.workspace); if (!entity) { editor_view.dispatch({ effects: [set_connections_footer_dom_effect.of(null)] }); return; } if (this.container_map[entity.key]?.isConnected) { editor_view.dispatch({ effects: [set_connections_footer_dom_effect.of(this.container_map[entity.key])] }); return; } const footer_container = await render30.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_obsidian71.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) clearTimeout(handle_current_source_debounce); handle_current_source_debounce = setTimeout(() => { this.render_view(); }, 250); }); this.register_env_listener("settings:changed", (event) => { if (event.path?.includes("connections_lists")) { this.render_view(); } }); } register_env_listener(event_key, callback) { if (!this.env_listeners) this.env_listeners = []; this.env_listeners.push(this.env.events.on(event_key, callback)); } async open_settings() { await this.app.setting.open(); await this.app.setting.openTabById(this.plugin.manifest.id); } unload() { this.detach_visibility_guard(); if (this.env_listeners) { this.env_listeners.forEach((off) => off()); this.env_listeners = []; } } }; // src/views/connections_codeblock.js async function register_smart_connections_codeblock(plugin) { plugin.registerMarkdownCodeBlockProcessor( "smart-connections", async (cb_content, container, mpp_ctx) => { container.empty(); container.createEl("span", { text: "Loading\u2026" }); const cb_config = JSON.parse(cb_content.trim() || "{}"); const env = plugin.env; const entity = env.smart_sources.get(mpp_ctx.sourcePath) ?? env.smart_sources.init_file_path(mpp_ctx.sourcePath); const smart_view = env.smart_view; if (!entity) { container.empty(); container.createEl("p", { text: "Entity not found: " + mpp_ctx.sourcePath }); return; } const render_codeblock = async () => { const connections_list = entity.connections; if (!connections_list?.env) { container.empty(); container.createEl("p", { text: "Smart Environment / Connections loading\u2026" }); 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) => { console.log("connections codeblock view detected settings change", event); if (event.path?.includes("connections_lists")) { render_codeblock(); } })); smart_view.attach_disposer(container, disposers); } render_codeblock(); } ); } // src/utils/build_connections_codeblock.js function build_connections_codeblock(settings = null) { const json = settings ? JSON.stringify(settings, null, 2) : ""; return `\`\`\`smart-connections ${json} \`\`\` `; } // src/main.js var { Plugin: Plugin2, requestUrl: requestUrl7, Platform: Platform16 } = import_obsidian72.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, // TEMP during transition to Lookup as standalone plugin (conditionally include ConnectionsLookupItemView if Smart Lookup is not enabled to avoid conflicts) ...!this.app.plugins.enabledPlugins.has("smart-lookup") ? { ConnectionsLookupItemView } : {} }; } get obsidian() { return import_obsidian72.default; } get api() { return this._api; } onload() { this.app.workspace.onLayoutReady(this.initialize.bind(this)); this.SmartEnv.create(this, this.smart_env_config); this.addSettingTab(new this.ConnectionsSettingsTab(this.app, this)); add_smart_dice_icon(); this.register_commands(); this.register_item_views(); this.register_ribbon_icons(); } onunload() { console.log("Unloading Smart Connections plugin"); this.connections_footer_view?.unload(); this.notices?.unload(); this.env?.unload_main?.(this); } async initialize() { this.smart_connections_view = null; this.is_new_user().then(async (is_new) => { if (!is_new) return; 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 }); setTimeout(() => { this.apply_connections_view_location(); this.open_connections_view(); }, 1e3); this.add_to_gitignore("\n\n# Ignore Smart Environment folder\n.smart-env"); }); await this.SmartEnv.wait_for({ loaded: true }); this.wrap_connections_view_open(); this.apply_connections_view_location(); this.register_connections_view_location_listener(); register_smart_connections_codeblock(this); if (!this.connections_footer_view) { this.registerEditorExtension(connections_footer_plugin); this.connections_footer_view = new ConnectionsFooterView(this); } this.toggled_footer_connections(); await this.check_for_updates(); } get ribbon_icons() { return { connections: { icon_name: "smart-connections", description: "Smart Connections: Open connections view", callback: () => { this.open_connections_view(); } }, footer_connections: { description: "Toggle Footer Connections", icon_name: "smart-footer-connections", callback: () => { const settings = this.env.connections_lists.settings; settings.footer_connections = !settings.footer_connections; } }, random_note: { icon_name: "smart-dice", description: "Smart Connections: Open random connection", callback: () => { this.open_random_connection(); } }, // TEMP during transition to Lookup as standalone plugin (conditionally include ribbon icon if Smart Lookup is not enabled to avoid conflicts) ...app.plugins.enabledPlugins.has("smart-lookup") ? {} : { lookup: { icon_name: "smart-lookup", description: "Smart Lookup: Open lookup view", callback: () => { this.open_lookup_view_connections(); } } } }; } get settings() { return this.env?.settings || {}; } /** * Sync connections view location with settings. * @returns {void} */ apply_connections_view_location() { const connections_view_location = this.env?.connections_lists?.settings?.connections_view_location ?? "right"; ConnectionsItemView.default_open_location = connections_view_location === "left" ? "left" : "right"; this.ensure_connections_view_leaf_location(); } wrap_connections_view_open() { if (this._open_connections_view_base || typeof this.open_connections_view !== "function") { return; } this._open_connections_view_base = this.open_connections_view.bind(this); this.open_connections_view = (...args) => { this.ensure_connections_view_leaf_location(); return this._open_connections_view_base(...args); }; } ensure_connections_view_leaf_location() { const workspace = this.app?.workspace; if (!workspace) { return; } const desired_location = ConnectionsItemView.default_open_location; const connections_leaf = ConnectionsItemView.get_leaf(workspace); if (!should_relocate_leaf({ workspace, leaf: connections_leaf, desired_location })) { return; } connections_leaf.detach(); } register_connections_view_location_listener() { if (this.connections_view_location_listener || !this.env?.events) return; this.connections_view_location_listener = this.env.events.on("settings:changed", (event) => { if (!event?.path?.includes?.("connections_view_location")) return; this.apply_connections_view_location(); }); } async check_for_updates() { if (await this.is_new_plugin_version(this.manifest.version)) { console.log("opening release notes modal"); try { this.ReleaseNotesView.open(this.app.workspace, this.manifest.version); } catch (error) { console.error("Failed to open ReleaseNotesView", error); } await this.set_last_known_version(this.manifest.version); } setTimeout(this.check_for_update.bind(this), 3e3); setInterval(this.check_for_update.bind(this), 108e5); } async check_for_update() { try { const { json: response } = await requestUrl7({ 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); } } async restart_plugin() { this.env?.unload_main?.(this); await new Promise((resolve) => setTimeout(resolve, 3e3)); window.restart_plugin = async (id) => { await window.app.plugins.disablePlugin(id); await window.app.plugins.enablePlugin(id); }; await window.restart_plugin(this.manifest.id); } get commands() { return { ...super.commands, random_connection: { id: "smart-connections-random", name: "Open: Random note from connections", callback: async () => { await this.open_random_connection(); } }, getting_started: { id: "smart-connections-getting-started", name: "Show: Getting started slideshow", callback: () => { StoryModal.open(this, { title: "Getting Started With Smart Connections", url: "https://smartconnections.app/story/smart-connections-getting-started/?utm_source=sc-op-command" }); } }, insert_connections_codeblock: { id: "insert-connections-codeblock", name: "Insert: Connections codeblock", editorCallback: (editor) => { editor.replaceSelection(build_connections_codeblock()); } }, toggle_footer_connections: { id: "toggle-footer-connections", name: "Toggle: Footer connections", callback: () => { const settings = this.env.connections_lists.settings; settings.footer_connections = !settings.footer_connections; } } }; } async open_random_connection() { const curr_file = this.app.workspace.getActiveFile(); if (!curr_file) { this.env?.events?.emit("connections:open_random_unavailable", { level: "warning", message: "No active file to find connections for.", event_source: "open_random_connection" }); return; } const rand_entity = await get_random_connection(this.env, curr_file.path); if (!rand_entity) { this.env?.events?.emit("connections:open_random_unavailable", { level: "warning", message: `Cannot open random connection for non-embedded source: ${curr_file.path}`, event_source: "open_random_connection" }); return; } this.open_note(rand_entity.item.path); this.env?.events?.emit?.("connections:open_random"); } /** * Attempts to retrieve the CodeMirror 6 EditorView for the active markdown file. * @returns {EditorView|null} */ get_editor_view() { const file = this.app.workspace.getActiveFile(); if (!file) { console.log("Smart Connections: No active file found"); return null; } const markdown_view = this.app.workspace.getActiveFileView(); if (!markdown_view) { console.log("Smart Connections: No active file view found"); return null; } return markdown_view.editor?.cm || null; } toggled_footer_connections() { const view = this.get_editor_view(); if (view && this.env.connections_lists.settings.footer_connections) { this.connections_footer_view?.render_view(); } else { this.connections_footer_view?.remove(); } } async open_note(target_path, event = null) { await open_note(this, target_path, event); } /** * @deprecated extract into utility */ async add_to_gitignore(ignore, message = null) { if (!await this.app.vault.adapter.exists(".gitignore")) return; let gitignore_file = await this.app.vault.adapter.read(".gitignore"); if (gitignore_file.indexOf(ignore) < 0) { await this.app.vault.adapter.append(".gitignore", ` ${message ? "# " + message + "\n" : ""}${ignore}`); console.log("Added to .gitignore: " + ignore); } } }; /* nosourcemap */