1// Mind Map Database Object 2// Provides local and remote storage for Mind Map data 3// (including extended edge metadata and node field bindings). 4// 5// Local: localStorage (key: mindmap-db-${id}) â offline cache only 6// Remote: zim ws-studio (eShellDoc store via shellDocStore) â system of record 7// 8// Usage: 9// const db = createMindMapDb("my-mindmap-id"); 10// db.saveLocal(ndjson); // cache 11// const ndjson = db.loadLocal(); // cache 12// await db.saveApi(ndjson); // persist to zim (write-through to cache) 13// const ndjson = await db.loadApi(); // load from zim (null => fall back to cache) 14 15import { saveDoc, loadDoc, listDocs, listVersions, loadVersion } from "../../common/shellDocStore.js"; 16 17const SHELL_TYPE = "mindmap"; 18const LOCAL_KEY = (id) => `mindmap-db-${id || "default"}`; 19const UPDATE_EVENT = "mindmap-db:updated"; 20 21/** 22 * createMindMapDb(id) 23 * Returns a database object scoped to this mind map ID. 24 * Synchronous methods for local storage; async methods for backend API. 25 * 26 * @param {string} id Mind map identifier (used as storage key). 27 */ 28export function createMindMapDb(id) { 29 const key = LOCAL_KEY(id); 30 31 return { 32 /** The mind map ID this db is scoped to. */ 33 id, 34 35 // ââ Local Storage ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 36 37 /** 38 * Save NDJSON string to localStorage. 39 * @param {string} ndjson Serialised mind map. 40 * @returns {boolean} true on success. 41 */ 42 saveLocal(ndjson) { 43 try { 44 localStorage.setItem(key, ndjson); 45 window.dispatchEvent( 46 new CustomEvent(UPDATE_EVENT, { detail: { id, action: "save" } }) 47 ); 48 return true; 49 } catch (err) { 50 console.error("Mind Map local save failed:", err); 51 return false; 52 } 53 }, 54 55 /** 56 * Load NDJSON string from localStorage. 57 * @returns {string|null} NDJSON data or null if not found. 58 */ 59 loadLocal() { 60 try { 61 return localStorage.getItem(key) || null; 62 } catch { 63 return null; 64 } 65 }, 66 67 /** 68 * Delete saved data from localStorage. 69 */ 70 clearLocal() { 71 try { 72 localStorage.removeItem(key); 73 window.dispatchEvent( 74 new CustomEvent(UPDATE_EVENT, { detail: { id, action: "clear" } }) 75 ); 76 } catch {} 77 }, 78 79 // ââ Backend API ââââââââââââââââââââââââââââââââââââââââââââââââââââââââââââ 80 81 /** 82 * Persist the mind map to the zim store (system of record), mirroring it to 83 * the local cache so an offline reload still works. 84 * @param {string} ndjson Serialised mind map. 85 * @returns {Promise<{docId, savedCount, status}>} 86 */ 87 async saveApi(ndjson, docName) { 88 // The map's NAME is its identity: docId = mindmap:<name>, so saving the same 89 // titled map upserts it and the Open picker lists one row per named map. 90 const name = String(docName || id || "mindmap").trim() || "mindmap"; 91 const result = await saveDoc({ 92 shellType: SHELL_TYPE, 93 instanceId: name, 94 docName: name, 95 body: ndjson, 96 }); 97 try { localStorage.setItem(key, ndjson); } catch {} 98 return result; 99 }, 100 101 /** 102 * List all saved mind maps for the tenant (for the Open-from-server picker). 103 * @returns {Promise<Array<{docId, docName, updatedAt, lineCount}>>} 104 */ 105 async listApi() { 106 // Dedupe by docId defensively: the picker must show one entry per distinct 107 // map even if the store ever returns repeated/duplicate rows. Keep the 108 // newest (max updatedAt) per docId. 109 const rows = await listDocs({ shellType: SHELL_TYPE }); 110 const byId = new Map(); 111 for (const d of rows) { 112 const id = d && d.docId; 113 if (!id) continue; 114 const prev = byId.get(id); 115 if (!prev || String(d.updatedAt || "") > String(prev.updatedAt || "")) { 116 byId.set(id, d); 117 } 118 } 119 return [...byId.values()]; 120 }, 121 122 /** 123 * List all versions (latest + archives) of a named mind map. 124 * @param {string} docName The map's name (its identity == clean base id). 125 * @returns {Promise<Array<{docId,version,updatedAt,lineCount,isLatest}>>} 126 */ 127 async listVersions(docName) { 128 const name = String(docName || id || "mindmap").trim() || "mindmap"; 129 return listVersions({ shellType: SHELL_TYPE, instanceId: name }); 130 }, 131 132 /** 133 * Load a specific archived version by its full version docId ("<base>::v<n>"). 134 * @returns {Promise<string|null>} NDJSON string or null if empty. 135 */ 136 async loadVersion(versionDocId) { 137 const text = await loadVersion({ docId: versionDocId }); 138 return (typeof text === "string" && text.trim()) ? text : null; 139 }, 140 141 /** 142 * Load a specific saved mind map by its docId (chosen in the Open picker). 143 * @returns {Promise<string|null>} NDJSON string or null if empty. 144 */ 145 async loadApiById(docId) { 146 const text = await loadDoc({ shellType: SHELL_TYPE, docId }); 147 if (typeof text === "string" && text.trim()) { 148 try { localStorage.setItem(key, text); } catch {} 149 return text; 150 } 151 return null; 152 }, 153 154 /** 155 * Load the mind map from the zim store. Returns null when nothing is saved 156 * server-side, so the caller can fall back to loadLocal(). 157 * @returns {Promise<string|null>} NDJSON string or null. 158 */ 159 async loadApi() { 160 const text = await loadDoc({ shellType: SHELL_TYPE, instanceId: id || "default" }); 161 if (typeof text === "string" && text.trim()) { 162 try { localStorage.setItem(key, text); } catch {} 163 return text; 164 } 165 return null; 166 }, 167 168 // ââ Change Subscriptions âââââââââââââââââââââââââââââââââââââââââââââââââââ 169 170 /** 171 * Subscribe to local DB changes (same-tab via CustomEvent). 172 * @param {function} handler Called with { id, action } on any change. 173 * @returns {function} Cleanup function to unsubscribe. 174 */ 175 onChange(handler) { 176 const listener = (e) => {
177 if (e.detail?.id === id) handler(e.detail); 178 }; 179 window.addEventListener(UPDATE_EVENT, listener); 180 return () => window.removeEventListener(UPDATE_EVENT, listener); 181 }, 182 }; 183}
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.