Updated the weighting logic + doing some preparation for #6
Headings now have more weight. Also set TS strict mode
This commit is contained in:
@@ -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
|
||||
}
|
||||
|
||||
21
src/main.ts
21
src/main.ts
@@ -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
|
||||
|
||||
55
src/modal.ts
55
src/modal.ts
@@ -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,8 +95,10 @@ export class OmnisearchModal extends SuggestModal<ResultNote> {
|
||||
}
|
||||
|
||||
onClose(): void {
|
||||
if (this.mutationObserver) {
|
||||
this.mutationObserver.disconnect()
|
||||
}
|
||||
}
|
||||
|
||||
async getSuggestions(query: string): Promise<ResultNote[]> {
|
||||
this.plugin.lastSearch = query
|
||||
@@ -102,17 +108,25 @@ 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
|
||||
|
||||
// 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)
|
||||
|
||||
// If the body contains a searched term, find its position
|
||||
// and trim the text around it
|
||||
@@ -127,22 +141,22 @@ export class OmnisearchModal extends SuggestModal<ResultNote> {
|
||||
(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')
|
||||
// console.log(matches)
|
||||
content = content.replace(reg, highlighter)
|
||||
basename = basename.replace(reg, highlighter)
|
||||
|
||||
const resultNote: ResultNote = {
|
||||
content,
|
||||
basename,
|
||||
path: note.path,
|
||||
keyword: result.terms[0],
|
||||
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
|
||||
|
||||
|
||||
31
src/utils.ts
31
src/utils.ts
@@ -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) ?? []
|
||||
)
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"strict": true,
|
||||
"baseUrl": ".",
|
||||
"inlineSourceMap": true,
|
||||
"inlineSources": true,
|
||||
|
||||
Reference in New Issue
Block a user