undo zap/downzap and improve downzap ux
This commit is contained in:
parent
7e0da18878
commit
65744364f1
@ -141,7 +141,7 @@ async function itemQueryWithMeta ({ me, models, query, orderBy = '' }, ...args)
|
|||||||
return await models.$queryRawUnsafe(`
|
return await models.$queryRawUnsafe(`
|
||||||
SELECT "Item".*, to_jsonb(users.*) || jsonb_build_object('meMute', "Mute"."mutedId" IS NOT NULL) as user,
|
SELECT "Item".*, to_jsonb(users.*) || jsonb_build_object('meMute', "Mute"."mutedId" IS NOT NULL) as user,
|
||||||
COALESCE("ItemAct"."meMsats", 0) as "meMsats",
|
COALESCE("ItemAct"."meMsats", 0) as "meMsats",
|
||||||
COALESCE("ItemAct"."meDontLike", false) as "meDontLike", b."itemId" IS NOT NULL AS "meBookmark",
|
COALESCE("ItemAct"."meDontLikeMsats", 0) as "meDontLikeMsats", b."itemId" IS NOT NULL AS "meBookmark",
|
||||||
"ThreadSubscription"."itemId" IS NOT NULL AS "meSubscription", "ItemForward"."itemId" IS NOT NULL AS "meForward"
|
"ThreadSubscription"."itemId" IS NOT NULL AS "meSubscription", "ItemForward"."itemId" IS NOT NULL AS "meForward"
|
||||||
FROM (
|
FROM (
|
||||||
${query}
|
${query}
|
||||||
@ -153,7 +153,7 @@ async function itemQueryWithMeta ({ me, models, query, orderBy = '' }, ...args)
|
|||||||
LEFT JOIN "ItemForward" ON "ItemForward"."itemId" = "Item".id AND "ItemForward"."userId" = ${me.id}
|
LEFT JOIN "ItemForward" ON "ItemForward"."itemId" = "Item".id AND "ItemForward"."userId" = ${me.id}
|
||||||
LEFT JOIN LATERAL (
|
LEFT JOIN LATERAL (
|
||||||
SELECT "itemId", sum("ItemAct".msats) FILTER (WHERE act = 'FEE' OR act = 'TIP') AS "meMsats",
|
SELECT "itemId", sum("ItemAct".msats) FILTER (WHERE act = 'FEE' OR act = 'TIP') AS "meMsats",
|
||||||
bool_or(act = 'DONT_LIKE_THIS') AS "meDontLike"
|
sum("ItemAct".msats) FILTER (WHERE act = 'DONT_LIKE_THIS') AS "meDontLikeMsats"
|
||||||
FROM "ItemAct"
|
FROM "ItemAct"
|
||||||
WHERE "ItemAct"."userId" = ${me.id}
|
WHERE "ItemAct"."userId" = ${me.id}
|
||||||
AND "ItemAct"."itemId" = "Item".id
|
AND "ItemAct"."itemId" = "Item".id
|
||||||
@ -805,7 +805,7 @@ export default {
|
|||||||
{ me, models, lnd, hash, hmac }
|
{ me, models, lnd, hash, hmac }
|
||||||
)
|
)
|
||||||
|
|
||||||
return true
|
return sats
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
Item: {
|
Item: {
|
||||||
@ -933,11 +933,16 @@ export default {
|
|||||||
|
|
||||||
return (msats && msatsToSats(msats)) || 0
|
return (msats && msatsToSats(msats)) || 0
|
||||||
},
|
},
|
||||||
meDontLike: async (item, args, { me, models }) => {
|
meDontLikeSats: async (item, args, { me, models }) => {
|
||||||
if (!me) return false
|
if (!me) return false
|
||||||
if (typeof item.meDontLike !== 'undefined') return item.meDontLike
|
if (typeof item.meMsats !== 'undefined') {
|
||||||
|
return msatsToSats(item.meDontLikeMsats)
|
||||||
|
}
|
||||||
|
|
||||||
const dontLike = await models.itemAct.findFirst({
|
const { _sum: { msats } } = await models.itemAct.aggregate({
|
||||||
|
_sum: {
|
||||||
|
msats: true
|
||||||
|
},
|
||||||
where: {
|
where: {
|
||||||
itemId: Number(item.id),
|
itemId: Number(item.id),
|
||||||
userId: me.id,
|
userId: me.id,
|
||||||
@ -945,7 +950,7 @@ export default {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
return !!dontLike
|
return (msats && msatsToSats(msats)) || 0
|
||||||
},
|
},
|
||||||
meBookmark: async (item, args, { me, models }) => {
|
meBookmark: async (item, args, { me, models }) => {
|
||||||
if (!me) return false
|
if (!me) return false
|
||||||
|
@ -34,7 +34,7 @@ export default gql`
|
|||||||
upsertPoll(id: ID, sub: String, title: String!, text: String, options: [String!]!, boost: Int, forward: [ItemForwardInput], hash: String, hmac: String): Item!
|
upsertPoll(id: ID, sub: String, title: String!, text: String, options: [String!]!, boost: Int, forward: [ItemForwardInput], hash: String, hmac: String): Item!
|
||||||
updateNoteId(id: ID!, noteId: String!): Item!
|
updateNoteId(id: ID!, noteId: String!): Item!
|
||||||
upsertComment(id:ID, text: String!, parentId: ID, hash: String, hmac: String): Item!
|
upsertComment(id:ID, text: String!, parentId: ID, hash: String, hmac: String): Item!
|
||||||
dontLikeThis(id: ID!, sats: Int, hash: String, hmac: String): Boolean!
|
dontLikeThis(id: ID!, sats: Int, hash: String, hmac: String): Int!
|
||||||
act(id: ID!, sats: Int, hash: String, hmac: String): ItemActResult!
|
act(id: ID!, sats: Int, hash: String, hmac: String): ItemActResult!
|
||||||
pollVote(id: ID!, hash: String, hmac: String): ID!
|
pollVote(id: ID!, hash: String, hmac: String): ID!
|
||||||
}
|
}
|
||||||
@ -90,7 +90,7 @@ export default gql`
|
|||||||
lastCommentAt: Date
|
lastCommentAt: Date
|
||||||
upvotes: Int!
|
upvotes: Int!
|
||||||
meSats: Int!
|
meSats: Int!
|
||||||
meDontLike: Boolean!
|
meDontLikeSats: Int!
|
||||||
meBookmark: Boolean!
|
meBookmark: Boolean!
|
||||||
meSubscription: Boolean!
|
meSubscription: Boolean!
|
||||||
meForward: Boolean
|
meForward: Boolean
|
||||||
|
@ -14,7 +14,6 @@ import { ignoreClick } from '../lib/clicks'
|
|||||||
import PayBounty from './pay-bounty'
|
import PayBounty from './pay-bounty'
|
||||||
import BountyIcon from '../svgs/bounty-bag.svg'
|
import BountyIcon from '../svgs/bounty-bag.svg'
|
||||||
import ActionTooltip from './action-tooltip'
|
import ActionTooltip from './action-tooltip'
|
||||||
import Flag from '../svgs/flag-fill.svg'
|
|
||||||
import { numWithUnits } from '../lib/format'
|
import { numWithUnits } from '../lib/format'
|
||||||
import Share from './share'
|
import Share from './share'
|
||||||
import ItemInfo from './item-info'
|
import ItemInfo from './item-info'
|
||||||
@ -22,6 +21,7 @@ import Badge from 'react-bootstrap/Badge'
|
|||||||
import { RootProvider, useRoot } from './root'
|
import { RootProvider, useRoot } from './root'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import { useQuoteReply } from './use-quote-reply'
|
import { useQuoteReply } from './use-quote-reply'
|
||||||
|
import { DownZap } from './dont-link-this'
|
||||||
|
|
||||||
function Parent ({ item, rootText }) {
|
function Parent ({ item, rootText }) {
|
||||||
const root = useRoot()
|
const root = useRoot()
|
||||||
@ -146,8 +146,8 @@ export default function Comment ({
|
|||||||
onTouchStart={() => ref.current.classList.add('outline-new-comment-unset')}
|
onTouchStart={() => ref.current.classList.add('outline-new-comment-unset')}
|
||||||
>
|
>
|
||||||
<div className={`${itemStyles.item} ${styles.item}`}>
|
<div className={`${itemStyles.item} ${styles.item}`}>
|
||||||
{item.meDontLike
|
{item.meDontLikeSats > item.meSats
|
||||||
? <Flag width={24} height={24} className={styles.dontLike} />
|
? <DownZap width={24} height={24} className={styles.dontLike} id={item.id} meDontLikeSats={item.meDontLikeSats} />
|
||||||
: <UpVote item={item} className={styles.upvote} pendingSats={pendingSats} setPendingSats={setPendingSats} />}
|
: <UpVote item={item} className={styles.upvote} pendingSats={pendingSats} setPendingSats={setPendingSats} />}
|
||||||
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
|
<div className={`${itemStyles.hunk} ${styles.hunk}`}>
|
||||||
<div className='d-flex align-items-center'>
|
<div className='d-flex align-items-center'>
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
padding: 2px;
|
padding: 2px;
|
||||||
margin-left: 1px;
|
margin-left: 1px;
|
||||||
margin-top: 9px;
|
margin-top: 9px;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.text {
|
.text {
|
||||||
|
@ -4,8 +4,23 @@ import { useShowModal } from './modal'
|
|||||||
import { useToast } from './toast'
|
import { useToast } from './toast'
|
||||||
import ItemAct from './item-act'
|
import ItemAct from './item-act'
|
||||||
import AccordianItem from './accordian-item'
|
import AccordianItem from './accordian-item'
|
||||||
|
import Flag from '../svgs/flag-fill.svg'
|
||||||
|
import { useMemo } from 'react'
|
||||||
|
import getColor from '../lib/rainbow'
|
||||||
|
|
||||||
export default function DontLikeThisDropdownItem ({ id }) {
|
export function DownZap ({ id, meDontLikeSats, ...props }) {
|
||||||
|
const style = useMemo(() => (meDontLikeSats
|
||||||
|
? {
|
||||||
|
fill: getColor(meDontLikeSats),
|
||||||
|
filter: `drop-shadow(0 0 6px ${getColor(meDontLikeSats)}90)`
|
||||||
|
}
|
||||||
|
: undefined), [meDontLikeSats])
|
||||||
|
return (
|
||||||
|
<DownZapper id={id} As={({ ...oprops }) => <Flag {...props} {...oprops} style={style} />} />
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
function DownZapper ({ id, As, children }) {
|
||||||
const toaster = useToast()
|
const toaster = useToast()
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
|
|
||||||
@ -14,12 +29,12 @@ export default function DontLikeThisDropdownItem ({ id }) {
|
|||||||
mutation dontLikeThis($id: ID!, $sats: Int, $hash: String, $hmac: String) {
|
mutation dontLikeThis($id: ID!, $sats: Int, $hash: String, $hmac: String) {
|
||||||
dontLikeThis(id: $id, sats: $sats, hash: $hash, hmac: $hmac)
|
dontLikeThis(id: $id, sats: $sats, hash: $hash, hmac: $hmac)
|
||||||
}`, {
|
}`, {
|
||||||
update (cache) {
|
update (cache, { data: { dontLikeThis } }) {
|
||||||
cache.modify({
|
cache.modify({
|
||||||
id: `Item:${id}`,
|
id: `Item:${id}`,
|
||||||
fields: {
|
fields: {
|
||||||
meDontLike () {
|
meDontLikeSats (existingSats = 0) {
|
||||||
return true
|
return existingSats + dontLikeThis
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
@ -28,7 +43,7 @@ export default function DontLikeThisDropdownItem ({ id }) {
|
|||||||
)
|
)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Dropdown.Item
|
<As
|
||||||
onClick={async () => {
|
onClick={async () => {
|
||||||
try {
|
try {
|
||||||
showModal(onClose =>
|
showModal(onClose =>
|
||||||
@ -53,7 +68,18 @@ export default function DontLikeThisDropdownItem ({ id }) {
|
|||||||
}
|
}
|
||||||
}}
|
}}
|
||||||
>
|
>
|
||||||
<span className='text-danger'>downzap</span>
|
{children}
|
||||||
</Dropdown.Item>
|
</As>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export default function DontLikeThisDropdownItem ({ id }) {
|
||||||
|
return (
|
||||||
|
<DownZapper
|
||||||
|
As={Dropdown.Item}
|
||||||
|
id={id}
|
||||||
|
>
|
||||||
|
<span className='text-danger'>downzap</span>
|
||||||
|
</DownZapper>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -18,6 +18,7 @@ import Hat from './hat'
|
|||||||
import { AD_USER_ID } from '../lib/constants'
|
import { AD_USER_ID } from '../lib/constants'
|
||||||
import ActionDropdown from './action-dropdown'
|
import ActionDropdown from './action-dropdown'
|
||||||
import MuteDropdownItem from './mute'
|
import MuteDropdownItem from './mute'
|
||||||
|
import { DropdownItemUpVote } from './upvote'
|
||||||
|
|
||||||
export default function ItemInfo ({
|
export default function ItemInfo ({
|
||||||
item, pendingSats, full, commentsText = 'comments',
|
item, pendingSats, full, commentsText = 'comments',
|
||||||
@ -39,7 +40,7 @@ export default function ItemInfo ({
|
|||||||
}, [item])
|
}, [item])
|
||||||
|
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
if (item) setMeTotalSats(item.meSats + item.meAnonSats + pendingSats)
|
if (item) setMeTotalSats((item.meSats || 0) + (item.meAnonSats || 0) + (pendingSats || 0))
|
||||||
}, [item?.meSats, item?.meAnonSats, pendingSats])
|
}, [item?.meSats, item?.meAnonSats, pendingSats])
|
||||||
|
|
||||||
return (
|
return (
|
||||||
@ -52,7 +53,9 @@ export default function ItemInfo ({
|
|||||||
unitPlural: 'stackers'
|
unitPlural: 'stackers'
|
||||||
})} ${item.mine
|
})} ${item.mine
|
||||||
? `\\ ${numWithUnits(item.meSats, { abbreviate: false })} to post`
|
? `\\ ${numWithUnits(item.meSats, { abbreviate: false })} to post`
|
||||||
: `(${numWithUnits(meTotalSats, { abbreviate: false })} from me)`} `}
|
: `(${numWithUnits(meTotalSats, { abbreviate: false })}${item.meDontLikeSats
|
||||||
|
? ` & ${numWithUnits(item.meDontLikeSats, { abbreviate: false, unitSingular: 'downsat', unitPlural: 'downsats' })}`
|
||||||
|
: ''} from me)`} `}
|
||||||
>
|
>
|
||||||
{numWithUnits(item.sats + pendingSats)}
|
{numWithUnits(item.sats + pendingSats)}
|
||||||
</span>
|
</span>
|
||||||
@ -143,8 +146,11 @@ export default function ItemInfo ({
|
|||||||
<Link href={`/items/${item.id}/ots`} className='text-reset dropdown-item'>
|
<Link href={`/items/${item.id}/ots`} className='text-reset dropdown-item'>
|
||||||
opentimestamp
|
opentimestamp
|
||||||
</Link>}
|
</Link>}
|
||||||
{me && !item.meSats && !item.position &&
|
{me && !item.position &&
|
||||||
!item.mine && !item.deletedAt && <DontLikeThisDropdownItem id={item.id} />}
|
!item.mine && !item.deletedAt &&
|
||||||
|
(item.meDontLikeSats > meTotalSats
|
||||||
|
? <DropdownItemUpVote item={item} />
|
||||||
|
: <DontLikeThisDropdownItem id={item.id} />)}
|
||||||
{me && item?.noteId && (
|
{me && item?.noteId && (
|
||||||
<Dropdown.Item onClick={() => window.open(`https://nostr.com/${item.noteId}`, '_blank', 'noopener')}>
|
<Dropdown.Item onClick={() => window.open(`https://nostr.com/${item.noteId}`, '_blank', 'noopener')}>
|
||||||
nostr note
|
nostr note
|
||||||
|
@ -8,7 +8,6 @@ import reactStringReplace from 'react-string-replace'
|
|||||||
import PollIcon from '../svgs/bar-chart-horizontal-fill.svg'
|
import PollIcon from '../svgs/bar-chart-horizontal-fill.svg'
|
||||||
import BountyIcon from '../svgs/bounty-bag.svg'
|
import BountyIcon from '../svgs/bounty-bag.svg'
|
||||||
import ActionTooltip from './action-tooltip'
|
import ActionTooltip from './action-tooltip'
|
||||||
import Flag from '../svgs/flag-fill.svg'
|
|
||||||
import ImageIcon from '../svgs/image-fill.svg'
|
import ImageIcon from '../svgs/image-fill.svg'
|
||||||
import { numWithUnits } from '../lib/format'
|
import { numWithUnits } from '../lib/format'
|
||||||
import ItemInfo from './item-info'
|
import ItemInfo from './item-info'
|
||||||
@ -17,6 +16,7 @@ import { commentsViewedAt } from '../lib/new-comments'
|
|||||||
import { useRouter } from 'next/router'
|
import { useRouter } from 'next/router'
|
||||||
import { Badge } from 'react-bootstrap'
|
import { Badge } from 'react-bootstrap'
|
||||||
import AdIcon from '../svgs/advertisement-fill.svg'
|
import AdIcon from '../svgs/advertisement-fill.svg'
|
||||||
|
import { DownZap } from './dont-link-this'
|
||||||
|
|
||||||
export function SearchTitle ({ title }) {
|
export function SearchTitle ({ title }) {
|
||||||
return reactStringReplace(title, /\*\*\*([^*]+)\*\*\*/g, (match, i) => {
|
return reactStringReplace(title, /\*\*\*([^*]+)\*\*\*/g, (match, i) => {
|
||||||
@ -43,8 +43,8 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s
|
|||||||
<div className={`${styles.item} ${siblingComments ? 'pt-3' : ''}`}>
|
<div className={`${styles.item} ${siblingComments ? 'pt-3' : ''}`}>
|
||||||
{item.position
|
{item.position
|
||||||
? <Pin width={24} height={24} className={styles.pin} />
|
? <Pin width={24} height={24} className={styles.pin} />
|
||||||
: item.meDontLike
|
: item.meDontLikeSats > item.meSats
|
||||||
? <Flag width={24} height={24} className={styles.dontLike} />
|
? <DownZap width={24} height={24} className={styles.dontLike} id={item.id} meDontLikeSats={item.meDontLikeSats} />
|
||||||
: Number(item.user?.id) === AD_USER_ID
|
: Number(item.user?.id) === AD_USER_ID
|
||||||
? <AdIcon width={24} height={24} className={styles.ad} />
|
? <AdIcon width={24} height={24} className={styles.ad} />
|
||||||
: <UpVote item={item} className={styles.upvote} pendingSats={pendingSats} setPendingSats={setPendingSats} />}
|
: <UpVote item={item} className={styles.upvote} pendingSats={pendingSats} setPendingSats={setPendingSats} />}
|
||||||
|
@ -65,6 +65,7 @@ a.title:visited {
|
|||||||
margin-right: .35rem;
|
margin-right: .35rem;
|
||||||
margin-left: -.2rem;
|
margin-left: -.2rem;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
|
cursor: pointer;
|
||||||
}
|
}
|
||||||
|
|
||||||
.case {
|
.case {
|
||||||
|
@ -4,7 +4,7 @@ import { gql, useMutation } from '@apollo/client'
|
|||||||
import ActionTooltip from './action-tooltip'
|
import ActionTooltip from './action-tooltip'
|
||||||
import ItemAct from './item-act'
|
import ItemAct from './item-act'
|
||||||
import { useMe } from './me'
|
import { useMe } from './me'
|
||||||
import Rainbow from '../lib/rainbow'
|
import getColor from '../lib/rainbow'
|
||||||
import { useCallback, useMemo, useRef, useState } from 'react'
|
import { useCallback, useMemo, useRef, useState } from 'react'
|
||||||
import LongPressable from 'react-longpressable'
|
import LongPressable from 'react-longpressable'
|
||||||
import Overlay from 'react-bootstrap/Overlay'
|
import Overlay from 'react-bootstrap/Overlay'
|
||||||
@ -15,17 +15,7 @@ import { numWithUnits } from '../lib/format'
|
|||||||
import { payOrLoginError, useInvoiceModal } from './invoice'
|
import { payOrLoginError, useInvoiceModal } from './invoice'
|
||||||
import useDebounceCallback from './use-debounce-callback'
|
import useDebounceCallback from './use-debounce-callback'
|
||||||
import { useToast } from './toast'
|
import { useToast } from './toast'
|
||||||
|
import { Dropdown } from 'react-bootstrap'
|
||||||
const getColor = (meSats) => {
|
|
||||||
if (!meSats || meSats <= 10) {
|
|
||||||
return 'var(--bs-secondary)'
|
|
||||||
}
|
|
||||||
|
|
||||||
const idx = Math.min(
|
|
||||||
Math.floor((Math.log(meSats) / Math.log(10000)) * (Rainbow.length - 1)),
|
|
||||||
Rainbow.length - 1)
|
|
||||||
return Rainbow[idx]
|
|
||||||
}
|
|
||||||
|
|
||||||
const UpvotePopover = ({ target, show, handleClose }) => {
|
const UpvotePopover = ({ target, show, handleClose }) => {
|
||||||
const me = useMe()
|
const me = useMe()
|
||||||
@ -66,6 +56,72 @@ const TipPopover = ({ target, show, handleClose }) => (
|
|||||||
</Overlay>
|
</Overlay>
|
||||||
)
|
)
|
||||||
|
|
||||||
|
function useAct ({ item, setVoteShow = () => {}, setTipShow = () => {} }) {
|
||||||
|
const me = useMe()
|
||||||
|
return useMutation(
|
||||||
|
gql`
|
||||||
|
mutation act($id: ID!, $sats: Int!, $hash: String, $hmac: String) {
|
||||||
|
act(id: $id, sats: $sats, hash: $hash, hmac: $hmac) {
|
||||||
|
sats
|
||||||
|
}
|
||||||
|
}`, {
|
||||||
|
update (cache, { data: { act: { sats } } }) {
|
||||||
|
cache.modify({
|
||||||
|
id: `Item:${item.id}`,
|
||||||
|
fields: {
|
||||||
|
sats (existingSats = 0) {
|
||||||
|
return existingSats + sats
|
||||||
|
},
|
||||||
|
meSats: me
|
||||||
|
? (existingSats = 0) => {
|
||||||
|
if (sats <= me.privates?.sats) {
|
||||||
|
if (existingSats === 0) {
|
||||||
|
setVoteShow(true)
|
||||||
|
} else {
|
||||||
|
setTipShow(true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return existingSats + sats
|
||||||
|
}
|
||||||
|
: undefined
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
// update all ancestors
|
||||||
|
item.path.split('.').forEach(id => {
|
||||||
|
if (Number(id) === Number(item.id)) return
|
||||||
|
cache.modify({
|
||||||
|
id: `Item:${id}`,
|
||||||
|
fields: {
|
||||||
|
commentSats (existingCommentSats = 0) {
|
||||||
|
return existingCommentSats + sats
|
||||||
|
}
|
||||||
|
}
|
||||||
|
})
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
export function DropdownItemUpVote ({ item }) {
|
||||||
|
const showModal = useShowModal()
|
||||||
|
|
||||||
|
const [act] = useAct({ item })
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Dropdown.Item
|
||||||
|
onClick={async () => {
|
||||||
|
showModal(onClose =>
|
||||||
|
<ItemAct onClose={onClose} itemId={item.id} act={act} />)
|
||||||
|
}}
|
||||||
|
>
|
||||||
|
<span className='text-success'>zap</span>
|
||||||
|
</Dropdown.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
export default function UpVote ({ item, className, pendingSats, setPendingSats }) {
|
export default function UpVote ({ item, className, pendingSats, setPendingSats }) {
|
||||||
const showModal = useShowModal()
|
const showModal = useShowModal()
|
||||||
const [voteShow, _setVoteShow] = useState(false)
|
const [voteShow, _setVoteShow] = useState(false)
|
||||||
@ -110,51 +166,8 @@ export default function UpVote ({ item, className, pendingSats, setPendingSats }
|
|||||||
}
|
}
|
||||||
}, [me, tipShow, setWalkthrough])
|
}, [me, tipShow, setWalkthrough])
|
||||||
|
|
||||||
const [act] = useMutation(
|
const [act] = useAct({ item, setVoteShow, setTipShow })
|
||||||
gql`
|
|
||||||
mutation act($id: ID!, $sats: Int!, $hash: String, $hmac: String) {
|
|
||||||
act(id: $id, sats: $sats, hash: $hash, hmac: $hmac) {
|
|
||||||
sats
|
|
||||||
}
|
|
||||||
}`, {
|
|
||||||
update (cache, { data: { act: { sats } } }) {
|
|
||||||
cache.modify({
|
|
||||||
id: `Item:${item.id}`,
|
|
||||||
fields: {
|
|
||||||
sats (existingSats = 0) {
|
|
||||||
return existingSats + sats
|
|
||||||
},
|
|
||||||
meSats: me
|
|
||||||
? (existingSats = 0) => {
|
|
||||||
if (sats <= me.privates?.sats) {
|
|
||||||
if (existingSats === 0) {
|
|
||||||
setVoteShow(true)
|
|
||||||
} else {
|
|
||||||
setTipShow(true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return existingSats + sats
|
|
||||||
}
|
|
||||||
: undefined
|
|
||||||
}
|
|
||||||
})
|
|
||||||
|
|
||||||
// update all ancestors
|
|
||||||
item.path.split('.').forEach(id => {
|
|
||||||
if (Number(id) === Number(item.id)) return
|
|
||||||
cache.modify({
|
|
||||||
id: `Item:${id}`,
|
|
||||||
fields: {
|
|
||||||
commentSats (existingCommentSats = 0) {
|
|
||||||
return existingCommentSats + sats
|
|
||||||
}
|
|
||||||
}
|
|
||||||
})
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
const showInvoiceModal = useInvoiceModal(
|
const showInvoiceModal = useInvoiceModal(
|
||||||
async ({ hash, hmac }, { variables }) => {
|
async ({ hash, hmac }, { variables }) => {
|
||||||
await act({ variables: { ...variables, hash, hmac } })
|
await act({ variables: { ...variables, hash, hmac } })
|
||||||
|
@ -21,7 +21,7 @@ export const COMMENT_FIELDS = gql`
|
|||||||
freedFreebie
|
freedFreebie
|
||||||
boost
|
boost
|
||||||
meSats
|
meSats
|
||||||
meDontLike
|
meDontLikeSats
|
||||||
meBookmark
|
meBookmark
|
||||||
meSubscription
|
meSubscription
|
||||||
outlawed
|
outlawed
|
||||||
|
@ -28,7 +28,7 @@ export const ITEM_FIELDS = gql`
|
|||||||
path
|
path
|
||||||
upvotes
|
upvotes
|
||||||
meSats
|
meSats
|
||||||
meDontLike
|
meDontLikeSats
|
||||||
meBookmark
|
meBookmark
|
||||||
meSubscription
|
meSubscription
|
||||||
meForward
|
meForward
|
||||||
|
@ -1,4 +1,15 @@
|
|||||||
export default [
|
export default function getColor (meSats) {
|
||||||
|
if (!meSats || meSats <= 10) {
|
||||||
|
return 'var(--bs-secondary)'
|
||||||
|
}
|
||||||
|
|
||||||
|
const idx = Math.min(
|
||||||
|
Math.floor((Math.log(meSats) / Math.log(10000)) * (Rainbow.length - 1)),
|
||||||
|
Rainbow.length - 1)
|
||||||
|
return Rainbow[idx]
|
||||||
|
}
|
||||||
|
|
||||||
|
const Rainbow = [
|
||||||
'#f6911d',
|
'#f6911d',
|
||||||
'#f6921e',
|
'#f6921e',
|
||||||
'#f6931f',
|
'#f6931f',
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
CREATE OR REPLACE FUNCTION item_comments_zaprank_with_me(_item_id int, _global_seed int, _me_id int, _level int, _where text, _order_by text)
|
||||||
|
RETURNS jsonb
|
||||||
|
LANGUAGE plpgsql VOLATILE PARALLEL SAFE AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
result jsonb;
|
||||||
|
BEGIN
|
||||||
|
IF _level < 1 THEN
|
||||||
|
RETURN '[]'::jsonb;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
EXECUTE 'CREATE TEMP TABLE IF NOT EXISTS t_item ON COMMIT DROP AS'
|
||||||
|
|| ' SELECT "Item".*, "Item".created_at at time zone ''UTC'' AS "createdAt", "Item".updated_at at time zone ''UTC'' AS "updatedAt", '
|
||||||
|
|| ' to_jsonb(users.*) || jsonb_build_object(''meMute'', "Mute"."mutedId" IS NOT NULL) AS user, '
|
||||||
|
|| ' COALESCE("ItemAct"."meMsats", 0) AS "meMsats", COALESCE("ItemAct"."meDontLikeMsats", 0) AS "meDontLikeMsats", '
|
||||||
|
|| ' "Bookmark"."itemId" IS NOT NULL AS "meBookmark", "ThreadSubscription"."itemId" IS NOT NULL AS "meSubscription", '
|
||||||
|
|| ' GREATEST(g.tf_hot_score, l.tf_hot_score) AS personal_hot_score, GREATEST(g.tf_top_score, l.tf_top_score) AS personal_top_score '
|
||||||
|
|| ' FROM "Item" '
|
||||||
|
|| ' JOIN users ON users.id = "Item"."userId" '
|
||||||
|
|| ' LEFT JOIN "Mute" ON "Mute"."muterId" = $5 AND "Mute"."mutedId" = "Item"."userId"'
|
||||||
|
|| ' LEFT JOIN "Bookmark" ON "Bookmark"."userId" = $5 AND "Bookmark"."itemId" = "Item".id '
|
||||||
|
|| ' LEFT JOIN "ThreadSubscription" ON "ThreadSubscription"."userId" = $5 AND "ThreadSubscription"."itemId" = "Item".id '
|
||||||
|
|| ' LEFT JOIN LATERAL ( '
|
||||||
|
|| ' SELECT "itemId", sum("ItemAct".msats) FILTER (WHERE act = ''FEE'' OR act = ''TIP'') AS "meMsats", '
|
||||||
|
|| ' sum("ItemAct".msats) FILTER (WHERE act = ''DONT_LIKE_THIS'') AS "meDontLikeMsats" '
|
||||||
|
|| ' FROM "ItemAct" '
|
||||||
|
|| ' WHERE "ItemAct"."userId" = $5 '
|
||||||
|
|| ' AND "ItemAct"."itemId" = "Item".id '
|
||||||
|
|| ' GROUP BY "ItemAct"."itemId" '
|
||||||
|
|| ' ) "ItemAct" ON true '
|
||||||
|
|| ' LEFT JOIN zap_rank_personal_view g ON g."viewerId" = $6 AND g.id = "Item".id '
|
||||||
|
|| ' LEFT JOIN zap_rank_personal_view l ON l."viewerId" = $5 AND l.id = g.id '
|
||||||
|
|| ' WHERE "Item".path <@ (SELECT path FROM "Item" WHERE id = $1) ' || _where || ' '
|
||||||
|
USING _item_id, _level, _where, _order_by, _me_id, _global_seed;
|
||||||
|
|
||||||
|
EXECUTE ''
|
||||||
|
|| 'SELECT COALESCE(jsonb_agg(sub), ''[]''::jsonb) AS comments '
|
||||||
|
|| 'FROM ( '
|
||||||
|
|| ' SELECT "Item".*, item_comments_zaprank_with_me("Item".id, $6, $5, $2 - 1, $3, $4) AS comments '
|
||||||
|
|| ' FROM t_item "Item" '
|
||||||
|
|| ' WHERE "Item"."parentId" = $1 '
|
||||||
|
|| _order_by
|
||||||
|
|| ' ) sub'
|
||||||
|
INTO result USING _item_id, _level, _where, _order_by, _me_id, _global_seed;
|
||||||
|
|
||||||
|
RETURN result;
|
||||||
|
END
|
||||||
|
$$;
|
||||||
|
|
||||||
|
CREATE OR REPLACE FUNCTION item_comments(_item_id int, _level int, _where text, _order_by text)
|
||||||
|
RETURNS jsonb
|
||||||
|
LANGUAGE plpgsql VOLATILE PARALLEL SAFE AS
|
||||||
|
$$
|
||||||
|
DECLARE
|
||||||
|
result jsonb;
|
||||||
|
BEGIN
|
||||||
|
IF _level < 1 THEN
|
||||||
|
RETURN '[]'::jsonb;
|
||||||
|
END IF;
|
||||||
|
|
||||||
|
EXECUTE 'CREATE TEMP TABLE IF NOT EXISTS t_item ON COMMIT DROP AS'
|
||||||
|
|| ' SELECT "Item".*, "Item".created_at at time zone ''UTC'' AS "createdAt", "Item".updated_at at time zone ''UTC'' AS "updatedAt", '
|
||||||
|
|| ' to_jsonb(users.*) as user '
|
||||||
|
|| ' FROM "Item" '
|
||||||
|
|| ' JOIN users ON users.id = "Item"."userId" '
|
||||||
|
|| ' WHERE "Item".path <@ (SELECT path FROM "Item" WHERE id = $1) ' || _where
|
||||||
|
USING _item_id, _level, _where, _order_by;
|
||||||
|
|
||||||
|
EXECUTE ''
|
||||||
|
|| 'SELECT COALESCE(jsonb_agg(sub), ''[]''::jsonb) AS comments '
|
||||||
|
|| 'FROM ( '
|
||||||
|
|| ' SELECT "Item".*, item_comments("Item".id, $2 - 1, $3, $4) AS comments '
|
||||||
|
|| ' FROM t_item "Item"'
|
||||||
|
|| ' WHERE "Item"."parentId" = $1 '
|
||||||
|
|| _order_by
|
||||||
|
|| ' ) sub'
|
||||||
|
INTO result USING _item_id, _level, _where, _order_by;
|
||||||
|
RETURN result;
|
||||||
|
END
|
||||||
|
$$;
|
Loading…
x
Reference in New Issue
Block a user