Updated the weighting logic + doing some preparation for #6

Headings now have more weight. Also set TS strict mode
This commit is contained in:
Simon Cambier
2022-04-14 20:41:27 +02:00
parent a57582a389
commit 98c277e541
5 changed files with 117 additions and 48 deletions

View File

@@ -1,3 +1,5 @@
import { match } from 'assert'
// Matches a wikiling that begins a string
export const regexWikilink = /^!?\[\[(?<name>.+?)(\|(?<alias>.+?))?\]\]/
export const regexLineSplit = /\r?\n|\r|((\.|\?|!)( |\r?\n|\r))/g
@@ -13,12 +15,23 @@ export type IndexedNote = {
path: string
basename: string
content: string
headings1: string
headings2: string
headings3: string
}
export type SearchMatch = {
match: string
index: number
}
export const isSearchMatch = (o: { index?: number }): o is SearchMatch => {
return o.index !== undefined
}
export type ResultNote = {
path: string
basename: string
content: string
keyword: string
matches: SearchMatch[]
occurence: number
}

View File

@@ -1,13 +1,17 @@
import { Notice, Plugin, TAbstractFile, TFile } from 'obsidian'
import MiniSearch from 'minisearch'
import { clearContent, getTitleLine, wait } from './utils'
import {
clearContent,
extractHeadingsFromCache,
wait,
} from './utils'
import { IndexedNote } from './globals'
import { OmnisearchModal } from './modal'
export default class OmnisearchPlugin extends Plugin {
minisearch: MiniSearch<IndexedNote>
minisearch!: MiniSearch<IndexedNote>
lastSearch?: string
indexedNotes: Record<string, IndexedNote>
indexedNotes: Record<string, IndexedNote> = {}
async onload(): Promise<void> {
await this.instantiateMinisearch()
@@ -53,11 +57,7 @@ export default class OmnisearchPlugin extends Plugin {
this.indexedNotes = {}
this.minisearch = new MiniSearch({
idField: 'path',
fields: ['content', 'title', 'basename'],
extractField: (document, fieldname: 'content' | 'title' | 'basename') => {
if (fieldname === 'title') return getTitleLine(document.content)
return document[fieldname]
},
fields: ['basename', 'content', 'headings1', 'headings2', 'headings3'],
})
// Index files that are already present
@@ -89,6 +89,8 @@ export default class OmnisearchPlugin extends Plugin {
if (!(file instanceof TFile) || file.extension !== 'md') return
try {
// console.log(`Omnisearch - adding ${file.path} to index`)
const fileCache = this.app.metadataCache.getFileCache(file)
// console.log(fileCache)
if (this.indexedNotes[file.path]) {
throw new Error(`${file.basename} is already indexed`)
@@ -106,6 +108,9 @@ export default class OmnisearchPlugin extends Plugin {
basename: file.basename,
content: tmp.innerText,
path: file.path,
headings1: fileCache ? extractHeadingsFromCache(fileCache, 1).join(' ') : '',
headings2: fileCache ? extractHeadingsFromCache(fileCache, 2).join(' ') : '',
headings3: fileCache ? extractHeadingsFromCache(fileCache, 3).join(' ') : '',
}
this.minisearch.add(note)
this.indexedNotes[file.path] = note

View File

@@ -1,7 +1,7 @@
import { MarkdownView, SuggestModal, TFile } from 'obsidian'
import { ResultNote } from './globals'
import OmnisearchPlugin from './main'
import { escapeRegex, highlighter } from './utils'
import { escapeRegex, getAllIndexes, highlighter } from './utils'
export class OmnisearchModal extends SuggestModal<ResultNote> {
private plugin: OmnisearchPlugin
@@ -43,9 +43,13 @@ export class OmnisearchModal extends SuggestModal<ResultNote> {
await this.app.workspace.openLinkText(file.path, '')
}
catch (e) {
if (e.message === 'File already exists.') {
if (e instanceof Error && e.message === 'File already exists.') {
// Open the existing file instead of creating it
await this.app.workspace.openLinkText(this.inputEl.value, '')
}
else {
console.error(e)
}
}
}
this.close()
@@ -60,7 +64,7 @@ export class OmnisearchModal extends SuggestModal<ResultNote> {
const record = events.find(event =>
(event.target as HTMLDivElement).classList.contains('is-selected'),
)
const id = (record?.target as HTMLElement).getAttribute('data-note-id')
const id = (record?.target as HTMLElement)?.getAttribute('data-note-id') ?? null
if (id) {
this.selectedNoteId = id
}
@@ -91,7 +95,9 @@ export class OmnisearchModal extends SuggestModal<ResultNote> {
}
onClose(): void {
this.mutationObserver.disconnect()
if (this.mutationObserver) {
this.mutationObserver.disconnect()
}
}
async getSuggestions(query: string): Promise<ResultNote[]> {
@@ -102,47 +108,55 @@ export class OmnisearchModal extends SuggestModal<ResultNote> {
prefix: true,
fuzzy: term => (term.length > 4 ? 0.2 : false),
combineWith: 'AND',
boost: { basename: 2, title: 1.5 },
boost: { basename: 2, headings1: 1.5, headings2: 1.3, headings3: 1.1 },
})
.sort((a, b) => b.score - a.score)
.slice(0, 50)
// console.log('Omnisearch - Results:')
// console.log(`Omnisearch - Results for "${query}"`)
// console.log(results)
return results.map(result => {
const note = this.plugin.indexedNotes[result.id]
let content = note.content
let basename = note.basename
const suggestions = await Promise.all(
results.map(async result => {
const file = this.app.vault.getAbstractFileByPath(result.id) as TFile
// const metadata = this.app.metadataCache.getFileCache(file)
let content = (await this.app.vault.cachedRead(file)).toLowerCase()
let basename = file.basename
// If the body contains a searched term, find its position
// and trim the text around it
const pos = content.toLowerCase().indexOf(result.terms[0])
const surroundLen = 180
if (pos > -1) {
const from = Math.max(0, pos - surroundLen)
const to = Math.min(content.length - 1, pos + surroundLen)
content =
(from > 0 ? '…' : '') +
content.slice(from, to).trim() +
(to < content.length - 1 ? '…' : '')
}
// Sort the terms from smaller to larger
// and highlight them in the title and body
const terms = result.terms.sort((a, b) => a.length - b.length)
const reg = new RegExp(terms.map(escapeRegex).join('|'), 'gi')
const matches = getAllIndexes(content, reg)
// Sort the terms from smaller to larger
// and highlight them in the title and body
const terms = result.terms.sort((a, b) => a.length - b.length)
const reg = new RegExp(terms.map(escapeRegex).join('|'), 'gi')
content = content.replace(reg, highlighter)
basename = basename.replace(reg, highlighter)
// If the body contains a searched term, find its position
// and trim the text around it
const pos = content.toLowerCase().indexOf(result.terms[0])
const surroundLen = 180
if (pos > -1) {
const from = Math.max(0, pos - surroundLen)
const to = Math.min(content.length - 1, pos + surroundLen)
content =
(from > 0 ? '…' : '') +
content.slice(from, to).trim() +
(to < content.length - 1 ? '…' : '')
}
const resultNote: ResultNote = {
content,
basename,
path: note.path,
keyword: result.terms[0],
occurence: 0,
}
return resultNote
})
// console.log(matches)
content = content.replace(reg, highlighter)
basename = basename.replace(reg, highlighter)
const resultNote: ResultNote = {
content,
basename,
path: file.path,
matches,
occurence: 0,
}
return resultNote
}),
)
return suggestions
}
renderSuggestion(value: ResultNote, el: HTMLElement): void {
@@ -160,11 +174,18 @@ export class OmnisearchModal extends SuggestModal<ResultNote> {
async onChooseSuggestion(item: ResultNote): Promise<void> {
const file = this.app.vault.getAbstractFileByPath(item.path) as TFile
// const fileCache = this.app.metadataCache.getFileCache(file)
// console.log(fileCache)
const content = (await this.app.vault.cachedRead(file)).toLowerCase()
const offset = content.indexOf(item.keyword.toLowerCase())
const offset = content.indexOf(
item.matches[item.occurence].match.toLowerCase(),
)
await this.app.workspace.openLinkText(item.path, '')
const view = this.app.workspace.getActiveViewOfType(MarkdownView)
if (!view) {
throw new Error('OmniSearch - No active MarkdownView')
}
const pos = view.editor.offsetToPos(offset)
pos.ch = 0

View File

@@ -1,5 +1,12 @@
import markdownToTxt from 'markdown-to-txt'
import { regexLineSplit, regexWikilink, regexYaml } from './globals'
import { CachedMetadata } from 'obsidian'
import {
isSearchMatch,
regexLineSplit,
regexWikilink,
regexYaml,
SearchMatch,
} from './globals'
export function highlighter(str: string): string {
return '<span class="search-result-file-matched-text">' + str + '</span>'
@@ -64,3 +71,25 @@ export function wait(ms: number): Promise<void> {
export function escapeRegex(str: string): string {
return str.replace(/[-/\\^$*+?.()|[\]{}]/g, '\\$&')
}
/**
* Returns the positions of all occurences of `val` inside of `text`
* https://stackoverflow.com/a/58828841
* @param text
* @param val
* @returns
*/
export function getAllIndexes(text: string, val: RegExp): SearchMatch[] {
return [...text.matchAll(val)]
.map(o => ({ match: o[0], index: o.index }))
.filter(isSearchMatch)
}
export function extractHeadingsFromCache(
cache: CachedMetadata,
level: number,
): string[] {
return (
cache.headings?.filter(h => h.level === level).map(h => h.heading) ?? []
)
}

View File

@@ -1,5 +1,6 @@
{
"compilerOptions": {
"strict": true,
"baseUrl": ".",
"inlineSourceMap": true,
"inlineSources": true,