Added filename in search fields, updated minisearch to beta, auto-search
This commit is contained in:
@@ -1,5 +1,13 @@
|
|||||||
.osresult__title {
|
.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 {
|
.osresult__body {
|
||||||
|
|||||||
@@ -60,5 +60,7 @@ esbuild
|
|||||||
logLevel: 'info',
|
logLevel: 'info',
|
||||||
sourcemap: prod ? false : 'inline',
|
sourcemap: prod ? false : 'inline',
|
||||||
treeShaking: true,
|
treeShaking: true,
|
||||||
|
minify: prod,
|
||||||
|
legalComments: 'none',
|
||||||
})
|
})
|
||||||
.catch(() => process.exit(1))
|
.catch(() => process.exit(1))
|
||||||
|
|||||||
@@ -30,7 +30,7 @@
|
|||||||
"typescript": "4.4.4"
|
"typescript": "4.4.4"
|
||||||
},
|
},
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"minisearch": "^4.0.3",
|
"minisearch": "^5.0.0-beta1",
|
||||||
"remove-markdown": "^0.3.0"
|
"remove-markdown": "^0.3.0"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
8
pnpm-lock.yaml
generated
8
pnpm-lock.yaml
generated
@@ -12,7 +12,7 @@ specifiers:
|
|||||||
eslint-plugin-import: 2.22.1
|
eslint-plugin-import: 2.22.1
|
||||||
eslint-plugin-node: 11.1.0
|
eslint-plugin-node: 11.1.0
|
||||||
eslint-plugin-promise: 5.0.0
|
eslint-plugin-promise: 5.0.0
|
||||||
minisearch: ^4.0.3
|
minisearch: ^5.0.0-beta1
|
||||||
obsidian: latest
|
obsidian: latest
|
||||||
prettier: ^2.6.2
|
prettier: ^2.6.2
|
||||||
prettier-eslint: ^13.0.0
|
prettier-eslint: ^13.0.0
|
||||||
@@ -21,7 +21,7 @@ specifiers:
|
|||||||
typescript: 4.4.4
|
typescript: 4.4.4
|
||||||
|
|
||||||
dependencies:
|
dependencies:
|
||||||
minisearch: 4.0.3
|
minisearch: 5.0.0-beta1
|
||||||
remove-markdown: 0.3.0
|
remove-markdown: 0.3.0
|
||||||
|
|
||||||
devDependencies:
|
devDependencies:
|
||||||
@@ -1540,8 +1540,8 @@ packages:
|
|||||||
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
|
resolution: {integrity: sha512-Jsjnk4bw3YJqYzbdyBiNsPWHPfO++UGG749Cxs6peCu5Xg4nrena6OVxOYxrQTqww0Jmwt+Ref8rggumkTLz9Q==}
|
||||||
dev: true
|
dev: true
|
||||||
|
|
||||||
/minisearch/4.0.3:
|
/minisearch/5.0.0-beta1:
|
||||||
resolution: {integrity: sha512-yEbNeb41Qk0g9IMJAgmp/kEaW2Ub2cs9MAKphTel24O8mymLheFU7esyfiumeUsvs2f4O0boO0zrp4bc9Po2cA==}
|
resolution: {integrity: sha512-/huf6dMqE0182OzJPTIU+SyjY4HBds2CL82ADJboPsROiuFpnOz9wyCxWJwHX+fwehYS1hUAr87W5AGVcfn4jQ==}
|
||||||
dev: false
|
dev: false
|
||||||
|
|
||||||
/mkdirp/0.5.6:
|
/mkdirp/0.5.6:
|
||||||
|
|||||||
141
src/main.ts
141
src/main.ts
@@ -1,43 +1,48 @@
|
|||||||
import { App, Plugin, SuggestModal, TFile } from 'obsidian'
|
import { Plugin, SuggestModal } from 'obsidian'
|
||||||
import MiniSearch from 'minisearch'
|
import MiniSearch from 'minisearch'
|
||||||
import removeMarkdown from 'remove-markdown'
|
import removeMarkdown from 'remove-markdown'
|
||||||
|
|
||||||
type OmnisearchMatch = {
|
|
||||||
path: string
|
|
||||||
body: string
|
|
||||||
title: string
|
|
||||||
}
|
|
||||||
|
|
||||||
type Note = {
|
type Note = {
|
||||||
path: string
|
path: string
|
||||||
content: string
|
name: string
|
||||||
|
title: string
|
||||||
|
body: string
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const regexWikilink = /\[\[(?<name>.+?)(\|(?<alias>.+?))?\]\]/g
|
||||||
|
const regexEmbed = /!\[\[.+?\]\]/g
|
||||||
|
|
||||||
export default class OmnisearchPlugin extends Plugin {
|
export default class OmnisearchPlugin extends Plugin {
|
||||||
minisearch: MiniSearch<Note>
|
minisearch: MiniSearch<Note>
|
||||||
files: TFile[]
|
lastSearch?: string
|
||||||
contents: Record<string, string>
|
|
||||||
|
|
||||||
setupIndex(): void {
|
async instantiateMinisearch(): Promise<void> {
|
||||||
this.minisearch = new MiniSearch<Note>({
|
this.minisearch = new MiniSearch<Note>({
|
||||||
idField: 'path',
|
idField: 'path',
|
||||||
fields: ['content', 'title', 'path'],
|
fields: ['body', 'title', 'name'],
|
||||||
// storeFields: ['path'],
|
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<void> {
|
async onload(): Promise<void> {
|
||||||
this.contents = {}
|
|
||||||
|
|
||||||
this.setupIndex()
|
|
||||||
|
|
||||||
this.app.workspace.onLayoutReady(async () => {
|
this.app.workspace.onLayoutReady(async () => {
|
||||||
this.files = this.app.vault.getMarkdownFiles()
|
await this.instantiateMinisearch()
|
||||||
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 })
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
|
|
||||||
this.addCommand({
|
this.addCommand({
|
||||||
@@ -51,13 +56,15 @@ export default class OmnisearchPlugin extends Plugin {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
class OmnisearchModal extends SuggestModal<OmnisearchMatch> {
|
class OmnisearchModal extends SuggestModal<Note> {
|
||||||
plugin: OmnisearchPlugin
|
plugin: OmnisearchPlugin
|
||||||
|
|
||||||
constructor(plugin: OmnisearchPlugin) {
|
constructor(plugin: OmnisearchPlugin) {
|
||||||
super(plugin.app)
|
super(plugin.app)
|
||||||
this.plugin = plugin
|
this.plugin = plugin
|
||||||
|
|
||||||
this.setPlaceholder('Type to search through your notes')
|
this.setPlaceholder('Type to search through your notes')
|
||||||
|
|
||||||
this.setInstructions([
|
this.setInstructions([
|
||||||
{ command: '↑↓', purpose: 'to navigate' },
|
{ command: '↑↓', purpose: 'to navigate' },
|
||||||
{ command: '↵', purpose: 'to open' },
|
{ command: '↵', purpose: 'to open' },
|
||||||
@@ -67,71 +74,95 @@ class OmnisearchModal extends SuggestModal<OmnisearchMatch> {
|
|||||||
])
|
])
|
||||||
}
|
}
|
||||||
|
|
||||||
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
|
const results = this.plugin.minisearch
|
||||||
.search(query, {
|
.search(query, {
|
||||||
prefix: true,
|
prefix: true,
|
||||||
fuzzy: term => (term.length > 4 ? 0.2 : false),
|
fuzzy: term => (term.length > 4 ? 0.2 : false),
|
||||||
combineWith: 'AND',
|
combineWith: 'AND',
|
||||||
// processTerm: term => term.length <= minLength ? false : term,
|
boost: { name: 2, title: 1.5 },
|
||||||
boost: { title: 2 },
|
|
||||||
})
|
})
|
||||||
.sort((a, b) => b.score - a.score)
|
.sort((a, b) => b.score - a.score)
|
||||||
|
.slice(0, 50)
|
||||||
|
console.log(results)
|
||||||
|
|
||||||
return results.map(result => {
|
return results.map(result => {
|
||||||
const file = this.plugin.files.find(f => f.path === result.id)
|
// result.id == the file's path
|
||||||
let title = getFirstLine(this.plugin.contents[file.path])
|
let name = result.name
|
||||||
let body = removeFirstLine(this.plugin.contents[file.path])
|
let title = result.title
|
||||||
|
let body = result.body
|
||||||
// Highlight the words
|
|
||||||
const highlight = (str: string): string =>
|
|
||||||
'<span class="search-result-file-matched-text">' + str + '</span>'
|
|
||||||
|
|
||||||
|
// 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 pos = body.toLowerCase().indexOf(result.terms[0])
|
||||||
|
const surroundLen = 200
|
||||||
if (pos > -1) {
|
if (pos > -1) {
|
||||||
const from = Math.max(0, pos - 150)
|
const from = Math.max(0, pos - surroundLen)
|
||||||
const to = Math.min(body.length - 1, pos + 150)
|
const to = Math.min(body.length - 1, pos + surroundLen)
|
||||||
body =
|
body =
|
||||||
(from > 0 ? '…' : '') +
|
(from > 0 ? '…' : '') +
|
||||||
body.slice(from, to).trim() +
|
body.slice(from, to).trim() +
|
||||||
(to < body.length - 1 ? '…' : '')
|
(to < body.length - 1 ? '…' : '')
|
||||||
}
|
}
|
||||||
|
|
||||||
result.terms
|
// Sort the terms from smaller to larger
|
||||||
.sort((a, b) => a.length - b.length)
|
// and highlight them in the title and body
|
||||||
.forEach(term => {
|
const terms = result.terms.sort((a, b) => a.length - b.length)
|
||||||
term = term.toLowerCase()
|
const reg = new RegExp(terms.join('|'), 'gi')
|
||||||
|
body = body.replace(reg, highlighter)
|
||||||
term = term.replace(/[-\/\\^$*+?.()|[\]{}]/g, '\\$&')
|
title = title.replace(reg, highlighter)
|
||||||
const reg = new RegExp(term, 'gi')
|
name = name.replace(reg, highlighter)
|
||||||
body = body.replace(reg, highlight)
|
|
||||||
title = title.replace(reg, highlight)
|
|
||||||
})
|
|
||||||
|
|
||||||
return {
|
return {
|
||||||
path: file.path,
|
path: result.id,
|
||||||
|
name,
|
||||||
title,
|
title,
|
||||||
body,
|
body,
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
renderSuggestion(value: OmnisearchMatch, el: HTMLElement): void {
|
renderSuggestion(value: Note, el: HTMLElement): void {
|
||||||
|
// title
|
||||||
const title = el.createEl('div', { cls: 'osresult__title' })
|
const title = el.createEl('div', { cls: 'osresult__title' })
|
||||||
title.innerHTML = value.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' })
|
const body = el.createEl('div', { cls: 'osresult__body' })
|
||||||
body.innerHTML = value.body
|
body.innerHTML = value.body
|
||||||
}
|
}
|
||||||
|
|
||||||
onChooseSuggestion(
|
onChooseSuggestion(item: Note, evt: MouseEvent | KeyboardEvent): void {
|
||||||
item: OmnisearchMatch,
|
|
||||||
evt: MouseEvent | KeyboardEvent,
|
|
||||||
): void {
|
|
||||||
// this.app.workspace
|
// this.app.workspace
|
||||||
this.app.workspace.openLinkText(item.path, '')
|
this.app.workspace.openLinkText(item.path, '')
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function highlighter(str: string): string {
|
||||||
|
return '<span class="search-result-file-matched-text">' + str + '</span>'
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Strips the markdown and frontmatter
|
* Strips the markdown and frontmatter
|
||||||
* @param text
|
* @param text
|
||||||
@@ -161,12 +192,8 @@ function removeFirstLine(text: string): string {
|
|||||||
return lines.join('\n')
|
return lines.join('\n')
|
||||||
}
|
}
|
||||||
|
|
||||||
function truncateText(text: string, len = 500): string {
|
|
||||||
return text.substring(0, len) + (text.length > 0 ? '...' : '')
|
|
||||||
}
|
|
||||||
|
|
||||||
function splitLines(text: string): string[] {
|
function splitLines(text: string): string[] {
|
||||||
return text.split(/\r?\n|\r|\./)
|
return text.split(/\r?\n|\r|(\. )/)
|
||||||
}
|
}
|
||||||
|
|
||||||
function removeFrontMatter(text: string): string {
|
function removeFrontMatter(text: string): string {
|
||||||
|
|||||||
Reference in New Issue
Block a user