feat: rebuild search index
This commit is contained in:
@@ -1015,6 +1015,62 @@ export class PostEngine extends EventEmitter {
|
|||||||
console.log(`Rebuilt FTS index for ${allPosts.length} posts`);
|
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> {
|
async rebuildDatabaseFromFiles(): Promise<void> {
|
||||||
const postsBaseDir = this.getPostsBaseDir();
|
const postsBaseDir = this.getPostsBaseDir();
|
||||||
const task: Task<void> = {
|
const task: Task<void> = {
|
||||||
|
|||||||
@@ -214,6 +214,19 @@ export function registerIpcHandlers(): void {
|
|||||||
return engine.rebuildAllPostLinks();
|
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 ============
|
// ============ Media Handlers ============
|
||||||
|
|
||||||
ipcMain.handle('media:import', async (_, sourcePath: string, metadata?: Partial<MediaData>) => {
|
ipcMain.handle('media:import', async (_, sourcePath: string, metadata?: Partial<MediaData>) => {
|
||||||
|
|||||||
@@ -237,6 +237,12 @@ function createApplicationMenu(): Menu {
|
|||||||
mainWindow?.webContents.send('menu:rebuildDatabase');
|
mainWindow?.webContents.send('menu:rebuildDatabase');
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
label: 'Reindex Search Text',
|
||||||
|
click: () => {
|
||||||
|
mainWindow?.webContents.send('menu:reindexText');
|
||||||
|
},
|
||||||
|
},
|
||||||
],
|
],
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ contextBridge.exposeInMainWorld('electronAPI', {
|
|||||||
discard: (id: string) => ipcRenderer.invoke('posts:discard', id),
|
discard: (id: string) => ipcRenderer.invoke('posts:discard', id),
|
||||||
hasPublishedVersion: (id: string) => ipcRenderer.invoke('posts:hasPublishedVersion', id),
|
hasPublishedVersion: (id: string) => ipcRenderer.invoke('posts:hasPublishedVersion', id),
|
||||||
rebuildFromFiles: () => ipcRenderer.invoke('posts:rebuildFromFiles'),
|
rebuildFromFiles: () => ipcRenderer.invoke('posts:rebuildFromFiles'),
|
||||||
|
reindexText: () => ipcRenderer.invoke('posts:reindexText'),
|
||||||
search: (query: string) => ipcRenderer.invoke('posts:search', query),
|
search: (query: string) => ipcRenderer.invoke('posts:search', query),
|
||||||
filter: (filter: unknown) => ipcRenderer.invoke('posts:filter', filter),
|
filter: (filter: unknown) => ipcRenderer.invoke('posts:filter', filter),
|
||||||
getTags: () => ipcRenderer.invoke('posts:getTags'),
|
getTags: () => ipcRenderer.invoke('posts:getTags'),
|
||||||
|
|||||||
@@ -302,6 +302,13 @@ const App: React.FC = () => {
|
|||||||
}) || (() => {})
|
}) || (() => {})
|
||||||
);
|
);
|
||||||
|
|
||||||
|
unsubscribers.push(
|
||||||
|
window.electronAPI?.on('menu:reindexText', () => {
|
||||||
|
// Fire and forget - runs as a background task
|
||||||
|
window.electronAPI?.posts.reindexText();
|
||||||
|
}) || (() => {})
|
||||||
|
);
|
||||||
|
|
||||||
return () => {
|
return () => {
|
||||||
unsubscribers.forEach(unsub => unsub());
|
unsubscribers.forEach(unsub => unsub());
|
||||||
};
|
};
|
||||||
|
|||||||
1
src/renderer/types/electron.d.ts
vendored
1
src/renderer/types/electron.d.ts
vendored
@@ -149,6 +149,7 @@ export interface ElectronAPI {
|
|||||||
discard: (id: string) => Promise<PostData | null>;
|
discard: (id: string) => Promise<PostData | null>;
|
||||||
hasPublishedVersion: (id: string) => Promise<boolean>;
|
hasPublishedVersion: (id: string) => Promise<boolean>;
|
||||||
rebuildFromFiles: () => Promise<void>;
|
rebuildFromFiles: () => Promise<void>;
|
||||||
|
reindexText: () => Promise<void>;
|
||||||
search: (query: string) => Promise<SearchResult[]>;
|
search: (query: string) => Promise<SearchResult[]>;
|
||||||
filter: (filter: PostFilter) => Promise<PostData[]>;
|
filter: (filter: PostFilter) => Promise<PostData[]>;
|
||||||
getTags: () => Promise<string[]>;
|
getTags: () => Promise<string[]>;
|
||||||
|
|||||||
Reference in New Issue
Block a user