287 lines
8.5 KiB
JavaScript
287 lines
8.5 KiB
JavaScript
(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";
|
|
const CACHE_ENTRY_LIMIT = 100;
|
|
|
|
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 pruneStore(store, limit) {
|
|
const request = store.getAll();
|
|
request.onsuccess = function () {
|
|
const records = request.result || [];
|
|
if (records.length <= limit) {
|
|
return;
|
|
}
|
|
|
|
records
|
|
.sort(function (a, b) {
|
|
return (a.cachedAt || 0) - (b.cachedAt || 0);
|
|
})
|
|
.slice(0, records.length - limit)
|
|
.forEach(function (record) {
|
|
if (record && record.path) {
|
|
store.delete(record.path);
|
|
}
|
|
});
|
|
};
|
|
}
|
|
|
|
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() });
|
|
pruneStore(store, CACHE_ENTRY_LIMIT);
|
|
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(),
|
|
});
|
|
pruneStore(store, CACHE_ENTRY_LIMIT);
|
|
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,
|
|
};
|
|
})();
|