(function () { const DB_NAME = "md-hub-cache"; const DB_VERSION = 3; const STORE_DOCUMENTS = "documents"; const STORE_CONTENT = "content"; const STORE_PAGES = "pages"; const STORE_PENDING_EDITS = "pending_edits"; function openDB() { return new Promise(function (resolve, reject) { const request = indexedDB.open(DB_NAME, DB_VERSION); request.onerror = function () { reject(request.error); }; request.onsuccess = function () { resolve(request.result); }; request.onupgradeneeded = function (event) { const db = event.target.result; if (!db.objectStoreNames.contains(STORE_DOCUMENTS)) { db.createObjectStore(STORE_DOCUMENTS, { keyPath: "path" }); } if (!db.objectStoreNames.contains(STORE_CONTENT)) { db.createObjectStore(STORE_CONTENT, { keyPath: "path" }); } if (!db.objectStoreNames.contains(STORE_PAGES)) { db.createObjectStore(STORE_PAGES, { keyPath: "path" }); } if (!db.objectStoreNames.contains(STORE_PENDING_EDITS)) { db.createObjectStore(STORE_PENDING_EDITS, { keyPath: "path" }); } }; }); } function normalizePath(path) { if (!path) return "/"; if (path === "/") return path; return path.replace(/\/$/, "") || "/"; } function cacheDocuments(documents) { return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_DOCUMENTS, "readwrite"); const store = tx.objectStore(STORE_DOCUMENTS); documents.forEach(function (doc) { store.put(doc); }); tx.oncomplete = function () { resolve(); }; tx.onerror = function () { reject(tx.error); }; }); }); } function cacheContent(path, html, title) { return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_CONTENT, "readwrite"); const store = tx.objectStore(STORE_CONTENT); store.put({ path: path, html: html, title: title, cachedAt: Date.now() }); tx.oncomplete = function () { resolve(); }; tx.onerror = function () { reject(tx.error); }; }); }); } function getCachedDocuments() { return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_DOCUMENTS, "readonly"); const store = tx.objectStore(STORE_DOCUMENTS); const request = store.getAll(); request.onsuccess = function () { resolve(request.result); }; request.onerror = function () { reject(request.error); }; }); }); } function getCachedContent(path) { return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_CONTENT, "readonly"); const store = tx.objectStore(STORE_CONTENT); const request = store.get(path); request.onsuccess = function () { resolve(request.result); }; request.onerror = function () { reject(request.error); }; }); }); } function cachePage(path, html, title) { const normalizedPath = normalizePath(path); return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_PAGES, "readwrite"); const store = tx.objectStore(STORE_PAGES); store.put({ path: normalizedPath, html: html, title: title, cachedAt: Date.now(), }); tx.oncomplete = function () { resolve(); }; tx.onerror = function () { reject(tx.error); }; }); }); } function getCachedPage(path) { const normalizedPath = normalizePath(path); return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_PAGES, "readonly"); const store = tx.objectStore(STORE_PAGES); const request = store.get(normalizedPath); request.onsuccess = function () { resolve(request.result); }; request.onerror = function () { reject(request.error); }; }); }); } function putPendingEdit(path, content, baseHash) { const normalizedPath = normalizePath(path); return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_PENDING_EDITS, "readwrite"); const store = tx.objectStore(STORE_PENDING_EDITS); store.put({ path: normalizedPath, content: content, baseHash: baseHash || "", conflict: null, retries: 0, updatedAt: Date.now(), }); tx.oncomplete = function () { resolve(); }; tx.onerror = function () { reject(tx.error); }; }); }); } function markPendingEditConflict(path, conflict) { const normalizedPath = normalizePath(path); return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_PENDING_EDITS, "readwrite"); const store = tx.objectStore(STORE_PENDING_EDITS); const request = store.get(normalizedPath); request.onsuccess = function () { const record = request.result; if (!record) { resolve(); return; } record.conflict = conflict; record.updatedAt = Date.now(); store.put(record); }; tx.oncomplete = function () { resolve(); }; tx.onerror = function () { reject(tx.error); }; }); }); } function getPendingEdits() { return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_PENDING_EDITS, "readonly"); const store = tx.objectStore(STORE_PENDING_EDITS); const request = store.getAll(); request.onsuccess = function () { resolve(request.result || []); }; request.onerror = function () { reject(request.error); }; }); }); } function getPendingEdit(path) { const normalizedPath = normalizePath(path); return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_PENDING_EDITS, "readonly"); const store = tx.objectStore(STORE_PENDING_EDITS); const request = store.get(normalizedPath); request.onsuccess = function () { resolve(request.result); }; request.onerror = function () { reject(request.error); }; }); }); } function deletePendingEdit(path) { const normalizedPath = normalizePath(path); return openDB().then(function (db) { return new Promise(function (resolve, reject) { const tx = db.transaction(STORE_PENDING_EDITS, "readwrite"); const store = tx.objectStore(STORE_PENDING_EDITS); store.delete(normalizedPath); tx.oncomplete = function () { resolve(); }; tx.onerror = function () { reject(tx.error); }; }); }); } window.MDHubCache = { cacheDocuments: cacheDocuments, cacheContent: cacheContent, cachePage: cachePage, putPendingEdit: putPendingEdit, markPendingEditConflict: markPendingEditConflict, getPendingEdits: getPendingEdits, getPendingEdit: getPendingEdit, deletePendingEdit: deletePendingEdit, getCachedDocuments: getCachedDocuments, getCachedContent: getCachedContent, getCachedPage: getCachedPage, normalizePath: normalizePath, }; })();