feat: listen for content-index-updated to incrementally index new entries

Subscribe to the content-index-updated CustomEvent and incrementally add
newly-patched entries to the FlexSearch index. The existing indexInitialized
memoization is preserved -- no full rebuild, just additive inserts via
index.addAsync(), plus updates to idDataMap, contentData, and allTags.

This enables runtime extension of the search index by other plugins. The
primary consumer is @quartz-community/encrypted-pages, which patches the
resolved fetchData object with decrypted entries from its shadow content
index and dispatches content-index-updated so users can find and open
decrypted pages via search within the same browser session.

Non-fatal: the listener swallows errors from the in-place fetchData read
and continues with whatever content-index data was loaded originally.
This commit is contained in:
saberzero1 2026-04-11 13:49:38 +02:00
parent 1a6b49d662
commit 6940ceb2d6
No known key found for this signature in database
6 changed files with 784 additions and 18 deletions

View file

@ -9,4 +9,6 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Added
- Listen for `content-index-updated` `CustomEvent` dispatches and incrementally add the new entries to the FlexSearch index. This enables runtime extension of the search index by other plugins — in particular `@quartz-community/encrypted-pages`, which patches the in-memory content index with newly-decrypted unlisted pages and dispatches this event so the user can find and open those pages via search within the same browser session.
- Initial Quartz community plugin template.

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

371
dist/index.js vendored

File diff suppressed because one or more lines are too long

2
dist/index.js.map vendored

File diff suppressed because one or more lines are too long

View file

@ -820,6 +820,60 @@ async function initIndex() {
indexInitialized = true;
}
async function addContentIndexEntries(patch: Record<string, Item>): Promise<number> {
if (!indexInitialized || !contentData) return 0;
const tagSet = new Set<string>(allTags);
let added = 0;
for (const slug of Object.keys(patch)) {
if (contentData[slug]) continue;
const fileData = patch[slug];
if (!fileData) continue;
const id = idDataMap.length;
idDataMap[id] = slug;
contentData[slug] = fileData;
for (const tag of fileData.tags || []) tagSet.add(tag);
await index.addAsync(id, {
id,
slug,
title: fileData.title || "",
content: fileData.content || "",
tags: fileData.tags || [],
});
added++;
}
allTags = [...tagSet].sort();
return added;
}
document.addEventListener("content-index-updated", (event) => {
const detail = (event as CustomEvent).detail as { slugs?: string[] } | undefined;
if (!indexInitialized || !contentData) return;
const slugs = detail?.slugs;
if (!Array.isArray(slugs) || slugs.length === 0) return;
(async () => {
try {
const base = (await fetchData) as unknown as Record<string, unknown>;
if (!base || typeof base !== "object") return;
const root =
(base as Record<string, unknown>).content &&
typeof (base as Record<string, unknown>).content === "object"
? ((base as Record<string, unknown>).content as Record<string, unknown>)
: (base as Record<string, unknown>);
const patch: Record<string, Item> = {};
for (const slug of slugs) {
const entry = root[slug];
if (entry && typeof entry === "object") {
patch[slug] = entry as Item;
}
}
await addContentIndexEntries(patch);
} catch {
// non-fatal: shadow index patch failed
}
})();
});
function scrollToSearchTerm() {
const term = sessionStorage.getItem("search-term");
if (!term) return;