mirror of
https://github.com/litruv/Docs-Viewer.git
synced 2026-07-24 02:36:07 +10:00
Add caching + LazyLoading to searches and images
This commit is contained in:
@@ -15,6 +15,10 @@ export class DocumentService {
|
|||||||
this.indexService = indexService;
|
this.indexService = indexService;
|
||||||
/** @type {Promise<marked>} */
|
/** @type {Promise<marked>} */
|
||||||
this.markedPromise = this.initializeMarked();
|
this.markedPromise = this.initializeMarked();
|
||||||
|
/** @type {Map<string, LoadedDocument>} */
|
||||||
|
this.documentCache = new Map();
|
||||||
|
/** @type {number} */
|
||||||
|
this.cacheMaxSize = 20;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -50,8 +54,15 @@ export class DocumentService {
|
|||||||
*/
|
*/
|
||||||
setupRenderer(renderer) {
|
setupRenderer(renderer) {
|
||||||
const originalLink = renderer.link.bind(renderer);
|
const originalLink = renderer.link.bind(renderer);
|
||||||
|
const originalImage = renderer.image.bind(renderer);
|
||||||
|
|
||||||
renderer.code = this.renderCode;
|
renderer.code = this.renderCode;
|
||||||
|
|
||||||
|
renderer.image = (href, title, text) => {
|
||||||
|
const titleAttr = title ? ` title="${title}"` : '';
|
||||||
|
return `<img src="${href}" alt="${text}"${titleAttr} loading="lazy">`;
|
||||||
|
};
|
||||||
|
|
||||||
renderer.link = (href, title, text) => {
|
renderer.link = (href, title, text) => {
|
||||||
const isExternal = href.startsWith('http');
|
const isExternal = href.startsWith('http');
|
||||||
const attrs = isExternal ? ' target="_blank" rel="noopener noreferrer"' : '';
|
const attrs = isExternal ? ' target="_blank" rel="noopener noreferrer"' : '';
|
||||||
@@ -138,6 +149,10 @@ export class DocumentService {
|
|||||||
* @throws {Error} If the document fails to load.
|
* @throws {Error} If the document fails to load.
|
||||||
*/
|
*/
|
||||||
async loadDocument(path) {
|
async loadDocument(path) {
|
||||||
|
if (this.documentCache.has(path)) {
|
||||||
|
return this.documentCache.get(path);
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
const [response, marked] = await Promise.all([
|
const [response, marked] = await Promise.all([
|
||||||
fetch(path),
|
fetch(path),
|
||||||
@@ -163,12 +178,20 @@ export class DocumentService {
|
|||||||
const titleContent = metadata.title || indexDoc?.title || path.split('/').pop().replace('.md', '');
|
const titleContent = metadata.title || indexDoc?.title || path.split('/').pop().replace('.md', '');
|
||||||
processedContent = this.ensureTitle(processedContent, titleContent);
|
processedContent = this.ensureTitle(processedContent, titleContent);
|
||||||
|
|
||||||
return {
|
const result = {
|
||||||
content: processedContent,
|
content: processedContent,
|
||||||
metadata,
|
metadata,
|
||||||
marked,
|
marked,
|
||||||
title: titleContent
|
title: titleContent
|
||||||
};
|
};
|
||||||
|
|
||||||
|
if (this.documentCache.size >= this.cacheMaxSize) {
|
||||||
|
const firstKey = this.documentCache.keys().next().value;
|
||||||
|
this.documentCache.delete(firstKey);
|
||||||
|
}
|
||||||
|
this.documentCache.set(path, result);
|
||||||
|
|
||||||
|
return result;
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
throw new Error('Failed to load document');
|
throw new Error('Failed to load document');
|
||||||
}
|
}
|
||||||
@@ -239,15 +262,30 @@ export class DocumentService {
|
|||||||
const mediaPath = `./docs/images/${filename}`;
|
const mediaPath = `./docs/images/${filename}`;
|
||||||
|
|
||||||
if (filename.toLowerCase().endsWith('.mp4')) {
|
if (filename.toLowerCase().endsWith('.mp4')) {
|
||||||
return `\n<video controls width="100%">
|
return `\n<video controls width="100%" preload="metadata">
|
||||||
<source src="${mediaPath}" type="video/mp4">
|
<source src="${mediaPath}" type="video/mp4">
|
||||||
Your browser does not support the video tag.
|
Your browser does not support the video tag.
|
||||||
</video>\n\n`;
|
</video>\n\n`;
|
||||||
}
|
}
|
||||||
|
|
||||||
return `\n\n\n`;
|
return `\n<img src="${mediaPath}" alt="${filename}" loading="lazy">\n\n`;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the entire document cache.
|
||||||
|
*/
|
||||||
|
clearCache() {
|
||||||
|
this.documentCache.clear();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Invalidates a specific document from the cache.
|
||||||
|
* @param {string} path - The document path to invalidate.
|
||||||
|
*/
|
||||||
|
invalidateDocument(path) {
|
||||||
|
this.documentCache.delete(path);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -15,6 +15,10 @@ export class SearchService {
|
|||||||
this.indexService = indexService;
|
this.indexService = indexService;
|
||||||
/** @type {Array<SearchIndexItem>} */
|
/** @type {Array<SearchIndexItem>} */
|
||||||
this.searchIndex = [];
|
this.searchIndex = [];
|
||||||
|
/** @type {Map<string, Array<SearchResult>>} */
|
||||||
|
this.searchCache = new Map();
|
||||||
|
/** @type {number} */
|
||||||
|
this.cacheMaxSize = 50;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -23,6 +27,7 @@ export class SearchService {
|
|||||||
*/
|
*/
|
||||||
buildSearchIndex(documents) {
|
buildSearchIndex(documents) {
|
||||||
this.searchIndex = [];
|
this.searchIndex = [];
|
||||||
|
this.searchCache.clear();
|
||||||
this.processDocuments(documents);
|
this.processDocuments(documents);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -132,11 +137,23 @@ export class SearchService {
|
|||||||
if (!query) return [];
|
if (!query) return [];
|
||||||
query = query.toLowerCase();
|
query = query.toLowerCase();
|
||||||
|
|
||||||
return this.searchIndex
|
if (this.searchCache.has(query)) {
|
||||||
|
return this.searchCache.get(query);
|
||||||
|
}
|
||||||
|
|
||||||
|
const results = this.searchIndex
|
||||||
.map(item => this.scoreItem(item, query))
|
.map(item => this.scoreItem(item, query))
|
||||||
.filter(item => item.score > 0)
|
.filter(item => item.score > 0)
|
||||||
.sort((a, b) => b.score - a.score)
|
.sort((a, b) => b.score - a.score)
|
||||||
.slice(0, 10);
|
.slice(0, 10);
|
||||||
|
|
||||||
|
if (this.searchCache.size >= this.cacheMaxSize) {
|
||||||
|
const firstKey = this.searchCache.keys().next().value;
|
||||||
|
this.searchCache.delete(firstKey);
|
||||||
|
}
|
||||||
|
this.searchCache.set(query, results);
|
||||||
|
|
||||||
|
return results;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -160,6 +177,13 @@ export class SearchService {
|
|||||||
|
|
||||||
return { ...item, score };
|
return { ...item, score };
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears the search result cache.
|
||||||
|
*/
|
||||||
|
clearCache() {
|
||||||
|
this.searchCache.clear();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
Reference in New Issue
Block a user