Compare commits
18 Commits
3310925155
...
18fbd17025
Author | SHA1 | Date |
---|---|---|
ekzyis | 18fbd17025 | |
k00b | 2b5a1cbfe9 | |
k00b | b6dcee4f26 | |
ekzyis | d30dace266 | |
k00b | 894d02a196 | |
k00b | 83458fdc9e | |
k00b | 101574b605 | |
k00b | 59d5fd60f2 | |
k00b | f90a9905ba | |
k00b | 8447a4a8b2 | |
k00b | 4981d572bb | |
ekzyis | 4e7b4ee571 | |
k00b | 8c9d4aa59b | |
k00b | ad9a65ce78 | |
ekzyis | b4e143460b | |
k00b | 731df5fc67 | |
k00b | 2f191e04f9 | |
k00b | 09be42844e |
|
@ -190,7 +190,7 @@ export async function retryPaidAction (actionType, args, context) {
|
|||
|
||||
const failedInvoice = await models.invoice.findUnique({ where: { id: invoiceId, actionState: 'FAILED' } })
|
||||
if (!failedInvoice) {
|
||||
throw new Error(`retryPaidAction - invoice not found or not in failed state ${actionType}`)
|
||||
throw new Error(`retryPaidAction ${actionType} - invoice ${invoiceId} not found or not in failed state`)
|
||||
}
|
||||
|
||||
const { msatsRequested, actionId } = failedInvoice
|
||||
|
|
|
@ -105,6 +105,8 @@ const orderByClause = (by, me, models, type) => {
|
|||
return 'ORDER BY "Item".msats DESC'
|
||||
case 'zaprank':
|
||||
return topOrderByWeightedSats(me, models)
|
||||
case 'boost':
|
||||
return 'ORDER BY "Item".boost DESC'
|
||||
case 'random':
|
||||
return 'ORDER BY RANDOM()'
|
||||
default:
|
||||
|
@ -378,6 +380,7 @@ export default {
|
|||
activeOrMine(me),
|
||||
nsfwClause(showNsfw),
|
||||
typeClause(type),
|
||||
by === 'boost' && '"Item".boost > 0',
|
||||
whenClause(when || 'forever', table))}
|
||||
${orderByClause(by, me, models, type)}
|
||||
OFFSET $4
|
||||
|
@ -421,6 +424,7 @@ export default {
|
|||
typeClause(type),
|
||||
whenClause(when, 'Item'),
|
||||
await filterClause(me, models, type),
|
||||
by === 'boost' && '"Item".boost > 0',
|
||||
muteClause(me))}
|
||||
${orderByClause(by || 'zaprank', me, models, type)}
|
||||
OFFSET $3
|
||||
|
@ -479,7 +483,7 @@ export default {
|
|||
'"pinId" IS NULL',
|
||||
subClause(sub, 4)
|
||||
)}
|
||||
ORDER BY group_rank, rank
|
||||
ORDER BY group_rank DESC, rank
|
||||
OFFSET $2
|
||||
LIMIT $3`,
|
||||
orderBy: 'ORDER BY group_rank DESC, rank'
|
||||
|
@ -690,7 +694,8 @@ export default {
|
|||
boost: { gte: boost },
|
||||
status: 'ACTIVE',
|
||||
deletedAt: null,
|
||||
outlawed: false
|
||||
outlawed: false,
|
||||
parentId: null
|
||||
}
|
||||
if (id) {
|
||||
where.id = { not: Number(id) }
|
||||
|
@ -698,7 +703,7 @@ export default {
|
|||
|
||||
return {
|
||||
home: await models.item.count({ where }) === 0,
|
||||
sub: await models.item.count({ where: { ...where, subName: sub } }) === 0
|
||||
sub: sub ? await models.item.count({ where: { ...where, subName: sub } }) === 0 : false
|
||||
}
|
||||
}
|
||||
},
|
||||
|
@ -1139,7 +1144,7 @@ export default {
|
|||
return item.weightedVotes - item.weightedDownVotes > 0
|
||||
},
|
||||
freebie: async (item) => {
|
||||
return item.cost === 0
|
||||
return item.cost === 0 && item.boost === 0
|
||||
},
|
||||
meSats: async (item, args, { me, models }) => {
|
||||
if (!me) return 0
|
||||
|
|
|
@ -90,8 +90,8 @@ export function BoostItemInput ({ item, sub, act = false, ...props }) {
|
|||
const [boost, setBoost] = useState(Number(item?.boost) + (act ? BOOST_MULT : 0))
|
||||
|
||||
const [getBoostPosition, { data }] = useLazyQuery(gql`
|
||||
query BoostPosition($id: ID, $boost: Int) {
|
||||
boostPosition(sub: "${item?.subName || sub?.name}", id: $id, boost: $boost) {
|
||||
query BoostPosition($sub: String, $id: ID, $boost: Int) {
|
||||
boostPosition(sub: $sub, id: $id, boost: $boost) {
|
||||
home
|
||||
sub
|
||||
}
|
||||
|
@ -101,10 +101,10 @@ export function BoostItemInput ({ item, sub, act = false, ...props }) {
|
|||
const getPositionDebounce = useDebounceCallback((...args) => getBoostPosition(...args), 1000, [getBoostPosition])
|
||||
|
||||
useEffect(() => {
|
||||
if (boost) {
|
||||
getPositionDebounce({ variables: { boost: Number(boost), id: item?.id } })
|
||||
if (boost >= 0 && !item?.parentId) {
|
||||
getPositionDebounce({ variables: { sub: item?.subName || sub?.name, boost: Number(boost), id: item?.id } })
|
||||
}
|
||||
}, [boost, item?.id])
|
||||
}, [boost, item?.id, !item?.parentId, item?.subName || sub?.name])
|
||||
|
||||
const boostMessage = useMemo(() => {
|
||||
if (!item?.parentId) {
|
||||
|
|
|
@ -2,9 +2,9 @@ import { useShowModal } from './modal'
|
|||
import { useToast } from './toast'
|
||||
import ItemAct from './item-act'
|
||||
import AccordianItem from './accordian-item'
|
||||
import { useMemo } from 'react'
|
||||
import { useMemo, useState } from 'react'
|
||||
import getColor from '@/lib/rainbow'
|
||||
import UpBolt from '@/svgs/bolt.svg'
|
||||
import BoostIcon from '@/svgs/arrow-up-double-line.svg'
|
||||
import styles from './upvote.module.css'
|
||||
import { BoostHelp } from './adv-post-form'
|
||||
import { BOOST_MULT } from '@/lib/constants'
|
||||
|
@ -12,15 +12,17 @@ import classNames from 'classnames'
|
|||
|
||||
export default function Boost ({ item, className, ...props }) {
|
||||
const { boost } = item
|
||||
const style = useMemo(() => (boost
|
||||
const [hover, setHover] = useState(false)
|
||||
|
||||
const [color, nextColor] = useMemo(() => [getColor(boost), getColor(boost + BOOST_MULT)], [boost])
|
||||
|
||||
const style = useMemo(() => (hover || boost
|
||||
? {
|
||||
fill: getColor(boost),
|
||||
filter: `drop-shadow(0 0 6px ${getColor(boost)}90)`,
|
||||
transform: 'scaleX(-1)'
|
||||
fill: hover ? nextColor : color,
|
||||
filter: `drop-shadow(0 0 6px ${hover ? nextColor : color}90)`
|
||||
}
|
||||
: {
|
||||
transform: 'scaleX(-1)'
|
||||
}), [boost])
|
||||
: undefined), [boost, hover])
|
||||
|
||||
return (
|
||||
<Booster
|
||||
item={item} As={({ ...oprops }) =>
|
||||
|
@ -28,11 +30,14 @@ export default function Boost ({ item, className, ...props }) {
|
|||
<div
|
||||
className={styles.upvoteWrapper}
|
||||
>
|
||||
<UpBolt
|
||||
<BoostIcon
|
||||
{...props} {...oprops} style={style}
|
||||
width={26}
|
||||
height={26}
|
||||
className={classNames(styles.upvote, className, boost && styles.voted)}
|
||||
onPointerEnter={() => setHover(true)}
|
||||
onMouseLeave={() => setHover(false)}
|
||||
onTouchEnd={() => setHover(false)}
|
||||
className={classNames(styles.boost, className, boost && styles.voted)}
|
||||
/>
|
||||
</div>
|
||||
</div>}
|
||||
|
|
|
@ -255,6 +255,11 @@ export function MarkdownInput ({ label, topLevel, groupClassName, onChange, onKe
|
|||
e.preventDefault()
|
||||
insertMarkdownItalicFormatting(innerRef.current, helpers.setValue, setSelectionRange)
|
||||
}
|
||||
if (e.key === 'u') {
|
||||
// some browsers might use CTRL+U to do something else so prevent that behavior too
|
||||
e.preventDefault()
|
||||
imageUploadRef.current?.click()
|
||||
}
|
||||
if (e.key === 'Tab' && e.altKey) {
|
||||
e.preventDefault()
|
||||
insertMarkdownTabFormatting(innerRef.current, helpers.setValue, setSelectionRange)
|
||||
|
|
|
@ -277,7 +277,7 @@ export function useZap () {
|
|||
const reason = error?.message || error?.toString?.()
|
||||
toaster.danger(reason)
|
||||
}
|
||||
}, [me?.id, strike])
|
||||
}, [act, me?.id, strike])
|
||||
}
|
||||
|
||||
export class ActCanceledError extends Error {
|
||||
|
|
|
@ -113,7 +113,6 @@ function TopLevelItem ({ item, noReply, ...props }) {
|
|||
<Reply
|
||||
item={item}
|
||||
replyOpen
|
||||
placeholder={item.ncomments > 3 ? 'fractions of a penny for your thoughts?' : 'early comments get more zaps'}
|
||||
onCancelQuote={cancelQuote}
|
||||
onQuoteReply={quoteReply}
|
||||
quote={quote}
|
||||
|
|
|
@ -106,7 +106,7 @@ export default function Item ({
|
|||
<div className={classNames(styles.item, itemClassName)}>
|
||||
{item.position && (pinnable || !item.subName)
|
||||
? <Pin width={24} height={24} className={styles.pin} />
|
||||
: item.mine
|
||||
: item.mine || item.meForward
|
||||
? <Boost item={item} className={styles.upvote} />
|
||||
: item.meDontLikeSats > item.meSats
|
||||
? <DownZap width={24} height={24} className={styles.dontLike} item={item} />
|
||||
|
|
|
@ -242,12 +242,12 @@ function RevenueNotification ({ n }) {
|
|||
return (
|
||||
<div className='d-flex'>
|
||||
<BountyIcon className='align-self-center fill-success mx-1' width={24} height={24} style={{ flex: '0 0 24px' }} />
|
||||
<div className=' pb-1'>
|
||||
<div className='fw-bold text-success'>
|
||||
<div className='ms-2'>
|
||||
<NoteHeader color='success' big>
|
||||
you stacked {numWithUnits(n.earnedSats, { abbreviate: false })} in territory revenue<small className='text-muted ms-1 fw-normal' suppressHydrationWarning>{timeSince(new Date(n.sortTime))}</small>
|
||||
</div>
|
||||
</NoteHeader>
|
||||
<div style={{ lineHeight: '140%' }}>
|
||||
As the founder of territory <Link href={`/~${n.subName}`}>~{n.subName}</Link>, you receive 50% of the revenue it generates and the other 50% go to <Link href='/rewards'>rewards</Link>.
|
||||
As the founder of territory <Link href={`/~${n.subName}`}>~{n.subName}</Link>, you receive 70% of the post, comment, boost, and zap fees. The other 30% go to <Link href='/rewards'>rewards</Link>.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
|
|
@ -2,7 +2,7 @@ import { Form, MarkdownInput } from '@/components/form'
|
|||
import styles from './reply.module.css'
|
||||
import { COMMENTS } from '@/fragments/comments'
|
||||
import { useMe } from './me'
|
||||
import { forwardRef, useCallback, useEffect, useState, useRef } from 'react'
|
||||
import { forwardRef, useCallback, useEffect, useState, useRef, useMemo } from 'react'
|
||||
import Link from 'next/link'
|
||||
import { FeeButtonProvider, postCommentBaseLineItems, postCommentUseRemoteLineItems } from './fee-button'
|
||||
import { commentsViewedAfterComment } from '@/lib/new-comments'
|
||||
|
@ -34,7 +34,6 @@ export default forwardRef(function Reply ({
|
|||
item,
|
||||
replyOpen,
|
||||
children,
|
||||
placeholder,
|
||||
onQuoteReply,
|
||||
onCancelQuote,
|
||||
quote
|
||||
|
@ -53,6 +52,14 @@ export default forwardRef(function Reply ({
|
|||
}
|
||||
}, [replyOpen, quote, parentId])
|
||||
|
||||
const placeholder = useMemo(() => {
|
||||
return [
|
||||
'comment for currency?',
|
||||
'fractions of a penny for your thoughts?',
|
||||
'put your money where your mouth is?'
|
||||
][parentId % 3]
|
||||
}, [parentId])
|
||||
|
||||
const onSubmit = useItemSubmit(CREATE_COMMENT, {
|
||||
extraValues: { parentId },
|
||||
paidMutationOptions: {
|
||||
|
|
|
@ -227,14 +227,15 @@ export default function UpVote ({ item, className }) {
|
|||
}
|
||||
}
|
||||
|
||||
const fillColor = meSats && (hover || pending ? nextColor : color)
|
||||
|
||||
const style = useMemo(() => (fillColor
|
||||
? {
|
||||
fill: fillColor,
|
||||
filter: `drop-shadow(0 0 6px ${fillColor}90)`
|
||||
}
|
||||
: undefined), [fillColor])
|
||||
const style = useMemo(() => {
|
||||
const fillColor = pending || hover ? nextColor : color
|
||||
return meSats || hover || pending
|
||||
? {
|
||||
fill: fillColor,
|
||||
filter: `drop-shadow(0 0 6px ${fillColor}90)`
|
||||
}
|
||||
: undefined
|
||||
}, [hover, pending, nextColor, color, meSats])
|
||||
|
||||
return (
|
||||
<div ref={ref} className='upvoteParent'>
|
||||
|
|
|
@ -5,6 +5,13 @@
|
|||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
.boost {
|
||||
fill: var(--theme-clickToContextColor);
|
||||
user-select: none;
|
||||
-webkit-user-select: none;
|
||||
-webkit-touch-callout: none;
|
||||
}
|
||||
|
||||
.upvoteWrapper {
|
||||
position: relative;
|
||||
padding-right: .2rem;
|
||||
|
|
|
@ -62,7 +62,7 @@ export const ITEM_FILTER_THRESHOLD = 1.2
|
|||
export const DONT_LIKE_THIS_COST = 1
|
||||
export const COMMENT_TYPE_QUERY = ['comments', 'freebies', 'outlawed', 'borderland', 'all', 'bookmarks']
|
||||
export const USER_SORTS = ['value', 'stacking', 'spending', 'comments', 'posts', 'referrals']
|
||||
export const ITEM_SORTS = ['zaprank', 'comments', 'sats']
|
||||
export const ITEM_SORTS = ['zaprank', 'comments', 'sats', 'boost']
|
||||
export const SUB_SORTS = ['stacking', 'revenue', 'spending', 'posts', 'comments']
|
||||
export const WHENS = ['day', 'week', 'month', 'year', 'forever', 'custom']
|
||||
export const ITEM_TYPES_USER = ['all', 'posts', 'comments', 'bounties', 'links', 'discussions', 'polls', 'freebies', 'jobs', 'bookmarks']
|
||||
|
|
|
@ -22,14 +22,14 @@ export class Relay {
|
|||
constructor (relayUrl) {
|
||||
const ws = new WebSocket(relayUrl)
|
||||
|
||||
ws.onmessage = function (msg) {
|
||||
ws.onmessage = (msg) => {
|
||||
const [type, notice] = JSON.parse(msg.data)
|
||||
if (type === 'NOTICE') {
|
||||
console.log('relay notice:', notice)
|
||||
}
|
||||
}
|
||||
|
||||
ws.onerror = function (err) {
|
||||
ws.onerror = (err) => {
|
||||
console.error('websocket error:', err.message)
|
||||
this.error = err.message
|
||||
}
|
||||
|
|
|
@ -1,5 +1,9 @@
|
|||
export default function getColor (meSats) {
|
||||
if (!meSats || meSats <= 10) {
|
||||
if (!meSats || meSats === 0) {
|
||||
return '#a5a5a5'
|
||||
}
|
||||
|
||||
if (meSats <= 10) {
|
||||
return 'var(--bs-secondary)'
|
||||
}
|
||||
|
||||
|
|
|
@ -131,7 +131,7 @@ export default function Rewards ({ ssrData }) {
|
|||
return (
|
||||
<Layout footerLinks>
|
||||
{ad &&
|
||||
<div className='pt-3 align-self-center' style={{ maxWidth: '480px', width: '100%' }}>
|
||||
<div className='pt-3 align-self-center' style={{ maxWidth: '500px', width: '100%' }}>
|
||||
<div className='fw-bold text-muted pb-2'>
|
||||
top boost this month
|
||||
</div>
|
||||
|
|
|
@ -0,0 +1,18 @@
|
|||
CREATE OR REPLACE FUNCTION expire_boost_jobs()
|
||||
RETURNS INTEGER
|
||||
LANGUAGE plpgsql
|
||||
AS $$
|
||||
DECLARE
|
||||
BEGIN
|
||||
INSERT INTO pgboss.job (name, data, retrylimit, retrybackoff, startafter, expirein)
|
||||
SELECT 'expireBoost', jsonb_build_object('id', "Item".id), 21, true, now(), interval '1 days'
|
||||
FROM "Item"
|
||||
WHERE "Item".boost > 0 ON CONFLICT DO NOTHING;
|
||||
return 0;
|
||||
EXCEPTION WHEN OTHERS THEN
|
||||
return 0;
|
||||
END;
|
||||
$$;
|
||||
|
||||
SELECT expire_boost_jobs();
|
||||
DROP FUNCTION IF EXISTS expire_boost_jobs;
|
|
@ -1,8 +1,8 @@
|
|||
const { ApolloClient, InMemoryCache, HttpLink, gql } = require('@apollo/client')
|
||||
|
||||
const ITEMS = gql`
|
||||
query items ($sort: String, $when: String, $sub: String) {
|
||||
items (sort: $sort, when: $when, sub: $sub) {
|
||||
query items ($sort: String, $when: String, $sub: String, $by: String) {
|
||||
items (sort: $sort, when: $when, sub: $sub, by: $by) {
|
||||
cursor
|
||||
items {
|
||||
id
|
||||
|
@ -15,6 +15,7 @@ const ITEMS = gql`
|
|||
location
|
||||
remote
|
||||
boost
|
||||
subName
|
||||
user {
|
||||
id
|
||||
name
|
||||
|
@ -152,15 +153,15 @@ async function main () {
|
|||
variables: { sort: 'top', when: 'week', sub: 'meta' }
|
||||
})
|
||||
|
||||
const jobs = await client.query({
|
||||
const ama = await client.query({
|
||||
query: ITEMS,
|
||||
variables: { sub: 'jobs' }
|
||||
variables: { sort: 'top', when: 'week', sub: 'ama' }
|
||||
})
|
||||
|
||||
// const thisDay = await client.query({
|
||||
// query: SEARCH,
|
||||
// variables: { q: 'This Day in Stacker News @Undisciplined', sort: 'recent', what: 'posts', when: 'week' }
|
||||
// })
|
||||
const boosts = await client.query({
|
||||
query: ITEMS,
|
||||
variables: { sort: 'top', when: 'forever', by: 'boost' }
|
||||
})
|
||||
|
||||
const topMeme = await bountyWinner('meme monday')
|
||||
const topFact = await bountyWinner('fun fact')
|
||||
|
@ -179,6 +180,13 @@ ${top.data.items.items.map((item, i) =>
|
|||
`${i + 1}. [${item.title}](https://stacker.news/items/${item.id})
|
||||
- ${abbrNum(item.sats)} sats${item.boost ? ` \\ ${abbrNum(item.boost)} boost` : ''} \\ ${item.ncomments} comments \\ [@${item.user.name}](https://stacker.news/${item.user.name})\n`).join('')}
|
||||
|
||||
##### Top AMAs
|
||||
${ama.data.items.items.slice(0, 3).map((item, i) =>
|
||||
`${i + 1}. [${item.title}](https://stacker.news/items/${item.id})
|
||||
- ${abbrNum(item.sats)} sats${item.boost ? ` \\ ${abbrNum(item.boost)} boost` : ''} \\ ${item.ncomments} comments \\ [@${item.user.name}](https://stacker.news/${item.user.name})\n`).join('')}
|
||||
|
||||
[**all AMAs**](https://stacker.news/~meta/top/posts/forever)
|
||||
|
||||
##### Don't miss
|
||||
${top.data.items.items.map((item, i) =>
|
||||
`- [${item.title}](https://stacker.news/items/${item.id})\n`).join('')}
|
||||
|
@ -230,9 +238,12 @@ ${topCowboys.map((user, i) =>
|
|||
|
||||
------
|
||||
|
||||
##### Promoted jobs
|
||||
${jobs.data.items.items.filter(i => i.boost > 0).slice(0, 5).map((item, i) =>
|
||||
`${i + 1}. [${item.title.trim()} \\ ${item.company} \\ ${item.location}${item.remote ? ' or Remote' : ''}](https://stacker.news/items/${item.id})\n`).join('')}
|
||||
##### Top Boosts
|
||||
${boosts.data.items.items.map((item, i) =>
|
||||
item.subName === 'jobs'
|
||||
? `${i + 1}. [${item.title.trim()} \\ ${item.company} \\ ${item.location}${item.remote ? ' or Remote' : ''}](https://stacker.news/items/${item.id})\n`
|
||||
: `${i + 1}. [${item.title.trim()}](https://stacker.news/items/${item.id})\n`
|
||||
).join('')}
|
||||
|
||||
[**all jobs**](https://stacker.news/~jobs)
|
||||
|
||||
|
|
|
@ -0,0 +1 @@
|
|||
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="currentColor"><path d="M12 4.83582L5.79291 11.0429L7.20712 12.4571L12 7.66424L16.7929 12.4571L18.2071 11.0429L12 4.83582ZM12 10.4857L5.79291 16.6928L7.20712 18.107L12 13.3141L16.7929 18.107L18.2071 16.6928L12 10.4857Z"></path></svg>
|
After Width: | Height: | Size: 298 B |
|
@ -16,7 +16,7 @@ export async function expireBoost ({ data: { id }, models }) {
|
|||
AND "itemId" = ${Number(id)}::INTEGER
|
||||
)
|
||||
UPDATE "Item"
|
||||
SET boost = COALESCE(boost.cur_msats, 0), "oldBoost" = COALESCE(boost.old_msats, 0)
|
||||
SET boost = COALESCE(boost.cur_msats, 0) / 1000, "oldBoost" = COALESCE(boost.old_msats, 0) / 1000
|
||||
FROM boost
|
||||
WHERE "Item".id = ${Number(id)}::INTEGER`
|
||||
],
|
||||
|
|
Loading…
Reference in New Issue