mirror of
https://github.com/khoaliber/khoj.git
synced 2026-03-09 13:25:11 +00:00
Enable excluding folders to sync from obsidian plugin settings (#1235)
- Add excludeFolders field to KhojSetting interface - Rename 'Sync Folders' to 'Include Folders' for clarity - Add 'Exclude Folders' UI section with folder picker - Filter out excluded folders during content sync - Show file counts when syncing (X of Y files) - Prevent excluding root folder This allows users to exclude specific directories (e.g., Inbox, Highlights) from being indexed, while the existing Include Folders acts as a whitelist. --------- Co-authored-by: Debanjum <debanjum@gmail.com>
This commit is contained in:
@@ -36,6 +36,7 @@ export interface KhojSetting {
|
|||||||
syncFileType: SyncFileTypes;
|
syncFileType: SyncFileTypes;
|
||||||
userInfo: UserInfo | null;
|
userInfo: UserInfo | null;
|
||||||
syncFolders: string[];
|
syncFolders: string[];
|
||||||
|
excludeFolders: string[];
|
||||||
syncInterval: number;
|
syncInterval: number;
|
||||||
autoVoiceResponse: boolean;
|
autoVoiceResponse: boolean;
|
||||||
fileAccessMode: 'none' | 'read' | 'write';
|
fileAccessMode: 'none' | 'read' | 'write';
|
||||||
@@ -57,6 +58,7 @@ export const DEFAULT_SETTINGS: KhojSetting = {
|
|||||||
},
|
},
|
||||||
userInfo: null,
|
userInfo: null,
|
||||||
syncFolders: [],
|
syncFolders: [],
|
||||||
|
excludeFolders: [],
|
||||||
syncInterval: 60,
|
syncInterval: 60,
|
||||||
autoVoiceResponse: true,
|
autoVoiceResponse: true,
|
||||||
fileAccessMode: 'read',
|
fileAccessMode: 'read',
|
||||||
@@ -273,11 +275,11 @@ export class KhojSettingTab extends PluginSettingTab {
|
|||||||
this.plugin.restartSyncTimer();
|
this.plugin.restartSyncTimer();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Add setting to manage sync folders
|
// Add setting to manage include folders
|
||||||
const syncFoldersContainer = containerEl.createDiv('sync-folders-container');
|
const includeFoldersContainer = containerEl.createDiv('include-folders-container');
|
||||||
new Setting(syncFoldersContainer)
|
new Setting(includeFoldersContainer)
|
||||||
.setName('Sync Folders')
|
.setName('Include Folders')
|
||||||
.setDesc('Specify folders to sync (leave empty to sync entire vault)')
|
.setDesc('Folders to sync (leave empty to sync entire vault)')
|
||||||
.addButton(button => button
|
.addButton(button => button
|
||||||
.setButtonText('Add Folder')
|
.setButtonText('Add Folder')
|
||||||
.onClick(() => {
|
.onClick(() => {
|
||||||
@@ -285,15 +287,42 @@ export class KhojSettingTab extends PluginSettingTab {
|
|||||||
if (!this.plugin.settings.syncFolders.includes(folder)) {
|
if (!this.plugin.settings.syncFolders.includes(folder)) {
|
||||||
this.plugin.settings.syncFolders.push(folder);
|
this.plugin.settings.syncFolders.push(folder);
|
||||||
this.plugin.saveSettings();
|
this.plugin.saveSettings();
|
||||||
this.updateFolderList(folderListEl);
|
this.updateIncludeFolderList(includeFolderListEl);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
modal.open();
|
modal.open();
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Create a list to display selected folders
|
// Create a list to display selected include folders
|
||||||
const folderListEl = syncFoldersContainer.createDiv('folder-list');
|
const includeFolderListEl = includeFoldersContainer.createDiv('folder-list');
|
||||||
this.updateFolderList(folderListEl);
|
this.updateIncludeFolderList(includeFolderListEl);
|
||||||
|
|
||||||
|
// Add setting to manage exclude folders
|
||||||
|
const excludeFoldersContainer = containerEl.createDiv('exclude-folders-container');
|
||||||
|
new Setting(excludeFoldersContainer)
|
||||||
|
.setName('Exclude Folders')
|
||||||
|
.setDesc('Folders to exclude from sync (takes precedence over includes)')
|
||||||
|
.addButton(button => button
|
||||||
|
.setButtonText('Add Folder')
|
||||||
|
.onClick(() => {
|
||||||
|
const modal = new FolderSuggestModal(this.app, (folder: string) => {
|
||||||
|
// Don't allow excluding root folder
|
||||||
|
if (folder === '') {
|
||||||
|
new Notice('Cannot exclude the root folder');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (!this.plugin.settings.excludeFolders.includes(folder)) {
|
||||||
|
this.plugin.settings.excludeFolders.push(folder);
|
||||||
|
this.plugin.saveSettings();
|
||||||
|
this.updateExcludeFolderList(excludeFolderListEl);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
modal.open();
|
||||||
|
}));
|
||||||
|
|
||||||
|
// Create a list to display selected exclude folders
|
||||||
|
const excludeFolderListEl = excludeFoldersContainer.createDiv('folder-list');
|
||||||
|
this.updateExcludeFolderList(excludeFolderListEl);
|
||||||
|
|
||||||
let indexVaultSetting = new Setting(containerEl);
|
let indexVaultSetting = new Setting(containerEl);
|
||||||
indexVaultSetting
|
indexVaultSetting
|
||||||
@@ -441,19 +470,52 @@ export class KhojSettingTab extends PluginSettingTab {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
// Helper method to update the folder list display
|
// Helper method to update the include folder list display
|
||||||
private updateFolderList(containerEl: HTMLElement) {
|
private updateIncludeFolderList(containerEl: HTMLElement) {
|
||||||
|
this.updateFolderList(
|
||||||
|
containerEl,
|
||||||
|
this.plugin.settings.syncFolders,
|
||||||
|
'Including entire vault',
|
||||||
|
async (folder) => {
|
||||||
|
this.plugin.settings.syncFolders = this.plugin.settings.syncFolders.filter(f => f !== folder);
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
this.updateIncludeFolderList(containerEl);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Helper method to update the exclude folder list display
|
||||||
|
private updateExcludeFolderList(containerEl: HTMLElement) {
|
||||||
|
this.updateFolderList(
|
||||||
|
containerEl,
|
||||||
|
this.plugin.settings.excludeFolders,
|
||||||
|
'No folders excluded',
|
||||||
|
async (folder) => {
|
||||||
|
this.plugin.settings.excludeFolders = this.plugin.settings.excludeFolders.filter(f => f !== folder);
|
||||||
|
await this.plugin.saveSettings();
|
||||||
|
this.updateExcludeFolderList(containerEl);
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Shared helper to render a folder list with remove buttons
|
||||||
|
private updateFolderList(
|
||||||
|
containerEl: HTMLElement,
|
||||||
|
folders: string[],
|
||||||
|
emptyText: string,
|
||||||
|
onRemove: (folder: string) => void
|
||||||
|
) {
|
||||||
containerEl.empty();
|
containerEl.empty();
|
||||||
if (this.plugin.settings.syncFolders.length === 0) {
|
if (folders.length === 0) {
|
||||||
containerEl.createEl('div', {
|
containerEl.createEl('div', {
|
||||||
text: 'Syncing entire vault',
|
text: emptyText,
|
||||||
cls: 'folder-list-empty'
|
cls: 'folder-list-empty'
|
||||||
});
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
const list = containerEl.createEl('ul', { cls: 'folder-list' });
|
const list = containerEl.createEl('ul', { cls: 'folder-list' });
|
||||||
this.plugin.settings.syncFolders.forEach(folder => {
|
folders.forEach(folder => {
|
||||||
const item = list.createEl('li', { cls: 'folder-list-item' });
|
const item = list.createEl('li', { cls: 'folder-list-item' });
|
||||||
item.createSpan({ text: folder });
|
item.createSpan({ text: folder });
|
||||||
|
|
||||||
@@ -461,11 +523,7 @@ export class KhojSettingTab extends PluginSettingTab {
|
|||||||
cls: 'folder-list-remove',
|
cls: 'folder-list-remove',
|
||||||
text: '×'
|
text: '×'
|
||||||
});
|
});
|
||||||
removeButton.addEventListener('click', async () => {
|
removeButton.addEventListener('click', () => onRemove(folder));
|
||||||
this.plugin.settings.syncFolders = this.plugin.settings.syncFolders.filter(f => f !== folder);
|
|
||||||
await this.plugin.saveSettings();
|
|
||||||
this.updateFolderList(containerEl);
|
|
||||||
});
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -73,7 +73,7 @@ export async function updateContentIndex(vault: Vault, setting: KhojSetting, las
|
|||||||
if (fileTypeToExtension.image.includes(file.extension)) return setting.syncFileType.images;
|
if (fileTypeToExtension.image.includes(file.extension)) return setting.syncFileType.images;
|
||||||
return false;
|
return false;
|
||||||
})
|
})
|
||||||
// Filter files based on specified folders
|
// Filter files based on specified folders (include)
|
||||||
.filter(file => {
|
.filter(file => {
|
||||||
// If no folders are specified, sync all files
|
// If no folders are specified, sync all files
|
||||||
if (setting.syncFolders.length === 0) return true;
|
if (setting.syncFolders.length === 0) return true;
|
||||||
@@ -81,12 +81,35 @@ export async function updateContentIndex(vault: Vault, setting: KhojSetting, las
|
|||||||
return setting.syncFolders.some(folder =>
|
return setting.syncFolders.some(folder =>
|
||||||
file.path.startsWith(folder + '/') || file.path === folder
|
file.path.startsWith(folder + '/') || file.path === folder
|
||||||
);
|
);
|
||||||
|
})
|
||||||
|
// Filter out excluded folders
|
||||||
|
.filter(file => {
|
||||||
|
// If no folders are excluded, include all files
|
||||||
|
if (setting.excludeFolders.length === 0) return true;
|
||||||
|
// Exclude files in any of the excluded folders
|
||||||
|
return !setting.excludeFolders.some(folder =>
|
||||||
|
file.path.startsWith(folder + '/') || file.path === folder
|
||||||
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
// Log total eligible files
|
||||||
|
console.log(`Khoj: Found ${files.length} eligible files in vault`);
|
||||||
|
|
||||||
let countOfFilesToIndex = 0;
|
let countOfFilesToIndex = 0;
|
||||||
let countOfFilesToDelete = 0;
|
let countOfFilesToDelete = 0;
|
||||||
lastSync = lastSync.size > 0 ? lastSync : new Map<TFile, number>();
|
lastSync = lastSync.size > 0 ? lastSync : new Map<TFile, number>();
|
||||||
|
|
||||||
|
// Count files that need indexing (modified since last sync or regenerating)
|
||||||
|
const filesToSync = regenerate
|
||||||
|
? files
|
||||||
|
: files.filter(file => file.stat.mtime >= (lastSync.get(file) ?? 0));
|
||||||
|
|
||||||
|
// Show notice with file counts when user triggers sync
|
||||||
|
if (userTriggered) {
|
||||||
|
new Notice(`🔄 Syncing ${filesToSync.length} of ${files.length} files to Khoj...`);
|
||||||
|
}
|
||||||
|
console.log(`Khoj: ${filesToSync.length} files to sync (${files.length} total eligible)`);
|
||||||
|
|
||||||
// Add all files to index as multipart form data
|
// Add all files to index as multipart form data
|
||||||
let fileData = [];
|
let fileData = [];
|
||||||
let currentBatchSize = 0;
|
let currentBatchSize = 0;
|
||||||
@@ -233,8 +256,9 @@ export async function updateContentIndex(vault: Vault, setting: KhojSetting, las
|
|||||||
if (error_message) {
|
if (error_message) {
|
||||||
new Notice(error_message);
|
new Notice(error_message);
|
||||||
} else {
|
} else {
|
||||||
if (userTriggered) new Notice('✅ Updated Khoj index.');
|
const summary = `Updated ${countOfFilesToIndex}, deleted ${countOfFilesToDelete} files`;
|
||||||
console.log(`✅ Refreshed Khoj content index. Updated: ${countOfFilesToIndex} files, Deleted: ${countOfFilesToDelete} files.`);
|
if (userTriggered) new Notice(`✅ ${summary}`);
|
||||||
|
console.log(`✅ Refreshed Khoj content index. ${summary}.`);
|
||||||
}
|
}
|
||||||
|
|
||||||
return lastSync;
|
return lastSync;
|
||||||
|
|||||||
Reference in New Issue
Block a user