163 lines
3.9 KiB
TypeScript
163 lines
3.9 KiB
TypeScript
/* eslint-disable @typescript-eslint/restrict-template-expressions */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-return */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-call */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-member-access */
|
|
/* eslint-disable @typescript-eslint/no-unsafe-assignment */
|
|
import {
|
|
parse,
|
|
SearchParserKeyWordOffset,
|
|
SearchParserTextOffset,
|
|
} from 'search-query-parser'
|
|
import { PageType } from '../generated/graphql'
|
|
|
|
export enum ReadFilter {
|
|
ALL,
|
|
READ,
|
|
UNREAD,
|
|
}
|
|
|
|
export enum InFilter {
|
|
ALL,
|
|
INBOX,
|
|
ARCHIVE,
|
|
}
|
|
|
|
export type SearchFilter = {
|
|
query: string | undefined
|
|
inFilter: InFilter
|
|
readFilter: ReadFilter
|
|
typeFilter?: PageType | undefined
|
|
labelFilters?: string[]
|
|
}
|
|
|
|
const parseIsFilter = (str: string | undefined): ReadFilter => {
|
|
switch (str?.toUpperCase()) {
|
|
case 'READ':
|
|
return ReadFilter.READ
|
|
case 'UNREAD':
|
|
return ReadFilter.UNREAD
|
|
}
|
|
return ReadFilter.ALL
|
|
}
|
|
|
|
const parseInFilter = (
|
|
str: string | undefined,
|
|
query: string | undefined
|
|
): InFilter => {
|
|
switch (str?.toUpperCase()) {
|
|
case 'ALL':
|
|
return InFilter.ALL
|
|
case 'INBOX':
|
|
return InFilter.INBOX
|
|
case 'ARCHIVE':
|
|
return InFilter.ARCHIVE
|
|
}
|
|
return query ? InFilter.ALL : InFilter.INBOX
|
|
}
|
|
|
|
const parseTypeFilter = (str: string | undefined): PageType | undefined => {
|
|
if (str === undefined) {
|
|
return undefined
|
|
}
|
|
|
|
switch (str) {
|
|
case 'article':
|
|
return PageType.Article
|
|
case 'book':
|
|
return PageType.Book
|
|
case 'pdf':
|
|
case 'file':
|
|
return PageType.File
|
|
case 'profile':
|
|
return PageType.Profile
|
|
case 'website':
|
|
return PageType.Website
|
|
case 'unknown':
|
|
return PageType.Unknown
|
|
}
|
|
return undefined
|
|
}
|
|
|
|
const parseLabelFilters = (
|
|
str: string | undefined,
|
|
labelFilters: string[] | undefined
|
|
): string[] | undefined => {
|
|
if (str === undefined) {
|
|
return labelFilters
|
|
}
|
|
|
|
// use lower case for label names
|
|
const label = str.toLowerCase()
|
|
|
|
return labelFilters ? labelFilters.concat(label) : [label]
|
|
}
|
|
|
|
export const parseSearchQuery = (query: string | undefined): SearchFilter => {
|
|
const searchQuery = query ? query.replace(/\W\s":/g, '') : undefined
|
|
const result: SearchFilter = {
|
|
query: searchQuery,
|
|
readFilter: ReadFilter.ALL,
|
|
inFilter: searchQuery ? InFilter.ALL : InFilter.INBOX,
|
|
}
|
|
|
|
if (!searchQuery) {
|
|
return {
|
|
query: undefined,
|
|
inFilter: InFilter.INBOX,
|
|
readFilter: ReadFilter.ALL,
|
|
}
|
|
}
|
|
|
|
const parsed = parse(searchQuery, {
|
|
keywords: ['in', 'is', 'type', 'label'],
|
|
tokenize: true,
|
|
})
|
|
if (parsed.offsets) {
|
|
const texts = parsed.offsets
|
|
.filter((offset) => 'text' in offset)
|
|
.map((offset) => offset as SearchParserTextOffset)
|
|
|
|
if (texts.length > 0) {
|
|
result.query = texts
|
|
.map((offset: SearchParserTextOffset) => {
|
|
// TODO: the parser library doesn't let us accurately
|
|
// pull out quoted text, so we are just assuming
|
|
// anything with spaces is quoted.
|
|
if (offset.text.indexOf(' ') > -1) {
|
|
return `"${offset.text}"`
|
|
}
|
|
return offset.text
|
|
})
|
|
.join(' ')
|
|
} else {
|
|
result.query = undefined
|
|
}
|
|
|
|
const keywords = parsed.offsets
|
|
.filter((offset) => 'keyword' in offset)
|
|
.map((offset) => offset as SearchParserKeyWordOffset)
|
|
|
|
for (const keyword of keywords) {
|
|
switch (keyword.keyword) {
|
|
case 'in':
|
|
result.inFilter = parseInFilter(keyword.value, result.query)
|
|
break
|
|
case 'is':
|
|
result.readFilter = parseIsFilter(keyword.value)
|
|
break
|
|
case 'type':
|
|
result.typeFilter = parseTypeFilter(keyword.value)
|
|
break
|
|
case 'label':
|
|
result.labelFilters = parseLabelFilters(
|
|
keyword.value,
|
|
result.labelFilters
|
|
)
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
return result
|
|
}
|