Skip to content

Commit

Permalink
Quote reply support on text-based posts and comments
Browse files Browse the repository at this point in the history
  • Loading branch information
SatsAllDay committed Sep 27, 2023
1 parent 5129982 commit 91881c2
Show file tree
Hide file tree
Showing 7 changed files with 78 additions and 37 deletions.
6 changes: 5 additions & 1 deletion components/comment.js
Original file line number Diff line number Diff line change
Expand Up @@ -136,6 +136,7 @@ export default function Comment ({
? 'OPG'
: null
const bountyPaid = root.bountyPaidTo?.includes(Number(item.id))
const replyRef = useRef()

return (
<div
Expand All @@ -156,6 +157,9 @@ export default function Comment ({
commentTextSingular='reply'
className={`${itemStyles.other} ${styles.other}`}
embellishUser={op && <><span> </span><Badge bg='boost' className={`${styles.op} bg-opacity-75`}>{op}</Badge></>}
onQuoteReply={() => {
replyRef.current?.quoteReply?.()
}}
extraInfo={
<>
{includeParent && <Parent item={item} rootText={rootText} />}
Expand Down Expand Up @@ -211,7 +215,7 @@ export default function Comment ({
: (
<div className={styles.children}>
{!noReply &&
<Reply depth={depth + 1} item={item} replyOpen={replyOpen}>
<Reply depth={depth + 1} item={item} replyOpen={replyOpen} innerRef={replyRef}>
{root.bounty && !bountyPaid && <PayBounty item={item} />}
</Reply>}
{children}
Expand Down
3 changes: 2 additions & 1 deletion components/form.js
Original file line number Diff line number Diff line change
Expand Up @@ -483,7 +483,7 @@ export function Checkbox ({ children, label, groupClassName, hiddenLabel, extra,
const StorageKeyPrefixContext = createContext()

export function Form ({
initial, schema, onSubmit, children, initialError, validateImmediately, storageKeyPrefix, validateOnChange = true, invoiceable, ...props
initial, schema, onSubmit, children, initialError, validateImmediately, storageKeyPrefix, validateOnChange = true, invoiceable, innerRef, ...props
}) {
const toaster = useToast()
const initialErrorToasted = useRef(false)
Expand Down Expand Up @@ -538,6 +538,7 @@ export function Form ({
toaster.danger(err.message || err.toString?.())
}
}}
innerRef={innerRef}
>
<FormikForm {...props} noValidate>
<StorageKeyPrefixContext.Provider value={storageKeyPrefix}>
Expand Down
6 changes: 4 additions & 2 deletions components/item-full.js
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ import Button from 'react-bootstrap/Button'
import { TwitterTweetEmbed } from 'react-twitter-embed'
import YouTube from 'react-youtube'
import useDarkMode from './dark-mode'
import { useEffect, useState } from 'react'
import { useEffect, useRef, useState } from 'react'
import Poll from './poll'
import { commentsViewed } from '../lib/new-comments'
import Related from './related'
Expand Down Expand Up @@ -121,10 +121,12 @@ function FwdUsers ({ forwards }) {

function TopLevelItem ({ item, noReply, ...props }) {
const ItemComponent = item.isJob ? ItemJob : Item
const replyRef = useRef()

return (
<ItemComponent
item={item}
replyRef={replyRef}
full
right={
!noReply &&
Expand Down Expand Up @@ -156,7 +158,7 @@ function TopLevelItem ({ item, noReply, ...props }) {
</div>
{!noReply &&
<>
<Reply item={item} replyOpen placeholder={item.ncomments ? undefined : 'start the conversation ...'} />
<Reply item={item} replyOpen placeholder={item.ncomments ? undefined : 'start the conversation ...'} innerRef={replyRef} />
{!item.position && !item.isJob && !item.parentId && !item.bounty > 0 && <Related title={item.title} itemId={item.id} />}
{item.bounty > 0 && <PastBounties item={item} />}
</>}
Expand Down
6 changes: 5 additions & 1 deletion components/item-info.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@ import Link from 'next/link'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
import Badge from 'react-bootstrap/Badge'
import Dropdown from 'react-bootstrap/Dropdown'
import Countdown from './countdown'
import { abbrNum, numWithUnits } from '../lib/format'
import { newComments, commentsViewedAt } from '../lib/new-comments'
Expand All @@ -19,7 +20,8 @@ import ActionDropdown from './action-dropdown'

export default function ItemInfo ({
item, pendingSats, full, commentsText = 'comments',
commentTextSingular = 'comment', className, embellishUser, extraInfo, onEdit, editText
commentTextSingular = 'comment', className, embellishUser, extraInfo, onEdit, editText,
onQuoteReply
}) {
const editThreshold = new Date(item.createdAt).getTime() + 10 * 60000
const me = useMe()
Expand Down Expand Up @@ -140,6 +142,8 @@ export default function ItemInfo ({
!item.mine && !item.deletedAt && <DontLikeThisDropdownItem id={item.id} />}
{item.mine && !item.position && !item.deletedAt &&
<DeleteDropdownItem itemId={item.id} type={item.title ? 'post' : 'comment'} />}
{(item.parentId || item.text) && onQuoteReply &&
<Dropdown.Item onClick={onQuoteReply}>quote reply</Dropdown.Item>}
</ActionDropdown>
{extraInfo}
</div>
Expand Down
5 changes: 4 additions & 1 deletion components/item.js
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ export function SearchTitle ({ title }) {
})
}

export default function Item ({ item, rank, belowTitle, right, full, children, siblingComments }) {
export default function Item ({ item, rank, belowTitle, right, full, children, siblingComments, replyRef }) {
const titleRef = useRef()
const router = useRouter()
const [pendingSats, setPendingSats] = useState(0)
Expand Down Expand Up @@ -85,6 +85,9 @@ export default function Item ({ item, rank, belowTitle, right, full, children, s
</div>
<ItemInfo
full={full} item={item} pendingSats={pendingSats}
onQuoteReply={() => {
replyRef?.current?.quoteReply?.()
}}
embellishUser={Number(item?.user?.id) === AD_USER_ID && <Badge className={styles.newComment} bg={null}>AD</Badge>}
/>
{belowTitle}
Expand Down
84 changes: 53 additions & 31 deletions components/reply.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import FeeButton from './fee-button'
import { commentsViewedAfterComment } from '../lib/new-comments'
import { commentSchema } from '../lib/validate'
import Info from './info'
import { quote } from '../lib/md'

export function ReplyOnAnotherPage ({ parentId }) {
return (
Expand All @@ -33,10 +34,32 @@ function FreebieDialog () {
)
}

export default function Reply ({ item, onSuccess, replyOpen, children, placeholder }) {
export default function Reply ({ item, onSuccess, replyOpen, children, placeholder, innerRef }) {
const [reply, setReply] = useState(replyOpen)
const me = useMe()
const parentId = item.id
const replyInput = useRef(null)
const formInnerRef = useRef()
const quoteReply = useCallback(() => {
if (!reply) {
setReply(true)
}
let updatedValue
if (formInnerRef.current && formInnerRef.current.values && !formInnerRef.current.values.text) {
updatedValue = quote(item.text)
} else if (formInnerRef.current?.values?.text) {
// append quote reply text if the input already has content
updatedValue = `${replyInput.current.value}\n${quote(item.text)}`
}
if (updatedValue) {
replyInput.current.value = updatedValue
formInnerRef.current.setValues({ text: updatedValue })
window.localStorage.setItem(`reply-${parentId}-text`, updatedValue)
}
}, [reply, item])
if (innerRef) {
innerRef.current = { quoteReply }
}

useEffect(() => {
setReply(replyOpen || !!window.localStorage.getItem('reply-' + parentId + '-' + 'text'))
Expand Down Expand Up @@ -96,7 +119,6 @@ export default function Reply ({ item, onSuccess, replyOpen, children, placehold
setReply(replyOpen || false)
}, [upsertComment, setReply, parentId])

const replyInput = useRef(null)
useEffect(() => {
if (replyInput.current && reply && !replyOpen) replyInput.current.focus()
}, [reply])
Expand All @@ -115,35 +137,35 @@ export default function Reply ({ item, onSuccess, replyOpen, children, placehold
{/* HACK if we need more items, we should probably do a comment toolbar */}
{children}
</div>)}
{reply &&
<div className={styles.reply}>
<Form
initial={{
text: ''
}}
schema={commentSchema}
invoiceable
onSubmit={onSubmit}
storageKeyPrefix={'reply-' + parentId}
>
<MarkdownInput
name='text'
minRows={6}
autoFocus={!replyOpen}
required
placeholder={placeholder}
hint={me?.sats < 1 && <FreebieDialog />}
innerRef={replyInput}
/>
{reply &&
<div className='mt-1'>
<FeeButton
baseFee={1} parentId={parentId} text='reply'
ChildButton={SubmitButton} variant='secondary' alwaysShow
/>
</div>}
</Form>
</div>}
<div className={styles.reply} style={{ display: reply ? 'block' : 'none' }}>
<Form
initial={{
text: ''
}}
schema={commentSchema}
invoiceable
onSubmit={onSubmit}
storageKeyPrefix={`reply-${parentId}`}
innerRef={formInnerRef}
>
<MarkdownInput
name='text'
minRows={6}
autoFocus={!replyOpen}
required
placeholder={placeholder}
hint={me?.sats < 1 && <FreebieDialog />}
innerRef={replyInput}
/>
{reply &&
<div className='mt-1'>
<FeeButton
baseFee={1} parentId={parentId} text='reply'
ChildButton={SubmitButton} variant='secondary' alwaysShow
/>
</div>}
</Form>
</div>
</div>
)
}
Expand Down
5 changes: 5 additions & 0 deletions lib/md.js
Original file line number Diff line number Diff line change
Expand Up @@ -35,3 +35,8 @@ export function extractUrls (md) {

return Array.from(urls)
}

export const quote = (orig) =>
orig.split('\n')
.map(line => `> ${line}`)
.join('\n')

0 comments on commit 91881c2

Please sign in to comment.