working search

This commit is contained in:
keyan 2022-01-27 13:18:48 -06:00
parent cc567d301e
commit afed19430c
14 changed files with 170 additions and 46 deletions

View File

@ -275,7 +275,7 @@ export default {
comments: async (parent, { id, sort }, { models }) => {
return comments(models, id, sort)
},
search: async (parent, { query, cursor }, { models, search }) => {
search: async (parent, { q: query, cursor }, { models, search }) => {
const decodedCursor = decodeCursor(cursor)
const sitems = await search.search({
@ -503,7 +503,7 @@ export default {
const [{ count }] = await models.$queryRaw`
SELECT count(*)
FROM "Item"
WHERE path <@ text2ltree(${item.path}) AND id != ${item.id}`
WHERE path <@ text2ltree(${item.path}) AND id != ${Number(item.id)}`
return count || 0
},
sats: async (item, args, { models }) => {
@ -512,9 +512,9 @@ export default {
sats: true
},
where: {
itemId: item.id,
itemId: Number(item.id),
userId: {
not: item.userId
not: Number(item.userId)
},
act: {
not: 'BOOST'
@ -530,9 +530,9 @@ export default {
sats: true
},
where: {
itemId: item.id,
itemId: Number(item.id),
userId: {
not: item.userId
not: Number(item.userId)
},
act: 'VOTE'
}
@ -546,7 +546,7 @@ export default {
sats: true
},
where: {
itemId: item.id,
itemId: Number(item.id),
act: 'BOOST'
}
})
@ -561,7 +561,7 @@ export default {
sats: true
},
where: {
itemId: item.id,
itemId: Number(item.id),
userId: me.id,
OR: [
{

View File

@ -9,7 +9,7 @@ export default gql`
pageTitle(url: String!): String
dupes(url: String!): [Item!]
allItems(cursor: String): Items
search(query: String, cursor: String): Items
search(q: String, cursor: String): Items
}
type ItemActResult {
@ -49,6 +49,7 @@ export default gql`
parent: Item
root: Item
user: User!
userId: Int!
depth: Int!
mine: Boolean!
boost: Int!

View File

@ -12,6 +12,7 @@ import { useRouter } from 'next/router'
import CommentEdit from './comment-edit'
import Countdown from './countdown'
import { NOFOLLOW_LIMIT } from '../lib/constants'
import { ignoreClick } from '../lib/clicks'
function Parent ({ item, rootText }) {
const ParentFrag = () => (
@ -43,6 +44,26 @@ const truncateString = (string = '', maxLength = 140) =>
? `${string.substring(0, maxLength)} […]`
: string
export function CommentFlat ({ item, ...props }) {
const router = useRouter()
return (
<div
className='clickToContext py-2'
onClick={e => {
if (ignoreClick(e)) {
return
}
router.push({
pathname: '/items/[id]',
query: { id: item.root.id, commentId: item.id }
}, `/items/${item.root.id}`)
}}
>
<Comment item={item} {...props} />
</div>
)
}
export default function Comment ({
item, children, replyOpen, includeParent,
rootText, noComments, noReply, truncate

View File

@ -1,12 +1,9 @@
import { useQuery } from '@apollo/client'
import { MORE_FLAT_COMMENTS } from '../fragments/comments'
import Comment, { CommentSkeleton } from './comment'
import { useRouter } from 'next/router'
import { CommentFlat, CommentSkeleton } from './comment'
import MoreFooter from './more-footer'
import { ignoreClick } from '../lib/clicks'
export default function CommentsFlat ({ variables, comments, cursor, ...props }) {
const router = useRouter()
const { data, fetchMore } = useQuery(MORE_FLAT_COMMENTS, {
variables
})
@ -21,23 +18,9 @@ export default function CommentsFlat ({ variables, comments, cursor, ...props })
return (
<>
{comments.map(item => (
<div
key={item.id}
className='clickToContext py-2'
onClick={e => {
if (ignoreClick(e)) {
return
}
router.push({
pathname: '/items/[id]',
query: { id: item.root.id, commentId: item.id }
}, `/items/${item.root.id}`)
}}
>
<Comment item={item} {...props} />
</div>
))}
{comments.map(item =>
<CommentFlat key={item.id} item={item} {...props} />
)}
<MoreFooter cursor={cursor} fetchMore={fetchMore} Skeleton={CommentsFlatSkeleton} />
</>
)

View File

@ -42,12 +42,12 @@ function ItemEmbed ({ item }) {
return null
}
function TopLevelItem ({ item }) {
function TopLevelItem ({ item, noReply }) {
return (
<Item item={item}>
{item.text && <ItemText item={item} />}
{item.url && <ItemEmbed item={item} />}
<Reply parentId={item.id} replyOpen />
{!noReply && <Reply parentId={item.id} replyOpen />}
</Item>
)
}

View File

@ -109,10 +109,12 @@ export default function Item ({ item, rank, children }) {
export function ItemSkeleton ({ rank, children }) {
return (
<>
{rank &&
<div className={styles.rank}>
{rank}
</div>}
{rank
? (
<div className={styles.rank}>
{rank}
</div>)
: <div />}
<div className={`${styles.item} ${styles.skeleton}`}>
<UpVote className={styles.upvote} />
<div className={styles.hunk}>

View File

@ -4,6 +4,7 @@ import styles from './items.module.css'
import { MORE_ITEMS } from '../fragments/items'
import MoreFooter from './more-footer'
import React from 'react'
import Comment from './comment'
export default function Items ({ variables, rank, items, pins, cursor }) {
const { data, fetchMore } = useQuery(MORE_ITEMS, { variables })
@ -24,7 +25,9 @@ export default function Items ({ variables, rank, items, pins, cursor }) {
{items.map((item, i) => (
<React.Fragment key={item.id}>
{pinMap && pinMap[i + 1] && <Item item={pinMap[i + 1]} key={pinMap[i + 1].id} />}
<Item item={item} rank={rank && i + 1} key={item.id} />
{item.parentId
? <><div /><div className='pb-3'><Comment item={item} noReply includeParent /></div></>
: <Item item={item} rank={rank && i + 1} key={item.id} />}
</React.Fragment>
))}
</div>

View File

@ -1,4 +1,4 @@
.grid {
display: grid;
grid-template-columns: auto 1fr;
grid-template-columns: auto minmax(0, 1fr);
}

View File

@ -0,0 +1,50 @@
import { useQuery } from '@apollo/client'
import { ItemSkeleton } from './item'
import styles from './items.module.css'
import { ITEM_SEARCH } from '../fragments/items'
import MoreFooter from './more-footer'
import React from 'react'
import Comment from './comment'
import ItemFull from './item-full'
export default function SearchItems ({ variables, items, pins, cursor }) {
const { data, fetchMore } = useQuery(ITEM_SEARCH, { variables })
if (!data && !items) {
return <ItemsSkeleton />
}
if (data) {
({ search: { items, cursor } } = data)
}
return (
<>
<div className={styles.grid}>
{items.map((item, i) => (
<React.Fragment key={item.id}>
{item.parentId
? <><div /><div className='pb-3'><Comment item={item} noReply includeParent /></div></>
: <><div /><div className={item.text ? 'pb-3' : ''}><ItemFull item={item} noReply /></div></>}
</React.Fragment>
))}
</div>
<MoreFooter
cursor={cursor} fetchMore={fetchMore}
Skeleton={() => <ItemsSkeleton />}
/>
</>
)
}
function ItemsSkeleton () {
const items = new Array(21).fill(null)
return (
<div className={styles.grid}>
{items.map((_, i) => (
<ItemSkeleton key={i} />
))}
</div>
)
}

View File

@ -2,24 +2,42 @@ import { Button, Container } from 'react-bootstrap'
import styles from './search.module.css'
import SearchIcon from '../svgs/search-fill.svg'
import CloseIcon from '../svgs/close-line.svg'
import { useState } from 'react'
import { useEffect, useState } from 'react'
import { Form, Input, SubmitButton } from './form'
import { useRouter } from 'next/router'
export default function Search () {
const [searching, setSearching] = useState()
const [q, setQ] = useState()
const router = useRouter()
const [searching, setSearching] = useState(router.query.q)
const [q, setQ] = useState(router.query.q)
const [atBottom, setAtBottom] = useState()
useEffect(() => {
setAtBottom((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight)
window.onscroll = function (ev) {
if ((window.innerHeight + window.pageYOffset) >= document.body.offsetHeight) {
setAtBottom(true)
} else {
setAtBottom(false)
}
}
}, [])
const showSearch = atBottom || searching || router.query.q
return (
<>
<div className={`${styles.searchSection} ${searching ? styles.solid : styles.hidden}`}>
<div className={`${styles.searchSection} ${showSearch ? styles.solid : styles.hidden}`}>
<Container className={`px-sm-0 ${styles.searchContainer}`}>
{searching
{showSearch
? (
<Form
initial={{
q: ''
q: router.query.q || ''
}}
inline
className={`w-auto ${styles.active}`}
onSubmit={async ({ q }) => {
router.push(`/search?q=${q}`)
}}
>
<Input
name='q'
@ -28,10 +46,11 @@ export default function Search () {
groupClassName='mr-3 mb-0 flex-grow-1'
className='w-100'
onChange={async (formik, e) => {
setSearching(true)
setQ(e.target.value?.trim())
}}
/>
{q
{q || atBottom || router.query.q
? (
<SubmitButton variant='primary' className={styles.search}>
<SearchIcon width={22} height={22} />

View File

@ -89,3 +89,16 @@ export const ITEM_WITH_COMMENTS = gql`
...CommentsRecursive
}
}`
export const ITEM_SEARCH = gql`
${ITEM_FIELDS}
query Search($q: String!, $cursor: String) {
search(q: $q, cursor: $cursor) {
cursor
items {
...ItemFields
text
}
}
}
`

View File

@ -52,6 +52,19 @@ export default function getApolloClient () {
}
}
},
search: {
keyArgs: ['q'],
merge (existing, incoming) {
if (isFirstPage(incoming.cursor, existing?.items)) {
return incoming
}
return {
cursor: incoming.cursor,
items: [...(existing?.items || []), ...incoming.items]
}
}
},
moreFlatComments: {
keyArgs: ['name', 'sort', 'within'],
merge (existing, incoming) {

18
pages/search.js Normal file
View File

@ -0,0 +1,18 @@
import Layout from '../components/layout'
import { getGetServerSideProps } from '../api/ssrApollo'
import { ITEM_SEARCH } from '../fragments/items'
import SearchItems from '../components/search-items'
import { useRouter } from 'next/router'
export const getServerSideProps = getGetServerSideProps(ITEM_SEARCH)
export default function Index ({ data: { search: { items, cursor } } }) {
const router = useRouter()
return (
<Layout>
<SearchItems
items={items} cursor={cursor} variables={{ q: router.query?.q }}
/>
</Layout>
)
}

View File

@ -12,6 +12,7 @@ const ITEM_SEARCH_FIELDS = gql`
title
text
url
userId
user {
name
}