diff --git a/jest.config.js b/jest.config.js index b39456117..c34b34324 100644 --- a/jest.config.js +++ b/jest.config.js @@ -1,4 +1,5 @@ import { testConfig } from './server.configs.js'; +import structuredClone from '@ungap/structured-clone'; const { hostname, port } = testConfig; const TEST_HOST = `http://${hostname}:${port}`; @@ -31,6 +32,10 @@ export default { displayName: 'integration', ...sharedConfig, testMatch: ['/test/integration/*.test.js'], + setupFiles: ['fake-indexeddb/auto'], + globals: { + structuredClone: structuredClone.default, + }, }, ], }; diff --git a/package-lock.json b/package-lock.json index 08714a9a2..7b8e42d6a 100644 --- a/package-lock.json +++ b/package-lock.json @@ -10,6 +10,7 @@ "hasInstallScript": true, "license": "MIT", "dependencies": { + "dexie": "^4.0.8", "medium-zoom": "^1.1.0", "opencollective-postinstall": "^2.0.2", "prismjs": "^1.29.0", @@ -26,10 +27,13 @@ "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-terser": "^0.4.3", "@types/eslint": "^8.40.2", + "@types/ungap__structured-clone": "^1.2.0", + "@ungap/structured-clone": "^1.2.0", "axios": "^1.5.0", "browser-sync": "^3.0.2", "common-tags": "^1.8.0", "conventional-changelog-cli": "^3.0.0", + "core-js": "^3.37.1", "cross-env": "^7.0.3", "cssnano": "^7.0.1", "eslint": "^9.3.0", @@ -37,6 +41,7 @@ "eslint-plugin-jest": "^28.5.0", "eslint-plugin-playwright": "^1.6.1", "eslint-plugin-prettier": "^5.1.3", + "fake-indexeddb": "^6.0.0", "glob": "^10.3.15", "globals": "^15.3.0", "husky": "^9.0.11", @@ -3475,6 +3480,12 @@ "integrity": "sha512-/Ad8+nIOV7Rl++6f1BdKxFSMgmoqEoYbHRpPcx3JEfv8VRsQe9Z4mCXeJBzxs7mbHY/XOZZuXlRNfhpVPbs6ZA==", "dev": true }, + "node_modules/@types/ungap__structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@types/ungap__structured-clone/-/ungap__structured-clone-1.2.0.tgz", + "integrity": "sha512-ZoaihZNLeZSxESbk9PUAPZOlSpcKx81I1+4emtULDVmBLkYutTcMlCj2K9VNlf9EWODxdO6gkAqEaLorXwZQVA==", + "dev": true + }, "node_modules/@types/yargs": { "version": "17.0.32", "resolved": "https://registry.npmjs.org/@types/yargs/-/yargs-17.0.32.tgz", @@ -3633,6 +3644,12 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==", + "dev": true + }, "node_modules/@vue/compiler-core": { "version": "3.4.27", "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.4.27.tgz", @@ -5257,6 +5274,17 @@ "node": ">= 0.6" } }, + "node_modules/core-js": { + "version": "3.37.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.37.1.tgz", + "integrity": "sha512-Xn6qmxrQZyB0FFY8E3bgRXei3lWDJHhvI+u0q9TKIYM49G8pAr0FgnnrFRAmsbptZL1yxRADVXn+x5AGsbBfyw==", + "dev": true, + "hasInstallScript": true, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/core-js" + } + }, "node_modules/core-js-compat": { "version": "3.37.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.37.1.tgz", @@ -5873,6 +5901,11 @@ "node": ">= 0.8.0" } }, + "node_modules/dexie": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/dexie/-/dexie-4.0.8.tgz", + "integrity": "sha512-1G6cJevS17KMDK847V3OHvK2zei899GwpDiqfEXHP1ASvme6eWJmAp9AU4s1son2TeGkWmC0g3y8ezOBPnalgQ==" + }, "node_modules/diff-sequences": { "version": "29.6.3", "resolved": "https://registry.npmjs.org/diff-sequences/-/diff-sequences-29.6.3.tgz", @@ -6933,6 +6966,15 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/fake-indexeddb": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/fake-indexeddb/-/fake-indexeddb-6.0.0.tgz", + "integrity": "sha512-YEboHE5VfopUclOck7LncgIqskAqnv4q0EWbYCaxKKjAvO93c+TJIaBuGy8CBFdbg9nKdpN3AuPRwVBJ4k7NrQ==", + "dev": true, + "engines": { + "node": ">=18" + } + }, "node_modules/fast-deep-equal": { "version": "3.1.3", "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", diff --git a/package.json b/package.json index cc0bd26da..acf151c18 100644 --- a/package.json +++ b/package.json @@ -36,6 +36,7 @@ "*.js": "eslint --fix" }, "dependencies": { + "dexie": "^4.0.8", "medium-zoom": "^1.1.0", "opencollective-postinstall": "^2.0.2", "prismjs": "^1.29.0", @@ -52,10 +53,13 @@ "@rollup/plugin-replace": "^5.0.2", "@rollup/plugin-terser": "^0.4.3", "@types/eslint": "^8.40.2", + "@types/ungap__structured-clone": "^1.2.0", + "@ungap/structured-clone": "^1.2.0", "axios": "^1.5.0", "browser-sync": "^3.0.2", "common-tags": "^1.8.0", "conventional-changelog-cli": "^3.0.0", + "core-js": "^3.37.1", "cross-env": "^7.0.3", "cssnano": "^7.0.1", "eslint": "^9.3.0", @@ -63,6 +67,7 @@ "eslint-plugin-jest": "^28.5.0", "eslint-plugin-playwright": "^1.6.1", "eslint-plugin-prettier": "^5.1.3", + "fake-indexeddb": "^6.0.0", "glob": "^10.3.15", "globals": "^15.3.0", "husky": "^9.0.11", diff --git a/src/plugins/search/search.js b/src/plugins/search/search.js index 95aa83a8b..6e8f4e96f 100644 --- a/src/plugins/search/search.js +++ b/src/plugins/search/search.js @@ -2,8 +2,24 @@ import { getAndRemoveConfig, getAndRemoveDocisfyIgnoreConfig, } from '../../core/render/utils.js'; +import Dexie from 'dexie'; -let INDEXS = {}; +let INDEXES = {}; + +const db = new Dexie('DocsifySearchDB'); +db.version(1).stores({ + search: 'key, value', +}); + +async function saveData(maxAge, expireKey, indexKey) { + await db.search.put({ key: expireKey, value: Date.now() + maxAge }); + await db.search.put({ key: indexKey, value: JSON.stringify(INDEXES) }); +} + +async function getData(key) { + const item = await db.search.get(key); + return item ? item.value : null; +} const LOCAL_STORAGE = { EXPIRE_KEY: 'docsify.search.expires', @@ -73,11 +89,6 @@ function getListData(token) { return token.text; } -function saveData(maxAge, expireKey, indexKey) { - localStorage.setItem(expireKey, Date.now() + maxAge); - localStorage.setItem(indexKey, JSON.stringify(INDEXS)); -} - export function genIndex(path, content = '', router, depth) { const tokens = window.marked.lexer(content); const slugify = window.Docsify.slugify; @@ -149,10 +160,10 @@ export function ignoreDiacriticalMarks(keyword) { export function search(query) { const matchingResults = []; let data = []; - Object.keys(INDEXS).forEach(key => { + Object.keys(INDEXES).forEach(key => { data = [ ...data, - ...Object.keys(INDEXS[key]).map(page => INDEXS[key][page]), + ...Object.keys(INDEXES[key]).map(page => INDEXES[key][page]), ]; }); @@ -240,7 +251,7 @@ export function search(query) { return matchingResults.sort((r1, r2) => r2.score - r1.score); } -export function init(config, vm) { +export async function init(config, vm) { const isAuto = config.paths === 'auto'; const paths = isAuto ? getAllPaths(vm.router) : config.paths; @@ -274,12 +285,12 @@ export function init(config, vm) { const expireKey = resolveExpireKey(config.namespace) + namespaceSuffix; const indexKey = resolveIndexKey(config.namespace) + namespaceSuffix; - const isExpired = localStorage.getItem(expireKey) < Date.now(); + const isExpired = (await getData(expireKey)) < Date.now(); - INDEXS = JSON.parse(localStorage.getItem(indexKey)); + INDEXES = JSON.parse(await getData(indexKey)); if (isExpired) { - INDEXS = {}; + INDEXES = {}; } else if (!isAuto) { return; } @@ -288,14 +299,16 @@ export function init(config, vm) { let count = 0; paths.forEach(path => { - if (INDEXS[path]) { + if (INDEXES[path]) { return count++; } Docsify.get(vm.router.getFile(path), false, vm.config.requestHeaders).then( - result => { - INDEXS[path] = genIndex(path, result, vm.router, config.depth); - len === ++count && saveData(config.maxAge, expireKey, indexKey); + async result => { + INDEXES[path] = genIndex(path, result, vm.router, config.depth); + if (len === ++count) { + await saveData(config.maxAge, expireKey, indexKey); + } }, ); }); diff --git a/test/integration/example.test.js b/test/integration/example.test.js index baadd978f..79fe799bb 100644 --- a/test/integration/example.test.js +++ b/test/integration/example.test.js @@ -1,5 +1,6 @@ import { waitForFunction, waitForText } from '../helpers/wait-for.js'; import docsifyInit from '../helpers/docsify-init.js'; +import 'core-js/stable/structured-clone'; describe('Creating a Docsify site (integration tests in Jest)', function () { test('Docsify /docs/ site using docsifyInit()', async () => { @@ -20,7 +21,6 @@ describe('Creating a Docsify site (integration tests in Jest)', function () { const docsifyInitConfig = { config: { name: 'Docsify Name', - themeColor: 'red', }, markdown: { coverpage: ` @@ -76,7 +76,6 @@ describe('Creating a Docsify site (integration tests in Jest)', function () { // Verify config options expect(typeof window.$docsify).toBe('object'); - expect(window.$docsify).toHaveProperty('themeColor', 'red'); expect(document.querySelector('.app-name').textContent).toContain( 'Docsify Name', );