From 54bbe59db83333885969f400954a8795dc88aa8c Mon Sep 17 00:00:00 2001 From: Simon Cambier Date: Fri, 17 May 2024 23:10:00 +0200 Subject: [PATCH] #151 - User-defined boosted fields --- src/search/omnisearch.ts | 43 ++++++++++++++--------- src/settings.ts | 76 ++++++++++++++++++++++++++++++++++------ 2 files changed, 93 insertions(+), 26 deletions(-) diff --git a/src/search/omnisearch.ts b/src/search/omnisearch.ts index 88e9cb3..57ac7e9 100644 --- a/src/search/omnisearch.ts +++ b/src/search/omnisearch.ts @@ -188,6 +188,7 @@ export class Omnisearch { headings1: settings.weightH1, headings2: settings.weightH2, headings3: settings.weightH3, + tags: settings.weightUnmarkedTags, unmarkedTags: settings.weightUnmarkedTags, }, // The query is already tokenized, don't tokenize again @@ -232,6 +233,11 @@ export class Omnisearch { return results.filter(r => r.id === options.singleFilePath) } + logDebug( + 'searching with downranked folders', + settings.downrankedFoldersFilters + ) + // Hide or downrank files that are in Obsidian's excluded list if (settings.hideExcluded) { // Filter the files out @@ -254,14 +260,13 @@ export class Omnisearch { }) } - logDebug( - 'searching with downranked folders', - settings.downrankedFoldersFilters - ) - // downrank files that are in folders listed in the downrankedFoldersFilters - if (settings.downrankedFoldersFilters.length > 0) { - results.forEach(result => { - const path = result.id + // Extract tags from the query + const tags = query.getTags() + + for (const result of results) { + const path = result.id + if (settings.downrankedFoldersFilters.length > 0) { + // downrank files that are in folders listed in the downrankedFoldersFilters let downrankingFolder = false settings.downrankedFoldersFilters.forEach(filter => { if (path.startsWith(filter)) { @@ -285,21 +290,27 @@ export class Omnisearch { break } } - }) - } + } - // Extract tags from the query - const tags = query.getTags() + // Boost custom properties + const metadata = app.metadataCache.getCache(path) + if (metadata) { + for (const { name, weight } of settings.weightCustomProperties) { + const values = metadata?.frontmatter?.[name] + if (values && result.terms.some(t => values.includes(t))) { + logDebug(`Boosting field "${name}" x${weight} for ${path}`) + result.score *= weight + } + } + } - // Put the results with tags on top - for (const tag of tags) { - for (const result of results) { + // Put the results with tags on top + for (const tag of tags) { if ((result.tags ?? []).includes(tag)) { result.score *= 100 } } } - logDebug('Sorting and limiting results') // Sort results and keep the 50 best diff --git a/src/settings.ts b/src/settings.ts index ad9b494..5e7b648 100644 --- a/src/settings.ts +++ b/src/settings.ts @@ -1,3 +1,4 @@ +// noinspection CssUnresolvedCustomProperty import { Notice, Platform, @@ -25,6 +26,7 @@ interface WeightingSettings { } export interface OmnisearchSettings extends WeightingSettings { + weightCustomProperties: { name: string; weight: number }[] /** Enables caching to speed up indexing */ useCache: boolean /** Respect the "excluded files" Obsidian setting by downranking results ignored files */ @@ -100,7 +102,7 @@ export class SettingsTab extends PluginSettingTab { } // Settings main title - containerEl.createEl('h2', { text: 'Omnisearch' }) + containerEl.createEl('h1', { text: 'Omnisearch' }) // Sponsor link - Thank you! const divSponsor = containerEl.createDiv() @@ -131,7 +133,7 @@ export class SettingsTab extends PluginSettingTab { // PDF Indexing const indexPDFsDesc = new DocumentFragment() indexPDFsDesc.createSpan({}, span => { - span.innerHTML = `Omnisearch will use Text Extractor to index the content of your PDFs` + span.innerHTML = `Omnisearch will use Text Extractor to index the content of your PDFs.` }) new Setting(containerEl) .setName( @@ -150,7 +152,7 @@ export class SettingsTab extends PluginSettingTab { // Images Indexing const indexImagesDesc = new DocumentFragment() indexImagesDesc.createSpan({}, span => { - span.innerHTML = `Omnisearch will use Text Extractor to OCR your images and index their content` + span.innerHTML = `Omnisearch will use Text Extractor to OCR your images and index their content.` }) new Setting(containerEl) .setName(`Images OCR indexing ${getTextExtractor() ? '' : '⚠️ Disabled'}`) @@ -167,7 +169,7 @@ export class SettingsTab extends PluginSettingTab { // Office Documents Indexing const indexOfficesDesc = new DocumentFragment() indexOfficesDesc.createSpan({}, span => { - span.innerHTML = `Omnisearch will use Text Extractor to index the content of your office documents (currently
.docx
and
.xlsx
)` + span.innerHTML = `Omnisearch will use Text Extractor to index the content of your office documents (currently
.docx
and
.xlsx
).` }) new Setting(containerEl) .setName( @@ -189,7 +191,7 @@ export class SettingsTab extends PluginSettingTab { span.innerHTML = ` Omnisearch can index filenames of "unsupported" files, such as e.g.
.mp4
or non-extracted PDFs & images.
- "Obsidian setting" will respect the value of "Files & Links > Detect all file extensions"` + "Obsidian setting" will respect the value of "Files & Links > Detect all file extensions".` }) new Setting(containerEl) .setName('Index paths of unsupported files') @@ -262,7 +264,7 @@ export class SettingsTab extends PluginSettingTab { .setName('Respect Obsidian\'s "Excluded Files"') .setDesc( `By default, files that are in Obsidian\'s "Options > Files & Links > Excluded Files" list are downranked in results. - Enable this option to completely hide them` + Enable this option to completely hide them.` ) .addToggle(toggle => toggle.setValue(settings.hideExcluded).onChange(async v => { @@ -495,10 +497,63 @@ export class SettingsTab extends PluginSettingTab { new Setting(containerEl) .setName( - `Tags without the # (default: ${DEFAULT_SETTINGS.weightUnmarkedTags})` + `Tags (default: ${DEFAULT_SETTINGS.weightUnmarkedTags})` ) .addSlider(cb => this.weightSlider(cb, 'weightUnmarkedTags')) + //#region Specific tags + + new Setting(containerEl) + .setName('Header properties fields') + .setDesc('You can set custom weights for values of header properties (e.g. "keywords").') + + + for (let i = 0; i < settings.weightCustomProperties.length; i++) { + const item = settings.weightCustomProperties[i] + new Setting(containerEl) + .setName((i + 1).toString() + '.') + // TODO: add autocompletion from app.metadataCache.getAllPropertyInfos() + .addText(text => { + text + .setPlaceholder('Property name') + .setValue(item.name) + .onChange(async v => { + item.name = v + await saveSettings(this.plugin) + }) + }) + .addSlider(cb => { + cb.setLimits(1, 5, 0.1) + .setValue(item.weight) + .setDynamicTooltip() + .onChange(async v => { + item.weight = v + await saveSettings(this.plugin) + }) + }) + // Remove the tag + .addButton(btn => { + btn.setButtonText('Remove') + btn.onClick(async () => { + settings.weightCustomProperties.splice(i, 1) + await saveSettings(this.plugin) + this.display() + }) + }) + } + + // Add a new custom tag + new Setting(containerEl) + .addButton(btn => { + btn.setButtonText('Add a new property') + btn.onClick(cb => { + settings.weightCustomProperties.push({ name: '', weight: 1 }) + this.display() + }) + }) + + //#endregion Specific tags + //#endregion Results Weighting //#region HTTP Server @@ -632,9 +687,9 @@ export class SettingsTab extends PluginSettingTab { new Setting(containerEl) .setName('Clear cache data') .setDesc(resetCacheDesc) - .addButton(cb => { - cb.setButtonText('Clear cache') - cb.onClick(async () => { + .addButton(btn => { + btn.setButtonText('Clear cache') + btn.onClick(async () => { await database.clearCache() }) }) @@ -683,6 +738,7 @@ export const DEFAULT_SETTINGS: OmnisearchSettings = { weightH2: 1.3, weightH3: 1.1, weightUnmarkedTags: 1.1, + weightCustomProperties: [] as { name: string; weight: number }[], httpApiEnabled: false, httpApiPort: '51361',