working search
This commit is contained in:
parent
cc567d301e
commit
afed19430c
|
@ -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: [
|
||||
{
|
||||
|
|
|
@ -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!
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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} />
|
||||
</>
|
||||
)
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -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}>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
.grid {
|
||||
display: grid;
|
||||
grid-template-columns: auto 1fr;
|
||||
grid-template-columns: auto minmax(0, 1fr);
|
||||
}
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -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} />
|
||||
|
|
|
@ -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
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -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) {
|
||||
|
|
|
@ -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>
|
||||
)
|
||||
}
|
|
@ -12,6 +12,7 @@ const ITEM_SEARCH_FIELDS = gql`
|
|||
title
|
||||
text
|
||||
url
|
||||
userId
|
||||
user {
|
||||
name
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue