Feature/40 key value current folder (#218)
* #40 - Reworked Query * #40 - added a "path:" option in the query field * #40 - folder exclusion * Cleaner code
This commit is contained in:
@@ -72,8 +72,8 @@ export class Omnisearch {
|
||||
}
|
||||
private minisearch: MiniSearch
|
||||
private indexedDocuments: Map<string, number> = new Map()
|
||||
private previousResults: SearchResult[] = []
|
||||
private previousQuery: Query | null = null
|
||||
// private previousResults: SearchResult[] = []
|
||||
// private previousQuery: Query | null = null
|
||||
|
||||
constructor() {
|
||||
this.minisearch = new MiniSearch(Omnisearch.options)
|
||||
@@ -175,8 +175,8 @@ export class Omnisearch {
|
||||
options: { prefixLength: number; singleFilePath?: string }
|
||||
): Promise<SearchResult[]> {
|
||||
if (query.isEmpty()) {
|
||||
this.previousResults = []
|
||||
this.previousQuery = null
|
||||
// this.previousResults = []
|
||||
// this.previousQuery = null
|
||||
return []
|
||||
}
|
||||
|
||||
@@ -210,6 +210,22 @@ export class Omnisearch {
|
||||
})
|
||||
}
|
||||
|
||||
// Filter query results that match the path
|
||||
if (query.query.path) {
|
||||
results = results.filter(r =>
|
||||
query.query.path?.some(p =>
|
||||
(r.id as string).toLowerCase().includes(p.toLowerCase())
|
||||
)
|
||||
)
|
||||
}
|
||||
if (query.query.exclude.path) {
|
||||
results = results.filter(r =>
|
||||
!query.query.exclude.path?.some(p =>
|
||||
(r.id as string).toLowerCase().includes(p.toLowerCase())
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
// If the query does not return any result,
|
||||
// retry but with a shorter prefix limit
|
||||
if (!results.length) {
|
||||
@@ -243,9 +259,7 @@ export class Omnisearch {
|
||||
}
|
||||
|
||||
// Extract tags from the query
|
||||
const tags = query.segments
|
||||
.filter(s => s.value.startsWith('#'))
|
||||
.map(s => s.value)
|
||||
const tags = query.getTags()
|
||||
|
||||
// Put the results with tags on top
|
||||
for (const tag of tags) {
|
||||
@@ -280,14 +294,14 @@ export class Omnisearch {
|
||||
}
|
||||
|
||||
// If the search query contains exclude terms, filter out results that have them
|
||||
const exclusions = query.exclusions
|
||||
const exclusions = query.query.exclude.text
|
||||
if (exclusions.length) {
|
||||
logDebug('Filtering with exclusions')
|
||||
results = results.filter(r => {
|
||||
const content = stripMarkdownCharacters(
|
||||
documents.find(d => d.path === r.id)?.content ?? ''
|
||||
).toLowerCase()
|
||||
return exclusions.every(q => !content.includes(q.value))
|
||||
return exclusions.every(q => !content.includes(q))
|
||||
})
|
||||
}
|
||||
|
||||
@@ -298,8 +312,8 @@ export class Omnisearch {
|
||||
(result, index, arr) => arr.findIndex(t => t.id === result.id) === index
|
||||
)
|
||||
|
||||
this.previousQuery = query
|
||||
this.previousResults = results
|
||||
// this.previousQuery = query
|
||||
// this.previousResults = results
|
||||
|
||||
return results
|
||||
}
|
||||
@@ -375,16 +389,6 @@ export class Omnisearch {
|
||||
} as IndexedDocument
|
||||
}
|
||||
|
||||
// Remove '#' from tags, for highlighting
|
||||
query.segments.forEach(s => {
|
||||
s.value = s.value.replace(/^#/, '')
|
||||
})
|
||||
|
||||
// Extract tags from the query
|
||||
const tags = query.segments
|
||||
.filter(s => s.value.startsWith('#'))
|
||||
.map(s => s.value)
|
||||
|
||||
// Clean search matches that match quoted expressions,
|
||||
// and inject those expressions instead
|
||||
const foundWords = [
|
||||
@@ -393,10 +397,10 @@ export class Omnisearch {
|
||||
...Object.keys(result.match),
|
||||
|
||||
// Quoted expressions
|
||||
...query.segments.filter(s => s.exact).map(s => s.value),
|
||||
...query.getExactTerms(),
|
||||
|
||||
// Tags, starting with #
|
||||
...tags,
|
||||
...query.getTags(),
|
||||
].filter(w => w.length > 1 || /\p{Emoji}/u.test(w))
|
||||
logDebug('Matching tokens:', foundWords)
|
||||
|
||||
|
||||
@@ -1,83 +1,77 @@
|
||||
import { settings } from '../settings'
|
||||
import { removeDiacritics, stripSurroundingQuotes } from '../tools/utils'
|
||||
import { parseQuery } from '../vendor/parse-query'
|
||||
import { regexExtensions } from '../globals'
|
||||
import { removeDiacritics } from '../tools/utils'
|
||||
import { parse } from 'search-query-parser'
|
||||
|
||||
type QueryToken = {
|
||||
/**
|
||||
* The query token string value
|
||||
*/
|
||||
value: string
|
||||
const keywords = ['ext', 'path'] as const
|
||||
|
||||
/**
|
||||
* Was this token encased in quotes?
|
||||
*/
|
||||
exact: boolean
|
||||
}
|
||||
type Keywords = {
|
||||
[K in typeof keywords[number]]?: string[]
|
||||
} & { text: string[] }
|
||||
|
||||
/**
|
||||
* This class is used to parse a query string into a structured object
|
||||
*/
|
||||
export class Query {
|
||||
public segments: QueryToken[] = []
|
||||
public exclusions: QueryToken[] = []
|
||||
public extensions: string[] = []
|
||||
query: Keywords & { exclude: Keywords }
|
||||
/**
|
||||
* @deprecated
|
||||
*/
|
||||
extensions: string[] = []
|
||||
|
||||
constructor(text = '') {
|
||||
// Extract & remove extensions from the query
|
||||
this.extensions = this.extractExtensions(text)
|
||||
text = this.removeExtensions(text)
|
||||
if (settings.ignoreDiacritics) {
|
||||
text = removeDiacritics(text)
|
||||
}
|
||||
const parsed = parse(text.toLowerCase(), {
|
||||
tokenize: true,
|
||||
keywords: keywords as unknown as string[],
|
||||
}) as unknown as typeof this.query
|
||||
|
||||
if (settings.ignoreDiacritics) text = removeDiacritics(text)
|
||||
const tokens = parseQuery(text.toLowerCase(), { tokenize: true })
|
||||
this.exclusions = tokens.exclude.text
|
||||
.map(this.formatToken)
|
||||
.filter(o => !!o.value)
|
||||
this.segments = tokens.text.reduce<QueryToken[]>((prev, curr) => {
|
||||
const formatted = this.formatToken(curr)
|
||||
if (formatted.value) {
|
||||
prev.push(formatted)
|
||||
// Default values
|
||||
parsed.text = parsed.text ?? []
|
||||
parsed.exclude = parsed.exclude ?? {}
|
||||
parsed.exclude.text = parsed.exclude.text ?? []
|
||||
if (!Array.isArray(parsed.exclude.text)) {
|
||||
parsed.exclude.text = [parsed.exclude.text]
|
||||
}
|
||||
|
||||
// Make sure that all fields are string[]
|
||||
for (const k of keywords) {
|
||||
const v = parsed[k]
|
||||
if (v) {
|
||||
parsed[k] = Array.isArray(v) ? v : [v]
|
||||
}
|
||||
return prev
|
||||
}, [])
|
||||
const e = parsed.exclude[k]
|
||||
if (e) {
|
||||
parsed.exclude[k] = Array.isArray(e) ? e : [e]
|
||||
}
|
||||
}
|
||||
this.query = parsed
|
||||
this.extensions = this.query.ext ?? []
|
||||
}
|
||||
|
||||
public isEmpty(): boolean {
|
||||
return this.segments.length === 0
|
||||
for (const k of keywords) {
|
||||
if (this.query[k]?.length) {
|
||||
return false
|
||||
}
|
||||
if (this.query.text.length) {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
public segmentsToStr(): string {
|
||||
return this.segments.map(({ value }) => value).join(' ')
|
||||
return this.query.text.join(' ')
|
||||
}
|
||||
|
||||
public getTags(): string[] {
|
||||
return this.query.text.filter(o => o.startsWith('#'))
|
||||
}
|
||||
|
||||
public getTagsWithoutHashtag(): string[] {
|
||||
return this.getTags().map(o => o.replace(/^#/, ''))
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the terms that are encased in quotes
|
||||
* @returns
|
||||
*/
|
||||
public getExactTerms(): string[] {
|
||||
return this.segments.filter(({ exact }) => exact).map(({ value }) => value)
|
||||
}
|
||||
|
||||
private formatToken(str: string): QueryToken {
|
||||
const stripped = stripSurroundingQuotes(str)
|
||||
return {
|
||||
value: stripped,
|
||||
exact: stripped !== str,
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Extracts an array of extensions like ".png" from a string
|
||||
*/
|
||||
private extractExtensions(str: string): string[] {
|
||||
const extensions = (str.match(regexExtensions) ?? []).map(o => o.trim())
|
||||
if (extensions) {
|
||||
return extensions.map(ext => ext.toLowerCase())
|
||||
}
|
||||
return []
|
||||
}
|
||||
|
||||
private removeExtensions(str: string): string {
|
||||
return str.replace(regexExtensions, '')
|
||||
return this.query.text.filter(o => o.split(' ').length > 1)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user