feat: use flexsearch for all local searches (#997)
Co-authored-by: kolaente <k@knt.li> Reviewed-on: https://kolaente.dev/vikunja/frontend/pulls/997 Reviewed-by: dpschen <dpschen@noreply.kolaente.de> Co-authored-by: konrad <k@knt.li> Co-committed-by: konrad <k@knt.li>
This commit is contained in:
parent
1fa164453c
commit
507a73e74c
11 changed files with 157 additions and 80 deletions
|
@ -30,6 +30,7 @@
|
||||||
"dompurify": "2.3.3",
|
"dompurify": "2.3.3",
|
||||||
"easymde": "2.15.0",
|
"easymde": "2.15.0",
|
||||||
"flatpickr": "4.6.9",
|
"flatpickr": "4.6.9",
|
||||||
|
"flexsearch": "^0.7.21",
|
||||||
"highlight.js": "11.3.1",
|
"highlight.js": "11.3.1",
|
||||||
"is-touch-device": "1.0.1",
|
"is-touch-device": "1.0.1",
|
||||||
"lodash.clonedeep": "4.5.0",
|
"lodash.clonedeep": "4.5.0",
|
||||||
|
@ -54,6 +55,7 @@
|
||||||
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
"@fortawesome/free-regular-svg-icons": "5.15.4",
|
||||||
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
"@fortawesome/free-solid-svg-icons": "5.15.4",
|
||||||
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
"@fortawesome/vue-fontawesome": "3.0.0-5",
|
||||||
|
"@types/flexsearch": "^0.7.2",
|
||||||
"@types/jest": "27.0.2",
|
"@types/jest": "27.0.2",
|
||||||
"@typescript-eslint/eslint-plugin": "5.3.1",
|
"@typescript-eslint/eslint-plugin": "5.3.1",
|
||||||
"@typescript-eslint/parser": "5.3.1",
|
"@typescript-eslint/parser": "5.3.1",
|
||||||
|
@ -81,8 +83,8 @@
|
||||||
"typescript": "4.4.4",
|
"typescript": "4.4.4",
|
||||||
"vite": "2.6.14",
|
"vite": "2.6.14",
|
||||||
"vite-plugin-pwa": "0.11.5",
|
"vite-plugin-pwa": "0.11.5",
|
||||||
"vue-tsc": "0.29.4",
|
|
||||||
"vite-svg-loader": "3.1.0",
|
"vite-svg-loader": "3.1.0",
|
||||||
|
"vue-tsc": "0.29.4",
|
||||||
"wait-on": "6.0.0",
|
"wait-on": "6.0.0",
|
||||||
"workbox-cli": "6.3.0"
|
"workbox-cli": "6.3.0"
|
||||||
},
|
},
|
||||||
|
|
|
@ -25,15 +25,7 @@ export default {
|
||||||
},
|
},
|
||||||
computed: {
|
computed: {
|
||||||
namespaces() {
|
namespaces() {
|
||||||
if (this.query === '') {
|
return this.$store.getters['namespaces/searchNamespace'](this.query)
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
return this.$store.state.namespaces.namespaces.filter(n => {
|
|
||||||
return !n.isArchived &&
|
|
||||||
n.id > 0 &&
|
|
||||||
n.title.toLowerCase().includes(this.query.toLowerCase())
|
|
||||||
})
|
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
|
|
|
@ -110,40 +110,32 @@ export default {
|
||||||
results() {
|
results() {
|
||||||
let lists = []
|
let lists = []
|
||||||
if (this.searchMode === SEARCH_MODE_ALL || this.searchMode === SEARCH_MODE_LISTS) {
|
if (this.searchMode === SEARCH_MODE_ALL || this.searchMode === SEARCH_MODE_LISTS) {
|
||||||
const ncache = {}
|
|
||||||
|
|
||||||
const history = getHistory()
|
|
||||||
// Puts recently visited lists at the top
|
|
||||||
const allLists = [...new Set([
|
|
||||||
...history.map(l => {
|
|
||||||
return this.$store.getters['lists/getListById'](l.id)
|
|
||||||
}),
|
|
||||||
...Object.values(this.$store.state.lists)])]
|
|
||||||
|
|
||||||
const {list} = this.parsedQuery
|
const {list} = this.parsedQuery
|
||||||
|
|
||||||
if (list === null) {
|
if (list === null) {
|
||||||
lists = []
|
lists = []
|
||||||
} else {
|
} else {
|
||||||
|
const ncache = {}
|
||||||
|
const history = getHistory()
|
||||||
|
// Puts recently visited lists at the top
|
||||||
|
const allLists = [...new Set([
|
||||||
|
...history.map(l => {
|
||||||
|
return this.$store.getters['lists/getListById'](l.id)
|
||||||
|
}),
|
||||||
|
...this.$store.getters['lists/searchList'](list),
|
||||||
|
])]
|
||||||
|
|
||||||
lists = allLists.filter(l => {
|
lists = allLists.filter(l => {
|
||||||
if (typeof l === 'undefined' || l === null) {
|
if (typeof l === 'undefined' || l === null) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
|
||||||
if (l.isArchived) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
if (typeof ncache[l.namespaceId] === 'undefined') {
|
if (typeof ncache[l.namespaceId] === 'undefined') {
|
||||||
ncache[l.namespaceId] = this.$store.getters['namespaces/getNamespaceById'](l.namespaceId)
|
ncache[l.namespaceId] = this.$store.getters['namespaces/getNamespaceById'](l.namespaceId)
|
||||||
}
|
}
|
||||||
|
|
||||||
if (ncache[l.namespaceId].isArchived) {
|
return !ncache[l.namespaceId].isArchived
|
||||||
return false
|
})
|
||||||
}
|
|
||||||
|
|
||||||
return l.title.toLowerCase().includes(list.toLowerCase())
|
|
||||||
}) ?? []
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
<template>
|
<template>
|
||||||
<multiselect
|
<multiselect
|
||||||
class="control is-expanded"
|
class="control is-expanded"
|
||||||
:loading="listSerivce.loading"
|
|
||||||
:placeholder="$t('list.search')"
|
:placeholder="$t('list.search')"
|
||||||
@search="findLists"
|
@search="findLists"
|
||||||
:search-results="foundLists"
|
:search-results="foundLists"
|
||||||
|
@ -18,7 +17,6 @@
|
||||||
</template>
|
</template>
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
import ListService from '../../../services/list'
|
|
||||||
import ListModel from '../../../models/list'
|
import ListModel from '../../../models/list'
|
||||||
import Multiselect from '@/components/input/multiselect.vue'
|
import Multiselect from '@/components/input/multiselect.vue'
|
||||||
|
|
||||||
|
@ -26,7 +24,6 @@ export default {
|
||||||
name: 'listSearch',
|
name: 'listSearch',
|
||||||
data() {
|
data() {
|
||||||
return {
|
return {
|
||||||
listSerivce: new ListService(),
|
|
||||||
list: new ListModel(),
|
list: new ListModel(),
|
||||||
foundLists: [],
|
foundLists: [],
|
||||||
}
|
}
|
||||||
|
@ -50,17 +47,8 @@ export default {
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
methods: {
|
methods: {
|
||||||
async findLists(query) {
|
findLists(query) {
|
||||||
if (query === '') {
|
this.foundLists = this.$store.getters['lists/searchList'](query)
|
||||||
this.clearAll()
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
this.foundLists = await this.listSerivce.getAll({}, {s: query})
|
|
||||||
},
|
|
||||||
|
|
||||||
clearAll() {
|
|
||||||
this.foundLists = []
|
|
||||||
},
|
},
|
||||||
|
|
||||||
select(list) {
|
select(list) {
|
||||||
|
@ -82,6 +70,6 @@ export default {
|
||||||
|
|
||||||
<style lang="scss" scoped>
|
<style lang="scss" scoped>
|
||||||
.list-namespace-title {
|
.list-namespace-title {
|
||||||
color: $grey-500;
|
color: $grey-500;
|
||||||
}
|
}
|
||||||
</style>
|
</style>
|
|
@ -1,20 +1,25 @@
|
||||||
import {filterLabelsByQuery} from './labels'
|
import {filterLabelsByQuery} from './labels'
|
||||||
|
import {createNewIndexer} from '../indexes'
|
||||||
|
|
||||||
|
const {add} = createNewIndexer('labels', ['title', 'description'])
|
||||||
|
|
||||||
describe('filter labels', () => {
|
describe('filter labels', () => {
|
||||||
const state = {
|
const state = {
|
||||||
labels: [
|
labels: {
|
||||||
{id: 1, title: 'label1'},
|
1: {id: 1, title: 'label1'},
|
||||||
{id: 2, title: 'label2'},
|
2: {id: 2, title: 'label2'},
|
||||||
{id: 3, title: 'label3'},
|
3: {id: 3, title: 'label3'},
|
||||||
{id: 4, title: 'label4'},
|
4: {id: 4, title: 'label4'},
|
||||||
{id: 5, title: 'label5'},
|
5: {id: 5, title: 'label5'},
|
||||||
{id: 6, title: 'label6'},
|
6: {id: 6, title: 'label6'},
|
||||||
{id: 7, title: 'label7'},
|
7: {id: 7, title: 'label7'},
|
||||||
{id: 8, title: 'label8'},
|
8: {id: 8, title: 'label8'},
|
||||||
{id: 9, title: 'label9'},
|
9: {id: 9, title: 'label9'},
|
||||||
],
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Object.values(state.labels).forEach(add)
|
||||||
|
|
||||||
it('should return an empty array for an empty query', () => {
|
it('should return an empty array for an empty query', () => {
|
||||||
const labels = filterLabelsByQuery(state, [], '')
|
const labels = filterLabelsByQuery(state, [], '')
|
||||||
|
|
||||||
|
|
|
@ -1,10 +1,16 @@
|
||||||
interface label {
|
import {createNewIndexer} from '../indexes'
|
||||||
|
|
||||||
|
const {search} = createNewIndexer('labels', ['title', 'description'])
|
||||||
|
|
||||||
|
export interface label {
|
||||||
id: number,
|
id: number,
|
||||||
title: string,
|
title: string,
|
||||||
}
|
}
|
||||||
|
|
||||||
interface labelState {
|
interface labelState {
|
||||||
labels: label[],
|
labels: {
|
||||||
|
[k: number]: label,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,17 +21,12 @@ interface labelState {
|
||||||
* @returns {Array}
|
* @returns {Array}
|
||||||
*/
|
*/
|
||||||
export function filterLabelsByQuery(state: labelState, labelsToHide: label[], query: string) {
|
export function filterLabelsByQuery(state: labelState, labelsToHide: label[], query: string) {
|
||||||
if (query === '') {
|
const labelIdsToHide: number[] = labelsToHide.map(({id}) => id)
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
const labelQuery = query.toLowerCase()
|
return search(query)
|
||||||
const labelIds = labelsToHide.map(({id}) => id)
|
?.filter(value => !labelIdsToHide.includes(value))
|
||||||
return Object
|
.map(id => state.labels[id])
|
||||||
.values(state.labels)
|
|| []
|
||||||
.filter(({id, title}) => {
|
|
||||||
return !labelIds.includes(id) && title.toLowerCase().includes(labelQuery)
|
|
||||||
})
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
52
src/indexes/index.ts
Normal file
52
src/indexes/index.ts
Normal file
|
@ -0,0 +1,52 @@
|
||||||
|
import {Document, SimpleDocumentSearchResultSetUnit} from 'flexsearch'
|
||||||
|
|
||||||
|
export interface withId {
|
||||||
|
id: number,
|
||||||
|
}
|
||||||
|
|
||||||
|
const indexes: { [k: string]: Document<withId> } = {}
|
||||||
|
|
||||||
|
export const createNewIndexer = (name: string, fieldsToIndex: string[]) => {
|
||||||
|
if (typeof indexes[name] === 'undefined') {
|
||||||
|
indexes[name] = new Document<withId>({
|
||||||
|
tokenize: 'full',
|
||||||
|
document: {
|
||||||
|
id: 'id',
|
||||||
|
index: fieldsToIndex,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
const index = indexes[name]
|
||||||
|
|
||||||
|
function add(item: withId) {
|
||||||
|
return index.add(item.id, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
function remove(item: withId) {
|
||||||
|
return index.remove(item.id)
|
||||||
|
}
|
||||||
|
|
||||||
|
function update(item: withId) {
|
||||||
|
return index.update(item.id, item)
|
||||||
|
}
|
||||||
|
|
||||||
|
function search(query: string | null): number[] | null {
|
||||||
|
if (query === '' || query === null) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
// @ts-ignore
|
||||||
|
return index.search(query)
|
||||||
|
?.flatMap(r => r.result)
|
||||||
|
.filter((value, index, self) => self.indexOf(value) === index)
|
||||||
|
|| null
|
||||||
|
}
|
||||||
|
|
||||||
|
return {
|
||||||
|
add,
|
||||||
|
remove,
|
||||||
|
update,
|
||||||
|
search,
|
||||||
|
}
|
||||||
|
}
|
|
@ -3,6 +3,9 @@ import {setLoading} from '@/store/helper'
|
||||||
import {success} from '@/message'
|
import {success} from '@/message'
|
||||||
import {i18n} from '@/i18n'
|
import {i18n} from '@/i18n'
|
||||||
import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels'
|
import {getLabelsByIds, filterLabelsByQuery} from '@/helpers/labels'
|
||||||
|
import {createNewIndexer} from '@/indexes'
|
||||||
|
|
||||||
|
const {add, remove, update} = createNewIndexer('labels', ['title', 'description'])
|
||||||
|
|
||||||
async function getAllLabels(page = 1) {
|
async function getAllLabels(page = 1) {
|
||||||
const labelService = new LabelService()
|
const labelService = new LabelService()
|
||||||
|
@ -26,13 +29,16 @@ export default {
|
||||||
setLabels(state, labels) {
|
setLabels(state, labels) {
|
||||||
labels.forEach(l => {
|
labels.forEach(l => {
|
||||||
state.labels[l.id] = l
|
state.labels[l.id] = l
|
||||||
|
add(l)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
setLabel(state, label) {
|
setLabel(state, label) {
|
||||||
state.labels[label.id] = label
|
state.labels[label.id] = label
|
||||||
|
update(label)
|
||||||
},
|
},
|
||||||
removeLabelById(state, label) {
|
removeLabelById(state, label) {
|
||||||
delete state.labels[label.id]
|
delete state.labels[label.id]
|
||||||
|
remove(label)
|
||||||
},
|
},
|
||||||
setLoaded(state, loaded) {
|
setLoaded(state, loaded) {
|
||||||
state.loaded = loaded
|
state.loaded = loaded
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
import ListService from '@/services/list'
|
import ListService from '@/services/list'
|
||||||
import {setLoading} from '@/store/helper'
|
import {setLoading} from '@/store/helper'
|
||||||
import {removeListFromHistory} from '@/modules/listHistory.ts'
|
import {removeListFromHistory} from '@/modules/listHistory.ts'
|
||||||
|
import {createNewIndexer} from '@/indexes'
|
||||||
|
|
||||||
|
const {add, remove, search, update} = createNewIndexer('lists', ['title', 'description'])
|
||||||
|
|
||||||
const FavoriteListsNamespace = -2
|
const FavoriteListsNamespace = -2
|
||||||
|
|
||||||
|
@ -11,14 +14,17 @@ export default {
|
||||||
mutations: {
|
mutations: {
|
||||||
setList(state, list) {
|
setList(state, list) {
|
||||||
state[list.id] = list
|
state[list.id] = list
|
||||||
|
update(list)
|
||||||
},
|
},
|
||||||
setLists(state, lists) {
|
setLists(state, lists) {
|
||||||
lists.forEach(l => {
|
lists.forEach(l => {
|
||||||
state[l.id] = l
|
state[l.id] = l
|
||||||
|
add(l)
|
||||||
})
|
})
|
||||||
},
|
},
|
||||||
removeListById(state, list) {
|
removeListById(state, list) {
|
||||||
delete state[list.id]
|
delete state[list.id]
|
||||||
|
remove(list)
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
getters: {
|
getters: {
|
||||||
|
@ -34,6 +40,13 @@ export default {
|
||||||
})
|
})
|
||||||
return typeof list === 'undefined' ? null : list
|
return typeof list === 'undefined' ? null : list
|
||||||
},
|
},
|
||||||
|
searchList: state => (query, includeArchived = false) => {
|
||||||
|
return search(query)
|
||||||
|
?.filter(value => value > 0)
|
||||||
|
.map(id => state[id])
|
||||||
|
.filter(list => list.isArchived === includeArchived)
|
||||||
|
|| []
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
toggleListFavorite(ctx, list) {
|
toggleListFavorite(ctx, list) {
|
||||||
|
@ -81,7 +94,7 @@ export default {
|
||||||
ctx.dispatch('namespaces/loadNamespacesIfFavoritesDontExist', null, {root: true})
|
ctx.dispatch('namespaces/loadNamespacesIfFavoritesDontExist', null, {root: true})
|
||||||
ctx.dispatch('namespaces/removeFavoritesNamespaceIfEmpty', null, {root: true})
|
ctx.dispatch('namespaces/removeFavoritesNamespaceIfEmpty', null, {root: true})
|
||||||
return newList
|
return newList
|
||||||
} catch(e) {
|
} catch (e) {
|
||||||
// Reset the list state to the initial one to avoid confusion for the user
|
// Reset the list state to the initial one to avoid confusion for the user
|
||||||
ctx.commit('setList', {
|
ctx.commit('setList', {
|
||||||
...list,
|
...list,
|
||||||
|
@ -103,7 +116,7 @@ export default {
|
||||||
ctx.commit('namespaces/removeListFromNamespaceById', list, {root: true})
|
ctx.commit('namespaces/removeListFromNamespaceById', list, {root: true})
|
||||||
removeListFromHistory({id: list.id})
|
removeListFromHistory({id: list.id})
|
||||||
return response
|
return response
|
||||||
} finally{
|
} finally {
|
||||||
cancel()
|
cancel()
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
|
@ -1,5 +1,8 @@
|
||||||
import NamespaceService from '../../services/namespace'
|
import NamespaceService from '../../services/namespace'
|
||||||
import {setLoading} from '@/store/helper'
|
import {setLoading} from '@/store/helper'
|
||||||
|
import {createNewIndexer} from '@/indexes'
|
||||||
|
|
||||||
|
const {add, remove, search, update} = createNewIndexer('namespaces', ['title', 'description'])
|
||||||
|
|
||||||
export default {
|
export default {
|
||||||
namespaced: true,
|
namespaced: true,
|
||||||
|
@ -9,6 +12,9 @@ export default {
|
||||||
mutations: {
|
mutations: {
|
||||||
namespaces(state, namespaces) {
|
namespaces(state, namespaces) {
|
||||||
state.namespaces = namespaces
|
state.namespaces = namespaces
|
||||||
|
namespaces.forEach(n => {
|
||||||
|
add(n)
|
||||||
|
})
|
||||||
},
|
},
|
||||||
setNamespaceById(state, namespace) {
|
setNamespaceById(state, namespace) {
|
||||||
const namespaceIndex = state.namespaces.findIndex(n => n.id === namespace.id)
|
const namespaceIndex = state.namespaces.findIndex(n => n.id === namespace.id)
|
||||||
|
@ -24,6 +30,7 @@ export default {
|
||||||
}
|
}
|
||||||
|
|
||||||
state.namespaces[namespaceIndex] = namespace
|
state.namespaces[namespaceIndex] = namespace
|
||||||
|
update(namespace)
|
||||||
},
|
},
|
||||||
setListInNamespaceById(state, list) {
|
setListInNamespaceById(state, list) {
|
||||||
for (const n in state.namespaces) {
|
for (const n in state.namespaces) {
|
||||||
|
@ -43,11 +50,13 @@ export default {
|
||||||
},
|
},
|
||||||
addNamespace(state, namespace) {
|
addNamespace(state, namespace) {
|
||||||
state.namespaces.push(namespace)
|
state.namespaces.push(namespace)
|
||||||
|
add(namespace)
|
||||||
},
|
},
|
||||||
removeNamespaceById(state, namespaceId) {
|
removeNamespaceById(state, namespaceId) {
|
||||||
for (const n in state.namespaces) {
|
for (const n in state.namespaces) {
|
||||||
if (state.namespaces[n].id === namespaceId) {
|
if (state.namespaces[n].id === namespaceId) {
|
||||||
state.namespaces.splice(n, 1)
|
state.namespaces.splice(n, 1)
|
||||||
|
remove(state.namespaces[n])
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -79,7 +88,7 @@ export default {
|
||||||
getListAndNamespaceById: state => (listId, ignorePseudoNamespaces = false) => {
|
getListAndNamespaceById: state => (listId, ignorePseudoNamespaces = false) => {
|
||||||
for (const n in state.namespaces) {
|
for (const n in state.namespaces) {
|
||||||
|
|
||||||
if(ignorePseudoNamespaces && state.namespaces[n].id < 0) {
|
if (ignorePseudoNamespaces && state.namespaces[n].id < 0) {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -97,6 +106,13 @@ export default {
|
||||||
getNamespaceById: state => namespaceId => {
|
getNamespaceById: state => namespaceId => {
|
||||||
return state.namespaces.find(({id}) => id == namespaceId) || null
|
return state.namespaces.find(({id}) => id == namespaceId) || null
|
||||||
},
|
},
|
||||||
|
searchNamespace: (state, getters) => query => {
|
||||||
|
return search(query)
|
||||||
|
?.filter(value => value > 0)
|
||||||
|
.map(getters.getNamespaceById)
|
||||||
|
.filter(n => n !== null)
|
||||||
|
|| []
|
||||||
|
},
|
||||||
},
|
},
|
||||||
actions: {
|
actions: {
|
||||||
async loadNamespaces(ctx) {
|
async loadNamespaces(ctx) {
|
||||||
|
|
10
yarn.lock
10
yarn.lock
|
@ -3168,6 +3168,11 @@
|
||||||
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f"
|
||||||
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw==
|
||||||
|
|
||||||
|
"@types/flexsearch@^0.7.2":
|
||||||
|
version "0.7.2"
|
||||||
|
resolved "https://registry.yarnpkg.com/@types/flexsearch/-/flexsearch-0.7.2.tgz#05d982d292e70fcb9fc59347a4a4f98c4ecd9e56"
|
||||||
|
integrity sha512-Nq0CSpOCyUhaF7tAXSvMtoyBMPGlhNyF+uElhIrrgSiXDmX/bnn9jUX7Us3l81Hzowb9rcgNISke0Nj+3xhd3g==
|
||||||
|
|
||||||
"@types/glob@^7.1.1":
|
"@types/glob@^7.1.1":
|
||||||
version "7.2.0"
|
version "7.2.0"
|
||||||
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
|
resolved "https://registry.yarnpkg.com/@types/glob/-/glob-7.2.0.tgz#bc1b5bf3aa92f25bd5dd39f35c57361bdce5b2eb"
|
||||||
|
@ -7121,6 +7126,11 @@ flatten@^1.0.2:
|
||||||
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
resolved "https://registry.yarnpkg.com/flatten/-/flatten-1.0.3.tgz#c1283ac9f27b368abc1e36d1ff7b04501a30356b"
|
||||||
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
integrity sha512-dVsPA/UwQ8+2uoFe5GHtiBMu48dWLTdsuEd7CKGlZlD78r1TTWBvDuFaFGKCo/ZfEr95Uk56vZoX86OsHkUeIg==
|
||||||
|
|
||||||
|
flexsearch@^0.7.21:
|
||||||
|
version "0.7.21"
|
||||||
|
resolved "https://registry.yarnpkg.com/flexsearch/-/flexsearch-0.7.21.tgz#0f5ede3f2aae67ddc351efbe3b24b69d29e9d48b"
|
||||||
|
integrity sha512-W7cHV7Hrwjid6lWmy0IhsWDFQboWSng25U3VVywpHOTJnnAZNPScog67G+cVpeX9f7yDD21ih0WDrMMT+JoaYg==
|
||||||
|
|
||||||
flush-write-stream@^2.0.0:
|
flush-write-stream@^2.0.0:
|
||||||
version "2.0.0"
|
version "2.0.0"
|
||||||
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-2.0.0.tgz#6f58e776154f5eefacff92a6e5a681c88ac50f7c"
|
resolved "https://registry.yarnpkg.com/flush-write-stream/-/flush-write-stream-2.0.0.tgz#6f58e776154f5eefacff92a6e5a681c88ac50f7c"
|
||||||
|
|
Loading…
Reference in a new issue