refactor string matching (#649)

This commit is contained in:
Sinclair Chen 2022-07-15 14:16:00 -07:00 committed by GitHub
parent 38c26f8b5c
commit 2543bdcdfc
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 52 additions and 53 deletions

View File

@ -49,6 +49,16 @@ export function parseWordsAsTags(text: string) {
return parseTags(taggedText) return parseTags(taggedText)
} }
// TODO: fuzzy matching
export const wordIn = (word: string, corpus: string) =>
corpus.toLocaleLowerCase().includes(word.toLocaleLowerCase())
const checkAgainstQuery = (query: string, corpus: string) =>
query.split(' ').every((word) => wordIn(word, corpus))
export const searchInAny = (query: string, ...fields: string[]) =>
fields.some((field) => checkAgainstQuery(query, field))
// can't just do [StarterKit, Image...] because it doesn't work with cjs imports // can't just do [StarterKit, Image...] because it doesn't work with cjs imports
export const exhibitExts = [ export const exhibitExts = [
Blockquote, Blockquote,

View File

@ -7,6 +7,7 @@ import { Menu, Transition } from '@headlessui/react'
import { Avatar } from 'web/components/avatar' import { Avatar } from 'web/components/avatar'
import { Row } from 'web/components/layout/row' import { Row } from 'web/components/layout/row'
import { UserLink } from 'web/components/user-page' import { UserLink } from 'web/components/user-page'
import { searchInAny } from 'common/util/parse'
export function FilterSelectUsers(props: { export function FilterSelectUsers(props: {
setSelectedUsers: (users: User[]) => void setSelectedUsers: (users: User[]) => void
@ -35,8 +36,7 @@ export function FilterSelectUsers(props: {
return ( return (
!selectedUsers.map((user) => user.name).includes(user.name) && !selectedUsers.map((user) => user.name).includes(user.name) &&
!ignoreUserIds.includes(user.id) && !ignoreUserIds.includes(user.id) &&
(user.name.toLowerCase().includes(query.toLowerCase()) || searchInAny(query, user.name, user.username)
user.username.toLowerCase().includes(query.toLowerCase()))
) )
}) })
) )

View File

@ -11,6 +11,7 @@ import { CreateGroupButton } from 'web/components/groups/create-group-button'
import { useState } from 'react' import { useState } from 'react'
import { useMemberGroups } from 'web/hooks/use-group' import { useMemberGroups } from 'web/hooks/use-group'
import { User } from 'common/user' import { User } from 'common/user'
import { searchInAny } from 'common/util/parse'
export function GroupSelector(props: { export function GroupSelector(props: {
selectedGroup?: Group selectedGroup?: Group
@ -22,14 +23,10 @@ export function GroupSelector(props: {
const [isCreatingNewGroup, setIsCreatingNewGroup] = useState(false) const [isCreatingNewGroup, setIsCreatingNewGroup] = useState(false)
const [query, setQuery] = useState('') const [query, setQuery] = useState('')
const memberGroups = useMemberGroups(creator?.id) const memberGroups = useMemberGroups(creator?.id) ?? []
const filteredGroups = memberGroups const filteredGroups = memberGroups.filter((group) =>
? query === '' searchInAny(query, group.name)
? memberGroups )
: memberGroups.filter((group) => {
return group.name.toLowerCase().includes(query.toLowerCase())
})
: []
if (!showSelector || !creator) { if (!showSelector || !creator) {
return ( return (

View File

@ -16,11 +16,6 @@ export type Sort =
| 'resolve-date' | 'resolve-date'
| 'last-updated' | 'last-updated'
export function checkAgainstQuery(query: string, corpus: string) {
const queryWords = query.toLowerCase().split(' ')
return queryWords.every((word) => corpus.toLowerCase().includes(word))
}
export function getSavedSort() { export function getSavedSort() {
// TODO: this obviously doesn't work with SSR, common sense would suggest // TODO: this obviously doesn't work with SSR, common sense would suggest
// that we should save things like this in cookies so the server has them // that we should save things like this in cookies so the server has them

View File

@ -20,6 +20,7 @@ import { manaToUSD } from 'common/util/format'
import { quadraticMatches } from 'common/quadratic-funding' import { quadraticMatches } from 'common/quadratic-funding'
import { Txn } from 'common/txn' import { Txn } from 'common/txn'
import { useTracking } from 'web/hooks/use-tracking' import { useTracking } from 'web/hooks/use-tracking'
import { searchInAny } from 'common/util/parse'
export async function getStaticProps() { export async function getStaticProps() {
const txns = await getAllCharityTxns() const txns = await getAllCharityTxns()
@ -88,10 +89,12 @@ export default function Charity(props: {
() => () =>
charities.filter( charities.filter(
(charity) => (charity) =>
charity.name.toLowerCase().includes(query.toLowerCase()) || searchInAny(
charity.preview.toLowerCase().includes(query.toLowerCase()) || query,
charity.description.toLowerCase().includes(query.toLowerCase()) || charity.name,
(charity.tags as string[])?.includes(query.toLowerCase()) charity.preview,
charity.description
) || (charity.tags as string[])?.includes(query.toLowerCase())
), ),
[charities, query] [charities, query]
) )

View File

@ -1,4 +1,5 @@
import { Answer } from 'common/answer' import { Answer } from 'common/answer'
import { searchInAny } from 'common/util/parse'
import { sortBy } from 'lodash' import { sortBy } from 'lodash'
import { useState } from 'react' import { useState } from 'react'
import { ContractsGrid } from 'web/components/contract/contracts-list' import { ContractsGrid } from 'web/components/contract/contracts-list'
@ -28,22 +29,14 @@ export default function ContractSearchFirestore(props: {
const [sort, setSort] = useState(initialSort || 'newest') const [sort, setSort] = useState(initialSort || 'newest')
const [query, setQuery] = useState(initialQuery) const [query, setQuery] = useState(initialQuery)
const queryWords = query.toLowerCase().split(' ') let matches = (contracts ?? []).filter((c) =>
function check(corpus: string) { searchInAny(
return queryWords.every((word) => corpus.toLowerCase().includes(word)) query,
} c.question,
c.creatorName,
let matches = (contracts ?? []).filter( c.lowercaseTags.map((tag) => `#${tag}`).join(' '),
(c) => ((c as any).answers ?? []).map((answer: Answer) => answer.text).join(' ')
check(c.question) || )
check(c.creatorName) ||
check(c.creatorUsername) ||
check(c.lowercaseTags.map((tag) => `#${tag}`).join(' ')) ||
check(
((c as any).answers ?? [])
.map((answer: Answer) => answer.text)
.join(' ')
)
) )
if (sort === 'newest') { if (sort === 'newest') {

View File

@ -40,10 +40,7 @@ import React, { useEffect, useState } from 'react'
import { GroupChat } from 'web/components/groups/group-chat' import { GroupChat } from 'web/components/groups/group-chat'
import { LoadingIndicator } from 'web/components/loading-indicator' import { LoadingIndicator } from 'web/components/loading-indicator'
import { Modal } from 'web/components/layout/modal' import { Modal } from 'web/components/layout/modal'
import { import { getSavedSort } from 'web/hooks/use-sort-and-query-params'
checkAgainstQuery,
getSavedSort,
} from 'web/hooks/use-sort-and-query-params'
import { ChoicesToggleGroup } from 'web/components/choices-toggle-group' import { ChoicesToggleGroup } from 'web/components/choices-toggle-group'
import { toast } from 'react-hot-toast' import { toast } from 'react-hot-toast'
import { useCommentsOnGroup } from 'web/hooks/use-comments' import { useCommentsOnGroup } from 'web/hooks/use-comments'
@ -56,6 +53,7 @@ import { SearchIcon } from '@heroicons/react/outline'
import { useTipTxns } from 'web/hooks/use-tip-txns' import { useTipTxns } from 'web/hooks/use-tip-txns'
import { JoinOrLeaveGroupButton } from 'web/components/groups/groups-button' import { JoinOrLeaveGroupButton } from 'web/components/groups/groups-button'
import { OnlineUserList } from 'web/components/online-user-list' import { OnlineUserList } from 'web/components/online-user-list'
import { searchInAny } from 'common/util/parse'
export const getStaticProps = fromPropz(getStaticPropz) export const getStaticProps = fromPropz(getStaticPropz)
export async function getStaticPropz(props: { params: { slugs: string[] } }) { export async function getStaticPropz(props: { params: { slugs: string[] } }) {
@ -446,9 +444,8 @@ function GroupMemberSearch(props: { members: User[]; group: Group }) {
} }
// TODO use find-active-contracts to sort by? // TODO use find-active-contracts to sort by?
const matches = sortBy(members, [(member) => member.name]).filter( const matches = sortBy(members, [(member) => member.name]).filter((m) =>
(m) => searchInAny(query, m.name, m.username)
checkAgainstQuery(query, m.name) || checkAgainstQuery(query, m.username)
) )
const matchLimit = 25 const matchLimit = 25

View File

@ -12,12 +12,12 @@ import { useUser } from 'web/hooks/use-user'
import { groupPath, listAllGroups } from 'web/lib/firebase/groups' import { groupPath, listAllGroups } from 'web/lib/firebase/groups'
import { getUser, User } from 'web/lib/firebase/users' import { getUser, User } from 'web/lib/firebase/users'
import { Tabs } from 'web/components/layout/tabs' import { Tabs } from 'web/components/layout/tabs'
import { checkAgainstQuery } from 'web/hooks/use-sort-and-query-params'
import { SiteLink } from 'web/components/site-link' import { SiteLink } from 'web/components/site-link'
import clsx from 'clsx' import clsx from 'clsx'
import { Avatar } from 'web/components/avatar' import { Avatar } from 'web/components/avatar'
import { JoinOrLeaveGroupButton } from 'web/components/groups/groups-button' import { JoinOrLeaveGroupButton } from 'web/components/groups/groups-button'
import { UserLink } from 'web/components/user-page' import { UserLink } from 'web/components/user-page'
import { searchInAny } from 'common/util/parse'
export async function getStaticProps() { export async function getStaticProps() {
const groups = await listAllGroups().catch((_) => []) const groups = await listAllGroups().catch((_) => [])
@ -71,11 +71,13 @@ export default function Groups(props: {
const matches = sortBy(groups, [ const matches = sortBy(groups, [
(group) => -1 * group.contractIds.length, (group) => -1 * group.contractIds.length,
(group) => -1 * group.memberIds.length, (group) => -1 * group.memberIds.length,
]).filter( ]).filter((g) =>
(g) => searchInAny(
checkAgainstQuery(query, g.name) || query,
checkAgainstQuery(query, g.about || '') || g.name,
checkAgainstQuery(query, creatorsDict[g.creatorId].username) g.about || '',
creatorsDict[g.creatorId].username
)
) )
const matchesOrderedByRecentActivity = sortBy(groups, [ const matchesOrderedByRecentActivity = sortBy(groups, [
@ -84,11 +86,13 @@ export default function Groups(props: {
(group.mostRecentChatActivityTime ?? (group.mostRecentChatActivityTime ??
group.mostRecentContractAddedTime ?? group.mostRecentContractAddedTime ??
group.mostRecentActivityTime), group.mostRecentActivityTime),
]).filter( ]).filter((g) =>
(g) => searchInAny(
checkAgainstQuery(query, g.name) || query,
checkAgainstQuery(query, g.about || '') || g.name,
checkAgainstQuery(query, creatorsDict[g.creatorId].username) g.about || '',
creatorsDict[g.creatorId].username
)
) )
// Not strictly necessary, but makes the "hold delete" experience less laggy // Not strictly necessary, but makes the "hold delete" experience less laggy