mirror of
https://github.com/hedgedoc/hedgedoc.git
synced 2025-05-23 11:37:02 -04:00
fix(deps): replace flexsearch-ts with orama
Signed-off-by: Tilman Vatteroth <git@tilmanvatteroth.de>
This commit is contained in:
parent
931ce68a32
commit
07162b7807
25 changed files with 266 additions and 215 deletions
58
frontend/src/hooks/common/use-cheatsheet-search.spec.ts
Normal file
58
frontend/src/hooks/common/use-cheatsheet-search.spec.ts
Normal file
|
@ -0,0 +1,58 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { CheatsheetSearchIndexEntry } from './use-cheatsheet-search'
|
||||
import { useCheatsheetSearch } from './use-cheatsheet-search'
|
||||
import { renderHook, waitFor } from '@testing-library/react'
|
||||
|
||||
describe('useDocumentSearch', () => {
|
||||
const searchEntries: CheatsheetSearchIndexEntry[] = [
|
||||
{
|
||||
id: 'test1',
|
||||
extensionId: 'test1',
|
||||
title: 'title1',
|
||||
description: 'description1',
|
||||
example: 'example1'
|
||||
},
|
||||
{
|
||||
id: 'test2',
|
||||
extensionId: 'test2',
|
||||
title: 'title2 sub',
|
||||
description: 'description2',
|
||||
example: 'example2'
|
||||
},
|
||||
{
|
||||
id: 'test3',
|
||||
extensionId: 'test3',
|
||||
title: 'title3 sub',
|
||||
description: 'description3',
|
||||
example: 'example3'
|
||||
}
|
||||
]
|
||||
|
||||
it('returns all entries if no search term is given', async () => {
|
||||
const { result } = renderHook((searchTerm: string) => useCheatsheetSearch(searchEntries, searchTerm), {
|
||||
initialProps: ''
|
||||
})
|
||||
|
||||
await waitFor(() => expect(result.current).toStrictEqual(searchEntries))
|
||||
})
|
||||
|
||||
it('results no entries if nothing has been found', async () => {
|
||||
const { result } = renderHook((searchTerm: string) => useCheatsheetSearch(searchEntries, searchTerm), {
|
||||
initialProps: 'Foo'
|
||||
})
|
||||
|
||||
await waitFor(() => expect(result.current).toHaveLength(0))
|
||||
})
|
||||
|
||||
it('returns multiple entries if matching', async () => {
|
||||
const { result } = renderHook((searchTerm: string) => useCheatsheetSearch(searchEntries, searchTerm), {
|
||||
initialProps: 'sub'
|
||||
})
|
||||
|
||||
await waitFor(() => expect(result.current).toHaveLength(2))
|
||||
})
|
||||
})
|
80
frontend/src/hooks/common/use-cheatsheet-search.ts
Normal file
80
frontend/src/hooks/common/use-cheatsheet-search.ts
Normal file
|
@ -0,0 +1,80 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2024 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
|
||||
import { useEffect, useState } from 'react'
|
||||
import { create, insert, search } from '@orama/orama'
|
||||
import { useAsync } from 'react-use'
|
||||
import { Logger } from '../../utils/logger'
|
||||
|
||||
export interface CheatsheetSearchIndexEntry {
|
||||
readonly id: string
|
||||
readonly title: string
|
||||
readonly description: string
|
||||
readonly example: string
|
||||
readonly extensionId: string
|
||||
}
|
||||
|
||||
const logger = new Logger('Cheatsheet Search')
|
||||
|
||||
/**
|
||||
* Generate document search index and provide functions to search.
|
||||
*
|
||||
* @param entries The list of entries to build the search index from
|
||||
* @param searchTerm What to search for
|
||||
* @return An array of the search results
|
||||
*/
|
||||
export const useCheatsheetSearch = (
|
||||
entries: CheatsheetSearchIndexEntry[],
|
||||
searchTerm: string
|
||||
): CheatsheetSearchIndexEntry[] => {
|
||||
const [results, setResults] = useState<CheatsheetSearchIndexEntry[]>([])
|
||||
|
||||
const {
|
||||
value: searchIndex,
|
||||
loading: searchIndexLoading,
|
||||
error: searchIndexError
|
||||
} = useAsync(async () => {
|
||||
const db = await create({
|
||||
schema: {
|
||||
id: 'string',
|
||||
title: 'string',
|
||||
description: 'string',
|
||||
example: 'string',
|
||||
extensionId: 'string'
|
||||
} as const
|
||||
})
|
||||
const adds = entries.map((entry) => {
|
||||
logger.debug('Add to search entry:', entry)
|
||||
return insert(db, entry)
|
||||
})
|
||||
await Promise.all(adds)
|
||||
return db
|
||||
}, [entries])
|
||||
|
||||
useEffect(() => {
|
||||
if (searchIndexLoading || searchIndexError !== undefined || searchIndex === undefined || searchTerm === '') {
|
||||
return setResults(entries)
|
||||
}
|
||||
search(searchIndex, {
|
||||
term: searchTerm,
|
||||
tolerance: 1,
|
||||
properties: ['title', 'description', 'example'],
|
||||
boost: {
|
||||
title: 3,
|
||||
description: 2,
|
||||
example: 1
|
||||
}
|
||||
})
|
||||
.then((results) => {
|
||||
setResults(results.hits.map((entry) => entry.document))
|
||||
})
|
||||
.catch((error) => {
|
||||
logger.error(error)
|
||||
})
|
||||
}, [entries, searchIndexError, searchIndexLoading, searchIndex, searchTerm])
|
||||
|
||||
return results
|
||||
}
|
|
@ -1,64 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { SearchIndexEntry } from './use-document-search'
|
||||
import { useDocumentSearch } from './use-document-search'
|
||||
import { renderHook } from '@testing-library/react'
|
||||
|
||||
describe('useDocumentSearch', () => {
|
||||
interface TestSearchIndexEntry extends SearchIndexEntry {
|
||||
name: string
|
||||
text: string
|
||||
}
|
||||
|
||||
const searchOptions = {
|
||||
document: {
|
||||
id: 'id',
|
||||
field: ['name', 'text']
|
||||
}
|
||||
}
|
||||
const searchEntries: TestSearchIndexEntry[] = [
|
||||
{
|
||||
id: 1,
|
||||
name: 'Foo',
|
||||
text: 'Lorem ipsum dolor sit amet, consectetur adipiscing elit. Aenean fermentum odio in bibendum venenatis. Cras aliquet ultrices finibus. Lorem ipsum dolor sit amet, consectetur adipiscing elit.'
|
||||
},
|
||||
{
|
||||
id: 2,
|
||||
name: 'Bar',
|
||||
text: 'Vivamus sed mauris eget magna sodales blandit. Aliquam tincidunt nunc et sapien scelerisque placerat. Pellentesque a orci ac risus molestie suscipit id vel arcu.'
|
||||
},
|
||||
{
|
||||
id: 3,
|
||||
name: 'Cras',
|
||||
text: 'Cras consectetur sit amet tortor eget sollicitudin. Ut convallis orci ipsum, eget dignissim nibh dignissim eget. Nunc commodo est neque, eget venenatis urna condimentum eget. Suspendisse dapibus ligula et enim venenatis hendrerit. '
|
||||
}
|
||||
]
|
||||
it('results get populated', () => {
|
||||
const { result, rerender } = renderHook(
|
||||
(searchTerm: string) => useDocumentSearch(searchEntries, searchOptions, searchTerm),
|
||||
{
|
||||
initialProps: ''
|
||||
}
|
||||
)
|
||||
rerender('Foo')
|
||||
|
||||
expect(result.current).toHaveLength(1)
|
||||
expect(result.current[0]).toEqual({ field: 'name', result: [1] })
|
||||
})
|
||||
it('finds in multiple fields', () => {
|
||||
const { result, rerender } = renderHook(
|
||||
(searchTerm: string) => useDocumentSearch(searchEntries, searchOptions, searchTerm),
|
||||
{
|
||||
initialProps: ''
|
||||
}
|
||||
)
|
||||
rerender('Cras')
|
||||
|
||||
expect(result.current).toHaveLength(2)
|
||||
expect(result.current[0]).toEqual({ field: 'name', result: [3] })
|
||||
expect(result.current[1]).toEqual({ field: 'text', result: [3, 1] })
|
||||
})
|
||||
})
|
|
@ -1,43 +0,0 @@
|
|||
/*
|
||||
* SPDX-FileCopyrightText: 2023 The HedgeDoc developers (see AUTHORS file)
|
||||
*
|
||||
* SPDX-License-Identifier: AGPL-3.0-only
|
||||
*/
|
||||
import type { IndexOptionsForDocumentSearch, Id, SimpleDocumentSearchResultSetUnit, StoreOption } from 'flexsearch-ts'
|
||||
import { Document } from 'flexsearch-ts'
|
||||
import { useEffect, useMemo, useState } from 'react'
|
||||
|
||||
export interface SearchIndexEntry {
|
||||
id: Id
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate document search index and provide functions to search.
|
||||
*
|
||||
* @param entries The list of entries to built the search index from
|
||||
* @param options Options for the search index
|
||||
* @param searchTerm What to search for
|
||||
* @return An array of the search results
|
||||
*/
|
||||
export const useDocumentSearch = <T extends SearchIndexEntry>(
|
||||
entries: Array<T>,
|
||||
options: IndexOptionsForDocumentSearch<T, StoreOption>,
|
||||
searchTerm: string
|
||||
): SimpleDocumentSearchResultSetUnit[] => {
|
||||
const [results, setResults] = useState<SimpleDocumentSearchResultSetUnit[]>([])
|
||||
const searchIndex = useMemo(() => {
|
||||
const index = new Document<T, StoreOption>({
|
||||
tokenize: 'full',
|
||||
...options
|
||||
})
|
||||
entries.forEach((entry) => {
|
||||
index.add(entry)
|
||||
})
|
||||
return index
|
||||
}, [entries, options])
|
||||
useEffect(() => {
|
||||
setResults(searchIndex.search(searchTerm))
|
||||
}, [searchIndex, searchTerm])
|
||||
|
||||
return results
|
||||
}
|
Loading…
Add table
Add a link
Reference in a new issue