diff --git a/api/resolvers/item.js b/api/resolvers/item.js
index 7ed1b3a9..6fe0d88a 100644
--- a/api/resolvers/item.js
+++ b/api/resolvers/item.js
@@ -6,7 +6,7 @@ import { getMetadata, metadataRuleSets } from 'page-metadata-parser'
import domino from 'domino'
import {
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'
import { msatsToSats } from '../../lib/format'
import { parse } from 'tldts'
@@ -27,20 +27,9 @@ async function comments (me, models, id, sort, root) {
break
}
- const flat = await models.$queryRaw(`
- WITH RECURSIVE base AS (
- ${SELECT}, ARRAY[row_number() OVER (${orderBy}, "Item".path)] AS sort_path
- 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]
+ const filter = await filterClause(me, models)
+ const [{ item_comments: comments }] = await models.$queryRaw('SELECT item_comments($1, $2, $3, $4)', Number(id), COMMENT_DEPTH_LIMIT, orderBy, filter)
+ return comments
}
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
}
-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
export const SELECT =
`SELECT "Item".id, "Item".created_at as "createdAt", "Item".updated_at as "updatedAt", "Item".title,
diff --git a/components/comment.js b/components/comment.js
index bb21390a..4f48f416 100644
--- a/components/comment.js
+++ b/components/comment.js
@@ -19,8 +19,11 @@ import { abbrNum } from '../lib/format'
import Share from './share'
import ItemInfo from './item-info'
import { Badge } from 'react-bootstrap'
+import { RootProvider, useRoot } from './root'
function Parent ({ item, rootText }) {
+ const root = useRoot()
+
const ParentFrag = () => (
<>
\
@@ -30,20 +33,16 @@ function Parent ({ item, rootText }) {
>
)
- if (!item.root) {
- return
- }
-
return (
<>
- {Number(item.root.id) !== Number(item.parentId) && }
+ {Number(root.id) !== Number(item.parentId) && }
\
-
- {rootText || 'on:'} {item.root.title}
+
+ {rootText || 'on:'} {root?.title}
- {item.root.subName &&
-
- {' '}{item.root.subName}
+ {root.subName &&
+
+ {' '}{root.subName}
}
>
)
@@ -76,7 +75,9 @@ export function CommentFlat ({ item, ...props }) {
}
}}
>
-
+
+
+
)
}
@@ -89,6 +90,7 @@ export default function Comment ({
const [collapse, setCollapse] = useState(false)
const ref = useRef(null)
const router = useRouter()
+ const root = useRoot()
useEffect(() => {
if (Number(router.query.commentId) === Number(item.id)) {
@@ -103,9 +105,8 @@ export default function Comment ({
}, [item])
const bottomedOut = depth === COMMENT_DEPTH_LIMIT
-
- const op = item.root?.user.name === item.user.name
- const bountyPaid = item.root?.bountyPaidTo?.includes(Number(item.id))
+ const op = root.user.name === item.user.name
+ const bountyPaid = root.bountyPaidTo?.includes(Number(item.id))
return (
{includeParent &&
}
{bountyPaid &&
-
+
}
>
@@ -177,7 +178,7 @@ export default function Comment ({
{!noReply &&
- {item.root?.bounty && !bountyPaid && }
+ {root.bounty && !bountyPaid && }
}
{children}
diff --git a/components/error-boundary.js b/components/error-boundary.js
index 5ae01f06..d411f605 100644
--- a/components/error-boundary.js
+++ b/components/error-boundary.js
@@ -1,8 +1,8 @@
-import React from 'react'
+import { Component } from 'react'
import LayoutStatic from './layout-static'
import styles from '../styles/404.module.css'
-class ErrorBoundary extends React.Component {
+class ErrorBoundary extends Component {
constructor (props) {
super(props)
diff --git a/components/item-full.js b/components/item-full.js
index b1282ba6..fc76a332 100644
--- a/components/item-full.js
+++ b/components/item-full.js
@@ -20,6 +20,7 @@ import Check from '../svgs/check-double-line.svg'
import Share from './share'
import Toc from './table-of-contents'
import Link from 'next/link'
+import { RootProvider } from './root'
function BioItem ({ item, handleClick }) {
const me = useMe()
@@ -157,7 +158,7 @@ export default function ItemFull ({ item, bio, ...props }) {
}, [item.lastCommentAt])
return (
- <>
+
{item.parentId
?
: (
@@ -171,6 +172,6 @@ export default function ItemFull ({ item, bio, ...props }) {
}
- >
+
)
}
diff --git a/components/items-mixed.js b/components/items-mixed.js
index 678ec080..f95f9172 100644
--- a/components/items-mixed.js
+++ b/components/items-mixed.js
@@ -1,7 +1,5 @@
-import { useRouter } from 'next/router'
-import React from 'react'
-import { ignoreClick } from '../lib/clicks'
-import Comment from './comment'
+import { Fragment } from 'react'
+import { CommentFlat } from './comment'
import Item from './item'
import ItemJob from './item-job'
import { ItemsSkeleton } from './items'
@@ -9,33 +7,22 @@ import styles from './items.module.css'
import MoreFooter from './more-footer'
export default function MixedItems ({ rank, items, cursor, fetchMore }) {
- const router = useRouter()
return (
<>
{items.map((item, i) => (
-
+
{item.parentId
? (
<>
- {
- if (ignoreClick(e)) {
- return
- }
- router.push({
- pathname: '/items/[id]',
- query: { id: item.root.id, commentId: item.id }
- }, `/items/${item.root.id}`)
- }}
- >
-
+
+
>)
: (item.isJob
?
:
)}
-
+
))}
{items.map((item, i) => (
-
+
{pinMap && pinMap[i + 1] && }
{item.parentId
- ? <>
>
+ ? <>
>
: (item.isJob
?
: (item.title
?
: (
-
+
)))}
-
+
))}
: (
-
+
+
+
)}
>)}
diff --git a/components/past-bounties.js b/components/past-bounties.js
index f687e3ce..fe7729b3 100644
--- a/components/past-bounties.js
+++ b/components/past-bounties.js
@@ -1,4 +1,3 @@
-import React from 'react'
import { useQuery } from '@apollo/client'
import AccordianItem from './accordian-item'
import Item, { ItemSkeleton } from './item'
diff --git a/components/pay-bounty.js b/components/pay-bounty.js
index 64023050..b18fe806 100644
--- a/components/pay-bounty.js
+++ b/components/pay-bounty.js
@@ -8,10 +8,12 @@ import { useMe } from './me'
import { abbrNum } from '../lib/format'
import { useShowModal } from './modal'
import FundError from './fund-error'
+import { useRoot } from './root'
export default function PayBounty ({ children, item }) {
const me = useMe()
const showModal = useShowModal()
+ const root = useRoot()
const [act] = useMutation(
gql`
@@ -48,7 +50,7 @@ export default function PayBounty ({ children, item }) {
// update root bounty status
cache.modify({
- id: `Item:${item.root.id}`,
+ id: `Item:${root.id}`,
fields: {
bountyPaidTo (existingPaidTo = []) {
return [...(existingPaidTo || []), Number(item.id)]
@@ -62,11 +64,11 @@ export default function PayBounty ({ children, item }) {
const handlePayBounty = async () => {
try {
await act({
- variables: { id: item.id, sats: item.root.bounty },
+ variables: { id: item.id, sats: root.bounty },
optimisticResponse: {
act: {
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 (
diff --git a/components/root.js b/components/root.js
new file mode 100644
index 00000000..f0cea78f
--- /dev/null
+++ b/components/root.js
@@ -0,0 +1,15 @@
+import { useContext, createContext } from 'react'
+
+export const RootContext = createContext()
+
+export function RootProvider ({ root, children }) {
+ return (
+
+ {children}
+
+ )
+}
+
+export function useRoot () {
+ return useContext(RootContext)
+}
diff --git a/components/search-items.js b/components/search-items.js
index fb2598a3..4c6594dd 100644
--- a/components/search-items.js
+++ b/components/search-items.js
@@ -3,8 +3,8 @@ 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 { Fragment } from 'react'
+import { CommentFlat } from './comment'
import ItemFull from './item-full'
export default function SearchItems ({ variables, items, pins, cursor }) {
@@ -22,11 +22,11 @@ export default function SearchItems ({ variables, items, pins, cursor }) {
<>
{items.map((item, i) => (
-
+
{item.parentId
- ? <>
>
+ ? <>>
: <>
>}
-
+
))}
}
- return
+ return
}
export default function Satistics ({ data: { walletHistory: { facts, cursor } } }) {
@@ -231,7 +231,7 @@ export default function Satistics ({ data: { walletHistory: { facts, cursor } }
{facts.map((f, i) => {
const uri = href(f)
- const Wrapper = uri ? Link : ({ href, ...props }) =>
+ const Wrapper = uri ? Link : ({ href, ...props }) =>
return (
diff --git a/prisma/migrations/20230506214933_comments_func/migration.sql b/prisma/migrations/20230506214933_comments_func/migration.sql
new file mode 100644
index 00000000..aa5ab141
--- /dev/null
+++ b/prisma/migrations/20230506214933_comments_func/migration.sql
@@ -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
+$$;
\ No newline at end of file