From 8139045ef3294ebd2955739007f28f026a61a2a8 Mon Sep 17 00:00:00 2001 From: Simon Cambier Date: Sat, 9 Apr 2022 23:05:15 +0200 Subject: [PATCH] Added filename in search fields, updated minisearch to beta, auto-search --- assets/styles.css | 10 +++- esbuild.config.mjs | 2 + package.json | 2 +- pnpm-lock.yaml | 8 +-- src/main.ts | 141 +++++++++++++++++++++++++++------------------ 5 files changed, 100 insertions(+), 63 deletions(-) diff --git a/assets/styles.css b/assets/styles.css index 6701820..ffe377b 100644 --- a/assets/styles.css +++ b/assets/styles.css @@ -1,5 +1,13 @@ .osresult__title { - /* font-size: var(--font-adaptive-normal); */ + font-weight: bold; +} + +.osresult__name { + padding-left: 1em; + font-size: small; + font-weight: normal; + /* color: var(--text-muted); + text-decoration: underline; */ } .osresult__body { diff --git a/esbuild.config.mjs b/esbuild.config.mjs index dedef2a..82cf317 100644 --- a/esbuild.config.mjs +++ b/esbuild.config.mjs @@ -60,5 +60,7 @@ esbuild logLevel: 'info', sourcemap: prod ? false : 'inline', treeShaking: true, + minify: prod, + legalComments: 'none', }) .catch(() => process.exit(1)) diff --git a/package.json b/package.json index 0ba9f84..c2ff8bd 100644 --- a/package.json +++ b/package.json @@ -30,7 +30,7 @@ "typescript": "4.4.4" }, "dependencies": { - "minisearch": "^4.0.3", + "minisearch": "^5.0.0-beta1", "remove-markdown": "^0.3.0" } } \ No newline at end of file diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index cd27e23..cbce60a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -12,7 +12,7 @@ specifiers: eslint-plugin-import: 2.22.1 eslint-plugin-node: 11.1.0 eslint-plugin-promise: 5.0.0 - minisearch: ^4.0.3 + minisearch: ^5.0.0-beta1 obsidian: latest prettier: ^2.6.2 prettier-eslint: ^13.0.0 @@ -21,7 +21,7 @@ specifiers: typescript: 4.4.4 dependencies: - minisearch: 4.0.3 + minisearch: 5.0.0-beta1 remove-markdown: 0.3.0 devDependencies: @@ -1540,8 +1540,8 @@ packages: resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==} dev: true - /minisearch/4.0.3: - resolution: {integrity: sha512-yEbNeb41Qk0g9IMJAgmp/kEaW2Ub2cs9MAKphTel24O8mymLheFU7esyfiumeUsvs2f4O0boO0zrp4bc9Po2cA==} + /minisearch/5.0.0-beta1: + resolution: {integrity: sha512-/huf6dMqE0182OzJPTIU+SyjY4HBds2CL82ADJboPsROiuFpnOz9wyCxWJwHX+fwehYS1hUAr87W5AGVcfn4jQ==} dev: false /mkdirp/0.5.6: diff --git a/src/main.ts b/src/main.ts index 47ed868..86717c2 100644 --- a/src/main.ts +++ b/src/main.ts @@ -1,43 +1,48 @@ -import { App, Plugin, SuggestModal, TFile } from 'obsidian' +import { Plugin, SuggestModal } from 'obsidian' import MiniSearch from 'minisearch' import removeMarkdown from 'remove-markdown' -type OmnisearchMatch = { - path: string - body: string - title: string -} - type Note = { path: string - content: string + name: string + title: string + body: string } +const regexWikilink = /\[\[(?.+?)(\|(?.+?))?\]\]/g +const regexEmbed = /!\[\[.+?\]\]/g + export default class OmnisearchPlugin extends Plugin { minisearch: MiniSearch - files: TFile[] - contents: Record + lastSearch?: string - setupIndex(): void { + async instantiateMinisearch(): Promise { this.minisearch = new MiniSearch({ idField: 'path', - fields: ['content', 'title', 'path'], - // storeFields: ['path'], + fields: ['body', 'title', 'name'], + storeFields: ['body', 'title', 'name'], }) + + const files = this.app.vault.getMarkdownFiles() + for (const file of files) { + // Fetch content from the cache, + // trim the markdown, remove embeds and clear wikilinks + const content = clearContent(await this.app.vault.cachedRead(file)) + .replace(regexEmbed, '') + .replace(regexWikilink, (sub, name, sep, alias) => alias ?? name) + + // Split the "title" (the first line/sentence) from the rest of the content + const title = getFirstLine(content) + const body = removeFirstLine(content) + + // Index those fields inside Minisearch + this.minisearch.add({ title, body, path: file.path, name: file.name }) + } } async onload(): Promise { - this.contents = {} - - this.setupIndex() - this.app.workspace.onLayoutReady(async () => { - this.files = this.app.vault.getMarkdownFiles() - for (const file of this.files) { - const content = await this.app.vault.cachedRead(file) - this.contents[file.path] = clearContent(content) // truncateText(clearContent(content)) - this.minisearch.add({ content, path: file.path }) - } + await this.instantiateMinisearch() }) this.addCommand({ @@ -51,13 +56,15 @@ export default class OmnisearchPlugin extends Plugin { } } -class OmnisearchModal extends SuggestModal { +class OmnisearchModal extends SuggestModal { plugin: OmnisearchPlugin constructor(plugin: OmnisearchPlugin) { super(plugin.app) this.plugin = plugin + this.setPlaceholder('Type to search through your notes') + this.setInstructions([ { command: '↑↓', purpose: 'to navigate' }, { command: '↵', purpose: 'to open' }, @@ -67,71 +74,95 @@ class OmnisearchModal extends SuggestModal { ]) } - getSuggestions(query: string): OmnisearchMatch[] { + onOpen(): void { + this.inputEl.focus() + if (this.plugin.lastSearch) { + const event = new Event('input', { + bubbles: true, + cancelable: true, + }) + this.inputEl.value = this.plugin.lastSearch + this.inputEl.dispatchEvent(event) + this.inputEl.select() + } + } + + getSuggestions(query: string): Note[] { + console.log('query: ' + query) + this.plugin.lastSearch = query + const results = this.plugin.minisearch .search(query, { prefix: true, fuzzy: term => (term.length > 4 ? 0.2 : false), combineWith: 'AND', - // processTerm: term => term.length <= minLength ? false : term, - boost: { title: 2 }, + boost: { name: 2, title: 1.5 }, }) .sort((a, b) => b.score - a.score) + .slice(0, 50) + console.log(results) return results.map(result => { - const file = this.plugin.files.find(f => f.path === result.id) - let title = getFirstLine(this.plugin.contents[file.path]) - let body = removeFirstLine(this.plugin.contents[file.path]) - - // Highlight the words - const highlight = (str: string): string => - '' + str + '' + // result.id == the file's path + let name = result.name + let title = result.title + let body = result.body + // If the body contains a searched term, find its position + // and trim the text around it const pos = body.toLowerCase().indexOf(result.terms[0]) + const surroundLen = 200 if (pos > -1) { - const from = Math.max(0, pos - 150) - const to = Math.min(body.length - 1, pos + 150) + const from = Math.max(0, pos - surroundLen) + const to = Math.min(body.length - 1, pos + surroundLen) body = (from > 0 ? '…' : '') + body.slice(from, to).trim() + (to < body.length - 1 ? '…' : '') } - result.terms - .sort((a, b) => a.length - b.length) - .forEach(term => { - term = term.toLowerCase() - - term = term.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&') - const reg = new RegExp(term, 'gi') - body = body.replace(reg, highlight) - title = title.replace(reg, highlight) - }) + // 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.join('|'), 'gi') + body = body.replace(reg, highlighter) + title = title.replace(reg, highlighter) + name = name.replace(reg, highlighter) return { - path: file.path, + path: result.id, + name, title, body, } }) } - renderSuggestion(value: OmnisearchMatch, el: HTMLElement): void { + renderSuggestion(value: Note, el: HTMLElement): void { + // title const title = el.createEl('div', { cls: 'osresult__title' }) title.innerHTML = value.title + + // filename + const name = el.createEl('span', { cls: 'osresult__name' }) + name.innerHTML = value.name + title.appendChild(name) + + // body const body = el.createEl('div', { cls: 'osresult__body' }) body.innerHTML = value.body } - onChooseSuggestion( - item: OmnisearchMatch, - evt: MouseEvent | KeyboardEvent, - ): void { + onChooseSuggestion(item: Note, evt: MouseEvent | KeyboardEvent): void { // this.app.workspace this.app.workspace.openLinkText(item.path, '') } } +function highlighter(str: string): string { + return '' + str + '' +} + /** * Strips the markdown and frontmatter * @param text @@ -161,12 +192,8 @@ function removeFirstLine(text: string): string { return lines.join('\n') } -function truncateText(text: string, len = 500): string { - return text.substring(0, len) + (text.length > 0 ? '...' : '') -} - function splitLines(text: string): string[] { - return text.split(/\r?\n|\r|\./) + return text.split(/\r?\n|\r|(\. )/) } function removeFrontMatter(text: string): string {