From e4ccc5cf50883b8ae3d468e19cea356c88c76a13 Mon Sep 17 00:00:00 2001 From: Xubai Wang Date: Tue, 22 Feb 2022 23:20:20 +0800 Subject: [PATCH] feat: add browser cache for data fix search name --- static/declaration-data.js | 129 ++++++++++++++++++++++++++++++++----- static/search.js | 6 +- 2 files changed, 117 insertions(+), 18 deletions(-) diff --git a/static/declaration-data.js b/static/declaration-data.js index 0d07024..0cdc869 100644 --- a/static/declaration-data.js +++ b/static/declaration-data.js @@ -6,6 +6,9 @@ import { SITE_ROOT } from "./site-root.js"; +const CACHE_DB_NAME = "declaration-data"; +const CACHE_DB_VERSION = 1; + /** * The DeclarationDataCenter is used for declaration searching. * @@ -40,26 +43,46 @@ export class DeclarationDataCenter { */ static async init() { if (!DeclarationDataCenter.singleton) { + const timestampUrl = new URL( + `${SITE_ROOT}declaration-data.timestamp`, + window.location + ); const dataUrl = new URL( `${SITE_ROOT}declaration-data.bmp`, window.location ); - const response = await fetch(dataUrl); - const json = await response.json(); - // the data is a map of name (original case) to declaration data. - const data = new Map( - json.map(({ name, doc, link, source: sourceLink }) => [ - name, - { + + const timestampRes = await fetch(timestampUrl); + const timestamp = await timestampRes.text(); + + // try to use cache first + let store = await getDeclarationStore(); + const data = await fetchCachedDeclarationData(store, timestamp); + if (data) { + // if data is defined, use the cached one. + DeclarationDataCenter.singleton = new DeclarationDataCenter(data); + } else { + // undefined. then fetch the data from the server. + const dataRes = await fetch(dataUrl); + const dataJson = await dataRes.json(); + // the data is a map of name (original case) to declaration data. + const data = new Map( + dataJson.map(({ name, doc, link, source: sourceLink }) => [ name, - lowerName: name.toLowerCase(), - lowerDoc: doc.toLowerCase(), - link, - sourceLink, - }, - ]) - ); - DeclarationDataCenter.singleton = new DeclarationDataCenter(data); + { + name, + lowerName: name.toLowerCase(), + lowerDoc: doc.toLowerCase(), + link, + sourceLink, + }, + ]) + ); + // get store again in case it's inactive + let store = await getDeclarationStore(); + await cacheDeclarationData(store, timestamp, data); + DeclarationDataCenter.singleton = new DeclarationDataCenter(data); + } } return DeclarationDataCenter.singleton; } @@ -136,3 +159,79 @@ function getMatches(declarations, pattern, maxResults = 30) { } return results.sort(({ err: a }, { err: b }) => a - b).slice(0, maxResults); } + +// TODO: refactor the indexedDB part to be more robust + +/** + * Get the indexedDB database, automatically initialized. + * @returns {Promise} + */ +function getDeclarationStore() { + return new Promise((resolve, reject) => { + const request = indexedDB.open(CACHE_DB_NAME, CACHE_DB_VERSION); + request.onerror = function (event) { + reject( + new Error( + `fail to open indexedDB ${CACHE_DB_NAME} of version ${CACHE_DB_VERSION}` + ) + ); + }; + request.onupgradeneeded = function (event) { + let db = event.target.result; + // We only need to store one object, so no key path or increment is needed. + let objectStore = db.createObjectStore("declaration"); + objectStore.transaction.oncomplete = function (event) { + resolve(objectStore); + }; + }; + request.onsuccess = function (event) { + resolve( + event.target.result + .transaction("declaration", "readwrite") + .objectStore("declaration") + ); + }; + }); +} + +/** + * Store data in indexedDB object store. + * @param {IDBObjectStore} store + * @param {string} timestamp + * @param {Map} data + */ +function cacheDeclarationData(store, timestamp, data) { + return new Promise((resolve, reject) => { + let clearRequest = store.clear(); + clearRequest.onsuccess = function (event) { + let addRequest = store.add(data, timestamp); + addRequest.onsuccess = function (event) { + resolve(); + }; + addRequest.onerror = function (event) { + reject(new Error(`fail to store declaration data`)); + }; + }; + clearRequest.onerror = function (event) { + reject(new Error("fail to clear object store")); + }; + }); +} + +/** + * Retrieve data from indexedDB database. + * @param {IDBObjectStore} store + * @param {string} timestamp + * @returns {Promise|undefined>} + */ +async function fetchCachedDeclarationData(store, timestamp) { + return new Promise((resolve, reject) => { + let transactionRequest = store.get(timestamp); + transactionRequest.onsuccess = function (event) { + resolve(event.result); + }; + transactionRequest.onerror = function (event) { + reject(new Error(`fail to store declaration data`)); + }; + }); +} diff --git a/static/search.js b/static/search.js index a6a72ea..d5e2fb6 100644 --- a/static/search.js +++ b/static/search.js @@ -94,10 +94,10 @@ SEARCH_INPUT.addEventListener("input", async (ev) => { // update search results removeAllChildren(sr); - for (const { decl, link } of result) { + for (const { name, link } of result) { const d = sr.appendChild(document.createElement("a")); - d.innerText = decl; - d.title = decl; + d.innerText = name; + d.title = name; d.href = link; } sr.setAttribute("state", "done");