Files
obsidian-tannersearch/src/CmpModal.svelte
2022-04-16 15:31:48 +02:00

172 lines
4.9 KiB
Svelte

<script lang="ts">
import { MarkdownView, TFile } from "obsidian"
import { tick } from "svelte"
import CmpInput from "./CmpInput.svelte"
import CmpNoteResult from "./CmpNoteResult.svelte"
import type { ResultNote } from "./globals"
import type OmnisearchPlugin from "./main"
import type { OmnisearchModal } from "./modal"
import { resultNotes, searchQuery, selectedNote } from "./stores"
import { escapeHTML, escapeRegex, getAllIndexes, highlighter } from "./utils"
export let plugin: OmnisearchPlugin
export let modal: OmnisearchModal
searchQuery.subscribe(async (q) => {
const results = getSuggestions(q)
resultNotes.set(results)
if (results.length) {
await tick()
selectedNote.set(results[0])
}
})
function getSuggestions(query: string): ResultNote[] {
const results = plugin.minisearch
.search(query, {
prefix: true,
fuzzy: (term) => (term.length > 4 ? 0.2 : false),
combineWith: "AND",
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 for "${query}"`)
// console.log(results)
const suggestions = results.map((result) => {
let note = plugin.indexedNotes[result.id]
let basename = escapeHTML(note.basename)
let content = escapeHTML(note.content)
// 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
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 ? "…" : "")
}
// console.log(matches)
content = content.replace(reg, highlighter)
basename = basename.replace(reg, highlighter)
const resultNote: ResultNote = {
content,
basename,
path: note.path,
matches,
occurence: 0,
}
return resultNote
})
return suggestions
}
async function openNote(item: ResultNote, newPane = false): Promise<void> {
const file = plugin.app.vault.getAbstractFileByPath(item.path) as TFile
// const fileCache = this.app.metadataCache.getFileCache(file)
// console.log(fileCache)
const content = (await plugin.app.vault.cachedRead(file)).toLowerCase()
const offset = content.indexOf(
item.matches[item.occurence].match.toLowerCase()
)
await plugin.app.workspace.openLinkText(item.path, "", newPane)
const view = plugin.app.workspace.getActiveViewOfType(MarkdownView)
if (!view) {
throw new Error("OmniSearch - No active MarkdownView")
}
const pos = view.editor.offsetToPos(offset)
pos.ch = 0
view.editor.setCursor(pos)
view.editor.scrollIntoView({
from: { line: pos.line - 10, ch: 0 },
to: { line: pos.line + 10, ch: 0 },
})
}
async function createOrOpenNote(item: ResultNote): Promise<void> {
try {
const file = await plugin.app.vault.create(
$searchQuery + ".md",
"# " + $searchQuery
)
await plugin.app.workspace.openLinkText(file.path, "")
} catch (e) {
if (e instanceof Error && e.message === "File already exists.") {
// Open the existing file instead of creating it
await openNote(item)
} else {
console.error(e)
}
}
}
function onInputEnter(event: CustomEvent<ResultNote>): void {
openNote(event.detail)
modal.close()
}
function onInputCtrlEnter(event: CustomEvent<ResultNote>): void {
openNote(event.detail, true)
modal.close()
}
function onInputShiftEnter(event: CustomEvent<ResultNote>): void {
createOrOpenNote(event.detail)
modal.close()
}
</script>
<CmpInput
on:enter={onInputEnter}
on:shift-enter={onInputShiftEnter}
on:ctrl-enter={onInputCtrlEnter}
/>
<div class="prompt-results">
{#each $resultNotes as result}
<CmpNoteResult selected={result === $selectedNote} note={result} />
{/each}
</div>
<div class="prompt-instructions">
<div class="prompt-instruction">
<span class="prompt-instruction-command">↑↓</span><span>to navigate</span>
</div>
<div class="prompt-instruction">
<span class="prompt-instruction-command"></span><span>to open</span>
</div>
<div class="prompt-instruction">
<span class="prompt-instruction-command">ctrl ↵</span><span
>to open in a new pane</span
>
</div>
<div class="prompt-instruction">
<span class="prompt-instruction-command">shift ↵</span><span>to create</span
>
</div>
<div class="prompt-instruction">
<span class="prompt-instruction-command">esc</span><span>to dismiss</span>
</div>
</div>