related items
This commit is contained in:
parent
9c5937b9be
commit
760b6b6e10
|
@ -1,8 +1,96 @@
|
|||
import { decodeCursor, LIMIT, nextCursorEncoded } from '../../lib/cursor'
|
||||
import { getItem } from './item'
|
||||
|
||||
const STOP_WORDS = ['a', 'an', 'and', 'are', 'as', 'at', 'be', 'but',
|
||||
'by', 'for', 'if', 'in', 'into', 'is', 'it', 'no', 'not',
|
||||
'of', 'on', 'or', 'such', 'that', 'the', 'their', 'then',
|
||||
'there', 'these', 'they', 'this', 'to', 'was', 'will',
|
||||
'with', 'bitcoin', 'page', 'adds', 'how', 'why', 'what',
|
||||
'works', 'now', 'available', 'breaking', 'app', 'powered',
|
||||
'just', 'dev', 'using', 'crypto', 'has', 'my', 'i', 'apps',
|
||||
'really', 'new', 'era', 'application', 'best', 'year',
|
||||
'latest', 'still', 'few', 'crypto', 'keep', 'public', 'current',
|
||||
'levels', 'from', 'cryptocurrencies', 'confirmed', 'news', 'network',
|
||||
'about', 'sources', 'vote', 'considerations', 'hope',
|
||||
'keep', 'keeps', 'including', 'we', 'brings', "don't", 'do',
|
||||
'interesting', 'us']
|
||||
|
||||
export default {
|
||||
Query: {
|
||||
related: async (parent, { title, id, cursor, limit }, { me, models, search }) => {
|
||||
const decodedCursor = decodeCursor(cursor)
|
||||
if (!title || title.trim().split(/\s+/).length < 1) {
|
||||
if (id) {
|
||||
const item = await getItem(parent, { id }, { me, models })
|
||||
title = item?.title
|
||||
}
|
||||
if (!title) {
|
||||
return {
|
||||
items: [],
|
||||
cursor: null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
const mustNot = []
|
||||
if (id) {
|
||||
mustNot.push({ term: { id } })
|
||||
}
|
||||
|
||||
let items = await search.search({
|
||||
index: 'item',
|
||||
size: limit || LIMIT,
|
||||
from: decodedCursor.offset,
|
||||
body: {
|
||||
query: {
|
||||
bool: {
|
||||
should: [
|
||||
{
|
||||
more_like_this: {
|
||||
fields: ['title'],
|
||||
like: title,
|
||||
min_term_freq: 1,
|
||||
min_doc_freq: 1,
|
||||
max_query_terms: 25,
|
||||
min_word_length: 2,
|
||||
minimum_should_match: '50%',
|
||||
stop_words: STOP_WORDS,
|
||||
boost: 400
|
||||
}
|
||||
},
|
||||
{
|
||||
more_like_this: {
|
||||
fields: ['title'],
|
||||
like: title,
|
||||
min_term_freq: 1,
|
||||
min_doc_freq: 1,
|
||||
min_word_length: 2,
|
||||
max_query_terms: 25,
|
||||
minimum_should_match: '30%',
|
||||
stop_words: STOP_WORDS
|
||||
}
|
||||
}
|
||||
],
|
||||
must_not: [{ exists: { field: 'parentId' } }, ...mustNot],
|
||||
filter: {
|
||||
range: { sats: { gte: 10 } }
|
||||
}
|
||||
}
|
||||
},
|
||||
sort: ['_score', { sats: 'desc' }]
|
||||
}
|
||||
})
|
||||
|
||||
items = items.body.hits.hits.map(async e => {
|
||||
// this is super inefficient but will suffice until we do something more generic
|
||||
return await getItem(parent, { id: e._source.id }, { me, models })
|
||||
})
|
||||
|
||||
return {
|
||||
cursor: items.length === (limit || LIMIT) ? nextCursorEncoded(decodedCursor) : null,
|
||||
items
|
||||
}
|
||||
},
|
||||
search: async (parent, { q: query, sub, cursor, sort, what, when }, { me, models, search }) => {
|
||||
const decodedCursor = decodeCursor(cursor)
|
||||
let sitems
|
||||
|
|
|
@ -8,6 +8,7 @@ export default gql`
|
|||
comments(id: ID!, sort: String): [Item!]!
|
||||
pageTitle(url: String!): String
|
||||
dupes(url: String!): [Item!]
|
||||
related(cursor: String, title: String, id: ID, limit: Int): Items
|
||||
allItems(cursor: String): Items
|
||||
search(q: String, sub: String, cursor: String, what: String, sort: String, when: String): Items
|
||||
auctionPosition(sub: String, id: ID, bid: Int!): Int!
|
||||
|
|
|
@ -14,6 +14,7 @@ import useDarkMode from 'use-dark-mode'
|
|||
import { useEffect, useState } from 'react'
|
||||
import Poll from './poll'
|
||||
import { commentsViewed } from '../lib/new-comments'
|
||||
import Related from './related'
|
||||
|
||||
function BioItem ({ item, handleClick }) {
|
||||
const me = useMe()
|
||||
|
@ -90,7 +91,11 @@ function TopLevelItem ({ item, noReply, ...props }) {
|
|||
{item.text && <ItemText item={item} />}
|
||||
{item.url && <ItemEmbed item={item} />}
|
||||
{item.poll && <Poll item={item} />}
|
||||
{!noReply && <Reply item={item} replyOpen />}
|
||||
{!noReply &&
|
||||
<>
|
||||
<Reply item={item} replyOpen />
|
||||
{!item.position && !item.isJob && !item.parentId && <Related title={item.title} itemId={item.id} />}
|
||||
</>}
|
||||
</ItemComponent>
|
||||
)
|
||||
}
|
||||
|
|
|
@ -0,0 +1,38 @@
|
|||
import { useQuery } from '@apollo/client'
|
||||
import Link from 'next/link'
|
||||
import { RELATED_ITEMS } from '../fragments/items'
|
||||
import AccordianItem from './accordian-item'
|
||||
import Item, { ItemSkeleton } from './item'
|
||||
import styles from './items.module.css'
|
||||
|
||||
export default function Related ({ title, itemId }) {
|
||||
const emptyItems = new Array(5).fill(null)
|
||||
const { data, loading } = useQuery(RELATED_ITEMS, {
|
||||
fetchPolicy: 'cache-first',
|
||||
variables: { title, id: itemId, limit: 5 }
|
||||
})
|
||||
|
||||
let items, cursor
|
||||
if (data) {
|
||||
({ related: { items, cursor } } = data)
|
||||
}
|
||||
|
||||
return (
|
||||
<AccordianItem
|
||||
header={<div className='font-weight-bold'>related</div>}
|
||||
body={
|
||||
<>
|
||||
<div className={styles.grid}>
|
||||
{loading
|
||||
? emptyItems.map((_, i) => <ItemSkeleton key={i} />)
|
||||
: (items?.length
|
||||
? items.map(item => <Item key={item.id} item={item} />)
|
||||
: <div className='text-muted' style={{ fontFamily: 'lightning', fontSize: '2rem', opacity: '0.75' }}>EMPTY</div>
|
||||
)}
|
||||
</div>
|
||||
{cursor && itemId && <Link href={`/items/${itemId}/related`} passHref><a className='text-reset text-muted font-weight-bold'>view all related</a></Link>}
|
||||
</>
|
||||
}
|
||||
/>
|
||||
)
|
||||
}
|
|
@ -204,3 +204,30 @@ export const ITEM_SEARCH = gql`
|
|||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const RELATED_ITEMS = gql`
|
||||
${ITEM_FIELDS}
|
||||
query Related($title: String, $id: ID, $cursor: String, $limit: Int) {
|
||||
related(title: $title, id: $id, cursor: $cursor, limit: $limit) {
|
||||
cursor
|
||||
items {
|
||||
...ItemFields
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
||||
export const RELATED_ITEMS_WITH_ITEM = gql`
|
||||
${ITEM_FIELDS}
|
||||
query Related($title: String, $id: ID, $cursor: String, $limit: Int) {
|
||||
item(id: $id) {
|
||||
...ItemFields
|
||||
}
|
||||
related(title: $title, id: $id, cursor: $cursor, limit: $limit) {
|
||||
cursor
|
||||
items {
|
||||
...ItemFields
|
||||
}
|
||||
}
|
||||
}
|
||||
`
|
||||
|
|
|
@ -79,6 +79,19 @@ export default function getApolloClient () {
|
|||
}
|
||||
}
|
||||
},
|
||||
related: {
|
||||
keyArgs: ['id', 'title', 'limit'],
|
||||
merge (existing, incoming) {
|
||||
if (isFirstPage(incoming.cursor, existing?.items)) {
|
||||
return incoming
|
||||
}
|
||||
|
||||
return {
|
||||
cursor: incoming.cursor,
|
||||
items: [...(existing?.items || []), ...incoming.items]
|
||||
}
|
||||
}
|
||||
},
|
||||
outlawedItems: {
|
||||
keyArgs: [],
|
||||
merge (existing, incoming) {
|
||||
|
|
|
@ -0,0 +1,28 @@
|
|||
// ssr the related query with an adequate limit
|
||||
// need to use a cursor on related
|
||||
import { RELATED_ITEMS, RELATED_ITEMS_WITH_ITEM } from '../../../fragments/items'
|
||||
import { getGetServerSideProps } from '../../../api/ssrApollo'
|
||||
import Items from '../../../components/items'
|
||||
import Layout from '../../../components/layout'
|
||||
import { useRouter } from 'next/router'
|
||||
import Item from '../../../components/item'
|
||||
|
||||
export const getServerSideProps = getGetServerSideProps(RELATED_ITEMS_WITH_ITEM, null,
|
||||
data => !data.item)
|
||||
|
||||
export default function Related ({ data: { item, related: { items, cursor } } }) {
|
||||
const router = useRouter()
|
||||
|
||||
return (
|
||||
<Layout>
|
||||
<Item item={item} />
|
||||
<div className='font-weight-bold my-2'>related</div>
|
||||
<Items
|
||||
items={items} cursor={cursor}
|
||||
query={RELATED_ITEMS}
|
||||
destructureData={data => data.related}
|
||||
variables={{ id: router.query.id }}
|
||||
/>
|
||||
</Layout>
|
||||
)
|
||||
}
|
Loading…
Reference in New Issue