chore(git): merge pull request #215 from issues/212-improved-search into staging
All checks were successful
Release to Staging / build_and_release (push) Successful in 1m6s

Reviewed-on: #215
This commit is contained in:
enes 2025-02-04 09:27:09 +00:00
commit 0f16de1750
4 changed files with 108 additions and 23 deletions

View File

@ -5,11 +5,14 @@ import { Game } from 'types'
import { log, LogType } from 'utils' import { log, LogType } from 'utils'
import gameFiles from '../utils/games' import gameFiles from '../utils/games'
let cachedGamesData: Game[] | null = null
export const useGames = () => { export const useGames = () => {
const hasProcessedFiles = useRef(false) const hasProcessedFiles = useRef(false)
const [games, setGames] = useState<Game[]>([]) const [games, setGames] = useState<Game[]>(cachedGamesData || [])
useEffect(() => { useEffect(() => {
if (cachedGamesData) return
if (hasProcessedFiles.current) return if (hasProcessedFiles.current) return
hasProcessedFiles.current = true hasProcessedFiles.current = true
@ -52,6 +55,7 @@ export const useGames = () => {
}) })
await Promise.all(promises) await Promise.all(promises)
cachedGamesData = uniqueGames
setGames(uniqueGames) setGames(uniqueGames)
} catch (err) { } catch (err) {
log( log(

View File

@ -9,7 +9,7 @@ import '../../styles/pagination.css'
import '../../styles/search.css' import '../../styles/search.css'
import '../../styles/styles.css' import '../../styles/styles.css'
import { PaginationWithPageNumbers } from 'components/Pagination' import { PaginationWithPageNumbers } from 'components/Pagination'
import { scrollIntoView } from 'utils' import { normalizeSearchString, scrollIntoView } from 'utils'
import { LoadingSpinner } from 'components/LoadingSpinner' import { LoadingSpinner } from 'components/LoadingSpinner'
import { Filter } from 'components/Filters' import { Filter } from 'components/Filters'
import { Dropdown } from 'components/Filters/Dropdown' import { Dropdown } from 'components/Filters/Dropdown'
@ -63,15 +63,17 @@ export const BlogsPage = () => {
} }
let filtered = blogs?.filter(filterNsfwFn) || [] let filtered = blogs?.filter(filterNsfwFn) || []
const lowerCaseSearchTerm = searchTerm.toLowerCase() const normalizedSearchTerm = normalizeSearchString(searchTerm)
if (searchTerm !== '') { if (normalizedSearchTerm !== '') {
const filterSearchTermFn = (blog: Partial<BlogCardDetails>) => const filterSearchTermFn = (blog: Partial<BlogCardDetails>) =>
(blog.title || '').toLowerCase().includes(lowerCaseSearchTerm) || normalizeSearchString(blog.title || '').includes(
(blog.summary || '').toLowerCase().includes(lowerCaseSearchTerm) || normalizedSearchTerm
(blog.content || '').toLowerCase().includes(lowerCaseSearchTerm) || ) ||
(blog.summary || '').toLowerCase().includes(normalizedSearchTerm) ||
(blog.content || '').toLowerCase().includes(normalizedSearchTerm) ||
(blog.tTags || []).findIndex((tag) => (blog.tTags || []).findIndex((tag) =>
tag.toLowerCase().includes(lowerCaseSearchTerm) tag.toLowerCase().includes(normalizedSearchTerm)
) > -1 ) > -1
filtered = filtered.filter(filterSearchTermFn) filtered = filtered.filter(filterSearchTermFn)
} }

View File

@ -36,6 +36,9 @@ import {
DEFAULT_FILTER_OPTIONS, DEFAULT_FILTER_OPTIONS,
extractModData, extractModData,
isModDataComplete, isModDataComplete,
memoizedNormalizeSearchString,
normalizeSearchString,
normalizeUserSearchString,
scrollIntoView scrollIntoView
} from 'utils' } from 'utils'
import { useCuratedSet } from 'hooks/useCuratedSet' import { useCuratedSet } from 'hooks/useCuratedSet'
@ -293,18 +296,17 @@ const ModsResult = ({
}, [searchTerm]) }, [searchTerm])
const filteredMods = useMemo(() => { const filteredMods = useMemo(() => {
const normalizedSearchTerm = normalizeSearchString(searchTerm)
// Search page requires search term // Search page requires search term
if (searchTerm === '') return [] if (normalizedSearchTerm === '') return []
const lowerCaseSearchTerm = searchTerm.toLowerCase()
const filterFn = (mod: ModDetails) => const filterFn = (mod: ModDetails) =>
mod.title.toLowerCase().includes(lowerCaseSearchTerm) || normalizeSearchString(mod.title).includes(normalizedSearchTerm) ||
mod.game.toLowerCase().includes(lowerCaseSearchTerm) || memoizedNormalizeSearchString(mod.game).includes(normalizedSearchTerm) ||
mod.summary.toLowerCase().includes(lowerCaseSearchTerm) || mod.summary.toLowerCase().includes(normalizedSearchTerm) ||
mod.body.toLowerCase().includes(lowerCaseSearchTerm) || mod.body.toLowerCase().includes(normalizedSearchTerm) ||
mod.tags.findIndex((tag) => mod.tags.findIndex((tag) =>
tag.toLowerCase().includes(lowerCaseSearchTerm) tag.toLowerCase().includes(normalizedSearchTerm)
) > -1 ) > -1
const filterSourceFn = (mod: ModDetails) => { const filterSourceFn = (mod: ModDetails) => {
@ -377,13 +379,14 @@ const UsersResult = ({
const userState = useAppSelector((state) => state.user) const userState = useAppSelector((state) => state.user)
useEffect(() => { useEffect(() => {
if (searchTerm === '') { const normalizedSearchTerm = normalizeUserSearchString(searchTerm)
if (normalizedSearchTerm === '') {
setProfiles([]) setProfiles([])
} else { } else {
const sub = ndk.subscribe( const sub = ndk.subscribe(
{ {
kinds: [NDKKind.Metadata], kinds: [NDKKind.Metadata],
search: searchTerm search: normalizedSearchTerm
}, },
{ {
closeOnEose: true, closeOnEose: true,
@ -395,7 +398,7 @@ const UsersResult = ({
// Stop the sub after 10 seconds if we are still searching the same term as before // Stop the sub after 10 seconds if we are still searching the same term as before
window.setTimeout(() => { window.setTimeout(() => {
if (sub.filter.search === searchTerm) { if (sub.filter.search === normalizedSearchTerm) {
sub.stop() sub.stop()
} }
}, 10000) }, 10000)
@ -500,12 +503,13 @@ const GamesResult = ({ searchTerm }: GamesResultProps) => {
}, [searchTerm]) }, [searchTerm])
const filteredGames = useMemo(() => { const filteredGames = useMemo(() => {
if (searchTerm === '') return [] const normalizedSearchTerm = normalizeSearchString(searchTerm)
if (normalizedSearchTerm === '') return []
const lowerCaseSearchTerm = searchTerm.toLowerCase()
return games.filter((game) => return games.filter((game) =>
game['Game Name'].toLowerCase().includes(lowerCaseSearchTerm) memoizedNormalizeSearchString(game['Game Name']).includes(
normalizedSearchTerm
)
) )
}, [searchTerm, games]) }, [searchTerm, games])

View File

@ -198,3 +198,78 @@ export function adjustTextareaHeight(textarea: HTMLTextAreaElement) {
textarea.style.height = 'auto' textarea.style.height = 'auto'
textarea.style.height = `${textarea.scrollHeight}px` textarea.style.height = `${textarea.scrollHeight}px`
} }
// Normalizing search terms
const removeAccents = (str: string): string => {
return str.normalize('NFD').replace(/[\u0300-\u036f]/g, '')
}
const removeSpecialCharacters = (str: string): string => {
return str.replace(/[.,/#!$%^&*;:{}=\-_`~()&\s]/g, '')
}
// Replace Roman numerals with their Arabic counterparts
const ROMAN_TO_ARABIC_MAP: { [key: string]: string } = {
i: '1',
ii: '2',
iii: '3',
iv: '4',
v: '5',
vi: '6',
vii: '7',
viii: '8',
ix: '9',
x: '10',
xi: '11',
xii: '12',
xiii: '13',
xiv: '14',
xv: '15',
xvi: '16',
xvii: '17',
xviii: '18',
xix: '19',
xx: '20'
}
const romanRegex = new RegExp(
`\\b(${Object.keys(ROMAN_TO_ARABIC_MAP).join('|')})\\b`,
'g'
)
export const normalizeSearchString = (str: string): string => {
str = str.trim()
str = str.toLowerCase()
str = str.replace(romanRegex, (match) => ROMAN_TO_ARABIC_MAP[match])
str = removeAccents(str)
str = removeSpecialCharacters(str)
return str
}
// Memoization function to cache normalized results
const memoizeNormalize = (func: (str: string) => string) => {
const cache: { [key: string]: string } = {}
return (str: string): string => {
if (cache[str] !== undefined) {
return cache[str]
}
const result = func(str)
cache[str] = result
return result
}
}
/**
* Memoize normalized search strings
* Should only be used for games (large list)
*/
export const memoizedNormalizeSearchString = memoizeNormalize(
normalizeSearchString
)
export const normalizeUserSearchString = (str: string): string => {
str = str.trim()
str = str.toLowerCase()
str = removeAccents(str)
return str
}