feat: rebuild search index

This commit is contained in:
2026-02-11 09:43:13 +01:00
parent 0b5efbb5e1
commit c7827a2d77
6 changed files with 84 additions and 0 deletions

View File

@@ -1015,6 +1015,62 @@ export class PostEngine extends EventEmitter {
console.log(`Rebuilt FTS index for ${allPosts.length} posts`);
}
/**
* Reindex all text for full-text search.
* Runs as a background task with progress updates.
* Call this when search algorithms change or to fix search issues.
*/
async reindexText(): Promise<void> {
const task: Task<void> = {
id: uuidv4(),
name: 'Reindex search text',
execute: async (onProgress) => {
const client = getDatabase().getLocalClient();
if (!client) {
throw new Error('Database client not available');
}
onProgress(0, 'Clearing existing search index...');
// Clear the entire FTS table
await client.execute('DELETE FROM posts_fts');
onProgress(5, 'Loading posts...');
const allPosts = await this.getAllPostsUnpaginated();
const total = allPosts.length;
if (total === 0) {
onProgress(100, 'No posts to index');
return;
}
onProgress(10, `Indexing ${total} posts...`);
for (let i = 0; i < allPosts.length; i++) {
const post = allPosts[i];
await this.updateFTSIndex(post);
// Update progress (10% to 100%)
const progress = 10 + Math.round((i + 1) / total * 90);
if (i % 10 === 0 || i === allPosts.length - 1) {
onProgress(progress, `Indexed ${i + 1} of ${total} posts`);
}
// Yield to event loop periodically
if (i % 20 === 0) {
await new Promise(resolve => setImmediate(resolve));
}
}
onProgress(100, `Reindexed ${total} posts`);
console.log(`Reindexed search text for ${total} posts`);
},
};
await taskManager.runTask(task);
}
async rebuildDatabaseFromFiles(): Promise<void> {
const postsBaseDir = this.getPostsBaseDir();
const task: Task<void> = {

View File

@@ -214,6 +214,19 @@ export function registerIpcHandlers(): void {
return engine.rebuildAllPostLinks();
});
ipcMain.handle('posts:reindexText', async () => {
const projectEngine = getProjectEngine();
const project = await projectEngine.getActiveProject();
const engine = getPostEngine();
if (project) {
engine.setProjectContext(project.id);
}
// Fire and forget - let it run as a background task
engine.reindexText().catch(err => {
console.error('Text reindex failed:', err);
});
});
// ============ Media Handlers ============
ipcMain.handle('media:import', async (_, sourcePath: string, metadata?: Partial<MediaData>) => {

View File

@@ -237,6 +237,12 @@ function createApplicationMenu(): Menu {
mainWindow?.webContents.send('menu:rebuildDatabase');
},
},
{
label: 'Reindex Search Text',
click: () => {
mainWindow?.webContents.send('menu:reindexText');
},
},
],
},
{

View File

@@ -28,6 +28,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
discard: (id: string) => ipcRenderer.invoke('posts:discard', id),
hasPublishedVersion: (id: string) => ipcRenderer.invoke('posts:hasPublishedVersion', id),
rebuildFromFiles: () => ipcRenderer.invoke('posts:rebuildFromFiles'),
reindexText: () => ipcRenderer.invoke('posts:reindexText'),
search: (query: string) => ipcRenderer.invoke('posts:search', query),
filter: (filter: unknown) => ipcRenderer.invoke('posts:filter', filter),
getTags: () => ipcRenderer.invoke('posts:getTags'),