improve comment performance
This commit is contained in:
parent
1a5d8880dd
commit
347a6a54d2
|
@ -6,7 +6,7 @@ import { getMetadata, metadataRuleSets } from 'page-metadata-parser'
|
||||||
import domino from 'domino'
|
import domino from 'domino'
|
||||||
import {
|
import {
|
||||||
BOOST_MIN, ITEM_SPAM_INTERVAL,
|
BOOST_MIN, ITEM_SPAM_INTERVAL,
|
||||||
MAX_TITLE_LENGTH, ITEM_FILTER_THRESHOLD, DONT_LIKE_THIS_COST
|
MAX_TITLE_LENGTH, ITEM_FILTER_THRESHOLD, DONT_LIKE_THIS_COST, COMMENT_DEPTH_LIMIT
|
||||||
} from '../../lib/constants'
|
} from '../../lib/constants'
|
||||||
import { msatsToSats } from '../../lib/format'
|
import { msatsToSats } from '../../lib/format'
|
||||||
import { parse } from 'tldts'
|
import { parse } from 'tldts'
|
||||||
|
@ -27,20 +27,9 @@ async function comments (me, models, id, sort, root) {
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
|
|
||||||
const flat = await models.$queryRaw(`
|
const filter = await filterClause(me, models)
|
||||||
WITH RECURSIVE base AS (
|
const [{ item_comments: comments }] = await models.$queryRaw('SELECT item_comments($1, $2, $3, $4)', Number(id), COMMENT_DEPTH_LIMIT, orderBy, filter)
|
||||||
${SELECT}, ARRAY[row_number() OVER (${orderBy}, "Item".path)] AS sort_path
|
return comments
|
||||||
FROM "Item"
|
|
||||||
WHERE "parentId" = $1
|
|
||||||
${await filterClause(me, models)}
|
|
||||||
UNION ALL
|
|
||||||
${SELECT}, p.sort_path || row_number() OVER (${orderBy}, "Item".path)
|
|
||||||
FROM base p
|
|
||||||
JOIN "Item" ON "Item"."parentId" = p.id
|
|
||||||
WHERE true
|
|
||||||
${await filterClause(me, models)})
|
|
||||||
SELECT * FROM base ORDER BY sort_path`, Number(id))
|
|
||||||
return nestComments(flat, id, root)[0]
|
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function getItem (parent, { id }, { me, models }) {
|
export async function getItem (parent, { id }, { me, models }) {
|
||||||
|
@ -1120,32 +1109,6 @@ const createItem = async (parent, { sub, title, url, text, boost, forward, bount
|
||||||
return item
|
return item
|
||||||
}
|
}
|
||||||
|
|
||||||
function nestComments (flat, parentId, root) {
|
|
||||||
const result = []
|
|
||||||
let added = 0
|
|
||||||
for (let i = 0; i < flat.length;) {
|
|
||||||
flat[i].root = root
|
|
||||||
if (!flat[i].comments) flat[i].comments = []
|
|
||||||
if (Number(flat[i].parentId) === Number(parentId)) {
|
|
||||||
result.push(flat[i])
|
|
||||||
added++
|
|
||||||
i++
|
|
||||||
} else if (result.length > 0) {
|
|
||||||
const item = result[result.length - 1]
|
|
||||||
const [nested, newAdded] = nestComments(flat.slice(i), item.id, root)
|
|
||||||
if (newAdded === 0) {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
item.comments.push(...nested)
|
|
||||||
i += newAdded
|
|
||||||
added += newAdded
|
|
||||||
} else {
|
|
||||||
break
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return [result, added]
|
|
||||||
}
|
|
||||||
|
|
||||||
// we have to do our own query because ltree is unsupported
|
// we have to do our own query because ltree is unsupported
|
||||||
export const SELECT =
|
export const SELECT =
|
||||||
`SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title,
|
`SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title,
|
||||||
|
|
|
@ -19,8 +19,11 @@ import { abbrNum } from '../lib/format'
|
||||||
import Share from './share'
|
import Share from './share'
|
||||||
import ItemInfo from './item-info'
|
import ItemInfo from './item-info'
|
||||||
import { Badge } from 'react-bootstrap'
|
import { Badge } from 'react-bootstrap'
|
||||||
|
import { RootProvider, useRoot } from './root'
|
||||||
|
|
||||||
function Parent ({ item, rootText }) {
|
function Parent ({ item, rootText }) {
|
||||||
|
const root = useRoot()
|
||||||
|
|
||||||
const ParentFrag = () => (
|
const ParentFrag = () => (
|
||||||
<>
|
<>
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
|
@ -30,20 +33,16 @@ function Parent ({ item, rootText }) {
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
|
||||||
if (!item.root) {
|
|
||||||
return <ParentFrag />
|
|
||||||
}
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{Number(item.root.id) !== Number(item.parentId) && <ParentFrag />}
|
{Number(root.id) !== Number(item.parentId) && <ParentFrag />}
|
||||||
<span> \ </span>
|
<span> \ </span>
|
||||||
<Link href={`/items/${item.root.id}`} passHref>
|
<Link href={`/items/${root.id}`} passHref>
|
||||||
<a className='text-reset'>{rootText || 'on:'} {item.root.title}</a>
|
<a className='text-reset'>{rootText || 'on:'} {root?.title}</a>
|
||||||
</Link>
|
</Link>
|
||||||
{item.root.subName &&
|
{root.subName &&
|
||||||
<Link href={`/~${item.root.subName}`}>
|
<Link href={`/~${root.subName}`}>
|
||||||
<a>{' '}<Badge className={itemStyles.newComment} variant={null}>{item.root.subName}</Badge></a>
|
<a>{' '}<Badge className={itemStyles.newComment} variant={null}>{root.subName}</Badge></a>
|
||||||
</Link>}
|
</Link>}
|
||||||
</>
|
</>
|
||||||
)
|
)
|
||||||
|
@ -76,7 +75,9 @@ export function CommentFlat ({ item, ...props }) {
|
||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<Comment item={item} {...props} />
|
<RootProvider root={item.root}>
|
||||||
|
<Comment item={item} {...props} />
|
||||||
|
</RootProvider>
|
||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -89,6 +90,7 @@ export default function Comment ({
|
||||||
const [collapse, setCollapse] = useState(false)
|
const [collapse, setCollapse] = useState(false)
|
||||||
const ref = useRef(null)
|
const ref = useRef(null)
|
||||||
const router = useRouter()
|
const router = useRouter()
|
||||||
|
const root = useRoot()
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (Number(router.query.commentId) === Number(item.id)) {
|
if (Number(router.query.commentId) === Number(item.id)) {
|
||||||
|
@ -103,9 +105,8 @@ export default function Comment ({
|
||||||
}, [item])
|
}, [item])
|
||||||
|
|
||||||
const bottomedOut = depth === COMMENT_DEPTH_LIMIT
|
const bottomedOut = depth === COMMENT_DEPTH_LIMIT
|
||||||
|
const op = root.user.name === item.user.name
|
||||||
const op = item.root?.user.name === item.user.name
|
const bountyPaid = root.bountyPaidTo?.includes(Number(item.id))
|
||||||
const bountyPaid = item.root?.bountyPaidTo?.includes(Number(item.id))
|
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
@ -126,7 +127,7 @@ export default function Comment ({
|
||||||
<>
|
<>
|
||||||
{includeParent && <Parent item={item} rootText={rootText} />}
|
{includeParent && <Parent item={item} rootText={rootText} />}
|
||||||
{bountyPaid &&
|
{bountyPaid &&
|
||||||
<ActionTooltip notForm overlayText={`${abbrNum(item.root.bounty)} sats paid`}>
|
<ActionTooltip notForm overlayText={`${abbrNum(root.bounty)} sats paid`}>
|
||||||
<BountyIcon className={`${styles.bountyIcon} ${'fill-success vertical-align-middle'}`} height={16} width={16} />
|
<BountyIcon className={`${styles.bountyIcon} ${'fill-success vertical-align-middle'}`} height={16} width={16} />
|
||||||
</ActionTooltip>}
|
</ActionTooltip>}
|
||||||
</>
|
</>
|
||||||
|
@ -177,7 +178,7 @@ export default function Comment ({
|
||||||
<div className={`${styles.children}`}>
|
<div className={`${styles.children}`}>
|
||||||
{!noReply &&
|
{!noReply &&
|
||||||
<Reply depth={depth + 1} item={item} replyOpen={replyOpen}>
|
<Reply depth={depth + 1} item={item} replyOpen={replyOpen}>
|
||||||
{item.root?.bounty && !bountyPaid && <PayBounty item={item} />}
|
{root.bounty && !bountyPaid && <PayBounty item={item} />}
|
||||||
</Reply>}
|
</Reply>}
|
||||||
{children}
|
{children}
|
||||||
<div className={`${styles.comments} ml-sm-1 ml-md-3`}>
|
<div className={`${styles.comments} ml-sm-1 ml-md-3`}>
|
||||||
|
|
|
@ -1,8 +1,8 @@
|
||||||
import React from 'react'
|
import { Component } from 'react'
|
||||||
import LayoutStatic from './layout-static'
|
import LayoutStatic from './layout-static'
|
||||||
import styles from '../styles/404.module.css'
|
import styles from '../styles/404.module.css'
|
||||||
|
|
||||||
class ErrorBoundary extends React.Component {
|
class ErrorBoundary extends Component {
|
||||||
constructor (props) {
|
constructor (props) {
|
||||||
super(props)
|
super(props)
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,7 @@ import Check from '../svgs/check-double-line.svg'
|
||||||
import Share from './share'
|
import Share from './share'
|
||||||
import Toc from './table-of-contents'
|
import Toc from './table-of-contents'
|
||||||
import Link from 'next/link'
|
import Link from 'next/link'
|
||||||
|
import { RootProvider } from './root'
|
||||||
|
|
||||||
function BioItem ({ item, handleClick }) {
|
function BioItem ({ item, handleClick }) {
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
|
@ -157,7 +158,7 @@ export default function ItemFull ({ item, bio, ...props }) {
|
||||||
}, [item.lastCommentAt])
|
}, [item.lastCommentAt])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<>
|
<RootProvider root={item.root || item}>
|
||||||
{item.parentId
|
{item.parentId
|
||||||
? <Comment topLevel item={item} replyOpen includeParent noComments {...props} />
|
? <Comment topLevel item={item} replyOpen includeParent noComments {...props} />
|
||||||
: (
|
: (
|
||||||
|
@ -171,6 +172,6 @@ export default function ItemFull ({ item, bio, ...props }) {
|
||||||
<div className={styles.comments}>
|
<div className={styles.comments}>
|
||||||
<Comments parentId={item.id} pinned={item.position} commentSats={item.commentSats} comments={item.comments} />
|
<Comments parentId={item.id} pinned={item.position} commentSats={item.commentSats} comments={item.comments} />
|
||||||
</div>}
|
</div>}
|
||||||
</>
|
</RootProvider>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,7 +1,5 @@
|
||||||
import { useRouter } from 'next/router'
|
import { Fragment } from 'react'
|
||||||
import React from 'react'
|
import { CommentFlat } from './comment'
|
||||||
import { ignoreClick } from '../lib/clicks'
|
|
||||||
import Comment from './comment'
|
|
||||||
import Item from './item'
|
import Item from './item'
|
||||||
import ItemJob from './item-job'
|
import ItemJob from './item-job'
|
||||||
import { ItemsSkeleton } from './items'
|
import { ItemsSkeleton } from './items'
|
||||||
|
@ -9,33 +7,22 @@ import styles from './items.module.css'
|
||||||
import MoreFooter from './more-footer'
|
import MoreFooter from './more-footer'
|
||||||
|
|
||||||
export default function MixedItems ({ rank, items, cursor, fetchMore }) {
|
export default function MixedItems ({ rank, items, cursor, fetchMore }) {
|
||||||
const router = useRouter()
|
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<React.Fragment key={item.id}>
|
<Fragment key={item.id}>
|
||||||
{item.parentId
|
{item.parentId
|
||||||
? (
|
? (
|
||||||
<><div />
|
<><div />
|
||||||
<div
|
<div className='pb-1 mb-1'>
|
||||||
className='pb-1 mb-1 clickToContext' onClick={e => {
|
<CommentFlat item={item} noReply includeParent clickToContext />
|
||||||
if (ignoreClick(e)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
router.push({
|
|
||||||
pathname: '/items/[id]',
|
|
||||||
query: { id: item.root.id, commentId: item.id }
|
|
||||||
}, `/items/${item.root.id}`)
|
|
||||||
}}
|
|
||||||
>
|
|
||||||
<Comment item={item} noReply includeParent clickToContext />
|
|
||||||
</div>
|
</div>
|
||||||
</>)
|
</>)
|
||||||
: (item.isJob
|
: (item.isJob
|
||||||
? <ItemJob item={item} rank={rank && i + 1} />
|
? <ItemJob item={item} rank={rank && i + 1} />
|
||||||
: <Item item={item} rank={rank && i + 1} />)}
|
: <Item item={item} rank={rank && i + 1} />)}
|
||||||
</React.Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<MoreFooter
|
<MoreFooter
|
||||||
|
|
|
@ -4,8 +4,8 @@ import ItemJob from './item-job'
|
||||||
import styles from './items.module.css'
|
import styles from './items.module.css'
|
||||||
import { ITEMS } from '../fragments/items'
|
import { ITEMS } from '../fragments/items'
|
||||||
import MoreFooter from './more-footer'
|
import MoreFooter from './more-footer'
|
||||||
import React from 'react'
|
import { Fragment } from 'react'
|
||||||
import Comment from './comment'
|
import { CommentFlat } from './comment'
|
||||||
|
|
||||||
export default function Items ({ variables = {}, query, destructureData, rank, items, pins, cursor }) {
|
export default function Items ({ variables = {}, query, destructureData, rank, items, pins, cursor }) {
|
||||||
const { data, fetchMore } = useQuery(query || ITEMS, { variables })
|
const { data, fetchMore } = useQuery(query || ITEMS, { variables })
|
||||||
|
@ -28,19 +28,19 @@ export default function Items ({ variables = {}, query, destructureData, rank, i
|
||||||
<>
|
<>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<React.Fragment key={item.id}>
|
<Fragment key={item.id}>
|
||||||
{pinMap && pinMap[i + 1] && <Item item={pinMap[i + 1]} />}
|
{pinMap && pinMap[i + 1] && <Item item={pinMap[i + 1]} />}
|
||||||
{item.parentId
|
{item.parentId
|
||||||
? <><div /><div className='pb-3'><Comment item={item} noReply includeParent /></div></>
|
? <><div /><div className='pb-3'><CommentFlat item={item} noReply includeParent /></div></>
|
||||||
: (item.isJob
|
: (item.isJob
|
||||||
? <ItemJob item={item} rank={rank && i + 1} />
|
? <ItemJob item={item} rank={rank && i + 1} />
|
||||||
: (item.title
|
: (item.title
|
||||||
? <Item item={item} rank={rank && i + 1} />
|
? <Item item={item} rank={rank && i + 1} />
|
||||||
: (
|
: (
|
||||||
<div className='pb-2'>
|
<div className='pb-2'>
|
||||||
<Comment item={item} noReply includeParent clickToContext />
|
<CommentFlat item={item} noReply includeParent clickToContext />
|
||||||
</div>)))}
|
</div>)))}
|
||||||
</React.Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<MoreFooter
|
<MoreFooter
|
||||||
|
|
|
@ -14,6 +14,7 @@ import HandCoin from '../svgs/hand-coin-fill.svg'
|
||||||
import { COMMENT_DEPTH_LIMIT } from '../lib/constants'
|
import { COMMENT_DEPTH_LIMIT } from '../lib/constants'
|
||||||
import CowboyHatIcon from '../svgs/cowboy.svg'
|
import CowboyHatIcon from '../svgs/cowboy.svg'
|
||||||
import BaldIcon from '../svgs/bald.svg'
|
import BaldIcon from '../svgs/bald.svg'
|
||||||
|
import { RootProvider } from './root'
|
||||||
|
|
||||||
// TODO: oh man, this is a mess ... each notification type should just be a component ...
|
// TODO: oh man, this is a mess ... each notification type should just be a component ...
|
||||||
function Notification ({ n }) {
|
function Notification ({ n }) {
|
||||||
|
@ -132,7 +133,9 @@ function Notification ({ n }) {
|
||||||
? <Item item={n.item} />
|
? <Item item={n.item} />
|
||||||
: (
|
: (
|
||||||
<div className='pb-2'>
|
<div className='pb-2'>
|
||||||
<Comment item={n.item} noReply includeParent rootText={n.__typename === 'Reply' ? 'replying on:' : undefined} clickToContext />
|
<RootProvider root={n.item.root}>
|
||||||
|
<Comment item={n.item} noReply includeParent rootText={n.__typename === 'Reply' ? 'replying on:' : undefined} clickToContext />
|
||||||
|
</RootProvider>
|
||||||
</div>)}
|
</div>)}
|
||||||
</div>
|
</div>
|
||||||
</>)}
|
</>)}
|
||||||
|
|
|
@ -1,4 +1,3 @@
|
||||||
import React from 'react'
|
|
||||||
import { useQuery } from '@apollo/client'
|
import { useQuery } from '@apollo/client'
|
||||||
import AccordianItem from './accordian-item'
|
import AccordianItem from './accordian-item'
|
||||||
import Item, { ItemSkeleton } from './item'
|
import Item, { ItemSkeleton } from './item'
|
||||||
|
|
|
@ -8,10 +8,12 @@ import { useMe } from './me'
|
||||||
import { abbrNum } from '../lib/format'
|
import { abbrNum } from '../lib/format'
|
||||||
import { useShowModal } from './modal'
|
import { useShowModal } from './modal'
|
||||||
import FundError from './fund-error'
|
import FundError from './fund-error'
|
||||||
|
import { useRoot } from './root'
|
||||||
|
|
||||||
export default function PayBounty ({ children, item }) {
|
export default function PayBounty ({ children, item }) {
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
|
const root = useRoot()
|
||||||
|
|
||||||
const [act] = useMutation(
|
const [act] = useMutation(
|
||||||
gql`
|
gql`
|
||||||
|
@ -48,7 +50,7 @@ export default function PayBounty ({ children, item }) {
|
||||||
|
|
||||||
// update root bounty status
|
// update root bounty status
|
||||||
cache.modify({
|
cache.modify({
|
||||||
id: `Item:${item.root.id}`,
|
id: `Item:${root.id}`,
|
||||||
fields: {
|
fields: {
|
||||||
bountyPaidTo (existingPaidTo = []) {
|
bountyPaidTo (existingPaidTo = []) {
|
||||||
return [...(existingPaidTo || []), Number(item.id)]
|
return [...(existingPaidTo || []), Number(item.id)]
|
||||||
|
@ -62,11 +64,11 @@ export default function PayBounty ({ children, item }) {
|
||||||
const handlePayBounty = async () => {
|
const handlePayBounty = async () => {
|
||||||
try {
|
try {
|
||||||
await act({
|
await act({
|
||||||
variables: { id: item.id, sats: item.root.bounty },
|
variables: { id: item.id, sats: root.bounty },
|
||||||
optimisticResponse: {
|
optimisticResponse: {
|
||||||
act: {
|
act: {
|
||||||
id: `Item:${item.id}`,
|
id: `Item:${item.id}`,
|
||||||
sats: item.root.bounty
|
sats: root.bounty
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
@ -81,14 +83,14 @@ export default function PayBounty ({ children, item }) {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!me || item.mine || item.root.user.name !== me.name) {
|
if (!me || item.mine || root.user.name !== me.name) {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<ActionTooltip
|
<ActionTooltip
|
||||||
notForm
|
notForm
|
||||||
overlayText={`${item.root.bounty} sats`}
|
overlayText={`${root.bounty} sats`}
|
||||||
>
|
>
|
||||||
<ModalButton
|
<ModalButton
|
||||||
clicker={
|
clicker={
|
||||||
|
@ -102,7 +104,7 @@ export default function PayBounty ({ children, item }) {
|
||||||
</div>
|
</div>
|
||||||
<div className='text-center'>
|
<div className='text-center'>
|
||||||
<Button className='mt-4' variant='primary' onClick={() => handlePayBounty()}>
|
<Button className='mt-4' variant='primary' onClick={() => handlePayBounty()}>
|
||||||
pay <small>{abbrNum(item.root.bounty)} sats</small>
|
pay <small>{abbrNum(root.bounty)} sats</small>
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
</ModalButton>
|
</ModalButton>
|
||||||
|
|
|
@ -0,0 +1,15 @@
|
||||||
|
import { useContext, createContext } from 'react'
|
||||||
|
|
||||||
|
export const RootContext = createContext()
|
||||||
|
|
||||||
|
export function RootProvider ({ root, children }) {
|
||||||
|
return (
|
||||||
|
<RootContext.Provider value={root}>
|
||||||
|
{children}
|
||||||
|
</RootContext.Provider>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useRoot () {
|
||||||
|
return useContext(RootContext)
|
||||||
|
}
|
|
@ -3,8 +3,8 @@ import { ItemSkeleton } from './item'
|
||||||
import styles from './items.module.css'
|
import styles from './items.module.css'
|
||||||
import { ITEM_SEARCH } from '../fragments/items'
|
import { ITEM_SEARCH } from '../fragments/items'
|
||||||
import MoreFooter from './more-footer'
|
import MoreFooter from './more-footer'
|
||||||
import React from 'react'
|
import { Fragment } from 'react'
|
||||||
import Comment from './comment'
|
import { CommentFlat } from './comment'
|
||||||
import ItemFull from './item-full'
|
import ItemFull from './item-full'
|
||||||
|
|
||||||
export default function SearchItems ({ variables, items, pins, cursor }) {
|
export default function SearchItems ({ variables, items, pins, cursor }) {
|
||||||
|
@ -22,11 +22,11 @@ export default function SearchItems ({ variables, items, pins, cursor }) {
|
||||||
<>
|
<>
|
||||||
<div className={styles.grid}>
|
<div className={styles.grid}>
|
||||||
{items.map((item, i) => (
|
{items.map((item, i) => (
|
||||||
<React.Fragment key={item.id}>
|
<Fragment key={item.id}>
|
||||||
{item.parentId
|
{item.parentId
|
||||||
? <><div /><div className='pb-3'><Comment item={item} noReply includeParent /></div></>
|
? <><div /><CommentFlat item={item} noReply includeParent /></>
|
||||||
: <><div /><div className={item.text ? 'pb-3' : ''}><ItemFull item={item} noReply /></div></>}
|
: <><div /><div className={item.text ? 'pb-3' : ''}><ItemFull item={item} noReply /></div></>}
|
||||||
</React.Fragment>
|
</Fragment>
|
||||||
))}
|
))}
|
||||||
</div>
|
</div>
|
||||||
<MoreFooter
|
<MoreFooter
|
||||||
|
|
|
@ -26,19 +26,6 @@ export const COMMENT_FIELDS = gql`
|
||||||
mine
|
mine
|
||||||
otsHash
|
otsHash
|
||||||
ncomments
|
ncomments
|
||||||
root {
|
|
||||||
id
|
|
||||||
title
|
|
||||||
bounty
|
|
||||||
bountyPaidTo
|
|
||||||
subName
|
|
||||||
user {
|
|
||||||
name
|
|
||||||
streak
|
|
||||||
hideCowboyHat
|
|
||||||
id
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
`
|
`
|
||||||
|
|
||||||
|
@ -50,6 +37,19 @@ export const MORE_FLAT_COMMENTS = gql`
|
||||||
cursor
|
cursor
|
||||||
comments {
|
comments {
|
||||||
...CommentFields
|
...CommentFields
|
||||||
|
root {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
bounty
|
||||||
|
bountyPaidTo
|
||||||
|
subName
|
||||||
|
user {
|
||||||
|
name
|
||||||
|
streak
|
||||||
|
hideCowboyHat
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -63,6 +63,19 @@ export const TOP_COMMENTS = gql`
|
||||||
cursor
|
cursor
|
||||||
comments {
|
comments {
|
||||||
...CommentFields
|
...CommentFields
|
||||||
|
root {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
bounty
|
||||||
|
bountyPaidTo
|
||||||
|
subName
|
||||||
|
user {
|
||||||
|
name
|
||||||
|
streak
|
||||||
|
hideCowboyHat
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -72,6 +72,19 @@ export const SUB_FLAT_COMMENTS = gql`
|
||||||
cursor
|
cursor
|
||||||
comments {
|
comments {
|
||||||
...CommentFields
|
...CommentFields
|
||||||
|
root {
|
||||||
|
id
|
||||||
|
title
|
||||||
|
bounty
|
||||||
|
bountyPaidTo
|
||||||
|
subName
|
||||||
|
user {
|
||||||
|
name
|
||||||
|
streak
|
||||||
|
hideCowboyHat
|
||||||
|
id
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -12,8 +12,8 @@ import ThumbDown from '../svgs/thumb-down-fill.svg'
|
||||||
import { Checkbox, Form } from '../components/form'
|
import { Checkbox, Form } from '../components/form'
|
||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import Item from '../components/item'
|
import Item from '../components/item'
|
||||||
import Comment from '../components/comment'
|
import { CommentFlat } from '../components/comment'
|
||||||
import React from 'react'
|
import { Fragment } from 'react'
|
||||||
import ItemJob from '../components/item-job'
|
import ItemJob from '../components/item-job'
|
||||||
|
|
||||||
export const getServerSideProps = getGetServerSideProps(WALLET_HISTORY)
|
export const getServerSideProps = getGetServerSideProps(WALLET_HISTORY)
|
||||||
|
@ -134,7 +134,7 @@ function Detail ({ fact }) {
|
||||||
return <div className={styles.itemWrapper}><Item item={fact.item} /></div>
|
return <div className={styles.itemWrapper}><Item item={fact.item} /></div>
|
||||||
}
|
}
|
||||||
|
|
||||||
return <div className={styles.commentWrapper}><Comment item={fact.item} includeParent noReply truncate /></div>
|
return <div className={styles.commentWrapper}><CommentFlat item={fact.item} includeParent noReply truncate /></div>
|
||||||
}
|
}
|
||||||
|
|
||||||
export default function Satistics ({ data: { walletHistory: { facts, cursor } } }) {
|
export default function Satistics ({ data: { walletHistory: { facts, cursor } } }) {
|
||||||
|
@ -231,7 +231,7 @@ export default function Satistics ({ data: { walletHistory: { facts, cursor } }
|
||||||
<tbody>
|
<tbody>
|
||||||
{facts.map((f, i) => {
|
{facts.map((f, i) => {
|
||||||
const uri = href(f)
|
const uri = href(f)
|
||||||
const Wrapper = uri ? Link : ({ href, ...props }) => <React.Fragment {...props} />
|
const Wrapper = uri ? Link : ({ href, ...props }) => <Fragment {...props} />
|
||||||
return (
|
return (
|
||||||
<Wrapper href={uri} key={f.id}>
|
<Wrapper href={uri} key={f.id}>
|
||||||
<tr className={styles.row}>
|
<tr className={styles.row}>
|
||||||
|
|
|
@ -0,0 +1,26 @@
|
||||||
|
CREATE OR REPLACE FUNCTION item_comments(_item_id int, _level int, _where text, _order_by text)
|
||||||
|
RETURNS jsonb
|
||||||
|
LANGUAGE plpgsql STABLE PARALLEL SAFE AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
result jsonb;
|
||||||
|
BEGIN
|
||||||
|
IF _level < 1 THEN
|
||||||
|
RETURN '[]'::jsonb;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
EXECUTE ''
|
||||||
|
|| 'SELECT COALESCE(jsonb_agg(sub), ''[]''::jsonb) AS comments '
|
||||||
|
|| 'FROM ( '
|
||||||
|
|| ' SELECT "Item".*, "Item".created_at AS "createdAt", "Item".updated_at AS "updatedAt", '
|
||||||
|
|| ' item_comments("Item".id, $2 - 1, $3, $4) AS comments '
|
||||||
|
|| ' FROM "Item" p '
|
||||||
|
|| ' JOIN "Item" ON "Item"."parentId" = p.id '
|
||||||
|
|| ' WHERE p.id = $1 '
|
||||||
|
|| _where || ' '
|
||||||
|
|| _order_by
|
||||||
|
|| ' ) sub'
|
||||||
|
INTO result USING _item_id, _level, _where, _order_by;
|
||||||
|
RETURN result;
|
||||||
|
END
|
||||||
|
$$;
|
Loading…
Reference in New Issue