Stricter TS, matches counter
This commit is contained in:
@@ -23,12 +23,12 @@ function moveNoteSelection(ev: KeyboardEvent): void {
|
|||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
selectedNote.previous()
|
selectedNote.previous()
|
||||||
break
|
break
|
||||||
case "ArrowLeft":
|
// case "ArrowLeft":
|
||||||
ev.preventDefault()
|
// ev.preventDefault()
|
||||||
break
|
// break
|
||||||
case "ArrowRight":
|
// case "ArrowRight":
|
||||||
ev.preventDefault()
|
// ev.preventDefault()
|
||||||
break
|
// break
|
||||||
|
|
||||||
case "Enter":
|
case "Enter":
|
||||||
ev.preventDefault()
|
ev.preventDefault()
|
||||||
|
|||||||
@@ -16,9 +16,10 @@ import {
|
|||||||
searchQuery.subscribe(async (q) => {
|
searchQuery.subscribe(async (q) => {
|
||||||
const results = getSuggestions(q)
|
const results = getSuggestions(q)
|
||||||
resultNotes.set(results)
|
resultNotes.set(results)
|
||||||
if (results.length) {
|
const firstResult = results[0]
|
||||||
|
if (firstResult) {
|
||||||
await tick()
|
await tick()
|
||||||
selectedNote.set(results[0])
|
selectedNote.set(firstResult)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
@@ -1,16 +1,26 @@
|
|||||||
<script lang="ts">
|
<script lang="ts">
|
||||||
import type { ResultNote } from "./globals"
|
import type { ResultNote } from "./globals"
|
||||||
import { openNote } from "./notes"
|
import { openNote } from "./notes"
|
||||||
|
import { getMatches } from "./search";
|
||||||
import { modal, selectedNote } from "./stores"
|
import { modal, selectedNote } from "./stores"
|
||||||
import { escapeHTML, escapeRegex, getAllIndices, highlighter } from "./utils"
|
import { escapeHTML, highlighter, stringsToRegex } from "./utils"
|
||||||
|
|
||||||
export let selected = false
|
export let selected = false
|
||||||
export let note: ResultNote
|
export let note: ResultNote
|
||||||
|
|
||||||
|
// function getMatches(text: string) {
|
||||||
|
// let match: RegExpExecArray | null = null
|
||||||
|
// const matches: { term: string; index: number }[] = []
|
||||||
|
// while (null !== (match = reg.exec(text))) {
|
||||||
|
// matches.push({ term: match[0], index: match.index })
|
||||||
|
// }
|
||||||
|
// return matches
|
||||||
|
// }
|
||||||
|
|
||||||
function cleanContent(content: string): string {
|
function cleanContent(content: string): string {
|
||||||
const pos = content.toLowerCase().indexOf(note.searchResult.terms[0])
|
const pos = note.matches[0]?.offset ?? -1
|
||||||
const surroundLen = 180
|
|
||||||
if (pos > -1) {
|
if (pos > -1) {
|
||||||
|
const surroundLen = 180
|
||||||
const from = Math.max(0, pos - surroundLen)
|
const from = Math.max(0, pos - surroundLen)
|
||||||
const to = Math.min(content.length - 1, pos + surroundLen)
|
const to = Math.min(content.length - 1, pos + surroundLen)
|
||||||
content =
|
content =
|
||||||
@@ -18,13 +28,11 @@ function cleanContent(content: string): string {
|
|||||||
content.slice(from, to).trim() +
|
content.slice(from, to).trim() +
|
||||||
(to < content.length - 1 ? "…" : "")
|
(to < content.length - 1 ? "…" : "")
|
||||||
}
|
}
|
||||||
const tmp = document.createElement("div")
|
return escapeHTML(content)
|
||||||
tmp.innerHTML = escapeHTML(content)
|
|
||||||
|
|
||||||
return tmp.textContent ?? ''
|
|
||||||
}
|
}
|
||||||
$: reg = new RegExp(note.searchResult.terms.map(escapeRegex).join('|'), 'gi')
|
|
||||||
|
|
||||||
|
$: reg = stringsToRegex(note.foundWords)
|
||||||
|
$: matches = getMatches(note.content, reg)
|
||||||
$: cleanedContent = cleanContent(note.content)
|
$: cleanedContent = cleanContent(note.content)
|
||||||
|
|
||||||
function onHover() {
|
function onHover() {
|
||||||
@@ -45,10 +53,12 @@ function onClick() {
|
|||||||
on:click={onClick}
|
on:click={onClick}
|
||||||
>
|
>
|
||||||
<span class="omnisearch-result__title">
|
<span class="omnisearch-result__title">
|
||||||
{@html note.basename.replace(reg, highlighter)}
|
<!-- {@html note.basename.replace(reg, highlighter)} -->
|
||||||
|
{@html note.basename}
|
||||||
</span>
|
</span>
|
||||||
|
|
||||||
<span class="omnisearch-result__counter">
|
<span class="omnisearch-result__counter">
|
||||||
{note.matches.length} {note.matches.length > 1 ? "matches" : "match"}
|
{matches.length} {matches.length > 1 ? "matches" : "match"}
|
||||||
</span>
|
</span>
|
||||||
<div class="omnisearch-result__body">
|
<div class="omnisearch-result__body">
|
||||||
{@html cleanedContent.replace(reg, highlighter)}
|
{@html cleanedContent.replace(reg, highlighter)}
|
||||||
|
|||||||
@@ -22,17 +22,18 @@ export type IndexedNote = {
|
|||||||
|
|
||||||
export type SearchMatch = {
|
export type SearchMatch = {
|
||||||
match: string
|
match: string
|
||||||
index: number
|
offset: number
|
||||||
}
|
}
|
||||||
export const isSearchMatch = (o: { index?: number }): o is SearchMatch => {
|
export const isSearchMatch = (o: { offset?: number }): o is SearchMatch => {
|
||||||
return o.index !== undefined
|
return o.offset !== undefined
|
||||||
}
|
}
|
||||||
|
|
||||||
export type ResultNote = {
|
export type ResultNote = {
|
||||||
searchResult: SearchResult
|
// searchResult: SearchResult
|
||||||
path: string
|
path: string
|
||||||
basename: string
|
basename: string
|
||||||
content: string
|
content: string
|
||||||
|
foundWords: string[]
|
||||||
matches: SearchMatch[]
|
matches: SearchMatch[]
|
||||||
occurence: number
|
occurence: number
|
||||||
}
|
}
|
||||||
|
|||||||
13
src/notes.ts
13
src/notes.ts
@@ -1,20 +1,17 @@
|
|||||||
import { MarkdownView, TFile } from 'obsidian'
|
import { MarkdownView } from 'obsidian'
|
||||||
import { get } from 'svelte/store'
|
import { get } from 'svelte/store'
|
||||||
import type { ResultNote } from './globals'
|
import type { ResultNote } from './globals'
|
||||||
import { plugin } from './stores'
|
import { plugin } from './stores'
|
||||||
|
import { stringsToRegex } from './utils'
|
||||||
|
|
||||||
export async function openNote(
|
export async function openNote(
|
||||||
item: ResultNote,
|
item: ResultNote,
|
||||||
newPane = false,
|
newPane = false,
|
||||||
): Promise<void> {
|
): Promise<void> {
|
||||||
const app = get(plugin).app
|
const app = get(plugin).app
|
||||||
// const file = app.vault.getAbstractFileByPath(item.path) as TFile
|
const reg = stringsToRegex(item.foundWords)
|
||||||
// const fileCache = app.metadataCache.getFileCache(file)
|
reg.exec(item.content)
|
||||||
// console.log(fileCache)
|
const offset = reg.lastIndex
|
||||||
const content = item.content// (await app.vault.cachedRead(file)).toLowerCase()
|
|
||||||
const offset = content.indexOf(
|
|
||||||
item.matches[item.occurence].match.toLowerCase(),
|
|
||||||
)
|
|
||||||
await app.workspace.openLinkText(item.path, '', newPane)
|
await app.workspace.openLinkText(item.path, '', newPane)
|
||||||
|
|
||||||
const view = app.workspace.getActiveViewOfType(MarkdownView)
|
const view = app.workspace.getActiveViewOfType(MarkdownView)
|
||||||
|
|||||||
@@ -1,12 +1,13 @@
|
|||||||
import { Notice, TFile, type TAbstractFile } from 'obsidian'
|
import { Notice, TFile, type TAbstractFile } from 'obsidian'
|
||||||
import MiniSearch from 'minisearch'
|
import MiniSearch from 'minisearch'
|
||||||
import type { IndexedNote, ResultNote } from './globals'
|
import type { IndexedNote, ResultNote, SearchMatch } from './globals'
|
||||||
import { indexedNotes, plugin } from './stores'
|
import { indexedNotes, plugin } from './stores'
|
||||||
import { get } from 'svelte/store'
|
import { get } from 'svelte/store'
|
||||||
import {
|
import {
|
||||||
escapeRegex,
|
escapeRegex,
|
||||||
extractHeadingsFromCache,
|
extractHeadingsFromCache,
|
||||||
getAllIndices,
|
getAllIndices,
|
||||||
|
stringsToRegex,
|
||||||
wait,
|
wait,
|
||||||
} from './utils'
|
} from './utils'
|
||||||
|
|
||||||
@@ -30,7 +31,8 @@ export async function instantiateMinisearch(): Promise<void> {
|
|||||||
}
|
}
|
||||||
for (let i = 0; i < files.length; ++i) {
|
for (let i = 0; i < files.length; ++i) {
|
||||||
if (i % 10 === 0) await wait(0)
|
if (i % 10 === 0) await wait(0)
|
||||||
await addToIndex(files[i])
|
const file = files[i]
|
||||||
|
if (file) await addToIndex(file)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (files.length > 0) {
|
if (files.length > 0) {
|
||||||
@@ -42,6 +44,16 @@ export async function instantiateMinisearch(): Promise<void> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export function getMatches(text: string, reg: RegExp): SearchMatch[] {
|
||||||
|
let match: RegExpExecArray | null = null
|
||||||
|
const matches: SearchMatch[] = []
|
||||||
|
while ((match = reg.exec(text)) !== null) {
|
||||||
|
const m = match[0]
|
||||||
|
if (m) matches.push({ match: m, offset: match.index })
|
||||||
|
}
|
||||||
|
return matches
|
||||||
|
}
|
||||||
|
|
||||||
export function getSuggestions(query: string): ResultNote[] {
|
export function getSuggestions(query: string): ResultNote[] {
|
||||||
const results = minisearch
|
const results = minisearch
|
||||||
.search(query, {
|
.search(query, {
|
||||||
@@ -65,21 +77,20 @@ export function getSuggestions(query: string): ResultNote[] {
|
|||||||
if (!note) {
|
if (!note) {
|
||||||
throw new Error(`Note "${result.id}" not indexed`)
|
throw new Error(`Note "${result.id}" not indexed`)
|
||||||
}
|
}
|
||||||
const reg = new RegExp(result.terms.map(escapeRegex).join('|'), 'gi')
|
const words = Object.keys(result.match)
|
||||||
const matches = getAllIndices(note.content, reg)
|
const matches = getMatches(note.content, stringsToRegex(words))
|
||||||
const resultNote: ResultNote = {
|
const resultNote: ResultNote = {
|
||||||
searchResult: result,
|
// searchResult: result,
|
||||||
|
foundWords: words,
|
||||||
occurence: 0,
|
occurence: 0,
|
||||||
matches,
|
matches,
|
||||||
...note,
|
...note,
|
||||||
}
|
}
|
||||||
if (note.basename === 'Search') {
|
// if (note.basename === 'Search') {
|
||||||
console.log('=======')
|
// console.log('=======')
|
||||||
// console.log([...note.content.matchAll(reg)])
|
// console.log(result)
|
||||||
// console.log(reg)
|
// console.log(resultNote)
|
||||||
console.log(result)
|
// }
|
||||||
console.log(resultNote)
|
|
||||||
}
|
|
||||||
return resultNote
|
return resultNote
|
||||||
})
|
})
|
||||||
|
|
||||||
|
|||||||
38
src/utils.ts
38
src/utils.ts
@@ -21,38 +21,6 @@ export function escapeHTML(html: string): string {
|
|||||||
.replaceAll("'", ''')
|
.replaceAll("'", ''')
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* The "title" line is the first line that isn't a wikilink
|
|
||||||
* @param text
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function getTitleLineIndex(lines: string[]): number {
|
|
||||||
const index = lines.findIndex(l => !regexWikilink.test(l))
|
|
||||||
return index > -1 ? index : 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the "title" line from a text
|
|
||||||
* @param text
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function getTitleLine(text: string): string {
|
|
||||||
const lines = splitLines(text.trim())
|
|
||||||
return lines[getTitleLineIndex(lines)]
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the "title" line from a text
|
|
||||||
* @param text
|
|
||||||
* @returns
|
|
||||||
*/
|
|
||||||
export function removeTitleLine(text: string): string {
|
|
||||||
const lines = splitLines(text.trim())
|
|
||||||
const index = getTitleLineIndex(lines)
|
|
||||||
lines.splice(index, 1)
|
|
||||||
return lines.join('. ')
|
|
||||||
}
|
|
||||||
|
|
||||||
export function splitLines(text: string): string[] {
|
export function splitLines(text: string): string[] {
|
||||||
return text.split(regexLineSplit).filter(l => !!l && l.length > 2)
|
return text.split(regexLineSplit).filter(l => !!l && l.length > 2)
|
||||||
}
|
}
|
||||||
@@ -82,7 +50,7 @@ export function escapeRegex(str: string): string {
|
|||||||
*/
|
*/
|
||||||
export function getAllIndices(text: string, regex: RegExp): SearchMatch[] {
|
export function getAllIndices(text: string, regex: RegExp): SearchMatch[] {
|
||||||
return [...text.matchAll(regex)]
|
return [...text.matchAll(regex)]
|
||||||
.map(o => ({ match: o[0], index: o.index }))
|
.map(o => ({ match: o[0], offset: o.index }))
|
||||||
.filter(isSearchMatch)
|
.filter(isSearchMatch)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -101,6 +69,10 @@ export function getAllIndices(text: string, regex: RegExp): SearchMatch[] {
|
|||||||
// // return uniqBy(matches, 'index')
|
// // return uniqBy(matches, 'index')
|
||||||
// }
|
// }
|
||||||
|
|
||||||
|
export function stringsToRegex(strings: string[]): RegExp {
|
||||||
|
return new RegExp(strings.map(escapeRegex).join('|'), 'gi')
|
||||||
|
}
|
||||||
|
|
||||||
export function replaceAll(
|
export function replaceAll(
|
||||||
text: string,
|
text: string,
|
||||||
terms: string[],
|
terms: string[],
|
||||||
|
|||||||
@@ -6,6 +6,7 @@
|
|||||||
"node"
|
"node"
|
||||||
],
|
],
|
||||||
"strict": true,
|
"strict": true,
|
||||||
|
"noUncheckedIndexedAccess": true,
|
||||||
"baseUrl": ".",
|
"baseUrl": ".",
|
||||||
"module": "ESNext",
|
"module": "ESNext",
|
||||||
"target": "ES6",
|
"target": "ES6",
|
||||||
|
|||||||
Reference in New Issue
Block a user