diff --git a/src/components/ResultItemVault.svelte b/src/components/ResultItemVault.svelte index cc3dd55..d11e79e 100644 --- a/src/components/ResultItemVault.svelte +++ b/src/components/ResultItemVault.svelte @@ -98,9 +98,9 @@ }, } } - let elFolderPathIcon: HTMLElement - let elFilePathIcon: HTMLElement - let elEmbedIcon: HTMLElement + let elFolderPathIcon: HTMLElement | null = null + let elFilePathIcon: HTMLElement | null = null + let elEmbedIcon: HTMLElement | null = null $: { imagePath = null diff --git a/src/globals.ts b/src/globals.ts index ab2a233..bc20c73 100644 --- a/src/globals.ts +++ b/src/globals.ts @@ -41,6 +41,13 @@ export const enum Action { OpenInNewLeaf = 'open-in-new-leaf', } +export const enum RecencyCutoff { + Disabled = '0', + Day = '1', + Week = '2', + Month = '3', +} + export type DocumentRef = { path: string; mtime: number } export type IndexedDocument = { @@ -100,8 +107,8 @@ export type TextExtractorApi = { } export type AIImageAnalyzerAPI = { - analyzeImage: (file: TFile) => Promise; - canBeAnalyzed: (file: TFile) => boolean; + analyzeImage: (file: TFile) => Promise + canBeAnalyzed: (file: TFile) => boolean } export const SEPARATORS = diff --git a/src/search/search-engine.ts b/src/search/search-engine.ts index ec123a5..3a3bdc0 100644 --- a/src/search/search-engine.ts +++ b/src/search/search-engine.ts @@ -3,7 +3,12 @@ import MiniSearch, { type Options, type SearchResult, } from 'minisearch' -import type { DocumentRef, IndexedDocument, ResultNote } from '../globals' +import { + RecencyCutoff, + type DocumentRef, + type IndexedDocument, + type ResultNote, +} from '../globals' import { chunkArray, logVerbose, removeDiacritics } from '../tools/utils' import { Notice } from 'obsidian' @@ -60,7 +65,7 @@ export class SearchEngine { !this.indexedDocuments.has(d.path) || this.indexedDocuments.get(d.path) !== d.mtime ) - + const toRemove = [...this.indexedDocuments] .filter( ([path, mtime]) => !docsMap.has(path) || docsMap.get(path) !== mtime @@ -171,6 +176,25 @@ export class SearchEngine { }, // The query is already tokenized, don't tokenize again tokenize: text => [text], + boostDocument(_id, _term, storedFields) { + if ( + !storedFields?.mtime || + settings.recencyBoost === RecencyCutoff.Disabled + ) { + return 1 + } + const mtime = storedFields?.mtime as number + const now = new Date().valueOf() + const daysElapsed = (now - mtime) / (24 * 3600) + + // Documents boost + const cutoff = { + [RecencyCutoff.Day]: -3, + [RecencyCutoff.Week]: -0.3, + [RecencyCutoff.Month]: -0.1, + } as const + return 1 + Math.exp(cutoff[settings.recencyBoost] * daysElapsed) + }, }) logVerbose(`Found ${results.length} results`, results) @@ -300,7 +324,8 @@ export class SearchEngine { const documents = await Promise.all( results.map( - async result => await this.plugin.documentsRepository.getDocument(result.id) + async result => + await this.plugin.documentsRepository.getDocument(result.id) ) ) @@ -376,7 +401,8 @@ export class SearchEngine { const documents = await Promise.all( results.map( - async result => await this.plugin.documentsRepository.getDocument(result.id) + async result => + await this.plugin.documentsRepository.getDocument(result.id) ) ) @@ -503,7 +529,7 @@ export class SearchEngine { 'headings2', 'headings3', ], - storeFields: ['tags'], + storeFields: ['tags', 'mtime'], logger(_level, _message, code) { if (code === 'version_conflict') { new Notice( diff --git a/src/settings.ts b/src/settings.ts index d00b5b0..684a55d 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -9,7 +9,7 @@ import { SliderComponent, } from 'obsidian' import { writable } from 'svelte/store' -import { K_DISABLE_OMNISEARCH } from './globals' +import { K_DISABLE_OMNISEARCH, RecencyCutoff } from './globals' import type OmnisearchPlugin from './main' import { enableVerboseLogging, getCtrlKeyLabel } from './tools/utils' import { debounce } from 'lodash-es' @@ -29,6 +29,8 @@ export interface OmnisearchSettings extends WeightingSettings { useCache: boolean /** Respect the "excluded files" Obsidian setting by downranking results ignored files */ hideExcluded: boolean + /** Boost more recent files */ + recencyBoost: RecencyCutoff /** downrank files in the given folders */ downrankedFoldersFilters: string[] /** Ignore diacritics when indexing files */ @@ -317,6 +319,24 @@ export class SettingsTab extends PluginSettingTab { }) ) + new Setting(containerEl) + .setName('Recency boost (experimental)') + .setDesc('Files that have been modified more recently than [selected cutoff] are given a higher rank.') + .addDropdown(dropdown => + dropdown + .addOptions({ + [RecencyCutoff.Disabled]: 'Disabled', + [RecencyCutoff.Day]: '24 hours', + [RecencyCutoff.Week]: '7 days', + [RecencyCutoff.Month]: '30 days', + }) + .setValue(settings.recencyBoost) + .onChange(async v => { + settings.recencyBoost = v as RecencyCutoff + await saveSettings(this.plugin) + }) + ) + // Downranked files new Setting(containerEl) .setName('Folders to downrank in search results') @@ -398,7 +418,9 @@ export class SettingsTab extends PluginSettingTab { // Set Vim like navigation keys new Setting(containerEl) .setName('Set Vim like navigation keys') - .setDesc(`Navigate down the results with ${getCtrlKeyLabel()} + J/N, or navigate up with ${getCtrlKeyLabel()} + K/P.`) + .setDesc( + `Navigate down the results with ${getCtrlKeyLabel()} + J/N, or navigate up with ${getCtrlKeyLabel()} + K/P.` + ) .addToggle(toggle => toggle .setValue(settings.vimLikeNavigationShortcut) @@ -793,6 +815,7 @@ export function getDefaultSettings(app: App): OmnisearchSettings { return { useCache: true, hideExcluded: false, + recencyBoost: RecencyCutoff.Disabled, downrankedFoldersFilters: [] as string[], ignoreDiacritics: true, ignoreArabicDiacritics: false,